@jini-ai/daemon 0.6.8 → 0.6.10

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 (79) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/dist/agent-executor/antigravity-permissions.d.ts +11 -0
  3. package/dist/agent-executor/antigravity-permissions.js +64 -0
  4. package/dist/agent-executor/contracts.d.ts +37 -6
  5. package/dist/agent-executor/event-translation.d.ts +6 -1
  6. package/dist/agent-executor/event-translation.js +3 -1
  7. package/dist/agent-executor/events.d.ts +5 -0
  8. package/dist/agent-executor/events.js +143 -87
  9. package/dist/agent-executor/launch.d.ts +5 -3
  10. package/dist/agent-executor/launch.js +315 -217
  11. package/dist/agent-executor/mcp-bridge-probe.js +51 -35
  12. package/dist/agent-executor/mcp-bridge.d.ts +18 -8
  13. package/dist/agent-executor/mcp-bridge.js +105 -65
  14. package/dist/agent-executor/run-output.d.ts +20 -0
  15. package/dist/agent-executor/run-output.js +80 -0
  16. package/dist/attachment-content.js +18 -15
  17. package/dist/core/read-only-tools.d.ts +15 -0
  18. package/dist/core/read-only-tools.js +29 -1
  19. package/dist/core/tool-audit.d.ts +3 -1
  20. package/dist/core/tool-audit.js +35 -22
  21. package/dist/core/tool-gaps.d.ts +60 -0
  22. package/dist/core/tool-gaps.js +199 -0
  23. package/dist/delegated-result-correlation.d.ts +26 -0
  24. package/dist/delegated-result-correlation.js +68 -0
  25. package/dist/delegated-tool-bridge.d.ts +6 -14
  26. package/dist/delegated-tool-bridge.js +155 -155
  27. package/dist/event-log.d.ts +45 -2
  28. package/dist/event-log.js +83 -4
  29. package/dist/http/attachments.d.ts +26 -1
  30. package/dist/http/attachments.js +129 -65
  31. package/dist/http/connectors.d.ts +1 -32
  32. package/dist/http/connectors.js +13 -68
  33. package/dist/http/delegated-tools.d.ts +8 -1
  34. package/dist/http/delegated-tools.js +30 -30
  35. package/dist/http/host-tools.d.ts +1 -0
  36. package/dist/http/host-tools.js +64 -59
  37. package/dist/http/index.d.ts +2 -2
  38. package/dist/http/index.js +1 -1
  39. package/dist/http/media.js +48 -69
  40. package/dist/http/memory.js +45 -36
  41. package/dist/http/model-proxy-fields.d.ts +23 -0
  42. package/dist/http/model-proxy-fields.js +29 -0
  43. package/dist/http/model-proxy.js +193 -395
  44. package/dist/http/research-tavily.adapter.js +72 -49
  45. package/dist/http/research.js +33 -23
  46. package/dist/http/resource-request.d.ts +37 -0
  47. package/dist/http/resource-request.js +59 -0
  48. package/dist/http/routines.js +49 -90
  49. package/dist/http/runs.d.ts +7 -5
  50. package/dist/http/runs.js +21 -7
  51. package/dist/http/terminals.js +23 -48
  52. package/dist/http/tool-catalog.d.ts +5 -0
  53. package/dist/http/xai.d.ts +11 -1
  54. package/dist/http/xai.js +125 -124
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +1 -0
  57. package/dist/legacy-data-migration.js +41 -24
  58. package/dist/run/core/retry.js +51 -35
  59. package/dist/run/diagnostics/diagnostics.js +5 -17
  60. package/dist/run-credentials.js +49 -27
  61. package/dist/run-lifecycle.d.ts +31 -2
  62. package/dist/run-lifecycle.js +49 -42
  63. package/dist/store/agent-sessions/sqlite.d.ts +6 -0
  64. package/dist/store/agent-sessions/sqlite.js +6 -4
  65. package/dist/surface-exchanges.js +2 -1
  66. package/dist/terminal-session.d.ts +1 -1
  67. package/dist/terminal-session.js +26 -33
  68. package/dist/tool-audit.d.ts +1 -0
  69. package/dist/tool-audit.js +1 -0
  70. package/dist/tool-executor.d.ts +24 -3
  71. package/dist/tool-executor.js +276 -139
  72. package/dist/tool-recovery/recovery.js +64 -35
  73. package/dist/tool-recovery/stack.d.ts +2 -1
  74. package/dist/tool-recovery/stack.js +1 -1
  75. package/dist/tool-result-diagnostics.d.ts +27 -0
  76. package/dist/tool-result-diagnostics.js +77 -0
  77. package/dist/tool-result-media.d.ts +27 -14
  78. package/dist/tool-result-media.js +51 -38
  79. package/package.json +14 -14
@@ -1,5 +1,8 @@
1
+ import { createToolResultDiagnosticProjection, projectDiagnosticFrame } from '../tool-result-diagnostics.js';
2
+ import { appendSerialTask } from "@jini-ai/core/primitives";
1
3
  import { randomUUID } from 'node:crypto';
2
4
  import { createModelReceiptTracker } from './model-receipts.js';
5
+ import { observeRunOutput } from './run-output.js';
3
6
  import { messageContentWithImages } from '../attachment-content.js';
4
7
  import { redactSecrets, } from '@jini-ai/core';
5
8
  import { createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
@@ -7,7 +10,7 @@ import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@j
7
10
  import { classifyRunCloseStatus, } from '../close-status.js';
8
11
  import { resolveContinuationTransport, } from '../continuation/continuation-transport.js';
9
12
  import { resultContent, } from '../delegated-tool-bridge.js';
10
- import { extractResultMedia, } from '../tool-result-media.js';
13
+ import { projectToolResultEvent, } from '../tool-result-media.js';
11
14
  import { translateAgentRuntimeEvent, translateAcpError, applyAgentTranslationSideEffects, } from './event-translation.js';
12
15
  import { errorMessage, isRecord, } from './values.js';
13
16
  import { MCP_BRIDGE_UNAVAILABLE, unavailableJiniBridgeStatus, bridgeUnavailableMessage, } from './mcp-bridge.js';
@@ -61,6 +64,11 @@ async function terminateChildTree(deps, child) {
61
64
  const pids = deps.collectProcessTreePids({ processes, rootPids: [child.pid] });
62
65
  await deps.stopProcesses({ pids });
63
66
  }
67
+ // A late emit can race a terminal run. Keep later events/cleanup ordered even after failure;
68
+ // lifecycle exceptions can contain provider text, so report only this fixed diagnostic.
69
+ function reportDroppedEmit() {
70
+ console.warn('[daemon] Queued lifecycle event could not be delivered.');
71
+ }
64
72
  /** Default sink when a host does not supply `onCleanupFailure`: still observable, never silent. Redacted per SEC-007 (a spawn/permission error can embed paths/host detail). */
65
73
  export function defaultCleanupFailureSink(context) {
66
74
  // eslint-disable-next-line no-console
@@ -195,6 +203,35 @@ export const DEFAULT_BUFFERED_STDOUT_MAX_BYTES = 8 * 1024 * 1024;
195
203
  function bufferedStdoutTruncationNotice(droppedBytes, maxBytes) {
196
204
  return `\n[jini] agent stdout truncated: ${droppedBytes} byte(s) dropped after the ${maxBytes}-byte buffer limit was reached.\n`;
197
205
  }
206
+ /** Keeps exact-byte auditing separate from retained diagnostics on the run's existing FIFO.
207
+ * Projection retains at most one bounded frame; ordinary stdout keeps its legacy byte journal. */
208
+ function createStdoutDiagnostics({ ctx, enqueueEmit }, _options = {}) {
209
+ const { runId, lifecycle, journal, stdoutJournalBlobs } = ctx;
210
+ const projection = ctx.delegatedResultCorrelation && ctx.streamFormat === 'claude-stream-json'
211
+ ? createToolResultDiagnosticProjection({}) : undefined;
212
+ function emit({ text }) {
213
+ if (text)
214
+ enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'stdout', data: { chunk: text } } }));
215
+ }
216
+ return {
217
+ record({ chunk, text }) {
218
+ if (!journal)
219
+ return;
220
+ if (projection && stdoutJournalBlobs) {
221
+ enqueueEmit(() => stdoutJournalBlobs.record({ runId, chunk }));
222
+ }
223
+ else {
224
+ enqueueEmit(() => journal.record({ runId, entry: receivedJournalEntry('stdout', text) }));
225
+ }
226
+ },
227
+ feed({ text }) {
228
+ emit({ text: projection ? projection.feed({ chunk: text }) : text });
229
+ },
230
+ flush() {
231
+ emit({ text: projection?.flush({}) ?? '' });
232
+ },
233
+ };
234
+ }
198
235
  /**
199
236
  * Wires one spawned child's full observable lifecycle: raw stdout/stderr
200
237
  * forwarding, structured stream-parser dispatch (translated via
@@ -257,6 +294,8 @@ function bufferedStdoutTruncationNotice(droppedBytes, maxBytes) {
257
294
  */
258
295
  export function wireChildLifecycle(ctx) {
259
296
  const { runId, def, streamFormat, child, lifecycle, journal, continuation, classifyFailure } = ctx;
297
+ const stdoutDiagnostics = createStdoutDiagnostics({ ctx, enqueueEmit });
298
+ const output = observeRunOutput({ lifecycle, runId, agentName: def.name }, { plainStdout: streamFormat === 'plain' });
260
299
  const modelReceipt = createModelReceiptTracker(ctx.startingModel, ctx.modelCatalog?.models);
261
300
  let stdinClosed = false;
262
301
  let cancelRequested = false;
@@ -324,16 +363,7 @@ export function wireChildLifecycle(ctx) {
324
363
  /** Bytes the ceiling refused, reported verbatim on flush so truncation is never silent. */
325
364
  let droppedStdoutBytes = 0;
326
365
  function enqueueEmit(task) {
327
- emitQueue = emitQueue.then(async () => {
328
- try {
329
- await task();
330
- }
331
- catch {
332
- // A single emit failing (e.g. a race against an already-terminal
333
- // run) must not block delivery of subsequently queued events —
334
- // see this function's own doc.
335
- }
336
- });
366
+ emitQueue = appendSerialTask({ previous: emitQueue, task, onError: reportDroppedEmit });
337
367
  }
338
368
  if (ctx.startingModel)
339
369
  enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.startingModel } } }));
@@ -398,8 +428,10 @@ export function wireChildLifecycle(ctx) {
398
428
  controlScanBuffer += text;
399
429
  if (!text.includes('\n'))
400
430
  return;
401
- const lines = controlScanBuffer.split('\n');
402
- controlScanBuffer = lines.pop() ?? '';
431
+ // `text` holds a newline, so the buffer does: split off the complete lines, keep the partial tail.
432
+ const lastNewline = controlScanBuffer.lastIndexOf('\n');
433
+ const lines = controlScanBuffer.slice(0, lastNewline).split('\n');
434
+ controlScanBuffer = controlScanBuffer.slice(lastNewline + 1);
403
435
  for (const line of lines) {
404
436
  // Both checked: either word can also appear inside the other kind of line's text.
405
437
  if (line.includes('"control_response"'))
@@ -477,11 +509,8 @@ export function wireChildLifecycle(ctx) {
477
509
  const run = { id: runId };
478
510
  let content;
479
511
  let isError;
480
- // Same extraction `delegated-tool-bridge.ts`'s `execute()` runs, kept consistent per
481
- // `resultContent`'s own doc ("both callers share one mapping"). This path never ran
482
- // `splitToolResultSurfaces` (it has no `mcp-ui` withhold-from-model concept — the flattened
483
- // `content` below already carries the whole raw output, a pre-existing, unrelated gap), so
484
- // there is no `remainder` to thread back in — only the extracted blocks are used here.
512
+ // Event projection removes pixels only from saved content; stdin keeps the original.
513
+ let eventContent;
485
514
  let media = [];
486
515
  // Suspend the slow-run watchdog for the duration of this daemon-awaited execution: the daemon
487
516
  // knows exactly why the run is quiet here (it dispatched the tool itself and is waiting on it),
@@ -493,11 +522,14 @@ export function wireChildLifecycle(ctx) {
493
522
  try {
494
523
  const result = await continuation.toolExecutor.execute({ principal: continuation.principal, run: run, toolId: toolUse.name, input: toolUse.input });
495
524
  content = resultContent({ result: result });
496
- isError = result.status !== 'completed';
497
- media = extractResultMedia({ output: result.output }).media;
525
+ const projection = projectToolResultEvent({ result });
526
+ eventContent = projection.content;
527
+ isError = projection.isError === true;
528
+ media = projection.media ?? [];
498
529
  }
499
530
  catch (error) {
500
531
  content = errorMessage(error);
532
+ eventContent = content;
501
533
  isError = true;
502
534
  }
503
535
  finally {
@@ -508,7 +540,7 @@ export function wireChildLifecycle(ctx) {
508
540
  data: {
509
541
  type: 'tool_result',
510
542
  toolUseId: toolUse.id,
511
- content,
543
+ content: eventContent,
512
544
  ...(isError ? { isError: true } : {}),
513
545
  ...(media.length > 0 ? { media } : {}),
514
546
  },
@@ -526,25 +558,22 @@ export function wireChildLifecycle(ctx) {
526
558
  const receipt = modelReceipt(translation.payload);
527
559
  if (receipt)
528
560
  enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
529
- if (translation.sessionId !== undefined)
530
- capturedSessionId = translation.sessionId;
531
- if (!bridgeChecked && translation.payload.type === 'status' && translation.payload.label === 'initializing') {
532
- bridgeChecked = true;
533
- const bridgeStatus = unavailableJiniBridgeStatus({ rawEvent: rawEvent });
534
- if (bridgeStatus !== undefined) {
535
- stopForUnavailableBridge(translation.payload, bridgeStatus);
536
- return;
537
- }
538
- }
539
- if (translation.payload.type === 'tool_use') {
540
- pendingToolUse = { id: translation.payload.id, name: translation.payload.name, input: translation.payload.input };
541
- toolCallSeen = true;
542
- }
543
- else if ((translation.payload.type === 'text_delta' || translation.payload.type === 'thinking_delta') &&
544
- translation.payload.delta.length > 0) {
545
- userVisibleOutputSeen = true;
546
- }
547
- enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: statusWithChildIdentity({ payload: translation.payload, childPid: ctx.child.pid }, {}) } }));
561
+ applyAgentTranslationSideEffects({ payload: translation.payload, sessionId: translation.sessionId, sink: {
562
+ onSessionId: ({ sessionId }) => { capturedSessionId = sessionId; },
563
+ onToolCall: ({ toolUse }) => {
564
+ // A tool signal always carries the tool_use variant (the sink hands it over narrowed);
565
+ // retain its full input for continuation.
566
+ pendingToolUse = { id: toolUse.id, name: toolUse.name, input: toolUse.input };
567
+ toolCallSeen = true;
568
+ },
569
+ onUserVisibleOutput: () => { userVisibleOutputSeen = true; },
570
+ } });
571
+ if (rejectUnavailableBridge({ payload: translation.payload, rawEvent }, {}))
572
+ return;
573
+ enqueueEmit(() => {
574
+ const payload = projectEcho({ payload: translation.payload, rawEvent }, {});
575
+ return lifecycle.emit({ runId, input: { event: 'agent', data: statusWithChildIdentity({ payload, childPid: ctx.child.pid }, {}) } });
576
+ });
548
577
  }
549
578
  else if (translation.kind === 'error') {
550
579
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
@@ -553,6 +582,28 @@ export function wireChildLifecycle(ctx) {
553
582
  handleTurnEnd(translation.stopReason);
554
583
  }
555
584
  });
585
+ /** Verify against an already-saved call in this run; unverified metadata preserves all output. */
586
+ function projectEcho({ payload, rawEvent }, _options = {}) {
587
+ if (!ctx.delegatedResultCorrelation)
588
+ return payload;
589
+ if (payload.type === 'raw')
590
+ return { ...payload, line: projectDiagnosticFrame({ line: payload.line }) };
591
+ if (payload.type !== 'tool_result' || !payload.delegatedToolUseId || !Array.isArray(rawEvent.delegatedResultContent))
592
+ return payload;
593
+ const matches = ctx.delegatedResultCorrelation.matches({ runId, toolUseId: payload.delegatedToolUseId, content: rawEvent.delegatedResultContent, isError: payload.isError === true });
594
+ return matches ? { ...payload, content: `Result shown by delegated call ${payload.delegatedToolUseId}.` } : payload;
595
+ }
596
+ function rejectUnavailableBridge({ payload, rawEvent }, _options = {}) {
597
+ if (!bridgeChecked && payload.type === 'status' && payload.label === 'initializing') {
598
+ bridgeChecked = true;
599
+ const bridgeStatus = unavailableJiniBridgeStatus({ rawEvent });
600
+ if (bridgeStatus !== undefined) {
601
+ stopForUnavailableBridge(payload, bridgeStatus);
602
+ return true;
603
+ }
604
+ }
605
+ return false;
606
+ }
556
607
  /**
557
608
  * Ends a run whose CLI started without the `jini` bridge: emits the init status and a
558
609
  * {@link MCP_BRIDGE_UNAVAILABLE} error, then stops the child's process tree before the model can
@@ -610,8 +661,7 @@ export function wireChildLifecycle(ctx) {
610
661
  }
611
662
  child.stdout?.on('data', (chunk) => {
612
663
  const text = chunk.toString('utf8');
613
- if (journal)
614
- enqueueEmit(() => journal.record({ runId: runId, entry: receivedJournalEntry('stdout', text) }));
664
+ stdoutDiagnostics.record({ chunk, text });
615
665
  if (streamFormat === 'plain') {
616
666
  if (text.length > 0)
617
667
  userVisibleOutputSeen = true;
@@ -637,7 +687,7 @@ export function wireChildLifecycle(ctx) {
637
687
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: { type: 'text_delta', delta: text } } }));
638
688
  return;
639
689
  }
640
- enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
690
+ stdoutDiagnostics.feed({ text });
641
691
  // Before the parser: an ack and the aborted turn's end can share a chunk, and the turn end must
642
692
  // see that no interrupt is still waiting (see `stdinHoldAfterTurnEnd`); a background task
643
693
  // starting and the turn's end can share one too.
@@ -680,6 +730,7 @@ export function wireChildLifecycle(ctx) {
680
730
  // driver dispatches to. `streamHandler` is null for `'plain'` (no
681
731
  // parser, hence nothing to flush) — `?.` skips it cleanly.
682
732
  streamHandler?.flush();
733
+ stdoutDiagnostics.flush();
683
734
  // Queued before `await emitQueue` so the flushed text is durably appended ahead of
684
735
  // `finish()`'s `'end'` event, exactly like every live-path emit already is.
685
736
  flushBufferedStdout();
@@ -693,20 +744,23 @@ export function wireChildLifecycle(ctx) {
693
744
  // Both of the next two steps are guarded: neither a failed cleanup nor a rejecting host
694
745
  // classifier may prevent the terminal transition below — see each helper's own doc.
695
746
  await cleanupStagedFilesSafely(ctx);
696
- const status = bridgeUnavailable ? 'failed' : classifyRunCloseStatus({ cancelRequested, code }, { signal });
697
- // A bridge failure is not resumable: resuming would start the same CLI with the same bridge.
698
- const resumable = status === 'failed' && !bridgeUnavailable && classifyFailure !== undefined
699
- ? await classifyFailureSafely(ctx, classifyFailure, {
700
- runId,
701
- agentId: def.id,
702
- code,
703
- signal: signal ?? null,
704
- sideEffects: { userVisibleOutputSeen, toolCallSeen },
705
- })
706
- : false;
747
+ const status = await output.settle({ status: bridgeUnavailable ? 'failed' : classifyRunCloseStatus({ cancelRequested, code }, { signal }) });
748
+ const resumable = await resolveChildResumable({ status, code, signal }, {});
707
749
  await lifecycle.finish({ runId, status, code, signal: signal ?? null, resumable }, { ...(capturedSessionId !== undefined ? { sessionRef: capturedSessionId } : {}) });
708
750
  })();
709
751
  });
752
+ async function resolveChildResumable({ status, code, signal }, _options = {}) {
753
+ // A bridge failure is not resumable: resuming would start the same CLI with the same bridge.
754
+ return status === 'failed' && !bridgeUnavailable && classifyFailure !== undefined
755
+ ? await classifyFailureSafely(ctx, classifyFailure, {
756
+ runId,
757
+ agentId: def.id,
758
+ code,
759
+ signal: signal ?? null,
760
+ sideEffects: { userVisibleOutputSeen, toolCallSeen },
761
+ })
762
+ : false;
763
+ }
710
764
  /**
711
765
  * A mid-run message: first an `interrupt` control request on the still-open stdin, so the agent
712
766
  * stops generating (or abandons the tool it is running) at once instead of finishing its whole
@@ -718,8 +772,10 @@ export function wireChildLifecycle(ctx) {
718
772
  * `turn_end` has closed stdin, or the process exits before the ack, there is no session left to
719
773
  * reach — `'not-running'`. Between turns, while stdin is held only for background tasks, there is
720
774
  * no generation to stop: the line is written at once and starts the next turn.
775
+ * Host-prepared images use the same content blocks as the initial stdin turn; no file tools
776
+ * or new directory grants are needed to see these pixels.
721
777
  */
722
- async function sendUserMessage(text) {
778
+ async function sendUserMessage(text, images = []) {
723
779
  if (!acceptsMidRunUserMessages({ def: def }))
724
780
  return 'unsupported';
725
781
  const stdin = child.stdin;
@@ -727,7 +783,7 @@ export function wireChildLifecycle(ctx) {
727
783
  return 'not-running';
728
784
  if (heldForBackgroundTasks) {
729
785
  heldForBackgroundTasks = false;
730
- writeUserMessage(stdin, text);
786
+ writeUserMessage(stdin, text, images);
731
787
  return 'delivered';
732
788
  }
733
789
  const requestId = `jini-interrupt-${randomUUID()}`;
@@ -753,12 +809,12 @@ export function wireChildLifecycle(ctx) {
753
809
  pendingInterruptAcks.delete(requestId);
754
810
  if (!processAlive || stdinClosed)
755
811
  return 'not-running';
756
- writeUserMessage(stdin, text);
812
+ writeUserMessage(stdin, text, images);
757
813
  return 'delivered';
758
814
  }
759
- /** Writes a mid-run message as the next user line, journals it, and emits its `user_message` run event. */
760
- function writeUserMessage(stdin, text) {
761
- stdin.write(`${JSON.stringify(streamJsonUserMessage({ content: [{ type: 'text', text: text }] }))}\n`, 'utf8');
815
+ /** Writes a complete mid-run turn using the idle image encoder; journals text without base64. */
816
+ function writeUserMessage(stdin, text, images) {
817
+ stdin.write(`${JSON.stringify(streamJsonUserMessage({ content: messageContentWithImages({ prompt: text, images }, {}) }))}\n`, 'utf8');
762
818
  if (journal)
763
819
  enqueueEmit(() => journal.record({ runId: runId, entry: sentJournalEntry(text) }));
764
820
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: { type: 'user_message', id: randomUUID(), text: text } } }));
@@ -815,6 +871,22 @@ function backgroundTaskListLength({ line }) {
815
871
  function streamJsonUserMessage({ content }) {
816
872
  return { type: 'user', message: { role: 'user', content: content } };
817
873
  }
874
+ /** Preserve ACP's distinction between absent fields and explicitly supplied empty values. */
875
+ function acpSessionOptions({ ctx }, _options = {}) {
876
+ return {
877
+ cwd: ctx.cwd,
878
+ ...(ctx.cliParity ? { cliParity: true, stageTimeoutMs: 0 } : {}),
879
+ ...(ctx.permissionMode !== undefined ? { permissionMode: ctx.permissionMode } : {}),
880
+ ...(ctx.model !== undefined ? { model: ctx.model } : {}),
881
+ ...(ctx.imagePaths.length > 0 ? { imagePaths: [...ctx.imagePaths] } : {}),
882
+ ...(ctx.envFormat !== undefined ? { envFormat: ctx.envFormat } : {}),
883
+ // Spread-when-present rather than always: passing `mcpServers: []` is not the same as passing
884
+ // nothing for every downstream ACP agent, and "no bridge configured" must stay byte-identical
885
+ // to before this field existed.
886
+ ...(ctx.mcpServers !== undefined && ctx.mcpServers.length > 0 ? { mcpServers: [...ctx.mcpServers] } : {}),
887
+ ...(ctx.onPermissionRequest !== undefined ? { onPermissionRequest: ctx.onPermissionRequest } : {}),
888
+ };
889
+ }
818
890
  /**
819
891
  * Wires an ACP child to a run. Unlike the JSON-stream path, ACP owns the
820
892
  * prompt protocol and reports its parsed events through `attachAcpSession`'s
@@ -827,6 +899,7 @@ function streamJsonUserMessage({ content }) {
827
899
  function wireAcpLifecycle(ctx) {
828
900
  const modelReceipt = createModelReceiptTracker(ctx.model, ctx.modelCatalog?.models);
829
901
  const { runId, agentId, child, lifecycle, journal, classifyFailure } = ctx;
902
+ const output = observeRunOutput({ lifecycle, runId, agentName: agentId });
830
903
  let cancelRequested = false;
831
904
  let emitQueue = Promise.resolve();
832
905
  // Gap 5 (session resume) — see wireChildLifecycle's identical local for the full rationale.
@@ -835,15 +908,7 @@ function wireAcpLifecycle(ctx) {
835
908
  let userVisibleOutputSeen = false;
836
909
  let toolCallSeen = false;
837
910
  function enqueueEmit(task) {
838
- emitQueue = emitQueue.then(async () => {
839
- try {
840
- await task();
841
- }
842
- catch {
843
- // A late event racing a terminal lifecycle is intentionally dropped;
844
- // it must not prevent subsequent queued cleanup from running.
845
- }
846
- });
911
+ emitQueue = appendSerialTask({ previous: emitQueue, task, onError: reportDroppedEmit });
847
912
  }
848
913
  if (ctx.model)
849
914
  enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.model } } }));
@@ -873,7 +938,7 @@ function wireAcpLifecycle(ctx) {
873
938
  unsubscribeCancel();
874
939
  // Guarded for the same reasons as the child-driven handler above.
875
940
  await cleanupStagedFilesSafely(ctx);
876
- const status = cancelRequested ? 'cancelled' : controller?.completedSuccessfully() ? 'succeeded' : 'failed';
941
+ const status = await output.settle({ status: cancelRequested ? 'cancelled' : controller?.completedSuccessfully() ? 'succeeded' : 'failed' });
877
942
  const resumable = status === 'failed' && classifyFailure !== undefined
878
943
  ? await classifyFailureSafely(ctx, classifyFailure, {
879
944
  runId,
@@ -908,11 +973,7 @@ function wireAcpLifecycle(ctx) {
908
973
  if (event === 'error') {
909
974
  enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translateAcpError(payload) } }));
910
975
  }
911
- } }, { cwd: ctx.cwd, ...(ctx.permissionMode !== undefined ? { permissionMode: ctx.permissionMode } : {}), ...(ctx.model !== undefined ? { model: ctx.model } : {}), ...(ctx.imagePaths.length > 0 ? { imagePaths: [...ctx.imagePaths] } : {}), ...(ctx.envFormat !== undefined ? { envFormat: ctx.envFormat } : {}),
912
- // Spread-when-present rather than always: passing `mcpServers: []` is not the same as passing
913
- // nothing for every downstream ACP agent, and "no bridge configured" must stay byte-identical
914
- // to before this field existed.
915
- ...(ctx.mcpServers !== undefined && ctx.mcpServers.length > 0 ? { mcpServers: [...ctx.mcpServers] } : {}), ...(ctx.onPermissionRequest !== undefined ? { onPermissionRequest: ctx.onPermissionRequest } : {}) });
976
+ } }, acpSessionOptions({ ctx }, {}));
916
977
  return controller;
917
978
  }
918
979
  /**
@@ -937,6 +998,7 @@ function wireAcpLifecycle(ctx) {
937
998
  function wirePiRpcLifecycle(ctx) {
938
999
  const modelReceipt = createModelReceiptTracker(ctx.model, ctx.modelCatalog?.models);
939
1000
  const { runId, agentId, child, lifecycle, journal, classifyFailure } = ctx;
1001
+ const output = observeRunOutput({ lifecycle, runId, agentName: agentId });
940
1002
  let cancelRequested = false;
941
1003
  let emitQueue = Promise.resolve();
942
1004
  // Gap 5 (session resume) — see wireChildLifecycle's identical local for the full rationale.
@@ -945,15 +1007,7 @@ function wirePiRpcLifecycle(ctx) {
945
1007
  let userVisibleOutputSeen = false;
946
1008
  let toolCallSeen = false;
947
1009
  function enqueueEmit(task) {
948
- emitQueue = emitQueue.then(async () => {
949
- try {
950
- await task();
951
- }
952
- catch {
953
- // A late event racing a terminal lifecycle is intentionally dropped;
954
- // it must not prevent subsequent queued cleanup from running.
955
- }
956
- });
1010
+ emitQueue = appendSerialTask({ previous: emitQueue, task, onError: reportDroppedEmit });
957
1011
  }
958
1012
  if (ctx.model)
959
1013
  enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: { type: 'status', label: 'starting_model', model: ctx.model } } }));
@@ -983,7 +1037,7 @@ function wirePiRpcLifecycle(ctx) {
983
1037
  unsubscribeCancel();
984
1038
  // Guarded for the same reasons as the child-driven handler above.
985
1039
  await cleanupStagedFilesSafely(ctx);
986
- const status = cancelRequested ? 'cancelled' : session?.hasFatalError() ? 'failed' : 'succeeded';
1040
+ const status = await output.settle({ status: cancelRequested ? 'cancelled' : session?.hasFatalError() ? 'failed' : 'succeeded' });
987
1041
  const resumable = status === 'failed' && classifyFailure !== undefined
988
1042
  ? await classifyFailureSafely(ctx, classifyFailure, {
989
1043
  runId,
@@ -1062,6 +1116,7 @@ export async function runAcpDispatch({ input, deps }) {
1062
1116
  prompt: input.prompt,
1063
1117
  cwd: input.cwd,
1064
1118
  permissionMode: input.permissionMode,
1119
+ cliParity: input.cliParity,
1065
1120
  model: input.model,
1066
1121
  ...(input.modelCatalog ? { modelCatalog: input.modelCatalog } : {}),
1067
1122
  imagePaths: input.imagePaths,
@@ -1086,7 +1141,8 @@ export async function runAcpDispatch({ input, deps }) {
1086
1141
  // fired-and-forgotten (SEC-007: "await where lifecycle ordering allows it").
1087
1142
  await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'acp-attach-failure', deps.onCleanupFailure);
1088
1143
  await deps.releaseStagedResources();
1089
- await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
1144
+ // `failBeforeSpawn` always rejects (`Promise<never>`); returning it propagates that rejection.
1145
+ return deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
1090
1146
  }
1091
1147
  }
1092
1148
  /** Phase 15 (pi-rpc branch): same discipline as {@link runAcpDispatch}, for the one `'pi-rpc'` def. */
@@ -1118,7 +1174,7 @@ export async function runPiRpcDispatch({ input, deps }) {
1118
1174
  // fire-and-forget (SEC-007).
1119
1175
  await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'pi-rpc-attach-failure', deps.onCleanupFailure);
1120
1176
  await deps.releaseStagedResources();
1121
- await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
1177
+ return deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
1122
1178
  }
1123
1179
  }
1124
1180
  //# sourceMappingURL=events.js.map
@@ -189,9 +189,9 @@ export declare function computeRuntimeContext({ preparedPromptFile, preparedLogF
189
189
  /** Phase 9a: the def's `buildArgs` 4th argument. Historically omitted when every override was absent;
190
190
  * that let defs silently enable auto-approval. Always supply the validated restricted default now. Pure. */
191
191
  export declare function buildAgentBuildArgsOptions({ input, systemPromptOverlay }: {
192
- readonly input: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode' | 'disallowedTools' | 'allowedTools' | 'settingSources' | 'settings'>;
192
+ readonly input: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode' | 'nativeToolAccess' | 'nativeCapabilities' | 'cliParity' | 'disallowedTools' | 'allowedTools' | 'settingSources' | 'settings' | 'chromeIntegration'>;
193
193
  readonly systemPromptOverlay: string | null | undefined;
194
- }): RuntimeBuildOptions | undefined;
194
+ }): RuntimeBuildOptions;
195
195
  /**
196
196
  * **The single dispatch point from a computed system-prompt overlay to its delivery mechanism** —
197
197
  * see `RuntimeAgentDef.systemPromptDelivery`'s own doc for the declared shape. Pure and
@@ -290,6 +290,7 @@ export declare function buildRunArgs({ input, deps }: {
290
290
  * a defensive extra.
291
291
  * @param input.def - Only used for its `id`, in the failure message, and its `systemPromptDelivery` declaration.
292
292
  * @param input.overlay - The computed `PromptAugmenter.systemOverlay()` result for this run.
293
+ * @param deps.mkdtemp - Creates the run-scoped directory from a prefix; default a fresh `mkdtemp` under `os.tmpdir()`.
293
294
  * @complexity O(1) plus one directory creation and one file write.
294
295
  * @overallScore 100/100
295
296
  */
@@ -302,6 +303,7 @@ export declare function prepareSystemPromptOverlayFileIfNeeded({ input, deps }:
302
303
  readonly deps: {
303
304
  readonly releaseStagedResources: () => Promise<void>;
304
305
  readonly failBeforeSpawn: FailBeforeSpawn;
306
+ readonly mkdtemp?: (prefix: string) => Promise<string>;
305
307
  };
306
308
  }): Promise<PreparedSystemPromptOverlayFile | null>;
307
309
  /** Phase 11: post-`buildArgs` guard for argv-bound defs whose resolved binary is a Windows shim/.exe — a no-op off-Windows and for non-argv-bound defs. */
@@ -379,6 +381,6 @@ export declare function confirmChildSpawned({ input, deps }: {
379
381
  * @complexity `run()`'s own setup is O(1); steady-state cost is the chosen stream parser's.
380
382
  * @overallScore 100/100
381
383
  */
382
- export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "runtimeState" | "codexDisablePlugins" | "piGracefulShutdownMs" | "hostEnv" | "getAgentDef" | "resolveAgentLaunch" | "resolveModelForLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "probeMcpBridge" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
384
+ export declare function createAgentExecutor(requiredArgs: Pick<CreateAgentExecutorOptions, "lifecycle">, optionalArgs?: Pick<CreateAgentExecutorOptions, "runtimeState" | "codexDisablePlugins" | "piGracefulShutdownMs" | "hostEnv" | "getAgentDef" | "resolveAgentLaunch" | "resolveModelForLaunch" | "ensureAgentCapabilities" | "applyAgentLaunchEnv" | "createCommandInvocation" | "spawn" | "probeMcpBridge" | "attachAcpSession" | "acpPermissionHandler" | "attachPiRpcSession" | "preparePromptFileForAgent" | "prepareAgentLogFile" | "listProcessSnapshots" | "collectProcessTreePids" | "stopProcesses" | "onCleanupFailure" | "journal" | "delegatedResultCorrelation" | "stdoutJournalBlobs" | "continuation" | "classifyFailure" | "mcpJsonInjection" | "claudeConfigDirIsolation" | "claudeConfigDirIsolationEnabled" | "bufferedStdoutMaxBytes" | "promptAugmenter">): AgentExecutor;
383
385
  export {};
384
386
  //# sourceMappingURL=launch.d.ts.map