@hassangameryt/freeflow 0.8.0 → 0.9.0

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 (290) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,178 @@
1
+ // A local relay between a Claude Code subject and Anthropic's API that records what each model request carried.
2
+ // Claude Code has no extension point that sees its requests, and it adds context of its own (an identity line, an
3
+ // environment note, reminders) even when the evaluator replaces its system prompt. So every subject's requests pass
4
+ // through here: the run keeps a host-context record of what Claude Code sent besides the evaluator's prompt, and,
5
+ // when a group checks context text, the same observations Pi's observer writes.
6
+
7
+ import { createHash } from "node:crypto";
8
+ import { appendFile, writeFile } from "node:fs/promises";
9
+ import http from "node:http";
10
+ import https from "node:https";
11
+
12
+ const DEFAULT_UPSTREAM = "https://api.anthropic.com";
13
+
14
+ function sha256(value) {
15
+ return createHash("sha256").update(value).digest("hex");
16
+ }
17
+
18
+ function blockText(block) {
19
+ if (typeof block === "string") return block;
20
+ if (!block || typeof block !== "object") return "";
21
+ if (block.type === "text" && typeof block.text === "string") return block.text;
22
+ if (block.type === "tool_result") {
23
+ if (typeof block.content === "string") return block.content;
24
+ if (Array.isArray(block.content)) return block.content.map(blockText).filter(Boolean).join("\n");
25
+ }
26
+ if (block.type === "image") return `[image:${block.source?.media_type ?? "unknown"}]`;
27
+ return "";
28
+ }
29
+
30
+ function contentText(content) {
31
+ if (typeof content === "string") return content;
32
+ if (!Array.isArray(content)) return "";
33
+ return content.map(blockText).filter(Boolean).join("\n");
34
+ }
35
+
36
+ function systemText(system) {
37
+ if (typeof system === "string") return system;
38
+ if (!Array.isArray(system)) return "";
39
+ return system.map(blockText).join("\n");
40
+ }
41
+
42
+ /**
43
+ * Starts the relay. `currentTurn()` names the session's turn when a request arrives. Context observations go to
44
+ * `observationPath` when it is set; the host-context record is written to `hostContextPath` on close.
45
+ *
46
+ * @param {{upstream?: string, observationPath: string | null, hostContextPath: string, currentTurn: () => number}} options
47
+ */
48
+ export async function startRecorder({ upstream = DEFAULT_UPSTREAM, observationPath, hostContextPath, currentTurn }) {
49
+ const target = new URL(upstream);
50
+ const transport = target.protocol === "http:" ? http : https;
51
+ const basePath = target.pathname.replace(/\/$/, "");
52
+ const requests = [];
53
+ const systems = {};
54
+ let first = null;
55
+ let providerRequest = 0;
56
+ let lastTurn = 0;
57
+ let requestInTurn = 0;
58
+ let writes = Promise.resolve();
59
+ const observe = (entry) => {
60
+ if (observationPath === null) return;
61
+ const line = `${JSON.stringify({ timestamp: new Date().toISOString(), ...entry })}\n`;
62
+ writes = writes.then(() => appendFile(observationPath, line, "utf8"));
63
+ };
64
+
65
+ const record = (request, body) => {
66
+ let parsed;
67
+ try {
68
+ parsed = JSON.parse(body.toString("utf8"));
69
+ } catch {
70
+ requests.push({ method: request.method, path: request.url, bytes: body.length });
71
+ return;
72
+ }
73
+ const system = systemText(parsed.system);
74
+ const systemSha = sha256(system);
75
+ systems[systemSha] = Array.isArray(parsed.system) ? parsed.system.map(blockText) : [system];
76
+ const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
77
+ requests.push({
78
+ method: request.method,
79
+ path: request.url,
80
+ model: parsed.model ?? null,
81
+ system: systemSha,
82
+ tools: Array.isArray(parsed.tools) ? parsed.tools.map((tool) => tool?.name ?? null) : [],
83
+ messages: messages.length,
84
+ roles: [...new Set(messages.map((message) => message?.role ?? null))],
85
+ });
86
+ if (!request.url.startsWith("/v1/messages") || request.url.includes("count_tokens")) return;
87
+ // The first request shows everything Claude Code put before the subject's first prompt.
88
+ first ??= {
89
+ system: systems[systemSha],
90
+ messages: messages.map((message) => ({ role: message?.role ?? null, text: contentText(message?.content) })),
91
+ };
92
+ const turn = currentTurn();
93
+ if (turn !== lastTurn) {
94
+ lastTurn = turn;
95
+ requestInTurn = 0;
96
+ observe({
97
+ turn,
98
+ kind: "context",
99
+ surface: "system-prompt",
100
+ prompt: null,
101
+ characters: system.length,
102
+ text: system,
103
+ systemPrompt: system,
104
+ });
105
+ }
106
+ providerRequest += 1;
107
+ requestInTurn += 1;
108
+ const normalized = messages.map((message) => ({
109
+ role: message?.role ?? null,
110
+ toolName: null,
111
+ text: contentText(message?.content),
112
+ }));
113
+ const text = normalized
114
+ .map((message) => message.text)
115
+ .filter(Boolean)
116
+ .join("\n");
117
+ observe({
118
+ turn,
119
+ kind: "context",
120
+ surface: "provider-context",
121
+ providerRequest,
122
+ requestInTurn,
123
+ characters: text.length,
124
+ text,
125
+ messages: normalized,
126
+ });
127
+ };
128
+
129
+ const server = http.createServer((request, response) => {
130
+ const chunks = [];
131
+ request.on("data", (chunk) => chunks.push(chunk));
132
+ request.on("end", () => {
133
+ const body = Buffer.concat(chunks);
134
+ if (request.method === "POST") record(request, body);
135
+ else requests.push({ method: request.method, path: request.url });
136
+ const headers = { ...request.headers, host: target.host };
137
+ const relayed = transport.request(
138
+ {
139
+ protocol: target.protocol,
140
+ hostname: target.hostname,
141
+ port: target.port || undefined,
142
+ path: `${basePath}${request.url}`,
143
+ method: request.method,
144
+ headers,
145
+ },
146
+ (upstreamResponse) => {
147
+ response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers);
148
+ upstreamResponse.pipe(response);
149
+ },
150
+ );
151
+ relayed.on("error", (error) => {
152
+ if (!response.headersSent) response.writeHead(502, { "content-type": "text/plain" });
153
+ response.end(`relay error: ${error.message}`);
154
+ });
155
+ relayed.end(body);
156
+ });
157
+ });
158
+ await new Promise((resolve, reject) => {
159
+ server.once("error", reject);
160
+ server.listen(0, "127.0.0.1", resolve);
161
+ });
162
+
163
+ return {
164
+ url: `http://127.0.0.1:${server.address().port}`,
165
+ /** Stops the relay and writes the host-context record, with what Claude Code reported loading (`init`). */
166
+ async close({ init = null } = {}) {
167
+ await new Promise((resolve) => {
168
+ server.closeAllConnections?.();
169
+ server.close(() => resolve());
170
+ });
171
+ await writes;
172
+ await writeFile(
173
+ hostContextPath,
174
+ `${JSON.stringify({ schema_version: 1, upstream: target.origin, init, first, requests, systems }, null, 2)}\n`,
175
+ );
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,494 @@
1
+ // A Claude Code process in headless stream-json mode: the evaluator writes each prompt as a user message on stdin,
2
+ // reads Claude Code's events from stdout, and sends the next prompt after each turn's `result`. Events are kept as
3
+ // Claude Code wrote them (events.jsonl) and normalized into the observation every host returns (see subject.mjs):
4
+ // tool calls under the evaluator's tool names, with Claude Code's own names and arguments kept beside them.
5
+
6
+ import { spawn } from "node:child_process";
7
+ import { once } from "node:events";
8
+ import { createWriteStream } from "node:fs";
9
+ import path from "node:path";
10
+ import { StringDecoder } from "node:string_decoder";
11
+
12
+ import {
13
+ mergeUsage,
14
+ messageText,
15
+ NO_PROGRESS_MS,
16
+ pipeStream,
17
+ processGroupExists,
18
+ signalProcessTree,
19
+ TERMINATION_GRACE_MS,
20
+ waitForForcedKill,
21
+ waitForProcessTreeExit,
22
+ writeStream,
23
+ } from "../../process.mjs";
24
+ import { startRecorder } from "./recorder.mjs";
25
+ import { readSkill, skillCommandPrompt } from "./skills.mjs";
26
+
27
+ // Claude Code's tool names, the evaluator's, and the argument holding the path. Glob is find when the group declared
28
+ // find, and ls otherwise.
29
+ const TOOL_NAMES = { Read: "read", Write: "write", Edit: "edit", Grep: "grep", Bash: "bash" };
30
+ const PATH_ARGUMENT = { Read: "file_path", Write: "file_path", Edit: "file_path", Glob: "path", Grep: "path" };
31
+
32
+ /** The evaluator's name and arguments for one of Claude Code's tool calls. */
33
+ export function evaluatorToolCall(name, input, declaredTools) {
34
+ const toolName =
35
+ name === "Glob"
36
+ ? declaredTools.includes("find") || !declaredTools.includes("ls")
37
+ ? "find"
38
+ : "ls"
39
+ : TOOL_NAMES[name];
40
+ const key = PATH_ARGUMENT[name];
41
+ let args = input ?? null;
42
+ if (key !== undefined && args !== null && typeof args === "object") {
43
+ const { [key]: value, ...rest } = args;
44
+ args = value === undefined ? rest : { path: value, ...rest };
45
+ }
46
+ return { toolName: toolName ?? name, args };
47
+ }
48
+
49
+ function argumentValue(args, flag) {
50
+ const index = args.indexOf(flag);
51
+ return index === -1 ? undefined : args[index + 1];
52
+ }
53
+
54
+ function usageFrom(result, previousCost) {
55
+ const usage = result.usage ?? {};
56
+ const cost = typeof result.total_cost_usd === "number" ? Math.max(0, result.total_cost_usd - previousCost) : 0;
57
+ return {
58
+ input: usage.input_tokens ?? 0,
59
+ output: usage.output_tokens ?? 0,
60
+ cacheRead: usage.cache_read_input_tokens ?? 0,
61
+ cacheWrite: usage.cache_creation_input_tokens ?? 0,
62
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: cost },
63
+ };
64
+ }
65
+
66
+ /** A persistent session over the declared prompts, then any `nextPrompt` replies. */
67
+ export async function runClaudeSession(options) {
68
+ return new ClaudeSession(options).run(options.prompts, options.targetPath ?? null);
69
+ }
70
+
71
+ /** One prompt, reported as a one-shot run: the first turn's reads and tool calls at the top level. */
72
+ export async function runClaudeOneShot(options) {
73
+ const observation = await new ClaudeSession(options).run(options.prompts, null);
74
+ const turn = observation.turns[0];
75
+ const { turns: _turns, delivery: _delivery, ...rest } = observation;
76
+ return { ...rest, successfulReads: turn?.successfulReads ?? [], toolActivity: turn?.toolActivity ?? [] };
77
+ }
78
+
79
+ class ClaudeSession {
80
+ constructor({
81
+ subject = null,
82
+ command = "claude",
83
+ args,
84
+ cwd,
85
+ eventsFile,
86
+ stderrFile,
87
+ signal,
88
+ environment,
89
+ afterTurn = null,
90
+ nextPrompt = null,
91
+ record = subject === null
92
+ ? null
93
+ : {
94
+ observationPath: subject.requiresContextObservation ? subject.contextObservationPath : null,
95
+ hostContextPath: path.join(path.dirname(eventsFile), "host-context.json"),
96
+ },
97
+ }) {
98
+ this.command = command;
99
+ this.args = args;
100
+ this.cwd = cwd;
101
+ this.environment = environment;
102
+ this.record = record;
103
+ this.signal = signal;
104
+ this.afterTurn = afterTurn;
105
+ this.nextPrompt = nextPrompt;
106
+ this.eventsFile = eventsFile;
107
+ this.stderrFile = stderrFile;
108
+ const tools = argumentValue(args, "--tools");
109
+ this.expectedTools = tools === undefined || tools === "" ? [] : tools.split(",");
110
+ this.declaredTools = JSON.parse(environment.SKILL_EVAL_ALLOWED_TOOLS ?? "[]");
111
+ this.turns = [];
112
+ this.transcript = [];
113
+ this.parseErrors = [];
114
+ this.protocolErrors = [];
115
+ this.activeTurn = null;
116
+ this.turnNumber = 0;
117
+ this.init = null;
118
+ this.observedModel = null;
119
+ this.reportedCost = 0;
120
+ this.terminationReason = null;
121
+ this.operationError = null;
122
+ this.decoder = new StringDecoder("utf8");
123
+ this.buffer = "";
124
+ this.eventLine = 0;
125
+ this.watchdog = undefined;
126
+ this.forcedKill = undefined;
127
+ /** @type {Promise<void> | null} */
128
+ this.forcedKillPromise = null;
129
+ }
130
+
131
+ async start() {
132
+ let environment = this.environment;
133
+ if (this.record !== null) {
134
+ this.recorder = await startRecorder({
135
+ upstream: this.environment.ANTHROPIC_BASE_URL,
136
+ observationPath: this.record.observationPath,
137
+ hostContextPath: this.record.hostContextPath,
138
+ currentTurn: () => this.turnNumber,
139
+ });
140
+ // The relay is on this machine, so a proxy the machine uses must not stand between them.
141
+ const noProxy = [this.environment.NO_PROXY, "127.0.0.1", "localhost"].filter(Boolean).join(",");
142
+ environment = { ...environment, ANTHROPIC_BASE_URL: this.recorder.url, NO_PROXY: noProxy, no_proxy: noProxy };
143
+ }
144
+ this.child = spawn(this.command, this.args, {
145
+ cwd: this.cwd,
146
+ detached: process.platform !== "win32",
147
+ env: environment,
148
+ shell: false,
149
+ stdio: ["pipe", "pipe", "pipe"],
150
+ });
151
+ this.processGroupId = this.child.pid;
152
+ this.events = createWriteStream(this.eventsFile, { encoding: "utf8" });
153
+ this.stderr = createWriteStream(this.stderrFile, { encoding: "utf8" });
154
+ this.onAbort = () => this.requestTermination("cancelled");
155
+ if (this.signal?.aborted) this.onAbort();
156
+ else this.signal?.addEventListener("abort", this.onAbort, { once: true });
157
+ this.resetWatchdog();
158
+ this.closed = new Promise((resolve, reject) => {
159
+ this.child.once("error", reject);
160
+ this.child.once("close", (exitCode, exitSignal) => {
161
+ this.activeTurn?.rejectSettled(new Error("Claude Code exited before the turn completed"));
162
+ resolve({ exitCode, exitSignal });
163
+ });
164
+ });
165
+ // A write to a process that already exited is reported by its exit, not as an unhandled error.
166
+ this.child.stdin.on("error", () => {});
167
+ this.stderrTask = pipeStream(this.child.stderr, this.stderr, () => this.resetWatchdog());
168
+ this.stdoutTask = this.readStdout();
169
+ }
170
+
171
+ async run(prompts, targetPath) {
172
+ let deliveredPrompts = [...prompts];
173
+ /** @type {any} */
174
+ let delivery = { kind: "unavailable", turn: 1, targetPath };
175
+ try {
176
+ if (targetPath !== null) {
177
+ // Claude Code's skill loader stays off, so the target skill arrives as Pi's skill command would expand it.
178
+ const skill = await readSkill(path.dirname(targetPath));
179
+ deliveredPrompts = [skillCommandPrompt(skill, prompts[0]), ...prompts.slice(1)];
180
+ delivery = { kind: "explicit-skill-command", turn: 1, targetPath, command: `skill:${skill.name}` };
181
+ }
182
+ await this.start();
183
+ for (let index = 0; index < deliveredPrompts.length; index += 1) {
184
+ if (this.signal?.aborted || this.terminationReason !== null) break;
185
+ if (delivery.kind === "unavailable") delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
186
+ const turn = await this.runTurn(index + 1, deliveredPrompts[index]);
187
+ this.turns.push(turn);
188
+ if (this.afterTurn !== null) turn.workspace = await this.afterTurn(turn.turn);
189
+ if (turn.assistantError !== null) break;
190
+ if (this.nextPrompt !== null && index === deliveredPrompts.length - 1 && this.terminationReason === null) {
191
+ const next = await this.nextPrompt(this.turns);
192
+ // The simulated user's time is not the subject's lack of progress.
193
+ this.resetWatchdog();
194
+ if (typeof next === "string") deliveredPrompts.push(next);
195
+ }
196
+ }
197
+ } catch (error) {
198
+ this.operationError = error;
199
+ if (this.terminationReason === null) {
200
+ this.protocolErrors.push({
201
+ reason: "operation-failed",
202
+ message: error instanceof Error ? error.message : String(error),
203
+ });
204
+ if (this.child !== undefined) this.requestTermination("protocol-error");
205
+ }
206
+ } finally {
207
+ if (this.child !== undefined && !this.child.stdin.destroyed) this.child.stdin.end();
208
+ }
209
+
210
+ const { exitCode, exitSignal } =
211
+ this.child === undefined ? { exitCode: null, exitSignal: null } : await this.finish();
212
+ this.finalizeInterruptedTurn();
213
+ this.recordUnclassifiedProcessError();
214
+ return { ...this.observation(deliveredPrompts, exitCode, exitSignal), delivery };
215
+ }
216
+
217
+ async runTurn(turnNumber, prompt) {
218
+ let resolveSettled;
219
+ let rejectSettled;
220
+ const settlement = new Promise((resolve, reject) => {
221
+ resolveSettled = resolve;
222
+ rejectSettled = reject;
223
+ });
224
+ // Awaited below; a rejection that arrives while the prompt is still being written is handled there.
225
+ settlement.catch(() => {});
226
+ this.turnNumber = turnNumber;
227
+ this.activeTurn = {
228
+ turn: turnNumber,
229
+ prompt,
230
+ response: "",
231
+ transcript: [],
232
+ messages: new Map(),
233
+ successfulReads: [],
234
+ toolActivity: [],
235
+ calls: new Map(),
236
+ promptAccepted: false,
237
+ usage: null,
238
+ assistantError: null,
239
+ settled: false,
240
+ resolveSettled,
241
+ rejectSettled,
242
+ };
243
+ const entry = { role: "user", content: prompt };
244
+ this.activeTurn.transcript.push(entry);
245
+ this.transcript.push(entry);
246
+ this.resetWatchdog();
247
+ const message = { type: "user", message: { role: "user", content: prompt } };
248
+ await writeStream(this.child.stdin, `${JSON.stringify(message)}\n`);
249
+ await settlement;
250
+ const result = finishTurn(this.activeTurn);
251
+ this.activeTurn = null;
252
+ return result;
253
+ }
254
+
255
+ async readStdout() {
256
+ for await (const chunk of this.child.stdout) {
257
+ this.resetWatchdog();
258
+ this.buffer += this.decoder.write(chunk);
259
+ while (true) {
260
+ const newline = this.buffer.indexOf("\n");
261
+ if (newline === -1) break;
262
+ const line = this.buffer.slice(0, newline);
263
+ this.buffer = this.buffer.slice(newline + 1);
264
+ await writeStream(this.events, `${line}\n`);
265
+ this.observeLine(line.endsWith("\r") ? line.slice(0, -1) : line);
266
+ }
267
+ }
268
+ this.buffer += this.decoder.end();
269
+ if (this.buffer.length > 0) {
270
+ await writeStream(this.events, this.buffer);
271
+ this.parseErrors.push({ line: this.eventLine + 1, reason: "unterminated-record" });
272
+ }
273
+ this.events.end();
274
+ await once(this.events, "finish");
275
+ }
276
+
277
+ observeLine(line) {
278
+ this.eventLine += 1;
279
+ if (line.trim() === "") return;
280
+ let event;
281
+ try {
282
+ event = JSON.parse(line);
283
+ } catch {
284
+ this.parseErrors.push({ line: this.eventLine, reason: "invalid-json" });
285
+ this.failProtocol("invalid-json", { line: this.eventLine });
286
+ return;
287
+ }
288
+ if (event.type === "system" && event.subtype === "init") this.observeInit(event);
289
+ else if (this.activeTurn === null) return;
290
+ else if (event.type === "assistant") this.observeAssistant(event.message);
291
+ else if (event.type === "user") this.observeUser(event.message);
292
+ else if (event.type === "result") this.observeResult(event);
293
+ }
294
+
295
+ // Claude Code reports what it loaded; anything beyond the declared tools, or any MCP server, voids the run.
296
+ observeInit(event) {
297
+ this.init = {
298
+ version: event.claude_code_version ?? null,
299
+ model: event.model ?? null,
300
+ tools: event.tools ?? [],
301
+ mcpServers: event.mcp_servers ?? [],
302
+ skills: event.skills ?? [],
303
+ slashCommands: event.slash_commands ?? [],
304
+ agents: event.agents ?? [],
305
+ plugins: event.plugins ?? [],
306
+ permissionMode: event.permissionMode ?? null,
307
+ apiKeySource: event.apiKeySource ?? null,
308
+ outputStyle: event.output_style ?? null,
309
+ };
310
+ const tools = Array.isArray(event.tools) ? event.tools : [];
311
+ const extra = tools.filter((tool) => !this.expectedTools.includes(tool));
312
+ const missing = this.expectedTools.filter((tool) => !tools.includes(tool));
313
+ if (extra.length > 0 || missing.length > 0) this.failProtocol("host-tools-differ", { extra, missing });
314
+ if (Array.isArray(event.mcp_servers) && event.mcp_servers.length > 0) {
315
+ this.failProtocol("undeclared-mcp-servers", { servers: event.mcp_servers.map((server) => server?.name ?? null) });
316
+ }
317
+ }
318
+
319
+ // Claude Code reports each content block of an assistant message as its own event; they are one message.
320
+ observeAssistant(message) {
321
+ if (!message || typeof message !== "object") return;
322
+ const turn = this.activeTurn;
323
+ turn.promptAccepted = true;
324
+ this.observedModel = { provider: "anthropic", model: message.model ?? null };
325
+ let entry = typeof message.id === "string" ? turn.messages.get(message.id) : undefined;
326
+ if (entry === undefined) {
327
+ entry = { role: "assistant", id: message.id ?? null, model: message.model ?? null, content: [] };
328
+ if (typeof message.id === "string") turn.messages.set(message.id, entry);
329
+ turn.transcript.push(entry);
330
+ this.transcript.push(entry);
331
+ }
332
+ entry.stopReason = message.stop_reason ?? entry.stopReason ?? null;
333
+ for (const block of Array.isArray(message.content) ? message.content : []) {
334
+ entry.content.push(block);
335
+ if (block?.type !== "tool_use") continue;
336
+ const { toolName, args } = evaluatorToolCall(block.name, block.input, this.declaredTools);
337
+ const activity = {
338
+ toolCallId: block.id ?? null,
339
+ toolName,
340
+ args,
341
+ hostToolName: block.name ?? null,
342
+ hostArgs: block.input ?? null,
343
+ completed: false,
344
+ isError: null,
345
+ };
346
+ turn.toolActivity.push(activity);
347
+ if (typeof block.id === "string") turn.calls.set(block.id, activity);
348
+ }
349
+ }
350
+
351
+ observeUser(message) {
352
+ if (!message || typeof message !== "object") return;
353
+ const turn = this.activeTurn;
354
+ const entry = { role: "user", content: message.content };
355
+ turn.transcript.push(entry);
356
+ this.transcript.push(entry);
357
+ for (const block of Array.isArray(message.content) ? message.content : []) {
358
+ if (block?.type !== "tool_result") continue;
359
+ const activity = turn.calls.get(block.tool_use_id);
360
+ if (!activity) continue;
361
+ activity.completed = true;
362
+ activity.isError = block.is_error === true;
363
+ if (!activity.isError && activity.toolName === "read" && typeof activity.args?.path === "string") {
364
+ turn.successfulReads.push(activity.args.path);
365
+ }
366
+ }
367
+ }
368
+
369
+ observeResult(event) {
370
+ const turn = this.activeTurn;
371
+ if (turn.settled) {
372
+ this.failProtocol("unexpected-result", { line: this.eventLine });
373
+ return;
374
+ }
375
+ turn.promptAccepted = true;
376
+ turn.usage = usageFrom(event, this.reportedCost);
377
+ if (typeof event.total_cost_usd === "number") this.reportedCost = event.total_cost_usd;
378
+ const lastAssistant = turn.transcript.filter((message) => message.role === "assistant").at(-1);
379
+ turn.response = typeof event.result === "string" ? event.result : lastAssistant ? messageText(lastAssistant) : "";
380
+ if (event.is_error === true || event.subtype !== "success") {
381
+ turn.assistantError =
382
+ (typeof event.result === "string" && event.result !== "" ? event.result : null) ??
383
+ (event.api_error_status ? `API error ${event.api_error_status}` : `Claude Code stopped with ${event.subtype}`);
384
+ }
385
+ turn.settled = true;
386
+ turn.resolveSettled();
387
+ }
388
+
389
+ failProtocol(reason, details = {}) {
390
+ this.protocolErrors.push({ reason, ...details });
391
+ this.activeTurn?.rejectSettled(new Error(`Claude Code protocol error: ${reason}`));
392
+ this.requestTermination("protocol-error");
393
+ }
394
+
395
+ resetWatchdog() {
396
+ clearTimeout(this.watchdog);
397
+ this.watchdog = setTimeout(() => this.requestTermination("no-progress-watchdog"), NO_PROGRESS_MS);
398
+ this.watchdog.unref();
399
+ }
400
+
401
+ requestTermination(reason) {
402
+ if (this.terminationReason !== null) return;
403
+ this.terminationReason = reason;
404
+ this.activeTurn?.rejectSettled(new Error(`Claude Code session terminated: ${reason}`));
405
+ if (this.child === undefined) return;
406
+ signalProcessTree(this.processGroupId, this.child, "SIGTERM");
407
+ if (process.platform !== "win32") {
408
+ this.forcedKillPromise = new Promise((resolve) => {
409
+ this.forcedKill = setTimeout(() => {
410
+ signalProcessTree(this.processGroupId, this.child, "SIGKILL");
411
+ resolve();
412
+ }, TERMINATION_GRACE_MS);
413
+ });
414
+ }
415
+ }
416
+
417
+ async finish() {
418
+ let exitCode = null;
419
+ let exitSignal = null;
420
+ try {
421
+ const closed = await this.closed;
422
+ exitCode = closed.exitCode;
423
+ exitSignal = closed.exitSignal;
424
+ await Promise.all([this.stdoutTask, this.stderrTask]);
425
+ } catch (error) {
426
+ this.operationError ??= error;
427
+ this.requestTermination("process-error");
428
+ await Promise.allSettled([this.stdoutTask, this.stderrTask]);
429
+ } finally {
430
+ clearTimeout(this.watchdog);
431
+ if (this.forcedKillPromise !== null) {
432
+ if (processGroupExists(this.processGroupId)) await waitForForcedKill(this.forcedKillPromise);
433
+ else clearTimeout(this.forcedKill);
434
+ } else if (processGroupExists(this.processGroupId)) {
435
+ signalProcessTree(this.processGroupId, this.child, "SIGTERM");
436
+ await waitForProcessTreeExit(this.processGroupId, this.child);
437
+ }
438
+ this.signal?.removeEventListener("abort", this.onAbort);
439
+ await this.recorder?.close({ init: this.init });
440
+ }
441
+ return { exitCode, exitSignal };
442
+ }
443
+
444
+ finalizeInterruptedTurn() {
445
+ const turn = this.activeTurn;
446
+ if (turn === null) return;
447
+ const observed = turn.promptAccepted || turn.settled || turn.transcript.length > 0 || turn.toolActivity.length > 0;
448
+ if (observed) this.turns.push(finishTurn(turn));
449
+ this.activeTurn = null;
450
+ }
451
+
452
+ recordUnclassifiedProcessError() {
453
+ if (this.operationError === null || this.terminationReason === "cancelled" || this.protocolErrors.length > 0) {
454
+ return;
455
+ }
456
+ this.protocolErrors.push({
457
+ reason: "process-error",
458
+ message: this.operationError instanceof Error ? this.operationError.message : String(this.operationError),
459
+ });
460
+ }
461
+
462
+ observation(prompts, exitCode, exitSignal) {
463
+ return {
464
+ exitCode,
465
+ signal: exitSignal,
466
+ settled: this.turns.length === prompts.length && this.turns.every((turn) => turn.settled),
467
+ assistantError: this.turns.find((turn) => turn.assistantError !== null)?.assistantError ?? null,
468
+ model: this.observedModel,
469
+ terminationReason: this.terminationReason,
470
+ parseErrors: this.parseErrors,
471
+ protocolErrors: this.protocolErrors,
472
+ turns: this.turns,
473
+ transcript: this.transcript,
474
+ response: this.turns.at(-1)?.response ?? "",
475
+ usage: this.turns.reduce((total, turn) => mergeUsage(total, turn.usage), null),
476
+ host: this.init,
477
+ };
478
+ }
479
+ }
480
+
481
+ function finishTurn(turn) {
482
+ return {
483
+ turn: turn.turn,
484
+ prompt: turn.prompt,
485
+ response: turn.response,
486
+ transcript: turn.transcript,
487
+ successfulReads: turn.successfulReads,
488
+ toolActivity: turn.toolActivity,
489
+ promptAccepted: turn.promptAccepted,
490
+ usage: turn.usage,
491
+ assistantError: turn.assistantError,
492
+ settled: turn.settled,
493
+ };
494
+ }