@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
@@ -1,236 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { fixture } from "../fixtures/routing-native.js";
4
- import { replay } from "../../dist/cognitive-routing-v2/state.js";
5
-
6
- test(
7
- "resume after attention restores saved-return correction without reopening task work",
8
- { timeout: 30000 },
9
- async () => {
10
- let reportId;
11
- await fixture(
12
- (n, body, manager) => {
13
- if (n === 1)
14
- return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Read and report." } }];
15
- if (n === 2) return [{ name: "read", args: { path: "evidence.txt" } }];
16
- if (n === 3)
17
- return [
18
- { name: "freeflow_project", args: { operation: "add", refs: ["ctx:missing"] } },
19
- {
20
- name: "freeflow_return",
21
- args: { operation: "submit", report: "ORIGINAL_SAVED_REPORT", outcome: "partial" },
22
- },
23
- ];
24
- if (n === 4) {
25
- reportId = replay(manager.getBranch()).pendingId;
26
- return [];
27
- }
28
- if (n === 5) {
29
- assert.equal(body.model, "gpt-4o");
30
- return [];
31
- }
32
- if (n === 6) {
33
- assert.equal(body.model, "gpt-4.1-mini");
34
- return [{ name: "read", args: { path: "unselected.txt" } }];
35
- }
36
- if (n === 7) {
37
- assert.equal(
38
- manager
39
- .getBranch()
40
- .filter((e) => e.message?.toolName === "read")
41
- .at(-1).message.isError,
42
- true,
43
- );
44
- return [
45
- {
46
- name: "freeflow_project",
47
- args: {
48
- operation: "remove",
49
- refs: ["ctx:missing"],
50
- reason: "Invalid reference; evidence gap remains disclosed.",
51
- },
52
- },
53
- { name: "freeflow_return", args: { operation: "retry" } },
54
- ];
55
- }
56
- assert.equal(body.model, "gpt-4o");
57
- assert.match(JSON.stringify(body), /ORIGINAL_SAVED_REPORT/);
58
- assert.match(JSON.stringify(body), /evidence gap remains disclosed/);
59
- const state = replay(manager.getBranch());
60
- assert.equal(state.assessment.handoffId, reportId);
61
- assert.equal(state.handoffs.get(reportId).reportRevision, 1);
62
- assert.equal(state.assessment.view, "active");
63
- return [];
64
- },
65
- true,
66
- async ({ session, requests }) => {
67
- await session.prompt("Explain the saved return's problem.");
68
- await session.waitForIdle();
69
- await session.prompt("/freeflow resume");
70
- await session.waitForIdle();
71
- assert.equal(requests.length, 8);
72
- },
73
- );
74
- },
75
- );
76
-
77
- const normal = (n) =>
78
- n === 1
79
- ? [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Read evidence.txt and report." } }]
80
- : n === 2
81
- ? [{ name: "read", args: { path: "evidence.txt" } }]
82
- : n === 3
83
- ? [{ name: "freeflow_return", args: { operation: "submit", report: "Read completed.", outcome: "completed" } }]
84
- : [];
85
-
86
- for (const cut of ["delegate-accepted", "executor-configured", "executor-call", "read-result", "return-accepted"]) {
87
- test(`native navigation reconciles valid ${cut} without replaying work`, { timeout: 30000 }, async () => {
88
- await fixture(normal, false, async ({ session, manager, requests }) => {
89
- const chosen = manager
90
- .getEntries()
91
- .find((e) =>
92
- cut === "executor-configured"
93
- ? e.data?.data?.type === "handoff-state" && e.data.data.state === "configured"
94
- : cut === "executor-call"
95
- ? e.message?.role === "assistant" && e.message.content?.some((b) => b.name === "read")
96
- : cut === "read-result"
97
- ? e.message?.toolName === "read"
98
- : e.data?.data?.type === cut,
99
- );
100
- const reads = manager.getEntries().filter((e) => e.message?.toolName === "read").length;
101
- await session.navigateTree(chosen.id, { summarize: false });
102
- const before = requests.length;
103
- await session.prompt("Explain the saved state at this point.");
104
- await session.waitForIdle();
105
- assert.equal(requests.length, before + 1);
106
- assert.equal(requests.at(-1).model, "gpt-4o");
107
- assert.equal(manager.getEntries().filter((e) => e.message?.toolName === "read").length, reads);
108
- assert.equal(replay(manager.getBranch()).profile, "coordinator");
109
- });
110
- });
111
- }
112
-
113
- test(
114
- "explicit resume continues the same assignment after Coordinator handles new input",
115
- { timeout: 30000 },
116
- async () => {
117
- let assignment, unit;
118
- await fixture(
119
- (n, body, manager) => {
120
- if (n <= 2) return normal(n);
121
- if (n === 3) {
122
- const s = replay(manager.getBranch());
123
- assignment = s.assignmentId;
124
- unit = s.unitId;
125
- return [];
126
- }
127
- if (n === 4) {
128
- assert.equal(body.model, "gpt-4o");
129
- return [];
130
- }
131
- if (n === 5) {
132
- assert.equal(body.model, "gpt-4.1-mini");
133
- const s = replay(manager.getBranch());
134
- assert.equal(s.assignmentId, assignment);
135
- assert.equal(s.unitId, unit);
136
- assert.equal(s.assignments.size, 1);
137
- assert.match(JSON.stringify(body), /Do not edit files/);
138
- assert.doesNotMatch(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
139
- return [{ name: "read", args: { path: "unselected.txt" } }];
140
- }
141
- if (n === 6) {
142
- const read = manager
143
- .getBranch()
144
- .filter((e) => e.message?.toolName === "read")
145
- .at(-1);
146
- assert.equal(read.message.isError, false);
147
- return [
148
- {
149
- name: "freeflow_return",
150
- args: { operation: "submit", report: "Resumed read completed; no edits.", outcome: "completed" },
151
- },
152
- ];
153
- }
154
- assert.equal(body.model, "gpt-4o");
155
- return [];
156
- },
157
- false,
158
- async ({ session, manager, requests }) => {
159
- await session.prompt("Do not edit files. Explain the current partial result.");
160
- await session.waitForIdle();
161
- await session.prompt("/freeflow resume");
162
- await session.waitForIdle();
163
- assert.equal(requests.length, 7);
164
- assert.equal(replay(manager.getBranch()).assignments.get(assignment).state, "returned");
165
- },
166
- );
167
- },
168
- );
169
-
170
- test("current manual hold survives navigation and resume cannot release it", { timeout: 30000 }, async () => {
171
- await fixture(normal, false, async ({ session, manager, requests }) => {
172
- const old = manager.getEntries().find((e) => e.data?.data?.type === "delegate-accepted");
173
- await session.prompt("/freeflow profile executor");
174
- await session.navigateTree(old.id, { summarize: false });
175
- const before = requests.length;
176
- await session.prompt("/freeflow resume");
177
- assert.equal(requests.length, before);
178
- const s = replay(manager.getBranch());
179
- assert.equal(s.control, "manual");
180
- assert.equal(s.profile, "executor");
181
- assert.equal(session.model.id, "gpt-4.1-mini");
182
- });
183
- });
184
-
185
- test(
186
- "late steering keeps the captured Executor request coherent and blocks dependent task tools",
187
- { timeout: 30000 },
188
- async () => {
189
- let active;
190
- await fixture(
191
- async (n, body, manager) => {
192
- if (n === 1) return normal(n);
193
- if (n === 2) {
194
- await active.steer("Stop further reads and explain the current result.");
195
- return normal(n);
196
- }
197
- if (n === 3) {
198
- assert.equal(body.model, "gpt-4.1-mini");
199
- assert.match(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
200
- assert.ok(!body.tools.some((t) => t.name === "freeflow_delegate"));
201
- assert.deepEqual(body.tools.find((t) => t.name === "freeflow_unit").parameters.properties.operation.enum, [
202
- "inspect",
203
- ]);
204
- return [{ name: "read", args: { path: "unselected.txt" } }];
205
- }
206
- if (n === 4) {
207
- assert.equal(
208
- manager
209
- .getBranch()
210
- .filter((e) => e.message?.toolName === "read")
211
- .at(-1).message.isError,
212
- true,
213
- );
214
- return [
215
- {
216
- name: "freeflow_return",
217
- args: { operation: "submit", report: "Stopped for the newer instruction.", outcome: "partial" },
218
- },
219
- ];
220
- }
221
- assert.equal(body.model, "gpt-4o");
222
- assert.ok(!body.tools.some((t) => t.name === "freeflow_return" || t.name === "freeflow_project"));
223
- assert.match(JSON.stringify(body), /Stop further reads/);
224
- return [];
225
- },
226
- false,
227
- undefined,
228
- true,
229
- {
230
- onSession: (session) => {
231
- active = session;
232
- },
233
- },
234
- );
235
- },
236
- );
@@ -1,230 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { visibleWidth } from "@earendil-works/pi-tui";
4
- import { renderRoutingCall, renderRoutingResult } from "../../dist/cognitive-routing-v2/render.js";
5
-
6
- test("evidence inspection renders metadata and gaps without source previews", () => {
7
- const receipt = {
8
- count: 1,
9
- returned: 1,
10
- selected: ["ctx:old"],
11
- candidates: [
12
- {
13
- ref: "ctx:old",
14
- kind: "toolResult",
15
- producer: "executor",
16
- toolName: "read",
17
- assignment: "a1",
18
- selected: true,
19
- active: false,
20
- eligible: true,
21
- targetReady: false,
22
- preview: "SOURCE_BODY_MUST_NOT_APPEAR",
23
- limitations: [{ code: "target_representation", detail: "Required image unsupported" }],
24
- },
25
- ],
26
- };
27
- const rendered = renderRoutingResult({ details: receipt }, { expanded: true }, "freeflow_project", {
28
- args: { operation: "inspect" },
29
- })
30
- .render(100)
31
- .join("\n");
32
- assert.doesNotMatch(rendered, /SOURCE_BODY_MUST_NOT_APPEAR|undefined/);
33
- for (const value of ["ctx:old", "executor", "read", "assignment a1", "Required image unsupported"])
34
- assert.ok(rendered.includes(value));
35
- });
36
-
37
- test("expanded receipts retain complete captured reports at narrow and wide widths", () => {
38
- const report = "Captured report " + "long evidence with Unicode 界 and words ".repeat(20) + " END_OF_REPORT";
39
- const receipt = {
40
- status: "accepted",
41
- reportSaved: true,
42
- transition: "blocked",
43
- ready: false,
44
- report,
45
- evidence: { withdrawals: [{ ref: "ctx:a", reason: "Reason stays visible" }] },
46
- };
47
- for (const width of [24, 40, 100]) {
48
- const lines = renderRoutingResult({ details: receipt }, { expanded: true }).render(width);
49
- assert.ok(lines.every((line) => visibleWidth(line) <= width));
50
- assert.match(lines.join("\n"), /END_OF_REPORT/);
51
- assert.match(lines.join("\n").replace(/\s+/g, " "), /Reason stays visible/);
52
- const compact = renderRoutingResult({ details: receipt }, {}).render(width).join("\n");
53
- assert.match(compact, /Report saved/);
54
- assert.match(compact, /needs correction/);
55
- assert.doesNotMatch(compact, /END_OF_REPORT/);
56
- }
57
- });
58
-
59
- test("streaming arguments follow the latest six wrapped lines; expansion retains the whole draft", () => {
60
- const report = Array.from({ length: 12 }, (_, i) => `Report line ${i + 1}`).join("\n");
61
- const live = { argsComplete: false, isPartial: true, expanded: false };
62
- const lines = renderRoutingCall("freeflow_return", { operation: "submit", report }, live).render(40);
63
- assert.match(lines.join("\n"), /Writing report/);
64
- assert.match(lines.join("\n"), /Report line 12/);
65
- assert.doesNotMatch(lines.join("\n"), /Report line 1\s/);
66
- assert.equal(lines.filter((line) => line.includes("Report line")).length, 6);
67
- const full = renderRoutingCall("freeflow_return", { operation: "submit", report }, { ...live, expanded: true })
68
- .render(40)
69
- .join("\n");
70
- assert.match(full, /Report line 1\s/);
71
- assert.match(full, /Report line 12/);
72
- const done = renderRoutingCall(
73
- "freeflow_return",
74
- { operation: "submit", report },
75
- { ...live, argsComplete: true, isPartial: false },
76
- )
77
- .render(40)
78
- .join("\n");
79
- assert.equal(done.trim(), "Return to Coordinator");
80
- });
81
-
82
- test("report streaming is not obscured by limitations sent before or after the report", () => {
83
- const limitations = ["First limitation", "Second limitation", "Third limitation"];
84
- const live = { isPartial: true, argsComplete: false };
85
- for (const reportFirst of [true, false]) {
86
- for (const length of [2, 12, 20]) {
87
- const report = Array.from({ length }, (_, i) => `Report progress ${i + 1}`).join("\n");
88
- const args = reportFirst
89
- ? { operation: "submit", report, limitations }
90
- : { operation: "submit", limitations, report };
91
- const collapsed = renderRoutingCall("freeflow_return", args, live).render(60).join("\n");
92
- assert.match(collapsed, new RegExp(`Report progress ${length}`));
93
- assert.doesNotMatch(collapsed, /Limitation:/);
94
- const expanded = renderRoutingCall("freeflow_return", args, { ...live, expanded: true })
95
- .render(60)
96
- .join("\n");
97
- for (const limit of limitations) assert.ok(expanded.includes(limit));
98
- assert.match(expanded, /Report progress 1/);
99
- }
100
- }
101
- assert.doesNotMatch(
102
- renderRoutingCall("freeflow_return", { operation: "submit", limitations }, live).render(60).join("\n"),
103
- /Limitation:/,
104
- );
105
- });
106
-
107
- test("replacement streaming follows the contract without a sticky reason", () => {
108
- const reason = "Replacement reason " + "keeps its full explanation. ".repeat(30);
109
- const live = { isPartial: true, argsComplete: false };
110
- for (const length of [2, 12]) {
111
- const contract = Array.from({ length }, (_, i) => `Replacement progress ${i + 1}`).join("\n");
112
- for (const args of [
113
- { operation: "replace", reason, contract },
114
- { operation: "replace", contract, reason },
115
- ]) {
116
- const compact = renderRoutingCall("freeflow_delegate", args, live).render(60).join("\n");
117
- assert.match(compact, new RegExp(`Replacement progress ${length}`));
118
- assert.doesNotMatch(compact, /Reason:|Replacement reason/);
119
- const expanded = renderRoutingCall("freeflow_delegate", args, { ...live, expanded: true })
120
- .render(60)
121
- .join("\n");
122
- assert.match(expanded, /Reason: Replacement reason/);
123
- assert.match(expanded, /Replacement progress 1/);
124
- }
125
- }
126
- });
127
-
128
- test("completed receipts describe outcomes without protocol state disclaimers", () => {
129
- for (const [name, operation, receipt, expected] of [
130
- ["freeflow_delegate", "assign", { status: "accepted", transition: "pending" }, "Assignment saved"],
131
- ["freeflow_delegate", "replace", { status: "accepted", transition: "pending" }, "Replacement saved"],
132
- [
133
- "freeflow_return",
134
- "submit",
135
- { status: "accepted", reportSaved: true, ready: true, transition: "pending" },
136
- "Report saved",
137
- ],
138
- [
139
- "freeflow_return",
140
- "retry",
141
- { status: "accepted", reportSaved: true, reportUnchanged: true, ready: true },
142
- "Saved report retained",
143
- ],
144
- ["freeflow_project", "add", { status: "saved", selected: ["ctx:a", "ctx:b"], ready: true }, "2 sources selected"],
145
- ["freeflow_project", "list", { status: "listed", count: 12, selectableCount: 8 }, "12 sources · 8 selectable"],
146
- ["freeflow_unit", "close", { status: "closed", outcome: "deferred" }, "Deferred"],
147
- ["freeflow_unit", "history", { history: [{}, {}] }, "2 events"],
148
- ["freeflow_project", "remove", { selected: ["ctx:b"], items: [{ status: "removed" }] }, "1 removed · 1 remaining"],
149
- // Older receipts used withdrawn for selected and unresolved references alike.
150
- [
151
- "freeflow_project",
152
- "remove",
153
- { selected: [], items: [{ status: "withdrawn" }] },
154
- "1 reference withdrawn · 0 remaining",
155
- ],
156
- [
157
- "freeflow_project",
158
- "inspect",
159
- { selected: ["ctx:a"], ready: true },
160
- "1 source selected · No unresolved references",
161
- ],
162
- [
163
- "freeflow_project",
164
- "add",
165
- { selected: ["ctx:a"], items: [{ status: "already_selected" }], ready: true },
166
- "No changes · 1 source selected",
167
- ],
168
- ["freeflow_unit", "assess", { status: "resumed", ready: true }, "Evidence prepared"],
169
- ["freeflow_unit", "assess", { status: "suspended", ready: false }, "Assessment remains paused"],
170
- [
171
- "freeflow_unit",
172
- "status",
173
- { effective: true, activeProfile: "executor", controlMode: "automatic", assignment: { state: "outstanding" } },
174
- "Executor · Assignment in progress",
175
- ],
176
- ]) {
177
- const text = renderRoutingResult({ details: receipt }, {}, name, { args: { operation } }).render(100).join("\n");
178
- assert.ok(text.includes(expected), text);
179
- assert.doesNotMatch(text, /transfer pending|not acceptance|status:/);
180
- }
181
- });
182
-
183
- test("preview limits apply after wrapping long text, and interrupted drafts remain inspectable", () => {
184
- const args = {
185
- operation: "assign",
186
- contract: "Unicode 界 and \u001b[32mcoloured\u001b[0m text ".repeat(40) + " FINAL_WORD",
187
- };
188
- const component = renderRoutingCall("freeflow_delegate", args, { isPartial: true, argsComplete: false });
189
- for (const width of [24, 50, 100]) {
190
- const lines = component.render(width);
191
- assert.ok(lines.every((line) => visibleWidth(line) <= width));
192
- assert.ok(lines.length <= 12, "header, hint and six preview rows stay bounded");
193
- assert.match(lines.join("\n"), /FINAL_WORD/);
194
- }
195
- const failed = renderRoutingResult(
196
- { content: [{ type: "text", text: "Operation aborted" }] },
197
- { expanded: true },
198
- "freeflow_delegate",
199
- { args },
200
- )
201
- .render(50)
202
- .join("\n");
203
- assert.match(failed, /Submitted contract/);
204
- assert.match(failed, /FINAL_WORD/);
205
- });
206
- test("partial and absent arguments/results render without requiring live runtime state", () => {
207
- assert.ok(renderRoutingCall("freeflow_delegate", undefined).render(30).length);
208
- assert.match(renderRoutingResult({}, { isPartial: true }).render(30).join(""), /Working/);
209
- const inspection = renderRoutingCall(
210
- "freeflow_unit",
211
- { operation: "inspect", view: "detail", ref: "assignment:existing" },
212
- { isPartial: true, argsComplete: false },
213
- )
214
- .render(60)
215
- .join("\n");
216
- assert.match(inspection, /View: detail/);
217
- assert.match(inspection, /Ref: assignment:existing/);
218
- });
219
-
220
- test("native error results with empty details never look like successful saved assignments", () => {
221
- const result = { content: [{ type: "text", text: "Operation aborted" }], details: {} };
222
- const rendered = renderRoutingResult(result, {}, "freeflow_delegate", {
223
- args: { operation: "assign" },
224
- isError: true,
225
- })
226
- .render(60)
227
- .join("\n");
228
- assert.match(rendered, /Operation aborted/);
229
- assert.doesNotMatch(rendered, /Assignment saved/);
230
- });
@@ -1,153 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { mkdtemp, rm } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { SessionManager } from "@earendil-works/pi-coding-agent";
7
- import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
8
- import { EventStore } from "../../dist/session-sources/events.js";
9
- import { replay } from "../../dist/cognitive-routing-v2/state.js";
10
-
11
- const models = Object.fromEntries(
12
- ["coordinator", "executor"].map((id) => [
13
- id,
14
- { id, provider: "fixture", api: "fixture", input: ["text"], contextWindow: 100000, maxTokens: 1000 },
15
- ]),
16
- );
17
- const cap = {
18
- effective: true,
19
- enabled: true,
20
- projection: false,
21
- profiles: Object.fromEntries(
22
- Object.keys(models).map((id) => [id, { provider: "fixture", model: id, thinking: "off" }]),
23
- ),
24
- blockingReason: { code: "", message: "" },
25
- };
26
- async function environment(run) {
27
- const root = await mkdtemp(join(tmpdir(), "routing-runtime-"));
28
- let active;
29
- const make = (label) => {
30
- const manager = SessionManager.create(root, join(root, label));
31
- manager.appendMessage({
32
- role: "assistant",
33
- content: [{ type: "text", text: "seed" }],
34
- model: "coordinator",
35
- provider: "fixture",
36
- api: "fixture",
37
- stopReason: "stop",
38
- timestamp: 1,
39
- });
40
- return {
41
- sessionManager: manager,
42
- model: models.coordinator,
43
- thinkingLevel: "off",
44
- modelRegistry: { find: (_p, id) => models[id], getApiKeyAndHeaders: async () => ({ ok: true }) },
45
- getSystemPrompt: () => "",
46
- isIdle: () => true,
47
- abort() {},
48
- };
49
- };
50
- const pi = {
51
- appendEntry: (type, data) => active.sessionManager.appendCustomEntry(type, data),
52
- setModel: async (model) => {
53
- active.model = model;
54
- return true;
55
- },
56
- setThinkingLevel: (level) => {
57
- active.thinkingLevel = level;
58
- },
59
- getAllTools: () => [],
60
- sendMessage() {},
61
- };
62
- const runtime = new RoutingRuntime(pi);
63
- try {
64
- await run({
65
- runtime,
66
- make,
67
- activate: (ctx) => {
68
- active = ctx;
69
- },
70
- pi,
71
- });
72
- } finally {
73
- runtime.unbind();
74
- await rm(root, { recursive: true, force: true });
75
- }
76
- }
77
-
78
- test("delayed old control cannot change or poison a newly bound session", async () =>
79
- environment(async ({ runtime, make, activate }) => {
80
- const a = make("a"),
81
- b = make("b");
82
- activate(a);
83
- await runtime.bind(a, cap);
84
- const original = EventStore.prototype.reconcile;
85
- let release, entered;
86
- const gate = new Promise((r) => {
87
- release = r;
88
- });
89
- const entrance = new Promise((r) => {
90
- entered = r;
91
- });
92
- EventStore.prototype.reconcile = async function () {
93
- await original.call(this);
94
- if (this.reader === a.sessionManager) {
95
- entered();
96
- await gate;
97
- }
98
- };
99
- try {
100
- const old = runtime.setManualProfile("executor");
101
- await entrance;
102
- activate(b);
103
- await runtime.bind(b, cap);
104
- const before = b.sessionManager.getEntries().length;
105
- release();
106
- assert.equal((await old).status, "blocked");
107
- assert.equal(b.sessionManager.getEntries().length, before);
108
- assert.equal(b.model.id, "coordinator");
109
- assert.equal(runtime.state().runtimeStatus, "active");
110
- assert.equal(replay(b.sessionManager.getBranch()).control, "automatic");
111
- } finally {
112
- release();
113
- EventStore.prototype.reconcile = original;
114
- }
115
- }));
116
-
117
- test("settled unbound attempt is retired truthfully and next request has a new identity", async () =>
118
- environment(async ({ runtime, make, activate }) => {
119
- const ctx = make("interrupt");
120
- activate(ctx);
121
- await runtime.bind(ctx, cap);
122
- const first = { role: "user", content: "one", timestamp: 2 };
123
- ctx.sessionManager.appendMessage(first);
124
- await runtime.context(ctx, [first]);
125
- const assistant = {
126
- role: "assistant",
127
- content: [{ type: "toolCall", id: "call", name: "read", arguments: {} }],
128
- timestamp: 3,
129
- };
130
- runtime.messageEnd(assistant);
131
- ctx.sessionManager.appendMessage(assistant);
132
- runtime.preflight({ toolName: "read", toolCallId: "call" }, ctx);
133
- const old = [...replay(ctx.sessionManager.getBranch()).executions.values()][0];
134
- await runtime.settled(ctx);
135
- const retired = replay(ctx.sessionManager.getBranch()).executions.get(old.id);
136
- assert.match(retired.interrupted, /unresolved/);
137
- assert.equal(retired.assistantEntryId, undefined);
138
- const second = { role: "user", content: "two", timestamp: 4 };
139
- ctx.sessionManager.appendMessage(second);
140
- await runtime.context(ctx, [first, assistant, second]);
141
- const next = {
142
- role: "assistant",
143
- content: [{ type: "toolCall", id: "next", name: "read", arguments: {} }],
144
- timestamp: 5,
145
- };
146
- runtime.messageEnd(next);
147
- ctx.sessionManager.appendMessage(next);
148
- runtime.preflight({ toolName: "read", toolCallId: "next" }, ctx);
149
- const executions = [...replay(ctx.sessionManager.getBranch()).executions.values()];
150
- assert.equal(executions.length, 2);
151
- assert.notEqual(executions[1].id, old.id);
152
- assert.notEqual(executions[1].basisUserEntryId, old.basisUserEntryId);
153
- }));