wave-agent-sdk 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +58 -4
- package/dist/agent.js +91 -19
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/skills/settings.js +1 -12
- package/dist/builtin/skills/wave-daemon.d.ts +1 -0
- package/dist/builtin/skills/wave-daemon.js +194 -0
- package/dist/constants/images.d.ts +26 -0
- package/dist/constants/images.js +26 -0
- package/dist/constants/index.d.ts +16 -0
- package/dist/constants/index.js +16 -0
- package/dist/constants/memory.d.ts +26 -0
- package/dist/constants/memory.js +34 -0
- package/dist/constants/messages.d.ts +11 -0
- package/dist/constants/messages.js +11 -0
- package/dist/constants/plugins.d.ts +8 -0
- package/dist/constants/plugins.js +8 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.js +1 -0
- package/dist/core/plugin.d.ts +53 -13
- package/dist/core/plugin.js +134 -26
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.js +1 -1
- package/dist/exec/catalog.d.ts +140 -0
- package/dist/exec/catalog.js +470 -0
- package/dist/exec/catalogAnnouncement.d.ts +89 -0
- package/dist/exec/catalogAnnouncement.js +293 -0
- package/dist/exec/constants.d.ts +51 -0
- package/dist/exec/constants.js +51 -0
- package/dist/exec/execRuntime.d.ts +55 -0
- package/dist/exec/execRuntime.js +217 -0
- package/dist/exec/workerSource.d.ts +28 -0
- package/dist/exec/workerSource.js +299 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -1
- package/dist/managers/aiManager.js +190 -21
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/hookManager.d.ts +13 -0
- package/dist/managers/hookManager.js +31 -4
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +103 -8
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +45 -10
- package/dist/managers/mcpManager.js +103 -1
- package/dist/managers/messageManager.d.ts +48 -5
- package/dist/managers/messageManager.js +107 -21
- package/dist/managers/permissionManager.d.ts +40 -0
- package/dist/managers/permissionManager.js +63 -8
- package/dist/managers/pluginManager.d.ts +46 -2
- package/dist/managers/pluginManager.js +117 -11
- package/dist/managers/pluginScopeManager.d.ts +15 -2
- package/dist/managers/pluginScopeManager.js +20 -1
- package/dist/managers/skillManager.d.ts +19 -0
- package/dist/managers/skillManager.js +44 -0
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +35 -3
- package/dist/managers/subagentManager.d.ts +8 -0
- package/dist/managers/subagentManager.js +20 -0
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/prompts/autoMemory.d.ts +9 -0
- package/dist/prompts/autoMemory.js +30 -31
- package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
- package/dist/prompts/autoMemoryExtraction.js +8 -111
- package/dist/prompts/memoryTypes.d.ts +63 -0
- package/dist/prompts/memoryTypes.js +191 -0
- package/dist/services/GitService.d.ts +7 -0
- package/dist/services/GitService.js +23 -0
- package/dist/services/MarketplaceService.d.ts +101 -17
- package/dist/services/MarketplaceService.js +318 -119
- package/dist/services/artifactContent.d.ts +84 -0
- package/dist/services/artifactContent.js +204 -0
- package/dist/services/artifactSession.d.ts +6 -0
- package/dist/services/artifactSession.js +17 -0
- package/dist/services/autoMemoryService.js +5 -13
- package/dist/services/configurationService.d.ts +60 -9
- package/dist/services/configurationService.js +129 -54
- package/dist/services/contentSummarizer.d.ts +15 -0
- package/dist/services/contentSummarizer.js +45 -0
- package/dist/services/execAvailability.d.ts +9 -0
- package/dist/services/execAvailability.js +32 -0
- package/dist/services/fileWatcher.js +61 -6
- package/dist/services/initializationService.js +19 -15
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +84 -0
- package/dist/services/jsonlHandler.js +209 -14
- package/dist/services/memory.d.ts +3 -1
- package/dist/services/memory.js +13 -9
- package/dist/services/officialMarketplaceMirror.js +3 -2
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +16 -2
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +144 -3
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/stdio/index.d.ts +3 -1
- package/dist/stdio/index.js +3 -1
- package/dist/stdio/notificationRouter.js +1 -0
- package/dist/stdio/stdioAgent.d.ts +14 -7
- package/dist/stdio/stdioAgent.js +19 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +8 -6
- package/dist/tools/editTool.js +6 -3
- package/dist/tools/execTool.d.ts +2 -0
- package/dist/tools/execTool.js +165 -0
- package/dist/tools/grepTool.js +7 -1
- package/dist/tools/readTool.js +30 -2
- package/dist/tools/types.d.ts +34 -8
- package/dist/tools/webFetchTool.js +15 -166
- package/dist/tools/workflowTool.js +40 -8
- package/dist/tools/writeTool.js +6 -3
- package/dist/types/agent.d.ts +20 -5
- package/dist/types/configuration.d.ts +39 -1
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +39 -0
- package/dist/types/permissions.d.ts +22 -0
- package/dist/types/permissions.js +17 -0
- package/dist/types/plugins.d.ts +26 -2
- package/dist/types/skills.d.ts +15 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +43 -0
- package/dist/utils/convertMessagesForAPI.d.ts +7 -1
- package/dist/utils/convertMessagesForAPI.js +64 -14
- package/dist/utils/fileChangeReminder.d.ts +20 -0
- package/dist/utils/fileChangeReminder.js +153 -0
- package/dist/utils/fileSearch.js +4 -3
- package/dist/utils/fileUtils.d.ts +33 -0
- package/dist/utils/fileUtils.js +81 -0
- package/dist/utils/frontmatterYaml.d.ts +33 -0
- package/dist/utils/frontmatterYaml.js +192 -0
- package/dist/utils/imageBudget.d.ts +85 -0
- package/dist/utils/imageBudget.js +109 -0
- package/dist/utils/imageDimensions.d.ts +83 -0
- package/dist/utils/imageDimensions.js +232 -0
- package/dist/utils/imageProcessor.d.ts +66 -0
- package/dist/utils/imageProcessor.js +84 -0
- package/dist/utils/imageRewrite.d.ts +29 -0
- package/dist/utils/imageRewrite.js +251 -0
- package/dist/utils/markdownParser.d.ts +5 -1
- package/dist/utils/markdownParser.js +9 -51
- package/dist/utils/mcpInstructions.d.ts +61 -0
- package/dist/utils/mcpInstructions.js +126 -0
- package/dist/utils/mcpUtils.d.ts +7 -0
- package/dist/utils/mcpUtils.js +11 -2
- package/dist/utils/memoryAge.d.ts +32 -0
- package/dist/utils/memoryAge.js +47 -0
- package/dist/utils/memoryEntrypoint.d.ts +20 -0
- package/dist/utils/memoryEntrypoint.js +49 -0
- package/dist/utils/memoryIndex.d.ts +30 -0
- package/dist/utils/memoryIndex.js +76 -0
- package/dist/utils/messageOperations.d.ts +6 -2
- package/dist/utils/messageOperations.js +40 -29
- package/dist/utils/nestedMemory.d.ts +22 -0
- package/dist/utils/nestedMemory.js +61 -0
- package/dist/utils/npmTarball.d.ts +19 -0
- package/dist/utils/npmTarball.js +92 -0
- package/dist/utils/pluginSource.d.ts +37 -0
- package/dist/utils/pluginSource.js +73 -0
- package/dist/utils/ripgrep.d.ts +18 -4
- package/dist/utils/ripgrep.js +56 -4
- package/dist/utils/runtimeDeps.d.ts +35 -0
- package/dist/utils/runtimeDeps.js +426 -0
- package/dist/utils/skillParser.js +22 -52
- package/dist/utils/subagentParser.js +39 -43
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/package.json +10 -7
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP catalog, announced in the conversation instead of the `Exec` tool
|
|
3
|
+
* description. opencode only made this move on its v2 line (v1 shipped the catalog
|
|
4
|
+
* inside `execute`'s description, which is what this repo did until now).
|
|
5
|
+
*
|
|
6
|
+
* Why it cannot stay in the description: `tools[]` sits inside the cached prefix,
|
|
7
|
+
* and the catalog is a function of the pool. Every server that connects, drops, or
|
|
8
|
+
* changes its `tools/list` would rewrite the declaration and take the whole cached
|
|
9
|
+
* prefix with it. The description is a pool-independent constant now; the catalog is
|
|
10
|
+
* appended at the tail, where an addition invalidates nothing that already went out.
|
|
11
|
+
*
|
|
12
|
+
* State lives in the history, not in the process (same rule as
|
|
13
|
+
* `utils/mcpInstructions.ts`): each announcement leads with a marker line carrying
|
|
14
|
+
* the hash of a full catalog body. Three consequences, all deliberate:
|
|
15
|
+
* - Once the marker is gone (compaction, rewind), the next turn announces the full
|
|
16
|
+
* catalog again. A duplicate is better than a loss.
|
|
17
|
+
* - A delta is only emitted while the full catalog it is relative to is still in
|
|
18
|
+
* the history. The marker carries that baseline's hash (`bh`) so the check stays
|
|
19
|
+
* local: a delta is relative, and without its basis it says nothing.
|
|
20
|
+
* - The hash covers rendered text, so editing the renderer re-announces the catalog
|
|
21
|
+
* once for existing sessions. That is what buys the invariant "same hash ⇒ the
|
|
22
|
+
* same bytes the model read". It is also why a full catalog carries no "this
|
|
23
|
+
* supersedes the previous one" preamble: such a line would depend on history, and
|
|
24
|
+
* a history-dependent body can never compare equal to a freshly rendered one —
|
|
25
|
+
* the session would re-announce on every single turn.
|
|
26
|
+
*/
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
28
|
+
import { renderSearchSignature } from "./catalog.js";
|
|
29
|
+
/** Marks an announcement so the history scan can find it. */
|
|
30
|
+
export const EXEC_CATALOG_MARKER_PREFIX = "<!-- exec-catalog ";
|
|
31
|
+
export const EXEC_CATALOG_MARKER_SUFFIX = " -->";
|
|
32
|
+
/** Opens a full catalog body; the entries follow. */
|
|
33
|
+
const CATALOG_HEADER = "MCP tools reachable inside a sandbox script, called as `tools.<name>` (no other name resolves):";
|
|
34
|
+
/**
|
|
35
|
+
* What the model is told when a pool it has seen empties. It names no tool on purpose:
|
|
36
|
+
* with an empty pool `Exec` is not even declared, so naming it would point at something
|
|
37
|
+
* the model cannot call.
|
|
38
|
+
*
|
|
39
|
+
* This text is only ever reached by a session that already read a catalog, which is why
|
|
40
|
+
* it says "updates may add or remove tools" rather than introducing the concept: to a
|
|
41
|
+
* session that never had one, "no MCP tools are available" describes a capability it had
|
|
42
|
+
* no reason to expect (see the `last === null` branch).
|
|
43
|
+
*/
|
|
44
|
+
const EMPTY_POOL_NOTE = "No MCP tools are currently available. Later catalog updates may add or remove tools.";
|
|
45
|
+
/**
|
|
46
|
+
* What the model is told when the channel closes: `Exec` was switched off, excluded
|
|
47
|
+
* or denied, so the catalog's calling form no longer exists and the flat
|
|
48
|
+
* declarations are back. The listed tools are not gone — the way they were listed is.
|
|
49
|
+
*/
|
|
50
|
+
const REMOVED_NOTE = "The MCP tool catalog is no longer available, so the tools an earlier catalog listed cannot be called this way any more. The tool declarations of this session are the current source of truth.";
|
|
51
|
+
const DELTA_HEADER = "The MCP tool catalog has changed:";
|
|
52
|
+
/**
|
|
53
|
+
* A full catalog body: the header, the rendered entries, and — only when the budget
|
|
54
|
+
* truncated them — how to reach the rest.
|
|
55
|
+
*
|
|
56
|
+
* The search section is the single place the call form is taught (the entry point
|
|
57
|
+
* stays registered either way, it is merely not advertised). Making it conditional
|
|
58
|
+
* is what keeps the two statements from drifting: while the catalog is complete,
|
|
59
|
+
* nothing anywhere claims a search is needed.
|
|
60
|
+
*/
|
|
61
|
+
export function renderFullCatalogNote(catalog) {
|
|
62
|
+
if (catalog.total === 0)
|
|
63
|
+
return EMPTY_POOL_NOTE;
|
|
64
|
+
const parts = [CATALOG_HEADER, catalog.text];
|
|
65
|
+
if (catalog.truncated) {
|
|
66
|
+
parts.push("", "The catalog above is partial. Call this to list or search the complete pool:", "", ...renderSearchSignature()
|
|
67
|
+
.split("\n")
|
|
68
|
+
.map((line) => ` ${line}`));
|
|
69
|
+
}
|
|
70
|
+
return parts.join("\n");
|
|
71
|
+
}
|
|
72
|
+
/** `h` in the marker payload, and the value `bh` points back at. */
|
|
73
|
+
function hashNote(note) {
|
|
74
|
+
return createHash("sha256").update(note).digest("hex").slice(0, 12);
|
|
75
|
+
}
|
|
76
|
+
/** `{ k, h, t, ns, bh }` — the marker, as one line. */
|
|
77
|
+
function renderMarker(marker) {
|
|
78
|
+
const payload = { k: marker.kind };
|
|
79
|
+
if (marker.hash !== undefined)
|
|
80
|
+
payload.h = marker.hash;
|
|
81
|
+
if (marker.truncated !== undefined)
|
|
82
|
+
payload.t = marker.truncated;
|
|
83
|
+
if (marker.namespaces !== undefined)
|
|
84
|
+
payload.ns = marker.namespaces;
|
|
85
|
+
if (marker.base !== undefined)
|
|
86
|
+
payload.bh = marker.base;
|
|
87
|
+
return `${EXEC_CATALOG_MARKER_PREFIX}${JSON.stringify(payload)}${EXEC_CATALOG_MARKER_SUFFIX}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read the announcement state out of the history.
|
|
91
|
+
*
|
|
92
|
+
* Only this module's own messages are read: a marker quoted anywhere else — a reply
|
|
93
|
+
* explaining the mechanism, a hook echoing one — is prose *about* a marker, not a
|
|
94
|
+
* statement about the pool. Counting it would invent an announcement, and the
|
|
95
|
+
* invented state then misleads every later turn. A marker that cannot be parsed is
|
|
96
|
+
* skipped for the same reason (worst case: one duplicate announcement).
|
|
97
|
+
*/
|
|
98
|
+
export function collectExecCatalogState(messages) {
|
|
99
|
+
const fullHashes = new Set();
|
|
100
|
+
let last = null;
|
|
101
|
+
for (const message of messages) {
|
|
102
|
+
if (message.isMeta !== true)
|
|
103
|
+
continue;
|
|
104
|
+
for (const block of message.blocks) {
|
|
105
|
+
if (block.type !== "text")
|
|
106
|
+
continue;
|
|
107
|
+
for (const line of block.content.split("\n")) {
|
|
108
|
+
const marker = parseMarkerLine(line);
|
|
109
|
+
if (!marker)
|
|
110
|
+
continue;
|
|
111
|
+
last = marker;
|
|
112
|
+
if (marker.kind === "full" && marker.hash)
|
|
113
|
+
fullHashes.add(marker.hash);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { last, fullHashes };
|
|
118
|
+
}
|
|
119
|
+
function parseMarkerLine(line) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
if (!trimmed.startsWith(EXEC_CATALOG_MARKER_PREFIX) ||
|
|
122
|
+
!trimmed.endsWith(EXEC_CATALOG_MARKER_SUFFIX)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const payload = trimmed.slice(EXEC_CATALOG_MARKER_PREFIX.length, trimmed.length - EXEC_CATALOG_MARKER_SUFFIX.length);
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(payload);
|
|
128
|
+
const kind = parsed.k;
|
|
129
|
+
if (kind !== "full" && kind !== "delta" && kind !== "removed")
|
|
130
|
+
return null;
|
|
131
|
+
return {
|
|
132
|
+
kind,
|
|
133
|
+
hash: typeof parsed.h === "string" ? parsed.h : undefined,
|
|
134
|
+
truncated: parsed.t === 1 ? 1 : 0,
|
|
135
|
+
namespaces: readNamespaceCounts(parsed.ns),
|
|
136
|
+
base: typeof parsed.bh === "string" ? parsed.bh : undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// A marker we cannot read (hand-edited or truncated history) must not take the
|
|
141
|
+
// turn down; the worst case is announcing the catalog once more.
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function readNamespaceCounts(value) {
|
|
146
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
147
|
+
return undefined;
|
|
148
|
+
const counts = {};
|
|
149
|
+
for (const [name, count] of Object.entries(value)) {
|
|
150
|
+
if (typeof count !== "number" || !Number.isFinite(count))
|
|
151
|
+
return undefined;
|
|
152
|
+
counts[name] = count;
|
|
153
|
+
}
|
|
154
|
+
return counts;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The next announcement for this turn, or null when there is nothing to say.
|
|
158
|
+
*
|
|
159
|
+
* The decision table, in order: a channel that was never open says nothing; a closed
|
|
160
|
+
* channel is announced once; a session that has announced nothing (or whose last word
|
|
161
|
+
* was the closing notice) gets the full catalog; an unchanged hash says nothing; a
|
|
162
|
+
* changed pool with its basis still present may go out as a namespace-level delta;
|
|
163
|
+
* anything else is the full catalog.
|
|
164
|
+
*/
|
|
165
|
+
export function buildExecCatalogAnnouncement(state, current) {
|
|
166
|
+
const last = state.last;
|
|
167
|
+
if (current === undefined) {
|
|
168
|
+
// Nothing was ever announced, so there is nothing to retract: most sessions have
|
|
169
|
+
// no `Exec` at all, and a closing notice there would be a message about a channel
|
|
170
|
+
// the model never saw. Silence, not "observed absence".
|
|
171
|
+
if (last === null)
|
|
172
|
+
return null;
|
|
173
|
+
// `removed` carries no hash: the channel is not a catalog, so there is nothing
|
|
174
|
+
// to compare. Re-opening it therefore always re-announces in full, even when the
|
|
175
|
+
// pool never moved — going by content here would leave the session mute forever
|
|
176
|
+
// after a switch was flipped off and back on.
|
|
177
|
+
if (last.kind === "removed")
|
|
178
|
+
return null;
|
|
179
|
+
return envelope({ kind: "removed" }, REMOVED_NOTE);
|
|
180
|
+
}
|
|
181
|
+
const note = renderFullCatalogNote(current);
|
|
182
|
+
const hash = hashNote(note);
|
|
183
|
+
const namespaces = namespaceCounts(current);
|
|
184
|
+
const full = {
|
|
185
|
+
kind: "full",
|
|
186
|
+
hash,
|
|
187
|
+
truncated: current.truncated ? 1 : 0,
|
|
188
|
+
namespaces,
|
|
189
|
+
};
|
|
190
|
+
if (last === null) {
|
|
191
|
+
// A session that never had a catalog has nothing to learn from "there is nothing":
|
|
192
|
+
// with an empty pool `Exec` is not even declared, so the note points at no tool the
|
|
193
|
+
// model can see or call. It is announced only to a session that has already seen a
|
|
194
|
+
// catalog, where "the catalog is now empty" is a change rather than a first word.
|
|
195
|
+
return current.total === 0 ? null : envelope(full, note);
|
|
196
|
+
}
|
|
197
|
+
if (last.kind === "removed")
|
|
198
|
+
return envelope(full, note);
|
|
199
|
+
if (last.hash === hash) {
|
|
200
|
+
// The pool renders identically, so an unchanged turn stays silent — unless the
|
|
201
|
+
// last word was a delta whose basis has since been compacted away: nothing the
|
|
202
|
+
// model can still see describes the catalog then, so it is re-announced in full.
|
|
203
|
+
if (last.kind === "delta" && !hasBasis(last, state)) {
|
|
204
|
+
return envelope(full, note);
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (!hasBasis(last, state))
|
|
209
|
+
return envelope(full, note);
|
|
210
|
+
const changed = chooseChangedAnnouncement(last, current, note, namespaces, hash);
|
|
211
|
+
return changed;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Is the full catalog a delta would be relative to still in the history? For a
|
|
215
|
+
* `full` last word that is the message itself; for a delta it is the anchor it
|
|
216
|
+
* passed along its chain.
|
|
217
|
+
*/
|
|
218
|
+
function hasBasis(last, state) {
|
|
219
|
+
const anchor = last.kind === "full" ? last.hash : last.base;
|
|
220
|
+
return anchor !== undefined && state.fullHashes.has(anchor);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Delta or full catalog, for a pool that changed while the basis is still present.
|
|
224
|
+
*
|
|
225
|
+
* Namespace counts are all a marker carries, so a delta can only say "this server
|
|
226
|
+
* appeared / changed size / went away" — never a signature. That is enough only
|
|
227
|
+
* while the catalog is truncated (the model already has the entries it can see) and
|
|
228
|
+
* only when some count actually moved. Otherwise the entries themselves are what
|
|
229
|
+
* changed, and only the full catalog can say how.
|
|
230
|
+
*/
|
|
231
|
+
function chooseChangedAnnouncement(last, current, note, namespaces, hash) {
|
|
232
|
+
const full = {
|
|
233
|
+
kind: "full",
|
|
234
|
+
hash,
|
|
235
|
+
truncated: current.truncated ? 1 : 0,
|
|
236
|
+
namespaces,
|
|
237
|
+
};
|
|
238
|
+
const truncated = current.truncated ? 1 : 0;
|
|
239
|
+
if (last.truncated !== truncated || !current.truncated) {
|
|
240
|
+
return envelope(full, note);
|
|
241
|
+
}
|
|
242
|
+
const delta = renderDeltaNote(last, current);
|
|
243
|
+
// A delta is a means, not an end: when it is no longer the shorter of the two
|
|
244
|
+
// (a wholesale server swap, say) the full catalog wins.
|
|
245
|
+
if (delta === null || delta.length >= note.length) {
|
|
246
|
+
return envelope(full, note);
|
|
247
|
+
}
|
|
248
|
+
return envelope({
|
|
249
|
+
kind: "delta",
|
|
250
|
+
hash,
|
|
251
|
+
truncated,
|
|
252
|
+
namespaces,
|
|
253
|
+
base: last.kind === "full" ? last.hash : last.base,
|
|
254
|
+
}, delta);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The namespace-level differences between two snapshots, or null when the counts
|
|
258
|
+
* are identical (nothing a delta could say).
|
|
259
|
+
*/
|
|
260
|
+
function renderDeltaNote(last, current) {
|
|
261
|
+
const before = last.namespaces;
|
|
262
|
+
if (!before)
|
|
263
|
+
return null;
|
|
264
|
+
const after = namespaceCounts(current);
|
|
265
|
+
const lines = [];
|
|
266
|
+
for (const [name, count] of Object.entries(after)) {
|
|
267
|
+
const previous = before[name];
|
|
268
|
+
if (previous === undefined) {
|
|
269
|
+
lines.push(`- \`mcp__${name}\` is now available (${tools(count)}). Search it for signatures.`);
|
|
270
|
+
}
|
|
271
|
+
else if (previous !== count) {
|
|
272
|
+
lines.push(`- \`mcp__${name}\` now has ${tools(count)} (was ${previous}). ` +
|
|
273
|
+
"Search it again before relying on results from before this change.");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const name of Object.keys(before)) {
|
|
277
|
+
if (after[name] === undefined) {
|
|
278
|
+
lines.push(`- \`mcp__${name}\` is no longer available and must not be used.`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (lines.length === 0)
|
|
282
|
+
return null;
|
|
283
|
+
return [DELTA_HEADER, ...lines].join("\n");
|
|
284
|
+
}
|
|
285
|
+
function tools(count) {
|
|
286
|
+
return `${count} tool${count === 1 ? "" : "s"}`;
|
|
287
|
+
}
|
|
288
|
+
function namespaceCounts(catalog) {
|
|
289
|
+
return Object.fromEntries(catalog.namespaces.map((namespace) => [namespace.name, namespace.count]));
|
|
290
|
+
}
|
|
291
|
+
function envelope(marker, body) {
|
|
292
|
+
return `${renderMarker(marker)}\n${body}`;
|
|
293
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exec sandbox constants.
|
|
3
|
+
*
|
|
4
|
+
* Almost everything here is a tunable default, not a contract. In particular no
|
|
5
|
+
* *knob* may be rendered into model-visible text: the catalog has to stay
|
|
6
|
+
* byte-identical for an unchanged MCP pool, so changing a budget must not change
|
|
7
|
+
* what the model reads (see `Exec`'s description and
|
|
8
|
+
* `exec/catalogAnnouncement.ts`).
|
|
9
|
+
*
|
|
10
|
+
* The exception is `EXEC_RESERVED_NAMESPACE`: it is part of the sandbox API
|
|
11
|
+
* surface, so the tool description has to name it (and it is derived from the
|
|
12
|
+
* same constant, so the two cannot drift).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Wall-clock budget for one Exec script. The parent terminates the sandbox
|
|
16
|
+
* worker when it expires, so a script blocked in `await` is killed instead of
|
|
17
|
+
* spinning on the host event loop forever.
|
|
18
|
+
*/
|
|
19
|
+
export declare const EXEC_DEFAULT_TIMEOUT_MS = 60000;
|
|
20
|
+
/** Maximum number of sandbox -> host tool calls per Exec script. */
|
|
21
|
+
export declare const EXEC_DEFAULT_MAX_TOOL_CALLS = 50;
|
|
22
|
+
/** Maximum number of `console` output characters returned to the model. */
|
|
23
|
+
export declare const EXEC_DEFAULT_MAX_LOG_CHARS = 8000;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of characters of the *script return value* handed back to the
|
|
26
|
+
* model. Mirrors the `DEFAULT_MAX_RESULT_SIZE_CHARS` bound that the flat MCP and
|
|
27
|
+
* Bash paths apply to their own results: without it a script could return a
|
|
28
|
+
* payload far larger than the call it replaced would have produced.
|
|
29
|
+
*/
|
|
30
|
+
export declare const EXEC_DEFAULT_MAX_RESULT_CHARS = 100000;
|
|
31
|
+
/** Maximum number of images propagated from nested MCP calls. */
|
|
32
|
+
export declare const EXEC_DEFAULT_MAX_IMAGES = 4;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum size of the MCP catalog, in estimated tokens (a plain `chars / 4`; see
|
|
35
|
+
* `estimateCatalogTokens`).
|
|
36
|
+
*
|
|
37
|
+
* Provenance: opencode's `catalogBudget` — `defaultCatalogBudget = 2_000` in
|
|
38
|
+
* `packages/codemode/src/tool-runtime.ts`. Deliberately not CJK-aware: MCP tool
|
|
39
|
+
* descriptions are overwhelmingly English. Tunable, not a contract.
|
|
40
|
+
*/
|
|
41
|
+
export declare const EXEC_DEFAULT_CATALOG_TOKENS = 2000;
|
|
42
|
+
/**
|
|
43
|
+
* Reserved property on the sandbox `tools` object hosting search. The `$`
|
|
44
|
+
* prefix cannot collide with an MCP tool name (`[A-Za-z0-9_.-]`).
|
|
45
|
+
*/
|
|
46
|
+
export declare const EXEC_RESERVED_NAMESPACE = "$codemode";
|
|
47
|
+
/**
|
|
48
|
+
* Internal name the sandbox uses to reach host-side catalog search. Matches the
|
|
49
|
+
* sandbox path `tools.$codemode.search` but never appears in model-visible text.
|
|
50
|
+
*/
|
|
51
|
+
export declare const EXEC_SEARCH_CALL = "$codemode.search";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exec sandbox constants.
|
|
3
|
+
*
|
|
4
|
+
* Almost everything here is a tunable default, not a contract. In particular no
|
|
5
|
+
* *knob* may be rendered into model-visible text: the catalog has to stay
|
|
6
|
+
* byte-identical for an unchanged MCP pool, so changing a budget must not change
|
|
7
|
+
* what the model reads (see `Exec`'s description and
|
|
8
|
+
* `exec/catalogAnnouncement.ts`).
|
|
9
|
+
*
|
|
10
|
+
* The exception is `EXEC_RESERVED_NAMESPACE`: it is part of the sandbox API
|
|
11
|
+
* surface, so the tool description has to name it (and it is derived from the
|
|
12
|
+
* same constant, so the two cannot drift).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Wall-clock budget for one Exec script. The parent terminates the sandbox
|
|
16
|
+
* worker when it expires, so a script blocked in `await` is killed instead of
|
|
17
|
+
* spinning on the host event loop forever.
|
|
18
|
+
*/
|
|
19
|
+
export const EXEC_DEFAULT_TIMEOUT_MS = 60000;
|
|
20
|
+
/** Maximum number of sandbox -> host tool calls per Exec script. */
|
|
21
|
+
export const EXEC_DEFAULT_MAX_TOOL_CALLS = 50;
|
|
22
|
+
/** Maximum number of `console` output characters returned to the model. */
|
|
23
|
+
export const EXEC_DEFAULT_MAX_LOG_CHARS = 8000;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of characters of the *script return value* handed back to the
|
|
26
|
+
* model. Mirrors the `DEFAULT_MAX_RESULT_SIZE_CHARS` bound that the flat MCP and
|
|
27
|
+
* Bash paths apply to their own results: without it a script could return a
|
|
28
|
+
* payload far larger than the call it replaced would have produced.
|
|
29
|
+
*/
|
|
30
|
+
export const EXEC_DEFAULT_MAX_RESULT_CHARS = 100000;
|
|
31
|
+
/** Maximum number of images propagated from nested MCP calls. */
|
|
32
|
+
export const EXEC_DEFAULT_MAX_IMAGES = 4;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum size of the MCP catalog, in estimated tokens (a plain `chars / 4`; see
|
|
35
|
+
* `estimateCatalogTokens`).
|
|
36
|
+
*
|
|
37
|
+
* Provenance: opencode's `catalogBudget` — `defaultCatalogBudget = 2_000` in
|
|
38
|
+
* `packages/codemode/src/tool-runtime.ts`. Deliberately not CJK-aware: MCP tool
|
|
39
|
+
* descriptions are overwhelmingly English. Tunable, not a contract.
|
|
40
|
+
*/
|
|
41
|
+
export const EXEC_DEFAULT_CATALOG_TOKENS = 2000;
|
|
42
|
+
/**
|
|
43
|
+
* Reserved property on the sandbox `tools` object hosting search. The `$`
|
|
44
|
+
* prefix cannot collide with an MCP tool name (`[A-Za-z0-9_.-]`).
|
|
45
|
+
*/
|
|
46
|
+
export const EXEC_RESERVED_NAMESPACE = "$codemode";
|
|
47
|
+
/**
|
|
48
|
+
* Internal name the sandbox uses to reach host-side catalog search. Matches the
|
|
49
|
+
* sandbox path `tools.$codemode.search` but never appears in model-visible text.
|
|
50
|
+
*/
|
|
51
|
+
export const EXEC_SEARCH_CALL = "$codemode.search";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ToolContext } from "../tools/types.js";
|
|
2
|
+
import type { ExecPoolEntry } from "./catalog.js";
|
|
3
|
+
export interface RunExecOptions {
|
|
4
|
+
code: string;
|
|
5
|
+
/** Every MCP tool the sandbox may call. Also the allowlist for nested calls. */
|
|
6
|
+
pool: ExecPoolEntry[];
|
|
7
|
+
context: ToolContext;
|
|
8
|
+
/**
|
|
9
|
+
* Called as each nested call is issued, before it is dispatched, in the order
|
|
10
|
+
* the script issues them. Lets the caller show live progress; a call that is
|
|
11
|
+
* refused afterwards (over the limit) still reports.
|
|
12
|
+
*/
|
|
13
|
+
onToolCall?: (name: string) => void;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
maxToolCalls?: number;
|
|
16
|
+
maxLogChars?: number;
|
|
17
|
+
maxResultChars?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ExecCallResult {
|
|
20
|
+
/**
|
|
21
|
+
* What the script's `await` resolves to — the tool's structured output, else its
|
|
22
|
+
* text, else `null` (the rule `renderToolSignature` promises in the catalog).
|
|
23
|
+
* Plain JSON only: it crosses the worker boundary and is deep-cloned inside the
|
|
24
|
+
* sandbox.
|
|
25
|
+
*/
|
|
26
|
+
output: unknown;
|
|
27
|
+
/**
|
|
28
|
+
* Images to hoist onto the `Exec` result. Never seen by the script: a nested call
|
|
29
|
+
* resolves to the tool's output, and an image is not something a script composes
|
|
30
|
+
* with.
|
|
31
|
+
*/
|
|
32
|
+
images?: Array<{
|
|
33
|
+
data: string;
|
|
34
|
+
mediaType?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface ExecRunResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
/** Serialized script return value. Only set when `ok`. */
|
|
40
|
+
value?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
logs: string[];
|
|
43
|
+
toolCalls: number;
|
|
44
|
+
images: Array<{
|
|
45
|
+
data: string;
|
|
46
|
+
mediaType?: string;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run one Exec script in a terminable sandbox worker.
|
|
51
|
+
*
|
|
52
|
+
* Never rejects: every failure mode (script throw, budget, abort, worker crash)
|
|
53
|
+
* comes back as `{ ok: false, error }` so the model gets a result to act on.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runExecScript(options: RunExecOptions): Promise<ExecRunResult>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-side driver for the Exec sandbox worker.
|
|
3
|
+
*
|
|
4
|
+
* Owns everything the sandbox must not: the MCP pool, the permission context,
|
|
5
|
+
* the wall-clock budget and termination. The sandbox only ever sends plain
|
|
6
|
+
* JSON (`{ name, args }`) and only ever receives plain JSON back.
|
|
7
|
+
*/
|
|
8
|
+
import { Worker } from "node:worker_threads";
|
|
9
|
+
import { logger } from "../utils/globalLogger.js";
|
|
10
|
+
import { EXEC_DEFAULT_MAX_IMAGES, EXEC_DEFAULT_MAX_LOG_CHARS, EXEC_DEFAULT_MAX_RESULT_CHARS, EXEC_DEFAULT_MAX_TOOL_CALLS, EXEC_DEFAULT_TIMEOUT_MS, EXEC_RESERVED_NAMESPACE, EXEC_SEARCH_CALL, } from "./constants.js";
|
|
11
|
+
import { EXEC_WORKER_SOURCE } from "./workerSource.js";
|
|
12
|
+
import { renderSearchCallForm, renderToolSignature, resolveSearchQuery, } from "./catalog.js";
|
|
13
|
+
const DYNAMIC_IMPORT_PATTERN = /dynamic import callback/i;
|
|
14
|
+
/**
|
|
15
|
+
* Rewrite engine-internal messages the model cannot act on into something it
|
|
16
|
+
* can. `node:vm` refuses `import()` with an internal-sounding message that
|
|
17
|
+
* mentions a callback the model has no way to know about.
|
|
18
|
+
*/
|
|
19
|
+
function humanizeError(message) {
|
|
20
|
+
if (DYNAMIC_IMPORT_PATTERN.test(message)) {
|
|
21
|
+
return "import() is not available inside Exec";
|
|
22
|
+
}
|
|
23
|
+
return message;
|
|
24
|
+
}
|
|
25
|
+
async function handleExecCall(name, args, pool, context) {
|
|
26
|
+
if (name === EXEC_SEARCH_CALL) {
|
|
27
|
+
// The shape is validated against the schema the tool description renders from,
|
|
28
|
+
// not by hand — see `resolveSearchQuery`.
|
|
29
|
+
const query = resolveSearchQuery(args);
|
|
30
|
+
const matches = [...pool.values()].filter((entry) => {
|
|
31
|
+
if (query.length === 0)
|
|
32
|
+
return true;
|
|
33
|
+
return (entry.name.toLowerCase().includes(query) ||
|
|
34
|
+
(entry.description ?? "").toLowerCase().includes(query));
|
|
35
|
+
});
|
|
36
|
+
// Return the same rendered signature the catalog shows, not the raw schema,
|
|
37
|
+
// so a hit can be copied verbatim into a call. Rendering is done on the
|
|
38
|
+
// matched entries only, never the whole pool. The value is the array itself,
|
|
39
|
+
// not its JSON text — same rule as an MCP call: a script composes values, and
|
|
40
|
+
// making it parse a string first is the kind of extra step a signature should
|
|
41
|
+
// not have to mention.
|
|
42
|
+
return {
|
|
43
|
+
output: matches.map((entry) => ({
|
|
44
|
+
name: entry.name,
|
|
45
|
+
description: entry.description,
|
|
46
|
+
signature: renderToolSignature(entry),
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!pool.has(name)) {
|
|
51
|
+
throw new Error(`Unknown tool "${name}". Only MCP tools are reachable from Exec. ` +
|
|
52
|
+
`Use ${renderSearchCallForm()} to find one.`);
|
|
53
|
+
}
|
|
54
|
+
const mcpManager = context.mcpManager;
|
|
55
|
+
if (!mcpManager) {
|
|
56
|
+
throw new Error("MCP manager is not available in the Exec context");
|
|
57
|
+
}
|
|
58
|
+
// The single MCP funnel: it runs the permission/approval check internally and
|
|
59
|
+
// keys it on the flattened name, so a nested call is approved exactly like a
|
|
60
|
+
// flat MCP call.
|
|
61
|
+
const result = await mcpManager.executeMcpTool(name, args, context);
|
|
62
|
+
return { output: result.output, images: result.images };
|
|
63
|
+
}
|
|
64
|
+
function terminate(worker) {
|
|
65
|
+
worker.terminate().catch(() => {
|
|
66
|
+
/* already gone */
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Run one Exec script in a terminable sandbox worker.
|
|
71
|
+
*
|
|
72
|
+
* Never rejects: every failure mode (script throw, budget, abort, worker crash)
|
|
73
|
+
* comes back as `{ ok: false, error }` so the model gets a result to act on.
|
|
74
|
+
*/
|
|
75
|
+
export function runExecScript(options) {
|
|
76
|
+
const timeoutMs = options.timeoutMs ?? EXEC_DEFAULT_TIMEOUT_MS;
|
|
77
|
+
const maxToolCalls = options.maxToolCalls ?? EXEC_DEFAULT_MAX_TOOL_CALLS;
|
|
78
|
+
const maxLogChars = options.maxLogChars ?? EXEC_DEFAULT_MAX_LOG_CHARS;
|
|
79
|
+
const maxResultChars = options.maxResultChars ?? EXEC_DEFAULT_MAX_RESULT_CHARS;
|
|
80
|
+
const pool = new Map(options.pool.map((entry) => [entry.name, entry]));
|
|
81
|
+
const images = [];
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const worker = new Worker(EXEC_WORKER_SOURCE, { eval: true });
|
|
84
|
+
let settled = false;
|
|
85
|
+
let toolCalls = 0;
|
|
86
|
+
const finish = (result) => {
|
|
87
|
+
if (settled)
|
|
88
|
+
return;
|
|
89
|
+
settled = true;
|
|
90
|
+
clearTimeout(budget);
|
|
91
|
+
options.context.abortSignal?.removeEventListener("abort", onAbort);
|
|
92
|
+
terminate(worker);
|
|
93
|
+
resolve(result);
|
|
94
|
+
};
|
|
95
|
+
const budget = setTimeout(() => {
|
|
96
|
+
finish({
|
|
97
|
+
ok: false,
|
|
98
|
+
error: `Exec script exceeded the ${timeoutMs}ms budget and was terminated. ` +
|
|
99
|
+
`Tool calls that already completed still apply.`,
|
|
100
|
+
logs: [],
|
|
101
|
+
toolCalls,
|
|
102
|
+
images,
|
|
103
|
+
});
|
|
104
|
+
}, timeoutMs);
|
|
105
|
+
const onAbort = () => {
|
|
106
|
+
finish({
|
|
107
|
+
ok: false,
|
|
108
|
+
error: "Exec script was aborted.",
|
|
109
|
+
logs: [],
|
|
110
|
+
toolCalls,
|
|
111
|
+
images,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
if (options.context.abortSignal?.aborted) {
|
|
115
|
+
onAbort();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
options.context.abortSignal?.addEventListener("abort", onAbort, {
|
|
119
|
+
once: true,
|
|
120
|
+
});
|
|
121
|
+
worker.on("message", (message) => {
|
|
122
|
+
if (settled)
|
|
123
|
+
return;
|
|
124
|
+
if (message.kind === "call") {
|
|
125
|
+
const call = message;
|
|
126
|
+
toolCalls += 1;
|
|
127
|
+
options.onToolCall?.(call.name);
|
|
128
|
+
if (toolCalls > maxToolCalls) {
|
|
129
|
+
worker.postMessage({
|
|
130
|
+
kind: "result",
|
|
131
|
+
id: call.id,
|
|
132
|
+
ok: false,
|
|
133
|
+
error: `Exec tool-call limit reached (${maxToolCalls}).`,
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
handleExecCall(call.name, call.args, pool, options.context).then((result) => {
|
|
138
|
+
if (result.images?.length) {
|
|
139
|
+
for (const image of result.images) {
|
|
140
|
+
if (images.length < EXEC_DEFAULT_MAX_IMAGES)
|
|
141
|
+
images.push(image);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (settled)
|
|
145
|
+
return;
|
|
146
|
+
worker.postMessage({
|
|
147
|
+
kind: "result",
|
|
148
|
+
id: call.id,
|
|
149
|
+
ok: true,
|
|
150
|
+
value: result.output,
|
|
151
|
+
});
|
|
152
|
+
}, (error) => {
|
|
153
|
+
if (settled)
|
|
154
|
+
return;
|
|
155
|
+
worker.postMessage({
|
|
156
|
+
kind: "result",
|
|
157
|
+
id: call.id,
|
|
158
|
+
ok: false,
|
|
159
|
+
error: humanizeError(error instanceof Error ? error.message : String(error)),
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (message.kind === "done") {
|
|
165
|
+
const done = message;
|
|
166
|
+
const logs = Array.isArray(done.logs) ? done.logs : [];
|
|
167
|
+
if (done.ok) {
|
|
168
|
+
finish({
|
|
169
|
+
ok: true,
|
|
170
|
+
value: done.value,
|
|
171
|
+
logs,
|
|
172
|
+
toolCalls,
|
|
173
|
+
images,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
finish({
|
|
178
|
+
ok: false,
|
|
179
|
+
error: humanizeError(done.error ?? "Exec script failed"),
|
|
180
|
+
logs,
|
|
181
|
+
toolCalls,
|
|
182
|
+
images,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
worker.on("error", (error) => {
|
|
188
|
+
logger.error(`[Exec] sandbox worker error: ${error.message}`);
|
|
189
|
+
finish({
|
|
190
|
+
ok: false,
|
|
191
|
+
error: humanizeError(error.message),
|
|
192
|
+
logs: [],
|
|
193
|
+
toolCalls,
|
|
194
|
+
images,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
worker.on("exit", (code) => {
|
|
198
|
+
finish({
|
|
199
|
+
ok: false,
|
|
200
|
+
error: `Exec sandbox exited unexpectedly (code ${code})`,
|
|
201
|
+
logs: [],
|
|
202
|
+
toolCalls,
|
|
203
|
+
images,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
worker.postMessage({
|
|
207
|
+
kind: "run",
|
|
208
|
+
code: options.code,
|
|
209
|
+
toolNames: options.pool.map((entry) => entry.name),
|
|
210
|
+
searchName: EXEC_SEARCH_CALL,
|
|
211
|
+
reservedNamespace: EXEC_RESERVED_NAMESPACE,
|
|
212
|
+
timeoutMs,
|
|
213
|
+
maxLogChars,
|
|
214
|
+
maxResultChars,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|