@springbrand/agent-runtime 0.2.0-alpha.40 → 0.2.0-alpha.41

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.
@@ -1,61 +1,16 @@
1
- import {
2
- createWorkspaceStateBackend,
3
- type WorkspaceFsLike,
4
- } from "@cloudflare/shell";
5
1
  import { createBrowserTools } from "@cloudflare/think/tools/browser";
6
- import { createExecuteRuntime } from "@cloudflare/think/tools/execute";
7
2
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
8
- import type { Usage } from "@earendil-works/pi-ai";
9
3
  import type { ToolSet } from "ai";
10
4
  import type {
11
5
  RuntimeBrowserPort,
12
6
  RuntimeCodeExecutionPort,
13
- WorkspacePort,
14
7
  } from "../../kernel/bindings";
15
- import type { RuntimeCodeExecutionFactory } from "../../kernel/bindings";
16
8
  import { serializeOutput } from "../../lib/artifacts";
17
- import type { PiToolCandidate } from "./compiler";
18
- import { piCandidatesToAiTools } from "./nested-tools";
19
9
 
20
- // 本文件沿用 `../../index.ts` 入口定义的 Workspace、PortTool Candidate 术语。
10
+ // 本文件沿用 `../../index.ts` 入口定义的 BrowserPort 术语。
21
11
 
22
12
  const CODEMODE_SANDBOX_TIMEOUT_MS = 55_000;
23
13
 
24
- function directoryEntry(name: string, label: string): string {
25
- const singleLineLabel = label.replaceAll(/\s+/g, " ").trim().replaceAll("`", "'");
26
- return `- \`${name}\` — ${singleLineLabel}`;
27
- }
28
-
29
- function codeExecutionDescription(
30
- candidates: readonly PiToolCandidate[],
31
- ): string {
32
- const list = (
33
- entries: readonly { readonly name: string; readonly label: string }[],
34
- ) => entries.length > 0
35
- ? entries.map(({ name, label }) => directoryEntry(name, label)).join("\n")
36
- : "- None.";
37
-
38
- return [
39
- "Execute plain JavaScript in a sandbox using the exact Tool directory below.",
40
- "",
41
- "## `tools.*` Available",
42
- list(candidates.map(({ tool }) => ({
43
- name: tool.name,
44
- label: tool.label ?? tool.name,
45
- }))),
46
- "",
47
- "Call only the methods listed above through `tools.*`; never guess or construct a method name.",
48
- "Use `codemode.describe(\"tools.method\")` when you need the exact input type for a listed method.",
49
- "`codemode.search` cannot add methods to `tools.*` or load top-level Tools; it searches only connector methods and snippets already installed in this Code Mode Runtime.",
50
- "Use `state.*` for the Workspace filesystem. Every method takes one object argument, for example `state.readFile({ path })` and `state.writeFile({ path, content })`.",
51
- "Wrap raw fetch, random values, time, and other nondeterministic work in `codemode.step(name, fn)` so replay runs them once.",
52
- "Some connector methods pause for approval and resume automatically. Do not re-issue paused code.",
53
- "Keep all code outside connector calls and `codemode.step` deterministic.",
54
- "Raw `fetch` is available inside `codemode.step(...)`; prefer connector SDKs when one owns the target.",
55
- "There is no Node.js `require`, `process`, package manager, or Python runtime.",
56
- ].join("\n");
57
- }
58
-
59
14
  /**
60
15
  * 宿主的 Browser Rendering 绑定,只取本仓真正用到的那一面。
61
16
  *
@@ -66,13 +21,6 @@ export interface RuntimeBrowserBinding {
66
21
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
67
22
  }
68
23
 
69
- /**
70
- * 把宿主的 Worker Loader、出站网络和 Workspace 组装成代码执行 Port。
71
- *
72
- * Worker 宿主在具备 Durable Object state 和完整平台绑定时调用,然后把返回值交给 Runtime 工具组装。
73
- *
74
- * Think 的独立 execute factory 接受显式宿主参数,不要求 Agent 继承 Think;这里只借它组装 Codemode Runtime、Dynamic Worker executor 和已限定范围的 Workspace state connector。
75
- */
76
24
  function result(details: unknown): AgentToolResult<unknown> {
77
25
  return {
78
26
  content: [{ type: "text", text: serializeOutput(details).text }],
@@ -80,56 +28,6 @@ function result(details: unknown): AgentToolResult<unknown> {
80
28
  };
81
29
  }
82
30
 
83
- function sumUsage(results: readonly AgentToolResult<unknown>[]): Usage | undefined {
84
- const usages = results.flatMap(({ usage }) => usage ? [usage] : []);
85
- if (usages.length === 0) return undefined;
86
- return usages.reduce<Usage>((total, usage) => ({
87
- input: total.input + usage.input,
88
- output: total.output + usage.output,
89
- cacheRead: total.cacheRead + usage.cacheRead,
90
- cacheWrite: total.cacheWrite + usage.cacheWrite,
91
- ...(total.cacheWrite1h === undefined && usage.cacheWrite1h === undefined
92
- ? {}
93
- : { cacheWrite1h: (total.cacheWrite1h ?? 0) + (usage.cacheWrite1h ?? 0) }),
94
- ...(total.reasoning === undefined && usage.reasoning === undefined
95
- ? {}
96
- : { reasoning: (total.reasoning ?? 0) + (usage.reasoning ?? 0) }),
97
- totalTokens: total.totalTokens + usage.totalTokens,
98
- cost: {
99
- input: total.cost.input + usage.cost.input,
100
- output: total.cost.output + usage.cost.output,
101
- cacheRead: total.cost.cacheRead + usage.cost.cacheRead,
102
- cacheWrite: total.cost.cacheWrite + usage.cost.cacheWrite,
103
- total: total.cost.total + usage.cost.total,
104
- },
105
- }), {
106
- input: 0,
107
- output: 0,
108
- cacheRead: 0,
109
- cacheWrite: 0,
110
- totalTokens: 0,
111
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
112
- });
113
- }
114
-
115
- function codeExecutionResult(
116
- details: unknown,
117
- innerResults: readonly AgentToolResult<unknown>[],
118
- ): AgentToolResult<unknown> {
119
- const addedToolNames = [...new Set(
120
- innerResults.flatMap(({ addedToolNames }) => addedToolNames ?? []),
121
- )].sort();
122
- const usage = sumUsage(innerResults);
123
- return {
124
- ...result(details),
125
- ...(addedToolNames.length > 0 ? { addedToolNames } : {}),
126
- ...(usage ? { usage } : {}),
127
- ...(innerResults.length > 0 && innerResults.every(({ terminate }) => terminate === true)
128
- ? { terminate: true }
129
- : {}),
130
- };
131
- }
132
-
133
31
  // 上游把 Code Mode 类工具交付为 AI SDK tool;本仓只取 description 和 execute 两件,
134
32
  // 并在这里就地校验,使装配期缺件立刻失败,而不是等模型调用时才炸。
135
33
  function toCodeExecutionPort(
@@ -195,7 +93,6 @@ function browserToolDescription(): string {
195
93
  *
196
94
  * Worker 宿主在具备 DO state 与 Browser 绑定时调用,返回值经 Platform Port 交给 Runtime 工具组装。
197
95
  *
198
- * 形状与 `createWorkspaceCodeExecutionFactory` 同构:宿主提供 DO state 与平台绑定,Runtime 只拿到一个可选装配输入。
199
96
  * `create()` 延迟到 Tool Surface 真的要注册时才调,被 deny 的装配不会白建连接器。
200
97
  */
201
98
  export function createBrowserExecutionFactory(options: {
@@ -240,42 +137,3 @@ export function createBrowserExecutionFactory(options: {
240
137
  },
241
138
  };
242
139
  }
243
-
244
- export function createWorkspaceCodeExecutionFactory(options: {
245
- readonly ctx: DurableObjectState;
246
- readonly loader: WorkerLoader;
247
- readonly outbound: Fetcher;
248
- readonly workspace: WorkspacePort;
249
- }): RuntimeCodeExecutionFactory {
250
- return {
251
- create(candidates) {
252
- const description = codeExecutionDescription(candidates);
253
- return {
254
- description,
255
- async execute(input) {
256
- const innerResults: AgentToolResult<unknown>[] = [];
257
- const { tool } = createExecuteRuntime({
258
- ctx: options.ctx,
259
- loader: options.loader,
260
- globalOutbound: options.outbound,
261
- tools: piCandidatesToAiTools(candidates, {
262
- onResult: (value) => innerResults.push(value),
263
- }),
264
- description,
265
- // 先于外层 60s 截止结束,给 Runtime RPC 结算和 Worker 释放留出时间。
266
- timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
267
- state: createWorkspaceStateBackend(
268
- options.workspace as unknown as WorkspaceFsLike,
269
- ),
270
- name: "execute",
271
- });
272
- return toCodeExecutionPort(
273
- tool,
274
- "execute",
275
- (details) => codeExecutionResult(details, innerResults),
276
- ).execute(input);
277
- },
278
- };
279
- },
280
- };
281
- }
@@ -202,7 +202,7 @@ export function listExtensionsPiToolCandidate(
202
202
 
203
203
  // #endregion
204
204
 
205
- // #region Code Mode
205
+ // #region Browser Code Mode
206
206
 
207
207
  const executeParameters = Type.Object({
208
208
  code: Type.String({
@@ -213,9 +213,8 @@ const executeParameters = Type.Object({
213
213
  });
214
214
  const CODEMODE_EXECUTE_TIMEOUT_MS = 60_000;
215
215
 
216
- // 把模型提供的代码交给 Codemode Runtime 执行,并在外层再压一道截止。
217
- // 两个 Code Mode 类工具(`execute` 与 `browser_execute`)共用同一段时序,
218
- // 避免两套心智模型;`label` 只用于超时文案,因为模型看到的名字由候选项决定。
216
+ // 把模型提供的浏览器代码交给 Codemode Runtime 执行,并在外层再压一道截止。
217
+ // `label` 只用于超时文案,因为模型看到的名字由候选项决定。
219
218
  function runCodemode(
220
219
  runtime: RuntimeCodeExecutionPort,
221
220
  label: string,
@@ -254,36 +253,6 @@ function runCodemode(
254
253
  };
255
254
  }
256
255
 
257
- /**
258
- * 把 Cloudflare Codemode Runtime handle 包装为 Pi 代码执行工具候选项。
259
- *
260
- * Tool Surface 收到 Host 已组装的 Code Execution Port 后调用,
261
- * 模型再通过 `execute` 运行代码。
262
- *
263
- * Code Mode 作为一个完整的 safe 工具对外暴露,内部能力不再单独提权。
264
- */
265
- export function codeExecutionPiToolCandidate(
266
- runtime: RuntimeCodeExecutionPort,
267
- ): PiToolCandidate {
268
- const tool: AgentTool<typeof executeParameters> = {
269
- name: "execute",
270
- label: "Execute JavaScript",
271
- description: runtime.description,
272
- parameters: executeParameters,
273
- // Pi 工具循环在模型选择 `execute` 时调用,调用前允许 Turn 取消。
274
- // 必须通过 Runtime handle 而不是直接调用 executor,因为 Cloudflare Codemode 把重放、审批和执行日志放在持久化 Runtime 层。
275
- execute: runCodemode(runtime, "Code Mode execute"),
276
- };
277
- return {
278
- owner: "core:codemode",
279
- requiredExecutionLevel: "safe",
280
- outputBudget: { kind: "structure" },
281
- source: "codemode",
282
- summary: "Run JavaScript with network and configured connector access",
283
- tool,
284
- };
285
- }
286
-
287
256
  /** 模型可见的浏览器工具名;属外部契约,改名是破坏性变更。 */
288
257
  export const BROWSER_EXECUTE_TOOL_NAME = "browser_execute";
289
258
 
@@ -49,7 +49,6 @@ export interface PiDeclaredToolPolicy<Reply = unknown> {
49
49
  readonly modelName: string;
50
50
  readonly requiredExecutionLevel: PiToolCandidate["requiredExecutionLevel"];
51
51
  readonly requiredExecutionLevelForInput?: PiToolCandidate["requiredExecutionLevelForInput"];
52
- readonly direct?: true;
53
52
  readonly retry?: PiToolCandidate["retry"];
54
53
  readonly source?: PiToolCandidate["source"];
55
54
  readonly summary?: string;
@@ -116,7 +115,6 @@ export function createPiDeclaredToolCandidate<Reply = unknown>(
116
115
  ...(policy.requiredExecutionLevelForInput
117
116
  ? { requiredExecutionLevelForInput: policy.requiredExecutionLevelForInput }
118
117
  : {}),
119
- ...(policy.direct ? { direct: policy.direct } : {}),
120
118
  ...(policy.retry ? { retry: policy.retry } : {}),
121
119
  ...(policy.source ? { source: policy.source } : {}),
122
120
  };
@@ -92,7 +92,7 @@ function candidate<T extends TSchema>(
92
92
  options: Partial<
93
93
  Pick<
94
94
  PiToolCandidate,
95
- "alwaysRequiresApproval" | "direct" | "owner" | "requiredExecutionLevel" | "summary"
95
+ "alwaysRequiresApproval" | "owner" | "requiredExecutionLevel" | "summary"
96
96
  >
97
97
  > = {},
98
98
  ): PiToolCandidate {
@@ -104,7 +104,6 @@ function candidate<T extends TSchema>(
104
104
  ...(options.alwaysRequiresApproval
105
105
  ? { alwaysRequiresApproval: true }
106
106
  : {}),
107
- ...(options.direct ? { direct: true } : {}),
108
107
  ...(options.summary ? { summary: options.summary } : {}),
109
108
  };
110
109
  }
@@ -134,7 +133,6 @@ export function schedulePiToolCandidates(
134
133
  },
135
134
  {
136
135
  alwaysRequiresApproval: true,
137
- direct: true,
138
136
  summary: "Create a scheduled task",
139
137
  },
140
138
  ),
@@ -93,7 +93,7 @@ function budgetSkillResources(skill: LoadedSkill): LoadedSkill {
93
93
  if (portable.length > 0) {
94
94
  lines.push(
95
95
  `> If any of these resources are needed, copy all required ones into the Workspace ` +
96
- `in one execute with tools.materialize_skill_resource: ` +
96
+ `in one materialize_skill_resource call using its resources array: ` +
97
97
  `${portable.map((entry) => entry.path).join(", ")}.`,
98
98
  );
99
99
  }
@@ -175,6 +175,21 @@ const SKILL_TOOL_LABELS: Readonly<Record<string, string>> = {
175
175
  materialize_skill_resource: "Materialize Skill resource",
176
176
  };
177
177
 
178
+ const SKILL_TOOL_PARAMETER_DESCRIPTIONS: Readonly<Record<string, Readonly<Record<string, string>>>> = {
179
+ activate_skill: {
180
+ name: "Exact name of the available Skill to activate.",
181
+ },
182
+ read_skill_resource: {
183
+ name: "Name of the activated Skill. Omit only when path starts with the Skill name.",
184
+ path: "Bundled resource path listed by activate_skill.",
185
+ },
186
+ run_skill_script: {
187
+ name: "Name of the activated Skill that supplies the script.",
188
+ path: "Bundled script path listed by activate_skill.",
189
+ input: "JSON input expected by the Skill script. Defaults to an empty object.",
190
+ },
191
+ };
192
+
178
193
  const SKILL_ENTRY_READ_GUIDANCE =
179
194
  "SKILL.md contains the Skill instructions; use activate_skill instead.";
180
195
 
@@ -196,44 +211,95 @@ function materializeSkillResourceTool(
196
211
  ) {
197
212
  const names = bindings.map(({ name }) => name) as [string, ...string[]];
198
213
  const byName = new Map(bindings.map((binding) => [binding.name, binding]));
214
+ const resourceInput = z.object({
215
+ name: z.enum(names).describe("Activated Skill name"),
216
+ path: z.string().min(1).describe("Bundled resource path listed by activate_skill"),
217
+ destination: z.string().min(1).startsWith("/").describe(
218
+ "Absolute destination path in the Workspace",
219
+ ),
220
+ });
199
221
  return tool({
200
222
  description:
201
- "Copy a complete bundled Skill resource directly into the Workspace without returning its contents to the model. " +
223
+ "Copy complete bundled Skill resources directly into the Workspace without returning their contents to the model. " +
202
224
  "Use this instead of read_skill_resource when a template, script, image, font, or other asset must become a Workspace file. " +
203
- "When multiple resources are needed, copy them in one execute with a loop or Promise.all; never start one execute per resource. " +
225
+ "Pass every required item in the resources array; items are copied in order and failures are reported per item. " +
204
226
  "The destination is created or overwritten, including parent directories.",
205
227
  inputSchema: z.object({
206
- name: z.enum(names).describe("Activated Skill name"),
207
- path: z.string().min(1).describe("Bundled resource path listed by activate_skill"),
208
- destination: z.string().min(1).describe("Absolute destination path in the Workspace"),
228
+ resources: z.array(resourceInput).min(1).max(100),
209
229
  }),
210
- execute: async ({ name, path, destination }, { abortSignal }) => {
230
+ execute: async ({ resources }, { abortSignal }) => {
211
231
  abortSignal?.throwIfAborted();
212
- const source = byName.get(name)?.source;
213
- if (!source?.readResource) {
214
- throw new Error(`Skill \"${name}\" has no readable resources.`);
232
+ const destinations = new Set<string>();
233
+ for (const { destination } of resources) {
234
+ if (destinations.has(destination)) {
235
+ throw new Error(`Duplicate materialize destination: ${destination}`);
236
+ }
237
+ destinations.add(destination);
215
238
  }
216
- const resource = await source.readResource(name, path);
217
- if (!resource) throw new Error(`Resource not found: ${name}/${path}`);
218
- abortSignal?.throwIfAborted();
219
- const bytes = resourceBytes(resource);
220
239
 
221
- await serializeWorkspaceMutation(workspace, destination, async () => {
240
+ const results: Array<Record<string, unknown>> = [];
241
+ for (const { name, path, destination } of resources) {
222
242
  abortSignal?.throwIfAborted();
223
- const parent = destination.replace(/\/[^/]+$/, "");
224
- if (parent && parent !== "/") {
225
- await workspace.mkdir(parent, { recursive: true });
243
+ const source = byName.get(name)?.source;
244
+ if (!source?.readResource) {
245
+ results.push({
246
+ status: "error",
247
+ name,
248
+ path,
249
+ destination,
250
+ error: `Skill \"${name}\" has no readable resources.`,
251
+ });
252
+ continue;
253
+ }
254
+ try {
255
+ const resource = await source.readResource(name, path);
256
+ if (!resource) {
257
+ results.push({
258
+ status: "error",
259
+ name,
260
+ path,
261
+ destination,
262
+ error: `Resource not found: ${name}/${path}`,
263
+ });
264
+ continue;
265
+ }
266
+ abortSignal?.throwIfAborted();
267
+ const bytes = resourceBytes(resource);
268
+
269
+ await serializeWorkspaceMutation(workspace, destination, async () => {
270
+ abortSignal?.throwIfAborted();
271
+ const parent = destination.replace(/\/[^/]+$/, "");
272
+ if (parent && parent !== "/") {
273
+ await workspace.mkdir(parent, { recursive: true });
274
+ }
275
+ await workspace.writeFileBytes(destination, bytes, resource.mimeType);
276
+ });
277
+
278
+ results.push({
279
+ status: "written",
280
+ name,
281
+ path: resource.path,
282
+ destination,
283
+ bytesWritten: bytes.byteLength,
284
+ encoding: resource.encoding ?? "text",
285
+ ...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
286
+ });
287
+ } catch (error) {
288
+ abortSignal?.throwIfAborted();
289
+ results.push({
290
+ status: "error",
291
+ name,
292
+ path,
293
+ destination,
294
+ error: "Resource materialization failed",
295
+ });
226
296
  }
227
- await workspace.writeFileBytes(destination, bytes, resource.mimeType);
228
- });
297
+ }
229
298
 
230
299
  return {
231
- name,
232
- path: resource.path,
233
- destination,
234
- bytesWritten: bytes.byteLength,
235
- encoding: resource.encoding ?? "text",
236
- ...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
300
+ written: results.filter(({ status }) => status === "written").length,
301
+ failed: results.filter(({ status }) => status === "error").length,
302
+ results,
237
303
  };
238
304
  },
239
305
  });
@@ -270,6 +336,17 @@ export async function skillPiToolCandidates(
270
336
  }
271
337
  : {}),
272
338
  });
339
+ const properties = (adapted.parameters as { properties?: unknown }).properties;
340
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
341
+ for (const [parameter, description] of Object.entries(
342
+ SKILL_TOOL_PARAMETER_DESCRIPTIONS[name] ?? {},
343
+ )) {
344
+ const schema = (properties as Record<string, unknown>)[parameter];
345
+ if (schema && typeof schema === "object" && !Array.isArray(schema)) {
346
+ (schema as Record<string, unknown>).description ??= description;
347
+ }
348
+ }
349
+ }
273
350
  if (name === "read_skill_resource") {
274
351
  const execute = adapted.execute;
275
352
  adapted.execute = (toolCallId, input, signal) => {
@@ -303,9 +380,6 @@ export async function skillPiToolCandidates(
303
380
  ...(name === "activate_skill"
304
381
  ? { outputBudget: { kind: "structure" as const } }
305
382
  : {}),
306
- ...(name === "materialize_skill_resource"
307
- ? { codeExecutionOnly: true as const }
308
- : {}),
309
383
  tool: adapted,
310
384
  };
311
385
  });
@@ -70,9 +70,16 @@ const workspaceReadParameters = Type.Object({
70
70
  })),
71
71
  });
72
72
  const workspaceEditParameters = Type.Object({
73
- path: Type.String({ minLength: 1, maxLength: 4_096 }),
74
- old_string: Type.String(),
75
- new_string: Type.String(),
73
+ path: Type.String({
74
+ minLength: 1,
75
+ maxLength: 4_096,
76
+ description: "Absolute Workspace path of the file to edit.",
77
+ }),
78
+ old_string: Type.String({
79
+ description:
80
+ "Exact existing text to replace. Include enough surrounding context to match one location.",
81
+ }),
82
+ new_string: Type.String({ description: "Replacement text." }),
76
83
  });
77
84
 
78
85
  /**
@@ -430,24 +437,47 @@ export function workspacePiToolCandidates(
430
437
  // #region Sandbox tools
431
438
 
432
439
  const sandboxCommandParameters = {
433
- command: Type.String({ minLength: 1, maxLength: 32_768 }),
434
- cwd: Type.Optional(Type.String({ maxLength: 4_096 })),
435
- stdin: Type.Optional(Type.String({ maxLength: 65_536 })),
440
+ command: Type.String({
441
+ minLength: 1,
442
+ maxLength: 32_768,
443
+ description: "Shell command to run in the isolated Linux Sandbox.",
444
+ }),
445
+ cwd: Type.Optional(Type.String({
446
+ maxLength: 4_096,
447
+ description: "Sandbox working directory. Omit to use the Sandbox default.",
448
+ })),
449
+ stdin: Type.Optional(Type.String({
450
+ maxLength: 65_536,
451
+ description: "Optional text to pass to the command on standard input.",
452
+ })),
436
453
  };
437
454
  const sandboxExecParameters = Type.Object({
438
455
  ...sandboxCommandParameters,
439
456
  timeoutMs: Type.Optional(
440
- Type.Integer({ minimum: 1, maximum: 60_000 }),
457
+ Type.Integer({
458
+ minimum: 1,
459
+ maximum: 60_000,
460
+ description: "Maximum command runtime in milliseconds, from 1 to 60000.",
461
+ }),
441
462
  ),
442
463
  });
443
464
  const sandboxStartParameters = Type.Object(sandboxCommandParameters);
444
465
  const sandboxProcessParameters = Type.Object({
445
- id: Type.String({ minLength: 1, maxLength: 128 }),
466
+ id: Type.String({
467
+ minLength: 1,
468
+ maxLength: 128,
469
+ description: "Process ID returned by sandbox_start_process.",
470
+ }),
446
471
  });
447
472
  const sandboxPublishParameters = Type.Object({
448
- paths: Type.Array(Type.String({ minLength: 1, maxLength: 4_096 }), {
473
+ paths: Type.Array(Type.String({
474
+ minLength: 1,
475
+ maxLength: 4_096,
476
+ description: "Sandbox file path to publish.",
477
+ }), {
449
478
  minItems: 1,
450
479
  maxItems: 100,
480
+ description: "Files to copy from the temporary Sandbox into the persistent Workspace.",
451
481
  }),
452
482
  });
453
483
 
@@ -672,9 +672,6 @@ export function defineRuntimeAgent<
672
672
  ...(assembly.bindings?.workspace
673
673
  ? { workspace: assembly.bindings.workspace }
674
674
  : {}),
675
- ...(assembly.bindings?.codeExecution
676
- ? { codeExecution: assembly.bindings.codeExecution }
677
- : {}),
678
675
  },
679
676
  memoryProfile: {
680
677
  enabled: false,