@useorgx/openclaw-plugin 0.7.18 → 0.7.23

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 (161) hide show
  1. package/dashboard/dist/assets/9gFmK3Kr.js +1 -0
  2. package/dashboard/dist/assets/9gFmK3Kr.js.br +0 -0
  3. package/dashboard/dist/assets/9gFmK3Kr.js.gz +0 -0
  4. package/dashboard/dist/assets/{DS79hzMu.js → BrMXbzQ-.js} +2 -2
  5. package/dashboard/dist/assets/BrMXbzQ-.js.br +0 -0
  6. package/dashboard/dist/assets/BrMXbzQ-.js.gz +0 -0
  7. package/dashboard/dist/assets/By0MIBj_.js +1 -0
  8. package/dashboard/dist/assets/By0MIBj_.js.br +0 -0
  9. package/dashboard/dist/assets/By0MIBj_.js.gz +0 -0
  10. package/dashboard/dist/assets/C1u2SGin.css +1 -0
  11. package/dashboard/dist/assets/C1u2SGin.css.br +0 -0
  12. package/dashboard/dist/assets/C1u2SGin.css.gz +0 -0
  13. package/dashboard/dist/assets/{467jKHFJ.js → CGJiHCIx.js} +1 -1
  14. package/dashboard/dist/assets/CGJiHCIx.js.br +0 -0
  15. package/dashboard/dist/assets/CGJiHCIx.js.gz +0 -0
  16. package/dashboard/dist/assets/CSd4rSuU.js +212 -0
  17. package/dashboard/dist/assets/CSd4rSuU.js.br +0 -0
  18. package/dashboard/dist/assets/CSd4rSuU.js.gz +0 -0
  19. package/dashboard/dist/assets/{5Ihga-4X.js → CZXS5i_5.js} +1 -1
  20. package/dashboard/dist/assets/CZXS5i_5.js.br +0 -0
  21. package/dashboard/dist/assets/CZXS5i_5.js.gz +0 -0
  22. package/dashboard/dist/assets/{a6qcPiWt.js → CbVWL74-.js} +1 -1
  23. package/dashboard/dist/assets/CbVWL74-.js.br +0 -0
  24. package/dashboard/dist/assets/CbVWL74-.js.gz +0 -0
  25. package/dashboard/dist/assets/{qDJ6rqcs.js → D-FuHfT8.js} +1 -1
  26. package/dashboard/dist/assets/D-FuHfT8.js.br +0 -0
  27. package/dashboard/dist/assets/D-FuHfT8.js.gz +0 -0
  28. package/dashboard/dist/assets/{BcJmNILk.js → D0PN5_vY.js} +1 -1
  29. package/dashboard/dist/assets/D0PN5_vY.js.br +0 -0
  30. package/dashboard/dist/assets/D0PN5_vY.js.gz +0 -0
  31. package/dashboard/dist/assets/DDCPrZRt.js +1 -0
  32. package/dashboard/dist/assets/DDCPrZRt.js.br +0 -0
  33. package/dashboard/dist/assets/DDCPrZRt.js.gz +0 -0
  34. package/dashboard/dist/assets/{B71dt9yu.js → DNQ-iFO2.js} +1 -1
  35. package/dashboard/dist/assets/DNQ-iFO2.js.br +0 -0
  36. package/dashboard/dist/assets/DNQ-iFO2.js.gz +0 -0
  37. package/dashboard/dist/assets/{PVi0vr9a.js → DhPuHPK7.js} +1 -1
  38. package/dashboard/dist/assets/DhPuHPK7.js.br +0 -0
  39. package/dashboard/dist/assets/DhPuHPK7.js.gz +0 -0
  40. package/dashboard/dist/assets/Dhz7qPtn.js +1 -0
  41. package/dashboard/dist/assets/Dhz7qPtn.js.br +0 -0
  42. package/dashboard/dist/assets/Dhz7qPtn.js.gz +0 -0
  43. package/dashboard/dist/assets/LOFrVoPD.js +1 -0
  44. package/dashboard/dist/assets/LOFrVoPD.js.br +0 -0
  45. package/dashboard/dist/assets/LOFrVoPD.js.gz +0 -0
  46. package/dashboard/dist/assets/OlLPtzdz.js +1 -0
  47. package/dashboard/dist/assets/OlLPtzdz.js.br +0 -0
  48. package/dashboard/dist/assets/OlLPtzdz.js.gz +0 -0
  49. package/dashboard/dist/assets/{sdoPH_Z1.js → RN4M9u9W.js} +2 -2
  50. package/dashboard/dist/assets/RN4M9u9W.js.br +0 -0
  51. package/dashboard/dist/assets/RN4M9u9W.js.gz +0 -0
  52. package/dashboard/dist/assets/VCHu272d.js +1 -0
  53. package/dashboard/dist/assets/VCHu272d.js.br +0 -0
  54. package/dashboard/dist/assets/VCHu272d.js.gz +0 -0
  55. package/dashboard/dist/assets/m2smti3F.js +1 -0
  56. package/dashboard/dist/assets/m2smti3F.js.br +0 -0
  57. package/dashboard/dist/assets/m2smti3F.js.gz +0 -0
  58. package/dashboard/dist/assets/{C3_j_W9V.js → nra1yvJX.js} +1 -1
  59. package/dashboard/dist/assets/nra1yvJX.js.br +0 -0
  60. package/dashboard/dist/assets/nra1yvJX.js.gz +0 -0
  61. package/dashboard/dist/assets/qLX6NZ-J.js +1 -0
  62. package/dashboard/dist/assets/qLX6NZ-J.js.br +0 -0
  63. package/dashboard/dist/assets/qLX6NZ-J.js.gz +0 -0
  64. package/dashboard/dist/index.html +2 -2
  65. package/dashboard/dist/index.html.br +0 -0
  66. package/dashboard/dist/index.html.gz +0 -0
  67. package/dist/agent-run-store.js +162 -24
  68. package/dist/cli/orgx.d.ts +3 -0
  69. package/dist/config/resolution.d.ts +7 -0
  70. package/dist/config/resolution.js +13 -5
  71. package/dist/contracts/onboarding-state.d.ts +2 -0
  72. package/dist/contracts/onboarding-state.js +23 -0
  73. package/dist/contracts/shared-types.d.ts +17 -0
  74. package/dist/http/helpers/auto-continue-engine.d.ts +62 -0
  75. package/dist/http/helpers/auto-continue-engine.js +329 -53
  76. package/dist/http/helpers/autopilot-runtime.js +5 -1
  77. package/dist/http/helpers/autopilot-slice-utils.js +25 -1
  78. package/dist/http/helpers/decision-mapper.d.ts +1 -0
  79. package/dist/http/helpers/decision-mapper.js +19 -2
  80. package/dist/http/helpers/dispatch-lifecycle.js +3 -0
  81. package/dist/http/helpers/mission-control.d.ts +1 -0
  82. package/dist/http/helpers/mission-control.js +5 -2
  83. package/dist/http/helpers/slice-run-projections.d.ts +27 -0
  84. package/dist/http/helpers/slice-run-projections.js +198 -10
  85. package/dist/http/helpers/triage-mapper.js +220 -6
  86. package/dist/http/index.d.ts +1 -0
  87. package/dist/http/index.js +94 -46
  88. package/dist/http/router.js +64 -9
  89. package/dist/http/routes/live-legacy.d.ts +19 -2
  90. package/dist/http/routes/live-legacy.js +110 -27
  91. package/dist/http/routes/live-snapshot.d.ts +16 -2
  92. package/dist/http/routes/live-snapshot.js +169 -25
  93. package/dist/http/routes/mission-control-actions.js +28 -0
  94. package/dist/http/routes/mission-control-read.d.ts +18 -0
  95. package/dist/http/routes/mission-control-read.js +130 -218
  96. package/dist/http/routes/onboarding.d.ts +1 -0
  97. package/dist/http/routes/onboarding.js +17 -0
  98. package/dist/index.d.ts +5 -0
  99. package/dist/index.js +199 -123
  100. package/dist/outbox.d.ts +0 -2
  101. package/dist/outbox.js +268 -150
  102. package/dist/reporting/rollups.js +18 -11
  103. package/dist/runtime-instance-store.js +212 -58
  104. package/dist/stores/materialized-snapshot-store.d.ts +18 -0
  105. package/dist/stores/materialized-snapshot-store.js +91 -0
  106. package/dist/stores/sqlite-state.d.ts +6 -0
  107. package/dist/stores/sqlite-state.js +179 -0
  108. package/package.json +6 -1
  109. package/dashboard/dist/assets/467jKHFJ.js.br +0 -0
  110. package/dashboard/dist/assets/467jKHFJ.js.gz +0 -0
  111. package/dashboard/dist/assets/5Ihga-4X.js.br +0 -0
  112. package/dashboard/dist/assets/5Ihga-4X.js.gz +0 -0
  113. package/dashboard/dist/assets/B71dt9yu.js.br +0 -0
  114. package/dashboard/dist/assets/B71dt9yu.js.gz +0 -0
  115. package/dashboard/dist/assets/BCudUvwg.js +0 -1
  116. package/dashboard/dist/assets/BCudUvwg.js.br +0 -0
  117. package/dashboard/dist/assets/BCudUvwg.js.gz +0 -0
  118. package/dashboard/dist/assets/BEnI6kNR.js +0 -1
  119. package/dashboard/dist/assets/BEnI6kNR.js.br +0 -0
  120. package/dashboard/dist/assets/BEnI6kNR.js.gz +0 -0
  121. package/dashboard/dist/assets/BcJmNILk.js.br +0 -0
  122. package/dashboard/dist/assets/BcJmNILk.js.gz +0 -0
  123. package/dashboard/dist/assets/C-MOJWHs.js +0 -1
  124. package/dashboard/dist/assets/C-MOJWHs.js.br +0 -0
  125. package/dashboard/dist/assets/C-MOJWHs.js.gz +0 -0
  126. package/dashboard/dist/assets/C-XuWXGi.js +0 -1
  127. package/dashboard/dist/assets/C-XuWXGi.js.br +0 -0
  128. package/dashboard/dist/assets/C-XuWXGi.js.gz +0 -0
  129. package/dashboard/dist/assets/C3_j_W9V.js.br +0 -0
  130. package/dashboard/dist/assets/C3_j_W9V.js.gz +0 -0
  131. package/dashboard/dist/assets/C9-UYhBb.js +0 -1
  132. package/dashboard/dist/assets/C9-UYhBb.js.br +0 -0
  133. package/dashboard/dist/assets/C9-UYhBb.js.gz +0 -0
  134. package/dashboard/dist/assets/C9yV06GS.js +0 -1
  135. package/dashboard/dist/assets/C9yV06GS.js.br +0 -0
  136. package/dashboard/dist/assets/C9yV06GS.js.gz +0 -0
  137. package/dashboard/dist/assets/CReugbyT.js +0 -1
  138. package/dashboard/dist/assets/CReugbyT.js.br +0 -0
  139. package/dashboard/dist/assets/CReugbyT.js.gz +0 -0
  140. package/dashboard/dist/assets/CSDhTbKy.js +0 -1
  141. package/dashboard/dist/assets/CSDhTbKy.js.br +0 -0
  142. package/dashboard/dist/assets/CSDhTbKy.js.gz +0 -0
  143. package/dashboard/dist/assets/CfMS9yIf.js +0 -1
  144. package/dashboard/dist/assets/CfMS9yIf.js.br +0 -0
  145. package/dashboard/dist/assets/CfMS9yIf.js.gz +0 -0
  146. package/dashboard/dist/assets/D2Kqcmv9.js +0 -212
  147. package/dashboard/dist/assets/D2Kqcmv9.js.br +0 -0
  148. package/dashboard/dist/assets/D2Kqcmv9.js.gz +0 -0
  149. package/dashboard/dist/assets/DS79hzMu.js.br +0 -0
  150. package/dashboard/dist/assets/DS79hzMu.js.gz +0 -0
  151. package/dashboard/dist/assets/PVi0vr9a.js.br +0 -0
  152. package/dashboard/dist/assets/PVi0vr9a.js.gz +0 -0
  153. package/dashboard/dist/assets/RZkbqlJk.css +0 -1
  154. package/dashboard/dist/assets/RZkbqlJk.css.br +0 -0
  155. package/dashboard/dist/assets/RZkbqlJk.css.gz +0 -0
  156. package/dashboard/dist/assets/a6qcPiWt.js.br +0 -0
  157. package/dashboard/dist/assets/a6qcPiWt.js.gz +0 -0
  158. package/dashboard/dist/assets/qDJ6rqcs.js.br +0 -0
  159. package/dashboard/dist/assets/qDJ6rqcs.js.gz +0 -0
  160. package/dashboard/dist/assets/sdoPH_Z1.js.br +0 -0
  161. package/dashboard/dist/assets/sdoPH_Z1.js.gz +0 -0
@@ -13,6 +13,8 @@ type LocalLiveActivity = {
13
13
  };
14
14
  type LiveActivityPage = {
15
15
  activities: LiveActivityItem[];
16
+ total?: number;
17
+ storeUpdatedAt?: string;
16
18
  cursor?: string | null;
17
19
  nextCursor?: string | null;
18
20
  prevCursor?: string | null;
@@ -34,7 +36,7 @@ type RouteResLike = {
34
36
  on?: (event: string, listener: () => void) => void;
35
37
  once?: (event: string, listener: () => void) => void;
36
38
  };
37
- type RegisterLiveLegacyRoutesDeps<TRes extends RouteResLike> = {
39
+ type RegisterLiveLegacyRoutesDeps<TReq extends RouteReqLike, TRes extends RouteResLike> = {
38
40
  getLiveSessions: (input: {
39
41
  initiative: string | null;
40
42
  projectId: string | null;
@@ -82,6 +84,15 @@ type RegisterLiveLegacyRoutesDeps<TRes extends RouteResLike> = {
82
84
  sessionKey: string | null;
83
85
  runId: string | null;
84
86
  }) => Promise<Record<string, unknown> | null>;
87
+ summarizeActivityHeadline: (input: {
88
+ text: string;
89
+ title: string | null;
90
+ type: string | null;
91
+ }) => Promise<{
92
+ headline: string;
93
+ source: string;
94
+ model: string | null;
95
+ }>;
85
96
  sendJson: (res: TRes, status: number, payload: unknown) => void;
86
97
  safeErrorMessage: (err: unknown) => string;
87
98
  sendHtml: (res: TRes, status: number, html: string) => void;
@@ -112,6 +123,12 @@ type RegisterLiveLegacyRoutesDeps<TRes extends RouteResLike> = {
112
123
  };
113
124
  isUserScopedApiKey: (apiKey: string) => boolean;
114
125
  streamIdleTimeoutMs: number;
126
+ renderLiveStreamV2?: (input: {
127
+ path: string;
128
+ query: URLSearchParams;
129
+ req: TReq;
130
+ res: TRes;
131
+ }) => Promise<void>;
115
132
  };
116
- export declare function registerLiveLegacyRoutes<TReq extends RouteReqLike, TRes extends RouteResLike>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterLiveLegacyRoutesDeps<TRes>): void;
133
+ export declare function registerLiveLegacyRoutes<TReq extends RouteReqLike, TRes extends RouteResLike>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterLiveLegacyRoutesDeps<TReq, TRes>): void;
117
134
  export {};
@@ -1,3 +1,4 @@
1
+ import { resolveWorkspaceScope } from "../helpers/workspace-scope.js";
1
2
  export function registerLiveLegacyRoutes(router, deps) {
2
3
  function toContextBundle(value) {
3
4
  return {
@@ -5,26 +6,40 @@ export function registerLiveLegacyRoutes(router, deps) {
5
6
  runs: value.runs ?? {},
6
7
  };
7
8
  }
8
- function parseWorkspaceScope(query) {
9
- const candidates = [
10
- query.get("workspace_id"),
11
- query.get("workspaceId"),
12
- query.get("command_center_id"),
13
- query.get("commandCenterId"),
14
- query.get("center"),
15
- query.get("project_id"),
16
- query.get("projectId"),
17
- ];
18
- for (const candidate of candidates) {
19
- if (typeof candidate !== "string")
20
- continue;
21
- const normalized = candidate.trim();
22
- if (!normalized || normalized.toLowerCase() === "all")
23
- continue;
24
- return normalized;
9
+ function readActivityMetadataValue(metadata, keys) {
10
+ if (!metadata)
11
+ return null;
12
+ for (const key of keys) {
13
+ const value = metadata[key];
14
+ if (typeof value === "string" && value.trim().length > 0) {
15
+ return value.trim();
16
+ }
25
17
  }
26
18
  return null;
27
19
  }
20
+ function resolveActivityInitiativeId(item) {
21
+ const direct = item.initiativeId?.trim();
22
+ if (direct)
23
+ return direct;
24
+ const metadata = item.metadata && typeof item.metadata === "object" && !Array.isArray(item.metadata)
25
+ ? item.metadata
26
+ : null;
27
+ return readActivityMetadataValue(metadata, [
28
+ "initiativeId",
29
+ "initiative_id",
30
+ "initiative",
31
+ ]);
32
+ }
33
+ function filterActivityByInitiativeIds(items, initiativeIds) {
34
+ if (!initiativeIds)
35
+ return items;
36
+ if (initiativeIds.size === 0)
37
+ return [];
38
+ return items.filter((item) => {
39
+ const initiativeId = resolveActivityInitiativeId(item);
40
+ return initiativeId ? initiativeIds.has(initiativeId) : false;
41
+ });
42
+ }
28
43
  const sendDeprecated = (res, endpoint, replacement) => {
29
44
  deps.sendJson(res, 410, {
30
45
  error: `${endpoint} is deprecated`,
@@ -44,11 +59,26 @@ export function registerLiveLegacyRoutes(router, deps) {
44
59
  const since = query.get("since");
45
60
  const until = query.get("until");
46
61
  const cursor = query.get("cursor");
47
- const projectId = parseWorkspaceScope(query);
62
+ const projectId = resolveWorkspaceScope(query, null, { allowProjectScope: true }).workspaceId ??
63
+ null;
48
64
  const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
49
65
  const limit = Number.isFinite(limitRaw)
50
66
  ? Math.max(1, Math.floor(Number(limitRaw)))
51
67
  : 200;
68
+ const scopedInitiativeIds = projectId
69
+ ? new Set((await deps.listInitiativeIdsForProject({ projectId }))
70
+ .map((value) => value.trim())
71
+ .filter((value) => value.length > 0))
72
+ : null;
73
+ if (scopedInitiativeIds && scopedInitiativeIds.size === 0) {
74
+ deps.sendJson(res, 200, {
75
+ activities: [],
76
+ total: 0,
77
+ nextCursor: null,
78
+ storeUpdatedAt: null,
79
+ });
80
+ return;
81
+ }
52
82
  let page = deps.listActivityPage({
53
83
  limit,
54
84
  runId: run,
@@ -60,7 +90,7 @@ export function registerLiveLegacyRoutes(router, deps) {
60
90
  const ctx = toContextBundle(deps.readAgentContexts());
61
91
  page = {
62
92
  ...page,
63
- activities: deps.applyAgentContextsToActivity(page.activities, ctx),
93
+ activities: filterActivityByInitiativeIds(deps.applyAgentContextsToActivity(page.activities, ctx), scopedInitiativeIds),
64
94
  };
65
95
  }
66
96
  const warmKey = `${projectId ?? ""}::${run ?? ""}::${since ?? ""}::${until ?? ""}`;
@@ -94,7 +124,7 @@ export function registerLiveLegacyRoutes(router, deps) {
94
124
  const ctx = toContextBundle(deps.readAgentContexts());
95
125
  page = {
96
126
  ...page,
97
- activities: deps.applyAgentContextsToActivity(page.activities, ctx),
127
+ activities: filterActivityByInitiativeIds(deps.applyAgentContextsToActivity(page.activities, ctx), scopedInitiativeIds),
98
128
  };
99
129
  }
100
130
  }
@@ -102,6 +132,12 @@ export function registerLiveLegacyRoutes(router, deps) {
102
132
  // best effort
103
133
  }
104
134
  }
135
+ if (scopedInitiativeIds) {
136
+ page = {
137
+ ...page,
138
+ total: page.activities.length,
139
+ };
140
+ }
105
141
  deps.sendJson(res, 200, page);
106
142
  }
107
143
  router.add("GET", "live/activity/page", async ({ query, res }) => renderLiveActivityPage(query, res), "Paginated live activity");
@@ -114,9 +150,41 @@ export function registerLiveLegacyRoutes(router, deps) {
114
150
  router.add("GET", "live/activity", async ({ query, res }) => renderLiveActivity(query, res), "Legacy live activity endpoint");
115
151
  router.add("HEAD", "live/activity", async ({ query, res }) => renderLiveActivity(query, res), "Legacy live activity endpoint (HEAD)");
116
152
  async function renderLiveActivityDetail(query, res) {
117
- sendDeprecated(res, "/orgx/api/live/activity/detail", "/orgx/api/live/snapshot-v2");
118
- void query;
119
- return;
153
+ try {
154
+ const turnId = (query.get("turnId") ?? "").trim();
155
+ if (!turnId) {
156
+ deps.sendJson(res, 400, { error: "turnId is required" });
157
+ return;
158
+ }
159
+ const sessionKey = (query.get("sessionKey") ?? "").trim() || null;
160
+ const runId = (query.get("run") ?? query.get("runId") ?? "").trim() || null;
161
+ const detail = await deps.loadLocalTurnDetail({ turnId, sessionKey, runId });
162
+ if (!detail) {
163
+ deps.sendJson(res, 404, { error: "activity detail not found" });
164
+ return;
165
+ }
166
+ const summary = typeof detail.summary === "string" && detail.summary.trim().length > 0
167
+ ? detail.summary.trim()
168
+ : null;
169
+ if (!summary) {
170
+ deps.sendJson(res, 200, { detail, headline: null, headlineSource: null, headlineModel: null });
171
+ return;
172
+ }
173
+ const headline = await deps.summarizeActivityHeadline({
174
+ text: summary,
175
+ title: null,
176
+ type: "activity",
177
+ });
178
+ deps.sendJson(res, 200, {
179
+ detail,
180
+ headline: headline.headline,
181
+ headlineSource: headline.source,
182
+ headlineModel: headline.model,
183
+ });
184
+ }
185
+ catch (err) {
186
+ deps.sendJson(res, 500, { error: deps.safeErrorMessage(err) });
187
+ }
120
188
  }
121
189
  router.add("GET", "live/activity/detail", async ({ query, res }) => renderLiveActivityDetail(query, res), "Detailed activity turn view");
122
190
  router.add("HEAD", "live/activity/detail", async ({ query, res }) => renderLiveActivityDetail(query, res), "Detailed activity turn view (HEAD)");
@@ -246,10 +314,25 @@ export function registerLiveLegacyRoutes(router, deps) {
246
314
  });
247
315
  }, "Reject unsupported methods for live/terminal/open");
248
316
  async function renderLiveStream(query, req, res) {
249
- sendDeprecated(res, "/orgx/api/live/stream", "/orgx/api/live/snapshot-v2");
250
- void query;
251
- void req;
252
- return;
317
+ if (typeof deps.renderLiveStreamV2 === "function") {
318
+ await deps.renderLiveStreamV2({
319
+ path: "live/stream",
320
+ query,
321
+ req,
322
+ res,
323
+ });
324
+ return;
325
+ }
326
+ const suffix = query.toString();
327
+ const location = suffix
328
+ ? `/orgx/api/live/stream-v2?${suffix}`
329
+ : "/orgx/api/live/stream-v2";
330
+ res.writeHead(307, {
331
+ Location: location,
332
+ Deprecation: "true",
333
+ Link: `</orgx/api/live/stream-v2>; rel="successor-version"`,
334
+ });
335
+ res.end();
253
336
  }
254
337
  router.add("GET", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream");
255
338
  router.add("HEAD", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream (HEAD)");
@@ -15,8 +15,11 @@ type SnapshotPersistState = {
15
15
  };
16
16
  type LiveSnapshotRoutesDeps<TReq, TRes> = {
17
17
  parsePositiveInt: (raw: string | null, fallback: number, max?: number) => number;
18
- readSnapshotResponseCache: (key: string) => Record<string, unknown> | null;
18
+ readSnapshotResponseCache: (key: string, options?: {
19
+ allowStale?: boolean;
20
+ }) => Record<string, unknown> | null;
19
21
  writeSnapshotResponseCache: (key: string, payload: Record<string, unknown>) => void;
22
+ getSnapshotCacheGeneration: () => number;
20
23
  safeErrorMessage: (err: unknown) => string;
21
24
  readAgentContexts: () => AgentContextState;
22
25
  getScopedAgentIds: (contexts: Record<string, AgentLaunchContext>) => Set<string>;
@@ -141,6 +144,17 @@ type LiveSnapshotRoutesDeps<TReq, TRes> = {
141
144
  lastTransitionAt: string;
142
145
  } | null;
143
146
  sendJson: (res: TRes, status: number, payload: unknown) => void;
147
+ securityHeaders: Record<string, string>;
148
+ corsHeaders: Record<string, string>;
144
149
  };
145
- export declare function registerLiveSnapshotRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: LiveSnapshotRoutesDeps<TReq, TRes>): void;
150
+ export declare function registerLiveSnapshotRoutes<TReq extends {
151
+ on?: (event: string, listener: () => void) => void;
152
+ }, TRes extends {
153
+ write?: (chunk: string | Buffer) => boolean | void;
154
+ end: (chunk?: string | Buffer) => void;
155
+ writeHead: (statusCode: number, headers?: Record<string, string>) => unknown;
156
+ writableEnded?: boolean;
157
+ on?: (event: string, listener: () => void) => void;
158
+ once?: (event: string, listener: () => void) => void;
159
+ }>(router: Router<Record<string, never>, TReq, TRes>, deps: LiveSnapshotRoutesDeps<TReq, TRes>): void;
146
160
  export {};
@@ -16,6 +16,8 @@ const LIVE_SNAPSHOT_NEXT_UP_TIMEOUT_MS = (() => {
16
16
  return 350;
17
17
  return Math.max(250, Math.min(15_000, Math.floor(raw)));
18
18
  })();
19
+ const LIVE_STREAM_REFRESH_INTERVAL_MS = 1_500;
20
+ const LIVE_STREAM_KEEPALIVE_MS = 20_000;
19
21
  async function withSoftTimeout(work, timeoutMs, label) {
20
22
  let timer = null;
21
23
  try {
@@ -59,6 +61,12 @@ function emptyOutboxStatus() {
59
61
  lastReplayError: null,
60
62
  };
61
63
  }
64
+ function writeSseEvent(res, event, payload) {
65
+ if (typeof res.write !== "function")
66
+ return;
67
+ res.write(`event: ${event}\n`);
68
+ res.write(`data: ${JSON.stringify(payload ?? null)}\n\n`);
69
+ }
62
70
  function filterSessionsByInitiative(sessions, initiative) {
63
71
  if (!initiative || initiative.trim().length === 0)
64
72
  return sessions;
@@ -230,6 +238,7 @@ export function registerLiveSnapshotRoutes(router, deps) {
230
238
  return `${base}:${normalized}`;
231
239
  };
232
240
  const headerScopeFromRequest = (req) => workspaceScopeFromHeaders(req?.headers);
241
+ const snapshotInflight = new Map();
233
242
  function parseSnapshotQuery(query, headerScope) {
234
243
  const sessionsLimit = deps.parsePositiveInt(query.get("sessionsLimit") ?? query.get("sessions_limit"), 320, 1000);
235
244
  const activityLimit = deps.parsePositiveInt(query.get("activityLimit") ?? query.get("activity_limit"), 600, 2000);
@@ -269,6 +278,24 @@ export function registerLiveSnapshotRoutes(router, deps) {
269
278
  });
270
279
  return false;
271
280
  };
281
+ function withSnapshotMeta(payload, input) {
282
+ const existingMeta = payload.meta && typeof payload.meta === "object" && !Array.isArray(payload.meta)
283
+ ? payload.meta
284
+ : {};
285
+ return {
286
+ ...payload,
287
+ meta: {
288
+ ...existingMeta,
289
+ generatedAt: (typeof payload.generatedAt === "string" && payload.generatedAt) ||
290
+ (typeof existingMeta.generatedAt === "string" && existingMeta.generatedAt) ||
291
+ new Date().toISOString(),
292
+ stale: input.stale,
293
+ staleReason: input.stale ? input.staleReason ?? "upstream_unavailable" : null,
294
+ sequence: deps.getSnapshotCacheGeneration(),
295
+ cacheKey: input.cacheKey,
296
+ },
297
+ };
298
+ }
272
299
  async function buildSnapshotBundle(query, headerScope) {
273
300
  const parsed = parseSnapshotQuery(query, headerScope);
274
301
  const { sessionsLimit, activityLimit, decisionsLimit, initiative, projectId, run, since, decisionStatus, includeIdle, scopeError, } = parsed;
@@ -583,34 +610,21 @@ export function registerLiveSnapshotRoutes(router, deps) {
583
610
  decisionsRaw: decisions,
584
611
  };
585
612
  }
586
- async function renderSnapshot(path, query, res, headerScope) {
587
- if (!validateWorkspaceScope(query, res, "live.snapshot.validation", headerScope))
588
- return;
589
- const snapshotCacheKey = `${path}?${query.toString()}`;
590
- const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
591
- if (cachedSnapshot) {
592
- deps.sendJson(res, 200, cachedSnapshot);
593
- return;
594
- }
613
+ async function buildSnapshotPayload(version, path, query, headerScope) {
595
614
  const bundle = await buildSnapshotBundle(query, headerScope);
596
615
  const parsed = parseSnapshotQuery(query, headerScope);
597
- deps.writeSnapshotResponseCache(snapshotCacheKey, bundle.payload);
598
- deps.writeSnapshotResponseCache("live-snapshot", bundle.payload);
599
- deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot", parsed.projectId), bundle.payload);
600
- deps.sendJson(res, 200, bundle.payload);
601
- }
602
- async function renderSnapshotV2(path, query, res, headerScope) {
603
- if (!validateWorkspaceScope(query, res, "live.snapshot-v2.validation", headerScope))
604
- return;
605
616
  const snapshotCacheKey = `${path}?${query.toString()}`;
606
- const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
607
- if (cachedSnapshot) {
608
- deps.sendJson(res, 200, cachedSnapshot);
609
- return;
617
+ if (version === "v1") {
618
+ const payload = withSnapshotMeta(bundle.payload, {
619
+ cacheKey: snapshotCacheKey,
620
+ stale: false,
621
+ });
622
+ deps.writeSnapshotResponseCache(snapshotCacheKey, payload);
623
+ deps.writeSnapshotResponseCache("live-snapshot", payload);
624
+ deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot", parsed.projectId), payload);
625
+ return payload;
610
626
  }
611
- const bundle = await buildSnapshotBundle(query, headerScope);
612
- const parsed = parseSnapshotQuery(query, headerScope);
613
- const payload = {
627
+ const payload = withSnapshotMeta({
614
628
  ...bundle.v2,
615
629
  sessions: bundle.payload.sessions,
616
630
  activity: bundle.payload.activity,
@@ -622,12 +636,140 @@ export function registerLiveSnapshotRoutes(router, deps) {
622
636
  outbox: bundle.payload.outbox,
623
637
  chat: bundle.payload.chat,
624
638
  degraded: bundle.payload.degraded,
625
- };
639
+ }, {
640
+ cacheKey: snapshotCacheKey,
641
+ stale: false,
642
+ });
626
643
  deps.writeSnapshotResponseCache(snapshotCacheKey, payload);
627
644
  deps.writeSnapshotResponseCache("live-snapshot-v2", payload);
628
645
  deps.writeSnapshotResponseCache(snapshotAliasKey("live-snapshot-v2", parsed.projectId), payload);
646
+ return payload;
647
+ }
648
+ async function getSnapshotPayload(version, path, query, headerScope) {
649
+ const snapshotCacheKey = `${path}?${query.toString()}`;
650
+ const cachedSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey);
651
+ if (cachedSnapshot)
652
+ return cachedSnapshot;
653
+ const inFlight = snapshotInflight.get(snapshotCacheKey);
654
+ if (inFlight)
655
+ return await inFlight;
656
+ const work = buildSnapshotPayload(version, path, query, headerScope)
657
+ .catch((err) => {
658
+ const staleSnapshot = deps.readSnapshotResponseCache(snapshotCacheKey, {
659
+ allowStale: true,
660
+ });
661
+ if (staleSnapshot) {
662
+ return withSnapshotMeta(staleSnapshot, {
663
+ cacheKey: snapshotCacheKey,
664
+ stale: true,
665
+ staleReason: deps.safeErrorMessage(err),
666
+ });
667
+ }
668
+ throw err;
669
+ })
670
+ .finally(() => {
671
+ snapshotInflight.delete(snapshotCacheKey);
672
+ });
673
+ snapshotInflight.set(snapshotCacheKey, work);
674
+ return await work;
675
+ }
676
+ async function renderSnapshot(path, query, res, headerScope) {
677
+ if (!validateWorkspaceScope(query, res, "live.snapshot.validation", headerScope))
678
+ return;
679
+ const payload = await getSnapshotPayload("v1", path, query, headerScope);
629
680
  deps.sendJson(res, 200, payload);
630
681
  }
682
+ async function renderSnapshotV2(path, query, res, headerScope) {
683
+ if (!validateWorkspaceScope(query, res, "live.snapshot-v2.validation", headerScope))
684
+ return;
685
+ const payload = await getSnapshotPayload("v2", path, query, headerScope);
686
+ deps.sendJson(res, 200, payload);
687
+ }
688
+ async function renderSnapshotStreamV2(path, query, req, res, headerScope) {
689
+ if (!validateWorkspaceScope(query, res, "live.stream-v2.validation", headerScope))
690
+ return;
691
+ const streamHeaders = {
692
+ ...deps.securityHeaders,
693
+ ...deps.corsHeaders,
694
+ "Content-Type": "text/event-stream; charset=utf-8",
695
+ "Cache-Control": "no-cache, no-transform",
696
+ Connection: "keep-alive",
697
+ "X-Accel-Buffering": "no",
698
+ };
699
+ res.writeHead(200, streamHeaders);
700
+ if (typeof res.write !== "function") {
701
+ res.end();
702
+ return;
703
+ }
704
+ let closed = false;
705
+ let lastGeneration = -1;
706
+ let refreshTimer = null;
707
+ let keepaliveTimer = null;
708
+ const cleanup = () => {
709
+ if (closed)
710
+ return;
711
+ closed = true;
712
+ if (refreshTimer) {
713
+ clearInterval(refreshTimer);
714
+ refreshTimer = null;
715
+ }
716
+ if (keepaliveTimer) {
717
+ clearInterval(keepaliveTimer);
718
+ keepaliveTimer = null;
719
+ }
720
+ try {
721
+ if (!res.writableEnded)
722
+ res.end();
723
+ }
724
+ catch {
725
+ // ignore
726
+ }
727
+ };
728
+ req.on?.("close", cleanup);
729
+ res.on?.("close", cleanup);
730
+ res.once?.("finish", cleanup);
731
+ const emitSnapshot = async () => {
732
+ if (closed)
733
+ return;
734
+ try {
735
+ const payload = await getSnapshotPayload("v2", path, query, headerScope);
736
+ if (closed)
737
+ return;
738
+ lastGeneration = deps.getSnapshotCacheGeneration();
739
+ writeSseEvent(res, "snapshot", payload);
740
+ }
741
+ catch (err) {
742
+ writeSseEvent(res, "error", {
743
+ error: deps.safeErrorMessage(err),
744
+ });
745
+ }
746
+ };
747
+ await emitSnapshot();
748
+ refreshTimer = setInterval(() => {
749
+ if (closed) {
750
+ cleanup();
751
+ return;
752
+ }
753
+ const generation = deps.getSnapshotCacheGeneration();
754
+ if (generation === lastGeneration)
755
+ return;
756
+ void emitSnapshot();
757
+ }, LIVE_STREAM_REFRESH_INTERVAL_MS);
758
+ refreshTimer.unref?.();
759
+ keepaliveTimer = setInterval(() => {
760
+ if (closed || typeof res.write !== "function") {
761
+ cleanup();
762
+ return;
763
+ }
764
+ try {
765
+ res.write(`: ping ${Date.now()}\n\n`);
766
+ }
767
+ catch {
768
+ cleanup();
769
+ }
770
+ }, LIVE_STREAM_KEEPALIVE_MS);
771
+ keepaliveTimer.unref?.();
772
+ }
631
773
  async function renderSliceNarrative(path, query, res, headerScope) {
632
774
  if (!validateWorkspaceScope(query, res, "live.snapshot.slice-narrative.validation", headerScope))
633
775
  return;
@@ -914,6 +1056,8 @@ export function registerLiveSnapshotRoutes(router, deps) {
914
1056
  router.add("HEAD", "live/snapshot", async ({ path, query, res, req }) => renderSnapshot(path, query, res, headerScopeFromRequest(req)), "Live snapshot (HEAD)");
915
1057
  router.add("GET", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections)");
916
1058
  router.add("HEAD", "live/snapshot-v2", async ({ path, query, res, req }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot (v2 projections, HEAD)");
1059
+ router.add("GET", "live/stream-v2", async ({ path, query, req, res }) => renderSnapshotStreamV2(path, query, req, res, headerScopeFromRequest(req)), "Live snapshot stream (v2)");
1060
+ router.add("HEAD", "live/stream-v2", async ({ path, query, req, res }) => renderSnapshotV2(path, query, res, headerScopeFromRequest(req)), "Live snapshot stream (v2, HEAD)");
917
1061
  router.add("GET", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections");
918
1062
  router.add("HEAD", "slices/*", async ({ path, query, res, req }) => renderSliceNarrative(path, query, res, headerScopeFromRequest(req)), "Slice narrative/timeline projections (HEAD)");
919
1063
  router.add("POST", "slices/*", async ({ path, query, req, res }) => executeSliceAction(path, query, req, res, headerScopeFromRequest(req)), "Slice action contracts");
@@ -399,6 +399,19 @@ export function registerMissionControlActionsRoutes(router, deps) {
399
399
  : null;
400
400
  const maxParallelSlices = normalizeMaxParallelSlices(requestedMaxParallelSlicesRaw, queuePreferredParallel ?? 1);
401
401
  const parallelMode = normalizeParallelMode(requestedParallelModeRaw);
402
+ const requestedWorkspaceId = deps.pickString(payload, [
403
+ "workspaceId",
404
+ "workspace_id",
405
+ "command_center_id",
406
+ "projectId",
407
+ "project_id",
408
+ ]) ??
409
+ query.get("workspaceId") ??
410
+ query.get("workspace_id") ??
411
+ query.get("command_center_id") ??
412
+ query.get("projectId") ??
413
+ query.get("project_id") ??
414
+ null;
402
415
  const existingRun = deps.autoContinueRuns.get(initiativeId) ?? null;
403
416
  const existingActiveRunIds = Array.isArray(existingRun?.activeSliceRunIds)
404
417
  ? (existingRun?.activeSliceRunIds)
@@ -429,6 +442,7 @@ export function registerMissionControlActionsRoutes(router, deps) {
429
442
  }
430
443
  const run = await deps.startAutoContinueRun({
431
444
  initiativeId,
445
+ workspaceId: requestedWorkspaceId,
432
446
  agentId,
433
447
  agentName: requestedAgentName,
434
448
  tokenBudget,
@@ -1566,8 +1580,22 @@ export function registerMissionControlActionsRoutes(router, deps) {
1566
1580
  const startScope = startScopeRaw === "milestone" || startScopeRaw === "workstream"
1567
1581
  ? startScopeRaw
1568
1582
  : "task";
1583
+ const requestedWorkspaceId = deps.pickString(payload, [
1584
+ "workspaceId",
1585
+ "workspace_id",
1586
+ "command_center_id",
1587
+ "projectId",
1588
+ "project_id",
1589
+ ]) ??
1590
+ query.get("workspaceId") ??
1591
+ query.get("workspace_id") ??
1592
+ query.get("command_center_id") ??
1593
+ query.get("projectId") ??
1594
+ query.get("project_id") ??
1595
+ null;
1569
1596
  const run = await deps.startAutoContinueRun({
1570
1597
  initiativeId,
1598
+ workspaceId: requestedWorkspaceId,
1571
1599
  agentId,
1572
1600
  agentName: await deps.resolveAgentDisplayName(agentId, null),
1573
1601
  tokenBudget,
@@ -38,6 +38,7 @@ type NextUpQueueItem = {
38
38
  sliceTaskIds?: string[];
39
39
  sliceTaskCount?: number | null;
40
40
  sliceMilestoneId?: string | null;
41
+ milestoneBreakdown?: MilestoneBreakdownEntry[];
41
42
  isPinned?: boolean;
42
43
  pinnedRank?: number | null;
43
44
  compositeScore?: number;
@@ -55,9 +56,26 @@ type SliceRunnerAgent = {
55
56
  type NextUpQueue = {
56
57
  items: NextUpQueueItem[];
57
58
  degraded: string[];
59
+ summary?: {
60
+ visibleTotal: number;
61
+ stateCounts: Record<NextUpQueueItem["queueState"], number>;
62
+ };
63
+ };
64
+ type MilestoneBreakdownTask = {
65
+ id: string;
66
+ title: string;
67
+ status: string;
68
+ };
69
+ type MilestoneBreakdownEntry = {
70
+ id: string;
71
+ title: string;
72
+ tasks: MilestoneBreakdownTask[];
73
+ totalTasks: number;
74
+ doneTasks: number;
58
75
  };
59
76
  type RegisterMissionControlReadRoutesDeps<TRes> = {
60
77
  autoContinueRuns: Map<string, AutoContinueRunRecord>;
78
+ restoreAutoContinueRun?: (initiativeId: string) => Promise<AutoContinueRunRecord | null>;
61
79
  defaultAutoContinueTokenBudget: () => number | null;
62
80
  defaultAutoContinueMaxParallelSlices?: () => number;
63
81
  autoContinueTickMs: number;