@hassangameryt/freeflow 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,142 @@
1
+ /*
2
+ * The inbound channel: content that reaches the model from outside the turn (a background command's exit, and later
3
+ * monitor events and messages from other sessions). One delivery rule for all of them:
4
+ * - while the agent works, a notice is steered in after the current tool batch;
5
+ * - while it is idle, a notice that should wake it starts a turn as Freeflow's own message (host/section-keeper.ts
6
+ * keeps the last prompt's system sections on such a run);
7
+ * - a notice that should not wake the agent waits for the next turn, and so does every notice to a session that has
8
+ * never had a prompt: Pi before 1.2.0 builds the system prompt only for prompts, so a turn there would have none;
9
+ * Notices from one source within a batching window join one notice, and a source that sends faster than its limit is
10
+ * stopped with one notice saying so. Pi sends every custom message to the model in the user role, so a notice marks
11
+ * its source itself: a `<freeflow-notice>` tag for Freeflow's own (freeflowNotice()), a `<message>` tag for a message
12
+ * from another session. Notices are appended to the conversation; nothing earlier changes.
13
+ */
14
+ import { Text } from "@earendil-works/pi-tui";
15
+ /** The marker Freeflow's notices carried before the tag; saved sessions still contain it, so rows still read it. */
16
+ export const LEGACY_NOTICE_PREFIX = "[Freeflow notice, not from the user]";
17
+ /**
18
+ * Text that came from outside Freeflow (a command's output, another session's message) with every `<freeflow-notice`
19
+ * tag in it broken, so it can neither close a notice nor open one that looks like Freeflow's.
20
+ */
21
+ export function withoutNoticeTags(text) {
22
+ return text.replace(/<(\/?)(freeflow-notice)/gi, "<\\$1$2");
23
+ }
24
+ /**
25
+ * Freeflow's own notice as the model reads it. Pi gives the model every custom message in the user role; the tag, which
26
+ * Freeflow's guidance explains, says the text is Freeflow's and not the user's. The text cannot open or close the tag.
27
+ */
28
+ export function freeflowNotice(type, text) {
29
+ return `<freeflow-notice type="${type}">\n${withoutNoticeTags(text)}\n</freeflow-notice>`;
30
+ }
31
+ /** A notice's text without its tag, or without the old prefix. */
32
+ export function noticeBody(content) {
33
+ return content
34
+ .replace(/^\s*<freeflow-notice[^>]*>\n?/, "")
35
+ .replace(/\n?<\/freeflow-notice>\s*$/, "")
36
+ .split(LEGACY_NOTICE_PREFIX)
37
+ .join("")
38
+ .trim();
39
+ }
40
+ export class Inbound {
41
+ host;
42
+ pending = new Map();
43
+ sent = new Map();
44
+ limited = new Set();
45
+ constructor(host) {
46
+ this.host = host;
47
+ }
48
+ /** Deliver one notice now, or, with a batch key, after its window together with the others for that key. */
49
+ deliver(notice, batch) {
50
+ if (!batch) return void this.send(notice);
51
+ if (this.limited.has(batch.key)) return;
52
+ const pending = this.pending.get(batch.key);
53
+ if (pending) {
54
+ pending.notices.push(notice);
55
+ return;
56
+ }
57
+ const timer = setTimeout(() => this.flush(batch.key), batch.windowMs ?? 200);
58
+ this.pending.set(batch.key, { notices: [notice], timer, batch });
59
+ }
60
+ /** Deliver what a key has pending now, before its window ends: its source finished and has more to say after it. */
61
+ flush(key) {
62
+ const pending = this.pending.get(key);
63
+ if (!pending) return;
64
+ clearTimeout(pending.timer);
65
+ this.pending.delete(key);
66
+ this.sendBatch(pending);
67
+ }
68
+ /** Forget a source: drop what it has pending and its limit state. */
69
+ close(key) {
70
+ const pending = this.pending.get(key);
71
+ if (pending) clearTimeout(pending.timer);
72
+ this.pending.delete(key);
73
+ this.sent.delete(key);
74
+ this.limited.delete(key);
75
+ }
76
+ /** Drop everything pending, at session end. */
77
+ reset() {
78
+ for (const { timer } of this.pending.values()) clearTimeout(timer);
79
+ this.pending.clear();
80
+ this.sent.clear();
81
+ this.limited.clear();
82
+ }
83
+ sendBatch({ notices, batch }) {
84
+ const now = Date.now();
85
+ const recent = (this.sent.get(batch.key) ?? []).filter((at) => now - at < 60_000);
86
+ if (batch.perMinute !== undefined && recent.length >= batch.perMinute) {
87
+ this.limited.add(batch.key);
88
+ batch.onLimit?.();
89
+ return;
90
+ }
91
+ this.sent.set(batch.key, [...recent, now]);
92
+ const [first] = notices;
93
+ // send() never rejects: a failed delivery is dropped there.
94
+ void this.send({
95
+ customType: first.customType,
96
+ content: batch.join ? batch.join(notices) : notices.map((notice) => notice.content).join("\n"),
97
+ details: { ...first.details, batched: notices.length },
98
+ wake: notices.some((notice) => notice.wake),
99
+ });
100
+ }
101
+ send(notice) {
102
+ const message = { customType: notice.customType, content: notice.content, display: true, details: notice.details };
103
+ const wake = notice.wake && this.host.canWake?.() !== false;
104
+ // A send that throws at once is caught like one that rejects later: a notice never breaks its source.
105
+ const asMessage = () =>
106
+ new Promise((resolve) =>
107
+ resolve(
108
+ this.host.sendMessage(message, wake ? { triggerTurn: true, deliverAs: "steer" } : { triggerTurn: false }),
109
+ ),
110
+ ).then(
111
+ () => {},
112
+ () => {},
113
+ );
114
+ // Always Freeflow's own message, never the user's: the run it starts keeps Freeflow's system sections
115
+ // (host/section-keeper.ts); Pi #10267 once made idle notices go out as user messages.
116
+ return asMessage();
117
+ }
118
+ }
119
+ /**
120
+ * A notice in the transcript: one line naming its source and what happened, expanding to the whole notice. The model
121
+ * receives the same text either way.
122
+ */
123
+ export function renderNotice(label) {
124
+ return (message, options, theme) => {
125
+ const content = typeof message?.content === "string" ? message.content : "";
126
+ const body = noticeBody(content);
127
+ const paint = (color, text) => (theme?.fg ? theme.fg(color, text) : text);
128
+ // Blank lines separate a notice's parts; the row skips them.
129
+ const lines = body.split("\n").filter((line) => line.trim() !== "");
130
+ // A heading that ends in ":" introduces lines (a monitor's events): the row shows the first of them too.
131
+ const introduces = (lines[0] ?? "").endsWith(":") && lines.length > 1;
132
+ const more = introduces && lines.length > 2 ? ` (+${lines.length - 2} more)` : "";
133
+ // A long first line (a compaction notice is one paragraph) shows its first sentence; the rest is on expand.
134
+ const opening = lines[0] ?? "";
135
+ const sentence = opening.length > 140 ? opening.slice(0, opening.search(/\.(\s|$)/) + 1) || opening : opening;
136
+ const first = introduces ? `${lines[0]} ${lines[1]}${more}` : sentence;
137
+ // "Background command bg1 completed" reads as label plus the rest; anything else gets the label in front.
138
+ const rest = first.toLowerCase().startsWith(label.toLowerCase()) ? first.slice(label.length).trimStart() : first;
139
+ const head = `${paint("customMessageLabel", label)} ${paint("muted", rest)}`;
140
+ return new Text(options?.expanded && (body.includes("\n") || first !== body) ? `${head}\n\n${body}` : head, 0, 0);
141
+ };
142
+ }
@@ -17,7 +17,7 @@ export function tagProjectedMessages(messages, projection, branch) {
17
17
  for (const projected of entry.messages) if (projected?.role !== messages[index++]?.role) return;
18
18
  // Host edits change the visible body, so their targets keep exact content association.
19
19
  const edited = new Set();
20
- for (const entry of branch) if (entry.type === "context_edit") edited.add(entry.targetId);
20
+ for (const entry of branch) if (entry.type === "context_edit" && entry.targetId) edited.add(entry.targetId);
21
21
  index = 0;
22
22
  for (const entry of projection.entries) {
23
23
  const source = entry.sourceEntry;
@@ -50,33 +50,44 @@ function runtimeContextCacheSatisfies(capabilityState) {
50
50
  interactionContractPrompt: capabilityState?.enabled === true,
51
51
  cognitiveRoutingPrompt: capabilityState?.cognitiveRouting?.effective === true,
52
52
  };
53
- return Object.entries(expected).every(([key, required]) => !required || isPromptAvailable(runtimeContextCache[key]));
53
+ return Object.entries(expected).every(
54
+ ([key, required]) => !required || isPromptAvailable(runtimeContextCache?.[key]),
55
+ );
54
56
  }
55
57
  export async function refreshRuntimeContext(capabilityState = undefined) {
56
58
  runtimeContextCache = await loadRuntimeContext(capabilityState);
57
59
  return runtimeContextCache;
58
60
  }
59
61
  export async function getRuntimeContext(capabilityState = undefined) {
60
- if (runtimeContextCacheSatisfies(capabilityState)) {
62
+ if (runtimeContextCache && runtimeContextCacheSatisfies(capabilityState)) {
61
63
  return runtimeContextCache;
62
64
  }
63
65
  return refreshRuntimeContext(capabilityState);
64
66
  }
65
- export function stableRuntimeContext(context) {
67
+ /**
68
+ * Freeflow's system section for the current configuration: sent only while Freeflow is enabled, with the routing cue
69
+ * only while Cognitive Routing is effective (Task 003 D-002). It changes only when a setting changes, which costs one
70
+ * prompt-cache write (D-001); between such changes it is identical from request to request.
71
+ */
72
+ export function stableRuntimeContext(context, options = {}) {
73
+ // A prompt the user turned off (`prompts` in the config) is left out; a missing file is a different matter, which
74
+ // hasUsableMandatoryPrompts reports.
75
+ const on = (prompt) => options.prompts?.[prompt] !== false;
66
76
  const sections = [
67
- ["Freeflow core", context?.corePrompt],
68
- ["Freeflow core", context?.interactionContractPrompt],
69
- ["Working Method", context?.workingMethodPrompt],
70
- ["Cognitive Routing", context?.cognitiveRoutingPrompt],
77
+ ...(on("core") ? [["Freeflow core", context?.corePrompt]] : []),
78
+ ...(on("interactionContract") ? [["Freeflow core", context?.interactionContractPrompt]] : []),
79
+ ...(on("workingMethod") ? [["Working Method", context?.workingMethodPrompt]] : []),
80
+ ...(options.routing ? [["Cognitive Routing", context?.cognitiveRoutingPrompt]] : []),
71
81
  ];
72
82
  return [
73
- "# Freeflow availability contract",
74
- "Freeflow keeps a fixed reference catalog of its instructions, skills, and tools to preserve prompt caching. Presence in this catalog does not mean a feature is active or an operation is permitted.",
75
- "Apply the following guidance and Freeflow skills only when the latest extension-generated Freeflow Runtime State marks the corresponding feature active. When Freeflow is inactive, unavailable, or not configured, its core and capability guidance is dormant; do not bootstrap or follow it merely because it is listed. Explicit user instructions retain their normal authority.",
76
- "For Cognitive Routing, follow the latest control/profile/responsibility snapshots on this branch. Earlier snapshots and notices are historical; they grant no current permission. Tool permissions are checked by the runtime, even though all definitions remain visible. Never call a disabled operation.",
77
- "Messages that start with `[Freeflow notice, not from the user]` come from Freeflow, not the user. They report facts and harness steps; they are not the user's instructions.",
83
+ "Text inside `<freeflow-notice>` tags comes from Freeflow, not the user: it reports facts and harness steps and is not the user's instruction, except the user's own earlier messages it quotes in `<user-message>` tags.",
84
+ ...(options.routing
85
+ ? [
86
+ "For Cognitive Routing, follow the latest control/profile/responsibility snapshots on this branch. Earlier snapshots and notices are historical; they grant no current permission. Tool permissions are checked by the runtime.",
87
+ ]
88
+ : []),
78
89
  ...sections
79
90
  .filter(([, text]) => isPromptAvailable(text))
80
- .map(([feature, text]) => `## Reference guidance: ${feature}\n\n${text.trim()}`),
91
+ .map(([feature, text]) => `## ${feature}\n\n${text.trim()}`),
81
92
  ].join("\n\n");
82
93
  }
@@ -253,7 +253,12 @@ export async function persistedBranchMatches(reader, leaf, branch) {
253
253
  const matches =
254
254
  snapshot.sessionId === reader.getSessionId() &&
255
255
  JSON.stringify(activeReadOnlySessionBranch(snapshot, leaf)) === JSON.stringify(branch);
256
- if (matches) trustLoadedSession(reader);
256
+ // The trust point covers every entry Pi holds, so it is set only when the file holds them all: a match on this
257
+ // branch says nothing of an entry on another branch that was never written.
258
+ const onDisk = new Set(snapshot.entries.slice(1).map((entry) => entry.id));
259
+ const inMemory = reader.getEntries();
260
+ const complete = inMemory.length === onDisk.size && inMemory.every((entry) => onDisk.has(entry.id));
261
+ if (matches && complete) trustLoadedSession(reader);
257
262
  else verified.delete(path);
258
263
  return matches;
259
264
  }
@@ -62,7 +62,7 @@ export class RequestHistory {
62
62
  branchCache;
63
63
  frameHash = (entry) => {
64
64
  const cached = this.validatedFrames.get(entry.id);
65
- return cached?.entry === entry ? cached.hash : hash(entry);
65
+ return cached !== undefined && cached.entry === entry ? cached.hash : hash(entry);
66
66
  };
67
67
  // Unedited native entries and Freeflow's renderings of them never change under their identity, so their
68
68
  // fingerprints are computed once per session.
@@ -178,11 +178,15 @@ export class RequestHistory {
178
178
  const byId = extendsCache ? (newEntries.length ? new Map(cachedBranch.byId) : cachedBranch.byId) : new Map();
179
179
  for (const entry of newEntries) {
180
180
  const cached = this.validatedFrames.get(entry.id);
181
- const same = cached?.entry === entry;
181
+ const same = cached !== undefined && cached.entry === entry;
182
182
  const frame = same ? cached.frame : parse(entry.data);
183
183
  if (
184
184
  (frame.basis !== entry.parentId &&
185
- !(reader.getHeader?.()?.parentSession && !ids.has(frame.basis) && !newIds.has(frame.basis))) ||
185
+ // A null basis is in neither set, so it counts as outside the parent session's branch.
186
+ !(
187
+ reader.getHeader?.()?.parentSession &&
188
+ (frame.basis === null || (!ids.has(frame.basis) && !newIds.has(frame.basis)))
189
+ )) ||
186
190
  byId.has(frame.id)
187
191
  )
188
192
  throw new Error("Context frame anchor differs");
@@ -0,0 +1,25 @@
1
+ import { Text } from "@earendil-works/pi-tui";
2
+ /**
3
+ * A transcript row the way Pi draws its own: a one-line head, then the body. A short body (a few lines) shows in both
4
+ * views; a long one shows only when the row is expanded (ctrl+o), with a count of what is hidden.
5
+ */
6
+ export const SHORT_LINES = 4;
7
+ const SHORT_CHARS = 400;
8
+ export function paint(theme, color, text) {
9
+ return theme?.fg ? theme.fg(color, text) : text;
10
+ }
11
+ export function firstLine(text, max = 100) {
12
+ const line =
13
+ String(text ?? "")
14
+ .split("\n")
15
+ .find((each) => each.trim()) ?? "";
16
+ const clean = line.trim();
17
+ return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
18
+ }
19
+ export function foldRow(head, body, expanded, theme) {
20
+ const lines = body.flatMap((each) => String(each).split("\n"));
21
+ const short = lines.length <= SHORT_LINES && lines.join("\n").length <= SHORT_CHARS;
22
+ if (!lines.length) return new Text(head, 0, 0);
23
+ if (expanded || short) return new Text(`${head}\n${lines.map((line) => ` ${line}`).join("\n")}`, 0, 0);
24
+ return new Text(`${head} ${paint(theme, "dim", `(${lines.length} lines, ctrl+o to expand)`)}`, 0, 0);
25
+ }
@@ -1,3 +1,4 @@
1
+ import { reviewStateLine, skillsStateLine } from "./config-validation.js";
1
2
  import { hasUsableMandatoryPrompts } from "./prompts.js";
2
3
  /**
3
4
  * The Freeflow Runtime State message: what is active this request, placed so unchanged state keeps its cached
@@ -38,12 +39,14 @@ export function filterBootstrapMessage(message) {
38
39
  }
39
40
  function publicCognitiveRoutingControl(controlMode) {
40
41
  if (controlMode === "automatic") return "automatic";
41
- if (["manual-helper", "manual-executor", "manual-coordinator"].includes(controlMode)) return "manual";
42
+ if (["manual-helper", "manual-executor", "manual-coordinator"].includes(String(controlMode))) return "manual";
42
43
  return "unavailable";
43
44
  }
44
45
  function publicCognitiveRoutingProfile(activeProfile, effective) {
45
46
  if (effective !== true) return "unavailable";
46
- return ["helper", "executor", "coordinator"].includes(activeProfile) ? activeProfile : "unavailable";
47
+ return typeof activeProfile === "string" && ["helper", "executor", "coordinator"].includes(activeProfile)
48
+ ? activeProfile
49
+ : "unavailable";
47
50
  }
48
51
  function publicCapabilityStatus(capability) {
49
52
  if (capability?.effective === true) return "active";
@@ -93,6 +96,9 @@ export function freeflowRuntimeStateMessage(
93
96
  : capabilityState?.configExists
94
97
  ? "config error"
95
98
  : "setup needed";
99
+ // Only a choice the user made: the default adds nothing, so most users' state stays as it was.
100
+ const review = freeflowStatus === "active" ? reviewStateLine(capabilityState?.review) : null;
101
+ const skillsOff = freeflowStatus === "active" ? skillsStateLine(capabilityState?.skills?.off) : null;
96
102
  return {
97
103
  role: "custom",
98
104
  customType: FREEFLOW_RUNTIME_STATE_MESSAGE_TYPE,
@@ -102,10 +108,15 @@ export function freeflowRuntimeStateMessage(
102
108
  "This is extension-generated runtime state. Use it to interpret the stable Freeflow guidance.",
103
109
  "",
104
110
  `Freeflow: ${freeflowStatus}`,
111
+ ...(review ? [review] : []),
112
+ ...(skillsOff ? [skillsOff] : []),
105
113
  "",
106
114
  "Capabilities:",
107
115
  `- Cognitive Routing: ${publicCognitiveRoutingStatus(capabilityState?.cognitiveRouting, cognitiveRoutingRuntime)}`,
108
116
  `- Tool Execution: ${publicCapabilityStatus(capabilityState?.toolExecution)}`,
117
+ ...(capabilityState?.messaging?.enabled
118
+ ? [`- Messaging: ${publicCapabilityStatus(capabilityState.messaging)}`]
119
+ : []),
109
120
  "",
110
121
  "Cognitive Routing:",
111
122
  `- Control: \`${control}\``,
@@ -113,6 +124,8 @@ export function freeflowRuntimeStateMessage(
113
124
  ...(control === "automatic" ? [] : [`- Profile: \`${profile}\``]),
114
125
  `- Delegation: \`${cognitiveRoutingRuntime?.delegation ?? capabilityState?.cognitiveRouting?.delegation ?? "executor"}\``,
115
126
  `- Projection: \`${projectionMode}\``,
127
+ ...(options.skills?.length ? ["", `Freeflow skills read in this context: ${options.skills.join(", ")}`] : []),
128
+ ...(options.taskMemory?.length ? ["", ...options.taskMemory] : []),
116
129
  ].join("\n"),
117
130
  display: false,
118
131
  details: { source: "provider-request-runtime-state" },
@@ -146,6 +159,8 @@ export function withFreeflowRuntimeState(
146
159
  const source = Array.isArray(messages) ? messages : [];
147
160
  const runtimeState = freeflowRuntimeStateMessage(capabilityState, cognitiveRoutingRuntime, freeflowContext, {
148
161
  projectionFailure: options.projectionFailure,
162
+ taskMemory: options.taskMemory,
163
+ skills: options.skills,
149
164
  });
150
165
  const runtimeStateMessages = source.filter(
151
166
  (message) =>
@@ -0,0 +1,85 @@
1
+ /** The value of each section after replaying the transcript's system messages. */
2
+ export function currentSections(messages) {
3
+ const current = {};
4
+ for (const message of messages)
5
+ if (message?.role === "system" && message.sections && typeof message.sections === "object")
6
+ for (const [name, value] of Object.entries(message.sections)) current[name] = value;
7
+ return current;
8
+ }
9
+ // One filtered copy per patch and section set, so a request reuses the object the previous request carried.
10
+ const copies = new WeakMap();
11
+ /** The sections of the first system message, the head of the prompt. */
12
+ export function headSections(messages) {
13
+ const head = messages.find((message) => message?.role === "system" && message.sections);
14
+ return head ? { ...head.sections } : undefined;
15
+ }
16
+ function sameSections(a, b) {
17
+ const names = Object.keys(a);
18
+ return names.length === Object.keys(b).length && names.every((name) => a[name] === b[name]);
19
+ }
20
+ /**
21
+ * `version` changes whenever `own` does; it keys the reused copies without serialising the sections per request.
22
+ * `head` is the head's sections when `own` was recorded: a head still equal to it is kept as it is, since later
23
+ * patches carry any change; one that differs was rebuilt, and gets the recorded values.
24
+ */
25
+ export function keepSections(messages, own, version = 0, head) {
26
+ if (!own || !Object.keys(own).length) return [...messages];
27
+ const key = String(version);
28
+ const current = {};
29
+ const out = [];
30
+ let first = true;
31
+ for (const message of messages) {
32
+ const sections = message?.role === "system" ? message.sections : undefined;
33
+ if (!sections || typeof sections !== "object") {
34
+ out.push(message);
35
+ continue;
36
+ }
37
+ // A head Pi rebuilt from its base options (after a compaction in a run it started) gets the recorded sections; the
38
+ // head the recording saw stays as it is, so the prompt's start never changes.
39
+ if (first) {
40
+ first = false;
41
+ const rebuilt = !head || !sameSections(sections, head);
42
+ const missing = rebuilt ? Object.entries(own).filter(([name, value]) => sections[name] !== value) : [];
43
+ for (const [name, value] of Object.entries(sections)) current[name] = value;
44
+ if (!missing.length) {
45
+ out.push(message);
46
+ continue;
47
+ }
48
+ for (const [name, value] of missing) current[name] = value;
49
+ out.push(reused(message, key, { ...sections, ...Object.fromEntries(missing) }));
50
+ continue;
51
+ }
52
+ let kept;
53
+ for (const [name, value] of Object.entries(sections)) {
54
+ const wanted = own[name];
55
+ const reverts = wanted !== undefined && current[name] === wanted && value !== wanted;
56
+ const restates = value === current[name];
57
+ if (wanted !== undefined && (reverts || restates)) {
58
+ const trimmed = (kept ??= { ...sections });
59
+ delete trimmed[name];
60
+ continue;
61
+ }
62
+ current[name] = value;
63
+ }
64
+ if (!kept) {
65
+ out.push(message);
66
+ continue;
67
+ }
68
+ const empty =
69
+ !Object.keys(kept).length &&
70
+ !message.content?.length &&
71
+ !message.toolsAdded?.length &&
72
+ !message.toolsRemoved?.length;
73
+ if (empty) continue;
74
+ out.push(reused(message, key, kept));
75
+ }
76
+ return out;
77
+ }
78
+ /** The same copy for the same message and section set, so later requests carry the identical object. */
79
+ function reused(message, key, sections) {
80
+ let byOwn = copies.get(message);
81
+ if (!byOwn) copies.set(message, (byOwn = new Map()));
82
+ let copy = byOwn.get(key);
83
+ if (!copy) byOwn.set(key, (copy = { ...message, sections }));
84
+ return copy;
85
+ }