@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
@@ -9,14 +9,34 @@ import { RoutingRuntime } from "./cognitive-routing/runtime.js";
9
9
  import { applyRoutingToolVisibility, registerRoutingTools } from "./cognitive-routing/tools.js";
10
10
  import { handleFreeflowCommand } from "./host/settings/freeflow-command.js";
11
11
  import { toolExecutionSections, toolExecutionTail } from "./tool-execution/prompt.js";
12
- import { FileTracking } from "./tool-execution/file-tracking.js";
12
+ import { FileTracking, withLines } from "./tool-execution/file-tracking.js";
13
+ import { GuidanceNotices } from "./tool-execution/guidance-notices.js";
13
14
  import { registerApplyPatch } from "./tool-execution/apply-patch/tool.js";
14
15
  import { applyToolExecutionTools } from "./tool-execution/tools.js";
15
- import { BackgroundJobs, NOTICE_PREFIX, NOTICE_TYPE, registerBackgroundTools } from "./tool-execution/background.js";
16
+ import {
17
+ BackgroundJobs,
18
+ MONITOR_PER_MINUTE,
19
+ MONITOR_TYPE,
20
+ freeflowNotice,
21
+ NOTICE_TYPE,
22
+ registerBackgroundTools,
23
+ } from "./tool-execution/background.js";
24
+ import { Inbound, renderNotice } from "./host/inbound.js";
25
+ import { currentSections, headSections, keepSections } from "./host/section-keeper.js";
16
26
  import { backgroundRefusal } from "./tool-execution/bash-guard.js";
17
- import { CompactionController, USER_REQUEST } from "./compaction/controller.js";
27
+ import { CompactionController, RECOVERY_TYPE, USER_REQUEST } from "./compaction/controller.js";
18
28
  import { applyCompactionTools, registerCompactionTool } from "./compaction/tool.js";
19
29
  import { strictest } from "./compaction/thresholds.js";
30
+ import { SNAPSHOT_TYPE, TASK_ENTRY, TaskMemory } from "./task-memory/runtime.js";
31
+ import { applyTaskMemoryTools, registerTaskMemoryTool } from "./task-memory/tool.js";
32
+ import { freeflowHome } from "./task-memory/registry.js";
33
+ import { DEFAULT_INBOUND_POLICY } from "./messaging/config.js";
34
+ import { MESSAGE_TYPE, Messaging } from "./messaging/messaging.js";
35
+ import { applyMessagingTools, registerMessagingTools } from "./messaging/tools.js";
36
+ import { renderMessage } from "./messaging/render.js";
37
+ import { renderSnapshotMessage } from "./task-memory/render.js";
38
+ import { applyAskUserTool, registerAskUserTool } from "./ask-user/tool.js";
39
+ import { confirmWithUser } from "./ask-user/ask.js";
20
40
  import { tagProjectedMessages } from "./host/projection-tags.js";
21
41
  import { trustStartedSession } from "./host/read-only-session.js";
22
42
  import { takeStage } from "./host/staging.js";
@@ -24,6 +44,7 @@ import {
24
44
  CONTRIBUTOR_COMMANDS,
25
45
  WORKFLOW_COMMANDS,
26
46
  freeflowModelSkillPaths,
47
+ freeflowSkillPath,
27
48
  STABLE_FREEFLOW_SURFACE,
28
49
  skillPrompt,
29
50
  } from "./host/catalog.js";
@@ -44,6 +65,8 @@ import { filterBootstrapMessage, withFreeflowRuntimeState } from "./host/runtime
44
65
  import { setFreeflowStatus } from "./host/status.js";
45
66
  import { cachedBranch } from "./host/branch.js";
46
67
  const COMPACTION_NOTICE_TYPE = "freeflow-compaction-notice";
68
+ // The user's /freeflow compact, delivered as Freeflow's message.
69
+ const COMPACT_REQUEST_TYPE = "freeflow-compaction-request";
47
70
  /** Estimated size of the full native history plus the system prompt: what a request without projection carries. */
48
71
  /**
49
72
  * The context a turn ended with, as Pi estimates it: the turn's own usage plus the tool results after it. Pi's
@@ -60,6 +83,8 @@ function turnContextTokens(turn) {
60
83
  );
61
84
  }
62
85
  /** Entry types that add no message to Pi's session projection. */
86
+ /** The sections Freeflow sets: its own, and Pi's rules and docs, which Tool Execution changes. */
87
+ const OWN_SECTION_NAMES = ["freeflow_guidance", "environment", "tool_execution", "rules", "docs"];
63
88
  const NO_MESSAGE = new Set(["custom", "label", "session_info", "model_change", "thinking_level_change"]);
64
89
  const historyEstimate = new WeakMap();
65
90
  /**
@@ -88,30 +113,40 @@ function fullHistoryTokens(ctx) {
88
113
  previous.tokens,
89
114
  );
90
115
  }
91
- if (tokens === undefined)
92
- tokens = (manager.buildSessionProjection()?.messages ?? []).reduce(
93
- (sum, message) => sum + estimateTokens(message),
94
- 0,
95
- );
96
- historyEstimate.set(manager, { branch, tokens });
97
- return tokens + system;
116
+ const counted =
117
+ tokens ??
118
+ (manager.buildSessionProjection()?.messages ?? []).reduce((sum, message) => sum + estimateTokens(message), 0);
119
+ historyEstimate.set(manager, { branch, tokens: counted });
120
+ return counted + system;
121
+ }
122
+ /**
123
+ * Send a skill command's prompt as the user's message. A command can run while Pi is busy, which from Pi 1.2.0 includes
124
+ * the start of a run (before_agent_start); Pi then refuses a user message without `deliverAs`, so it is queued to
125
+ * follow the current work instead.
126
+ */
127
+ function sendSkillPrompt(pi, skill, args) {
128
+ pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true, deliverAs: "followUp" });
98
129
  }
99
130
  async function sendSkillCommand(pi, ctx, skill, args) {
100
131
  const state = await readCapabilityState(ctx.cwd, ctx);
101
132
  if (skill === "setup-freeflow" && !state.configured) {
102
- await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
133
+ sendSkillPrompt(pi, skill, args);
103
134
  return;
104
135
  }
105
136
  if (!state.configured || !state.enabled) {
106
137
  ctx.ui.notify("Run /setup-freeflow or enable Freeflow before dispatching this skill.", "warning");
107
138
  return;
108
139
  }
140
+ if (state.skills?.off?.includes(skill)) {
141
+ ctx.ui.notify(`${skill} is turned off in /freeflow settings (Skills).`, "warning");
142
+ return;
143
+ }
109
144
  const prompts = await getRuntimeContext(state);
110
145
  if (!hasUsableMandatoryPrompts(prompts)) {
111
146
  ctx.ui.notify("Freeflow core prompts are unavailable; the requested skill cannot be dispatched.", "warning");
112
147
  return;
113
148
  }
114
- await pi.sendUserMessage(skillPrompt(skill, args), { expandPromptTemplates: true });
149
+ sendSkillPrompt(pi, skill, args);
115
150
  }
116
151
  function freeflowCompletions(prefix, routingAvailable) {
117
152
  const query = prefix ?? "";
@@ -121,27 +156,50 @@ function freeflowCompletions(prefix, routingAvailable) {
121
156
  ["settings local", "local", "Edit personal overrides for this repository"],
122
157
  ["settings repo", "repo", "Edit shared repository settings"],
123
158
  ]
124
- : query.startsWith("profile ") && routingAvailable
159
+ : query.startsWith("task ")
125
160
  ? [
126
- ["profile coordinator", "coordinator", "Hold Coordinator manually"],
127
- ["profile helper", "helper", "Hold Helper manually when enabled"],
128
- ["profile executor", "executor", "Hold Executor manually when enabled"],
129
- ["profile auto", "auto", "Return to automatic Coordinator reconciliation"],
130
- ["profile history", "history", "Read routing observations"],
161
+ ["task list", "list", "This repository's records, or a project's tasks"],
162
+ ["task new", "new", "Create a task in this repository's project and attach it"],
163
+ ["task attach", "attach", "Attach a record by task ID or path"],
164
+ ["task focus", "focus", "Move the focus to an attached record"],
165
+ ["task detach", "detach", "Detach a record"],
166
+ ["task confirm", "confirm", "Make held records writable here"],
167
+ ["task fanout", "fanout", "Create a child of the focused task with its own worktree"],
168
+ ["task child", "child", "Create a child of a task and attach it here"],
169
+ ["task import", "import", "Move a repository record into a project"],
170
+ ["task remove", "remove", "Move a task and its children to the trash"],
131
171
  ]
132
- : [
133
- ["settings", "settings", "Open settings; Tab switches Session, Personal and Repository"],
134
- ["status", "status", "Show effective Freeflow state"],
135
- ["compact", "compact", "Prepare, compact and recover now"],
136
- ...(routingAvailable
137
- ? [
138
- ["profile", "profile", "Hold or release Cognitive Routing profile control"],
139
- ["resume", "resume", "Resume the current saved routing responsibility"],
140
- ]
141
- : []),
142
- ["enable", "enable", "Enable Freeflow for this repository"],
143
- ["disable", "disable", "Disable Freeflow for this repository"],
144
- ];
172
+ : query.startsWith("project ")
173
+ ? [
174
+ ["project list", "list", "Every project"],
175
+ ["project new", "new", "Create a project and link this repository"],
176
+ ["project link", "link", "Link this repository to a project"],
177
+ ["project remove", "remove", "Move a project and its tasks to the trash"],
178
+ ]
179
+ : query.startsWith("profile ") && routingAvailable
180
+ ? [
181
+ ["profile coordinator", "coordinator", "Hold Coordinator manually"],
182
+ ["profile helper", "helper", "Hold Helper manually when enabled"],
183
+ ["profile executor", "executor", "Hold Executor manually when enabled"],
184
+ ["profile auto", "auto", "Return to automatic Coordinator reconciliation"],
185
+ ["profile history", "history", "Read routing observations"],
186
+ ]
187
+ : [
188
+ ["settings", "settings", "Open settings; Tab switches Session, Personal and Repository"],
189
+ ["status", "status", "Show effective Freeflow state"],
190
+ ["compact", "compact", "Prepare, compact and recover now"],
191
+ ["task", "task", "Attach, focus, detach or confirm Working Records (task memory)"],
192
+ ["project", "project", "Create or link a task-memory project"],
193
+ ["trash", "trash", "Removed tasks and projects; trash empty deletes them for good"],
194
+ ...(routingAvailable
195
+ ? [
196
+ ["profile", "profile", "Hold or release Cognitive Routing profile control"],
197
+ ["resume", "resume", "Resume the current saved routing responsibility"],
198
+ ]
199
+ : []),
200
+ ["enable", "enable", "Enable Freeflow for this repository"],
201
+ ["disable", "disable", "Disable Freeflow for this repository"],
202
+ ];
145
203
  return choices
146
204
  .filter(([value]) => value.startsWith(query))
147
205
  .map(([value, label, description]) => ({ value, label, description }));
@@ -171,12 +229,33 @@ export default function freeflow(pi) {
171
229
  delete runtimeStateAnchor.index;
172
230
  };
173
231
  let capability;
232
+ // The system sections of the last prompt's run, while Freeflow sets sections; see keepSections.
233
+ let ownSections;
234
+ let ownSectionsVersion = 0;
235
+ let ownHead;
236
+ // The sections Freeflow sets, and whether the current run is one a prompt started (so its requests carry them), or
237
+ // one an extension's message started that Freeflow still set its sections for (Pi 1.2.0 and later).
238
+ let ownNames = [];
239
+ let promptRun = false;
240
+ let messageRun = false;
241
+ // Whether the session has had a prompt. Pi before 1.2.0 builds the system prompt only for prompts, so until then a
242
+ // turn Freeflow started for a notice would reach the model with none: notices wait for the user's first prompt.
243
+ let prompted = false;
174
244
  const files = new FileTracking();
175
245
  const toolExecutionEffective = () => capability?.toolExecution?.effective === true;
246
+ const guidance = new GuidanceNotices(packageRoot);
247
+ const guidanceNoticesEffective = () =>
248
+ toolExecutionEffective() && capability?.toolExecution?.guidanceNotices !== false;
176
249
  const compactionEffective = () => capability?.compaction?.effective === true;
250
+ const taskMemoryEffective = () => capability?.taskMemory?.effective === true;
251
+ const askUserEffective = () => capability?.askUser?.effective === true;
252
+ const messagingEffective = () => capability?.messaging?.effective === true;
177
253
  const cacheHealth = new CacheHealth();
178
254
  let prompts;
179
255
  let refreshState = true;
256
+ // Skills already read, as the Runtime State lists them: taken only where the state refreshes anyway (run
257
+ // boundaries, settings changes, compaction, navigation), so a skill read mid-run adds no state message.
258
+ let stateSkills = [];
180
259
  // Pi's event contexts read the host model and effort live. Its interactive shortcut contexts copy them at the
181
260
  // key press, so after a switch they still name the previous model; routing must never judge a switch by them.
182
261
  let liveHost;
@@ -213,6 +292,10 @@ export default function freeflow(pi) {
213
292
  applyRoutingToolVisibility(api, routing, capability?.cognitiveRouting?.effective === true);
214
293
  applyToolExecutionTools(api, toolExecutionEffective());
215
294
  applyCompactionTools(api, compactionEffective());
295
+ applyTaskMemoryTools(api, taskMemoryEffective());
296
+ applyAskUserTool(api, askUserEffective());
297
+ applyMessagingTools(api, messagingEffective());
298
+ syncMessaging(ctx);
216
299
  setFreeflowStatus(ctx, capability, routing.state(), prompts);
217
300
  }
218
301
  // Control and setting changes made between prompts are staged; their net effect is written as a prompt starts.
@@ -226,6 +309,8 @@ export default function freeflow(pi) {
226
309
  await routing.refresh(ctx, next.cognitiveRouting);
227
310
  status(ctx);
228
311
  refreshState = true;
312
+ // The Coordinator's projected view may lack reads the full history holds, and every profile shares the state.
313
+ stateSkills = toolExecutionEffective() && !routing.projectionEnabled ? files.loadedSkills(ctx) : [];
229
314
  }
230
315
  /**
231
316
  * Turn Cognitive Routing on for this session when it is off: clear a session "off" (such as the one an outside model
@@ -242,32 +327,56 @@ export default function freeflow(pi) {
242
327
  await update(ctx);
243
328
  }
244
329
  registerRoutingTools(api, routing);
330
+ registerAskUserTool(api, {
331
+ // Under automatic routing only the Coordinator talks to the user; a worker returns the choice in its report.
332
+ refusal: () => {
333
+ const scope = routing.observationScope();
334
+ return scope.control === "automatic" && (scope.profile === "helper" || scope.profile === "executor")
335
+ ? "A Cognitive Routing worker does not ask the user. Put the choice, its options and your recommendation in your report (return_work); the Coordinator asks."
336
+ : undefined;
337
+ },
338
+ });
245
339
  registerApplyPatch(api, {
246
340
  effective: toolExecutionEffective,
247
341
  files: (ctx) => files.forPatch(ctx),
248
342
  written: (paths, ctx) => files.written(paths, ctx),
249
343
  });
344
+ // Content arriving from outside the turn (host/inbound.ts): background exits now, later monitors and messages.
345
+ const inbound = new Inbound({
346
+ sendMessage: (message, options) => api.sendMessage(message, options),
347
+ sendUserMessage: (content) => api.sendUserMessage(content),
348
+ isIdle: () => liveHost?.isIdle?.(),
349
+ canWake: () => prompted,
350
+ });
351
+ api.registerMessageRenderer?.(NOTICE_TYPE, renderNotice("Background"));
352
+ api.registerMessageRenderer?.(MONITOR_TYPE, renderNotice("Monitor"));
353
+ api.registerMessageRenderer?.(COMPACT_REQUEST_TYPE, renderNotice("Compaction"));
354
+ // Compaction's warnings and its recovery message, and the changed-file notice: one line each, the rest on expand.
355
+ api.registerMessageRenderer?.(COMPACTION_NOTICE_TYPE, renderNotice("Compaction"));
356
+ api.registerMessageRenderer?.(RECOVERY_TYPE, renderNotice("Compaction"));
357
+ api.registerMessageRenderer?.("freeflow-files", renderNotice("Files"));
250
358
  const backgroundHost = {
251
359
  effective: toolExecutionEffective,
252
360
  shellPath: () => api.getSettings?.()?.shellPath,
253
361
  // An exit notice starts a turn when idle; mid-run it is steered in after the current turn's tool results.
254
- send: (content, details, triggerTurn) => {
255
- const notice = () =>
256
- Promise.resolve(
257
- api.sendMessage(
258
- { customType: NOTICE_TYPE, content, display: true, details },
259
- triggerTurn ? { triggerTurn: true, deliverAs: "steer" } : { triggerTurn: false },
260
- ),
261
- ).catch(() => {});
262
- // When idle, the notice starts the run as a user message: a run an extension message starts loses Freeflow's
263
- // system sections from its second request (Pi #10267). If a run started meanwhile, it is steered in instead.
264
- if (!triggerTurn || liveHost?.isIdle?.() !== true) return notice();
265
- try {
266
- return Promise.resolve(api.sendUserMessage(content)).catch(notice);
267
- } catch {
268
- return notice();
269
- }
362
+ send: (content, details, triggerTurn) =>
363
+ inbound.deliver({ customType: NOTICE_TYPE, content, details, wake: triggerTurn }),
364
+ // A monitor's lines wake an idle agent (spec O-6); the rate limit bounds what that costs.
365
+ event: (id, line, join, onLimit) =>
366
+ inbound.deliver(
367
+ { customType: MONITOR_TYPE, content: line, details: { id }, wake: true },
368
+ {
369
+ key: `monitor:${id}`,
370
+ perMinute: MONITOR_PER_MINUTE,
371
+ onLimit,
372
+ join: (notices) => join(notices.map((notice) => notice.content)),
373
+ },
374
+ ),
375
+ endEvents: (id) => {
376
+ inbound.flush(`monitor:${id}`);
377
+ inbound.close(`monitor:${id}`);
270
378
  },
379
+ discardEvents: (id) => inbound.close(`monitor:${id}`),
271
380
  };
272
381
  const background = new BackgroundJobs(backgroundHost);
273
382
  registerBackgroundTools(api, background, backgroundHost);
@@ -280,7 +389,7 @@ export default function freeflow(pi) {
280
389
  routingAssignment: () => routing.currentAssignment(),
281
390
  assignmentResults: () => routing.assignmentResults(),
282
391
  background: () => background.running().map(({ id, label, outputPath }) => ({ id, label, outputPath })),
283
- noticePrefix: NOTICE_PREFIX,
392
+ notice: (text) => freeflowNotice("compaction", text),
284
393
  carryEnabled: () => capability?.compaction?.carry !== false,
285
394
  nativeRefs: () => capability?.cognitiveRouting?.effective === true,
286
395
  resolveRef: (ref) => routing.evidenceSource(ref),
@@ -326,6 +435,273 @@ export default function freeflow(pi) {
326
435
  },
327
436
  });
328
437
  registerCompactionTool(api, compaction);
438
+ const sendSnapshot = (content, record) =>
439
+ void Promise.resolve(
440
+ api.sendMessage({ customType: SNAPSHOT_TYPE, content, display: true, details: { record } }),
441
+ ).catch(() => {});
442
+ const taskMemory = new TaskMemory({
443
+ effective: taskMemoryEffective,
444
+ append: (data) => api.appendEntry(TASK_ENTRY, data),
445
+ send: sendSnapshot,
446
+ owner: () => ({
447
+ session: liveHost?.sessionManager?.getSessionId?.() ?? "unknown",
448
+ ...(liveHost?.sessionManager?.getSessionFile?.() ? { file: liveHost.sessionManager.getSessionFile() } : {}),
449
+ since: new Date().toISOString(),
450
+ }),
451
+ confirm: async (title, message) => (liveHost?.hasUI ? confirmWithUser(liveHost, title, message) : false),
452
+ cwd: () => liveHost?.cwd ?? process.cwd(),
453
+ packageRoot,
454
+ });
455
+ // Messaging between live Pi sessions (Task 002 D-005). Its tools are deferred: tool_search loads them.
456
+ // Read from the event context that last synced messaging, while it was current: a captured context goes stale when
457
+ // Pi replaces the session, so later asynchronous work uses these values, not the context.
458
+ let messagingSeen;
459
+ let agentRunning = false;
460
+ const live = (read, fallback) => {
461
+ try {
462
+ return read();
463
+ } catch {
464
+ return fallback;
465
+ }
466
+ };
467
+ const messaging = new Messaging(
468
+ {
469
+ identity: async () => {
470
+ const seen = messagingSeen ?? { sessionId: `pid-${process.pid}`, cwd: process.cwd() };
471
+ const project = await taskMemory.registry.projectFor(seen.cwd).catch(() => undefined);
472
+ const focus = taskMemory.state().focus;
473
+ const task = focus ? await taskMemory.registry.byRecord(focus).catch(() => undefined) : undefined;
474
+ // The tasks this session owns, so a child finds its parent's session in list_sessions (Task 002 D-009).
475
+ const tasks = await taskMemory.registry
476
+ .read()
477
+ .then((data) => data.tasks)
478
+ .catch(() => ({}));
479
+ const owns = Object.entries(tasks)
480
+ .filter(([, each]) => each?.owner?.session === seen.sessionId)
481
+ .map(([id]) => id);
482
+ return {
483
+ ...seen,
484
+ ...(project ? { project: project.slug } : {}),
485
+ ...(task ? { task: task.id } : {}),
486
+ ...(owns.length ? { owns } : {}),
487
+ };
488
+ },
489
+ policy: () => capability?.messaging?.inbound ?? DEFAULT_INBOUND_POLICY,
490
+ busy: () => agentRunning,
491
+ deliver: (content, details) => {
492
+ inbound.deliver({ customType: MESSAGE_TYPE, content, details, wake: true });
493
+ if (!prompted && details.kind === "message")
494
+ live(
495
+ () =>
496
+ liveHost?.ui?.notify?.(
497
+ `A message from ${String(details.name ?? "another session")} is waiting: the agent reads it with your next prompt.`,
498
+ "info",
499
+ ),
500
+ undefined,
501
+ );
502
+ },
503
+ canWake: () => prompted,
504
+ canAsk: () => live(() => liveHost?.hasUI === true, false),
505
+ // The same dialog as ask_user, with a countdown (Task 002 D-008).
506
+ confirm: async (title, message, timeout) =>
507
+ (await Promise.resolve(
508
+ live(
509
+ () => confirmWithUser(liveHost, title, message, { timeoutMs: timeout, yes: "Deliver", no: "Refuse" }),
510
+ false,
511
+ ),
512
+ ).catch(() => false)) === true,
513
+ },
514
+ freeflowHome(),
515
+ );
516
+ registerMessagingTools(api, messaging, messagingEffective);
517
+ api.registerMessageRenderer?.(MESSAGE_TYPE, renderMessage);
518
+ // Start or stop listening as messaging's effective state changes; a running session refreshes its registry entry.
519
+ let messagingSync = Promise.resolve();
520
+ function syncMessaging(ctx) {
521
+ const effective = messagingEffective();
522
+ if (effective)
523
+ messagingSeen = live(() => {
524
+ const manager = ctx.sessionManager;
525
+ const name = manager?.getSessionName?.();
526
+ return {
527
+ sessionId: manager?.getSessionId?.() ?? `pid-${process.pid}`,
528
+ ...(name ? { name } : {}),
529
+ cwd: ctx.cwd ?? process.cwd(),
530
+ };
531
+ }, messagingSeen);
532
+ messagingSync = messagingSync
533
+ .then(() => (effective ? messaging.start() : messaging.running() ? messaging.stop() : undefined))
534
+ .catch(() => {});
535
+ }
536
+ // A delivered resume view shows as one line naming the task; expanding shows the view the model received.
537
+ api.registerMessageRenderer?.(SNAPSHOT_TYPE, (message, options, theme) =>
538
+ renderSnapshotMessage(message, options, theme),
539
+ );
540
+ registerTaskMemoryTool(api, {
541
+ memory: taskMemory,
542
+ // Under automatic routing only the Coordinator proposes task memory actions; workers do not talk to the user.
543
+ refusal: () => {
544
+ const scope = routing.observationScope();
545
+ return scope.control === "automatic" && (scope.profile === "helper" || scope.profile === "executor")
546
+ ? "A Cognitive Routing worker may only read task memory (projects, list, view, search, env). Put the action you would propose in your report (return_work); the Coordinator proposes it."
547
+ : undefined;
548
+ },
549
+ // Proposals in groups the user allowed run without the dialog (Task 004 D-002).
550
+ autoApproved: (group) => capability?.taskMemory?.autoApprove?.[group] === true,
551
+ changed: (ctx) => {
552
+ refreshState = true;
553
+ status(ctx);
554
+ },
555
+ });
556
+ // /freeflow project: the user creates projects and links repositories to them; the model never does (D-015).
557
+ // Where the user can browse the project's records, when the project link was made (Task 004 D-003).
558
+ async function browseNote(cwd) {
559
+ const link = await taskMemory.registry.linkHere(cwd).catch(() => undefined);
560
+ return link && !link.error ? ` Browse its records in ${link.link}.` : "";
561
+ }
562
+ // Removal asks even from the user's own command: it moves records out of every session that holds them (Task 006).
563
+ async function removeWithConfirmation(ctx, target, title) {
564
+ const { question, error } = await taskMemory.removalQuestion(target);
565
+ if (!question) return { ok: false, text: error ?? "Nothing to remove." };
566
+ if (!ctx.hasUI || !(await confirmWithUser(ctx, title, question))) return { ok: false, text: "Not removed." };
567
+ return taskMemory.remove(target);
568
+ }
569
+ // The trash holds removed tasks and projects. Only this command empties it, so only the user can (Task 006).
570
+ async function trashCommand(rest, ctx) {
571
+ const where = await taskMemory.registry.trashPath();
572
+ const entries = await taskMemory.registry.trashEntries();
573
+ if (rest.trim().toLowerCase() !== "empty") {
574
+ ctx.ui.notify(
575
+ entries.length
576
+ ? `Trash (${where}):\n${entries.map((entry) => `- ${entry}`).join("\n")}\n/freeflow trash empty deletes them for good.`
577
+ : "The trash is empty.",
578
+ "info",
579
+ );
580
+ return;
581
+ }
582
+ if (!entries.length) {
583
+ ctx.ui.notify("The trash is empty.", "info");
584
+ return;
585
+ }
586
+ const yes =
587
+ ctx.hasUI &&
588
+ (await confirmWithUser(
589
+ ctx,
590
+ "Empty the trash?",
591
+ `Delete ${entries.length} removed item(s) in ${where} for good? This cannot be undone.`,
592
+ ));
593
+ if (!yes) {
594
+ ctx.ui.notify("The trash was not emptied.", "info");
595
+ return;
596
+ }
597
+ const deleted = await taskMemory.registry.emptyTrash();
598
+ ctx.ui.notify(`Deleted ${deleted.length} item(s) from the trash.`, "info");
599
+ }
600
+ async function projectCommand(rest, ctx) {
601
+ if (!taskMemoryEffective()) {
602
+ ctx.ui.notify("Task memory is off. Turn it on in /freeflow settings (Task Memory).", "warning");
603
+ return;
604
+ }
605
+ const [sub, ...words] = rest.trim().split(/\s+/).filter(Boolean);
606
+ // --worktrees <dir>: where fan-out puts this project's child worktrees.
607
+ const at = words.indexOf("--worktrees");
608
+ const worktrees = at >= 0 ? words.splice(at, 2)[1] : undefined;
609
+ const name = words.join(" ");
610
+ try {
611
+ if (sub === "new" && name) {
612
+ const slug = await taskMemory.registry.createProject(name, ctx.cwd, worktrees);
613
+ ctx.ui.notify(`Project ${slug} created and linked to this repository.${await browseNote(ctx.cwd)}`, "info");
614
+ } else if (sub === "link" && name) {
615
+ const slug = await taskMemory.registry.linkProject(name, ctx.cwd, worktrees);
616
+ ctx.ui.notify(`This repository is linked to project ${slug}.${await browseNote(ctx.cwd)}`, "info");
617
+ } else if (sub === "remove" && name) {
618
+ const result = await removeWithConfirmation(ctx, { project: name }, "Remove project?");
619
+ ctx.ui.notify(result.text, result.ok ? "info" : "warning");
620
+ refreshState = true;
621
+ status(ctx);
622
+ } else if (sub === "list") {
623
+ const result = await taskMemory.projects(ctx.cwd);
624
+ ctx.ui.notify(result.text, result.ok ? "info" : "warning");
625
+ } else {
626
+ const found = await taskMemory.registry.projectFor(ctx.cwd);
627
+ ctx.ui.notify(
628
+ found
629
+ ? `Project ${found.project.name} (${found.slug}), store ${found.project.store}.${await browseNote(ctx.cwd)}\n${await taskMemory.list(ctx.cwd)}`
630
+ : "This repository is not linked to a project. Use /freeflow project new <name> or link <name>.",
631
+ "info",
632
+ );
633
+ }
634
+ } catch (error) {
635
+ ctx.ui.notify(error?.message ?? String(error), "warning");
636
+ }
637
+ }
638
+ // /freeflow task: the user's own attach, focus, detach and confirm, and a picker over this repository's records.
639
+ async function taskCommand(rest, ctx) {
640
+ if (!taskMemoryEffective()) {
641
+ ctx.ui.notify("Task memory is off. Turn it on in /freeflow settings (Task Memory).", "warning");
642
+ return;
643
+ }
644
+ const [sub, ...words] = rest.trim().split(/\s+/).filter(Boolean);
645
+ let action = sub?.toLowerCase();
646
+ let target = words.join(" ");
647
+ if (!action || action === "pick") {
648
+ const records = await taskMemory.records(ctx.cwd);
649
+ if (!records.length || !ctx.hasUI) {
650
+ ctx.ui.notify(await taskMemory.list(ctx.cwd), "info");
651
+ return;
652
+ }
653
+ const labels = records.map((record) => `${taskMemory.name(record)} — ${record}`);
654
+ const chosen = await ctx.ui.select("Task records", labels);
655
+ if (!chosen) return;
656
+ target = records[labels.indexOf(chosen)];
657
+ action = taskMemory.state().attached.includes(target) ? "focus" : "attach";
658
+ }
659
+ let result;
660
+ if (action === "list") result = { ok: true, text: await taskMemory.list(ctx.cwd, target) };
661
+ else if (action === "new") {
662
+ // The user's own command: create the task in this repository's project and attach it, with no dialog.
663
+ const created = await taskMemory.create(target, target, ctx.cwd);
664
+ result = created.ok && created.id ? await taskMemory.attach(created.id, ctx.cwd) : created;
665
+ } else if (action === "attach") result = await taskMemory.attach(target, ctx.cwd);
666
+ else if (action === "focus") result = await taskMemory.focus(target, ctx.cwd);
667
+ else if (action === "detach") result = await taskMemory.detach(target, ctx.cwd);
668
+ else if (action === "confirm") result = await taskMemory.confirm(target, ctx.cwd);
669
+ else if (action === "child") {
670
+ // From the new session: create a child of the named parent and attach it here.
671
+ const [parent, ...title] = words;
672
+ const created = await taskMemory.child(title.join(" "), title.join(" "), parent);
673
+ const id = /Created child (\S+)/.exec(created.text)?.[1];
674
+ result = created.ok && id ? await taskMemory.attach(id, ctx.cwd) : created;
675
+ } else if (action === "fanout") {
676
+ const title = words.join(" ");
677
+ result = await taskMemory.fanout(title, title);
678
+ } else if (action === "import") {
679
+ const [path, ...project] = words;
680
+ const yes = ctx.hasUI
681
+ ? await confirmWithUser(ctx, "Import record?", `Move ${path} into the ${project.join(" ")} project's store?`)
682
+ : false;
683
+ result = yes
684
+ ? await taskMemory.importRecord(path, project.join(" "), ctx.cwd)
685
+ : { ok: false, text: "Not imported." };
686
+ } else if (action === "remove") {
687
+ result = await removeWithConfirmation(ctx, { task: target }, "Remove task?");
688
+ } else
689
+ result = {
690
+ ok: false,
691
+ text: "Use /freeflow task [list [project]|new <title>|attach <id or record>|focus <id or record>|detach <id or record>|confirm [record]|child <parent-id> <title>|fanout <title>|import <record> <project>|remove <id>].",
692
+ };
693
+ if (result.snapshot) {
694
+ sendSnapshot(result.text, result.snapshot);
695
+ ctx.ui.notify(
696
+ `Task memory: ${(await taskMemory.brief(result.snapshot)).label} is the focus. ${taskMemory.attachedText()}`,
697
+ "info",
698
+ );
699
+ } else if (result.command)
700
+ ctx.ui.notify(`${result.headline ?? result.text}\nRun in a new terminal:\n ${result.command}`, "info");
701
+ else ctx.ui.notify(result.text, result.ok ? "info" : "warning");
702
+ refreshState = true;
703
+ status(ctx);
704
+ }
329
705
  // The user's /freeflow compact makes compaction due now: the agent prepares, compacts and recovers.
330
706
  async function compactNow(ctx) {
331
707
  if (!compactionEffective()) {
@@ -333,22 +709,49 @@ export default function freeflow(pi) {
333
709
  return;
334
710
  }
335
711
  compaction.requestByUser(ctx);
336
- // The user asked, so this is their message. A user message also starts an ordinary run, which keeps Freeflow's
337
- // system sections; a run started by an extension message loses them from its second request (Pi #10267).
712
+ // Freeflow's message saying the user asked, not text in the user's name (Task 002 D-008); the run it starts keeps
713
+ // Freeflow's system sections (host/section-keeper.ts).
338
714
  const index = compaction.indexText(ctx);
339
- const text = `${USER_REQUEST}${index ? `\n\n${index}` : ""}`;
715
+ const text = freeflowNotice(
716
+ "compaction",
717
+ `The user ran /freeflow compact. ${USER_REQUEST}${index ? `\n\n${index}` : ""}`,
718
+ );
340
719
  await Promise.resolve(
341
- api.sendUserMessage(text, ctx.isIdle?.() === false ? { deliverAs: "steer" } : undefined),
720
+ api.sendMessage(
721
+ { customType: COMPACT_REQUEST_TYPE, content: text, display: true, details: {} },
722
+ { triggerTurn: true, deliverAs: "steer" },
723
+ ),
342
724
  ).catch(() => {});
343
725
  }
344
726
  pi.on("resources_discover", async (event, ctx) => {
345
727
  const state = capability ?? (await loadSurface(ctx ?? { cwd: event?.cwd ?? process.cwd() }));
346
728
  return {
347
- skillPaths: freeflowModelSkillPaths({ ...STABLE_FREEFLOW_SURFACE, toolExecution: state.toolExecution }),
729
+ // Only what this configuration can use (Task 003 D-002): capability skills only while their capability is
730
+ // effective, and while Freeflow is inactive (disabled, unconfigured, or its prompts missing) only Setup Freeflow,
731
+ // so /setup-freeflow still works.
732
+ skillPaths:
733
+ state?.enabled === true && hasUsableMandatoryPrompts(prompts)
734
+ ? freeflowModelSkillPaths(state)
735
+ : [freeflowSkillPath("setup-freeflow")],
348
736
  };
349
737
  });
738
+ /** Sections recorded on another session or branch are not this one's; whether this branch had a prompt is its own. */
739
+ const forgetSections = (ctx) => {
740
+ ownSections = undefined;
741
+ ownHead = undefined;
742
+ ownSectionsVersion++;
743
+ prompted = (cachedBranch(ctx.sessionManager) ?? []).some(
744
+ (entry) =>
745
+ entry?.type === "message" &&
746
+ (entry.message?.role === "user" || (entry.message?.role === "system" && entry.message.sections)),
747
+ );
748
+ };
350
749
  pi.on("session_start", async (event, ctx) => {
351
750
  liveHost = ctx;
751
+ // Sections recorded for another session (after /new or /resume) are not this one's.
752
+ forgetSections(ctx);
753
+ promptRun = false;
754
+ messageRun = false;
352
755
  const generation = ++surfaceGeneration;
353
756
  resetHistory();
354
757
  // Where Pi has just parsed this session file, later checks read only the bytes appended since.
@@ -363,15 +766,63 @@ export default function freeflow(pi) {
363
766
  await loadSurface(ctx);
364
767
  cacheHealth.reset();
365
768
  files.reset();
769
+ stateSkills = [];
366
770
  compaction.reset();
367
771
  await routing.bind(ctx, capability.cognitiveRouting, event?.reason !== "reload");
772
+ taskMemory.rebuild(cachedBranch(ctx.sessionManager));
773
+ if (taskMemoryEffective()) {
774
+ await taskMemory.load();
775
+ // The project link for the user to browse records in the editor, made again if missing (Task 004 D-003). Not
776
+ // awaited: it runs Git, and nothing at startup reads it.
777
+ void taskMemory.registry.linkHere(ctx.cwd).catch(() => undefined);
778
+ // A fork or clone copies the branch's attachments; they stay held until the user confirms them here.
779
+ if (event?.reason === "fork") {
780
+ const held = taskMemory.holdInherited();
781
+ if (held.length)
782
+ ctx.ui?.notify?.(
783
+ `Task memory: ${held.length} inherited record(s) are held, not writable. Release with /freeflow task confirm.`,
784
+ "info",
785
+ );
786
+ }
787
+ await taskMemory.deliver(cachedBranch(ctx.sessionManager));
788
+ // A session started in a task's directory with nothing attached offers that task (D-017). Not awaited, so
789
+ // startup finishes first and the user answers when the dialog appears; a decline is kept on the branch.
790
+ const suggested = ctx.hasUI ? await taskMemory.suggestion(cachedBranch(ctx.sessionManager)) : undefined;
791
+ if (suggested)
792
+ void (async () => {
793
+ const yes = await confirmWithUser(
794
+ ctx,
795
+ `Attach ${suggested.id}?`,
796
+ `This directory belongs to ${suggested.title} (${suggested.id}). Attach it to this session?`,
797
+ );
798
+ if (generation !== surfaceGeneration) return;
799
+ if (!yes) {
800
+ taskMemory.decline(suggested.record);
801
+ return;
802
+ }
803
+ const result = await taskMemory.attach(suggested.id, ctx.cwd);
804
+ if (result.snapshot) sendSnapshot(result.text, result.snapshot);
805
+ ctx.ui.notify(
806
+ result.ok ? `Task memory: ${suggested.id} attached.` : result.text,
807
+ result.ok ? "info" : "warning",
808
+ );
809
+ refreshState = true;
810
+ status(ctx);
811
+ })().catch(() => {});
812
+ }
368
813
  status(ctx);
369
814
  });
370
815
  pi.on("session_shutdown", async () => {
371
816
  surfaceGeneration++;
817
+ // Leave the live registry and close the socket, after any start still queued; sessions waiting for this one learn
818
+ // that it ended.
819
+ messagingSync = messagingSync.then(() => messaging.stop()).catch(() => {});
820
+ await messagingSync;
372
821
  // Background processes belong to this extension instance; none may report into the next session.
373
822
  await background.stopAll();
823
+ inbound.reset();
374
824
  files.reset();
825
+ stateSkills = [];
375
826
  compaction.reset();
376
827
  resetHistory();
377
828
  routing.unbind();
@@ -379,9 +830,11 @@ export default function freeflow(pi) {
379
830
  prompts = undefined;
380
831
  });
381
832
  pi.on("before_agent_start", async (event, ctx) => {
833
+ // Pi 1.2.0 and later fire this for a run an extension's message starts too (messageType "custom").
834
+ const fromMessage = event.messageType === "custom";
382
835
  await update(ctx);
383
836
  try {
384
- await routing.beforeRun(ctx);
837
+ await routing.beforeRun(ctx, fromMessage ? "message" : "prompt");
385
838
  } finally {
386
839
  // Pi reports a handler error and still sends the prompt, so the fixed guidance section and the staged
387
840
  // control must never be skipped: a missing section would change the cached prompt head.
@@ -397,16 +850,33 @@ export default function freeflow(pi) {
397
850
  if (toolExecution)
398
851
  Object.assign(sections, toolExecutionSections(event.systemPrompt, api.getSettings?.()?.shellPath));
399
852
  for (const name of ["freeflow_guidance", "environment", "tool_execution"]) delete sections[name];
400
- sections.freeflow_guidance = stableRuntimeContext(prompts);
853
+ // Nothing of Freeflow while it is disabled or unconfigured (Task 003 D-002).
854
+ if (capability?.enabled === true && hasUsableMandatoryPrompts(prompts))
855
+ sections.freeflow_guidance = stableRuntimeContext(prompts, {
856
+ routing: capability?.cognitiveRouting?.effective === true,
857
+ prompts: capability?.prompts,
858
+ });
401
859
  if (toolExecution)
402
860
  Object.assign(sections, toolExecutionTail(api.getSettings?.()?.shellPath, prompts?.toolExecutionPrompt));
861
+ // What Freeflow set, so requests of runs an extension's message starts keep it (host/section-keeper.ts).
862
+ ownNames = OWN_SECTION_NAMES.filter(
863
+ (name) => (toolExecution || (name !== "rules" && name !== "docs")) && typeof sections[name] === "string",
864
+ );
865
+ promptRun = !fromMessage;
866
+ messageRun = fromMessage;
867
+ prompted = true;
403
868
  }
404
869
  // Like Claude Code's next-turn reminder: files the model read that changed while it was not running a tool.
405
870
  const changed = toolExecutionEffective() ? await files.changedNotice(ctx, false) : undefined;
406
871
  if (changed)
407
- return { message: { customType: "freeflow-files", content: `${NOTICE_PREFIX} ${changed}`, display: true } };
872
+ return { message: { customType: "freeflow-files", content: freeflowNotice("files", changed), display: true } };
873
+ });
874
+ pi.on("agent_start", () => {
875
+ routing.runStarted();
876
+ guidance.runStarted();
877
+ agentRunning = true;
878
+ if (messaging.running()) void messaging.setBusy(true).catch(() => {});
408
879
  });
409
- pi.on("agent_start", () => routing.runStarted());
410
880
  pi.on("message_end", async (event, ctx) => {
411
881
  const message = event.message;
412
882
  if (
@@ -432,10 +902,11 @@ export default function freeflow(pi) {
432
902
  }
433
903
  routing.messageEnd(event.message);
434
904
  // A run started by an extension's message (a background notice, for one) gets the same preparation as a prompt.
905
+ // Pi 1.2.0 and later prepare it through before_agent_start; this covers earlier Pi, which skips that event there.
435
906
  if (routing.startedByMessage(event.message)) {
436
907
  await update(ctx);
437
908
  try {
438
- await routing.beforeRun(ctx, false);
909
+ await routing.beforeRun(ctx, "late");
439
910
  } finally {
440
911
  writeStagedControl(ctx);
441
912
  status(ctx);
@@ -443,19 +914,34 @@ export default function freeflow(pi) {
443
914
  }
444
915
  });
445
916
  pi.on("tool_call", async (event, ctx) => {
917
+ const recordGate = await taskMemory.gate(event.toolName, event.input, ctx.cwd);
918
+ if (recordGate) return recordGate;
446
919
  const gate = routing.preflight(event, ctx);
447
920
  if (gate?.block || !toolExecutionEffective()) return gate;
448
921
  const refusal = event.toolName === "bash" ? backgroundRefusal(event.input?.command) : undefined;
449
922
  if (refusal) return { block: true, reason: refusal };
450
923
  return (await files.toolCall(event, ctx)) ?? gate;
451
924
  });
452
- pi.on("tool_result", async (event, ctx) => (toolExecutionEffective() ? files.toolResult(event, ctx) : undefined));
925
+ pi.on("tool_result", async (event, ctx) => {
926
+ if (!toolExecutionEffective()) return undefined;
927
+ const shaped = await files.toolResult(event, ctx);
928
+ // The push retriever's notice joins the result before it is delivered, after anything Tool Execution added.
929
+ const executeWorkUnread = () =>
930
+ !(capability?.skills?.off ?? []).includes("execute-work") && !files.loadedSkills(ctx).includes("execute-work");
931
+ const lines = guidanceNoticesEffective() ? await guidance.toolResult(event, ctx.cwd, executeWorkUnread) : [];
932
+ return lines.length ? { content: withLines(shaped?.content ?? event.content ?? [], lines) } : shaped;
933
+ });
453
934
  pi.on("turn_end", async (event, ctx) => {
454
935
  await routing.turnEnd(event, ctx);
455
936
  files.clearPending();
456
937
  status(ctx);
457
938
  const compacting = await compaction.turnEnd(event, ctx);
458
- if (compacting) return compacting;
939
+ if (compacting) {
940
+ // The focused record arrives after the summary, so recovery starts from it rather than a path to look up.
941
+ const snapshot = await taskMemory.snapshotEntry();
942
+ if (snapshot) compacting.entries.push(snapshot);
943
+ return compacting;
944
+ }
459
945
  const notice = compaction.observe(ctx, event);
460
946
  if (notice) {
461
947
  // Mid-run the notice joins the next request; when the run is ending it waits for the next prompt rather than
@@ -474,10 +960,15 @@ export default function freeflow(pi) {
474
960
  pi.on("turn_start", async (_event, ctx) => {
475
961
  const compacted = compaction.takeCompacted();
476
962
  if (!compacted) return;
477
- await restore(ctx, false);
963
+ // File state first, so the state restore refreshes lists no skill from before the compaction.
478
964
  await files.compacted(compacted.carriedFiles, ctx);
965
+ await restore(ctx, false);
479
966
  });
480
967
  pi.on("agent_settled", async (_event, ctx) => {
968
+ promptRun = false;
969
+ messageRun = false;
970
+ agentRunning = false;
971
+ if (messaging.running()) await messaging.setBusy(false).catch(() => {});
481
972
  await routing.settled(ctx);
482
973
  await update(ctx);
483
974
  });
@@ -502,11 +993,57 @@ export default function freeflow(pi) {
502
993
  ctx.sessionManager?.buildSessionProjection?.(),
503
994
  cachedBranch(ctx.sessionManager) ?? [],
504
995
  );
505
- let messages = event.messages.map(filterBootstrapMessage).filter(Boolean);
996
+ // A run an extension's message starts keeps the prompt of the last prompt's run: every section, as Pi rendered it,
997
+ // so the sections other extensions set only for prompts (Pi's own MCP servers, for one) are not removed either; on
998
+ // Pi 1.2.0 and later Freeflow's own follow what it set for that run. Recorded only while Freeflow sets sections;
999
+ // with Freeflow off, Pi's prompt passes through unchanged.
1000
+ const strings = (current) =>
1001
+ Object.fromEntries(Object.entries(current).filter((entry) => typeof entry[1] === "string"));
1002
+ if (promptRun) {
1003
+ const own = ownNames.length ? strings(currentSections(event.messages)) : {};
1004
+ if (
1005
+ !ownSections ||
1006
+ Object.keys(own).length !== Object.keys(ownSections).length ||
1007
+ Object.entries(own).some(([name, value]) => ownSections[name] !== value)
1008
+ ) {
1009
+ ownSections = own;
1010
+ ownSectionsVersion++;
1011
+ }
1012
+ ownHead = headSections(event.messages);
1013
+ } else if (messageRun && ownSections) {
1014
+ // Pi 1.2.0 and later give a run an extension's message starts the sections Freeflow set for it, so Freeflow's
1015
+ // follow this run, while every other section keeps the last prompt's value (its extension may set it only for
1016
+ // prompts). The head stays the one recorded, so a head Pi rebuilds here still gets the recorded sections.
1017
+ const current = currentSections(event.messages);
1018
+ const next = { ...ownSections };
1019
+ for (const name of OWN_SECTION_NAMES) {
1020
+ const value = current[name];
1021
+ if (typeof value === "string") next[name] = value;
1022
+ else delete next[name];
1023
+ }
1024
+ if (OWN_SECTION_NAMES.some((name) => next[name] !== ownSections[name])) {
1025
+ ownSections = next;
1026
+ ownSectionsVersion++;
1027
+ }
1028
+ } else if (!ownSections) {
1029
+ // A run Pi started before any prompt of this process (a resumed session messaged while idle): the transcript
1030
+ // still carries the last prompt's sections, when Freeflow's guidance is among them.
1031
+ const current = currentSections(event.messages);
1032
+ if (typeof current.freeflow_guidance === "string") {
1033
+ ownSections = strings(current);
1034
+ ownHead = headSections(event.messages);
1035
+ ownSectionsVersion++;
1036
+ }
1037
+ }
1038
+ let messages = keepSections(event.messages, ownSections, ownSectionsVersion, ownHead)
1039
+ .map(filterBootstrapMessage)
1040
+ .filter(Boolean);
506
1041
  messages = withFreeflowRuntimeState(messages, capability, routing.state(), prompts, {
507
1042
  force: refreshState,
508
1043
  anchor: runtimeStateAnchor,
509
1044
  projectionFailure: routing.state().projectionFailure,
1045
+ taskMemory: taskMemory.runtimeLines(),
1046
+ skills: stateSkills,
510
1047
  });
511
1048
  refreshState = false;
512
1049
  // Disabled Freeflow pays no per-request provenance work; re-enabling rebuilds it from session history.
@@ -535,12 +1072,31 @@ export default function freeflow(pi) {
535
1072
  await routing.ancestryChanged(ctx, navigation);
536
1073
  await update(ctx);
537
1074
  };
538
- pi.on("session_tree", async (_event, ctx) => restore(ctx));
1075
+ pi.on("session_tree", async (_event, ctx) => {
1076
+ // A run Pi starts on this branch keeps this branch's last prompt's sections, not the branch left behind.
1077
+ forgetSections(ctx);
1078
+ // Task memory follows the branch: the records attached at this point of the conversation.
1079
+ const before = taskMemory.state();
1080
+ const changed = taskMemory.rebuild(cachedBranch(ctx.sessionManager));
1081
+ if (taskMemoryEffective()) await taskMemory.load();
1082
+ await restore(ctx);
1083
+ if (!taskMemoryEffective()) return;
1084
+ if (changed) {
1085
+ const focus = taskMemory.state().focus;
1086
+ ctx.ui?.notify?.(
1087
+ `Task memory follows this branch: focus ${before.focus ? taskMemory.name(before.focus) : "none"} -> ${focus ? taskMemory.name(focus) : "none"}. ${taskMemory.attachedText()} Change it with /freeflow task.`,
1088
+ "info",
1089
+ );
1090
+ }
1091
+ await taskMemory.deliver(cachedBranch(ctx.sessionManager));
1092
+ });
539
1093
  // Pi compacting on its own (threshold, overflow, /compact) keeps its summarizer and kept tail, with Freeflow's additions.
540
1094
  pi.on("session_before_compact", async (event, ctx) => compaction.fallback(event, ctx));
541
1095
  pi.on("session_compact", async (_event, ctx) => {
1096
+ files.piCompacted();
542
1097
  await restore(ctx, false);
543
1098
  background.restate();
1099
+ await taskMemory.deliver(cachedBranch(ctx.sessionManager));
544
1100
  });
545
1101
  pi.on("session_compact_failed", async (_event, ctx) => {
546
1102
  refreshState = true;
@@ -582,15 +1138,6 @@ export default function freeflow(pi) {
582
1138
  for (const { command, skill } of WORKFLOW_COMMANDS)
583
1139
  pi.registerCommand(command, {
584
1140
  description: command === skill ? `Run Freeflow ${skill}` : `Run Freeflow ${skill} via ${command}`,
585
- ...(command === "bypass"
586
- ? {
587
- getArgumentCompletions: (prefix) =>
588
- [
589
- { value: "next", label: "next", description: "Skip one optional step" },
590
- { value: "task", label: "task", description: "Reduce optional pressure for the current task" },
591
- ].filter((item) => item.value.startsWith(prefix ?? "")),
592
- }
593
- : {}),
594
1141
  handler: (args, ctx) => sendSkillCommand(pi, ctx, skill, args),
595
1142
  });
596
1143
  for (const skill of CONTRIBUTOR_COMMANDS)
@@ -608,6 +1155,21 @@ export default function freeflow(pi) {
608
1155
  typeof api.setThinkingLevel === "function",
609
1156
  ),
610
1157
  handler: async (args, ctx) => {
1158
+ const task = /^task(?:\s+(.*))?$/i.exec((args ?? "").trim());
1159
+ if (task) {
1160
+ await taskCommand(task[1] ?? "", ctx);
1161
+ return;
1162
+ }
1163
+ const trash = /^trash(?:\s+(.*))?$/i.exec((args ?? "").trim());
1164
+ if (trash) {
1165
+ await trashCommand(trash[1] ?? "", ctx);
1166
+ return;
1167
+ }
1168
+ const project = /^project(?:\s+(.*))?$/i.exec((args ?? "").trim());
1169
+ if (project) {
1170
+ await projectCommand(project[1] ?? "", ctx);
1171
+ return;
1172
+ }
611
1173
  if ((args ?? "").trim().toLowerCase() === "compact") {
612
1174
  await compactNow(ctx);
613
1175
  return;
@@ -623,6 +1185,10 @@ export default function freeflow(pi) {
623
1185
  cacheHealth: [...cacheHealth.status(), ...cacheMonitor.lines()],
624
1186
  backgroundRunning: background.running().length,
625
1187
  compaction: compaction.statusText(ctx),
1188
+ taskMemory: taskMemory.attachedText(),
1189
+ messaging: messaging.self()
1190
+ ? `this session is ${messaging.self().name}${messaging.heldCount() ? `, ${messaging.heldCount()} held` : ""}`
1191
+ : undefined,
626
1192
  }),
627
1193
  });
628
1194
  },