@springbrand/agent-runtime 0.2.0-alpha.41 → 0.2.0-alpha.43
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/package.json +1 -1
- package/src/adapter/cloudflare/resources/r2-skill-source.ts +215 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +1 -18
- package/src/adapter/cloudflare/subagent/tools.ts +2 -5
- package/src/adapter/cloudflare/universal-agent/preparation.ts +21 -9
- package/src/adapter/cloudflare/universal-agent/tools.ts +3 -2
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +15 -0
- package/src/index.ts +3 -0
- package/src/kernel/bindings.ts +38 -6
- package/src/layers/context/budget/gate.ts +3 -3
- package/src/layers/orchestration/temporary-agent/workspace.ts +2 -0
- package/src/lib/prompt.ts +30 -14
- package/src/pi/assembly/snapshot.ts +7 -1
- package/src/pi/runtime-adapter/assembly.ts +8 -3
- package/src/pi/runtime-adapter/execution.ts +108 -13
- package/src/pi/runtime-adapter/models.ts +9 -6
- package/src/pi/runtime-adapter/openrouter-messages.ts +10 -3
- package/src/pi/tool/base.ts +42 -14
- package/src/pi/tool/compiler.ts +15 -2
- package/src/pi/tool/core-host.ts +228 -1
- package/src/pi/tool/core.ts +37 -5
- package/src/pi/tool/declared.ts +3 -0
- package/src/pi/tool/nested-tools.ts +5 -1
- package/src/pi/tool/schedule.ts +12 -10
- package/src/pi/tool/skill.ts +240 -86
- package/src/pi/tool/subagent.ts +2 -0
- package/src/pi/tool/time.ts +1 -1
- package/src/pi/tool/web-fetch.ts +1 -1
- package/src/pi/tool/web-search/web-search.ts +2 -1
- package/src/pi/tool/workspace-revision.ts +2 -1
- package/src/pi/tool/workspace-sandbox.ts +10 -21
- package/src/runtime-agent.ts +3 -0
- package/src/runtime-assembler.ts +105 -7
- package/src/runtime-definition.ts +1 -0
- package/src/runtime.ts +50 -0
package/src/pi/tool/skill.ts
CHANGED
|
@@ -6,12 +6,15 @@ import {
|
|
|
6
6
|
type SkillScriptRunner,
|
|
7
7
|
type SkillSource,
|
|
8
8
|
} from "agents/skills";
|
|
9
|
+
import { truncateResponse, truncateResult } from "@cloudflare/codemode";
|
|
9
10
|
import { tool } from "ai";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import type {
|
|
12
13
|
RuntimeSkillScriptPolicy,
|
|
13
14
|
WorkspacePort,
|
|
14
15
|
} from "../../kernel/bindings";
|
|
16
|
+
import { STORAGE_LEAF_MAX_CHARS } from "../../layers/context/budget/gate";
|
|
17
|
+
import { serializeOutput } from "../../lib/artifacts";
|
|
15
18
|
import { aiToolToPi } from "./ai-adapter";
|
|
16
19
|
import type { PiToolCandidate } from "./compiler";
|
|
17
20
|
import { piCandidatesToAiTools } from "./nested-tools";
|
|
@@ -46,6 +49,13 @@ const SKILL_RESOURCE_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
|
46
49
|
|
|
47
50
|
type LoadedSkill = NonNullable<Awaited<ReturnType<SkillSource["load"]>>>;
|
|
48
51
|
|
|
52
|
+
const SKILL_RESOURCE_READ_GUIDANCE =
|
|
53
|
+
"Bundled Skill resources are not Workspace files. " +
|
|
54
|
+
"Inside execute, use tools.read_skill_resource for text instead of state.* on /skills.";
|
|
55
|
+
const SKILL_RESOURCE_MATERIALIZE_GUIDANCE =
|
|
56
|
+
"Inside execute, use tools.materialize_skill_resource for Workspace assets when available.";
|
|
57
|
+
const SKILL_SCRIPT_OUTPUT_ROOT = "/scratch/skill-output";
|
|
58
|
+
|
|
49
59
|
/**
|
|
50
60
|
* 按体积裁掉超预算的 Skill 资源,并把裁掉的事实写回 Skill 正文。
|
|
51
61
|
*
|
|
@@ -93,7 +103,7 @@ function budgetSkillResources(skill: LoadedSkill): LoadedSkill {
|
|
|
93
103
|
if (portable.length > 0) {
|
|
94
104
|
lines.push(
|
|
95
105
|
`> If any of these resources are needed, copy all required ones into the Workspace ` +
|
|
96
|
-
`in one
|
|
106
|
+
`in one execute with tools.materialize_skill_resource: ` +
|
|
97
107
|
`${portable.map((entry) => entry.path).join(", ")}.`,
|
|
98
108
|
);
|
|
99
109
|
}
|
|
@@ -121,7 +131,21 @@ function catalogSkillSource(binding: PiSkillBinding): SkillSource {
|
|
|
121
131
|
}],
|
|
122
132
|
load: async (name) => {
|
|
123
133
|
const skill = await source.load(name);
|
|
124
|
-
|
|
134
|
+
if (!skill) return null;
|
|
135
|
+
const loaded = budgetSkillResources(skill);
|
|
136
|
+
const hasOversizedResource = skill.resources?.some((resource) =>
|
|
137
|
+
typeof resource.size === "number" &&
|
|
138
|
+
resource.size > SKILL_RESOURCE_BUDGET_BYTES
|
|
139
|
+
);
|
|
140
|
+
const guidance = hasOversizedResource
|
|
141
|
+
? SKILL_RESOURCE_READ_GUIDANCE
|
|
142
|
+
: `${SKILL_RESOURCE_READ_GUIDANCE} ${SKILL_RESOURCE_MATERIALIZE_GUIDANCE}`;
|
|
143
|
+
return loaded.resources?.length
|
|
144
|
+
? {
|
|
145
|
+
...loaded,
|
|
146
|
+
body: `${loaded.body}\n\n${guidance}`,
|
|
147
|
+
}
|
|
148
|
+
: loaded;
|
|
125
149
|
},
|
|
126
150
|
...(source.readResource
|
|
127
151
|
? { readResource: (name: string, path: string) =>
|
|
@@ -193,6 +217,141 @@ const SKILL_TOOL_PARAMETER_DESCRIPTIONS: Readonly<Record<string, Readonly<Record
|
|
|
193
217
|
const SKILL_ENTRY_READ_GUIDANCE =
|
|
194
218
|
"SKILL.md contains the Skill instructions; use activate_skill instead.";
|
|
195
219
|
|
|
220
|
+
function normalizeRunSkillScriptArguments(input: unknown): unknown {
|
|
221
|
+
if (input === null || typeof input !== "object") return input;
|
|
222
|
+
const value = input as Record<string, unknown>;
|
|
223
|
+
if (typeof value.input !== "string") return input;
|
|
224
|
+
try {
|
|
225
|
+
const decoded = JSON.parse(value.input);
|
|
226
|
+
return decoded !== null && typeof decoded === "object"
|
|
227
|
+
? { ...value, input: decoded }
|
|
228
|
+
: input;
|
|
229
|
+
} catch {
|
|
230
|
+
return input;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function result(details: unknown) {
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text" as const, text: serializeOutput(details).text }],
|
|
237
|
+
details,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resourceTarget(
|
|
242
|
+
bindings: readonly PiSkillBinding[],
|
|
243
|
+
input: unknown,
|
|
244
|
+
): { binding: PiSkillBinding; path: string } | null {
|
|
245
|
+
if (input === null || typeof input !== "object") return null;
|
|
246
|
+
const target = input as { name?: unknown; path?: unknown };
|
|
247
|
+
if (typeof target.path !== "string") return null;
|
|
248
|
+
if (typeof target.name === "string") {
|
|
249
|
+
const binding = bindings.find(({ name }) => name === target.name);
|
|
250
|
+
return binding ? { binding, path: target.path } : null;
|
|
251
|
+
}
|
|
252
|
+
const [name, ...rest] = target.path.split("/");
|
|
253
|
+
const binding = bindings.find((candidate) => candidate.name === name);
|
|
254
|
+
return binding && rest.length > 0
|
|
255
|
+
? { binding, path: rest.join("/") }
|
|
256
|
+
: null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function materializationRequired(name: string, path: string, bytes?: number) {
|
|
260
|
+
return result({
|
|
261
|
+
status: "materialization_required",
|
|
262
|
+
name,
|
|
263
|
+
path,
|
|
264
|
+
...(bytes === undefined ? {} : { bytes }),
|
|
265
|
+
next: "tools.materialize_skill_resource",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
type ScriptOutputFile = {
|
|
270
|
+
path?: unknown;
|
|
271
|
+
content?: unknown;
|
|
272
|
+
encoding?: unknown;
|
|
273
|
+
mimeType?: unknown;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
function scratchSegment(value: string): string {
|
|
277
|
+
const segment = value.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
278
|
+
return !segment || segment === "." || segment === ".." ? "_" : segment;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function outputRelativePath(value: unknown): string {
|
|
282
|
+
const path = String(value ?? "output.txt").replace(/^\/?output\//, "");
|
|
283
|
+
const parts = path.split("/");
|
|
284
|
+
if (parts.some((part) => !part || part === "." || part === "..")) {
|
|
285
|
+
throw new Error(`Invalid Skill output path: ${path}`);
|
|
286
|
+
}
|
|
287
|
+
return parts.join("/");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function projectSkillScriptResult(
|
|
291
|
+
details: unknown,
|
|
292
|
+
input: unknown,
|
|
293
|
+
toolCallId: string,
|
|
294
|
+
workspace: WorkspacePort | undefined,
|
|
295
|
+
signal: AbortSignal | undefined,
|
|
296
|
+
): Promise<unknown> {
|
|
297
|
+
const record = details !== null && typeof details === "object"
|
|
298
|
+
? details as Record<string, unknown>
|
|
299
|
+
: null;
|
|
300
|
+
const outputFiles = Array.isArray(record?.outputFiles)
|
|
301
|
+
? record.outputFiles as ScriptOutputFile[]
|
|
302
|
+
: [];
|
|
303
|
+
const target = input as { name?: unknown };
|
|
304
|
+
const outputs: Array<{ path: string; bytes: number; mediaType?: string }> = [];
|
|
305
|
+
if (outputFiles.length > 0 && !workspace) {
|
|
306
|
+
throw new Error("Skill output files require Workspace access");
|
|
307
|
+
}
|
|
308
|
+
for (const file of outputFiles) {
|
|
309
|
+
signal?.throwIfAborted();
|
|
310
|
+
const relative = outputRelativePath(file.path);
|
|
311
|
+
const destination = `${SKILL_SCRIPT_OUTPUT_ROOT}/${scratchSegment(String(target.name ?? "skill"))}/${scratchSegment(toolCallId)}/${relative}`;
|
|
312
|
+
const content = String(file.content ?? "");
|
|
313
|
+
const binary = file.encoding === "base64";
|
|
314
|
+
const mediaType = typeof file.mimeType === "string"
|
|
315
|
+
? file.mimeType
|
|
316
|
+
: binary ? "application/octet-stream" : "text/plain";
|
|
317
|
+
let bytes = new Blob([content]).size;
|
|
318
|
+
await serializeWorkspaceMutation(workspace!, destination, async () => {
|
|
319
|
+
const parent = destination.replace(/\/[^/]+$/, "");
|
|
320
|
+
await workspace!.mkdir(parent, { recursive: true });
|
|
321
|
+
if (!binary) {
|
|
322
|
+
await workspace!.writeFile(destination, content, mediaType);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const decoded = resourceBytes({
|
|
326
|
+
path: relative,
|
|
327
|
+
kind: "file",
|
|
328
|
+
encoding: "base64",
|
|
329
|
+
content,
|
|
330
|
+
});
|
|
331
|
+
bytes = decoded.byteLength;
|
|
332
|
+
await workspace!.writeFileBytes(destination, decoded, mediaType);
|
|
333
|
+
});
|
|
334
|
+
outputs.push({ path: destination, bytes, mediaType });
|
|
335
|
+
}
|
|
336
|
+
const scriptResult = record && "result" in record
|
|
337
|
+
? record.result
|
|
338
|
+
: record && ("outputFiles" in record || "logs" in record)
|
|
339
|
+
? Object.fromEntries(Object.entries(record).filter(([key]) =>
|
|
340
|
+
key !== "outputFiles" && key !== "logs"
|
|
341
|
+
))
|
|
342
|
+
: details;
|
|
343
|
+
const logs = Array.isArray(record?.logs)
|
|
344
|
+
? record.logs.map(String)
|
|
345
|
+
: [];
|
|
346
|
+
return {
|
|
347
|
+
result: truncateResult(scriptResult),
|
|
348
|
+
...(logs.length > 0
|
|
349
|
+
? { logs: [truncateResponse(logs.join("\n"))] }
|
|
350
|
+
: {}),
|
|
351
|
+
...(outputs.length > 0 ? { outputs } : {}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
196
355
|
function resourceBytes(resource: SkillResource): Uint8Array {
|
|
197
356
|
if ((resource.encoding ?? "text") === "text") {
|
|
198
357
|
return new TextEncoder().encode(resource.content);
|
|
@@ -211,95 +370,44 @@ function materializeSkillResourceTool(
|
|
|
211
370
|
) {
|
|
212
371
|
const names = bindings.map(({ name }) => name) as [string, ...string[]];
|
|
213
372
|
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
|
-
});
|
|
221
373
|
return tool({
|
|
222
374
|
description:
|
|
223
|
-
"Copy complete bundled Skill
|
|
375
|
+
"Copy a complete bundled Skill resource directly into the Workspace without returning its contents to the model. " +
|
|
224
376
|
"Use this instead of read_skill_resource when a template, script, image, font, or other asset must become a Workspace file. " +
|
|
225
|
-
"
|
|
377
|
+
"When multiple resources are needed, copy them sequentially in one execute; never start one execute per resource. " +
|
|
226
378
|
"The destination is created or overwritten, including parent directories.",
|
|
227
379
|
inputSchema: z.object({
|
|
228
|
-
|
|
380
|
+
name: z.enum(names).describe("Activated Skill name"),
|
|
381
|
+
path: z.string().min(1).describe("Bundled resource path listed by activate_skill"),
|
|
382
|
+
destination: z.string().min(1).describe("Absolute destination path in the Workspace"),
|
|
229
383
|
}),
|
|
230
|
-
execute: async ({
|
|
384
|
+
execute: async ({ name, path, destination }, { abortSignal }) => {
|
|
231
385
|
abortSignal?.throwIfAborted();
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
throw new Error(`Duplicate materialize destination: ${destination}`);
|
|
236
|
-
}
|
|
237
|
-
destinations.add(destination);
|
|
386
|
+
const source = byName.get(name)?.source;
|
|
387
|
+
if (!source?.readResource) {
|
|
388
|
+
throw new Error(`Skill \"${name}\" has no readable resources.`);
|
|
238
389
|
}
|
|
390
|
+
const resource = await source.readResource(name, path);
|
|
391
|
+
if (!resource) throw new Error(`Resource not found: ${name}/${path}`);
|
|
392
|
+
abortSignal?.throwIfAborted();
|
|
393
|
+
const bytes = resourceBytes(resource);
|
|
239
394
|
|
|
240
|
-
|
|
241
|
-
for (const { name, path, destination } of resources) {
|
|
395
|
+
await serializeWorkspaceMutation(workspace, destination, async () => {
|
|
242
396
|
abortSignal?.throwIfAborted();
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
status: "error",
|
|
247
|
-
name,
|
|
248
|
-
path,
|
|
249
|
-
destination,
|
|
250
|
-
error: `Skill \"${name}\" has no readable resources.`,
|
|
251
|
-
});
|
|
252
|
-
continue;
|
|
397
|
+
const parent = destination.replace(/\/[^/]+$/, "");
|
|
398
|
+
if (parent && parent !== "/") {
|
|
399
|
+
await workspace.mkdir(parent, { recursive: true });
|
|
253
400
|
}
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
401
|
+
await workspace.writeFileBytes(destination, bytes, resource.mimeType);
|
|
402
|
+
});
|
|
298
403
|
|
|
299
404
|
return {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
405
|
+
name,
|
|
406
|
+
path: resource.path,
|
|
407
|
+
destination,
|
|
408
|
+
bytesWritten: bytes.byteLength,
|
|
409
|
+
encoding: resource.encoding ?? "text",
|
|
410
|
+
...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
|
|
303
411
|
};
|
|
304
412
|
},
|
|
305
413
|
});
|
|
@@ -349,7 +457,7 @@ export async function skillPiToolCandidates(
|
|
|
349
457
|
}
|
|
350
458
|
if (name === "read_skill_resource") {
|
|
351
459
|
const execute = adapted.execute;
|
|
352
|
-
adapted.execute = (toolCallId, input, signal) => {
|
|
460
|
+
adapted.execute = async (toolCallId, input, signal) => {
|
|
353
461
|
const target = input as { name?: unknown; path?: unknown };
|
|
354
462
|
const parts = typeof target.path === "string"
|
|
355
463
|
? target.path.split("/")
|
|
@@ -360,26 +468,72 @@ export async function skillPiToolCandidates(
|
|
|
360
468
|
parts.length === 2 &&
|
|
361
469
|
parts[1] === "SKILL.md")
|
|
362
470
|
) {
|
|
363
|
-
return
|
|
471
|
+
return {
|
|
364
472
|
content: [{ type: "text", text: SKILL_ENTRY_READ_GUIDANCE }],
|
|
365
473
|
details: SKILL_ENTRY_READ_GUIDANCE,
|
|
366
|
-
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const resolved = resourceTarget(bindings, input);
|
|
477
|
+
if (resolved) {
|
|
478
|
+
const skill = await resolved.binding.source
|
|
479
|
+
.load(resolved.binding.name)
|
|
480
|
+
.catch(() => null);
|
|
481
|
+
const descriptor = skill?.resources?.find(({ path }) =>
|
|
482
|
+
path === resolved.path
|
|
483
|
+
);
|
|
484
|
+
if (
|
|
485
|
+
typeof descriptor?.size === "number" &&
|
|
486
|
+
descriptor.size > STORAGE_LEAF_MAX_CHARS
|
|
487
|
+
) {
|
|
488
|
+
return materializationRequired(
|
|
489
|
+
resolved.binding.name,
|
|
490
|
+
resolved.path,
|
|
491
|
+
descriptor.size,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const output = await execute(toolCallId, input, signal);
|
|
496
|
+
if (
|
|
497
|
+
resolved &&
|
|
498
|
+
typeof output.details === "string" &&
|
|
499
|
+
output.details.length > STORAGE_LEAF_MAX_CHARS
|
|
500
|
+
) {
|
|
501
|
+
return materializationRequired(
|
|
502
|
+
resolved.binding.name,
|
|
503
|
+
resolved.path,
|
|
504
|
+
);
|
|
367
505
|
}
|
|
368
|
-
return
|
|
506
|
+
return output;
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (name === "run_skill_script") {
|
|
510
|
+
adapted.description +=
|
|
511
|
+
" Large artifacts must be written directly to Workspace, not returned or logged; scratch outputs are materialized to Workspace and returned only as file references.";
|
|
512
|
+
adapted.prepareArguments = normalizeRunSkillScriptArguments;
|
|
513
|
+
const execute = adapted.execute;
|
|
514
|
+
adapted.execute = async (toolCallId, input, signal) => {
|
|
515
|
+
const output = await execute(toolCallId, input, signal);
|
|
516
|
+
return result(await projectSkillScriptResult(
|
|
517
|
+
output.details,
|
|
518
|
+
input,
|
|
519
|
+
toolCallId,
|
|
520
|
+
options.workspace,
|
|
521
|
+
signal,
|
|
522
|
+
));
|
|
369
523
|
};
|
|
370
524
|
}
|
|
371
525
|
return {
|
|
372
526
|
owner: "runtime-skill",
|
|
373
|
-
requiredExecutionLevel:
|
|
374
|
-
name === "run_skill_script" || name === "materialize_skill_resource"
|
|
375
|
-
? "high"
|
|
376
|
-
: "safe",
|
|
527
|
+
requiredExecutionLevel: "safe",
|
|
377
528
|
// activate_skill 返回的是 Skill 指令本身,是模型接下来所有动作的依据。把它外置
|
|
378
529
|
// 成文件,模型手里就只剩一个路径,必须再取一次才能知道该做什么 —— 而典型
|
|
379
530
|
// SKILL.md 正好落在会触发外置的区间。指令必须当场到手。
|
|
380
531
|
...(name === "activate_skill"
|
|
381
532
|
? { outputBudget: { kind: "structure" as const } }
|
|
382
533
|
: {}),
|
|
534
|
+
...(name === "activate_skill"
|
|
535
|
+
? { exposureMode: "direct" as const }
|
|
536
|
+
: { exposureMode: "codemode" as const }),
|
|
383
537
|
tool: adapted,
|
|
384
538
|
};
|
|
385
539
|
});
|
package/src/pi/tool/subagent.ts
CHANGED
|
@@ -180,6 +180,7 @@ export function subagentPiToolCandidates(
|
|
|
180
180
|
};
|
|
181
181
|
return {
|
|
182
182
|
owner: `subagent:${type.name}`,
|
|
183
|
+
exposureMode: "direct",
|
|
183
184
|
requiredExecutionLevel: "safe",
|
|
184
185
|
tool,
|
|
185
186
|
};
|
|
@@ -262,6 +263,7 @@ export function subagentPiToolCandidates(
|
|
|
262
263
|
};
|
|
263
264
|
candidates.push({
|
|
264
265
|
owner: "subagent:background",
|
|
266
|
+
exposureMode: "direct",
|
|
265
267
|
requiredExecutionLevel: "low",
|
|
266
268
|
summary: "Dispatch a background sub-agent",
|
|
267
269
|
source: "action",
|
package/src/pi/tool/time.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { PiToolCandidate } from "./compiler";
|
|
|
4
4
|
|
|
5
5
|
export const GET_TIME_TOOL_NAME = "get_time";
|
|
6
6
|
|
|
7
|
-
const parameters = Type.Object({});
|
|
7
|
+
const parameters = Type.Object({}, { additionalProperties: false });
|
|
8
8
|
|
|
9
9
|
/** Product-neutral current-time Tool for Host registries. */
|
|
10
10
|
export function getTimePiToolCandidate(owner = "utilities"): PiToolCandidate {
|
package/src/pi/tool/web-fetch.ts
CHANGED
|
@@ -18,7 +18,7 @@ export const webSearchParameters = Type.Object({
|
|
|
18
18
|
description: "Additional URLs to analyze along with search (up to 20)",
|
|
19
19
|
maxItems: 20,
|
|
20
20
|
})),
|
|
21
|
-
});
|
|
21
|
+
}, { additionalProperties: false });
|
|
22
22
|
|
|
23
23
|
export interface WebSearchDetails extends Omit<WebSearchResult, "text"> {
|
|
24
24
|
resultCount: number;
|
|
@@ -119,6 +119,7 @@ export function webSearchPiToolCandidate(
|
|
|
119
119
|
};
|
|
120
120
|
return {
|
|
121
121
|
owner: "runtime-base",
|
|
122
|
+
exposureMode: "direct",
|
|
122
123
|
requiredExecutionLevel: "safe",
|
|
123
124
|
source: "action",
|
|
124
125
|
tool,
|
|
@@ -14,7 +14,7 @@ const parameters = Type.Object({
|
|
|
14
14
|
], {
|
|
15
15
|
description: "Use previous for the revision immediately before the current one.",
|
|
16
16
|
}),
|
|
17
|
-
});
|
|
17
|
+
}, { additionalProperties: false });
|
|
18
18
|
|
|
19
19
|
function result<T>(details: T): AgentToolResult<T> {
|
|
20
20
|
return {
|
|
@@ -43,6 +43,7 @@ export function workspaceRevisionPiToolCandidate(
|
|
|
43
43
|
};
|
|
44
44
|
return {
|
|
45
45
|
owner: "workspace",
|
|
46
|
+
exposureMode: "direct",
|
|
46
47
|
tool,
|
|
47
48
|
requiredExecutionLevel: "safe",
|
|
48
49
|
alwaysRequiresApproval: true,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createBashTool,
|
|
3
2
|
createDeleteTool,
|
|
4
3
|
createEditTool,
|
|
5
4
|
createFindTool,
|
|
@@ -68,7 +67,7 @@ const workspaceReadParameters = Type.Object({
|
|
|
68
67
|
minimum: 1,
|
|
69
68
|
description: "Maximum lines to return; a page is capped by size regardless",
|
|
70
69
|
})),
|
|
71
|
-
});
|
|
70
|
+
}, { additionalProperties: false });
|
|
72
71
|
const workspaceEditParameters = Type.Object({
|
|
73
72
|
path: Type.String({
|
|
74
73
|
minLength: 1,
|
|
@@ -80,7 +79,7 @@ const workspaceEditParameters = Type.Object({
|
|
|
80
79
|
"Exact existing text to replace. Include enough surrounding context to match one location.",
|
|
81
80
|
}),
|
|
82
81
|
new_string: Type.String({ description: "Replacement text." }),
|
|
83
|
-
});
|
|
82
|
+
}, { additionalProperties: false });
|
|
84
83
|
|
|
85
84
|
/**
|
|
86
85
|
* 单页 read 返回的字符上限。
|
|
@@ -176,7 +175,7 @@ function pageReadResult(
|
|
|
176
175
|
const span = `lines ${fromLine}-${toLine} of ${totalLines ?? toLine}`;
|
|
177
176
|
const lossyNote = lossy
|
|
178
177
|
? " Some lines were longer than 2000 chars and are cut short; " +
|
|
179
|
-
"paging cannot recover them — use grep
|
|
178
|
+
"paging cannot recover them — use grep on this path instead."
|
|
180
179
|
: "";
|
|
181
180
|
const marker = fromLine === 1 && eof && !lossy
|
|
182
181
|
? ""
|
|
@@ -399,12 +398,6 @@ export function workspacePiToolCandidates(
|
|
|
399
398
|
},
|
|
400
399
|
};
|
|
401
400
|
|
|
402
|
-
const bash = aiToolToPi(
|
|
403
|
-
"bash",
|
|
404
|
-
createBashTool({ ops: workspace }),
|
|
405
|
-
{ label: "Workspace Bash" },
|
|
406
|
-
);
|
|
407
|
-
|
|
408
401
|
return [
|
|
409
402
|
{
|
|
410
403
|
owner: "workspace",
|
|
@@ -420,13 +413,6 @@ export function workspacePiToolCandidates(
|
|
|
420
413
|
requiredExecutionLevel: "safe" as const,
|
|
421
414
|
tool,
|
|
422
415
|
})),
|
|
423
|
-
{
|
|
424
|
-
owner: "workspace",
|
|
425
|
-
requiredExecutionLevel: "high" as const,
|
|
426
|
-
summary: "Run a Bash script over Workspace files",
|
|
427
|
-
source: "action" as const,
|
|
428
|
-
tool: bash,
|
|
429
|
-
},
|
|
430
416
|
];
|
|
431
417
|
}
|
|
432
418
|
|
|
@@ -460,15 +446,18 @@ const sandboxExecParameters = Type.Object({
|
|
|
460
446
|
description: "Maximum command runtime in milliseconds, from 1 to 60000.",
|
|
461
447
|
}),
|
|
462
448
|
),
|
|
463
|
-
});
|
|
464
|
-
const sandboxStartParameters = Type.Object(
|
|
449
|
+
}, { additionalProperties: false });
|
|
450
|
+
const sandboxStartParameters = Type.Object(
|
|
451
|
+
sandboxCommandParameters,
|
|
452
|
+
{ additionalProperties: false },
|
|
453
|
+
);
|
|
465
454
|
const sandboxProcessParameters = Type.Object({
|
|
466
455
|
id: Type.String({
|
|
467
456
|
minLength: 1,
|
|
468
457
|
maxLength: 128,
|
|
469
458
|
description: "Process ID returned by sandbox_start_process.",
|
|
470
459
|
}),
|
|
471
|
-
});
|
|
460
|
+
}, { additionalProperties: false });
|
|
472
461
|
const sandboxPublishParameters = Type.Object({
|
|
473
462
|
paths: Type.Array(Type.String({
|
|
474
463
|
minLength: 1,
|
|
@@ -479,7 +468,7 @@ const sandboxPublishParameters = Type.Object({
|
|
|
479
468
|
maxItems: 100,
|
|
480
469
|
description: "Files to copy from the temporary Sandbox into the persistent Workspace.",
|
|
481
470
|
}),
|
|
482
|
-
});
|
|
471
|
+
}, { additionalProperties: false });
|
|
483
472
|
|
|
484
473
|
/**
|
|
485
474
|
* 为现有 RuntimeSandboxPort 创建 Pi 命令、进程和文件发布工具。
|
package/src/runtime-agent.ts
CHANGED
|
@@ -672,6 +672,9 @@ 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
|
+
: {}),
|
|
675
678
|
},
|
|
676
679
|
memoryProfile: {
|
|
677
680
|
enabled: false,
|