@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
@@ -2,10 +2,12 @@ import { existsSync, realpathSync } from "node:fs";
2
2
  import { relative, resolve as resolvePath } from "node:path";
3
3
  import { ROUTING_RECOVERY_HINT } from "./session.js";
4
4
  export { ROUTING_RECOVERY_HINT };
5
+ import { requiredReads } from "./bootstrap.js";
5
6
  import { bodyHash } from "./sources.js";
6
7
  import { initialState } from "./state.js";
7
8
  import { RoutingError, isWorkerProfile, requireCondition as check, ROUTING_TOOLS } from "./types.js";
8
9
  import { cachedBranch } from "../host/branch.js";
10
+ const BOOTSTRAP_GATED = new Set(["bash", "bash_background", "monitor", "edit", "write", "apply_patch"]);
9
11
  /**
10
12
  * What routing allows while a profile runs, and what it tells the tool runtime: which ordinary tools a worker may
11
13
  * still call, which recovery reads an assessment permits, handoff batching rules, and the operation, admission, and
@@ -115,9 +117,9 @@ export class ToolGate {
115
117
  }
116
118
  }
117
119
  handoffOperation(name, input) {
118
- if (name === "freeflow_delegate") return true;
119
- if (name === "freeflow_return") return ["submit", "supplement", "retry"].includes(input?.operation);
120
- return name === "freeflow_unit" && input?.operation === "recover";
120
+ if (name === "delegate_work") return true;
121
+ if (name === "return_work") return ["submit", "supplement", "retry"].includes(input?.operation);
122
+ return name === "routing_unit" && input?.operation === "recover";
121
123
  }
122
124
  stableRecoveryPath(path) {
123
125
  return (
@@ -192,7 +194,7 @@ export class ToolGate {
192
194
  !handoffs.length ||
193
195
  (handoffs.length === 1 &&
194
196
  this.handoffOperation(calls.at(-1)?.name, calls.at(-1)?.arguments) &&
195
- calls.every((b) => this.handoffOperation(b.name, b.arguments) || b.name === "freeflow_project")),
197
+ calls.every((b) => this.handoffOperation(b.name, b.arguments) || b.name === "select_evidence")),
196
198
  "invalid_handoff_batch",
197
199
  "A handoff must be last and cannot accompany ordinary task tools.",
198
200
  );
@@ -211,12 +213,25 @@ export class ToolGate {
211
213
  try {
212
214
  const state = this.session.stateData();
213
215
  if (state.control === "manual")
214
- return isRouting && name !== "freeflow_unit" ? { block: true, reason: "Manual control is active." } : undefined;
216
+ return isRouting && name !== "routing_unit" ? { block: true, reason: "Manual control is active." } : undefined;
215
217
  if (state.control !== "automatic")
216
218
  return isRouting ? { block: true, reason: "Automatic routing is inactive." } : undefined;
217
219
  this.batch(event.toolCallId, name, nested);
218
220
  check(!this.session.error && !this.session.store?.blocked, "routing_blocked");
219
221
  this.session.openTurn();
222
+ const skill = this.session.routingSkill;
223
+ // Commands and edits wait for the bootstrap read of the skill and the profile's role reference; reads and
224
+ // controls do not (`bootstrap.ts`). A codemode script's calls reach this gate under their own names.
225
+ if (skill && BOOTSTRAP_GATED.has(name) && !state.recoveryId && this.session.turn?.skillInView === false) {
226
+ const [path, reference] = requiredReads(skill, this.session.turn?.profile);
227
+ check(
228
+ false,
229
+ "routing_skill_unread",
230
+ reference
231
+ ? `Read the complete cognitive-routing skill and the reference for your profile first: ${path} and ${reference}. Under automatic routing each profile needs both in view before commands or edits, and a compaction takes them out of view. Read them in a response of their own.`
232
+ : `Read the complete cognitive-routing skill first: ${path}. Under automatic routing each profile needs it in view before commands or edits, and a compaction takes it out of view. Read it in a response of their own.`,
233
+ );
234
+ }
220
235
  if (isWorkerProfile(this.session.turn?.profile) && !isRouting) {
221
236
  const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
222
237
  const worker = a ? this.session.assignedWorker(state, a.id) : undefined;
@@ -4,7 +4,7 @@ import { bodyHash, isTaskEvidence } from "./sources.js";
4
4
  import { RoutingError, canonical, emptySelection, idFor, isWorkerProfile, requireCondition as check } from "./types.js";
5
5
  import { cachedBranch } from "../host/branch.js";
6
6
  /**
7
- * Runs the routing tools (freeflow_delegate, freeflow_return, freeflow_project, freeflow_unit) one at a time on the
7
+ * Runs the routing tools (delegate_work, return_work, select_evidence, routing_unit) one at a time on the
8
8
  * session's queue. Each accepted call is recorded once under its operation id, so a repeated call returns the saved
9
9
  * result instead of acting again; a refused call returns a blocked result that names what to reconcile.
10
10
  */
@@ -42,13 +42,13 @@ export class ToolHandlers {
42
42
  this.session.ctx = ctx;
43
43
  try {
44
44
  check(!signal?.aborted, "cancelled");
45
- if (name === "freeflow_unit" && input.operation === "inspect") {
45
+ if (name === "routing_unit" && input.operation === "inspect") {
46
46
  check(this.session.supported(), "routing_unavailable");
47
47
  return this.result(this.inspectUnit(input));
48
48
  }
49
49
  const state = this.session.stateData();
50
50
  const expected =
51
- name === "freeflow_delegate" || name === "freeflow_unit" ? "coordinator" : this.session.assignedWorker(state);
51
+ name === "delegate_work" || name === "routing_unit" ? "coordinator" : this.session.assignedWorker(state);
52
52
  this.session.assertAvailable(expected);
53
53
  const op = this.callOperation(callId, name);
54
54
  const cached = this.session.receipts.get(op);
@@ -57,13 +57,13 @@ export class ToolHandlers {
57
57
  return this.result(structuredClone(cached.value));
58
58
  }
59
59
  const value =
60
- name === "freeflow_delegate"
60
+ name === "delegate_work"
61
61
  ? this.delegate(input, callId, op)
62
- : name === "freeflow_return"
62
+ : name === "return_work"
63
63
  ? this.returnReport(input, callId, op)
64
- : name === "freeflow_project"
64
+ : name === "select_evidence"
65
65
  ? this.project(input, op)
66
- : name === "freeflow_unit"
66
+ : name === "routing_unit"
67
67
  ? await this.unit(input, callId, op)
68
68
  : undefined;
69
69
  if (value !== undefined) {
@@ -79,9 +79,9 @@ export class ToolHandlers {
79
79
  problems: error instanceof RoutingError ? error.problems : [],
80
80
  observed: this.session.observed(),
81
81
  expectedProfile:
82
- name === "freeflow_unit" && input.operation === "inspect"
82
+ name === "routing_unit" && input.operation === "inspect"
83
83
  ? undefined
84
- : name === "freeflow_delegate" || name === "freeflow_unit"
84
+ : name === "delegate_work" || name === "routing_unit"
85
85
  ? "coordinator"
86
86
  : (() => {
87
87
  try {
@@ -101,7 +101,7 @@ export class ToolHandlers {
101
101
  "invalid_inspection",
102
102
  ].includes(error.code)
103
103
  ? "Inspect again without a cursor and use a returned work ref. Lookup recovery does not require a new assignment."
104
- : "Preserve saved work. Use freeflow_unit inspect; reconcile current input/control before retrying the named operation.",
104
+ : "Preserve saved work. Use routing_unit inspect; reconcile current input/control before retrying the named operation.",
105
105
  });
106
106
  }
107
107
  });
@@ -341,8 +341,10 @@ export class ToolHandlers {
341
341
  warnings: prepared.warnings,
342
342
  stage: "completed-input preparation",
343
343
  finalExchangePending: true,
344
- provisional:
345
- "The finalized handoff exchange, target configuration and budget are revalidated at turn_end; this is not delivery evidence.",
344
+ // The Coordinator reads this receipt too, after delivery; it must not read as "the evidence was not delivered".
345
+ provisional: prepared.ready
346
+ ? "Saved. When this turn ends, Freeflow checks the handoff and delivers the report to Coordinator; selected evidence stays in place in the worker's run, where Coordinator sees it."
347
+ : "Saved, but blocked: Coordinator receives nothing until the problems below are fixed. Fix the selection with select_evidence, then call return_work with operation retry.",
346
348
  // The text is already in the call arguments; the hash identifies it without a second copy.
347
349
  ...(isSupplement ? { supplementSha256: bodyHash(h.text) } : { reportSha256: bodyHash(h.text) }),
348
350
  evidence: this.assembler.evidenceFacts(state),
@@ -547,7 +549,7 @@ export class ToolHandlers {
547
549
  check(
548
550
  !idFor(resultId),
549
551
  "result_unavailable",
550
- `${resultId} is a context ref, not a captured result id: name it in the request instead, and the worker selects it as evidence with freeflow_project.`,
552
+ `${resultId} is a context ref, not a captured result id: name it in the request instead, and the worker selects it as evidence with select_evidence.`,
551
553
  );
552
554
  const grant = await this.resultGrants?.resolve(resultId, this.session.ctx);
553
555
  check(grant?.id === resultId, "result_unavailable", `Captured result is unavailable: ${resultId}`);
@@ -33,30 +33,38 @@ export class ModelControl {
33
33
  }
34
34
  return observed && !samePair(observed, expected) ? { profile: state.profile, expected, observed } : undefined;
35
35
  }
36
- /** Set by a user prompt's before_agent_start; consumed by the run it starts. */
36
+ /** Set when a run is prepared before it starts (in beforeRun, or by routing itself); consumed by the run it starts. */
37
37
  promptRun = false;
38
- /** A run that no user prompt prepared has started and its first message has not been seen yet. */
38
+ /** A run that nothing prepared has started and its first message has not been seen yet. */
39
39
  unpreparedRun = false;
40
40
  /** The next run is already prepared by routing itself (explicit resume sends the message that starts it). */
41
41
  preparedRun() {
42
42
  this.promptRun = true;
43
43
  }
44
- /** Pi starts a run: from a user prompt (prepared in beforeRun), a message an extension sent, or a retry. */
44
+ /** Pi starts a run: from a user prompt or an extension's message (prepared in beforeRun), or a retry. */
45
45
  runStarted() {
46
46
  this.unpreparedRun = !this.promptRun;
47
47
  this.promptRun = false;
48
48
  }
49
49
  /**
50
- * Whether this message starts a run an extension's message triggered. Pi skips before_agent_start for those runs,
51
- * so routing prepares them here, before their first request. A retry's run starts with no message and is left as is.
50
+ * Whether this message starts a run an extension's message triggered that beforeRun did not prepare. Pi before 1.2.0
51
+ * skips before_agent_start for those runs, so routing prepares them here, before their first request; from 1.2.0 Pi
52
+ * fires it for them and this returns false. A retry's run starts with no message and is left as is.
52
53
  */
53
54
  startedByMessage(message) {
54
55
  if (!this.unpreparedRun) return false;
55
56
  this.unpreparedRun = false;
56
57
  return message?.role === "custom";
57
58
  }
58
- async beforeRun(ctx, fromPrompt = true) {
59
- if (fromPrompt) this.promptRun = true;
59
+ /**
60
+ * Prepare a run before its first request. `prompt` and `message` come from before_agent_start, which Pi 1.2.0 and
61
+ * later also fire for a run an extension's message starts; a run routing prepared itself (explicit resume) is not
62
+ * prepared again, so the resumed worker keeps its model. `late` comes from the first message of a run Pi started
63
+ * without before_agent_start.
64
+ */
65
+ async beforeRun(ctx, trigger = "prompt") {
66
+ if (trigger === "message" && this.promptRun) return;
67
+ if (trigger !== "late") this.promptRun = true;
60
68
  this.session.ctx = ctx;
61
69
  if (this.session.store && this.session.supported() && !this.session.store.blocked && !this.session.error) {
62
70
  if (this.session.stateData().control === "automatic" && isWorkerProfile(this.session.stateData().profile)) {
@@ -123,7 +131,8 @@ export class ModelControl {
123
131
  this.session.enabledWorkers(state),
124
132
  (provider, modelId) => this.session.ctx?.modelRegistry?.find?.(provider, modelId),
125
133
  (model) => keepsCacheAcrossEffort(model, this.session.ctx),
126
- // Pi's only retention control; unset means the short tier.
134
+ // The host's default retention, which Freeflow reads and never sets; unset means the short tier. (Pi also takes a
135
+ // per-request cacheRetention option, which Freeflow does not pass.)
127
136
  process.env.PI_CACHE_RETENTION === "long" ? "long" : "short",
128
137
  );
129
138
  }
@@ -1,6 +1,6 @@
1
1
  import { annotateSources } from "./provenance.js";
2
2
  import { randomUUID } from "node:crypto";
3
- import { bodyHash, deliveredSelection } from "./sources.js";
3
+ import { bodyHash, deliveredSelection, isTaskEvidence } from "./sources.js";
4
4
  import { canonical, emptySelection, isWorkerProfile, PROFILES, requireCondition as check } from "./types.js";
5
5
  import { estimateRequest } from "./budget.js";
6
6
  import { occurrenceIdentity, tagDerived } from "../host/projection-tags.js";
@@ -217,9 +217,18 @@ export function prepareView(options) {
217
217
  // A structural worker envelope keeps only its calls for the selected results; worker narration is never evidence.
218
218
  const structuralWorker =
219
219
  role === "assistant" && selective && isWorkerProfile(source.producer) && !full.has(source.ref);
220
- const omitted = role === "toolResult" && !full.has(source.ref);
220
+ // Why a result is not shown, said plainly: "omitted" read as lost evidence, and grok-4.7 Coordinators recovered
221
+ // results that were shown in full beside it (Task 004 S-005).
222
+ const omitted =
223
+ role === "toolResult" && !full.has(source.ref)
224
+ ? required.has(source.ref)
225
+ ? "[Selected, but not shown: see the evidence problems in the Cognitive Routing Runtime State]"
226
+ : isTaskEvidence(source)
227
+ ? "[Not shown: the worker did not select this result as evidence]"
228
+ : "[Routing receipt, not shown]"
229
+ : undefined;
221
230
  const renamed = role === "toolResult" ? unpaired.get(source.message.toolCallId) : undefined;
222
- const variant = `${withheld ? 1 : 0}${structuralWorker ? 1 : 0}${omitted ? 1 : 0}${renamed === undefined ? "" : `:${renamed}`}`;
231
+ const variant = `${withheld ? 1 : 0}${structuralWorker ? 1 : 0}${omitted ?? 0}${renamed === undefined ? "" : `:${renamed}`}`;
223
232
  let variants = cache.renderings.get(source.entry);
224
233
  if (!variants) cache.renderings.set(source.entry, (variants = new Map()));
225
234
  let rendering = variants.get(variant);
@@ -231,7 +240,7 @@ export function prepareView(options) {
231
240
  if (omitted)
232
241
  message = {
233
242
  ...message,
234
- content: [{ type: "text", text: "[Worker result omitted from this view]" }],
243
+ content: [{ type: "text", text: omitted }],
235
244
  details: undefined,
236
245
  };
237
246
  if (renamed !== undefined) message = { ...message, toolCallId: renamed };
@@ -254,6 +263,13 @@ export function prepareView(options) {
254
263
  const shared = sources.nonWorkerBefore;
255
264
  const activeRefs = new Set(associated.flatMap((i) => (i.source ? [i.source.ref] : [])));
256
265
  const deferredAt = new Map();
266
+ // Deferred evidence is delivered between call groups: providers such as Anthropic reject anything between a call
267
+ // and its results. A selection is recorded before Pi stores the select_evidence result, so its admission point is
268
+ // that result.
269
+ const afterResults = (at) => {
270
+ while (sources.entries[at]?.message?.role === "toolResult") at++;
271
+ return at;
272
+ };
257
273
  if (selective && (options.admissions || options.resumedAt !== undefined))
258
274
  for (const source of full.values()) {
259
275
  // An attention view omitted required evidence that is no longer in the active context, so after a
@@ -263,7 +279,8 @@ export function prepareView(options) {
263
279
  ? options.resumedAt
264
280
  : undefined;
265
281
  const admitted = options.admissions?.get(source.ref);
266
- const at = admitted === undefined ? resumed : Math.max(admitted, resumed ?? 0),
282
+ const latest = admitted === undefined ? resumed : Math.max(admitted, resumed ?? 0),
283
+ at = latest === undefined ? undefined : afterResults(latest),
267
284
  own = rank.get(source.entry.id) ?? 0;
268
285
  if (at === undefined || at <= own || !isWorkerProfile(source.producer) || shared[at] <= shared[own + 1]) continue;
269
286
  const group = sources.exchange(source).sources;
@@ -408,7 +425,7 @@ export function prepareView(options) {
408
425
  a.id,
409
426
  options.compactedInAssignment === undefined
410
427
  ? a.contract
411
- : `${a.contract}\n\nCompaction: you compacted during this assignment, and cycle ${options.compactedInAssignment} began then. A compaction this contract asks for is done: continue with the rest of the contract, or return it with freeflow_return if compacting was all it asked.`,
428
+ : `${a.contract}\n\nCompaction: you compacted during this assignment, and cycle ${options.compactedInAssignment} began then. A compaction this contract asks for is done: continue with the rest of the contract, or return it with return_work if compacting was all it asked.`,
412
429
  );
413
430
  }
414
431
  if (baseReport) {
@@ -56,8 +56,10 @@ export function annotateSources(
56
56
  isWorkerProfile(s.producer) && isTaskEvidence(s) && full.has(s.ref)
57
57
  ? "task evidence; selection checks apply"
58
58
  : "not offered for new evidence selection";
59
+ // The call's path or command lets a reader match this ref to the result above it.
60
+ const label = s.message.toolName ? sources.callLabel(s.ref) : undefined;
59
61
  return [
60
- `${s.ref} | producer: ${s.producer} | ${s.message.role}${s.message.toolName ? ` | ${s.message.toolName}` : ""}${s.assignmentId ? ` | assignment: ${s.assignmentId}` : ""} | ${representation} | ${selection}`,
62
+ `${s.ref} | producer: ${s.producer} | ${s.message.role}${s.message.toolName ? ` | ${s.message.toolName}${label ? `: ${label}` : ""}` : ""}${s.assignmentId ? ` | assignment: ${s.assignmentId}` : ""} | ${representation} | ${selection}`,
61
63
  ];
62
64
  });
63
65
  const keys = new Set(attributed.map((s) => `${s.producer}\u0000${s.assignmentId ?? ""}`));
@@ -9,14 +9,14 @@ const locatorText = (locator) => {
9
9
  return locator.label ? `${locator.label}${locator.truncated ? " [truncated]" : ""} · ${identity}` : identity;
10
10
  };
11
11
  function title(name, operation) {
12
- if (name === "freeflow_delegate") return operation === "replace" ? "Replace assignment" : "Delegate assignment";
13
- if (name === "freeflow_return")
12
+ if (name === "delegate_work") return operation === "replace" ? "Replace assignment" : "Delegate assignment";
13
+ if (name === "return_work")
14
14
  return operation === "retry"
15
15
  ? "Retry return"
16
16
  : operation === "supplement"
17
17
  ? "Return recovery supplement"
18
18
  : "Return to Coordinator";
19
- if (name === "freeflow_project")
19
+ if (name === "select_evidence")
20
20
  return (
21
21
  {
22
22
  add: "Select evidence",
@@ -25,7 +25,7 @@ function title(name, operation) {
25
25
  list: "List evidence",
26
26
  }[operation ?? ""] ?? "Evidence"
27
27
  );
28
- if (name === "freeflow_unit")
28
+ if (name === "routing_unit")
29
29
  return (
30
30
  {
31
31
  inspect: "Inspect routing",
@@ -41,14 +41,14 @@ function title(name, operation) {
41
41
  }
42
42
  function draft(name, args, expanded) {
43
43
  const sections = [];
44
- if (name === "freeflow_delegate") sections.push(text(args.contract));
45
- if (name === "freeflow_return") sections.push(text(args.report));
46
- if (name === "freeflow_unit" && args.operation === "close") sections.push(text(args.assessment));
47
- if (name === "freeflow_unit" && args.operation === "recover") sections.push(text(args.request));
44
+ if (name === "delegate_work") sections.push(text(args.contract));
45
+ if (name === "return_work") sections.push(text(args.report));
46
+ if (name === "routing_unit" && args.operation === "close") sections.push(text(args.assessment));
47
+ if (name === "routing_unit" && args.operation === "recover") sections.push(text(args.request));
48
48
  // Metadata can arrive before the main text and otherwise occupy the entire
49
49
  // trailing preview forever. Keep the live prose moving; expansion keeps all fields.
50
50
  if (!expanded && sections.length) return sections.filter(Boolean).join("\n\n");
51
- if (name === "freeflow_project" && Array.isArray(args.refs))
51
+ if (name === "select_evidence" && Array.isArray(args.refs))
52
52
  sections.push(args.refs.filter((r) => typeof r === "string").join("\n"));
53
53
  for (const key of ["scope", "view", "ref", "cursor"])
54
54
  if (typeof args[key] === "string") sections.push(`${capital(key)}: ${args[key]}`);
@@ -63,13 +63,13 @@ export function renderRoutingCall(name, args = {}, context = {}) {
63
63
  if (context.isPartial === false) return new Text(heading, 0, 0);
64
64
  const writing = context.argsComplete !== true;
65
65
  const noun =
66
- name === "freeflow_delegate"
66
+ name === "delegate_work"
67
67
  ? "assignment"
68
- : name === "freeflow_return"
68
+ : name === "return_work"
69
69
  ? args.operation === "supplement"
70
70
  ? "supplement"
71
71
  : "report"
72
- : name === "freeflow_unit" && args.operation === "close"
72
+ : name === "routing_unit" && args.operation === "close"
73
73
  ? "assessment"
74
74
  : undefined;
75
75
  const body = draft(name, args, context.expanded === true);
@@ -108,8 +108,8 @@ function summary(receipt, name, args) {
108
108
  return reasons[receipt.code] || text(receipt.message).split("\n")[0] || "Operation needs attention";
109
109
  }
110
110
  if (receipt.status === "suspended") return "Couldn’t restore evidence · Assessment remains paused";
111
- if (name === "freeflow_delegate") return `${operation === "replace" ? "Replacement" : "Assignment"} saved`;
112
- if (name === "freeflow_project") {
111
+ if (name === "delegate_work") return `${operation === "replace" ? "Replacement" : "Assignment"} saved`;
112
+ if (name === "select_evidence") {
113
113
  const selected = (receipt.selected ?? receipt.evidence?.selected ?? []).length;
114
114
  if (operation === "list")
115
115
  return `${count(receipt.count ?? receipt.items?.length ?? 0, "source")}${Number.isInteger(receipt.selectableCount) ? ` · ${receipt.selectableCount} selectable` : ""}`;
@@ -132,7 +132,7 @@ function summary(receipt, name, args) {
132
132
  receipt.items.every((i) => ["already_selected", "unchanged"].includes(i.status));
133
133
  return `${unchanged ? "No changes · " : ""}${count(selected, "source")} selected${receipt.ready === false ? ` · ${issue}` : operation === "inspect" ? " · No unresolved references" : ""}`;
134
134
  }
135
- if (name === "freeflow_unit") {
135
+ if (name === "routing_unit") {
136
136
  if (operation === "close") return capital(receipt.outcome ?? "closed");
137
137
  if (operation === "recover") return "Recovery request saved";
138
138
  if (operation === "cancel-recovery") return "Evidence recovery cancelled";
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { findRoutingSkill } from "./bootstrap.js";
2
3
  import { ContextAssembler } from "./assembler.js";
3
4
  import { EventStore } from "./event-store.js";
4
5
  import { ToolGate } from "./gate.js";
@@ -29,6 +30,7 @@ export class RoutingRuntime {
29
30
  constructor(pi, packageRoots = []) {
30
31
  this.pi = pi;
31
32
  this.packageRoots = packageRoots;
33
+ this.session.routingSkill = findRoutingSkill(this.packageRoots);
32
34
  this.models = new ModelControl(this.session, this.pi);
33
35
  this.status = new RoutingStatus(this.session, this.models);
34
36
  this.gate = new ToolGate(this.session, this.packageRoots);
@@ -280,8 +282,8 @@ export class RoutingRuntime {
280
282
  }
281
283
  return true;
282
284
  }
283
- beforeRun(ctx, fromPrompt = true) {
284
- return this.models.beforeRun(ctx, fromPrompt);
285
+ beforeRun(ctx, trigger) {
286
+ return this.models.beforeRun(ctx, trigger);
285
287
  }
286
288
  runStarted() {
287
289
  this.models.runStarted();
@@ -40,7 +40,7 @@ const report = (name) =>
40
40
  ["operation", "report", "outcome"],
41
41
  );
42
42
  export const ROUTING_SCHEMAS = {
43
- freeflow_delegate: {
43
+ delegate_work: {
44
44
  oneOf: [
45
45
  object({ operation: operation("assign"), contract: string(), worker }, ["operation", "contract"]),
46
46
  object({ operation: operation("replace"), contract: string(), reason: string(2048), worker }, [
@@ -50,8 +50,8 @@ export const ROUTING_SCHEMAS = {
50
50
  ]),
51
51
  ],
52
52
  },
53
- freeflow_return: { oneOf: [report("submit"), report("supplement"), object({ operation: operation("retry") })] },
54
- freeflow_unit: {
53
+ return_work: { oneOf: [report("submit"), report("supplement"), object({ operation: operation("retry") })] },
54
+ routing_unit: {
55
55
  oneOf: [
56
56
  object(
57
57
  {
@@ -81,7 +81,7 @@ export const ROUTING_SCHEMAS = {
81
81
  }),
82
82
  ],
83
83
  },
84
- freeflow_project: {
84
+ select_evidence: {
85
85
  oneOf: [
86
86
  object(
87
87
  {
@@ -18,6 +18,8 @@ export class RoutingSession {
18
18
  capability;
19
19
  token = randomUUID();
20
20
  turn;
21
+ /** The packaged routing skill and role references the bootstrap read must cover; undefined when the skill cannot be read. */
22
+ routingSkill;
21
23
  messages = [];
22
24
  error;
23
25
  projectionError;
@@ -216,6 +218,6 @@ export class RoutingSession {
216
218
  workerBasis(state, assignmentId) {
217
219
  const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
218
220
  if (recovery?.state === "reading") return state.handoffs.get(recovery.requestHandoffId)?.basisUserEntryId ?? null;
219
- return this.taskBasis(state, assignmentId);
221
+ return this.taskBasis(state, assignmentId) ?? null;
220
222
  }
221
223
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
3
- import { canonical, idFor, isWorkerProfile, refFor } from "./types.js";
3
+ import { canonical, idFor, isWorkerProfile, refFor, ROUTING_TOOLS, routingToolName } from "./types.js";
4
4
  import { isHostEdited, isProjected, projectedEntryId } from "../host/projection-tags.js";
5
5
  export const bodyHash = (value) => createHash("sha256").update(canonical(value)).digest("hex");
6
6
  /** Define a memoized hash so history is hashed only when a consumer needs it. */
@@ -11,11 +11,11 @@ function lazyHash(target, compute) {
11
11
  get: () => (value ??= compute()),
12
12
  });
13
13
  }
14
- const routingTools = new Set(["freeflow_delegate", "freeflow_return", "freeflow_project", "freeflow_unit"]);
14
+ const routingTools = new Set(ROUTING_TOOLS);
15
15
  // Discovery, new selections, and delivery share this policy: evidence is what tools produced. What a worker
16
16
  // says belongs in its report, so its assistant messages are never evidence.
17
17
  export function isTaskEvidence(source) {
18
- return source.message.role === "toolResult" && !routingTools.has(source.message.toolName);
18
+ return source.message.role === "toolResult" && !routingTools.has(routingToolName(source.message.toolName));
19
19
  }
20
20
  /** Saved selections keep their delivery, except assistant messages (whole or `#text`), which are never evidence. */
21
21
  export function deliveredSelection(sources, ref) {
@@ -83,7 +83,7 @@ export class Sources {
83
83
  const source = lazyHash({ ref: refFor(entry.id), entry, message, producer: "common", active: false }, () =>
84
84
  bodyHash(message),
85
85
  );
86
- if (message.role === "toolResult" && message.toolName === "freeflow_return") {
86
+ if (message.role === "toolResult" && routingToolName(message.toolName) === "return_work") {
87
87
  for (const block of message.content ?? [])
88
88
  if (block.type === "text") {
89
89
  try {
@@ -281,6 +281,31 @@ export class Sources {
281
281
  );
282
282
  return matches.length === 1 ? locatorForCall(group.assistant, matches[0], this.locatorProvider) : undefined;
283
283
  }
284
+ /**
285
+ * What a tool result's call did, in a few words: its path, command or first short string argument. Coordinator
286
+ * matches a selected ref to the result in its view by this label (Task 004 S-005); undefined when unknown.
287
+ */
288
+ callLabel(ref) {
289
+ const source = this.byRef.get(ref);
290
+ if (!source || source.message.role !== "toolResult") return undefined;
291
+ const call = (this.owners.get(source.ref)?.assistant.message.content ?? []).find(
292
+ (block) =>
293
+ block.type === "toolCall" && block.id === source.message.toolCallId && block.name === source.message.toolName,
294
+ );
295
+ const args = call?.arguments;
296
+ if (!args || typeof args !== "object") return undefined;
297
+ // A path keeps its end, where the file name is; anything else keeps its start.
298
+ const path = typeof args.path === "string" ? args.path.trim() : "";
299
+ if (path) return path.length > 100 ? `...${path.slice(-97)}` : path;
300
+ const chosen =
301
+ ["command", "pattern", "query", "url"]
302
+ .map((key) => args[key])
303
+ .find((value) => typeof value === "string" && value.trim()) ??
304
+ Object.values(args).find((value) => typeof value === "string" && value.trim() && value.length <= 200);
305
+ const label = normalizeLabel(chosen)?.label;
306
+ if (!label) return undefined;
307
+ return label.length > 100 ? `${label.slice(0, 97)}...` : label;
308
+ }
284
309
  ordered(sources) {
285
310
  const ids = new Set([...sources].map((s) => s.ref));
286
311
  return this.entries.flatMap((entry) => {
@@ -115,7 +115,7 @@ function applyEvent(state, event, owned = false) {
115
115
  for (const profile of PROFILES) {
116
116
  if (!Object.hasOwn(d.overrides, profile)) continue;
117
117
  const override = d.overrides[profile];
118
- if (override === null) s.profileOverrides.delete(profile);
118
+ if (override === null || override === undefined) s.profileOverrides.delete(profile);
119
119
  else s.profileOverrides.set(profile, override);
120
120
  }
121
121
  break;
@@ -242,8 +242,8 @@ function applyEvent(state, event, owned = false) {
242
242
  d.recovery.supplementRevision === 0 &&
243
243
  d.recovery.supplementHandoffId === undefined &&
244
244
  d.recovery.cancellationReason === undefined &&
245
- s.assessment?.handoffId === base?.id &&
246
245
  base?.kind === "return" &&
246
+ s.assessment?.handoffId === base.id &&
247
247
  a.returnHandoffId === base.id &&
248
248
  base.state === "configured" &&
249
249
  base.reportRevision === d.recovery.baseReportRevision &&
@@ -267,9 +267,13 @@ function applyEvent(state, event, owned = false) {
267
267
  s.handoffs.set(h.id, h);
268
268
  s.pendingId = h.id;
269
269
  s.assessment.view = "suspended";
270
- s.assessment.suspensionReason = "recovery";
271
- s.assessment.basisUserEntryId = h.basisUserEntryId;
272
- s.assessment.problems = [];
270
+ // A recovery answers the Coordinator's evidence question, not new user input: an assessment already waiting
271
+ // for that attention keeps waiting for it after the recovery, instead of resuming when the supplement arrives.
272
+ if (s.assessment.suspensionReason !== "user-attention") {
273
+ s.assessment.suspensionReason = "recovery";
274
+ s.assessment.basisUserEntryId = h.basisUserEntryId;
275
+ s.assessment.problems = [];
276
+ }
273
277
  break;
274
278
  }
275
279
  case "recovery-supplement-accepted": {
@@ -281,7 +285,8 @@ function applyEvent(state, event, owned = false) {
281
285
  s.control === "automatic" &&
282
286
  s.profile === assignmentWorker() &&
283
287
  a?.state === "returned" &&
284
- r?.id === s.recoveryId &&
288
+ r !== undefined &&
289
+ r.id === s.recoveryId &&
285
290
  r.assignmentId === a.id &&
286
291
  s.assessment?.handoffId === r.assessmentHandoffId &&
287
292
  s.handoffs.get(r.assessmentHandoffId)?.reportRevision === r.baseReportRevision &&
@@ -308,7 +313,8 @@ function applyEvent(state, event, owned = false) {
308
313
  check(
309
314
  s.control === "automatic" &&
310
315
  s.profile === "coordinator" &&
311
- r?.id === s.recoveryId &&
316
+ r !== undefined &&
317
+ r.id === s.recoveryId &&
312
318
  ["requested", "reading", "returning"].includes(r.state),
313
319
  "recovery_not_cancellable",
314
320
  );
@@ -421,7 +427,11 @@ function applyEvent(state, event, owned = false) {
421
427
  const u = s.units.get(d.unitId),
422
428
  a = assignment();
423
429
  check(
424
- s.control === "automatic" && s.profile === "coordinator" && u?.id === s.unitId && u.state === "open",
430
+ s.control === "automatic" &&
431
+ s.profile === "coordinator" &&
432
+ u !== undefined &&
433
+ u.id === s.unitId &&
434
+ u.state === "open",
425
435
  "unit_not_open",
426
436
  );
427
437
  if (d.outcome === "accepted")
@@ -19,7 +19,9 @@ export class RoutingStatus {
19
19
  } catch {
20
20
  state = initialState();
21
21
  }
22
- const blocked = this.session.error ?? this.session.store?.blocked;
22
+ // A model or effort named on Pi's command line holds routing at startup (`startup_selection`). Its store is not
23
+ // reconciled then, so the store's own reason would hide why (Task 008 S-005).
24
+ const blocked = this.session.error ?? (this.session.suppressed ? "startup_selection" : this.session.store?.blocked);
23
25
  return {
24
26
  effective: this.session.supported() && state.control !== "inactive" && !blocked,
25
27
  activeProfile: state.profile,
@@ -30,8 +32,7 @@ export class RoutingStatus {
30
32
  : this.session.supported() && state.control !== "inactive"
31
33
  ? "active"
32
34
  : "inactive",
33
- runtimeReason:
34
- blocked ?? (this.session.suppressed ? "startup_selection" : this.session.capability?.blockingReason.message),
35
+ runtimeReason: blocked ?? this.session.capability?.blockingReason.message,
35
36
  projectionFailure: this.session.projectionError,
36
37
  ...((pending) => (pending ? { pendingPair: `${pending.modelId}/${pending.thinking}` } : {}))(
37
38
  this.models.pending(),