@jini-ai/daemon 0.6.15-rc.2 → 0.6.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +14 -4
  3. package/dist/agent-executor/codex/home-config.js +3 -2
  4. package/dist/agent-executor/codex/home-staging.js +2 -2
  5. package/dist/agent-executor/codex/isolation.js +4 -3
  6. package/dist/agent-executor/contracts.d.ts +1 -1
  7. package/dist/agent-executor/contracts.js +0 -1
  8. package/dist/agent-executor/event-translation.d.ts +3 -3
  9. package/dist/agent-executor/event-translation.js +6 -6
  10. package/dist/agent-executor/events.js +65 -51
  11. package/dist/agent-executor/index.d.ts +5 -5
  12. package/dist/agent-executor/index.js +5 -5
  13. package/dist/agent-executor/launch.js +30 -46
  14. package/dist/agent-executor/live-session-state.js +2 -2
  15. package/dist/agent-executor/mcp-bridge.js +5 -0
  16. package/dist/agent-executor/message-relay.js +5 -6
  17. package/dist/agent-executor/run-output.js +7 -2
  18. package/dist/ai-trace-lifecycle.d.ts +1 -1
  19. package/dist/ai-trace-lifecycle.js +1 -1
  20. package/dist/close-status.js +6 -2
  21. package/dist/core/omit-keys.d.ts +7 -0
  22. package/dist/core/omit-keys.js +12 -0
  23. package/dist/core/read-only-tools.js +5 -1
  24. package/dist/core/tool-audit.js +11 -7
  25. package/dist/core/tool-gaps.js +1 -1
  26. package/dist/delegated-result-correlation.js +2 -2
  27. package/dist/frontend-capability-tools.d.ts +2 -0
  28. package/dist/frontend-capability-tools.js +7 -1
  29. package/dist/frontend-session-registry.d.ts +42 -3
  30. package/dist/frontend-session-registry.js +61 -9
  31. package/dist/http/agents.d.ts +4 -0
  32. package/dist/http/attachments.js +13 -12
  33. package/dist/http/component-catalog.js +1 -1
  34. package/dist/http/connectors.js +7 -5
  35. package/dist/http/db-ops.js +5 -0
  36. package/dist/http/delegated-tools.js +5 -0
  37. package/dist/http/frontend-control.d.ts +13 -0
  38. package/dist/http/frontend-control.js +14 -3
  39. package/dist/http/frontend-guest-scope.d.ts +32 -0
  40. package/dist/http/frontend-guest-scope.js +63 -0
  41. package/dist/http/frontend-sessions.d.ts +29 -6
  42. package/dist/http/frontend-sessions.js +108 -28
  43. package/dist/http/host-tools.js +7 -5
  44. package/dist/http/index.d.ts +1 -0
  45. package/dist/http/index.js +1 -0
  46. package/dist/http/memory.js +3 -1
  47. package/dist/http/model-proxy.js +13 -8
  48. package/dist/http/research-tavily.adapter.js +2 -1
  49. package/dist/http/routines.js +7 -5
  50. package/dist/http/terminals.js +5 -0
  51. package/dist/http/tool-catalog.js +1 -1
  52. package/dist/http/xai.js +10 -7
  53. package/dist/index.d.ts +2 -2
  54. package/dist/index.js +2 -2
  55. package/dist/legacy-data-migration.d.ts +0 -12
  56. package/dist/legacy-data-migration.js +21 -13
  57. package/dist/remote-tool-executor.js +2 -1
  58. package/dist/routines/schedule.js +2 -1
  59. package/dist/routines/scheduler.js +6 -7
  60. package/dist/run-lifecycle.js +2 -1
  61. package/dist/store/agent-sessions/sql.js +7 -3
  62. package/dist/store/event-log/sqlite-kernel.js +7 -2
  63. package/dist/store/event-log/sqlite.js +6 -2
  64. package/dist/surface-exchanges.js +3 -3
  65. package/dist/terminal-session.d.ts +51 -20
  66. package/dist/terminal-session.js +10 -94
  67. package/dist/terminal.adapter.d.ts +22 -0
  68. package/dist/terminal.adapter.js +47 -0
  69. package/dist/tool-executor.d.ts +4 -2
  70. package/dist/tool-executor.js +95 -15
  71. package/dist/tool-recovery/recovery.js +5 -3
  72. package/dist/tool-recovery/redaction.js +1 -2
  73. package/dist/tool-recovery/stack.js +5 -1
  74. package/dist/tool-result-media.d.ts +4 -0
  75. package/dist/tool-result-media.js +10 -3
  76. package/dist/tool-result-surfaces.js +10 -9
  77. package/package.json +30 -34
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.6.16
2
+
3
+ ### Minor Changes
4
+
5
+ - Remove the GitHub Copilot CLI agent: its def, `createCopilotStreamHandler`/`CopilotStreamEvent`,
6
+ account model discovery and the `@github/copilot-sdk` optional peer, the MCP installer's `copilot`
7
+ slug, and its icon. A saved `copilot` agent id no longer resolves (`getAgentDef` returns `null`).
8
+ CopilotKit/AG-UI is unrelated and unchanged.
9
+
10
+ Drop peers no runtime entry loads: `express` from core and http-kit (type-only imports; type
11
+ consumers keep `@types/express`), `vue` from ui-kit, `react-dom` from vibecoding.
12
+
1
13
  ## 0.6.13
2
14
 
3
15
  ### Patch Changes
package/README.md CHANGED
@@ -20,8 +20,18 @@ npm install @jini-ai/daemon
20
20
 
21
21
  `node-pty` is an **optional peer dependency** — the package boots and every other capability
22
22
  (run lifecycle, tool executor, agent executor, routines) works without it. Only
23
- `createTerminalSessionManager`'s real (non-test) `PtySpawn` — an actual interactive terminal/PTY
24
- spawn — needs `node-pty` installed; everything else has no native-addon dependency at all.
23
+ the native adapter at `@jini-ai/daemon/terminal` needs `node-pty` installed.
24
+ The default entry's session manager requires a host-supplied `terminalService` or `loadSpawnPty`:
25
+
26
+ ```ts
27
+ import { createTerminalSessionManager } from '@jini-ai/daemon';
28
+ import { loadRealSpawnPty } from '@jini-ai/daemon/terminal';
29
+
30
+ const manager = createTerminalSessionManager({}, { loadSpawnPty: () => loadRealSpawnPty({}) });
31
+ ```
32
+
33
+ With `@jini-ai/server`, pass that loader as `featureOptions.terminal.loadSpawnPty` to
34
+ `composeJiniKernel`, or as `loadTerminalSpawnPty` to `createLocalNodeDaemon`.
25
35
 
26
36
  ## What you get
27
37
 
@@ -47,8 +57,8 @@ spawn — needs `node-pty` installed; everything else has no native-addon depend
47
57
  browser surface) and `createFrontendCapabilityRegistrations` (projects a frontend capability
48
58
  manifest into `ToolRegistry` registrations — the only door into it).
49
59
  - **Terminal sessions** — `createTerminalSessionManager`, `createTerminalToolRegistrations` — a
50
- `node-pty`-backed session manager built on `@jini-ai/platform`'s generic ring-buffer engine, plus
51
- session-token gating.
60
+ session manager built on `@jini-ai/platform`'s generic ring-buffer engine, plus session-token
61
+ gating. The optional native `loadRealSpawnPty` adapter lives at `@jini-ai/daemon/terminal`.
52
62
  - **Routines** — `RoutineService`, `createInMemoryRoutineStore` (`routines/`) — a DST-safe
53
63
  wall-clock scheduler plus CRUD + run-history store port.
54
64
  - **Run-orchestration helpers** (`run/`) — `runResultFromStatus`, `deriveRunErrorCode`,
@@ -103,7 +103,7 @@ function scanBareTomlKeyChar(scan, ch) {
103
103
  scan.current += ch;
104
104
  }
105
105
  // An inline or dotted root `mcp_servers` declaration, which the table scanner below cannot rewrite.
106
- const INLINE_MCP_SERVERS = /^\s*(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*(?:\.|=)/;
106
+ const INLINE_MCP_SERVERS = /^\s*(?:mcp_servers|"mcp_servers"|'mcp_servers')\s*[.=]/;
107
107
  // `[<key>]` optionally followed only by a `# comment`; `[[...]]` never matches (see below).
108
108
  const TOML_TABLE_HEADER = /^\s*\[([^[\]]+)\]\s*(#.*)?$/;
109
109
  /**
@@ -203,7 +203,8 @@ function stripExistingJiniMcpServerTable(existingRaw, optional = {}) {
203
203
  */
204
204
  export function buildCodexHomeConfigToml({ existingRaw, entry, inheritUserMcp = true, inheritUserSettings = true }, _optional = {}) {
205
205
  const base = stripExistingJiniMcpServerTable(existingRaw ?? '', { allServers: !inheritUserMcp, mcpOnly: !inheritUserSettings });
206
- const separator = base.length === 0 ? '' : base.endsWith('\n') ? '\n' : '\n\n';
206
+ const blockBreak = base.endsWith('\n') ? '\n' : '\n\n';
207
+ const separator = base.length === 0 ? '' : blockBreak;
207
208
  return `${base}${separator}${buildCodexMcpServerToml({ entry: entry })}`;
208
209
  }
209
210
  //# sourceMappingURL=home-config.js.map
@@ -5,6 +5,7 @@ import { errorMessage } from '../values.js';
5
5
  import { defaultReadMcpJsonFile, defaultWriteMcpJsonFile } from '../mcp-bridge.js';
6
6
  import { buildCodexHomeConfigToml } from './home-config.js';
7
7
  import { assertCodexHomeConfigParses, assertStagedHomeIsolated, CodexMcpIsolationError, codexIsolationRefusalMessage } from './isolation.js';
8
+ import { omitKeys } from '../../core/omit-keys.js';
8
9
  /**
9
10
  * Where `'codex-toml'` reads the operator's REAL Codex config from, to seed a run's scratch copy —
10
11
  * never where it writes. Resolved against the daemon HOST process's own environment (`hostEnv`,
@@ -27,8 +28,7 @@ export function resolveSourceCodexHomeDir({ hostEnv }) {
27
28
  * `mcp_skill_dependencies.rs`), and `codex exec` is `codex_exec` without it.
28
29
  */
29
30
  export function withStagedCodexHome({ env, codexHomeDir }, _options = {}) {
30
- const { CODEX_INTERNAL_ORIGINATOR_OVERRIDE: _originator, ...rest } = env;
31
- return { ...rest, CODEX_HOME: codexHomeDir };
31
+ return { ...omitKeys(env, ['CODEX_INTERNAL_ORIGINATOR_OVERRIDE']), CODEX_HOME: codexHomeDir };
32
32
  }
33
33
  function defaultMkdtempCodexHome({ prefix }) {
34
34
  return fsPromises.mkdtemp(join(tmpdir(), prefix));
@@ -15,7 +15,8 @@ export function codexIsolationRefusalMessage(reason) {
15
15
  * known, and Codex could not load it either.
16
16
  * @complexity O(n) in the file length.
17
17
  */
18
- function parseCodexLayer(raw, path) {
18
+ function parseCodexLayer({ raw, path }, _optional = {}) {
19
+ // Codex isolation is a default security boundary; its parser must ship with the daemon.
19
20
  try {
20
21
  return parse(raw);
21
22
  }
@@ -41,7 +42,7 @@ function assertTrustsNoProject(config, path) {
41
42
  */
42
43
  export function assertCodexHomeConfigParses({ raw, path }) {
43
44
  if (raw !== undefined)
44
- parseCodexLayer(raw, path);
45
+ parseCodexLayer({ raw, path });
45
46
  }
46
47
  /**
47
48
  * The staged home config, parsed: it must declare no MCP server but the bridge and trust no
@@ -54,7 +55,7 @@ export function assertStagedHomeIsolated({ homeConfig, path }) {
54
55
  const refusal = new CodexMcpIsolationError(`${path} declares MCP servers that can't be removed safely.`);
55
56
  let config;
56
57
  try {
57
- config = parse(homeConfig);
58
+ config = parseCodexLayer({ raw: homeConfig, path });
58
59
  }
59
60
  catch {
60
61
  throw refusal;
@@ -23,7 +23,7 @@ export type StreamHandler = {
23
23
  }): void;
24
24
  flush(): void;
25
25
  };
26
- export declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "copilot-stream-json", "qoder-stream-json", "acp-json-rpc", "pi-rpc", "plain"];
26
+ export declare const SUPPORTED_STREAM_FORMATS: readonly ["claude-stream-json", "json-event-stream", "qoder-stream-json", "acp-json-rpc", "pi-rpc", "plain"];
27
27
  /** The families of the registry's stream-format this driver implements — see index.ts module doc. */
28
28
  export type SupportedStreamFormat = (typeof SUPPORTED_STREAM_FORMATS)[number];
29
29
  export type JsonStreamFormat = Exclude<SupportedStreamFormat, 'acp-json-rpc' | 'pi-rpc' | 'plain'>;
@@ -1,7 +1,6 @@
1
1
  export const SUPPORTED_STREAM_FORMATS = [
2
2
  'claude-stream-json',
3
3
  'json-event-stream',
4
- 'copilot-stream-json',
5
4
  'qoder-stream-json',
6
5
  'acp-json-rpc',
7
6
  'pi-rpc',
@@ -35,9 +35,9 @@ export declare function translateTurnEndEvent({ rawEvent }: {
35
35
  * assertable in isolation.
36
36
  *
37
37
  * Defensive by construction: several real parser emissions carry fields
38
- * looser than `RunAgentPayload`'s types promise (e.g. copilot's
39
- * `tool.execution_start` emits `id: data.toolCallId ?? null` — a literal
40
- * `null`, not the `string` `RunAgentPayload['tool_use']['id']` demands).
38
+ * looser than `RunAgentPayload`'s types promise (e.g. an untrusted
39
+ * tool event can carry a literal `null`, not the `string`
40
+ * `RunAgentPayload['tool_use']['id']` demands).
41
41
  * Every field read here is defensively coerced (`asString`/
42
42
  * `asOptionalString`/`asOptionalNumber`) rather than trusted, so a
43
43
  * malformed or null field degrades to a safe default instead of
@@ -26,7 +26,7 @@ export function extractUsageTokens({ rawUsage }, _options = {}) {
26
26
  * fields beyond the basic counts — `thought_tokens`,
27
27
  * `cached_read_tokens`/`cached_write_tokens` (opencode/gemini/codex),
28
28
  * `modelUsage`/`stopReason`/`isError` (qoder), a top-level `stopReason`
29
- * (claude/copilot). Cache/reasoning counters and stop reasons now survive tracing; vendor-only
29
+ * (claude). Cache/reasoning counters and stop reasons now survive tracing; vendor-only
30
30
  * `modelUsage` and `isError` remain outside this neutral usage contract.
31
31
  * @param rawEvent - The raw `{type:'usage', ...}` record from a stream parser.
32
32
  * @returns The narrowed `RunAgentPayload` `usage` variant.
@@ -164,9 +164,9 @@ const EVENT_TYPE_TRANSLATORS = {
164
164
  * assertable in isolation.
165
165
  *
166
166
  * Defensive by construction: several real parser emissions carry fields
167
- * looser than `RunAgentPayload`'s types promise (e.g. copilot's
168
- * `tool.execution_start` emits `id: data.toolCallId ?? null` — a literal
169
- * `null`, not the `string` `RunAgentPayload['tool_use']['id']` demands).
167
+ * looser than `RunAgentPayload`'s types promise (e.g. an untrusted
168
+ * tool event can carry a literal `null`, not the `string`
169
+ * `RunAgentPayload['tool_use']['id']` demands).
170
170
  * Every field read here is defensively coerced (`asString`/
171
171
  * `asOptionalString`/`asOptionalNumber`) rather than trusted, so a
172
172
  * malformed or null field degrades to a safe default instead of
@@ -227,9 +227,9 @@ export function applyAgentTranslationSideEffects({ payload, sessionId, sink }) {
227
227
  sink.onSessionId({ sessionId });
228
228
  if (payload.type === 'tool_use') {
229
229
  sink.onToolCall({ toolUse: payload });
230
+ return;
230
231
  }
231
- else if ((payload.type === 'text_delta' || payload.type === 'thinking_delta') && payload.delta.length > 0) {
232
+ if ((payload.type === 'text_delta' || payload.type === 'thinking_delta') && payload.delta.length > 0)
232
233
  sink.onUserVisibleOutput();
233
- }
234
234
  }
235
235
  //# sourceMappingURL=event-translation.js.map
@@ -7,8 +7,8 @@ import { createModelReceiptTracker } from './model-receipts.js';
7
7
  import { observeRunOutput } from './run-output.js';
8
8
  import { messageContentWithImages } from '../attachment-content.js';
9
9
  import { redactSecrets, } from '@jini-ai/core';
10
- import { createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
11
- import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
10
+ import { createClaudeStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
11
+ import { listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
12
12
  import { classifyRunCloseStatus, } from '../close-status.js';
13
13
  import { resolveContinuationTransport, } from '../continuation/continuation-transport.js';
14
14
  import { resultContent, } from '../delegated-tool-bridge.js';
@@ -42,12 +42,15 @@ function createStreamHandlerForDef(def, streamFormat, onEvent) {
42
42
  switch (streamFormat) {
43
43
  case 'claude-stream-json':
44
44
  return createClaudeStreamHandler({ onEvent: onEvent }, { costBasis: def.usageCostBasis ?? 'run' });
45
- case 'copilot-stream-json':
46
- return createCopilotStreamHandler({ onEvent: onEvent });
47
45
  case 'qoder-stream-json':
48
46
  return createQoderStreamHandler({ onEvent: onEvent });
49
47
  case 'json-event-stream':
50
48
  return createJsonEventStreamHandler({ kind: def.eventParser ?? '', onEvent: onEvent });
49
+ default: {
50
+ // Unreachable: every stream format has a case above, and a new one fails typecheck here.
51
+ const unknownFormat = streamFormat;
52
+ throw new TypeError(`Unknown agent stream format: ${String(unknownFormat)}`);
53
+ }
51
54
  }
52
55
  }
53
56
  /**
@@ -463,9 +466,8 @@ export function wireChildLifecycle(ctx) {
463
466
  */
464
467
  function handleTurnEnd(stopReason) {
465
468
  const toolUse = pendingToolUse;
466
- const shouldInject = stopReason === 'tool_use' &&
467
- toolUse !== undefined &&
468
- continuation !== undefined &&
469
+ const toolTurnPending = stopReason === 'tool_use' && toolUse !== undefined && continuation !== undefined;
470
+ const shouldInject = toolTurnPending &&
469
471
  resolveContinuationTransport({ def: def }) === 'stdin-injection' &&
470
472
  continuation.autonomousToolNames.has(toolUse.name);
471
473
  if (!shouldInject) {
@@ -547,10 +549,10 @@ export function wireChildLifecycle(ctx) {
547
549
  return lifecycle.emit({ runId, input: { event: 'agent', data: statusWithChildIdentity({ payload, childPid: ctx.child.pid }, {}) } });
548
550
  });
549
551
  }
550
- else if (translation.kind === 'error') {
552
+ if (translation.kind === 'error') {
551
553
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
552
554
  }
553
- else if (translation.kind === 'turn-end') {
555
+ if (translation.kind === 'turn-end') {
554
556
  handleTurnEnd(translation.stopReason);
555
557
  }
556
558
  });
@@ -607,7 +609,8 @@ export function wireChildLifecycle(ctx) {
607
609
  }
608
610
  /**
609
611
  * Emits the accumulated `'until-close'` stdout — sanitized — as exactly one raw `'stdout'` echo
610
- * plus one `text_delta`, through the same `enqueueEmit` FIFO queue every other emit uses, so the
612
+ * plus one `text_delta` for plain output, or through the declared structured parser. Both use
613
+ * the same `enqueueEmit` FIFO queue every other emit uses, so the
611
614
  * flush is ordered after every already-queued event and before `finish()`'s `'end'`. A no-op for
612
615
  * every live-streaming def (nothing was ever accumulated) and for a buffered run that produced
613
616
  * no stdout at all — an empty `text_delta` is noise, not information.
@@ -622,10 +625,19 @@ export function wireChildLifecycle(ctx) {
622
625
  const safe = sanitizeBufferedStdout ? sanitizeBufferedStdout({ fullText: bufferedStdout }) : bufferedStdout;
623
626
  bufferedStdout = '';
624
627
  bufferedStdoutBytes = 0;
625
- const text = droppedStdoutBytes > 0
626
- ? `${safe}${bufferedStdoutTruncationNotice(droppedStdoutBytes, ctx.bufferedStdoutMaxBytes)}`
627
- : safe;
628
+ const dropped = droppedStdoutBytes;
628
629
  droppedStdoutBytes = 0;
630
+ if (streamHandler !== null) {
631
+ // Redact the complete wire text before parsing: structured errors can contain sign-in URLs too.
632
+ stdoutDiagnostics.feed({ text: safe });
633
+ streamHandler.feed({ chunk: safe });
634
+ if (dropped > 0)
635
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'error', data: {
636
+ message: bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes),
637
+ } } }));
638
+ return;
639
+ }
640
+ const text = dropped > 0 ? `${safe}${bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes)}` : safe;
629
641
  if (text.length === 0)
630
642
  return;
631
643
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
@@ -634,27 +646,27 @@ export function wireChildLifecycle(ctx) {
634
646
  child.stdout?.on('data', (chunk) => {
635
647
  const text = chunk.toString('utf8');
636
648
  stdoutDiagnostics.record({ chunk, text });
637
- if (streamFormat === 'plain') {
638
- if (text.length > 0)
639
- userVisibleOutputSeen = true;
640
- if (bufferStdoutUntilClose) {
641
- // Nothing is emitted on *either* channel yet — see this function's doc on why holding the
642
- // raw echo back matters as much as holding back the chat copy.
643
- //
644
- // Whole chunks only: a chunk that would cross the ceiling is dropped entirely rather than
645
- // sliced to fit, which keeps the accumulator free of half-written multi-byte characters (a
646
- // `data` event boundary already need not align with one) and makes the kept prefix exactly
647
- // the bytes some prefix of chunks produced. Everything after the first refusal is dropped
648
- // too — the point is a hard ceiling on resident bytes, not a best-effort tail.
649
- const chunkBytes = Buffer.byteLength(text, 'utf8');
650
- if (droppedStdoutBytes > 0 || bufferedStdoutBytes + chunkBytes > ctx.bufferedStdoutMaxBytes) {
651
- droppedStdoutBytes += chunkBytes;
652
- return;
653
- }
654
- bufferedStdout += text;
655
- bufferedStdoutBytes += chunkBytes;
649
+ if (streamFormat === 'plain' && text.length > 0)
650
+ userVisibleOutputSeen = true;
651
+ if (bufferStdoutUntilClose) {
652
+ // Nothing is emitted on *either* channel yet — see this function's doc on why holding the
653
+ // raw echo back matters as much as holding back the chat copy.
654
+ //
655
+ // Whole chunks only: a chunk that would cross the ceiling is dropped entirely rather than
656
+ // sliced to fit, which keeps the accumulator free of half-written multi-byte characters (a
657
+ // `data` event boundary already need not align with one) and makes the kept prefix exactly
658
+ // the bytes some prefix of chunks produced. Everything after the first refusal is dropped
659
+ // too — the point is a hard ceiling on resident bytes, not a best-effort tail.
660
+ const chunkBytes = Buffer.byteLength(text, 'utf8');
661
+ if (droppedStdoutBytes > 0 || bufferedStdoutBytes + chunkBytes > ctx.bufferedStdoutMaxBytes) {
662
+ droppedStdoutBytes += chunkBytes;
656
663
  return;
657
664
  }
665
+ bufferedStdout += text;
666
+ bufferedStdoutBytes += chunkBytes;
667
+ return;
668
+ }
669
+ if (streamFormat === 'plain') {
658
670
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
659
671
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: { type: 'text_delta', delta: text } } }));
660
672
  return;
@@ -702,12 +714,12 @@ export function wireChildLifecycle(ctx) {
702
714
  feedSessionLine(controlScanBuffer);
703
715
  controlScanBuffer = '';
704
716
  }
717
+ flushBufferedStdout();
705
718
  streamHandler?.flush();
706
719
  session.endSession();
707
720
  stdoutDiagnostics.flush();
708
- // Queued before `await emitQueue` so the flushed text is durably appended ahead of
709
- // `finish()`'s `'end'` event, exactly like every live-path emit already is.
710
- flushBufferedStdout();
721
+ // The buffered flush is queued before `await emitQueue` so its sanitized events are durable
722
+ // ahead of `finish()`'s `'end'`, exactly like every live-path emit already is.
711
723
  await emitQueue;
712
724
  if (cancelRequested)
713
725
  await cancelUnresolvedToolCalls();
@@ -843,7 +855,10 @@ function wireAcpLifecycle(ctx) {
843
855
  unsubscribeCancel();
844
856
  // Guarded for the same reasons as the child-driven handler above.
845
857
  await cleanupStagedFilesSafely(ctx);
846
- const status = await output.settle({ status: cancelRequested ? 'cancelled' : controller?.completedSuccessfully() ? 'succeeded' : 'failed' });
858
+ let finalStatus = 'cancelled';
859
+ if (!cancelRequested)
860
+ finalStatus = controller?.completedSuccessfully() ? 'succeeded' : 'failed';
861
+ const status = await output.settle({ status: finalStatus });
847
862
  const resumable = status === 'failed' && classifyFailure !== undefined
848
863
  ? await classifyFailureSafely(ctx, classifyFailure, {
849
864
  runId,
@@ -870,7 +885,7 @@ function wireAcpLifecycle(ctx) {
870
885
  } });
871
886
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
872
887
  }
873
- else if (translation.kind === 'error') {
888
+ if (translation.kind === 'error') {
874
889
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
875
890
  }
876
891
  return;
@@ -942,7 +957,10 @@ function wirePiRpcLifecycle(ctx) {
942
957
  unsubscribeCancel();
943
958
  // Guarded for the same reasons as the child-driven handler above.
944
959
  await cleanupStagedFilesSafely(ctx);
945
- const status = await output.settle({ status: cancelRequested ? 'cancelled' : session?.hasFatalError() ? 'failed' : 'succeeded' });
960
+ let finalStatus = 'cancelled';
961
+ if (!cancelRequested)
962
+ finalStatus = session?.hasFatalError() ? 'failed' : 'succeeded';
963
+ const status = await output.settle({ status: finalStatus });
946
964
  const resumable = status === 'failed' && classifyFailure !== undefined
947
965
  ? await classifyFailureSafely(ctx, classifyFailure, {
948
966
  runId,
@@ -961,18 +979,14 @@ function wirePiRpcLifecycle(ctx) {
961
979
  const receipt = modelReceipt(translation.payload);
962
980
  if (receipt)
963
981
  enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
964
- if (translation.sessionId !== undefined)
965
- capturedSessionId = translation.sessionId;
966
- if (translation.payload.type === 'tool_use') {
967
- toolCallSeen = true;
968
- }
969
- else if ((translation.payload.type === 'text_delta' || translation.payload.type === 'thinking_delta') &&
970
- translation.payload.delta.length > 0) {
971
- userVisibleOutputSeen = true;
972
- }
982
+ applyAgentTranslationSideEffects({ payload: translation.payload, sessionId: translation.sessionId, sink: {
983
+ onSessionId: ({ sessionId }) => { capturedSessionId = sessionId; },
984
+ onToolCall: () => { toolCallSeen = true; },
985
+ onUserVisibleOutput: () => { userVisibleOutputSeen = true; },
986
+ } });
973
987
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
974
988
  }
975
- else if (translation.kind === 'error') {
989
+ if (translation.kind === 'error') {
976
990
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
977
991
  }
978
992
  } }, { cwd: ctx.cwd, ...(ctx.model !== undefined ? { model: ctx.model } : {}), ...(ctx.imagePaths.length > 0 ? { imagePaths: [...ctx.imagePaths] } : {}), ...(ctx.uploadRoot !== undefined ? { uploadRoot: ctx.uploadRoot } : {}) });
@@ -1047,8 +1061,8 @@ export async function runAcpDispatch({ input, deps }) {
1047
1061
  // fired-and-forgotten (SEC-007: "await where lifecycle ordering allows it").
1048
1062
  await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'acp-attach-failure', deps.onCleanupFailure);
1049
1063
  await deps.releaseStagedResources();
1050
- // `failBeforeSpawn` always rejects (`Promise<never>`); returning it propagates that rejection.
1051
- return deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
1064
+ // `failBeforeSpawn` always rejects (`Promise<never>`); awaiting it propagates that rejection.
1065
+ await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
1052
1066
  }
1053
1067
  }
1054
1068
  /** Phase 15 (pi-rpc branch): same discipline as {@link runAcpDispatch}, for the one `'pi-rpc'` def. */
@@ -1080,7 +1094,7 @@ export async function runPiRpcDispatch({ input, deps }) {
1080
1094
  // fire-and-forget (SEC-007).
1081
1095
  await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'pi-rpc-attach-failure', deps.onCleanupFailure);
1082
1096
  await deps.releaseStagedResources();
1083
- return deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
1097
+ await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
1084
1098
  }
1085
1099
  }
1086
1100
  //# sourceMappingURL=events.js.map
@@ -11,12 +11,12 @@
11
11
  *
12
12
  * ## v1 scope: all 24 registered agent defs
13
13
  *
14
- * `@jini-ai/agent-runtime`'s registry ships 24 built-in defs across four
15
- * `streamFormat` families. The JSON-stream-parser family — the four
14
+ * `@jini-ai/agent-runtime`'s registry ships built-in defs across four
15
+ * `streamFormat` families. The JSON-stream-parser family — the three
16
16
  * `createXStreamHandler`-shaped parsers (`claude-stream-json`,
17
- * `json-event-stream`, `copilot-stream-json`, `qoder-stream-json`), covering
18
- * 9 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
19
- * copilot, qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
17
+ * `json-event-stream`, `qoder-stream-json`), covering
18
+ * 8 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
19
+ * qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
20
20
  * def (`pi`), are wired here. ACP and pi-rpc each own their own JSON-RPC
21
21
  * prompt-delivery protocol, so each takes its own lifecycle branch rather
22
22
  * than being treated as a stdout-tail parser; pi-rpc's events arrive through
@@ -11,12 +11,12 @@
11
11
  *
12
12
  * ## v1 scope: all 24 registered agent defs
13
13
  *
14
- * `@jini-ai/agent-runtime`'s registry ships 24 built-in defs across four
15
- * `streamFormat` families. The JSON-stream-parser family — the four
14
+ * `@jini-ai/agent-runtime`'s registry ships built-in defs across four
15
+ * `streamFormat` families. The JSON-stream-parser family — the three
16
16
  * `createXStreamHandler`-shaped parsers (`claude-stream-json`,
17
- * `json-event-stream`, `copilot-stream-json`, `qoder-stream-json`), covering
18
- * 9 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
19
- * copilot, qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
17
+ * `json-event-stream`, `qoder-stream-json`), covering
18
+ * 8 defs (amp, codebuddy, claude, codex, cursor-agent, opencode, mimo,
19
+ * qoder) — plus all 9 `acp-json-rpc` defs, plus the one `pi-rpc`
20
20
  * def (`pi`), are wired here. ACP and pi-rpc each own their own JSON-RPC
21
21
  * prompt-delivery protocol, so each takes its own lifecycle branch rather
22
22
  * than being treated as a stdout-tail parser; pi-rpc's events arrive through