@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10

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.
Files changed (172) hide show
  1. package/package.json +7 -7
  2. package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
  3. package/src/adapter/cloudflare/agent.ts +1682 -0
  4. package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
  5. package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
  6. package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
  7. package/src/adapter/cloudflare/index.ts +18 -0
  8. package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
  9. package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
  10. package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
  11. package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
  12. package/src/{db → adapter/cloudflare}/schema.ts +28 -80
  13. package/src/adapter/cloudflare/subagent/definition.ts +63 -33
  14. package/src/adapter/cloudflare/subagent/runner.ts +174 -120
  15. package/src/adapter/cloudflare/subagent/tools.ts +13 -14
  16. package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
  17. package/src/adapter/cloudflare/universal-agent/tools.ts +10 -12
  18. package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
  19. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
  20. package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
  21. package/src/config/activation.ts +140 -0
  22. package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
  23. package/src/{runtime-assembler.ts → config/assembler.ts} +45 -46
  24. package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
  25. package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
  26. package/src/{runtime-definition.ts → config/definition.ts} +13 -13
  27. package/src/config/harness-runtime.ts +190 -0
  28. package/src/config/index.ts +14 -0
  29. package/src/config/model-guidance.ts +21 -0
  30. package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
  31. package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
  32. package/src/{kernel → config}/profile.ts +7 -0
  33. package/src/{kernel → config}/public-contracts.ts +6 -6
  34. package/src/{kernel → config}/runtime-load.ts +1 -1
  35. package/src/{pi/assembly/context.ts → context/assembly.ts} +5 -207
  36. package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
  37. package/src/context/extension/index.ts +3 -0
  38. package/src/context/index.ts +6 -0
  39. package/src/{layers/context → context/memory}/budget/gate.ts +21 -5
  40. package/src/conversation/harness-resume.ts +288 -0
  41. package/src/conversation/harness-stream.ts +99 -0
  42. package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
  43. package/src/conversation/index.ts +6 -0
  44. package/src/{pi → conversation}/message/contract.ts +3 -0
  45. package/src/{pi → conversation}/message/conversion.ts +75 -0
  46. package/src/{pi → conversation}/message/index.ts +1 -0
  47. package/src/{pi → conversation}/message/model-input.ts +1 -1
  48. package/src/{pi → conversation}/message/projection.ts +113 -30
  49. package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
  50. package/src/conversation/state.ts +196 -0
  51. package/src/harness/index.ts +7 -0
  52. package/src/harness/session-harness.ts +562 -0
  53. package/src/harness/submission-operation.ts +49 -0
  54. package/src/index.ts +38 -43
  55. package/src/lib/artifacts.ts +18 -0
  56. package/src/lib/execution-level.ts +13 -0
  57. package/src/lib/mcp.ts +1 -1
  58. package/src/lib/prompt.ts +7 -6
  59. package/src/lib/token-estimate.ts +8 -0
  60. package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
  61. package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
  62. package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
  63. package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
  64. package/src/session/index.ts +39 -0
  65. package/src/skills/springbrand-worker-website/index.ts +1 -1
  66. package/src/submission/admission.ts +286 -0
  67. package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
  68. package/src/submission/delivery.ts +212 -0
  69. package/src/submission/finalization.ts +111 -0
  70. package/src/submission/index.ts +9 -0
  71. package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
  72. package/src/{db/submission.repo.ts → submission/repository.ts} +117 -114
  73. package/src/submission/steer-delivery.ts +167 -0
  74. package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
  75. package/src/submission/unattended.ts +40 -0
  76. package/src/telemetry/contract.ts +12 -0
  77. package/src/telemetry/coordinator.ts +65 -0
  78. package/src/telemetry/delivery.ts +2 -2
  79. package/src/telemetry/index.ts +5 -0
  80. package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
  81. package/src/telemetry/recorder.ts +1 -1
  82. package/src/telemetry/runtime-telemetry.ts +12 -0
  83. package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
  84. package/src/telemetry/turn-events.ts +279 -0
  85. package/src/tools/README.md +44 -0
  86. package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
  87. package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
  88. package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
  89. package/src/tools/assistant/ask-user.ts +317 -0
  90. package/src/tools/assistant/candidate.ts +15 -0
  91. package/src/tools/assistant/followups.ts +34 -0
  92. package/src/tools/assistant/index.ts +19 -0
  93. package/src/tools/assistant/memory.ts +73 -0
  94. package/src/tools/assistant/plan.ts +98 -0
  95. package/src/{pi/tool → tools/assistant}/time.ts +1 -1
  96. package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
  97. package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
  98. package/src/tools/codemode/workspace-connector.ts +198 -0
  99. package/src/tools/extension/index.ts +42 -0
  100. package/src/tools/index.ts +32 -0
  101. package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
  102. package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
  103. package/src/tools/runtime/approval/index.ts +1 -0
  104. package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
  105. package/src/tools/runtime/compiler.ts +264 -0
  106. package/src/tools/runtime/contracts.ts +117 -0
  107. package/src/tools/runtime/decision-gate.ts +337 -0
  108. package/src/tools/runtime/human-decisions.ts +127 -0
  109. package/src/tools/runtime/interaction/index.ts +1 -0
  110. package/src/{db/interaction.repo.ts → tools/runtime/interaction/repository.ts} +1 -7
  111. package/src/tools/runtime/result.ts +22 -0
  112. package/src/tools/runtime/unattended-error.ts +4 -0
  113. package/src/tools/sandbox/index.ts +228 -0
  114. package/src/tools/schedule/index.ts +301 -0
  115. package/src/tools/skill/contracts.ts +20 -0
  116. package/src/tools/skill/index.ts +99 -0
  117. package/src/tools/skill/resources.ts +322 -0
  118. package/src/tools/skill/scripts.ts +168 -0
  119. package/src/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
  120. package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
  121. package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
  122. package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
  123. package/src/tools/subagent/temporary/approvals.ts +123 -0
  124. package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
  125. package/src/tools/subagent/temporary/core.ts +53 -0
  126. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
  127. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
  128. package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
  129. package/src/tools/web/index.ts +2 -0
  130. package/src/tools/web/search/api.ts +1320 -0
  131. package/src/tools/web/search/index.ts +2 -0
  132. package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
  133. package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
  134. package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
  135. package/src/db/milestone.repo.ts +0 -104
  136. package/src/db/settlement.repo.ts +0 -104
  137. package/src/kernel/approval-lifecycle.ts +0 -638
  138. package/src/kernel/interaction-lifecycle.ts +0 -433
  139. package/src/kernel/recoverable-chat-agent.ts +0 -1020
  140. package/src/kernel/state.ts +0 -91
  141. package/src/pi/assembly/index.ts +0 -19
  142. package/src/pi/runtime-adapter/execution.ts +0 -1163
  143. package/src/pi/runtime-adapter/index.ts +0 -293
  144. package/src/pi/runtime-adapter/recovery.ts +0 -982
  145. package/src/pi/session/index.ts +0 -24
  146. package/src/pi/tool/base.ts +0 -550
  147. package/src/pi/tool/compiler.ts +0 -483
  148. package/src/pi/tool/index.ts +0 -38
  149. package/src/pi/tool/schedule.ts +0 -261
  150. package/src/pi/tool/skill.ts +0 -565
  151. package/src/pi/tool/web-search/api.ts +0 -1318
  152. package/src/pi/tool/web-search/index.ts +0 -2
  153. package/src/pi/tool/workspace-state-connector.ts +0 -146
  154. package/src/pi/turn/approval.ts +0 -181
  155. package/src/pi/turn/index.ts +0 -82
  156. package/src/pi/turn/interaction.ts +0 -181
  157. package/src/runtime.ts +0 -4760
  158. package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
  159. package/src/{kernel → config}/degradation.ts +0 -0
  160. package/src/{kernel → config}/runtime-config.ts +0 -0
  161. package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
  162. package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
  163. package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
  164. package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
  165. package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
  166. package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
  167. package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
  168. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/contract.ts +0 -0
  169. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/extract/index.ts +0 -0
  170. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/fanout/index.ts +0 -0
  171. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/registry.ts +1 -1
  172. /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
@@ -55,7 +55,8 @@ export function nextCronOccurrence(
55
55
  for (const second of parsed.seconds) {
56
56
  const wall = day + hour * 3_600_000 + minute * 60_000 + second * 1_000;
57
57
  if (wall <= wallAfter) continue;
58
- const resolved = wallTimeCandidates(wall, timeZone, after)[0];
58
+ const resolved = wallTimeInstants(wall, timeZone)
59
+ .find((instant) => instant > after);
59
60
  if (resolved !== undefined) return resolved;
60
61
  }
61
62
  }
@@ -73,18 +74,22 @@ export function isValidTimeZone(timeZone: string): boolean {
73
74
  }
74
75
  }
75
76
 
76
- function wallTimeCandidates(wallUtc: number, timeZone: string, after: number): number[] {
77
+ /**
78
+ * 所有本地墙上时间恰好等于 `wallUtc` 的 UTC 瞬时,升序。
79
+ *
80
+ * `wallUtc` 是"把本地钟点当作 UTC"的伪瞬时(见 {@link zoneOffsetMs})。
81
+ * 返回空数组 = 该钟点在当地被夏令时拨快抹掉;返回两个 = 拨慢导致同一钟点出现两次。
82
+ * 这是 `nextCronOccurrence` 与本地规则计算共用的唯一钟点解析边界。
83
+ */
84
+ export function wallTimeInstants(wallUtc: number, timeZone: string): number[] {
77
85
  const offsets = new Set([
78
- zoneOffsetMs(after, timeZone),
79
86
  zoneOffsetMs(wallUtc - 2 * 86_400_000, timeZone),
80
87
  zoneOffsetMs(wallUtc, timeZone),
81
88
  zoneOffsetMs(wallUtc + 2 * 86_400_000, timeZone),
82
89
  ]);
83
90
  return [...offsets]
84
91
  .map((offset) => wallUtc - offset)
85
- .filter((instant) =>
86
- instant > after && instant + zoneOffsetMs(instant, timeZone) === wallUtc
87
- )
92
+ .filter((instant) => instant + zoneOffsetMs(instant, timeZone) === wallUtc)
88
93
  .sort((left, right) => left - right);
89
94
  }
90
95
 
@@ -4,8 +4,7 @@ import type { SqlTaggedTemplate } from "agents/chat";
4
4
  // 创建当前 Runtime 需要但尚不存在的 SQLite 表。
5
5
  // AgentRuntimeKernel 构造时通过 RuntimeDatabase.initializeSchema 调用,重复启动依赖 IF NOT EXISTS 保留现有表。
6
6
  // pi_approvals 有一次显式的 risk → 四档升级;步骤均可重入,中断后下次初始化会继续。
7
- // pi_tool_interactions 是 client-settled tool 的 pending 索引:结果的权威副本仍在 pi_tool_settlements,
8
- // 这张表只回答「这次工具调用还等不等得到客户端响应」。
7
+ // pi_tool_interactions 只保存宿主交互事实;Tool 结果由 Pi Session 唯一持久化。
9
8
  export function initializeSchema(sql: SqlTaggedTemplate): void {
10
9
  sql`CREATE TABLE IF NOT EXISTS pi_message_ui (
11
10
  message_id TEXT PRIMARY KEY,
@@ -23,31 +22,21 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
23
22
  assembly_revision TEXT NOT NULL,
24
23
  assembly_descriptor TEXT NOT NULL,
25
24
  assistant_message_id TEXT NOT NULL,
26
- abort_reason TEXT,
27
- recovery_error_count INTEGER NOT NULL DEFAULT 0,
28
- recovery_reason TEXT,
29
- context_overflow_retried INTEGER NOT NULL DEFAULT 0
25
+ abort_reason TEXT
30
26
  )`;
31
27
  const submissionColumns = new Set(
32
28
  sql<{ name: string }>`PRAGMA table_info(pi_submissions)`
33
29
  .map((column) => column.name),
34
30
  );
31
+ if (!submissionColumns.has("runtime_key")) {
32
+ sql`ALTER TABLE pi_submissions ADD COLUMN runtime_key TEXT NOT NULL DEFAULT ''`;
33
+ }
35
34
  if (!submissionColumns.has("run_id")) {
36
35
  sql`ALTER TABLE pi_submissions ADD COLUMN run_id TEXT`;
37
36
  }
38
37
  if (!submissionColumns.has("admission_retryable")) {
39
38
  sql`ALTER TABLE pi_submissions ADD COLUMN admission_retryable INTEGER`;
40
39
  }
41
- // 跨执行片累计的模型回合数。让步把一个 Turn 切成多片后,每片的回合计数都从 0 开始,
42
- // 只有把总数落到持久层,全局预算才拦得住「一直让步、永不收敛」的工具循环。
43
- if (!submissionColumns.has("model_turns")) {
44
- sql`ALTER TABLE pi_submissions
45
- ADD COLUMN model_turns INTEGER NOT NULL DEFAULT 0`;
46
- }
47
- // 当前让步片的标识。用来丢弃重复到期的续跑 alarm,并让看门狗判断这一轮有没有推进。
48
- if (!submissionColumns.has("continuation_id")) {
49
- sql`ALTER TABLE pi_submissions ADD COLUMN continuation_id TEXT`;
50
- }
51
40
  sql`CREATE UNIQUE INDEX IF NOT EXISTS pi_submissions_run_id
52
41
  ON pi_submissions(run_id) WHERE run_id IS NOT NULL`;
53
42
  if (!submissionColumns.has("queued_input_json")) {
@@ -62,16 +51,17 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
62
51
  if (!submissionColumns.has("regenerate_message_id")) {
63
52
  sql`ALTER TABLE pi_submissions ADD COLUMN regenerate_message_id TEXT`;
64
53
  }
65
- if (!submissionColumns.has("recovery_error_count")) {
66
- sql`ALTER TABLE pi_submissions
67
- ADD COLUMN recovery_error_count INTEGER NOT NULL DEFAULT 0`;
54
+ if (!submissionColumns.has("origin")) {
55
+ sql`ALTER TABLE pi_submissions ADD COLUMN origin TEXT NOT NULL DEFAULT 'user'`;
56
+ }
57
+ if (!submissionColumns.has("scheduled_max_execution_level")) {
58
+ sql`ALTER TABLE pi_submissions ADD COLUMN scheduled_max_execution_level TEXT`;
68
59
  }
69
- if (!submissionColumns.has("recovery_reason")) {
70
- sql`ALTER TABLE pi_submissions ADD COLUMN recovery_reason TEXT`;
60
+ if (!submissionColumns.has("unattended_execution_grant_json")) {
61
+ sql`ALTER TABLE pi_submissions ADD COLUMN unattended_execution_grant_json TEXT`;
71
62
  }
72
- if (!submissionColumns.has("context_overflow_retried")) {
73
- sql`ALTER TABLE pi_submissions
74
- ADD COLUMN context_overflow_retried INTEGER NOT NULL DEFAULT 0`;
63
+ if (!submissionColumns.has("unattended_failure")) {
64
+ sql`ALTER TABLE pi_submissions ADD COLUMN unattended_failure TEXT`;
75
65
  }
76
66
  sql`CREATE UNIQUE INDEX IF NOT EXISTS pi_submissions_one_running
77
67
  ON pi_submissions(status)
@@ -80,20 +70,21 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
80
70
  steer_id TEXT PRIMARY KEY,
81
71
  submission_id TEXT NOT NULL,
82
72
  message_id TEXT NOT NULL UNIQUE,
73
+ harness_entry_id TEXT,
83
74
  canonical_json TEXT NOT NULL,
84
75
  ui_message_json TEXT,
85
76
  created_at INTEGER NOT NULL
86
77
  )`;
87
- sql`CREATE TABLE IF NOT EXISTS pi_tool_settlements (
88
- submission_id TEXT NOT NULL,
89
- tool_call_id TEXT NOT NULL,
90
- tool_name TEXT NOT NULL,
91
- args TEXT NOT NULL,
92
- result TEXT NOT NULL,
93
- is_error INTEGER NOT NULL,
94
- created_at INTEGER NOT NULL,
95
- PRIMARY KEY (submission_id, tool_call_id)
96
- )`;
78
+ const steerColumns = new Set(
79
+ sql<{ name: string }>`PRAGMA table_info(pi_pending_steers)`
80
+ .map((column) => column.name),
81
+ );
82
+ if (!steerColumns.has("harness_entry_id")) {
83
+ sql`ALTER TABLE pi_pending_steers ADD COLUMN harness_entry_id TEXT`;
84
+ }
85
+ sql`CREATE UNIQUE INDEX IF NOT EXISTS pi_pending_steers_harness_entry_id
86
+ ON pi_pending_steers(harness_entry_id)
87
+ WHERE harness_entry_id IS NOT NULL`;
97
88
  sql`CREATE TABLE IF NOT EXISTS pi_turn_event_outbox (
98
89
  event_id TEXT PRIMARY KEY,
99
90
  body TEXT NOT NULL,
@@ -177,6 +168,9 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
177
168
  .map((column) => column.name),
178
169
  );
179
170
  let columns = approvalColumns();
171
+ if (!columns.has("confirmation_json")) {
172
+ sql`ALTER TABLE pi_approvals ADD COLUMN confirmation_json TEXT`;
173
+ }
180
174
  if (!columns.has("execution_level")) {
181
175
  // 旧 Session 没有持久化当时的授权档位,safe 失败关闭;只影响旧 pending 回执展示,不会静默放行。
182
176
  sql`ALTER TABLE pi_approvals
@@ -211,52 +205,6 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
211
205
  responded_at INTEGER,
212
206
  UNIQUE (submission_id, tool_call_id)
213
207
  )`;
214
- sql`CREATE TABLE IF NOT EXISTS pi_recovery_milestones (
215
- submission_id TEXT NOT NULL,
216
- seq INTEGER NOT NULL,
217
- milestone_key TEXT NOT NULL,
218
- body TEXT NOT NULL,
219
- PRIMARY KEY (submission_id, seq),
220
- UNIQUE (submission_id, milestone_key)
221
- )`;
222
- sql`UPDATE pi_recovery_milestones
223
- SET body = json_remove(
224
- json_set(
225
- body,
226
- '$.approval.executionLevel', 'safe',
227
- '$.approval.requiredExecutionLevel',
228
- COALESCE(json_extract(body, '$.approval.risk'), 'safe')
229
- ),
230
- '$.approval.risk'
231
- )
232
- WHERE json_valid(body)
233
- AND json_extract(body, '$.version') = 1
234
- AND json_extract(body, '$.type') = 'approval'
235
- AND json_type(body, '$.turnId') = 'text'
236
- AND json_type(body, '$.assemblyRevision') = 'text'
237
- AND json_type(body, '$.approval') = 'object'
238
- AND json_type(body, '$.approval.executionId') = 'text'
239
- AND json_type(body, '$.approval.requestId') = 'text'
240
- AND json_extract(body, '$.approval.source') IN (
241
- 'action', 'codemode', 'temporary-agent'
242
- )
243
- AND json_type(body, '$.approval.toolCallId') = 'text'
244
- AND json_type(body, '$.approval.toolName') = 'text'
245
- AND json_type(body, '$.approval.summary') = 'text'
246
- AND json_type(body, '$.approval.inputJson') = 'text'
247
- AND json_type(body, '$.approval.createdAt') IN ('integer', 'real')
248
- AND json_extract(body, '$.approval.status') IN (
249
- 'pending', 'approved', 'rejected'
250
- )
251
- AND (
252
- json_type(body, '$.approval.risk') IS NULL
253
- OR json_type(body, '$.approval.risk') = 'null'
254
- OR json_extract(body, '$.approval.risk') IN (
255
- 'low', 'medium', 'high'
256
- )
257
- )
258
- AND json_type(body, '$.approval.executionLevel') IS NULL
259
- AND json_type(body, '$.approval.requiredExecutionLevel') IS NULL`;
260
208
  sql`CREATE TABLE IF NOT EXISTS pi_extension_context (
261
209
  label TEXT PRIMARY KEY,
262
210
  content TEXT NOT NULL,
@@ -7,29 +7,31 @@ import {
7
7
  type FiberRecoveryContext,
8
8
  type FiberRecoveryResult,
9
9
  } from "agents";
10
- import type { AgentEvent } from "@earendil-works/pi-agent-core";
10
+ import type { HarnessEvent } from "@earendil-works/pi-agent-core";
11
+ import type { UIMessageChunk } from "ai";
11
12
  import {
12
13
  AGENT_TYPES,
13
- } from "../../../layers/orchestration/subagents/agent-types/registry";
14
- import type { AgentType } from "../../../layers/orchestration/subagents/agent-types/contract";
14
+ } from "../../../tools/subagent/types/registry";
15
+ import type { AgentType } from "../../../tools/subagent/types/contract";
15
16
  import type {
16
17
  RuntimeProviderPort,
17
18
  WorkspacePort,
18
- } from "../../../kernel/bindings";
19
- import { PiChunkEncoder } from "../../../pi/message";
19
+ } from "../../../config/ports";
20
+ import { HarnessStreamProjection } from "../../../conversation/harness-stream";
20
21
  import {
21
22
  createPiModels,
22
23
  resolvePiModel,
23
- withProviderRetry,
24
- } from "../../../pi/runtime-adapter/models";
24
+ withProviderBoundary,
25
+ } from "../../../models";
25
26
  import { runCloudflareSubAgent } from "./runner";
27
+ import { openCloudflareSession } from "../../../session";
26
28
  import {
27
29
  addSubagentUsage,
28
30
  decodeSubagentCompletion,
29
31
  emptySubagentUsage,
30
32
  encodeSubagentCompletion,
31
- } from "../../../kernel/subagent-runtime";
32
- import type { RuntimeSubagentUsageMetrics } from "../../../kernel/submission-authority";
33
+ } from "../../../tools/subagent/runtime";
34
+ import type { RuntimeSubagentUsageMetrics } from "../../../tools/subagent/authority";
33
35
  import { createCloudflareSubAgentTools } from "./tools";
34
36
 
35
37
  type RunStatus =
@@ -92,24 +94,24 @@ function stringifyJson(value: unknown): string {
92
94
  }
93
95
 
94
96
  function progressFor(
95
- event: AgentEvent,
97
+ event: HarnessEvent,
96
98
  ): Omit<AgentToolProgressSnapshot, "at"> | null {
97
- if (event.type === "agent_start") {
99
+ if (event.type === "run_start" || event.type === "run_resume") {
98
100
  return { phase: "running", message: "SubAgent started" };
99
101
  }
100
- if (event.type === "tool_execution_start") {
102
+ if (event.type === "tool_start") {
101
103
  return {
102
104
  phase: "tool",
103
105
  message: `Running ${event.toolName}`,
104
106
  };
105
107
  }
106
- if (event.type === "tool_execution_update") {
108
+ if (event.type === "tool_update") {
107
109
  return {
108
110
  phase: "tool",
109
111
  message: `${event.toolName} is still running`,
110
112
  };
111
113
  }
112
- if (event.type === "tool_execution_end") {
114
+ if (event.type === "tool_end") {
113
115
  return {
114
116
  phase: event.isError ? "tool-error" : "tool",
115
117
  message: event.isError
@@ -362,6 +364,15 @@ export abstract class CloudflareSubAgent<
362
364
  `;
363
365
 
364
366
  const usage = this.readUsage(runId);
367
+ let streamSequence = 0;
368
+ let projection: HarnessStreamProjection | undefined;
369
+ const publish = (chunks: readonly UIMessageChunk[]) => {
370
+ for (const chunk of chunks) {
371
+ this.appendChunk(runId, stringifyJson({
372
+ v: 1, messageId: runId, sequence: streamSequence++, chunk,
373
+ }));
374
+ }
375
+ };
365
376
  try {
366
377
  const { provider, workspace, loader, outbound } =
367
378
  await this.prepareExecution();
@@ -387,16 +398,19 @@ export abstract class CloudflareSubAgent<
387
398
  );
388
399
  },
389
400
  });
390
- let streamSequence = 0;
391
- const encoder = new PiChunkEncoder({
401
+ projection = new HarnessStreamProjection({
392
402
  messageId: runId,
403
+ turnId: runId,
393
404
  startedAt: this.readRun(runId)?.started_at ?? Date.now(),
394
405
  });
395
406
  const output = await runCloudflareSubAgent({
396
407
  type,
397
408
  input,
398
409
  model,
399
- streamFn: withProviderRetry(models.streamSimple.bind(models)),
410
+ models,
411
+ openSession: () => openCloudflareSession(this.ctx.storage),
412
+ operationId: runId,
413
+ streamFn: withProviderBoundary(models.streamSimple.bind(models)),
400
414
  tools,
401
415
  signal,
402
416
  onEvent: (event) => {
@@ -404,20 +418,19 @@ export abstract class CloudflareSubAgent<
404
418
  addSubagentUsage(usage, event.message.usage);
405
419
  this.persistUsage(runId, usage);
406
420
  }
407
- for (const chunk of encoder.encode(event)) {
408
- this.appendChunk(runId, stringifyJson({
409
- v: 1,
410
- messageId: runId,
411
- sequence: streamSequence++,
412
- chunk,
413
- }));
414
- }
421
+ publish(projection!.event(event));
415
422
  const progress = progressFor(event);
416
423
  if (progress) this.persistProgress(runId, progress);
417
424
  },
418
425
  });
426
+ publish(projection.finish({ status: "completed", endedAt: Date.now() }));
419
427
  this.completeRun(runId, output, usage);
420
428
  } catch (error) {
429
+ if (projection) publish(projection.finish({
430
+ status: signal.aborted ? "aborted" : "failed",
431
+ endedAt: Date.now(),
432
+ error: { code: signal.aborted ? "aborted" : "failed", message: errorMessage(error) },
433
+ }));
421
434
  this.failRun(
422
435
  runId,
423
436
  signal.aborted ? "aborted" : "error",
@@ -611,16 +624,33 @@ export abstract class CloudflareSubAgent<
611
624
  typeof context.metadata?.runId === "string"
612
625
  ? context.metadata.runId
613
626
  : context.idempotencyKey;
614
- if (runId) {
615
- this.failRun(
616
- runId,
617
- "error",
618
- "SubAgent run was interrupted before completion",
619
- );
627
+ if (!runId) {
628
+ return { status: "interrupted", reason: "SubAgent run ID is missing" };
629
+ }
630
+ const row = this.readRun(runId);
631
+ if (!row) {
632
+ return { status: "interrupted", reason: "SubAgent run is missing" };
633
+ }
634
+ if (!TERMINAL.has(row.status)) {
635
+ const type = this.resolveType(row.agent_type);
636
+ const input = type.inputSchema.parse(parseJson(row.input_json));
637
+ await this.executeRun(runId, type, input, new AbortController().signal);
638
+ }
639
+ const recovered = this.readRun(runId);
640
+ if (recovered?.status === "completed") {
641
+ return { status: "completed", snapshot: { runId } };
642
+ }
643
+ if (recovered?.status === "aborted") {
644
+ return {
645
+ status: "aborted",
646
+ reason: recovered.error_message ?? "SubAgent run aborted",
647
+ snapshot: { runId },
648
+ };
620
649
  }
621
650
  return {
622
- status: "interrupted",
623
- reason: "SubAgent run was interrupted before completion",
651
+ status: "error",
652
+ error: recovered?.error_message ?? "SubAgent recovery failed",
653
+ snapshot: { runId },
624
654
  };
625
655
  }
626
656
  }