@jini-ai/daemon 0.6.15-rc.1 → 0.6.15
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/dist/agent-executor/codex/home-config.js +3 -2
- package/dist/agent-executor/codex/home-staging.js +2 -2
- package/dist/agent-executor/codex/isolation.js +6 -4
- package/dist/agent-executor/event-translation.js +2 -2
- package/dist/agent-executor/events.js +68 -49
- package/dist/agent-executor/launch.js +39 -50
- package/dist/agent-executor/live-session-state.js +6 -3
- package/dist/agent-executor/mcp-bridge.js +13 -5
- package/dist/agent-executor/message-relay.js +5 -6
- package/dist/agent-executor/run-output.js +7 -2
- package/dist/close-status.js +6 -2
- package/dist/core/omit-keys.d.ts +7 -0
- package/dist/core/omit-keys.js +12 -0
- package/dist/core/read-only-tools.js +5 -1
- package/dist/core/tool-gaps.js +1 -1
- package/dist/delegated-result-correlation.js +2 -2
- package/dist/http/agents.d.ts +4 -0
- package/dist/http/attachments.js +13 -12
- package/dist/http/component-catalog.js +1 -1
- package/dist/http/connectors.js +7 -5
- package/dist/http/db-ops.js +5 -0
- package/dist/http/delegated-tools.js +5 -0
- package/dist/http/frontend-sessions.js +7 -5
- package/dist/http/host-tools.js +7 -5
- package/dist/http/memory.js +3 -1
- package/dist/http/message-relay.js +4 -1
- package/dist/http/model-proxy.js +13 -8
- package/dist/http/research-tavily.adapter.js +2 -1
- package/dist/http/routines.js +7 -5
- package/dist/http/terminals.js +5 -0
- package/dist/http/tool-catalog.js +1 -1
- package/dist/http/xai.js +10 -7
- package/dist/legacy-data-migration.d.ts +0 -12
- package/dist/legacy-data-migration.js +21 -13
- package/dist/remote-tool-executor.js +2 -1
- package/dist/routines/schedule.js +2 -1
- package/dist/routines/scheduler.js +6 -7
- package/dist/run-lifecycle.js +2 -1
- package/dist/store/agent-sessions/sql.js +7 -3
- package/dist/store/event-log/sqlite-kernel.js +5 -1
- package/dist/store/event-log/sqlite.js +6 -2
- package/dist/surface-exchanges.js +3 -3
- package/dist/terminal-session.js +4 -5
- package/dist/tool-executor.js +47 -3
- package/dist/tool-recovery/recovery.js +5 -3
- package/dist/tool-recovery/redaction.js +1 -2
- package/dist/tool-recovery/stack.js +5 -1
- package/dist/tool-result-media.js +7 -1
- package/dist/tool-result-surfaces.js +10 -9
- package/package.json +20 -15
|
@@ -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
|
|
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
|
-
|
|
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));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
2
|
import { JINI_MCP_SERVER_KEY } from '../mcp-bridge.js';
|
|
3
3
|
import { isRecord } from '../values.js';
|
|
4
4
|
/** A run with personal MCP off that Codex cannot keep to the bridge alone: refused, never run with a leak. */
|
|
@@ -15,7 +15,9 @@ 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
|
+
// The optional parser is needed only for Codex isolation, so memory-only hosts can still boot.
|
|
20
|
+
const { parse } = createRequire(import.meta.url)('smol-toml');
|
|
19
21
|
try {
|
|
20
22
|
return parse(raw);
|
|
21
23
|
}
|
|
@@ -41,7 +43,7 @@ function assertTrustsNoProject(config, path) {
|
|
|
41
43
|
*/
|
|
42
44
|
export function assertCodexHomeConfigParses({ raw, path }) {
|
|
43
45
|
if (raw !== undefined)
|
|
44
|
-
parseCodexLayer(raw, path);
|
|
46
|
+
parseCodexLayer({ raw, path });
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* The staged home config, parsed: it must declare no MCP server but the bridge and trust no
|
|
@@ -54,7 +56,7 @@ export function assertStagedHomeIsolated({ homeConfig, path }) {
|
|
|
54
56
|
const refusal = new CodexMcpIsolationError(`${path} declares MCP servers that can't be removed safely.`);
|
|
55
57
|
let config;
|
|
56
58
|
try {
|
|
57
|
-
config =
|
|
59
|
+
config = parseCodexLayer({ raw: homeConfig, path });
|
|
58
60
|
}
|
|
59
61
|
catch {
|
|
60
62
|
throw refusal;
|
|
@@ -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
|
-
|
|
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
|
|
@@ -8,7 +8,7 @@ import { observeRunOutput } from './run-output.js';
|
|
|
8
8
|
import { messageContentWithImages } from '../attachment-content.js';
|
|
9
9
|
import { redactSecrets, } from '@jini-ai/core';
|
|
10
10
|
import { createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, attachAcpSession, attachPiRpcSession, } from '@jini-ai/agent-runtime';
|
|
11
|
-
import {
|
|
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';
|
|
@@ -48,6 +48,11 @@ function createStreamHandlerForDef(def, streamFormat, onEvent) {
|
|
|
48
48
|
return createQoderStreamHandler({ onEvent: onEvent });
|
|
49
49
|
case 'json-event-stream':
|
|
50
50
|
return createJsonEventStreamHandler({ kind: def.eventParser ?? '', onEvent: onEvent });
|
|
51
|
+
default: {
|
|
52
|
+
// Unreachable: every stream format has a case above, and a new one fails typecheck here.
|
|
53
|
+
const unknownFormat = streamFormat;
|
|
54
|
+
throw new TypeError(`Unknown agent stream format: ${String(unknownFormat)}`);
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
@@ -463,9 +468,8 @@ export function wireChildLifecycle(ctx) {
|
|
|
463
468
|
*/
|
|
464
469
|
function handleTurnEnd(stopReason) {
|
|
465
470
|
const toolUse = pendingToolUse;
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
continuation !== undefined &&
|
|
471
|
+
const toolTurnPending = stopReason === 'tool_use' && toolUse !== undefined && continuation !== undefined;
|
|
472
|
+
const shouldInject = toolTurnPending &&
|
|
469
473
|
resolveContinuationTransport({ def: def }) === 'stdin-injection' &&
|
|
470
474
|
continuation.autonomousToolNames.has(toolUse.name);
|
|
471
475
|
if (!shouldInject) {
|
|
@@ -547,10 +551,10 @@ export function wireChildLifecycle(ctx) {
|
|
|
547
551
|
return lifecycle.emit({ runId, input: { event: 'agent', data: statusWithChildIdentity({ payload, childPid: ctx.child.pid }, {}) } });
|
|
548
552
|
});
|
|
549
553
|
}
|
|
550
|
-
|
|
554
|
+
if (translation.kind === 'error') {
|
|
551
555
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
552
556
|
}
|
|
553
|
-
|
|
557
|
+
if (translation.kind === 'turn-end') {
|
|
554
558
|
handleTurnEnd(translation.stopReason);
|
|
555
559
|
}
|
|
556
560
|
});
|
|
@@ -607,7 +611,8 @@ export function wireChildLifecycle(ctx) {
|
|
|
607
611
|
}
|
|
608
612
|
/**
|
|
609
613
|
* Emits the accumulated `'until-close'` stdout — sanitized — as exactly one raw `'stdout'` echo
|
|
610
|
-
* plus one `text_delta
|
|
614
|
+
* plus one `text_delta` for plain output, or through the declared structured parser. Both use
|
|
615
|
+
* the same `enqueueEmit` FIFO queue every other emit uses, so the
|
|
611
616
|
* flush is ordered after every already-queued event and before `finish()`'s `'end'`. A no-op for
|
|
612
617
|
* every live-streaming def (nothing was ever accumulated) and for a buffered run that produced
|
|
613
618
|
* no stdout at all — an empty `text_delta` is noise, not information.
|
|
@@ -622,10 +627,19 @@ export function wireChildLifecycle(ctx) {
|
|
|
622
627
|
const safe = sanitizeBufferedStdout ? sanitizeBufferedStdout({ fullText: bufferedStdout }) : bufferedStdout;
|
|
623
628
|
bufferedStdout = '';
|
|
624
629
|
bufferedStdoutBytes = 0;
|
|
625
|
-
const
|
|
626
|
-
? `${safe}${bufferedStdoutTruncationNotice(droppedStdoutBytes, ctx.bufferedStdoutMaxBytes)}`
|
|
627
|
-
: safe;
|
|
630
|
+
const dropped = droppedStdoutBytes;
|
|
628
631
|
droppedStdoutBytes = 0;
|
|
632
|
+
if (streamHandler !== null) {
|
|
633
|
+
// Redact the complete wire text before parsing: structured errors can contain sign-in URLs too.
|
|
634
|
+
stdoutDiagnostics.feed({ text: safe });
|
|
635
|
+
streamHandler.feed({ chunk: safe });
|
|
636
|
+
if (dropped > 0)
|
|
637
|
+
enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'error', data: {
|
|
638
|
+
message: bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes),
|
|
639
|
+
} } }));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const text = dropped > 0 ? `${safe}${bufferedStdoutTruncationNotice(dropped, ctx.bufferedStdoutMaxBytes)}` : safe;
|
|
629
643
|
if (text.length === 0)
|
|
630
644
|
return;
|
|
631
645
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
|
|
@@ -634,27 +648,27 @@ export function wireChildLifecycle(ctx) {
|
|
|
634
648
|
child.stdout?.on('data', (chunk) => {
|
|
635
649
|
const text = chunk.toString('utf8');
|
|
636
650
|
stdoutDiagnostics.record({ chunk, text });
|
|
637
|
-
if (streamFormat === 'plain')
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
droppedStdoutBytes += chunkBytes;
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
bufferedStdout += text;
|
|
655
|
-
bufferedStdoutBytes += chunkBytes;
|
|
651
|
+
if (streamFormat === 'plain' && text.length > 0)
|
|
652
|
+
userVisibleOutputSeen = true;
|
|
653
|
+
if (bufferStdoutUntilClose) {
|
|
654
|
+
// Nothing is emitted on *either* channel yet — see this function's doc on why holding the
|
|
655
|
+
// raw echo back matters as much as holding back the chat copy.
|
|
656
|
+
//
|
|
657
|
+
// Whole chunks only: a chunk that would cross the ceiling is dropped entirely rather than
|
|
658
|
+
// sliced to fit, which keeps the accumulator free of half-written multi-byte characters (a
|
|
659
|
+
// `data` event boundary already need not align with one) and makes the kept prefix exactly
|
|
660
|
+
// the bytes some prefix of chunks produced. Everything after the first refusal is dropped
|
|
661
|
+
// too — the point is a hard ceiling on resident bytes, not a best-effort tail.
|
|
662
|
+
const chunkBytes = Buffer.byteLength(text, 'utf8');
|
|
663
|
+
if (droppedStdoutBytes > 0 || bufferedStdoutBytes + chunkBytes > ctx.bufferedStdoutMaxBytes) {
|
|
664
|
+
droppedStdoutBytes += chunkBytes;
|
|
656
665
|
return;
|
|
657
666
|
}
|
|
667
|
+
bufferedStdout += text;
|
|
668
|
+
bufferedStdoutBytes += chunkBytes;
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (streamFormat === 'plain') {
|
|
658
672
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'stdout', data: { chunk: text } } }));
|
|
659
673
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: { type: 'text_delta', delta: text } } }));
|
|
660
674
|
return;
|
|
@@ -702,12 +716,12 @@ export function wireChildLifecycle(ctx) {
|
|
|
702
716
|
feedSessionLine(controlScanBuffer);
|
|
703
717
|
controlScanBuffer = '';
|
|
704
718
|
}
|
|
719
|
+
flushBufferedStdout();
|
|
705
720
|
streamHandler?.flush();
|
|
706
721
|
session.endSession();
|
|
707
722
|
stdoutDiagnostics.flush();
|
|
708
|
-
//
|
|
709
|
-
// `finish()`'s `'end'
|
|
710
|
-
flushBufferedStdout();
|
|
723
|
+
// The buffered flush is queued before `await emitQueue` so its sanitized events are durable
|
|
724
|
+
// ahead of `finish()`'s `'end'`, exactly like every live-path emit already is.
|
|
711
725
|
await emitQueue;
|
|
712
726
|
if (cancelRequested)
|
|
713
727
|
await cancelUnresolvedToolCalls();
|
|
@@ -741,7 +755,7 @@ export function wireChildLifecycle(ctx) {
|
|
|
741
755
|
* confirms the write; UUID-attributed CLI receipts separately confirm consumption. */
|
|
742
756
|
async function sendUserMessage({ text, id = randomUUID(), attachmentIds }, { imageContents: images = [] } = {}) {
|
|
743
757
|
if (!acceptsMidRunUserMessages({ def }))
|
|
744
|
-
return
|
|
758
|
+
return relayUserMessage({ id, text, images, attachmentIds });
|
|
745
759
|
const stdin = child.stdin;
|
|
746
760
|
if (stdinClosed || !stdin)
|
|
747
761
|
return 'not-running';
|
|
@@ -749,6 +763,9 @@ export function wireChildLifecycle(ctx) {
|
|
|
749
763
|
writeUserMessage(stdin, text, images, id);
|
|
750
764
|
return 'delivered';
|
|
751
765
|
}
|
|
766
|
+
function relayUserMessage({ id, text, images, attachmentIds }) {
|
|
767
|
+
return relay?.enqueue({ id, text, hasAttachments: images.length > 0 || (attachmentIds?.length ?? 0) > 0 }) ?? 'unsupported';
|
|
768
|
+
}
|
|
752
769
|
/** Writes a complete mid-run turn using the idle image encoder; journals text without base64.
|
|
753
770
|
* The same UUID identifies stdin, queue acceptance and subsequent consumption receipts. */
|
|
754
771
|
function writeUserMessage(stdin, text, images, id) {
|
|
@@ -840,7 +857,10 @@ function wireAcpLifecycle(ctx) {
|
|
|
840
857
|
unsubscribeCancel();
|
|
841
858
|
// Guarded for the same reasons as the child-driven handler above.
|
|
842
859
|
await cleanupStagedFilesSafely(ctx);
|
|
843
|
-
|
|
860
|
+
let finalStatus = 'cancelled';
|
|
861
|
+
if (!cancelRequested)
|
|
862
|
+
finalStatus = controller?.completedSuccessfully() ? 'succeeded' : 'failed';
|
|
863
|
+
const status = await output.settle({ status: finalStatus });
|
|
844
864
|
const resumable = status === 'failed' && classifyFailure !== undefined
|
|
845
865
|
? await classifyFailureSafely(ctx, classifyFailure, {
|
|
846
866
|
runId,
|
|
@@ -867,7 +887,7 @@ function wireAcpLifecycle(ctx) {
|
|
|
867
887
|
} });
|
|
868
888
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
|
|
869
889
|
}
|
|
870
|
-
|
|
890
|
+
if (translation.kind === 'error') {
|
|
871
891
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
872
892
|
}
|
|
873
893
|
return;
|
|
@@ -939,7 +959,10 @@ function wirePiRpcLifecycle(ctx) {
|
|
|
939
959
|
unsubscribeCancel();
|
|
940
960
|
// Guarded for the same reasons as the child-driven handler above.
|
|
941
961
|
await cleanupStagedFilesSafely(ctx);
|
|
942
|
-
|
|
962
|
+
let finalStatus = 'cancelled';
|
|
963
|
+
if (!cancelRequested)
|
|
964
|
+
finalStatus = session?.hasFatalError() ? 'failed' : 'succeeded';
|
|
965
|
+
const status = await output.settle({ status: finalStatus });
|
|
943
966
|
const resumable = status === 'failed' && classifyFailure !== undefined
|
|
944
967
|
? await classifyFailureSafely(ctx, classifyFailure, {
|
|
945
968
|
runId,
|
|
@@ -958,18 +981,14 @@ function wirePiRpcLifecycle(ctx) {
|
|
|
958
981
|
const receipt = modelReceipt(translation.payload);
|
|
959
982
|
if (receipt)
|
|
960
983
|
enqueueEmit(() => lifecycle.emit({ runId, input: { event: 'agent', data: receipt } }));
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
else if ((translation.payload.type === 'text_delta' || translation.payload.type === 'thinking_delta') &&
|
|
967
|
-
translation.payload.delta.length > 0) {
|
|
968
|
-
userVisibleOutputSeen = true;
|
|
969
|
-
}
|
|
984
|
+
applyAgentTranslationSideEffects({ payload: translation.payload, sessionId: translation.sessionId, sink: {
|
|
985
|
+
onSessionId: ({ sessionId }) => { capturedSessionId = sessionId; },
|
|
986
|
+
onToolCall: () => { toolCallSeen = true; },
|
|
987
|
+
onUserVisibleOutput: () => { userVisibleOutputSeen = true; },
|
|
988
|
+
} });
|
|
970
989
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'agent', data: translation.payload } }));
|
|
971
990
|
}
|
|
972
|
-
|
|
991
|
+
if (translation.kind === 'error') {
|
|
973
992
|
enqueueEmit(() => lifecycle.emit({ runId: runId, input: { event: 'error', data: translation.payload } }));
|
|
974
993
|
}
|
|
975
994
|
} }, { cwd: ctx.cwd, ...(ctx.model !== undefined ? { model: ctx.model } : {}), ...(ctx.imagePaths.length > 0 ? { imagePaths: [...ctx.imagePaths] } : {}), ...(ctx.uploadRoot !== undefined ? { uploadRoot: ctx.uploadRoot } : {}) });
|
|
@@ -1044,8 +1063,8 @@ export async function runAcpDispatch({ input, deps }) {
|
|
|
1044
1063
|
// fired-and-forgotten (SEC-007: "await where lifecycle ordering allows it").
|
|
1045
1064
|
await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'acp-attach-failure', deps.onCleanupFailure);
|
|
1046
1065
|
await deps.releaseStagedResources();
|
|
1047
|
-
// `failBeforeSpawn` always rejects (`Promise<never>`);
|
|
1048
|
-
|
|
1066
|
+
// `failBeforeSpawn` always rejects (`Promise<never>`); awaiting it propagates that rejection.
|
|
1067
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach ACP session for agent "${input.agentId}": ${errorMessage(err)}` });
|
|
1049
1068
|
}
|
|
1050
1069
|
}
|
|
1051
1070
|
/** Phase 15 (pi-rpc branch): same discipline as {@link runAcpDispatch}, for the one `'pi-rpc'` def. */
|
|
@@ -1077,7 +1096,7 @@ export async function runPiRpcDispatch({ input, deps }) {
|
|
|
1077
1096
|
// fire-and-forget (SEC-007).
|
|
1078
1097
|
await terminateChildTreeBestEffort({ listProcessSnapshots: deps.listProcessSnapshots, collectProcessTreePids: deps.collectProcessTreePids, stopProcesses: deps.stopProcesses }, input.child, input.runId, 'pi-rpc-attach-failure', deps.onCleanupFailure);
|
|
1079
1098
|
await deps.releaseStagedResources();
|
|
1080
|
-
|
|
1099
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: could not attach pi-rpc session for agent "${input.agentId}": ${errorMessage(err)}` });
|
|
1081
1100
|
}
|
|
1082
1101
|
}
|
|
1083
1102
|
//# sourceMappingURL=events.js.map
|
|
@@ -6,7 +6,7 @@ import { spawn as nodeSpawn, } from 'node:child_process';
|
|
|
6
6
|
import { promises as fsPromises, } from 'node:fs';
|
|
7
7
|
import { tmpdir, } from 'node:os';
|
|
8
8
|
import { join, } from 'node:path';
|
|
9
|
-
import { agentCapabilities, createClaudeFilesystemSupportProbe, claudeFilesystemRuntimePaths, claudeFilesystemSettings, validateFilesystemLaunch, validateReadOnlyExtraDirs, bindRuntimeAgentDef, defaultRuntimeState, getRuntimeModelDiscoveryDeps, userToolchainBinDirs, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
9
|
+
import { agentCapabilities, assessNativeToolPolicy, resolveNativeToolPolicy, createClaudeFilesystemSupportProbe, claudeFilesystemRuntimePaths, claudeFilesystemSettings, validateFilesystemLaunch, validateReadOnlyExtraDirs, bindRuntimeAgentDef, defaultRuntimeState, getRuntimeModelDiscoveryDeps, userToolchainBinDirs, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
10
10
|
import { createCommandInvocation } from '@jini-ai/platform/command';
|
|
11
11
|
import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
|
|
12
12
|
import { applyImagePromptDelivery, } from '../image-prompt-delivery.js';
|
|
@@ -580,8 +580,8 @@ export async function guardWindowsCommandLineBudget({ input, deps }) {
|
|
|
580
580
|
checkWindowsDirectExeCommandLineBudget({ def: input.def, resolvedBin: input.launchPath, args: input.args });
|
|
581
581
|
if (windowsBudgetError) {
|
|
582
582
|
await deps.releaseStagedResources();
|
|
583
|
-
// `failBeforeSpawn` always rejects (`Promise<never>`);
|
|
584
|
-
|
|
583
|
+
// `failBeforeSpawn` always rejects (`Promise<never>`); awaiting it propagates that rejection.
|
|
584
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_PROMPT_TOO_LARGE', message: windowsBudgetError.message });
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
/**
|
|
@@ -631,29 +631,13 @@ export async function confirmChildSpawned({ input, deps }) {
|
|
|
631
631
|
}
|
|
632
632
|
catch (err) {
|
|
633
633
|
await deps.releaseStagedResources();
|
|
634
|
-
|
|
634
|
+
await deps.failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: failed to spawn agent "${input.def.id}": ${errorMessage(err)}` });
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
637
|
/** Only include declared capability restrictions; absence has a distinct downstream contract. */
|
|
638
638
|
function nativeCapabilityInput({ input }, _options = {}) {
|
|
639
639
|
return { ...(input.nativeCapabilities !== undefined ? { nativeCapabilities: input.nativeCapabilities } : {}) };
|
|
640
640
|
}
|
|
641
|
-
/** Switch names a person sees for each native capability (the host's settings use the same words). */
|
|
642
|
-
const NATIVE_CAPABILITY_LABELS = {
|
|
643
|
-
shell: 'Bash/shell', fileWrite: 'File edit/write', userMcp: 'Your MCP servers', subagents: 'Subagents/Task',
|
|
644
|
-
userSettings: 'Your skills and settings', web: 'Web fetch/search', workflows: 'Workflows', loginEnvironment: 'Your shell environment',
|
|
645
|
-
};
|
|
646
|
-
/** The refusal a person reads when a CLI without a native-tool policy cannot honor this run's switches:
|
|
647
|
-
* it names the CLI and the switch to turn on. Owner 2026-10-27: the old "no native-tool policy and
|
|
648
|
-
* audit adapter" told a Gemini user nothing. Pure, O(c) for c OFF switches.
|
|
649
|
-
* @example nativeToolPolicyRefusal({ cliName: 'Antigravity', offSwitches: ['userMcp'] }, {});
|
|
650
|
-
*/
|
|
651
|
-
function nativeToolPolicyRefusal({ cliName, offSwitches }, _options = {}) {
|
|
652
|
-
const switches = offSwitches.map(key => `'${NATIVE_CAPABILITY_LABELS[key] ?? key}'`);
|
|
653
|
-
const blocked = switches.length > 0 ? `with ${new Intl.ListFormat('en', { type: 'conjunction' }).format(switches)} off` : `with this chat's CLI tool limits`;
|
|
654
|
-
const action = switches.length === 0 ? `Turn on 'Full CLI access'` : `Turn ${switches.length === 1 ? 'it' : 'them'} on`;
|
|
655
|
-
return `${cliName} can't run ${blocked}. ${action} in the assistant settings, or choose Claude or Codex.`;
|
|
656
|
-
}
|
|
657
641
|
/** Correlation projects retained stdout, so exact-byte journals need a separate blob writer. */
|
|
658
642
|
function validateStdoutJournalOptions(options, _options = {}) {
|
|
659
643
|
if (options.delegatedResultCorrelation && options.journal && !options.stdoutJournalBlobs) {
|
|
@@ -709,13 +693,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
709
693
|
const { createCommandInvocation: createCommandInvocationFn, spawn: spawnFn, listProcessSnapshots: listProcessSnapshotsFn, collectProcessTreePids: collectProcessTreePidsFn, stopProcesses: stopProcessesFn, } = resolveProcessDeps(options);
|
|
710
694
|
const { onCleanupFailure: onCleanupFailureFn, bufferedStdoutMaxBytes } = resolveMiscExecutorDeps(options);
|
|
711
695
|
validateStdoutJournalOptions(options);
|
|
712
|
-
const journal = options
|
|
713
|
-
const continuation = options.continuation;
|
|
714
|
-
const classifyFailure = options.classifyFailure;
|
|
715
|
-
const mcpJsonInjection = options.mcpJsonInjection;
|
|
716
|
-
const claudeConfigDirIsolation = options.claudeConfigDirIsolation;
|
|
696
|
+
const { journal, continuation, classifyFailure, mcpJsonInjection, claudeConfigDirIsolation, promptAugmenter } = options;
|
|
717
697
|
const claudeConfigDirIsolationEnabled = options.claudeConfigDirIsolationEnabled ?? false;
|
|
718
|
-
const promptAugmenter = options.promptAugmenter;
|
|
719
698
|
const filesystemTemps = new Map();
|
|
720
699
|
const verifyClaudeFilesystem = options.assertClaudeFilesystemSupport ?? createClaudeFilesystemSupportProbe({});
|
|
721
700
|
/** Remove this run's private temp directories on startup failure, exit, crash or cancellation.
|
|
@@ -765,7 +744,9 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
765
744
|
/** `AgentExecutor.sendUserMessage()` — see that interface method's own doc. @complexity O(1). */
|
|
766
745
|
async function sendUserMessage({ runId, text, id = randomUUID(), attachmentIds }, { imageContents } = {}) {
|
|
767
746
|
const deliver = liveRunInputs.get(runId);
|
|
768
|
-
|
|
747
|
+
if (!deliver)
|
|
748
|
+
return 'not-running';
|
|
749
|
+
return deliver({ text, id, ...(attachmentIds ? { attachmentIds } : {}) }, imageContents ? { imageContents } : {});
|
|
769
750
|
}
|
|
770
751
|
/** Validates launch input and selects the definition synchronously before yielding. */
|
|
771
752
|
async function prepareRunContext({ requiredArgs, optionalArgs }, _options = {}) {
|
|
@@ -778,14 +759,13 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
778
759
|
return { input, ...resolved };
|
|
779
760
|
}
|
|
780
761
|
async function enforceNativeToolPolicy({ input, def }, _options = {}) {
|
|
762
|
+
Object.assign(input, resolveNativeToolPolicy({ def, policy: input }));
|
|
781
763
|
// Never silently ignore a host's locked native-tool policy on another CLI.
|
|
782
764
|
// Parity cannot override an explicit lock or a restricted run.
|
|
783
765
|
input.cliParity = input.cliParity === true && input.permissionMode === 'bypass' && input.nativeToolAccess !== false;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (input.nativeToolAccess !== undefined && !input.cliParity || offSwitches.length > 0) {
|
|
788
|
-
return failBeforeSpawn({ runId: input.runId, code: 'NATIVE_TOOL_POLICY_UNSUPPORTED', message: nativeToolPolicyRefusal({ cliName: def.name, offSwitches }, {}) });
|
|
766
|
+
const refusal = assessNativeToolPolicy({ def, policy: input });
|
|
767
|
+
if (refusal !== null) {
|
|
768
|
+
await failBeforeSpawn({ runId: input.runId, code: 'NATIVE_TOOL_POLICY_UNSUPPORTED', message: refusal });
|
|
789
769
|
}
|
|
790
770
|
}
|
|
791
771
|
/** Validate every directory after image augmentation, before model probes or resource staging.
|
|
@@ -804,7 +784,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
804
784
|
input.cwd = input.filesystemRoot;
|
|
805
785
|
}
|
|
806
786
|
catch {
|
|
807
|
-
|
|
787
|
+
await failBeforeSpawn({ runId: input.runId, code: 'FILESYSTEM_POLICY_INVALID', message: 'Folder-limited launch refused: invalid filesystem root, working directory or attachment path.' });
|
|
808
788
|
}
|
|
809
789
|
}
|
|
810
790
|
/** Apply supported confinement without removing providers; unknown versions keep the settings.
|
|
@@ -829,12 +809,14 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
829
809
|
spawnEnv.TMP = input.filesystemTempDir;
|
|
830
810
|
spawnEnv.TEMP = input.filesystemTempDir;
|
|
831
811
|
if (def.id === 'claude' && process.platform !== 'win32') {
|
|
832
|
-
//
|
|
833
|
-
// plus /claude-<uid>
|
|
834
|
-
|
|
812
|
+
// Read denies also block create/unlink, so Claude's cwd files cannot live under /tmp.
|
|
813
|
+
// /var/tmp exists on macOS/Linux; its realpath plus /claude-<uid> must fit 44 bytes
|
|
814
|
+
// or the policy refuses launch instead of letting Bash fall back to shared scratch.
|
|
815
|
+
// mkdtemp creates a private 0700 directory; register cleanup before resolving aliases.
|
|
816
|
+
const runtimeTemp = await fsPromises.mkdtemp('/var/tmp/jini-');
|
|
835
817
|
filesystemTemps.get(input.runId).push(runtimeTemp);
|
|
836
|
-
input.filesystemRuntimeTempBase = runtimeTemp;
|
|
837
|
-
spawnEnv.CLAUDE_CODE_TMPDIR =
|
|
818
|
+
input.filesystemRuntimeTempBase = await fsPromises.realpath(runtimeTemp);
|
|
819
|
+
spawnEnv.CLAUDE_CODE_TMPDIR = input.filesystemRuntimeTempBase;
|
|
838
820
|
}
|
|
839
821
|
if (supported === false) {
|
|
840
822
|
input.filesystemNotice = 'files and shell are not folder-limited for this agent';
|
|
@@ -854,7 +836,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
854
836
|
spawnEnv.CLAUDE_CODE_DISABLE_AUTO_MEMORY = '1';
|
|
855
837
|
}
|
|
856
838
|
catch {
|
|
857
|
-
|
|
839
|
+
await failBeforeSpawn({ runId: input.runId, code: 'FILESYSTEM_POLICY_UNSUPPORTED', message: 'Folder-limited launch refused: invalid host settings or private runtime directory.' });
|
|
858
840
|
}
|
|
859
841
|
}
|
|
860
842
|
function resolveRunOverlay({ input, def, imageDelivery }, _options = {}) {
|
|
@@ -936,8 +918,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
936
918
|
const stdinHandle = isStdinDrivenFormat(formatForStdin)
|
|
937
919
|
? wireChildLifecycle({
|
|
938
920
|
...(canRegisterRunMessageRelay({ def, mcpBridge }) ? { registerMessageRelay: (relay) => liveRunRelays.set(input.runId, relay) } : {}),
|
|
939
|
-
...(
|
|
940
|
-
...(modelCatalog ? { modelCatalog } : {}),
|
|
921
|
+
...childLifecycleOptions({ input, modelCatalog }),
|
|
941
922
|
runId: input.runId,
|
|
942
923
|
def,
|
|
943
924
|
streamFormat: formatForStdin.streamFormat,
|
|
@@ -949,8 +930,6 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
949
930
|
onCleanupFailure: onCleanupFailureFn,
|
|
950
931
|
cleanupStagedFiles,
|
|
951
932
|
journal,
|
|
952
|
-
...(options.delegatedResultCorrelation ? { delegatedResultCorrelation: options.delegatedResultCorrelation } : {}),
|
|
953
|
-
...(options.stdoutJournalBlobs ? { stdoutJournalBlobs: options.stdoutJournalBlobs } : {}),
|
|
954
933
|
continuation,
|
|
955
934
|
classifyFailure,
|
|
956
935
|
bufferedStdoutMaxBytes,
|
|
@@ -968,6 +947,14 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
968
947
|
child.once('close', () => { liveRunInputs.delete(runId); liveRunRelays.delete(runId); });
|
|
969
948
|
return stdinHandle;
|
|
970
949
|
}
|
|
950
|
+
function childLifecycleOptions({ input, modelCatalog }) {
|
|
951
|
+
return {
|
|
952
|
+
...(input.model ? { startingModel: input.model } : {}),
|
|
953
|
+
...(modelCatalog ? { modelCatalog } : {}),
|
|
954
|
+
...(options.delegatedResultCorrelation ? { delegatedResultCorrelation: options.delegatedResultCorrelation } : {}),
|
|
955
|
+
...(options.stdoutJournalBlobs ? { stdoutJournalBlobs: options.stdoutJournalBlobs } : {}),
|
|
956
|
+
};
|
|
957
|
+
}
|
|
971
958
|
async function dispatchAcpPrompt({ input, def, child, modelCatalog, cleanupStagedFiles, releaseStagedResources, mcpBridge, overlayDelivery }, _options = {}) {
|
|
972
959
|
await runAcpDispatch({ input: {
|
|
973
960
|
runId: input.runId,
|
|
@@ -1050,10 +1037,9 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
1050
1037
|
}
|
|
1051
1038
|
/**
|
|
1052
1039
|
* `AgentExecutor.run()` — see that interface method's own doc for the
|
|
1053
|
-
* public contract. Implementation note on shape: every
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
* so each failure path reads as a single, obviously-terminal statement.
|
|
1040
|
+
* public contract. Implementation note on shape: every failure path below
|
|
1041
|
+
* awaits `failBeforeSpawn(...)` (a `Promise<never>`, so the await always
|
|
1042
|
+
* throws) and then returns, so each one reads as obviously terminal.
|
|
1057
1043
|
* @param input - `{runId, agentId, prompt, cwd, model?, reasoning?, imagePaths?, extraAllowedDirs?, uploadRoot?, env?}` — `runId` must already be `lifecycle.start()`-ed.
|
|
1058
1044
|
* @throws {@link AgentExecutorError} — see index.ts module doc's Invariant; never a bare `Error`.
|
|
1059
1045
|
* @complexity O(1) setup (registry lookup, launch resolution, one spawn call); steady-state cost thereafter belongs to {@link wireChildLifecycle}.
|
|
@@ -1250,7 +1236,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
1250
1236
|
const spawnResult = spawnAgentChildProcess({ input: { cwd: input.cwd, childEnv, invocation }, deps: { spawn: spawnFn } });
|
|
1251
1237
|
if (spawnResult.kind === 'error') {
|
|
1252
1238
|
await releaseStagedResources();
|
|
1253
|
-
|
|
1239
|
+
await failBeforeSpawn({ runId: input.runId, code: 'AGENT_SPAWN_FAILED', message: `AgentExecutor: spawn threw synchronously for agent "${def.id}": ${errorMessage(spawnResult.error)}` });
|
|
1240
|
+
return;
|
|
1254
1241
|
}
|
|
1255
1242
|
const child = spawnResult.child;
|
|
1256
1243
|
// Registered before the spawn-confirmation await below, for the same reason
|
|
@@ -1277,10 +1264,12 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
1277
1264
|
// worse than releasing early (see `RuntimeLockHold.waitForHandoff`'s own doc).
|
|
1278
1265
|
armHandoffWatcher({ runtimeLockHold: runtimeLockHold, handoffInput: { logFilePath: preparedLogFile?.path, model: selectedModel, processExited: processExitedController.signal }, release: releaseRuntimeLock });
|
|
1279
1266
|
if (streamFormat === 'acp-json-rpc') {
|
|
1280
|
-
|
|
1267
|
+
await dispatchAcpPrompt({ input, def, child, modelCatalog, cleanupStagedFiles, releaseStagedResources, mcpBridge, overlayDelivery }, {});
|
|
1268
|
+
return;
|
|
1281
1269
|
}
|
|
1282
1270
|
if (streamFormat === 'pi-rpc') {
|
|
1283
|
-
|
|
1271
|
+
await dispatchPiPrompt({ input, def, child, modelCatalog, cleanupStagedFiles, releaseStagedResources, overlayDelivery }, {});
|
|
1272
|
+
return;
|
|
1284
1273
|
}
|
|
1285
1274
|
dispatchStdinPrompt({ input, def, child, overlayDelivery, imageDelivery, stdinHandle }, {});
|
|
1286
1275
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRecord } from './values.js';
|
|
2
|
+
import { omitKeys } from '../core/omit-keys.js';
|
|
2
3
|
/** Parse only records; malformed stdout remains the stream parser's responsibility. */
|
|
3
4
|
function frameRecord(line) {
|
|
4
5
|
try {
|
|
@@ -39,16 +40,18 @@ export function createLiveSessionState({ emit }, _options = {}) {
|
|
|
39
40
|
let backgroundTasks = 0;
|
|
40
41
|
function publishTask(task) {
|
|
41
42
|
tasks.set(task.id, task);
|
|
42
|
-
|
|
43
|
-
emit(payload);
|
|
43
|
+
emit(omitKeys(task, ['local']));
|
|
44
44
|
}
|
|
45
45
|
function taskDetails(frame, previous) {
|
|
46
46
|
const patch = isRecord(frame.patch) ? frame.patch : frame;
|
|
47
47
|
const description = typeof patch.description === 'string' ? patch.description : previous?.description;
|
|
48
48
|
const detail = typeof frame.summary === 'string' ? frame.summary : previous?.detail;
|
|
49
|
-
const local =
|
|
49
|
+
const local = isLocalTask({ frame, previous });
|
|
50
50
|
return { local, ...(description === undefined ? {} : { description }), ...(detail === undefined ? {} : { detail }) };
|
|
51
51
|
}
|
|
52
|
+
function isLocalTask({ frame, previous }) {
|
|
53
|
+
return frame.task_type === 'local_agent' || typeof frame.subagent_type === 'string' || previous?.local === true;
|
|
54
|
+
}
|
|
52
55
|
function nextTaskStatus(frame, previous) {
|
|
53
56
|
if (frame.subtype === 'task_started')
|
|
54
57
|
return 'running';
|
|
@@ -260,13 +260,10 @@ export function mergeEnvContentInstructions({ existingRaw, instructionsFilePath
|
|
|
260
260
|
* @overallScore 100/100
|
|
261
261
|
*/
|
|
262
262
|
export function buildMcpBridgeDelivery(input) {
|
|
263
|
-
const { cwd, runId, strategy, options
|
|
263
|
+
const { cwd, runId, strategy, options } = input;
|
|
264
264
|
if (options === undefined || strategy === undefined)
|
|
265
265
|
return null;
|
|
266
|
-
const serverEntry = buildMcpJsonServerEntry({ runId: runId, options: options },
|
|
267
|
-
...(credential === undefined ? {} : { credential }),
|
|
268
|
-
...(input.messageRelayNonce ? { messageRelayNonce: input.messageRelayNonce } : {}),
|
|
269
|
-
});
|
|
266
|
+
const serverEntry = buildMcpJsonServerEntry({ runId: runId, options: options }, bridgeCredentialOptions(input));
|
|
270
267
|
switch (strategy) {
|
|
271
268
|
case 'claude-mcp-json':
|
|
272
269
|
return { kind: 'claude-mcp-json', mcpJsonPath: mcpJsonPathForRun(cwd, runId), serverEntry };
|
|
@@ -279,8 +276,19 @@ export function buildMcpBridgeDelivery(input) {
|
|
|
279
276
|
return { kind: 'codex-toml', serverEntry };
|
|
280
277
|
case 'env-passthrough':
|
|
281
278
|
return { kind: 'env-passthrough', serverEntry };
|
|
279
|
+
default: {
|
|
280
|
+
// Unreachable: every bridge strategy has a case above, and a new one fails typecheck here.
|
|
281
|
+
const unknownStrategy = strategy;
|
|
282
|
+
throw new TypeError(`Unknown MCP bridge strategy: ${String(unknownStrategy)}`);
|
|
283
|
+
}
|
|
282
284
|
}
|
|
283
285
|
}
|
|
286
|
+
function bridgeCredentialOptions(input) {
|
|
287
|
+
return {
|
|
288
|
+
...(input.credential === undefined ? {} : { credential: input.credential }),
|
|
289
|
+
...(input.messageRelayNonce ? { messageRelayNonce: input.messageRelayNonce } : {}),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
284
292
|
export function defaultReadMcpJsonFile({ path }) {
|
|
285
293
|
return fsPromises.readFile(path, 'utf8');
|
|
286
294
|
}
|
|
@@ -26,7 +26,8 @@ export function createMessageRelay({ emit }, { maxChars = 32_000 } = {}) {
|
|
|
26
26
|
enqueue({ id, text, hasAttachments }) {
|
|
27
27
|
if (closed)
|
|
28
28
|
return 'not-running';
|
|
29
|
-
|
|
29
|
+
seq += 1;
|
|
30
|
+
pending.push({ id, text, hasAttachments, seq });
|
|
30
31
|
emit({ type: 'user_message', id, text, deliveryState: 'queued' });
|
|
31
32
|
return 'delivered';
|
|
32
33
|
},
|
|
@@ -36,11 +37,9 @@ export function createMessageRelay({ emit }, { maxChars = 32_000 } = {}) {
|
|
|
36
37
|
let count = 0;
|
|
37
38
|
let chars = 0;
|
|
38
39
|
for (const message of pending) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Whole messages only; a single oversized message is delivered alone.
|
|
43
|
-
if (count > 0 && chars + size > maxChars)
|
|
40
|
+
// Whole messages only, stopping at an attachment; a single oversized message is delivered alone.
|
|
41
|
+
const size = message.hasAttachments ? 0 : JSON.stringify({ id: message.id, seq: message.seq, text: message.text }).length;
|
|
42
|
+
if (message.hasAttachments || (count > 0 && chars + size > maxChars))
|
|
44
43
|
break;
|
|
45
44
|
chars += size;
|
|
46
45
|
count++;
|