@astrosheep/keiyaku 2.9.3 → 2.9.5

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 (282) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +14 -94
  3. package/build/agents/harness/activity-values.js +16 -0
  4. package/build/agents/harness/event-persistence.js +218 -0
  5. package/build/agents/harness/execution-handle.js +26 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +68 -5
  8. package/build/agents/harness/projection.js +179 -124
  9. package/build/agents/harness/provider-adapter.js +58 -0
  10. package/build/agents/harness/router.js +11 -187
  11. package/build/agents/harness/runtime.js +17 -4
  12. package/build/agents/harness/session.js +15 -0
  13. package/build/agents/launch-snapshot/model.js +15 -236
  14. package/build/agents/launch-snapshot/parse-helpers.js +114 -0
  15. package/build/agents/launch-snapshot/resolve.js +17 -43
  16. package/build/agents/opencode-sdk.js +45 -12
  17. package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
  18. package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
  19. package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
  20. package/build/agents/providers/codex-app-server/adapter.js +82 -53
  21. package/build/agents/providers/codex-app-server/events.js +90 -11
  22. package/build/agents/providers/codex-app-server/identity.js +1 -0
  23. package/build/agents/providers/codex-app-server/policy.js +29 -0
  24. package/build/agents/providers/codex-app-server/registration.js +248 -0
  25. package/build/agents/providers/codex-app-server/session-schema.js +6 -0
  26. package/build/agents/providers/codex-app-server/session.js +23 -17
  27. package/build/agents/providers/codex-app-server/terms.js +1 -0
  28. package/build/agents/providers/opencode-sdk/adapter.js +94 -50
  29. package/build/agents/providers/opencode-sdk/events.js +8 -4
  30. package/build/agents/providers/opencode-sdk/registration.js +135 -0
  31. package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
  32. package/build/agents/providers/opencode-sdk/session.js +21 -14
  33. package/build/agents/providers/opencode-sdk/terms.js +1 -0
  34. package/build/agents/providers/pi/adapter.js +36 -30
  35. package/build/agents/providers/pi/events.js +4 -3
  36. package/build/agents/providers/pi/registration.js +145 -0
  37. package/build/agents/providers/pi/session-schema.js +6 -0
  38. package/build/agents/providers/pi/terms.js +1 -0
  39. package/build/agents/providers/registration.js +7 -0
  40. package/build/agents/providers/registry.js +76 -0
  41. package/build/agents/selector.js +2 -29
  42. package/build/agents/types.js +10 -0
  43. package/build/artifact-directories.js +22 -0
  44. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  45. package/build/cli/commands/akuma/akuma/meta.js +1 -1
  46. package/build/cli/commands/akuma/list/meta-ls.js +1 -1
  47. package/build/cli/commands/akuma/view/handler.js +5 -4
  48. package/build/cli/commands/akuma/view/meta.js +1 -1
  49. package/build/cli/commands/akuma.js +6 -0
  50. package/build/cli/commands/contract/amend/handler.js +8 -2
  51. package/build/cli/commands/contract/amend/meta.js +4 -4
  52. package/build/cli/commands/contract/arc/handler.js +8 -2
  53. package/build/cli/commands/contract/arc/meta.js +4 -4
  54. package/build/cli/commands/contract/audit/handler.js +16 -0
  55. package/build/cli/commands/contract/audit/meta.js +16 -0
  56. package/build/cli/commands/contract/bind/handler.js +4 -3
  57. package/build/cli/commands/contract/bind/meta.js +44 -8
  58. package/build/cli/commands/contract/catalog.js +2 -0
  59. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  60. package/build/cli/commands/contract/forfeit/meta.js +1 -1
  61. package/build/cli/commands/contract/log/handler.js +3 -3
  62. package/build/cli/commands/contract/log/meta.js +1 -1
  63. package/build/cli/commands/contract/petition/handler.js +5 -8
  64. package/build/cli/commands/contract/petition/meta.js +6 -6
  65. package/build/cli/commands/contract/renew/handler.js +20 -6
  66. package/build/cli/commands/contract/renew/meta.js +6 -5
  67. package/build/cli/commands/metadata.js +59 -0
  68. package/build/cli/commands/projection/call/handler.js +6 -5
  69. package/build/cli/commands/projection/call/meta.js +4 -1
  70. package/build/cli/commands/projection/kill/handler.js +16 -32
  71. package/build/cli/commands/projection/kill/meta.js +1 -1
  72. package/build/cli/commands/projection/revive/meta.js +4 -1
  73. package/build/cli/commands/projection/shared.js +28 -0
  74. package/build/cli/commands/projection/status/handler.js +1 -1
  75. package/build/cli/commands/projection/status/meta.js +1 -1
  76. package/build/cli/commands/projection/tell/handler.js +14 -30
  77. package/build/cli/commands/projection/tell/meta.js +4 -1
  78. package/build/cli/commands/projection/wait/handler.js +45 -22
  79. package/build/cli/commands/projection/wait/meta.js +8 -3
  80. package/build/cli/commands/registry.js +2 -54
  81. package/build/cli/commands/shared.js +10 -7
  82. package/build/cli/commands/skills/install/handler.js +2 -5
  83. package/build/cli/commands/skills/install/meta.js +1 -1
  84. package/build/cli/commands/skills/skills/handler.js +1 -1
  85. package/build/cli/commands/skills/skills/meta.js +1 -1
  86. package/build/cli/commands/system/completion/meta.js +1 -1
  87. package/build/cli/commands/system/dump-env/meta.js +1 -1
  88. package/build/cli/commands/system/guide/meta.js +1 -1
  89. package/build/cli/commands/task/add/handler.js +7 -15
  90. package/build/cli/commands/task/add/meta.js +4 -1
  91. package/build/cli/commands/task/catalog.js +2 -2
  92. package/build/cli/commands/task/doctor/meta.js +1 -1
  93. package/build/cli/commands/task/done/handler.js +4 -8
  94. package/build/cli/commands/task/done/meta.js +1 -1
  95. package/build/cli/commands/task/drop/handler.js +3 -6
  96. package/build/cli/commands/task/drop/meta.js +1 -1
  97. package/build/cli/commands/task/ls/handler.js +12 -8
  98. package/build/cli/commands/task/ls/meta.js +3 -3
  99. package/build/cli/commands/task/shared.js +14 -4
  100. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  101. package/build/cli/commands/task/show/meta.js +8 -0
  102. package/build/cli/commands/task/start/handler.js +3 -6
  103. package/build/cli/commands/task/start/meta.js +1 -1
  104. package/build/cli/commands/task/stop/handler.js +4 -8
  105. package/build/cli/commands/task/stop/meta.js +1 -1
  106. package/build/cli/commands/task/task/handler.js +1 -1
  107. package/build/cli/commands/task/task/meta.js +1 -1
  108. package/build/cli/commands/task/update/handler.js +6 -11
  109. package/build/cli/commands/task/update/meta.js +4 -1
  110. package/build/cli/completion.js +1 -1
  111. package/build/cli/flags.js +37 -6
  112. package/build/cli/help.js +7 -14
  113. package/build/cli/index.js +112 -154
  114. package/build/cli/missing-subcommand.js +7 -0
  115. package/build/cli/output-stream-error-policy.js +19 -5
  116. package/build/cli/parse-flags.js +33 -7
  117. package/build/cli/parse-metadata.js +3 -9
  118. package/build/cli/parse-selectors.js +2 -1
  119. package/build/cli/parse.js +35 -5
  120. package/build/cli/projection-address.js +12 -12
  121. package/build/cli/render/address.js +41 -37
  122. package/build/cli/render/arc.js +24 -12
  123. package/build/cli/render/audit.js +121 -0
  124. package/build/cli/render/call.js +11 -5
  125. package/build/cli/render/errors.js +36 -1
  126. package/build/cli/render/format.js +4 -0
  127. package/build/cli/render/kanshi.js +6 -12
  128. package/build/cli/render/kill.js +9 -0
  129. package/build/cli/render/misc.js +58 -13
  130. package/build/cli/render/petition.js +20 -15
  131. package/build/cli/render/projection-activity.js +15 -6
  132. package/build/cli/render/shared.js +60 -21
  133. package/build/cli/render/skills.js +10 -0
  134. package/build/cli/render/status.js +56 -23
  135. package/build/cli/render/success-response.js +78 -0
  136. package/build/cli/render/task.js +66 -57
  137. package/build/cli/render/tell.js +5 -0
  138. package/build/cli/render/terminal-failure.js +63 -0
  139. package/build/cli/render/types.js +1 -0
  140. package/build/cli/render/wait.js +68 -38
  141. package/build/cli/types.js +1 -1
  142. package/build/config/akuma-loader.js +0 -5
  143. package/build/config/env-keys.js +3 -1
  144. package/build/config/env.js +3 -0
  145. package/build/config/provider-policy-ownership.js +3 -32
  146. package/build/config/settings/disease.js +8 -1
  147. package/build/config/settings/loader.js +11 -3
  148. package/build/config/settings/provider-instance-fields.js +51 -0
  149. package/build/config/settings/schema.js +6 -176
  150. package/build/core/addressing.js +66 -16
  151. package/build/core/amend.js +61 -18
  152. package/build/core/arc.js +33 -73
  153. package/build/core/audit.js +650 -0
  154. package/build/core/bind.js +82 -35
  155. package/build/core/call/call.js +15 -9
  156. package/build/core/call/context.js +6 -51
  157. package/build/core/call/execution.js +22 -22
  158. package/build/core/call/prompt.js +14 -1
  159. package/build/core/command-markdown.js +45 -0
  160. package/build/core/context.js +13 -7
  161. package/build/core/contract-view.js +22 -6
  162. package/build/core/draft.js +139 -19
  163. package/build/core/entry.js +3 -4
  164. package/build/core/forfeit.js +5 -2
  165. package/build/core/hints.js +1 -75
  166. package/build/core/identifier-slug.js +66 -0
  167. package/build/core/ids.js +25 -11
  168. package/build/core/ledger.js +20 -68
  169. package/build/core/markdown/lex.js +2 -1
  170. package/build/core/markdown/parser.js +0 -68
  171. package/build/core/markdown/render.js +68 -0
  172. package/build/core/markdown/sections.js +2 -1
  173. package/build/core/outcome-base.js +4 -0
  174. package/build/core/projection/generation/database.js +41 -52
  175. package/build/core/projection/generation/model.js +63 -2
  176. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  177. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
  178. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  179. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
  180. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  181. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
  182. package/build/core/projection/generation/store.js +3 -3
  183. package/build/core/projection/generation/transitions.js +19 -3
  184. package/build/core/projection/index.js +27 -0
  185. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  186. package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
  187. package/build/core/projection/projection-coordinate.js +61 -0
  188. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  189. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
  190. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  191. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  192. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
  193. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  194. package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
  195. package/build/core/projection/projection-terminal-failure.js +106 -0
  196. package/build/core/projection/projection-wait.js +275 -0
  197. package/build/core/projection/projection-wake.js +227 -0
  198. package/build/core/provider-loader-ports.js +4 -0
  199. package/build/core/ref-log.js +65 -40
  200. package/build/core/registry.js +63 -10
  201. package/build/core/renew-session.js +128 -0
  202. package/build/core/renew.js +448 -102
  203. package/build/core/scope.js +272 -68
  204. package/build/core/seal.js +165 -40
  205. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
  206. package/build/core/{claim.js → settlement/claim.js} +32 -8
  207. package/build/core/settlement/index.js +19 -0
  208. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
  209. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  210. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  211. package/build/core/settlement/petition-io.js +1 -0
  212. package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
  213. package/build/core/{petition.js → settlement/petition.js} +21 -12
  214. package/build/core/settlement/queue-read-model.js +143 -0
  215. package/build/core/settlement/queue-seat-allocation.js +156 -0
  216. package/build/core/settlement/queue.js +6 -0
  217. package/build/core/settlement/settlement.js +288 -0
  218. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  219. package/build/core/settlement/verification.js +59 -0
  220. package/build/core/status/board.js +62 -41
  221. package/build/core/status/ledger-batch.js +158 -0
  222. package/build/core/status/reconciliation.js +10 -4
  223. package/build/core/stored-agent-event.js +6 -2
  224. package/build/core/task/board.js +41 -4
  225. package/build/core/task/commands.js +45 -28
  226. package/build/core/task/document.js +5 -0
  227. package/build/core/task/index.js +12 -0
  228. package/build/core/task/model.js +8 -1
  229. package/build/core/task/query.js +172 -0
  230. package/build/core/task/settlement-git.js +265 -74
  231. package/build/core/task/settlement-policy.js +67 -29
  232. package/build/core/task/source-board.js +1 -0
  233. package/build/core/task/task-contract.js +444 -0
  234. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  235. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  236. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  237. package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
  238. package/build/core/task/task-repository.js +1 -0
  239. package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
  240. package/build/core/task/task-store.js +1 -0
  241. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  242. package/build/core/task/task.js +7 -0
  243. package/build/core/task/tree.js +37 -0
  244. package/build/core/task/validation.js +5 -2
  245. package/build/core/transcripts.js +15 -3
  246. package/build/core/worktree-path.js +43 -28
  247. package/build/flow-error.js +83 -39
  248. package/build/fs/path-coordinate.js +16 -0
  249. package/build/generated/version.js +3 -2
  250. package/build/git/branches.js +2 -1
  251. package/build/git/core.js +106 -2
  252. package/build/git/diff/parsers.js +23 -13
  253. package/build/git/diff/structured.js +127 -0
  254. package/build/git/path-coordinate.js +17 -0
  255. package/build/git/refs.js +57 -0
  256. package/build/git/streaming-batch.js +301 -0
  257. package/build/git/worktree.js +65 -0
  258. package/build/index.js +9 -9
  259. package/build/keiyaku.js +2 -36
  260. package/build/telemetry/logger.js +4 -2
  261. package/package.json +4 -10
  262. package/skills/keiyaku/SKILL.md +5 -4
  263. package/skills/keiyaku-akuma/SKILL.md +25 -11
  264. package/skills/keiyaku-task/SKILL.md +17 -8
  265. package/skills/keiyaku-workflow/SKILL.md +58 -9
  266. package/build/agents/providers/codex-sdk.js +0 -329
  267. package/build/cli/commands/task/view/meta.js +0 -8
  268. package/build/core/command-io.js +0 -174
  269. package/build/core/path-coordinate.js +0 -27
  270. package/build/core/petition-claim.js +0 -117
  271. package/build/core/petition-run.js +0 -83
  272. package/build/core/projection-coordinate.js +0 -63
  273. package/build/core/projection-wait.js +0 -165
  274. package/build/core/projection-wake.js +0 -136
  275. package/build/core/queue.js +0 -343
  276. package/build/core/task-contract.js +0 -237
  277. package/build/core/task.js +0 -4
  278. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  279. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  280. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  281. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  282. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { StringTailBuffer } from "../string-tail-buffer.js";
2
2
  import { SUBAGENT_SESSION_VERSION, } from "../session.js";
3
- import { SubagentFailure, } from "../types.js";
3
+ import { OUTCOME_ERROR_VERSION, SubagentFailure, } from "../types.js";
4
+ import { isProviderTerminalError, } from "./provider-adapter.js";
4
5
  import { renderCapturedText } from "./runtime.js";
5
6
  export function createOutcomeState(maxCaptureChars) {
6
7
  return {
@@ -63,7 +64,15 @@ export function failFromFailEvent(shared, state, failEvent) {
63
64
  exitCode: failEvent.exitCode ?? null,
64
65
  }, state.finalMessageCandidate, failEvent.diagnostics ?? diagnostics);
65
66
  }
66
- export function classifyFailure(shared, state, error, idleTimedOut) {
67
+ export function classifyFailure(shared, state, error, idleTimedOut, transportFailure, policyBudgetExceeded = false) {
68
+ if (policyBudgetExceeded) {
69
+ return failWithError(shared, state, {
70
+ code: "SUBAGENT_POLICY_BUDGET",
71
+ message: `${shared.terms.provider} execution policy budget expired`,
72
+ timeoutMs: shared.terms.executionTimeoutMs,
73
+ exitCode: null,
74
+ }, state.finalMessageCandidate);
75
+ }
67
76
  if (shared.dismissed || shared.externallyAborted) {
68
77
  return mintOutcome({
69
78
  status: "dismissed",
@@ -74,6 +83,18 @@ export function classifyFailure(shared, state, error, idleTimedOut) {
74
83
  warnings: [...state.warnings],
75
84
  });
76
85
  }
86
+ if (transportFailure) {
87
+ const message = transportFailure instanceof Error ? transportFailure.message : `${shared.terms.provider} session transport probe failed`;
88
+ const timeoutMs = isProviderTerminalError(transportFailure) && transportFailure.fact.kind === "failed"
89
+ ? transportFailure.fact.timeoutMs
90
+ : shared.terms.idleTimeoutMs;
91
+ return failWithError(shared, state, {
92
+ code: "SUBAGENT_TRANSPORT_ERROR",
93
+ message,
94
+ timeoutMs,
95
+ exitCode: null,
96
+ }, state.finalMessageCandidate);
97
+ }
77
98
  if (idleTimedOut) {
78
99
  return failWithError(shared, state, {
79
100
  code: "SUBAGENT_TIMEOUT",
@@ -82,13 +103,55 @@ export function classifyFailure(shared, state, error, idleTimedOut) {
82
103
  exitCode: null,
83
104
  }, state.finalMessageCandidate);
84
105
  }
106
+ return classifyExecutionFailure(shared, state, error);
107
+ }
108
+ /**
109
+ * A rejected graceful settlement is terminal evidence, not an outcome intent.
110
+ * In particular, dismiss/external-cancel intent must not downgrade it to a
111
+ * clean dismissed result.
112
+ */
113
+ export function classifySettlementFailure(shared, state, error) {
114
+ return classifyExecutionFailure(shared, state, error);
115
+ }
116
+ function classifyFromFailureFact(shared, state, fact) {
117
+ return failWithError(shared, state, {
118
+ code: fact.code,
119
+ message: fact.message,
120
+ timeoutMs: fact.timeoutMs,
121
+ exitCode: fact.exitCode,
122
+ ...(fact.publicCause ? { publicCause: fact.publicCause } : {}),
123
+ }, state.finalMessageCandidate ?? fact.message, fact.diagnostics || renderCapturedText(state.diagnosticsBuffer), fact.output || renderCapturedText(state.outputBuffer));
124
+ }
125
+ function classifyFromAbortFact(shared, state, fact) {
126
+ // Abort without dismiss/external-cancel remains failed truth (historical bytes).
127
+ appendThrownMessage(state.diagnosticsBuffer, fact.message);
128
+ return failWithError(shared, state, {
129
+ code: "SUBAGENT_EXEC_ERROR",
130
+ message: fact.message,
131
+ timeoutMs: null,
132
+ exitCode: null,
133
+ }, state.finalMessageCandidate ?? fact.message);
134
+ }
135
+ function classifyExecutionFailure(shared, state, error) {
136
+ if (isProviderTerminalError(error)) {
137
+ if (error.fact.kind === "aborted") {
138
+ return classifyFromAbortFact(shared, state, error.fact);
139
+ }
140
+ return classifyFromFailureFact(shared, state, error.fact);
141
+ }
85
142
  if (error instanceof SubagentFailure) {
86
- return failWithError(shared, state, {
143
+ return classifyFromFailureFact(shared, state, {
144
+ kind: "failed",
87
145
  code: error.code,
88
146
  message: error.message,
89
147
  timeoutMs: error.timeoutMs,
90
148
  exitCode: error.exitCode,
91
- }, state.finalMessageCandidate ?? error.message, error.diagnostics || renderCapturedText(state.diagnosticsBuffer), error.output || renderCapturedText(state.outputBuffer));
149
+ diagnostics: error.diagnostics,
150
+ output: error.output,
151
+ });
152
+ }
153
+ if (error instanceof Error && error.name === "AbortError") {
154
+ return classifyFromAbortFact(shared, state, { kind: "aborted", message: error.message });
92
155
  }
93
156
  const message = error instanceof Error ? error.message : `Unknown ${shared.terms.provider} exec error`;
94
157
  appendThrownMessage(state.diagnosticsBuffer, message);
@@ -102,7 +165,7 @@ export function classifyFailure(shared, state, error, idleTimedOut) {
102
165
  export function failWithError(shared, state, error, finalMessage, diagnostics = renderCapturedText(state.diagnosticsBuffer), output = renderCapturedText(state.outputBuffer)) {
103
166
  return mintOutcome({
104
167
  status: "failed",
105
- error,
168
+ error: { version: OUTCOME_ERROR_VERSION, ...error },
106
169
  finalMessage: finalMessage ?? error.message,
107
170
  output,
108
171
  diagnostics,
@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import { FlowError } from "../../flow-error.js";
3
3
  import { createEventChannel } from "./event-channel.js";
4
4
  import { overflowMarkerFromDropped, prepareAgentEventForPersistence, } from "./execution-handle.js";
5
- import { classifyFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, renderOutcomeCapturedText, } from "./outcome.js";
5
+ import { classifyFailure, classifySettlementFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, renderOutcomeCapturedText, } from "./outcome.js";
6
6
  import { startLiveProjection } from "./router.js";
7
7
  import { createAbortError } from "./runtime.js";
8
+ const POLICY_BUDGET_CLEANUP_GRACE_MS = 5_000;
8
9
  export function call(terms, options) {
9
10
  return createProjection(terms, undefined, options);
10
11
  }
@@ -12,17 +13,7 @@ export function revive(terms, options) {
12
13
  return createProjection(terms, terms, options);
13
14
  }
14
15
  function createProjection(terms, reviveTerms, options) {
15
- switch (terms.provider) {
16
- case "pi":
17
- case "claude-agent-sdk":
18
- case "codex-app-server":
19
- case "opencode-sdk":
20
- return createSteerableProjection(terms, reviveTerms, options);
21
- case "codex-sdk":
22
- return createReplayProjection(terms, reviveTerms, options);
23
- default:
24
- return assertNever(terms);
25
- }
16
+ return createSteerableProjection(terms, reviveTerms, options);
26
17
  }
27
18
  function createSteerableProjection(terms, reviveTerms, options) {
28
19
  const shared = createProjectionState(terms, reviveTerms, options);
@@ -84,31 +75,6 @@ function createSteerableProjection(terms, reviveTerms, options) {
84
75
  },
85
76
  };
86
77
  }
87
- function createReplayProjection(terms, reviveTerms, options) {
88
- const shared = createProjectionState(terms, reviveTerms, options);
89
- shared.onLive = (live) => {
90
- shared.live = live;
91
- };
92
- const outcome = settleProjection(shared);
93
- return {
94
- mode: "replay",
95
- get id() {
96
- return shared.id;
97
- },
98
- get events() {
99
- return shared.publicEvents.iterable;
100
- },
101
- get outcome() {
102
- return outcome;
103
- },
104
- abort(mode) {
105
- return abortProjection(shared, mode);
106
- },
107
- dismiss() {
108
- dismissProjection(shared);
109
- },
110
- };
111
- }
112
78
  function createProjectionState(terms, reviveTerms, options) {
113
79
  const shared = {
114
80
  id: randomUUID(),
@@ -129,9 +95,7 @@ function createProjectionState(terms, reviveTerms, options) {
129
95
  if (terms.signal) {
130
96
  const listener = () => {
131
97
  shared.externallyAborted = true;
132
- if (!shared.lifecycleAbort.signal.aborted) {
133
- shared.lifecycleAbort.abort(terms.signal?.reason);
134
- }
98
+ requestControlledGracefulAbort(shared, terms.signal?.reason);
135
99
  };
136
100
  terms.signal.addEventListener("abort", listener, { once: true });
137
101
  if (terms.signal.aborted) {
@@ -144,20 +108,17 @@ function dismissProjection(shared) {
144
108
  if (shared.dismissed || shared.terminal)
145
109
  return;
146
110
  shared.dismissed = true;
147
- if (!shared.lifecycleAbort.signal.aborted) {
148
- shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec dismissed`));
149
- }
150
- requestDismissAbort(shared.live);
111
+ requestControlledGracefulAbort(shared, createAbortError(`${shared.terms.provider} exec dismissed`));
151
112
  }
152
113
  async function abortProjection(shared, mode = "graceful") {
153
114
  shared.explicitlyAborted = true;
154
- if (!shared.lifecycleAbort.signal.aborted) {
155
- shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec cancelled by client`));
156
- }
157
115
  if (mode !== "force") {
158
- await shared.live?.abort("graceful");
116
+ await requestControlledGracefulAbort(shared, createAbortError(`${shared.terms.provider} exec cancelled by client`));
159
117
  return;
160
118
  }
119
+ if (!shared.lifecycleAbort.signal.aborted) {
120
+ shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec cancelled by client`));
121
+ }
161
122
  shared.forceAbort ??= (async () => {
162
123
  const live = shared.live ?? (shared.terminal ? undefined : await new Promise((resolve) => {
163
124
  shared.forceWaiters.add(resolve);
@@ -171,14 +132,44 @@ function resolveForceWaiters(shared, live) {
171
132
  resolve(live);
172
133
  shared.forceWaiters.clear();
173
134
  }
174
- function requestDismissAbort(live) {
175
- if (!live)
176
- return;
177
- void Promise.resolve()
178
- .then(() => live.abort("graceful"))
179
- .catch(() => undefined);
135
+ function requestControlledGracefulAbort(shared, reason) {
136
+ if (shared.gracefulAbort)
137
+ return shared.gracefulAbort;
138
+ const live = shared.live;
139
+ if (!live) {
140
+ if (!shared.lifecycleAbort.signal.aborted)
141
+ shared.lifecycleAbort.abort(reason);
142
+ return Promise.resolve();
143
+ }
144
+ shared.gracefulAbort = (async () => {
145
+ try {
146
+ await live.abort("graceful");
147
+ }
148
+ finally {
149
+ if (!shared.lifecycleAbort.signal.aborted)
150
+ shared.lifecycleAbort.abort(reason);
151
+ }
152
+ })();
153
+ // Dismiss, timeout, and external cancellation are outcome-driven controls;
154
+ // keep their rejection observed while explicit abort still receives it.
155
+ shared.gracefulAbort.catch(() => undefined);
156
+ return shared.gracefulAbort;
180
157
  }
181
- function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, observerEnabled) {
158
+ function requestPolicyBudgetCleanup(shared, reason) {
159
+ const graceful = requestControlledGracefulAbort(shared, reason);
160
+ const force = () => {
161
+ void shared.live?.abort("force").catch(() => undefined);
162
+ };
163
+ const cleanupTimeout = setTimeout(force, POLICY_BUDGET_CLEANUP_GRACE_MS);
164
+ cleanupTimeout.unref?.();
165
+ void graceful.then(() => clearTimeout(cleanupTimeout), () => {
166
+ clearTimeout(cleanupTimeout);
167
+ force();
168
+ });
169
+ }
170
+ function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled) {
171
+ if (shared.terminal)
172
+ return;
182
173
  const prepared = prepareAgentEventForPersistence(event);
183
174
  if (observerEnabled.value && shared.observer) {
184
175
  try {
@@ -197,24 +188,63 @@ function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, obse
197
188
  }
198
189
  }
199
190
  shared.publicEvents.push(event);
191
+ if (event.type === "activity") {
192
+ if (event.phase === "started")
193
+ knownActiveTools.add(event.id);
194
+ if (event.phase === "completed")
195
+ knownActiveTools.delete(event.id);
196
+ }
200
197
  refreshIdleTimeout();
201
198
  processEvent(state, event);
202
199
  }
203
200
  async function settleProjection(shared) {
204
201
  const state = createOutcomeState(shared.terms.maxCaptureChars);
205
202
  let idleTimedOut = false;
203
+ let transportFailure;
204
+ let policyBudgetExceeded = false;
205
+ const knownActiveTools = new Set();
206
206
  let abortRequested = false;
207
207
  const observerEnabled = { value: shared.observer !== undefined };
208
208
  let idleTimeout;
209
+ let executionTimeout;
210
+ let liveDriver;
211
+ let probeInFlight;
212
+ let resolvePolicyBudget;
213
+ const policyBudget = new Promise((resolve) => {
214
+ resolvePolicyBudget = resolve;
215
+ });
209
216
  const refreshIdleTimeout = () => {
217
+ if (shared.terminal)
218
+ return;
210
219
  if (idleTimeout)
211
220
  clearTimeout(idleTimeout);
212
221
  idleTimeout = setTimeout(() => {
222
+ if (knownActiveTools.size > 0) {
223
+ if (!liveDriver?.probe || probeInFlight)
224
+ return;
225
+ probeInFlight = liveDriver.probe()
226
+ .then(() => refreshIdleTimeout())
227
+ .catch((error) => {
228
+ transportFailure = error;
229
+ return requestControlledGracefulAbort(shared, error);
230
+ })
231
+ .finally(() => {
232
+ probeInFlight = undefined;
233
+ });
234
+ probeInFlight.catch(() => undefined);
235
+ return;
236
+ }
213
237
  idleTimedOut = true;
214
- shared.lifecycleAbort.abort(createAbortError(`${shared.terms.provider} exec idle timed out`));
238
+ requestControlledGracefulAbort(shared, createAbortError(`${shared.terms.provider} exec idle timed out`));
215
239
  }, shared.terms.idleTimeoutMs);
216
240
  idleTimeout.unref?.();
217
241
  };
242
+ executionTimeout = setTimeout(() => {
243
+ policyBudgetExceeded = true;
244
+ requestPolicyBudgetCleanup(shared, createAbortError(`${shared.terms.provider} execution policy budget expired`));
245
+ resolvePolicyBudget();
246
+ }, shared.terms.executionTimeoutMs);
247
+ executionTimeout.unref?.();
218
248
  refreshIdleTimeout();
219
249
  const requestLiveAbort = () => {
220
250
  if (shared.explicitlyAborted)
@@ -222,89 +252,114 @@ async function settleProjection(shared) {
222
252
  if (abortRequested)
223
253
  return;
224
254
  abortRequested = true;
225
- requestDismissAbort(shared.live);
255
+ requestControlledGracefulAbort(shared, shared.lifecycleAbort.signal.reason);
226
256
  };
227
257
  shared.lifecycleAbort.signal.addEventListener("abort", requestLiveAbort, { once: true });
228
- try {
229
- const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer);
230
- shared.onLive(live);
231
- resolveForceWaiters(shared, live);
232
- if (shared.lifecycleAbort.signal.aborted) {
233
- requestLiveAbort();
234
- }
258
+ const providerSettlement = (async () => {
235
259
  try {
236
- for await (const event of live.events) {
237
- deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, observerEnabled);
260
+ const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer);
261
+ liveDriver = live;
262
+ shared.onLive(live);
263
+ resolveForceWaiters(shared, live);
264
+ if (shared.lifecycleAbort.signal.aborted) {
265
+ requestLiveAbort();
238
266
  }
239
- }
240
- catch (error) {
241
- if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
242
- const event = live.mintEvent({
243
- type: "fail",
244
- code: "PROJECTION_STATE_ERROR",
245
- message: error.message,
246
- });
247
- if (event)
248
- deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, observerEnabled);
267
+ try {
268
+ for await (const event of live.events) {
269
+ deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
270
+ }
249
271
  }
250
- else {
251
- const event = live.mintEvent({
252
- type: "diagnostic",
253
- text: `pump error: ${String(error)}`,
254
- });
255
- if (event)
256
- deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, observerEnabled);
272
+ catch (error) {
273
+ if (error instanceof Error && error.code === "PROJECTION_STATE_ERROR") {
274
+ const event = live.mintEvent({
275
+ type: "fail",
276
+ code: "PROJECTION_STATE_ERROR",
277
+ message: error.message,
278
+ });
279
+ if (event)
280
+ deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
281
+ }
282
+ else {
283
+ const event = live.mintEvent({
284
+ type: "diagnostic",
285
+ text: `pump error: ${String(error)}`,
286
+ });
287
+ if (event)
288
+ deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, knownActiveTools, observerEnabled);
289
+ }
257
290
  }
291
+ const completionResult = await live.completion.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
292
+ const completionOutcome = completionResult.ok ? completionResult.value : undefined;
293
+ // Outcome classification is the terminal publication boundary. A
294
+ // controlled abort may let the provider run settle before its graceful
295
+ // cleanup promise does, so prove that shared settlement before publishing
296
+ // dismissed, failed-for-another-reason, or completed truth.
297
+ const gracefulResult = shared.gracefulAbort
298
+ ? await shared.gracefulAbort.then(() => ({ ok: true }), (error) => ({ ok: false, error }))
299
+ : undefined;
300
+ if (gracefulResult && !gracefulResult.ok && !transportFailure) {
301
+ return classifySettlementFailure(shared, state, gracefulResult.error);
302
+ }
303
+ if (policyBudgetExceeded || transportFailure) {
304
+ return classifyFailure(shared, state, completionResult.ok ? undefined : completionResult.error, idleTimedOut, transportFailure, policyBudgetExceeded);
305
+ }
306
+ if (state.failEvent) {
307
+ return failFromFailEvent(shared, state, state.failEvent);
308
+ }
309
+ if (!completionResult.ok) {
310
+ return classifyFailure(shared, state, completionResult.error, idleTimedOut, transportFailure, policyBudgetExceeded);
311
+ }
312
+ if (shared.lifecycleAbort.signal.aborted) {
313
+ return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, idleTimedOut, transportFailure, policyBudgetExceeded);
314
+ }
315
+ if (!state.doneEvent && !completionOutcome) {
316
+ return failWithError(shared, state, {
317
+ code: "SUBAGENT_EXEC_ERROR",
318
+ message: `${shared.terms.provider} exec ended before completion`,
319
+ timeoutMs: null,
320
+ exitCode: null,
321
+ }, state.finalMessageCandidate);
322
+ }
323
+ const finalMessage = state.doneEvent?.finalMessage ??
324
+ completionOutcome?.finalMessage ??
325
+ state.finalMessageCandidate ??
326
+ renderOutcomeCapturedText(state.outputBuffer).trim();
327
+ const session = state.doneEvent?.session ?? completionOutcome?.session;
328
+ if (!session) {
329
+ return failWithError(shared, state, {
330
+ code: "SUBAGENT_EXEC_ERROR",
331
+ message: `${shared.terms.provider} exec ended without a session handle`,
332
+ timeoutMs: null,
333
+ exitCode: null,
334
+ }, finalMessage);
335
+ }
336
+ return completeOutcome(shared, state, finalMessage, session);
258
337
  }
259
- const completionResult = await live.completion.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
260
- const completionOutcome = completionResult.ok ? completionResult.value : undefined;
261
- if (state.failEvent) {
262
- return failFromFailEvent(shared, state, state.failEvent);
263
- }
264
- if (!completionResult.ok) {
265
- return classifyFailure(shared, state, completionResult.error, idleTimedOut);
266
- }
267
- if (shared.lifecycleAbort.signal.aborted) {
268
- return classifyFailure(shared, state, shared.lifecycleAbort.signal.reason, idleTimedOut);
269
- }
270
- if (!state.doneEvent && !completionOutcome) {
271
- return failWithError(shared, state, {
272
- code: "SUBAGENT_EXEC_ERROR",
273
- message: `${shared.terms.provider} exec ended before completion`,
274
- timeoutMs: null,
275
- exitCode: null,
276
- }, state.finalMessageCandidate);
277
- }
278
- const finalMessage = state.doneEvent?.finalMessage ??
279
- completionOutcome?.finalMessage ??
280
- state.finalMessageCandidate ??
281
- renderOutcomeCapturedText(state.outputBuffer).trim();
282
- const session = state.doneEvent?.session ?? completionOutcome?.session;
283
- if (!session) {
284
- return failWithError(shared, state, {
285
- code: "SUBAGENT_EXEC_ERROR",
286
- message: `${shared.terms.provider} exec ended without a session handle`,
287
- timeoutMs: null,
288
- exitCode: null,
289
- }, finalMessage);
290
- }
291
- return completeOutcome(shared, state, finalMessage, session);
292
- }
293
- catch (error) {
294
- if (error instanceof FlowError) {
295
- throw error;
338
+ catch (error) {
339
+ if (error instanceof FlowError) {
340
+ throw error;
341
+ }
342
+ return classifyFailure(shared, state, error, idleTimedOut, transportFailure, policyBudgetExceeded);
296
343
  }
297
- return classifyFailure(shared, state, error, idleTimedOut);
344
+ })();
345
+ providerSettlement.catch(() => undefined);
346
+ try {
347
+ const settled = await Promise.race([
348
+ providerSettlement.then((outcome) => ({ kind: "provider", outcome })),
349
+ policyBudget.then(() => ({ kind: "policy-budget" })),
350
+ ]);
351
+ return settled.kind === "provider"
352
+ ? settled.outcome
353
+ : classifyFailure(shared, state, undefined, idleTimedOut, transportFailure, true);
298
354
  }
299
355
  finally {
300
356
  if (idleTimeout)
301
357
  clearTimeout(idleTimeout);
358
+ if (executionTimeout)
359
+ clearTimeout(executionTimeout);
302
360
  shared.lifecycleAbort.signal.removeEventListener("abort", requestLiveAbort);
303
361
  shared.terminal = true;
304
362
  resolveForceWaiters(shared, undefined);
305
363
  shared.publicEvents.close();
306
364
  }
307
365
  }
308
- function assertNever(_value) {
309
- throw new Error("Unhandled provider");
310
- }
@@ -0,0 +1,58 @@
1
+ import { SubagentFailure, } from "../types.js";
2
+ /**
3
+ * Typed rejection carrier for failed/aborted adapter settlement.
4
+ * Abort keeps name "AbortError" so existing name checks and CLI exit codes hold.
5
+ */
6
+ export class ProviderTerminalError extends Error {
7
+ fact;
8
+ constructor(fact, options) {
9
+ super(fact.message, options?.cause === undefined ? undefined : { cause: options.cause });
10
+ this.name = fact.kind === "aborted" ? "AbortError" : "ProviderTerminalError";
11
+ this.fact = fact;
12
+ }
13
+ }
14
+ export function isProviderTerminalError(error) {
15
+ return error instanceof ProviderTerminalError;
16
+ }
17
+ export function createProviderFailure(message, options = {}) {
18
+ return new ProviderTerminalError({
19
+ kind: "failed",
20
+ code: options.code ?? "SUBAGENT_EXEC_ERROR",
21
+ message,
22
+ timeoutMs: options.timeoutMs ?? null,
23
+ exitCode: options.exitCode ?? null,
24
+ ...(options.diagnostics !== undefined ? { diagnostics: options.diagnostics } : {}),
25
+ ...(options.output !== undefined ? { output: options.output } : {}),
26
+ ...(options.publicCause !== undefined ? { publicCause: options.publicCause } : {}),
27
+ }, options.cause === undefined ? undefined : { cause: options.cause });
28
+ }
29
+ /**
30
+ * Abort-fact constructor. Abort semantics for every live adapter:
31
+ * - abort("graceful") requests provider-native interrupt and keeps local signals
32
+ * open until native settlement proof (or bounded abort-settlement timeout);
33
+ * - abort("force") must establish zero later provider side effects or reject;
34
+ * - run settlement for a requested abort exits as kind "aborted" (name AbortError).
35
+ */
36
+ export function createAbortError(message, cause) {
37
+ return new ProviderTerminalError({ kind: "aborted", message }, cause === undefined ? undefined : { cause });
38
+ }
39
+ /** Normalize any thrown value into the typed failure/abort egress fact. */
40
+ export function toProviderTerminalError(error) {
41
+ if (error instanceof ProviderTerminalError)
42
+ return error;
43
+ if (error instanceof SubagentFailure) {
44
+ return new ProviderTerminalError({
45
+ kind: "failed",
46
+ code: error.code,
47
+ message: error.message,
48
+ timeoutMs: error.timeoutMs,
49
+ exitCode: error.exitCode,
50
+ diagnostics: error.diagnostics,
51
+ output: error.output,
52
+ }, { cause: error });
53
+ }
54
+ if (error instanceof Error && error.name === "AbortError") {
55
+ return new ProviderTerminalError({ kind: "aborted", message: error.message }, { cause: error });
56
+ }
57
+ return createProviderFailure(error instanceof Error ? error.message : String(error), { cause: error instanceof Error ? error : undefined });
58
+ }