@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.0.2
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/LICENSE +201 -0
- package/NOTICE +1 -0
- package/README.md +124 -2
- package/THIRD_PARTY_NOTICES.md +66 -0
- package/ZCode-LICENSE +202 -0
- package/dist/index.mjs +4871 -0
- package/dist/native-command-plan.LICENSE.txt +221 -0
- package/dist/native-command-plan.json +16 -0
- package/dist/native-command-plan.mjs +8 -0
- package/dist/rpc-entry.mjs +5682 -0
- package/package.json +169 -4
- package/src/native/a2a-receipt-source.mjs +19 -0
- package/src/native/a2a-tools.mjs +517 -0
- package/src/native/activity-status.mjs +30 -0
- package/src/native/artifact-codec.mjs +481 -0
- package/src/native/artifact-store.mjs +1162 -0
- package/src/native/assistant-stream.mjs +76 -0
- package/src/native/background-command-port.mjs +95 -0
- package/src/native/browser-action-observation.mjs +43 -0
- package/src/native/browser-availability.mjs +22 -0
- package/src/native/browser-canvas-metrics.mjs +71 -0
- package/src/native/browser-dom.mjs +193 -0
- package/src/native/browser-playwright.mjs +1249 -0
- package/src/native/browser-vision.mjs +369 -0
- package/src/native/browser-workspace-policy.mjs +22 -0
- package/src/native/browser-workspace-protocol.mjs +95 -0
- package/src/native/browser-workspace-server.mjs +164 -0
- package/src/native/browser-workspace.mjs +355 -0
- package/src/native/captured-source.mjs +3 -0
- package/src/native/child-budget-allocation.mjs +87 -0
- package/src/native/child-capabilities.mjs +35 -0
- package/src/native/child-command-resource.mjs +20 -0
- package/src/native/child-history.mjs +188 -0
- package/src/native/child-merge.mjs +98 -0
- package/src/native/child-model-surface.mjs +19 -0
- package/src/native/child-observation.mjs +100 -0
- package/src/native/child-owner.mjs +620 -0
- package/src/native/child-publication.mjs +76 -0
- package/src/native/child-readers.mjs +366 -0
- package/src/native/child-tools.mjs +46 -0
- package/src/native/child-workspace-binding.mjs +29 -0
- package/src/native/child-workspace-tools.mjs +1 -0
- package/src/native/child-workspaces.mjs +655 -0
- package/src/native/children.mjs +365 -0
- package/src/native/command-plan-worker.mjs +14 -0
- package/src/native/command-plan.mjs +31 -0
- package/src/native/command-progress.mjs +64 -0
- package/src/native/command-shell.mjs +9 -0
- package/src/native/command-workspace-observation.mjs +36 -0
- package/src/native/component-tools.mjs +280 -0
- package/src/native/context-counter.mjs +133 -0
- package/src/native/context-markers.mjs +51 -0
- package/src/native/context-profile.mjs +72 -0
- package/src/native/context-status.mjs +27 -0
- package/src/native/conversation-command-control.mjs +133 -0
- package/src/native/delivery-timeline.mjs +39 -0
- package/src/native/dsh-background-ownership.mjs +102 -0
- package/src/native/dsh-managed-ownership.mjs +23 -0
- package/src/native/dsh-turn-contract.mjs +3 -0
- package/src/native/dsh-turn-ownership.mjs +54 -0
- package/src/native/dsh-turn-runtime.mjs +498 -0
- package/src/native/e2b-background-supervisor.mjs +347 -0
- package/src/native/e2b-background.mjs +368 -0
- package/src/native/e2b-command-state.mjs +40 -0
- package/src/native/e2b-command-tools.mjs +486 -0
- package/src/native/e2b-sandbox-owner.mjs +227 -0
- package/src/native/hook-executor.mjs +249 -0
- package/src/native/instruction-assembly.mjs +60 -0
- package/src/native/instruction-context.mjs +54 -0
- package/src/native/local-background-supervisor.mjs +108 -0
- package/src/native/local-background.mjs +207 -0
- package/src/native/local-command-evidence.mjs +57 -0
- package/src/native/local-command-policy-worker.mjs +197 -0
- package/src/native/local-command-policy.mjs +46 -0
- package/src/native/local-command-state.mjs +177 -0
- package/src/native/local-command-tools.mjs +302 -0
- package/src/native/local-execution-profile.mjs +36 -0
- package/src/native/local-output-policy.mjs +92 -0
- package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
- package/src/native/local-process-bin/darwin-x64/launcher +0 -0
- package/src/native/local-process-bin/linux-arm64/launcher +0 -0
- package/src/native/local-process-bin/linux-x64/launcher +0 -0
- package/src/native/local-process-launcher.c +183 -0
- package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
- package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
- package/src/native/local-process-licenses/NOTICE +21 -0
- package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
- package/src/native/local-process-manifest.json +32 -0
- package/src/native/local-process.mjs +246 -0
- package/src/native/local-resource-profile.mjs +32 -0
- package/src/native/memory-candidate.mjs +121 -0
- package/src/native/memory-extraction-run.mjs +60 -0
- package/src/native/memory-extraction-tools.mjs +116 -0
- package/src/native/memory-recall.mjs +65 -0
- package/src/native/memory-store.mjs +334 -0
- package/src/native/memory-tools.mjs +329 -0
- package/src/native/model-anthropic-cache.mjs +49 -0
- package/src/native/model-anthropic-tool-search.mjs +85 -0
- package/src/native/model-anthropic.mjs +753 -0
- package/src/native/model-chat-usage.mjs +24 -0
- package/src/native/model-inline-reasoning.mjs +111 -0
- package/src/native/model-media.mjs +174 -0
- package/src/native/model-normalize.mjs +228 -0
- package/src/native/model-openai-chat.mjs +561 -0
- package/src/native/model-port.mjs +361 -0
- package/src/native/model-provider-compat.mjs +129 -0
- package/src/native/model-replay.mjs +153 -0
- package/src/native/model-request.mjs +15 -0
- package/src/native/model-responses.mjs +1205 -0
- package/src/native/model-sse.mjs +171 -0
- package/src/native/model-surface-host.mjs +27 -0
- package/src/native/model-tool-search-policy.mjs +16 -0
- package/src/native/model-transport-observation.mjs +27 -0
- package/src/native/owned-child.mjs +114 -0
- package/src/native/owned-command.mjs +106 -0
- package/src/native/python-syntax-worker.mjs +60 -0
- package/src/native/python-syntax.mjs +50 -0
- package/src/native/read-interruption.mjs +16 -0
- package/src/native/result-stream.mjs +63 -0
- package/src/native/ruff-ast.mjs +95 -0
- package/src/native/run-observation.mjs +128 -0
- package/src/native/session-delivery-journal.mjs +96 -0
- package/src/native/session-delivery.mjs +434 -0
- package/src/native/shell-analysis.mjs +50 -0
- package/src/native/skill-disclosure.mjs +404 -0
- package/src/native/skill-events.mjs +164 -0
- package/src/native/skill-tools.mjs +813 -0
- package/src/native/source-page.mjs +73 -0
- package/src/native/sqlite-session-inbox.mjs +42 -0
- package/src/native/sqlite-store.mjs +617 -0
- package/src/native/tool-authority.mjs +99 -0
- package/src/native/tool-guidance.mjs +12 -0
- package/src/native/tool-observation.mjs +75 -0
- package/src/native/tool-states.mjs +14 -0
- package/src/native/verification-authority.mjs +37 -0
- package/src/native/web-tools.mjs +643 -0
- package/src/native/workspace-checkpoints.mjs +312 -0
- package/src/native/workspace-context.mjs +61 -0
- package/src/native/workspace-display.mjs +39 -0
- package/src/native/workspace-edit-match.mjs +19 -0
- package/src/native/workspace-edit-worker.mjs +15 -0
- package/src/native/workspace-editing.mjs +447 -0
- package/src/native/workspace-image.mjs +72 -0
- package/src/native/workspace-instructions.mjs +107 -0
- package/src/native/workspace-mutation-lock.mjs +74 -0
- package/src/native/workspace-patch-worker.mjs +17 -0
- package/src/native/workspace-patch.mjs +17 -0
- package/src/native/workspace-paths.mjs +225 -0
- package/src/native/workspace-state.mjs +272 -0
- package/src/native/workspace-subject-observer.mjs +84 -0
- package/src/native/workspace-tools.mjs +1740 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
// E2B remote background command ToolPort — Node Native tool definitions + factory.
|
|
2
|
+
//
|
|
3
|
+
// Key contracts:
|
|
4
|
+
// - Uses canonical backgroundCommandPolicy 'owned-command-launch/v1' and ref
|
|
5
|
+
// {taskId, sourceRunId, operationKey}.
|
|
6
|
+
// - A UNIFIED guard checks authority+drift+abort using the ORIGINAL snapshot.
|
|
7
|
+
// - After a side-effect: uncertain with known locator, NOT executed:false.
|
|
8
|
+
// - budget: EVERY return path is bounded so canonical(content) <= maxOutputBytes.
|
|
9
|
+
// If the budget cannot hold the minimal uncertain result with the FULL original
|
|
10
|
+
// identity (operationKey NOT truncated), the command is REJECTED before any
|
|
11
|
+
// ensure/start with executed:false — no remote side-effect, no fake success.
|
|
12
|
+
// A successful background launch preserves status=ok and backgroundCommandRef;
|
|
13
|
+
// only the presentation content is truncated. Identity fields are never
|
|
14
|
+
// truncated or dropped to fit a budget.
|
|
15
|
+
import { canonical, copy, invariant, scopeKey, toolInputRejection } from '@kingsoftcloud/agentkit-harness-core';
|
|
16
|
+
import { TERMINAL, delay } from './e2b-command-state.mjs';
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
|
|
19
|
+
export const E2B_COMMAND_TOOLS_CONTRACT = 'agentkit.native-e2b-command-tools/v1';
|
|
20
|
+
|
|
21
|
+
const schemas = { nullableSeconds: { anyOf: [{ type: 'integer', minimum: 1 }, { type: 'null' }] },
|
|
22
|
+
cursor: { type: 'integer', minimum: 0, maximum: Number.MAX_SAFE_INTEGER } };
|
|
23
|
+
|
|
24
|
+
function base(name, description, properties, required, mutating, timeoutMs) {
|
|
25
|
+
return { name, description, parameters: { type: 'object', properties, required, additionalProperties: false },
|
|
26
|
+
readOnly: !mutating, concurrentSafe: !mutating, permissions: mutating ? [name === 'stop_command' ? 'sandbox:cancel' : 'sandbox:execute'] : [],
|
|
27
|
+
timeoutMs, contextRole: 'none', observationRole: 'none', progressObservation: false, contextRetention: 'normal',
|
|
28
|
+
...(!mutating ? { recoveryPolicy: 'safe-read/v1' } : {}) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function e2bCommandToolDefinitions(foregroundTimeoutSeconds = 300) {
|
|
32
|
+
invariant(Number.isSafeInteger(foregroundTimeoutSeconds) && foregroundTimeoutSeconds > 0
|
|
33
|
+
&& foregroundTimeoutSeconds <= 86400, 'e2b_command_foreground_limit_invalid');
|
|
34
|
+
return [
|
|
35
|
+
base('sandbox_status', 'Report the bound E2B remote sandbox backend and its execution boundary.', {}, [], false, 10000),
|
|
36
|
+
{ ...base('run_command', 'Run a command inside the bound E2B remote sandbox. background=true returns an owned task_id for non-blocking reads via read_command_output; stop_command requests cancellation. The sandbox is remote — cwd is a remote path, not a host-local one. An unknown outcome is never re-launched.',
|
|
37
|
+
{ command: { type: 'string', minLength: 1 }, cwd: { anyOf: [{ type: 'string' }, { type: 'null' }] },
|
|
38
|
+
timeout: schemas.nullableSeconds, env: { anyOf: [{ type: 'object', additionalProperties: { type: 'string' } }, { type: 'null' }] },
|
|
39
|
+
background: { type: 'boolean' }, background_timeout: schemas.nullableSeconds },
|
|
40
|
+
['command'], true, foregroundTimeoutSeconds * 1000 + 20000), backgroundCommandPolicy: 'owned-command-launch/v1' },
|
|
41
|
+
base('read_command_output', 'Read an owned E2B command status and UTF-8 byte cursors. Reusing a cursor is read-only. Waiting never changes the command deadline. No sandbox creation or command restart.',
|
|
42
|
+
{ task_id: { type: 'string', pattern: '^[a-f0-9]{64}$' }, stdout_cursor: schemas.cursor,
|
|
43
|
+
stderr_cursor: schemas.cursor, max_bytes: { type: 'integer', minimum: 4, maximum: 65536 },
|
|
44
|
+
wait_seconds: { type: 'integer', minimum: 0, maximum: 600 } }, ['task_id'], false, 610000),
|
|
45
|
+
base('stop_command', 'Request cancellation of one E2B command launched by this Session. Read its output to confirm the final status; a cancel request is not proof of completion.',
|
|
46
|
+
{ task_id: { type: 'string', pattern: '^[a-f0-9]{64}$' } }, ['task_id'], true, 10000),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const E2B_CODE_TOOLS_CONTRACT = 'agentkit.native-e2b-code-tools/v1';
|
|
51
|
+
export const DSH_E2B_CODE_TOOLS_CONTRACT = 'agentkit.dsh-e2b-code-tools/v1';
|
|
52
|
+
export function e2bCodeToolDefinitions(timeoutSeconds = 300, name = 'run_code') {
|
|
53
|
+
e2bCommandToolDefinitions(timeoutSeconds); // Same Host timeout bounds.
|
|
54
|
+
invariant(['run_code', 'sandbox_run_code'].includes(name), 'e2b_code_name_invalid');
|
|
55
|
+
return [base(name, 'Execute a Python, JavaScript or Bash snippet in an isolated remote sandbox. Requires E2B; never runs a snippet on the Host. Unknown outcomes require reconciliation of the original task.',
|
|
56
|
+
{ code: { type: 'string', minLength: 1 }, language: { type: 'string' }, timeout: schemas.nullableSeconds,
|
|
57
|
+
env: { anyOf: [{ type: 'object', additionalProperties: { type: 'string' } }, { type: 'null' }] } },
|
|
58
|
+
['code'], true, timeoutSeconds * 1000 + 20000)];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function snippetCommand(args, before) {
|
|
62
|
+
const aliases = { py: 'python', python3: 'python', js: 'javascript', node: 'javascript', nodejs: 'javascript', sh: 'bash', shell: 'bash' };
|
|
63
|
+
const selected = (args.language || 'python').trim().toLowerCase(), language = aliases[selected] ?? selected;
|
|
64
|
+
const runner = { python: ['.py', 'python'], javascript: ['.js', 'node'], bash: ['.sh', 'bash'] }[language];
|
|
65
|
+
if (!runner) return null;
|
|
66
|
+
const path = `/tmp/ksadk-run-code-${createHash('sha256').update(before.body).digest('hex')}${runner[0]}`;
|
|
67
|
+
return { path, language, command: `${runner[1]} '${path}'`,
|
|
68
|
+
launch: `chmod 600 '${path}' && exec ${runner[1]} '${path}'` };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const commandOutput = result => ({ status: result.status, stdout: result.stdout, stderr: result.stderr,
|
|
72
|
+
exit_code: result.exitCode, stdout_cursor: result.stdoutCursor, stderr_cursor: result.stderrCursor,
|
|
73
|
+
cancel_requested: result.cancelRequested, output_may_be_truncated: result.outputMayBeTruncated,
|
|
74
|
+
evidence_incomplete: result.evidenceIncomplete, observed_exit_code: result.observedExitCode,
|
|
75
|
+
...(result.outcomeReason ? { outcome_reason: result.outcomeReason } : {}) });
|
|
76
|
+
const errorResult = (code, extra = {}) => ({ status: 'error', content: { ok: false, error_type: code, ...extra } });
|
|
77
|
+
function utf8Preview(text, maxBytes) {
|
|
78
|
+
return new TextDecoder('utf8', { ignoreBOM: true }).decode(Buffer.from(text).subarray(0, maxBytes), { stream: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function syncValue(value) {
|
|
82
|
+
if (value && typeof value.then === 'function') {
|
|
83
|
+
Promise.resolve(value).catch(() => {});
|
|
84
|
+
invariant(false, 'e2b_command_sync_contract_invalid');
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function abortable(promise, signal) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const aborted = () => { signal.removeEventListener('abort', aborted); reject(signal.reason); };
|
|
92
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
93
|
+
Promise.resolve(promise).then(value => { signal.removeEventListener('abort', aborted); resolve(value); },
|
|
94
|
+
error => { signal.removeEventListener('abort', aborted); reject(error); });
|
|
95
|
+
if (signal.aborted) aborted();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createNativeE2BCommandTools(options = {}) {
|
|
100
|
+
return createNativeTools(options, false);
|
|
101
|
+
}
|
|
102
|
+
export function createNativeE2BCodeTools(options = {}) {
|
|
103
|
+
return createNativeTools(options, true);
|
|
104
|
+
}
|
|
105
|
+
function createNativeTools(options, codeOnly) {
|
|
106
|
+
invariant(options.scope, 'e2b_command_scope_required');
|
|
107
|
+
const fixedScope = copy(options.scope); scopeKey(fixedScope);
|
|
108
|
+
return createCommandTools(options, { codeOnly, contract: codeOnly ? E2B_CODE_TOOLS_CONTRACT : E2B_COMMAND_TOOLS_CONTRACT, fields: { scope: fixedScope },
|
|
109
|
+
identity(request) {
|
|
110
|
+
invariant(request.scope && canonical(request.scope) === canonical(fixedScope), 'e2b_command_scope_unbound');
|
|
111
|
+
invariant(['runId','turnId','buildId','taskChainId'].every(k => typeof request[k] === 'string' && request[k].trim()),
|
|
112
|
+
'e2b_command_identity_required');
|
|
113
|
+
invariant(request.parentRunId === null || typeof request.parentRunId === 'string' && request.parentRunId.trim(),
|
|
114
|
+
'e2b_command_parent_run_id_invalid');
|
|
115
|
+
invariant(Number.isSafeInteger(request.executionFence) && request.executionFence > 0, 'e2b_command_fence_required');
|
|
116
|
+
return { scope: copy(request.scope), runId: request.runId, turnId: request.turnId, buildId: request.buildId,
|
|
117
|
+
parentRunId: request.parentRunId, taskChainId: request.taskChainId, executionFence: request.executionFence };
|
|
118
|
+
}, backgroundRef: (before, taskId) => ({ taskId, sourceRunId: before.identity.runId, operationKey: before.operationKey }) });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const DSH_E2B_COMMAND_TOOLS_CONTRACT = 'agentkit.dsh-e2b-command-tools/v1';
|
|
122
|
+
/** Share command semantics with Native, using real DSH ownership only. */
|
|
123
|
+
export function createDshE2BCommandTools(options = {}) {
|
|
124
|
+
return createDshTools(options, false);
|
|
125
|
+
}
|
|
126
|
+
export function createDshE2BCodeTools(options = {}) {
|
|
127
|
+
return createDshTools(options, true);
|
|
128
|
+
}
|
|
129
|
+
function createDshTools(options, codeOnly) {
|
|
130
|
+
const { agentId } = options;
|
|
131
|
+
invariant(typeof agentId === 'string' && agentId.trim(), 'e2b_command_agent_required');
|
|
132
|
+
return createCommandTools(options, { codeOnly, contract: codeOnly ? DSH_E2B_CODE_TOOLS_CONTRACT : DSH_E2B_COMMAND_TOOLS_CONTRACT, fields: { agentId },
|
|
133
|
+
identity(request) {
|
|
134
|
+
invariant(request.agentId === agentId && ['scope','runId','turnId','buildId','taskChainId','parentRunId','executionFence']
|
|
135
|
+
.every(key => request[key] === undefined), 'e2b_command_identity_required');
|
|
136
|
+
return { agentId };
|
|
137
|
+
}, backgroundRef: (before, taskId) => ({ taskId, agentId, sessionId: before.sessionId,
|
|
138
|
+
sourceCallId: before.callId, operationKey: before.operationKey }) });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createCommandTools({ sessionId, owner, storeFactory,
|
|
142
|
+
authorize, assertAuthority, resolveOwnedCommand, artifacts, publishSnippet, maxOutputBytes = 24000,
|
|
143
|
+
allowInternetAccess = false, foregroundTimeoutSeconds = 300, backgroundTimeoutSeconds = 300 }, ownership) {
|
|
144
|
+
invariant(typeof sessionId === 'string' && !!sessionId.trim(), 'e2b_command_scope_required');
|
|
145
|
+
invariant(Number.isSafeInteger(maxOutputBytes) && maxOutputBytes >= 256, 'e2b_command_max_output_bytes_invalid');
|
|
146
|
+
invariant(owner && typeof owner.ensure === 'function' && typeof owner.inspect === 'function', 'e2b_command_owner_required');
|
|
147
|
+
invariant(typeof storeFactory === 'function', 'e2b_command_store_factory_required');
|
|
148
|
+
invariant(!ownership.codeOnly || typeof publishSnippet === 'function', 'e2b_code_publication_required');
|
|
149
|
+
invariant(typeof authorize === 'function', 'e2b_command_authorize_required');
|
|
150
|
+
invariant(typeof assertAuthority === 'function', 'e2b_command_assert_authority_required');
|
|
151
|
+
invariant(typeof resolveOwnedCommand === 'function', 'e2b_command_resolve_owned_required');
|
|
152
|
+
invariant(Number.isSafeInteger(backgroundTimeoutSeconds) && backgroundTimeoutSeconds > 0
|
|
153
|
+
&& backgroundTimeoutSeconds <= 86400, 'e2b_command_background_limit_invalid');
|
|
154
|
+
const definitions = ownership.codeOnly ? e2bCodeToolDefinitions(foregroundTimeoutSeconds, ownership.fields.agentId ? 'sandbox_run_code' : 'run_code')
|
|
155
|
+
: e2bCommandToolDefinitions(foregroundTimeoutSeconds);
|
|
156
|
+
const formal = new Map(definitions.map(d => [d.name, copy(d)]));
|
|
157
|
+
let store = null;
|
|
158
|
+
|
|
159
|
+
function snapshot(request) {
|
|
160
|
+
invariant(request && request.call && typeof request.call.name === 'string' && typeof request.call.callId === 'string' && request.call.callId.trim(),
|
|
161
|
+
'e2b_command_request_unbound');
|
|
162
|
+
invariant(typeof request.operationKey === 'string' && request.operationKey.trim(), 'e2b_command_operation_key_required');
|
|
163
|
+
const def = formal.get(request.call.name);
|
|
164
|
+
invariant(def, 'e2b_command_tool_unbound');
|
|
165
|
+
invariant(request.definition && canonical(request.definition) === canonical(def), 'e2b_command_definition_unbound');
|
|
166
|
+
invariant(request.signal instanceof AbortSignal, 'e2b_command_signal_unbound');
|
|
167
|
+
invariant(typeof request.sessionId === 'string' && request.sessionId === sessionId, 'e2b_command_session_unbound');
|
|
168
|
+
const identity = ownership.identity(request);
|
|
169
|
+
const { signal: _signal, ...body } = request;
|
|
170
|
+
return { body: canonical(body), signal: request.signal,
|
|
171
|
+
identity, sessionId: request.sessionId, operationKey: request.operationKey,
|
|
172
|
+
callId: request.call.callId };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function drifted(request, before) {
|
|
176
|
+
if (before.signal !== request.signal) return true;
|
|
177
|
+
const { signal: _signal, ...body } = request;
|
|
178
|
+
return before.body !== canonical(body);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function checkAuthority(request) {
|
|
182
|
+
const result = assertAuthority(request);
|
|
183
|
+
if (result === true) return true;
|
|
184
|
+
if (result && typeof result.then === 'function')
|
|
185
|
+
result.then(() => {}, () => {});
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The full original identity — NEVER truncated or dropped. This is the
|
|
190
|
+
// minimum that must fit in maxOutputBytes for any uncertain/error result
|
|
191
|
+
// to be representable. If it does not fit, the command is rejected before
|
|
192
|
+
// any side-effect.
|
|
193
|
+
function originLocator(before, sandboxId, taskId) {
|
|
194
|
+
return { ...copy(before.identity), sessionId: before.sessionId, operationKey: before.operationKey, callId: before.callId,
|
|
195
|
+
...(sandboxId ? { sandbox_id: sandboxId } : {}),
|
|
196
|
+
...(taskId ? { task_id: taskId } : {}) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Reserve complete ownership plus the largest validated remote identifiers
|
|
200
|
+
// and fixed receipt fields BEFORE any owner/command effect. A rejected call
|
|
201
|
+
// has no external locator to preserve; its full request remains in the intent.
|
|
202
|
+
function minimalUncertainBudget(before) {
|
|
203
|
+
const sample = { ok: false, error_type: 'e2b_command_evidence_incomplete',
|
|
204
|
+
evidence_incomplete: true, observed_result: 'output_limit',
|
|
205
|
+
...originLocator(before, 's'.repeat(128), 'a'.repeat(64)) };
|
|
206
|
+
return Buffer.byteLength(canonical(sample)) + 512;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function uncertainResult(known, before, reason, sandboxId, taskId) {
|
|
210
|
+
const loc = originLocator(before, sandboxId ?? known?.content?.sandbox_id, taskId ?? known?.content?.task_id);
|
|
211
|
+
const code = typeof reason === 'string' && /^[a-z0-9_]{1,96}$/.test(reason)
|
|
212
|
+
? reason : 'e2b_command_evidence_incomplete';
|
|
213
|
+
const content = { ok: false, error_type: code, evidence_incomplete: true,
|
|
214
|
+
observed_result: TERMINAL.has(known?.content?.status) ? known.content.status : null, ...loc };
|
|
215
|
+
const bgRef = known?.backgroundCommandRef;
|
|
216
|
+
return { status: 'uncertain', content, ...(bgRef ? { backgroundCommandRef: bgRef } : {}) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Enforce budget on shrinkable content fields only (command, stdout, stderr).
|
|
220
|
+
// Identity fields (operationKey, runId, scope, etc.) are NEVER truncated.
|
|
221
|
+
function enforceBudget(content) {
|
|
222
|
+
const keys = ['command', 'stdout', 'stderr', 'instruction', 'outcome_reason'];
|
|
223
|
+
for (let iter = 0; iter < 40; iter++) {
|
|
224
|
+
if (Buffer.byteLength(canonical(content)) <= maxOutputBytes) return true;
|
|
225
|
+
let largest = null, largestLen = 0;
|
|
226
|
+
for (const key of keys) {
|
|
227
|
+
const len = typeof content[key] === 'string' ? Buffer.byteLength(content[key]) : 0;
|
|
228
|
+
if (len > largestLen) { largest = key; largestLen = len; }
|
|
229
|
+
}
|
|
230
|
+
if (!largest || largestLen <= 1) { if (largest) content[largest] = ''; break; }
|
|
231
|
+
content[largest] = utf8Preview(content[largest], Math.floor(largestLen / 2));
|
|
232
|
+
}
|
|
233
|
+
return Buffer.byteLength(canonical(content)) <= maxOutputBytes;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Every return is bounded. Admission reserves the complete fixed receipt;
|
|
237
|
+
// only presentation text may be shortened after dispatch.
|
|
238
|
+
function boundResult(result) {
|
|
239
|
+
if (Buffer.byteLength(canonical(result.content)) <= maxOutputBytes) return result;
|
|
240
|
+
const content = { ...result.content };
|
|
241
|
+
if (enforceBudget(content)) return { ...result, content };
|
|
242
|
+
invariant(false, 'e2b_command_result_budget_contract_violation');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function budget(result, request, before, sandboxId, taskId) {
|
|
246
|
+
if (request.resultGovernance === 'runtime-artifacts/v1') return guard(request, before, true, result) ?? result;
|
|
247
|
+
const body = canonical(result.content);
|
|
248
|
+
if (Buffer.byteLength(body) <= maxOutputBytes) return result;
|
|
249
|
+
const guardResult = guard(request, before, true, result);
|
|
250
|
+
if (guardResult) return guardResult;
|
|
251
|
+
const content = { ...result.content };
|
|
252
|
+
if (typeof artifacts?.write === 'function') {
|
|
253
|
+
try {
|
|
254
|
+
const ref = await abortable(artifacts.write({ ...request, operationKey: `${request.operationKey}:command-output`,
|
|
255
|
+
content: body, mediaType: 'application/json', metadata: { toolName: request.call.name, sessionId } }), before.signal);
|
|
256
|
+
const guardResult2 = guard(request, before, true, result);
|
|
257
|
+
if (guardResult2) return guardResult2;
|
|
258
|
+
content.output_budget = { truncated: true, artifact_uri: ref.uri, size_bytes: ref.sizeBytes };
|
|
259
|
+
if (enforceBudget(content)) return { ...result, content, artifactRefs: [...result.artifactRefs ?? [], ref] };
|
|
260
|
+
content.output_budget = { truncated: true };
|
|
261
|
+
if (enforceBudget(content)) return { ...result, content, artifactRefs: [...result.artifactRefs ?? [], ref] };
|
|
262
|
+
} catch {
|
|
263
|
+
const guardResult3 = guard(request, before, true, result);
|
|
264
|
+
if (guardResult3) return guardResult3;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
content.output_budget = { truncated: true };
|
|
268
|
+
if (enforceBudget(content)) return { ...result, content };
|
|
269
|
+
return boundResult(result);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function guard(request, before, dispatched, known) {
|
|
273
|
+
before.signal.throwIfAborted();
|
|
274
|
+
if (drifted(request, before)) return dispatched
|
|
275
|
+
? uncertainResult(known, before, 'e2b_command_request_drifted')
|
|
276
|
+
: { status: 'error', content: { code: 'e2b_command_request_drifted', executed: false } };
|
|
277
|
+
if (!checkAuthority(request)) return dispatched
|
|
278
|
+
? uncertainResult(known, before, 'e2b_command_authority_lost')
|
|
279
|
+
: { status: 'error', content: { error_type: 'e2b_command_authority_unproven', executed: false } };
|
|
280
|
+
// The synchronous authority callback is also an external boundary.
|
|
281
|
+
before.signal.throwIfAborted();
|
|
282
|
+
if (drifted(request, before)) return dispatched
|
|
283
|
+
? uncertainResult(known, before, 'e2b_command_request_drifted')
|
|
284
|
+
: { status: 'error', content: { code: 'e2b_command_request_drifted', executed: false } };
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function execute(request) {
|
|
289
|
+
let dispatched = false, known, sandboxId, taskId;
|
|
290
|
+
let before;
|
|
291
|
+
try {
|
|
292
|
+
before = snapshot(request);
|
|
293
|
+
const minBudget = minimalUncertainBudget(before);
|
|
294
|
+
if (minBudget > maxOutputBytes) return errorResult('e2b_command_budget_below_minimum_identity',
|
|
295
|
+
{ executed: false, required_bytes: minBudget, max_output_bytes: maxOutputBytes });
|
|
296
|
+
request.signal.throwIfAborted();
|
|
297
|
+
const rejection = toolInputRejection(formal.get(request.call.name), request.call);
|
|
298
|
+
if (rejection) return Buffer.byteLength(canonical(rejection)) <= maxOutputBytes
|
|
299
|
+
? { status: 'error', content: rejection } : errorResult('e2b_command_arguments_invalid', { executed: false });
|
|
300
|
+
request.signal.throwIfAborted();
|
|
301
|
+
|
|
302
|
+
const decision = await abortable(authorize(request), before.signal);
|
|
303
|
+
const guardResult = guard(request, before, false, null);
|
|
304
|
+
if (guardResult) return guardResult;
|
|
305
|
+
if (decision !== 'allow') return decision?.status === 'approval_required' && typeof decision.interactionId === 'string'
|
|
306
|
+
? decision : errorResult('policy_denied');
|
|
307
|
+
|
|
308
|
+
if (request.call.name === 'sandbox_status') {
|
|
309
|
+
const guardResult = guard(request, before, false, null);
|
|
310
|
+
if (guardResult) return guardResult;
|
|
311
|
+
const inspect = syncValue(owner.inspect());
|
|
312
|
+
invariant(!inspect || (inspect.sandboxId == null || typeof inspect.sandboxId === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(inspect.sandboxId))
|
|
313
|
+
&& ['absent','creating','ready','closing','closed','unknown'].includes(inspect.phase), 'e2b_owner_status_invalid');
|
|
314
|
+
const afterInspect = guard(request, before, false, null); if (afterInspect) return afterInspect;
|
|
315
|
+
return { status: 'ok', content: { ok: true, backend: 'e2b', enabled: true, isolated: true,
|
|
316
|
+
sandbox_id: inspect?.sandboxId ?? null, sandbox_phase: inspect?.phase ?? 'absent',
|
|
317
|
+
allow_internet_access: allowInternetAccess,
|
|
318
|
+
boundary: 'Remote E2B sandbox. The Host filesystem is not shared.',
|
|
319
|
+
contracts: [ownership.contract] } };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (request.call.name === 'read_command_output' || request.call.name === 'stop_command') {
|
|
323
|
+
const args = request.call.arguments;
|
|
324
|
+
const guardResult = guard(request, before, false, null);
|
|
325
|
+
if (guardResult) return guardResult;
|
|
326
|
+
const owned = syncValue(resolveOwnedCommand(request, args.task_id));
|
|
327
|
+
invariant(owned?.backend === 'e2b' && owned.ref?.taskId === args.task_id, 'e2b_command_backend_mismatch');
|
|
328
|
+
const afterResolve = guard(request, before, false, null); if (afterResolve) return afterResolve;
|
|
329
|
+
taskId = args.task_id;
|
|
330
|
+
if (!store) store = syncValue(storeFactory({ ...copy(ownership.fields), sessionId }));
|
|
331
|
+
invariant(typeof store.commandId === 'function'
|
|
332
|
+
&& syncValue(store.commandId(owned.ref.operationKey)) === taskId, 'e2b_command_owned_operation_mismatch');
|
|
333
|
+
const handle = syncValue(store.connect(owned.ref.taskId));
|
|
334
|
+
const afterConnect = guard(request, before, false, null); if (afterConnect) return afterConnect;
|
|
335
|
+
known = { content: { task_id: args.task_id, ...originLocator(before, undefined, taskId) } };
|
|
336
|
+
if (request.call.name === 'stop_command') {
|
|
337
|
+
dispatched = true;
|
|
338
|
+
const cancelOutput = syncValue(handle.requestCancel());
|
|
339
|
+
const guardResult = guard(request, before, dispatched, known);
|
|
340
|
+
if (guardResult) return guardResult;
|
|
341
|
+
const uncertain = cancelOutput.status === 'unknown' || cancelOutput.evidenceIncomplete;
|
|
342
|
+
known = { status: uncertain ? 'uncertain' : 'ok', content: { ok: !uncertain, task_id: args.task_id,
|
|
343
|
+
...commandOutput(cancelOutput), ...originLocator(before, undefined, taskId) } };
|
|
344
|
+
} else {
|
|
345
|
+
const until = performance.now() + (args.wait_seconds ?? 0) * 1000;
|
|
346
|
+
let interval = 50;
|
|
347
|
+
while (!TERMINAL.has(syncValue(handle.readOutput()).status) && performance.now() < until) {
|
|
348
|
+
const guardResult = guard(request, before, false, known);
|
|
349
|
+
if (guardResult) return guardResult;
|
|
350
|
+
await delay(Math.min(interval, Math.max(0, until - performance.now())));
|
|
351
|
+
interval = Math.min(1000, interval * 2);
|
|
352
|
+
}
|
|
353
|
+
const guardResult = guard(request, before, false, known);
|
|
354
|
+
if (guardResult) return guardResult;
|
|
355
|
+
const output = handle.readOutput({ stdoutCursor: args.stdout_cursor ?? 0,
|
|
356
|
+
stderrCursor: args.stderr_cursor ?? 0, maxBytes: args.max_bytes ?? 65536 });
|
|
357
|
+
const uncertain = output.status === 'unknown' || output.evidenceIncomplete;
|
|
358
|
+
known = { status: uncertain ? 'uncertain' : 'ok', content: { ok: !uncertain, task_id: args.task_id,
|
|
359
|
+
...commandOutput(output), ...originLocator(before, undefined, taskId) } };
|
|
360
|
+
}
|
|
361
|
+
const afterRead = guard(request, before, dispatched, known); if (afterRead) return afterRead;
|
|
362
|
+
return await budget(known, request, before, undefined, taskId);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// The original durable tool intent surrounds both source publication
|
|
366
|
+
// and its supervised execution. Never put source in a shell argument:
|
|
367
|
+
// even a valid large snippet can exceed the remote OS argv limit.
|
|
368
|
+
const args = request.call.arguments;
|
|
369
|
+
const snippet = ownership.codeOnly ? snippetCommand(args, before) : null;
|
|
370
|
+
if (ownership.codeOnly && !snippet) return errorResult('unsupported_language', { executed: false });
|
|
371
|
+
const command = snippet?.launch ?? args.command.trim();
|
|
372
|
+
if (!command) return errorResult('command_required');
|
|
373
|
+
if (!args.background && args.background_timeout != null) return errorResult('background_timeout_requires_background');
|
|
374
|
+
|
|
375
|
+
known = { content: { ...originLocator(before) } };
|
|
376
|
+
const ownerRequest = { ...request, definition: formal.get(request.call.name) };
|
|
377
|
+
const guardResult0 = guard(request, before, false, known);
|
|
378
|
+
if (guardResult0) return guardResult0;
|
|
379
|
+
let ensured;
|
|
380
|
+
dispatched = true;
|
|
381
|
+
try { ensured = await abortable(owner.ensure(ownerRequest), before.signal); }
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (error?.uncertain) return uncertainResult(known, before, 'e2b_sandbox_outcome_unknown');
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
invariant(typeof ensured?.sandboxId === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(ensured.sandboxId), 'e2b_sandbox_identity_invalid');
|
|
387
|
+
sandboxId = ensured.sandboxId;
|
|
388
|
+
known = { content: { ...originLocator(before, sandboxId) } };
|
|
389
|
+
const guardResult1 = guard(request, before, dispatched, known);
|
|
390
|
+
if (guardResult1) return guardResult1;
|
|
391
|
+
|
|
392
|
+
if (!store) store = syncValue(storeFactory({ ...copy(ownership.fields), sessionId, sandboxId }));
|
|
393
|
+
const suppliedTimeout = args.background ? (args.background_timeout ?? backgroundTimeoutSeconds)
|
|
394
|
+
: (args.timeout ?? foregroundTimeoutSeconds);
|
|
395
|
+
const maximum = args.background ? 86400 : foregroundTimeoutSeconds;
|
|
396
|
+
const timeoutSeconds = Number(suppliedTimeout > maximum ? maximum : suppliedTimeout);
|
|
397
|
+
const afterFactory = guard(request, before, dispatched, known); if (afterFactory) return afterFactory;
|
|
398
|
+
if (typeof store.commandId === 'function') {
|
|
399
|
+
taskId = syncValue(store.commandId(before.operationKey));
|
|
400
|
+
invariant(typeof taskId === 'string' && /^[a-f0-9]{64}$/.test(taskId), 'e2b_command_identity_invalid');
|
|
401
|
+
known = { content: { ...originLocator(before, sandboxId, taskId) } };
|
|
402
|
+
const afterId = guard(request, before, dispatched, known); if (afterId) return afterId;
|
|
403
|
+
}
|
|
404
|
+
if (snippet) {
|
|
405
|
+
const assertCurrent = () => { invariant(!guard(request, before, dispatched, known), 'e2b_code_authority_lost'); return true; };
|
|
406
|
+
await abortable(publishSnippet({ request, sandboxId, path: snippet.path, source: args.code, assertCurrent }), before.signal);
|
|
407
|
+
const afterPublish = guard(request, before, dispatched, known); if (afterPublish) return afterPublish;
|
|
408
|
+
}
|
|
409
|
+
const handle = await abortable(store.start({ operationKey: before.operationKey, command,
|
|
410
|
+
cwd: args.cwd ?? null, env: args.env ?? null, timeoutSeconds,
|
|
411
|
+
assertCurrent: () => {
|
|
412
|
+
const rejected = guard(request, before, dispatched, known);
|
|
413
|
+
invariant(!rejected, 'e2b_command_authority_lost'); return true;
|
|
414
|
+
} }), before.signal);
|
|
415
|
+
invariant(typeof handle?.commandId === 'string' && /^[a-f0-9]{64}$/.test(handle.commandId), 'e2b_command_identity_invalid');
|
|
416
|
+
invariant(taskId === undefined || taskId === handle.commandId, 'e2b_command_identity_changed');
|
|
417
|
+
taskId = handle.commandId;
|
|
418
|
+
known = { content: { ...originLocator(before, sandboxId, taskId) },
|
|
419
|
+
...(args.background ? { backgroundCommandRef: ownership.backgroundRef(before, taskId) } : {}) };
|
|
420
|
+
const guardResult2 = guard(request, before, dispatched, known);
|
|
421
|
+
if (guardResult2) return guardResult2;
|
|
422
|
+
|
|
423
|
+
if (args.background) {
|
|
424
|
+
const observed = syncValue(handle.readOutput());
|
|
425
|
+
const uncertain = observed.status === 'unknown' || observed.evidenceIncomplete;
|
|
426
|
+
known = { status: uncertain ? 'uncertain' : 'ok',
|
|
427
|
+
content: { ok: !uncertain, backend: 'sandbox/e2b', sandbox_id: sandboxId,
|
|
428
|
+
task_id: taskId, ...commandOutput(observed), ...originLocator(before, sandboxId, taskId),
|
|
429
|
+
process_timeout_seconds: timeoutSeconds },
|
|
430
|
+
backgroundCommandRef: ownership.backgroundRef(before, taskId) };
|
|
431
|
+
} else {
|
|
432
|
+
const until = performance.now() + timeoutSeconds * 1000 + 15000;
|
|
433
|
+
let output;
|
|
434
|
+
while (!TERMINAL.has((output = handle.readOutput()).status) && performance.now() < until) {
|
|
435
|
+
const guardResult = guard(request, before, dispatched, known);
|
|
436
|
+
if (guardResult) return guardResult;
|
|
437
|
+
await delay(50);
|
|
438
|
+
}
|
|
439
|
+
const guardResult3 = guard(request, before, dispatched, known);
|
|
440
|
+
if (guardResult3) return guardResult3;
|
|
441
|
+
// The model sees a bounded preview, but its artifact must contain all
|
|
442
|
+
// retained foreground output, not just the first 64 KiB cursor window.
|
|
443
|
+
const stdout = [], stderr = []; let stdoutCursor = 0, stderrCursor = 0, bytes = 0;
|
|
444
|
+
for (;;) {
|
|
445
|
+
const rejected = guard(request, before, dispatched, known); if (rejected) return rejected;
|
|
446
|
+
output = syncValue(handle.readOutput({ stdoutCursor, stderrCursor, maxBytes: 65536 }));
|
|
447
|
+
invariant(typeof output.stdout === 'string' && typeof output.stderr === 'string', 'e2b_command_output_invalid');
|
|
448
|
+
bytes += Buffer.byteLength(output.stdout) + Buffer.byteLength(output.stderr);
|
|
449
|
+
invariant(bytes <= 16 * 1024 * 1024, 'e2b_command_retained_output_too_large');
|
|
450
|
+
stdout.push(output.stdout); stderr.push(output.stderr);
|
|
451
|
+
if (!output.outputMayBeTruncated) break;
|
|
452
|
+
invariant(Number.isSafeInteger(output.stdoutCursor) && output.stdoutCursor >= stdoutCursor
|
|
453
|
+
&& Number.isSafeInteger(output.stderrCursor) && output.stderrCursor >= stderrCursor
|
|
454
|
+
&& (output.stdoutCursor > stdoutCursor || output.stderrCursor > stderrCursor), 'e2b_command_output_cursor_stalled');
|
|
455
|
+
stdoutCursor = output.stdoutCursor; stderrCursor = output.stderrCursor;
|
|
456
|
+
}
|
|
457
|
+
output = { ...output, stdout: stdout.join(''), stderr: stderr.join('') };
|
|
458
|
+
const uncertain = output.status === 'unknown' || output.evidenceIncomplete
|
|
459
|
+
|| !TERMINAL.has(output.status)
|
|
460
|
+
|| (['timed_out', 'canceled'].includes(output.status) && output.exitCode === null);
|
|
461
|
+
known = { status: uncertain ? 'uncertain' : 'ok',
|
|
462
|
+
content: { ok: !uncertain, status: output.status, backend: 'sandbox/e2b', sandbox_id: sandboxId, command,
|
|
463
|
+
...(snippet ? { execution_model: 'snippet_runner', language: snippet.language, path: snippet.path, command: snippet.command } : {}),
|
|
464
|
+
task_id: taskId, ...originLocator(before, sandboxId, taskId),
|
|
465
|
+
stdout: output.stdout, stderr: output.stderr,
|
|
466
|
+
exit_code: uncertain ? null : output.exitCode,
|
|
467
|
+
observed_exit_code: uncertain ? output.exitCode : null,
|
|
468
|
+
evidence_incomplete: uncertain,
|
|
469
|
+
...(output.outcomeReason ? { outcome_reason: output.outcomeReason } : {}) } };
|
|
470
|
+
}
|
|
471
|
+
const afterOutput = guard(request, before, dispatched, known); if (afterOutput) return afterOutput;
|
|
472
|
+
return await budget(known, request, before, sandboxId, taskId);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
if (!before) return errorResult('e2b_command_request_invalid', { executed: false });
|
|
475
|
+
const originalAborted = before.signal.aborted;
|
|
476
|
+
return dispatched
|
|
477
|
+
? uncertainResult(known, before, originalAborted ? 'e2b_command_aborted' : (error?.code ?? 'e2b_command_evidence_incomplete'), sandboxId, taskId)
|
|
478
|
+
: errorResult(originalAborted ? 'canceled' : 'e2b_command_error', { executed: false });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return Object.freeze({ definitions: copy(definitions), async execute(request) {
|
|
483
|
+
const result = await execute(request);
|
|
484
|
+
return result.content === undefined || request.resultGovernance === 'runtime-artifacts/v1' ? result : boundResult(result);
|
|
485
|
+
}, contract: ownership.contract });
|
|
486
|
+
}
|