@bolt-foundry/gambit-core 0.8.1 → 0.8.5-rc.3
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/README.md +60 -34
- package/cards/context.card.md +5 -5
- package/cards/generate-test-input.card.md +12 -0
- package/{script/deps/jsr.io/@std/collections/1.1.4 → esm/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
- package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
- package/esm/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
- package/esm/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
- package/esm/mod.d.ts +20 -10
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +11 -5
- package/esm/schemas/graders/contexts/conversation.d.ts +22 -0
- package/esm/schemas/graders/contexts/conversation.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation.js +17 -0
- package/esm/schemas/graders/contexts/conversation.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation.zod.js +2 -0
- package/esm/schemas/graders/contexts/conversation_tools.d.ts +31 -0
- package/esm/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation_tools.js +25 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/conversation_tools.zod.js +2 -0
- package/esm/schemas/graders/contexts/tools.d.ts +4 -0
- package/esm/schemas/graders/contexts/tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/tools.js +3 -0
- package/esm/schemas/graders/contexts/tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/tools.zod.js +2 -0
- package/esm/schemas/graders/contexts/turn.d.ts +10 -0
- package/esm/schemas/graders/contexts/turn.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn.js +8 -0
- package/esm/schemas/graders/contexts/turn.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn.zod.js +2 -0
- package/esm/schemas/graders/contexts/turn_tools.d.ts +32 -0
- package/esm/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn_tools.js +28 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
- package/esm/schemas/graders/contexts/turn_tools.zod.js +2 -0
- package/esm/schemas/graders/grader_output.d.ts +10 -0
- package/esm/schemas/graders/grader_output.d.ts.map +1 -0
- package/esm/schemas/graders/grader_output.js +8 -0
- package/esm/schemas/graders/grader_output.zod.d.ts +3 -0
- package/esm/schemas/graders/grader_output.zod.d.ts.map +1 -0
- package/esm/schemas/graders/grader_output.zod.js +2 -0
- package/esm/schemas/graders/respond.d.ts +12 -0
- package/esm/schemas/graders/respond.d.ts.map +1 -0
- package/esm/schemas/graders/respond.js +10 -0
- package/esm/schemas/graders/respond.zod.d.ts +3 -0
- package/esm/schemas/graders/respond.zod.d.ts.map +1 -0
- package/esm/schemas/graders/respond.zod.js +2 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.js +5 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_input_optional.zod.js +2 -0
- package/esm/schemas/scenarios/plain_chat_output.d.ts +5 -0
- package/esm/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_output.js +4 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
- package/esm/schemas/scenarios/plain_chat_output.zod.js +2 -0
- package/esm/src/builtins.d.ts +2 -0
- package/esm/src/builtins.d.ts.map +1 -1
- package/esm/src/builtins.js +45 -1
- package/esm/src/constants.d.ts +4 -0
- package/esm/src/constants.d.ts.map +1 -1
- package/esm/src/constants.js +5 -0
- package/esm/src/definitions.d.ts +5 -1
- package/esm/src/definitions.d.ts.map +1 -1
- package/esm/src/loader.d.ts.map +1 -1
- package/esm/src/loader.js +119 -13
- package/esm/src/markdown.d.ts.map +1 -1
- package/esm/src/markdown.js +222 -53
- package/esm/src/permissions.d.ts +143 -0
- package/esm/src/permissions.d.ts.map +1 -0
- package/esm/src/permissions.js +406 -0
- package/esm/src/render.d.ts.map +1 -1
- package/esm/src/render.js +22 -8
- package/esm/src/runtime.d.ts +28 -2
- package/esm/src/runtime.d.ts.map +1 -1
- package/esm/src/runtime.js +3051 -100
- package/esm/src/runtime_exec_host.d.ts +6 -0
- package/esm/src/runtime_exec_host.d.ts.map +1 -0
- package/esm/src/runtime_exec_host.js +17 -0
- package/esm/src/runtime_exec_host_contract.d.ts +23 -0
- package/esm/src/runtime_exec_host_contract.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_contract.js +14 -0
- package/esm/src/runtime_exec_host_deno.d.ts +3 -0
- package/esm/src/runtime_exec_host_deno.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_deno.js +35 -0
- package/esm/src/runtime_exec_host_unsupported.d.ts +3 -0
- package/esm/src/runtime_exec_host_unsupported.d.ts.map +1 -0
- package/esm/src/runtime_exec_host_unsupported.js +8 -0
- package/esm/src/runtime_worker_host.d.ts +6 -0
- package/esm/src/runtime_worker_host.d.ts.map +1 -0
- package/esm/src/runtime_worker_host.js +17 -0
- package/esm/src/runtime_worker_host_contract.d.ts +33 -0
- package/esm/src/runtime_worker_host_contract.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_contract.js +14 -0
- package/esm/src/runtime_worker_host_deno.d.ts +3 -0
- package/esm/src/runtime_worker_host_deno.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_deno.js +26 -0
- package/esm/src/runtime_worker_host_unsupported.d.ts +3 -0
- package/esm/src/runtime_worker_host_unsupported.d.ts.map +1 -0
- package/esm/src/runtime_worker_host_unsupported.js +8 -0
- package/esm/src/state.d.ts +4 -1
- package/esm/src/state.d.ts.map +1 -1
- package/esm/src/state.js +48 -2
- package/esm/src/types.d.ts +381 -1
- package/esm/src/types.d.ts.map +1 -1
- package/esm/src/types.js +102 -1
- package/package.json +73 -2
- package/schemas/graders/contexts/conversation.ts +32 -9
- package/schemas/graders/contexts/conversation.zod.ts +1 -0
- package/schemas/graders/contexts/conversation_tools.ts +63 -0
- package/schemas/graders/contexts/conversation_tools.zod.ts +1 -0
- package/schemas/graders/contexts/tools.ts +5 -0
- package/schemas/graders/contexts/tools.zod.ts +1 -0
- package/schemas/graders/contexts/turn.ts +8 -1
- package/schemas/graders/contexts/turn.zod.ts +1 -0
- package/schemas/graders/contexts/turn_tools.ts +63 -0
- package/schemas/graders/contexts/turn_tools.zod.ts +1 -0
- package/schemas/graders/grader_output.ts +15 -0
- package/schemas/graders/grader_output.zod.ts +1 -0
- package/schemas/graders/respond.ts +13 -3
- package/schemas/graders/respond.zod.ts +1 -0
- package/schemas/scenarios/plain_chat_input_optional.ts +6 -0
- package/schemas/scenarios/plain_chat_input_optional.zod.ts +1 -0
- package/schemas/scenarios/plain_chat_output.ts +5 -0
- package/schemas/scenarios/plain_chat_output.zod.ts +1 -0
- package/{esm/deps/jsr.io/@std/collections/1.1.4 → script/deps/jsr.io/@std/collections/1.1.5}/deep_merge.d.ts +2 -2
- package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.d.ts.map +1 -1
- package/script/deps/jsr.io/@std/collections/{1.1.4 → 1.1.5}/deep_merge.js +29 -19
- package/script/deps/jsr.io/@std/toml/1.0.11/_parser.js +1 -1
- package/script/mod.d.ts +20 -10
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +25 -9
- package/script/schemas/graders/contexts/conversation.d.ts +22 -0
- package/script/schemas/graders/contexts/conversation.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation.js +20 -0
- package/script/schemas/graders/contexts/conversation.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/conversation.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation.zod.js +9 -0
- package/script/schemas/graders/contexts/conversation_tools.d.ts +31 -0
- package/script/schemas/graders/contexts/conversation_tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation_tools.js +28 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/conversation_tools.zod.js +9 -0
- package/script/schemas/graders/contexts/tools.d.ts +4 -0
- package/script/schemas/graders/contexts/tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/tools.js +12 -0
- package/script/schemas/graders/contexts/tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/tools.zod.js +9 -0
- package/script/schemas/graders/contexts/turn.d.ts +10 -0
- package/script/schemas/graders/contexts/turn.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn.js +10 -0
- package/script/schemas/graders/contexts/turn.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/turn.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn.zod.js +9 -0
- package/script/schemas/graders/contexts/turn_tools.d.ts +32 -0
- package/script/schemas/graders/contexts/turn_tools.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn_tools.js +31 -0
- package/script/schemas/graders/contexts/turn_tools.zod.d.ts +3 -0
- package/script/schemas/graders/contexts/turn_tools.zod.d.ts.map +1 -0
- package/script/schemas/graders/contexts/turn_tools.zod.js +9 -0
- package/script/schemas/graders/grader_output.d.ts +10 -0
- package/script/schemas/graders/grader_output.d.ts.map +1 -0
- package/script/schemas/graders/grader_output.js +10 -0
- package/script/schemas/graders/grader_output.zod.d.ts +3 -0
- package/script/schemas/graders/grader_output.zod.d.ts.map +1 -0
- package/script/schemas/graders/grader_output.zod.js +9 -0
- package/script/schemas/graders/respond.d.ts +12 -0
- package/script/schemas/graders/respond.d.ts.map +1 -0
- package/script/schemas/graders/respond.js +12 -0
- package/script/schemas/graders/respond.zod.d.ts +3 -0
- package/script/schemas/graders/respond.zod.d.ts.map +1 -0
- package/script/schemas/graders/respond.zod.js +9 -0
- package/script/schemas/scenarios/plain_chat_input_optional.d.ts +5 -0
- package/script/schemas/scenarios/plain_chat_input_optional.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_input_optional.js +7 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts +3 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_input_optional.zod.js +9 -0
- package/script/schemas/scenarios/plain_chat_output.d.ts +5 -0
- package/script/schemas/scenarios/plain_chat_output.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_output.js +6 -0
- package/script/schemas/scenarios/plain_chat_output.zod.d.ts +3 -0
- package/script/schemas/scenarios/plain_chat_output.zod.d.ts.map +1 -0
- package/script/schemas/scenarios/plain_chat_output.zod.js +9 -0
- package/script/src/builtins.d.ts +2 -0
- package/script/src/builtins.d.ts.map +1 -1
- package/script/src/builtins.js +47 -1
- package/script/src/constants.d.ts +4 -0
- package/script/src/constants.d.ts.map +1 -1
- package/script/src/constants.js +6 -1
- package/script/src/definitions.d.ts +5 -1
- package/script/src/definitions.d.ts.map +1 -1
- package/script/src/loader.d.ts.map +1 -1
- package/script/src/loader.js +118 -12
- package/script/src/markdown.d.ts.map +1 -1
- package/script/src/markdown.js +221 -52
- package/script/src/permissions.d.ts +143 -0
- package/script/src/permissions.d.ts.map +1 -0
- package/script/src/permissions.js +453 -0
- package/script/src/render.d.ts.map +1 -1
- package/script/src/render.js +22 -8
- package/script/src/runtime.d.ts +28 -2
- package/script/src/runtime.d.ts.map +1 -1
- package/script/src/runtime.js +3053 -99
- package/script/src/runtime_exec_host.d.ts +6 -0
- package/script/src/runtime_exec_host.d.ts.map +1 -0
- package/script/src/runtime_exec_host.js +56 -0
- package/script/src/runtime_exec_host_contract.d.ts +23 -0
- package/script/src/runtime_exec_host_contract.d.ts.map +1 -0
- package/script/src/runtime_exec_host_contract.js +18 -0
- package/script/src/runtime_exec_host_deno.d.ts +3 -0
- package/script/src/runtime_exec_host_deno.d.ts.map +1 -0
- package/script/src/runtime_exec_host_deno.js +71 -0
- package/script/src/runtime_exec_host_unsupported.d.ts +3 -0
- package/script/src/runtime_exec_host_unsupported.d.ts.map +1 -0
- package/script/src/runtime_exec_host_unsupported.js +11 -0
- package/script/src/runtime_worker_host.d.ts +6 -0
- package/script/src/runtime_worker_host.d.ts.map +1 -0
- package/script/src/runtime_worker_host.js +56 -0
- package/script/src/runtime_worker_host_contract.d.ts +33 -0
- package/script/src/runtime_worker_host_contract.d.ts.map +1 -0
- package/script/src/runtime_worker_host_contract.js +18 -0
- package/script/src/runtime_worker_host_deno.d.ts +3 -0
- package/script/src/runtime_worker_host_deno.d.ts.map +1 -0
- package/script/src/runtime_worker_host_deno.js +62 -0
- package/script/src/runtime_worker_host_unsupported.d.ts +3 -0
- package/script/src/runtime_worker_host_unsupported.d.ts.map +1 -0
- package/script/src/runtime_worker_host_unsupported.js +11 -0
- package/script/src/state.d.ts +4 -1
- package/script/src/state.d.ts.map +1 -1
- package/script/src/state.js +48 -2
- package/script/src/types.d.ts +381 -1
- package/script/src/types.d.ts.map +1 -1
- package/script/src/types.js +103 -0
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -18
- package/esm/src/openai_compat.d.ts +0 -63
- package/esm/src/openai_compat.d.ts.map +0 -1
- package/esm/src/openai_compat.js +0 -272
- package/esm/src/providers/openrouter.d.ts +0 -8
- package/esm/src/providers/openrouter.d.ts.map +0 -1
- package/esm/src/providers/openrouter.js +0 -168
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts +0 -6
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/collections/1.1.4/_utils.js +0 -21
- package/script/src/openai_compat.d.ts +0 -63
- package/script/src/openai_compat.d.ts.map +0 -1
- package/script/src/openai_compat.js +0 -276
- package/script/src/providers/openrouter.d.ts +0 -8
- package/script/src/providers/openrouter.d.ts.map +0 -1
- package/script/src/providers/openrouter.js +0 -207
package/esm/src/runtime.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import * as dntShim from "../_dnt.shims.js";
|
|
1
2
|
import * as path from "../deps/jsr.io/@std/path/1.1.4/mod.js";
|
|
2
|
-
import { DEFAULT_GUARDRAILS, DEFAULT_STATUS_DELAY_MS, GAMBIT_TOOL_COMPLETE, GAMBIT_TOOL_END, GAMBIT_TOOL_INIT, GAMBIT_TOOL_RESPOND, } from "./constants.js";
|
|
3
|
+
import { DEFAULT_GUARDRAILS, DEFAULT_STATUS_DELAY_MS, GAMBIT_TOOL_COMPLETE, GAMBIT_TOOL_CONTEXT, GAMBIT_TOOL_END, GAMBIT_TOOL_INIT, GAMBIT_TOOL_RESPOND, } from "./constants.js";
|
|
3
4
|
import { loadDeck } from "./loader.js";
|
|
5
|
+
import { canReadPath, canRunCommand, canRunPath, canWritePath, intersectPermissions, resolveEffectivePermissions, } from "./permissions.js";
|
|
6
|
+
import { ExecToolUnsupportedHostError, executeBuiltinCommand, } from "./runtime_exec_host.js";
|
|
7
|
+
import { createWorkerSandboxBridge, isWorkerSandboxHostSupported, WorkerSandboxUnsupportedHostError, } from "./runtime_worker_host.js";
|
|
4
8
|
import { assertZodSchema, toJsonSchema, validateWithSchema } from "./schema.js";
|
|
5
9
|
export function isGambitEndSignal(value) {
|
|
6
10
|
return Boolean(value &&
|
|
@@ -13,6 +17,300 @@ function randomId(prefix) {
|
|
|
13
17
|
// Keep IDs short enough for OpenAI/OpenRouter tool_call id limits (~40 chars).
|
|
14
18
|
return `${prefix}-${suffix}`;
|
|
15
19
|
}
|
|
20
|
+
const WORKER_SANDBOX_ENV = "GAMBIT_DECK_WORKER_SANDBOX";
|
|
21
|
+
const WORKER_TIMEOUT_MESSAGE = "Timeout exceeded";
|
|
22
|
+
const RUN_CANCELED_MESSAGE = "Run canceled";
|
|
23
|
+
const WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE = "workerSandbox is unsupported when `signal` is provided.";
|
|
24
|
+
const INSPECT_WORKER_TIMEOUT_MS = 1_500;
|
|
25
|
+
const INSPECT_WORKER_TIMEOUT_MESSAGE = "Deck inspection timed out";
|
|
26
|
+
const BUILTIN_TOOL_READ_FILE = "read_file";
|
|
27
|
+
const BUILTIN_TOOL_LIST_DIR = "list_dir";
|
|
28
|
+
const BUILTIN_TOOL_GREP_FILES = "grep_files";
|
|
29
|
+
const BUILTIN_TOOL_APPLY_PATCH = "apply_patch";
|
|
30
|
+
const BUILTIN_TOOL_EXEC = "exec";
|
|
31
|
+
const BUILTIN_TOOL_NAMES = new Set([
|
|
32
|
+
BUILTIN_TOOL_READ_FILE,
|
|
33
|
+
BUILTIN_TOOL_LIST_DIR,
|
|
34
|
+
BUILTIN_TOOL_GREP_FILES,
|
|
35
|
+
BUILTIN_TOOL_APPLY_PATCH,
|
|
36
|
+
BUILTIN_TOOL_EXEC,
|
|
37
|
+
]);
|
|
38
|
+
const TRUSTED_SCHEMA_IMPORT_PREFIXES = [
|
|
39
|
+
"@bolt-foundry/gambit-core/schemas",
|
|
40
|
+
"gambit://schemas",
|
|
41
|
+
];
|
|
42
|
+
export class RunCanceledError extends Error {
|
|
43
|
+
constructor(message = RUN_CANCELED_MESSAGE) {
|
|
44
|
+
super(message);
|
|
45
|
+
Object.defineProperty(this, "code", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: "run_canceled"
|
|
50
|
+
});
|
|
51
|
+
this.name = "RunCanceledError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class WorkerSandboxSignalUnsupportedError extends Error {
|
|
55
|
+
constructor(message = WORKER_SANDBOX_SIGNAL_UNSUPPORTED_MESSAGE) {
|
|
56
|
+
super(message);
|
|
57
|
+
Object.defineProperty(this, "code", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: "worker_sandbox_signal_unsupported"
|
|
62
|
+
});
|
|
63
|
+
this.name = "WorkerSandboxSignalUnsupportedError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function isRunCanceledError(err) {
|
|
67
|
+
if (!err || typeof err !== "object")
|
|
68
|
+
return false;
|
|
69
|
+
const name = err.name;
|
|
70
|
+
const code = err.code;
|
|
71
|
+
if (name === "RunCanceledError" || code === "run_canceled")
|
|
72
|
+
return true;
|
|
73
|
+
if (name === "AbortError")
|
|
74
|
+
return true;
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function shouldUseWorkerSandbox() {
|
|
78
|
+
let raw;
|
|
79
|
+
try {
|
|
80
|
+
raw = dntShim.Deno.env.get(WORKER_SANDBOX_ENV);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
raw = raw?.trim().toLowerCase();
|
|
86
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
87
|
+
}
|
|
88
|
+
function normalizedScopeToWire(scope) {
|
|
89
|
+
if (scope.all)
|
|
90
|
+
return true;
|
|
91
|
+
if (scope.values.size === 0)
|
|
92
|
+
return false;
|
|
93
|
+
return Array.from(scope.values).sort();
|
|
94
|
+
}
|
|
95
|
+
function normalizedRunToWire(scope) {
|
|
96
|
+
if (scope.all)
|
|
97
|
+
return true;
|
|
98
|
+
if (scope.paths.size === 0 && scope.commands.size === 0)
|
|
99
|
+
return false;
|
|
100
|
+
return {
|
|
101
|
+
paths: Array.from(scope.paths).sort(),
|
|
102
|
+
commands: Array.from(scope.commands).sort(),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function toWirePermissionSet(set) {
|
|
106
|
+
return {
|
|
107
|
+
baseDir: set.baseDir,
|
|
108
|
+
read: normalizedScopeToWire(set.read),
|
|
109
|
+
write: normalizedScopeToWire(set.write),
|
|
110
|
+
run: normalizedRunToWire(set.run),
|
|
111
|
+
net: normalizedScopeToWire(set.net),
|
|
112
|
+
env: normalizedScopeToWire(set.env),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function wireScopeToNormalized(scope) {
|
|
116
|
+
if (scope === true)
|
|
117
|
+
return { all: true, values: new Set() };
|
|
118
|
+
if (scope === false)
|
|
119
|
+
return { all: false, values: new Set() };
|
|
120
|
+
return { all: false, values: new Set(scope) };
|
|
121
|
+
}
|
|
122
|
+
function wireRunToNormalized(scope) {
|
|
123
|
+
if (scope === true) {
|
|
124
|
+
return {
|
|
125
|
+
all: true,
|
|
126
|
+
paths: new Set(),
|
|
127
|
+
commands: new Set(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (scope === false) {
|
|
131
|
+
return {
|
|
132
|
+
all: false,
|
|
133
|
+
paths: new Set(),
|
|
134
|
+
commands: new Set(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
all: false,
|
|
139
|
+
paths: new Set(scope.paths),
|
|
140
|
+
commands: new Set(scope.commands),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function fromWirePermissionSet(set) {
|
|
144
|
+
return {
|
|
145
|
+
baseDir: set.baseDir,
|
|
146
|
+
read: wireScopeToNormalized(set.read),
|
|
147
|
+
write: wireScopeToNormalized(set.write),
|
|
148
|
+
run: wireRunToNormalized(set.run),
|
|
149
|
+
net: wireScopeToNormalized(set.net),
|
|
150
|
+
env: wireScopeToNormalized(set.env),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function normalizePermissionBaseDir(set, baseDir) {
|
|
154
|
+
return {
|
|
155
|
+
...set,
|
|
156
|
+
baseDir,
|
|
157
|
+
read: { all: set.read.all, values: new Set(set.read.values) },
|
|
158
|
+
write: { all: set.write.all, values: new Set(set.write.values) },
|
|
159
|
+
run: {
|
|
160
|
+
all: set.run.all,
|
|
161
|
+
paths: new Set(set.run.paths),
|
|
162
|
+
commands: new Set(set.run.commands),
|
|
163
|
+
},
|
|
164
|
+
net: { all: set.net.all, values: new Set(set.net.values) },
|
|
165
|
+
env: { all: set.env.all, values: new Set(set.env.values) },
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function deadlineForRun(guardrails, existing) {
|
|
169
|
+
const timeoutDeadline = performance.now() + guardrails.timeoutMs;
|
|
170
|
+
if (typeof existing === "number" && Number.isFinite(existing)) {
|
|
171
|
+
return Math.min(existing, timeoutDeadline);
|
|
172
|
+
}
|
|
173
|
+
return timeoutDeadline;
|
|
174
|
+
}
|
|
175
|
+
function ensureNotExpired(deadlineMs) {
|
|
176
|
+
if (performance.now() > deadlineMs) {
|
|
177
|
+
throw new Error(WORKER_TIMEOUT_MESSAGE);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function throwIfCanceled(signal) {
|
|
181
|
+
if (!signal?.aborted)
|
|
182
|
+
return;
|
|
183
|
+
const reason = signal.reason;
|
|
184
|
+
if (typeof reason === "string" && reason.trim().length > 0) {
|
|
185
|
+
throw new RunCanceledError(reason);
|
|
186
|
+
}
|
|
187
|
+
if (reason instanceof Error && reason.message.trim().length > 0) {
|
|
188
|
+
throw new RunCanceledError(reason.message);
|
|
189
|
+
}
|
|
190
|
+
throw new RunCanceledError();
|
|
191
|
+
}
|
|
192
|
+
function ensureRunActive(deadlineMs, signal) {
|
|
193
|
+
throwIfCanceled(signal);
|
|
194
|
+
ensureNotExpired(deadlineMs);
|
|
195
|
+
}
|
|
196
|
+
function isTrustedSchemaImportKey(key) {
|
|
197
|
+
const normalized = key.trim();
|
|
198
|
+
if (!normalized)
|
|
199
|
+
return false;
|
|
200
|
+
return TRUSTED_SCHEMA_IMPORT_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
|
|
201
|
+
}
|
|
202
|
+
function tryReadWorkspaceConfigPath(deckPath) {
|
|
203
|
+
const startDir = path.dirname(path.resolve(deckPath));
|
|
204
|
+
let current = startDir;
|
|
205
|
+
while (true) {
|
|
206
|
+
const denoJson = path.join(current, "deno.json");
|
|
207
|
+
const denoJsonc = path.join(current, "deno.jsonc");
|
|
208
|
+
try {
|
|
209
|
+
if (dntShim.Deno.statSync(denoJson).isFile)
|
|
210
|
+
return denoJson;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// continue search
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
if (dntShim.Deno.statSync(denoJsonc).isFile)
|
|
217
|
+
return denoJsonc;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// continue search
|
|
221
|
+
}
|
|
222
|
+
const parent = path.dirname(current);
|
|
223
|
+
if (parent === current)
|
|
224
|
+
break;
|
|
225
|
+
current = parent;
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
function readWorkspaceImportMapKeys(configPath) {
|
|
230
|
+
const text = dntShim.Deno.readTextFileSync(configPath);
|
|
231
|
+
const parsed = parseWorkspaceConfig(text);
|
|
232
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) ||
|
|
233
|
+
!parsed.imports || typeof parsed.imports !== "object" ||
|
|
234
|
+
Array.isArray(parsed.imports)) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
return Object.keys(parsed.imports);
|
|
238
|
+
}
|
|
239
|
+
function parseWorkspaceConfig(text) {
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(text);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
const stripped = stripJsonComments(text);
|
|
245
|
+
return JSON.parse(stripped);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function stripJsonComments(text) {
|
|
249
|
+
let out = "";
|
|
250
|
+
let inString = false;
|
|
251
|
+
let escapeNext = false;
|
|
252
|
+
let inLineComment = false;
|
|
253
|
+
let inBlockComment = false;
|
|
254
|
+
for (let i = 0; i < text.length; i++) {
|
|
255
|
+
const ch = text[i];
|
|
256
|
+
const next = text[i + 1];
|
|
257
|
+
if (inLineComment) {
|
|
258
|
+
if (ch === "\n") {
|
|
259
|
+
inLineComment = false;
|
|
260
|
+
out += ch;
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (inBlockComment) {
|
|
265
|
+
if (ch === "*" && next === "/") {
|
|
266
|
+
inBlockComment = false;
|
|
267
|
+
i++;
|
|
268
|
+
}
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (inString) {
|
|
272
|
+
out += ch;
|
|
273
|
+
if (escapeNext) {
|
|
274
|
+
escapeNext = false;
|
|
275
|
+
}
|
|
276
|
+
else if (ch === "\\") {
|
|
277
|
+
escapeNext = true;
|
|
278
|
+
}
|
|
279
|
+
else if (ch === '"') {
|
|
280
|
+
inString = false;
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (ch === '"') {
|
|
285
|
+
inString = true;
|
|
286
|
+
out += ch;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (ch === "/" && next === "/") {
|
|
290
|
+
inLineComment = true;
|
|
291
|
+
i++;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (ch === "/" && next === "*") {
|
|
295
|
+
inBlockComment = true;
|
|
296
|
+
i++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
out += ch;
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
function enforceTrustedSchemaImportMapPolicy(deckPath) {
|
|
304
|
+
if (deckPath.startsWith("gambit://"))
|
|
305
|
+
return;
|
|
306
|
+
const configPath = tryReadWorkspaceConfigPath(deckPath);
|
|
307
|
+
if (!configPath)
|
|
308
|
+
return;
|
|
309
|
+
const violations = readWorkspaceImportMapKeys(configPath).filter((key) => isTrustedSchemaImportKey(key));
|
|
310
|
+
if (violations.length === 0)
|
|
311
|
+
return;
|
|
312
|
+
throw new Error(`[gambit] trust-boundary violation: workspace import map at ${configPath} remaps trusted schema namespace (${violations.join(", ")})`);
|
|
313
|
+
}
|
|
16
314
|
export async function runDeck(opts) {
|
|
17
315
|
const guardrails = {
|
|
18
316
|
...DEFAULT_GUARDRAILS,
|
|
@@ -25,34 +323,241 @@ export async function runDeck(opts) {
|
|
|
25
323
|
throw new Error(`Max depth ${guardrails.maxDepth} exceeded`);
|
|
26
324
|
}
|
|
27
325
|
const runId = opts.runId ?? opts.state?.runId ?? randomId("run");
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
326
|
+
enforceTrustedSchemaImportMapPolicy(opts.path);
|
|
327
|
+
const workerSandboxRequested = opts.workerSandbox ??
|
|
328
|
+
shouldUseWorkerSandbox();
|
|
329
|
+
if (workerSandboxRequested && !isWorkerSandboxHostSupported()) {
|
|
330
|
+
throw new WorkerSandboxUnsupportedHostError();
|
|
331
|
+
}
|
|
332
|
+
if (workerSandboxRequested && opts.signal) {
|
|
333
|
+
throw new WorkerSandboxSignalUnsupportedError();
|
|
334
|
+
}
|
|
335
|
+
const workerSandbox = workerSandboxRequested;
|
|
34
336
|
const isRoot = Boolean(inferredRoot);
|
|
35
|
-
ensureSchemaPresence(deck, isRoot);
|
|
36
|
-
const resolvedInput = resolveInput({
|
|
37
|
-
deck,
|
|
38
|
-
input: opts.input,
|
|
39
|
-
state: opts.state,
|
|
40
|
-
isRoot,
|
|
41
|
-
initialUserMessage: opts.initialUserMessage,
|
|
42
|
-
});
|
|
43
|
-
const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
|
|
44
337
|
const shouldEmitRun = opts.depth === undefined || opts.depth === 0;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
338
|
+
let canceled = false;
|
|
339
|
+
let cancelHandled = false;
|
|
340
|
+
const handleCancel = async () => {
|
|
341
|
+
if (cancelHandled)
|
|
342
|
+
return;
|
|
343
|
+
cancelHandled = true;
|
|
344
|
+
if (!opts.onCancel)
|
|
345
|
+
return;
|
|
346
|
+
try {
|
|
347
|
+
await opts.onCancel();
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
logger.warn(`[gambit] runDeck onCancel callback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
55
353
|
try {
|
|
354
|
+
throwIfCanceled(opts.signal);
|
|
355
|
+
if (workerSandbox) {
|
|
356
|
+
const preInspectRunDeadlineMs = deadlineForRun(guardrails, opts.runDeadlineMs);
|
|
357
|
+
ensureRunActive(preInspectRunDeadlineMs, opts.signal);
|
|
358
|
+
const inspectedDeck = await inspectDeckInWorker(opts.path, preInspectRunDeadlineMs);
|
|
359
|
+
const deckDir = path.dirname(inspectedDeck.deckPath);
|
|
360
|
+
const permissions = resolveEffectivePermissions({
|
|
361
|
+
baseDir: deckDir,
|
|
362
|
+
parent: opts.parentPermissions,
|
|
363
|
+
workspace: opts.workspacePermissions
|
|
364
|
+
? {
|
|
365
|
+
baseDir: opts.workspacePermissionsBaseDir ?? deckDir,
|
|
366
|
+
permissions: opts.workspacePermissions,
|
|
367
|
+
}
|
|
368
|
+
: undefined,
|
|
369
|
+
declaration: inspectedDeck.permissions
|
|
370
|
+
? { baseDir: deckDir, permissions: inspectedDeck.permissions }
|
|
371
|
+
: undefined,
|
|
372
|
+
reference: opts.referencePermissions
|
|
373
|
+
? {
|
|
374
|
+
baseDir: opts.referencePermissionsBaseDir ?? deckDir,
|
|
375
|
+
permissions: opts.referencePermissions,
|
|
376
|
+
}
|
|
377
|
+
: undefined,
|
|
378
|
+
session: opts.sessionPermissions
|
|
379
|
+
? {
|
|
380
|
+
baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
|
|
381
|
+
permissions: opts.sessionPermissions,
|
|
382
|
+
}
|
|
383
|
+
: undefined,
|
|
384
|
+
});
|
|
385
|
+
const effectiveGuardrails = {
|
|
386
|
+
...guardrails,
|
|
387
|
+
...(inspectedDeck.guardrails ?? {}),
|
|
388
|
+
};
|
|
389
|
+
const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
|
|
390
|
+
ensureRunActive(runDeadlineMs, opts.signal);
|
|
391
|
+
const resolvedInput = resolveInputWithoutDeck({
|
|
392
|
+
input: opts.input,
|
|
393
|
+
state: opts.state,
|
|
394
|
+
isRoot,
|
|
395
|
+
initialUserMessage: opts.initialUserMessage,
|
|
396
|
+
});
|
|
397
|
+
if (!inspectedDeck.hasModelParams) {
|
|
398
|
+
if (shouldEmitRun) {
|
|
399
|
+
opts.trace?.({
|
|
400
|
+
type: "run.start",
|
|
401
|
+
runId,
|
|
402
|
+
deckPath: inspectedDeck.deckPath,
|
|
403
|
+
input: resolvedInput,
|
|
404
|
+
initialUserMessage: opts
|
|
405
|
+
.initialUserMessage,
|
|
406
|
+
permissions: permissions.trace,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return await runComputeDeckInWorker({
|
|
410
|
+
deckPath: inspectedDeck.deckPath,
|
|
411
|
+
guardrails: effectiveGuardrails,
|
|
412
|
+
depth,
|
|
413
|
+
runId,
|
|
414
|
+
initialUserMessage: opts.initialUserMessage,
|
|
415
|
+
parentActionCallId: opts.parentActionCallId,
|
|
416
|
+
modelProvider: opts.modelProvider,
|
|
417
|
+
input: resolvedInput,
|
|
418
|
+
defaultModel: opts.defaultModel,
|
|
419
|
+
modelOverride: opts.modelOverride,
|
|
420
|
+
trace: opts.trace,
|
|
421
|
+
stream: opts.stream,
|
|
422
|
+
state: opts.state,
|
|
423
|
+
onStateUpdate: opts.onStateUpdate,
|
|
424
|
+
onStreamText: opts.onStreamText,
|
|
425
|
+
responsesMode: opts.responsesMode,
|
|
426
|
+
permissions: permissions.effective,
|
|
427
|
+
permissionsTrace: permissions.trace,
|
|
428
|
+
workspacePermissions: opts.workspacePermissions,
|
|
429
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
430
|
+
sessionPermissions: opts.sessionPermissions,
|
|
431
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
432
|
+
runDeadlineMs,
|
|
433
|
+
isRoot,
|
|
434
|
+
allowRootStringInput: opts.allowRootStringInput ?? false,
|
|
435
|
+
signal: opts.signal,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
if (!opts.inOrchestrationWorker) {
|
|
439
|
+
return await runLlmDeckInWorker({
|
|
440
|
+
deckPath: inspectedDeck.deckPath,
|
|
441
|
+
guardrails: effectiveGuardrails,
|
|
442
|
+
depth,
|
|
443
|
+
runId,
|
|
444
|
+
parentActionCallId: opts.parentActionCallId,
|
|
445
|
+
modelProvider: opts.modelProvider,
|
|
446
|
+
input: resolvedInput,
|
|
447
|
+
inputProvided: opts.inputProvided ?? true,
|
|
448
|
+
initialUserMessage: opts.initialUserMessage,
|
|
449
|
+
defaultModel: opts.defaultModel,
|
|
450
|
+
modelOverride: opts.modelOverride,
|
|
451
|
+
trace: opts.trace,
|
|
452
|
+
stream: opts.stream,
|
|
453
|
+
state: opts.state,
|
|
454
|
+
onStateUpdate: opts.onStateUpdate,
|
|
455
|
+
onStreamText: opts.onStreamText,
|
|
456
|
+
responsesMode: opts.responsesMode,
|
|
457
|
+
permissions: permissions.effective,
|
|
458
|
+
permissionsTrace: permissions.trace,
|
|
459
|
+
workspacePermissions: opts.workspacePermissions,
|
|
460
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
461
|
+
sessionPermissions: opts.sessionPermissions,
|
|
462
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
463
|
+
runDeadlineMs,
|
|
464
|
+
workerSandbox,
|
|
465
|
+
allowRootStringInput: opts.allowRootStringInput,
|
|
466
|
+
isRoot,
|
|
467
|
+
signal: opts.signal,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const deck = await loadDeck(opts.path);
|
|
472
|
+
const permissions = resolveEffectivePermissions({
|
|
473
|
+
baseDir: path.dirname(deck.path),
|
|
474
|
+
parent: opts.parentPermissions,
|
|
475
|
+
workspace: opts.workspacePermissions
|
|
476
|
+
? {
|
|
477
|
+
baseDir: opts.workspacePermissionsBaseDir ?? path.dirname(deck.path),
|
|
478
|
+
permissions: opts.workspacePermissions,
|
|
479
|
+
}
|
|
480
|
+
: undefined,
|
|
481
|
+
declaration: deck.permissions
|
|
482
|
+
? { baseDir: path.dirname(deck.path), permissions: deck.permissions }
|
|
483
|
+
: undefined,
|
|
484
|
+
reference: opts.referencePermissions
|
|
485
|
+
? {
|
|
486
|
+
baseDir: opts.referencePermissionsBaseDir ?? path.dirname(deck.path),
|
|
487
|
+
permissions: opts.referencePermissions,
|
|
488
|
+
}
|
|
489
|
+
: undefined,
|
|
490
|
+
session: opts.sessionPermissions
|
|
491
|
+
? {
|
|
492
|
+
baseDir: opts.sessionPermissionsBaseDir ?? dntShim.Deno.cwd(),
|
|
493
|
+
permissions: opts.sessionPermissions,
|
|
494
|
+
}
|
|
495
|
+
: undefined,
|
|
496
|
+
});
|
|
497
|
+
const deckGuardrails = deck.guardrails ?? {};
|
|
498
|
+
const effectiveGuardrails = {
|
|
499
|
+
...guardrails,
|
|
500
|
+
...deckGuardrails,
|
|
501
|
+
};
|
|
502
|
+
const runDeadlineMs = deadlineForRun(effectiveGuardrails, opts.runDeadlineMs);
|
|
503
|
+
ensureRunActive(runDeadlineMs, opts.signal);
|
|
504
|
+
ensureSchemaPresence(deck, isRoot);
|
|
505
|
+
const resolvedInput = resolveInput({
|
|
506
|
+
deck,
|
|
507
|
+
input: opts.input,
|
|
508
|
+
state: opts.state,
|
|
509
|
+
isRoot,
|
|
510
|
+
initialUserMessage: opts.initialUserMessage,
|
|
511
|
+
});
|
|
512
|
+
const validatedInput = validateInput(deck, resolvedInput, isRoot, opts.allowRootStringInput ?? false);
|
|
513
|
+
const useOrchestrationWorker = workerSandbox &&
|
|
514
|
+
!opts.inOrchestrationWorker &&
|
|
515
|
+
isRoot &&
|
|
516
|
+
!opts.onTool &&
|
|
517
|
+
Boolean(deck.modelParams?.model || deck.modelParams?.temperature !== undefined);
|
|
518
|
+
if (useOrchestrationWorker) {
|
|
519
|
+
return await runLlmDeckInWorker({
|
|
520
|
+
deckPath: deck.path,
|
|
521
|
+
guardrails: effectiveGuardrails,
|
|
522
|
+
depth,
|
|
523
|
+
runId,
|
|
524
|
+
parentActionCallId: opts.parentActionCallId,
|
|
525
|
+
modelProvider: opts.modelProvider,
|
|
526
|
+
input: validatedInput,
|
|
527
|
+
inputProvided: opts.inputProvided ?? true,
|
|
528
|
+
initialUserMessage: opts.initialUserMessage,
|
|
529
|
+
defaultModel: opts.defaultModel,
|
|
530
|
+
modelOverride: opts.modelOverride,
|
|
531
|
+
trace: opts.trace,
|
|
532
|
+
stream: opts.stream,
|
|
533
|
+
state: opts.state,
|
|
534
|
+
onStateUpdate: opts.onStateUpdate,
|
|
535
|
+
onStreamText: opts.onStreamText,
|
|
536
|
+
responsesMode: opts.responsesMode,
|
|
537
|
+
permissions: permissions.effective,
|
|
538
|
+
permissionsTrace: permissions.trace,
|
|
539
|
+
workspacePermissions: opts.workspacePermissions,
|
|
540
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
541
|
+
sessionPermissions: opts.sessionPermissions,
|
|
542
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
543
|
+
runDeadlineMs,
|
|
544
|
+
workerSandbox,
|
|
545
|
+
allowRootStringInput: opts.allowRootStringInput,
|
|
546
|
+
isRoot,
|
|
547
|
+
signal: opts.signal,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (shouldEmitRun) {
|
|
551
|
+
opts.trace?.({
|
|
552
|
+
type: "run.start",
|
|
553
|
+
runId,
|
|
554
|
+
deckPath: deck.path,
|
|
555
|
+
input: validatedInput,
|
|
556
|
+
initialUserMessage: opts
|
|
557
|
+
.initialUserMessage,
|
|
558
|
+
permissions: permissions.trace,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
56
561
|
if (deck.modelParams?.model || deck.modelParams?.temperature !== undefined) {
|
|
57
562
|
return await runLlmDeck({
|
|
58
563
|
deck,
|
|
@@ -71,6 +576,17 @@ export async function runDeck(opts) {
|
|
|
71
576
|
state: opts.state,
|
|
72
577
|
onStateUpdate: opts.onStateUpdate,
|
|
73
578
|
onStreamText: opts.onStreamText,
|
|
579
|
+
responsesMode: opts.responsesMode,
|
|
580
|
+
permissions: permissions.effective,
|
|
581
|
+
permissionsTrace: permissions.trace,
|
|
582
|
+
workspacePermissions: opts.workspacePermissions,
|
|
583
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
584
|
+
sessionPermissions: opts.sessionPermissions,
|
|
585
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
586
|
+
runDeadlineMs,
|
|
587
|
+
workerSandbox,
|
|
588
|
+
onTool: opts.onTool,
|
|
589
|
+
signal: opts.signal,
|
|
74
590
|
});
|
|
75
591
|
}
|
|
76
592
|
if (!deck.executor) {
|
|
@@ -81,6 +597,7 @@ export async function runDeck(opts) {
|
|
|
81
597
|
guardrails: effectiveGuardrails,
|
|
82
598
|
depth,
|
|
83
599
|
runId,
|
|
600
|
+
initialUserMessage: opts.initialUserMessage,
|
|
84
601
|
parentActionCallId: opts.parentActionCallId,
|
|
85
602
|
modelProvider: opts.modelProvider,
|
|
86
603
|
input: validatedInput,
|
|
@@ -88,19 +605,42 @@ export async function runDeck(opts) {
|
|
|
88
605
|
modelOverride: opts.modelOverride,
|
|
89
606
|
trace: opts.trace,
|
|
90
607
|
stream: opts.stream,
|
|
608
|
+
state: opts.state,
|
|
609
|
+
onStateUpdate: opts.onStateUpdate,
|
|
91
610
|
onStreamText: opts.onStreamText,
|
|
611
|
+
responsesMode: opts.responsesMode,
|
|
612
|
+
permissions: permissions.effective,
|
|
613
|
+
permissionsTrace: permissions.trace,
|
|
614
|
+
workspacePermissions: opts.workspacePermissions,
|
|
615
|
+
workspacePermissionsBaseDir: opts.workspacePermissionsBaseDir,
|
|
616
|
+
sessionPermissions: opts.sessionPermissions,
|
|
617
|
+
sessionPermissionsBaseDir: opts.sessionPermissionsBaseDir,
|
|
618
|
+
runDeadlineMs,
|
|
619
|
+
workerSandbox,
|
|
620
|
+
onTool: opts.onTool,
|
|
621
|
+
signal: opts.signal,
|
|
92
622
|
});
|
|
93
623
|
}
|
|
624
|
+
catch (err) {
|
|
625
|
+
if (isRunCanceledError(err)) {
|
|
626
|
+
canceled = true;
|
|
627
|
+
await handleCancel();
|
|
628
|
+
}
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
94
631
|
finally {
|
|
95
632
|
if (shouldEmitRun) {
|
|
96
633
|
opts.trace?.({ type: "run.end", runId });
|
|
97
634
|
}
|
|
635
|
+
if (opts.signal?.aborted && !canceled) {
|
|
636
|
+
await handleCancel();
|
|
637
|
+
}
|
|
98
638
|
}
|
|
99
639
|
}
|
|
100
640
|
function toProviderParams(params) {
|
|
101
641
|
if (!params)
|
|
102
642
|
return undefined;
|
|
103
|
-
const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, } = params;
|
|
643
|
+
const { model: _model, temperature, top_p, frequency_penalty, presence_penalty, max_tokens, verbosity, reasoning, } = params;
|
|
104
644
|
const out = {};
|
|
105
645
|
if (temperature !== undefined)
|
|
106
646
|
out.temperature = temperature;
|
|
@@ -113,15 +653,51 @@ function toProviderParams(params) {
|
|
|
113
653
|
out.presence_penalty = presence_penalty;
|
|
114
654
|
if (max_tokens !== undefined)
|
|
115
655
|
out.max_tokens = max_tokens;
|
|
656
|
+
if (verbosity !== undefined)
|
|
657
|
+
out.verbosity = verbosity;
|
|
658
|
+
if (reasoning !== undefined)
|
|
659
|
+
out.reasoning = reasoning;
|
|
116
660
|
return Object.keys(out).length ? out : undefined;
|
|
117
661
|
}
|
|
662
|
+
async function resolveModelChoice(args) {
|
|
663
|
+
const resolver = args.modelProvider.resolveModel;
|
|
664
|
+
if (resolver) {
|
|
665
|
+
return await resolver({
|
|
666
|
+
model: args.model,
|
|
667
|
+
params: args.params,
|
|
668
|
+
deckPath: args.deckPath,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (Array.isArray(args.model)) {
|
|
672
|
+
const first = args.model.find((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
673
|
+
if (!first) {
|
|
674
|
+
throw new Error(`No model configured for deck ${args.deckPath}`);
|
|
675
|
+
}
|
|
676
|
+
return { model: first, params: args.params };
|
|
677
|
+
}
|
|
678
|
+
if (!args.model || !args.model.trim()) {
|
|
679
|
+
throw new Error(`No model configured for deck ${args.deckPath}`);
|
|
680
|
+
}
|
|
681
|
+
return { model: args.model, params: args.params };
|
|
682
|
+
}
|
|
683
|
+
function resolveContextSchema(deck) {
|
|
684
|
+
return deck.contextSchema ?? deck.inputSchema;
|
|
685
|
+
}
|
|
686
|
+
function resolveResponseSchema(deck) {
|
|
687
|
+
return deck.responseSchema ?? deck.outputSchema;
|
|
688
|
+
}
|
|
689
|
+
function isContextToolName(name) {
|
|
690
|
+
return name === GAMBIT_TOOL_CONTEXT || name === GAMBIT_TOOL_INIT;
|
|
691
|
+
}
|
|
118
692
|
function ensureSchemaPresence(deck, isRoot) {
|
|
119
693
|
if (!isRoot) {
|
|
120
|
-
|
|
121
|
-
|
|
694
|
+
const contextSchema = resolveContextSchema(deck);
|
|
695
|
+
const responseSchema = resolveResponseSchema(deck);
|
|
696
|
+
if (!contextSchema || !responseSchema) {
|
|
697
|
+
throw new Error(`Deck ${deck.path} must declare contextSchema and responseSchema (non-root)`);
|
|
122
698
|
}
|
|
123
|
-
assertZodSchema(
|
|
124
|
-
assertZodSchema(
|
|
699
|
+
assertZodSchema(contextSchema, "contextSchema");
|
|
700
|
+
assertZodSchema(responseSchema, "responseSchema");
|
|
125
701
|
}
|
|
126
702
|
}
|
|
127
703
|
function resolveInput(args) {
|
|
@@ -129,11 +705,11 @@ function resolveInput(args) {
|
|
|
129
705
|
return args.input;
|
|
130
706
|
if (!args.isRoot)
|
|
131
707
|
return args.input;
|
|
132
|
-
const persisted =
|
|
708
|
+
const persisted = extractContextInput(args.state);
|
|
133
709
|
if (persisted !== undefined)
|
|
134
710
|
return persisted;
|
|
135
711
|
if (args.initialUserMessage !== undefined) {
|
|
136
|
-
const schema = args.deck
|
|
712
|
+
const schema = resolveContextSchema(args.deck);
|
|
137
713
|
if (schema?.safeParse) {
|
|
138
714
|
const candidates = [undefined, {}, ""];
|
|
139
715
|
for (const candidate of candidates) {
|
|
@@ -151,12 +727,30 @@ function resolveInput(args) {
|
|
|
151
727
|
}
|
|
152
728
|
return args.input;
|
|
153
729
|
}
|
|
154
|
-
function
|
|
155
|
-
if (
|
|
730
|
+
function resolveInputWithoutDeck(args) {
|
|
731
|
+
if (args.input !== undefined)
|
|
732
|
+
return args.input;
|
|
733
|
+
if (!args.isRoot)
|
|
734
|
+
return args.input;
|
|
735
|
+
const persisted = extractContextInput(args.state);
|
|
736
|
+
if (persisted !== undefined)
|
|
737
|
+
return persisted;
|
|
738
|
+
if (args.initialUserMessage !== undefined) {
|
|
739
|
+
return "";
|
|
740
|
+
}
|
|
741
|
+
return args.input;
|
|
742
|
+
}
|
|
743
|
+
function extractContextInput(state) {
|
|
744
|
+
if (!state)
|
|
745
|
+
return undefined;
|
|
746
|
+
if (state.format === "responses" && Array.isArray(state.items)) {
|
|
747
|
+
return extractContextInputFromItems(state.items);
|
|
748
|
+
}
|
|
749
|
+
if (!state.messages)
|
|
156
750
|
return undefined;
|
|
157
751
|
for (let i = state.messages.length - 1; i >= 0; i--) {
|
|
158
752
|
const msg = state.messages[i];
|
|
159
|
-
if (msg.role === "tool" && msg.name
|
|
753
|
+
if (msg.role === "tool" && isContextToolName(msg.name ?? "")) {
|
|
160
754
|
const content = msg.content;
|
|
161
755
|
if (typeof content !== "string")
|
|
162
756
|
return undefined;
|
|
@@ -170,17 +764,244 @@ function extractInitInput(state) {
|
|
|
170
764
|
}
|
|
171
765
|
return undefined;
|
|
172
766
|
}
|
|
767
|
+
function extractContextInputFromItems(items) {
|
|
768
|
+
const contextToolNames = new Set([GAMBIT_TOOL_CONTEXT, GAMBIT_TOOL_INIT]);
|
|
769
|
+
const callNameById = new Map();
|
|
770
|
+
for (const item of items) {
|
|
771
|
+
if (item.type === "function_call") {
|
|
772
|
+
callNameById.set(item.call_id, item.name);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
776
|
+
const item = items[i];
|
|
777
|
+
if (item.type !== "function_call_output")
|
|
778
|
+
continue;
|
|
779
|
+
const name = callNameById.get(item.call_id);
|
|
780
|
+
if (!name || !contextToolNames.has(name))
|
|
781
|
+
continue;
|
|
782
|
+
try {
|
|
783
|
+
return JSON.parse(item.output);
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
return item.output;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
function messagesFromResponseItems(items) {
|
|
792
|
+
const messages = [];
|
|
793
|
+
const callNameById = new Map();
|
|
794
|
+
for (const item of items) {
|
|
795
|
+
if (item.type === "message") {
|
|
796
|
+
const text = item.content.map((part) => part.text).join("");
|
|
797
|
+
messages.push({
|
|
798
|
+
role: item.role,
|
|
799
|
+
content: text || null,
|
|
800
|
+
});
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (item.type === "function_call") {
|
|
804
|
+
callNameById.set(item.call_id, item.name);
|
|
805
|
+
messages.push({
|
|
806
|
+
role: "assistant",
|
|
807
|
+
content: null,
|
|
808
|
+
tool_calls: [{
|
|
809
|
+
id: item.call_id,
|
|
810
|
+
type: "function",
|
|
811
|
+
function: { name: item.name, arguments: item.arguments },
|
|
812
|
+
}],
|
|
813
|
+
});
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (item.type === "function_call_output") {
|
|
817
|
+
messages.push({
|
|
818
|
+
role: "tool",
|
|
819
|
+
name: callNameById.get(item.call_id),
|
|
820
|
+
tool_call_id: item.call_id,
|
|
821
|
+
content: item.output,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return messages;
|
|
826
|
+
}
|
|
827
|
+
function responseItemsFromMessages(messages) {
|
|
828
|
+
const items = [];
|
|
829
|
+
for (const message of messages) {
|
|
830
|
+
if (message.role === "tool") {
|
|
831
|
+
if (!message.tool_call_id || message.content === null)
|
|
832
|
+
continue;
|
|
833
|
+
items.push({
|
|
834
|
+
type: "function_call_output",
|
|
835
|
+
call_id: message.tool_call_id,
|
|
836
|
+
output: String(message.content),
|
|
837
|
+
});
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const contentText = message.content ?? "";
|
|
841
|
+
if (typeof contentText === "string" && contentText.length > 0) {
|
|
842
|
+
items.push({
|
|
843
|
+
type: "message",
|
|
844
|
+
role: message.role,
|
|
845
|
+
content: [{
|
|
846
|
+
type: message.role === "assistant" ? "output_text" : "input_text",
|
|
847
|
+
text: contentText,
|
|
848
|
+
}],
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
852
|
+
for (const call of message.tool_calls) {
|
|
853
|
+
items.push({
|
|
854
|
+
type: "function_call",
|
|
855
|
+
call_id: call.id,
|
|
856
|
+
name: call.function.name,
|
|
857
|
+
arguments: call.function.arguments,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return items;
|
|
863
|
+
}
|
|
864
|
+
function safeJsonArgs(value) {
|
|
865
|
+
if (!value)
|
|
866
|
+
return {};
|
|
867
|
+
try {
|
|
868
|
+
const parsed = JSON.parse(value);
|
|
869
|
+
if (parsed && typeof parsed === "object") {
|
|
870
|
+
return parsed;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
catch {
|
|
874
|
+
// ignore
|
|
875
|
+
}
|
|
876
|
+
return {};
|
|
877
|
+
}
|
|
878
|
+
function asToolKind(value, fallback) {
|
|
879
|
+
if (value === "action" || value === "external" || value === "mcp_bridge" ||
|
|
880
|
+
value === "internal") {
|
|
881
|
+
return value;
|
|
882
|
+
}
|
|
883
|
+
return fallback;
|
|
884
|
+
}
|
|
885
|
+
function projectStreamToolTraceEvents(input) {
|
|
886
|
+
if (!input.trace)
|
|
887
|
+
return;
|
|
888
|
+
const type = typeof input.streamEvent.type === "string"
|
|
889
|
+
? input.streamEvent.type
|
|
890
|
+
: "";
|
|
891
|
+
if (type !== "tool.call" && type !== "tool.result")
|
|
892
|
+
return;
|
|
893
|
+
const actionCallId = typeof input.streamEvent.actionCallId === "string"
|
|
894
|
+
? input.streamEvent.actionCallId
|
|
895
|
+
: "";
|
|
896
|
+
const name = typeof input.streamEvent.name === "string"
|
|
897
|
+
? input.streamEvent.name
|
|
898
|
+
: input.toolNames.get(actionCallId) ?? "";
|
|
899
|
+
if (!actionCallId || !name)
|
|
900
|
+
return;
|
|
901
|
+
if (type === "tool.call") {
|
|
902
|
+
if (input.emittedCalls.has(actionCallId))
|
|
903
|
+
return;
|
|
904
|
+
input.emittedCalls.add(actionCallId);
|
|
905
|
+
input.toolNames.set(actionCallId, name);
|
|
906
|
+
const args = "args" in input.streamEvent
|
|
907
|
+
? (input.streamEvent.args ?? {})
|
|
908
|
+
: {};
|
|
909
|
+
const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
|
|
910
|
+
input.trace({
|
|
911
|
+
type: "tool.call",
|
|
912
|
+
runId: input.runId,
|
|
913
|
+
actionCallId,
|
|
914
|
+
name,
|
|
915
|
+
args,
|
|
916
|
+
toolKind,
|
|
917
|
+
parentActionCallId: input.parentActionCallId,
|
|
918
|
+
});
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (input.emittedResults.has(actionCallId))
|
|
922
|
+
return;
|
|
923
|
+
input.emittedResults.add(actionCallId);
|
|
924
|
+
const result = "result" in input.streamEvent
|
|
925
|
+
? (input.streamEvent.result ?? null)
|
|
926
|
+
: null;
|
|
927
|
+
const toolKind = asToolKind(input.streamEvent.toolKind, "mcp_bridge");
|
|
928
|
+
input.trace({
|
|
929
|
+
type: "tool.result",
|
|
930
|
+
runId: input.runId,
|
|
931
|
+
actionCallId,
|
|
932
|
+
name,
|
|
933
|
+
result,
|
|
934
|
+
toolKind,
|
|
935
|
+
parentActionCallId: input.parentActionCallId,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
function traceOpenResponsesStreamEvent(input) {
|
|
939
|
+
if (!input.trace)
|
|
940
|
+
return false;
|
|
941
|
+
const type = typeof input.streamEvent.type === "string"
|
|
942
|
+
? input.streamEvent.type
|
|
943
|
+
: "";
|
|
944
|
+
if (!type.startsWith("response."))
|
|
945
|
+
return false;
|
|
946
|
+
const rawMeta = input.streamEvent._gambit;
|
|
947
|
+
const existingMeta = rawMeta && typeof rawMeta === "object" &&
|
|
948
|
+
!Array.isArray(rawMeta)
|
|
949
|
+
? rawMeta
|
|
950
|
+
: {};
|
|
951
|
+
input.trace({
|
|
952
|
+
...input.streamEvent,
|
|
953
|
+
type,
|
|
954
|
+
_gambit: {
|
|
955
|
+
...existingMeta,
|
|
956
|
+
run_id: input.runId,
|
|
957
|
+
action_call_id: input.actionCallId,
|
|
958
|
+
parent_action_call_id: input.parentActionCallId,
|
|
959
|
+
deck_path: input.deckPath,
|
|
960
|
+
model: input.model,
|
|
961
|
+
},
|
|
962
|
+
});
|
|
963
|
+
return true;
|
|
964
|
+
}
|
|
965
|
+
function mapResponseOutput(output) {
|
|
966
|
+
const toolCalls = [];
|
|
967
|
+
const textParts = [];
|
|
968
|
+
for (const item of output) {
|
|
969
|
+
if (item.type === "function_call") {
|
|
970
|
+
toolCalls.push({
|
|
971
|
+
id: item.call_id,
|
|
972
|
+
name: item.name,
|
|
973
|
+
args: safeJsonArgs(item.arguments),
|
|
974
|
+
});
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
978
|
+
for (const part of item.content) {
|
|
979
|
+
if (part.type === "output_text") {
|
|
980
|
+
textParts.push(part.text);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
message: {
|
|
987
|
+
role: "assistant",
|
|
988
|
+
content: textParts.length ? textParts.join("") : null,
|
|
989
|
+
},
|
|
990
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
173
993
|
function validateInput(deck, input, isRoot, allowRootStringInput) {
|
|
174
|
-
|
|
994
|
+
const contextSchema = resolveContextSchema(deck);
|
|
995
|
+
if (contextSchema) {
|
|
175
996
|
if (isRoot && typeof input === "string" && allowRootStringInput) {
|
|
176
997
|
try {
|
|
177
|
-
return validateWithSchema(
|
|
998
|
+
return validateWithSchema(contextSchema, input);
|
|
178
999
|
}
|
|
179
1000
|
catch {
|
|
180
1001
|
return input;
|
|
181
1002
|
}
|
|
182
1003
|
}
|
|
183
|
-
return validateWithSchema(
|
|
1004
|
+
return validateWithSchema(contextSchema, input);
|
|
184
1005
|
}
|
|
185
1006
|
if (isRoot) {
|
|
186
1007
|
if (input === undefined)
|
|
@@ -189,28 +1010,1209 @@ function validateInput(deck, input, isRoot, allowRootStringInput) {
|
|
|
189
1010
|
return input;
|
|
190
1011
|
return input;
|
|
191
1012
|
}
|
|
192
|
-
throw new Error(`Deck ${deck.path} requires
|
|
1013
|
+
throw new Error(`Deck ${deck.path} requires contextSchema (non-root)`);
|
|
193
1014
|
}
|
|
194
1015
|
function validateOutput(deck, output, isRoot) {
|
|
195
|
-
|
|
196
|
-
|
|
1016
|
+
const responseSchema = resolveResponseSchema(deck);
|
|
1017
|
+
if (responseSchema) {
|
|
1018
|
+
return validateWithSchema(responseSchema, output);
|
|
1019
|
+
}
|
|
1020
|
+
if (isRoot) {
|
|
1021
|
+
if (typeof output === "string")
|
|
1022
|
+
return output;
|
|
1023
|
+
return JSON.stringify(output);
|
|
1024
|
+
}
|
|
1025
|
+
throw new Error(`Deck ${deck.path} requires responseSchema (non-root)`);
|
|
1026
|
+
}
|
|
1027
|
+
async function runComputeDeck(ctx) {
|
|
1028
|
+
if (ctx.workerSandbox) {
|
|
1029
|
+
return await runComputeDeckInWorker({
|
|
1030
|
+
guardrails: ctx.guardrails,
|
|
1031
|
+
depth: ctx.depth,
|
|
1032
|
+
runId: ctx.runId,
|
|
1033
|
+
inputProvided: ctx.inputProvided,
|
|
1034
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
1035
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
1036
|
+
modelProvider: ctx.modelProvider,
|
|
1037
|
+
input: ctx.input,
|
|
1038
|
+
defaultModel: ctx.defaultModel,
|
|
1039
|
+
modelOverride: ctx.modelOverride,
|
|
1040
|
+
trace: ctx.trace,
|
|
1041
|
+
stream: ctx.stream,
|
|
1042
|
+
state: ctx.state,
|
|
1043
|
+
onStateUpdate: ctx.onStateUpdate,
|
|
1044
|
+
onStreamText: ctx.onStreamText,
|
|
1045
|
+
responsesMode: ctx.responsesMode,
|
|
1046
|
+
permissions: ctx.permissions,
|
|
1047
|
+
permissionsTrace: ctx.permissionsTrace,
|
|
1048
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
1049
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
1050
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
1051
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
1052
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
1053
|
+
deckPath: ctx.deck.path,
|
|
1054
|
+
isRoot: ctx.depth === 0 && !ctx.parentActionCallId,
|
|
1055
|
+
allowRootStringInput: false,
|
|
1056
|
+
signal: ctx.signal,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return await runComputeDeckInProcess(ctx);
|
|
1060
|
+
}
|
|
1061
|
+
function toDenoPermissionList(scope) {
|
|
1062
|
+
if (scope.all)
|
|
1063
|
+
return true;
|
|
1064
|
+
if (scope.values.size === 0)
|
|
1065
|
+
return false;
|
|
1066
|
+
return Array.from(scope.values).sort();
|
|
1067
|
+
}
|
|
1068
|
+
function toDenoRunPermission(scope) {
|
|
1069
|
+
if (scope.all)
|
|
1070
|
+
return true;
|
|
1071
|
+
const values = new Set([
|
|
1072
|
+
...Array.from(scope.paths),
|
|
1073
|
+
...Array.from(scope.commands),
|
|
1074
|
+
]);
|
|
1075
|
+
if (values.size === 0)
|
|
1076
|
+
return false;
|
|
1077
|
+
return Array.from(values).sort();
|
|
1078
|
+
}
|
|
1079
|
+
const IMPORT_SOURCE_EXTENSIONS = new Set([
|
|
1080
|
+
".ts",
|
|
1081
|
+
".tsx",
|
|
1082
|
+
".mts",
|
|
1083
|
+
".cts",
|
|
1084
|
+
".js",
|
|
1085
|
+
".jsx",
|
|
1086
|
+
".mjs",
|
|
1087
|
+
".cjs",
|
|
1088
|
+
]);
|
|
1089
|
+
const RESOLVABLE_MODULE_EXTENSIONS = [
|
|
1090
|
+
".ts",
|
|
1091
|
+
".tsx",
|
|
1092
|
+
".mts",
|
|
1093
|
+
".cts",
|
|
1094
|
+
".js",
|
|
1095
|
+
".jsx",
|
|
1096
|
+
".mjs",
|
|
1097
|
+
".cjs",
|
|
1098
|
+
".json",
|
|
1099
|
+
];
|
|
1100
|
+
function stripSpecifierSuffix(specifier) {
|
|
1101
|
+
let out = specifier;
|
|
1102
|
+
const q = out.indexOf("?");
|
|
1103
|
+
if (q >= 0)
|
|
1104
|
+
out = out.slice(0, q);
|
|
1105
|
+
const h = out.indexOf("#");
|
|
1106
|
+
if (h >= 0)
|
|
1107
|
+
out = out.slice(0, h);
|
|
1108
|
+
return out.trim();
|
|
1109
|
+
}
|
|
1110
|
+
function isIdentifierStart(ch) {
|
|
1111
|
+
return /[A-Za-z_$]/.test(ch);
|
|
1112
|
+
}
|
|
1113
|
+
function isIdentifierContinue(ch) {
|
|
1114
|
+
return /[A-Za-z0-9_$]/.test(ch);
|
|
1115
|
+
}
|
|
1116
|
+
function skipWhitespaceAndComments(source, start) {
|
|
1117
|
+
let i = start;
|
|
1118
|
+
while (i < source.length) {
|
|
1119
|
+
const ch = source[i];
|
|
1120
|
+
if (/\s/.test(ch)) {
|
|
1121
|
+
i++;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
1125
|
+
i += 2;
|
|
1126
|
+
while (i < source.length && source[i] !== "\n" && source[i] !== "\r") {
|
|
1127
|
+
i++;
|
|
1128
|
+
}
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
1132
|
+
i += 2;
|
|
1133
|
+
while (i < source.length) {
|
|
1134
|
+
if (source[i] === "*" && source[i + 1] === "/") {
|
|
1135
|
+
i += 2;
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
i++;
|
|
1139
|
+
}
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
return i;
|
|
1145
|
+
}
|
|
1146
|
+
function readIdentifier(source, start) {
|
|
1147
|
+
if (start >= source.length)
|
|
1148
|
+
return undefined;
|
|
1149
|
+
if (!isIdentifierStart(source[start]))
|
|
1150
|
+
return undefined;
|
|
1151
|
+
let i = start + 1;
|
|
1152
|
+
while (i < source.length && isIdentifierContinue(source[i]))
|
|
1153
|
+
i++;
|
|
1154
|
+
return { value: source.slice(start, i), end: i };
|
|
1155
|
+
}
|
|
1156
|
+
function readStringLiteral(source, start) {
|
|
1157
|
+
const quote = source[start];
|
|
1158
|
+
if (quote !== "'" && quote !== '"')
|
|
1159
|
+
return undefined;
|
|
1160
|
+
let i = start + 1;
|
|
1161
|
+
let value = "";
|
|
1162
|
+
while (i < source.length) {
|
|
1163
|
+
const ch = source[i];
|
|
1164
|
+
if (ch === "\\") {
|
|
1165
|
+
if (i + 1 >= source.length)
|
|
1166
|
+
return undefined;
|
|
1167
|
+
value += source[i + 1];
|
|
1168
|
+
i += 2;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (ch === quote)
|
|
1172
|
+
return { value, end: i + 1 };
|
|
1173
|
+
if (ch === "\n" || ch === "\r")
|
|
1174
|
+
return undefined;
|
|
1175
|
+
value += ch;
|
|
1176
|
+
i++;
|
|
1177
|
+
}
|
|
1178
|
+
return undefined;
|
|
1179
|
+
}
|
|
1180
|
+
function skipTemplateExpression(source, start) {
|
|
1181
|
+
let i = start;
|
|
1182
|
+
let depth = 1;
|
|
1183
|
+
while (i < source.length && depth > 0) {
|
|
1184
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1185
|
+
if (i >= source.length)
|
|
1186
|
+
break;
|
|
1187
|
+
const ch = source[i];
|
|
1188
|
+
if (ch === "'" || ch === '"') {
|
|
1189
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1190
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
if (ch === "`") {
|
|
1194
|
+
i = skipTemplateLiteral(source, i);
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (ch === "{") {
|
|
1198
|
+
depth++;
|
|
1199
|
+
i++;
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
if (ch === "}") {
|
|
1203
|
+
depth--;
|
|
1204
|
+
i++;
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
i++;
|
|
1208
|
+
}
|
|
1209
|
+
return i;
|
|
1210
|
+
}
|
|
1211
|
+
function skipTemplateLiteral(source, start) {
|
|
1212
|
+
let i = start + 1;
|
|
1213
|
+
while (i < source.length) {
|
|
1214
|
+
const ch = source[i];
|
|
1215
|
+
if (ch === "\\") {
|
|
1216
|
+
i += 2;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
if (ch === "`")
|
|
1220
|
+
return i + 1;
|
|
1221
|
+
if (ch === "$" && source[i + 1] === "{") {
|
|
1222
|
+
i = skipTemplateExpression(source, i + 2);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
i++;
|
|
1226
|
+
}
|
|
1227
|
+
return i;
|
|
1228
|
+
}
|
|
1229
|
+
function readSpecifierAfterFrom(source, start) {
|
|
1230
|
+
const i = skipWhitespaceAndComments(source, start);
|
|
1231
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1232
|
+
if (!stringLiteral)
|
|
1233
|
+
return { end: i };
|
|
1234
|
+
return { specifier: stringLiteral.value, end: stringLiteral.end };
|
|
1235
|
+
}
|
|
1236
|
+
function readImportCallSpecifier(source, start) {
|
|
1237
|
+
let i = skipWhitespaceAndComments(source, start);
|
|
1238
|
+
if (source[i] !== "(")
|
|
1239
|
+
return { end: i };
|
|
1240
|
+
i = skipWhitespaceAndComments(source, i + 1);
|
|
1241
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1242
|
+
if (!stringLiteral)
|
|
1243
|
+
return { end: i };
|
|
1244
|
+
i = skipWhitespaceAndComments(source, stringLiteral.end);
|
|
1245
|
+
if (source[i] === ")")
|
|
1246
|
+
i++;
|
|
1247
|
+
return { specifier: stringLiteral.value, end: i };
|
|
1248
|
+
}
|
|
1249
|
+
function readImportOrExportStatementSpecifier(source, start, keyword) {
|
|
1250
|
+
let i = skipWhitespaceAndComments(source, start);
|
|
1251
|
+
if (keyword === "import") {
|
|
1252
|
+
if (source[i] === ".")
|
|
1253
|
+
return { end: i + 1 }; // import.meta
|
|
1254
|
+
const sideEffectImport = readStringLiteral(source, i);
|
|
1255
|
+
if (sideEffectImport) {
|
|
1256
|
+
return { specifier: sideEffectImport.value, end: sideEffectImport.end };
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
let depth = 0;
|
|
1260
|
+
while (i < source.length) {
|
|
1261
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1262
|
+
if (i >= source.length)
|
|
1263
|
+
break;
|
|
1264
|
+
const ch = source[i];
|
|
1265
|
+
if (ch === "'" || ch === '"') {
|
|
1266
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1267
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
if (ch === "`") {
|
|
1271
|
+
i = skipTemplateLiteral(source, i);
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
1275
|
+
depth++;
|
|
1276
|
+
i++;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
1280
|
+
if (depth > 0)
|
|
1281
|
+
depth--;
|
|
1282
|
+
i++;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (depth === 0) {
|
|
1286
|
+
if (ch === ";")
|
|
1287
|
+
return { end: i + 1 };
|
|
1288
|
+
const identifier = readIdentifier(source, i);
|
|
1289
|
+
if (identifier?.value === "from") {
|
|
1290
|
+
return readSpecifierAfterFrom(source, identifier.end);
|
|
1291
|
+
}
|
|
1292
|
+
if (identifier) {
|
|
1293
|
+
i = identifier.end;
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
i++;
|
|
1298
|
+
}
|
|
1299
|
+
return { end: i };
|
|
1300
|
+
}
|
|
1301
|
+
function extractModuleSpecifiers(source) {
|
|
1302
|
+
const out = new Set();
|
|
1303
|
+
let i = 0;
|
|
1304
|
+
while (i < source.length) {
|
|
1305
|
+
i = skipWhitespaceAndComments(source, i);
|
|
1306
|
+
if (i >= source.length)
|
|
1307
|
+
break;
|
|
1308
|
+
const ch = source[i];
|
|
1309
|
+
if (ch === "'" || ch === '"') {
|
|
1310
|
+
const stringLiteral = readStringLiteral(source, i);
|
|
1311
|
+
i = stringLiteral ? stringLiteral.end : i + 1;
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (ch === "`") {
|
|
1315
|
+
i = skipTemplateLiteral(source, i);
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const identifier = readIdentifier(source, i);
|
|
1319
|
+
if (!identifier) {
|
|
1320
|
+
i++;
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (identifier.value === "import") {
|
|
1324
|
+
const afterImport = skipWhitespaceAndComments(source, identifier.end);
|
|
1325
|
+
if (source[afterImport] === "(") {
|
|
1326
|
+
const result = readImportCallSpecifier(source, afterImport);
|
|
1327
|
+
if (result.specifier)
|
|
1328
|
+
out.add(result.specifier);
|
|
1329
|
+
i = Math.max(result.end, afterImport + 1);
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const result = readImportOrExportStatementSpecifier(source, identifier.end, "import");
|
|
1333
|
+
if (result.specifier)
|
|
1334
|
+
out.add(result.specifier);
|
|
1335
|
+
i = Math.max(result.end, identifier.end);
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (identifier.value === "export") {
|
|
1339
|
+
const result = readImportOrExportStatementSpecifier(source, identifier.end, "export");
|
|
1340
|
+
if (result.specifier)
|
|
1341
|
+
out.add(result.specifier);
|
|
1342
|
+
i = Math.max(result.end, identifier.end);
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
i = identifier.end;
|
|
1346
|
+
}
|
|
1347
|
+
return out;
|
|
1348
|
+
}
|
|
1349
|
+
function resolveExistingModulePath(candidate) {
|
|
1350
|
+
const resolved = path.resolve(candidate);
|
|
1351
|
+
const candidates = new Set([resolved]);
|
|
1352
|
+
if (!path.extname(resolved)) {
|
|
1353
|
+
for (const ext of RESOLVABLE_MODULE_EXTENSIONS) {
|
|
1354
|
+
candidates.add(`${resolved}${ext}`);
|
|
1355
|
+
candidates.add(path.join(resolved, `index${ext}`));
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
for (const filePath of candidates) {
|
|
1359
|
+
try {
|
|
1360
|
+
if (dntShim.Deno.statSync(filePath).isFile) {
|
|
1361
|
+
return path.resolve(filePath);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
catch {
|
|
1365
|
+
// ignore unresolved module candidates
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
function resolveLocalImportPath(importerPath, specifier) {
|
|
1371
|
+
const cleaned = stripSpecifierSuffix(specifier);
|
|
1372
|
+
if (!cleaned)
|
|
1373
|
+
return undefined;
|
|
1374
|
+
if (cleaned.startsWith("file://")) {
|
|
1375
|
+
try {
|
|
1376
|
+
return resolveExistingModulePath(path.fromFileUrl(cleaned));
|
|
1377
|
+
}
|
|
1378
|
+
catch {
|
|
1379
|
+
return undefined;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (!(cleaned.startsWith("./") || cleaned.startsWith("../") ||
|
|
1383
|
+
path.isAbsolute(cleaned))) {
|
|
1384
|
+
return undefined;
|
|
1385
|
+
}
|
|
1386
|
+
const base = path.isAbsolute(cleaned)
|
|
1387
|
+
? cleaned
|
|
1388
|
+
: path.resolve(path.dirname(importerPath), cleaned);
|
|
1389
|
+
return resolveExistingModulePath(base);
|
|
1390
|
+
}
|
|
1391
|
+
function collectLocalImportGraph(entryPath) {
|
|
1392
|
+
const visited = new Set();
|
|
1393
|
+
const queue = [path.resolve(entryPath)];
|
|
1394
|
+
while (queue.length > 0) {
|
|
1395
|
+
const current = queue.pop();
|
|
1396
|
+
if (visited.has(current))
|
|
1397
|
+
continue;
|
|
1398
|
+
visited.add(current);
|
|
1399
|
+
const ext = path.extname(current).toLowerCase();
|
|
1400
|
+
if (!IMPORT_SOURCE_EXTENSIONS.has(ext)) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
let source;
|
|
1404
|
+
try {
|
|
1405
|
+
source = dntShim.Deno.readTextFileSync(current);
|
|
1406
|
+
}
|
|
1407
|
+
catch {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const specifiers = extractModuleSpecifiers(source);
|
|
1411
|
+
for (const specifier of specifiers) {
|
|
1412
|
+
const resolved = resolveLocalImportPath(current, specifier);
|
|
1413
|
+
if (resolved && !visited.has(resolved)) {
|
|
1414
|
+
queue.push(resolved);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return visited;
|
|
1419
|
+
}
|
|
1420
|
+
const WORKER_ENTRY_PATHS = [
|
|
1421
|
+
"./runtime_worker.ts",
|
|
1422
|
+
"./runtime_orchestration_worker.ts",
|
|
1423
|
+
].map((relative) => path.fromFileUrl(new URL(relative, globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url)));
|
|
1424
|
+
const BUILTIN_SCHEMAS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url)), "../schemas");
|
|
1425
|
+
const BUILTIN_SNIPPETS_DIR = path.resolve(path.dirname(path.fromFileUrl(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url)), "../snippets");
|
|
1426
|
+
let builtinSchemaBootstrapCache;
|
|
1427
|
+
function builtinSchemaBootstrapReads() {
|
|
1428
|
+
if (builtinSchemaBootstrapCache)
|
|
1429
|
+
return builtinSchemaBootstrapCache;
|
|
1430
|
+
const schemaModules = [];
|
|
1431
|
+
const stack = [BUILTIN_SCHEMAS_DIR];
|
|
1432
|
+
while (stack.length > 0) {
|
|
1433
|
+
const current = stack.pop();
|
|
1434
|
+
let entries = [];
|
|
1435
|
+
try {
|
|
1436
|
+
entries = Array.from(dntShim.Deno.readDirSync(current));
|
|
1437
|
+
}
|
|
1438
|
+
catch {
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
for (const entry of entries) {
|
|
1442
|
+
const target = path.join(current, entry.name);
|
|
1443
|
+
if (entry.isDirectory) {
|
|
1444
|
+
stack.push(target);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (!entry.isFile)
|
|
1448
|
+
continue;
|
|
1449
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1450
|
+
if (ext !== ".ts")
|
|
1451
|
+
continue;
|
|
1452
|
+
schemaModules.push(target);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
builtinSchemaBootstrapCache = Array.from(new Set(schemaModules.flatMap((entry) => Array.from(collectLocalImportGraph(entry))))).sort();
|
|
1456
|
+
return builtinSchemaBootstrapCache;
|
|
1457
|
+
}
|
|
1458
|
+
let builtinSnippetBootstrapCache;
|
|
1459
|
+
function builtinSnippetBootstrapReads() {
|
|
1460
|
+
if (builtinSnippetBootstrapCache)
|
|
1461
|
+
return builtinSnippetBootstrapCache;
|
|
1462
|
+
const snippetFiles = [];
|
|
1463
|
+
const stack = [BUILTIN_SNIPPETS_DIR];
|
|
1464
|
+
while (stack.length > 0) {
|
|
1465
|
+
const current = stack.pop();
|
|
1466
|
+
let entries = [];
|
|
1467
|
+
try {
|
|
1468
|
+
entries = Array.from(dntShim.Deno.readDirSync(current));
|
|
1469
|
+
}
|
|
1470
|
+
catch {
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
for (const entry of entries) {
|
|
1474
|
+
const target = path.join(current, entry.name);
|
|
1475
|
+
if (entry.isDirectory) {
|
|
1476
|
+
stack.push(target);
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
if (!entry.isFile)
|
|
1480
|
+
continue;
|
|
1481
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1482
|
+
if (ext !== ".md")
|
|
1483
|
+
continue;
|
|
1484
|
+
snippetFiles.push(target);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
builtinSnippetBootstrapCache = Array.from(new Set(snippetFiles)).sort();
|
|
1488
|
+
return builtinSnippetBootstrapCache;
|
|
1489
|
+
}
|
|
1490
|
+
function workerBootstrapReadAllowlist(deckPath) {
|
|
1491
|
+
return Array.from(new Set([
|
|
1492
|
+
...Array.from(collectLocalImportGraph(deckPath)),
|
|
1493
|
+
...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
|
|
1494
|
+
...builtinSchemaBootstrapReads(),
|
|
1495
|
+
...builtinSnippetBootstrapReads(),
|
|
1496
|
+
])).sort();
|
|
1497
|
+
}
|
|
1498
|
+
let trustedWorkerBootstrapCache;
|
|
1499
|
+
function trustedWorkerBootstrapReads() {
|
|
1500
|
+
if (trustedWorkerBootstrapCache)
|
|
1501
|
+
return trustedWorkerBootstrapCache;
|
|
1502
|
+
const definitionsPath = path.fromFileUrl(new URL("./definitions.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
|
|
1503
|
+
const modPath = path.fromFileUrl(new URL("../mod.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
|
|
1504
|
+
trustedWorkerBootstrapCache = Array.from(new Set([
|
|
1505
|
+
...WORKER_ENTRY_PATHS.flatMap((entry) => Array.from(collectLocalImportGraph(entry))),
|
|
1506
|
+
...Array.from(collectLocalImportGraph(definitionsPath)),
|
|
1507
|
+
...Array.from(collectLocalImportGraph(modPath)),
|
|
1508
|
+
...builtinSchemaBootstrapReads(),
|
|
1509
|
+
...builtinSnippetBootstrapReads(),
|
|
1510
|
+
])).sort();
|
|
1511
|
+
return trustedWorkerBootstrapCache;
|
|
1512
|
+
}
|
|
1513
|
+
function pathMatchesPermissionRoot(root, target) {
|
|
1514
|
+
if (root === target)
|
|
1515
|
+
return true;
|
|
1516
|
+
const rel = path.relative(root, target);
|
|
1517
|
+
return rel.length > 0 && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
1518
|
+
}
|
|
1519
|
+
function constrainBootstrapReads(permissions, roots, trustedReads, reads) {
|
|
1520
|
+
const allowedRoots = [
|
|
1521
|
+
...roots.map((entry) => path.resolve(entry)),
|
|
1522
|
+
...Array.from(permissions.read.values).map((entry) => path.resolve(permissions.baseDir, entry)),
|
|
1523
|
+
];
|
|
1524
|
+
if (permissions.read.all) {
|
|
1525
|
+
return Array.from(new Set(reads)).sort();
|
|
1526
|
+
}
|
|
1527
|
+
if (allowedRoots.length === 0)
|
|
1528
|
+
return [];
|
|
1529
|
+
return reads.filter((entry) => {
|
|
1530
|
+
const target = path.resolve(permissions.baseDir, entry);
|
|
1531
|
+
if (trustedReads.has(target))
|
|
1532
|
+
return true;
|
|
1533
|
+
return allowedRoots.some((root) => pathMatchesPermissionRoot(root, target));
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
function buildWorkerPermissions(permissions, deckPath) {
|
|
1537
|
+
const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
|
|
1538
|
+
const bootstrapReads = constrainBootstrapReads(permissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
|
|
1539
|
+
const mergedRead = permissions.read.all ? true : Array.from(new Set([
|
|
1540
|
+
...Array.from(permissions.read.values),
|
|
1541
|
+
...bootstrapReads,
|
|
1542
|
+
])).sort();
|
|
1543
|
+
return {
|
|
1544
|
+
permissions: {
|
|
1545
|
+
read: mergedRead === true
|
|
1546
|
+
? true
|
|
1547
|
+
: mergedRead.length > 0
|
|
1548
|
+
? mergedRead
|
|
1549
|
+
: false,
|
|
1550
|
+
write: toDenoPermissionList(permissions.write),
|
|
1551
|
+
run: toDenoRunPermission(permissions.run),
|
|
1552
|
+
net: toDenoPermissionList(permissions.net),
|
|
1553
|
+
env: toDenoPermissionList(permissions.env),
|
|
1554
|
+
// Worker module graphs include JSR dependencies (e.g. @std/*). Allow
|
|
1555
|
+
// manifest resolution without widening deck runtime file/run permissions.
|
|
1556
|
+
import: ["jsr.io:443"],
|
|
1557
|
+
},
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function buildDeckInspectWorkerPermissions(deckPath) {
|
|
1561
|
+
const deckDir = path.dirname(deckPath);
|
|
1562
|
+
const workerDirs = WORKER_ENTRY_PATHS.map((entry) => path.dirname(entry));
|
|
1563
|
+
const inspectSeedPermissions = {
|
|
1564
|
+
baseDir: deckDir,
|
|
1565
|
+
read: { all: false, values: new Set() },
|
|
1566
|
+
write: { all: false, values: new Set() },
|
|
1567
|
+
run: { all: false, paths: new Set(), commands: new Set() },
|
|
1568
|
+
net: { all: false, values: new Set() },
|
|
1569
|
+
env: { all: false, values: new Set() },
|
|
1570
|
+
};
|
|
1571
|
+
const bootstrapReads = constrainBootstrapReads(inspectSeedPermissions, [path.dirname(deckPath), ...workerDirs], new Set(trustedWorkerBootstrapReads()), workerBootstrapReadAllowlist(deckPath));
|
|
1572
|
+
const inspectReads = Array.from(new Set([deckDir, ...bootstrapReads])).sort();
|
|
1573
|
+
return {
|
|
1574
|
+
permissions: {
|
|
1575
|
+
read: inspectReads.length > 0 ? inspectReads : false,
|
|
1576
|
+
write: false,
|
|
1577
|
+
run: false,
|
|
1578
|
+
net: false,
|
|
1579
|
+
env: false,
|
|
1580
|
+
},
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
async function inspectDeckInWorker(deckPath, runDeadlineMs) {
|
|
1584
|
+
if (typeof runDeadlineMs === "number" && Number.isFinite(runDeadlineMs)) {
|
|
1585
|
+
ensureNotExpired(runDeadlineMs);
|
|
1586
|
+
}
|
|
1587
|
+
const bridgeSession = randomId("bridge");
|
|
1588
|
+
const worker = createWorkerSandboxBridge(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).href, buildDeckInspectWorkerPermissions(deckPath));
|
|
1589
|
+
let settled = false;
|
|
1590
|
+
const clearAndTerminate = () => {
|
|
1591
|
+
try {
|
|
1592
|
+
worker.terminate();
|
|
1593
|
+
}
|
|
1594
|
+
catch {
|
|
1595
|
+
// ignore
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
let timeoutId;
|
|
1599
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1600
|
+
const finishResolve = (value) => {
|
|
1601
|
+
if (settled)
|
|
1602
|
+
return;
|
|
1603
|
+
settled = true;
|
|
1604
|
+
if (timeoutId !== undefined)
|
|
1605
|
+
clearTimeout(timeoutId);
|
|
1606
|
+
resolve(value);
|
|
1607
|
+
};
|
|
1608
|
+
const finishReject = (err) => {
|
|
1609
|
+
if (settled)
|
|
1610
|
+
return;
|
|
1611
|
+
settled = true;
|
|
1612
|
+
if (timeoutId !== undefined)
|
|
1613
|
+
clearTimeout(timeoutId);
|
|
1614
|
+
reject(err);
|
|
1615
|
+
};
|
|
1616
|
+
const deadlineConstrained = typeof runDeadlineMs === "number" &&
|
|
1617
|
+
Number.isFinite(runDeadlineMs);
|
|
1618
|
+
const timeoutMs = deadlineConstrained
|
|
1619
|
+
? Math.max(0, Math.min(INSPECT_WORKER_TIMEOUT_MS, Math.floor(runDeadlineMs - performance.now())))
|
|
1620
|
+
: INSPECT_WORKER_TIMEOUT_MS;
|
|
1621
|
+
const timeoutMessage = deadlineConstrained &&
|
|
1622
|
+
timeoutMs < INSPECT_WORKER_TIMEOUT_MS
|
|
1623
|
+
? WORKER_TIMEOUT_MESSAGE
|
|
1624
|
+
: INSPECT_WORKER_TIMEOUT_MESSAGE;
|
|
1625
|
+
timeoutId = setTimeout(() => {
|
|
1626
|
+
finishReject(new Error(timeoutMessage));
|
|
1627
|
+
clearAndTerminate();
|
|
1628
|
+
}, timeoutMs);
|
|
1629
|
+
worker.addEventListener("error", (event) => {
|
|
1630
|
+
event.preventDefault?.();
|
|
1631
|
+
finishReject(event.error ??
|
|
1632
|
+
new Error(typeof event.message === "string"
|
|
1633
|
+
? event.message
|
|
1634
|
+
: "Worker execution failed"));
|
|
1635
|
+
});
|
|
1636
|
+
worker.addEventListener("messageerror", () => {
|
|
1637
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1638
|
+
});
|
|
1639
|
+
worker.addEventListener("message", (event) => {
|
|
1640
|
+
const msg = event.data;
|
|
1641
|
+
const receivedSession = typeof msg.bridgeSession === "string"
|
|
1642
|
+
? msg.bridgeSession
|
|
1643
|
+
: "";
|
|
1644
|
+
if (receivedSession !== bridgeSession) {
|
|
1645
|
+
if (typeof msg.type === "string") {
|
|
1646
|
+
logger.warn(`[gambit] rejected inspect-worker message with mismatched bridge session (type=${msg.type})`);
|
|
1647
|
+
}
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1651
|
+
if (type === "deck.inspect.result") {
|
|
1652
|
+
finishResolve(msg.result);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (type === "deck.inspect.error" || type === "run.error") {
|
|
1656
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
});
|
|
1660
|
+
try {
|
|
1661
|
+
worker.postMessage({ type: "deck.inspect", bridgeSession, deckPath });
|
|
1662
|
+
return await outcome;
|
|
1663
|
+
}
|
|
1664
|
+
finally {
|
|
1665
|
+
if (timeoutId !== undefined)
|
|
1666
|
+
clearTimeout(timeoutId);
|
|
1667
|
+
clearAndTerminate();
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function normalizeWorkerError(err) {
|
|
1671
|
+
if (!err || typeof err !== "object") {
|
|
1672
|
+
return new Error(String(err));
|
|
1673
|
+
}
|
|
1674
|
+
const rec = err;
|
|
1675
|
+
const message = typeof rec.message === "string" && rec.message.trim().length > 0
|
|
1676
|
+
? rec.message
|
|
1677
|
+
: "Worker execution failed";
|
|
1678
|
+
const code = typeof rec.code === "string" ? rec.code : undefined;
|
|
1679
|
+
const name = typeof rec.name === "string" ? rec.name : undefined;
|
|
1680
|
+
const source = typeof rec.source === "string" ? rec.source : undefined;
|
|
1681
|
+
const out = new Error(source ? `[${source}] ${message}${code ? ` (${code})` : ""}` : message);
|
|
1682
|
+
if (name)
|
|
1683
|
+
out.name = name;
|
|
1684
|
+
return out;
|
|
1685
|
+
}
|
|
1686
|
+
async function runLlmDeckInWorker(ctx) {
|
|
1687
|
+
throwIfCanceled(ctx.signal);
|
|
1688
|
+
const bridgeSession = randomId("bridge");
|
|
1689
|
+
const completionNonce = randomId("done");
|
|
1690
|
+
const worker = createWorkerSandboxBridge(new URL("./runtime_orchestration_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
|
|
1691
|
+
let settled = false;
|
|
1692
|
+
const clearAndTerminate = () => {
|
|
1693
|
+
try {
|
|
1694
|
+
worker.terminate();
|
|
1695
|
+
}
|
|
1696
|
+
catch {
|
|
1697
|
+
// ignore
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
let timeoutId;
|
|
1701
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1702
|
+
const finishResolve = (value) => {
|
|
1703
|
+
if (settled)
|
|
1704
|
+
return;
|
|
1705
|
+
settled = true;
|
|
1706
|
+
if (timeoutId !== undefined)
|
|
1707
|
+
clearTimeout(timeoutId);
|
|
1708
|
+
resolve(value);
|
|
1709
|
+
};
|
|
1710
|
+
const finishReject = (err) => {
|
|
1711
|
+
if (settled)
|
|
1712
|
+
return;
|
|
1713
|
+
settled = true;
|
|
1714
|
+
if (timeoutId !== undefined)
|
|
1715
|
+
clearTimeout(timeoutId);
|
|
1716
|
+
reject(err);
|
|
1717
|
+
};
|
|
1718
|
+
const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
|
|
1719
|
+
timeoutId = setTimeout(() => {
|
|
1720
|
+
finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
|
|
1721
|
+
clearAndTerminate();
|
|
1722
|
+
}, remainingMs);
|
|
1723
|
+
worker.addEventListener("error", (event) => {
|
|
1724
|
+
event.preventDefault?.();
|
|
1725
|
+
finishReject(event.error ??
|
|
1726
|
+
new Error(typeof event.message === "string"
|
|
1727
|
+
? event.message
|
|
1728
|
+
: "Worker execution failed"));
|
|
1729
|
+
});
|
|
1730
|
+
worker.addEventListener("messageerror", () => {
|
|
1731
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1732
|
+
});
|
|
1733
|
+
worker.addEventListener("message", (event) => {
|
|
1734
|
+
const msg = event.data;
|
|
1735
|
+
if (!msg || typeof msg !== "object")
|
|
1736
|
+
return;
|
|
1737
|
+
if (msg.bridgeSession !== bridgeSession) {
|
|
1738
|
+
logger.warn(`[gambit] rejected orchestration-worker message with mismatched bridge session (type=${msg.type})`);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (msg.type === "trace.event") {
|
|
1742
|
+
ctx.trace?.(msg.event);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (msg.type === "state.update") {
|
|
1746
|
+
ctx.onStateUpdate?.(msg.state);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
if (msg.type === "stream.text") {
|
|
1750
|
+
ctx.onStreamText?.(msg.chunk);
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (msg.type === "model.chat.request") {
|
|
1754
|
+
(async () => {
|
|
1755
|
+
try {
|
|
1756
|
+
const result = await ctx.modelProvider.chat({
|
|
1757
|
+
...msg.input,
|
|
1758
|
+
signal: ctx.signal,
|
|
1759
|
+
onStreamText: (chunk) => {
|
|
1760
|
+
worker.postMessage({
|
|
1761
|
+
type: "model.chat.stream",
|
|
1762
|
+
requestId: msg.requestId,
|
|
1763
|
+
chunk,
|
|
1764
|
+
});
|
|
1765
|
+
},
|
|
1766
|
+
});
|
|
1767
|
+
worker.postMessage({
|
|
1768
|
+
type: "model.chat.result",
|
|
1769
|
+
requestId: msg.requestId,
|
|
1770
|
+
result,
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
catch (err) {
|
|
1774
|
+
worker.postMessage({
|
|
1775
|
+
type: "model.chat.error",
|
|
1776
|
+
requestId: msg.requestId,
|
|
1777
|
+
error: {
|
|
1778
|
+
source: "model",
|
|
1779
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1780
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1781
|
+
code: err?.code,
|
|
1782
|
+
},
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
})();
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (msg.type === "model.responses.request") {
|
|
1789
|
+
(async () => {
|
|
1790
|
+
try {
|
|
1791
|
+
if (!ctx.modelProvider.responses) {
|
|
1792
|
+
throw new Error("Responses API unavailable for current model provider");
|
|
1793
|
+
}
|
|
1794
|
+
const result = await ctx.modelProvider.responses({
|
|
1795
|
+
...msg.input,
|
|
1796
|
+
signal: ctx.signal,
|
|
1797
|
+
onStreamEvent: (streamEvent) => {
|
|
1798
|
+
worker.postMessage({
|
|
1799
|
+
type: "model.responses.event",
|
|
1800
|
+
requestId: msg.requestId,
|
|
1801
|
+
event: streamEvent,
|
|
1802
|
+
});
|
|
1803
|
+
},
|
|
1804
|
+
});
|
|
1805
|
+
worker.postMessage({
|
|
1806
|
+
type: "model.responses.result",
|
|
1807
|
+
requestId: msg.requestId,
|
|
1808
|
+
result,
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
catch (err) {
|
|
1812
|
+
worker.postMessage({
|
|
1813
|
+
type: "model.responses.error",
|
|
1814
|
+
requestId: msg.requestId,
|
|
1815
|
+
error: {
|
|
1816
|
+
source: "model",
|
|
1817
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1818
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1819
|
+
code: err?.code,
|
|
1820
|
+
},
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
})();
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (msg.type === "model.resolveModel.request") {
|
|
1827
|
+
(async () => {
|
|
1828
|
+
try {
|
|
1829
|
+
const result = ctx.modelProvider.resolveModel
|
|
1830
|
+
? await ctx.modelProvider.resolveModel(msg.input)
|
|
1831
|
+
: {
|
|
1832
|
+
model: Array.isArray(msg.input.model)
|
|
1833
|
+
? msg.input.model[0]
|
|
1834
|
+
: msg.input.model,
|
|
1835
|
+
params: msg.input.params,
|
|
1836
|
+
};
|
|
1837
|
+
worker.postMessage({
|
|
1838
|
+
type: "model.resolveModel.result",
|
|
1839
|
+
requestId: msg.requestId,
|
|
1840
|
+
result,
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
catch (err) {
|
|
1844
|
+
worker.postMessage({
|
|
1845
|
+
type: "model.resolveModel.error",
|
|
1846
|
+
requestId: msg.requestId,
|
|
1847
|
+
error: {
|
|
1848
|
+
source: "model",
|
|
1849
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1850
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1851
|
+
code: err?.code,
|
|
1852
|
+
},
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
})();
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (msg.type === "run.result") {
|
|
1859
|
+
if (msg.completionNonce !== completionNonce) {
|
|
1860
|
+
logger.warn(`[gambit] rejected orchestration-worker run.result with invalid completion nonce`);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
finishResolve(msg.result);
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (msg.type === "run.error") {
|
|
1867
|
+
if (msg.completionNonce !== completionNonce) {
|
|
1868
|
+
logger.warn(`[gambit] rejected orchestration-worker run.error with invalid completion nonce`);
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
});
|
|
1875
|
+
try {
|
|
1876
|
+
worker.postMessage({
|
|
1877
|
+
type: "run.start",
|
|
1878
|
+
bridgeSession,
|
|
1879
|
+
completionNonce,
|
|
1880
|
+
options: {
|
|
1881
|
+
path: ctx.deckPath,
|
|
1882
|
+
input: ctx.input,
|
|
1883
|
+
inputProvided: ctx.inputProvided,
|
|
1884
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
1885
|
+
isRoot: ctx.isRoot,
|
|
1886
|
+
guardrails: ctx.guardrails,
|
|
1887
|
+
depth: ctx.depth,
|
|
1888
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
1889
|
+
runId: ctx.runId,
|
|
1890
|
+
defaultModel: ctx.defaultModel,
|
|
1891
|
+
modelOverride: ctx.modelOverride,
|
|
1892
|
+
stream: ctx.stream,
|
|
1893
|
+
state: ctx.state,
|
|
1894
|
+
responsesMode: ctx.responsesMode,
|
|
1895
|
+
allowRootStringInput: ctx.allowRootStringInput,
|
|
1896
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
1897
|
+
},
|
|
1898
|
+
permissionCeiling: toWirePermissionSet(ctx.permissions),
|
|
1899
|
+
});
|
|
1900
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
1901
|
+
return await outcome;
|
|
1902
|
+
}
|
|
1903
|
+
finally {
|
|
1904
|
+
if (timeoutId !== undefined)
|
|
1905
|
+
clearTimeout(timeoutId);
|
|
1906
|
+
clearAndTerminate();
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
async function runComputeDeckInWorker(ctx) {
|
|
1910
|
+
throwIfCanceled(ctx.signal);
|
|
1911
|
+
const { runId } = ctx;
|
|
1912
|
+
const actionCallId = randomId("action");
|
|
1913
|
+
const bridgeSession = randomId("bridge");
|
|
1914
|
+
const completionNonce = randomId("done");
|
|
1915
|
+
const worker = createWorkerSandboxBridge(new URL("./runtime_worker.ts", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).href, buildWorkerPermissions(ctx.permissions, ctx.deckPath));
|
|
1916
|
+
let settled = false;
|
|
1917
|
+
const clearAndTerminate = () => {
|
|
1918
|
+
try {
|
|
1919
|
+
worker.terminate();
|
|
1920
|
+
}
|
|
1921
|
+
catch {
|
|
1922
|
+
// ignore
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
let timeoutId;
|
|
1926
|
+
const activeSpawnRequests = new Set();
|
|
1927
|
+
let currentState = ctx.state;
|
|
1928
|
+
const outcome = new Promise((resolve, reject) => {
|
|
1929
|
+
const finishResolve = (value) => {
|
|
1930
|
+
if (settled)
|
|
1931
|
+
return;
|
|
1932
|
+
settled = true;
|
|
1933
|
+
if (timeoutId !== undefined)
|
|
1934
|
+
clearTimeout(timeoutId);
|
|
1935
|
+
resolve(value);
|
|
1936
|
+
};
|
|
1937
|
+
const finishReject = (err) => {
|
|
1938
|
+
if (settled)
|
|
1939
|
+
return;
|
|
1940
|
+
settled = true;
|
|
1941
|
+
if (timeoutId !== undefined)
|
|
1942
|
+
clearTimeout(timeoutId);
|
|
1943
|
+
reject(err);
|
|
1944
|
+
};
|
|
1945
|
+
const remainingMs = Math.max(0, Math.floor(ctx.runDeadlineMs - performance.now()));
|
|
1946
|
+
timeoutId = setTimeout(() => {
|
|
1947
|
+
finishReject(new Error(WORKER_TIMEOUT_MESSAGE));
|
|
1948
|
+
clearAndTerminate();
|
|
1949
|
+
}, remainingMs);
|
|
1950
|
+
worker.addEventListener("error", (event) => {
|
|
1951
|
+
event.preventDefault?.();
|
|
1952
|
+
finishReject(event.error ??
|
|
1953
|
+
new Error(typeof event.message === "string"
|
|
1954
|
+
? event.message
|
|
1955
|
+
: "Worker execution failed"));
|
|
1956
|
+
});
|
|
1957
|
+
worker.addEventListener("messageerror", () => {
|
|
1958
|
+
finishReject(new Error("Worker bridge message serialization failed"));
|
|
1959
|
+
});
|
|
1960
|
+
worker.addEventListener("message", (event) => {
|
|
1961
|
+
const msg = event.data;
|
|
1962
|
+
const receivedBridgeSession = typeof msg.bridgeSession === "string"
|
|
1963
|
+
? msg.bridgeSession
|
|
1964
|
+
: "";
|
|
1965
|
+
if (receivedBridgeSession !== bridgeSession) {
|
|
1966
|
+
const type = typeof msg.type === "string" ? msg.type : "unknown";
|
|
1967
|
+
logger.warn(`[gambit] rejected compute-worker message with mismatched bridge session (type=${type})`);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
// Ignore any late worker messages once this run has already settled.
|
|
1971
|
+
if (settled)
|
|
1972
|
+
return;
|
|
1973
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1974
|
+
if (type === "log.entry") {
|
|
1975
|
+
if (!ctx.trace)
|
|
1976
|
+
return;
|
|
1977
|
+
const entry = msg.entry;
|
|
1978
|
+
const raw = typeof entry === "string"
|
|
1979
|
+
? { message: entry }
|
|
1980
|
+
: entry && typeof entry === "object"
|
|
1981
|
+
? entry
|
|
1982
|
+
: { message: "" };
|
|
1983
|
+
const message = typeof raw.message === "string"
|
|
1984
|
+
? raw.message
|
|
1985
|
+
: raw.message !== undefined
|
|
1986
|
+
? String(raw.message)
|
|
1987
|
+
: "";
|
|
1988
|
+
const title = typeof raw.title === "string" ? raw.title : undefined;
|
|
1989
|
+
const body = raw.body ?? raw.message ?? message;
|
|
1990
|
+
ctx.trace({
|
|
1991
|
+
type: "log",
|
|
1992
|
+
runId,
|
|
1993
|
+
deckPath: ctx.deckPath,
|
|
1994
|
+
actionCallId,
|
|
1995
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
1996
|
+
level: raw.level ?? "info",
|
|
1997
|
+
title: title ?? (message || undefined),
|
|
1998
|
+
message,
|
|
1999
|
+
body,
|
|
2000
|
+
meta: raw.meta,
|
|
2001
|
+
});
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (type === "spawn.request") {
|
|
2005
|
+
const req = msg;
|
|
2006
|
+
const requestId = req.requestId;
|
|
2007
|
+
if (!requestId)
|
|
2008
|
+
return;
|
|
2009
|
+
if (activeSpawnRequests.has(requestId)) {
|
|
2010
|
+
logger.warn(`[gambit] rejected duplicate compute-worker spawn.request (${requestId})`);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
activeSpawnRequests.add(requestId);
|
|
2014
|
+
(async () => {
|
|
2015
|
+
try {
|
|
2016
|
+
const parentFromWorker = normalizePermissionBaseDir(fromWirePermissionSet(req.payload.parentPermissions), req.payload.parentPermissionsBaseDir);
|
|
2017
|
+
// Enforce monotonicity against the parent effective ceiling.
|
|
2018
|
+
const bridgedParent = intersectPermissions(ctx.permissions, parentFromWorker, req.payload.parentPermissionsBaseDir);
|
|
2019
|
+
const childResult = await runDeck({
|
|
2020
|
+
path: req.payload.path,
|
|
2021
|
+
input: req.payload.input,
|
|
2022
|
+
modelProvider: ctx.modelProvider,
|
|
2023
|
+
isRoot: false,
|
|
2024
|
+
guardrails: ctx.guardrails,
|
|
2025
|
+
depth: ctx.depth + 1,
|
|
2026
|
+
parentActionCallId: req.payload.parentActionCallId,
|
|
2027
|
+
runId,
|
|
2028
|
+
defaultModel: ctx.defaultModel,
|
|
2029
|
+
modelOverride: ctx.modelOverride,
|
|
2030
|
+
trace: ctx.trace,
|
|
2031
|
+
stream: ctx.stream,
|
|
2032
|
+
state: currentState,
|
|
2033
|
+
onStateUpdate: (state) => {
|
|
2034
|
+
currentState = state;
|
|
2035
|
+
ctx.onStateUpdate?.(state);
|
|
2036
|
+
},
|
|
2037
|
+
onStreamText: ctx.onStreamText,
|
|
2038
|
+
responsesMode: ctx.responsesMode,
|
|
2039
|
+
initialUserMessage: req.payload.initialUserMessage,
|
|
2040
|
+
inputProvided: true,
|
|
2041
|
+
parentPermissions: bridgedParent,
|
|
2042
|
+
workspacePermissions: req.payload.workspacePermissions,
|
|
2043
|
+
workspacePermissionsBaseDir: req.payload.workspacePermissionsBaseDir,
|
|
2044
|
+
sessionPermissions: req.payload.sessionPermissions,
|
|
2045
|
+
sessionPermissionsBaseDir: req.payload.sessionPermissionsBaseDir,
|
|
2046
|
+
runDeadlineMs: Math.min(ctx.runDeadlineMs, Number.isFinite(req.payload.runDeadlineMs)
|
|
2047
|
+
? req.payload.runDeadlineMs
|
|
2048
|
+
: ctx.runDeadlineMs),
|
|
2049
|
+
workerSandbox: true,
|
|
2050
|
+
signal: ctx.signal,
|
|
2051
|
+
onTool: ctx.onTool,
|
|
2052
|
+
});
|
|
2053
|
+
worker.postMessage({
|
|
2054
|
+
type: "spawn.result",
|
|
2055
|
+
requestId,
|
|
2056
|
+
result: childResult,
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
catch (err) {
|
|
2060
|
+
worker.postMessage({
|
|
2061
|
+
type: "spawn.error",
|
|
2062
|
+
requestId,
|
|
2063
|
+
error: {
|
|
2064
|
+
source: "child",
|
|
2065
|
+
name: err instanceof Error ? err.name : undefined,
|
|
2066
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2067
|
+
code: err?.code,
|
|
2068
|
+
},
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
finally {
|
|
2072
|
+
activeSpawnRequests.delete(requestId);
|
|
2073
|
+
}
|
|
2074
|
+
})();
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
if (type === "state.update") {
|
|
2078
|
+
const nextState = msg.state;
|
|
2079
|
+
if (!nextState || typeof nextState !== "object")
|
|
2080
|
+
return;
|
|
2081
|
+
currentState = nextState;
|
|
2082
|
+
ctx.onStateUpdate?.(nextState);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (type === "run.result") {
|
|
2086
|
+
if (msg.completionNonce !==
|
|
2087
|
+
completionNonce) {
|
|
2088
|
+
logger.warn(`[gambit] rejected compute-worker run.result with invalid completion nonce`);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
finishResolve(msg.result);
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
if (type === "run.error") {
|
|
2095
|
+
if (msg.completionNonce !==
|
|
2096
|
+
completionNonce) {
|
|
2097
|
+
logger.warn(`[gambit] rejected compute-worker run.error with invalid completion nonce`);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
finishReject(normalizeWorkerError(msg.error));
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
});
|
|
2104
|
+
try {
|
|
2105
|
+
worker.postMessage({
|
|
2106
|
+
type: "run.start",
|
|
2107
|
+
bridgeSession,
|
|
2108
|
+
completionNonce,
|
|
2109
|
+
runId,
|
|
2110
|
+
actionCallId,
|
|
2111
|
+
deckPath: ctx.deckPath,
|
|
2112
|
+
input: ctx.input,
|
|
2113
|
+
state: ctx.state,
|
|
2114
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
2115
|
+
depth: ctx.depth,
|
|
2116
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2117
|
+
permissions: toWirePermissionSet(ctx.permissions),
|
|
2118
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2119
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2120
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2121
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2122
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2123
|
+
isRoot: ctx.isRoot,
|
|
2124
|
+
allowRootStringInput: ctx.allowRootStringInput,
|
|
2125
|
+
});
|
|
2126
|
+
const raw = await outcome;
|
|
2127
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
2128
|
+
return raw;
|
|
197
2129
|
}
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
2130
|
+
finally {
|
|
2131
|
+
if (timeoutId !== undefined)
|
|
2132
|
+
clearTimeout(timeoutId);
|
|
2133
|
+
clearAndTerminate();
|
|
202
2134
|
}
|
|
203
|
-
throw new Error(`Deck ${deck.path} requires outputSchema (non-root)`);
|
|
204
2135
|
}
|
|
205
|
-
async function
|
|
2136
|
+
async function runComputeDeckInProcess(ctx) {
|
|
206
2137
|
const { deck, runId } = ctx;
|
|
207
2138
|
const actionCallId = randomId("action");
|
|
2139
|
+
let computeState = ctx.state
|
|
2140
|
+
? {
|
|
2141
|
+
...ctx.state,
|
|
2142
|
+
messages: Array.isArray(ctx.state.messages)
|
|
2143
|
+
? ctx.state.messages.map(sanitizeMessage)
|
|
2144
|
+
: [],
|
|
2145
|
+
meta: ctx.state.meta ? { ...ctx.state.meta } : undefined,
|
|
2146
|
+
messageRefs: Array.isArray(ctx.state.messageRefs)
|
|
2147
|
+
? [...ctx.state.messageRefs]
|
|
2148
|
+
: undefined,
|
|
2149
|
+
}
|
|
2150
|
+
: undefined;
|
|
2151
|
+
const ensureComputeState = () => {
|
|
2152
|
+
if (computeState)
|
|
2153
|
+
return computeState;
|
|
2154
|
+
computeState = {
|
|
2155
|
+
runId,
|
|
2156
|
+
messages: [],
|
|
2157
|
+
meta: {},
|
|
2158
|
+
messageRefs: [],
|
|
2159
|
+
};
|
|
2160
|
+
return computeState;
|
|
2161
|
+
};
|
|
2162
|
+
const publishComputeState = () => {
|
|
2163
|
+
if (!computeState)
|
|
2164
|
+
return;
|
|
2165
|
+
ctx.onStateUpdate?.({
|
|
2166
|
+
...computeState,
|
|
2167
|
+
messages: computeState.messages.map(sanitizeMessage),
|
|
2168
|
+
meta: computeState.meta ? { ...computeState.meta } : undefined,
|
|
2169
|
+
messageRefs: Array.isArray(computeState.messageRefs)
|
|
2170
|
+
? [...computeState.messageRefs]
|
|
2171
|
+
: undefined,
|
|
2172
|
+
});
|
|
2173
|
+
};
|
|
208
2174
|
const execContext = {
|
|
209
2175
|
runId,
|
|
210
2176
|
actionCallId,
|
|
211
2177
|
parentActionCallId: ctx.parentActionCallId,
|
|
212
2178
|
depth: ctx.depth,
|
|
213
2179
|
input: ctx.input,
|
|
2180
|
+
initialUserMessage: ctx.initialUserMessage,
|
|
2181
|
+
getSessionMeta: (key) => {
|
|
2182
|
+
if (!key)
|
|
2183
|
+
return undefined;
|
|
2184
|
+
return computeState?.meta?.[key];
|
|
2185
|
+
},
|
|
2186
|
+
setSessionMeta: (key, value) => {
|
|
2187
|
+
if (!key)
|
|
2188
|
+
return;
|
|
2189
|
+
const state = ensureComputeState();
|
|
2190
|
+
const nextMeta = { ...(state.meta ?? {}) };
|
|
2191
|
+
if (value === undefined) {
|
|
2192
|
+
delete nextMeta[key];
|
|
2193
|
+
}
|
|
2194
|
+
else {
|
|
2195
|
+
nextMeta[key] = value;
|
|
2196
|
+
}
|
|
2197
|
+
state.meta = nextMeta;
|
|
2198
|
+
publishComputeState();
|
|
2199
|
+
},
|
|
2200
|
+
appendMessage: (message) => {
|
|
2201
|
+
const role = message.role;
|
|
2202
|
+
const content = String(message.content ?? "");
|
|
2203
|
+
if ((role !== "user" && role !== "assistant") || !content.trim()) {
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
const state = ensureComputeState();
|
|
2207
|
+
const sanitized = sanitizeMessage({ role, content: content.trim() });
|
|
2208
|
+
state.messages = [...(state.messages ?? []), sanitized];
|
|
2209
|
+
const refs = Array.isArray(state.messageRefs)
|
|
2210
|
+
? [...state.messageRefs]
|
|
2211
|
+
: [];
|
|
2212
|
+
refs.push({ id: randomId("msg"), role: sanitized.role });
|
|
2213
|
+
state.messageRefs = refs;
|
|
2214
|
+
publishComputeState();
|
|
2215
|
+
},
|
|
214
2216
|
label: deck.label,
|
|
215
2217
|
log: (entry) => {
|
|
216
2218
|
if (!ctx.trace)
|
|
@@ -241,9 +2243,13 @@ async function runComputeDeck(ctx) {
|
|
|
241
2243
|
});
|
|
242
2244
|
},
|
|
243
2245
|
spawnAndWait: async (opts) => {
|
|
2246
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
244
2247
|
const childPath = path.isAbsolute(opts.path)
|
|
245
2248
|
? opts.path
|
|
246
2249
|
: path.resolve(path.dirname(deck.path), opts.path);
|
|
2250
|
+
const childInitialUserMessage = Object.hasOwn(opts, "initialUserMessage")
|
|
2251
|
+
? opts.initialUserMessage
|
|
2252
|
+
: ctx.initialUserMessage;
|
|
247
2253
|
return await runDeck({
|
|
248
2254
|
path: childPath,
|
|
249
2255
|
input: opts.input,
|
|
@@ -257,11 +2263,33 @@ async function runComputeDeck(ctx) {
|
|
|
257
2263
|
modelOverride: ctx.modelOverride,
|
|
258
2264
|
trace: ctx.trace,
|
|
259
2265
|
stream: ctx.stream,
|
|
260
|
-
state:
|
|
261
|
-
onStateUpdate:
|
|
2266
|
+
state: computeState,
|
|
2267
|
+
onStateUpdate: (state) => {
|
|
2268
|
+
computeState = {
|
|
2269
|
+
...state,
|
|
2270
|
+
messages: Array.isArray(state.messages)
|
|
2271
|
+
? state.messages.map(sanitizeMessage)
|
|
2272
|
+
: [],
|
|
2273
|
+
meta: state.meta ? { ...state.meta } : undefined,
|
|
2274
|
+
messageRefs: Array.isArray(state.messageRefs)
|
|
2275
|
+
? [...state.messageRefs]
|
|
2276
|
+
: undefined,
|
|
2277
|
+
};
|
|
2278
|
+
ctx.onStateUpdate?.(state);
|
|
2279
|
+
},
|
|
262
2280
|
onStreamText: ctx.onStreamText,
|
|
263
|
-
|
|
2281
|
+
responsesMode: ctx.responsesMode,
|
|
2282
|
+
initialUserMessage: childInitialUserMessage,
|
|
264
2283
|
inputProvided: true,
|
|
2284
|
+
parentPermissions: ctx.permissions,
|
|
2285
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2286
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2287
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2288
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2289
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2290
|
+
workerSandbox: ctx.workerSandbox,
|
|
2291
|
+
signal: ctx.signal,
|
|
2292
|
+
onTool: ctx.onTool,
|
|
265
2293
|
});
|
|
266
2294
|
},
|
|
267
2295
|
fail: (opts) => {
|
|
@@ -269,7 +2297,9 @@ async function runComputeDeck(ctx) {
|
|
|
269
2297
|
},
|
|
270
2298
|
return: (payload) => Promise.resolve(payload),
|
|
271
2299
|
};
|
|
2300
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
272
2301
|
const raw = await deck.executor(execContext);
|
|
2302
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
273
2303
|
return validateOutput(deck, raw, ctx.depth === 0);
|
|
274
2304
|
}
|
|
275
2305
|
async function runLlmDeck(ctx) {
|
|
@@ -277,13 +2307,17 @@ async function runLlmDeck(ctx) {
|
|
|
277
2307
|
const actionCallId = randomId("action");
|
|
278
2308
|
const start = performance.now();
|
|
279
2309
|
const respondEnabled = Boolean(deck.respond);
|
|
2310
|
+
const useResponses = Boolean(ctx.responsesMode) ||
|
|
2311
|
+
ctx.state?.format === "responses";
|
|
280
2312
|
const systemPrompt = buildSystemPrompt(deck);
|
|
281
2313
|
const refToolCallId = randomId("call");
|
|
282
|
-
const messages = ctx.state?.messages
|
|
2314
|
+
const messages = ctx.state?.messages?.length
|
|
283
2315
|
? ctx.state.messages.map(sanitizeMessage)
|
|
284
|
-
:
|
|
2316
|
+
: ctx.state?.items?.length
|
|
2317
|
+
? messagesFromResponseItems(ctx.state.items).map(sanitizeMessage)
|
|
2318
|
+
: [];
|
|
285
2319
|
const resumed = messages.length > 0;
|
|
286
|
-
const
|
|
2320
|
+
const sendContext = Boolean(inputProvided) && input !== undefined && !resumed;
|
|
287
2321
|
const idleController = createIdleController({
|
|
288
2322
|
cfg: deck.handlers?.onIdle,
|
|
289
2323
|
deck,
|
|
@@ -298,11 +2332,21 @@ async function runLlmDeck(ctx) {
|
|
|
298
2332
|
stream: ctx.stream,
|
|
299
2333
|
onStreamText: ctx.onStreamText,
|
|
300
2334
|
pushMessages: (msgs) => messages.push(...msgs.map(sanitizeMessage)),
|
|
2335
|
+
responsesMode: ctx.responsesMode,
|
|
2336
|
+
permissions: ctx.permissions,
|
|
2337
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2338
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2339
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2340
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2341
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2342
|
+
workerSandbox: ctx.workerSandbox,
|
|
2343
|
+
signal: ctx.signal,
|
|
2344
|
+
onTool: ctx.onTool,
|
|
301
2345
|
});
|
|
302
2346
|
let streamingBuffer = "";
|
|
303
2347
|
let streamingCommitted = false;
|
|
304
2348
|
const wrappedOnStreamText = (chunk) => {
|
|
305
|
-
if (!chunk)
|
|
2349
|
+
if (!chunk || ctx.signal?.aborted)
|
|
306
2350
|
return;
|
|
307
2351
|
idleController.touch();
|
|
308
2352
|
streamingBuffer += chunk;
|
|
@@ -310,13 +2354,14 @@ async function runLlmDeck(ctx) {
|
|
|
310
2354
|
};
|
|
311
2355
|
if (!resumed) {
|
|
312
2356
|
messages.push(sanitizeMessage({ role: "system", content: systemPrompt }));
|
|
313
|
-
if (
|
|
2357
|
+
if (sendContext) {
|
|
314
2358
|
ctx.trace?.({
|
|
315
2359
|
type: "tool.call",
|
|
316
2360
|
runId,
|
|
317
2361
|
actionCallId: refToolCallId,
|
|
318
|
-
name:
|
|
2362
|
+
name: GAMBIT_TOOL_CONTEXT,
|
|
319
2363
|
args: {},
|
|
2364
|
+
toolKind: "internal",
|
|
320
2365
|
parentActionCallId: actionCallId,
|
|
321
2366
|
});
|
|
322
2367
|
messages.push(sanitizeMessage({
|
|
@@ -326,13 +2371,13 @@ async function runLlmDeck(ctx) {
|
|
|
326
2371
|
id: refToolCallId,
|
|
327
2372
|
type: "function",
|
|
328
2373
|
function: {
|
|
329
|
-
name:
|
|
2374
|
+
name: GAMBIT_TOOL_CONTEXT,
|
|
330
2375
|
arguments: "{}",
|
|
331
2376
|
},
|
|
332
2377
|
}],
|
|
333
2378
|
}), sanitizeMessage({
|
|
334
2379
|
role: "tool",
|
|
335
|
-
name:
|
|
2380
|
+
name: GAMBIT_TOOL_CONTEXT,
|
|
336
2381
|
tool_call_id: refToolCallId,
|
|
337
2382
|
content: JSON.stringify(input),
|
|
338
2383
|
}));
|
|
@@ -340,8 +2385,9 @@ async function runLlmDeck(ctx) {
|
|
|
340
2385
|
type: "tool.result",
|
|
341
2386
|
runId,
|
|
342
2387
|
actionCallId: refToolCallId,
|
|
343
|
-
name:
|
|
2388
|
+
name: GAMBIT_TOOL_CONTEXT,
|
|
344
2389
|
result: input,
|
|
2390
|
+
toolKind: "internal",
|
|
345
2391
|
parentActionCallId: actionCallId,
|
|
346
2392
|
});
|
|
347
2393
|
}
|
|
@@ -362,29 +2408,36 @@ async function runLlmDeck(ctx) {
|
|
|
362
2408
|
});
|
|
363
2409
|
}
|
|
364
2410
|
idleController.touch();
|
|
365
|
-
const tools = await buildToolDefs(deck);
|
|
2411
|
+
const tools = await buildToolDefs(deck, ctx.permissions);
|
|
366
2412
|
ctx.trace?.({
|
|
367
2413
|
type: "deck.start",
|
|
368
2414
|
runId,
|
|
369
2415
|
deckPath: deck.path,
|
|
370
2416
|
actionCallId,
|
|
371
2417
|
parentActionCallId: ctx.parentActionCallId,
|
|
2418
|
+
permissions: ctx.permissionsTrace,
|
|
372
2419
|
});
|
|
373
2420
|
let passes = 0;
|
|
374
2421
|
try {
|
|
375
2422
|
while (passes < guardrails.maxPasses) {
|
|
376
2423
|
passes++;
|
|
377
|
-
|
|
378
|
-
throw new Error("Timeout exceeded");
|
|
379
|
-
}
|
|
2424
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
380
2425
|
streamingBuffer = "";
|
|
381
2426
|
streamingCommitted = false;
|
|
382
|
-
const
|
|
2427
|
+
const modelCandidate = ctx.modelOverride ??
|
|
383
2428
|
deck.modelParams?.model ??
|
|
384
2429
|
ctx.defaultModel ??
|
|
385
2430
|
(() => {
|
|
386
2431
|
throw new Error(`No model configured for deck ${deck.path} and no --model provided`);
|
|
387
2432
|
})();
|
|
2433
|
+
const resolved = await resolveModelChoice({
|
|
2434
|
+
model: modelCandidate,
|
|
2435
|
+
params: toProviderParams(deck.modelParams),
|
|
2436
|
+
modelProvider,
|
|
2437
|
+
deckPath: deck.path,
|
|
2438
|
+
});
|
|
2439
|
+
const model = resolved.model;
|
|
2440
|
+
const providerParams = resolved.params;
|
|
388
2441
|
const stateMessages = ctx.state?.messages?.length;
|
|
389
2442
|
ctx.trace?.({
|
|
390
2443
|
type: "model.call",
|
|
@@ -398,21 +2451,134 @@ async function runLlmDeck(ctx) {
|
|
|
398
2451
|
messages: messages.map(sanitizeMessage),
|
|
399
2452
|
tools,
|
|
400
2453
|
stateMessages,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
model,
|
|
405
|
-
messages,
|
|
406
|
-
tools,
|
|
407
|
-
stream: ctx.stream,
|
|
408
|
-
state: ctx.state,
|
|
409
|
-
params: toProviderParams(deck.modelParams),
|
|
410
|
-
onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
|
|
411
|
-
? wrappedOnStreamText
|
|
2454
|
+
mode: useResponses ? "responses" : "chat",
|
|
2455
|
+
responseItems: useResponses
|
|
2456
|
+
? responseItemsFromMessages(messages)
|
|
412
2457
|
: undefined,
|
|
2458
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
413
2459
|
});
|
|
2460
|
+
let responseOutputItems;
|
|
2461
|
+
const responses = modelProvider.responses;
|
|
2462
|
+
const projectedToolCalls = new Set();
|
|
2463
|
+
const projectedToolResults = new Set();
|
|
2464
|
+
const projectedToolNames = new Map();
|
|
2465
|
+
const result = (useResponses && responses)
|
|
2466
|
+
? await (async () => {
|
|
2467
|
+
const responseItems = responseItemsFromMessages(messages);
|
|
2468
|
+
let sawDelta = false;
|
|
2469
|
+
const response = await responses({
|
|
2470
|
+
request: {
|
|
2471
|
+
model,
|
|
2472
|
+
input: responseItems,
|
|
2473
|
+
tools: tools,
|
|
2474
|
+
stream: ctx.stream,
|
|
2475
|
+
params: providerParams,
|
|
2476
|
+
},
|
|
2477
|
+
state: ctx.state,
|
|
2478
|
+
deckPath: deck.path,
|
|
2479
|
+
signal: ctx.signal,
|
|
2480
|
+
onStreamEvent: (ctx.trace || ctx.onStreamText || deck.handlers?.onIdle)
|
|
2481
|
+
? (event) => {
|
|
2482
|
+
if (ctx.trace) {
|
|
2483
|
+
const streamEvent = event;
|
|
2484
|
+
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2485
|
+
streamEvent,
|
|
2486
|
+
runId,
|
|
2487
|
+
actionCallId,
|
|
2488
|
+
deckPath: deck.path,
|
|
2489
|
+
model,
|
|
2490
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2491
|
+
trace: ctx.trace,
|
|
2492
|
+
});
|
|
2493
|
+
if (!handledAsResponse) {
|
|
2494
|
+
ctx.trace({
|
|
2495
|
+
type: "model.stream.event",
|
|
2496
|
+
runId,
|
|
2497
|
+
actionCallId,
|
|
2498
|
+
deckPath: deck.path,
|
|
2499
|
+
model,
|
|
2500
|
+
event: streamEvent,
|
|
2501
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
projectStreamToolTraceEvents({
|
|
2505
|
+
streamEvent,
|
|
2506
|
+
runId,
|
|
2507
|
+
parentActionCallId: actionCallId,
|
|
2508
|
+
trace: ctx.trace,
|
|
2509
|
+
emittedCalls: projectedToolCalls,
|
|
2510
|
+
emittedResults: projectedToolResults,
|
|
2511
|
+
toolNames: projectedToolNames,
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
if (event.type === "response.output_text.delta") {
|
|
2515
|
+
sawDelta = true;
|
|
2516
|
+
wrappedOnStreamText(event.delta);
|
|
2517
|
+
}
|
|
2518
|
+
else if (event.type === "response.output_text.done" && !sawDelta) {
|
|
2519
|
+
wrappedOnStreamText(event.text);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
: undefined,
|
|
2523
|
+
});
|
|
2524
|
+
responseOutputItems = response.output ?? [];
|
|
2525
|
+
const mapped = mapResponseOutput(responseOutputItems);
|
|
2526
|
+
return {
|
|
2527
|
+
message: mapped.message,
|
|
2528
|
+
finishReason: mapped.toolCalls?.length ? "tool_calls" : "stop",
|
|
2529
|
+
toolCalls: mapped.toolCalls,
|
|
2530
|
+
usage: response.usage,
|
|
2531
|
+
updatedState: response.updatedState,
|
|
2532
|
+
};
|
|
2533
|
+
})()
|
|
2534
|
+
: await modelProvider.chat({
|
|
2535
|
+
model,
|
|
2536
|
+
messages,
|
|
2537
|
+
tools,
|
|
2538
|
+
stream: ctx.stream,
|
|
2539
|
+
state: ctx.state,
|
|
2540
|
+
deckPath: deck.path,
|
|
2541
|
+
signal: ctx.signal,
|
|
2542
|
+
params: providerParams,
|
|
2543
|
+
onStreamText: (ctx.onStreamText || deck.handlers?.onIdle)
|
|
2544
|
+
? wrappedOnStreamText
|
|
2545
|
+
: undefined,
|
|
2546
|
+
onStreamEvent: ctx.trace
|
|
2547
|
+
? (event) => {
|
|
2548
|
+
const handledAsResponse = traceOpenResponsesStreamEvent({
|
|
2549
|
+
streamEvent: event,
|
|
2550
|
+
runId,
|
|
2551
|
+
actionCallId,
|
|
2552
|
+
deckPath: deck.path,
|
|
2553
|
+
model,
|
|
2554
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2555
|
+
trace: ctx.trace,
|
|
2556
|
+
});
|
|
2557
|
+
if (!handledAsResponse) {
|
|
2558
|
+
ctx.trace?.({
|
|
2559
|
+
type: "model.stream.event",
|
|
2560
|
+
runId,
|
|
2561
|
+
actionCallId,
|
|
2562
|
+
deckPath: deck.path,
|
|
2563
|
+
model,
|
|
2564
|
+
event,
|
|
2565
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
projectStreamToolTraceEvents({
|
|
2569
|
+
streamEvent: event,
|
|
2570
|
+
runId,
|
|
2571
|
+
parentActionCallId: actionCallId,
|
|
2572
|
+
trace: ctx.trace,
|
|
2573
|
+
emittedCalls: projectedToolCalls,
|
|
2574
|
+
emittedResults: projectedToolResults,
|
|
2575
|
+
toolNames: projectedToolNames,
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
: undefined,
|
|
2579
|
+
});
|
|
414
2580
|
idleController.touch();
|
|
415
|
-
|
|
2581
|
+
let message = result.message;
|
|
416
2582
|
ctx.trace?.({
|
|
417
2583
|
type: "model.result",
|
|
418
2584
|
runId,
|
|
@@ -423,6 +2589,9 @@ async function runLlmDeck(ctx) {
|
|
|
423
2589
|
message: sanitizeMessage(message),
|
|
424
2590
|
toolCalls: result.toolCalls,
|
|
425
2591
|
stateMessages: result.updatedState?.messages?.length,
|
|
2592
|
+
usage: result.usage,
|
|
2593
|
+
mode: useResponses ? "responses" : "chat",
|
|
2594
|
+
responseItems: responseOutputItems,
|
|
426
2595
|
parentActionCallId: ctx.parentActionCallId,
|
|
427
2596
|
});
|
|
428
2597
|
const computeState = (updated) => {
|
|
@@ -431,17 +2600,31 @@ async function runLlmDeck(ctx) {
|
|
|
431
2600
|
const mergedMessages = base.messages && base.messages.length > 0
|
|
432
2601
|
? base.messages.map(sanitizeMessage)
|
|
433
2602
|
: messages.map(sanitizeMessage);
|
|
2603
|
+
const responseItems = useResponses
|
|
2604
|
+
? responseItemsFromMessages(mergedMessages)
|
|
2605
|
+
: updated?.items ?? ctx.state?.items;
|
|
434
2606
|
const priorRefs = updated?.messageRefs ?? ctx.state?.messageRefs ?? [];
|
|
435
2607
|
const messageRefs = mergedMessages.map((m, idx) => priorRefs[idx] ?? { id: randomId("msg"), role: m.role });
|
|
436
2608
|
const feedback = updated?.feedback ?? ctx.state?.feedback;
|
|
437
2609
|
const traces = updated?.traces ?? ctx.state?.traces;
|
|
2610
|
+
const meta = updated?.meta ?? ctx.state?.meta;
|
|
2611
|
+
const notes = updated?.notes ?? ctx.state?.notes;
|
|
2612
|
+
const conversationScore = updated?.conversationScore ??
|
|
2613
|
+
ctx.state?.conversationScore;
|
|
438
2614
|
return {
|
|
439
2615
|
...base,
|
|
440
2616
|
runId,
|
|
441
2617
|
messages: mergedMessages,
|
|
2618
|
+
format: useResponses
|
|
2619
|
+
? "responses"
|
|
2620
|
+
: updated?.format ?? ctx.state?.format,
|
|
2621
|
+
items: responseItems,
|
|
442
2622
|
messageRefs,
|
|
443
2623
|
feedback,
|
|
444
2624
|
traces,
|
|
2625
|
+
meta,
|
|
2626
|
+
notes,
|
|
2627
|
+
conversationScore,
|
|
445
2628
|
};
|
|
446
2629
|
};
|
|
447
2630
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
@@ -449,8 +2632,10 @@ async function runLlmDeck(ctx) {
|
|
|
449
2632
|
let respondValue;
|
|
450
2633
|
let endSignal;
|
|
451
2634
|
const appendedMessages = [];
|
|
452
|
-
|
|
453
|
-
|
|
2635
|
+
const toolCallText = streamingBuffer ||
|
|
2636
|
+
(typeof message.content === "string" ? message.content : "");
|
|
2637
|
+
if (!streamingCommitted && toolCallText) {
|
|
2638
|
+
messages.push(sanitizeMessage({ role: "assistant", content: toolCallText }));
|
|
454
2639
|
streamingCommitted = true;
|
|
455
2640
|
}
|
|
456
2641
|
for (const call of result.toolCalls) {
|
|
@@ -488,6 +2673,7 @@ async function runLlmDeck(ctx) {
|
|
|
488
2673
|
actionCallId: call.id,
|
|
489
2674
|
name: call.name,
|
|
490
2675
|
args: call.args,
|
|
2676
|
+
toolKind: "internal",
|
|
491
2677
|
parentActionCallId: actionCallId,
|
|
492
2678
|
});
|
|
493
2679
|
const toolContent = JSON.stringify(call.args ?? {});
|
|
@@ -517,6 +2703,7 @@ async function runLlmDeck(ctx) {
|
|
|
517
2703
|
actionCallId: call.id,
|
|
518
2704
|
name: call.name,
|
|
519
2705
|
result: respondEnvelope,
|
|
2706
|
+
toolKind: "internal",
|
|
520
2707
|
parentActionCallId: actionCallId,
|
|
521
2708
|
});
|
|
522
2709
|
continue;
|
|
@@ -543,6 +2730,7 @@ async function runLlmDeck(ctx) {
|
|
|
543
2730
|
actionCallId: call.id,
|
|
544
2731
|
name: call.name,
|
|
545
2732
|
args: call.args,
|
|
2733
|
+
toolKind: "internal",
|
|
546
2734
|
parentActionCallId: actionCallId,
|
|
547
2735
|
});
|
|
548
2736
|
const toolContent = JSON.stringify(call.args ?? {});
|
|
@@ -582,10 +2770,23 @@ async function runLlmDeck(ctx) {
|
|
|
582
2770
|
actionCallId: call.id,
|
|
583
2771
|
name: call.name,
|
|
584
2772
|
result: signal,
|
|
2773
|
+
toolKind: "internal",
|
|
585
2774
|
parentActionCallId: actionCallId,
|
|
586
2775
|
});
|
|
587
2776
|
continue;
|
|
588
2777
|
}
|
|
2778
|
+
const actionRef = deck.actionDecks.find((a) => a.name === call.name);
|
|
2779
|
+
const toolKind = actionRef ? "action" : "external";
|
|
2780
|
+
const actionPermissions = resolveEffectivePermissions({
|
|
2781
|
+
baseDir: path.dirname(deck.path),
|
|
2782
|
+
parent: ctx.permissions,
|
|
2783
|
+
reference: actionRef?.permissions
|
|
2784
|
+
? {
|
|
2785
|
+
baseDir: path.dirname(deck.path),
|
|
2786
|
+
permissions: actionRef.permissions,
|
|
2787
|
+
}
|
|
2788
|
+
: undefined,
|
|
2789
|
+
});
|
|
589
2790
|
ctx.trace?.({
|
|
590
2791
|
type: "action.start",
|
|
591
2792
|
runId,
|
|
@@ -593,6 +2794,7 @@ async function runLlmDeck(ctx) {
|
|
|
593
2794
|
name: call.name,
|
|
594
2795
|
path: call.name,
|
|
595
2796
|
parentActionCallId: actionCallId,
|
|
2797
|
+
permissions: actionPermissions.trace,
|
|
596
2798
|
});
|
|
597
2799
|
ctx.trace?.({
|
|
598
2800
|
type: "tool.call",
|
|
@@ -600,6 +2802,7 @@ async function runLlmDeck(ctx) {
|
|
|
600
2802
|
actionCallId: call.id,
|
|
601
2803
|
name: call.name,
|
|
602
2804
|
args: call.args,
|
|
2805
|
+
toolKind,
|
|
603
2806
|
parentActionCallId: actionCallId,
|
|
604
2807
|
});
|
|
605
2808
|
const toolResult = await handleToolCall(call, {
|
|
@@ -618,6 +2821,16 @@ async function runLlmDeck(ctx) {
|
|
|
618
2821
|
runStartedAt: start,
|
|
619
2822
|
inputProvided: true,
|
|
620
2823
|
idle: idleController,
|
|
2824
|
+
responsesMode: ctx.responsesMode,
|
|
2825
|
+
permissions: ctx.permissions,
|
|
2826
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
2827
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
2828
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
2829
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
2830
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
2831
|
+
workerSandbox: ctx.workerSandbox,
|
|
2832
|
+
signal: ctx.signal,
|
|
2833
|
+
onTool: ctx.onTool,
|
|
621
2834
|
});
|
|
622
2835
|
ctx.trace?.({
|
|
623
2836
|
type: "tool.result",
|
|
@@ -625,6 +2838,7 @@ async function runLlmDeck(ctx) {
|
|
|
625
2838
|
actionCallId: call.id,
|
|
626
2839
|
name: call.name,
|
|
627
2840
|
result: toolResult.toolContent,
|
|
2841
|
+
toolKind,
|
|
628
2842
|
parentActionCallId: actionCallId,
|
|
629
2843
|
});
|
|
630
2844
|
appendedMessages.push({
|
|
@@ -662,6 +2876,7 @@ async function runLlmDeck(ctx) {
|
|
|
662
2876
|
idleController.touch();
|
|
663
2877
|
}
|
|
664
2878
|
if (ctx.onStateUpdate) {
|
|
2879
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
665
2880
|
const state = computeState(result.updatedState);
|
|
666
2881
|
ctx.onStateUpdate(state);
|
|
667
2882
|
}
|
|
@@ -687,6 +2902,12 @@ async function runLlmDeck(ctx) {
|
|
|
687
2902
|
}
|
|
688
2903
|
continue;
|
|
689
2904
|
}
|
|
2905
|
+
if (!respondEnabled &&
|
|
2906
|
+
result.finishReason === "stop" &&
|
|
2907
|
+
(message.content === null || message.content === undefined) &&
|
|
2908
|
+
(!result.toolCalls || result.toolCalls.length === 0)) {
|
|
2909
|
+
message = { ...message, content: "" };
|
|
2910
|
+
}
|
|
690
2911
|
if (result.finishReason === "tool_calls") {
|
|
691
2912
|
throw new Error("Model requested tool_calls but provided none");
|
|
692
2913
|
}
|
|
@@ -696,6 +2917,7 @@ async function runLlmDeck(ctx) {
|
|
|
696
2917
|
}
|
|
697
2918
|
if (message.content !== null && message.content !== undefined) {
|
|
698
2919
|
messages.push(sanitizeMessage(message));
|
|
2920
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
699
2921
|
if (ctx.onStateUpdate) {
|
|
700
2922
|
const state = computeState(result.updatedState);
|
|
701
2923
|
ctx.onStateUpdate(state);
|
|
@@ -738,23 +2960,11 @@ async function runLlmDeck(ctx) {
|
|
|
738
2960
|
throw new Error("Model did not complete within guardrails");
|
|
739
2961
|
}
|
|
740
2962
|
async function handleToolCall(call, ctx) {
|
|
741
|
-
|
|
2963
|
+
ensureRunActive(ctx.runDeadlineMs, ctx.signal);
|
|
742
2964
|
const source = {
|
|
743
2965
|
deckPath: ctx.parentDeck.path,
|
|
744
|
-
actionName:
|
|
2966
|
+
actionName: call.name,
|
|
745
2967
|
};
|
|
746
|
-
if (!action) {
|
|
747
|
-
return {
|
|
748
|
-
toolContent: JSON.stringify({
|
|
749
|
-
runId: ctx.runId,
|
|
750
|
-
actionCallId: call.id,
|
|
751
|
-
parentActionCallId: ctx.parentActionCallId,
|
|
752
|
-
source,
|
|
753
|
-
status: 404,
|
|
754
|
-
message: "unknown action",
|
|
755
|
-
}),
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
2968
|
const baseComplete = (payload) => JSON.stringify({
|
|
759
2969
|
runId: ctx.runId,
|
|
760
2970
|
actionCallId: call.id,
|
|
@@ -768,6 +2978,480 @@ async function handleToolCall(call, ctx) {
|
|
|
768
2978
|
});
|
|
769
2979
|
const extraMessages = [];
|
|
770
2980
|
const started = performance.now();
|
|
2981
|
+
const runBuiltinTool = async () => {
|
|
2982
|
+
if (!isBuiltinTool(call.name))
|
|
2983
|
+
return null;
|
|
2984
|
+
const deny = (message) => ({
|
|
2985
|
+
toolContent: baseComplete({
|
|
2986
|
+
status: 403,
|
|
2987
|
+
code: "permission_denied",
|
|
2988
|
+
message,
|
|
2989
|
+
}),
|
|
2990
|
+
});
|
|
2991
|
+
if (call.name === BUILTIN_TOOL_READ_FILE) {
|
|
2992
|
+
let targetPath;
|
|
2993
|
+
try {
|
|
2994
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
2995
|
+
}
|
|
2996
|
+
catch (err) {
|
|
2997
|
+
return {
|
|
2998
|
+
toolContent: baseComplete({
|
|
2999
|
+
status: 400,
|
|
3000
|
+
code: "invalid_input",
|
|
3001
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3002
|
+
}),
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
if (!canReadPath(ctx.permissions, targetPath)) {
|
|
3006
|
+
return deny(`read_file denied for ${targetPath}`);
|
|
3007
|
+
}
|
|
3008
|
+
const text = await dntShim.Deno.readTextFile(targetPath);
|
|
3009
|
+
const lines = text.split(/\r?\n/);
|
|
3010
|
+
const { startLine, endLine } = parseLineRange(call.args);
|
|
3011
|
+
const sliced = lines.slice(startLine - 1, endLine).join("\n");
|
|
3012
|
+
return {
|
|
3013
|
+
toolContent: baseComplete({
|
|
3014
|
+
status: 200,
|
|
3015
|
+
payload: {
|
|
3016
|
+
path: targetPath,
|
|
3017
|
+
start_line: startLine,
|
|
3018
|
+
end_line: endLine,
|
|
3019
|
+
total_lines: lines.length,
|
|
3020
|
+
content: sliced,
|
|
3021
|
+
},
|
|
3022
|
+
}),
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
if (call.name === BUILTIN_TOOL_LIST_DIR) {
|
|
3026
|
+
let targetPath;
|
|
3027
|
+
try {
|
|
3028
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3029
|
+
}
|
|
3030
|
+
catch (err) {
|
|
3031
|
+
return {
|
|
3032
|
+
toolContent: baseComplete({
|
|
3033
|
+
status: 400,
|
|
3034
|
+
code: "invalid_input",
|
|
3035
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3036
|
+
}),
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
if (!canReadPath(ctx.permissions, targetPath)) {
|
|
3040
|
+
return deny(`list_dir denied for ${targetPath}`);
|
|
3041
|
+
}
|
|
3042
|
+
const recursive = Boolean(call.args.recursive);
|
|
3043
|
+
const maxEntries = parseToolLimit(call.args.max_entries, 200, 2000);
|
|
3044
|
+
const out = [];
|
|
3045
|
+
const pending = [targetPath];
|
|
3046
|
+
while (pending.length > 0 && out.length < maxEntries) {
|
|
3047
|
+
const current = pending.pop();
|
|
3048
|
+
for await (const entry of dntShim.Deno.readDir(current)) {
|
|
3049
|
+
if (out.length >= maxEntries)
|
|
3050
|
+
break;
|
|
3051
|
+
const entryPath = path.join(current, entry.name);
|
|
3052
|
+
if (!canReadPath(ctx.permissions, entryPath))
|
|
3053
|
+
continue;
|
|
3054
|
+
const type = entry.isDirectory
|
|
3055
|
+
? "dir"
|
|
3056
|
+
: entry.isSymlink
|
|
3057
|
+
? "symlink"
|
|
3058
|
+
: "file";
|
|
3059
|
+
out.push({ path: entryPath, type });
|
|
3060
|
+
if (recursive && entry.isDirectory) {
|
|
3061
|
+
pending.push(entryPath);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
toolContent: baseComplete({
|
|
3067
|
+
status: 200,
|
|
3068
|
+
payload: {
|
|
3069
|
+
path: targetPath,
|
|
3070
|
+
recursive,
|
|
3071
|
+
entries: out,
|
|
3072
|
+
truncated: out.length >= maxEntries,
|
|
3073
|
+
},
|
|
3074
|
+
}),
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
if (call.name === BUILTIN_TOOL_GREP_FILES) {
|
|
3078
|
+
let targetPath;
|
|
3079
|
+
try {
|
|
3080
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3081
|
+
}
|
|
3082
|
+
catch (err) {
|
|
3083
|
+
return {
|
|
3084
|
+
toolContent: baseComplete({
|
|
3085
|
+
status: 400,
|
|
3086
|
+
code: "invalid_input",
|
|
3087
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3088
|
+
}),
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
if (!canReadPath(ctx.permissions, targetPath)) {
|
|
3092
|
+
return deny(`grep_files denied for ${targetPath}`);
|
|
3093
|
+
}
|
|
3094
|
+
const query = typeof call.args.query === "string" ? call.args.query : "";
|
|
3095
|
+
if (!query) {
|
|
3096
|
+
return {
|
|
3097
|
+
toolContent: baseComplete({
|
|
3098
|
+
status: 400,
|
|
3099
|
+
code: "invalid_input",
|
|
3100
|
+
message: "query is required",
|
|
3101
|
+
}),
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
let re;
|
|
3105
|
+
try {
|
|
3106
|
+
re = new RegExp(query, "g");
|
|
3107
|
+
}
|
|
3108
|
+
catch (err) {
|
|
3109
|
+
return {
|
|
3110
|
+
toolContent: baseComplete({
|
|
3111
|
+
status: 400,
|
|
3112
|
+
code: "invalid_regex",
|
|
3113
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3114
|
+
}),
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
const maxMatches = parseToolLimit(call.args.max_matches, 200, 2000);
|
|
3118
|
+
const matches = [];
|
|
3119
|
+
const pending = [targetPath];
|
|
3120
|
+
while (pending.length > 0 && matches.length < maxMatches) {
|
|
3121
|
+
const current = pending.pop();
|
|
3122
|
+
const stat = await dntShim.Deno.stat(current);
|
|
3123
|
+
if (stat.isDirectory) {
|
|
3124
|
+
for await (const entry of dntShim.Deno.readDir(current)) {
|
|
3125
|
+
const entryPath = path.join(current, entry.name);
|
|
3126
|
+
if (!canReadPath(ctx.permissions, entryPath))
|
|
3127
|
+
continue;
|
|
3128
|
+
if (entry.isDirectory) {
|
|
3129
|
+
pending.push(entryPath);
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
if (!entry.isFile)
|
|
3133
|
+
continue;
|
|
3134
|
+
const text = await dntShim.Deno.readTextFile(entryPath).catch(() => null);
|
|
3135
|
+
if (text === null)
|
|
3136
|
+
continue;
|
|
3137
|
+
const lines = text.split(/\r?\n/);
|
|
3138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3139
|
+
re.lastIndex = 0;
|
|
3140
|
+
if (!re.test(lines[i]))
|
|
3141
|
+
continue;
|
|
3142
|
+
matches.push({ path: entryPath, line: i + 1, text: lines[i] });
|
|
3143
|
+
if (matches.length >= maxMatches)
|
|
3144
|
+
break;
|
|
3145
|
+
}
|
|
3146
|
+
if (matches.length >= maxMatches)
|
|
3147
|
+
break;
|
|
3148
|
+
}
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
if (!stat.isFile)
|
|
3152
|
+
continue;
|
|
3153
|
+
const text = await dntShim.Deno.readTextFile(current).catch(() => null);
|
|
3154
|
+
if (text === null)
|
|
3155
|
+
continue;
|
|
3156
|
+
const lines = text.split(/\r?\n/);
|
|
3157
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3158
|
+
re.lastIndex = 0;
|
|
3159
|
+
if (!re.test(lines[i]))
|
|
3160
|
+
continue;
|
|
3161
|
+
matches.push({ path: current, line: i + 1, text: lines[i] });
|
|
3162
|
+
if (matches.length >= maxMatches)
|
|
3163
|
+
break;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
return {
|
|
3167
|
+
toolContent: baseComplete({
|
|
3168
|
+
status: 200,
|
|
3169
|
+
payload: {
|
|
3170
|
+
path: targetPath,
|
|
3171
|
+
query,
|
|
3172
|
+
matches,
|
|
3173
|
+
truncated: matches.length >= maxMatches,
|
|
3174
|
+
},
|
|
3175
|
+
}),
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
if (call.name === BUILTIN_TOOL_APPLY_PATCH) {
|
|
3179
|
+
let targetPath;
|
|
3180
|
+
try {
|
|
3181
|
+
targetPath = resolveToolPath(ctx.permissions.baseDir, call.args.path);
|
|
3182
|
+
}
|
|
3183
|
+
catch (err) {
|
|
3184
|
+
return {
|
|
3185
|
+
toolContent: baseComplete({
|
|
3186
|
+
status: 400,
|
|
3187
|
+
code: "invalid_input",
|
|
3188
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3189
|
+
}),
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
if (!canWritePath(ctx.permissions, targetPath)) {
|
|
3193
|
+
return deny(`apply_patch denied for ${targetPath}`);
|
|
3194
|
+
}
|
|
3195
|
+
const rawEdits = Array.isArray(call.args.edits) ? call.args.edits : [];
|
|
3196
|
+
const edits = rawEdits.flatMap((entry) => {
|
|
3197
|
+
if (!entry || typeof entry !== "object")
|
|
3198
|
+
return [];
|
|
3199
|
+
const rec = entry;
|
|
3200
|
+
if (typeof rec.old_text !== "string" || typeof rec.new_text !== "string") {
|
|
3201
|
+
return [];
|
|
3202
|
+
}
|
|
3203
|
+
return [{
|
|
3204
|
+
oldText: rec.old_text,
|
|
3205
|
+
newText: rec.new_text,
|
|
3206
|
+
replaceAll: Boolean(rec.replace_all),
|
|
3207
|
+
}];
|
|
3208
|
+
});
|
|
3209
|
+
if (edits.length === 0) {
|
|
3210
|
+
return {
|
|
3211
|
+
toolContent: baseComplete({
|
|
3212
|
+
status: 400,
|
|
3213
|
+
code: "invalid_input",
|
|
3214
|
+
message: "edits must include at least one old_text/new_text pair",
|
|
3215
|
+
}),
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
const createIfMissing = Boolean(call.args.create_if_missing);
|
|
3219
|
+
let existing = "";
|
|
3220
|
+
let created = false;
|
|
3221
|
+
try {
|
|
3222
|
+
if (!canReadPath(ctx.permissions, targetPath)) {
|
|
3223
|
+
return deny(`apply_patch read denied for ${targetPath}`);
|
|
3224
|
+
}
|
|
3225
|
+
existing = await dntShim.Deno.readTextFile(targetPath);
|
|
3226
|
+
}
|
|
3227
|
+
catch (err) {
|
|
3228
|
+
if (err instanceof dntShim.Deno.errors.NotFound) {
|
|
3229
|
+
if (!createIfMissing) {
|
|
3230
|
+
return {
|
|
3231
|
+
toolContent: baseComplete({
|
|
3232
|
+
status: 404,
|
|
3233
|
+
code: "not_found",
|
|
3234
|
+
message: `file not found: ${targetPath}`,
|
|
3235
|
+
}),
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
created = true;
|
|
3239
|
+
existing = "";
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
throw err;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
const patched = applySimplePatch(existing, edits);
|
|
3246
|
+
if (!created && patched.applied === 0) {
|
|
3247
|
+
return {
|
|
3248
|
+
toolContent: baseComplete({
|
|
3249
|
+
status: 409,
|
|
3250
|
+
code: "no_changes",
|
|
3251
|
+
message: `No edit targets were found in ${targetPath}`,
|
|
3252
|
+
}),
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
if (created) {
|
|
3256
|
+
const parentDir = path.dirname(targetPath);
|
|
3257
|
+
if (parentDir && parentDir !== "." && parentDir !== targetPath) {
|
|
3258
|
+
await dntShim.Deno.mkdir(parentDir, { recursive: true });
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
try {
|
|
3262
|
+
await dntShim.Deno.writeTextFile(targetPath, patched.next);
|
|
3263
|
+
}
|
|
3264
|
+
catch (err) {
|
|
3265
|
+
if (err instanceof dntShim.Deno.errors.NotFound) {
|
|
3266
|
+
return {
|
|
3267
|
+
toolContent: baseComplete({
|
|
3268
|
+
status: 404,
|
|
3269
|
+
code: "not_found",
|
|
3270
|
+
message: `path not found: ${targetPath}`,
|
|
3271
|
+
}),
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
return {
|
|
3275
|
+
toolContent: baseComplete({
|
|
3276
|
+
status: 500,
|
|
3277
|
+
code: "write_failed",
|
|
3278
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3279
|
+
}),
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
return {
|
|
3283
|
+
toolContent: baseComplete({
|
|
3284
|
+
status: 200,
|
|
3285
|
+
payload: {
|
|
3286
|
+
path: targetPath,
|
|
3287
|
+
applied: patched.applied,
|
|
3288
|
+
created,
|
|
3289
|
+
},
|
|
3290
|
+
}),
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
if (call.name === BUILTIN_TOOL_EXEC) {
|
|
3294
|
+
const command = typeof call.args.command === "string"
|
|
3295
|
+
? call.args.command
|
|
3296
|
+
: "";
|
|
3297
|
+
if (!command) {
|
|
3298
|
+
return {
|
|
3299
|
+
toolContent: baseComplete({
|
|
3300
|
+
status: 400,
|
|
3301
|
+
code: "invalid_input",
|
|
3302
|
+
message: "command is required",
|
|
3303
|
+
}),
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
if (!canRunCommand(ctx.permissions, command) &&
|
|
3307
|
+
!canRunPath(ctx.permissions, command)) {
|
|
3308
|
+
return deny(`exec denied for command ${command}`);
|
|
3309
|
+
}
|
|
3310
|
+
const args = toStringArray(call.args.args);
|
|
3311
|
+
const cwd = typeof call.args.cwd === "string"
|
|
3312
|
+
? path.resolve(ctx.permissions.baseDir, call.args.cwd)
|
|
3313
|
+
: ctx.permissions.baseDir;
|
|
3314
|
+
const timeoutMs = parseToolLimit(call.args.timeout_ms, 5000, 30000);
|
|
3315
|
+
const remainingMs = Math.max(1, Math.min(timeoutMs, Math.floor(ctx.runDeadlineMs - performance.now())));
|
|
3316
|
+
const controller = new AbortController();
|
|
3317
|
+
const onAbort = () => controller.abort();
|
|
3318
|
+
if (ctx.signal?.aborted) {
|
|
3319
|
+
controller.abort();
|
|
3320
|
+
}
|
|
3321
|
+
else if (ctx.signal) {
|
|
3322
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
3323
|
+
}
|
|
3324
|
+
const timeoutId = setTimeout(() => controller.abort(), remainingMs);
|
|
3325
|
+
try {
|
|
3326
|
+
const output = await executeBuiltinCommand({
|
|
3327
|
+
command,
|
|
3328
|
+
args,
|
|
3329
|
+
cwd,
|
|
3330
|
+
signal: controller.signal,
|
|
3331
|
+
});
|
|
3332
|
+
const stdout = new TextDecoder().decode(output.stdout).slice(0, 65536);
|
|
3333
|
+
const stderr = new TextDecoder().decode(output.stderr).slice(0, 65536);
|
|
3334
|
+
return {
|
|
3335
|
+
toolContent: baseComplete({
|
|
3336
|
+
status: 200,
|
|
3337
|
+
payload: {
|
|
3338
|
+
command,
|
|
3339
|
+
args,
|
|
3340
|
+
cwd,
|
|
3341
|
+
code: output.code,
|
|
3342
|
+
success: output.success,
|
|
3343
|
+
stdout,
|
|
3344
|
+
stderr,
|
|
3345
|
+
},
|
|
3346
|
+
}),
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
catch (err) {
|
|
3350
|
+
if (err instanceof ExecToolUnsupportedHostError) {
|
|
3351
|
+
return {
|
|
3352
|
+
toolContent: baseComplete({
|
|
3353
|
+
status: 501,
|
|
3354
|
+
code: err.code,
|
|
3355
|
+
message: err.message,
|
|
3356
|
+
}),
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
return {
|
|
3360
|
+
toolContent: baseComplete({
|
|
3361
|
+
status: 500,
|
|
3362
|
+
code: "exec_failed",
|
|
3363
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3364
|
+
}),
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
finally {
|
|
3368
|
+
clearTimeout(timeoutId);
|
|
3369
|
+
if (ctx.signal) {
|
|
3370
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
return null;
|
|
3375
|
+
};
|
|
3376
|
+
const builtinResult = await runBuiltinTool();
|
|
3377
|
+
if (builtinResult) {
|
|
3378
|
+
return builtinResult;
|
|
3379
|
+
}
|
|
3380
|
+
const action = ctx.parentDeck.actionDecks.find((a) => a.name === call.name);
|
|
3381
|
+
if (!action) {
|
|
3382
|
+
const externalTool = ctx.parentDeck.tools.find((tool) => tool.name === call.name);
|
|
3383
|
+
if (!externalTool) {
|
|
3384
|
+
return {
|
|
3385
|
+
toolContent: JSON.stringify({
|
|
3386
|
+
runId: ctx.runId,
|
|
3387
|
+
actionCallId: call.id,
|
|
3388
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3389
|
+
source,
|
|
3390
|
+
status: 404,
|
|
3391
|
+
message: "unknown action",
|
|
3392
|
+
}),
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
let externalInput = call.args;
|
|
3396
|
+
if (externalTool.inputSchema) {
|
|
3397
|
+
try {
|
|
3398
|
+
externalInput = validateWithSchema(externalTool.inputSchema, call.args);
|
|
3399
|
+
}
|
|
3400
|
+
catch (err) {
|
|
3401
|
+
return {
|
|
3402
|
+
toolContent: baseComplete({
|
|
3403
|
+
status: 400,
|
|
3404
|
+
code: "invalid_input",
|
|
3405
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3406
|
+
}),
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
if (!ctx.onTool) {
|
|
3411
|
+
return {
|
|
3412
|
+
toolContent: baseComplete({
|
|
3413
|
+
status: 500,
|
|
3414
|
+
code: "missing_on_tool",
|
|
3415
|
+
message: `External tool ${call.name} requires runtime onTool handler`,
|
|
3416
|
+
}),
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
try {
|
|
3420
|
+
const result = await ctx.onTool({
|
|
3421
|
+
name: call.name,
|
|
3422
|
+
args: externalInput,
|
|
3423
|
+
runId: ctx.runId,
|
|
3424
|
+
actionCallId: call.id,
|
|
3425
|
+
parentActionCallId: ctx.parentActionCallId,
|
|
3426
|
+
deckPath: ctx.parentDeck.path,
|
|
3427
|
+
});
|
|
3428
|
+
return { toolContent: baseComplete(normalizeChildResult(result)) };
|
|
3429
|
+
}
|
|
3430
|
+
catch (err) {
|
|
3431
|
+
return {
|
|
3432
|
+
toolContent: baseComplete({
|
|
3433
|
+
status: 500,
|
|
3434
|
+
code: "tool_handler_error",
|
|
3435
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3436
|
+
}),
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
let actionInput = call.args;
|
|
3441
|
+
if (action.contextSchema) {
|
|
3442
|
+
try {
|
|
3443
|
+
actionInput = validateWithSchema(action.contextSchema, call.args);
|
|
3444
|
+
}
|
|
3445
|
+
catch (err) {
|
|
3446
|
+
return {
|
|
3447
|
+
toolContent: baseComplete({
|
|
3448
|
+
status: 400,
|
|
3449
|
+
code: "invalid_input",
|
|
3450
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3451
|
+
}),
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
771
3455
|
const busyCfg = ctx.parentDeck.handlers?.onBusy ??
|
|
772
3456
|
ctx.parentDeck.handlers?.onInterval;
|
|
773
3457
|
const busyDelay = busyCfg?.delayMs ?? DEFAULT_STATUS_DELAY_MS;
|
|
@@ -781,7 +3465,7 @@ async function handleToolCall(call, ctx) {
|
|
|
781
3465
|
try {
|
|
782
3466
|
const result = await runDeck({
|
|
783
3467
|
path: action.path,
|
|
784
|
-
input:
|
|
3468
|
+
input: actionInput,
|
|
785
3469
|
modelProvider: ctx.modelProvider,
|
|
786
3470
|
isRoot: false,
|
|
787
3471
|
guardrails: ctx.guardrails,
|
|
@@ -793,7 +3477,19 @@ async function handleToolCall(call, ctx) {
|
|
|
793
3477
|
trace: ctx.trace,
|
|
794
3478
|
stream: ctx.stream,
|
|
795
3479
|
onStreamText: ctx.onStreamText,
|
|
3480
|
+
responsesMode: ctx.responsesMode,
|
|
796
3481
|
initialUserMessage: undefined,
|
|
3482
|
+
parentPermissions: ctx.permissions,
|
|
3483
|
+
referencePermissions: action.permissions,
|
|
3484
|
+
referencePermissionsBaseDir: path.dirname(ctx.parentDeck.path),
|
|
3485
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3486
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3487
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3488
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3489
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3490
|
+
workerSandbox: ctx.workerSandbox,
|
|
3491
|
+
signal: ctx.signal,
|
|
3492
|
+
onTool: ctx.onTool,
|
|
797
3493
|
});
|
|
798
3494
|
return { ok: true, result };
|
|
799
3495
|
}
|
|
@@ -825,7 +3521,17 @@ async function handleToolCall(call, ctx) {
|
|
|
825
3521
|
trace: ctx.trace,
|
|
826
3522
|
stream: ctx.stream,
|
|
827
3523
|
onStreamText: ctx.onStreamText,
|
|
3524
|
+
responsesMode: ctx.responsesMode,
|
|
828
3525
|
initialUserMessage: undefined,
|
|
3526
|
+
permissions: ctx.permissions,
|
|
3527
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3528
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3529
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3530
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3531
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3532
|
+
workerSandbox: ctx.workerSandbox,
|
|
3533
|
+
signal: ctx.signal,
|
|
3534
|
+
onTool: ctx.onTool,
|
|
829
3535
|
});
|
|
830
3536
|
if (envelope.length) {
|
|
831
3537
|
extraMessages.push(...envelope.map(sanitizeMessage));
|
|
@@ -883,6 +3589,9 @@ async function handleToolCall(call, ctx) {
|
|
|
883
3589
|
throw childResult.error;
|
|
884
3590
|
}
|
|
885
3591
|
const normalized = normalizeChildResult(childResult.result);
|
|
3592
|
+
if (action.responseSchema) {
|
|
3593
|
+
normalized.payload = validateWithSchema(action.responseSchema, normalized.payload);
|
|
3594
|
+
}
|
|
886
3595
|
const toolContent = baseComplete(normalized);
|
|
887
3596
|
if (busyCfg?.path) {
|
|
888
3597
|
const elapsedFromAction = performance.now() - started;
|
|
@@ -904,7 +3613,17 @@ async function handleToolCall(call, ctx) {
|
|
|
904
3613
|
trace: ctx.trace,
|
|
905
3614
|
stream: ctx.stream,
|
|
906
3615
|
onStreamText: ctx.onStreamText,
|
|
3616
|
+
responsesMode: ctx.responsesMode,
|
|
907
3617
|
initialUserMessage: undefined,
|
|
3618
|
+
permissions: ctx.permissions,
|
|
3619
|
+
workspacePermissions: ctx.workspacePermissions,
|
|
3620
|
+
workspacePermissionsBaseDir: ctx.workspacePermissionsBaseDir,
|
|
3621
|
+
sessionPermissions: ctx.sessionPermissions,
|
|
3622
|
+
sessionPermissionsBaseDir: ctx.sessionPermissionsBaseDir,
|
|
3623
|
+
runDeadlineMs: ctx.runDeadlineMs,
|
|
3624
|
+
workerSandbox: ctx.workerSandbox,
|
|
3625
|
+
signal: ctx.signal,
|
|
3626
|
+
onTool: ctx.onTool,
|
|
908
3627
|
});
|
|
909
3628
|
if (envelope.length) {
|
|
910
3629
|
extraMessages.push(...envelope.map(sanitizeMessage));
|
|
@@ -954,6 +3673,7 @@ function normalizeChildResult(result) {
|
|
|
954
3673
|
}
|
|
955
3674
|
async function runBusyHandler(args) {
|
|
956
3675
|
try {
|
|
3676
|
+
ensureRunActive(args.runDeadlineMs, args.signal);
|
|
957
3677
|
const input = {
|
|
958
3678
|
kind: "busy",
|
|
959
3679
|
label: args.action.label ?? args.parentDeck.label,
|
|
@@ -978,8 +3698,18 @@ async function runBusyHandler(args) {
|
|
|
978
3698
|
trace: args.trace,
|
|
979
3699
|
stream: args.stream,
|
|
980
3700
|
onStreamText: args.onStreamText,
|
|
3701
|
+
responsesMode: args.responsesMode,
|
|
981
3702
|
initialUserMessage: args.initialUserMessage,
|
|
982
3703
|
inputProvided: true,
|
|
3704
|
+
parentPermissions: args.permissions,
|
|
3705
|
+
workspacePermissions: args.workspacePermissions,
|
|
3706
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3707
|
+
sessionPermissions: args.sessionPermissions,
|
|
3708
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3709
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3710
|
+
workerSandbox: args.workerSandbox,
|
|
3711
|
+
signal: args.signal,
|
|
3712
|
+
onTool: args.onTool,
|
|
983
3713
|
});
|
|
984
3714
|
const elapsedMs = Math.floor(args.elapsedMs);
|
|
985
3715
|
let message;
|
|
@@ -996,7 +3726,7 @@ async function runBusyHandler(args) {
|
|
|
996
3726
|
}
|
|
997
3727
|
if (!message)
|
|
998
3728
|
return [];
|
|
999
|
-
if (args.onStreamText) {
|
|
3729
|
+
if (args.onStreamText && !args.signal?.aborted) {
|
|
1000
3730
|
args.onStreamText(`${message}\n`);
|
|
1001
3731
|
}
|
|
1002
3732
|
else {
|
|
@@ -1056,6 +3786,16 @@ function createIdleController(args) {
|
|
|
1056
3786
|
trace: args.trace,
|
|
1057
3787
|
stream: args.stream,
|
|
1058
3788
|
onStreamText: args.onStreamText,
|
|
3789
|
+
responsesMode: args.responsesMode,
|
|
3790
|
+
permissions: args.permissions,
|
|
3791
|
+
workspacePermissions: args.workspacePermissions,
|
|
3792
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3793
|
+
sessionPermissions: args.sessionPermissions,
|
|
3794
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3795
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3796
|
+
workerSandbox: args.workerSandbox,
|
|
3797
|
+
signal: args.signal,
|
|
3798
|
+
onTool: args.onTool,
|
|
1059
3799
|
});
|
|
1060
3800
|
if (envelope.length)
|
|
1061
3801
|
args.pushMessages(envelope.map(sanitizeMessage));
|
|
@@ -1095,6 +3835,7 @@ function createIdleController(args) {
|
|
|
1095
3835
|
}
|
|
1096
3836
|
async function runIdleHandler(args) {
|
|
1097
3837
|
try {
|
|
3838
|
+
ensureRunActive(args.runDeadlineMs, args.signal);
|
|
1098
3839
|
const input = {
|
|
1099
3840
|
kind: "idle",
|
|
1100
3841
|
label: args.deck.label,
|
|
@@ -1118,8 +3859,18 @@ async function runIdleHandler(args) {
|
|
|
1118
3859
|
trace: args.trace,
|
|
1119
3860
|
stream: args.stream,
|
|
1120
3861
|
onStreamText: args.onStreamText,
|
|
3862
|
+
responsesMode: args.responsesMode,
|
|
1121
3863
|
initialUserMessage: undefined,
|
|
1122
3864
|
inputProvided: true,
|
|
3865
|
+
parentPermissions: args.permissions,
|
|
3866
|
+
workspacePermissions: args.workspacePermissions,
|
|
3867
|
+
workspacePermissionsBaseDir: args.workspacePermissionsBaseDir,
|
|
3868
|
+
sessionPermissions: args.sessionPermissions,
|
|
3869
|
+
sessionPermissionsBaseDir: args.sessionPermissionsBaseDir,
|
|
3870
|
+
runDeadlineMs: args.runDeadlineMs,
|
|
3871
|
+
workerSandbox: args.workerSandbox,
|
|
3872
|
+
signal: args.signal,
|
|
3873
|
+
onTool: args.onTool,
|
|
1123
3874
|
});
|
|
1124
3875
|
const elapsedMs = Math.floor(args.elapsedMs);
|
|
1125
3876
|
let message;
|
|
@@ -1136,7 +3887,7 @@ async function runIdleHandler(args) {
|
|
|
1136
3887
|
}
|
|
1137
3888
|
if (!message)
|
|
1138
3889
|
return [];
|
|
1139
|
-
if (args.onStreamText) {
|
|
3890
|
+
if (args.onStreamText && !args.signal?.aborted) {
|
|
1140
3891
|
args.onStreamText(`${message}\n`);
|
|
1141
3892
|
}
|
|
1142
3893
|
else {
|
|
@@ -1155,6 +3906,7 @@ async function maybeHandleError(args) {
|
|
|
1155
3906
|
const handlerPath = args.ctx.parentDeck.handlers?.onError?.path;
|
|
1156
3907
|
if (!handlerPath)
|
|
1157
3908
|
return undefined;
|
|
3909
|
+
ensureRunActive(args.ctx.runDeadlineMs, args.ctx.signal);
|
|
1158
3910
|
const message = args.err instanceof Error
|
|
1159
3911
|
? args.err.message
|
|
1160
3912
|
: String(args.err);
|
|
@@ -1183,8 +3935,18 @@ async function maybeHandleError(args) {
|
|
|
1183
3935
|
trace: args.ctx.trace,
|
|
1184
3936
|
stream: args.ctx.stream,
|
|
1185
3937
|
onStreamText: args.ctx.onStreamText,
|
|
3938
|
+
responsesMode: args.ctx.responsesMode,
|
|
1186
3939
|
initialUserMessage: undefined,
|
|
1187
3940
|
inputProvided: true,
|
|
3941
|
+
parentPermissions: args.ctx.permissions,
|
|
3942
|
+
workspacePermissions: args.ctx.workspacePermissions,
|
|
3943
|
+
workspacePermissionsBaseDir: args.ctx.workspacePermissionsBaseDir,
|
|
3944
|
+
sessionPermissions: args.ctx.sessionPermissions,
|
|
3945
|
+
sessionPermissionsBaseDir: args.ctx.sessionPermissionsBaseDir,
|
|
3946
|
+
runDeadlineMs: args.ctx.runDeadlineMs,
|
|
3947
|
+
workerSandbox: args.ctx.workerSandbox,
|
|
3948
|
+
signal: args.ctx.signal,
|
|
3949
|
+
onTool: args.ctx.onTool,
|
|
1188
3950
|
});
|
|
1189
3951
|
const parsed = typeof handlerOutput === "object" && handlerOutput !== null
|
|
1190
3952
|
? handlerOutput
|
|
@@ -1308,8 +4070,173 @@ function sanitizeMessage(msg) {
|
|
|
1308
4070
|
: undefined;
|
|
1309
4071
|
return { ...msg, tool_calls: toolCalls };
|
|
1310
4072
|
}
|
|
1311
|
-
|
|
4073
|
+
function resolveToolPath(baseDir, rawPath) {
|
|
4074
|
+
if (typeof rawPath !== "string" || rawPath.trim().length === 0) {
|
|
4075
|
+
throw new Error("path is required");
|
|
4076
|
+
}
|
|
4077
|
+
return path.resolve(baseDir, rawPath);
|
|
4078
|
+
}
|
|
4079
|
+
function parseLineRange(args) {
|
|
4080
|
+
const startLine = Number.isInteger(args.start_line)
|
|
4081
|
+
? Math.max(1, Number(args.start_line))
|
|
4082
|
+
: 1;
|
|
4083
|
+
const endLine = Number.isInteger(args.end_line)
|
|
4084
|
+
? Math.max(startLine, Number(args.end_line))
|
|
4085
|
+
: startLine + 399;
|
|
4086
|
+
return { startLine, endLine };
|
|
4087
|
+
}
|
|
4088
|
+
function parseToolLimit(value, fallback, max) {
|
|
4089
|
+
if (!Number.isInteger(value))
|
|
4090
|
+
return fallback;
|
|
4091
|
+
return Math.min(max, Math.max(1, Number(value)));
|
|
4092
|
+
}
|
|
4093
|
+
function toStringArray(value) {
|
|
4094
|
+
if (!Array.isArray(value))
|
|
4095
|
+
return [];
|
|
4096
|
+
return value.filter((entry) => typeof entry === "string");
|
|
4097
|
+
}
|
|
4098
|
+
function hasAnyScope(scope) {
|
|
4099
|
+
return scope.all || scope.values.size > 0;
|
|
4100
|
+
}
|
|
4101
|
+
function hasAnyRunScope(scope) {
|
|
4102
|
+
return scope.all || scope.paths.size > 0 || scope.commands.size > 0;
|
|
4103
|
+
}
|
|
4104
|
+
function isBuiltinTool(name) {
|
|
4105
|
+
return BUILTIN_TOOL_NAMES.has(name);
|
|
4106
|
+
}
|
|
4107
|
+
function applySimplePatch(content, edits) {
|
|
4108
|
+
let next = content;
|
|
4109
|
+
let applied = 0;
|
|
4110
|
+
for (const edit of edits) {
|
|
4111
|
+
const oldText = edit.oldText ?? "";
|
|
4112
|
+
const newText = edit.newText ?? "";
|
|
4113
|
+
if (!oldText)
|
|
4114
|
+
continue;
|
|
4115
|
+
if (edit.replaceAll) {
|
|
4116
|
+
if (!next.includes(oldText))
|
|
4117
|
+
continue;
|
|
4118
|
+
next = next.split(oldText).join(newText);
|
|
4119
|
+
applied++;
|
|
4120
|
+
continue;
|
|
4121
|
+
}
|
|
4122
|
+
const idx = next.indexOf(oldText);
|
|
4123
|
+
if (idx === -1)
|
|
4124
|
+
continue;
|
|
4125
|
+
next = `${next.slice(0, idx)}${newText}${next.slice(idx + oldText.length)}`;
|
|
4126
|
+
applied++;
|
|
4127
|
+
}
|
|
4128
|
+
return { next, applied };
|
|
4129
|
+
}
|
|
4130
|
+
async function buildToolDefs(deck, permissions) {
|
|
1312
4131
|
const defs = [];
|
|
4132
|
+
const addBuiltinTools = () => {
|
|
4133
|
+
if (hasAnyScope(permissions.read)) {
|
|
4134
|
+
defs.push({
|
|
4135
|
+
type: "function",
|
|
4136
|
+
function: {
|
|
4137
|
+
name: BUILTIN_TOOL_READ_FILE,
|
|
4138
|
+
description: "Read a UTF-8 text file.",
|
|
4139
|
+
parameters: {
|
|
4140
|
+
type: "object",
|
|
4141
|
+
properties: {
|
|
4142
|
+
path: { type: "string" },
|
|
4143
|
+
start_line: { type: "number" },
|
|
4144
|
+
end_line: { type: "number" },
|
|
4145
|
+
},
|
|
4146
|
+
required: ["path"],
|
|
4147
|
+
additionalProperties: false,
|
|
4148
|
+
},
|
|
4149
|
+
},
|
|
4150
|
+
}, {
|
|
4151
|
+
type: "function",
|
|
4152
|
+
function: {
|
|
4153
|
+
name: BUILTIN_TOOL_LIST_DIR,
|
|
4154
|
+
description: "List directory entries.",
|
|
4155
|
+
parameters: {
|
|
4156
|
+
type: "object",
|
|
4157
|
+
properties: {
|
|
4158
|
+
path: { type: "string" },
|
|
4159
|
+
recursive: { type: "boolean" },
|
|
4160
|
+
max_entries: { type: "number" },
|
|
4161
|
+
},
|
|
4162
|
+
required: ["path"],
|
|
4163
|
+
additionalProperties: false,
|
|
4164
|
+
},
|
|
4165
|
+
},
|
|
4166
|
+
}, {
|
|
4167
|
+
type: "function",
|
|
4168
|
+
function: {
|
|
4169
|
+
name: BUILTIN_TOOL_GREP_FILES,
|
|
4170
|
+
description: "Search text files using a regular expression.",
|
|
4171
|
+
parameters: {
|
|
4172
|
+
type: "object",
|
|
4173
|
+
properties: {
|
|
4174
|
+
path: { type: "string" },
|
|
4175
|
+
query: { type: "string" },
|
|
4176
|
+
max_matches: { type: "number" },
|
|
4177
|
+
},
|
|
4178
|
+
required: ["path", "query"],
|
|
4179
|
+
additionalProperties: false,
|
|
4180
|
+
},
|
|
4181
|
+
},
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
if (hasAnyScope(permissions.write)) {
|
|
4185
|
+
defs.push({
|
|
4186
|
+
type: "function",
|
|
4187
|
+
function: {
|
|
4188
|
+
name: BUILTIN_TOOL_APPLY_PATCH,
|
|
4189
|
+
description: "Apply text replacements to a file using old/new edit pairs.",
|
|
4190
|
+
parameters: {
|
|
4191
|
+
type: "object",
|
|
4192
|
+
properties: {
|
|
4193
|
+
path: { type: "string" },
|
|
4194
|
+
create_if_missing: { type: "boolean" },
|
|
4195
|
+
edits: {
|
|
4196
|
+
type: "array",
|
|
4197
|
+
items: {
|
|
4198
|
+
type: "object",
|
|
4199
|
+
properties: {
|
|
4200
|
+
old_text: { type: "string" },
|
|
4201
|
+
new_text: { type: "string" },
|
|
4202
|
+
replace_all: { type: "boolean" },
|
|
4203
|
+
},
|
|
4204
|
+
required: ["old_text", "new_text"],
|
|
4205
|
+
additionalProperties: false,
|
|
4206
|
+
},
|
|
4207
|
+
},
|
|
4208
|
+
},
|
|
4209
|
+
required: ["path", "edits"],
|
|
4210
|
+
additionalProperties: false,
|
|
4211
|
+
},
|
|
4212
|
+
},
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
4215
|
+
if (hasAnyRunScope(permissions.run)) {
|
|
4216
|
+
defs.push({
|
|
4217
|
+
type: "function",
|
|
4218
|
+
function: {
|
|
4219
|
+
name: BUILTIN_TOOL_EXEC,
|
|
4220
|
+
description: "Run an allowed command with optional args.",
|
|
4221
|
+
parameters: {
|
|
4222
|
+
type: "object",
|
|
4223
|
+
properties: {
|
|
4224
|
+
command: { type: "string" },
|
|
4225
|
+
args: {
|
|
4226
|
+
type: "array",
|
|
4227
|
+
items: { type: "string" },
|
|
4228
|
+
},
|
|
4229
|
+
cwd: { type: "string" },
|
|
4230
|
+
timeout_ms: { type: "number" },
|
|
4231
|
+
},
|
|
4232
|
+
required: ["command"],
|
|
4233
|
+
additionalProperties: false,
|
|
4234
|
+
},
|
|
4235
|
+
},
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
addBuiltinTools();
|
|
1313
4240
|
if (deck.allowEnd) {
|
|
1314
4241
|
defs.push({
|
|
1315
4242
|
type: "function",
|
|
@@ -1351,9 +4278,15 @@ async function buildToolDefs(deck) {
|
|
|
1351
4278
|
});
|
|
1352
4279
|
}
|
|
1353
4280
|
for (const action of deck.actionDecks) {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
4281
|
+
if (isBuiltinTool(action.name)) {
|
|
4282
|
+
throw new Error(`Action name ${action.name} conflicts with a built-in tool name`);
|
|
4283
|
+
}
|
|
4284
|
+
let schema = action.contextSchema;
|
|
4285
|
+
if (!schema) {
|
|
4286
|
+
const child = await loadDeck(action.path, deck.path);
|
|
4287
|
+
ensureSchemaPresence(child, false);
|
|
4288
|
+
schema = resolveContextSchema(child);
|
|
4289
|
+
}
|
|
1357
4290
|
const params = toJsonSchema(schema);
|
|
1358
4291
|
defs.push({
|
|
1359
4292
|
type: "function",
|
|
@@ -1364,5 +4297,23 @@ async function buildToolDefs(deck) {
|
|
|
1364
4297
|
},
|
|
1365
4298
|
});
|
|
1366
4299
|
}
|
|
4300
|
+
const actionNames = new Set(deck.actionDecks.map((action) => action.name));
|
|
4301
|
+
for (const external of deck.tools) {
|
|
4302
|
+
if (actionNames.has(external.name))
|
|
4303
|
+
continue;
|
|
4304
|
+
if (isBuiltinTool(external.name)) {
|
|
4305
|
+
throw new Error(`External tool name ${external.name} conflicts with a built-in tool name`);
|
|
4306
|
+
}
|
|
4307
|
+
defs.push({
|
|
4308
|
+
type: "function",
|
|
4309
|
+
function: {
|
|
4310
|
+
name: external.name,
|
|
4311
|
+
description: external.description,
|
|
4312
|
+
parameters: external.inputSchema
|
|
4313
|
+
? toJsonSchema(external.inputSchema)
|
|
4314
|
+
: { type: "object", additionalProperties: true },
|
|
4315
|
+
},
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
1367
4318
|
return defs;
|
|
1368
4319
|
}
|