@jini-ai/daemon 0.3.8 → 0.5.0
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/CHANGELOG.md +119 -0
- package/README.md +50 -8
- package/dist/agent-executor/contracts.d.ts +736 -0
- package/dist/agent-executor/contracts.js +28 -0
- package/dist/agent-executor/event-translation.d.ts +87 -0
- package/dist/agent-executor/event-translation.js +230 -0
- package/dist/agent-executor/events.d.ts +190 -0
- package/dist/agent-executor/events.js +848 -0
- package/dist/agent-executor/handoff.d.ts +18 -0
- package/dist/agent-executor/handoff.js +16 -0
- package/dist/agent-executor/index.d.ts +98 -0
- package/dist/agent-executor/index.js +98 -0
- package/dist/agent-executor/launch.d.ts +375 -0
- package/dist/agent-executor/launch.js +1001 -0
- package/dist/agent-executor/mcp-bridge.d.ts +338 -0
- package/dist/agent-executor/mcp-bridge.js +835 -0
- package/dist/agent-executor/values.d.ts +5 -0
- package/dist/agent-executor/values.js +13 -0
- package/dist/agent-executor.d.ts +375 -176
- package/dist/agent-executor.js +388 -335
- package/dist/artifacts/index.d.ts +14 -0
- package/dist/artifacts/index.js +14 -0
- package/dist/artifacts/manifest.d.ts +90 -0
- package/dist/artifacts/manifest.js +229 -0
- package/dist/artifacts/publication-guard.d.ts +28 -0
- package/dist/artifacts/publication-guard.js +77 -0
- package/dist/artifacts/runtime-compat.d.ts +37 -0
- package/dist/artifacts/runtime-compat.js +33 -0
- package/dist/artifacts/store.d.ts +85 -0
- package/dist/artifacts/store.js +92 -0
- package/dist/artifacts/stub-guard.d.ts +73 -0
- package/dist/artifacts/stub-guard.js +198 -0
- package/dist/artifacts/text-suppression.d.ts +68 -0
- package/dist/artifacts/text-suppression.js +184 -0
- package/dist/close-status.d.ts +11 -8
- package/dist/close-status.js +31 -19
- package/dist/continuation/continuation-transport.d.ts +3 -1
- package/dist/continuation/continuation-transport.js +1 -1
- package/dist/continuation/journal.d.ts +12 -5
- package/dist/continuation/journal.js +4 -4
- package/dist/continuation/run-scoped-context-store.d.ts +13 -4
- package/dist/continuation/run-scoped-context-store.js +6 -6
- package/dist/continuation/run-start-handler.d.ts +1 -1
- package/dist/continuation/run-start-handler.js +3 -16
- package/dist/delegated-tool-bridge.d.ts +7 -3
- package/dist/delegated-tool-bridge.js +32 -31
- package/dist/event-log.d.ts +7 -5
- package/dist/event-log.js +12 -11
- package/dist/frontend-capability-tools.d.ts +1 -1
- package/dist/frontend-capability-tools.js +3 -2
- package/dist/frontend-session-registry.d.ts +32 -9
- package/dist/frontend-session-registry.js +12 -11
- package/dist/http/active-context.d.ts +105 -0
- package/dist/http/active-context.js +67 -0
- package/dist/http/agents.d.ts +68 -0
- package/dist/http/agents.js +12 -0
- package/dist/http/attachments.d.ts +618 -0
- package/dist/http/attachments.js +1034 -0
- package/dist/http/cancel-owned-runs.d.ts +49 -0
- package/dist/http/cancel-owned-runs.js +35 -0
- package/dist/http/component-catalog.d.ts +84 -0
- package/dist/http/component-catalog.js +64 -0
- package/dist/http/connectors.d.ts +209 -0
- package/dist/http/connectors.js +409 -0
- package/dist/http/daemon-status.d.ts +76 -0
- package/dist/http/daemon-status.js +55 -0
- package/dist/http/db-ops.d.ts +123 -0
- package/dist/http/db-ops.js +189 -0
- package/dist/http/delegated-tools.d.ts +113 -0
- package/dist/http/delegated-tools.js +230 -0
- package/dist/http/frontend-control.d.ts +136 -0
- package/dist/http/frontend-control.js +79 -0
- package/dist/http/frontend-sessions.d.ts +110 -0
- package/dist/http/frontend-sessions.js +180 -0
- package/dist/http/health.d.ts +95 -0
- package/dist/http/health.js +40 -0
- package/dist/http/host-tools.d.ts +214 -0
- package/dist/http/host-tools.js +329 -0
- package/dist/http/index.d.ts +52 -0
- package/dist/http/index.js +27 -0
- package/dist/http/media.d.ts +146 -0
- package/dist/http/media.js +346 -0
- package/dist/http/memory.d.ts +265 -0
- package/dist/http/memory.js +328 -0
- package/dist/http/model-proxy.d.ts +49 -0
- package/dist/http/model-proxy.js +642 -0
- package/dist/http/remote-run-events.d.ts +70 -0
- package/dist/http/remote-run-events.js +224 -0
- package/dist/http/research.d.ts +48 -0
- package/dist/http/research.js +315 -0
- package/dist/http/route-manifest.d.ts +35 -0
- package/dist/http/route-manifest.js +103 -0
- package/dist/http/routines.d.ts +120 -0
- package/dist/http/routines.js +298 -0
- package/dist/http/run-credentials.d.ts +73 -0
- package/dist/http/run-credentials.js +71 -0
- package/dist/http/runs.d.ts +91 -0
- package/dist/http/runs.js +221 -0
- package/dist/http/terminals.d.ts +138 -0
- package/dist/http/terminals.js +325 -0
- package/dist/http/tool-catalog.d.ts +78 -0
- package/dist/http/tool-catalog.js +66 -0
- package/dist/http/workspace-root.d.ts +77 -0
- package/dist/http/workspace-root.js +63 -0
- package/dist/http/xai.d.ts +90 -0
- package/dist/http/xai.js +655 -0
- package/dist/image-prompt-delivery.d.ts +13 -3
- package/dist/image-prompt-delivery.js +5 -5
- package/dist/index.d.ts +556 -4
- package/dist/index.js +556 -4
- package/dist/legacy-data-migration.d.ts +59 -8
- package/dist/legacy-data-migration.js +38 -33
- package/dist/read-only-tools.d.ts +130 -0
- package/dist/read-only-tools.js +115 -0
- package/dist/remote-tool-bridge.d.ts +11 -5
- package/dist/remote-tool-bridge.js +15 -15
- package/dist/routines/index.d.ts +1 -1
- package/dist/routines/index.js +1 -1
- package/dist/routines/routine-store.d.ts +34 -12
- package/dist/routines/routine-store.js +21 -20
- package/dist/routines/schedule.d.ts +48 -10
- package/dist/routines/schedule.js +21 -21
- package/dist/routines/scheduler.d.ts +32 -11
- package/dist/routines/scheduler.js +61 -57
- package/dist/routines/types.d.ts +10 -3
- package/dist/run/core/result.d.ts +6 -2
- package/dist/run/core/result.js +3 -3
- package/dist/run/core/retry.d.ts +17 -4
- package/dist/run/core/retry.js +7 -11
- package/dist/run/diagnostics/diagnostics.d.ts +14 -4
- package/dist/run/diagnostics/diagnostics.js +7 -7
- package/dist/run/index.d.ts +1 -1
- package/dist/run/index.js +1 -1
- package/dist/run-credentials.d.ts +138 -0
- package/dist/run-credentials.js +152 -0
- package/dist/run-lifecycle.d.ts +92 -33
- package/dist/run-lifecycle.js +81 -74
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.js +2 -0
- package/dist/session-coordination.d.ts +115 -0
- package/dist/session-coordination.js +111 -0
- package/dist/store/agent-sessions/errors.d.ts +8 -0
- package/dist/store/agent-sessions/errors.js +10 -0
- package/dist/store/agent-sessions/index.d.ts +12 -0
- package/dist/store/agent-sessions/index.js +19 -0
- package/dist/store/agent-sessions/pglite.d.ts +12 -0
- package/dist/store/agent-sessions/pglite.js +7 -0
- package/dist/store/agent-sessions/ports.d.ts +24 -0
- package/dist/store/agent-sessions/ports.js +2 -0
- package/dist/store/agent-sessions/postgres.d.ts +12 -0
- package/dist/store/agent-sessions/postgres.js +7 -0
- package/dist/store/agent-sessions/schema.d.ts +3 -0
- package/dist/store/agent-sessions/schema.js +23 -0
- package/dist/store/agent-sessions/sql.d.ts +35 -0
- package/dist/store/agent-sessions/sql.js +59 -0
- package/dist/store/agent-sessions/sqlite.d.ts +117 -0
- package/dist/store/agent-sessions/sqlite.js +128 -0
- package/dist/store/event-log/sqlite.d.ts +75 -0
- package/dist/store/event-log/sqlite.js +230 -0
- package/dist/surface-exchanges.d.ts +97 -0
- package/dist/surface-exchanges.js +153 -0
- package/dist/terminal-session.d.ts +43 -11
- package/dist/terminal-session.js +22 -20
- package/dist/tokens.js +4 -4
- package/dist/tool-audit.d.ts +114 -0
- package/dist/tool-audit.js +95 -0
- package/dist/tool-executor.d.ts +22 -7
- package/dist/tool-executor.js +16 -16
- package/dist/tool-result-media.d.ts +3 -1
- package/dist/tool-result-media.js +1 -1
- package/dist/tool-result-surfaces.d.ts +3 -1
- package/dist/tool-result-surfaces.js +1 -1
- package/package.json +127 -9
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* task 5), plus the `ToolExecutor` tool-execution boundary (extraction-plan
|
|
6
6
|
* §2.5 / §8 task 6), and `AgentExecutor` — the driver that wires
|
|
7
7
|
* `@jini-ai/agent-runtime` into `RunLifecycle` (extraction-plan §2.1 / §3).
|
|
8
|
-
* See `
|
|
8
|
+
* See `archived provenance ledger` for full provenance and scope-decision notes.
|
|
9
9
|
*
|
|
10
10
|
* The generic `ArtifactStore` kernel port used to live here too — moved to its own
|
|
11
11
|
* `@jini-ai/artifacts` package on 2026-07-19 (see `tokens.ts`'s doc comment).
|
|
@@ -13,13 +13,565 @@
|
|
|
13
13
|
* `routines/` (2026-07-21) adds the `RoutineService` scheduler (DST-safe wall-clock schedule
|
|
14
14
|
* math, race-safe scheduled-slot persistence) and the `RoutineStore` CRUD + run-history port,
|
|
15
15
|
* mirroring `EventLog`'s kernel-owned, storage-injected precedent. See its own module doc and
|
|
16
|
-
* `
|
|
16
|
+
* `archived provenance ledger`'s dated section for provenance.
|
|
17
17
|
*
|
|
18
18
|
* `terminal-session.ts` (2026-07-21) adds the interactive-terminal session manager: a
|
|
19
19
|
* `node-pty`-backed `PtySpawn` wired into `@jini-ai/platform`'s generic `TerminalService`, plus
|
|
20
|
-
* session-ownership gating and the kill/write/resize lock `@jini-ai/http
|
|
20
|
+
* session-ownership gating and the kill/write/resize lock `@jini-ai/daemon/http`'s `terminals.ts` route
|
|
21
21
|
* pack calls into. This is this workspace's first native-compiled-addon dependency — see this
|
|
22
|
-
* package's `package.json` and `
|
|
22
|
+
* package's `package.json` and `archived provenance ledger`'s dated section.
|
|
23
|
+
|
|
24
|
+
* Archived provenance rationale:
|
|
25
|
+
* ## Design decisions (judgment calls the task brief explicitly asked for)
|
|
26
|
+
*
|
|
27
|
+
* **1. `RunLifecycle` does not spawn or signal subprocesses.** OD's `cancel()`
|
|
28
|
+
* (`runs.ts:361-401`) does real tiered signal escalation (ACP `.abort()` → RPC
|
|
29
|
+
* grace → `SIGTERM` on the process group → grace → `SIGKILL`, all
|
|
30
|
+
* env-var-tunable). That is deeply subprocess/agent-CLI-adapter-specific and is
|
|
31
|
+
* explicitly `@jini/agent-runtime`'s territory (task 7), not this task's. This
|
|
32
|
+
* package's `cancel()` only records intent (`cancelRequested` + notifies
|
|
33
|
+
* `onCancelRequested` listeners, an `AbortSignal`-shaped but framework-neutral
|
|
34
|
+
* callback registry) and never itself forces a terminal transition — a driver
|
|
35
|
+
* observes the signal and calls `finish()` once it knows the real outcome,
|
|
36
|
+
* exactly mirroring OD's actual invariant that `finish()`/`design.runs.finish`
|
|
37
|
+
* is the only path to a terminal state, called from the child's real `close`
|
|
38
|
+
* event, never synchronously from `cancel()` itself.
|
|
39
|
+
*
|
|
40
|
+
* **2. `resume()` is a generalization, not a literal port.** OD has two
|
|
41
|
+
* unrelated "resume" concepts (see the research notes cited in the handoff
|
|
42
|
+
* report): (a) resuming the *external agent CLI's own* session via
|
|
43
|
+
* `--resume <id>`/ACP `session/load`, persisted in OD's SQLite schema — a
|
|
44
|
+
* real subprocess/vendor-CLI concern out of scope here — and (b) a
|
|
45
|
+
* `run.resumable` flag surfaced to the client so a *next user turn* can invoke
|
|
46
|
+
* (a). Neither is "resume an interrupted daemon-side run object" — that
|
|
47
|
+
* concept doesn't exist in OD. `@jini/daemon`'s `resume()` builds that concept
|
|
48
|
+
* fresh: a terminal run with `resumable: true` (set by whoever called
|
|
49
|
+
* `finish()`) can transition back to `'running'` on the **same `runId`**,
|
|
50
|
+
* continuing the **same `EventLog` cursor sequence** unbroken — no new `'start'`
|
|
51
|
+
* event, no cursor reset. This was a deliberate choice to make `resume()`
|
|
52
|
+
* meaningful and testable at the kernel layer without a real subprocess/session
|
|
53
|
+
* concept to hang it on; see `characterization.test.ts` for the two-segment
|
|
54
|
+
* proof (start→agent*→end(failed,resumable)→resume→agent*→end(succeeded), ids
|
|
55
|
+
* monotonically increasing across the whole thing).
|
|
56
|
+
*
|
|
57
|
+
* **3. No `RunStore` port was extracted.** Extraction-plan §2.2's illustrative
|
|
58
|
+
* composition example lists `RunStore` and `EventLog` as siblings a pack
|
|
59
|
+
* depends on. This task's charter (§8 task 5) names only `RunLifecycle` +
|
|
60
|
+
* `EventLog` as deliverables. `RunLifecycle`'s own run-status registry
|
|
61
|
+
* (`Map<runId, RunRecord>`) is therefore an internal implementation detail of
|
|
62
|
+
* this package's `createRunLifecycle`, not a separately swappable port — a
|
|
63
|
+
* persistent run registry (surviving a daemon restart, not just an event
|
|
64
|
+
* replay) is not implemented. **Flagged as a real follow-up**: a future task
|
|
65
|
+
* (likely task 8, alongside the `@jini/sqlite` durable `EventLog` adapter)
|
|
66
|
+
* should decide whether `RunLifecycle`'s registry needs its own `RunStore`
|
|
67
|
+
* port for restart-durability parity with §2.2's example, or whether
|
|
68
|
+
* `RunLifecycle` itself becomes the thing `@jini/sqlite` backs directly.
|
|
69
|
+
*
|
|
70
|
+
* **4. The `RunState`/`RunEndPayload.status` spelling mismatch is bridged, not
|
|
71
|
+
* fixed.** `@jini/protocol`'s `RunState` uses `'cancelled'` (2 L) while
|
|
72
|
+
* `RunEndPayload.status` uses `'canceled'` (1 L) — this is extraction-plan §12
|
|
73
|
+
* C5's own cited vocabulary-firewall canary, already present in `@jini/protocol`
|
|
74
|
+
* before this task started. `packages/protocol` is explicitly out of scope for
|
|
75
|
+
* this task, so `run-lifecycle.ts` bridges the two spellings with an explicit
|
|
76
|
+
* `TERMINAL_OUTCOME_TO_END_STATUS` lookup table (with a comment citing this)
|
|
77
|
+
* rather than silently letting them drift or fixing the upstream package.
|
|
78
|
+
* `run-lifecycle.test.ts`'s finish() test asserts the bridge explicitly.
|
|
79
|
+
*
|
|
80
|
+
* **5. Driver-emittable events are a closed, explicit union
|
|
81
|
+
* (`DriverEmittableInput`)**, not `RunProtocolEvent` minus `'start'`/`'end'`
|
|
82
|
+
* derived mechanically — `'start'` is only ever produced by `start()`, `'end'`
|
|
83
|
+
* only by `finish()`; a driver calling `emit()` can only ever produce
|
|
84
|
+
* `'agent'`/`'stdout'`/`'stderr'`/`'error'`. This mirrors OD's actual invariant
|
|
85
|
+
* (`'start'` emitted once by the engine right before spawn, `'end'` only from
|
|
86
|
+
* `design.runs.finish`) as a compile-time-enforced contract rather than a
|
|
87
|
+
* runtime convention.
|
|
88
|
+
*
|
|
89
|
+
* **6. `emit()` throws (fails fast) on an unknown or already-terminal run**,
|
|
90
|
+
* rather than silently no-op'ing. OD's own code pattern is the caller checking
|
|
91
|
+
* `run.cancelRequested || design.runs.isTerminal(run.status)` defensively
|
|
92
|
+
* *before* emitting (repeated ~10 times in `start-chat-run.ts` per the
|
|
93
|
+
* research), i.e. OD treats "don't emit after terminal" as the caller's job,
|
|
94
|
+
* not something `emit()` itself silently absorbs. This package makes that the
|
|
95
|
+
* same explicit contract, but surfaces a violation as a thrown error (a driver
|
|
96
|
+
* bug) instead of a silently-observed defensive check, per this codebase's
|
|
97
|
+
* fail-fast-defaults convention.
|
|
98
|
+
*
|
|
99
|
+
* **7. A cancel listener that subscribes after `cancel()` already fired still
|
|
100
|
+
* receives the notification immediately** (`onCancelRequested` replays the
|
|
101
|
+
* last request if `cancelRequested` is already true at subscribe time). This
|
|
102
|
+
* was found and fixed during this task's own mandatory score-skepticism pass
|
|
103
|
+
* (a plain forward-only listener registry has no memory of past firings, so a
|
|
104
|
+
* driver attaching late — e.g. after being constructed asynchronously — would
|
|
105
|
+
* otherwise silently never learn a cancellation was already requested). No OD
|
|
106
|
+
* equivalent exists to compare against; this is new, `AbortSignal`-inspired
|
|
107
|
+
* behavior.
|
|
108
|
+
*
|
|
109
|
+
* ## New home decision
|
|
110
|
+
*
|
|
111
|
+
* Ranked alongside `RunLifecycle`/`EventLog` in `@jini/daemon` because, like
|
|
112
|
+
* those, it is a daemon-startup-lifecycle concern — the origin's own doc
|
|
113
|
+
* comment states it runs "at module import time in server.ts, before
|
|
114
|
+
* `openDatabase` opens SQLite," i.e. before any daemon service starts, which is
|
|
115
|
+
* exactly this package's charter ("stateful" lifecycle, not a pure-interfaces
|
|
116
|
+
* package like `@jini/core`).
|
|
117
|
+
*
|
|
118
|
+
* ### Design decisions
|
|
119
|
+
*
|
|
120
|
+
* **1. Authorization vs. confirmation are two distinct gates, not one.**
|
|
121
|
+
* `ToolPolicy.authorize` (owned by the tool's registration) answers "is this
|
|
122
|
+
* principal permitted to use this tool at all" — a rule-based decision.
|
|
123
|
+
* `ExecutionDelegate.onConfirm` (owned by the transport, only consulted when
|
|
124
|
+
* `ToolDescriptor.requiresConfirmation` is set) answers "does the user want
|
|
125
|
+
* to proceed with *this specific* invocation right now" — an interactive,
|
|
126
|
+
* per-call gate. `ExecutionDelegate.onAuthorize` exists too, but only as an
|
|
127
|
+
* additional veto *after* the policy already allows (e.g. "does this session
|
|
128
|
+
* actually hold an active grant") — it can turn an `'allow'` into a `'deny'`,
|
|
129
|
+
* never the reverse, and is never consulted when the policy itself denies
|
|
130
|
+
* (tested explicitly).
|
|
131
|
+
*
|
|
132
|
+
* **2. Confirmation resumability is a real parked Promise, not a polling
|
|
133
|
+
* flag.** When `ExecutionDelegate.onConfirm` doesn't supply a decision
|
|
134
|
+
* synchronously (returns/resolves `undefined`), `execute()`'s returned
|
|
135
|
+
* Promise is parked on an internal `Map<executionId, resolve>` and simply
|
|
136
|
+
* does not settle — `resumeConfirmation(executionId, decision)` is the only
|
|
137
|
+
* thing that can settle it, and it can be called from a completely separate
|
|
138
|
+
* tick/request, arbitrarily far in the future, no polling required. This is
|
|
139
|
+
* the concrete answer to "the headless kernel can't prompt, so the transport
|
|
140
|
+
* injects an `ExecutionDelegate`": the kernel (`ToolExecutor`) never renders
|
|
141
|
+
* anything; it hands the transport a notification (the `onConfirm` call) and
|
|
142
|
+
* waits indefinitely for `resumeConfirmation`, exactly the shape a real UI
|
|
143
|
+
* approval flow needs. One documented sharp edge: an `async` `onConfirm`
|
|
144
|
+
* that itself resolves to `undefined` is treated as "the decision is
|
|
145
|
+
* `undefined`" (i.e. denied) rather than "pending," since the code can only
|
|
146
|
+
* tell "no decision yet" apart from "the Promise's inner value happens to be
|
|
147
|
+
* undefined" by checking whether `onConfirm`'s *own return value* is
|
|
148
|
+
* `undefined` before awaiting it — documented on the interface, not
|
|
149
|
+
* silently surprising.
|
|
150
|
+
*
|
|
151
|
+
* **3. Output truncation is a text-output concern, not a general
|
|
152
|
+
* serialization limit.** `ToolDescriptor.maxOutputBytes` only truncates a
|
|
153
|
+
* `string` handler result exceeding that length; non-string (structured)
|
|
154
|
+
* output passes through untouched even when the limit is set. A real
|
|
155
|
+
* byte-accurate UTF-8 truncation or a generic-JSON-payload cap was judged
|
|
156
|
+
* out of scope for this task's gate ("output truncation" — proven, not
|
|
157
|
+
* maximally engineered) — a reasonable follow-up if a real tool handler
|
|
158
|
+
* starts returning large structured payloads.
|
|
159
|
+
*
|
|
160
|
+
* **4. `execute()` throws (doesn't return a status) for an unknown tool
|
|
161
|
+
* id.** Denial/confirmation-denial/timeout/cancellation/failure are all
|
|
162
|
+
* legitimate business-domain outcomes modeled as `ToolExecutionResult`
|
|
163
|
+
* variants; calling `execute()` with a `toolId` nothing registered is a
|
|
164
|
+
* routing/programming bug, not a business outcome, so it's a thrown `Error`
|
|
165
|
+
* instead — the same distinction OD's own `RunLifecycle`-equivalent code in
|
|
166
|
+
* this package (`requireRun`) already draws between "unknown id" (throw) and
|
|
167
|
+
* "legitimate terminal state" (return value).
|
|
168
|
+
*
|
|
169
|
+
* **5. No persistence.** Like `EventLog`'s in-memory reference
|
|
170
|
+
* implementation, `createToolExecutor`'s audit records and pending-
|
|
171
|
+
* confirmation state live only in the process; a real host that needs audit
|
|
172
|
+
* records to survive a restart, or confirmation to resume across a daemon
|
|
173
|
+
* restart, layers a durable store behind `getAuditRecord`/`resumeConfirmation`
|
|
174
|
+
* later. Out of this task's scope (the gate is "one allowed + one denied
|
|
175
|
+
* tool call, resumable confirmation, timeout, cancellation, output
|
|
176
|
+
* truncation, and an audit record — no HTTP involved," which this satisfies
|
|
177
|
+
* in-process).
|
|
178
|
+
*
|
|
179
|
+
* **6. A `finally` block was deliberately avoided around the handler's
|
|
180
|
+
* try/catch.** An early draft cleaned up the timeout handle and the active-
|
|
181
|
+
* `AbortController` map entry in a `finally` clause; istanbul/v8 instruments
|
|
182
|
+
* try/finally with a synthetic "abrupt completion through finally" branch
|
|
183
|
+
* that is unreachable here (nothing in the catch block can itself throw a
|
|
184
|
+
* second exception past the catch), which the coverage-driven pass (Phase
|
|
185
|
+
* 6.5) couldn't close without a contrived test. Repeating the two-line
|
|
186
|
+
* cleanup at each of the try/catch's own return points instead avoided
|
|
187
|
+
* introducing that uncoverable branch — a "dead branch, refactor away"
|
|
188
|
+
* call, same discipline as the agent-protocol port's (Part 1) precedent.
|
|
189
|
+
*
|
|
190
|
+
* ### Design decisions
|
|
191
|
+
*
|
|
192
|
+
* **1. `resumable` is always `false` in v1.** `RunRetryFailureSignal` (the
|
|
193
|
+
* richer classification `decideSafeRunRetry`, in `run/core/retry.ts`,
|
|
194
|
+
* consumes) has no producer anywhere in this codebase — OD's real
|
|
195
|
+
* ~20-vendor-CLI text-matching failure classifier
|
|
196
|
+
* (`run-failure-classification.ts`, researched but never ported; see this
|
|
197
|
+
* file's earlier "Not ported" section) was deliberately excluded as its own
|
|
198
|
+
* substantial, unscoped task. Every `lifecycle.finish()` call
|
|
199
|
+
* `AgentExecutor` makes — pre-spawn guard failures, spawn errors, and the
|
|
200
|
+
* real `child.on('close', ...)` terminal transition alike — passes
|
|
201
|
+
* `resumable: false`. A blanket "not retryable" default is the only honest
|
|
202
|
+
* answer without a classifier; building one is a separate, unscoped
|
|
203
|
+
* follow-up, not attempted here.
|
|
204
|
+
*
|
|
205
|
+
* **2. `turn_end` is not forwarded as an `'agent'` event, and v1 has no
|
|
206
|
+
* multi-turn stdin-injection loop.** `turn_end` is Claude-stream-specific
|
|
207
|
+
* (synthesized from an assistant message's `stop_reason`) and has no
|
|
208
|
+
* `RunAgentPayload` variant — `translateAgentRuntimeEvent` routes it to a
|
|
209
|
+
* `'turn-end'` translation kind that `run()` reacts to directly (closing
|
|
210
|
+
* stdin) rather than durably recording. **v1 unconditionally closes stdin on
|
|
211
|
+
* the first `turn_end` regardless of `stopReason`** — including
|
|
212
|
+
* `stop_reason: 'tool_use'`, which in a real multi-turn caller would instead
|
|
213
|
+
* inject the tool's result back into the same open stdin and keep the turn
|
|
214
|
+
* going. Without that injection loop, a `tool_use`-ending turn's stdin still
|
|
215
|
+
* closes (rather than hanging forever waiting for a continuation that will
|
|
216
|
+
* never come); the agent CLI is responsible for deciding how to behave when
|
|
217
|
+
* its stdin closes mid-tool-use, which each of the 9 defs already handles
|
|
218
|
+
* via its own inactivity/EOF behavior. Building the real tool-continuation
|
|
219
|
+
* loop needs `ToolExecutorToken` wiring (deciding what to send back) and is
|
|
220
|
+
* explicitly out of this task's scope — see the plan's "Explicitly out of
|
|
221
|
+
* scope" section.
|
|
222
|
+
*
|
|
223
|
+
* **3. Session-resume ids are an intentional, documented drop.** Some
|
|
224
|
+
* parsers attach a resumable session handle to their `status` event
|
|
225
|
+
* (OpenCode's `sessionID`, Codex's `thread_id`/`sess-*`, Qoder's
|
|
226
|
+
* `session_id`+`qodercliVersion`) — `translateAgentRuntimeEvent`'s `status`
|
|
227
|
+
* case only carries `label`/`model`/`ttftMs`/`detail` (the fields
|
|
228
|
+
* `RunAgentPayload`'s `status` variant actually declares), so these ids are
|
|
229
|
+
* silently dropped at translation time. This is unrelated to
|
|
230
|
+
* `RunLifecycle.resume()`, which is run-level (does the daemon-side run
|
|
231
|
+
* object transition back to `'running'`), not CLI-conversation-level (does
|
|
232
|
+
* the underlying agent CLI continue its own multi-turn session) — persisting
|
|
233
|
+
* and replaying these ids into a real `--resume`/`session/load` call on a
|
|
234
|
+
* later turn is a downstream chat-composition concern with no current owner
|
|
235
|
+
* in this codebase, matching the same "not this package's charter" boundary
|
|
236
|
+
* this file's earlier §2's "resume" design decision draws.
|
|
237
|
+
*
|
|
238
|
+
* **4. `translateAgentRuntimeEvent`'s `usage` payload drops every sub-field
|
|
239
|
+
* `RunAgentPayload`'s narrow `usage` variant has no room for.** The 4 parsers
|
|
240
|
+
* attach `thought_tokens`, `cached_read_tokens`/`cached_write_tokens`
|
|
241
|
+
* (opencode/gemini/codex), `modelUsage`/`stopReason`/`isError` (qoder), and a
|
|
242
|
+
* top-level `stopReason` (claude/copilot) that `RunAgentPayload['usage']`
|
|
243
|
+
* (only `usage?: {input_tokens?, output_tokens?}`, `costUsd?`, `durationMs?`)
|
|
244
|
+
* has no field for — dropped, not silently miscoerced. Widening
|
|
245
|
+
* `RunAgentPayload`'s `usage` shape to carry these is a `@jini/protocol`
|
|
246
|
+
* change, out of this task's scope (protocol is a separate package with its
|
|
247
|
+
* own owner).
|
|
248
|
+
*
|
|
249
|
+
* **5. `error` events carry a minimal `RunErrorPayload`** — `{message}`,
|
|
250
|
+
* plus `error: {code, message}` only when the parser attached a string
|
|
251
|
+
* `code` (currently only claude's error event does). The `raw` field some
|
|
252
|
+
* parsers attach (opencode/gemini/qoder — a stringified copy of the whole
|
|
253
|
+
* original event, for debugging) is not folded into the error payload; kept
|
|
254
|
+
* minimal rather than speculatively enriched.
|
|
255
|
+
*
|
|
256
|
+
* **6. Every `lifecycle.emit()` call is funneled through a per-run FIFO
|
|
257
|
+
* queue (`enqueueEmit`, private to `wireChildLifecycle`), not fired
|
|
258
|
+
* independently.** A single stdout `data` chunk can synchronously produce
|
|
259
|
+
* several parsed events (one JSON line's `feed()` call may invoke a stream
|
|
260
|
+
* parser's `onEvent` more than once — e.g. Codex's `item.completed` firing
|
|
261
|
+
* both a `tool_use` guard-check and a `tool_result`), and successive `data`
|
|
262
|
+
* events must not have their derived `emit()` calls race each other out of
|
|
263
|
+
* order. Each queued task is individually try/caught (a single failing
|
|
264
|
+
* `emit()` — e.g. a race against an already-terminal run — does not block
|
|
265
|
+
* delivery of subsequently queued events), and the `close` handler awaits
|
|
266
|
+
* the queue fully drained before computing the terminal outcome, so
|
|
267
|
+
* `finish()`'s `'end'` event is always durably last.
|
|
268
|
+
*
|
|
269
|
+
* **7. Cancellation escalates the child's full descendant process tree, not
|
|
270
|
+
* just the direct child.** `@jini/platform`'s `collectProcessTreePids`
|
|
271
|
+
* (cross-platform: POSIX `ps` / Windows `Get-CimInstance`) plus
|
|
272
|
+
* `stopProcesses` (SIGTERM → poll-wait → SIGKILL escalation) were both
|
|
273
|
+
* previously completely unused — zero callers anywhere in this codebase —
|
|
274
|
+
* despite being an exact fit; `AgentExecutor` is their first real caller.
|
|
275
|
+
* This catches MCP-server/tool-subprocess descendants an OD-style
|
|
276
|
+
* POSIX-process-group-only kill would miss.
|
|
277
|
+
*
|
|
278
|
+
* **8. Native-agent authorization and Jini tool execution are two explicit
|
|
279
|
+
* paths.** ACP's `onPermissionRequest` seam receives the tool-call metadata and
|
|
280
|
+
* offered option ids before the ACP agent executes its own tool; without an
|
|
281
|
+
* injected policy it fails closed, while a runnable host records and chooses an
|
|
282
|
+
* offered allow/reject/cancel outcome. The
|
|
283
|
+
* autonomous JSON-stream CLIs still report `tool_use` only after their internal
|
|
284
|
+
* execution, so their telemetry remains observational. Separately,
|
|
285
|
+
* `createDelegatedToolBridge` is the actual Jini-tool path: it emits a matching
|
|
286
|
+
* run event pair around `ToolExecutor.execute`, so registry policy,
|
|
287
|
+
* confirmation, timeout, cancellation, and the executor audit all apply. It is
|
|
288
|
+
* transport-neutral by design; an MCP or other server must decode a concrete
|
|
289
|
+
* delegated request before calling it.
|
|
290
|
+
*
|
|
291
|
+
* ## 2026-07-22 addition — terminal/PTY route pack: `pnpm guard` re-run, and `node-pty`'s missing Linux prebuild — a real deployment decision, not a bare flag
|
|
292
|
+
*
|
|
293
|
+
* The 2026-07-21 `terminal-session.ts` addition above flagged the `node-pty` Linux-prebuild gap but
|
|
294
|
+
* left it as "undiscovered rather than mitigated" pending a real decision. This pass makes that
|
|
295
|
+
* decision, backed by evidence gathered in this session, not assumption:
|
|
296
|
+
*
|
|
297
|
+
* **`pnpm guard`, actually run this session** (it had not been, per this task's own open-items
|
|
298
|
+
* list): root `pnpm guard` (scans all of `packages/@jini/**`, including `terminal-session.ts`) — zero
|
|
299
|
+
* violations. No R1–R7 boundary/neutrality/sprawl issues in this file.
|
|
300
|
+
*
|
|
301
|
+
* **The decision.** This repo's own `ADS-memory/reports/jini-port/START-HERE.md` states the architecture plainly:
|
|
302
|
+
* *"Jini is a headless daemon"* — a long-running Node server process, the deployment shape that
|
|
303
|
+
* overwhelmingly means a Linux host (container or bare server) in practice, not a developer's own
|
|
304
|
+
* macOS/Windows machine. `node-pty@1.1.0`'s npm tarball bundles prebuilt native addons for
|
|
305
|
+
* `darwin-arm64`/`darwin-x64`/`win32-arm64`/`win32-x64` but ships **no `linux-*` prebuild** — on a
|
|
306
|
+
* Linux install, `node-pty`'s own `install` script falls back to `node-gyp rebuild`, which needs a
|
|
307
|
+
* C/C++ toolchain (python3/make/a C compiler) present at `pnpm install` time.
|
|
308
|
+
*
|
|
309
|
+
* **Empirically verified in this exact session**, not inferred from reading `node-pty`'s source: this
|
|
310
|
+
* task's own `pnpm install` ran on a real Linux (x64) sandbox with a standard build toolchain present
|
|
311
|
+
* and the fallback **worked correctly and automatically** — `node-gyp rebuild` compiled
|
|
312
|
+
* `pty.node` from source (`CXX(target) Release/obj.target/pty/src/unix/pty.o` →
|
|
313
|
+
* `SOLINK_MODULE(target) Release/obj.target/pty.node`, `gyp info ok`) with zero manual intervention
|
|
314
|
+
* beyond the `pnpm.onlyBuiltDependencies` allow-list entry the 2026-07-21 addition already added
|
|
315
|
+
* (without that entry, pnpm's supply-chain-safety default silently skips native postinstall scripts
|
|
316
|
+
* entirely and this fallback would never run at all — confirmed by that entry already being present
|
|
317
|
+
* in this checkout's root `package.json` before this pass touched anything).
|
|
318
|
+
*
|
|
319
|
+
* **Decision: accept the gap, with a documented, actionable requirement — not a code fallback.**
|
|
320
|
+
* Build-from-source *is* the fallback (already the default, already proven working end-to-end this
|
|
321
|
+
* session); nothing further needs to be built. What was missing was making the requirement this
|
|
322
|
+
* implies for a deployer explicit rather than silent: **a Linux host deploying this daemon must
|
|
323
|
+
* have a C/C++ build toolchain (`python3`, `make`, a C compiler — e.g. Debian/Ubuntu's
|
|
324
|
+
* `build-essential` package) available at `pnpm install` time.** This is *not* satisfied by a
|
|
325
|
+
* minimal/`slim`/`alpine`-family base image out of the box (those deliberately omit build tooling to
|
|
326
|
+
* stay small) — a deploying host must either use a base image that already includes one (e.g. the
|
|
327
|
+
* non-slim `node:*-bookworm` family) or install the toolchain as an explicit build-stage step before
|
|
328
|
+
* `pnpm install`. No CI configuration exists anywhere in this repo to encode this in (confirmed: no
|
|
329
|
+
* `.github/workflows` directory) — this paragraph, plus the 2026-07-21 addition's own flag above, is
|
|
330
|
+
* this decision's durable record until a real deployment/CI configuration task exists to encode it
|
|
331
|
+
* as an executable check instead of documentation. Rejected alternatives: vendoring a prebuilt
|
|
332
|
+
* `linux-x64`/`linux-arm64` `.node` binary into this repo (adds binary artifacts + a maintenance
|
|
333
|
+
* burden for a native addon this package doesn't own) and pinning an older `node-pty` version that
|
|
334
|
+
* might ship a Linux prebuild (checked — no version in this package's supported range ships one;
|
|
335
|
+
* Linux users of `node-pty` upstream are documented to be expected to build from source).
|
|
336
|
+
*
|
|
337
|
+
* ## 2026-07-22 addition — two independent retry classifiers reconciled at merge time (audit fix, AUD-002)
|
|
338
|
+
*
|
|
339
|
+
* A second cloud session, working in parallel on a branch (`fix/audit-6-fixes-20260722`) that forked
|
|
340
|
+
* before the paragraph above landed, independently built its **own** answer to the identical gap-4
|
|
341
|
+
* problem: `classifyProcessExitFailure` + `defaultClassifyFailure`, both new exports added directly to
|
|
342
|
+
* `agent-executor.ts`, with a **materially different policy** — only `signal === 'SIGPIPE'` was
|
|
343
|
+
* classified retryable (`upstream_unavailable`/`network_error`); every other signal, including
|
|
344
|
+
* `SIGKILL`/`SIGTERM`, was `process_exit`/`signal_killed` and explicitly **not** retryable. Because
|
|
345
|
+
* this branch touched a file `main` had not (a different file than the `run/core/retry.ts` location
|
|
346
|
+
* above), a raw `git merge` would have combined both silently with no conflict marker at all — two
|
|
347
|
+
* same-shaped, contradictorily-behaving classifiers coexisting in the tree, one of them dead code by
|
|
348
|
+
* accident of which line happened to get wired last. A deep-dive audit
|
|
349
|
+
* (`ADS-memory/reports/audit-fastify-merge-and-six-gap-fixes-2026-07-22.md`, AUD-002) caught this
|
|
350
|
+
* before it landed.
|
|
351
|
+
*
|
|
352
|
+
* **Decision made at merge time: keep this file's `run/core/retry.ts` version; delete the branch's
|
|
353
|
+
* `agent-executor.ts` duplicate.** Reasoning: the branch's SIGPIPE-only policy is more conservative
|
|
354
|
+
* but misses the single most common real-world signal-kill scenario this classifier exists for — an
|
|
355
|
+
* OS/container OOM killer or infra-level eviction sending `SIGKILL` to a healthy process, which is
|
|
356
|
+
* presumptively transient and exactly the case worth retrying. The broader "any signal is
|
|
357
|
+
* presumptively transient" policy this file already documents does risk retrying a genuine crash
|
|
358
|
+
* signal (e.g. `SIGSEGV`) that a retry won't fix, but the blast radius of that miss is small and
|
|
359
|
+
* bounded: `DEFAULT_SAFE_RUN_RETRY_MAX_ATTEMPTS = 1`, so the cost of being too permissive here is at
|
|
360
|
+
* most one wasted extra attempt, while the cost of the branch's more conservative policy is silently
|
|
361
|
+
* never retrying the dominant legitimate case. `agent-executor.ts`'s own `classifyProcessExitFailure`/
|
|
362
|
+
* `defaultClassifyFailure` exports and their dedicated tests were removed as part of this merge;
|
|
363
|
+
* `FailureClassificationContext`'s `code`/`signal`-only scope note is unaffected (both
|
|
364
|
+
* implementations agreed on that boundary — see this file's now-single classifier for the reasoning).
|
|
365
|
+
*
|
|
366
|
+
* Also merged in from the same branch, independent of the classifier question: real `sideEffects`
|
|
367
|
+
* wiring (`userVisibleOutputSeen`/`toolCallSeen`, derived live from the translated agent-event stream
|
|
368
|
+
* in all three `wire*Lifecycle` drivers) so two of `decideSafeRunRetry`'s four side-effect-suppression
|
|
369
|
+
* guards are now genuinely exercised, not permanently dead code — see `resumableFromProcessExit`'s own
|
|
370
|
+
* doc in `retry.ts` for exactly which two, and why `artifactWriteSeen`/`liveArtifactSeen` still
|
|
371
|
+
* cannot be (no `'artifact'`/`'live_artifact'` event kind exists anywhere in `@jini/protocol`'s
|
|
372
|
+
* `RunAgentEventPayload` union today — a real protocol gap, not an oversight; see this repo's own
|
|
373
|
+
* "A2UI full protocol deferred" scope note). `attemptCount` stays a documented, correct `0`: no
|
|
374
|
+
* automatic same-run retry loop exists anywhere in this codebase yet (gap 4's `resumable` flag is
|
|
375
|
+
* read-only metadata for a host's own later follow-up run, not an auto-retry trigger — see
|
|
376
|
+
* `resumableFromProcessExit`'s doc), so every real call to this classifier genuinely is evaluating a
|
|
377
|
+
* first and only attempt; a future auto-retry loop would need to supply its own real count.
|
|
378
|
+
*
|
|
379
|
+
* **Verified, personally, this session**: `pnpm --dir packages/daemon exec tsc --noEmit` clean;
|
|
380
|
+
* `pnpm --dir packages/daemon run test:coverage` — all tests pass, `retry.ts` and `agent-executor.ts`
|
|
381
|
+
* both 100/100/100/100 after the branch's duplicate export and its 8 dedicated unit tests were
|
|
382
|
+
* removed and the 3 `wire*Lifecycle` drivers' new side-effect tracking got its own coverage.
|
|
383
|
+
*
|
|
384
|
+
* ### The decision: def-declared fields, not an id branch
|
|
385
|
+
*
|
|
386
|
+
* `PROP-plain-format-agent-driving-2026-07-21.md`'s open question 6 asked whether antigravity's
|
|
387
|
+
* divergence stays "an id-keyed special case inside `@jini-ai/daemon` (mirroring OD's own choice to
|
|
388
|
+
* hardcode `def.id === 'antigravity'` in `server.ts`)" or gets a `RuntimeAgentDef`-level field (its
|
|
389
|
+
* "Option C"). **Option C, deliberately** — and the evidence is not a style preference:
|
|
390
|
+
*
|
|
391
|
+
* - `RuntimeAgentDef` already carries **14** optional behavior flags/hooks the executor reads
|
|
392
|
+
* generically (`promptViaFile`, `promptViaStdin`, `resumesSessionViaCli`,
|
|
393
|
+
* `capturesSessionIdFromStream`, `resumesSessionViaAcpLoad`, `externalMcpInjection`, `authProbe`,
|
|
394
|
+
* `acpMcpEnvFormat`, `defaultModelEnvVar`, `inactivityTimeoutMs`, `maxPromptArgBytes`,
|
|
395
|
+
* `supportsCustomModel`, `promptInputFormat`, `promptViaStdin`'s `permissionMode`-aware `buildArgs`).
|
|
396
|
+
* - There was **zero** precedent anywhere in this package for the executor branching on a literal
|
|
397
|
+
* agent id to decide *behavior* — the antigravity rejection was the only id branch at all, and it
|
|
398
|
+
* decided *support*, not behavior.
|
|
399
|
+
* - OD's `server.ts` hardcodes `def.id === 'antigravity'` twice; §2d of the same proposal already
|
|
400
|
+
* records that OD's *prompt-delivery* call sites, by contrast, key only off declared fields with
|
|
401
|
+
* zero id branches. The field-driven half is the half that generalized cleanly.
|
|
402
|
+
*
|
|
403
|
+
* Consequence worth stating plainly: a second CLI with either property (prints a secret on stdout and
|
|
404
|
+
* exits 0; or mutates process-global state its own startup reads back) needs **no new executor code**.
|
|
405
|
+
*
|
|
406
|
+
* ## 2026-07-30 addition — post-merge audit fixes (Codex gpt-5.6-sol findings on `9cb4ffc50…085c4799a`)
|
|
407
|
+
*
|
|
408
|
+
* Full verification write-up, per finding, with the observed red/green output:
|
|
409
|
+
* `ADS-memory/reports/post-merge-audit-2026-07-29/daemon-fix-report.md`. Findings list:
|
|
410
|
+
* `daemon-findings.md` in that same directory. All 5 blocking and 2 of 3 non-blocking findings were real
|
|
411
|
+
* — no false positives. Every fix landed behind a test confirmed failing first.
|
|
412
|
+
*
|
|
413
|
+
* **`.mcp.json` is now one file per run.** `mcpServers.jini.env` carries this run's `JINI_RUN_ID` and its
|
|
414
|
+
* bearer `JINI_DAEMON_TOKEN`, and a shared `cwd/.mcp.json` cannot hold two runs' identities at once — a
|
|
415
|
+
* CLI loads its MCP config when it starts its client, not at spawn, so a second run in the same directory
|
|
416
|
+
* overwrote the entry the first run's child had not read yet and that child then called back with the
|
|
417
|
+
* *other* run's id and token. `mcpJsonPathForRun` writes `<cwd>/.mcp.jini-<runId>.json`; `cwd/.mcp.json`
|
|
418
|
+
* is now read-only (still the merge source, so a project's own servers survive, and nothing needs
|
|
419
|
+
* restoring); the run's file is removed through `cleanupStagedFiles` via a new
|
|
420
|
+
* `McpJsonInjectionOptions.removeFile` seam, so a live token no longer outlives the run. Refusing the
|
|
421
|
+
* second run instead was rejected: concurrent runs in one cwd are a documented design point (that is why
|
|
422
|
+
* `credential` is a per-run resolver at all).
|
|
423
|
+
*
|
|
424
|
+
* **One follow-up this leaves open, in `@jini-ai/agent-runtime` rather than here.** The run-scoped path
|
|
425
|
+
* only reaches the child because the def passes it on — `claude.ts` emits
|
|
426
|
+
* `--strict-mcp-config --mcp-config <runtimeContext.mcpJsonPath>`. **`codebuddy.ts`, the other
|
|
427
|
+
* `externalMcpInjection: 'claude-mcp-json'` def, ignores `mcpJsonPath`** and relies on its CLI
|
|
428
|
+
* auto-discovering `cwd/.mcp.json`, so a codebuddy run with `mcpJsonInjection` configured now writes a
|
|
429
|
+
* file nothing reads. That def wants the same two flags under the same guard — deliberately not done
|
|
430
|
+
* blind, because codebuddy's acceptance of those flags is unverified and an unsupported flag fails the
|
|
431
|
+
* run outright, which is worse than losing tools on a path `claude.ts`'s own live 2026-07-30 note says
|
|
432
|
+
* never worked headlessly anyway (auto-discovery needs an interactive trust prompt; the connection sat at
|
|
433
|
+
* `"pending"` forever). Verify with one live codebuddy run, then add the flags.
|
|
434
|
+
*
|
|
435
|
+
* **Resolved the same day, below** — the "the other 3 MCP injection mechanisms" entry closes this
|
|
436
|
+
* exact follow-up (Gap A) plus the two mechanisms nothing implemented at all (Gaps B/C), all through
|
|
437
|
+
* one dispatch point rather than one-off patches.
|
|
438
|
+
*
|
|
439
|
+
* **The `'until-close'` stdout accumulator is bounded.** `DEFAULT_BUFFERED_STDOUT_MAX_BYTES` (8 MiB,
|
|
440
|
+
* overridable via `CreateAgentExecutorOptions.bufferedStdoutMaxBytes`) — an `until-close` child can emit
|
|
441
|
+
* indefinitely or never close, and this driver treats agent CLIs as potentially adversarial elsewhere
|
|
442
|
+
* (SEC-001), so one run could exhaust the daemon heap and take every unrelated run with it. Whole chunks
|
|
443
|
+
* only (no sliced multi-byte characters), everything after the first refusal dropped, and truncation is
|
|
444
|
+
* reported in a host-authored note appended *after* the def's `sanitize` runs — a consumer redactor must
|
|
445
|
+
* not be able to delete the line saying output is missing, and a truncated run emits even when the
|
|
446
|
+
* sanitized text is empty.
|
|
447
|
+
*
|
|
448
|
+
* **Nothing fallible now sits in front of `finish()`.** `buildArgs` is guarded like every other
|
|
449
|
+
* staging→spawn step (it performs real filesystem writes — that is what `runtimeLock` exists for — so
|
|
450
|
+
* EACCES/ENOSPC used to escape `run()` as a bare `Error`, stranding the run *and* holding the
|
|
451
|
+
* process-global mutex forever). In all three close handlers, staged-file cleanup and the host's
|
|
452
|
+
* `classifyFailure` are guarded by `cleanupStagedFilesSafely`/`classifyFailureSafely`: previously either
|
|
453
|
+
* rejection took the terminal transition with it, leaving a `'running'` run whose child was already gone
|
|
454
|
+
* and surfacing only as an unhandled rejection. The finding cited only the child-driven handler; the ACP
|
|
455
|
+
* and pi-rpc handlers had the identical shape and the identical bug. `reportPostCloseFailure` also
|
|
456
|
+
* absorbs a throwing `onCleanupFailure` sink. `AgentCleanupFailurePhase` gains `'staged-file-cleanup'`
|
|
457
|
+
* and `'failure-classification'`; `AgentCleanupFailureContext.pid` widens to `number | undefined`.
|
|
458
|
+
*
|
|
459
|
+
* **`RunLifecycle` no longer exposes uncommitted starts.** `get`/`list` await a pending `startPromise` (a
|
|
460
|
+
* failed durable start reads as "no such run" rather than as a running one no restart could rehydrate),
|
|
461
|
+
* and `waitForTerminal` awaits *and propagates* it before parking a waiter — which is what closes the
|
|
462
|
+
* hang: nothing ever calls `finish()` for an unwound record, so such a waiter was previously never
|
|
463
|
+
* resolved or rejected.
|
|
464
|
+
*
|
|
465
|
+
* **Two smaller ones.** `FrontendSessionRegistry` keys run bindings on the attachment object, not the
|
|
466
|
+
* session id: a session id is reusable by design (a reconnecting tab re-attaches under the same id), so a
|
|
467
|
+
* stale handle's `detach()` used to remove the *replacement* session, its bindings, and the usability of
|
|
468
|
+
* its still-current bind token. `createDefaultRunStartHandler` now forwards `imagePaths`,
|
|
469
|
+
* `extraAllowedDirs` and `uploadRoot` — the same silent-drop failure mode `permissionMode` was already
|
|
470
|
+
* documented for.
|
|
471
|
+
*
|
|
472
|
+
* **Left as a note, not patched:** `remote-tool-bridge.ts` does not dedup on `toolUseId`, so a retried
|
|
473
|
+
* cross-process POST appends duplicate tool events. Real, but every in-reach fix is worse than the bug:
|
|
474
|
+
* the recorder holds only a `RunLifecycle` (no read API, so dedup could only be non-durable in-memory
|
|
475
|
+
* state that a restart defeats), it would need per-run lifetime machinery this module has no hook for,
|
|
476
|
+
* and `delegated-tool-bridge.ts`'s `execute()` does not dedup either — deduping one half would split two
|
|
477
|
+
* paths this module exists to keep byte-identical. The honest fix is idempotency at the
|
|
478
|
+
* `RunLifecycle.emit` (or transport) boundary.
|
|
479
|
+
*
|
|
480
|
+
* Tests: **`@jini-ai/daemon` 654 → 682**, 26 files, zero failures — `agent-executor.test.ts` 189 → 210,
|
|
481
|
+
* `frontend-session-registry.test.ts` 41 → 44, `run-lifecycle.test.ts` 53 → 56,
|
|
482
|
+
* `run-start-handler.test.ts` 7 → 8. No test deleted or weakened; three existing `.mcp.json` tests were
|
|
483
|
+
* updated for the genuinely-changed write path (two of them strengthened — see the report). Coverage:
|
|
484
|
+
* package 99.85/99.93/99.64/99.85, with `agent-executor.ts`, `frontend-session-registry.ts` and
|
|
485
|
+
* `run-start-handler.ts` all fully covered; the only residual gap is the pre-existing, deliberately
|
|
486
|
+
* uncovered `handleInactivityTimeout` guard in `run-lifecycle.ts` that function's own doc already explains.
|
|
487
|
+
* `pnpm typecheck` (package and `-r` workspace-wide, since two exported types changed): clean.
|
|
488
|
+
* `pnpm guard`: clean.
|
|
489
|
+
*
|
|
490
|
+
* ## 2026-07-30 addition — the other 3 MCP injection mechanisms get wired: `acp-merge`, the two `*-env-content` strategies, and codebuddy's missing argv
|
|
491
|
+
*
|
|
492
|
+
* The 2026-07-22 gap-3-part-2 entry above wired exactly one of the four declared
|
|
493
|
+
* `externalMcpInjection` strategies. Its own text named the other three as future work ("a future
|
|
494
|
+
* task wiring those two would extend `wireAcpLifecycle`'s existing `envFormat`/`mcpServers`
|
|
495
|
+
* passthrough or `applyAgentLaunchEnv`'s env composition respectively"). A peer review then confirmed
|
|
496
|
+
* the practical consequence: of 24 defs, only `claude` actually delivered MCP tools end to end. This
|
|
497
|
+
* entry closes all three remaining gaps. **Adding a strategy for a currently-undeclared agent (e.g.
|
|
498
|
+
* `codex`) was explicitly out of scope and was not done.**
|
|
499
|
+
*
|
|
500
|
+
* **The boundary this is organised around** is per-CLI vs per-mechanism. Identity, binary, and
|
|
501
|
+
* model/session/permission argv are genuinely bespoke and stay in each def's own `buildArgs`. MCP
|
|
502
|
+
* staging, ACP server merging, and env-content serialisation are *mechanisms*, and each now has
|
|
503
|
+
* exactly one implementation that every def declaring it flows through.
|
|
504
|
+
*
|
|
505
|
+
* **Gap A — `codebuddy` (`'claude-mcp-json'`).** The executor already computed and staged the same
|
|
506
|
+
* `.mcp.json` for it, but `defs/codebuddy.ts`'s `buildArgs` never read `runtimeContext.mcpJsonPath`,
|
|
507
|
+
* so it was still left on the headless auto-discovery path the claude fix exists to avoid. Rather
|
|
508
|
+
* than copy claude's branch, the flag-building moved into a shared
|
|
509
|
+
* `@jini-ai/agent-runtime` helper — **`buildClaudeMcpConfigArgs` in `src/defs/shared.ts`** — that both
|
|
510
|
+
* defs now call. `--strict-mcp-config` was verified as genuinely supported by CodeBuddy before being
|
|
511
|
+
* added (its own CLI reference documents `--mcp-config <fileOrString>` and `--strict-mcp-config`,
|
|
512
|
+
* "Only use MCP servers in --mcp-config, ignore other MCP configuration"), so codebuddy gets the same
|
|
513
|
+
* strict-isolation guarantee as claude rather than a silently weaker one. `shared.test.ts` pins that
|
|
514
|
+
* both defs emit byte-identical MCP argv for identical input, so a future copy-paste divergence fails
|
|
515
|
+
* there rather than in production.
|
|
516
|
+
*
|
|
517
|
+
* **Gap B — the 8 `'acp-merge'` defs** (devin, hermes, kilo, kimi, kiro, reasonix, trae-cli, vibe),
|
|
518
|
+
* which were getting *zero* MCP tools, not merely a missing flag. `attachAcpSession` has always
|
|
519
|
+
* accepted `mcpServers`; `wireAcpLifecycle` simply never passed any. Fixed once, in that shared
|
|
520
|
+
* function (`WireAcpLifecycleContext.mcpServers` → spread into the attach call when non-empty) —
|
|
521
|
+
* **no per-def file was touched**, because the gap was never in the defs. A 9th def declaring
|
|
522
|
+
* `'acp-merge'` is covered automatically. Each entry's `env` is emitted as a plain object so
|
|
523
|
+
* `buildAcpSessionNewParams` keeps owning the array-vs-map wire-shape fork (reasonix declares
|
|
524
|
+
* `acpMcpEnvFormat: 'map'`).
|
|
525
|
+
*
|
|
526
|
+
* **Gap C — `'opencode-env-content'` / `'mimo-env-content'`.** Two labels, one serialiser: MiMo's own
|
|
527
|
+
* def doc already stated it consumes the same JSON schema as OpenCode's, differing only in env-var
|
|
528
|
+
* namespace, and the tests pin that the emitted content is byte-identical modulo the run id. The
|
|
529
|
+
* difference is one row in `ENV_CONTENT_VAR_BY_STRATEGY` (`OPENCODE_CONFIG_CONTENT` /
|
|
530
|
+
* `MIMOCODE_CONFIG_CONTENT`), not a second code path. `mergeEnvContentMcpConfig` applies the same
|
|
531
|
+
* "merge, never clobber" discipline `mergeMcpJsonContent` does, and for a sharper reason: a host may
|
|
532
|
+
* already be handing the CLI the *user's* configured MCP servers through that very variable.
|
|
533
|
+
*
|
|
534
|
+
* **One dispatch point, one credential.** `buildMcpBridgeDelivery` (pure, exported) is the single
|
|
535
|
+
* place a declared strategy maps to a mechanism, returning a discriminated `McpBridgeDelivery` so a
|
|
536
|
+
* consumer cannot read another mechanism's payload. `run()` resolves the per-run bearer credential
|
|
537
|
+
* exactly once, before `buildArgs`, and hands it to whichever serialiser applies; a rejecting resolver
|
|
538
|
+
* still fails the run before spawn. Keyed off the *strategy*, never off `def.id` — a registry-level
|
|
539
|
+
* test asserts every real def declaring a strategy gets a non-null delivery, so a 25th def with an
|
|
540
|
+
* unhandled strategy fails there.
|
|
541
|
+
*
|
|
542
|
+
* **Security.** The credential reaches every mechanism through an *environment*, never through
|
|
543
|
+
* process arguments (`ps` is readable by other local users): `.mcp.json`'s `env`, the ACP server
|
|
544
|
+
* descriptor's `env` (which the ACP agent applies to the MCP child it spawns), and the env-content
|
|
545
|
+
* config's `environment` key — itself carried in the spawned CLI's own env var, deliberately not a
|
|
546
|
+
* `-c key=value`-style CLI argument. Three dedicated tests assert the secret is absent from argv while
|
|
547
|
+
* still present where it must be, including one driving a real subprocess.
|
|
548
|
+
*
|
|
549
|
+
* **Files changed.** `@jini-ai/agent-runtime`: `src/defs/shared.ts` (new
|
|
550
|
+
* `buildClaudeMcpConfigArgs`), `src/defs/claude.ts` and `src/defs/codebuddy.ts` (both call it).
|
|
551
|
+
* `@jini-ai/daemon`: `src/agent-executor.ts` (new `buildAcpMcpBridgeServers`,
|
|
552
|
+
* `ENV_CONTENT_VAR_BY_STRATEGY`, `mergeEnvContentMcpConfig`, `McpBridgeDelivery`,
|
|
553
|
+
* `buildMcpBridgeDelivery`; `writeMcpJsonForRun` narrowed to consume an already-built delivery;
|
|
554
|
+
* `wireAcpLifecycle` gained `mcpServers`; `run()` composes `childEnv`). `McpJsonInjectionOptions`
|
|
555
|
+
* keeps its name for API compatibility with `@jini-ai/server`'s `agentExecutor` passthrough even
|
|
556
|
+
* though it is no longer `.mcp.json`-specific — a naming-debt item, deliberately not renamed here.
|
|
557
|
+
* Reconciled the same session with the run-scoped-path fix above: `buildMcpBridgeDelivery`'s
|
|
558
|
+
* `'claude-mcp-json'` case calls `mcpJsonPathForRun`, not a shared `cwd/.mcp.json`, so the
|
|
559
|
+
* cross-run credential leak that fix closed stays closed under the generalized dispatch too;
|
|
560
|
+
* `writeMcpJsonForRun` takes `cwd` as an explicit param so it still reads the project's own
|
|
561
|
+
* `.mcp.json` as its merge base while writing the run-scoped path.
|
|
562
|
+
*
|
|
563
|
+
* **Tests / verification, run this session.** `@jini-ai/agent-runtime` **1848 → 1858**
|
|
564
|
+
* (`defs/__tests__/shared.test.ts` +5, `defs/__tests__/codebuddy.test.ts` +5). `@jini-ai/daemon`
|
|
565
|
+
* **654 → 687** (`__tests__/agent-executor.test.ts` +30 across `buildAcpMcpBridgeServers`,
|
|
566
|
+
* `mergeEnvContentMcpConfig`, `buildMcpBridgeDelivery`, the ACP passthrough block and the env-content
|
|
567
|
+
* block; `__tests__/agent-executor-acp.integration.test.ts` +3). Nothing existing regressed. The three
|
|
568
|
+
* integration additions are the closest available analogue to the live smoke check that verified the
|
|
569
|
+
* claude fix: the existing ACP fixture — a **real spawned Node subprocess** speaking real ACP JSON-RPC
|
|
570
|
+
* over real stdio, through the real `attachAcpSession` — now echoes back the `mcpServers` its own
|
|
571
|
+
* `session/new` received, so delivery is asserted from the agent's point of view rather than from a
|
|
572
|
+
* stubbed attach call, in both `array` and `map` env formats. No vendor CLI or credentials were
|
|
573
|
+
* needed, and none of the 24 real agent CLIs were spawned. `tsc --noEmit` and `tsc -p tsconfig.json`
|
|
574
|
+
* (build): clean for both packages; root `pnpm -r typecheck`: clean.
|
|
23
575
|
*/
|
|
24
576
|
export * from './event-log.js';
|
|
25
577
|
export * from './close-status.js';
|