@useorgx/openclaw-plugin 0.4.9 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +35 -0
  2. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  3. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  4. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  5. package/dashboard/dist/assets/BXWDRGm-.js +1 -0
  6. package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
  7. package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
  8. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  9. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  10. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  11. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  12. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  13. package/dashboard/dist/assets/CE38zU4U.js +1 -0
  14. package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
  15. package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
  16. package/dashboard/dist/assets/CFGKRAzG.js +1 -0
  17. package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
  18. package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
  19. package/dashboard/dist/assets/CGGR2GZh.js +1 -0
  20. package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
  21. package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
  22. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  23. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  24. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  25. package/dashboard/dist/assets/CPFiTmlw.js +8 -0
  26. package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
  27. package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
  28. package/dashboard/dist/assets/CZZTvkQZ.js +1 -0
  29. package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
  30. package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
  31. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  32. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  33. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  34. package/dashboard/dist/assets/D-bf6hEI.js +213 -0
  35. package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
  36. package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
  37. package/dashboard/dist/assets/DG6y9wJI.js +2 -0
  38. package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
  39. package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
  40. package/dashboard/dist/assets/DNxKz-GV.js +1 -0
  41. package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
  42. package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
  43. package/dashboard/dist/assets/DW_rKUic.js +11 -0
  44. package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
  45. package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
  46. package/dashboard/dist/assets/DbNoijHm.js +1 -0
  47. package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
  48. package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
  49. package/dashboard/dist/assets/DjcdE6jC.js +2 -0
  50. package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
  51. package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
  52. package/dashboard/dist/assets/FZYuCDnt.js +1 -0
  53. package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
  54. package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
  55. package/dashboard/dist/assets/PAUiij_z.js +1 -0
  56. package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
  57. package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
  58. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  59. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  60. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  61. package/dashboard/dist/assets/h5biQs2I.css +1 -0
  62. package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
  63. package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
  64. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  65. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  66. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  67. package/dashboard/dist/assets/nByHNHoW.js +1 -0
  68. package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
  69. package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
  70. package/dashboard/dist/assets/qm8xLgv-.css +1 -0
  71. package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
  72. package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
  73. package/dashboard/dist/assets/tS9mbYZi.js +1 -0
  74. package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
  75. package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
  76. package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
  77. package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
  78. package/dashboard/dist/brand/openai-mark.svg.br +0 -0
  79. package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
  80. package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
  81. package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
  82. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  83. package/dashboard/dist/index.html +7 -5
  84. package/dashboard/dist/index.html.br +0 -0
  85. package/dashboard/dist/index.html.gz +0 -0
  86. package/dist/activity-actor-fields.js +26 -4
  87. package/dist/activity-store.js +34 -8
  88. package/dist/agent-context-store.js +79 -17
  89. package/dist/agent-run-store.js +44 -3
  90. package/dist/agent-suite.d.ts +9 -0
  91. package/dist/agent-suite.js +149 -9
  92. package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
  93. package/dist/artifacts/artifact-domain-schemas.js +357 -0
  94. package/dist/artifacts/register-artifact.d.ts +4 -3
  95. package/dist/artifacts/register-artifact.js +170 -57
  96. package/dist/chat-store.d.ts +157 -0
  97. package/dist/chat-store.js +586 -0
  98. package/dist/cli/orgx.js +11 -0
  99. package/dist/contracts/client.d.ts +43 -3
  100. package/dist/contracts/client.js +159 -30
  101. package/dist/contracts/retro-schema.d.ts +81 -0
  102. package/dist/contracts/retro-schema.js +80 -0
  103. package/dist/contracts/shared-types.d.ts +159 -0
  104. package/dist/contracts/shared-types.js +177 -1
  105. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  106. package/dist/contracts/skill-pack-schema.js +180 -0
  107. package/dist/contracts/types.d.ts +227 -2
  108. package/dist/entities/auto-assignment.js +43 -17
  109. package/dist/event-sanitization.d.ts +11 -0
  110. package/dist/event-sanitization.js +113 -0
  111. package/dist/fs-utils.js +13 -1
  112. package/dist/gateway-watchdog.d.ts +5 -0
  113. package/dist/gateway-watchdog.js +50 -0
  114. package/dist/hooks/post-reporting-event.mjs +1 -5
  115. package/dist/http/helpers/activity-headline.js +13 -132
  116. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  117. package/dist/http/helpers/auto-continue-engine.js +2531 -186
  118. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  119. package/dist/http/helpers/autopilot-operations.js +182 -31
  120. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  121. package/dist/http/helpers/autopilot-runtime.js +308 -20
  122. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  123. package/dist/http/helpers/autopilot-slice-utils.js +516 -93
  124. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  125. package/dist/http/helpers/decision-mapper.js +223 -7
  126. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  127. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  128. package/dist/http/helpers/kickoff-context.js +74 -0
  129. package/dist/http/helpers/llm-client.d.ts +47 -0
  130. package/dist/http/helpers/llm-client.js +256 -0
  131. package/dist/http/helpers/mission-control.d.ts +102 -3
  132. package/dist/http/helpers/mission-control.js +498 -9
  133. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  134. package/dist/http/helpers/sentinel-catalog.js +193 -0
  135. package/dist/http/helpers/session-classification.d.ts +9 -0
  136. package/dist/http/helpers/session-classification.js +564 -0
  137. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  138. package/dist/http/helpers/slice-experience-v2.js +677 -0
  139. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  140. package/dist/http/helpers/slice-run-projections.js +860 -0
  141. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  142. package/dist/http/helpers/triage-mapper.js +549 -0
  143. package/dist/http/helpers/value-utils.js +7 -2
  144. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  145. package/dist/http/helpers/workspace-scope.js +170 -0
  146. package/dist/http/index.js +1354 -97
  147. package/dist/http/routes/agent-suite.d.ts +9 -0
  148. package/dist/http/routes/agent-suite.js +207 -8
  149. package/dist/http/routes/agents-catalog.js +64 -19
  150. package/dist/http/routes/chat.d.ts +19 -0
  151. package/dist/http/routes/chat.js +522 -0
  152. package/dist/http/routes/decision-actions.d.ts +8 -1
  153. package/dist/http/routes/decision-actions.js +42 -5
  154. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  155. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  156. package/dist/http/routes/entities.d.ts +16 -0
  157. package/dist/http/routes/entities.js +294 -6
  158. package/dist/http/routes/live-legacy.d.ts +5 -0
  159. package/dist/http/routes/live-legacy.js +23 -509
  160. package/dist/http/routes/live-misc.d.ts +12 -0
  161. package/dist/http/routes/live-misc.js +251 -31
  162. package/dist/http/routes/live-snapshot.d.ts +48 -2
  163. package/dist/http/routes/live-snapshot.js +638 -19
  164. package/dist/http/routes/live-terminal.d.ts +11 -0
  165. package/dist/http/routes/live-terminal.js +261 -0
  166. package/dist/http/routes/live-triage.d.ts +61 -0
  167. package/dist/http/routes/live-triage.js +248 -0
  168. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  169. package/dist/http/routes/mission-control-actions.js +1334 -84
  170. package/dist/http/routes/mission-control-read.d.ts +48 -3
  171. package/dist/http/routes/mission-control-read.js +1593 -20
  172. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  173. package/dist/http/routes/realtime-orchestrator.js +74 -0
  174. package/dist/http/routes/run-control.d.ts +5 -2
  175. package/dist/http/routes/run-control.js +10 -0
  176. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  177. package/dist/http/routes/sentinels-catalog.js +24 -0
  178. package/dist/http/routes/summary.js +10 -3
  179. package/dist/http/routes/usage.d.ts +24 -0
  180. package/dist/http/routes/usage.js +362 -0
  181. package/dist/http/routes/work-artifacts.js +28 -9
  182. package/dist/index.js +165 -27
  183. package/dist/local-openclaw.js +29 -6
  184. package/dist/mcp-client-setup.js +3 -3
  185. package/dist/mcp-http-handler.js +33 -59
  186. package/dist/next-up-queue-store.d.ts +16 -1
  187. package/dist/next-up-queue-store.js +89 -7
  188. package/dist/outbox.d.ts +5 -0
  189. package/dist/outbox.js +113 -9
  190. package/dist/paths.js +24 -5
  191. package/dist/reporting/rollups.d.ts +53 -0
  192. package/dist/reporting/rollups.js +148 -0
  193. package/dist/retro/domain-templates.d.ts +45 -0
  194. package/dist/retro/domain-templates.js +297 -0
  195. package/dist/retro/quality-rubric.d.ts +33 -0
  196. package/dist/retro/quality-rubric.js +213 -0
  197. package/dist/runtime-cleanup.d.ts +18 -0
  198. package/dist/runtime-cleanup.js +87 -0
  199. package/dist/services/background.d.ts +11 -0
  200. package/dist/services/background.js +22 -0
  201. package/dist/services/experiment-randomization.d.ts +21 -0
  202. package/dist/services/experiment-randomization.js +63 -0
  203. package/dist/skill-pack-state.d.ts +36 -5
  204. package/dist/skill-pack-state.js +273 -29
  205. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  206. package/dist/sync/local-agent-telemetry.js +128 -0
  207. package/dist/sync/outbox-replay.js +131 -24
  208. package/dist/team-context-store.d.ts +23 -0
  209. package/dist/team-context-store.js +116 -0
  210. package/dist/telemetry/posthog.js +4 -2
  211. package/dist/tools/core-tools.d.ts +10 -14
  212. package/dist/tools/core-tools.js +1289 -24
  213. package/dist/types.d.ts +2 -0
  214. package/dist/types.js +2 -0
  215. package/dist/worker-supervisor.js +23 -0
  216. package/package.json +14 -4
  217. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  218. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  219. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  220. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  221. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  222. package/dashboard/dist/assets/sAhvFnpk.js +0 -4
@@ -1,3 +1,38 @@
1
+ import { normalizeReportingBlockedSessions } from "../helpers/session-classification.js";
2
+ import { buildSliceRunProjections } from "../helpers/slice-run-projections.js";
3
+ import { buildSliceExperienceSnapshotV2, findSessionDetailProjection, findSliceNarrative, } from "../helpers/slice-experience-v2.js";
4
+ import { shouldHideActivityItem } from "../../event-sanitization.js";
5
+ import { KNOWN_DECISION_ACTION_TYPES, normalizeDecisionActionType, } from "../../contracts/shared-types.js";
6
+ import { resolveWorkspaceScope, workspaceScopeFromHeaders, } from "../helpers/workspace-scope.js";
7
+ const LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS = (() => {
8
+ const raw = Number(process.env.ORGX_LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS ?? "");
9
+ if (!Number.isFinite(raw))
10
+ return 3_500;
11
+ return Math.max(500, Math.min(60_000, Math.floor(raw)));
12
+ })();
13
+ const LIVE_SNAPSHOT_NEXT_UP_TIMEOUT_MS = (() => {
14
+ const raw = Number(process.env.ORGX_LIVE_SNAPSHOT_NEXT_UP_TIMEOUT_MS ?? "");
15
+ if (!Number.isFinite(raw))
16
+ return 1_200;
17
+ return Math.max(250, Math.min(15_000, Math.floor(raw)));
18
+ })();
19
+ async function withSoftTimeout(work, timeoutMs, label) {
20
+ let timer = null;
21
+ try {
22
+ return await Promise.race([
23
+ work,
24
+ new Promise((_, reject) => {
25
+ timer = setTimeout(() => {
26
+ reject(new Error(`${label} timed out after ${timeoutMs}ms`));
27
+ }, timeoutMs);
28
+ }),
29
+ ]);
30
+ }
31
+ finally {
32
+ if (timer)
33
+ clearTimeout(timer);
34
+ }
35
+ }
1
36
  function outboxStatusFromSummary(summary) {
2
37
  return {
3
38
  pendingTotal: summary.pendingTotal,
@@ -49,28 +84,205 @@ function maybeFilterActivity(items, input) {
49
84
  }
50
85
  return filtered;
51
86
  }
87
+ function filterSessionsByInitiativeSet(sessions, initiativeIds) {
88
+ if (initiativeIds.size === 0) {
89
+ return {
90
+ nodes: [],
91
+ edges: [],
92
+ groups: [],
93
+ };
94
+ }
95
+ const filteredNodes = sessions.nodes.filter((node) => {
96
+ const initiativeId = node.initiativeId?.trim() ?? "";
97
+ return initiativeId.length > 0 && initiativeIds.has(initiativeId);
98
+ });
99
+ const filteredNodeIds = new Set(filteredNodes.map((node) => node.id));
100
+ const filteredGroupIds = new Set(filteredNodes.map((node) => node.groupId));
101
+ return {
102
+ nodes: filteredNodes,
103
+ edges: sessions.edges.filter((edge) => filteredNodeIds.has(edge.parentId) && filteredNodeIds.has(edge.childId)),
104
+ groups: sessions.groups.filter((group) => filteredGroupIds.has(group.id)),
105
+ };
106
+ }
107
+ function asRecord(value) {
108
+ if (!value || typeof value !== "object" || Array.isArray(value))
109
+ return null;
110
+ return value;
111
+ }
112
+ function pickString(input, keys) {
113
+ if (!input)
114
+ return null;
115
+ for (const key of keys) {
116
+ const value = input[key];
117
+ if (typeof value !== "string")
118
+ continue;
119
+ const trimmed = value.trim();
120
+ if (trimmed.length > 0)
121
+ return trimmed;
122
+ }
123
+ return null;
124
+ }
125
+ function resolveActivitySliceRunId(item) {
126
+ if (item.runId && item.runId.trim().length > 0)
127
+ return item.runId.trim();
128
+ const metadata = asRecord(item.metadata);
129
+ return pickString(metadata, [
130
+ "slice_run_id",
131
+ "sliceRunId",
132
+ "active_run_id",
133
+ "activeRunId",
134
+ "run_id",
135
+ "runId",
136
+ "correlation_id",
137
+ "correlationId",
138
+ ]);
139
+ }
140
+ function resolveDecisionIdsForSlice(decisions, sliceRunId) {
141
+ const target = sliceRunId.trim();
142
+ if (!target)
143
+ return [];
144
+ const matched = [];
145
+ for (const decision of decisions) {
146
+ const decisionId = pickString(decision, ["id"]);
147
+ if (!decisionId)
148
+ continue;
149
+ const metadata = asRecord(decision.metadata);
150
+ const linkedSliceRunId = pickString(metadata, [
151
+ "slice_run_id",
152
+ "sliceRunId",
153
+ "run_id",
154
+ "runId",
155
+ "correlation_id",
156
+ "correlationId",
157
+ ]);
158
+ if (!linkedSliceRunId || linkedSliceRunId !== target)
159
+ continue;
160
+ const status = (pickString(decision, ["status"]) ?? "pending").toLowerCase();
161
+ if (status === "approved" || status === "declined" || status === "resolved" || status === "cancelled") {
162
+ continue;
163
+ }
164
+ if (matched.includes(decisionId))
165
+ continue;
166
+ matched.push(decisionId);
167
+ }
168
+ return matched;
169
+ }
170
+ function resolveInitiativeIdFromActivity(item) {
171
+ if (item.initiativeId && item.initiativeId.trim().length > 0) {
172
+ return item.initiativeId.trim();
173
+ }
174
+ const metadata = asRecord(item.metadata);
175
+ return pickString(metadata, ["initiative_id", "initiativeId"]);
176
+ }
177
+ function filterActivityByInitiativeSet(items, initiativeIds) {
178
+ if (initiativeIds.size === 0)
179
+ return [];
180
+ return items.filter((item) => {
181
+ const initiativeId = resolveInitiativeIdFromActivity(item);
182
+ return initiativeId ? initiativeIds.has(initiativeId) : false;
183
+ });
184
+ }
185
+ function resolveInitiativeIdFromRecord(entry) {
186
+ const direct = pickString(entry, ["initiative_id", "initiativeId", "initiative"]);
187
+ if (direct)
188
+ return direct;
189
+ const metadata = asRecord(entry.metadata);
190
+ return pickString(metadata, ["initiative_id", "initiativeId"]);
191
+ }
192
+ function filterRecordsByInitiativeSet(rows, initiativeIds) {
193
+ if (initiativeIds.size === 0)
194
+ return [];
195
+ return rows.filter((row) => {
196
+ const initiativeId = resolveInitiativeIdFromRecord(row);
197
+ return initiativeId ? initiativeIds.has(initiativeId) : false;
198
+ });
199
+ }
200
+ function filterRuntimeInstancesByInitiativeSet(rows, initiativeIds) {
201
+ if (initiativeIds.size === 0)
202
+ return [];
203
+ return rows.filter((row) => {
204
+ const initiativeId = row.initiativeId?.trim() ?? "";
205
+ return initiativeId.length > 0 && initiativeIds.has(initiativeId);
206
+ });
207
+ }
208
+ function filterHandoffsByInitiativeSet(rows, initiativeIds) {
209
+ if (initiativeIds.size === 0)
210
+ return [];
211
+ return rows.filter((entry) => {
212
+ const record = asRecord(entry);
213
+ const initiativeId = pickString(record, [
214
+ "initiative_id",
215
+ "initiativeId",
216
+ "initiative",
217
+ ]);
218
+ if (initiativeId && initiativeIds.has(initiativeId))
219
+ return true;
220
+ const metadata = asRecord(record?.metadata);
221
+ const metadataInitiativeId = pickString(metadata, ["initiative_id", "initiativeId"]);
222
+ return metadataInitiativeId ? initiativeIds.has(metadataInitiativeId) : false;
223
+ });
224
+ }
52
225
  export function registerLiveSnapshotRoutes(router, deps) {
53
- async function renderSnapshot(path, query, res) {
226
+ const snapshotAliasKey = (base, workspaceId) => {
227
+ const normalized = (workspaceId ?? "").trim();
228
+ if (!normalized)
229
+ return base;
230
+ return `${base}:${normalized}`;
231
+ };
232
+ const headerScopeFromRequest = (req) => workspaceScopeFromHeaders(req?.headers);
233
+ function parseSnapshotQuery(query, headerScope) {
54
234
  const sessionsLimit = deps.parsePositiveInt(query.get("sessionsLimit") ?? query.get("sessions_limit"), 320);
55
235
  const activityLimit = deps.parsePositiveInt(query.get("activityLimit") ?? query.get("activity_limit"), 600);
56
236
  const decisionsLimit = deps.parsePositiveInt(query.get("decisionsLimit") ?? query.get("decisions_limit"), 120);
57
237
  const initiative = query.get("initiative");
238
+ const scope = resolveWorkspaceScope(query, headerScope, {
239
+ allowProjectScope: false,
240
+ });
241
+ const projectId = scope.workspaceId;
58
242
  const run = query.get("run");
59
243
  const since = query.get("since");
60
244
  const decisionStatus = query.get("status") ?? "pending";
61
245
  const includeIdleRaw = query.get("include_idle");
62
246
  const includeIdle = includeIdleRaw === null ? undefined : includeIdleRaw !== "false";
63
- const snapshotCacheKey = `${path}?${query.toString()}`;
64
- const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
65
- if (cachedSnapshot) {
66
- deps.sendJson(res, 200, cachedSnapshot);
67
- return;
247
+ return {
248
+ sessionsLimit,
249
+ activityLimit,
250
+ decisionsLimit,
251
+ initiative,
252
+ projectId,
253
+ run,
254
+ since,
255
+ decisionStatus,
256
+ includeIdle,
257
+ scopeError: scope.error ?? null,
258
+ };
259
+ }
260
+ const validateWorkspaceScope = (query, res, location, headerScope) => {
261
+ const scope = resolveWorkspaceScope(query, headerScope, {
262
+ allowProjectScope: false,
263
+ });
264
+ if (!scope.error)
265
+ return true;
266
+ deps.sendJson(res, 400, {
267
+ error: scope.error,
268
+ error_location: location,
269
+ });
270
+ return false;
271
+ };
272
+ async function buildSnapshotBundle(query, headerScope) {
273
+ const parsed = parseSnapshotQuery(query, headerScope);
274
+ const { sessionsLimit, activityLimit, decisionsLimit, initiative, projectId, run, since, decisionStatus, includeIdle, scopeError, } = parsed;
275
+ if (scopeError) {
276
+ throw new Error(scopeError);
68
277
  }
69
278
  const degraded = [];
70
279
  const contextStore = deps.readAgentContexts();
71
280
  const agentContexts = contextStore.agents;
72
281
  const runContexts = contextStore.runs ?? {};
73
282
  const scopedAgentIds = deps.getScopedAgentIds(agentContexts);
283
+ const scopedProjectInitiativeIds = projectId && projectId.trim().length > 0
284
+ ? new Set(await deps.listInitiativeIdsForProject({ projectId: projectId.trim() }))
285
+ : null;
74
286
  let outboxStatus;
75
287
  try {
76
288
  const diagnosticsOutbox = await deps.readDiagnosticsOutboxStatus();
@@ -93,24 +305,28 @@ export function registerLiveSnapshotRoutes(router, deps) {
93
305
  return localSnapshot;
94
306
  };
95
307
  const settled = await Promise.allSettled([
96
- deps.getLiveSessions({
308
+ withSoftTimeout(deps.getLiveSessions({
97
309
  initiative,
310
+ projectId,
98
311
  limit: sessionsLimit,
99
- }),
100
- deps.getLiveActivity({
312
+ }), LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS, "live sessions"),
313
+ withSoftTimeout(deps.getLiveActivity({
101
314
  run,
102
315
  since,
316
+ projectId,
103
317
  limit: activityLimit,
104
- }),
105
- deps.getHandoffs(),
106
- deps.getLiveDecisions({
318
+ }), LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS, "live activity"),
319
+ withSoftTimeout(deps.getHandoffs(), LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS, "handoffs"),
320
+ withSoftTimeout(deps.getLiveDecisions({
107
321
  status: decisionStatus,
322
+ projectId,
108
323
  limit: decisionsLimit,
109
- }),
110
- deps.getLiveAgents({
324
+ }), LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS, "live decisions"),
325
+ withSoftTimeout(deps.getLiveAgents({
111
326
  initiative,
327
+ projectId,
112
328
  includeIdle,
113
- }),
329
+ }), LIVE_SNAPSHOT_UPSTREAM_TIMEOUT_MS, "live agents"),
114
330
  ]);
115
331
  let sessions = {
116
332
  nodes: [],
@@ -252,6 +468,14 @@ export function registerLiveSnapshotRoutes(router, deps) {
252
468
  if (run && run.trim().length > 0) {
253
469
  runtimeInstances = runtimeInstances.filter((instance) => instance.runId === run || instance.correlationId === run);
254
470
  }
471
+ if (scopedProjectInitiativeIds) {
472
+ sessions = filterSessionsByInitiativeSet(sessions, scopedProjectInitiativeIds);
473
+ activity = filterActivityByInitiativeSet(activity, scopedProjectInitiativeIds);
474
+ decisions = filterRecordsByInitiativeSet(decisions, scopedProjectInitiativeIds);
475
+ agents = filterRecordsByInitiativeSet(agents, scopedProjectInitiativeIds);
476
+ handoffs = filterHandoffsByInitiativeSet(handoffs, scopedProjectInitiativeIds);
477
+ runtimeInstances = filterRuntimeInstancesByInitiativeSet(runtimeInstances, scopedProjectInitiativeIds);
478
+ }
255
479
  sessions = deps.injectRuntimeInstancesAsSessions(sessions, runtimeInstances);
256
480
  sessions = deps.enrichSessionsWithRuntime(sessions, runtimeInstances);
257
481
  activity = deps.enrichActivityWithRuntime(activity, runtimeInstances);
@@ -259,6 +483,18 @@ export function registerLiveSnapshotRoutes(router, deps) {
259
483
  agents: agentContexts,
260
484
  runs: runContexts,
261
485
  });
486
+ activity = activity.filter((item) => !shouldHideActivityItem(item));
487
+ sessions = normalizeReportingBlockedSessions({
488
+ sessions,
489
+ activity,
490
+ runtimeInstances,
491
+ });
492
+ const sliceRuns = buildSliceRunProjections({
493
+ activity,
494
+ sessions: sessions.nodes,
495
+ decisions,
496
+ runtimeInstances,
497
+ });
262
498
  try {
263
499
  const fingerprint = deps.snapshotActivityFingerprint(activity);
264
500
  const now = Date.now();
@@ -281,17 +517,400 @@ export function registerLiveSnapshotRoutes(router, deps) {
281
517
  activity,
282
518
  handoffs,
283
519
  decisions,
520
+ sliceRuns,
284
521
  agents,
285
522
  runtimeInstances,
286
523
  outbox: outboxStatus,
287
524
  generatedAt: new Date().toISOString(),
525
+ projectId,
288
526
  degraded: degraded.length > 0 ? degraded : undefined,
289
527
  };
528
+ if (typeof deps.listChatThreads === "function") {
529
+ try {
530
+ const listed = deps.listChatThreads({
531
+ commandCenterId: projectId,
532
+ initiativeId: initiative,
533
+ limit: 120,
534
+ offset: 0,
535
+ });
536
+ payload.chat = {
537
+ threads: listed.threads,
538
+ total: listed.total,
539
+ updatedAt: listed.updatedAt,
540
+ };
541
+ }
542
+ catch (err) {
543
+ degraded.push(`chat unavailable (${deps.safeErrorMessage(err)})`);
544
+ }
545
+ }
546
+ let nextUpItems = [];
547
+ if (typeof deps.buildNextUpQueue === "function") {
548
+ try {
549
+ const nextUp = await withSoftTimeout(deps.buildNextUpQueue({ initiativeId: initiative, projectId }), LIVE_SNAPSHOT_NEXT_UP_TIMEOUT_MS, "next-up queue");
550
+ nextUpItems = Array.isArray(nextUp.items)
551
+ ? nextUp.items.filter((item) => Boolean(item))
552
+ : [];
553
+ for (const warning of nextUp.degraded ?? []) {
554
+ if (typeof warning !== "string" || warning.trim().length === 0)
555
+ continue;
556
+ degraded.push(`next-up ${warning}`);
557
+ }
558
+ }
559
+ catch (err) {
560
+ degraded.push(`next-up unavailable (${deps.safeErrorMessage(err)})`);
561
+ }
562
+ }
563
+ const v2 = buildSliceExperienceSnapshotV2({
564
+ generatedAt: payload.generatedAt,
565
+ sliceRuns: payload.sliceRuns,
566
+ sessions: payload.sessions.nodes,
567
+ activity: payload.activity,
568
+ runtimeInstances: payload.runtimeInstances,
569
+ nextUpItems,
570
+ });
571
+ return {
572
+ payload: {
573
+ ...payload,
574
+ degraded: degraded.length > 0 ? degraded : undefined,
575
+ },
576
+ v2,
577
+ decisionsRaw: decisions,
578
+ };
579
+ }
580
+ async function renderSnapshot(path, query, res, headerScope) {
581
+ if (!validateWorkspaceScope(query, res, "live.snapshot.validation", headerScope))
582
+ return;
583
+ const snapshotCacheKey = `${path}?${query.toString()}`;
584
+ const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
585
+ if (cachedSnapshot) {
586
+ deps.sendJson(res, 200, cachedSnapshot);
587
+ return;
588
+ }
589
+ const bundle = await buildSnapshotBundle(query, headerScope);
590
+ const parsed = parseSnapshotQuery(query, headerScope);
591
+ deps.writeSnapshotResponseCache(snapshotCacheKey, bundle.payload);
592
+ deps.writeSnapshotResponseCache("live-snapshot", bundle.payload);
593
+ deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot", parsed.projectId), bundle.payload);
594
+ deps.sendJson(res, 200, bundle.payload);
595
+ }
596
+ async function renderSnapshotV2(path, query, res, headerScope) {
597
+ if (!validateWorkspaceScope(query, res, "live.snapshot-v2.validation", headerScope))
598
+ return;
599
+ const snapshotCacheKey = `${path}?${query.toString()}`;
600
+ const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
601
+ if (cachedSnapshot) {
602
+ deps.sendJson(res, 200, cachedSnapshot);
603
+ return;
604
+ }
605
+ const bundle = await buildSnapshotBundle(query, headerScope);
606
+ const parsed = parseSnapshotQuery(query, headerScope);
607
+ const payload = {
608
+ ...bundle.v2,
609
+ sessions: bundle.payload.sessions,
610
+ activity: bundle.payload.activity,
611
+ handoffs: bundle.payload.handoffs,
612
+ decisions: bundle.payload.decisions,
613
+ sliceRuns: bundle.payload.sliceRuns,
614
+ agents: bundle.payload.agents,
615
+ runtimeInstances: bundle.payload.runtimeInstances,
616
+ outbox: bundle.payload.outbox,
617
+ chat: bundle.payload.chat,
618
+ degraded: bundle.payload.degraded,
619
+ };
290
620
  deps.writeSnapshotResponseCache(snapshotCacheKey, payload);
621
+ deps.writeSnapshotResponseCache("live-snapshot-v2", payload);
622
+ deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot-v2", parsed.projectId), payload);
291
623
  deps.sendJson(res, 200, payload);
292
624
  }
293
- router.add("GET", "dashboard-bundle", async ({ path, query, res }) => renderSnapshot(path, query, res), "Live dashboard bundle");
294
- router.add("HEAD", "dashboard-bundle", async ({ path, query, res }) => renderSnapshot(path, query, res), "Live dashboard bundle (HEAD)");
295
- router.add("GET", "live/snapshot", async ({ path, query, res }) => renderSnapshot(path, query, res), "Live snapshot");
296
- router.add("HEAD", "live/snapshot", async ({ path, query, res }) => renderSnapshot(path, query, res), "Live snapshot (HEAD)");
625
+ async function renderSliceNarrative(path, query, res, headerScope) {
626
+ if (!validateWorkspaceScope(query, res, "live.snapshot.slice-narrative.validation", headerScope))
627
+ return;
628
+ const narrativeMatch = path.match(/^slices\/([^/]+)\/narrative$/);
629
+ const timelineMatch = path.match(/^slices\/([^/]+)\/timeline$/);
630
+ const match = narrativeMatch ?? timelineMatch;
631
+ if (!match) {
632
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
633
+ return;
634
+ }
635
+ const sliceRunId = decodeURIComponent(match[1]).trim();
636
+ if (!sliceRunId) {
637
+ deps.sendJson(res, 400, { error: "sliceRunId is required." });
638
+ return;
639
+ }
640
+ const bundle = await buildSnapshotBundle(query, headerScope);
641
+ const narrative = findSliceNarrative(bundle.v2.timelineNarrative, sliceRunId);
642
+ const projection = bundle.v2.projections.find((entry) => entry.sliceRunId === sliceRunId) ?? null;
643
+ if (!narrative || !projection) {
644
+ deps.sendJson(res, 404, { error: "Slice narrative not found." });
645
+ return;
646
+ }
647
+ const chronology = bundle.payload.activity
648
+ .filter((item) => {
649
+ const activitySliceRunId = resolveActivitySliceRunId(item);
650
+ if (activitySliceRunId === sliceRunId)
651
+ return true;
652
+ if (projection.runId && item.runId === projection.runId)
653
+ return true;
654
+ return false;
655
+ })
656
+ .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
657
+ .map((item) => ({
658
+ id: item.id,
659
+ timestamp: item.timestamp,
660
+ type: item.type,
661
+ title: item.title,
662
+ summary: item.summary ?? item.description ?? null,
663
+ metadata: item.metadata ?? null,
664
+ }));
665
+ deps.sendJson(res, 200, {
666
+ ok: true,
667
+ sliceRunId,
668
+ narrative,
669
+ projection,
670
+ chronology,
671
+ });
672
+ }
673
+ async function renderSessionDetailV2(path, query, res, headerScope) {
674
+ if (!validateWorkspaceScope(query, res, "live.snapshot.session-detail.validation", headerScope))
675
+ return;
676
+ const detailMatch = path.match(/^sessions\/([^/]+)\/detail-v2$/);
677
+ if (!detailMatch) {
678
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
679
+ return;
680
+ }
681
+ const sessionId = decodeURIComponent(detailMatch[1]).trim();
682
+ if (!sessionId) {
683
+ deps.sendJson(res, 400, { error: "sessionId is required." });
684
+ return;
685
+ }
686
+ const bundle = await buildSnapshotBundle(query, headerScope);
687
+ const projection = findSessionDetailProjection(bundle.v2.projections, sessionId);
688
+ if (!projection) {
689
+ deps.sendJson(res, 404, { error: "Session detail not found." });
690
+ return;
691
+ }
692
+ const narrative = findSliceNarrative(bundle.v2.timelineNarrative, projection.sliceRunId);
693
+ const chronology = bundle.payload.activity
694
+ .filter((item) => {
695
+ const activitySliceRunId = resolveActivitySliceRunId(item);
696
+ if (activitySliceRunId === projection.sliceRunId)
697
+ return true;
698
+ if (projection.runId && item.runId === projection.runId)
699
+ return true;
700
+ if (item.runId && item.runId === sessionId)
701
+ return true;
702
+ return false;
703
+ })
704
+ .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
705
+ .map((item) => ({
706
+ id: item.id,
707
+ timestamp: item.timestamp,
708
+ type: item.type,
709
+ title: item.title,
710
+ summary: item.summary ?? item.description ?? null,
711
+ metadata: item.metadata ?? null,
712
+ }));
713
+ deps.sendJson(res, 200, {
714
+ ok: true,
715
+ sessionId,
716
+ activeSliceSummary: projection,
717
+ initiativeBreakdown: {
718
+ initiativeIds: projection.lineage.initiativeIds,
719
+ initiativeTitles: projection.lineage.initiativeTitles,
720
+ workstreamIds: projection.lineage.workstreamIds,
721
+ workstreamTitles: projection.lineage.workstreamTitles,
722
+ taskIds: projection.lineage.taskIds,
723
+ milestoneIds: projection.lineage.milestoneIds,
724
+ iwmtIds: projection.lineage.iwmtIds,
725
+ },
726
+ progressModel: {
727
+ lifecycleState: projection.lifecycleState,
728
+ outcomeState: projection.outcomeState,
729
+ confidence: projection.confidence,
730
+ statusExplainer: projection.statusExplainer,
731
+ },
732
+ artifactBundle: projection.artifacts,
733
+ decisionAction: projection.actionContract,
734
+ chronology,
735
+ narrative,
736
+ });
737
+ }
738
+ async function executeSliceAction(path, query, req, res, headerScope) {
739
+ if (!validateWorkspaceScope(query, res, "live.snapshot.slice-action.validation", headerScope))
740
+ return;
741
+ const actionMatch = path.match(/^slices\/([^/]+)\/actions\/([^/]+)$/);
742
+ if (!actionMatch) {
743
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
744
+ return;
745
+ }
746
+ const sliceRunId = decodeURIComponent(actionMatch[1]).trim();
747
+ const requestedAction = decodeURIComponent(actionMatch[2]).trim();
748
+ const actionType = normalizeDecisionActionType(requestedAction);
749
+ if (!sliceRunId) {
750
+ deps.sendJson(res, 400, { error: "sliceRunId is required." });
751
+ return;
752
+ }
753
+ if (!actionType) {
754
+ deps.sendJson(res, 400, {
755
+ error: "Action type is required.",
756
+ });
757
+ return;
758
+ }
759
+ const payload = typeof deps.parseJsonRequest === "function"
760
+ ? await deps.parseJsonRequest(req)
761
+ : {};
762
+ const note = pickString(payload, ["note", "context", "reason"]);
763
+ const optionId = pickString(payload, ["option_id", "optionId"]);
764
+ const bundle = await buildSnapshotBundle(query, headerScope);
765
+ const projection = bundle.v2.projections.find((entry) => entry.sliceRunId === sliceRunId) ?? null;
766
+ if (!projection) {
767
+ deps.sendJson(res, 404, { error: "Slice projection not found." });
768
+ return;
769
+ }
770
+ const declaredAction = normalizeDecisionActionType(projection.actionContract?.actionType ?? null);
771
+ if (declaredAction && declaredAction !== actionType && !(declaredAction === "open_artifact" && actionType === "open_artifact")) {
772
+ deps.sendJson(res, 409, {
773
+ error: "Action does not match current slice state.",
774
+ expectedAction: declaredAction,
775
+ requestedAction: actionType,
776
+ });
777
+ return;
778
+ }
779
+ if (actionType === "open_artifact") {
780
+ const artifact = projection.artifacts[0] ?? null;
781
+ if (!artifact) {
782
+ deps.sendJson(res, 409, { error: "No artifact is available for this slice." });
783
+ return;
784
+ }
785
+ deps.sendJson(res, 200, {
786
+ ok: true,
787
+ action: "open_artifact",
788
+ sliceRunId,
789
+ artifact,
790
+ });
791
+ return;
792
+ }
793
+ if (actionType === "retry" ||
794
+ actionType === "resume" ||
795
+ actionType === "start") {
796
+ if (typeof deps.runAction !== "function") {
797
+ deps.sendJson(res, 501, { error: "Run actions are not configured." });
798
+ return;
799
+ }
800
+ const runId = projection.runId ?? projection.sliceRunId;
801
+ const result = await deps.runAction(runId, "resume", {
802
+ reason: note ?? `slice_action:${actionType}`,
803
+ });
804
+ deps.sendJson(res, 200, {
805
+ ok: true,
806
+ action: actionType,
807
+ mappedAction: "resume",
808
+ sliceRunId,
809
+ runId,
810
+ result,
811
+ });
812
+ return;
813
+ }
814
+ if (actionType === "approve" || actionType === "reject") {
815
+ if (typeof deps.bulkDecideDecisions !== "function") {
816
+ deps.sendJson(res, 501, { error: "Decision actions are not configured." });
817
+ return;
818
+ }
819
+ const decisionAction = actionType === "approve" ? "approve" : "reject";
820
+ const decisionIdsFromBody = Array.isArray(payload.decisionIds)
821
+ ? payload.decisionIds
822
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
823
+ .filter(Boolean)
824
+ : [];
825
+ const singleDecisionId = pickString(payload, ["decisionId", "decision_id"]);
826
+ const decisionIds = Array.from(new Set([
827
+ ...decisionIdsFromBody,
828
+ ...(singleDecisionId ? [singleDecisionId] : []),
829
+ ...resolveDecisionIdsForSlice(bundle.decisionsRaw, sliceRunId),
830
+ ]));
831
+ if (decisionIds.length === 0) {
832
+ deps.sendJson(res, 400, {
833
+ error: "No matching pending decision was found for this slice.",
834
+ });
835
+ return;
836
+ }
837
+ const results = await deps.bulkDecideDecisions(decisionIds, decisionAction, {
838
+ note: note ?? undefined,
839
+ optionId: optionId ?? undefined,
840
+ });
841
+ const updated = results.filter((entry) => entry.ok === true).length;
842
+ const resolvedIds = results
843
+ .map((entry, index) => {
844
+ if (entry.ok !== true)
845
+ return null;
846
+ if (typeof entry.id === "string" && entry.id.trim().length > 0) {
847
+ return entry.id.trim();
848
+ }
849
+ return decisionIds[index] ?? null;
850
+ })
851
+ .filter((id) => typeof id === "string" && id.length > 0);
852
+ if (updated > 0 && typeof deps.emitDecisionResolvedActivity === "function") {
853
+ try {
854
+ await deps.emitDecisionResolvedActivity({
855
+ ids: resolvedIds.length > 0 ? resolvedIds : decisionIds,
856
+ action: decisionAction,
857
+ note: note ?? null,
858
+ optionId: optionId ?? null,
859
+ sliceRunId,
860
+ initiativeId: projection.lineage.initiativeIds[0] ?? null,
861
+ });
862
+ }
863
+ catch {
864
+ // best effort; mutation already completed
865
+ }
866
+ }
867
+ deps.sendJson(res, updated > 0 ? 200 : 207, {
868
+ ok: updated > 0,
869
+ action: decisionAction,
870
+ sliceRunId,
871
+ requested: decisionIds.length,
872
+ updated,
873
+ failed: decisionIds.length - updated,
874
+ results,
875
+ });
876
+ return;
877
+ }
878
+ if (actionType === "provide_context") {
879
+ deps.sendJson(res, 200, {
880
+ ok: true,
881
+ action: "provide_context",
882
+ sliceRunId,
883
+ note: note ?? null,
884
+ message: note
885
+ ? "Context captured for this slice."
886
+ : "No note submitted. Provide a note to attach additional context.",
887
+ });
888
+ return;
889
+ }
890
+ deps.sendJson(res, 400, {
891
+ error: "Unsupported action type.",
892
+ actionType,
893
+ supported: [
894
+ "approve",
895
+ "reject",
896
+ "retry",
897
+ "resume",
898
+ "start",
899
+ "open_artifact",
900
+ "provide_context",
901
+ ],
902
+ knownActionTypes: KNOWN_DECISION_ACTION_TYPES,
903
+ });
904
+ }
905
+ router.add("GET", "dashboard-bundle", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live dashboard bundle");
906
+ router.add("HEAD", "dashboard-bundle", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live dashboard bundle (HEAD)");
907
+ router.add("GET", "live/snapshot", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live snapshot");
908
+ router.add("HEAD", "live/snapshot", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live snapshot (HEAD)");
909
+ router.add("GET", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections)");
910
+ router.add("HEAD", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections, HEAD)");
911
+ router.add("GET", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections");
912
+ router.add("HEAD", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections (HEAD)");
913
+ router.add("POST", "slices/*", async ({ path, query, req, res }) => executeSliceAction(path, query, req, res, headerScopeFromRequest(req)), "Slice action contracts");
914
+ router.add("GET", "sessions/*", async ({ path, query, res, req }) => renderSessionDetailV2(path, query, res, headerScopeFromRequest(req)), "Session detail (v2 projections)");
915
+ router.add("HEAD", "sessions/*", async ({ path, query, res, req }) => renderSessionDetailV2(path, query, res, headerScopeFromRequest(req)), "Session detail (v2 projections, HEAD)");
297
916
  }