Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5f85d63
feat(acp): pure jsonrpc/map/permission + omp fixtures
petros-double-test1 Jul 24, 2026
77964e3
feat(omp): register ACP backend, detect, adapter, UI identity
petros-double-test1 Jul 24, 2026
2729a05
feat(acp): runtime pool, demux, session API, permission replies
petros-double-test1 Jul 24, 2026
70e8e39
feat(engine): ACP connection send/stop + empty inject for acp tools
petros-double-test1 Jul 24, 2026
3b2a876
feat(omp): resume command and rewind affordance
petros-double-test1 Jul 24, 2026
bc25030
feat(omp): slash cache, tool bounce shutdown, jsonl cut-before rewind
petros-double-test1 Jul 24, 2026
9742d68
Merge remote-tracking branch 'origin/main' into feat/omp-acp-runtime
petros-double-test1 Jul 24, 2026
89dc2ff
fix(acp): route idle hidden turns through spawn_acp_turn
petros-double-test1 Jul 24, 2026
2842573
fix(acp): ensure_running accepts connection tools without resident child
petros-double-test1 Jul 24, 2026
601dd85
fix(acp): drop mutex before initialize to avoid connect deadlock
petros-double-test1 Jul 24, 2026
82c6150
fix(acp): address Codex P1/P2 review on omp integration
petros-double-test1 Jul 25, 2026
fd0d2e0
merge(main): pull action_key ask API and other main fixes
petros-double-test1 Jul 25, 2026
5609684
fix(acp): pass ask action_key after main merge
petros-double-test1 Jul 25, 2026
e81bdfe
fix(acp): stop-safe finalize, curator fallback, ask cancel
petros-double-test1 Jul 25, 2026
186de3a
fix(acp): system-prepend match only on first omp user turn
petros-double-test1 Jul 25, 2026
a73e8c5
fix(acp): round-3 review — stop-before-prompt and safety nets
petros-double-test1 Jul 25, 2026
6cf087d
fix(acp): stop-safe buffer, tree reap, load errors; live omp path
petros-double-test1 Jul 25, 2026
f7930ae
fix(acp): stream thinking activity; fix Session Info for omp
petros-double-test1 Jul 25, 2026
8fdd2d0
fix(acp): show soft thinking chip for pre-token gap
petros-double-test1 Jul 25, 2026
d3110df
fix(session): flat Skills list; show Weft MCP pipes in panel
petros-double-test1 Jul 25, 2026
3a2bd03
fix(session): tag weft_curator as internal MCP pipe
petros-double-test1 Jul 25, 2026
3cef1f9
fix(acp): review round — interrupt asks, drain barrier, walk tests
petros-double-test1 Jul 25, 2026
02ead80
fix(session): keep engine skills for codex/claude; omp workspace-only
petros-double-test1 Jul 25, 2026
38a8105
fix(acp): drain queued turns, recycle on pin, worker meta, caps
petros-double-test1 Jul 25, 2026
353ce28
merge(main): resolve conflicts + ACP review P1/P2
petros-double-test1 Jul 25, 2026
082a10f
fix(acp): review P1/P2 — pin bounce, stop route, cancel recovery
petros-double-test1 Jul 25, 2026
9c6df8e
merge(main): resolve stop_quiet session_gate permit clear
petros-double-test1 Jul 25, 2026
ba762b7
fix(acp): interrupt guards, route replace, rewind caps, retire idle
petros-double-test1 Jul 25, 2026
fd88a1f
fix(acp): atomic idle reap, open-fail code, clear unprompted native
petros-double-test1 Jul 25, 2026
433b01a
merge(main): engine switch panel + keep omp in tool lists
petros-double-test1 Jul 25, 2026
feea1c6
merge(main): resolve ACP conflicts, fix CI, close 7 review threads
petros-double-test1 Jul 27, 2026
1e77822
fix(acp): close the five review-round findings on the merged branch
petros-double-test1 Jul 28, 2026
1e2ec99
fix(acp): close the seven findings from the third review round
petros-double-test1 Jul 28, 2026
338960a
fix(curator): localize the ACP-engine refusal instead of showing a ra…
petros-double-test1 Jul 28, 2026
035b458
merge(main): pick up quota-gate, auto-merge seam, and sentinel guards
petros-double-test1 Jul 28, 2026
dab6718
fix(acp): close the seven findings from the fourth review round
petros-double-test1 Jul 28, 2026
a8438de
fix(acp): close the five findings from the fifth review round
petros-double-test1 Jul 28, 2026
df423af
fix(acp): close the sixth review round; pin the post-switch rewind limit
petros-double-test1 Jul 28, 2026
ab038af
fix(acp): close the seventh review round
petros-double-test1 Jul 28, 2026
5dd2b7e
fix(acp): one source for notice copy; per-key ACP creation locks
petros-double-test1 Jul 28, 2026
fde95ea
fix(acp): freeze-recovery parity, catalog-generated notice copy, exit…
petros-double-test1 Jul 28, 2026
8cd25d3
fix(acp): the third teardown path, and the pooled-handle shutdown hole
petros-double-test1 Jul 28, 2026
79aacbd
fix(acp): hidden delivery must honour the teardown reservation
petros-double-test1 Jul 28, 2026
b9b20fa
fix(acp): a permission card must always name what it authorizes
petros-double-test1 Jul 28, 2026
edc31e6
fix(acp): Stop must block a queued prompt from dispatching
petros-double-test1 Jul 28, 2026
19221ec
test(ask): a hook that exits before reading stdin is not a test failure
petros-double-test1 Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions public/tools/omp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions scripts/gen-notice-copy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Generate the backend-readable mirror of the notice copy that lives in the
// i18n catalogs.
//
// `src/i18n/{en,zh}.ts` stay authoritative (AGENTS.md: user-facing strings go
// only through those two). Some notices are posted by detached backend tasks
// that have no locale, so they emit a stable TOKEN and each consumer renders
// it; the webview reads its catalogs directly, while the IM bridge is Rust and
// cannot. This writes what Rust bakes in.
//
// Run after editing any listed key. `tests/frontend/noticeCopy.test.ts` fails
// if the generated file drifts from the catalogs, so it cannot go stale
// unnoticed.
import { writeFileSync } from "node:fs";
import { en } from "../src/i18n/en.ts";
import { zh } from "../src/i18n/zh.ts";

/** Backend token -> the catalog key holding its copy. */
const TOKENS = {
"acp.force_reset_notice": "needs.acpForceResetNotice",
};

const OUT = new URL("../src-tauri/src/bus/notices.generated.json", import.meta.url);

function lookup(catalog, key) {
return key.split(".").reduce((node, part) => node?.[part], catalog);
}

export function buildNoticeCopy() {
const out = {};
for (const [token, key] of Object.entries(TOKENS)) {
const enCopy = lookup(en, key);
const zhCopy = lookup(zh, key);
if (typeof enCopy !== "string" || typeof zhCopy !== "string") {
throw new Error(`${token}: ${key} is missing from en or zh`);
}
out[token] = { en: enCopy, zh: zhCopy };
}
return out;
}

export function serialize(copy) {
return `${JSON.stringify(copy, null, 2)}\n`;
}

if (import.meta.url === `file://${process.argv[1]}`) {
writeFileSync(OUT, serialize(buildNoticeCopy()));
console.log(`wrote ${OUT.pathname}`);
}
103 changes: 103 additions & 0 deletions src-tauri/src/acp/backends/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! Per-CLI ACP backend descriptors.
//!
//! The runtime is generic; only this layer knows binary names and capability
//! quirks. Adding agent N+1 = new file + register + product enum entries.

use std::sync::{Arc, LazyLock};

use serde_json::Value;

mod omp;

pub use omp::OmpBackend;

/// HTTP MCP server Weft injects into `session/new|resume`.
#[derive(Debug, Clone)]
pub struct McpServerSpec {
pub name: String,
pub url: String,
}

/// Thin per-tool ACP identity. No process I/O.
pub trait AcpBackend: Send + Sync + 'static {
/// Stable tool identity (also the process-pool key), e.g. `"omp"`.
fn id(&self) -> &'static str;

/// `(program, args)` to spawn the ACP server. `command` is the resolved
/// binary/alias from Settings (e.g. `"omp"` or a user override).
fn spawn_argv(&self, command: &str) -> (String, Vec<String>);

/// `clientCapabilities` object for `initialize`.
fn client_capabilities(&self) -> Value;

/// Whether `session/fork` is advertised/usable for rewind helpers.
fn supports_fork(&self) -> bool {
true
}

/// Paint Weft MCP specs into the wire `mcpServers` array shape this agent accepts.
fn paint_mcp_servers(&self, servers: Vec<McpServerSpec>) -> Vec<Value> {
servers
.into_iter()
.map(|s| {
serde_json::json!({
"type": "http",
"name": s.name,
"url": s.url,
})
})
.collect()
}
}

static REGISTRY: LazyLock<Vec<Arc<dyn AcpBackend>>> =
LazyLock::new(|| vec![Arc::new(OmpBackend) as Arc<dyn AcpBackend>]);

fn registry() -> &'static Vec<Arc<dyn AcpBackend>> {
&REGISTRY
}

/// Look up an ACP backend by tool identity. `None` → not an ACP tool.
pub fn backend_for(tool: &str) -> Option<Arc<dyn AcpBackend>> {
registry().iter().find(|b| b.id() == tool).cloned()
}

/// All registered ACP tool ids (for detect/tests).
pub fn registered_ids() -> Vec<&'static str> {
registry().iter().map(|b| b.id()).collect()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn omp_is_registered() {
let b = backend_for("omp").expect("omp backend");
assert_eq!(b.id(), "omp");
let (prog, args) = b.spawn_argv("omp");
assert_eq!(prog, "omp");
assert_eq!(args, vec!["acp"]);
assert!(b.supports_fork());
let caps = b.client_capabilities();
assert_eq!(caps["session"]["requestPermission"], true);
}

#[test]
fn unknown_tool_is_none() {
assert!(backend_for("claude").is_none());
assert!(backend_for("codex").is_none());
}

#[test]
fn paints_http_mcp() {
let b = backend_for("omp").unwrap();
let painted = b.paint_mcp_servers(vec![McpServerSpec {
name: "weft_bus".into(),
url: "http://127.0.0.1:9/bus/1/1/mcp".into(),
}]);
assert_eq!(painted.len(), 1);
assert_eq!(painted[0]["type"], "http");
assert_eq!(painted[0]["name"], "weft_bus");
}
}
36 changes: 36 additions & 0 deletions src-tauri/src/acp/backends/omp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! omp (Oh My Pi) ACP backend descriptor.

use serde_json::{json, Value};

use super::AcpBackend;

pub struct OmpBackend;

impl AcpBackend for OmpBackend {
fn id(&self) -> &'static str {
"omp"
}

fn spawn_argv(&self, command: &str) -> (String, Vec<String>) {
(command.to_string(), vec!["acp".into()])
}

fn client_capabilities(&self) -> Value {
// fs/terminal off — agent runs tools locally in the worktree.
// requestPermission on so bash/edit gates surface to Weft Ask.
json!({
"fs": {
"readTextFile": false,
"writeTextFile": false,
},
"session": {
"requestPermission": true,
},
})
}

fn supports_fork(&self) -> bool {
// Full-history fork only; cut-before rewind uses jsonl rewrite + load.
true
}
}
Loading
Loading