@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,241 @@
1
+ // How the evaluator launches Pi itself: the command-line arguments for a subject and the one-shot JSON-mode process
2
+ // that reports Pi's events. RPC sessions are in rpc.mjs.
3
+
4
+ import { spawn } from "node:child_process";
5
+ import { once } from "node:events";
6
+ import { createWriteStream } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ import {
10
+ mergeUsage,
11
+ messageText,
12
+ NO_PROGRESS_MS,
13
+ pipeStream,
14
+ processGroupExists,
15
+ signalProcessTree,
16
+ TERMINATION_GRACE_MS,
17
+ waitForForcedKill,
18
+ writeStream,
19
+ } from "../../process.mjs";
20
+
21
+ const guardExtension = fileURLToPath(new URL("../../../pi-guard.mjs", import.meta.url));
22
+
23
+ /** The evaluator's guard extension, which every subject process (and a shell's confinement probe) loads first. */
24
+ export function guardExtensionPath() {
25
+ return guardExtension;
26
+ }
27
+ const commandToolExtension = fileURLToPath(new URL("../../../pi-command-tool.mjs", import.meta.url));
28
+ const containerRouteExtension = fileURLToPath(new URL("../../../pi-container-route.mjs", import.meta.url));
29
+
30
+ /**
31
+ * The extension that moves shell commands into a subject's container. It loads after every other extension, so
32
+ * theirs see each command as the model wrote it; the guard refuses container shell calls without it.
33
+ */
34
+ export function containerRouteExtensionPath() {
35
+ return containerRouteExtension;
36
+ }
37
+ const observerExtension = fileURLToPath(new URL("../../../pi-observer.mjs", import.meta.url));
38
+
39
+ export function piArguments(
40
+ group,
41
+ environment,
42
+ runtime,
43
+ mode,
44
+ observeContext,
45
+ sessionDirectory,
46
+ model,
47
+ freeflowEntry,
48
+ hostExtensions = [],
49
+ containerRoute = false,
50
+ ) {
51
+ const args = ["--mode", mode];
52
+ if (mode === "json") args.push("-p");
53
+ if (runtime.session) args.push("--session-dir", sessionDirectory);
54
+ else args.push("--no-session");
55
+ args.push("--no-extensions", "--extension", guardExtension);
56
+ // Provider extensions (such as a subscription's auth extension) load right after the guard.
57
+ for (const extension of hostExtensions) args.push("--extension", extension.path);
58
+ if (group.commands !== undefined) args.push("--extension", commandToolExtension);
59
+ if (freeflowEntry !== null) args.push("--extension", freeflowEntry);
60
+ for (const bundle of runtime.extensions) args.push("--extension", bundle.entry);
61
+ if (observeContext) args.push("--extension", observerExtension);
62
+ if (containerRoute) args.push("--extension", containerRouteExtension);
63
+ args.push("--no-skills", "--no-prompt-templates", "--no-themes", "--no-context-files", "--no-approve");
64
+ if (group.tools.length === 0) args.push("--no-tools");
65
+ else args.push("--tools", group.tools.join(","));
66
+ for (const skill of environment.skills) args.push("--skill", skill.path);
67
+ if (model?.model) args.push("--model", model.model);
68
+ if (model?.thinking) args.push("--thinking", model.thinking);
69
+ if (mode === "json") args.push(group.input.prompt);
70
+ return args;
71
+ }
72
+
73
+ export async function runPiProcess({ command, args, cwd, eventsFile, stderrFile, signal, environment }) {
74
+ const child = spawn(command, args, {
75
+ cwd,
76
+ detached: process.platform !== "win32",
77
+ env: environment,
78
+ shell: false,
79
+ stdio: ["ignore", "pipe", "pipe"],
80
+ });
81
+ const events = createWriteStream(eventsFile, { encoding: "utf8" });
82
+ const stderr = createWriteStream(stderrFile, { encoding: "utf8" });
83
+ const transcript = [];
84
+ const calls = new Map();
85
+ const toolActivity = [];
86
+ const successfulReads = [];
87
+ const parseErrors = [];
88
+ const protocolErrors = [];
89
+ let settled = false;
90
+ let response = "";
91
+ let usage = null;
92
+ let assistantError = null;
93
+ let observedModel = null;
94
+ let buffer = "";
95
+ let eventLine = 0;
96
+ const processGroupId = child.pid;
97
+ let watchdog;
98
+ let forcedKill;
99
+ /** @type {Promise<void> | null} */
100
+ let forcedKillPromise = null;
101
+ let terminationReason = null;
102
+
103
+ const resetWatchdog = () => {
104
+ clearTimeout(watchdog);
105
+ watchdog = setTimeout(() => requestTermination("no-progress-watchdog"), NO_PROGRESS_MS);
106
+ watchdog.unref();
107
+ };
108
+ const requestTermination = (reason) => {
109
+ if (terminationReason !== null) return;
110
+ terminationReason = reason;
111
+ signalProcessTree(processGroupId, child, "SIGTERM");
112
+ if (process.platform !== "win32") {
113
+ forcedKillPromise = new Promise((resolve) => {
114
+ forcedKill = setTimeout(() => {
115
+ signalProcessTree(processGroupId, child, "SIGKILL");
116
+ resolve();
117
+ }, TERMINATION_GRACE_MS);
118
+ });
119
+ }
120
+ };
121
+ const onAbort = () => requestTermination("cancelled");
122
+ if (signal?.aborted) onAbort();
123
+ else signal?.addEventListener("abort", onAbort, { once: true });
124
+ resetWatchdog();
125
+
126
+ const stderrTask = pipeStream(child.stderr, stderr, resetWatchdog);
127
+ const stdoutTask = (async () => {
128
+ for await (const chunk of child.stdout) {
129
+ resetWatchdog();
130
+ buffer += chunk.toString("utf8");
131
+ while (true) {
132
+ const newline = buffer.indexOf("\n");
133
+ if (newline === -1) break;
134
+ const line = buffer.slice(0, newline);
135
+ buffer = buffer.slice(newline + 1);
136
+ await writeStream(events, `${line}\n`);
137
+ observeLine(line.endsWith("\r") ? line.slice(0, -1) : line);
138
+ }
139
+ }
140
+ if (buffer.length > 0) {
141
+ await writeStream(events, buffer);
142
+ observeLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
143
+ }
144
+ events.end();
145
+ await once(events, "finish");
146
+ })();
147
+
148
+ function observeLine(line) {
149
+ eventLine += 1;
150
+ if (line.trim() === "") return;
151
+ let event;
152
+ try {
153
+ event = JSON.parse(line);
154
+ } catch {
155
+ parseErrors.push({ line: eventLine, reason: "invalid-json" });
156
+ return;
157
+ }
158
+ if (event.type === "extension_error") {
159
+ protocolErrors.push({
160
+ reason: "extension-error",
161
+ extensionPath: event.extensionPath ?? null,
162
+ event: event.event ?? null,
163
+ error: event.error ?? null,
164
+ });
165
+ requestTermination("protocol-error");
166
+ }
167
+ if (event.type === "agent_settled") settled = true;
168
+ if (event.type === "tool_execution_start") {
169
+ // Same shape as RPC turns record, so graders and the advisory reviewer see one-shot tool use too.
170
+ const activity = {
171
+ toolCallId: event.toolCallId ?? null,
172
+ toolName: event.toolName ?? null,
173
+ args: event.args ?? null,
174
+ completed: false,
175
+ isError: null,
176
+ };
177
+ toolActivity.push(activity);
178
+ calls.set(event.toolCallId, activity);
179
+ }
180
+ if (event.type === "tool_execution_end") {
181
+ const call = calls.get(event.toolCallId);
182
+ if (call) {
183
+ call.completed = true;
184
+ call.isError = event.isError === true;
185
+ }
186
+ if (!event.isError && call?.toolName === "read" && typeof call.args?.path === "string") {
187
+ successfulReads.push(call.args.path);
188
+ }
189
+ }
190
+ if (event.type === "message_end" && event.message) {
191
+ transcript.push(event.message);
192
+ if (event.message.role === "assistant") {
193
+ response = messageText(event.message);
194
+ usage = mergeUsage(usage, event.message.usage);
195
+ observedModel = {
196
+ provider: event.message.provider ?? null,
197
+ model: event.message.model ?? null,
198
+ };
199
+ assistantError =
200
+ event.message.stopReason === "error" || event.message.stopReason === "aborted"
201
+ ? (event.message.errorMessage ?? `assistant stopped with ${event.message.stopReason}`)
202
+ : null;
203
+ }
204
+ }
205
+ }
206
+
207
+ let exitCode = null;
208
+ let exitSignal = null;
209
+ try {
210
+ const [code, closedSignal] = await once(child, "close");
211
+ exitCode = code;
212
+ exitSignal = closedSignal;
213
+ await Promise.all([stdoutTask, stderrTask]);
214
+ } catch (error) {
215
+ requestTermination("process-error");
216
+ throw error;
217
+ } finally {
218
+ clearTimeout(watchdog);
219
+ if (forcedKillPromise !== null) {
220
+ if (processGroupExists(processGroupId)) await waitForForcedKill(forcedKillPromise);
221
+ else clearTimeout(forcedKill);
222
+ }
223
+ signal?.removeEventListener("abort", onAbort);
224
+ }
225
+
226
+ return {
227
+ exitCode,
228
+ signal: exitSignal,
229
+ settled,
230
+ assistantError,
231
+ model: observedModel,
232
+ terminationReason,
233
+ parseErrors,
234
+ protocolErrors,
235
+ successfulReads,
236
+ toolActivity,
237
+ transcript,
238
+ response,
239
+ usage,
240
+ };
241
+ }
@@ -15,7 +15,7 @@ import {
15
15
  waitForForcedKill,
16
16
  waitForProcessTreeExit,
17
17
  writeStream,
18
- } from "./process.mjs";
18
+ } from "../../process.mjs";
19
19
 
20
20
  const MAX_INCOMPLETE_FRAME_BYTES = 16 * 1024 * 1024;
21
21
 
@@ -28,7 +28,25 @@ export async function runRpcBodySession(options) {
28
28
  }
29
29
 
30
30
  class RpcSession {
31
- constructor({ command = "pi", args, cwd, eventsFile, stderrFile, signal, environment, afterTurn = null }) {
31
+ constructor({
32
+ command = "pi",
33
+ args,
34
+ cwd,
35
+ eventsFile,
36
+ stderrFile,
37
+ signal,
38
+ environment,
39
+ afterTurn = null,
40
+ nextPrompt = null,
41
+ settings = {},
42
+ resumeBefore = [],
43
+ }) {
44
+ this.settings = settings;
45
+ // Turn numbers before which the session is closed and resumed from its own file, as `pi --resume` would.
46
+ this.resumeBefore = new Set(resumeBefore);
47
+ // After the last declared prompt settles, `nextPrompt(turns)` may return one more prompt (a simulated user's
48
+ // reply), or null to end the session.
49
+ this.nextPrompt = nextPrompt;
32
50
  this.child = spawn(command, args, {
33
51
  cwd,
34
52
  detached: process.platform !== "win32",
@@ -47,6 +65,10 @@ class RpcSession {
47
65
  this.parseErrors = [];
48
66
  this.protocolErrors = [];
49
67
  this.activeTurn = null;
68
+ // A run Pi starts on its own while no prompt is in flight: an extension's message that starts a turn, such as
69
+ // Freeflow's notice that a background command exited or a monitor printed a line. Its activity joins the latest
70
+ // turn when it settles; the next prompt waits for it.
71
+ this.unprompted = null;
50
72
  this.decoder = new StringDecoder("utf8");
51
73
  this.buffer = "";
52
74
  this.incompleteFrameBytes = 0;
@@ -77,7 +99,7 @@ class RpcSession {
77
99
  }
78
100
 
79
101
  async run(prompts, targetPath = null) {
80
- let deliveredPrompts = prompts;
102
+ let deliveredPrompts = [...prompts];
81
103
  /** @type {any} */
82
104
  let delivery = { kind: "unavailable", turn: 1, targetPath };
83
105
  try {
@@ -88,12 +110,23 @@ class RpcSession {
88
110
  delivery = { kind: "explicit-skill-command", turn: 1, targetPath, command };
89
111
  }
90
112
  for (const [index, prompt] of deliveredPrompts.entries()) {
113
+ await this.unpromptedSettled();
91
114
  if (this.signal?.aborted || this.terminationReason !== null) break;
92
115
  if (delivery.kind === "unavailable") delivery = { kind: "natural-prompt", turn: 1, targetPath: null };
116
+ const resumed = this.resumeBefore.has(index + 1) ? await this.resume() : null;
93
117
  const turn = await this.runTurn(index + 1, prompt);
118
+ if (resumed !== null) turn.resumed = resumed;
94
119
  this.turns.push(turn);
95
120
  if (this.afterTurn !== null) turn.workspace = await this.afterTurn(turn.turn);
96
121
  if (turn.assistantError !== null) break;
122
+ if (this.nextPrompt !== null && index === deliveredPrompts.length - 1 && this.terminationReason === null) {
123
+ // A run a notice started right after the turn is part of what the user sees before replying.
124
+ await this.unpromptedSettled();
125
+ const next = await this.nextPrompt(this.turns);
126
+ // The simulated user's time is not the subject's lack of progress.
127
+ this.resetWatchdog();
128
+ if (typeof next === "string") deliveredPrompts.push(next);
129
+ }
97
130
  }
98
131
  } catch (error) {
99
132
  this.operationError = error;
@@ -104,6 +137,7 @@ class RpcSession {
104
137
  });
105
138
  this.requestTermination("protocol-error");
106
139
  }
140
+ await this.unpromptedSettled();
107
141
  } finally {
108
142
  if (!this.child.stdin.destroyed) this.child.stdin.end();
109
143
  }
@@ -114,9 +148,25 @@ class RpcSession {
114
148
  return { ...this.observation(deliveredPrompts, exitCode, exitSignal), delivery };
115
149
  }
116
150
 
151
+ // The subject's effective settings decide retry and compaction; the evaluator's defaults keep both off.
117
152
  async configure() {
118
- await this.sendCommand({ type: "set_auto_retry", enabled: false });
119
- await this.sendCommand({ type: "set_auto_compaction", enabled: false });
153
+ await this.sendCommand({ type: "set_auto_retry", enabled: this.settings.retry?.enabled === true });
154
+ await this.sendCommand({ type: "set_auto_compaction", enabled: this.settings.compaction?.enabled === true });
155
+ }
156
+
157
+ /**
158
+ * Closes the session and opens it again from its file: Pi tears the session down (`session_shutdown`) and starts
159
+ * it anew with `session_start` reason `resume`, as a user resuming it would.
160
+ */
161
+ async resume() {
162
+ /** @type {any} */
163
+ const state = await this.sendCommand({ type: "get_state" });
164
+ const sessionFile = state.data?.sessionFile;
165
+ if (typeof sessionFile !== "string" || sessionFile === "") throw new Error("Pi RPC session has no file to resume");
166
+ /** @type {any} */
167
+ const result = await this.sendCommand({ type: "switch_session", sessionPath: sessionFile });
168
+ if (result.data?.cancelled === true) throw new Error("Pi cancelled resuming the session");
169
+ return { sessionFile };
120
170
  }
121
171
 
122
172
  async resolveSkillCommand(targetPath) {
@@ -145,22 +195,31 @@ class RpcSession {
145
195
  resolveSettled = resolve;
146
196
  rejectSettled = reject;
147
197
  });
198
+ // An unprompted run still going when the prompt is sent becomes part of this turn.
199
+ const joined = this.unprompted;
200
+ this.unprompted = null;
148
201
  this.activeTurn = {
149
202
  turn: turnNumber,
150
203
  prompt,
151
- response: "",
152
- transcript: [],
153
- successfulReads: [],
154
- toolActivity: [],
155
- calls: new Map(),
204
+ response: joined?.response ?? "",
205
+ transcript: joined?.transcript ?? [],
206
+ successfulReads: joined?.successfulReads ?? [],
207
+ toolActivity: joined?.toolActivity ?? [],
208
+ calls: joined?.calls ?? new Map(),
156
209
  promptAccepted: false,
157
- usage: null,
158
- assistantError: null,
210
+ usage: joined?.usage ?? null,
211
+ assistantError: joined?.assistantError ?? null,
159
212
  settled: false,
160
213
  resolveSettled,
161
214
  rejectSettled,
162
215
  };
163
- await Promise.all([this.sendCommand({ type: "prompt", message: prompt }), settlement]);
216
+ joined?.resolveSettled();
217
+ // Pi refuses a prompt while it runs, unless told how to queue it. A run a notice starts as the prompt is sent
218
+ // takes the prompt as its follow-up, and the turn records both.
219
+ await Promise.all([
220
+ this.sendCommand({ type: "prompt", message: prompt, streamingBehavior: "followUp" }),
221
+ settlement,
222
+ ]);
164
223
  const result = finishTurn(this.activeTurn);
165
224
  this.activeTurn = null;
166
225
  return result;
@@ -238,6 +297,61 @@ class RpcSession {
238
297
  } else if (event.type === "response") this.observeResponse(event);
239
298
  else if (event.type === "agent_settled") this.observeSettlement();
240
299
  else if (this.activeTurn !== null) this.observeTurnEvent(event);
300
+ else if (event.type === "agent_start" || this.unprompted !== null) this.observeUnpromptedEvent(event);
301
+ }
302
+
303
+ /** Events of a run Pi started without a prompt; it opens with agent_start. */
304
+ observeUnpromptedEvent(event) {
305
+ if (this.unprompted === null) {
306
+ let resolveSettled;
307
+ const settled = new Promise((resolve) => {
308
+ resolveSettled = resolve;
309
+ });
310
+ this.unprompted = {
311
+ response: "",
312
+ transcript: [],
313
+ successfulReads: [],
314
+ toolActivity: [],
315
+ calls: new Map(),
316
+ usage: null,
317
+ assistantError: null,
318
+ settled,
319
+ resolveSettled,
320
+ };
321
+ }
322
+ this.observeTurnEvent(event, this.unprompted);
323
+ }
324
+
325
+ /** Resolves once no unprompted run is in progress. */
326
+ async unpromptedSettled() {
327
+ while (this.unprompted !== null && this.terminationReason === null) {
328
+ const { settled } = this.unprompted;
329
+ await Promise.race([settled, this.closed.catch(() => {})]);
330
+ if (this.unprompted?.settled === settled && this.terminationReason === null) {
331
+ // The process closed while the run was in progress; nothing more will arrive.
332
+ break;
333
+ }
334
+ }
335
+ }
336
+
337
+ /** An unprompted run settled: its activity joins the latest turn, as if that turn had continued. */
338
+ settleUnprompted() {
339
+ const run = this.unprompted;
340
+ this.unprompted = null;
341
+ const turn = this.turns.at(-1);
342
+ if (turn === undefined) {
343
+ run.resolveSettled();
344
+ this.failProtocol("unprompted-run-before-first-turn", { line: this.eventLine });
345
+ return;
346
+ }
347
+ turn.transcript.push(...run.transcript);
348
+ turn.successfulReads.push(...run.successfulReads);
349
+ turn.toolActivity.push(...run.toolActivity);
350
+ if (run.response !== "") turn.response = run.response;
351
+ turn.usage = mergeUsage(turn.usage, run.usage);
352
+ turn.assistantError ??= run.assistantError;
353
+ turn.unpromptedRuns = (turn.unpromptedRuns ?? 0) + 1;
354
+ run.resolveSettled();
241
355
  }
242
356
 
243
357
  observeResponse(event) {
@@ -268,6 +382,10 @@ class RpcSession {
268
382
  }
269
383
 
270
384
  observeSettlement() {
385
+ if (this.activeTurn === null && this.unprompted !== null) {
386
+ this.settleUnprompted();
387
+ return;
388
+ }
271
389
  if (this.activeTurn === null || this.activeTurn.settled) {
272
390
  this.failProtocol("unexpected-agent-settled", { line: this.eventLine });
273
391
  return;
@@ -276,7 +394,7 @@ class RpcSession {
276
394
  this.activeTurn.resolveSettled();
277
395
  }
278
396
 
279
- observeTurnEvent(event) {
397
+ observeTurnEvent(event, turn = this.activeTurn) {
280
398
  if (event.type === "tool_execution_start") {
281
399
  const activity = {
282
400
  toolCallId: event.toolCallId ?? null,
@@ -285,39 +403,39 @@ class RpcSession {
285
403
  completed: false,
286
404
  isError: null,
287
405
  };
288
- this.activeTurn.toolActivity.push(activity);
289
- if (typeof event.toolCallId === "string") this.activeTurn.calls.set(event.toolCallId, activity);
406
+ turn.toolActivity.push(activity);
407
+ if (typeof event.toolCallId === "string") turn.calls.set(event.toolCallId, activity);
290
408
  return;
291
409
  }
292
410
  if (event.type === "tool_execution_end") {
293
- this.observeToolEnd(event);
411
+ this.observeToolEnd(event, turn);
294
412
  return;
295
413
  }
296
- if (event.type === "message_end" && event.message) this.observeMessage(event.message);
414
+ if (event.type === "message_end" && event.message) this.observeMessage(event.message, turn);
297
415
  }
298
416
 
299
- observeToolEnd(event) {
300
- const activity = this.activeTurn.calls.get(event.toolCallId);
417
+ observeToolEnd(event, turn) {
418
+ const activity = turn.calls.get(event.toolCallId);
301
419
  if (!activity) return;
302
420
  activity.completed = true;
303
421
  activity.isError = event.isError === true;
304
422
  if (event.isError !== true && activity.toolName === "read" && typeof activity.args?.path === "string") {
305
- this.activeTurn.successfulReads.push(activity.args.path);
423
+ turn.successfulReads.push(activity.args.path);
306
424
  }
307
425
  }
308
426
 
309
- observeMessage(message) {
310
- this.activeTurn.transcript.push(message);
427
+ observeMessage(message, turn) {
428
+ turn.transcript.push(message);
311
429
  this.transcript.push(message);
312
430
  if (message.role !== "assistant") return;
313
- this.activeTurn.response = messageText(message);
314
- this.activeTurn.usage = mergeUsage(this.activeTurn.usage, message.usage);
431
+ turn.response = messageText(message);
432
+ turn.usage = mergeUsage(turn.usage, message.usage);
315
433
  this.observedModel = {
316
434
  provider: message.provider ?? null,
317
435
  model: message.model ?? null,
318
436
  };
319
437
  if (message.stopReason === "error" || message.stopReason === "aborted") {
320
- this.activeTurn.assistantError = message.errorMessage ?? `assistant stopped with ${message.stopReason}`;
438
+ turn.assistantError = message.errorMessage ?? `assistant stopped with ${message.stopReason}`;
321
439
  }
322
440
  }
323
441
 
@@ -0,0 +1,82 @@
1
+ import { execFile } from "node:child_process";
2
+ import { realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const run = promisify(execFile);
7
+
8
+ /**
9
+ * The identity of the working-tree inputs a run reads: the definition root's HEAD commit, and which of the
10
+ * inputs differ from it (modified, untracked, or ignored). A run is canonical only when nothing differs, so
11
+ * every input it read can be recovered from that commit.
12
+ *
13
+ * @param {string} root the definition root
14
+ * @param {string[]} inputs absolute paths of files and directories the run reads from the working tree
15
+ * @returns {Promise<{commit: string | null, uncommitted: string[], repository: boolean}>}
16
+ */
17
+ export async function inputIdentity(root, inputs) {
18
+ const canonicalRoot = await realpath(root);
19
+ let toplevel;
20
+ try {
21
+ toplevel = await realpath((await git(canonicalRoot, ["rev-parse", "--show-toplevel"])).trim());
22
+ } catch {
23
+ return { commit: null, uncommitted: [], repository: false };
24
+ }
25
+ const commit = await git(toplevel, ["rev-parse", "--verify", "HEAD"])
26
+ .then((output) => output.trim())
27
+ .catch(() => null);
28
+ const paths = [];
29
+ for (const input of inputs) {
30
+ const resolved = await realpath(input).catch(() => path.resolve(input));
31
+ paths.push(path.relative(toplevel, resolved) || ".");
32
+ }
33
+ if (paths.length === 0) return { commit, uncommitted: [], repository: true };
34
+ const status = await git(toplevel, [
35
+ "status",
36
+ "--porcelain=v1",
37
+ "-z",
38
+ "--untracked-files=all",
39
+ "--ignored=matching",
40
+ "--",
41
+ ...paths,
42
+ ]);
43
+ const uncommitted = new Set();
44
+ const entries = status.split("\0").filter(Boolean);
45
+ for (let index = 0; index < entries.length; index += 1) {
46
+ const entry = entries[index];
47
+ const code = entry.slice(0, 2);
48
+ uncommitted.add(
49
+ path
50
+ .relative(canonicalRoot, path.join(toplevel, entry.slice(3)))
51
+ .split(path.sep)
52
+ .join("/"),
53
+ );
54
+ if (code[0] === "R" || code[0] === "C") index += 1; // a rename or copy carries its source path next
55
+ }
56
+ if (commit === null) {
57
+ for (const entry of paths) uncommitted.add(path.relative(canonicalRoot, path.join(toplevel, entry)));
58
+ }
59
+ return { commit, uncommitted: [...uncommitted].sort(), repository: true };
60
+ }
61
+
62
+ /** The message that refuses a canonical run whose inputs cannot be recovered from a commit. */
63
+ export function identityRefusal(identity) {
64
+ if (!identity.repository) {
65
+ return "the evaluation inputs are not in a Git repository, so no commit identifies them; run with --dry-run";
66
+ }
67
+ if (identity.uncommitted.length === 0) return null;
68
+ const shown = identity.uncommitted.slice(0, 10).join(", ");
69
+ const more = identity.uncommitted.length > 10 ? ` and ${identity.uncommitted.length - 10} more` : "";
70
+ return `uncommitted inputs: ${shown}${more}; commit them, or run with --dry-run`;
71
+ }
72
+
73
+ /** The installed host's version, as `pi --version` prints it. */
74
+ export async function installedHostVersion(command, environment) {
75
+ const { stdout } = await run(command, ["--version"], { env: environment, timeout: 30000 });
76
+ return stdout.trim().split(/\s+/).pop() ?? "";
77
+ }
78
+
79
+ async function git(cwd, args) {
80
+ const { stdout } = await run("git", args, { cwd, maxBuffer: 16 * 1024 * 1024 });
81
+ return stdout;
82
+ }