@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,209 @@
1
+ import { isObject } from "./types.js";
2
+ const string = (maxLength = 32768, description) => ({
3
+ type: "string",
4
+ minLength: 1,
5
+ maxLength,
6
+ ...(description ? { description } : {}),
7
+ });
8
+ const object = (properties, required = Object.keys(properties), description, title) => ({
9
+ type: "object",
10
+ additionalProperties: false,
11
+ properties,
12
+ required,
13
+ ...(description ? { description } : {}),
14
+ ...(title ? { title } : {}),
15
+ });
16
+ const operation = (value, description) => ({
17
+ type: "string",
18
+ enum: [value],
19
+ ...(description ? { description } : {}),
20
+ });
21
+ const evidenceRefs = (description) => ({
22
+ type: "array",
23
+ maxItems: 64,
24
+ items: string(512),
25
+ description,
26
+ });
27
+ const worker = {
28
+ type: "string",
29
+ enum: ["helper", "executor"],
30
+ description: "Worker for this assignment. Required when delegation mode enables both workers.",
31
+ };
32
+ const report = (name) =>
33
+ object(
34
+ {
35
+ operation: operation(name),
36
+ report: string(),
37
+ outcome: { type: "string", enum: ["completed", "partial", "blocked"] },
38
+ limitations: { type: "array", maxItems: 32, items: string(2048) },
39
+ },
40
+ ["operation", "report", "outcome"],
41
+ );
42
+ export const ROUTING_SCHEMAS = {
43
+ freeflow_delegate: {
44
+ oneOf: [
45
+ object({ operation: operation("assign"), contract: string(), worker }, ["operation", "contract"]),
46
+ object({ operation: operation("replace"), contract: string(), reason: string(2048), worker }, [
47
+ "operation",
48
+ "contract",
49
+ "reason",
50
+ ]),
51
+ ],
52
+ },
53
+ freeflow_return: { oneOf: [report("submit"), report("supplement"), object({ operation: operation("retry") })] },
54
+ freeflow_unit: {
55
+ oneOf: [
56
+ object(
57
+ {
58
+ operation: operation("inspect"),
59
+ view: { type: "string", enum: ["current", "history", "detail"] },
60
+ ref: string(128),
61
+ cursor: string(128),
62
+ limit: { type: "integer", minimum: 1, maximum: 100 },
63
+ },
64
+ ["operation"],
65
+ ),
66
+ object({ operation: operation("assess") }),
67
+ object(
68
+ {
69
+ operation: operation("recover"),
70
+ request: string(),
71
+ paths: { type: "array", maxItems: 32, items: string(4096) },
72
+ results: { type: "array", maxItems: 32, uniqueItems: true, items: string(256) },
73
+ },
74
+ ["operation", "request"],
75
+ ),
76
+ object({ operation: operation("cancel-recovery"), reason: string(2048) }),
77
+ object({
78
+ operation: operation("close"),
79
+ outcome: { type: "string", enum: ["accepted", "cancelled", "deferred"] },
80
+ assessment: string(),
81
+ }),
82
+ ],
83
+ },
84
+ freeflow_project: {
85
+ oneOf: [
86
+ object(
87
+ {
88
+ operation: operation("inspect", "Read current selection and offered evidence candidates."),
89
+ scope: {
90
+ type: "string",
91
+ enum: ["selected", "assignment", "active", "history"],
92
+ description: "Optional candidate scope; omit to inspect the current assignment.",
93
+ },
94
+ cursor: string(128, "Use the nextCursor returned by a previous inspection."),
95
+ },
96
+ ["operation"],
97
+ "Inspect selection and candidates without changing evidence state.",
98
+ "Inspect evidence selection",
99
+ ),
100
+ object(
101
+ {
102
+ operation: operation("add", "Select eligible worker task-evidence refs; do not include reason."),
103
+ refs: evidenceRefs(
104
+ "Exact eligible visible refs for worker task evidence. Add known eligible refs directly; inspect when identity, eligibility, representation, or selection state is unclear. Never add a ref marked not offered for new evidence selection.",
105
+ ),
106
+ },
107
+ ["operation", "refs"],
108
+ "Add evidence refs. This shape has no reason field.",
109
+ "Add evidence",
110
+ ),
111
+ object(
112
+ {
113
+ operation: operation("remove", "Withdraw selected or unresolved refs; reason is required."),
114
+ refs: evidenceRefs(
115
+ "Currently selected or unresolved refs to withdraw. A non-selectable ref may be removed with a reason to clear its unresolved request.",
116
+ ),
117
+ reason: string(2048, "Required only for remove; explain why the selection is being withdrawn."),
118
+ },
119
+ ["operation", "refs", "reason"],
120
+ "Remove evidence refs. This is the only operation that accepts reason.",
121
+ "Remove evidence",
122
+ ),
123
+ ],
124
+ },
125
+ };
126
+ // Provider function schemas require an object root. Keep operation-specific branches
127
+ // for runtime validation without leaving the provider-facing root untyped.
128
+ for (const schema of Object.values(ROUTING_SCHEMAS)) {
129
+ schema.type = "object";
130
+ schema.additionalProperties = false;
131
+ schema.required = ["operation"];
132
+ schema.properties = Object.assign({}, ...schema.oneOf.map((branch) => branch.properties));
133
+ schema.properties.operation = {
134
+ type: "string",
135
+ enum: schema.oneOf.flatMap((branch) => branch.properties.operation.enum),
136
+ description: "Choose exactly one operation; each operation has a strict shape and rejects unknown fields.",
137
+ };
138
+ if (schema.properties.refs)
139
+ schema.properties.refs = {
140
+ ...schema.properties.refs,
141
+ description:
142
+ "For add, use exact eligible visible refs directly when their 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 to clear its unresolved request and requires reason.",
143
+ };
144
+ // The provider sees one flattened root, so each field names the operations whose strict shape accepts it.
145
+ const operations = schema.oneOf.map((branch) => branch.properties.operation.enum[0]);
146
+ for (const [field, property] of Object.entries(schema.properties)) {
147
+ if (field === "operation") continue;
148
+ const accepting = operations.filter((_op, i) => Object.hasOwn(schema.oneOf[i].properties, field));
149
+ if (accepting.length === operations.length) continue;
150
+ const scope = `Only for operation: ${accepting.join(", ")}.`;
151
+ schema.properties[field] = {
152
+ ...property,
153
+ description: property.description ? `${property.description} ${scope}` : scope,
154
+ };
155
+ }
156
+ }
157
+ const expected = (schema) =>
158
+ schema.enum
159
+ ? `one of ${schema.enum.join(", ")}`
160
+ : schema.type === "string"
161
+ ? `a non-empty string of at most ${schema.maxLength} characters`
162
+ : schema.type === "array"
163
+ ? `an array of at most ${schema.maxItems} items`
164
+ : schema.type === "integer"
165
+ ? `an integer from ${schema.minimum} to ${schema.maximum}`
166
+ : `type ${schema.type}`;
167
+ /** Explain why routing arguments match no strict operation shape, naming what to change. */
168
+ export function explainRoutingArguments(name, input) {
169
+ const schema = ROUTING_SCHEMAS[name];
170
+ const operations = schema.oneOf.map((branch) => branch.properties.operation.enum[0]);
171
+ const operation = isObject(input) ? input.operation : undefined;
172
+ const branch = schema.oneOf.find((b) => b.properties.operation.enum[0] === operation);
173
+ if (!isObject(input) || !branch)
174
+ return `Invalid ${name} arguments: operation must be one of ${operations.join(", ")}. No operation was accepted.`;
175
+ const allowed = Object.keys(branch.properties);
176
+ const problems = [];
177
+ const unknown = Object.keys(input).filter((field) => !allowed.includes(field));
178
+ if (unknown.length) problems.push(`remove field${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")}`);
179
+ const missing = branch.required.filter((field) => !Object.hasOwn(input, field));
180
+ if (missing.length) problems.push(`missing required field${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
181
+ for (const field of allowed)
182
+ if (Object.hasOwn(input, field) && !matches(input[field], branch.properties[field]))
183
+ problems.push(`${field} must be ${expected(branch.properties[field])}`);
184
+ if (!problems.length) problems.push("arguments do not match this operation's shape");
185
+ return `Invalid ${name} arguments for operation ${operation}: ${problems.join("; ")}. Allowed fields: ${allowed.join(", ")}. No operation was accepted.`;
186
+ }
187
+ export function matches(value, schema) {
188
+ if (schema.oneOf) return schema.oneOf.filter((branch) => matches(value, branch)).length === 1;
189
+ if (schema.enum && !schema.enum.includes(value)) return false;
190
+ if (schema.type === "object")
191
+ return (
192
+ isObject(value) &&
193
+ Object.keys(value).every((k) => schema.properties[k]) &&
194
+ schema.required.every((k) => Object.hasOwn(value, k)) &&
195
+ Object.entries(value).every(([k, v]) => matches(v, schema.properties[k]))
196
+ );
197
+ if (schema.type === "string")
198
+ return (
199
+ typeof value === "string" &&
200
+ value.length >= (schema.minLength ?? 0) &&
201
+ value.length <= (schema.maxLength ?? Infinity) &&
202
+ (!schema.pattern || new RegExp(schema.pattern).test(value))
203
+ );
204
+ if (schema.type === "array")
205
+ return Array.isArray(value) && value.length <= schema.maxItems && value.every((v) => matches(v, schema.items));
206
+ if (schema.type === "integer")
207
+ return Number.isSafeInteger(value) && value >= schema.minimum && value <= schema.maximum;
208
+ return false;
209
+ }
@@ -0,0 +1,221 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Sources } from "./sources.js";
3
+ import { ViewCache } from "./projection.js";
4
+ import { pairFromProfile } from "./config.js";
5
+ import { initialState } from "./state.js";
6
+ import { RoutingError, isWorkerProfile, requireCondition as check, samePair, workersForDelegation } from "./types.js";
7
+ import { cachedBranch } from "../host/branch.js";
8
+ const STAGED_CONTROL = new Set(["control", "profile-overrides", "delegation-override"]);
9
+ export const ROUTING_RECOVERY_HINT =
10
+ "Run /freeflow profile auto to reconcile routing, or /freeflow resume to continue saved work.";
11
+ /**
12
+ * State and identity shared by every part of routing for one bound session: the event journal, the host
13
+ * context, the current turn, blocking errors, control mode, and the queue that orders control changes.
14
+ */
15
+ export class RoutingSession {
16
+ store;
17
+ ctx;
18
+ capability;
19
+ token = randomUUID();
20
+ turn;
21
+ messages = [];
22
+ error;
23
+ projectionError;
24
+ suppressed = false;
25
+ operations = Promise.resolve();
26
+ revision = 0;
27
+ manualHold;
28
+ automaticControl = false;
29
+ sourceCache;
30
+ /** Rendered view messages reused across requests (see ViewCache); rebuilt after a rebind or ancestry change. */
31
+ viewCache = new ViewCache();
32
+ /** Replayed tool results for the current turn, keyed by operation id. */
33
+ receipts = new Map();
34
+ /** Recently served inspection pages, keyed by request; at most eight are kept. */
35
+ pages = new Map();
36
+ get projectionEnabled() {
37
+ return this.supported() && this.capability?.projection === true && this.stateData().control === "automatic";
38
+ }
39
+ /** Forgets everything derived for the current turn: its context view, sources, replay receipts, and pages. */
40
+ resetTurn() {
41
+ this.turn = undefined;
42
+ this.messages = [];
43
+ this.sourceCache = undefined;
44
+ this.viewCache = new ViewCache();
45
+ this.receipts.clear();
46
+ this.pages.clear();
47
+ }
48
+ subject() {
49
+ const branch = cachedBranch(this.store?.reader) ?? [];
50
+ return {
51
+ token: this.token,
52
+ revision: this.revision,
53
+ store: this.store,
54
+ leafId: branch.at(-1)?.id ?? null,
55
+ userId: branch.filter((e) => e.message?.role === "user").at(-1)?.id ?? null,
56
+ };
57
+ }
58
+ current(s) {
59
+ if (s.token !== this.token || s.revision !== this.revision || s.store !== this.store) return false;
60
+ const branch = cachedBranch(this.store?.reader) ?? [];
61
+ return (
62
+ (!s.leafId || branch.some((e) => e.id === s.leafId)) &&
63
+ (branch.filter((e) => e.message?.role === "user").at(-1)?.id ?? null) === s.userId
64
+ );
65
+ }
66
+ guard(s) {
67
+ check(
68
+ this.current(s),
69
+ "stale_operation",
70
+ "Routing state changed while the operation was waiting; no further effects applied.",
71
+ );
72
+ }
73
+ taskBasis(state, assignmentId) {
74
+ return state.resumeBasis.has(assignmentId)
75
+ ? state.resumeBasis.get(assignmentId)
76
+ : state.assignments.get(assignmentId)?.basisUserEntryId;
77
+ }
78
+ delegation(state = this.stateData()) {
79
+ return state.delegationOverride ?? this.capability?.delegation ?? "executor";
80
+ }
81
+ enabledWorkers(state = this.stateData()) {
82
+ return workersForDelegation(this.delegation(state));
83
+ }
84
+ assignedWorker(state, assignmentId = state.assignmentId) {
85
+ const assignment = assignmentId ? state.assignments.get(assignmentId) : undefined;
86
+ const handoff = assignment ? state.handoffs.get(assignment.delegateHandoffId) : undefined;
87
+ check(
88
+ handoff?.kind === "delegate" && handoff.assignmentId === assignment?.id && isWorkerProfile(handoff.to),
89
+ "assignment_worker_missing",
90
+ "The current assignment has no valid recorded worker.",
91
+ );
92
+ return handoff.to;
93
+ }
94
+ requiredProfiles(state = this.stateData(), delegation = this.delegation(state)) {
95
+ const profiles = new Set(["coordinator", ...workersForDelegation(delegation)]);
96
+ if (state.profile) profiles.add(state.profile);
97
+ if (this.manualHold) profiles.add(this.manualHold);
98
+ const assignment = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
99
+ if (assignment?.state === "outstanding" || state.recoveryId) profiles.add(this.assignedWorker(state));
100
+ const pending = state.pendingId ? state.handoffs.get(state.pendingId) : undefined;
101
+ if (pending && isWorkerProfile(pending.to)) profiles.add(pending.to);
102
+ return [...profiles];
103
+ }
104
+ retireUnbound(reason) {
105
+ for (const execution of this.stateData().executions.values()) {
106
+ if (!execution.assistantEntryId && !execution.interrupted)
107
+ this.append({ type: "execution-interrupted", executionId: execution.id, reason });
108
+ }
109
+ this.turn = undefined;
110
+ }
111
+ openTurn() {
112
+ if (!this.turn || this.turn.opened) return;
113
+ const t = this.turn;
114
+ this.append({
115
+ type: "execution-opened",
116
+ execution: {
117
+ id: t.id,
118
+ profile: t.profile,
119
+ assignmentId: t.assignmentId,
120
+ recoveryId: t.recoveryId,
121
+ basisUserEntryId: t.basisUserEntryId,
122
+ pair: t.pair,
123
+ resultEntryIds: [],
124
+ },
125
+ });
126
+ t.opened = true;
127
+ }
128
+ stateData() {
129
+ return this.store?.state() ?? initialState();
130
+ }
131
+ sources(state = this.stateData()) {
132
+ const branch = cachedBranch(this.ctx.sessionManager);
133
+ const cache = this.sourceCache;
134
+ // The cached branch is the same array until the leaf moves, so an unchanged branch needs no filtering.
135
+ if (cache && cache.branch === branch && cache.authors === state.authors.size) return cache.source;
136
+ const entries = branch.filter((e) =>
137
+ ["message", "custom_message", "compaction", "branch_summary"].includes(e.type),
138
+ );
139
+ if (
140
+ cache &&
141
+ cache.authors === state.authors.size &&
142
+ entries.length === cache.entries.length &&
143
+ entries.every((e, i) => e === cache.entries[i])
144
+ ) {
145
+ cache.branch = branch;
146
+ return cache.source;
147
+ }
148
+ const active = this.ctx.sessionManager.buildContextEntries?.();
149
+ const activeIds = active ? new Set(active.map((e) => e.id)) : undefined;
150
+ const source = cache?.source ?? new Sources([], state);
151
+ source.refresh(entries, state, activeIds);
152
+ this.sourceCache = { branch, entries, authors: state.authors.size, source };
153
+ return source;
154
+ }
155
+ observed(ctx = this.ctx) {
156
+ return ctx?.model?.provider && ctx?.model?.id && ctx?.thinkingLevel
157
+ ? { provider: ctx.model.provider, modelId: ctx.model.id, thinking: ctx.thinkingLevel }
158
+ : undefined;
159
+ }
160
+ supported() {
161
+ return !!this.capability?.effective && !this.suppressed;
162
+ }
163
+ append(data, op, step) {
164
+ check(this.store, "routing_unavailable");
165
+ // Control changes made while no run is active affect no request until the next prompt; stage them there.
166
+ const stage = STAGED_CONTROL.has(data.type) && this.ctx?.isIdle?.() !== false;
167
+ return this.store.append(this.store.make(data, op, step), stage);
168
+ }
169
+ enqueue(action) {
170
+ const token = this.token;
171
+ const result = this.operations.then(() => {
172
+ check(token === this.token, "stale_instance");
173
+ return action();
174
+ });
175
+ this.operations = result.catch(() => {});
176
+ return result;
177
+ }
178
+ mark(error) {
179
+ if (this.error && error instanceof RoutingError && error.code === "routing_blocked") return;
180
+ this.error =
181
+ error instanceof RoutingError
182
+ ? `${error.code}: ${error.message}`
183
+ : error instanceof Error
184
+ ? error.message
185
+ : String(error);
186
+ }
187
+ announced;
188
+ // Tell the user once per distinct block; routing stays blocked until an explicit control clears it.
189
+ announceBlock(ctx) {
190
+ if (!this.error || this.announced === this.error) return;
191
+ this.announced = this.error;
192
+ ctx?.ui?.notify?.(`Cognitive Routing blocked: ${this.error}. ${ROUTING_RECOVERY_HINT}`, "warning");
193
+ }
194
+ assertAvailable(profile) {
195
+ check(
196
+ this.supported() && this.store && !this.error && !this.store.blocked,
197
+ "routing_unavailable",
198
+ this.error ?? this.store?.blocked ?? "Routing is inactive.",
199
+ );
200
+ const state = this.stateData();
201
+ check(state.control === "automatic", "manual_control");
202
+ if (profile) check(state.profile === profile && this.turn?.profile === profile, "wrong_profile");
203
+ if (state.profile) check(samePair(this.observed(), this.profilePair(state.profile)), "configuration_mismatch");
204
+ }
205
+ configuredProfilePair(profile) {
206
+ const configured = this.capability?.profiles[profile];
207
+ check(configured, "profile_missing");
208
+ return pairFromProfile(configured);
209
+ }
210
+ profilePair(profile) {
211
+ return this.stateData().profileOverrides.get(profile) ?? this.configuredProfilePair(profile);
212
+ }
213
+ profilePairs(profiles = this.requiredProfiles()) {
214
+ return Object.fromEntries(profiles.map((profile) => [profile, this.profilePair(profile)]));
215
+ }
216
+ workerBasis(state, assignmentId) {
217
+ const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
218
+ if (recovery?.state === "reading") return state.handoffs.get(recovery.requestHandoffId)?.basisUserEntryId ?? null;
219
+ return this.taskBasis(state, assignmentId);
220
+ }
221
+ }