@springbrand/agent-runtime 0.2.0-alpha.42 → 0.2.0-alpha.44

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.
@@ -51,7 +51,7 @@ export const webFetchParameters = Type.Object({
51
51
  "默认 false(保留位,当前实现总是返回原始内容)。",
52
52
  }),
53
53
  ),
54
- });
54
+ }, { additionalProperties: false });
55
55
 
56
56
  /** webFetch 工具调用的结构化 details,随 AgentToolResult 一起持久化。 */
57
57
  export interface WebFetchDetails {
@@ -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 or bash on this path instead."
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(sandboxCommandParameters);
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 命令、进程和文件发布工具。
@@ -35,7 +35,10 @@ import {
35
35
  type PiRuntimeAssembly,
36
36
  } from "./pi/assembly/snapshot";
37
37
  import type { AgentTool } from "@earendil-works/pi-agent-core";
38
- import type { PiToolCandidate } from "./pi/tool/compiler";
38
+ import type {
39
+ PiToolCandidate,
40
+ ToolExposureMode,
41
+ } from "./pi/tool/compiler";
39
42
  import type {
40
43
  AssembleRuntimeSnapshotInput,
41
44
  RuntimeSettings,
@@ -178,6 +181,10 @@ interface ToolSurface {
178
181
  readonly degradations: readonly RuntimeDegradation[];
179
182
  }
180
183
 
184
+ function exposureOf(candidate: PiToolCandidate): ToolExposureMode {
185
+ return candidate.exposureMode ?? "both";
186
+ }
187
+
181
188
  // 唯一 Tool Surface seam:只读取已配置 Port、Resource 和 Agent policy,
182
189
  // 统一生成可见 Tool,并在一处做名称、执行档位与冲突校验。
183
190
  async function createToolSurface(
@@ -203,14 +210,14 @@ async function createToolSurface(
203
210
  });
204
211
  // 平台没有浏览器能力时注册空集:宁可没有这个 Tool,也不注册一个必然失败的 Tool 误导模型。
205
212
  // `create()` 推迟到确认这个名字真的可见之后,被 deny 的装配不白建一个浏览器连接器。
206
- // `direct` 使它只走 Direct 调用,不再被并进 `execute` 的工具集——两个 Code Mode 类工具互相嵌套
213
+ // `direct` exposure 使它只走 Direct 调用,不再被并进 `execute` 的工具集——两个 Code Mode 类工具互相嵌套
207
214
  // 会让「哪次执行被记录、被重放」变得无法解释,而它们的分工本就由 System Prompt 划清。
208
215
  const browserVisible = !deny.has(BROWSER_EXECUTE_TOOL_NAME) &&
209
216
  allowsTool?.(BROWSER_EXECUTE_TOOL_NAME) !== false;
210
217
  const browserCandidates = input.platform.browser && browserVisible
211
218
  ? [{
212
219
  ...browserExecutionPiToolCandidate(input.platform.browser.create()),
213
- direct: true as const,
220
+ exposureMode: "direct" as const,
214
221
  }]
215
222
  : [];
216
223
  const baseCandidates = basePiToolCandidates(input.webSearch);
@@ -256,7 +263,7 @@ async function createToolSurface(
256
263
 
257
264
  const finalized = [...tools.values()];
258
265
  const directVisible = finalized.filter(
259
- (candidate) => !candidate.codeExecutionOnly,
266
+ (candidate) => exposureOf(candidate) !== "codemode",
260
267
  );
261
268
  if (tools.has("execute")) {
262
269
  throw new Error("SpringBrand reserved Runtime Tool name: execute");
@@ -273,7 +280,7 @@ async function createToolSurface(
273
280
  }
274
281
  const mergeable = finalized.filter(
275
282
  (candidate) =>
276
- !candidate.direct &&
283
+ exposureOf(candidate) !== "direct" &&
277
284
  !candidate.interaction &&
278
285
  typeof candidate.tool.execute === "function",
279
286
  );
@@ -316,7 +323,7 @@ async function createToolSurface(
316
323
  ? "Skill resource copying is available only inside execute through " +
317
324
  "`tools.materialize_skill_resource({ name, path, destination })`; " +
318
325
  "no Direct Tool is registered for it. " +
319
- "Use a loop or Promise.all when copying multiple resources.\n\n"
326
+ "Copy multiple resources sequentially in one execute so durable replay preserves call order.\n\n"
320
327
  : "";
321
328
  return Object.freeze({
322
329
  candidates: Object.freeze([
package/src/runtime.ts CHANGED
@@ -48,6 +48,7 @@ import type {
48
48
  } from "./kernel/extensions";
49
49
  import type {
50
50
  RuntimeActivity,
51
+ RuntimeActivityProjection,
51
52
  RuntimeState,
52
53
  RuntimeTurnState,
53
54
  } from "./kernel/state";
@@ -153,6 +154,7 @@ const CHAT_RECOVERY_TERMINAL_MESSAGE =
153
154
 
154
155
  type RuntimeEventOutboxPayload =
155
156
  | { readonly type: "model-usage"; readonly event: RuntimeModelUsageEvent }
157
+ | { readonly type: "activity"; readonly projection: RuntimeActivityProjection }
156
158
  | {
157
159
  readonly type: "tool-settlement";
158
160
  readonly event: RuntimeToolSettlementEvent;
@@ -899,6 +901,7 @@ export abstract class AgentRuntimeKernel<
899
901
  return this.pi.createTurn({
900
902
  prepared: this.preparedPi(),
901
903
  pinnedDescriptor: submission.assemblyDescriptor,
904
+ modelSessionId: this.name,
902
905
  submission: {
903
906
  id: submission.submissionId,
904
907
  requestId: submission.requestId,
@@ -996,6 +999,7 @@ export abstract class AgentRuntimeKernel<
996
999
  ...(event.error ? { error: errorText(event.error) } : {}),
997
1000
  occurredAt: event.occurredAt,
998
1001
  });
1002
+ this.projectNestedToolSettlement(submission.submissionId, event);
999
1003
  },
1000
1004
  ...(toolExecutors && Object.keys(toolExecutors).length > 0
1001
1005
  ? { toolExecutors }
@@ -1103,12 +1107,12 @@ export abstract class AgentRuntimeKernel<
1103
1107
  async onStart(): Promise<void> {
1104
1108
  this.runtimeLoad.reset();
1105
1109
  this.telemetry.recover(() => this.ensureRuntimeReady());
1110
+ await this.broadcastApprovals();
1106
1111
  if (this.db.runtimeEvents.hasPending()) {
1107
1112
  this.ctx.waitUntil(
1108
1113
  this.ensureRuntimeReady().then(() => this.drainRuntimeEvents()),
1109
1114
  );
1110
1115
  }
1111
- await this.broadcastApprovals();
1112
1116
  await this.approvals.dispatchPendingContinuations();
1113
1117
  const running = this.db.submissions.findRunning() as StoredSubmission | null;
1114
1118
  if (running) {
@@ -1132,6 +1136,13 @@ export abstract class AgentRuntimeKernel<
1132
1136
  const payload = JSON.parse(row.body) as RuntimeEventOutboxPayload;
1133
1137
  if (payload.type === "model-usage") {
1134
1138
  await turnEvents.onModelUsage?.(payload.event);
1139
+ } else if (payload.type === "activity") {
1140
+ if (
1141
+ !this.state.activity ||
1142
+ payload.projection.revision >= this.state.activity.revision
1143
+ ) {
1144
+ await turnEvents.onActivityChanged?.(payload.projection);
1145
+ }
1135
1146
  } else if (payload.type === "tool-settlement") {
1136
1147
  await turnEvents.onToolSettled?.(payload.event);
1137
1148
  } else if (payload.type === "subagent-usage") {
@@ -2200,6 +2211,32 @@ export abstract class AgentRuntimeKernel<
2200
2211
  return this.drainRuntimeEvents();
2201
2212
  }
2202
2213
 
2214
+ private projectNestedToolSettlement(
2215
+ submissionId: string,
2216
+ event: Readonly<{
2217
+ toolCallId: string;
2218
+ toolName: string;
2219
+ outcome: "completed" | "failed" | "cancelled";
2220
+ occurredAt: number;
2221
+ }>,
2222
+ ): void {
2223
+ const settlement: RuntimeToolSettlementEvent = {
2224
+ eventId: `${submissionId}:tool:${event.toolCallId}`,
2225
+ submissionId,
2226
+ toolCallId: event.toolCallId,
2227
+ toolName: event.toolName,
2228
+ status: event.outcome === "completed" ? "success" : "error",
2229
+ };
2230
+ this.db.transaction(() => {
2231
+ this.db.runtimeEvents.insert({
2232
+ eventId: settlement.eventId,
2233
+ body: json({ type: "tool-settlement", event: settlement }),
2234
+ createdAt: event.occurredAt,
2235
+ });
2236
+ });
2237
+ this.ctx.waitUntil(this.drainRuntimeEvents());
2238
+ }
2239
+
2203
2240
  // 终态事务用这个同步内核先补齐 ToolResult,再在同一事务末尾写 marker。
2204
2241
  private settleToolSync(
2205
2242
  submissionId: string,
@@ -4570,6 +4607,13 @@ export abstract class AgentRuntimeKernel<
4570
4607
  backgroundWork,
4571
4608
  revision: (currentActivity?.revision ?? 0) + 1,
4572
4609
  };
4610
+ this.db.transaction(() => {
4611
+ this.db.runtimeEvents.insert({
4612
+ eventId: `session-activity:${nextActivity.revision}`,
4613
+ body: json({ type: "activity", projection: nextActivity }),
4614
+ createdAt: Date.now(),
4615
+ });
4616
+ });
4573
4617
  if (
4574
4618
  this.state?.approvals === undefined ||
4575
4619
  json(this.state.approvals) !== json(approvals) ||
@@ -4583,14 +4627,9 @@ export abstract class AgentRuntimeKernel<
4583
4627
  turn,
4584
4628
  });
4585
4629
  }
4586
- const projection = this.turnEventsPort()?.onActivityChanged?.(nextActivity);
4587
- if (projection) {
4588
- this.ctx.waitUntil(
4589
- projection.catch(() => undefined),
4590
- );
4591
- }
4630
+ this.ctx.waitUntil(this.drainRuntimeEvents());
4592
4631
  } catch {
4593
- // Approval projection is best effort and must not block execution.
4632
+ // Rebuilding UI state is best effort; queued activity delivery retries independently.
4594
4633
  }
4595
4634
  }
4596
4635