@jini-ai/daemon 0.6.9 → 0.6.11
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 +157 -0
- package/dist/agent-executor/contracts.d.ts +34 -11
- package/dist/agent-executor/event-translation.js +2 -0
- package/dist/agent-executor/events.d.ts +7 -15
- package/dist/agent-executor/events.js +142 -194
- package/dist/agent-executor/launch.d.ts +9 -3
- package/dist/agent-executor/launch.js +104 -8
- package/dist/agent-executor/live-session-state.d.ts +23 -0
- package/dist/agent-executor/live-session-state.js +155 -0
- package/dist/agent-executor/mcp-bridge.d.ts +3 -1
- package/dist/agent-executor/mcp-bridge.js +9 -2
- package/dist/agent-executor/message-relay.d.ts +39 -0
- package/dist/agent-executor/message-relay.js +56 -0
- package/dist/core/read-only-tools.d.ts +9 -4
- package/dist/core/read-only-tools.js +10 -7
- package/dist/core/tool-candidates.d.ts +8 -3
- package/dist/core/tool-candidates.js +39 -4
- package/dist/delegated-result-correlation.d.ts +26 -0
- package/dist/delegated-result-correlation.js +68 -0
- package/dist/delegated-tool-bridge.d.ts +5 -13
- package/dist/delegated-tool-bridge.js +19 -58
- package/dist/http/attachments.d.ts +13 -0
- package/dist/http/attachments.js +27 -1
- package/dist/http/delegated-tools.d.ts +3 -0
- package/dist/http/delegated-tools.js +2 -2
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +3 -1
- package/dist/http/message-relay.d.ts +21 -0
- package/dist/http/message-relay.js +44 -0
- package/dist/http/runs.d.ts +5 -2
- package/dist/http/runs.js +11 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/run-lifecycle.d.ts +20 -1
- package/dist/run-lifecycle.js +2 -16
- package/dist/tool-executor.js +5 -4
- 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,10 +1,12 @@
|
|
|
1
1
|
import { buildAgentEnv } from './agent-env.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { supportsMessageRelay } from './message-relay.js';
|
|
2
4
|
import { probeMcpBridge } from './mcp-bridge-probe.js';
|
|
3
5
|
import { spawn as nodeSpawn, } from 'node:child_process';
|
|
4
6
|
import { promises as fsPromises, } from 'node:fs';
|
|
5
7
|
import { tmpdir, } from 'node:os';
|
|
6
8
|
import { join, } from 'node:path';
|
|
7
|
-
import { agentCapabilities, bindRuntimeAgentDef, defaultRuntimeState, getRuntimeModelDiscoveryDeps, userToolchainBinDirs, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
9
|
+
import { agentCapabilities, assertClaudeFilesystemSupport, claudeFilesystemRuntimePaths, claudeFilesystemSettings, validateFilesystemLaunch, bindRuntimeAgentDef, defaultRuntimeState, getRuntimeModelDiscoveryDeps, userToolchainBinDirs, applyAgentLaunchEnv, ensureAgentCapabilities, resolveModelForLaunch, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
8
10
|
import { createCommandInvocation } from '@jini-ai/platform/command';
|
|
9
11
|
import { collectProcessTreePids, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform/process';
|
|
10
12
|
import { applyImagePromptDelivery, } from '../image-prompt-delivery.js';
|
|
@@ -337,6 +339,10 @@ function nativeBuildOptions({ input, permissionMode }, _options = {}) {
|
|
|
337
339
|
/** Preserve explicit empty restrictions and settings: they have meaning to the native CLI. */
|
|
338
340
|
function settingsBuildOptions({ input }, _options = {}) {
|
|
339
341
|
return {
|
|
342
|
+
...(input.filesystemRoot !== undefined ? { filesystemRoot: input.filesystemRoot } : {}),
|
|
343
|
+
...(input.filesystemSharedTempRoots ? { filesystemSharedTempRoots: input.filesystemSharedTempRoots } : {}),
|
|
344
|
+
...(input.filesystemTempDir ? { filesystemTempDir: input.filesystemTempDir } : {}),
|
|
345
|
+
...(input.filesystemRuntimeReadPaths ? { filesystemRuntimeReadPaths: input.filesystemRuntimeReadPaths } : {}),
|
|
340
346
|
...(input.disallowedTools !== undefined ? { disallowedTools: input.disallowedTools } : {}),
|
|
341
347
|
...(input.allowedTools !== undefined ? { allowedTools: input.allowedTools } : {}),
|
|
342
348
|
...(input.settingSources !== undefined ? { settingSources: input.settingSources } : {}),
|
|
@@ -616,6 +622,12 @@ export async function confirmChildSpawned({ input, deps }) {
|
|
|
616
622
|
function nativeCapabilityInput({ input }, _options = {}) {
|
|
617
623
|
return { ...(input.nativeCapabilities !== undefined ? { nativeCapabilities: input.nativeCapabilities } : {}) };
|
|
618
624
|
}
|
|
625
|
+
/** Correlation projects retained stdout, so exact-byte journals need a separate blob writer. */
|
|
626
|
+
function validateStdoutJournalOptions(options, _options = {}) {
|
|
627
|
+
if (options.delegatedResultCorrelation && options.journal && !options.stdoutJournalBlobs) {
|
|
628
|
+
throw new Error('delegated result correlation with a byte journal requires stdoutJournalBlobs');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
619
631
|
/** Model discovery consumes the exact spawn environment and caller-supplied settings. */
|
|
620
632
|
function launchModelContext({ input, launch, spawnEnv }, _options = {}) {
|
|
621
633
|
return {
|
|
@@ -664,6 +676,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
664
676
|
const { getAgentDef: getAgentDefFn, resolveAgentLaunch: resolveAgentLaunchFn, ensureAgentCapabilities: ensureAgentCapabilitiesFn, applyAgentLaunchEnv: applyAgentLaunchEnvFn, attachAcpSession: attachAcpSessionFn, attachPiRpcSession: attachPiRpcSessionFn, preparePromptFileForAgent: preparePromptFileForAgentFn, prepareAgentLogFile: prepareAgentLogFileFn, } = resolveAgentRuntimeDeps({ ...options, hostEnv });
|
|
665
677
|
const { createCommandInvocation: createCommandInvocationFn, spawn: spawnFn, listProcessSnapshots: listProcessSnapshotsFn, collectProcessTreePids: collectProcessTreePidsFn, stopProcesses: stopProcessesFn, } = resolveProcessDeps(options);
|
|
666
678
|
const { onCleanupFailure: onCleanupFailureFn, bufferedStdoutMaxBytes } = resolveMiscExecutorDeps(options);
|
|
679
|
+
validateStdoutJournalOptions(options);
|
|
667
680
|
const journal = options.journal;
|
|
668
681
|
const continuation = options.continuation;
|
|
669
682
|
const classifyFailure = options.classifyFailure;
|
|
@@ -671,6 +684,15 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
671
684
|
const claudeConfigDirIsolation = options.claudeConfigDirIsolation;
|
|
672
685
|
const claudeConfigDirIsolationEnabled = options.claudeConfigDirIsolationEnabled ?? false;
|
|
673
686
|
const promptAugmenter = options.promptAugmenter;
|
|
687
|
+
const filesystemTemps = new Map();
|
|
688
|
+
/** Remove only this run's private temp directory, on startup failure or child cleanup. */
|
|
689
|
+
async function cleanupFilesystemTemp({ runId }, _optional = {}) {
|
|
690
|
+
const path = filesystemTemps.get(runId);
|
|
691
|
+
if (!path)
|
|
692
|
+
return;
|
|
693
|
+
await fsPromises.rm(path, { recursive: true, force: true }).catch(() => console.warn('[agent-executor] private temp cleanup failed'));
|
|
694
|
+
filesystemTemps.delete(runId);
|
|
695
|
+
}
|
|
674
696
|
/**
|
|
675
697
|
* Transitions `runId` to `'failed'` (idempotent, never resumable — no
|
|
676
698
|
* classifier exists, see index.ts module doc) then rejects with a typed
|
|
@@ -691,6 +713,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
691
713
|
await lifecycle
|
|
692
714
|
.emit({ runId: runId, input: { event: 'error', data: { message: `The assistant could not start: ${message.replace(/^AgentExecutor:\s*/, '')}` } } })
|
|
693
715
|
.catch(() => undefined);
|
|
716
|
+
await cleanupFilesystemTemp({ runId }, {});
|
|
694
717
|
await lifecycle.finish({ runId, status: 'failed', code: null, signal: null, resumable: false });
|
|
695
718
|
throw new AgentExecutorError({ code: code, message: message });
|
|
696
719
|
}
|
|
@@ -701,10 +724,12 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
701
724
|
* finished or unknown run reads as `'not-running'`. Bounded by the number of live children.
|
|
702
725
|
*/
|
|
703
726
|
const liveRunInputs = new Map();
|
|
727
|
+
const liveRunRelays = new Map();
|
|
728
|
+
const claimUserMessages = ({ runId, via }) => liveRunRelays.get(runId)?.claim({ via }) ?? [];
|
|
704
729
|
/** `AgentExecutor.sendUserMessage()` — see that interface method's own doc. @complexity O(1). */
|
|
705
|
-
async function sendUserMessage({ runId, text }, { imageContents } = {}) {
|
|
730
|
+
async function sendUserMessage({ runId, text, id = randomUUID(), attachmentIds }, { imageContents } = {}) {
|
|
706
731
|
const deliver = liveRunInputs.get(runId);
|
|
707
|
-
return deliver ? deliver(text, imageContents) : 'not-running';
|
|
732
|
+
return deliver ? deliver({ text, id, ...(attachmentIds ? { attachmentIds } : {}) }, imageContents ? { imageContents } : {}) : 'not-running';
|
|
708
733
|
}
|
|
709
734
|
/** Validates launch input and selects the definition synchronously before yielding. */
|
|
710
735
|
async function prepareRunContext({ requiredArgs, optionalArgs }, _options = {}) {
|
|
@@ -724,6 +749,66 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
724
749
|
return failBeforeSpawn({ runId: input.runId, code: 'NATIVE_TOOL_POLICY_UNSUPPORTED', message: 'This CLI has no native-tool policy and audit adapter. Choose a CLI with native-tool policy support.' });
|
|
725
750
|
}
|
|
726
751
|
}
|
|
752
|
+
/** Validate every directory after image augmentation, before model probes or resource staging.
|
|
753
|
+
* Unknown containment fails this run; O(n) filesystem checks for n attachment directories.
|
|
754
|
+
*/
|
|
755
|
+
async function enforceFilesystemPaths({ input, imageDelivery }, _options = {}) {
|
|
756
|
+
if (input.filesystemRoot === undefined)
|
|
757
|
+
return;
|
|
758
|
+
try {
|
|
759
|
+
input.filesystemRoot = validateFilesystemLaunch({ filesystemRoot: input.filesystemRoot, cwd: input.cwd,
|
|
760
|
+
...(imageDelivery.extraAllowedDirs ? { extraAllowedDirs: imageDelivery.extraAllowedDirs } : {}),
|
|
761
|
+
...(input.imagePaths ? { imagePaths: input.imagePaths } : {}),
|
|
762
|
+
}, {});
|
|
763
|
+
input.cwd = input.filesystemRoot;
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return failBeforeSpawn({ runId: input.runId, code: 'FILESYSTEM_POLICY_INVALID', message: 'Folder-limited launch refused: invalid filesystem root, working directory or attachment path.' });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/** Apply supported confinement without removing providers; unknown versions keep the settings.
|
|
770
|
+
* Runtime and git config reads are explicit infrastructure exceptions; network is unchanged.
|
|
771
|
+
* O(1) metadata/staging I/O plus the cached, bounded version probe.
|
|
772
|
+
*/
|
|
773
|
+
async function enforceFilesystemRuntime({ input, def, launch, spawnEnv }, _options = {}) {
|
|
774
|
+
if (input.filesystemRoot === undefined)
|
|
775
|
+
return;
|
|
776
|
+
try {
|
|
777
|
+
const verify = options.assertClaudeFilesystemSupport ?? assertClaudeFilesystemSupport;
|
|
778
|
+
const supported = await verify({ agentId: def.id, launchPath: launch.launchPath, env: spawnEnv }, {});
|
|
779
|
+
const runtimeDir = join(input.filesystemRoot, '.agent-runtime');
|
|
780
|
+
await fsPromises.mkdir(runtimeDir, { recursive: true, mode: 0o700 });
|
|
781
|
+
// Refuse a runtime-folder symlink escaping the site before creating anything beneath it.
|
|
782
|
+
validateFilesystemLaunch({ filesystemRoot: input.filesystemRoot, cwd: input.cwd, extraAllowedDirs: [runtimeDir] }, {});
|
|
783
|
+
input.filesystemTempDir = await fsPromises.realpath(await fsPromises.mkdtemp(join(runtimeDir, 'tmp-')));
|
|
784
|
+
filesystemTemps.set(input.runId, input.filesystemTempDir);
|
|
785
|
+
// Recheck after creation too: swapping the runtime symlink must never grant outside temp access.
|
|
786
|
+
validateFilesystemLaunch({ filesystemRoot: input.filesystemRoot, cwd: input.cwd, extraAllowedDirs: [input.filesystemTempDir] }, {});
|
|
787
|
+
input.filesystemSharedTempRoots = [spawnEnv.TMPDIR, spawnEnv.TMP, spawnEnv.TEMP].filter((path) => Boolean(path));
|
|
788
|
+
spawnEnv.TMPDIR = input.filesystemTempDir;
|
|
789
|
+
spawnEnv.TMP = input.filesystemTempDir;
|
|
790
|
+
spawnEnv.TEMP = input.filesystemTempDir;
|
|
791
|
+
if (supported === false) {
|
|
792
|
+
input.filesystemNotice = 'files and shell are not folder-limited for this agent';
|
|
793
|
+
console.warn('[agent-executor] files and shell are not folder-limited for this agent');
|
|
794
|
+
// Personal Claude hooks/permissions stay excluded even on an unconfined CLI fallback.
|
|
795
|
+
if (def.id === 'claude')
|
|
796
|
+
input.settingSources = [];
|
|
797
|
+
delete input.filesystemRoot;
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (supported === undefined)
|
|
801
|
+
console.warn('[agent-executor] filesystem capability unknown; applying read block and sandbox settings');
|
|
802
|
+
input.filesystemRuntimeReadPaths = claudeFilesystemRuntimePaths({ launchPath: launch.launchPath }, {});
|
|
803
|
+
// Validates host settings here so a malformed value fails cleanly; buildArgs merges again (idempotent).
|
|
804
|
+
input.settings = claudeFilesystemSettings({ options: input }, {});
|
|
805
|
+
// A login environment can otherwise force auto-memory back on despite the host setting.
|
|
806
|
+
spawnEnv.CLAUDE_CODE_DISABLE_AUTO_MEMORY = '1';
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
return failBeforeSpawn({ runId: input.runId, code: 'FILESYSTEM_POLICY_UNSUPPORTED', message: 'Folder-limited launch refused: invalid host settings or private runtime directory.' });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
727
812
|
function resolveRunOverlay({ input, def, imageDelivery }, _options = {}) {
|
|
728
813
|
// Phase 8, hoisted deliberately above EVERY step that writes or sends the prompt. Four
|
|
729
814
|
// different channels carry a prompt in this driver, and they do not all run at the same point:
|
|
@@ -744,7 +829,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
744
829
|
// same overlay on every turn, not a first-turn-only one; a caller that needs finer-grained turn
|
|
745
830
|
// numbering can track it itself and ignore this arg.
|
|
746
831
|
const preStagingRuntimeContext = computeRuntimeContext({ preparedPromptFile: null, preparedLogFile: null, mcpBridge: null }, sessionRuntimeContext(input, {}));
|
|
747
|
-
const
|
|
832
|
+
const hostOverlay = computeSystemPromptOverlay(promptAugmenter, def.id);
|
|
833
|
+
const systemPromptOverlay = input.filesystemNotice ? `${hostOverlay ?? ''}\n${input.filesystemNotice}\n` : hostOverlay;
|
|
748
834
|
const overlayDelivery = resolveSystemPromptOverlayDelivery({
|
|
749
835
|
defId: def.id,
|
|
750
836
|
systemPromptDelivery: def.systemPromptDelivery,
|
|
@@ -798,6 +884,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
798
884
|
const formatForStdin = { streamFormat };
|
|
799
885
|
const stdinHandle = isStdinDrivenFormat(formatForStdin)
|
|
800
886
|
? wireChildLifecycle({
|
|
887
|
+
...(mcpBridge && supportsMessageRelay({ def }) ? { registerMessageRelay: (relay) => liveRunRelays.set(input.runId, relay) } : {}),
|
|
801
888
|
...(input.model ? { startingModel: input.model } : {}),
|
|
802
889
|
...(modelCatalog ? { modelCatalog } : {}),
|
|
803
890
|
runId: input.runId,
|
|
@@ -811,6 +898,8 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
811
898
|
onCleanupFailure: onCleanupFailureFn,
|
|
812
899
|
cleanupStagedFiles,
|
|
813
900
|
journal,
|
|
901
|
+
...(options.delegatedResultCorrelation ? { delegatedResultCorrelation: options.delegatedResultCorrelation } : {}),
|
|
902
|
+
...(options.stdoutJournalBlobs ? { stdoutJournalBlobs: options.stdoutJournalBlobs } : {}),
|
|
814
903
|
continuation,
|
|
815
904
|
classifyFailure,
|
|
816
905
|
bufferedStdoutMaxBytes,
|
|
@@ -822,8 +911,10 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
822
911
|
// Registered with the lifecycle wiring above, before the spawn-confirmation await, so a child
|
|
823
912
|
// that closes immediately never leaves a stale entry behind.
|
|
824
913
|
const runId = input.runId;
|
|
825
|
-
liveRunInputs.set(runId, stdinHandle ? stdinHandle.sendUserMessage : async () =>
|
|
826
|
-
|
|
914
|
+
liveRunInputs.set(runId, stdinHandle ? stdinHandle.sendUserMessage : async ({ text, id = randomUUID(), attachmentIds }, { imageContents } = {}) => {
|
|
915
|
+
return liveRunRelays.get(runId)?.enqueue({ id, text, hasAttachments: (attachmentIds?.length ?? 0) > 0 || (imageContents?.length ?? 0) > 0 }) ?? 'unsupported';
|
|
916
|
+
});
|
|
917
|
+
child.once('close', () => { liveRunInputs.delete(runId); liveRunRelays.delete(runId); });
|
|
827
918
|
return stdinHandle;
|
|
828
919
|
}
|
|
829
920
|
async function dispatchAcpPrompt({ input, def, child, modelCatalog, cleanupStagedFiles, releaseStagedResources, mcpBridge, overlayDelivery }, _options = {}) {
|
|
@@ -850,6 +941,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
850
941
|
}, deps: {
|
|
851
942
|
lifecycle,
|
|
852
943
|
attachAcpSession: attachAcpSessionFn,
|
|
944
|
+
...(mcpBridge && supportsMessageRelay({ def }) ? { registerMessageRelay: (relay) => liveRunRelays.set(input.runId, relay) } : {}),
|
|
853
945
|
onPermissionRequest: options.acpPermissionHandler,
|
|
854
946
|
listProcessSnapshots: listProcessSnapshotsFn,
|
|
855
947
|
collectProcessTreePids: collectProcessTreePidsFn,
|
|
@@ -930,8 +1022,10 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
930
1022
|
// treatment (the double-delivery hazard this mechanism exists to avoid).
|
|
931
1023
|
await enforceNativeToolPolicy({ input, def }, {});
|
|
932
1024
|
const imageDelivery = await resolveImageDeliveryAndArgvBudget({ input: { runId: input.runId, def, prompt: input.prompt, imagePaths: input.imagePaths, extraAllowedDirs: input.extraAllowedDirs }, deps: { failBeforeSpawn } });
|
|
933
|
-
|
|
1025
|
+
await enforceFilesystemPaths({ input, imageDelivery }, {});
|
|
934
1026
|
const { launch, spawnEnv } = await resolveRunLaunch({ input, def }, {});
|
|
1027
|
+
await enforceFilesystemRuntime({ input, def, launch, spawnEnv }, {});
|
|
1028
|
+
const { systemPromptOverlay, overlayDelivery } = resolveRunOverlay({ input, def, imageDelivery }, {});
|
|
935
1029
|
const modelCatalog = await selectRunModel({ input, def, launch, spawnEnv }, {});
|
|
936
1030
|
// Fill the def's `--help` capability gate before `buildArgs` reads it. Without this, only a
|
|
937
1031
|
// host that happened to call `detectAgents` in THIS process ever had the gate filled, so e.g.
|
|
@@ -989,6 +1083,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
989
1083
|
*/
|
|
990
1084
|
let preparedSystemPromptOverlayFile = null;
|
|
991
1085
|
const cleanupStagedFiles = async () => {
|
|
1086
|
+
await cleanupFilesystemTemp({ runId: input.runId }, {});
|
|
992
1087
|
if (preparedPromptFile)
|
|
993
1088
|
await preparedPromptFile.cleanup();
|
|
994
1089
|
if (preparedLogFile)
|
|
@@ -1121,6 +1216,7 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
1121
1216
|
}
|
|
1122
1217
|
catch (error) {
|
|
1123
1218
|
liveRunInputs.delete(runId);
|
|
1219
|
+
liveRunRelays.delete(runId);
|
|
1124
1220
|
throw error;
|
|
1125
1221
|
}
|
|
1126
1222
|
// Now — and only now — is there a live process that could consume the locked side effect, so
|
|
@@ -1137,6 +1233,6 @@ export function createAgentExecutor(requiredArgs, optionalArgs = {}) {
|
|
|
1137
1233
|
}
|
|
1138
1234
|
dispatchStdinPrompt({ input, def, child, overlayDelivery, imageDelivery, stdinHandle }, {});
|
|
1139
1235
|
}
|
|
1140
|
-
return { run, sendUserMessage };
|
|
1236
|
+
return { run, sendUserMessage, claimUserMessages };
|
|
1141
1237
|
}
|
|
1142
1238
|
//# sourceMappingURL=launch.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RunAgentPayload } from '@jini-ai/protocol';
|
|
2
|
+
/** Per-process receipts and task projection for the existing stdin lifecycle. O(p + t) retained
|
|
3
|
+
* state for pending inputs and observed tasks; each frame is O(frame size + affected tasks).
|
|
4
|
+
* The injected emitter uses the lifecycle's existing FIFO, preserving ordering with tool output.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createLiveSessionState({ emit }: {
|
|
7
|
+
emit: (payload: RunAgentPayload) => void;
|
|
8
|
+
}, _options?: {}): {
|
|
9
|
+
queue({ id, text }: {
|
|
10
|
+
id: string;
|
|
11
|
+
text: string;
|
|
12
|
+
}): void;
|
|
13
|
+
observe: ({ line }: {
|
|
14
|
+
line: string;
|
|
15
|
+
}) => {
|
|
16
|
+
result: boolean;
|
|
17
|
+
startedInput: boolean;
|
|
18
|
+
};
|
|
19
|
+
hasPendingInput: () => boolean;
|
|
20
|
+
backgroundTaskCount: () => number;
|
|
21
|
+
endSession(): void;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=live-session-state.d.ts.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { isRecord } from './values.js';
|
|
2
|
+
/** Parse only records; malformed stdout remains the stream parser's responsibility. */
|
|
3
|
+
function frameRecord(line) {
|
|
4
|
+
try {
|
|
5
|
+
const value = JSON.parse(line);
|
|
6
|
+
return isRecord(value) ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** UUID attribution is a consumption receipt; an echoed user line or HTTP 202 is not. */
|
|
13
|
+
function consumedIds(frame) {
|
|
14
|
+
if (!['assistant', 'stream_event', 'result'].includes(String(frame.type)))
|
|
15
|
+
return [];
|
|
16
|
+
const ids = Array.isArray(frame.user_message_uuids) ? frame.user_message_uuids : [frame.user_message_uuid];
|
|
17
|
+
return ids.filter((id) => typeof id === 'string');
|
|
18
|
+
}
|
|
19
|
+
/** Preserve unknown task outcomes rather than guessing success or who stopped them. */
|
|
20
|
+
function taskStatus(value) {
|
|
21
|
+
if (value === 'killed')
|
|
22
|
+
return 'stopped';
|
|
23
|
+
if (value === 'running' || value === 'completed' || value === 'failed' || value === 'stopped')
|
|
24
|
+
return value;
|
|
25
|
+
return 'unknown';
|
|
26
|
+
}
|
|
27
|
+
/** Per-process receipts and task projection for the existing stdin lifecycle. O(p + t) retained
|
|
28
|
+
* state for pending inputs and observed tasks; each frame is O(frame size + affected tasks).
|
|
29
|
+
* The injected emitter uses the lifecycle's existing FIFO, preserving ordering with tool output.
|
|
30
|
+
*/
|
|
31
|
+
export function createLiveSessionState({ emit }, _options = {}) {
|
|
32
|
+
const pending = new Map();
|
|
33
|
+
// Ids written since the last result. Only these and the CLI's own queued_turn_count hold stdin:
|
|
34
|
+
// a CLI that never attributes a receipt must not keep the run open forever. Closing after a
|
|
35
|
+
// result is safe for a line still in the pipe: the CLI answers everything written before EOF.
|
|
36
|
+
const unanswered = new Set();
|
|
37
|
+
const tasks = new Map();
|
|
38
|
+
let queuedTurns = 0;
|
|
39
|
+
let backgroundTasks = 0;
|
|
40
|
+
function publishTask(task) {
|
|
41
|
+
tasks.set(task.id, task);
|
|
42
|
+
const { local: _local, ...payload } = task;
|
|
43
|
+
emit(payload);
|
|
44
|
+
}
|
|
45
|
+
function taskDetails(frame, previous) {
|
|
46
|
+
const patch = isRecord(frame.patch) ? frame.patch : frame;
|
|
47
|
+
const description = typeof patch.description === 'string' ? patch.description : previous?.description;
|
|
48
|
+
const detail = typeof frame.summary === 'string' ? frame.summary : previous?.detail;
|
|
49
|
+
const local = frame.task_type === 'local_agent' || typeof frame.subagent_type === 'string' || previous?.local === true;
|
|
50
|
+
return { local, ...(description === undefined ? {} : { description }), ...(detail === undefined ? {} : { detail }) };
|
|
51
|
+
}
|
|
52
|
+
function nextTaskStatus(frame, previous) {
|
|
53
|
+
if (frame.subtype === 'task_started')
|
|
54
|
+
return 'running';
|
|
55
|
+
const patch = isRecord(frame.patch) ? frame.patch : frame;
|
|
56
|
+
if (patch.status === undefined)
|
|
57
|
+
return previous?.status ?? 'unknown';
|
|
58
|
+
return taskStatus(patch.status);
|
|
59
|
+
}
|
|
60
|
+
/** Older task runs and duplicate starts cannot overwrite a confirmed terminal receipt. */
|
|
61
|
+
function staleTaskUpdate(previous, taskRunId, status) {
|
|
62
|
+
if (!previous)
|
|
63
|
+
return false;
|
|
64
|
+
// The CLI mints run ids as zero-padded base36 time plus random hex, so they sort by start.
|
|
65
|
+
if (previous.taskRunId && taskRunId && taskRunId < previous.taskRunId)
|
|
66
|
+
return true;
|
|
67
|
+
const terminal = ['completed', 'failed', 'stopped'].includes(previous.status);
|
|
68
|
+
return terminal && previous.taskRunId === taskRunId && status === 'running';
|
|
69
|
+
}
|
|
70
|
+
function updateTask(frame) {
|
|
71
|
+
if (typeof frame.task_id !== 'string')
|
|
72
|
+
return;
|
|
73
|
+
const previous = tasks.get(frame.task_id);
|
|
74
|
+
const taskRunId = typeof frame.run_id === 'string' ? frame.run_id : previous?.taskRunId;
|
|
75
|
+
const status = nextTaskStatus(frame, previous);
|
|
76
|
+
// Resumed tasks reuse task_id. A late frame from an older run cannot stop its replacement.
|
|
77
|
+
if (staleTaskUpdate(previous, taskRunId, status))
|
|
78
|
+
return;
|
|
79
|
+
publishTask({ type: 'background_task', id: frame.task_id, status, ...taskDetails(frame, previous),
|
|
80
|
+
...(taskRunId === undefined ? {} : { taskRunId }) });
|
|
81
|
+
}
|
|
82
|
+
function updateMembership(frame) {
|
|
83
|
+
if (!Array.isArray(frame.tasks))
|
|
84
|
+
return;
|
|
85
|
+
backgroundTasks = frame.tasks.length;
|
|
86
|
+
const listed = new Set();
|
|
87
|
+
for (const task of frame.tasks) {
|
|
88
|
+
if (!isRecord(task))
|
|
89
|
+
continue;
|
|
90
|
+
if (typeof task.task_id === 'string')
|
|
91
|
+
listed.add(task.task_id);
|
|
92
|
+
updateTask({ ...task, subtype: 'task_started' });
|
|
93
|
+
}
|
|
94
|
+
// Level and edge notifications have unspecified ordering. Disappearance proves neither
|
|
95
|
+
// completion nor cancellation; a later task_notification supplies the actual outcome.
|
|
96
|
+
for (const task of tasks.values()) {
|
|
97
|
+
if (task.status === 'running' && !listed.has(task.id))
|
|
98
|
+
publishTask({ ...task, status: 'unknown', detail: 'status unknown' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function consume(frame) {
|
|
102
|
+
let consumed = false;
|
|
103
|
+
for (const id of consumedIds(frame)) {
|
|
104
|
+
const text = pending.get(id);
|
|
105
|
+
if (text === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
pending.delete(id);
|
|
108
|
+
unanswered.delete(id);
|
|
109
|
+
emit({ type: 'user_message', id, text, deliveryState: 'consumed' });
|
|
110
|
+
consumed = true;
|
|
111
|
+
}
|
|
112
|
+
return consumed;
|
|
113
|
+
}
|
|
114
|
+
function observe({ line }) {
|
|
115
|
+
const frame = frameRecord(line);
|
|
116
|
+
if (!frame)
|
|
117
|
+
return { result: false, startedInput: false };
|
|
118
|
+
const consumed = consume(frame);
|
|
119
|
+
if (frame.type === 'result') {
|
|
120
|
+
queuedTurns = typeof frame.queued_turn_count === 'number' ? Math.max(0, frame.queued_turn_count) : 0;
|
|
121
|
+
unanswered.clear();
|
|
122
|
+
}
|
|
123
|
+
if (frame.type === 'system')
|
|
124
|
+
observeSystem(frame);
|
|
125
|
+
return { result: frame.type === 'result', startedInput: consumed && frame.type !== 'result' };
|
|
126
|
+
}
|
|
127
|
+
function observeSystem(frame) {
|
|
128
|
+
if (frame.subtype === 'background_tasks_changed')
|
|
129
|
+
updateMembership(frame);
|
|
130
|
+
if (['task_started', 'task_notification', 'task_updated'].includes(String(frame.subtype)))
|
|
131
|
+
updateTask(frame);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
queue({ id, text }) {
|
|
135
|
+
pending.set(id, text);
|
|
136
|
+
unanswered.add(id);
|
|
137
|
+
emit({ type: 'user_message', id, text, deliveryState: 'queued' });
|
|
138
|
+
},
|
|
139
|
+
observe,
|
|
140
|
+
hasPendingInput: () => unanswered.size > 0 || queuedTurns > 0,
|
|
141
|
+
backgroundTaskCount: () => backgroundTasks,
|
|
142
|
+
endSession() {
|
|
143
|
+
for (const task of tasks.values()) {
|
|
144
|
+
if (task.status !== 'running' && task.status !== 'unknown')
|
|
145
|
+
continue;
|
|
146
|
+
// A task already missing from membership may have completed without a receipt. Only
|
|
147
|
+
// a still-running, process-local subagent is known to stop with this session.
|
|
148
|
+
const stopped = task.local && task.status === 'running';
|
|
149
|
+
publishTask({ ...task, status: stopped ? 'stopped' : 'unknown',
|
|
150
|
+
detail: stopped ? 'Stopped because the agent session ended' : 'status unknown' });
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=live-session-state.js.map
|
|
@@ -34,8 +34,9 @@ export declare function bridgeUnavailableMessage(status: string, probe?: McpBrid
|
|
|
34
34
|
export declare function buildMcpJsonServerEntry({ runId, options }: {
|
|
35
35
|
readonly runId: string;
|
|
36
36
|
readonly options: Pick<McpJsonInjectionOptions, 'command' | 'args' | 'daemonUrl' | 'env' | 'diagnosticToolIds' | 'startupToolIds'>;
|
|
37
|
-
}, { credential }?: {
|
|
37
|
+
}, { credential, messageRelayNonce }?: {
|
|
38
38
|
readonly credential?: string;
|
|
39
|
+
readonly messageRelayNonce?: string;
|
|
39
40
|
}): McpJsonServerEntry;
|
|
40
41
|
/**
|
|
41
42
|
* Merges {@link JINI_MCP_SERVER_KEY} into an existing `.mcp.json`'s `mcpServers` map, preserving
|
|
@@ -235,6 +236,7 @@ export declare function buildMcpBridgeDelivery(input: {
|
|
|
235
236
|
readonly strategy: RuntimeAgentDef['externalMcpInjection'];
|
|
236
237
|
readonly options: McpJsonInjectionOptions | undefined;
|
|
237
238
|
readonly credential: string | undefined;
|
|
239
|
+
readonly messageRelayNonce?: string;
|
|
238
240
|
}): McpBridgeDelivery | null;
|
|
239
241
|
export declare function defaultRemoveMcpJsonFile({ path }: {
|
|
240
242
|
readonly path: string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { promises as fsPromises, } from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { supportsMessageRelay } from './message-relay.js';
|
|
2
4
|
import { ensureAntigravityMcpPermission } from './antigravity-permissions.js';
|
|
3
5
|
import { homedir, tmpdir, } from 'node:os';
|
|
4
6
|
import { join, resolve, } from 'node:path';
|
|
@@ -46,7 +48,7 @@ export function bridgeUnavailableMessage(status, probe) {
|
|
|
46
48
|
* entirely. Omitting produces byte-identical output to before this parameter existed.
|
|
47
49
|
* @complexity O(t) output space/time for t launch environment and registered ID bytes.
|
|
48
50
|
*/
|
|
49
|
-
export function buildMcpJsonServerEntry({ runId, options }, { credential } = {}) {
|
|
51
|
+
export function buildMcpJsonServerEntry({ runId, options }, { credential, messageRelayNonce } = {}) {
|
|
50
52
|
const hostEnv = Object.entries(options.env ?? {}).filter(([key]) => !key.startsWith('JINI_'));
|
|
51
53
|
return {
|
|
52
54
|
command: options.command,
|
|
@@ -58,6 +60,7 @@ export function buildMcpJsonServerEntry({ runId, options }, { credential } = {})
|
|
|
58
60
|
...(options.diagnosticToolIds ? { JINI_DIAGNOSTIC_TOOL_IDS: JSON.stringify(options.diagnosticToolIds()) } : {}),
|
|
59
61
|
...(options.startupToolIds ? { JINI_STARTUP_TOOL_IDS: JSON.stringify(options.startupToolIds({ runId })) } : {}),
|
|
60
62
|
...(credential !== undefined ? { JINI_DAEMON_TOKEN: credential } : {}),
|
|
63
|
+
...(messageRelayNonce ? { JINI_MESSAGE_RELAY: '1', JINI_MESSAGE_RELAY_NONCE: messageRelayNonce } : {}),
|
|
61
64
|
},
|
|
62
65
|
};
|
|
63
66
|
}
|
|
@@ -479,7 +482,10 @@ export function buildMcpBridgeDelivery(input) {
|
|
|
479
482
|
const { cwd, runId, strategy, options, credential } = input;
|
|
480
483
|
if (options === undefined || strategy === undefined)
|
|
481
484
|
return null;
|
|
482
|
-
const serverEntry = buildMcpJsonServerEntry({ runId: runId, options: options },
|
|
485
|
+
const serverEntry = buildMcpJsonServerEntry({ runId: runId, options: options }, {
|
|
486
|
+
...(credential === undefined ? {} : { credential }),
|
|
487
|
+
...(input.messageRelayNonce ? { messageRelayNonce: input.messageRelayNonce } : {}),
|
|
488
|
+
});
|
|
483
489
|
switch (strategy) {
|
|
484
490
|
case 'claude-mcp-json':
|
|
485
491
|
return { kind: 'claude-mcp-json', mcpJsonPath: mcpJsonPathForRun(cwd, runId), serverEntry };
|
|
@@ -789,6 +795,7 @@ export async function resolveMcpBridgeForRun({ input, deps }) {
|
|
|
789
795
|
strategy: input.def.externalMcpInjection,
|
|
790
796
|
options: deps.mcpJsonInjection,
|
|
791
797
|
credential,
|
|
798
|
+
...(supportsMessageRelay({ def: input.def }) ? { messageRelayNonce: randomBytes(24).toString('hex') } : {}),
|
|
792
799
|
});
|
|
793
800
|
if (input.def.id === 'antigravity' && delivery?.kind === 'env-passthrough') {
|
|
794
801
|
await (deps.ensureAntigravityMcpPermission ?? ensureAntigravityMcpPermission)({
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RuntimeAgentDef } from '@jini-ai/agent-runtime';
|
|
2
|
+
import type { RunAgentPayload, RelayedUserMessage, MessageRelayVia } from '@jini-ai/protocol';
|
|
3
|
+
export interface RunMessageRelay {
|
|
4
|
+
enqueue(input: {
|
|
5
|
+
id: string;
|
|
6
|
+
text: string;
|
|
7
|
+
hasAttachments: boolean;
|
|
8
|
+
}): 'delivered' | 'not-running';
|
|
9
|
+
claim(input: {
|
|
10
|
+
via: MessageRelayVia;
|
|
11
|
+
}): readonly RelayedUserMessage[];
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether a def's live process can take another user message mid-run: only a stream-json stdin
|
|
16
|
+
* transport keeps stdin open for more JSONL user lines (see `RuntimeAgentDef.promptInputFormat`).
|
|
17
|
+
* Deliberately not `resolveContinuationTransport`: that answers how a tool result gets back, and
|
|
18
|
+
* reports `'mcp-callback'` for Claude even though its stdin takes user lines all the same.
|
|
19
|
+
* Native stream-json input wins even when the engine also has MCP.
|
|
20
|
+
*/
|
|
21
|
+
export declare function acceptsMidRunUserMessages({ def }: {
|
|
22
|
+
def: RuntimeAgentDef;
|
|
23
|
+
}, _options?: {}): boolean;
|
|
24
|
+
/** A definition without an injected MCP bridge must retain next-turn fallback. */
|
|
25
|
+
export declare function supportsMessageRelay({ def }: {
|
|
26
|
+
def: RuntimeAgentDef;
|
|
27
|
+
}, _options?: {}): boolean;
|
|
28
|
+
/** One synchronous FIFO claim per process: parallel tool results get disjoint prefixes.
|
|
29
|
+
* The browser retains the fallback copy until the emitted consumption receipt arrives.
|
|
30
|
+
* @param emit - Enqueues receipts on the driver's existing lifecycle FIFO; never waits on I/O.
|
|
31
|
+
* @returns A text-only inbox, closed synchronously before the driver's terminal event.
|
|
32
|
+
* @complexity O(n) retained messages; each claim scans its prefix and removes it in one splice.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createMessageRelay({ emit }: {
|
|
35
|
+
emit: (payload: RunAgentPayload) => void;
|
|
36
|
+
}, { maxChars }?: {
|
|
37
|
+
maxChars?: number;
|
|
38
|
+
}): RunMessageRelay;
|
|
39
|
+
//# sourceMappingURL=message-relay.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a def's live process can take another user message mid-run: only a stream-json stdin
|
|
3
|
+
* transport keeps stdin open for more JSONL user lines (see `RuntimeAgentDef.promptInputFormat`).
|
|
4
|
+
* Deliberately not `resolveContinuationTransport`: that answers how a tool result gets back, and
|
|
5
|
+
* reports `'mcp-callback'` for Claude even though its stdin takes user lines all the same.
|
|
6
|
+
* Native stream-json input wins even when the engine also has MCP.
|
|
7
|
+
*/
|
|
8
|
+
export function acceptsMidRunUserMessages({ def }, _options = {}) {
|
|
9
|
+
return def.promptViaStdin === true && def.promptInputFormat === 'stream-json';
|
|
10
|
+
}
|
|
11
|
+
/** A definition without an injected MCP bridge must retain next-turn fallback. */
|
|
12
|
+
export function supportsMessageRelay({ def }, _options = {}) {
|
|
13
|
+
return def.externalMcpInjection !== undefined && !acceptsMidRunUserMessages({ def });
|
|
14
|
+
}
|
|
15
|
+
/** One synchronous FIFO claim per process: parallel tool results get disjoint prefixes.
|
|
16
|
+
* The browser retains the fallback copy until the emitted consumption receipt arrives.
|
|
17
|
+
* @param emit - Enqueues receipts on the driver's existing lifecycle FIFO; never waits on I/O.
|
|
18
|
+
* @returns A text-only inbox, closed synchronously before the driver's terminal event.
|
|
19
|
+
* @complexity O(n) retained messages; each claim scans its prefix and removes it in one splice.
|
|
20
|
+
*/
|
|
21
|
+
export function createMessageRelay({ emit }, { maxChars = 32_000 } = {}) {
|
|
22
|
+
const pending = [];
|
|
23
|
+
let seq = 0;
|
|
24
|
+
let closed = false;
|
|
25
|
+
return {
|
|
26
|
+
enqueue({ id, text, hasAttachments }) {
|
|
27
|
+
if (closed)
|
|
28
|
+
return 'not-running';
|
|
29
|
+
pending.push({ id, text, hasAttachments, seq: ++seq });
|
|
30
|
+
emit({ type: 'user_message', id, text, deliveryState: 'queued' });
|
|
31
|
+
return 'delivered';
|
|
32
|
+
},
|
|
33
|
+
claim({ via }) {
|
|
34
|
+
if (closed)
|
|
35
|
+
return [];
|
|
36
|
+
let count = 0;
|
|
37
|
+
let chars = 0;
|
|
38
|
+
for (const message of pending) {
|
|
39
|
+
if (message.hasAttachments)
|
|
40
|
+
break;
|
|
41
|
+
const size = JSON.stringify({ id: message.id, seq: message.seq, text: message.text }).length;
|
|
42
|
+
// Whole messages only; a single oversized message is delivered alone.
|
|
43
|
+
if (count > 0 && chars + size > maxChars)
|
|
44
|
+
break;
|
|
45
|
+
chars += size;
|
|
46
|
+
count++;
|
|
47
|
+
}
|
|
48
|
+
return pending.splice(0, count).map(({ id, seq, text }) => {
|
|
49
|
+
emit({ type: 'user_message', id, text, deliveryState: 'consumed', via });
|
|
50
|
+
return { id, seq, text };
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
close() { closed = true; pending.length = 0; },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=message-relay.js.map
|
|
@@ -55,6 +55,8 @@ export interface ReadOnlyToolMessages {
|
|
|
55
55
|
}
|
|
56
56
|
export interface ReadOnlyToolCheckRequired {
|
|
57
57
|
readonly toolId: string;
|
|
58
|
+
/** Diagnostic selector only; never changes which registration is checked or executed. */
|
|
59
|
+
readonly input?: unknown;
|
|
58
60
|
readonly registry: Pick<ToolRegistry, 'list'> | undefined;
|
|
59
61
|
readonly messages: ReadOnlyToolMessages;
|
|
60
62
|
}
|
|
@@ -94,8 +96,9 @@ export declare function readOnlyToolRefusalMessage({ toolId, messages }: {
|
|
|
94
96
|
/**
|
|
95
97
|
* Single read-only registration decision, shared by routes and inner dispatch.
|
|
96
98
|
* @returns Null for an explicitly read-only registration, otherwise host refusal text.
|
|
99
|
+
* @complexity O(n) for known tools; unknown-tool diagnostics add bounded name scoring and O(n log n) sorting.
|
|
97
100
|
*/
|
|
98
|
-
export declare function checkReadOnlyTool({ toolId, registry, messages }: ReadOnlyToolCheckRequired, _optional?: Record<string, never>): string | null;
|
|
101
|
+
export declare function checkReadOnlyTool({ toolId, input, registry, messages }: ReadOnlyToolCheckRequired, _optional?: Record<string, never>): string | null;
|
|
99
102
|
/**
|
|
100
103
|
* THE read-only dispatch decision for any tool id a consumer's own `ToolExecutor` composition is
|
|
101
104
|
* about to invoke — the outer call the route already checked, or a nested one a decorator issues
|
|
@@ -110,8 +113,9 @@ export declare function checkReadOnlyTool({ toolId, registry, messages }: ReadOn
|
|
|
110
113
|
* @param required.registry - Descriptors to resolve `toolId` against; `undefined` ⇒ refuse (an
|
|
111
114
|
* unverifiable constrained dispatch is refused, never waived, matching `checkReadOnlyTool`).
|
|
112
115
|
* @returns `null` when the dispatch may proceed, otherwise the refusal text to report.
|
|
113
|
-
* @complexity O(1) for an unconstrained principal; O(n) in registered tool count for a
|
|
114
|
-
*
|
|
116
|
+
* @complexity O(1) for an unconstrained principal; O(n) in registered tool count for a known tool,
|
|
117
|
+
* since `ToolRegistry` exposes enumeration rather than lookup by id. Unknown ids also incur
|
|
118
|
+
* the bounded similarity scoring and sorting cost documented by `toolCandidates`.
|
|
115
119
|
*/
|
|
116
120
|
export declare function refuseNonReadOnlyDispatch(required: ReadOnlyToolCheckRequired & {
|
|
117
121
|
readonly principal: Principal;
|
|
@@ -147,7 +151,8 @@ export declare function readOnlyRemedyRefusalMessage({ refusal, formatMessage }:
|
|
|
147
151
|
*
|
|
148
152
|
* @returns A drop-in `ToolExecutor`; `resumeConfirmation`/`cancel`/`getAuditRecord` delegate
|
|
149
153
|
* straight through.
|
|
150
|
-
* @complexity One extra property read per unconstrained call; one registry scan per constrained one
|
|
154
|
+
* @complexity One extra property read per unconstrained call; one registry scan per constrained one,
|
|
155
|
+
* plus bounded similarity scoring and sorting for an unknown id.
|
|
151
156
|
*/
|
|
152
157
|
export declare function withReadOnlyToolConstraint({ inner, registry, idGenerator, messages }: ReadOnlyToolConstraintRequired, _optional?: Record<string, never>): ToolExecutor;
|
|
153
158
|
//# sourceMappingURL=read-only-tools.d.ts.map
|