@hassangameryt/freeflow 0.7.3 → 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 (276) 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 +26 -31
  7. package/capabilities/cognitive-routing/SKILL.md +106 -161
  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 +5 -2
  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/{session-sources/events.js → cognitive-routing/event-store.js} +103 -40
  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-v2 → cognitive-routing}/projection.js +229 -56
  29. package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
  30. package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
  31. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +43 -7
  32. package/pi-extension/dist/cognitive-routing/session.js +221 -0
  33. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +99 -85
  34. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +37 -378
  35. package/pi-extension/dist/cognitive-routing/status.js +80 -0
  36. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/tools.js +12 -12
  37. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +5 -11
  38. package/pi-extension/dist/compaction/carry.js +191 -0
  39. package/pi-extension/dist/compaction/config.js +22 -0
  40. package/pi-extension/dist/compaction/controller.js +471 -0
  41. package/pi-extension/dist/compaction/harness.js +77 -0
  42. package/pi-extension/dist/compaction/results.js +158 -0
  43. package/pi-extension/dist/compaction/thresholds.js +35 -0
  44. package/pi-extension/dist/compaction/tool.js +57 -0
  45. package/pi-extension/dist/host/branch.js +13 -0
  46. package/pi-extension/dist/host/canonical.js +12 -0
  47. package/pi-extension/dist/host/catalog.js +77 -0
  48. package/pi-extension/dist/host/config.js +374 -0
  49. package/pi-extension/dist/host/projection-tags.js +60 -0
  50. package/pi-extension/dist/host/prompts.js +82 -0
  51. package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
  52. package/pi-extension/dist/{runtime → host}/request-history.js +121 -39
  53. package/pi-extension/dist/host/runtime-state.js +189 -0
  54. package/pi-extension/dist/{settings/settings-ui.js → host/settings/freeflow-command.js} +620 -410
  55. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
  56. package/pi-extension/dist/host/staging.js +28 -0
  57. package/pi-extension/dist/host/status.js +101 -0
  58. package/pi-extension/dist/index.js +423 -173
  59. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  60. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  61. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  62. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  63. package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
  64. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  65. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  66. package/pi-extension/dist/provider-support/effort.js +13 -0
  67. package/pi-extension/dist/provider-support/index.js +44 -3
  68. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  69. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  70. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  71. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  72. package/pi-extension/dist/provider-support/routes.js +17 -0
  73. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  74. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  76. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  77. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  78. package/pi-extension/dist/tool-execution/background.js +238 -0
  79. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  80. package/pi-extension/dist/tool-execution/config.js +22 -0
  81. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  82. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  83. package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
  84. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  85. package/pi-extension/dist/tool-execution/tools.js +16 -0
  86. package/pi-extension/freeflow/README.md +9 -0
  87. package/plugin.json +1 -1
  88. package/runtime/prompts/cognitive-routing.md +4 -4
  89. package/runtime/prompts/core.md +22 -75
  90. package/runtime/prompts/interaction-contract.md +15 -8
  91. package/runtime/prompts/tool-execution.md +38 -0
  92. package/runtime/prompts/working-method.md +108 -0
  93. package/skills/action-selection/SKILL.md +39 -47
  94. package/skills/action-selection/references/trajectory-stalls.md +2 -0
  95. package/skills/bypass/SKILL.md +2 -2
  96. package/skills/commit-work/SKILL.md +31 -83
  97. package/skills/commit-work/references/git-state-edges.md +1 -1
  98. package/skills/commit-work/references/simple-push.md +2 -0
  99. package/skills/commit-work/references/staging-decisions.md +1 -1
  100. package/skills/decision-gate/SKILL.md +31 -50
  101. package/skills/design-for-depth/SKILL.md +44 -53
  102. package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
  103. package/skills/design-for-depth/references/implementation-decisions.md +1 -1
  104. package/skills/design-for-depth/references/interface-design-loop.md +5 -36
  105. package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
  106. package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
  107. package/skills/diagnose-failure/SKILL.md +39 -24
  108. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
  109. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
  110. package/skills/discuss/SKILL.md +35 -11
  111. package/skills/discuss/references/discussion-continuity.md +7 -27
  112. package/skills/evaluate-skill/SKILL.md +49 -80
  113. package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
  114. package/skills/evaluate-skill/references/evaluation-design.md +14 -14
  115. package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
  116. package/skills/evaluate-skill/references/review-and-revision.md +3 -1
  117. package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
  118. package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
  119. package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
  120. package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
  121. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
  122. package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
  123. package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
  124. package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
  125. package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
  126. package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
  127. package/skills/execute-work/SKILL.md +41 -9
  128. package/skills/execute-work/references/code-practices.md +2 -0
  129. package/skills/execute-work/references/execute-work-edges.md +11 -1
  130. package/skills/handoff/SKILL.md +1 -1
  131. package/skills/handoff/references/handoff-templates.md +1 -1
  132. package/skills/review-artifact/SKILL.md +42 -41
  133. package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
  134. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  135. package/skills/review-work/SKILL.md +38 -32
  136. package/skills/review-work/references/adjudicate-work-review.md +1 -1
  137. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  138. package/skills/review-work/references/security-risk-lens.md +2 -4
  139. package/skills/setup-freeflow/SKILL.md +3 -1
  140. package/skills/setup-freeflow/references/activation-contract.md +7 -13
  141. package/skills/setup-freeflow/references/host-setup.md +3 -3
  142. package/skills/track-work/SKILL.md +113 -161
  143. package/skills/track-work/references/working-record-format.md +189 -350
  144. package/skills/track-work/scripts/lib/document.mjs +62 -6
  145. package/skills/track-work/scripts/lib/format.mjs +16 -1
  146. package/skills/track-work/scripts/lib/store.mjs +9 -8
  147. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  148. package/skills/track-work/scripts/working-record.mjs +34 -12
  149. package/skills/verify-work/SKILL.md +29 -20
  150. package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
  151. package/skills/verify-work/references/integration-evidence.md +2 -0
  152. package/skills/verify-work/references/performance-evidence.md +3 -1
  153. package/skills/verify-work/references/test-design.md +3 -1
  154. package/skills/workflow/SKILL.md +65 -21
  155. package/skills/workflow/references/domain-skill-composition.md +5 -3
  156. package/skills/write-docs/SKILL.md +83 -0
  157. package/skills/write-docs/references/documentation-shapes.md +119 -0
  158. package/skills/write-docs/scripts/docs-check.mjs +139 -0
  159. package/skills/write-plan/SKILL.md +40 -40
  160. package/skills/write-plan/references/plan-shapes.md +1 -1
  161. package/skills/write-skill/SKILL.md +107 -147
  162. package/skills/write-skill/references/activation-boundaries.md +2 -0
  163. package/skills/write-skill/references/agent-first-instructions.md +22 -10
  164. package/skills/write-skill/references/progressive-disclosure.md +4 -2
  165. package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
  166. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  167. package/skills/write-skill/scripts/skill-author.mjs +18 -2
  168. package/skills/write-spec/SKILL.md +34 -41
  169. package/skills/write-spec/references/artifact-standards.md +1 -1
  170. package/skills/write-spec/references/decision-records.md +2 -0
  171. package/skills/write-spec/references/spec-shapes.md +1 -1
  172. package/capabilities/context-virtualization/SKILL.md +0 -103
  173. package/capabilities/conversation-history/SKILL.md +0 -53
  174. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  175. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  176. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  177. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  178. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  179. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  180. package/pi-extension/dist/context-virtualization/state.js +0 -57
  181. package/pi-extension/dist/context-virtualization/types.js +0 -10
  182. package/pi-extension/dist/conversation-history/passages.js +0 -66
  183. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  184. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  185. package/pi-extension/dist/conversation-history/search.js +0 -56
  186. package/pi-extension/dist/conversation-history/types.js +0 -1
  187. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  188. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  189. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  190. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  191. package/pi-extension/dist/freeflow-context/types.js +0 -12
  192. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  193. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  194. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  195. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  196. package/pi-extension/dist/session-sources/provenance.js +0 -32
  197. package/pi-extension/dist/ui/text-component.js +0 -57
  198. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  199. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  200. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  201. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  202. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  203. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  204. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  205. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  206. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  207. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  208. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  209. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  210. package/pi-extension/src/context-virtualization/state.ts +0 -79
  211. package/pi-extension/src/context-virtualization/types.ts +0 -92
  212. package/pi-extension/src/conversation-history/passages.ts +0 -81
  213. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  214. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  215. package/pi-extension/src/conversation-history/search.ts +0 -79
  216. package/pi-extension/src/conversation-history/types.ts +0 -31
  217. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  218. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  219. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  220. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  221. package/pi-extension/src/freeflow-context/types.ts +0 -70
  222. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  223. package/pi-extension/src/index.ts +0 -390
  224. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  225. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  226. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  227. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  228. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  229. package/pi-extension/src/provider-support/index.ts +0 -5
  230. package/pi-extension/src/runtime/request-history.ts +0 -243
  231. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  232. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  233. package/pi-extension/src/session-sources/events.ts +0 -230
  234. package/pi-extension/src/session-sources/provenance.ts +0 -39
  235. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  236. package/pi-extension/src/session-sources/sources.ts +0 -334
  237. package/pi-extension/src/settings/settings-tui.ts +0 -501
  238. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  239. package/pi-extension/src/ui/text-component.ts +0 -66
  240. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  241. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  242. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  243. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  244. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  245. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  246. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  247. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  248. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  249. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  250. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  251. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  252. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  253. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  254. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  255. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  256. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  257. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  258. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  259. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  260. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  261. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  262. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  263. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  264. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  265. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  266. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  267. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  268. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  269. package/pi-extension/tests/provider-support/native.test.js +0 -183
  270. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  271. package/pi-extension/tsconfig.json +0 -16
  272. package/runtime/prompts/context-virtualization.md +0 -3
  273. package/runtime/prompts/conversation-history.md +0 -3
  274. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  275. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  276. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +0 -0
@@ -1,19 +1,25 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
3
- import { canonical, idFor, isWorkerProfile, refFor } from "../cognitive-routing-v2/types.js";
3
+ import { canonical, idFor, isWorkerProfile, refFor } from "./types.js";
4
+ import { isHostEdited, isProjected, projectedEntryId } from "../host/projection-tags.js";
4
5
  export const bodyHash = (value) => createHash("sha256").update(canonical(value)).digest("hex");
5
- export const textRef = (ref) => `${ref}#text`;
6
+ /** Define a memoized hash so history is hashed only when a consumer needs it. */
7
+ function lazyHash(target, compute) {
8
+ let value;
9
+ return Object.defineProperty(target, "hash", {
10
+ enumerable: true,
11
+ get: () => (value ??= compute()),
12
+ });
13
+ }
6
14
  const routingTools = new Set(["freeflow_delegate", "freeflow_return", "freeflow_project", "freeflow_unit"]);
7
- // Discovery and new selections share this policy. Existing saved selections keep
8
- // their original delivery semantics; mixed assistant messages offer visible text.
15
+ // Discovery, new selections, and delivery share this policy: evidence is what tools produced. What a worker
16
+ // says belongs in its report, so its assistant messages are never evidence.
9
17
  export function isTaskEvidence(source) {
10
- if (source.original) return true;
11
- if (source.message.role === "toolResult") return !routingTools.has(source.message.toolName);
12
- return (
13
- source.message.role === "assistant" &&
14
- !source.message.content?.some((b) => b.type === "toolCall" && routingTools.has(b.name)) &&
15
- source.message.content?.some((b) => b.type === "text" && b.text?.trim())
16
- );
18
+ return source.message.role === "toolResult" && !routingTools.has(source.message.toolName);
19
+ }
20
+ /** Saved selections keep their delivery, except assistant messages (whole or `#text`), which are never evidence. */
21
+ export function deliveredSelection(sources, ref) {
22
+ return !ref.endsWith("#text") && sources.byRef.get(ref)?.message.role !== "assistant";
17
23
  }
18
24
  const callKey = (id, name) => JSON.stringify([id, name]);
19
25
  const locatorLimit = 160;
@@ -39,11 +45,15 @@ export class Sources {
39
45
  byRef = new Map();
40
46
  ambiguous = new Set();
41
47
  byBody = new Map();
48
+ // Sources not yet placed in byBody; the content index is only needed for unprojected association.
49
+ unindexed = [];
42
50
  exchanges = new Map();
43
51
  owners = new Map();
44
52
  tail;
45
53
  associated;
46
54
  entries = [];
55
+ ranks;
56
+ nonWorker;
47
57
  activeIds;
48
58
  locatorProvider;
49
59
  constructor(entries, state, activeIds, locatorProvider) {
@@ -54,9 +64,12 @@ export class Sources {
54
64
  const prefix = this.entries.length <= entries.length && this.entries.every((e, i) => e === entries[i]);
55
65
  const start = prefix ? this.entries.length : 0;
56
66
  this.associated = undefined;
67
+ this.ranks = undefined;
68
+ this.nonWorker = undefined;
57
69
  if (!prefix) {
58
70
  this.byRef.clear();
59
71
  this.byBody.clear();
72
+ this.unindexed = [];
60
73
  this.exchanges.clear();
61
74
  this.owners.clear();
62
75
  this.tail = undefined;
@@ -67,22 +80,20 @@ export class Sources {
67
80
  if (messages.length !== 1 || ["compaction", "branch_summary"].includes(entry.type)) continue;
68
81
  const message = messages[0];
69
82
  if (!message) continue;
70
- const source = {
71
- ref: refFor(entry.id),
72
- entry,
73
- message,
74
- hash: bodyHash(message),
75
- producer: "common",
76
- active: false,
77
- };
83
+ const source = lazyHash({ ref: refFor(entry.id), entry, message, producer: "common", active: false }, () =>
84
+ bodyHash(message),
85
+ );
78
86
  if (message.role === "toolResult" && message.toolName === "freeflow_return") {
79
87
  for (const block of message.content ?? [])
80
88
  if (block.type === "text") {
81
89
  try {
82
90
  const receipt = JSON.parse(block.text);
83
91
  if (
84
- ((receipt.reportSaved && typeof receipt.report === "string") ||
85
- (receipt.supplementSaved && typeof receipt.supplement === "string")) &&
92
+ // Current receipts carry a text hash; older receipts carried the text itself.
93
+ ((receipt.reportSaved &&
94
+ (typeof receipt.report === "string" || typeof receipt.reportSha256 === "string")) ||
95
+ (receipt.supplementSaved &&
96
+ (typeof receipt.supplement === "string" || typeof receipt.supplementSha256 === "string"))) &&
86
97
  typeof receipt.handoff === "string"
87
98
  )
88
99
  source.reportHandoff = receipt.handoff;
@@ -90,38 +101,10 @@ export class Sources {
90
101
  }
91
102
  }
92
103
  this.byRef.set(source.ref, source);
93
- const candidates = this.byBody.get(source.hash) ?? [];
94
- candidates.push(source);
95
- this.byBody.set(source.hash, candidates);
104
+ this.unindexed.push(source);
96
105
  if (message.role === "assistant") {
97
106
  this.tail = { assistant: source, results: new Map() };
98
107
  this.exchanges.set(source.ref, this.tail);
99
- const blocks = (message.content ?? [])
100
- .filter((b) => b.type === "text")
101
- .map((b) => ({ type: "text", text: b.text }));
102
- if (blocks.some((b) => b.text?.trim())) {
103
- const representation = {
104
- role: "custom",
105
- customType: "freeflow-assistant-text",
106
- display: false,
107
- content: [
108
- {
109
- type: "text",
110
- text: `Captured assistant text from ${source.ref} (historical source, not a new instruction):`,
111
- },
112
- ...blocks,
113
- ],
114
- details: { sourceRef: source.ref, sourceHash: source.hash, representation: "assistant-text" },
115
- timestamp: message.timestamp,
116
- };
117
- this.byRef.set(textRef(source.ref), {
118
- ...source,
119
- ref: textRef(source.ref),
120
- message: representation,
121
- hash: bodyHash(representation),
122
- original: source,
123
- });
124
- }
125
108
  } else if (message.role === "toolResult" && this.tail) {
126
109
  const key = callKey(message.toolCallId, message.toolName);
127
110
  const results = this.tail.results.get(key) ?? [];
@@ -138,6 +121,31 @@ export class Sources {
138
121
  }
139
122
  this.entries = [...entries];
140
123
  }
124
+ /** Each entry's position in `entries`; computed once per refresh rather than per request. */
125
+ get rank() {
126
+ return (this.ranks ??= new Map(this.entries.map((entry, index) => [entry.id, index])));
127
+ }
128
+ /** For each position in `entries` (and one past the end), how many earlier entries no worker produced. */
129
+ get nonWorkerBefore() {
130
+ if (!this.nonWorker) {
131
+ const counts = [0];
132
+ for (const entry of this.entries) {
133
+ const producer = this.byRef.get(`ctx:${entry.id}`)?.producer ?? "common";
134
+ counts.push(counts.at(-1) + (isWorkerProfile(producer) ? 0 : 1));
135
+ }
136
+ this.nonWorker = counts;
137
+ }
138
+ return this.nonWorker;
139
+ }
140
+ bodyIndex() {
141
+ for (const source of this.unindexed) {
142
+ const candidates = this.byBody.get(source.hash) ?? [];
143
+ candidates.push(source);
144
+ this.byBody.set(source.hash, candidates);
145
+ }
146
+ this.unindexed = [];
147
+ return this.byBody;
148
+ }
141
149
  associate(messages) {
142
150
  if (
143
151
  this.associated &&
@@ -147,38 +155,42 @@ export class Sources {
147
155
  return this.associated.items;
148
156
  this.ambiguous.clear();
149
157
  for (const source of this.byRef.values()) source.active = false;
150
- const used = new Set(),
151
- hashes = messages.map((message) => {
152
- const direct = bodyHash(message);
153
- if (this.byBody.has(direct) || message?.role !== "toolResult" || !Array.isArray(message.content)) return direct;
154
- const last = message.content.at(-1);
155
- if (last?.type !== "text") return direct;
156
- const original = bodyHash({ ...message, content: message.content.slice(0, -1) });
157
- // Freeflow's stable reference decoration does not change the native evidence body.
158
- return this.byBody.get(original)?.some((s) => last.text === `[context-ref: ${s.ref}]`) ? original : direct;
159
- }),
158
+ const projected = messages.some(isProjected);
159
+ // After an aligned projection, only host-edited messages can match a source by content: the others are
160
+ // tagged, sourceless (summaries, multi-message entries), or generated after the projection.
161
+ const contentOnly = (message) => !projected || isHostEdited(message);
162
+ const direct = messages.map((message) => {
163
+ const id = projectedEntryId(message);
164
+ const source = id === undefined ? undefined : this.byRef.get(refFor(id));
165
+ return source && (!this.activeIds || this.activeIds.has(source.entry.id)) ? source : undefined;
166
+ });
167
+ const used = new Set(direct.flatMap((source) => (source ? [source.ref] : []))),
168
+ hashes = messages.map((message, index) =>
169
+ direct[index] || !contentOnly(message) ? undefined : bodyHash(message),
170
+ ),
160
171
  remaining = new Map();
161
- for (const hash of hashes) remaining.set(hash, (remaining.get(hash) ?? 0) + 1);
172
+ for (const hash of hashes) if (hash) remaining.set(hash, (remaining.get(hash) ?? 0) + 1);
162
173
  const items = messages.map((message, index) => {
174
+ if (direct[index]) {
175
+ const source = direct[index];
176
+ source.active = true;
177
+ return { message, source };
178
+ }
179
+ if (hashes[index] === undefined) return { message };
163
180
  const hash = hashes[index],
164
181
  count = remaining.get(hash);
165
182
  remaining.set(hash, count - 1);
166
- const candidates = (this.byBody.get(hash) ?? []).filter(
183
+ const candidates = (this.bodyIndex().get(hash) ?? []).filter(
167
184
  (s) => !used.has(s.ref) && (!this.activeIds || this.activeIds.has(s.entry.id)),
168
185
  );
169
186
  const exact = candidates.find((s) => s.message === message);
170
187
  const source = exact ?? (candidates.length === 1 || candidates.length === count ? candidates[0] : undefined);
171
188
  if (!source) {
172
- for (const candidate of candidates) {
173
- this.ambiguous.add(candidate.ref);
174
- this.ambiguous.add(textRef(candidate.ref));
175
- }
189
+ for (const candidate of candidates) this.ambiguous.add(candidate.ref);
176
190
  return { message };
177
191
  }
178
192
  used.add(source.ref);
179
193
  source.active = true;
180
- const text = this.byRef.get(textRef(source.ref));
181
- if (text) text.active = true;
182
194
  return { message, source };
183
195
  });
184
196
  this.associated = { messages: [...messages], items };
@@ -188,8 +200,7 @@ export class Sources {
188
200
  const source = this.byRef.get(ref);
189
201
  if (!idFor(ref) || !source)
190
202
  return { ref, code: "source_unavailable", detail: "Exact source is unavailable on current native ancestry." };
191
- const original = source.original ?? source;
192
- if (bodyHash(original.message) !== original.hash || bodyHash(source.message) !== source.hash)
203
+ if (bodyHash(source.message) !== source.hash)
193
204
  return {
194
205
  ref,
195
206
  code: "source_changed",
@@ -197,7 +208,7 @@ export class Sources {
197
208
  };
198
209
  if (!isWorkerProfile(source.producer))
199
210
  return { ref, code: "source_origin", detail: "Selection requires observed worker attribution." };
200
- if (state.exposure.get(original.ref) !== original.hash && state.exposure.get(source.ref) !== source.hash)
211
+ if (state.exposure.get(source.ref) !== source.hash)
201
212
  return {
202
213
  ref,
203
214
  code: "source_unexposed",
@@ -210,17 +221,21 @@ export class Sources {
210
221
  if (problem) return problem;
211
222
  const source = this.byRef.get(ref);
212
223
  if (!isTaskEvidence(source))
213
- return {
214
- ref,
215
- code: "routing_source",
216
- detail: this.byRef.has(textRef(ref))
217
- ? `Routing control messages are not task evidence. Use ${textRef(ref)} if its visible assistant text is the intended evidence.`
218
- : "Routing control receipts are not task evidence; saved communication is delivered separately.",
219
- };
224
+ return source.message.role === "assistant"
225
+ ? {
226
+ ref,
227
+ code: "assistant_message",
228
+ detail:
229
+ "Assistant messages are not task evidence; say it in the report and select the tool results it rests on.",
230
+ }
231
+ : {
232
+ ref,
233
+ code: "routing_source",
234
+ detail: "Routing control receipts are not task evidence; saved communication is delivered separately.",
235
+ };
220
236
  return undefined;
221
237
  }
222
238
  exchange(source) {
223
- if (source.original) return { sources: [source], problems: [] };
224
239
  if (!["assistant", "toolResult"].includes(source.message.role)) return { sources: [source], problems: [] };
225
240
  const group = source.message.role === "assistant" ? this.exchanges.get(source.ref) : this.owners.get(source.ref);
226
241
  const calls = group?.assistant.message.content?.filter((b) => b.type === "toolCall") ?? [];
@@ -257,7 +272,7 @@ export class Sources {
257
272
  }
258
273
  locator(ref) {
259
274
  const source = this.byRef.get(ref);
260
- if (!source || source.original || source.message.role !== "toolResult") return undefined;
275
+ if (!source || source.message.role !== "toolResult") return undefined;
261
276
  const group = this.owners.get(source.ref);
262
277
  if (!group) return undefined;
263
278
  const calls = group.assistant.message.content?.filter((b) => b.type === "toolCall") ?? [];
@@ -268,10 +283,9 @@ export class Sources {
268
283
  }
269
284
  ordered(sources) {
270
285
  const ids = new Set([...sources].map((s) => s.ref));
271
- return this.entries.flatMap((entry) =>
272
- [this.byRef.get(refFor(entry.id)), this.byRef.get(textRef(refFor(entry.id)))].filter(
273
- (s) => !!s && ids.has(s.ref),
274
- ),
275
- );
286
+ return this.entries.flatMap((entry) => {
287
+ const source = this.byRef.get(refFor(entry.id));
288
+ return source && ids.has(source.ref) ? [source] : [];
289
+ });
276
290
  }
277
291
  }