@useorgx/openclaw-plugin 0.4.8 → 0.4.9

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 (125) hide show
  1. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  2. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  3. package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
  4. package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/activity-store.js +4 -18
  7. package/dist/agent-context-store.js +5 -25
  8. package/dist/agent-run-store.js +5 -25
  9. package/dist/agent-suite.js +1 -8
  10. package/dist/auth/flows.d.ts +47 -0
  11. package/dist/auth/flows.js +169 -0
  12. package/dist/auth-store.js +6 -26
  13. package/dist/byok-store.js +5 -19
  14. package/dist/cli/orgx.d.ts +66 -0
  15. package/dist/cli/orgx.js +91 -0
  16. package/dist/config/refresh.d.ts +32 -0
  17. package/dist/config/refresh.js +55 -0
  18. package/dist/config/resolution.d.ts +37 -0
  19. package/dist/config/resolution.js +178 -0
  20. package/dist/contracts/shared-types.d.ts +147 -0
  21. package/dist/contracts/shared-types.js +3 -0
  22. package/dist/contracts/types.d.ts +1 -134
  23. package/dist/contracts/types.js +5 -0
  24. package/dist/entities/auto-assignment.d.ts +36 -0
  25. package/dist/entities/auto-assignment.js +115 -0
  26. package/dist/entity-comment-store.js +5 -25
  27. package/dist/hash-utils.d.ts +2 -0
  28. package/dist/hash-utils.js +12 -0
  29. package/dist/http/helpers/activity-headline.d.ts +10 -0
  30. package/dist/http/helpers/activity-headline.js +192 -0
  31. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  32. package/dist/http/helpers/artifact-fallback.js +148 -0
  33. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  34. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  35. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  36. package/dist/http/helpers/autopilot-operations.js +403 -0
  37. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  38. package/dist/http/helpers/autopilot-runtime.js +319 -0
  39. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  40. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  41. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  42. package/dist/http/helpers/decision-mapper.js +44 -0
  43. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  44. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  45. package/dist/http/helpers/hash-utils.d.ts +1 -0
  46. package/dist/http/helpers/hash-utils.js +1 -0
  47. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  48. package/dist/http/helpers/kickoff-context.js +154 -0
  49. package/dist/http/helpers/mission-control.d.ts +94 -0
  50. package/dist/http/helpers/mission-control.js +894 -0
  51. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  52. package/dist/http/helpers/openclaw-cli.js +283 -0
  53. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  54. package/dist/http/helpers/runtime-sse.js +110 -0
  55. package/dist/http/helpers/value-utils.d.ts +6 -0
  56. package/dist/http/helpers/value-utils.js +67 -0
  57. package/dist/http/index.d.ts +88 -0
  58. package/dist/http/index.js +2353 -0
  59. package/dist/http/router.d.ts +23 -0
  60. package/dist/http/router.js +23 -0
  61. package/dist/http/routes/agent-control.d.ts +79 -0
  62. package/dist/http/routes/agent-control.js +684 -0
  63. package/dist/http/routes/agent-suite.d.ts +29 -0
  64. package/dist/http/routes/agent-suite.js +198 -0
  65. package/dist/http/routes/agents-catalog.d.ts +40 -0
  66. package/dist/http/routes/agents-catalog.js +83 -0
  67. package/dist/http/routes/billing.d.ts +23 -0
  68. package/dist/http/routes/billing.js +55 -0
  69. package/dist/http/routes/debug.d.ts +14 -0
  70. package/dist/http/routes/debug.js +21 -0
  71. package/dist/http/routes/decision-actions.d.ts +13 -0
  72. package/dist/http/routes/decision-actions.js +66 -0
  73. package/dist/http/routes/delegation.d.ts +19 -0
  74. package/dist/http/routes/delegation.js +32 -0
  75. package/dist/http/routes/entities.d.ts +47 -0
  76. package/dist/http/routes/entities.js +152 -0
  77. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  78. package/dist/http/routes/entity-dynamic.js +191 -0
  79. package/dist/http/routes/health.d.ts +22 -0
  80. package/dist/http/routes/health.js +49 -0
  81. package/dist/http/routes/live-legacy.d.ts +110 -0
  82. package/dist/http/routes/live-legacy.js +598 -0
  83. package/dist/http/routes/live-misc.d.ts +69 -0
  84. package/dist/http/routes/live-misc.js +206 -0
  85. package/dist/http/routes/live-snapshot.d.ts +90 -0
  86. package/dist/http/routes/live-snapshot.js +297 -0
  87. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  88. package/dist/http/routes/mission-control-actions.js +541 -0
  89. package/dist/http/routes/mission-control-read.d.ts +28 -0
  90. package/dist/http/routes/mission-control-read.js +67 -0
  91. package/dist/http/routes/onboarding.d.ts +34 -0
  92. package/dist/http/routes/onboarding.js +101 -0
  93. package/dist/http/routes/run-control.d.ts +24 -0
  94. package/dist/http/routes/run-control.js +86 -0
  95. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  96. package/dist/http/routes/runtime-hooks.js +437 -0
  97. package/dist/http/routes/settings-byok.d.ts +23 -0
  98. package/dist/http/routes/settings-byok.js +163 -0
  99. package/dist/http/routes/summary.d.ts +18 -0
  100. package/dist/http/routes/summary.js +42 -0
  101. package/dist/http/routes/work-artifacts.d.ts +9 -0
  102. package/dist/http/routes/work-artifacts.js +36 -0
  103. package/dist/http/shared-state.d.ts +16 -0
  104. package/dist/http/shared-state.js +1 -0
  105. package/dist/http-handler.d.ts +1 -88
  106. package/dist/http-handler.js +1 -10605
  107. package/dist/index.js +108 -2243
  108. package/dist/json-utils.d.ts +1 -0
  109. package/dist/json-utils.js +8 -0
  110. package/dist/next-up-queue-store.js +4 -18
  111. package/dist/runtime-instance-store.js +5 -31
  112. package/dist/services/background.d.ts +23 -0
  113. package/dist/services/background.js +23 -0
  114. package/dist/services/instrumentation.d.ts +29 -0
  115. package/dist/services/instrumentation.js +136 -0
  116. package/dist/snapshot-store.js +5 -25
  117. package/dist/stores/json-store.d.ts +11 -0
  118. package/dist/stores/json-store.js +42 -0
  119. package/dist/sync/outbox-replay.d.ts +55 -0
  120. package/dist/sync/outbox-replay.js +514 -0
  121. package/dist/tools/core-tools.d.ts +76 -0
  122. package/dist/tools/core-tools.js +1005 -0
  123. package/package.json +1 -1
  124. package/dashboard/dist/assets/BzkiMPmM.js +0 -215
  125. package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
@@ -0,0 +1,206 @@
1
+ export function registerLiveMiscRoutes(router, deps) {
2
+ router.add("POST", "live/activity/headline", async ({ req, res }) => {
3
+ try {
4
+ const payload = await deps.parseJsonRequest(req);
5
+ const text = deps.pickString(payload, ["text", "summary", "detail", "content"]);
6
+ if (!text) {
7
+ deps.sendJson(res, 400, { error: "text is required" });
8
+ return;
9
+ }
10
+ const title = deps.pickString(payload, ["title", "name"]);
11
+ const type = deps.pickString(payload, ["type", "kind"]);
12
+ const result = await deps.summarizeActivityHeadline({
13
+ text,
14
+ title,
15
+ type,
16
+ });
17
+ deps.sendJson(res, 200, {
18
+ headline: result.headline,
19
+ source: result.source,
20
+ model: result.model,
21
+ });
22
+ }
23
+ catch (err) {
24
+ deps.sendJson(res, 500, {
25
+ error: deps.safeErrorMessage(err),
26
+ });
27
+ }
28
+ }, "Summarize an activity headline");
29
+ router.add("*", "live/activity/headline", ({ res }) => {
30
+ deps.sendJson(res, 405, { error: "Use POST /orgx/api/live/activity/headline" });
31
+ }, "Reject unsupported methods for live/activity/headline");
32
+ async function renderLiveAgents(query, res) {
33
+ try {
34
+ const initiative = query.get("initiative");
35
+ const includeIdleRaw = query.get("include_idle");
36
+ const includeIdle = includeIdleRaw === null ? undefined : includeIdleRaw !== "false";
37
+ const data = await deps.getLiveAgents({
38
+ initiative,
39
+ includeIdle,
40
+ });
41
+ deps.sendJson(res, 200, data);
42
+ }
43
+ catch (err) {
44
+ try {
45
+ const initiative = query.get("initiative");
46
+ const includeIdleRaw = query.get("include_idle");
47
+ const includeIdle = includeIdleRaw === null ? undefined : includeIdleRaw !== "false";
48
+ const localSnapshot = await deps.loadLocalOpenClawSnapshot(240);
49
+ const local = deps.toLocalLiveAgents(localSnapshot);
50
+ let agents = local.agents;
51
+ if (initiative && initiative.trim().length > 0) {
52
+ agents = agents.filter((agent) => agent.initiativeId === initiative);
53
+ }
54
+ if (includeIdle === false) {
55
+ agents = agents.filter((agent) => agent.status !== "idle");
56
+ }
57
+ const summary = agents.reduce((acc, agent) => {
58
+ acc[agent.status] = (acc[agent.status] ?? 0) + 1;
59
+ return acc;
60
+ }, {});
61
+ deps.sendJson(res, 200, { agents, summary });
62
+ }
63
+ catch (localErr) {
64
+ deps.sendJson(res, 500, {
65
+ error: deps.safeErrorMessage(err),
66
+ localFallbackError: deps.safeErrorMessage(localErr),
67
+ });
68
+ }
69
+ }
70
+ }
71
+ router.add("GET", "live/agents", async ({ query, res }) => renderLiveAgents(query, res), "Get live agents");
72
+ router.add("HEAD", "live/agents", async ({ query, res }) => renderLiveAgents(query, res), "Get live agents (HEAD)");
73
+ async function renderLiveInitiatives(query, res) {
74
+ try {
75
+ const id = query.get("id");
76
+ const limit = query.get("limit") ? Number(query.get("limit")) : undefined;
77
+ const data = await deps.getLiveInitiatives({
78
+ id,
79
+ limit: Number.isFinite(limit) ? limit : undefined,
80
+ });
81
+ const payload = data;
82
+ const initiatives = Array.isArray(payload.initiatives)
83
+ ? payload.initiatives.map((entry) => {
84
+ if (!entry || typeof entry !== "object")
85
+ return entry;
86
+ const row = entry;
87
+ const initiativeId = deps.pickString(row, ["id"]);
88
+ if (!initiativeId)
89
+ return entry;
90
+ const override = deps.localInitiativeStatusOverrides.get(initiativeId) ?? null;
91
+ if (!override)
92
+ return entry;
93
+ return {
94
+ ...row,
95
+ status: override.status,
96
+ updatedAt: deps.pickString(row, ["updatedAt", "updated_at"]) ?? override.updatedAt,
97
+ };
98
+ })
99
+ : payload.initiatives;
100
+ deps.sendJson(res, 200, {
101
+ ...payload,
102
+ initiatives,
103
+ });
104
+ }
105
+ catch (err) {
106
+ try {
107
+ const id = query.get("id");
108
+ const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
109
+ const limit = Number.isFinite(limitRaw) ? Math.max(1, Number(limitRaw)) : 100;
110
+ const local = deps.toLocalLiveInitiatives(await deps.loadLocalOpenClawSnapshot(240));
111
+ let initiatives = local.initiatives;
112
+ if (id && id.trim().length > 0) {
113
+ initiatives = initiatives.filter((item) => item.id === id);
114
+ }
115
+ initiatives = initiatives.map((item) => {
116
+ const override = deps.localInitiativeStatusOverrides.get(item.id) ?? null;
117
+ if (!override)
118
+ return item;
119
+ return {
120
+ ...item,
121
+ status: override.status,
122
+ updatedAt: item.updatedAt ?? override.updatedAt,
123
+ };
124
+ });
125
+ const requestedId = id?.trim() ?? "";
126
+ if (requestedId.length > 0) {
127
+ const override = deps.localInitiativeStatusOverrides.get(requestedId) ?? null;
128
+ if (override && !initiatives.some((item) => item.id === requestedId)) {
129
+ initiatives.push({
130
+ id: requestedId,
131
+ title: `Initiative ${requestedId.slice(0, 8)}`,
132
+ status: override.status,
133
+ updatedAt: override.updatedAt,
134
+ sessionCount: 0,
135
+ activeAgents: 0,
136
+ });
137
+ }
138
+ }
139
+ else {
140
+ for (const [initiativeId, override] of deps.localInitiativeStatusOverrides.entries()) {
141
+ if (initiatives.some((item) => item.id === initiativeId))
142
+ continue;
143
+ initiatives.push({
144
+ id: initiativeId,
145
+ title: `Initiative ${initiativeId.slice(0, 8)}`,
146
+ status: override.status,
147
+ updatedAt: override.updatedAt,
148
+ sessionCount: 0,
149
+ activeAgents: 0,
150
+ });
151
+ }
152
+ }
153
+ deps.sendJson(res, 200, {
154
+ initiatives: initiatives.slice(0, limit),
155
+ total: initiatives.length,
156
+ localFallback: true,
157
+ warning: deps.safeErrorMessage(err),
158
+ });
159
+ }
160
+ catch (localErr) {
161
+ deps.sendJson(res, 500, {
162
+ error: deps.safeErrorMessage(err),
163
+ localFallbackError: deps.safeErrorMessage(localErr),
164
+ });
165
+ }
166
+ }
167
+ }
168
+ router.add("GET", "live/initiatives", async ({ query, res }) => renderLiveInitiatives(query, res), "Get live initiatives");
169
+ router.add("HEAD", "live/initiatives", async ({ query, res }) => renderLiveInitiatives(query, res), "Get live initiatives (HEAD)");
170
+ async function renderLiveDecisions(query, res) {
171
+ try {
172
+ const status = query.get("status") ?? "pending";
173
+ const limit = query.get("limit") ? Number(query.get("limit")) : 100;
174
+ const data = await deps.getLiveDecisions({
175
+ status,
176
+ limit: Number.isFinite(limit) ? limit : 100,
177
+ });
178
+ const decisions = data.decisions
179
+ .map(deps.mapDecisionEntity)
180
+ .sort((a, b) => b.waitingMinutes - a.waitingMinutes);
181
+ deps.sendJson(res, 200, {
182
+ decisions,
183
+ total: data.total,
184
+ });
185
+ }
186
+ catch {
187
+ deps.sendJson(res, 200, {
188
+ decisions: [],
189
+ total: 0,
190
+ });
191
+ }
192
+ }
193
+ router.add("GET", "live/decisions", async ({ query, res }) => renderLiveDecisions(query, res), "Get live decisions");
194
+ router.add("HEAD", "live/decisions", async ({ query, res }) => renderLiveDecisions(query, res), "Get live decisions (HEAD)");
195
+ async function renderHandoffs(res) {
196
+ try {
197
+ const data = await deps.getHandoffs();
198
+ deps.sendJson(res, 200, data);
199
+ }
200
+ catch {
201
+ deps.sendJson(res, 200, { handoffs: [] });
202
+ }
203
+ }
204
+ router.add("GET", "handoffs", async ({ res }) => renderHandoffs(res), "Get handoffs");
205
+ router.add("HEAD", "handoffs", async ({ res }) => renderHandoffs(res), "Get handoffs (HEAD)");
206
+ }
@@ -0,0 +1,90 @@
1
+ import type { HandoffSummary, LiveActivityItem, SessionTreeResponse } from "../../types.js";
2
+ import type { RuntimeInstanceRecord } from "../../runtime-instance-store.js";
3
+ import type { OutboxSummary } from "../../outbox.js";
4
+ import type { AgentLaunchContext, RunLaunchContext } from "../../agent-context-store.js";
5
+ import type { Router } from "../router.js";
6
+ type LocalSnapshot = Awaited<ReturnType<typeof import("../../local-openclaw.js").loadLocalOpenClawSnapshot>>;
7
+ type AgentContextState = {
8
+ agents: Record<string, AgentLaunchContext>;
9
+ runs?: Record<string, RunLaunchContext>;
10
+ };
11
+ type SnapshotPersistState = {
12
+ lastFingerprint: string;
13
+ lastPersistAt: number;
14
+ };
15
+ type LiveSnapshotRoutesDeps<TRes> = {
16
+ parsePositiveInt: (raw: string | null, fallback: number) => number;
17
+ readSnapshotResponseCache: (key: string) => Record<string, unknown> | null;
18
+ writeSnapshotResponseCache: (key: string, payload: Record<string, unknown>) => void;
19
+ safeErrorMessage: (err: unknown) => string;
20
+ readAgentContexts: () => AgentContextState;
21
+ getScopedAgentIds: (contexts: Record<string, AgentLaunchContext>) => Set<string>;
22
+ readDiagnosticsOutboxStatus: () => Promise<Record<string, unknown> | null>;
23
+ readOutboxSummary: () => Promise<OutboxSummary>;
24
+ readOutboxItems: () => Promise<LiveActivityItem[]>;
25
+ loadLocalOpenClawSnapshot: (limit: number) => Promise<LocalSnapshot>;
26
+ toLocalSessionTree: (snapshot: LocalSnapshot, limit?: number) => SessionTreeResponse;
27
+ toLocalLiveActivity: (snapshot: LocalSnapshot, limit?: number) => Promise<{
28
+ activities: LiveActivityItem[];
29
+ total: number;
30
+ }>;
31
+ toLocalLiveAgents: (snapshot: LocalSnapshot) => {
32
+ agents: Array<{
33
+ initiativeId: string | null;
34
+ status: string;
35
+ } & Record<string, unknown>>;
36
+ };
37
+ getLiveSessions: (input: {
38
+ initiative: string | null;
39
+ limit: number;
40
+ }) => Promise<SessionTreeResponse>;
41
+ getLiveActivity: (input: {
42
+ run: string | null;
43
+ since: string | null;
44
+ limit: number;
45
+ }) => Promise<{
46
+ activities: LiveActivityItem[];
47
+ }>;
48
+ getHandoffs: () => Promise<{
49
+ handoffs: HandoffSummary[];
50
+ }>;
51
+ getLiveDecisions: (input: {
52
+ status: string;
53
+ limit: number;
54
+ }) => Promise<{
55
+ decisions: unknown[];
56
+ }>;
57
+ getLiveAgents: (input: {
58
+ initiative: string | null;
59
+ includeIdle: boolean | undefined;
60
+ }) => Promise<{
61
+ agents?: unknown[];
62
+ }>;
63
+ mapDecisionEntity: (entry: unknown) => Record<string, unknown> & {
64
+ waitingMinutes: number;
65
+ };
66
+ applyAgentContextsToSessionTree: (input: SessionTreeResponse, contexts: {
67
+ agents: Record<string, AgentLaunchContext>;
68
+ runs: Record<string, RunLaunchContext>;
69
+ }) => SessionTreeResponse;
70
+ applyAgentContextsToActivity: (input: LiveActivityItem[], contexts: {
71
+ agents: Record<string, AgentLaunchContext>;
72
+ runs: Record<string, RunLaunchContext>;
73
+ }) => LiveActivityItem[];
74
+ mergeSessionTrees: (base: SessionTreeResponse, extra: SessionTreeResponse) => SessionTreeResponse;
75
+ mergeActivities: (base: LiveActivityItem[], extra: LiveActivityItem[], limit: number) => LiveActivityItem[];
76
+ listRuntimeInstances: (input: {
77
+ limit: number;
78
+ }) => RuntimeInstanceRecord[];
79
+ injectRuntimeInstancesAsSessions: (input: SessionTreeResponse, instances: RuntimeInstanceRecord[]) => SessionTreeResponse;
80
+ enrichSessionsWithRuntime: (input: SessionTreeResponse, instances: RuntimeInstanceRecord[]) => SessionTreeResponse;
81
+ enrichActivityWithRuntime: (input: LiveActivityItem[], instances: RuntimeInstanceRecord[]) => LiveActivityItem[];
82
+ snapshotActivityFingerprint: (items: LiveActivityItem[]) => string;
83
+ appendActivityItems: (items: LiveActivityItem[]) => void;
84
+ snapshotActivityPersistMinIntervalMs: number;
85
+ readSnapshotPersistState: () => SnapshotPersistState;
86
+ writeSnapshotPersistState: (state: SnapshotPersistState) => void;
87
+ sendJson: (res: TRes, status: number, payload: unknown) => void;
88
+ };
89
+ export declare function registerLiveSnapshotRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: LiveSnapshotRoutesDeps<TRes>): void;
90
+ export {};
@@ -0,0 +1,297 @@
1
+ function outboxStatusFromSummary(summary) {
2
+ return {
3
+ pendingTotal: summary.pendingTotal,
4
+ pendingByQueue: summary.pendingByQueue,
5
+ oldestEventAt: summary.oldestEventAt,
6
+ newestEventAt: summary.newestEventAt,
7
+ replayStatus: "idle",
8
+ lastReplayAttemptAt: null,
9
+ lastReplaySuccessAt: null,
10
+ lastReplayFailureAt: null,
11
+ lastReplayError: null,
12
+ };
13
+ }
14
+ function emptyOutboxStatus() {
15
+ return {
16
+ pendingTotal: 0,
17
+ pendingByQueue: {},
18
+ oldestEventAt: null,
19
+ newestEventAt: null,
20
+ replayStatus: "idle",
21
+ lastReplayAttemptAt: null,
22
+ lastReplaySuccessAt: null,
23
+ lastReplayFailureAt: null,
24
+ lastReplayError: null,
25
+ };
26
+ }
27
+ function filterSessionsByInitiative(sessions, initiative) {
28
+ if (!initiative || initiative.trim().length === 0)
29
+ return sessions;
30
+ const filteredNodes = sessions.nodes.filter((node) => node.initiativeId === initiative || node.groupId === initiative);
31
+ const filteredIds = new Set(filteredNodes.map((node) => node.id));
32
+ const filteredGroupIds = new Set(filteredNodes.map((node) => node.groupId));
33
+ return {
34
+ nodes: filteredNodes,
35
+ edges: sessions.edges.filter((edge) => filteredIds.has(edge.parentId) && filteredIds.has(edge.childId)),
36
+ groups: sessions.groups.filter((group) => filteredGroupIds.has(group.id)),
37
+ };
38
+ }
39
+ function maybeFilterActivity(items, input) {
40
+ let filtered = items;
41
+ if (input.run && input.run.trim().length > 0) {
42
+ filtered = filtered.filter((item) => item.runId === input.run);
43
+ }
44
+ if (input.since && input.since.trim().length > 0) {
45
+ const sinceEpoch = Date.parse(input.since);
46
+ if (Number.isFinite(sinceEpoch)) {
47
+ filtered = filtered.filter((item) => Date.parse(item.timestamp) >= sinceEpoch);
48
+ }
49
+ }
50
+ return filtered;
51
+ }
52
+ export function registerLiveSnapshotRoutes(router, deps) {
53
+ async function renderSnapshot(path, query, res) {
54
+ const sessionsLimit = deps.parsePositiveInt(query.get("sessionsLimit") ?? query.get("sessions_limit"), 320);
55
+ const activityLimit = deps.parsePositiveInt(query.get("activityLimit") ?? query.get("activity_limit"), 600);
56
+ const decisionsLimit = deps.parsePositiveInt(query.get("decisionsLimit") ?? query.get("decisions_limit"), 120);
57
+ const initiative = query.get("initiative");
58
+ const run = query.get("run");
59
+ const since = query.get("since");
60
+ const decisionStatus = query.get("status") ?? "pending";
61
+ const includeIdleRaw = query.get("include_idle");
62
+ 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;
68
+ }
69
+ const degraded = [];
70
+ const contextStore = deps.readAgentContexts();
71
+ const agentContexts = contextStore.agents;
72
+ const runContexts = contextStore.runs ?? {};
73
+ const scopedAgentIds = deps.getScopedAgentIds(agentContexts);
74
+ let outboxStatus;
75
+ try {
76
+ const diagnosticsOutbox = await deps.readDiagnosticsOutboxStatus();
77
+ if (diagnosticsOutbox) {
78
+ outboxStatus = diagnosticsOutbox;
79
+ }
80
+ else {
81
+ outboxStatus = outboxStatusFromSummary(await deps.readOutboxSummary());
82
+ }
83
+ }
84
+ catch (err) {
85
+ degraded.push(`outbox status unavailable (${deps.safeErrorMessage(err)})`);
86
+ outboxStatus = emptyOutboxStatus();
87
+ }
88
+ let localSnapshot = null;
89
+ const ensureLocalSnapshot = async (minimumLimit) => {
90
+ if (!localSnapshot || localSnapshot.sessions.length < minimumLimit) {
91
+ localSnapshot = await deps.loadLocalOpenClawSnapshot(minimumLimit);
92
+ }
93
+ return localSnapshot;
94
+ };
95
+ const settled = await Promise.allSettled([
96
+ deps.getLiveSessions({
97
+ initiative,
98
+ limit: sessionsLimit,
99
+ }),
100
+ deps.getLiveActivity({
101
+ run,
102
+ since,
103
+ limit: activityLimit,
104
+ }),
105
+ deps.getHandoffs(),
106
+ deps.getLiveDecisions({
107
+ status: decisionStatus,
108
+ limit: decisionsLimit,
109
+ }),
110
+ deps.getLiveAgents({
111
+ initiative,
112
+ includeIdle,
113
+ }),
114
+ ]);
115
+ let sessions = {
116
+ nodes: [],
117
+ edges: [],
118
+ groups: [],
119
+ };
120
+ const sessionsResult = settled[0];
121
+ if (sessionsResult.status === "fulfilled") {
122
+ sessions = sessionsResult.value;
123
+ }
124
+ else {
125
+ degraded.push(`sessions unavailable (${deps.safeErrorMessage(sessionsResult.reason)})`);
126
+ try {
127
+ let local = deps.toLocalSessionTree(await ensureLocalSnapshot(Math.max(sessionsLimit, 200)), sessionsLimit);
128
+ local = deps.applyAgentContextsToSessionTree(local, {
129
+ agents: agentContexts,
130
+ runs: runContexts,
131
+ });
132
+ sessions = filterSessionsByInitiative(local, initiative);
133
+ }
134
+ catch (localErr) {
135
+ degraded.push(`sessions local fallback failed (${deps.safeErrorMessage(localErr)})`);
136
+ }
137
+ }
138
+ let activity = [];
139
+ const activityResult = settled[1];
140
+ if (activityResult.status === "fulfilled") {
141
+ activity = Array.isArray(activityResult.value.activities)
142
+ ? activityResult.value.activities
143
+ : [];
144
+ }
145
+ else {
146
+ degraded.push(`activity unavailable (${deps.safeErrorMessage(activityResult.reason)})`);
147
+ try {
148
+ const local = await deps.toLocalLiveActivity(await ensureLocalSnapshot(Math.max(activityLimit, 240)), Math.max(activityLimit, 240));
149
+ const filtered = maybeFilterActivity(local.activities, { run, since });
150
+ const withContexts = deps.applyAgentContextsToActivity(filtered, {
151
+ agents: agentContexts,
152
+ runs: runContexts,
153
+ });
154
+ activity = withContexts.slice(0, activityLimit);
155
+ }
156
+ catch (localErr) {
157
+ degraded.push(`activity local fallback failed (${deps.safeErrorMessage(localErr)})`);
158
+ }
159
+ }
160
+ let handoffs = [];
161
+ const handoffsResult = settled[2];
162
+ if (handoffsResult.status === "fulfilled") {
163
+ handoffs = Array.isArray(handoffsResult.value.handoffs)
164
+ ? handoffsResult.value.handoffs
165
+ : [];
166
+ }
167
+ else {
168
+ degraded.push(`handoffs unavailable (${deps.safeErrorMessage(handoffsResult.reason)})`);
169
+ }
170
+ let decisions = [];
171
+ const decisionsResult = settled[3];
172
+ if (decisionsResult.status === "fulfilled") {
173
+ decisions = decisionsResult.value.decisions
174
+ .map(deps.mapDecisionEntity)
175
+ .sort((a, b) => b.waitingMinutes - a.waitingMinutes);
176
+ }
177
+ else {
178
+ degraded.push(`decisions unavailable (${deps.safeErrorMessage(decisionsResult.reason)})`);
179
+ }
180
+ let agents = [];
181
+ const agentsResult = settled[4];
182
+ if (agentsResult.status === "fulfilled") {
183
+ agents = Array.isArray(agentsResult.value.agents)
184
+ ? agentsResult.value.agents
185
+ : [];
186
+ }
187
+ else {
188
+ degraded.push(`agents unavailable (${deps.safeErrorMessage(agentsResult.reason)})`);
189
+ try {
190
+ const local = deps.toLocalLiveAgents(await ensureLocalSnapshot(Math.max(sessionsLimit, 240)));
191
+ let localAgents = local.agents;
192
+ if (initiative && initiative.trim().length > 0) {
193
+ localAgents = localAgents.filter((agent) => agent.initiativeId === initiative);
194
+ }
195
+ if (includeIdle === false) {
196
+ localAgents = localAgents.filter((agent) => agent.status !== "idle");
197
+ }
198
+ agents = localAgents;
199
+ }
200
+ catch (localErr) {
201
+ degraded.push(`agents local fallback failed (${deps.safeErrorMessage(localErr)})`);
202
+ }
203
+ }
204
+ if (scopedAgentIds.size > 0) {
205
+ try {
206
+ const minimum = Math.max(Math.max(sessionsLimit, activityLimit), 240);
207
+ const snapshot = await ensureLocalSnapshot(minimum);
208
+ const scopedSnapshot = {
209
+ ...snapshot,
210
+ sessions: snapshot.sessions.filter((session) => Boolean(session.agentId && scopedAgentIds.has(session.agentId))),
211
+ agents: snapshot.agents.filter((agent) => scopedAgentIds.has(agent.id)),
212
+ };
213
+ let localSessions = deps.applyAgentContextsToSessionTree(deps.toLocalSessionTree(scopedSnapshot, sessionsLimit), { agents: agentContexts, runs: runContexts });
214
+ localSessions = filterSessionsByInitiative(localSessions, initiative);
215
+ sessions = deps.mergeSessionTrees(sessions, localSessions);
216
+ const localActivity = await deps.toLocalLiveActivity(scopedSnapshot, Math.max(activityLimit, 240));
217
+ let localItems = deps.applyAgentContextsToActivity(localActivity.activities, {
218
+ agents: agentContexts,
219
+ runs: runContexts,
220
+ });
221
+ localItems = maybeFilterActivity(localItems, { run, since });
222
+ activity = deps.mergeActivities(activity, localItems, activityLimit);
223
+ }
224
+ catch (err) {
225
+ degraded.push(`local agent merge failed (${deps.safeErrorMessage(err)})`);
226
+ }
227
+ }
228
+ try {
229
+ const buffered = await deps.readOutboxItems();
230
+ if (buffered.length > 0) {
231
+ const merged = [...activity, ...buffered]
232
+ .sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp))
233
+ .slice(0, activityLimit);
234
+ const deduped = [];
235
+ const seen = new Set();
236
+ for (const item of merged) {
237
+ if (seen.has(item.id))
238
+ continue;
239
+ seen.add(item.id);
240
+ deduped.push(item);
241
+ }
242
+ activity = deduped;
243
+ }
244
+ }
245
+ catch (err) {
246
+ degraded.push(`outbox unavailable (${deps.safeErrorMessage(err)})`);
247
+ }
248
+ let runtimeInstances = deps.listRuntimeInstances({ limit: 320 });
249
+ if (initiative && initiative.trim().length > 0) {
250
+ runtimeInstances = runtimeInstances.filter((instance) => instance.initiativeId === initiative);
251
+ }
252
+ if (run && run.trim().length > 0) {
253
+ runtimeInstances = runtimeInstances.filter((instance) => instance.runId === run || instance.correlationId === run);
254
+ }
255
+ sessions = deps.injectRuntimeInstancesAsSessions(sessions, runtimeInstances);
256
+ sessions = deps.enrichSessionsWithRuntime(sessions, runtimeInstances);
257
+ activity = deps.enrichActivityWithRuntime(activity, runtimeInstances);
258
+ activity = deps.applyAgentContextsToActivity(activity, {
259
+ agents: agentContexts,
260
+ runs: runContexts,
261
+ });
262
+ try {
263
+ const fingerprint = deps.snapshotActivityFingerprint(activity);
264
+ const now = Date.now();
265
+ const persistState = deps.readSnapshotPersistState();
266
+ const shouldPersist = fingerprint !== persistState.lastFingerprint ||
267
+ now - persistState.lastPersistAt >= deps.snapshotActivityPersistMinIntervalMs;
268
+ if (shouldPersist) {
269
+ deps.appendActivityItems(activity);
270
+ deps.writeSnapshotPersistState({
271
+ lastFingerprint: fingerprint,
272
+ lastPersistAt: now,
273
+ });
274
+ }
275
+ }
276
+ catch {
277
+ // best effort
278
+ }
279
+ const payload = {
280
+ sessions,
281
+ activity,
282
+ handoffs,
283
+ decisions,
284
+ agents,
285
+ runtimeInstances,
286
+ outbox: outboxStatus,
287
+ generatedAt: new Date().toISOString(),
288
+ degraded: degraded.length > 0 ? degraded : undefined,
289
+ };
290
+ deps.writeSnapshotResponseCache(snapshotCacheKey, payload);
291
+ deps.sendJson(res, 200, payload);
292
+ }
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)");
297
+ }
@@ -0,0 +1,83 @@
1
+ import type { Router } from "../router.js";
2
+ type JsonRecord = Record<string, unknown>;
3
+ type AutoContinueRunRecord = Record<string, any> & {
4
+ activeRunId?: string | null;
5
+ stopReason?: string | null;
6
+ status?: string;
7
+ stopRequested?: boolean;
8
+ updatedAt?: string;
9
+ };
10
+ type NextUpQueue = {
11
+ items: Array<{
12
+ workstreamId: string;
13
+ runnerAgentId?: string | null;
14
+ runnerAgentName?: string | null;
15
+ runnerSource?: string | null;
16
+ initiativeTitle?: string | null;
17
+ workstreamTitle?: string | null;
18
+ nextTaskId?: string | null;
19
+ nextTaskTitle?: string | null;
20
+ }>;
21
+ degraded: string[];
22
+ };
23
+ type RegisterMissionControlActionsRoutesDeps<TReq, TRes> = {
24
+ parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
25
+ pickString: (input: Record<string, unknown>, keys: string[]) => string | null;
26
+ pickNumber: (input: Record<string, unknown>, keys: string[]) => number | null;
27
+ parseBooleanQuery: (value: string | null) => boolean | null;
28
+ pickStringArray: (input: Record<string, unknown>, keys: string[]) => string[];
29
+ dedupeStrings: (values: string[]) => string[];
30
+ resolveAgentDisplayName: (agentId: string, fallbackName: string | null) => Promise<string | null>;
31
+ buildNextUpQueue: (input: {
32
+ initiativeId: string;
33
+ }) => Promise<NextUpQueue>;
34
+ startAutoContinueRun: (input: any) => Promise<AutoContinueRunRecord>;
35
+ autoContinueRuns: Map<string, any>;
36
+ autoContinueSliceRuns: Map<string, any>;
37
+ dispatchFallbackWorkstreamTurn: (input: any) => Promise<{
38
+ sessionId: string | null;
39
+ pid: number | null;
40
+ blockedReason: string | null;
41
+ retryable: boolean;
42
+ executionPolicy: {
43
+ domain: string;
44
+ requiredSkills: string[];
45
+ };
46
+ spawnGuardResult: unknown | null;
47
+ }>;
48
+ tickAutoContinueRun: (run: any) => Promise<void>;
49
+ stopAutoContinueRun: (input: any) => Promise<void>;
50
+ updateInitiativeAutoContinueState: (input: any) => Promise<void>;
51
+ tickAllAutoContinue: () => Promise<void>;
52
+ upsertNextUpQueuePin: (input: {
53
+ initiativeId: string;
54
+ workstreamId: string;
55
+ preferredTaskId: string | null;
56
+ preferredMilestoneId: string | null;
57
+ }) => {
58
+ pins: unknown[];
59
+ updatedAt: string;
60
+ };
61
+ removeNextUpQueuePin: (input: {
62
+ initiativeId: string;
63
+ workstreamId: string;
64
+ }) => {
65
+ pins: unknown[];
66
+ updatedAt: string;
67
+ };
68
+ setNextUpQueuePinOrder: (input: {
69
+ order: Array<{
70
+ initiativeId: string;
71
+ workstreamId: string;
72
+ }>;
73
+ }) => {
74
+ pins: unknown[];
75
+ updatedAt: string;
76
+ };
77
+ resolveAutoAssignments: (input: any) => Promise<unknown>;
78
+ client: any;
79
+ sendJson: (res: TRes, status: number, payload: unknown) => void;
80
+ safeErrorMessage: (err: unknown) => string;
81
+ };
82
+ export declare function registerMissionControlActionsRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterMissionControlActionsRoutesDeps<TReq, TRes>): void;
83
+ export {};