zidane 7.0.14 → 7.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/dist/{acp-CZpX91YE.js → acp-Bw2b_kka.js} +3 -3
- package/dist/{acp-CZpX91YE.js.map → acp-Bw2b_kka.js.map} +1 -1
- package/dist/acp-cli.js +4 -4
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-B1jsFaDq.js → agent-B_uQo4f1.js} +649 -48
- package/dist/agent-B_uQo4f1.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-Da39cE-d.js → anthropic-QbHqqTkV.js} +2 -2
- package/dist/{anthropic-Da39cE-d.js.map → anthropic-QbHqqTkV.js.map} +1 -1
- package/dist/{approvals-S100xf1d.js → approvals-B9aIQJRQ.js} +287 -45
- package/dist/approvals-B9aIQJRQ.js.map +1 -0
- package/dist/artifacts-CV2hUKnL.js +112 -0
- package/dist/artifacts-CV2hUKnL.js.map +1 -0
- package/dist/{auth-DfEWnkC7.js → auth-plw6npda.js} +19 -7
- package/dist/auth-plw6npda.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +1 -1
- package/dist/chat.d.ts +6 -6
- package/dist/chat.js +5 -5
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/daemon-cli.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +2 -2
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +2 -2
- package/dist/{index-DFGECPfm.d.ts → index-Dc_zYpEu.d.ts} +351 -2
- package/dist/index-Dc_zYpEu.d.ts.map +1 -0
- package/dist/{index-DP7xYXr9.d.ts → index-Df2CH6bn.d.ts} +92 -7
- package/dist/index-Df2CH6bn.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -6
- package/dist/{ipc-BLLscoIl.js → ipc-T_pPnHkp.js} +2 -2
- package/dist/{ipc-BLLscoIl.js.map → ipc-T_pPnHkp.js.map} +1 -1
- package/dist/ipc.d.ts +54 -6
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +3 -3
- package/dist/mcp.d.ts +1 -1
- package/dist/{oauth-C4GvY_p1.js → oauth-EIBUuSDK.js} +2 -2
- package/dist/{oauth-C4GvY_p1.js.map → oauth-EIBUuSDK.js.map} +1 -1
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +2 -2
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +2 -2
- package/dist/{providers-BYgyPb15.js → providers-CoK1rZ1p.js} +2 -2
- package/dist/{providers-BYgyPb15.js.map → providers-CoK1rZ1p.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -3
- package/dist/restate.d.ts +1 -1
- package/dist/restate.js +1 -1
- package/dist/{server-C8gULJ0d.js → server-DXM8ZbGi.js} +64 -6
- package/dist/server-DXM8ZbGi.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +117 -1
- package/dist/session/sqlite.js.map +1 -1
- package/dist/{session-K3icbPH9.js → session-DPZnncaQ.js} +60 -1
- package/dist/session-DPZnncaQ.js.map +1 -0
- package/dist/session.d.ts +2 -2
- package/dist/session.js +3 -2
- package/dist/skills.d.ts +1 -1
- package/dist/{tool-formatters-C-s_-u0v.d.ts → tool-formatters-BgdOYAy9.d.ts} +2 -2
- package/dist/{tool-formatters-C-s_-u0v.d.ts.map → tool-formatters-BgdOYAy9.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-D1ToF-EW.d.ts → transcript-anchors-CtnwuQJM.d.ts} +3 -3
- package/dist/transcript-anchors-CtnwuQJM.d.ts.map +1 -0
- package/dist/tui.d.ts +3 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +576 -102
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-CZLsNgw-.js → turn-operations-DkJ1R0p8.js} +4 -3
- package/dist/turn-operations-DkJ1R0p8.js.map +1 -0
- package/dist/{turn-operations-Bp5-BAki.d.ts → turn-operations-khAvr7KJ.d.ts} +5 -3
- package/dist/turn-operations-khAvr7KJ.d.ts.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/{xai-DILdFGzN.js → xai-CazjStOK.js} +3 -3
- package/dist/{xai-DILdFGzN.js.map → xai-CazjStOK.js.map} +1 -1
- package/docs/ACP.md +6 -0
- package/docs/ARCHITECTURE.md +179 -356
- package/docs/ARTIFACTS.md +130 -0
- package/docs/CHAT.md +426 -1312
- package/docs/EXECUTION_CONTEXT.md +46 -19
- package/docs/EXTENSIONS.md +234 -584
- package/docs/HEADLESS.md +5 -0
- package/docs/INTERACTIONS.md +21 -22
- package/docs/IPC.md +318 -335
- package/docs/PLUGINS.md +2 -0
- package/docs/README.md +57 -0
- package/docs/RESTATE.md +199 -491
- package/docs/RUN_IN_BACKGROUND.md +107 -558
- package/docs/SKILL.md +171 -238
- package/docs/TUI.md +213 -591
- package/package.json +1 -1
- package/dist/agent-B1jsFaDq.js.map +0 -1
- package/dist/approvals-S100xf1d.js.map +0 -1
- package/dist/auth-DfEWnkC7.js.map +0 -1
- package/dist/index-DFGECPfm.d.ts.map +0 -1
- package/dist/index-DP7xYXr9.d.ts.map +0 -1
- package/dist/server-C8gULJ0d.js.map +0 -1
- package/dist/session-K3icbPH9.js.map +0 -1
- package/dist/transcript-anchors-D1ToF-EW.d.ts.map +0 -1
- package/dist/turn-operations-Bp5-BAki.d.ts.map +0 -1
- package/dist/turn-operations-CZLsNgw-.js.map +0 -1
package/docs/IPC.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# zidane/ipc — shared chat host, daemon, and multi-window sync
|
|
2
2
|
|
|
3
|
-
`zidane/ipc`
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
`zidane/ipc` lets several zidane windows — terminal sessions, the GUI,
|
|
4
|
+
`zidane attach` clients — share **one set of live agents** and stay
|
|
5
|
+
converged: same transcript, same pending approvals, same interaction
|
|
6
|
+
prompts. The engine (`createChatHost`) is transport-agnostic; the daemon
|
|
7
|
+
is one embedding of it, reached over a unix socket with JSON-RPC.
|
|
8
|
+
|
|
9
|
+
Entry points: `src/ipc/daemon/server.ts` (`startIpcDaemon`), `src/ipc/client.ts`
|
|
10
|
+
(`connectIpcClient` / `connectOrSpawnDaemon`), `src/tui/runtime/daemon.ts`
|
|
11
|
+
(`connectDaemonSession`, the TUI's daemon-mode controller).
|
|
7
12
|
|
|
8
13
|
```
|
|
9
14
|
┌────────────┐ ┌────────────┐ ┌─────────────┐
|
|
@@ -22,286 +27,280 @@ pending approvals, the same interaction prompts.
|
|
|
22
27
|
└────────────┘
|
|
23
28
|
```
|
|
24
29
|
|
|
30
|
+
Contents: [Pieces](#the-pieces) · [Host environment](#host-environment) ·
|
|
31
|
+
[RPC methods](#rpc-methods) · [Signals](#signals-and-convergence) ·
|
|
32
|
+
[Concurrent sends](#concurrent-sends) · [Extension bus](#extension-bus-ctxbus) ·
|
|
33
|
+
[Daemon lifecycle](#daemon-lifecycle) · [Turning it off](#turning-it-off-containment-guarantees) ·
|
|
34
|
+
[Execution semantics](#execution-semantics-read-this) · [Testing](#testing) ·
|
|
35
|
+
[Surface matrix](#surface-cabling-matrix-daemon-runtime) · [Removability](#removability) ·
|
|
36
|
+
[Status](#status--follow-ups)
|
|
37
|
+
|
|
25
38
|
## The pieces
|
|
26
39
|
|
|
27
40
|
| Module | What it is |
|
|
28
41
|
| --- | --- |
|
|
29
|
-
| `protocol.ts` | Transport contract:
|
|
30
|
-
| `src/chat/host/` | `createChatHost(env)` — the transport-agnostic engine. Owns live `Agent`s, forwards
|
|
31
|
-
| `daemon/` | Unix-socket JSON-RPC server (`startIpcDaemon`), disk-backed environment, lifecycle helpers (socket path, liveness probe, spawn-on-demand). |
|
|
32
|
-
| `client.ts` | `connectIpcClient` / `connectOrSpawnDaemon` — typed `request()`, `onSignal()`,
|
|
33
|
-
| `attach.ts` | `zidane attach` — a thin readline client proving the multi-window contract end-to-end. |
|
|
42
|
+
| `src/ipc/protocol.ts` | Transport contract: typed `IpcMethods` map, `IPC_PROTOCOL_VERSION`, handshake shapes. Re-exports `HostSignal` & co. from `src/chat/host/signals.ts`. |
|
|
43
|
+
| `src/chat/host/` | `createChatHost(env)` — the transport-agnostic engine. Owns live `Agent`s, forwards loop hooks as signals, runs the approval + interaction hubs. |
|
|
44
|
+
| `src/ipc/daemon/` | Unix-socket JSON-RPC server (`startIpcDaemon`), disk-backed environment, lifecycle helpers (socket path, liveness probe, spawn-on-demand). |
|
|
45
|
+
| `src/ipc/client.ts` | `connectIpcClient` / `connectOrSpawnDaemon` — typed `request()`, `onSignal()`, version handshake with stale-daemon drain. |
|
|
46
|
+
| `src/ipc/attach.ts` | `zidane attach` — a thin readline client proving the multi-window contract end-to-end. |
|
|
34
47
|
|
|
35
48
|
## Host environment
|
|
36
49
|
|
|
37
50
|
`createChatHost` never touches disk or Electron directly — the embedding
|
|
38
51
|
process supplies a `ChatHostEnvironment`:
|
|
39
52
|
|
|
40
|
-
- **GUI main** (`gui/src/main/chat/host.ts`): broadcast → `webContents.send
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
row is migrated by `initSettings`), pending approvals in sqlite; the
|
|
44
|
-
extension-aware runtime-config cache.
|
|
53
|
+
- **GUI main** (`gui/src/main/chat/host.ts`): broadcast → `webContents.send`;
|
|
54
|
+
settings in `state.json`'s `settings` slot (shared with the TUI/daemon);
|
|
55
|
+
pending approvals in sqlite.
|
|
45
56
|
- **Daemon** (`src/ipc/daemon/environment.ts`): broadcast → socket peers;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
same `state.json` settings slot; pending approvals in
|
|
58
|
+
`pending-approvals.json`; per-cwd extension discovery with the same
|
|
59
|
+
allowlist + trust contract as every other surface.
|
|
60
|
+
|
|
61
|
+
Both run the same engine — the gate flow, edit-approval pipeline,
|
|
62
|
+
compaction, and hook forwarding cannot drift between surfaces.
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
## RPC methods
|
|
65
|
+
|
|
66
|
+
Client → host, daemon-dispatched. All are mirrored by the GUI's embedded
|
|
67
|
+
path through the same `ChatHost` method. Source of truth: `IpcMethods` in
|
|
68
|
+
`src/ipc/protocol.ts`.
|
|
69
|
+
|
|
70
|
+
| Method | Purpose |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `chat.send` | Send a prompt; starts a run (errors if already running elsewhere). |
|
|
73
|
+
| `chat.interrupt` | Stop the session's active run. |
|
|
74
|
+
| `chat.steer` | Inject a message into the live run. |
|
|
75
|
+
| `chat.followUp` | Queue a message on the agent to run after the current turn (no steer). |
|
|
76
|
+
| `chat.pushQueued` | Race-free send: steers a live run, sends otherwise. |
|
|
77
|
+
| `chat.isRunning` | Whether the session has a run in flight. |
|
|
78
|
+
| `chat.rebuildAgent` | Retire the cached agent so the next run rebuilds (fresh MCP/creds/extensions). |
|
|
79
|
+
| `chat.listWork` | List cancellable tool calls / background tasks for the session. |
|
|
80
|
+
| `chat.cancelWork` | Cancel one work item, or `'all'`. |
|
|
81
|
+
| `sessions.list` | List sessions, filtered by project root / archive state. |
|
|
82
|
+
| `sessions.create` | Create a session. |
|
|
83
|
+
| `sessions.delete` | Delete a session. |
|
|
84
|
+
| `sessions.setTitle` | Rename a session. |
|
|
85
|
+
| `sessions.setArchived` | Archive / unarchive a session. |
|
|
86
|
+
| `sessions.setAgentId` | Change a session's agent profile. |
|
|
87
|
+
| `sessions.setEffort` | Change a session's thinking-effort level. |
|
|
88
|
+
| `sessions.setModel` | Set provider/model; `fallbacks` patches the chain (omit = untouched, `[]` = clear). |
|
|
89
|
+
| `sessions.setModelOptions` | Set per-model boolean options. |
|
|
90
|
+
| `sessions.loadEvents` | Canonical transcript replay — the re-convergence source of truth. |
|
|
91
|
+
| `sessions.getTurn` | Fetch one turn. |
|
|
92
|
+
| `sessions.contextBreakdown` | Context-usage breakdown for the context panel. |
|
|
93
|
+
| `sessions.artifacts` | One session's artifacts — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
94
|
+
| `sessions.touch` | Announce an out-of-band local `/`/`!` append; fans out `sessions:changed`. |
|
|
95
|
+
| `sessions.compact` | Trigger manual compaction. |
|
|
96
|
+
| `sessions.systemMd` | Attach-time backfill of the resolved `SYSTEM.md` override. |
|
|
97
|
+
| `sessions.contextSegments` | Every prompt segment with provenance (Instructions tab). |
|
|
98
|
+
| `sessions.createSystemOverride` | Seed a user-scope `SYSTEM.md`; throws when no override could apply. |
|
|
99
|
+
| `artifacts.list` | Cross-session artifact query — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
100
|
+
| `artifacts.delete` | Remove an artifact — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
101
|
+
| `artifacts.upsert` | Refresh an artifact (lazy `resolve()` write-back) — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
102
|
+
| `history.searchSession` | Transcript search over one session, against authoritative mid-run-fresh turns. |
|
|
103
|
+
| `history.searchAll` | Cross-session content search; one shared index built in the daemon. |
|
|
104
|
+
| `todos.get` | Current todos snapshot for a session. |
|
|
105
|
+
| `todos.set` | Apply a user todo edit to the live agent's `Session` (what `todowrite` reads). |
|
|
106
|
+
| `orchestration.main` | Resolve/create the pinned "main" session id. Gated on the `orchestration` setting. |
|
|
107
|
+
| `orchestration.createWorker` | Create a worker session under a main. |
|
|
108
|
+
| `orchestration.list` | List a main's workers. |
|
|
109
|
+
| `orchestration.read` | Read one worker's summary. |
|
|
110
|
+
| `orchestration.send` | Send a prompt to a worker; `wake` (default true) wakes the main on completion. |
|
|
111
|
+
| `orchestration.steer` | Steer a worker's live run. |
|
|
112
|
+
| `orchestration.cancel` | Cancel a worker's run. |
|
|
113
|
+
| `orchestration.transcript` | Fetch a worker's transcript text. |
|
|
114
|
+
| `commands.list` | Extension-contributed `/` commands discovered for a cwd. |
|
|
115
|
+
| `commands.completeArgs` | Argument suggestions for `/name <prefix>`. |
|
|
116
|
+
| `interactions.list` | Pending `present_plan` / `ask_user` requests. |
|
|
117
|
+
| `interactions.respond` | Answer an interaction. |
|
|
118
|
+
| `interactions.cancel` | Cancel an interaction. |
|
|
119
|
+
| `interactions.rehydrate` | Re-queue orphaned interactions from a reopened session. |
|
|
120
|
+
| `approvals.resolve` | Resolve a safe-mode approval request. |
|
|
121
|
+
| `approvals.denyAll` | Deny every pending approval (optionally scoped to a session). |
|
|
122
|
+
| `approvals.listPending` | Pending approvals — backfill for a late attacher. |
|
|
123
|
+
| `queue.list` | Current type-ahead queue for a session. |
|
|
124
|
+
| `queue.add` | Queue a message while a run is in flight; sends immediately otherwise. |
|
|
125
|
+
| `queue.remove` | Drop a queued entry. |
|
|
126
|
+
| `queue.update` | Edit a queued entry's text in place; order is kept. |
|
|
127
|
+
| `queue.pushNow` | Steer a queued entry into the live run now (sends if idle). |
|
|
128
|
+
| `drafts.get` | Current shared draft — backfill on window attach. |
|
|
129
|
+
| `drafts.set` | Replace the shared draft text (notification, fired per keystroke). |
|
|
130
|
+
| `drafts.setAttachments` | Replace the shared draft's composing attachments (notification). |
|
|
131
|
+
| `search.set` | Replace the shared transcript-search state (notification, per keystroke). |
|
|
132
|
+
| `extension.emit` | Forward a client-side `ctx.bus` emit (`'ipc'` permission); daemon validates + re-broadcasts. |
|
|
133
|
+
| `suggest.resolve` | Resolve inline ghost-text autocomplete daemon-side; latest `seq` per session wins. |
|
|
134
|
+
| `settings.get` | Read host settings. |
|
|
135
|
+
| `settings.set` | Patch host settings; fans out `settings:changed`. |
|
|
136
|
+
| `presence.list` | Current presence snapshot — backfill for a freshly opened session list. |
|
|
137
|
+
| `presence.watch` | Tag this connection as attached to a session; detach is implicit on close. |
|
|
138
|
+
| `daemon.info` | Handshake / diagnostics info. |
|
|
139
|
+
| `daemon.shutdown` | Graceful drain: deny pending approvals, abort live runs, close the socket. |
|
|
140
|
+
|
|
141
|
+
All methods above `IPC_PROTOCOL_VERSION` 9 are additive: an older daemon
|
|
142
|
+
answers "method not found" and clients degrade (local compute, or a
|
|
143
|
+
store-level fallback) instead of failing. `optional()` in
|
|
144
|
+
`src/tui/runtime/daemon.ts` is the TUI's wrapper for that pattern.
|
|
52
145
|
|
|
53
146
|
## Signals and convergence
|
|
54
147
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
clients: activation opens the worker.
|
|
134
|
-
- `system-md` — which `SYSTEM.md` override (if any) the host folded into the
|
|
135
|
-
session's prompt, emitted at agent-build time, plus the precedence losers.
|
|
136
|
-
Windows cannot derive this locally: the daemon owns the cwd and the
|
|
137
|
-
`userInstructionsScope` setting.
|
|
138
|
-
|
|
139
|
-
RPCs added alongside (all daemon-dispatched, all mirrored by the GUI's
|
|
140
|
-
embedded path through the same `ChatHost` method):
|
|
141
|
-
|
|
142
|
-
- `todos.set` — apply a user todo edit to the `Session` the MODEL reads
|
|
143
|
-
(`ChatHost.setTodos`, validated + capped host-side). Returns the resulting
|
|
144
|
-
snapshot, or `null` when no agent is live so the caller falls back to a
|
|
145
|
-
store-level write. This is what makes the todos modal's edits survive the
|
|
146
|
-
model's next `todowrite`.
|
|
147
|
-
- `sessions.systemMd` — attach-time backfill of the resolved `SYSTEM.md`
|
|
148
|
-
(windows cannot probe the daemon's filesystem).
|
|
149
|
-
- `sessions.contextSegments` — the Instructions tab: every prompt segment with
|
|
150
|
-
provenance, from the live agent's assembly.
|
|
151
|
-
- `sessions.createSystemOverride` — seed a user-scope `SYSTEM.md` from the
|
|
152
|
-
doctrine in effect; throws when an override could never apply (host-supplied
|
|
153
|
-
profile, or a scope that excludes user files) rather than writing a file
|
|
154
|
-
that would never be read.
|
|
155
|
-
|
|
156
|
-
> **Adding a signal.** The consuming ends fail CLOSED on purpose: both
|
|
157
|
-
> `src/tui/runtime/daemon.ts` and `gui/src/main/chat/host.ts` end their
|
|
158
|
-
> dispatch with `signal satisfies never`, so a new `HostSignal` kind does not
|
|
159
|
-
> compile until it is routed explicitly. Note that this catches an unrouted
|
|
160
|
-
> KIND, not an unconsumed CALLBACK — `onRetry` and `onModelFallback` were once
|
|
161
|
-
> declared, decoded, and dispatched while the TUI never supplied them, which
|
|
162
|
-
> silently removed the retry indicator and the model-fallback notice. Callbacks
|
|
163
|
-
> whose absence is a user-visible regression are therefore REQUIRED (not
|
|
164
|
-
> optional) in `DaemonSessionCallbacks`.
|
|
165
|
-
|
|
166
|
-
### Concurrent sends
|
|
148
|
+
Host → every connected client, one push channel. Source of truth:
|
|
149
|
+
`HostSignal` in `src/chat/host/signals.ts`.
|
|
150
|
+
|
|
151
|
+
| Signal | Purpose |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `run:start` | A run began. |
|
|
154
|
+
| `run:end` | A run ended (`ok`, optional `error`). |
|
|
155
|
+
| `turn:end` | A turn finished; carries `TurnUsage` (footer context gauge + live cost). |
|
|
156
|
+
| `event` | Canonical `StreamEvent`; `echo: true` marks the sender's own send/steer echo. |
|
|
157
|
+
| `compacting:start` | Manual or auto-compaction began. |
|
|
158
|
+
| `compacting:progress` | Live summary-token estimate during an in-flight compaction. |
|
|
159
|
+
| `compacting:end` | Compaction finished; `effectiveTokens` (success only) is the post-compact estimate. |
|
|
160
|
+
| `prompt-turns` | Persisted prompt turns, reduced to `PromptTurnRef` (id + texts) for echo binding. |
|
|
161
|
+
| `model:fallback` | The run fell back off its primary model. |
|
|
162
|
+
| `work:snapshot` | Cancellable tool calls / background tasks changed. |
|
|
163
|
+
| `todos:snapshot` | Todos state recomputed by the daemon-side agent. |
|
|
164
|
+
| `artifact:recorded` | An artifact was recorded — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
165
|
+
| `artifact:changed` | An artifact was deleted / refreshed — see [ARTIFACTS.md](./ARTIFACTS.md). |
|
|
166
|
+
| `interaction:request` | A `present_plan` / `ask_user` call is pending. |
|
|
167
|
+
| `interaction:resolved` | An interaction left the pending queue. |
|
|
168
|
+
| `approval:request` | A safe-mode approval is pending. |
|
|
169
|
+
| `approval:resolved` | An approval left the pending queue. |
|
|
170
|
+
| `sessions:changed` | Session created / updated / deleted. |
|
|
171
|
+
| `settings:changed` | Settings were written through the host. |
|
|
172
|
+
| `draft:changed` | The shared prompt draft changed (origin excluded from fan-out). |
|
|
173
|
+
| `draft:attachments` | The draft's composing attachments changed (origin excluded). |
|
|
174
|
+
| `search:changed` | The shared transcript-search state changed (origin excluded). |
|
|
175
|
+
| `suggest:changed` | Another window's ghost-text autocomplete resolved (origin excluded). |
|
|
176
|
+
| `skills:snapshot` | The daemon-side agent's active-skill set changed. |
|
|
177
|
+
| `mcp:auth` | A daemon-side MCP server needs (re-)authentication. |
|
|
178
|
+
| `stream:retry` | Provider retry in progress; `source` is `'parent'` or a retrying child's id. |
|
|
179
|
+
| `stream:retry:cleared` | A retried stream reached a terminal state for `source`. |
|
|
180
|
+
| `system-md` | Which `SYSTEM.md` override (if any) composed this session's prompt. |
|
|
181
|
+
| `cache:break` | A cache-prefix break was detected for the session's last request. |
|
|
182
|
+
| `presence:changed` | Full presence snapshot (attached windows, running, attention). |
|
|
183
|
+
| `hud:state` | Daemon-side extension HUD (status lines + banners); windows overlay their local HUD. |
|
|
184
|
+
| `hud:notify` | Daemon-side extension `ctx.tui.notify` — one-shot transcript line. |
|
|
185
|
+
| `queue:changed` | The shared type-ahead queue changed — broadcast to every peer, origin included. |
|
|
186
|
+
| `extension:signal` | Extension-bus message (`ctx.bus.emit`), private per `extensionId`, 64 KiB cap. |
|
|
187
|
+
|
|
188
|
+
`event { kind: 'session-event' }` is not a separate signal: a worker's
|
|
189
|
+
outcome to its main session rides the existing `event` kind as a
|
|
190
|
+
`StreamEvent` variant, clickable in both clients.
|
|
191
|
+
|
|
192
|
+
Cross-cutting contracts worth knowing before you touch a consumer:
|
|
193
|
+
|
|
194
|
+
- **Re-convergence guarantee**: at every run end and on reconnect, windows
|
|
195
|
+
replace their transcript via `sessions.loadEvents` — a signal missed to
|
|
196
|
+
a disconnect, attach gap, or daemon respawn reconciles at the next run
|
|
197
|
+
boundary.
|
|
198
|
+
- **User messages are part of the stream**: the host broadcasts
|
|
199
|
+
`user-prompt` (inside `event`) for every send/steer, before `run:start`,
|
|
200
|
+
tagging the sender's own copy `echo: true`. A client that appends its
|
|
201
|
+
own sends optimistically (the GUI) must consume exactly one pending
|
|
202
|
+
append per matching echo — never blanket-drop echo-tagged prompts, since
|
|
203
|
+
other windows' genuine sends carry the same tag (`StreamStore._pendingEcho`).
|
|
204
|
+
- **Drafts and search** are last-writer-wins, origin-excluded, backfilled
|
|
205
|
+
on attach. Shell mode rides `drafts.set`'s `shell` flag out-of-band —
|
|
206
|
+
never encoded into the text — so a literal leading `!` can't flip other
|
|
207
|
+
windows into shell-execution semantics.
|
|
208
|
+
- **Slash commands execute host-side** (`chat.send` / `chat.pushQueued`,
|
|
209
|
+
full parity incl. agent-spawning commands). The TUI's own `/` and `!`
|
|
210
|
+
shell still execute window-locally by design; effects converge via
|
|
211
|
+
`sessions.touch`.
|
|
212
|
+
- **Queue is host-owned**: `queue.add` queues while a run is in flight
|
|
213
|
+
(any window) and sends otherwise; the host drains FIFO at run end, so a
|
|
214
|
+
queued message still runs after the queuing window closes.
|
|
215
|
+
|
|
216
|
+
> **Adding a signal.** Both `src/tui/runtime/daemon.ts` and
|
|
217
|
+
> `gui/src/main/chat/host.ts` end their dispatch with
|
|
218
|
+
> `signal satisfies never`, so a new `HostSignal` kind fails to compile
|
|
219
|
+
> until routed explicitly. That catches an unrouted *kind*, not an
|
|
220
|
+
> unconsumed *callback* — `onModelFallback`/`onRetry`/`onRetryCleared` are
|
|
221
|
+
> therefore required (not optional) on `DaemonSessionCallbacks`: they were
|
|
222
|
+
> once dispatched while the TUI silently never supplied them, quietly
|
|
223
|
+
> dropping the fallback notice and the retry indicator.
|
|
224
|
+
|
|
225
|
+
## Concurrent sends
|
|
167
226
|
|
|
168
227
|
Two windows racing `chat.send` on one session is refused host-side: the
|
|
169
|
-
loser gets an explicit error. `chat.pushQueued` is the race-free
|
|
170
|
-
it
|
|
171
|
-
|
|
172
|
-
> **Cross-process run guard.**
|
|
173
|
-
>
|
|
174
|
-
> the
|
|
175
|
-
> running
|
|
176
|
-
>
|
|
177
|
-
>
|
|
178
|
-
> runs; live runs keep the marker fresh through a 60s heartbeat (targeted
|
|
179
|
-
> single-column status UPDATE — safe against concurrent turn appends), so
|
|
180
|
-
> long quiet tool calls never make a live run look stale.
|
|
228
|
+
loser gets an explicit error. `chat.pushQueued` is the race-free
|
|
229
|
+
spelling — it steers the live run when one is active and sends otherwise.
|
|
230
|
+
|
|
231
|
+
> **Cross-process run guard.** The canonical loop persists session-level
|
|
232
|
+
> `status: 'running'`, so the daemon refuses a session a local in-process
|
|
233
|
+
> window is running, and the local TUI refuses a session the daemon is
|
|
234
|
+
> running — each with an explicit error. A 5-minute freshness bound
|
|
235
|
+
> against `updatedAt` means a crashed process's leftover marker never
|
|
236
|
+
> blocks new runs; a live run refreshes the marker every 60s.
|
|
181
237
|
|
|
182
238
|
## Extension bus (`ctx.bus`)
|
|
183
239
|
|
|
184
240
|
A private, permission-gated, per-extension cross-window message channel.
|
|
185
241
|
An extension declaring the `'ipc'` permission gets `ctx.bus` —
|
|
186
|
-
`emit(payload, { sessionId? })` plus `onMessage(fn)`;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
**Privacy
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
ids without a local registration and deduping by `(extensionId, seq)`
|
|
202
|
-
(bounded last-seen window) so a self-echo through a second connection of
|
|
203
|
-
the same process, or the same signal arriving on several session
|
|
204
|
-
connections, lands at most once. Payloads are JSON values capped at
|
|
205
|
-
64 KiB serialized — enforced at emit (dropped with a logger warning) and
|
|
206
|
-
again at the daemon trust boundary.
|
|
207
|
-
|
|
208
|
-
Per embedding:
|
|
209
|
-
|
|
210
|
-
- **daemon** — shared bus across the per-cwd registries; emits broadcast
|
|
211
|
-
to every peer; client `extension.emit` notifications are validated,
|
|
212
|
-
delivered daemon-side, and re-broadcast to every peer except the origin.
|
|
213
|
-
- **TUI** — process-global bus (`tui-bus.ts`); local mode is loopback-only.
|
|
214
|
-
In daemon mode extensions still run in the TUI process: emits ride the
|
|
215
|
-
active session connection's `extension.emit` notification, and every
|
|
216
|
-
connection routes inbound `extension:signal`s back into the one bus.
|
|
217
|
-
- **GUI** — extensions run in the main process; loopback covers all
|
|
218
|
-
main-side subscribers and the renderer (no extension runtime) never
|
|
219
|
-
sees these signals. In daemon mode inbound signals re-inject via the
|
|
220
|
-
main demux, and main-side emits ride the daemon connection's
|
|
221
|
-
`extension.emit` notification (same shape as the TUI's outbound hook).
|
|
242
|
+
`emit(payload, { sessionId? })` plus `onMessage(fn)`; otherwise `ctx.bus`
|
|
243
|
+
is `undefined`.
|
|
244
|
+
|
|
245
|
+
- **Privacy**: channels are private per extension id, stamped by the host
|
|
246
|
+
from the registration identity. The daemon re-validates inbound
|
|
247
|
+
`extension.emit` ids against the session cwd's registry before fanning out.
|
|
248
|
+
- **Delivery**: `emit` delivers to local subscribers synchronously and
|
|
249
|
+
forwards the envelope to peers. Receivers dedupe by `(extensionId, seq)`
|
|
250
|
+
(or `(extensionId, nonce, seq)` on newer peers) so a self-echo or a
|
|
251
|
+
multi-connection message lands at most once. Payloads cap at 64 KiB,
|
|
252
|
+
enforced at emit and again at the daemon boundary.
|
|
253
|
+
- **Per embedding**: the daemon shares one bus per cwd registry across
|
|
254
|
+
peers; the TUI runs a process-global bus (`tui-bus.ts`) riding the
|
|
255
|
+
active session connection in daemon mode; the GUI's bus lives in the
|
|
256
|
+
main process (the renderer has no extension runtime).
|
|
222
257
|
|
|
223
258
|
## Daemon lifecycle
|
|
224
259
|
|
|
225
|
-
- Socket: `~/.zidane/daemon/daemon.sock` — a dedicated 0700 subdir
|
|
226
|
-
|
|
227
|
-
`
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
TUI's execution runtime — in-process agent (default) vs the shared
|
|
235
|
-
daemon. Plain `zidane` always opens the full TUI either way; the flag
|
|
236
|
-
only decides where the agent loop runs. With it on, two terminals
|
|
237
|
-
opening the same session converge on the same live stream: both see
|
|
238
|
-
the run, either can steer / interrupt / answer approvals, and closing
|
|
239
|
-
a window leaves the run executing. Window-local by design in daemon mode:
|
|
240
|
-
the TUI's own `/` command execution and `!` shell (both converge via
|
|
241
|
-
`sessions.touch`); slash commands sent over the wire execute host-side,
|
|
242
|
-
and attachments sync end-to-end. Implementation:
|
|
243
|
-
`src/tui/runtime/daemon.ts` (`connectDaemonSession`).
|
|
260
|
+
- Socket: `~/.zidane/daemon/daemon.sock` — a dedicated 0700 subdir
|
|
261
|
+
(honors `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX`), override with
|
|
262
|
+
`ZIDANE_DAEMON_SOCKET`. Dev builds (`ZIDANE_DEV=1`) use `dev.sock` in the
|
|
263
|
+
same dir so a checkout never attaches to or drains the installed daemon.
|
|
264
|
+
- **`useDaemon` setting** (Settings → agent, experimental): in-process
|
|
265
|
+
(default) vs. the shared daemon. On, two terminals on one session share
|
|
266
|
+
the live stream — either can steer/interrupt/answer approvals, and
|
|
267
|
+
closing a window leaves the run executing. Implementation:
|
|
268
|
+
`connectDaemonSession` in `src/tui/runtime/daemon.ts`.
|
|
244
269
|
- Start explicitly: `zidane daemon [--socket <path>] [--verbose]` or the
|
|
245
|
-
published `zidane-daemon` bin (`--idle-exit <ms
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
- Stale sockets: a leftover socket file from a crashed daemon is probed
|
|
250
|
-
before unlink — a live daemon is never yanked from under its peers; a
|
|
251
|
-
second daemon on the same socket refuses to start.
|
|
270
|
+
published `zidane-daemon` bin (`--idle-exit <ms>`, default 30 min, `0`
|
|
271
|
+
disables). `connectOrSpawnDaemon()` / `zidane attach` spawn one
|
|
272
|
+
on-demand when nothing is listening; a leftover socket from a crashed
|
|
273
|
+
daemon is probed before unlink, so a live daemon is never yanked.
|
|
252
274
|
- Version skew: the first request must be `ipc.hello` carrying
|
|
253
275
|
`IPC_PROTOCOL_VERSION`. On mismatch the daemon replies with a structured
|
|
254
|
-
error
|
|
255
|
-
(`daemon.shutdown`) and respawns a fresh one.
|
|
276
|
+
error, the client asks it to drain (`daemon.shutdown`), and respawns.
|
|
256
277
|
- **Build skew (dev)**: the handshake also carries a build fingerprint
|
|
257
|
-
(`IPC_BUILD_ID`, the ipc module's file mtime
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
stale code. Compiled binaries report `'unknown'` and skip the check
|
|
262
|
-
(releases are governed by the protocol version alone).
|
|
278
|
+
(`IPC_BUILD_ID`, the ipc module's file mtime); an older build of the
|
|
279
|
+
same protocol version is rejected, drained, and respawned, so a rebuild
|
|
280
|
+
retires stale daemons automatically. Compiled binaries report
|
|
281
|
+
`'unknown'` and skip the check.
|
|
263
282
|
|
|
264
283
|
## Turning it off (containment guarantees)
|
|
265
284
|
|
|
266
|
-
The daemon runtime is opt-in and
|
|
267
|
-
|
|
268
|
-
- **Default off**: `useDaemon` defaults to `false
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
- **
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
fresh process can meet is a crashed one's — ungated, that meant up to a
|
|
278
|
-
5-minute send lockout after an ungraceful exit mid-run. `loadSession`
|
|
279
|
-
additionally heals stale (>5 min) on-disk 'running' headers with a
|
|
280
|
-
targeted write, bounded so a reader can never clobber a live run's
|
|
281
|
-
marker.
|
|
282
|
-
- **Setting toggle**: flipping `useDaemon` off takes effect at the next
|
|
283
|
-
session activation; teardown disposes the controller and the local
|
|
284
|
-
runtime takes over. Detached daemons idle-exit on their own.
|
|
285
|
-
- **GUI, same gate**: the Electron main process gates on the SAME shared
|
|
286
|
-
`useDaemon` flag (settings live in the one `state.json`). Disabled, the
|
|
287
|
-
GUI never probes the socket; its daemon-mode handlers all carry
|
|
288
|
-
`if (daemon) wire / else local` branches where the local side is the
|
|
289
|
-
embedded-host path the whole GUI test suite exercises (the suites run
|
|
290
|
-
daemon-less), and the renderer sync surfaces are inert without signals
|
|
291
|
-
(the shared-queue latch only engages on a first `queue:changed` and
|
|
292
|
-
releases on `daemon:state` disconnect).
|
|
285
|
+
The daemon runtime is opt-in and inert when disabled:
|
|
286
|
+
|
|
287
|
+
- **Default off**: `useDaemon` defaults to `false`; every daemon touchpoint
|
|
288
|
+
(TUI and, on the same shared flag, GUI main) is gated on the live
|
|
289
|
+
controller — off, no socket, no spawn, no signals, byte-identical to
|
|
290
|
+
pre-IPC behavior. Toggling takes effect at the next session activation;
|
|
291
|
+
detached daemons idle-exit on their own.
|
|
292
|
+
- **Run guard + heartbeat gated too**: the pre-send running-marker guard
|
|
293
|
+
and its 60s heartbeat only engage when `useDaemon` is on, since with it
|
|
294
|
+
off there is exactly one writer per session. `loadSession` heals stale
|
|
295
|
+
(>5 min) on-disk `running` headers with a bounded targeted write.
|
|
293
296
|
- **Emergency kill-switch**: `zidane --no-daemon` (or `ZIDANE_NO_DAEMON=1`)
|
|
294
|
-
forces the in-process runtime for
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
daemon
|
|
301
|
-
- **Shared-state footprint when disabled**: none. The daemon's extra state
|
|
302
|
-
(socket, pidfile, `pending-approvals.json`, in-memory drafts/queues)
|
|
303
|
-
is only created by a running daemon; `sessions.db` / `state.json` access
|
|
304
|
-
from the local TUI is unchanged.
|
|
297
|
+
forces the in-process runtime for both TUI and GUI regardless of the
|
|
298
|
+
setting.
|
|
299
|
+
- **Fail-soft + zero footprint**: a spawn failure, socket problem, or
|
|
300
|
+
handshake bug falls back to the in-process runtime with a visible
|
|
301
|
+
notice; the daemon's extra state (socket, pidfile,
|
|
302
|
+
`pending-approvals.json`, in-memory drafts/queues) exists only while a
|
|
303
|
+
daemon is running.
|
|
305
304
|
|
|
306
305
|
## Execution semantics (read this)
|
|
307
306
|
|
|
@@ -309,117 +308,101 @@ Moving the host out of the window process changes two things users notice:
|
|
|
309
308
|
|
|
310
309
|
1. **Tools run in the daemon's environment.** cwd comes from the session
|
|
311
310
|
(`metadata.cwd`), but env vars are the daemon's, not the terminal's.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
leaves the run executing in the daemon — that's the feature. Interrupt
|
|
316
|
-
explicitly (`/interrupt`, Ctrl+C while running) to stop it.
|
|
311
|
+
2. **Runs survive window close.** Detaching (`/exit`, closing the
|
|
312
|
+
terminal) leaves the run executing in the daemon — that's the feature.
|
|
313
|
+
Interrupt explicitly (`/interrupt`, Ctrl+C while running) to stop it.
|
|
317
314
|
|
|
318
315
|
## Testing
|
|
319
316
|
|
|
320
317
|
- `test/ipc-host.test.ts` — host engine against a mock provider + fake
|
|
321
|
-
broadcast: run lifecycle, gate decisions
|
|
322
|
-
|
|
318
|
+
broadcast: run lifecycle, gate decisions, interaction flow, dispose/shutdown.
|
|
319
|
+
- `test/ipc-host-commands.test.ts` — host-side `/` command execution and
|
|
320
|
+
parity with the local runtime.
|
|
323
321
|
- `test/ipc-daemon.test.ts` — real unix-socket round-trips: handshake,
|
|
324
322
|
version mismatch, stale socket, multi-peer fan-out, late-attach replay,
|
|
325
|
-
cross-window approval convergence, the concurrent-send guard, and a
|
|
323
|
+
cross-window approval convergence, the concurrent-send guard, and a
|
|
326
324
|
scripted `zidane attach` session.
|
|
327
325
|
|
|
328
326
|
## Surface cabling matrix (daemon runtime)
|
|
329
327
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
known missing cabling, listed with its path forward.
|
|
328
|
+
**Synced** = converges across every window via host signals; **local** =
|
|
329
|
+
executes in the issuing window by design, converges through the shared
|
|
330
|
+
store at the next run-end resync; **gap** = known missing cabling.
|
|
334
331
|
|
|
335
332
|
| Surface | Status | How |
|
|
336
333
|
| --- | --- | --- |
|
|
337
334
|
| Prompt send / stream / interrupt / steer | synced | `chat.*` + `event` signals |
|
|
338
|
-
| User messages (incl. steered/queued) | synced |
|
|
339
|
-
| Attachments (
|
|
340
|
-
| Type-ahead queue
|
|
341
|
-
| Prompt draft
|
|
335
|
+
| User messages (incl. steered/queued) | synced | `user-prompt` broadcast before `run:start` |
|
|
336
|
+
| Attachments (images + docs) | synced | `queue.add`/`chat.send` carry base64; `user-prompt` carries chip metadata |
|
|
337
|
+
| Type-ahead queue | synced | host-owned; `queue:*` RPC + `queue:changed` |
|
|
338
|
+
| Prompt draft | synced | per-keystroke `drafts.set`, origin-excluded |
|
|
342
339
|
| Safe-mode approvals | synced | `approval:*` signals, cross-window resolve + retraction |
|
|
343
340
|
| `present_plan` / `ask_user` | synced | `interaction:*` signals, resumed orphans included |
|
|
344
|
-
| Settings (UI + behavior) | synced | `settings:changed`; daemon is
|
|
341
|
+
| Settings (UI + behavior) | synced | `settings:changed`; daemon is sole `state.json` writer in daemon mode |
|
|
345
342
|
| Session list / titles / archive | synced | `sessions:changed` + store reads |
|
|
346
|
-
| Session list "active" scope + status icons | synced | `presence
|
|
347
|
-
| Model fallback chains
|
|
348
|
-
| Compaction summarizer
|
|
349
|
-
| Model / effort / model-options picker | synced |
|
|
350
|
-
| Todos indicator + editing modal | synced |
|
|
351
|
-
| Running work + ctrl+k cancel picker | synced | `work:snapshot` mirror;
|
|
343
|
+
| Session list "active" scope + status icons | synced | `presence.*` RPC + `presence:changed` |
|
|
344
|
+
| Model fallback chains | synced | `metadata.modelChain`; `sessions.setModel` carries `fallbacks` |
|
|
345
|
+
| Compaction summarizer, skill-edit, folderless cwd | synced | ported from the GUI engine into the shared host |
|
|
346
|
+
| Model / effort / model-options picker | synced | `sessions.setModel/setEffort/setModelOptions` |
|
|
347
|
+
| Todos indicator + editing modal | synced | `todos:snapshot` mirror; `todos.set` writes the live `Session` |
|
|
348
|
+
| Running work + ctrl+k cancel picker | synced | `work:snapshot` mirror; cancel via `chat.cancelWork` |
|
|
352
349
|
| Context panel (ctrl+?) | synced | `sessions.contextBreakdown` RPC |
|
|
353
|
-
|
|
|
354
|
-
| History
|
|
355
|
-
|
|
|
356
|
-
|
|
|
357
|
-
|
|
|
358
|
-
|
|
|
359
|
-
|
|
|
360
|
-
|
|
|
361
|
-
|
|
|
350
|
+
| Artifacts (`/artifacts`, badges, `#pr` completion) | synced | see [ARTIFACTS.md § IPC](./ARTIFACTS.md#ipc) |
|
|
351
|
+
| History edits (selection modal) | synced | store refresh, `chat.rebuildAgent`, `sessions.touch` |
|
|
352
|
+
| History search (transcript + cross-session) | synced + accelerated | `history.searchSession`/`searchAll`, daemon LRU cache |
|
|
353
|
+
| Provider-retry indicator | synced | `stream:retry`; clears on resume / run end |
|
|
354
|
+
| Run ceilings (turns/cost/tokens/wall clock/retry) | synced | host reads the same settings-driven caps as local |
|
|
355
|
+
| Native server tools (e.g. web_search) | synced | host forwards `stream:server_tool_use/result` |
|
|
356
|
+
| Footer context gauge + live session cost | synced | `turn:end` carries `TurnUsage` |
|
|
357
|
+
| Subagent spawn banners | synced | host forwards `spawn:before/complete/error` |
|
|
358
|
+
| Active-skill chips | synced | `skills:snapshot` on activation change |
|
|
359
|
+
| MCP "needs login" badge | synced | `mcp:auth`; login flow itself runs window-local |
|
|
362
360
|
| Manual compaction indicator | synced | `compacting:*` signals |
|
|
363
|
-
| Transcript convergence after any miss | synced |
|
|
364
|
-
| `/` commands (incl. extension
|
|
365
|
-
| `!` shell mode | synced output | runs in the issuing window's cwd/env;
|
|
366
|
-
| Extension tools / hooks / MCPs | synced |
|
|
367
|
-
| Agent Plugins (
|
|
368
|
-
| `SYSTEM.md` prompt override | synced |
|
|
369
|
-
| Cache-break chip
|
|
370
|
-
| Main session (orchestration) | synced | opt-in (`orchestration
|
|
371
|
-
| Extension UI (renderers, themes, completion, HUD chrome) | local | loaded by each window's own registry
|
|
372
|
-
| Extension
|
|
373
|
-
| Draft attachments (pre-submit composing view) | synced | `drafts.setAttachments
|
|
374
|
-
| Background-task wake runs | synced | host-scheduled
|
|
375
|
-
| Steer / follow-up transcript order | synced | `user-prompt`
|
|
376
|
-
| MCP login → daemon credential pickup | synced | `chat.rebuildAgent`
|
|
377
|
-
| Cross-process run guard | synced | store-status guard
|
|
378
|
-
| Agent-spawning `/` commands
|
|
379
|
-
| GUI windows on the shared daemon | synced | GUI main is a daemon guest under the
|
|
380
|
-
| GUI composer draft
|
|
361
|
+
| Transcript convergence after any miss | synced | `loadEvents` replace at run end + reconnect |
|
|
362
|
+
| `/` commands (incl. extension) | synced | host-side (`commands.ts`); TUI's own `/` stays local, converges via `sessions.touch` |
|
|
363
|
+
| `!` shell mode | synced output | runs in the issuing window's cwd/env; `sessions.touch` on persist |
|
|
364
|
+
| Extension tools / hooks / MCPs | synced | daemon loads the same registry; tools run daemon-side |
|
|
365
|
+
| Agent Plugins (`enabledPlugins`) | synced | host applies both plugin gates before composing the preset |
|
|
366
|
+
| `SYSTEM.md` prompt override | synced | `system-md` carries the winning file + shadowed candidates |
|
|
367
|
+
| Cache-break chip + predictive drift | synced | `cache:break`; cost pricing stays window-side |
|
|
368
|
+
| Main session (orchestration) | synced | opt-in (`orchestration`); `orchestration.*` RPCs; requires the daemon |
|
|
369
|
+
| Extension UI (renderers, themes, completion, HUD chrome) | local | loaded by each window's own registry |
|
|
370
|
+
| Extension HUDs (status/banners/notify) | synced | daemon HUD bus forwards `hud:state`/`hud:notify`, local wins |
|
|
371
|
+
| Draft attachments (pre-submit composing view) | synced | `drafts.setAttachments`, origin-excluded |
|
|
372
|
+
| Background-task wake runs | synced | host-scheduled wake pass, no windows required |
|
|
373
|
+
| Steer / follow-up transcript order | synced | `user-prompt` fires at delivery via the `steer:inject` hook |
|
|
374
|
+
| MCP login → daemon credential pickup | synced | `chat.rebuildAgent` after a window-local OAuth login |
|
|
375
|
+
| Cross-process run guard | synced | store-status guard both ways (see [Concurrent sends](#concurrent-sends)) |
|
|
376
|
+
| Agent-spawning `/` commands | **gap** | needs a local spawn seam, or a host-side spawn RPC |
|
|
377
|
+
| GUI windows on the shared daemon | synced | GUI main is a daemon guest under the same opt-in gate |
|
|
378
|
+
| GUI composer draft/attachments/search/queue/presence | synced | renderer bound to daemon-mode channels; inert in-process |
|
|
381
379
|
|
|
382
380
|
## Removability
|
|
383
381
|
|
|
384
382
|
The dependency direction is strictly `src/ipc` → `src/chat`: the host and
|
|
385
|
-
its wire shapes (`src/chat/host/**`, incl. `signals.ts`) import nothing
|
|
386
|
-
`src/ipc`. Deleting the whole transport — `src/ipc/**`,
|
|
387
|
-
`src/tui/runtime/daemon.ts`, and the GUI's daemon
|
|
388
|
-
(`
|
|
389
|
-
`ipc.ts`/`engine.ts`/`interactions.ts`/`safeMode.ts`) — removes
|
|
390
|
-
sync and nothing else: `createChatHost` stays,
|
|
391
|
-
the GUI embeds unconditionally.
|
|
383
|
+
its wire shapes (`src/chat/host/**`, incl. `signals.ts`) import nothing
|
|
384
|
+
from `src/ipc`. Deleting the whole transport — `src/ipc/**`,
|
|
385
|
+
`src/daemon-cli.ts`, `src/tui/runtime/daemon.ts`, and the GUI's daemon
|
|
386
|
+
glue (`daemon-client.ts` plus the daemon branches in
|
|
387
|
+
`ipc.ts`/`engine.ts`/`interactions.ts`/`safeMode.ts`) — removes
|
|
388
|
+
multi-window sync and nothing else: `createChatHost` stays, since it's
|
|
389
|
+
the chat engine the GUI embeds unconditionally.
|
|
392
390
|
|
|
393
391
|
## Status / follow-ups
|
|
394
392
|
|
|
395
393
|
- The default TUI (`zidane`) still runs its agent in-process; `zidane
|
|
396
|
-
attach` is the daemon-backed
|
|
394
|
+
attach` is the daemon-backed client. Migrating the full OpenTUI
|
|
397
395
|
front-end onto `connectIpcClient` is the tracked follow-up.
|
|
398
|
-
- The GUI embeds
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
`zidane attach` clients but never adopted implicitly by the GUI. The
|
|
408
|
-
gate re-evaluates live: local `settings:set`, remote
|
|
409
|
-
`settings:changed` broadcasts, and a 5s poll all trigger
|
|
410
|
-
connect/disconnect (`ZIDANE_NO_DAEMON=1` is the kill-switch above the
|
|
411
|
-
setting, mirroring `--no-daemon`) (`drainOnMismatch: false`, never
|
|
412
|
-
spawns or retires a daemon) and routes execution, HITL, queue, drafts,
|
|
413
|
-
search, presence, and settings writes over the wire; the embedded host
|
|
414
|
-
stays as the fail-soft fallback. Settings live in the canonical
|
|
415
|
-
`state.json` (shared with the TUI): `settings:get` reads it locally in
|
|
416
|
-
both modes, `settings:set` in daemon mode is WIRE-ONLY — a local
|
|
417
|
-
pre-write would empty the daemon's before/after diff and skip its
|
|
418
|
-
agent-teardown ladder on extension/MCP toggles — with a local-write
|
|
419
|
-
fallback when the socket dies mid-write.
|
|
420
|
-
Wire-less session mutations (fork / deleteTurn / editTurnText /
|
|
396
|
+
- The GUI embeds `ChatHost` in Electron main and also speaks daemon: when
|
|
397
|
+
one is listening and `useDaemon` is on, `daemon-client.ts` connects as a
|
|
398
|
+
guest (`drainOnMismatch: false`), routing execution, HITL, queue,
|
|
399
|
+
drafts, search, presence, and settings writes over the wire; the
|
|
400
|
+
embedded host is the fail-soft fallback.
|
|
401
|
+
- `settings.set` in GUI daemon mode is wire-only (a local pre-write would
|
|
402
|
+
skip the daemon's agent-teardown ladder on extension/MCP toggles), with
|
|
403
|
+
a local-write fallback if the socket dies mid-write.
|
|
404
|
+
- Wire-less session mutations (fork / deleteTurn / editTurnText /
|
|
421
405
|
attachFolder / detachFolder) stay local-store + `sessions.touch` +
|
|
422
|
-
`chat.rebuildAgent`,
|
|
406
|
+
`chat.rebuildAgent`, guarded by a `chat.isRunning` refusal on history
|
|
423
407
|
edits. Remaining optional wire additions: paginated `sessions.loadEvents`
|
|
424
|
-
|
|
425
|
-
path (perf only — correctness converges through touch/replay today).
|
|
408
|
+
and a SQL-narrowed cross-session search fast path (perf only).
|