@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.
- package/CHANGELOG.md +148 -0
- package/dist/agent-executor/antigravity-permissions.d.ts +11 -0
- package/dist/agent-executor/antigravity-permissions.js +64 -0
- package/dist/agent-executor/contracts.d.ts +37 -6
- package/dist/agent-executor/event-translation.d.ts +6 -1
- package/dist/agent-executor/event-translation.js +3 -1
- package/dist/agent-executor/events.d.ts +5 -0
- package/dist/agent-executor/events.js +143 -87
- package/dist/agent-executor/launch.d.ts +5 -3
- package/dist/agent-executor/launch.js +315 -217
- package/dist/agent-executor/mcp-bridge-probe.js +51 -35
- package/dist/agent-executor/mcp-bridge.d.ts +18 -8
- package/dist/agent-executor/mcp-bridge.js +105 -65
- package/dist/agent-executor/run-output.d.ts +20 -0
- package/dist/agent-executor/run-output.js +80 -0
- package/dist/attachment-content.js +18 -15
- package/dist/core/read-only-tools.d.ts +15 -0
- package/dist/core/read-only-tools.js +29 -1
- package/dist/core/tool-audit.d.ts +3 -1
- package/dist/core/tool-audit.js +35 -22
- package/dist/core/tool-gaps.d.ts +60 -0
- package/dist/core/tool-gaps.js +199 -0
- package/dist/delegated-result-correlation.d.ts +26 -0
- package/dist/delegated-result-correlation.js +68 -0
- package/dist/delegated-tool-bridge.d.ts +6 -14
- package/dist/delegated-tool-bridge.js +155 -155
- package/dist/event-log.d.ts +45 -2
- package/dist/event-log.js +83 -4
- package/dist/http/attachments.d.ts +26 -1
- package/dist/http/attachments.js +129 -65
- package/dist/http/connectors.d.ts +1 -32
- package/dist/http/connectors.js +13 -68
- package/dist/http/delegated-tools.d.ts +8 -1
- package/dist/http/delegated-tools.js +30 -30
- package/dist/http/host-tools.d.ts +1 -0
- package/dist/http/host-tools.js +64 -59
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/media.js +48 -69
- package/dist/http/memory.js +45 -36
- package/dist/http/model-proxy-fields.d.ts +23 -0
- package/dist/http/model-proxy-fields.js +29 -0
- package/dist/http/model-proxy.js +193 -395
- package/dist/http/research-tavily.adapter.js +72 -49
- package/dist/http/research.js +33 -23
- package/dist/http/resource-request.d.ts +37 -0
- package/dist/http/resource-request.js +59 -0
- package/dist/http/routines.js +49 -90
- package/dist/http/runs.d.ts +7 -5
- package/dist/http/runs.js +21 -7
- package/dist/http/terminals.js +23 -48
- package/dist/http/tool-catalog.d.ts +5 -0
- package/dist/http/xai.d.ts +11 -1
- package/dist/http/xai.js +125 -124
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/legacy-data-migration.js +41 -24
- package/dist/run/core/retry.js +51 -35
- package/dist/run/diagnostics/diagnostics.js +5 -17
- package/dist/run-credentials.js +49 -27
- package/dist/run-lifecycle.d.ts +31 -2
- package/dist/run-lifecycle.js +49 -42
- package/dist/store/agent-sessions/sqlite.d.ts +6 -0
- package/dist/store/agent-sessions/sqlite.js +6 -4
- package/dist/surface-exchanges.js +2 -1
- package/dist/terminal-session.d.ts +1 -1
- package/dist/terminal-session.js +26 -33
- package/dist/tool-audit.d.ts +1 -0
- package/dist/tool-audit.js +1 -0
- package/dist/tool-executor.d.ts +24 -3
- package/dist/tool-executor.js +276 -139
- package/dist/tool-recovery/recovery.js +64 -35
- package/dist/tool-recovery/stack.d.ts +2 -1
- package/dist/tool-recovery/stack.js +1 -1
- package/dist/tool-result-diagnostics.d.ts +27 -0
- package/dist/tool-result-diagnostics.js +77 -0
- package/dist/tool-result-media.d.ts +27 -14
- package/dist/tool-result-media.js +51 -38
- 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 {
|
|
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 =
|
|
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
|
-
|
|
402
|
-
|
|
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
|
-
//
|
|
481
|
-
|
|
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
|
-
|
|
497
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (translation.payload
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
760
|
-
function writeUserMessage(stdin, text) {
|
|
761
|
-
stdin.write(`${JSON.stringify(streamJsonUserMessage({ content:
|
|
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 =
|
|
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
|
-
} },
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|