@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
@@ -0,0 +1,233 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { registrationChatSurface } from './command-surface.js';
3
+ const advertisedCommandInvocation = new AsyncLocalStorage();
4
+ const advertisedCustomMessage = new AsyncLocalStorage();
5
+ const invocationAwareSessions = new WeakSet();
6
+ const persistedRuntimeMessages = new WeakSet();
7
+ const endingPersistence = new WeakMap();
8
+ function persistenceDetails(target) {
9
+ return {
10
+ crouterPersistenceEnvelope: true,
11
+ crouterAdvertisedCommandInvocation: { raw: target.raw },
12
+ ...(target.details === undefined
13
+ ? {}
14
+ : { extensionDetails: target.details }),
15
+ };
16
+ }
17
+ function persistedDetails(value) {
18
+ if (typeof value !== 'object' || value === null)
19
+ return null;
20
+ const details = value;
21
+ return details.crouterPersistenceEnvelope === true &&
22
+ typeof details.crouterAdvertisedCommandInvocation?.raw === 'string'
23
+ ? details
24
+ : null;
25
+ }
26
+ function matchesTarget(target, message) {
27
+ const contentMatches = message.content === target.content ||
28
+ (target.content == null &&
29
+ Array.isArray(message.content) &&
30
+ message.content.length === 0);
31
+ return message.customType === target.customType &&
32
+ contentMatches &&
33
+ message.details === target.details &&
34
+ message.display === true;
35
+ }
36
+ function hydratePersistedMessage(message) {
37
+ if (typeof message !== 'object' || message === null)
38
+ return;
39
+ const record = message;
40
+ if (record['role'] !== 'custom')
41
+ return;
42
+ const persisted = persistedDetails(record['details']);
43
+ if (persisted === null)
44
+ return;
45
+ record['details'] = persisted.extensionDetails;
46
+ record['crouterAdvertisedCommandInvocation'] =
47
+ persisted.crouterAdvertisedCommandInvocation;
48
+ }
49
+ /** Restore extension-owned details while projecting persisted invocation
50
+ * metadata onto the runtime custom message. */
51
+ export function hydratePersistedAdvertisedCommandMessages(messages) {
52
+ for (const message of messages)
53
+ hydratePersistedMessage(message);
54
+ }
55
+ /** Add live invocation metadata without changing extension-owned content or
56
+ * details. EventProjection calls this before relaying a custom message. */
57
+ export function hydrateLiveAdvertisedCommandMessage(message) {
58
+ if (typeof message !== 'object' || message === null)
59
+ return;
60
+ const record = message;
61
+ if (record['role'] !== 'custom')
62
+ return;
63
+ if (typeof record['crouterAdvertisedCommandInvocation']?.raw === 'string') {
64
+ return;
65
+ }
66
+ const target = advertisedCustomMessage.getStore();
67
+ if (target === undefined ||
68
+ target.relayed ||
69
+ !matchesTarget(target, record)) {
70
+ return;
71
+ }
72
+ target.relayed = true;
73
+ record['crouterAdvertisedCommandInvocation'] = { raw: target.raw };
74
+ if (target.persisted)
75
+ persistedRuntimeMessages.add(record);
76
+ }
77
+ /** Bind the imminent message_end persistence call to the exact queued app
78
+ * message rather than to a payload-equality search across pending queues. */
79
+ export function prepareAdvertisedCommandPersistence(session, message) {
80
+ if (typeof message !== 'object' ||
81
+ message === null ||
82
+ persistedRuntimeMessages.has(message)) {
83
+ return;
84
+ }
85
+ const record = message;
86
+ if (record['role'] !== 'custom' || record['display'] !== true)
87
+ return;
88
+ const metadata = record['crouterAdvertisedCommandInvocation'];
89
+ if (typeof metadata?.raw !== 'string')
90
+ return;
91
+ endingPersistence.set(session.sessionManager, {
92
+ raw: metadata.raw,
93
+ customType: String(record['customType'] ?? ''),
94
+ content: record['content'],
95
+ details: record['details'],
96
+ persisted: false,
97
+ relayed: true,
98
+ });
99
+ }
100
+ /** Install persistence/reload plumbing once on a newly built broker session. */
101
+ export function installAdvertisedCommandInvocationContract(session) {
102
+ if (invocationAwareSessions.has(session))
103
+ return;
104
+ invocationAwareSessions.add(session);
105
+ const pendingPersistence = [];
106
+ const manager = session.sessionManager;
107
+ const appendCustomMessageEntry = manager.appendCustomMessageEntry.bind(manager);
108
+ manager.appendCustomMessageEntry = (customType, content, display, details) => {
109
+ const current = advertisedCustomMessage.getStore();
110
+ const ending = endingPersistence.get(manager);
111
+ const target = ending !== undefined &&
112
+ matchesTarget(ending, { customType, content, display, details })
113
+ ? ending
114
+ : current !== undefined &&
115
+ !current.persisted &&
116
+ matchesTarget(current, { customType, content, display, details })
117
+ ? current
118
+ : pendingPersistence.find((candidate) => !candidate.persisted &&
119
+ matchesTarget(candidate, { customType, content, display, details }));
120
+ if (target === undefined) {
121
+ return appendCustomMessageEntry(customType, content, display, details);
122
+ }
123
+ target.persisted = true;
124
+ if (target === ending) {
125
+ endingPersistence.delete(manager);
126
+ const queuedIndex = pendingPersistence.findIndex((candidate) => candidate.raw === target.raw &&
127
+ matchesTarget(candidate, { customType, content, display, details }));
128
+ if (queuedIndex !== -1)
129
+ pendingPersistence.splice(queuedIndex, 1);
130
+ }
131
+ else {
132
+ const pendingIndex = pendingPersistence.indexOf(target);
133
+ if (pendingIndex !== -1)
134
+ pendingPersistence.splice(pendingIndex, 1);
135
+ }
136
+ return appendCustomMessageEntry(customType, content, display, persistenceDetails(target));
137
+ };
138
+ const buildSessionContext = manager.buildSessionContext.bind(manager);
139
+ manager.buildSessionContext = () => {
140
+ const context = buildSessionContext();
141
+ hydratePersistedAdvertisedCommandMessages(context.messages);
142
+ return context;
143
+ };
144
+ hydratePersistedAdvertisedCommandMessages(session.messages);
145
+ // `.agent` is optional on a session — the fake-engine fixture has none, and
146
+ // every other consumer (rebind, frame-dispatch) guards the same way.
147
+ const agent = session.agent;
148
+ for (const method of agent === undefined ? [] : ['steer', 'followUp']) {
149
+ const original = agent?.[method];
150
+ if (typeof original !== 'function' || agent === undefined)
151
+ continue;
152
+ agent[method] = (message, ...args) => {
153
+ const target = advertisedCustomMessage.getStore();
154
+ if (target !== undefined &&
155
+ typeof message === 'object' &&
156
+ message !== null &&
157
+ matchesTarget(target, message)) {
158
+ hydrateLiveAdvertisedCommandMessage(message);
159
+ if (!target.persisted && !pendingPersistence.includes(target)) {
160
+ pendingPersistence.push(target);
161
+ }
162
+ }
163
+ return original.call(agent, message, ...args);
164
+ };
165
+ }
166
+ const sendCustomMessage = session.sendCustomMessage.bind(session);
167
+ session.sendCustomMessage = (message, options) => {
168
+ const invocation = advertisedCommandInvocation.getStore();
169
+ if (invocation === undefined ||
170
+ !invocation.active ||
171
+ invocation.claimed ||
172
+ message.display !== true) {
173
+ return sendCustomMessage(message, options);
174
+ }
175
+ invocation.claimed = true;
176
+ const target = {
177
+ raw: invocation.raw,
178
+ customType: message.customType,
179
+ content: message.content,
180
+ details: message.details,
181
+ persisted: false,
182
+ relayed: false,
183
+ };
184
+ return advertisedCustomMessage.run(target, () => {
185
+ const result = sendCustomMessage(message, options);
186
+ if (options?.deliverAs === 'nextTurn') {
187
+ const pending = session._pendingNextTurnMessages;
188
+ if (Array.isArray(pending)) {
189
+ for (let index = pending.length - 1; index >= 0; index -= 1) {
190
+ const queued = pending[index];
191
+ if (typeof queued !== 'object' ||
192
+ queued === null ||
193
+ !matchesTarget(target, queued)) {
194
+ continue;
195
+ }
196
+ hydrateLiveAdvertisedCommandMessage(queued);
197
+ pendingPersistence.push(target);
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ return result;
203
+ });
204
+ };
205
+ }
206
+ /** Preserve a chat-advertised extension command's exact submitted text on the
207
+ * first displayed custom message its handler persists. */
208
+ export function promptWithAdvertisedCommandInvocation(session, raw, runPrompt) {
209
+ if (!raw.startsWith('/'))
210
+ return runPrompt();
211
+ const spaceIndex = raw.indexOf(' ');
212
+ const commandName = spaceIndex === -1 ? raw.slice(1) : raw.slice(1, spaceIndex);
213
+ let command;
214
+ try {
215
+ command = session.extensionRunner?.getCommand(commandName);
216
+ }
217
+ catch {
218
+ return runPrompt();
219
+ }
220
+ if (!registrationChatSurface(command).gatewayCapable)
221
+ return runPrompt();
222
+ installAdvertisedCommandInvocationContract(session);
223
+ const invocation = { raw, claimed: false, active: true };
224
+ return advertisedCommandInvocation.run(invocation, async () => {
225
+ try {
226
+ return await runPrompt();
227
+ }
228
+ finally {
229
+ // Unawaited descendants are no longer part of the command outcome.
230
+ invocation.active = false;
231
+ }
232
+ });
233
+ }
@@ -164,7 +164,7 @@ export function profileNote(nodeId) {
164
164
  catch {
165
165
  return '';
166
166
  }
167
- const projects = entry.manifest.projects;
167
+ const projects = entry.manifest.projects.map((project) => project.path);
168
168
  if (projects.length === 0)
169
169
  return '';
170
170
  return (`Your working dir is \`${meta.cwd}\`. Your profile **${entry.manifest.name}** also has purview ` +
@@ -3,6 +3,7 @@ import { emitEvent } from '../../events/emit.js';
3
3
  import { releaseKnownStreamWait, resolveStreamWatchdogMs, StreamWatchdog } from '../stream-watchdog.js';
4
4
  import { withoutYieldAbort } from '../session-cycles.js';
5
5
  import { DEFAULT_WORKING_ACTIVITY, randomWorkingActivity } from '../../../shared/working-activity.js';
6
+ import { hydrateLiveAdvertisedCommandMessage, prepareAdvertisedCommandPersistence, } from '../advertised-command-invocation.js';
6
7
  const MESSAGE_UPDATE_COALESCE_MS = 75;
7
8
  /** Projects one live engine event stream to broker viewers and owns its relay timers. */
8
9
  export class EventProjection {
@@ -67,6 +68,12 @@ export class EventProjection {
67
68
  relayEvent(event, generation) {
68
69
  this.deps.faultRetry.onEvent(event);
69
70
  const type = event.type;
71
+ if (type === 'message_start') {
72
+ hydrateLiveAdvertisedCommandMessage(event.message);
73
+ }
74
+ else if (type === 'message_end') {
75
+ prepareAdvertisedCommandPersistence(this.deps.currentSession(), event.message);
76
+ }
70
77
  if (type === 'message_start' && event.message.role === 'user') {
71
78
  try {
72
79
  this.workingActivity = randomWorkingActivity(readConfig('user').working_gerunds);
@@ -1,5 +1,6 @@
1
1
  import type { AgentSessionServices, ModelRegistry, PromptOptions } from '@earendil-works/pi-coding-agent';
2
2
  import type { ImageContent } from '@earendil-works/pi-ai';
3
+ export { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
3
4
  import { type BrokerSdkConfig } from '../launch.js';
4
5
  import { type ClientToBroker } from '../broker-protocol.js';
5
6
  import type { BrokerClient, BrokerClientRegistry } from './client-registry.js';
@@ -237,4 +238,3 @@ export declare function createFrameDispatchContext(deps: FrameDispatchDeps): {
237
238
  };
238
239
  /** The socket listener calls this explicit dispatcher for every decoded client frame. */
239
240
  export declare function handleFrame(ctx: FrameDispatchContext, client: BrokerClient, frame: ClientToBroker): void;
240
- export {};
@@ -9,6 +9,9 @@ import { operationIdContext } from '../../events/operation-id.js';
9
9
  import { buildRefInventory } from '../../memory/inline-ref-inventory.js';
10
10
  import { buildGuidance } from '../../memory/inline-ref-guidance.js';
11
11
  import { clearSessionCache } from '../../substrate/session-cache.js';
12
+ import { captureSessionChatCapabilities } from '../command-surface.js';
13
+ import { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
14
+ export { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
12
15
  import { configuredLadderProviders, isProviderPinnedModelToken, nextLadderModel, normalizeModel, } from '../launch.js';
13
16
  import { clearFault, readFault } from '../fault.js';
14
17
  import { isManagedProvider, readSubscriptionPool, } from '../../subscription-state.js';
@@ -451,9 +454,7 @@ export function createFrameDispatchContext(deps) {
451
454
  // escalated by m-C — so it gets a fresh operation id. A true
452
455
  // (mid-stream) followUp rides the current turn's operation, like
453
456
  // steer.
454
- return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
455
- .prompt(frame.text, route.options)
456
- .catch(relayError))));
457
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options)).catch(relayError))));
457
458
  }
458
459
  // C1: pi's followUp() takes images as a POSITIONAL 2nd arg (`followUp(text, images?)`).
459
460
  return settle(currentSession()
@@ -470,10 +471,9 @@ export function createFrameDispatchContext(deps) {
470
471
  // ATOMICALLY clears its own pending entry if prompt() settles
471
472
  // without ever reaching that drain (an early throw/return leaves
472
473
  // nothing stranded to leak into a later, unrelated turn).
473
- return settle(trackPromptDispatch(promptWithNextTurnGuidance(currentSession(), guidance, () => operationIdContext.fresh(() => currentSession().prompt(frame.text, route.options))).catch(relayError)));
474
+ return settle(trackPromptDispatch(promptWithNextTurnGuidance(currentSession(), guidance, () => operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options)))).catch(relayError)));
474
475
  }
475
- return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
476
- .prompt(frame.text, route.options)
476
+ return settle(trackPromptDispatch(operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options))
477
477
  .catch(relayError)
478
478
  .then(() => sendGuidance(guidance, deliverAs)))));
479
479
  }
@@ -511,12 +511,10 @@ export function createFrameDispatchContext(deps) {
511
511
  // and executes it immediately even while streaming. It also expands a
512
512
  // matching skill/template before applying the same steer behavior.
513
513
  if (resolveSteerCall(frame.text, currentSession()) === 'prompt') {
514
- void currentSession()
515
- .prompt(frame.text, {
514
+ void promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, {
516
515
  images: frame.images,
517
516
  streamingBehavior: 'steer',
518
- })
519
- .catch(relayError);
517
+ })).catch(relayError);
520
518
  break;
521
519
  }
522
520
  const guidance = resolveFrameGuidance(frame.text);
@@ -1212,7 +1210,7 @@ export function createFrameDispatchContext(deps) {
1212
1210
  // cached docs. A FAILED reload (`.catch`) leaves both untouched: nothing
1213
1211
  // about the corpus is known to have changed.
1214
1212
  void currentSession()
1215
- .reload()
1213
+ .reload({ beforeSessionStart: () => captureSessionChatCapabilities(currentSession()) })
1216
1214
  .then(() => {
1217
1215
  // pi's reload() re-syncs queue modes from the shared settings.json
1218
1216
  // (its ONE post-bind syncQueueModesFromSettings call site) —
@@ -1225,6 +1223,10 @@ export function createFrameDispatchContext(deps) {
1225
1223
  }
1226
1224
  memoizedRefInventory = null;
1227
1225
  clearSessionCache();
1226
+ registry.forEachHelloed((observer) => {
1227
+ if (observer.role === 'observer')
1228
+ registry.sendFrame(observer, { type: 'resources_reloaded' });
1229
+ });
1228
1230
  ackTo(client, 'reload');
1229
1231
  })
1230
1232
  .catch(engineErrorTo(client));
@@ -9,6 +9,10 @@ export type CommandDisclosure = {
9
9
  source: string;
10
10
  template?: string;
11
11
  expansion?: DeterministicCommandExpansion;
12
+ /** Whether this command can complete its outcome from a non-terminal chat
13
+ * surface. Opt-in and fail-closed; builtins are never chat-capable. */
14
+ gatewayCapable: boolean;
15
+ argumentHint?: string;
12
16
  };
13
17
  export declare const modelKey: (model: {
14
18
  provider: string;
@@ -3,6 +3,7 @@ import { boundaryRootedTree } from '../session-visibility.js';
3
3
  import { isUnknownModel } from '../model-selection.js';
4
4
  import { BUILTIN_SLASH_COMMANDS, piSessionsRoot } from '../pi-vendored.js';
5
5
  import { isDeterministicCommandExpansion } from '../command-expansion.js';
6
+ import { registrationChatSurface, templateChatSurface } from '../command-surface.js';
6
7
  function registryOf(session) {
7
8
  return new ModelRegistry(session.modelRuntime);
8
9
  }
@@ -10,8 +11,10 @@ const toModelRef = (model) => model === undefined || isUnknownModel(model) ? nul
10
11
  export const modelKey = (model) => `${model.provider}/${model.id}`;
11
12
  export function buildCommandList(session) {
12
13
  const out = [];
14
+ // Builtins are pi's own terminal controls. They stay in the terminal
15
+ // disclosure and are never offered to a chat surface.
13
16
  for (const builtin of BUILTIN_SLASH_COMMANDS)
14
- out.push({ name: builtin.name, description: builtin.description, source: 'builtin' });
17
+ out.push({ name: builtin.name, description: builtin.description, source: 'builtin', gatewayCapable: false });
15
18
  try {
16
19
  for (const command of session.extensionRunner.getRegisteredCommands()) {
17
20
  const expansion = command.expansion;
@@ -20,6 +23,7 @@ export function buildCommandList(session) {
20
23
  description: command.description ?? '',
21
24
  source: 'command',
22
25
  ...(isDeterministicCommandExpansion(expansion) ? { expansion } : {}),
26
+ ...registrationChatSurface(command),
23
27
  });
24
28
  }
25
29
  }
@@ -28,7 +32,7 @@ export function buildCommandList(session) {
28
32
  }
29
33
  try {
30
34
  for (const template of session.promptTemplates)
31
- out.push({ name: template.name, description: template.description, source: 'template', template: template.content });
35
+ out.push({ name: template.name, description: template.description, source: 'template', template: template.content, ...templateChatSurface(template) });
32
36
  }
33
37
  catch {
34
38
  /* ignore */
@@ -73,7 +73,7 @@ function profileNote(node) {
73
73
  const entry = loadProfileManifest(node.profile_id);
74
74
  if (entry.manifest.projects.length === 0)
75
75
  return '';
76
- return `Your working dir is \`${node.cwd}\`. Your profile **${entry.manifest.name}** also has purview over: ${entry.manifest.projects.join(', ')}.`;
76
+ return `Your working dir is \`${node.cwd}\`. Your profile **${entry.manifest.name}** also has purview over: ${entry.manifest.projects.map((project) => project.path).join(', ')}.`;
77
77
  }
78
78
  catch {
79
79
  return '';
@@ -0,0 +1,4 @@
1
+ import type { ChatInventoryDTO } from '../../api/dto/chat-inventory.js';
2
+ /** Collect the chat inventories for one node. Always resolves — every failure
3
+ * mode is expressed in the returned document, not thrown. */
4
+ export declare function readChatInventory(nodeId: string): Promise<ChatInventoryDTO>;
@@ -0,0 +1,116 @@
1
+ // broker-inventory.ts — one short-lived OBSERVER round trip against a live
2
+ // node's broker that collects both chat-surface inventories at once: the
3
+ // command disclosure (`get_commands`) and the inline memory-reference
4
+ // inventory (`list_memory_refs`).
5
+ //
6
+ // It is modelled on `broker-request.ts`'s `oneShotControllerRequest` — the
7
+ // sanctioned host→live-broker dance — but is a different shape: two frames on
8
+ // one connection, settling on two independent answers, and never writable.
9
+ // Both read-ops are explicitly observer-safe, so this connection never claims
10
+ // controller and can never drive the engine.
11
+ //
12
+ // The two answers arrive by different routes because the frames differ.
13
+ // `get_commands` is uncorrelated and answers with an `ack`, which surfaces on
14
+ // the client's `frame` event. `list_memory_refs` answers with a correlated
15
+ // `data` frame, and `BrokerClient` swallows EVERY `data` frame into its
16
+ // pending-by-id resolver rather than re-emitting it — so that half must ride
17
+ // `client.request()`, which mints the id and owns the reply.
18
+ //
19
+ // A read NEVER revives. A dormant node, a refused connection, or an expired
20
+ // budget answers `broker_live:false` with empty arrays; one answer arriving
21
+ // without the other yields that part plus an empty peer. Nothing here is fatal,
22
+ // because a chat surface's behavior for "empty because it failed" and "empty
23
+ // because it is empty" is identical.
24
+ import { randomUUID } from 'node:crypto';
25
+ import { getNode } from '../canvas/canvas.js';
26
+ import { BrokerClient } from '../broker-client/index.js';
27
+ import { gatewayCommandRowsFromDetail, gatewayMemoryRows } from './chat-inventory-rows.js';
28
+ import { isBrokerLive } from './model-swap.js';
29
+ /** Same budget as `broker-request.ts`: this is local engine work, so it is
30
+ * generous slack rather than a tuned deadline. One budget covers BOTH answers —
31
+ * whichever part has not arrived when it expires is returned empty. */
32
+ const READ_TIMEOUT_MS = 15_000;
33
+ /** The transcript window this reader asks for in its welcome. Any hello triggers
34
+ * `sendWelcome`, and an untailed welcome makes the broker build and encode the
35
+ * whole conversation on its serialized frame loop for a payload this reader
36
+ * throws away — on every activation and reconnect, worst on the longest
37
+ * conversations. One entry is the smallest legal request. */
38
+ const SNAPSHOT_TAIL = 1;
39
+ const empty = (nodeId) => ({
40
+ node_id: nodeId,
41
+ broker_live: false,
42
+ commands: [],
43
+ memory_refs: [],
44
+ });
45
+ /** Collect the chat inventories for one node. Always resolves — every failure
46
+ * mode is expressed in the returned document, not thrown. */
47
+ export function readChatInventory(nodeId) {
48
+ const meta = getNode(nodeId);
49
+ if (meta === null || !isBrokerLive(meta))
50
+ return Promise.resolve(empty(nodeId));
51
+ return new Promise((resolve) => {
52
+ const client = new BrokerClient(nodeId);
53
+ const clientId = randomUUID();
54
+ let settled = false;
55
+ let connected = false;
56
+ let commands;
57
+ let memoryRefs;
58
+ const timer = setTimeout(() => finish(), READ_TIMEOUT_MS);
59
+ if (typeof timer.unref === 'function')
60
+ timer.unref();
61
+ /** Settle once with whatever arrived. A part that never answered stays
62
+ * empty; a connection that never established reports a dormant broker. */
63
+ function finish() {
64
+ if (settled)
65
+ return;
66
+ settled = true;
67
+ clearTimeout(timer);
68
+ try {
69
+ client.close();
70
+ }
71
+ catch {
72
+ /* best-effort */
73
+ }
74
+ resolve({
75
+ node_id: nodeId,
76
+ broker_live: connected,
77
+ commands: commands ?? [],
78
+ memory_refs: memoryRefs ?? [],
79
+ });
80
+ }
81
+ function settleWhenComplete() {
82
+ if (commands !== undefined && memoryRefs !== undefined)
83
+ finish();
84
+ }
85
+ client.on('error', () => finish());
86
+ client.on('close', () => finish());
87
+ client.on('connect', () => {
88
+ connected = true;
89
+ client.send({ type: 'hello', role: 'observer', client_id: clientId, snapshot_tail: SNAPSHOT_TAIL });
90
+ client.send({ type: 'get_commands' });
91
+ client.request({ type: 'list_memory_refs' }).then((frame) => {
92
+ memoryRefs = frame.kind === 'list_memory_refs' ? gatewayMemoryRows(frame.refs) : [];
93
+ settleWhenComplete();
94
+ }, () => {
95
+ memoryRefs = [];
96
+ settleWhenComplete();
97
+ });
98
+ });
99
+ client.on('frame', (frame) => {
100
+ // `get_commands` carries no id, so its ack is matched on `for` alone.
101
+ if (frame.type === 'ack' && frame.for === 'get_commands') {
102
+ commands = frame.ok ? gatewayCommandRowsFromDetail(frame.detail) : [];
103
+ settleWhenComplete();
104
+ return;
105
+ }
106
+ // The memory half's correlated error is consumed by its own request
107
+ // rejection, so an error reaching here is uncorrelated and can only be
108
+ // attributed to the uncorrelated half.
109
+ if (frame.type === 'error' && frame.id === undefined) {
110
+ commands ??= [];
111
+ settleWhenComplete();
112
+ }
113
+ });
114
+ client.connect();
115
+ });
116
+ }
@@ -44,7 +44,7 @@ export function transitionGuidanceFromBrokerState(node, from, to) {
44
44
  sections.push(to.mode === 'orchestrator' ? orchestrationGuidance(node) : baseModeGuidance());
45
45
  }
46
46
  if (from.lifecycle !== to.lifecycle) {
47
- sections.push(loadMemoryBody(to.lifecycle === 'resident' ? 'lifecycle/resident' : 'lifecycle/terminal'));
47
+ sections.push(loadMemoryBody(to.lifecycle === 'resident' ? 'turn-lifecycle/resident' : 'turn-lifecycle/terminal'));
48
48
  }
49
49
  return sections.join('\n\n');
50
50
  }
@@ -408,6 +408,11 @@ export interface ModelChangedFrame {
408
408
  * frames still parse. */
409
409
  spec?: string | null;
410
410
  }
411
+ /** Broadcast to attached observers after a successful reload has cleared the
412
+ * command and memory inventories. */
413
+ export interface ResourcesReloadedFrame {
414
+ type: 'resources_reloaded';
415
+ }
411
416
  /** The `error` control frame. SOURCED from the shared `@north-light/crouter-api`
412
417
  * contract (`src/api/dto/broker.ts`, the same zero-runtime-dep `/v1` module
413
418
  * Northlight Core imports) so the wire shape here and the shape Core consumes
@@ -508,12 +513,14 @@ export interface WireSettings extends SettingsConfig {
508
513
  * `'taste/foo'`) and what `crtr memory read <name>` expects; `scope` is the
509
514
  * WINNING scope after first-wins-by-name precedence dedup (display only —
510
515
  * resolution is by `name` alone). No body/source path: this is a metadata-only
511
- * inventory, never the document content. */
516
+ * inventory, never the document content. `gatewayVisible` is broker-computed
517
+ * chat-surface eligibility; terminal viewers retain every row. */
512
518
  export interface RefMeta {
513
519
  name: string;
514
520
  kind: 'knowledge' | 'preference';
515
521
  scope: 'node' | 'project' | 'profile' | 'user' | 'builtin';
516
522
  shortForm: string;
523
+ gatewayVisible: boolean;
517
524
  }
518
525
  export interface ListModelsData {
519
526
  type: 'data';
@@ -630,7 +637,7 @@ export interface ToolGroupSummaryFrame {
630
637
  /** Everything the broker can send. Live `AgentSessionEvent`s are relayed
631
638
  * verbatim (the broker adds nothing); the broker's own control frames carry
632
639
  * non-colliding `type` discriminants. */
633
- export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
640
+ export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ResourcesReloadedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
634
641
  /** Encode one frame as a single newline-terminated JSON line. */
635
642
  export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
636
643
  /** Byte bounds for a {@link FrameDecoder} (C5). */
@@ -31,6 +31,7 @@ import { FRONT_DOOR_ENV } from './front-door.js';
31
31
  import { CANVAS_EXTENSIONS, piInvocationToSdkConfig, } from './launch.js';
32
32
  import { assertEngineVersion, loadBrokerEngine } from './broker-sdk.js';
33
33
  import { resolveBundledPiPackageDir } from './pi-cli.js';
34
+ import { captureSessionChatCapabilities } from './command-surface.js';
34
35
  import { PoolCredentialStore } from '../pool-credential-store.js';
35
36
  import { clearCleanAbort, markBusy, markCleanAbort } from './busy.js';
36
37
  import { recordFaultForCurrentNode } from './fault.js';
@@ -45,7 +46,8 @@ import { ToolGroupTracker } from './broker/tool-groups.js';
45
46
  import { onNodeNamed } from './broker/node-named.js';
46
47
  import { FaultRetry } from './broker/fault-retry.js';
47
48
  import { EventProjection } from './broker/event-projection.js';
48
- import { createFrameDispatchContext, handleFrame } from './broker/frame-dispatch.js';
49
+ import { createFrameDispatchContext, handleFrame, } from './broker/frame-dispatch.js';
50
+ import { hydratePersistedAdvertisedCommandMessages, installAdvertisedCommandInvocationContract, } from './advertised-command-invocation.js';
49
51
  import { RebindDriver } from './broker/rebind.js';
50
52
  import { brokerExtensionState, commitBrokerModel } from './broker/daemon-ops.js';
51
53
  import { FrameOverflowError, normalizeSnapshotTail, } from './broker-protocol.js';
@@ -715,6 +717,9 @@ export function snapshotMessages(session, boundaryReviewId) {
715
717
  // `crtr-cycle` marker's `{cycle, fromLeaf}` shape — the fields we read off it
716
718
  // are ones ONLY this module's own `appendCustomEntry` call (above) ever writes.
717
719
  const messages = cycleAwareMessages(session.sessionManager);
720
+ if (messages !== undefined) {
721
+ hydratePersistedAdvertisedCommandMessages(messages.map(({ message }) => message));
722
+ }
718
723
  return messages === undefined ? undefined : visibleMessages(messages, { boundaryReviewId });
719
724
  }
720
725
  // ---------------------------------------------------------------------------
@@ -893,6 +898,10 @@ export async function buildBrokerSession(engine, cfg) {
893
898
  tools: cfg.tools,
894
899
  sessionStartEvent: o.sessionStartEvent,
895
900
  });
901
+ installAdvertisedCommandInvocationContract(created.session);
902
+ // The resource loader has completed its initial load, but extensions have
903
+ // not received session_start yet. Freeze the Gateway capability boundary.
904
+ captureSessionChatCapabilities(created.session);
896
905
  // CONSTRUCTION-ONLY MASK — see modelLessRegistry: reveal now that model
897
906
  // selection (construction-time only) is done, so a later /login re-drive's
898
907
  // list_models/cycle_model/provider-rotation see the real registry.
@@ -0,0 +1,8 @@
1
+ import type { ChatInventoryCommandDTO, ChatInventoryMemoryRefDTO } from '../../api/dto/chat-inventory.js';
2
+ /** Keep the command rows a Gateway can use. Malformed registrations are
3
+ * discarded individually so one producer cannot take down the inventory. */
4
+ export declare function gatewayCommandRows(rows: unknown): ChatInventoryCommandDTO[];
5
+ export declare function chatInventoryCommandRows(rows: unknown): ChatInventoryCommandDTO[];
6
+ export declare function gatewayCommandRowsFromDetail(detail: string | undefined): ChatInventoryCommandDTO[];
7
+ export declare function chatInventoryMemoryRows(rows: unknown): ChatInventoryMemoryRefDTO[];
8
+ export declare function gatewayMemoryRows(refs: unknown): ChatInventoryMemoryRefDTO[];