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