@useorgx/openclaw-plugin 0.4.9 → 0.7.2

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 (224) hide show
  1. package/README.md +77 -11
  2. package/dashboard/dist/assets/6mILZQ2a.js +1 -0
  3. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  4. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  5. package/dashboard/dist/assets/8dksYiq4.js +2 -0
  6. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  7. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  8. package/dashboard/dist/assets/B5zYRHc3.js +1 -0
  9. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  10. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  11. package/dashboard/dist/assets/B6wPWJ35.js +1 -0
  12. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  13. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  14. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  15. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  16. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  17. package/dashboard/dist/assets/BWEwjt1W.js +1 -0
  18. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  19. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  20. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  21. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  22. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  23. package/dashboard/dist/assets/BzRbDCAD.css +1 -0
  24. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  25. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  26. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  27. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  28. package/dashboard/dist/assets/C8uM3AX8.js +1 -0
  29. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  30. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  31. package/dashboard/dist/assets/C9jy61eu.js +212 -0
  32. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  33. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  34. package/dashboard/dist/assets/CC63EwFD.js +1 -0
  35. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  36. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  37. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  38. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  39. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  40. package/dashboard/dist/assets/CZaT3ob_.js +1 -0
  41. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  42. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  43. package/dashboard/dist/assets/CgaottFX.js +1 -0
  44. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  45. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  46. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  47. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  48. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  49. package/dashboard/dist/assets/CzCxAZlW.js +1 -0
  50. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  51. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  52. package/dashboard/dist/assets/D3iMTYEj.js +1 -0
  53. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  54. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  55. package/dashboard/dist/assets/D8JNX8kq.js +2 -0
  56. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  57. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  58. package/dashboard/dist/assets/DnA8dpj6.js +1 -0
  59. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  60. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  61. package/dashboard/dist/assets/IUexzymk.js +1 -0
  62. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  63. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  64. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  65. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  66. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  67. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  68. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  69. package/dashboard/dist/assets/ic2FaMnh.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/rttbDbEx.js +1 -0
  74. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  75. package/dashboard/dist/assets/rttbDbEx.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/practice-exercise-schema.d.ts +216 -0
  102. package/dist/contracts/practice-exercise-schema.js +314 -0
  103. package/dist/contracts/retro-schema.d.ts +81 -0
  104. package/dist/contracts/retro-schema.js +80 -0
  105. package/dist/contracts/shared-types.d.ts +159 -0
  106. package/dist/contracts/shared-types.js +199 -1
  107. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  108. package/dist/contracts/skill-pack-schema.js +180 -0
  109. package/dist/contracts/types.d.ts +247 -2
  110. package/dist/entities/auto-assignment.js +43 -17
  111. package/dist/event-sanitization.d.ts +11 -0
  112. package/dist/event-sanitization.js +113 -0
  113. package/dist/gateway-watchdog.d.ts +5 -0
  114. package/dist/gateway-watchdog.js +50 -0
  115. package/dist/hooks/post-reporting-event.mjs +1 -5
  116. package/dist/http/helpers/activity-headline.js +13 -132
  117. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  118. package/dist/http/helpers/auto-continue-engine.js +3145 -186
  119. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  120. package/dist/http/helpers/autopilot-operations.js +182 -31
  121. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  122. package/dist/http/helpers/autopilot-runtime.js +328 -25
  123. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  124. package/dist/http/helpers/autopilot-slice-utils.js +514 -93
  125. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  126. package/dist/http/helpers/decision-mapper.js +223 -7
  127. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  128. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  129. package/dist/http/helpers/kickoff-context.js +104 -0
  130. package/dist/http/helpers/llm-client.d.ts +47 -0
  131. package/dist/http/helpers/llm-client.js +256 -0
  132. package/dist/http/helpers/mission-control.d.ts +102 -3
  133. package/dist/http/helpers/mission-control.js +498 -9
  134. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  135. package/dist/http/helpers/sentinel-catalog.js +193 -0
  136. package/dist/http/helpers/session-classification.d.ts +9 -0
  137. package/dist/http/helpers/session-classification.js +564 -0
  138. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  139. package/dist/http/helpers/slice-experience-v2.js +677 -0
  140. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  141. package/dist/http/helpers/slice-run-projections.js +877 -0
  142. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  143. package/dist/http/helpers/triage-mapper.js +549 -0
  144. package/dist/http/helpers/value-utils.js +7 -2
  145. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  146. package/dist/http/helpers/workspace-scope.js +170 -0
  147. package/dist/http/index.js +1420 -105
  148. package/dist/http/routes/agent-suite.d.ts +9 -0
  149. package/dist/http/routes/agent-suite.js +294 -8
  150. package/dist/http/routes/agents-catalog.js +64 -19
  151. package/dist/http/routes/chat.d.ts +19 -0
  152. package/dist/http/routes/chat.js +522 -0
  153. package/dist/http/routes/decision-actions.d.ts +8 -1
  154. package/dist/http/routes/decision-actions.js +42 -5
  155. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  156. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  157. package/dist/http/routes/entities.d.ts +16 -0
  158. package/dist/http/routes/entities.js +232 -6
  159. package/dist/http/routes/live-legacy.d.ts +5 -0
  160. package/dist/http/routes/live-legacy.js +23 -509
  161. package/dist/http/routes/live-misc.d.ts +12 -0
  162. package/dist/http/routes/live-misc.js +251 -31
  163. package/dist/http/routes/live-snapshot.d.ts +49 -2
  164. package/dist/http/routes/live-snapshot.js +653 -23
  165. package/dist/http/routes/live-terminal.d.ts +11 -0
  166. package/dist/http/routes/live-terminal.js +154 -0
  167. package/dist/http/routes/live-triage.d.ts +61 -0
  168. package/dist/http/routes/live-triage.js +192 -0
  169. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  170. package/dist/http/routes/mission-control-actions.js +1246 -84
  171. package/dist/http/routes/mission-control-read.d.ts +48 -3
  172. package/dist/http/routes/mission-control-read.js +1658 -20
  173. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  174. package/dist/http/routes/realtime-orchestrator.js +74 -0
  175. package/dist/http/routes/run-control.d.ts +5 -2
  176. package/dist/http/routes/run-control.js +10 -0
  177. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  178. package/dist/http/routes/sentinels-catalog.js +24 -0
  179. package/dist/http/routes/summary.js +10 -3
  180. package/dist/http/routes/usage.d.ts +24 -0
  181. package/dist/http/routes/usage.js +362 -0
  182. package/dist/http/routes/work-artifacts.js +28 -9
  183. package/dist/index.js +165 -27
  184. package/dist/local-openclaw.js +29 -6
  185. package/dist/mcp-client-setup.js +3 -3
  186. package/dist/mcp-http-handler.d.ts +3 -0
  187. package/dist/mcp-http-handler.js +34 -60
  188. package/dist/next-up-queue-store.d.ts +16 -1
  189. package/dist/next-up-queue-store.js +89 -7
  190. package/dist/outbox.d.ts +5 -0
  191. package/dist/outbox.js +113 -9
  192. package/dist/paths.js +36 -5
  193. package/dist/reporting/rollups.d.ts +41 -0
  194. package/dist/reporting/rollups.js +113 -0
  195. package/dist/retro/domain-templates.d.ts +45 -0
  196. package/dist/retro/domain-templates.js +297 -0
  197. package/dist/retro/quality-rubric.d.ts +33 -0
  198. package/dist/retro/quality-rubric.js +213 -0
  199. package/dist/runtime-cleanup.d.ts +18 -0
  200. package/dist/runtime-cleanup.js +87 -0
  201. package/dist/services/background.d.ts +11 -0
  202. package/dist/services/background.js +22 -0
  203. package/dist/services/experiment-randomization.d.ts +21 -0
  204. package/dist/services/experiment-randomization.js +63 -0
  205. package/dist/skill-pack-state.d.ts +36 -5
  206. package/dist/skill-pack-state.js +273 -29
  207. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  208. package/dist/sync/local-agent-telemetry.js +128 -0
  209. package/dist/sync/outbox-replay.js +131 -24
  210. package/dist/team-context-store.d.ts +23 -0
  211. package/dist/team-context-store.js +116 -0
  212. package/dist/telemetry/posthog.js +4 -2
  213. package/dist/tools/core-tools.d.ts +10 -14
  214. package/dist/tools/core-tools.js +1289 -24
  215. package/dist/types.d.ts +2 -0
  216. package/dist/types.js +2 -0
  217. package/dist/worker-supervisor.js +23 -0
  218. package/package.json +20 -6
  219. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  220. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  221. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  222. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  223. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  224. 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: [],
@@ -229,14 +445,25 @@ export function registerLiveSnapshotRoutes(router, deps) {
229
445
  const buffered = await deps.readOutboxItems();
230
446
  if (buffered.length > 0) {
231
447
  const merged = [...activity, ...buffered]
232
- .sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp))
448
+ .sort((a, b) => {
449
+ const d = Date.parse(b.timestamp) - Date.parse(a.timestamp);
450
+ if (d !== 0)
451
+ return d;
452
+ return b.id.localeCompare(a.id);
453
+ })
233
454
  .slice(0, activityLimit);
234
455
  const deduped = [];
235
- const seen = new Set();
456
+ const seenIds = new Set();
457
+ const seenSemantic = new Set();
236
458
  for (const item of merged) {
237
- if (seen.has(item.id))
459
+ if (seenIds.has(item.id))
238
460
  continue;
239
- seen.add(item.id);
461
+ seenIds.add(item.id);
462
+ const sk = deps.semanticActivityKey(item);
463
+ if (sk && seenSemantic.has(sk))
464
+ continue;
465
+ if (sk)
466
+ seenSemantic.add(sk);
240
467
  deduped.push(item);
241
468
  }
242
469
  activity = deduped;
@@ -252,6 +479,14 @@ export function registerLiveSnapshotRoutes(router, deps) {
252
479
  if (run && run.trim().length > 0) {
253
480
  runtimeInstances = runtimeInstances.filter((instance) => instance.runId === run || instance.correlationId === run);
254
481
  }
482
+ if (scopedProjectInitiativeIds) {
483
+ sessions = filterSessionsByInitiativeSet(sessions, scopedProjectInitiativeIds);
484
+ activity = filterActivityByInitiativeSet(activity, scopedProjectInitiativeIds);
485
+ decisions = filterRecordsByInitiativeSet(decisions, scopedProjectInitiativeIds);
486
+ agents = filterRecordsByInitiativeSet(agents, scopedProjectInitiativeIds);
487
+ handoffs = filterHandoffsByInitiativeSet(handoffs, scopedProjectInitiativeIds);
488
+ runtimeInstances = filterRuntimeInstancesByInitiativeSet(runtimeInstances, scopedProjectInitiativeIds);
489
+ }
255
490
  sessions = deps.injectRuntimeInstancesAsSessions(sessions, runtimeInstances);
256
491
  sessions = deps.enrichSessionsWithRuntime(sessions, runtimeInstances);
257
492
  activity = deps.enrichActivityWithRuntime(activity, runtimeInstances);
@@ -259,6 +494,18 @@ export function registerLiveSnapshotRoutes(router, deps) {
259
494
  agents: agentContexts,
260
495
  runs: runContexts,
261
496
  });
497
+ activity = activity.filter((item) => !shouldHideActivityItem(item));
498
+ sessions = normalizeReportingBlockedSessions({
499
+ sessions,
500
+ activity,
501
+ runtimeInstances,
502
+ });
503
+ const sliceRuns = buildSliceRunProjections({
504
+ activity,
505
+ sessions: sessions.nodes,
506
+ decisions,
507
+ runtimeInstances,
508
+ });
262
509
  try {
263
510
  const fingerprint = deps.snapshotActivityFingerprint(activity);
264
511
  const now = Date.now();
@@ -281,17 +528,400 @@ export function registerLiveSnapshotRoutes(router, deps) {
281
528
  activity,
282
529
  handoffs,
283
530
  decisions,
531
+ sliceRuns,
284
532
  agents,
285
533
  runtimeInstances,
286
534
  outbox: outboxStatus,
287
535
  generatedAt: new Date().toISOString(),
536
+ projectId,
288
537
  degraded: degraded.length > 0 ? degraded : undefined,
289
538
  };
539
+ if (typeof deps.listChatThreads === "function") {
540
+ try {
541
+ const listed = deps.listChatThreads({
542
+ commandCenterId: projectId,
543
+ initiativeId: initiative,
544
+ limit: 120,
545
+ offset: 0,
546
+ });
547
+ payload.chat = {
548
+ threads: listed.threads,
549
+ total: listed.total,
550
+ updatedAt: listed.updatedAt,
551
+ };
552
+ }
553
+ catch (err) {
554
+ degraded.push(`chat unavailable (${deps.safeErrorMessage(err)})`);
555
+ }
556
+ }
557
+ let nextUpItems = [];
558
+ if (typeof deps.buildNextUpQueue === "function") {
559
+ try {
560
+ const nextUp = await withSoftTimeout(deps.buildNextUpQueue({ initiativeId: initiative, projectId }), LIVE_SNAPSHOT_NEXT_UP_TIMEOUT_MS, "next-up queue");
561
+ nextUpItems = Array.isArray(nextUp.items)
562
+ ? nextUp.items.filter((item) => Boolean(item))
563
+ : [];
564
+ for (const warning of nextUp.degraded ?? []) {
565
+ if (typeof warning !== "string" || warning.trim().length === 0)
566
+ continue;
567
+ degraded.push(`next-up ${warning}`);
568
+ }
569
+ }
570
+ catch (err) {
571
+ degraded.push(`next-up unavailable (${deps.safeErrorMessage(err)})`);
572
+ }
573
+ }
574
+ const v2 = buildSliceExperienceSnapshotV2({
575
+ generatedAt: payload.generatedAt,
576
+ sliceRuns: payload.sliceRuns,
577
+ sessions: payload.sessions.nodes,
578
+ activity: payload.activity,
579
+ runtimeInstances: payload.runtimeInstances,
580
+ nextUpItems,
581
+ });
582
+ return {
583
+ payload: {
584
+ ...payload,
585
+ degraded: degraded.length > 0 ? degraded : undefined,
586
+ },
587
+ v2,
588
+ decisionsRaw: decisions,
589
+ };
590
+ }
591
+ async function renderSnapshot(path, query, res, headerScope) {
592
+ if (!validateWorkspaceScope(query, res, "live.snapshot.validation", headerScope))
593
+ return;
594
+ const snapshotCacheKey = `${path}?${query.toString()}`;
595
+ const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
596
+ if (cachedSnapshot) {
597
+ deps.sendJson(res, 200, cachedSnapshot);
598
+ return;
599
+ }
600
+ const bundle = await buildSnapshotBundle(query, headerScope);
601
+ const parsed = parseSnapshotQuery(query, headerScope);
602
+ deps.writeSnapshotResponseCache(snapshotCacheKey, bundle.payload);
603
+ deps.writeSnapshotResponseCache("live-snapshot", bundle.payload);
604
+ deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot", parsed.projectId), bundle.payload);
605
+ deps.sendJson(res, 200, bundle.payload);
606
+ }
607
+ async function renderSnapshotV2(path, query, res, headerScope) {
608
+ if (!validateWorkspaceScope(query, res, "live.snapshot-v2.validation", headerScope))
609
+ return;
610
+ const snapshotCacheKey = `${path}?${query.toString()}`;
611
+ const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
612
+ if (cachedSnapshot) {
613
+ deps.sendJson(res, 200, cachedSnapshot);
614
+ return;
615
+ }
616
+ const bundle = await buildSnapshotBundle(query, headerScope);
617
+ const parsed = parseSnapshotQuery(query, headerScope);
618
+ const payload = {
619
+ ...bundle.v2,
620
+ sessions: bundle.payload.sessions,
621
+ activity: bundle.payload.activity,
622
+ handoffs: bundle.payload.handoffs,
623
+ decisions: bundle.payload.decisions,
624
+ sliceRuns: bundle.payload.sliceRuns,
625
+ agents: bundle.payload.agents,
626
+ runtimeInstances: bundle.payload.runtimeInstances,
627
+ outbox: bundle.payload.outbox,
628
+ chat: bundle.payload.chat,
629
+ degraded: bundle.payload.degraded,
630
+ };
290
631
  deps.writeSnapshotResponseCache(snapshotCacheKey, payload);
632
+ deps.writeSnapshotResponseCache("live-snapshot-v2", payload);
633
+ deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot-v2", parsed.projectId), payload);
291
634
  deps.sendJson(res, 200, payload);
292
635
  }
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)");
636
+ async function renderSliceNarrative(path, query, res, headerScope) {
637
+ if (!validateWorkspaceScope(query, res, "live.snapshot.slice-narrative.validation", headerScope))
638
+ return;
639
+ const narrativeMatch = path.match(/^slices\/([^/]+)\/narrative$/);
640
+ const timelineMatch = path.match(/^slices\/([^/]+)\/timeline$/);
641
+ const match = narrativeMatch ?? timelineMatch;
642
+ if (!match) {
643
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
644
+ return;
645
+ }
646
+ const sliceRunId = decodeURIComponent(match[1]).trim();
647
+ if (!sliceRunId) {
648
+ deps.sendJson(res, 400, { error: "sliceRunId is required." });
649
+ return;
650
+ }
651
+ const bundle = await buildSnapshotBundle(query, headerScope);
652
+ const narrative = findSliceNarrative(bundle.v2.timelineNarrative, sliceRunId);
653
+ const projection = bundle.v2.projections.find((entry) => entry.sliceRunId === sliceRunId) ?? null;
654
+ if (!narrative || !projection) {
655
+ deps.sendJson(res, 404, { error: "Slice narrative not found." });
656
+ return;
657
+ }
658
+ const chronology = bundle.payload.activity
659
+ .filter((item) => {
660
+ const activitySliceRunId = resolveActivitySliceRunId(item);
661
+ if (activitySliceRunId === sliceRunId)
662
+ return true;
663
+ if (projection.runId && item.runId === projection.runId)
664
+ return true;
665
+ return false;
666
+ })
667
+ .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
668
+ .map((item) => ({
669
+ id: item.id,
670
+ timestamp: item.timestamp,
671
+ type: item.type,
672
+ title: item.title,
673
+ summary: item.summary ?? item.description ?? null,
674
+ metadata: item.metadata ?? null,
675
+ }));
676
+ deps.sendJson(res, 200, {
677
+ ok: true,
678
+ sliceRunId,
679
+ narrative,
680
+ projection,
681
+ chronology,
682
+ });
683
+ }
684
+ async function renderSessionDetailV2(path, query, res, headerScope) {
685
+ if (!validateWorkspaceScope(query, res, "live.snapshot.session-detail.validation", headerScope))
686
+ return;
687
+ const detailMatch = path.match(/^sessions\/([^/]+)\/detail-v2$/);
688
+ if (!detailMatch) {
689
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
690
+ return;
691
+ }
692
+ const sessionId = decodeURIComponent(detailMatch[1]).trim();
693
+ if (!sessionId) {
694
+ deps.sendJson(res, 400, { error: "sessionId is required." });
695
+ return;
696
+ }
697
+ const bundle = await buildSnapshotBundle(query, headerScope);
698
+ const projection = findSessionDetailProjection(bundle.v2.projections, sessionId);
699
+ if (!projection) {
700
+ deps.sendJson(res, 404, { error: "Session detail not found." });
701
+ return;
702
+ }
703
+ const narrative = findSliceNarrative(bundle.v2.timelineNarrative, projection.sliceRunId);
704
+ const chronology = bundle.payload.activity
705
+ .filter((item) => {
706
+ const activitySliceRunId = resolveActivitySliceRunId(item);
707
+ if (activitySliceRunId === projection.sliceRunId)
708
+ return true;
709
+ if (projection.runId && item.runId === projection.runId)
710
+ return true;
711
+ if (item.runId && item.runId === sessionId)
712
+ return true;
713
+ return false;
714
+ })
715
+ .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
716
+ .map((item) => ({
717
+ id: item.id,
718
+ timestamp: item.timestamp,
719
+ type: item.type,
720
+ title: item.title,
721
+ summary: item.summary ?? item.description ?? null,
722
+ metadata: item.metadata ?? null,
723
+ }));
724
+ deps.sendJson(res, 200, {
725
+ ok: true,
726
+ sessionId,
727
+ activeSliceSummary: projection,
728
+ initiativeBreakdown: {
729
+ initiativeIds: projection.lineage.initiativeIds,
730
+ initiativeTitles: projection.lineage.initiativeTitles,
731
+ workstreamIds: projection.lineage.workstreamIds,
732
+ workstreamTitles: projection.lineage.workstreamTitles,
733
+ taskIds: projection.lineage.taskIds,
734
+ milestoneIds: projection.lineage.milestoneIds,
735
+ iwmtIds: projection.lineage.iwmtIds,
736
+ },
737
+ progressModel: {
738
+ lifecycleState: projection.lifecycleState,
739
+ outcomeState: projection.outcomeState,
740
+ confidence: projection.confidence,
741
+ statusExplainer: projection.statusExplainer,
742
+ },
743
+ artifactBundle: projection.artifacts,
744
+ decisionAction: projection.actionContract,
745
+ chronology,
746
+ narrative,
747
+ });
748
+ }
749
+ async function executeSliceAction(path, query, req, res, headerScope) {
750
+ if (!validateWorkspaceScope(query, res, "live.snapshot.slice-action.validation", headerScope))
751
+ return;
752
+ const actionMatch = path.match(/^slices\/([^/]+)\/actions\/([^/]+)$/);
753
+ if (!actionMatch) {
754
+ deps.sendJson(res, 404, { error: "Unknown API endpoint" });
755
+ return;
756
+ }
757
+ const sliceRunId = decodeURIComponent(actionMatch[1]).trim();
758
+ const requestedAction = decodeURIComponent(actionMatch[2]).trim();
759
+ const actionType = normalizeDecisionActionType(requestedAction);
760
+ if (!sliceRunId) {
761
+ deps.sendJson(res, 400, { error: "sliceRunId is required." });
762
+ return;
763
+ }
764
+ if (!actionType) {
765
+ deps.sendJson(res, 400, {
766
+ error: "Action type is required.",
767
+ });
768
+ return;
769
+ }
770
+ const payload = typeof deps.parseJsonRequest === "function"
771
+ ? await deps.parseJsonRequest(req)
772
+ : {};
773
+ const note = pickString(payload, ["note", "context", "reason"]);
774
+ const optionId = pickString(payload, ["option_id", "optionId"]);
775
+ const bundle = await buildSnapshotBundle(query, headerScope);
776
+ const projection = bundle.v2.projections.find((entry) => entry.sliceRunId === sliceRunId) ?? null;
777
+ if (!projection) {
778
+ deps.sendJson(res, 404, { error: "Slice projection not found." });
779
+ return;
780
+ }
781
+ const declaredAction = normalizeDecisionActionType(projection.actionContract?.actionType ?? null);
782
+ if (declaredAction && declaredAction !== actionType && !(declaredAction === "open_artifact" && actionType === "open_artifact")) {
783
+ deps.sendJson(res, 409, {
784
+ error: "Action does not match current slice state.",
785
+ expectedAction: declaredAction,
786
+ requestedAction: actionType,
787
+ });
788
+ return;
789
+ }
790
+ if (actionType === "open_artifact") {
791
+ const artifact = projection.artifacts[0] ?? null;
792
+ if (!artifact) {
793
+ deps.sendJson(res, 409, { error: "No artifact is available for this slice." });
794
+ return;
795
+ }
796
+ deps.sendJson(res, 200, {
797
+ ok: true,
798
+ action: "open_artifact",
799
+ sliceRunId,
800
+ artifact,
801
+ });
802
+ return;
803
+ }
804
+ if (actionType === "retry" ||
805
+ actionType === "resume" ||
806
+ actionType === "start") {
807
+ if (typeof deps.runAction !== "function") {
808
+ deps.sendJson(res, 501, { error: "Run actions are not configured." });
809
+ return;
810
+ }
811
+ const runId = projection.runId ?? projection.sliceRunId;
812
+ const result = await deps.runAction(runId, "resume", {
813
+ reason: note ?? `slice_action:${actionType}`,
814
+ });
815
+ deps.sendJson(res, 200, {
816
+ ok: true,
817
+ action: actionType,
818
+ mappedAction: "resume",
819
+ sliceRunId,
820
+ runId,
821
+ result,
822
+ });
823
+ return;
824
+ }
825
+ if (actionType === "approve" || actionType === "reject") {
826
+ if (typeof deps.bulkDecideDecisions !== "function") {
827
+ deps.sendJson(res, 501, { error: "Decision actions are not configured." });
828
+ return;
829
+ }
830
+ const decisionAction = actionType === "approve" ? "approve" : "reject";
831
+ const decisionIdsFromBody = Array.isArray(payload.decisionIds)
832
+ ? payload.decisionIds
833
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
834
+ .filter(Boolean)
835
+ : [];
836
+ const singleDecisionId = pickString(payload, ["decisionId", "decision_id"]);
837
+ const decisionIds = Array.from(new Set([
838
+ ...decisionIdsFromBody,
839
+ ...(singleDecisionId ? [singleDecisionId] : []),
840
+ ...resolveDecisionIdsForSlice(bundle.decisionsRaw, sliceRunId),
841
+ ]));
842
+ if (decisionIds.length === 0) {
843
+ deps.sendJson(res, 400, {
844
+ error: "No matching pending decision was found for this slice.",
845
+ });
846
+ return;
847
+ }
848
+ const results = await deps.bulkDecideDecisions(decisionIds, decisionAction, {
849
+ note: note ?? undefined,
850
+ optionId: optionId ?? undefined,
851
+ });
852
+ const updated = results.filter((entry) => entry.ok === true).length;
853
+ const resolvedIds = results
854
+ .map((entry, index) => {
855
+ if (entry.ok !== true)
856
+ return null;
857
+ if (typeof entry.id === "string" && entry.id.trim().length > 0) {
858
+ return entry.id.trim();
859
+ }
860
+ return decisionIds[index] ?? null;
861
+ })
862
+ .filter((id) => typeof id === "string" && id.length > 0);
863
+ if (updated > 0 && typeof deps.emitDecisionResolvedActivity === "function") {
864
+ try {
865
+ await deps.emitDecisionResolvedActivity({
866
+ ids: resolvedIds.length > 0 ? resolvedIds : decisionIds,
867
+ action: decisionAction,
868
+ note: note ?? null,
869
+ optionId: optionId ?? null,
870
+ sliceRunId,
871
+ initiativeId: projection.lineage.initiativeIds[0] ?? null,
872
+ });
873
+ }
874
+ catch {
875
+ // best effort; mutation already completed
876
+ }
877
+ }
878
+ deps.sendJson(res, updated > 0 ? 200 : 207, {
879
+ ok: updated > 0,
880
+ action: decisionAction,
881
+ sliceRunId,
882
+ requested: decisionIds.length,
883
+ updated,
884
+ failed: decisionIds.length - updated,
885
+ results,
886
+ });
887
+ return;
888
+ }
889
+ if (actionType === "provide_context") {
890
+ deps.sendJson(res, 200, {
891
+ ok: true,
892
+ action: "provide_context",
893
+ sliceRunId,
894
+ note: note ?? null,
895
+ message: note
896
+ ? "Context captured for this slice."
897
+ : "No note submitted. Provide a note to attach additional context.",
898
+ });
899
+ return;
900
+ }
901
+ deps.sendJson(res, 400, {
902
+ error: "Unsupported action type.",
903
+ actionType,
904
+ supported: [
905
+ "approve",
906
+ "reject",
907
+ "retry",
908
+ "resume",
909
+ "start",
910
+ "open_artifact",
911
+ "provide_context",
912
+ ],
913
+ knownActionTypes: KNOWN_DECISION_ACTION_TYPES,
914
+ });
915
+ }
916
+ router.add("GET", "dashboard-bundle", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live dashboard bundle");
917
+ router.add("HEAD", "dashboard-bundle", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live dashboard bundle (HEAD)");
918
+ router.add("GET", "live/snapshot", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live snapshot");
919
+ router.add("HEAD", "live/snapshot", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live snapshot (HEAD)");
920
+ router.add("GET", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections)");
921
+ router.add("HEAD", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections, HEAD)");
922
+ router.add("GET", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections");
923
+ router.add("HEAD", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections (HEAD)");
924
+ router.add("POST", "slices/*", async ({ path, query, req, res }) => executeSliceAction(path, query, req, res, headerScopeFromRequest(req)), "Slice action contracts");
925
+ router.add("GET", "sessions/*", async ({ path, query, res, req }) => renderSessionDetailV2(path, query, res, headerScopeFromRequest(req)), "Session detail (v2 projections)");
926
+ router.add("HEAD", "sessions/*", async ({ path, query, res, req }) => renderSessionDetailV2(path, query, res, headerScopeFromRequest(req)), "Session detail (v2 projections, HEAD)");
297
927
  }