@hassangameryt/freeflow 0.7.2 → 0.8.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 (267) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/NOTICE +14 -0
  6. package/README.md +151 -136
  7. package/capabilities/cognitive-routing/SKILL.md +110 -96
  8. package/capabilities/cognitive-routing/references/both-mode.md +36 -0
  9. package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
  10. package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +239 -0
  13. package/command-surface.json +4 -0
  14. package/gemini-extension.json +1 -1
  15. package/hooks/shared/runtime-context.mjs +28 -30
  16. package/licenses/Apache-2.0.txt +55 -0
  17. package/package.json +22 -9
  18. package/pi-extension/README.md +41 -0
  19. package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
  20. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +40 -21
  21. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  22. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  23. package/pi-extension/dist/cognitive-routing/event-store.js +280 -0
  24. package/pi-extension/dist/cognitive-routing/gate.js +241 -0
  25. package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
  26. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  27. package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
  28. package/pi-extension/dist/cognitive-routing/projection.js +503 -0
  29. package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
  30. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +31 -10
  31. package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
  32. package/pi-extension/dist/cognitive-routing/schemas.js +209 -0
  33. package/pi-extension/dist/cognitive-routing/session.js +221 -0
  34. package/pi-extension/dist/cognitive-routing/sources.js +291 -0
  35. package/pi-extension/dist/cognitive-routing/state.js +487 -0
  36. package/pi-extension/dist/cognitive-routing/status.js +80 -0
  37. package/pi-extension/dist/cognitive-routing/tools.js +60 -0
  38. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +10 -12
  39. package/pi-extension/dist/compaction/carry.js +191 -0
  40. package/pi-extension/dist/compaction/config.js +22 -0
  41. package/pi-extension/dist/compaction/controller.js +471 -0
  42. package/pi-extension/dist/compaction/harness.js +77 -0
  43. package/pi-extension/dist/compaction/results.js +158 -0
  44. package/pi-extension/dist/compaction/thresholds.js +35 -0
  45. package/pi-extension/dist/compaction/tool.js +57 -0
  46. package/pi-extension/dist/host/branch.js +13 -0
  47. package/pi-extension/dist/host/canonical.js +12 -0
  48. package/pi-extension/dist/host/catalog.js +77 -0
  49. package/pi-extension/dist/host/config.js +374 -0
  50. package/pi-extension/dist/host/projection-tags.js +60 -0
  51. package/pi-extension/dist/host/prompts.js +82 -0
  52. package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
  53. package/pi-extension/dist/host/request-history.js +298 -0
  54. package/pi-extension/dist/host/runtime-state.js +189 -0
  55. package/pi-extension/dist/host/settings/freeflow-command.js +1570 -0
  56. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
  57. package/pi-extension/dist/host/staging.js +28 -0
  58. package/pi-extension/dist/host/status.js +101 -0
  59. package/pi-extension/dist/index.js +441 -170
  60. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  61. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  62. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  63. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  64. package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
  65. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  66. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  67. package/pi-extension/dist/provider-support/effort.js +13 -0
  68. package/pi-extension/dist/provider-support/index.js +45 -0
  69. package/pi-extension/dist/provider-support/openai/adapter.js +127 -0
  70. package/pi-extension/dist/provider-support/openai/history.js +127 -0
  71. package/pi-extension/dist/provider-support/openai/index.js +15 -0
  72. package/pi-extension/dist/provider-support/openai/session-state.js +105 -0
  73. package/pi-extension/dist/provider-support/routes.js +17 -0
  74. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  76. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  77. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  78. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  79. package/pi-extension/dist/tool-execution/background.js +238 -0
  80. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  81. package/pi-extension/dist/tool-execution/config.js +22 -0
  82. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  83. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  84. package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
  85. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  86. package/pi-extension/dist/tool-execution/tools.js +16 -0
  87. package/pi-extension/freeflow/README.md +9 -0
  88. package/plugin.json +1 -1
  89. package/runtime/prompts/cognitive-routing.md +4 -4
  90. package/runtime/prompts/core.md +22 -75
  91. package/runtime/prompts/interaction-contract.md +15 -8
  92. package/runtime/prompts/tool-execution.md +38 -0
  93. package/runtime/prompts/working-method.md +108 -0
  94. package/skills/action-selection/SKILL.md +39 -47
  95. package/skills/action-selection/references/trajectory-stalls.md +2 -0
  96. package/skills/bypass/SKILL.md +2 -2
  97. package/skills/commit-work/SKILL.md +31 -83
  98. package/skills/commit-work/references/git-state-edges.md +1 -1
  99. package/skills/commit-work/references/simple-push.md +2 -0
  100. package/skills/commit-work/references/staging-decisions.md +1 -1
  101. package/skills/decision-gate/SKILL.md +31 -50
  102. package/skills/design-for-depth/SKILL.md +44 -53
  103. package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
  104. package/skills/design-for-depth/references/implementation-decisions.md +4 -4
  105. package/skills/design-for-depth/references/interface-design-loop.md +5 -36
  106. package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
  107. package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
  108. package/skills/diagnose-failure/SKILL.md +39 -24
  109. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
  110. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
  111. package/skills/discuss/SKILL.md +37 -13
  112. package/skills/discuss/references/discussion-continuity.md +7 -27
  113. package/skills/evaluate-skill/SKILL.md +49 -80
  114. package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
  115. package/skills/evaluate-skill/references/evaluation-design.md +14 -14
  116. package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
  117. package/skills/evaluate-skill/references/review-and-revision.md +3 -1
  118. package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
  119. package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
  120. package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
  121. package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
  122. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
  123. package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
  124. package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
  125. package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
  126. package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
  127. package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
  128. package/skills/execute-work/SKILL.md +41 -9
  129. package/skills/execute-work/references/code-practices.md +2 -0
  130. package/skills/execute-work/references/execute-work-edges.md +11 -1
  131. package/skills/handoff/SKILL.md +1 -1
  132. package/skills/handoff/references/handoff-templates.md +1 -1
  133. package/skills/review-artifact/SKILL.md +42 -41
  134. package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
  135. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  136. package/skills/review-work/SKILL.md +38 -32
  137. package/skills/review-work/references/adjudicate-work-review.md +1 -1
  138. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  139. package/skills/review-work/references/security-risk-lens.md +2 -4
  140. package/skills/setup-freeflow/SKILL.md +3 -1
  141. package/skills/setup-freeflow/references/activation-contract.md +7 -13
  142. package/skills/setup-freeflow/references/host-setup.md +3 -3
  143. package/skills/track-work/SKILL.md +113 -161
  144. package/skills/track-work/references/working-record-format.md +189 -350
  145. package/skills/track-work/scripts/lib/document.mjs +62 -6
  146. package/skills/track-work/scripts/lib/format.mjs +16 -1
  147. package/skills/track-work/scripts/lib/store.mjs +9 -8
  148. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  149. package/skills/track-work/scripts/working-record.mjs +34 -12
  150. package/skills/verify-work/SKILL.md +29 -20
  151. package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
  152. package/skills/verify-work/references/integration-evidence.md +2 -0
  153. package/skills/verify-work/references/performance-evidence.md +3 -1
  154. package/skills/verify-work/references/test-design.md +3 -1
  155. package/skills/workflow/SKILL.md +65 -21
  156. package/skills/workflow/references/domain-skill-composition.md +5 -3
  157. package/skills/write-docs/SKILL.md +83 -0
  158. package/skills/write-docs/references/documentation-shapes.md +119 -0
  159. package/skills/write-docs/scripts/docs-check.mjs +139 -0
  160. package/skills/write-plan/SKILL.md +40 -40
  161. package/skills/write-plan/references/plan-shapes.md +1 -1
  162. package/skills/write-skill/SKILL.md +107 -147
  163. package/skills/write-skill/references/activation-boundaries.md +2 -0
  164. package/skills/write-skill/references/agent-first-instructions.md +22 -10
  165. package/skills/write-skill/references/progressive-disclosure.md +4 -2
  166. package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
  167. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  168. package/skills/write-skill/scripts/skill-author.mjs +18 -2
  169. package/skills/write-spec/SKILL.md +34 -41
  170. package/skills/write-spec/references/artifact-standards.md +1 -1
  171. package/skills/write-spec/references/decision-records.md +2 -0
  172. package/skills/write-spec/references/spec-shapes.md +1 -1
  173. package/capabilities/context-virtualization/SKILL.md +0 -103
  174. package/capabilities/conversation-history/SKILL.md +0 -53
  175. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  176. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  177. package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -321
  178. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -1585
  179. package/pi-extension/dist/cognitive-routing-v2/schemas.js +0 -107
  180. package/pi-extension/dist/cognitive-routing-v2/state.js +0 -612
  181. package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -91
  182. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  183. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  184. package/pi-extension/dist/context-virtualization/runtime.js +0 -356
  185. package/pi-extension/dist/context-virtualization/state.js +0 -57
  186. package/pi-extension/dist/context-virtualization/types.js +0 -10
  187. package/pi-extension/dist/conversation-history/passages.js +0 -66
  188. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  189. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  190. package/pi-extension/dist/conversation-history/search.js +0 -56
  191. package/pi-extension/dist/conversation-history/types.js +0 -1
  192. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  193. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  194. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  195. package/pi-extension/dist/freeflow-context/tool.js +0 -313
  196. package/pi-extension/dist/freeflow-context/types.js +0 -12
  197. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  198. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  199. package/pi-extension/dist/runtime/runtime-context.js +0 -735
  200. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  201. package/pi-extension/dist/session-sources/events.js +0 -195
  202. package/pi-extension/dist/session-sources/sources.js +0 -233
  203. package/pi-extension/dist/settings/settings-ui.js +0 -1160
  204. package/pi-extension/dist/ui/text-component.js +0 -57
  205. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  206. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -143
  207. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -365
  208. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -256
  209. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -1634
  210. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -109
  211. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -622
  212. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -93
  213. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -222
  214. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  215. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  216. package/pi-extension/src/context-virtualization/runtime.ts +0 -406
  217. package/pi-extension/src/context-virtualization/state.ts +0 -79
  218. package/pi-extension/src/context-virtualization/types.ts +0 -92
  219. package/pi-extension/src/conversation-history/passages.ts +0 -81
  220. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  221. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  222. package/pi-extension/src/conversation-history/search.ts +0 -79
  223. package/pi-extension/src/conversation-history/types.ts +0 -31
  224. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  225. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  226. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  227. package/pi-extension/src/freeflow-context/tool.ts +0 -365
  228. package/pi-extension/src/freeflow-context/types.ts +0 -70
  229. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  230. package/pi-extension/src/index.ts +0 -368
  231. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  232. package/pi-extension/src/runtime/runtime-context.ts +0 -838
  233. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  234. package/pi-extension/src/session-sources/events.ts +0 -208
  235. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  236. package/pi-extension/src/session-sources/sources.ts +0 -263
  237. package/pi-extension/src/settings/settings-tui.ts +0 -501
  238. package/pi-extension/src/settings/settings-ui.ts +0 -1422
  239. package/pi-extension/src/ui/text-component.ts +0 -66
  240. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -484
  241. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  242. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -441
  243. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  244. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -245
  245. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  246. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -236
  247. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -230
  248. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -153
  249. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  250. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -275
  251. package/pi-extension/tests/context-virtualization/extension.test.js +0 -765
  252. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  253. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -216
  254. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  255. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  256. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  257. package/pi-extension/tests/fixtures/routing-native.js +0 -188
  258. package/pi-extension/tests/integration/mode-free-core.test.js +0 -219
  259. package/pi-extension/tests/integration/pi-extension.test.js +0 -968
  260. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -431
  261. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  262. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  263. package/pi-extension/tsconfig.json +0 -16
  264. package/runtime/prompts/context-virtualization.md +0 -3
  265. package/runtime/prompts/conversation-history.md +0 -3
  266. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  267. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
@@ -0,0 +1,487 @@
1
+ import {
2
+ PROFILES,
3
+ ROUTING_ENTRY,
4
+ canonical,
5
+ eventKey,
6
+ eventValue,
7
+ isWorkerProfile,
8
+ requireCondition as check,
9
+ } from "./types.js";
10
+ import { parseRoutingEvent } from "./event-schema.js";
11
+ export function initialState() {
12
+ return {
13
+ control: "inactive",
14
+ units: new Map(),
15
+ assignments: new Map(),
16
+ handoffs: new Map(),
17
+ recoveries: new Map(),
18
+ executions: new Map(),
19
+ selections: new Map(),
20
+ reservations: new Map(),
21
+ attempts: new Map(),
22
+ exposure: new Map(),
23
+ authors: new Map(),
24
+ resumeBasis: new Map(),
25
+ profileOverrides: new Map(),
26
+ events: new Map(),
27
+ eventIds: new Map(),
28
+ };
29
+ }
30
+ function applyEvent(state, event, owned = false) {
31
+ const e = parseRoutingEvent(event),
32
+ key = eventKey(e),
33
+ value = eventValue(e);
34
+ const previous = state.events.get(key),
35
+ priorId = state.eventIds.get(e.eventId);
36
+ check(!priorId || priorId === value, "event_identity_conflict");
37
+ if (previous) {
38
+ check(eventValue(previous) === value, "operation_conflict");
39
+ return state;
40
+ }
41
+ const s = owned
42
+ ? state
43
+ : {
44
+ ...state,
45
+ units: new Map([...state.units].map(([k, v]) => [k, { ...v }])),
46
+ assignments: new Map([...state.assignments].map(([k, v]) => [k, { ...v }])),
47
+ handoffs: new Map([...state.handoffs].map(([k, v]) => [k, { ...v }])),
48
+ recoveries: new Map(
49
+ [...state.recoveries].map(([k, v]) => [
50
+ k,
51
+ {
52
+ ...v,
53
+ paths: [...v.paths],
54
+ requestedResults: [...(v.requestedResults ?? [])],
55
+ results: (v.results ?? []).map((grant) => ({ ...grant })),
56
+ },
57
+ ]),
58
+ ),
59
+ executions: new Map([...state.executions].map(([k, v]) => [k, { ...v }])),
60
+ selections: new Map(state.selections),
61
+ reservations: new Map(state.reservations),
62
+ attempts: new Map(state.attempts),
63
+ exposure: new Map(state.exposure),
64
+ authors: new Map(state.authors),
65
+ resumeBasis: new Map(state.resumeBasis),
66
+ profileOverrides: new Map(state.profileOverrides),
67
+ events: new Map(state.events),
68
+ eventIds: new Map(state.eventIds),
69
+ assessment: state.assessment ? { ...state.assessment } : undefined,
70
+ },
71
+ d = e.data;
72
+ const assignment = () => (s.assignmentId ? s.assignments.get(s.assignmentId) : undefined);
73
+ const assignmentWorker = () => {
74
+ const a = assignment();
75
+ const h = a ? s.handoffs.get(a.delegateHandoffId) : undefined;
76
+ check(h?.kind === "delegate" && h.assignmentId === a?.id && isWorkerProfile(h.to), "assignment_worker_missing");
77
+ return h.to;
78
+ };
79
+ // A worker response that started but was neither bound nor retired may still be producing effects.
80
+ const workerRunning = (belongs) =>
81
+ [...s.executions.values()].some(
82
+ (x) => belongs(x) && isWorkerProfile(x.profile) && !x.assistantEntryId && !x.interrupted,
83
+ );
84
+ const pending = () => (s.pendingId ? s.handoffs.get(s.pendingId) : undefined);
85
+ const isPending = () => pending() && ["pending", "blocked"].includes(pending().state);
86
+ const currentReturn = (id) => {
87
+ const h = s.handoffs.get(id);
88
+ check(
89
+ h && ["return", "recovery-return"].includes(h.kind) && h.assignmentId === s.assignmentId,
90
+ "not_current_return",
91
+ );
92
+ return h;
93
+ };
94
+ switch (d.type) {
95
+ case "execution-interrupted": {
96
+ const x = s.executions.get(d.executionId);
97
+ check(x && !x.assistantEntryId, "execution_not_unbound");
98
+ x.interrupted = d.reason;
99
+ break;
100
+ }
101
+ case "assignment-resumed": {
102
+ const a = assignment();
103
+ check(
104
+ s.control === "automatic" && a?.id === d.assignmentId && a.state === "outstanding" && !isPending(),
105
+ "assignment_not_resumable",
106
+ );
107
+ s.resumeBasis.set(a.id, d.basisUserEntryId);
108
+ break;
109
+ }
110
+ case "control":
111
+ s.control = d.control;
112
+ s.profile = d.profile;
113
+ break;
114
+ case "profile-overrides":
115
+ for (const profile of PROFILES) {
116
+ if (!Object.hasOwn(d.overrides, profile)) continue;
117
+ const override = d.overrides[profile];
118
+ if (override === null) s.profileOverrides.delete(profile);
119
+ else s.profileOverrides.set(profile, override);
120
+ }
121
+ break;
122
+ case "delegation-override":
123
+ s.delegationOverride = d.delegation ?? undefined;
124
+ break;
125
+ case "execution-opened":
126
+ check(!s.executions.has(d.execution.id), "duplicate_execution");
127
+ s.executions.set(d.execution.id, d.execution);
128
+ break;
129
+ case "execution-bound": {
130
+ const x = s.executions.get(d.executionId);
131
+ check(x, "execution_missing");
132
+ if (x.assistantEntryId)
133
+ check(
134
+ x.assistantEntryId === d.assistantEntryId &&
135
+ canonical(x.resultEntryIds) === canonical(d.resultEntryIds) &&
136
+ x.outcome === d.outcome,
137
+ "binding_conflict",
138
+ );
139
+ x.assistantEntryId = d.assistantEntryId;
140
+ x.resultEntryIds = d.resultEntryIds;
141
+ x.outcome = d.outcome;
142
+ for (const id of [d.assistantEntryId, ...d.resultEntryIds]) {
143
+ const prev = s.authors.get(id);
144
+ check(!prev || prev.executionId === x.id, "source_authorship_conflict");
145
+ s.authors.set(id, { profile: x.profile, executionId: x.id, assignmentId: x.assignmentId });
146
+ }
147
+ break;
148
+ }
149
+ case "delegate-accepted": {
150
+ check(s.control === "automatic" && s.profile === "coordinator", "wrong_control");
151
+ const a = assignment(),
152
+ h = d.handoff,
153
+ u = s.unitId ? s.units.get(s.unitId) : undefined;
154
+ check(!s.assignments.has(d.assignment.id) && !s.handoffs.has(h.id), "reused_work_identity");
155
+ check(
156
+ h.kind === "delegate" &&
157
+ h.from === "coordinator" &&
158
+ isWorkerProfile(h.to) &&
159
+ h.state === "pending" &&
160
+ h.text === d.assignment.contract &&
161
+ h.assignmentId === d.assignment.id &&
162
+ d.assignment.delegateHandoffId === h.id &&
163
+ d.assignment.unitId === d.unit.id,
164
+ "delegate_identity_mismatch",
165
+ );
166
+ if (u) check(u.state === "open" && u.id === d.unit.id && u.objective === d.unit.objective, "unit_conflict");
167
+ else check(!s.units.has(d.unit.id), "closed_unit_reused");
168
+ check(
169
+ canonical(d.unit.assignmentIds) === canonical([...(u?.assignmentIds ?? []), d.assignment.id]),
170
+ "assignment_order",
171
+ );
172
+ if (d.replacement) {
173
+ check(a?.state === "outstanding" && a.id === d.replacement.assignmentId && u, "no_replaceable_assignment");
174
+ check(!workerRunning((x) => x.assignmentId === a.id), "unresolved_worker_execution");
175
+ check(
176
+ !isPending() || (pending().kind === "delegate" && d.replacement.supersededHandoffId === pending().id),
177
+ "handoff_pending",
178
+ );
179
+ a.state = "superseded";
180
+ s.handoffs.get(a.delegateHandoffId).state = "superseded";
181
+ } else {
182
+ check(a?.state !== "outstanding" && !isPending(), "assignment_outstanding");
183
+ check(!s.recoveryId, "recovery_outstanding");
184
+ }
185
+ s.units.set(d.unit.id, d.unit);
186
+ s.unitId = d.unit.id;
187
+ s.assignments.set(d.assignment.id, d.assignment);
188
+ s.assignmentId = d.assignment.id;
189
+ s.handoffs.set(h.id, h);
190
+ s.pendingId = h.id;
191
+ s.assessment = undefined;
192
+ break;
193
+ }
194
+ case "return-accepted": {
195
+ check(s.control === "automatic" && s.profile === assignmentWorker(), "wrong_control");
196
+ const a = assignment(),
197
+ h = d.handoff;
198
+ check(
199
+ a &&
200
+ h.assignmentId === a.id &&
201
+ h.kind === "return" &&
202
+ h.from === assignmentWorker() &&
203
+ h.to === "coordinator" &&
204
+ h.state === "pending",
205
+ "return_identity_mismatch",
206
+ );
207
+ const previousReturn = a.returnHandoffId ? s.handoffs.get(a.returnHandoffId) : undefined;
208
+ if (a.state === "outstanding")
209
+ check(!isPending() && !s.handoffs.has(h.id) && h.reportRevision === 1, "handoff_pending");
210
+ else
211
+ check(
212
+ a.state === "returned" &&
213
+ previousReturn &&
214
+ previousReturn.id === h.id &&
215
+ ["pending", "blocked"].includes(previousReturn.state) &&
216
+ h.reportRevision === previousReturn.reportRevision + 1,
217
+ "invalid_report_revision",
218
+ );
219
+ a.state = "returned";
220
+ a.returnHandoffId = h.id;
221
+ s.handoffs.set(h.id, h);
222
+ s.pendingId = h.id;
223
+ s.assessment = {
224
+ handoffId: h.id,
225
+ assignmentId: a.id,
226
+ view: "active",
227
+ basisUserEntryId: h.basisUserEntryId,
228
+ problems: [],
229
+ };
230
+ break;
231
+ }
232
+ case "recovery-request-accepted": {
233
+ const a = assignment(),
234
+ base = s.handoffs.get(d.recovery.assessmentHandoffId),
235
+ h = d.handoff;
236
+ check(
237
+ s.control === "automatic" &&
238
+ s.profile === "coordinator" &&
239
+ a?.state === "returned" &&
240
+ d.recovery.assignmentId === a.id &&
241
+ d.recovery.state === "requested" &&
242
+ d.recovery.supplementRevision === 0 &&
243
+ d.recovery.supplementHandoffId === undefined &&
244
+ d.recovery.cancellationReason === undefined &&
245
+ s.assessment?.handoffId === base?.id &&
246
+ base?.kind === "return" &&
247
+ a.returnHandoffId === base.id &&
248
+ base.state === "configured" &&
249
+ base.reportRevision === d.recovery.baseReportRevision &&
250
+ !s.recoveryId &&
251
+ !isPending() &&
252
+ h.kind === "recovery-request" &&
253
+ h.assignmentId === a.id &&
254
+ h.from === "coordinator" &&
255
+ h.to === assignmentWorker() &&
256
+ h.state === "pending" &&
257
+ !s.handoffs.has(h.id) &&
258
+ !s.recoveries.has(d.recovery.id),
259
+ "recovery_not_available",
260
+ );
261
+ s.recoveries.set(d.recovery.id, {
262
+ ...d.recovery,
263
+ requestedResults: [...(d.recovery.requestedResults ?? [])],
264
+ results: (d.recovery.results ?? []).map((grant) => ({ ...grant })),
265
+ });
266
+ s.recoveryId = d.recovery.id;
267
+ s.handoffs.set(h.id, h);
268
+ s.pendingId = h.id;
269
+ s.assessment.view = "suspended";
270
+ s.assessment.suspensionReason = "recovery";
271
+ s.assessment.basisUserEntryId = h.basisUserEntryId;
272
+ s.assessment.problems = [];
273
+ break;
274
+ }
275
+ case "recovery-supplement-accepted": {
276
+ const a = assignment(),
277
+ r = s.recoveries.get(d.recoveryId),
278
+ h = d.handoff,
279
+ previous = r?.supplementHandoffId ? s.handoffs.get(r.supplementHandoffId) : undefined;
280
+ check(
281
+ s.control === "automatic" &&
282
+ s.profile === assignmentWorker() &&
283
+ a?.state === "returned" &&
284
+ r?.id === s.recoveryId &&
285
+ r.assignmentId === a.id &&
286
+ s.assessment?.handoffId === r.assessmentHandoffId &&
287
+ s.handoffs.get(r.assessmentHandoffId)?.reportRevision === r.baseReportRevision &&
288
+ ["reading", "returning"].includes(r.state) &&
289
+ h.kind === "recovery-return" &&
290
+ h.assignmentId === a.id &&
291
+ h.from === assignmentWorker() &&
292
+ h.to === "coordinator" &&
293
+ h.state === "pending" &&
294
+ (!previous ? !s.handoffs.has(h.id) : ["pending", "blocked"].includes(previous.state) && previous.id === h.id),
295
+ "recovery_not_returnable",
296
+ );
297
+ const expectedRevision = (previous?.reportRevision ?? 0) + 1;
298
+ check(h.reportRevision === expectedRevision, "invalid_report_revision");
299
+ r.state = "returning";
300
+ r.supplementHandoffId = h.id;
301
+ r.supplementRevision = h.reportRevision;
302
+ s.handoffs.set(h.id, h);
303
+ s.pendingId = h.id;
304
+ break;
305
+ }
306
+ case "recovery-cancelled": {
307
+ const r = s.recoveries.get(d.recoveryId);
308
+ check(
309
+ s.control === "automatic" &&
310
+ s.profile === "coordinator" &&
311
+ r?.id === s.recoveryId &&
312
+ ["requested", "reading", "returning"].includes(r.state),
313
+ "recovery_not_cancellable",
314
+ );
315
+ check(!workerRunning((x) => x.recoveryId === r.id), "unresolved_worker_execution");
316
+ const p = pending();
317
+ if (p) {
318
+ check([r.requestHandoffId, r.supplementHandoffId].includes(p.id), "handoff_pending");
319
+ p.state = "superseded";
320
+ s.pendingId = undefined;
321
+ }
322
+ r.state = "cancelled";
323
+ r.cancellationReason = d.reason;
324
+ s.recoveryId = undefined;
325
+ break;
326
+ }
327
+ case "handoff-retry-requested": {
328
+ const h = currentReturn(d.handoffId);
329
+ check(
330
+ s.control === "automatic" &&
331
+ s.profile === assignmentWorker() &&
332
+ ["blocked", "pending"].includes(h.state) &&
333
+ h.reportRevision === d.reportRevision &&
334
+ (s.selections.get(h.assignmentId)?.revision ?? 0) === d.selectionRevision,
335
+ "invalid_return_retry",
336
+ );
337
+ s.attempts.set(h.id, {
338
+ executionId: d.executionId,
339
+ reportRevision: d.reportRevision,
340
+ selectionRevision: d.selectionRevision,
341
+ });
342
+ h.state = "pending";
343
+ break;
344
+ }
345
+ case "handoff-prepared": {
346
+ const h = currentReturn(d.handoffId);
347
+ check(["pending", "blocked"].includes(h.state), "handoff_terminal");
348
+ check(d.reservation.selectionRevision === (s.selections.get(h.assignmentId)?.revision ?? 0), "stale_selection");
349
+ s.reservations.set(h.id, d.reservation);
350
+ if (s.assessment?.handoffId === h.id) s.assessment.reservation = d.reservation;
351
+ if (h.kind === "recovery-return") {
352
+ const recovery = [...s.recoveries.values()].find((candidate) => candidate.supplementHandoffId === h.id);
353
+ if (recovery && s.assessment?.handoffId === recovery.assessmentHandoffId)
354
+ s.assessment.reservation = d.reservation;
355
+ }
356
+ break;
357
+ }
358
+ case "handoff-state": {
359
+ const h = s.handoffs.get(d.handoffId);
360
+ check(h && s.pendingId === h.id && ["pending", "blocked"].includes(h.state), "handoff_terminal");
361
+ h.state = d.state;
362
+ h.reason = d.reason;
363
+ if (d.state === "configured") {
364
+ check(d.observedPair, "configuration_unobserved");
365
+ s.profile = h.to;
366
+ s.pendingId = undefined;
367
+ const r = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
368
+ if (r && h.id === r.requestHandoffId) r.state = "reading";
369
+ if (r && h.id === r.supplementHandoffId) {
370
+ r.state = "completed";
371
+ s.recoveryId = undefined;
372
+ }
373
+ }
374
+ if (d.state === "superseded") s.pendingId = undefined;
375
+ break;
376
+ }
377
+ case "selection-changed": {
378
+ const a = assignment();
379
+ check(
380
+ a?.id === d.assignmentId &&
381
+ s.control === "automatic" &&
382
+ s.profile === assignmentWorker() &&
383
+ (a.state === "outstanding" ||
384
+ (a.state === "returned" &&
385
+ (isPending() || (s.recoveryId !== undefined && s.recoveries.get(s.recoveryId)?.state === "reading")))),
386
+ "selection_wrong_phase",
387
+ );
388
+ check(d.selection.revision === (s.selections.get(a.id)?.revision ?? 0) + 1, "selection_revision");
389
+ s.selections.set(a.id, d.selection);
390
+ break;
391
+ }
392
+ case "assessment-suspended": {
393
+ check(s.assessment?.handoffId === d.handoffId, "assessment_missing");
394
+ s.assessment.view = "suspended";
395
+ s.assessment.suspensionReason = d.reason;
396
+ s.assessment.basisUserEntryId = d.basisUserEntryId;
397
+ s.assessment.problems = d.problems;
398
+ break;
399
+ }
400
+ case "assessment-resumed": {
401
+ check(s.control === "automatic" && s.profile === "coordinator", "wrong_control");
402
+ check(!s.recoveryId, "recovery_outstanding");
403
+ check(s.assessment?.handoffId === d.handoffId, "assessment_missing");
404
+ check(s.assessment.view === "suspended", "assessment_not_suspended");
405
+ if (d.reservation)
406
+ check(
407
+ d.reservation.selectionRevision === (s.selections.get(s.assessment.assignmentId)?.revision ?? 0),
408
+ "stale_selection",
409
+ );
410
+ s.assessment.view = "active";
411
+ s.assessment.suspensionReason = undefined;
412
+ s.assessment.basisUserEntryId = d.basisUserEntryId;
413
+ if (d.reservation) {
414
+ s.assessment.reservation = d.reservation;
415
+ s.reservations.set(d.handoffId, d.reservation);
416
+ }
417
+ s.assessment.problems = [];
418
+ break;
419
+ }
420
+ case "unit-closed": {
421
+ const u = s.units.get(d.unitId),
422
+ a = assignment();
423
+ check(
424
+ s.control === "automatic" && s.profile === "coordinator" && u?.id === s.unitId && u.state === "open",
425
+ "unit_not_open",
426
+ );
427
+ if (d.outcome === "accepted")
428
+ check(a?.state !== "outstanding" && !isPending() && !s.recoveryId, "unfinished_work");
429
+ const recovery = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
430
+ if (recovery) {
431
+ check(
432
+ d.outcome !== "accepted" && ["requested", "reading", "returning"].includes(recovery.state),
433
+ "unfinished_work",
434
+ );
435
+ check(!workerRunning((x) => x.recoveryId === recovery.id), "unresolved_worker_execution");
436
+ const transfer = pending();
437
+ if (transfer) {
438
+ check([recovery.requestHandoffId, recovery.supplementHandoffId].includes(transfer.id), "handoff_pending");
439
+ transfer.state = "superseded";
440
+ s.pendingId = undefined;
441
+ }
442
+ recovery.state = "cancelled";
443
+ recovery.cancellationReason = d.assessment;
444
+ s.recoveryId = undefined;
445
+ }
446
+ if (a?.state === "outstanding") {
447
+ check(d.supersededAssignmentId === a.id && d.outcome !== "accepted", "supersession_required");
448
+ check(!workerRunning((x) => x.assignmentId === a.id), "unresolved_worker_execution");
449
+ a.state = "superseded";
450
+ }
451
+ if (pending()) pending().state = "superseded";
452
+ u.state = "closed";
453
+ u.disposition = d.outcome;
454
+ u.assessment = d.assessment;
455
+ s.unitId = undefined;
456
+ s.assignmentId = undefined;
457
+ s.pendingId = undefined;
458
+ s.assessment = undefined;
459
+ break;
460
+ }
461
+ case "request-observed": {
462
+ const x = s.executions.get(d.executionId);
463
+ check(x && x.profile === d.profile, "request_execution_missing");
464
+ if (d.handoffId) check(s.handoffs.has(d.handoffId), "request_handoff_missing");
465
+ break;
466
+ }
467
+ case "sources-exposed": {
468
+ const x = s.executions.get(d.executionId);
469
+ check(x && x.profile === d.view, "exposure_execution_missing");
470
+ if (isWorkerProfile(d.view)) for (const source of d.sources) s.exposure.set(source.ref, source.bodyHash);
471
+ break;
472
+ }
473
+ }
474
+ s.events.set(key, e);
475
+ s.eventIds.set(e.eventId, value);
476
+ return s;
477
+ }
478
+ export function reduce(state, event) {
479
+ return applyEvent(state, event);
480
+ }
481
+ export function replay(entries) {
482
+ let state = initialState();
483
+ for (const entry of entries)
484
+ if (entry.type === "custom" && entry.customType === ROUTING_ENTRY)
485
+ state = applyEvent(state, parseRoutingEvent(entry.data), true);
486
+ return state;
487
+ }
@@ -0,0 +1,80 @@
1
+ import { initialState } from "./state.js";
2
+ /**
3
+ * What routing reports about itself: the summary behind Freeflow's status line and the Runtime State sent with each
4
+ * request (whether routing is active or blocked and why, the running profile, control mode, a switch waiting for the
5
+ * next prompt, preset warnings), and the detailed view with unit, assignment, handoff, recovery, and recent events
6
+ * used by unit status and diagnostics.
7
+ */
8
+ export class RoutingStatus {
9
+ session;
10
+ models;
11
+ constructor(session, models) {
12
+ this.session = session;
13
+ this.models = models;
14
+ }
15
+ state() {
16
+ let state;
17
+ try {
18
+ state = this.session.stateData();
19
+ } catch {
20
+ state = initialState();
21
+ }
22
+ const blocked = this.session.error ?? this.session.store?.blocked;
23
+ return {
24
+ effective: this.session.supported() && state.control !== "inactive" && !blocked,
25
+ activeProfile: state.profile,
26
+ delegation: this.session.delegation(state),
27
+ controlMode: state.control === "manual" ? `manual-${state.profile}` : state.control,
28
+ runtimeStatus: blocked
29
+ ? "blocked"
30
+ : this.session.supported() && state.control !== "inactive"
31
+ ? "active"
32
+ : "inactive",
33
+ runtimeReason:
34
+ blocked ?? (this.session.suppressed ? "startup_selection" : this.session.capability?.blockingReason.message),
35
+ projectionFailure: this.session.projectionError,
36
+ ...((pending) => (pending ? { pendingPair: `${pending.modelId}/${pending.thinking}` } : {}))(
37
+ this.models.pending(),
38
+ ),
39
+ ...(this.models.heldMismatch(state) ? { pairMismatch: true } : {}),
40
+ ...((warnings) => (warnings.length ? { presetWarnings: warnings } : {}))(this.models.presetWarnings(state)),
41
+ };
42
+ }
43
+ detail(limit = 0) {
44
+ const state = this.session.stateData();
45
+ const unit = state.unitId ? state.units.get(state.unitId) : undefined;
46
+ const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
47
+ return {
48
+ ...this.state(),
49
+ unit: unit ? { id: unit.id, state: unit.state, assignments: unit.assignmentIds.length } : null,
50
+ assignment: a ? { id: a.id, state: a.state, worker: this.session.assignedWorker(state, a.id) } : null,
51
+ pendingHandoff: state.pendingId ?? null,
52
+ recovery: state.recoveryId
53
+ ? (() => {
54
+ const recovery = state.recoveries.get(state.recoveryId);
55
+ return recovery
56
+ ? {
57
+ id: recovery.id,
58
+ state: recovery.state,
59
+ assignmentId: recovery.assignmentId,
60
+ assessmentHandoffId: recovery.assessmentHandoffId,
61
+ paths: recovery.paths,
62
+ results: recovery.results ?? [],
63
+ supplementRevision: recovery.supplementRevision,
64
+ }
65
+ : null;
66
+ })()
67
+ : null,
68
+ assessment: state.assessment
69
+ ? { handoffId: state.assessment.handoffId, view: state.assessment.view, problems: state.assessment.problems }
70
+ : null,
71
+ ...(limit
72
+ ? {
73
+ history: [...state.events.values()]
74
+ .slice(-Math.min(100, Math.max(1, limit)))
75
+ .map((e) => ({ event: e.eventId, type: e.data.type, operation: e.operationId })),
76
+ }
77
+ : {}),
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,60 @@
1
+ import { ROUTING_TOOLS } from "./types.js";
2
+ import { ROUTING_SCHEMAS, explainRoutingArguments, matches } from "./schemas.js";
3
+ export { ROUTING_SCHEMAS } from "./schemas.js";
4
+ import { renderRoutingCall, renderRoutingResult } from "./render.js";
5
+ const descriptions = {
6
+ freeflow_delegate:
7
+ "Save a supported assignment contract and request enabled worker execution. Explicit replace supersedes a quiescent outstanding assignment in the same unit.",
8
+ freeflow_return:
9
+ "Save or revise the actual assignment report, save an attached recovery supplement, or retry the current saved return without resubmitting text. Returning does not accept or close the unit.",
10
+ freeflow_unit:
11
+ "Inspect current responsibility, paginated work history, or exact saved communication. Coordinator can restore an assessment, request or cancel attached evidence recovery, or close a unit without clearing history.",
12
+ freeflow_project:
13
+ "Inspect current selection and scoped candidates when needed, then add or remove exposed worker task-evidence refs. Strict shapes: inspect uses operation plus optional scope/cursor; add uses operation plus eligible refs only; remove uses operation plus currently selected or unresolved refs plus required reason. Reason is remove-only. Only tool results are evidence; put anything you would say in the report.",
14
+ };
15
+ const guidance = {
16
+ freeflow_delegate: [
17
+ "freeflow_delegate is Coordinator-only under Automatic control. Put the contract in the input. Choose worker when both Helper and Executor are enabled; the sole worker is inferred otherwise. Use replace only for explicit same-unit replacement; do not supply IDs.",
18
+ ],
19
+ freeflow_return: [
20
+ "freeflow_return submit saves/revises the assignment report; supplement saves separate recovery communication; retry completes the current unchanged saved return without text. Stop ordinary task or recovery-read work after its communication is saved. Keep the handoff last; do not batch it with new task tools.",
21
+ ],
22
+ freeflow_unit: [
23
+ "freeflow_unit inspect defaults to current state; view history returns work refs and detail reads saved communication. Coordinator recover requests bounded evidence work against the current returned assessment; cancel-recovery ends only that recovery; assess restores a suspended evidence obligation; close changes work status without clearing communication or completing a Working Record task.",
24
+ ],
25
+ freeflow_project: [
26
+ "Use exactly one shape: inspect {operation, scope?, cursor?}; add {operation, refs} with no reason; remove {operation, refs, reason}. Reason is required only for remove and invalid for add/inspect. Add exact eligible visible refs directly when identity and eligibility are clear; inspect when identity, eligibility, representation or selection state is unclear. Never add refs marked not offered for new evidence selection. For remove, use currently selected or unresolved refs; a non-selectable ref may be named only to clear its unresolved request and requires a reason. Only tool results are evidence: select the result bodies behind your claims, not calls or your own messages, and say everything else in the report. Correct or explicitly withdraw unresolved items with a limitation. Known previously exposed bodies resolve automatically; target-representation gaps are not successful delivery.",
27
+ ],
28
+ };
29
+ export function registerRoutingTools(pi, runtime) {
30
+ for (const name of ROUTING_TOOLS) {
31
+ const definition = {
32
+ name,
33
+ label: name.replace("freeflow_", "Routing "),
34
+ description: descriptions[name],
35
+ parameters: ROUTING_SCHEMAS[name],
36
+ // Handoffs and evidence selection are assistant-issued boundaries: codemode scripts must not call them.
37
+ exposure: "model-only",
38
+ executionMode: "sequential",
39
+ promptGuidelines: guidance[name],
40
+ renderCall: (args, _theme, context) => renderRoutingCall(name, args, context),
41
+ renderResult: (result, options, _theme, context) => renderRoutingResult(result, options, name, context),
42
+ async execute(id, input, signal, _update, ctx) {
43
+ if (!matches(input, ROUTING_SCHEMAS[name])) throw new Error(explainRoutingArguments(name, input));
44
+ return runtime.invoke(name, id, input, signal, ctx);
45
+ },
46
+ };
47
+ pi.registerTool(definition);
48
+ }
49
+ }
50
+ export function applyRoutingToolVisibility(pi, _runtime, _available = true) {
51
+ if (!pi.getActiveTools || !pi.setActiveTools) return;
52
+ const current = pi.getActiveTools();
53
+ // Missing routing tools are appended and present ones keep their place, so tools another extension adds later
54
+ // (tool_search, for one) are never reordered around them.
55
+ const kept = current.filter(
56
+ (name) => name !== "freeflow_switch_profile" && name !== "freeflow_cognitive_routing_history",
57
+ );
58
+ const next = [...kept, ...ROUTING_TOOLS.filter((name) => !kept.includes(name))];
59
+ if (JSON.stringify(next) !== JSON.stringify(current)) pi.setActiveTools(next);
60
+ }
@@ -1,7 +1,13 @@
1
+ import { canonical } from "../host/canonical.js";
1
2
  export const ROUTING_ENTRY = "freeflow-routing-v2";
2
3
  export const ROUTING_MESSAGE = "freeflow-routing-v2-state";
3
- export const PROFILES = ["coordinator", "executor"];
4
+ export const ROUTING_ATTENTION_MESSAGE = "freeflow-routing-attention";
5
+ export const WORKER_PROFILES = ["helper", "executor"];
6
+ export const PROFILES = ["coordinator", ...WORKER_PROFILES];
7
+ export const DELEGATION_MODES = ["executor", "helper", "both"];
4
8
  export const EFFORTS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
9
+ export const isWorkerProfile = (value) => WORKER_PROFILES.includes(value);
10
+ export const workersForDelegation = (mode) => (mode === "both" ? WORKER_PROFILES : [mode]);
5
11
  export class RoutingError extends Error {
6
12
  code;
7
13
  problems;
@@ -21,16 +27,6 @@ export function requireCondition(condition, code, message = code) {
21
27
  export function isObject(value) {
22
28
  return !!value && typeof value === "object" && !Array.isArray(value);
23
29
  }
24
- export function canonical(value) {
25
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
26
- if (isObject(value))
27
- return `{${Object.keys(value)
28
- .filter((k) => value[k] !== undefined)
29
- .sort()
30
- .map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`)
31
- .join(",")}}`;
32
- return JSON.stringify(value) ?? "null";
33
- }
34
30
  export function samePair(left, right) {
35
31
  return !!left && !!right && canonical(left) === canonical(right);
36
32
  }
@@ -40,5 +36,7 @@ export const eventValue = (event) =>
40
36
  export const emptySelection = () => ({ revision: 0, selected: [], unresolved: [], withdrawals: [] });
41
37
  export const refFor = (id) => `ctx:${id}`;
42
38
  export function idFor(ref) {
43
- return /^ctx:([^\s:#]+)(?:#text)?$/.exec(ref)?.[1];
39
+ return /^ctx:([^\s:#]+)$/.exec(ref)?.[1];
44
40
  }
41
+ export { canonical };
42
+ export const ROUTING_TOOLS = ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"];