@north-light/crouter 0.3.220 → 0.3.222

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 (271) hide show
  1. package/dist/api/client.d.ts +29 -0
  2. package/dist/api/client.js +44 -0
  3. package/dist/api/dto/chat-inventory.d.ts +56 -0
  4. package/dist/api/dto/chat-inventory.js +11 -0
  5. package/dist/api/dto/human-requests.d.ts +88 -0
  6. package/dist/api/dto/human-requests.js +4 -0
  7. package/dist/api/dto/human.d.ts +3 -0
  8. package/dist/api/dto/profiles.d.ts +19 -5
  9. package/dist/api/dto/profiles.js +2 -1
  10. package/dist/api/dto/reviews.d.ts +2 -0
  11. package/dist/api/index.d.ts +2 -0
  12. package/dist/api/index.js +2 -0
  13. package/dist/api/routes.d.ts +8 -0
  14. package/dist/api/routes.js +11 -0
  15. package/dist/build-root.d.ts +2 -6
  16. package/dist/build-root.js +51 -4
  17. package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
  18. package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
  19. package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
  20. package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
  21. package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
  22. package/dist/builtin-memory/04-base-worker.md +4 -8
  23. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  24. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
  25. package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
  26. package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
  27. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
  28. package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
  29. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
  30. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
  31. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
  32. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
  33. package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
  34. package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
  35. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
  36. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
  37. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
  38. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
  39. package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
  40. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
  41. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
  42. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
  43. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
  44. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
  45. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
  46. package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
  47. package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
  48. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
  49. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
  50. package/dist/builtin-memory/design/guide.md +35 -0
  51. package/dist/builtin-memory/design/roadmap.md +21 -0
  52. package/dist/builtin-memory/insights/capture.md +1 -1
  53. package/dist/builtin-memory/internal/agent-shaping.md +3 -1
  54. package/dist/builtin-memory/internal/memory-loading.md +4 -0
  55. package/dist/builtin-memory/internal/plugins.md +10 -1
  56. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  57. package/dist/builtin-memory/plan/roadmap.md +6 -22
  58. package/dist/builtin-memory/spec/guide.md +23 -6
  59. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +1 -1
  60. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -1
  61. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +34 -16
  62. package/dist/clients/attach/__tests__/ref-autocomplete.test.js +1 -1
  63. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -1
  64. package/dist/clients/attach/overlays/file-review.js +2 -2
  65. package/dist/clients/attach/render/markdown-source.js +106 -1
  66. package/dist/clients/attach/session/file-links.d.ts +13 -4
  67. package/dist/clients/attach/session/file-links.js +54 -58
  68. package/dist/clients/attach/session/keys.d.ts +1 -1
  69. package/dist/clients/attach/session/profile-files.js +1 -1
  70. package/dist/clients/attach/viewer.js +698 -696
  71. package/dist/clients/inbox/controller.js +1 -1
  72. package/dist/clients/inbox/resolve.d.ts +1 -0
  73. package/dist/clients/inbox/review/launch.d.ts +8 -4
  74. package/dist/clients/inbox/review/launch.js +55 -5
  75. package/dist/clients/inbox/review/review-client.d.ts +1 -0
  76. package/dist/clients/inbox/review/review-client.js +7 -1
  77. package/dist/clients/inbox/review-adapter.d.ts +1 -8
  78. package/dist/clients/inbox/review-adapter.js +4 -52
  79. package/dist/commands/__tests__/human.test.js +2 -2
  80. package/dist/commands/human/request.d.ts +2 -0
  81. package/dist/commands/human/request.js +281 -0
  82. package/dist/commands/human.js +5 -2
  83. package/dist/commands/memory/lint.js +2 -1
  84. package/dist/commands/memory/read.js +1 -0
  85. package/dist/commands/memory.js +1 -1
  86. package/dist/commands/pkg/market-manage.js +165 -75
  87. package/dist/commands/pkg/plugin-inspect.js +19 -2
  88. package/dist/commands/pkg/plugin-manage.d.ts +8 -3
  89. package/dist/commands/pkg/plugin-manage.js +72 -24
  90. package/dist/commands/profile/default.js +6 -10
  91. package/dist/commands/profile/list.js +5 -3
  92. package/dist/commands/profile/new.js +21 -8
  93. package/dist/commands/profile/project.js +25 -19
  94. package/dist/commands/profile/show.js +3 -3
  95. package/dist/commands/surface-inbox.js +1 -0
  96. package/dist/commands/sys/__tests__/migrate.test.js +16 -5
  97. package/dist/commands/sys/config.js +2 -2
  98. package/dist/commands/sys/doctor.js +87 -5
  99. package/dist/commands/sys/migrate.js +38 -19
  100. package/dist/commands/sys/setup-core.js +1 -1
  101. package/dist/commands/sys/sync-project-guidance.js +1 -1
  102. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
  103. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +1 -1
  104. package/dist/core/__tests__/fixtures/c5-command-boundary-ext.js +24 -0
  105. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -18
  106. package/dist/core/__tests__/fixtures/fake-engine.js +8 -1
  107. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
  108. package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
  109. package/dist/core/__tests__/human-action-delivery.test.js +140 -0
  110. package/dist/core/__tests__/human-actions.test.d.ts +1 -0
  111. package/dist/core/__tests__/human-actions.test.js +116 -0
  112. package/dist/core/__tests__/inline-memory-refs.test.js +36 -2
  113. package/dist/core/__tests__/profile-project-memory-delivery.test.d.ts +1 -0
  114. package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
  115. package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
  116. package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
  117. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
  118. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
  119. package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
  120. package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
  121. package/dist/core/__tests__/serial/broker-sdk-wiring.test.js +102 -2
  122. package/dist/core/bootstrap.js +6 -0
  123. package/dist/core/canvas/browse/app.js +5 -2
  124. package/dist/core/canvas/browse/model.d.ts +25 -15
  125. package/dist/core/canvas/browse/model.js +86 -65
  126. package/dist/core/canvas/db.js +23 -0
  127. package/dist/core/canvas/human-deliveries.d.ts +53 -0
  128. package/dist/core/canvas/human-deliveries.js +75 -0
  129. package/dist/core/canvas/render-source.d.ts +6 -0
  130. package/dist/core/canvas/render-source.js +7 -1
  131. package/dist/core/canvas/render.js +10 -2
  132. package/dist/core/command-hooks/artifact.d.ts +10 -0
  133. package/dist/core/command-hooks/artifact.js +129 -0
  134. package/dist/core/command-hooks/catalog.d.ts +14 -0
  135. package/dist/core/command-hooks/catalog.js +38 -0
  136. package/dist/core/command-hooks/compose.d.ts +15 -0
  137. package/dist/core/command-hooks/compose.js +99 -0
  138. package/dist/core/command-hooks/discovery.d.ts +87 -0
  139. package/dist/core/command-hooks/discovery.js +174 -0
  140. package/dist/core/command-hooks/help.d.ts +5 -0
  141. package/dist/core/command-hooks/help.js +18 -0
  142. package/dist/core/command-hooks/index.d.ts +6 -0
  143. package/dist/core/command-hooks/index.js +6 -0
  144. package/dist/core/command-hooks/report.d.ts +23 -0
  145. package/dist/core/command-hooks/report.js +19 -0
  146. package/dist/core/command-hooks/schema.d.ts +27 -0
  147. package/dist/core/command-hooks/schema.js +68 -0
  148. package/dist/core/command-hooks/transport/exec-invoke.d.ts +22 -0
  149. package/dist/core/command-hooks/transport/exec-invoke.js +274 -0
  150. package/dist/core/command-plugins/presence.d.ts +2 -0
  151. package/dist/core/command-plugins/presence.js +17 -0
  152. package/dist/core/command-plugins/transport/exec-invoke.d.ts +5 -0
  153. package/dist/core/command-plugins/transport/exec-invoke.js +58 -5
  154. package/dist/core/command.d.ts +8 -1
  155. package/dist/core/command.js +12 -10
  156. package/dist/core/config.d.ts +13 -1
  157. package/dist/core/config.js +51 -1
  158. package/dist/core/feed/inbox.d.ts +6 -0
  159. package/dist/core/feed/inbox.js +9 -1
  160. package/dist/core/help.d.ts +7 -1
  161. package/dist/core/human/action-binding.d.ts +21 -0
  162. package/dist/core/human/action-binding.js +40 -0
  163. package/dist/core/human/completion.d.ts +38 -0
  164. package/dist/core/human/completion.js +27 -0
  165. package/dist/core/human/convention.d.ts +2 -0
  166. package/dist/core/human/convention.js +2 -0
  167. package/dist/core/human/tickets.d.ts +25 -6
  168. package/dist/core/human/tickets.js +19 -13
  169. package/dist/core/human/types.d.ts +5 -0
  170. package/dist/core/human-actions.d.ts +25 -0
  171. package/dist/core/human-actions.js +101 -0
  172. package/dist/core/io.d.ts +9 -1
  173. package/dist/core/io.js +44 -2
  174. package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
  175. package/dist/core/memory/inline-ref-inventory.js +15 -8
  176. package/dist/core/memory-resolver.d.ts +13 -1
  177. package/dist/core/memory-resolver.js +26 -20
  178. package/dist/core/profiles/manifest.d.ts +13 -2
  179. package/dist/core/profiles/manifest.js +84 -18
  180. package/dist/core/profiles/select.d.ts +2 -0
  181. package/dist/core/profiles/select.js +29 -12
  182. package/dist/core/render.js +11 -0
  183. package/dist/core/runtime/advertised-command-invocation.d.ts +20 -0
  184. package/dist/core/runtime/advertised-command-invocation.js +233 -0
  185. package/dist/core/runtime/bearings.js +1 -1
  186. package/dist/core/runtime/broker/event-projection.js +7 -0
  187. package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
  188. package/dist/core/runtime/broker/frame-dispatch.js +13 -11
  189. package/dist/core/runtime/broker/read-ops.d.ts +4 -0
  190. package/dist/core/runtime/broker/read-ops.js +6 -2
  191. package/dist/core/runtime/broker-extension-render.js +1 -1
  192. package/dist/core/runtime/broker-inventory.d.ts +4 -0
  193. package/dist/core/runtime/broker-inventory.js +116 -0
  194. package/dist/core/runtime/broker-persona-guidance.js +1 -1
  195. package/dist/core/runtime/broker-protocol.d.ts +9 -2
  196. package/dist/core/runtime/broker.js +10 -1
  197. package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
  198. package/dist/core/runtime/chat-inventory-rows.js +105 -0
  199. package/dist/core/runtime/command-surface.d.ts +38 -0
  200. package/dist/core/runtime/command-surface.js +117 -0
  201. package/dist/core/runtime/launch-target.d.ts +25 -0
  202. package/dist/core/runtime/launch-target.js +54 -0
  203. package/dist/core/runtime/node-read.js +5 -0
  204. package/dist/core/runtime/persona.js +3 -3
  205. package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
  206. package/dist/core/runtime/prospective-inventory-cli.js +61 -0
  207. package/dist/core/runtime/prospective-inventory.d.ts +10 -0
  208. package/dist/core/runtime/prospective-inventory.js +88 -0
  209. package/dist/core/runtime/spawn.d.ts +3 -1
  210. package/dist/core/runtime/spawn.js +5 -3
  211. package/dist/core/scope.d.ts +26 -1
  212. package/dist/core/scope.js +52 -12
  213. package/dist/core/substrate/on-read.d.ts +7 -1
  214. package/dist/core/substrate/on-read.js +30 -32
  215. package/dist/core/substrate/render-node.d.ts +3 -2
  216. package/dist/core/substrate/render-node.js +3 -2
  217. package/dist/core/substrate/render.js +65 -24
  218. package/dist/core/substrate/schema.d.ts +16 -2
  219. package/dist/core/substrate/schema.js +14 -5
  220. package/dist/core/user-settings.d.ts +4 -0
  221. package/dist/core/user-settings.js +1 -0
  222. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +56 -7
  223. package/dist/daemon/api/handlers/chat-inventory.d.ts +2 -0
  224. package/dist/daemon/api/handlers/chat-inventory.js +25 -0
  225. package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
  226. package/dist/daemon/api/handlers/human-requests.js +409 -0
  227. package/dist/daemon/api/handlers/human.js +3 -0
  228. package/dist/daemon/api/handlers/inbox.js +3 -0
  229. package/dist/daemon/api/handlers/nodes.d.ts +1 -3
  230. package/dist/daemon/api/handlers/nodes.js +11 -46
  231. package/dist/daemon/api/handlers/profiles.js +7 -1
  232. package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
  233. package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
  234. package/dist/daemon/api/handlers/reviews.js +10 -2
  235. package/dist/daemon/api/map.d.ts +2 -1
  236. package/dist/daemon/api/map.js +3 -2
  237. package/dist/daemon/api/server.js +6 -0
  238. package/dist/daemon/crtrd.js +6 -0
  239. package/dist/daemon/human/deliver-action.d.ts +16 -0
  240. package/dist/daemon/human/deliver-action.js +168 -0
  241. package/dist/daemon/human/finish.d.ts +8 -5
  242. package/dist/daemon/human/finish.js +45 -6
  243. package/dist/daemon/human/sweep.js +4 -1
  244. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
  245. package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
  246. package/dist/daemon/review/finish.d.ts +8 -3
  247. package/dist/daemon/review/finish.js +19 -1
  248. package/dist/hook-authoring.d.ts +75 -0
  249. package/dist/hook-authoring.js +358 -0
  250. package/dist/hook-process.d.ts +7 -0
  251. package/dist/hook-process.js +34 -0
  252. package/dist/index.d.ts +2 -0
  253. package/dist/index.js +2 -0
  254. package/dist/migrations/002-profile-project-memory.d.ts +2 -0
  255. package/dist/migrations/002-profile-project-memory.js +71 -0
  256. package/dist/migrations/profile-manifests.d.ts +30 -0
  257. package/dist/migrations/profile-manifests.js +70 -0
  258. package/dist/migrations/registry.js +10 -5
  259. package/dist/migrations/types.d.ts +28 -1
  260. package/dist/migrations/types.js +15 -9
  261. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +21 -4
  262. package/dist/pi-extensions/canvas-structured-output.js +85 -2
  263. package/dist/types.d.ts +23 -6
  264. package/dist/types.js +1 -0
  265. package/package.json +1 -1
  266. package/runtime.lock.json +2 -2
  267. package/dist/builtin-memory/00-runtime-base.md +0 -55
  268. package/dist/builtin-memory/design.md +0 -55
  269. package/dist/clients/attach/__tests__/file-review-focus.test.js +0 -49
  270. /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
  271. /package/dist/{clients/attach/__tests__/file-review-focus.test.d.ts → core/__tests__/fixtures/memory-slash-live-probe.d.ts} +0 -0
@@ -1,21 +1,38 @@
1
1
  ---
2
2
  kind: knowledge
3
- when-and-why-to-read: When eliciting or writing a specification, this knowledge should be read because downstream design and planning need settled intent without making the user answer avoidable questions or forcing every request through the same ceremony.
4
- short-form: Elicit only consequential uncertainty, then write a right-sized behavioral contract a downstream reader can use without guessing.
5
- rationale: Specification quality and elicitation guidance lived inside an always-loaded spec persona while the lightweight /spec command had almost none, leaving agents to choose between a vague one-shot and a fixed discovery workflow.
3
+ when-and-why-to-read: When exploring, eliciting, or writing a specification, this knowledge should be read because the outcome has to be developed with the user before it is pinned down, and downstream design and planning then need it settled without avoidable questions or ceremony.
4
+ short-form: Explore openly with the user, converge on what they chose, then write a right-sized behavioral contract a downstream reader can use without guessing.
5
+ rationale: >-
6
+ Specification quality and elicitation guidance lived inside an always-loaded spec persona while the lightweight /spec command had almost none, leaving agents to choose between a vague one-shot and a fixed discovery workflow. Spec writers also promoted plausible nice-to-haves into requirements without asking, silently expanding the requested work. Every remaining lever then pointed at convergence — one interpretation reflected back, questions minimized, elicitation stopped as soon as no answer would change the contract — so the agent transcribed the request instead of developing it. The exploration is about the solution — challenging the user's premise is available when something genuinely does not fit, never a required move.
6
7
  ---
7
8
 
8
9
  # Writing a specification
9
10
 
10
11
  A specification settles **what outcome and behavior are required**. It is not an architecture document or an implementation plan. Its depth follows the stakes and unresolved intent: a small reversible change may need a few paragraphs; a consequential product surface may need collaborative discovery and separate design.
11
12
 
13
+ ## Explore before you converge
14
+
15
+ Take the request at face value and put the openness into what it could be. Understand what the user is trying to achieve and why now, then develop the possibilities with them: a specification is the output of a finished exploration, and the first shape anyone thinks of is rarely the best one available.
16
+
17
+ Develop a few genuinely different directions rather than enumerating shallow variants, and push each one several steps — what changes, what that makes possible next, what it looks like once it exists. Moves that open a direction: remove a constraint everyone assumed; change who or what is served; do materially less than asked and see what survives; ask what happens if nothing changes at all. Where the surrounding system or prior art would feed the thinking, read for it while you think, not as a validation pass afterward.
18
+
19
+ Bring these to the user as live options, in plain language, with what each buys and closes off. Do not open with objections, feasibility verdicts, or a recommendation, and do not pre-reject an unusual but coherent direction — nothing is committed until the user picks, so divergence is free. If something in the request genuinely does not fit what they are trying to achieve, say so once; questioning their premise is not the job.
20
+
21
+ Converge when the user has chosen among live options and what remains is detail.
22
+
12
23
  ## Elicit without interrogating
13
24
 
14
- Investigate before asking. Read the request, relevant code and documents, and already-settled decisions first. A fact available from the project is not a question for the user.
25
+ Investigate before asking. Read the request, relevant code and documents, and already-settled decisions first. A fact available from the project is not a question for the user; intent never is such a fact.
26
+
27
+ Reflect a concrete interpretation back so the user can confirm or correct it. Resolve the uncertainty whose answer could most change behavior, scope, or acceptance. When a decision really belongs to the user, give them a focused question with a proposed default or concrete options; use one decision or one small coherent set rather than a questionnaire.
28
+
29
+ Spend attention where judgment is load-bearing, not where detail is merely available. Keep settled points moving and fold each answer into the specification as current truth. Once converged, stop eliciting when another answer would not materially change the behavioral contract. Explicit approval is warranted when the user is co-authoring the document or the remaining decision is consequential; ordinary reversible work does not need a ritual approval loop.
30
+
31
+ ## Commit only what the user chose
15
32
 
16
- Reflect a concrete interpretation of the request so the user can confirm or correct it. Resolve the uncertainty whose answer could most change behavior, scope, or acceptance. When a decision really belongs to the user, give them a focused question with a proposed default or concrete options; use one decision or one small coherent set rather than a questionnaire.
33
+ Exploration is unbounded; the document is not. What you explored and the user did not choose stays out — speculative features, future extensibility, adjacent cleanup, and other merely plausible additions are not requirements just because they came up. The bar is not smallness for its own sake: nothing enters the specification without the user's assent.
17
34
 
18
- Spend attention where judgment is load-bearing, not where detail is merely available. Keep settled points moving and fold each answer into the specification as current truth. Stop eliciting when another answer would not materially change the behavioral contract. Explicit approval is warranted when the user is co-authoring the document or the remaining decision is consequential; ordinary reversible work does not need a ritual approval loop.
35
+ When something seems likely desirable but is not explicitly or implicitly required by the request, ask the user whether to include it through `crtr human` before finishing the specification (`crtr human send -h`), wait for their answer, and make the resulting boundary explicit. Do not hide the addition in an assumption, recommendation, or optional requirement.
19
36
 
20
37
  ## The finished specification
21
38
 
@@ -164,7 +164,7 @@ function selectedProfileProjects(): string[] {
164
164
  const profileId = process.env["CRTR_PROFILE_ID"] ?? "";
165
165
  if (profileId === "") return [];
166
166
  try {
167
- return loadProfileManifest(profileId).manifest.projects;
167
+ return loadProfileManifest(profileId).manifest.projects.map((project) => project.path);
168
168
  } catch {
169
169
  // A deleted or invalid selected profile must not hide cwd-local commands.
170
170
  return [];
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
4
4
  import { readFileSync, existsSync, realpathSync } from "node:fs";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { dirname, join } from "node:path";
7
+ import type { ChatCommandMetadata } from "../../../../core/runtime/command-surface.js";
7
8
 
8
9
  const exec = promisify(execFile);
9
10
 
@@ -149,6 +150,11 @@ export default async function (pi: ExtensionAPI) {
149
150
  const invocation = `Run \`crtr ${path.join(" ")} -h\` (pass args to run the real command)`;
150
151
  pi.registerCommand(name, {
151
152
  description: node.description ? `${node.description} — ${invocation}` : invocation,
153
+ // Chat-capable: the handler's outcome is the command's output placed into
154
+ // the conversation as a displayed message. The transient status line is
155
+ // decoration a surface without one simply does not show.
156
+ gateway: true as const,
157
+ argumentHint: "[args]",
152
158
  handler: async (args, ctx) => {
153
159
  const extra = (args ?? "").trim();
154
160
  // No args -> show help. With args -> run the real command.
@@ -165,7 +171,7 @@ export default async function (pi: ExtensionAPI) {
165
171
  { triggerTurn: TRIGGER_TURN },
166
172
  );
167
173
  },
168
- });
174
+ } as Parameters<ExtensionAPI["registerCommand"]>[1] & ChatCommandMetadata);
169
175
  };
170
176
 
171
177
  const filters = loadFilters();
@@ -6,6 +6,7 @@ import {
6
6
  type DeterministicCommandExpansion,
7
7
  } from "../../../core/runtime/command-expansion.js";
8
8
  import { expandShellBlocks, hasShellBlocks, DEFAULT_SHELL_TIMEOUT_MS } from "../../../core/runtime/shell-expansion.js";
9
+ import type { ChatCommandMetadata } from "../../../core/runtime/command-surface.js";
9
10
  import { piShellRunner } from "./pi-shell-runner.js";
10
11
 
11
12
  // ---------------------------------------------------------------------------
@@ -43,10 +44,9 @@ export interface SlashDoc {
43
44
  description: string;
44
45
  }
45
46
 
46
- // `memory list` already chose the winning path for each identity. Keep that
47
- // path for registration-body reads so every slash document does not re-walk the
48
- // full corpus through `memory read`.
49
- const resolvedMemoryPaths = new Map<string, string>();
47
+ // `memory list` already chose each winning document. Keep its canonical body
48
+ // for disclosure classification and its path for the existing execution read.
49
+ const resolvedMemoryDocs = new Map<string, { path: string; canonicalBody: string }>();
50
50
 
51
51
  async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
52
52
  const snapshot = createMemoryDocSnapshot();
@@ -67,15 +67,24 @@ async function defaultListMemoryDocs(): Promise<MemoryListItem[]> {
67
67
  return item.slash === true ? [item.name] : [];
68
68
  });
69
69
  const resolved = snapshot.resolve(names);
70
- resolvedMemoryPaths.clear();
71
- for (const [name, doc] of resolved) resolvedMemoryPaths.set(name, doc.path);
70
+ resolvedMemoryDocs.clear();
71
+ for (const [name, doc] of resolved) {
72
+ resolvedMemoryDocs.set(name, { path: doc.path, canonicalBody: doc.body });
73
+ }
72
74
  return items;
73
75
  }
74
76
 
75
- function defaultReadMemoryDoc(name: string): string {
76
- const path = resolvedMemoryPaths.get(name);
77
- if (path === undefined) throw new Error(`memory slash document was not resolved: ${name}`);
78
- return readMemoryDocContent(path);
77
+ function defaultReadMemoryDoc(name: string): { canonicalBody: string; executionBody: string } {
78
+ const resolved = resolvedMemoryDocs.get(name);
79
+ if (resolved === undefined) throw new Error(`memory slash document was not resolved: ${name}`);
80
+ return {
81
+ canonicalBody: resolved.canonicalBody,
82
+ executionBody: readMemoryDocContent(resolved.path),
83
+ };
84
+ }
85
+
86
+ function hasNodeRelativeExpansion(body: string): boolean {
87
+ return body.includes("$CRTR_CONTEXT_DIR") || body.includes("$CRTR_NODE_ID");
79
88
  }
80
89
 
81
90
  /** `/`-joined path-derived doc name -> the registered slash-command name. */
@@ -109,24 +118,33 @@ export default async function (pi: ExtensionAPI) {
109
118
  const docs = await discoverSlashDocs();
110
119
  for (const doc of docs) {
111
120
  let expansion: DeterministicCommandExpansion | undefined;
121
+ let discloseExpansion = false;
112
122
  let loadError: unknown;
113
123
  try {
124
+ const body = defaultReadMemoryDoc(doc.name);
114
125
  expansion = {
115
126
  kind: "memory-slash",
116
127
  commandName: doc.commandName,
117
- body: defaultReadMemoryDoc(doc.name),
128
+ body: body.executionBody,
118
129
  };
130
+ discloseExpansion = !hasNodeRelativeExpansion(body.canonicalBody);
119
131
  } catch (err) {
120
132
  loadError = err;
121
133
  }
122
134
 
123
135
  // pi preserves unknown registration fields on the resolved command object.
124
- // The broker discloses this serializable contract through get_commands; the
125
- // handler below consumes the SAME object and expander, so preview cannot
126
- // drift from submission. A failed/dynamic command has no expansion metadata.
136
+ // The broker discloses deterministic expansions through get_commands. A
137
+ // node-relative body still executes through the private expansion below,
138
+ // but cannot promise a concrete payload before the future node exists.
127
139
  pi.registerCommand(doc.commandName, {
128
140
  description: doc.description,
129
- ...(expansion === undefined ? {} : { expansion }),
141
+ // Chat-capable: the handler's whole outcome is a conversation turn
142
+ // carrying the expanded document, which any surface hosting the
143
+ // conversation receives. Only when the expansion loaded — without it the
144
+ // sole outcome is a terminal-only error notify, so it stays undisclosed.
145
+ ...(expansion === undefined
146
+ ? {}
147
+ : { gateway: true as const, ...(discloseExpansion ? { expansion } : {}) }),
130
148
  handler: async (args, ctx) => {
131
149
  if (expansion === undefined) {
132
150
  ctx.ui.notify(
@@ -157,6 +175,6 @@ export default async function (pi: ExtensionAPI) {
157
175
  { triggerTurn: true },
158
176
  );
159
177
  },
160
- } as Parameters<ExtensionAPI["registerCommand"]>[1] & { expansion?: DeterministicCommandExpansion });
178
+ } as Parameters<ExtensionAPI["registerCommand"]>[1] & { expansion?: DeterministicCommandExpansion } & ChatCommandMetadata);
161
179
  }
162
180
  }
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
  import { findRefCompletionContext, RefAwareAutocompleteProvider } from '../input/ref-autocomplete.js';
4
4
  function ref(name, shortForm = `${name} docs`) {
5
- return { name, kind: 'knowledge', scope: 'user', shortForm };
5
+ return { name, kind: 'knowledge', scope: 'user', shortForm, gatewayVisible: true };
6
6
  }
7
7
  /** The wrapped provider; ref handling must never reach it in a ref context. */
8
8
  const inertDelegate = {
@@ -47,7 +47,7 @@ test('Enter-confirming a non-leading ref completion accepts the token and return
47
47
  editor.onSubmit = (text) => {
48
48
  submitted = text;
49
49
  };
50
- const refs = [{ name: 'dev', kind: 'knowledge', scope: 'user', shortForm: 'dev docs' }];
50
+ const refs = [{ name: 'dev', kind: 'knowledge', scope: 'user', shortForm: 'dev docs', gatewayVisible: true }];
51
51
  const delegate = {
52
52
  async getSuggestions() {
53
53
  return null;
@@ -2,7 +2,7 @@ import { homedir } from 'node:os';
2
2
  import { isAbsolute, join, relative, resolve } from 'node:path';
3
3
  import { Input, SelectList, getKeybindings, } from '@earendil-works/pi-tui';
4
4
  import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
5
- import { openReviewSurface } from '../../inbox/review/launch.js';
5
+ import { openReviewWindow } from '../../inbox/review/launch.js';
6
6
  import { createReviewClient, ReviewTerminalError, terminalRefusalNotice } from '../../inbox/review/review-client.js';
7
7
  import { checkReviewableFile, extractFilePaths } from '#core/human/visible-paths';
8
8
  const MAX_VISIBLE_ROWS = 12;
@@ -123,6 +123,6 @@ export async function launchFileReview(host, file, opts) {
123
123
  opts.onNotice(err instanceof ReviewTerminalError ? terminalRefusalNotice(err) : err instanceof Error ? err.message : String(err));
124
124
  return;
125
125
  }
126
- await openReviewSurface(host, { review });
126
+ openReviewWindow(host, review);
127
127
  }
128
128
  export { extractFilePaths };
@@ -22,6 +22,7 @@
22
22
  // GLYPHS. Private-use `U+XXXX` references become their glyphs (see
23
23
  // glyph-codepoints.ts), so an agent that cannot type a Nerd Font character can
24
24
  // still put one on screen.
25
+ import { pathToFileURL } from 'node:url';
25
26
  import { expandGlyphCodepoints } from './glyph-codepoints.js';
26
27
  const ATX_HEADING = /^(\s{0,3})(#{3,6})(?:[ \t]+|$)(.*?)([ \t]+#+[ \t]*)?$/;
27
28
  const FENCE_OPEN = /^\s{0,3}(`{3,}|~{3,})/;
@@ -126,7 +127,111 @@ export function styleAttachMarkdownSource(markdown) {
126
127
  * message keeps `styleAttachMarkdownSource` alone — the `U+XXXX` escape hatch
127
128
  * exists for agents that cannot type a Nerd Font character. */
128
129
  export function styleAttachAgentMarkdown(markdown) {
129
- return expandGlyphCodepoints(styleAttachMarkdownSource(markdown));
130
+ return expandGlyphCodepoints(styleAttachMarkdownSource(rewriteAbsoluteFileLinks(markdown)));
131
+ }
132
+ /** Give iTerm a hidden fragment so its Semantic History action receives the local target. */
133
+ function rewriteAbsoluteFileLinks(markdown) {
134
+ let fence;
135
+ return markdown.split('\n').map((line) => {
136
+ const fenceMatch = FENCE_OPEN.exec(line);
137
+ if (fence !== undefined) {
138
+ if (fenceMatch?.[1][0] === fence)
139
+ fence = undefined;
140
+ return line;
141
+ }
142
+ if (fenceMatch) {
143
+ fence = fenceMatch[1][0];
144
+ return line;
145
+ }
146
+ return rewriteInlineFileLinks(line);
147
+ }).join('\n');
148
+ }
149
+ function rewriteInlineFileLinks(line) {
150
+ let result = '';
151
+ let rest = line;
152
+ while (rest !== '') {
153
+ const opening = rest.match(/`+/);
154
+ const prose = opening === null ? rest : rest.slice(0, opening.index);
155
+ result += rewriteProseFileLinks(prose);
156
+ if (opening === null)
157
+ break;
158
+ const delimiter = opening[0];
159
+ const end = rest.indexOf(delimiter, prose.length + delimiter.length);
160
+ if (end === -1)
161
+ return result + rest.slice(prose.length);
162
+ result += rest.slice(prose.length, end + delimiter.length);
163
+ rest = rest.slice(end + delimiter.length);
164
+ }
165
+ return result;
166
+ }
167
+ function rewriteProseFileLinks(prose) {
168
+ let result = '';
169
+ let index = 0;
170
+ while (index < prose.length) {
171
+ const start = prose.indexOf('[', index);
172
+ if (start === -1)
173
+ return result + prose.slice(index);
174
+ const link = parseLocalMarkdownLink(prose, start);
175
+ if (link === undefined) {
176
+ result += prose.slice(index, start + 1);
177
+ index = start + 1;
178
+ continue;
179
+ }
180
+ result += prose.slice(index, start) + `[${link.label}](${pathToFileURL(link.path).href}#crtr-file-open)`;
181
+ index = link.end;
182
+ }
183
+ return result;
184
+ }
185
+ function parseLocalMarkdownLink(line, start) {
186
+ const labelEnd = closingDelimiter(line, start + 1, ']');
187
+ if (labelEnd === undefined || line[labelEnd + 1] !== '(')
188
+ return undefined;
189
+ const destinationStart = labelEnd + 2;
190
+ if (line[destinationStart] === '<') {
191
+ const destinationEnd = closingDelimiter(line, destinationStart + 1, '>');
192
+ if (destinationEnd === undefined || line[destinationEnd + 1] !== ')')
193
+ return undefined;
194
+ const path = localPathFromDestination(line.slice(destinationStart + 1, destinationEnd));
195
+ return path === undefined ? undefined : { label: line.slice(start + 1, labelEnd), path, end: destinationEnd + 2 };
196
+ }
197
+ let depth = 0;
198
+ for (let index = destinationStart; index < line.length; index++) {
199
+ if (line[index] === '\\') {
200
+ index++;
201
+ continue;
202
+ }
203
+ if (line[index] === '(') {
204
+ depth++;
205
+ continue;
206
+ }
207
+ if (line[index] !== ')' || depth-- > 0)
208
+ continue;
209
+ const path = localPathFromDestination(line.slice(destinationStart, index));
210
+ return path === undefined ? undefined : { label: line.slice(start + 1, labelEnd), path, end: index + 1 };
211
+ }
212
+ return undefined;
213
+ }
214
+ function closingDelimiter(text, start, delimiter) {
215
+ for (let index = start; index < text.length; index++) {
216
+ if (text[index] === '\\') {
217
+ index++;
218
+ continue;
219
+ }
220
+ if (text[index] === delimiter)
221
+ return index;
222
+ }
223
+ return undefined;
224
+ }
225
+ function localPathFromDestination(destination) {
226
+ const unescaped = destination.replace(/\\([!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-])/g, '$1');
227
+ let path = unescaped;
228
+ try {
229
+ path = decodeURIComponent(unescaped);
230
+ }
231
+ catch {
232
+ // A literal percent is legal in a path but not in URI decoding.
233
+ }
234
+ return path.startsWith('/') ? path : undefined;
130
235
  }
131
236
  /** Copy a pi message for terminal rendering and decorate only Markdown-bearing
132
237
  * text/thinking blocks. The broker's source event is never mutated. */
@@ -1,9 +1,18 @@
1
- /** URI of the OSC-8 hyperlink occupying the given 1-indexed terminal cell. */
2
- export declare function osc8UriAt(frame: readonly string[], row: number, column: number): string | undefined;
3
- export type LinkedFileOpenResult = 'opened' | 'missing' | 'invalid' | 'failed';
1
+ /** A local file requested through iTerm Semantic History. */
2
+ export interface FileOpenInput {
3
+ file: string | undefined;
4
+ remaining: string;
5
+ }
6
+ /**
7
+ * Strip the private iTerm Semantic History input and return its local path.
8
+ * iTerm expands `\\1` as a shell-escaped word, so decode that representation
9
+ * without ever evaluating it as shell input.
10
+ */
11
+ export declare function extractFileOpenInput(data: string): FileOpenInput | undefined;
12
+ export type LinkedFileOpenResult = 'opened' | 'missing' | 'failed';
4
13
  /** Replace this local viewer pane with Neovim, then reattach it when Neovim exits. */
5
14
  export declare function openLinkedFileInPane(opts: {
6
- uri: string;
15
+ file: string;
7
16
  pane: string;
8
17
  cwd: string;
9
18
  nodeId: string;
@@ -1,72 +1,68 @@
1
- // session/file-links.ts — OSC-8 file links in the rendered attach frame.
1
+ // session/file-links.ts — iTerm Semantic History file-open bridge.
2
2
  //
3
- // A click is resolved against the physical rows last handed to the terminal,
4
- // not against transcript records: wrapping, fixed cockpit rows, ANSI styling,
5
- // and OSC-8 boundaries have all already been composed there.
3
+ // iTerm sends the clicked path through a private input sequence. The viewer
4
+ // validates it, replaces its pane with Neovim, then reattaches after exit.
6
5
  import { existsSync } from 'node:fs';
7
- import { fileURLToPath } from 'node:url';
8
- import { walkRow } from '../ansi-cells.js';
9
6
  import { respawnPaneInBackground, shellQuote, viewerSplitEnv } from '../../../core/runtime/placement-tmux.js';
10
- /** URI of the OSC-8 hyperlink occupying the given 1-indexed terminal cell. */
11
- export function osc8UriAt(frame, row, column) {
12
- if (!Number.isInteger(row) || !Number.isInteger(column) || row < 1 || column < 1)
7
+ const FILE_OPEN_PREFIX = '\x1b_crtr-file-open;';
8
+ const STRING_TERMINATOR = '\x1b\\';
9
+ /**
10
+ * Strip the private iTerm Semantic History input and return its local path.
11
+ * iTerm expands `\\1` as a shell-escaped word, so decode that representation
12
+ * without ever evaluating it as shell input.
13
+ */
14
+ export function extractFileOpenInput(data) {
15
+ const start = data.indexOf(FILE_OPEN_PREFIX);
16
+ if (start === -1)
13
17
  return undefined;
14
- let activeUri;
15
- for (let index = 0; index < Math.min(row, frame.length); index++) {
16
- const hitColumn = index === row - 1 ? column : undefined;
17
- const scanned = scanOsc8Row(frame[index] ?? '', activeUri, hitColumn);
18
- activeUri = scanned.activeUri;
19
- if (scanned.uri !== undefined)
20
- return scanned.uri;
21
- }
22
- return undefined;
18
+ const pathStart = start + FILE_OPEN_PREFIX.length;
19
+ const end = data.indexOf(STRING_TERMINATOR, pathStart);
20
+ if (end === -1)
21
+ return undefined;
22
+ const file = decodeShellEscapedPath(data.slice(pathStart, end));
23
+ return { file, remaining: data.slice(0, start) + data.slice(end + STRING_TERMINATOR.length) };
23
24
  }
24
- function scanOsc8Row(line, initialUri, hitColumn) {
25
- let activeUri = initialUri;
26
- let uri;
27
- walkRow(line, {
28
- onEscape: (escape) => {
29
- const osc8 = parseOsc8(escape);
30
- if (osc8 !== null)
31
- activeUri = osc8 === '' ? undefined : osc8;
32
- },
33
- onGlyph: (glyph) => {
34
- if (uri !== undefined || hitColumn === undefined || activeUri === undefined || glyph.width === 0)
35
- return;
36
- const cell = hitColumn - 1;
37
- if (cell >= glyph.cell && cell < glyph.cell + glyph.width)
38
- uri = activeUri;
39
- },
40
- });
41
- return { activeUri, uri };
25
+ function decodeShellEscapedPath(value) {
26
+ const decoded = value.startsWith("'") ? decodeSingleQuotedPath(value) : decodeBackslashPath(value);
27
+ return decoded !== undefined && decoded.startsWith('/') && !/[\x00-\x1f\x7f]/.test(decoded) ? decoded : undefined;
42
28
  }
43
- /** `null` means another escape sequence; an empty URI closes the active link. */
44
- function parseOsc8(sequence) {
45
- if (!sequence.startsWith('\x1b]8;'))
46
- return null;
47
- const terminatorLength = sequence.endsWith('\x1b\\') ? 2 : 1;
48
- const body = sequence.slice(4, -terminatorLength);
49
- const separator = body.indexOf(';');
50
- if (separator === -1)
51
- return null;
52
- return body.slice(separator + 1);
29
+ function decodeSingleQuotedPath(value) {
30
+ let result = '';
31
+ let index = 1;
32
+ while (true) {
33
+ const end = value.indexOf("'", index);
34
+ if (end === -1)
35
+ return undefined;
36
+ result += value.slice(index, end);
37
+ index = end + 1;
38
+ if (index === value.length)
39
+ return result;
40
+ if (value.slice(index, index + 3) !== "\\''")
41
+ return undefined;
42
+ result += "'";
43
+ index += 3;
44
+ }
45
+ }
46
+ function decodeBackslashPath(value) {
47
+ let result = '';
48
+ for (let index = 0; index < value.length; index++) {
49
+ if (value[index] !== '\\') {
50
+ result += value[index];
51
+ continue;
52
+ }
53
+ const escaped = value[++index];
54
+ if (escaped === undefined)
55
+ return undefined;
56
+ result += escaped;
57
+ }
58
+ return result;
53
59
  }
54
60
  /** Replace this local viewer pane with Neovim, then reattach it when Neovim exits. */
55
61
  export function openLinkedFileInPane(opts) {
56
- let file;
57
- try {
58
- const url = new URL(opts.uri);
59
- if (url.protocol !== 'file:')
60
- return 'invalid';
61
- file = fileURLToPath(url);
62
- }
63
- catch {
64
- return 'invalid';
65
- }
66
- if (!existsSync(file))
62
+ if (!existsSync(opts.file))
67
63
  return 'missing';
68
64
  const attach = `crtr surface attach to ${shellQuote(opts.nodeId)}${opts.observer ? ' --observer' : ''}`;
69
- const command = `nvim ${shellQuote(file)} || :; exec ${attach}`;
65
+ const command = `nvim ${shellQuote(opts.file)} || :; exec ${attach}`;
70
66
  opts.beforeRespawn();
71
67
  return respawnPaneInBackground({ pane: opts.pane, cwd: opts.cwd, env: viewerSplitEnv(), command }) ? 'opened' : 'failed';
72
68
  }
@@ -31,7 +31,7 @@ export interface KeyHooks {
31
31
  toggleInboxStrip: () => void;
32
32
  cycleModelLadder: (direction: 'forward' | 'backward') => void;
33
33
  inspectLoadedCommand: () => void;
34
- /** The file-review chord — open the transcript-file picker + review surface. */
34
+ /** The file-review chord — pick a transcript file and open its review window. */
35
35
  openFileReview: () => void;
36
36
  /** Search the current node's local profile project files. */
37
37
  openProfileFiles: () => void;
@@ -50,7 +50,7 @@ function profileSearchRoots(profileId) {
50
50
  return { projects: [], memory: null };
51
51
  try {
52
52
  const { profileId: resolvedId, manifest } = loadProfileManifest(profileId);
53
- return { projects: manifest.projects, memory: profileMemoryDir(resolvedId) };
53
+ return { projects: manifest.projects.map((project) => project.path), memory: profileMemoryDir(resolvedId) };
54
54
  }
55
55
  catch {
56
56
  return { projects: [], memory: null };