@useorgx/openclaw-plugin 0.4.9 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -11
- package/dashboard/dist/assets/6mILZQ2a.js +1 -0
- package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
- package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
- package/dashboard/dist/assets/8dksYiq4.js +2 -0
- package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
- package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
- package/dashboard/dist/assets/B5zYRHc3.js +1 -0
- package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
- package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
- package/dashboard/dist/assets/B6wPWJ35.js +1 -0
- package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
- package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -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/BWEwjt1W.js +1 -0
- package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
- package/dashboard/dist/assets/BWEwjt1W.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/BzRbDCAD.css +1 -0
- package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
- package/dashboard/dist/assets/BzRbDCAD.css.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/C8uM3AX8.js +1 -0
- package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
- package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
- package/dashboard/dist/assets/C9jy61eu.js +212 -0
- package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
- package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
- package/dashboard/dist/assets/CC63EwFD.js +1 -0
- package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
- package/dashboard/dist/assets/CC63EwFD.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/CZaT3ob_.js +1 -0
- package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
- package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
- package/dashboard/dist/assets/CgaottFX.js +1 -0
- package/dashboard/dist/assets/CgaottFX.js.br +0 -0
- package/dashboard/dist/assets/CgaottFX.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/CzCxAZlW.js +1 -0
- package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
- package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js +1 -0
- package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
- package/dashboard/dist/assets/D8JNX8kq.js +2 -0
- package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
- package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js +1 -0
- package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
- package/dashboard/dist/assets/IUexzymk.js +1 -0
- package/dashboard/dist/assets/IUexzymk.js.br +0 -0
- package/dashboard/dist/assets/IUexzymk.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/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/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/rttbDbEx.js +1 -0
- package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
- package/dashboard/dist/assets/rttbDbEx.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/practice-exercise-schema.d.ts +216 -0
- package/dist/contracts/practice-exercise-schema.js +314 -0
- 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 +199 -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 +247 -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/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 +3145 -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 +328 -25
- package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
- package/dist/http/helpers/autopilot-slice-utils.js +514 -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 +104 -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 +877 -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 +1420 -105
- package/dist/http/routes/agent-suite.d.ts +9 -0
- package/dist/http/routes/agent-suite.js +294 -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 +232 -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 +49 -2
- package/dist/http/routes/live-snapshot.js +653 -23
- package/dist/http/routes/live-terminal.d.ts +11 -0
- package/dist/http/routes/live-terminal.js +154 -0
- package/dist/http/routes/live-triage.d.ts +61 -0
- package/dist/http/routes/live-triage.js +192 -0
- package/dist/http/routes/mission-control-actions.d.ts +49 -1
- package/dist/http/routes/mission-control-actions.js +1246 -84
- package/dist/http/routes/mission-control-read.d.ts +48 -3
- package/dist/http/routes/mission-control-read.js +1658 -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.d.ts +3 -0
- package/dist/mcp-http-handler.js +34 -60
- 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 +36 -5
- package/dist/reporting/rollups.d.ts +41 -0
- package/dist/reporting/rollups.js +113 -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 +20 -6
- 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
package/dist/http/index.js
CHANGED
|
@@ -22,20 +22,22 @@ import { homedir } from "node:os";
|
|
|
22
22
|
import { join, dirname, extname, normalize, resolve, relative, sep } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { randomUUID } from "node:crypto";
|
|
25
|
-
import { readNextUpQueuePins, removeNextUpQueuePin, setNextUpQueuePinOrder, upsertNextUpQueuePin, } from "../next-up-queue-store.js";
|
|
25
|
+
import { readNextUpQueuePins, removeNextUpQueuePin, setNextUpQueuePinOrder, suppressNextUpQueueItem, upsertNextUpQueuePin, } from "../next-up-queue-store.js";
|
|
26
26
|
import { formatStatus, formatAgents, formatActivity, formatInitiatives, getOnboardingState, } from "../dashboard-api.js";
|
|
27
27
|
import { loadLocalOpenClawSnapshot, loadLocalTurnDetail, toLocalLiveActivity, toLocalLiveAgents, toLocalLiveInitiatives, toLocalSessionTree, } from "../local-openclaw.js";
|
|
28
28
|
import { defaultOutboxAdapter } from "../adapters/outbox.js";
|
|
29
|
+
import { appendToOutbox } from "../outbox.js";
|
|
29
30
|
import { readAgentContexts, upsertAgentContext, upsertRunContext } from "../agent-context-store.js";
|
|
30
31
|
import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from "../agent-run-store.js";
|
|
31
32
|
import { appendEntityComment, listEntityComments, mergeEntityComments, } from "../entity-comment-store.js";
|
|
33
|
+
import { listChatThreads } from "../chat-store.js";
|
|
32
34
|
import { appendActivityItems, listActivityPage, } from "../activity-store.js";
|
|
33
35
|
import { enrichActivityActorFields } from "../activity-actor-fields.js";
|
|
34
36
|
import { readByokKeys, writeByokKeys } from "../byok-store.js";
|
|
35
37
|
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan, generateAgentSuiteOperationId, } from "../agent-suite.js";
|
|
36
38
|
import { listRuntimeInstances, resolveRuntimeHookToken, upsertRuntimeInstanceFromHook, } from "../runtime-instance-store.js";
|
|
37
39
|
import { parseJsonSafe } from "../json-utils.js";
|
|
38
|
-
import { readSkillPackState, refreshSkillPackState, updateSkillPackPolicy } from "../skill-pack-state.js";
|
|
40
|
+
import { readSkillPackState, refreshSkillPackState, rollbackSkillPackPolicy, updateSkillPackPolicy, } from "../skill-pack-state.js";
|
|
39
41
|
import { posthogCapture } from "../telemetry/posthog.js";
|
|
40
42
|
import { createRouter } from "./router.js";
|
|
41
43
|
import { summarizeActivityHeadline } from "./helpers/activity-headline.js";
|
|
@@ -45,7 +47,7 @@ import { mapDecisionEntity } from "./helpers/decision-mapper.js";
|
|
|
45
47
|
import { idempotencyKey, stableHash } from "./helpers/hash-utils.js";
|
|
46
48
|
import { createCodexBinResolver, } from "./helpers/autopilot-slice-utils.js";
|
|
47
49
|
import { createLocalArtifactDetailFallbackBuilder } from "./helpers/artifact-fallback.js";
|
|
48
|
-
import { buildMissionControlGraph, dedupeStrings, isDoneStatus, isInProgressStatus, isTodoStatus, listEntitiesSafe, normalizeEntityMutationPayload, pickStringArray, resolveAutoAssignments, } from "./helpers/mission-control.js";
|
|
50
|
+
import { buildMissionControlGraph, deriveExecutionPolicy, dedupeStrings, isDispatchableWorkstreamStatus, isDoneStatus, isInProgressStatus, isTodoStatus, listEntitiesSafe, normalizeEntityMutationPayload, pickStringArray, resolveAutoAssignments, selectSliceTasksByScope, } from "./helpers/mission-control.js";
|
|
49
51
|
import { configureOpenClawProviderRouting, fetchBillingStatusSafe, isPidAlive, listOpenClawAgents, listOpenClawProviderModels, modelImpliesByok, normalizeOpenClawProvider, resolveAutoOpenClawProvider, resolveByokEnvOverrides, spawnOpenClawAgentTurn, stopDetachedProcess, } from "./helpers/openclaw-cli.js";
|
|
50
52
|
import { fetchKickoffContextSafe, renderKickoffMessage } from "./helpers/kickoff-context.js";
|
|
51
53
|
import { createDispatchLifecycle } from "./helpers/dispatch-lifecycle.js";
|
|
@@ -61,17 +63,23 @@ import { registerDebugRoutes } from "./routes/debug.js";
|
|
|
61
63
|
import { registerEntityDynamicRoutes } from "./routes/entity-dynamic.js";
|
|
62
64
|
import { registerEntitiesRoutes } from "./routes/entities.js";
|
|
63
65
|
import { registerHealthRoutes } from "./routes/health.js";
|
|
66
|
+
import { registerChatRoutes } from "./routes/chat.js";
|
|
64
67
|
import { registerLiveLegacyRoutes } from "./routes/live-legacy.js";
|
|
65
68
|
import { registerLiveMiscRoutes } from "./routes/live-misc.js";
|
|
69
|
+
import { registerLiveTerminalRoutes } from "./routes/live-terminal.js";
|
|
66
70
|
import { registerLiveSnapshotRoutes } from "./routes/live-snapshot.js";
|
|
67
71
|
import { registerMissionControlActionsRoutes } from "./routes/mission-control-actions.js";
|
|
68
72
|
import { registerMissionControlReadRoutes } from "./routes/mission-control-read.js";
|
|
69
73
|
import { registerOnboardingRoutes } from "./routes/onboarding.js";
|
|
70
74
|
import { registerRunControlRoutes } from "./routes/run-control.js";
|
|
71
75
|
import { registerRuntimeHookRoutes } from "./routes/runtime-hooks.js";
|
|
76
|
+
import { registerSentinelsCatalogRoutes } from "./routes/sentinels-catalog.js";
|
|
72
77
|
import { registerSettingsByokRoutes } from "./routes/settings-byok.js";
|
|
73
78
|
import { registerSummaryRoutes } from "./routes/summary.js";
|
|
79
|
+
import { registerUsageRoutes } from "./routes/usage.js";
|
|
74
80
|
import { registerWorkArtifactsRoutes } from "./routes/work-artifacts.js";
|
|
81
|
+
import { registerLiveTriageRoutes } from "./routes/live-triage.js";
|
|
82
|
+
import { registerRealtimeOrchestratorRoutes } from "./routes/realtime-orchestrator.js";
|
|
75
83
|
// =============================================================================
|
|
76
84
|
// Helpers
|
|
77
85
|
// =============================================================================
|
|
@@ -96,10 +104,23 @@ async function resolveSkillPackOverrides(input) {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
function safeErrorMessage(err) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
107
|
+
const raw = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
108
|
+
const normalized = raw.trim().toLowerCase();
|
|
109
|
+
if (normalized.length > 0) {
|
|
110
|
+
if (normalized.includes("signal is aborted") ||
|
|
111
|
+
normalized.includes("aborterror") ||
|
|
112
|
+
normalized.includes("request cancelled") ||
|
|
113
|
+
normalized.includes("request canceled")) {
|
|
114
|
+
return "request timed out before upstream completed";
|
|
115
|
+
}
|
|
116
|
+
if (normalized.includes("timed out") || normalized.includes("timeout")) {
|
|
117
|
+
return "request timed out before upstream completed";
|
|
118
|
+
}
|
|
119
|
+
if (normalized.includes("failed to fetch") || normalized.includes("network")) {
|
|
120
|
+
return "network request failed";
|
|
121
|
+
}
|
|
122
|
+
return raw;
|
|
123
|
+
}
|
|
103
124
|
return "Unexpected error";
|
|
104
125
|
}
|
|
105
126
|
function titleCaseFromSlug(value) {
|
|
@@ -181,17 +202,28 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
181
202
|
}
|
|
182
203
|
const ACTIVITY_WARM_THROTTLE_MS = 30_000;
|
|
183
204
|
const activityWarmByKey = new Map();
|
|
184
|
-
const SNAPSHOT_RESPONSE_CACHE_TTL_MS =
|
|
205
|
+
const SNAPSHOT_RESPONSE_CACHE_TTL_MS = 800;
|
|
185
206
|
const SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES = 16;
|
|
186
207
|
const SNAPSHOT_ACTIVITY_PERSIST_MIN_INTERVAL_MS = 15_000;
|
|
187
208
|
const SNAPSHOT_ACTIVITY_FINGERPRINT_DEPTH = 8;
|
|
188
|
-
const NEXT_UP_QUEUE_CACHE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_CACHE_TTL_MS",
|
|
209
|
+
const NEXT_UP_QUEUE_CACHE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_CACHE_TTL_MS", 30_000, { min: 250, max: 120_000 });
|
|
189
210
|
const NEXT_UP_QUEUE_STALE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_STALE_TTL_MS", 45_000, { min: 1_000, max: 600_000 });
|
|
190
211
|
const NEXT_UP_GRAPH_CONCURRENCY = readPositiveIntEnv("ORGX_NEXT_UP_GRAPH_CONCURRENCY", 20, { min: 1, max: 32 });
|
|
191
212
|
const NEXT_UP_LIVE_AGENTS_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_LIVE_AGENTS_TIMEOUT_MS", 1_500, { min: 200, max: 20_000 });
|
|
192
213
|
const NEXT_UP_AGENT_CATALOG_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_AGENT_CATALOG_TIMEOUT_MS", 900, { min: 100, max: 20_000 });
|
|
214
|
+
const NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS", 2_500, { min: 250, max: 30_000 });
|
|
215
|
+
const PROJECT_SCOPE_LOOKUP_TIMEOUT_MS = readPositiveIntEnv("ORGX_PROJECT_SCOPE_LOOKUP_TIMEOUT_MS", 2_500, { min: 250, max: 20_000 });
|
|
216
|
+
const PROJECT_SCOPE_MAX_INITIATIVE_PAGES = readPositiveIntEnv("ORGX_PROJECT_SCOPE_MAX_INITIATIVE_PAGES", 12, { min: 1, max: 100 });
|
|
217
|
+
const LIVE_WORKSPACE_INITIATIVE_STATUSES = [
|
|
218
|
+
"active",
|
|
219
|
+
"planning",
|
|
220
|
+
"paused",
|
|
221
|
+
"draft",
|
|
222
|
+
"in_progress",
|
|
223
|
+
];
|
|
193
224
|
let lastSnapshotActivityPersistAt = 0;
|
|
194
225
|
let lastSnapshotActivityFingerprint = "";
|
|
226
|
+
let snapshotCacheGeneration = 0;
|
|
195
227
|
const snapshotResponseCache = new Map();
|
|
196
228
|
const ACTIVITY_DECISION_EVENT_HINTS = new Set([
|
|
197
229
|
"decision_buffered",
|
|
@@ -284,6 +316,9 @@ function deriveStructuredActivityBucket(input) {
|
|
|
284
316
|
"nonBlockingDecisionCount",
|
|
285
317
|
]) ?? 0;
|
|
286
318
|
if (event === "autopilot_slice_result") {
|
|
319
|
+
// Any blocked slice result needs decision-first surfacing in the Activity UX.
|
|
320
|
+
if (input.phase === "blocked")
|
|
321
|
+
return "decision";
|
|
287
322
|
if (decisionRequired || blockingDecisions > 0)
|
|
288
323
|
return "decision";
|
|
289
324
|
if (artifacts > 0)
|
|
@@ -292,6 +327,13 @@ function deriveStructuredActivityBucket(input) {
|
|
|
292
327
|
return "decision";
|
|
293
328
|
return "message";
|
|
294
329
|
}
|
|
330
|
+
if (event === "auto_continue_stopped") {
|
|
331
|
+
const stopReason = typeof metadata?.stop_reason === "string"
|
|
332
|
+
? metadata.stop_reason.trim().toLowerCase()
|
|
333
|
+
: "";
|
|
334
|
+
if (stopReason === "blocked" || stopReason === "error")
|
|
335
|
+
return "decision";
|
|
336
|
+
}
|
|
295
337
|
if (event && ACTIVITY_ARTIFACT_EVENT_HINTS.has(event))
|
|
296
338
|
return "artifact";
|
|
297
339
|
if (event && ACTIVITY_DECISION_EVENT_HINTS.has(event))
|
|
@@ -319,7 +361,7 @@ function readSnapshotResponseCache(key) {
|
|
|
319
361
|
const entry = snapshotResponseCache.get(key);
|
|
320
362
|
if (!entry)
|
|
321
363
|
return null;
|
|
322
|
-
if (entry.expiresAt <= Date.now()) {
|
|
364
|
+
if (entry.generation !== snapshotCacheGeneration || entry.expiresAt <= Date.now()) {
|
|
323
365
|
snapshotResponseCache.delete(key);
|
|
324
366
|
return null;
|
|
325
367
|
}
|
|
@@ -329,6 +371,7 @@ function writeSnapshotResponseCache(key, payload) {
|
|
|
329
371
|
const now = Date.now();
|
|
330
372
|
snapshotResponseCache.set(key, {
|
|
331
373
|
expiresAt: now + SNAPSHOT_RESPONSE_CACHE_TTL_MS,
|
|
374
|
+
generation: snapshotCacheGeneration,
|
|
332
375
|
payload,
|
|
333
376
|
});
|
|
334
377
|
if (snapshotResponseCache.size <= SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES)
|
|
@@ -345,6 +388,7 @@ function writeSnapshotResponseCache(key, payload) {
|
|
|
345
388
|
}
|
|
346
389
|
}
|
|
347
390
|
function clearSnapshotResponseCache() {
|
|
391
|
+
snapshotCacheGeneration += 1;
|
|
348
392
|
snapshotResponseCache.clear();
|
|
349
393
|
}
|
|
350
394
|
function isUserScopedApiKey(apiKey) {
|
|
@@ -528,19 +572,29 @@ function applyAgentContextsToActivity(input, contexts) {
|
|
|
528
572
|
return enrichActivityActorFields(nextItem);
|
|
529
573
|
});
|
|
530
574
|
}
|
|
575
|
+
function sessionNodeEpoch(node) {
|
|
576
|
+
const raw = node.updatedAt ?? node.lastEventAt ?? node.startedAt;
|
|
577
|
+
if (!raw)
|
|
578
|
+
return 0;
|
|
579
|
+
const epoch = Date.parse(raw);
|
|
580
|
+
return Number.isFinite(epoch) ? epoch : 0;
|
|
581
|
+
}
|
|
531
582
|
function mergeSessionTrees(base, extra) {
|
|
532
|
-
const
|
|
533
|
-
const nodes
|
|
534
|
-
|
|
535
|
-
seenNodes.add(node.id);
|
|
536
|
-
nodes.push(node);
|
|
537
|
-
}
|
|
583
|
+
const nodeById = new Map();
|
|
584
|
+
for (const node of base.nodes ?? [])
|
|
585
|
+
nodeById.set(node.id, node);
|
|
538
586
|
for (const node of extra.nodes ?? []) {
|
|
539
|
-
|
|
587
|
+
const existing = nodeById.get(node.id);
|
|
588
|
+
if (!existing) {
|
|
589
|
+
nodeById.set(node.id, node);
|
|
540
590
|
continue;
|
|
541
|
-
|
|
542
|
-
|
|
591
|
+
}
|
|
592
|
+
const existingEpoch = sessionNodeEpoch(existing);
|
|
593
|
+
const extraEpoch = sessionNodeEpoch(node);
|
|
594
|
+
if (extraEpoch > existingEpoch)
|
|
595
|
+
nodeById.set(node.id, node);
|
|
543
596
|
}
|
|
597
|
+
const nodes = Array.from(nodeById.values());
|
|
544
598
|
const seenEdges = new Set();
|
|
545
599
|
const edges = [];
|
|
546
600
|
for (const edge of base.edges ?? []) {
|
|
@@ -576,6 +630,81 @@ function mergeSessionTrees(base, extra) {
|
|
|
576
630
|
groups: Array.from(groupsById.values()),
|
|
577
631
|
};
|
|
578
632
|
}
|
|
633
|
+
function asActivityMetadataRecord(value) {
|
|
634
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
635
|
+
return null;
|
|
636
|
+
return value;
|
|
637
|
+
}
|
|
638
|
+
function activityMetadataStr(metadata, keys) {
|
|
639
|
+
if (!metadata)
|
|
640
|
+
return null;
|
|
641
|
+
for (const key of keys) {
|
|
642
|
+
const value = metadata[key];
|
|
643
|
+
if (typeof value !== "string")
|
|
644
|
+
continue;
|
|
645
|
+
const normalized = value.trim();
|
|
646
|
+
if (normalized.length > 0)
|
|
647
|
+
return normalized;
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const SEMANTIC_ACTIVITY_EVENTS = new Set([
|
|
652
|
+
"autopilot_slice_result",
|
|
653
|
+
"auto_continue_started",
|
|
654
|
+
"auto_continue_stopped",
|
|
655
|
+
"next_up_manual_dispatch_started",
|
|
656
|
+
"autopilot_slice_mcp_handshake_failed",
|
|
657
|
+
"autopilot_slice_timeout",
|
|
658
|
+
"autopilot_slice_log_stall",
|
|
659
|
+
"auto_continue_spawn_guard_blocked",
|
|
660
|
+
"auto_continue_spawn_guard_rate_limited",
|
|
661
|
+
"autopilot_autofix_scheduled",
|
|
662
|
+
"autopilot_autofix_executed",
|
|
663
|
+
"autopilot_autofix_skipped",
|
|
664
|
+
]);
|
|
665
|
+
function semanticActivityKey(item) {
|
|
666
|
+
const metadata = asActivityMetadataRecord(item.metadata);
|
|
667
|
+
const eventRaw = metadata?.event;
|
|
668
|
+
const event = typeof eventRaw === "string" ? eventRaw.trim().toLowerCase() : "";
|
|
669
|
+
if (!event || !SEMANTIC_ACTIVITY_EVENTS.has(event))
|
|
670
|
+
return null;
|
|
671
|
+
const runLike = (typeof item.runId === "string" && item.runId.trim().length > 0
|
|
672
|
+
? item.runId.trim()
|
|
673
|
+
: null) ??
|
|
674
|
+
activityMetadataStr(metadata, [
|
|
675
|
+
"run_id",
|
|
676
|
+
"runId",
|
|
677
|
+
"slice_run_id",
|
|
678
|
+
"sliceRunId",
|
|
679
|
+
"active_run_id",
|
|
680
|
+
"activeRunId",
|
|
681
|
+
"last_run_id",
|
|
682
|
+
"lastRunId",
|
|
683
|
+
]);
|
|
684
|
+
const correlationId = activityMetadataStr(metadata, ["correlation_id", "correlationId"]);
|
|
685
|
+
const initiativeId = (typeof item.initiativeId === "string" && item.initiativeId.trim().length > 0
|
|
686
|
+
? item.initiativeId.trim()
|
|
687
|
+
: null) ??
|
|
688
|
+
activityMetadataStr(metadata, ["initiative_id", "initiativeId"]);
|
|
689
|
+
const workstreamId = activityMetadataStr(metadata, ["workstream_id", "workstreamId"]);
|
|
690
|
+
const taskId = activityMetadataStr(metadata, ["task_id", "taskId"]);
|
|
691
|
+
const stopReason = activityMetadataStr(metadata, ["stop_reason", "stopReason"]);
|
|
692
|
+
const parsedStatus = activityMetadataStr(metadata, ["parsed_status", "parsedStatus"]);
|
|
693
|
+
const title = (item.title ?? "").trim().toLowerCase();
|
|
694
|
+
if (!runLike && !correlationId && !workstreamId && !taskId)
|
|
695
|
+
return null;
|
|
696
|
+
return [
|
|
697
|
+
event,
|
|
698
|
+
initiativeId ?? "",
|
|
699
|
+
workstreamId ?? "",
|
|
700
|
+
taskId ?? "",
|
|
701
|
+
runLike ?? "",
|
|
702
|
+
correlationId ?? "",
|
|
703
|
+
stopReason ?? "",
|
|
704
|
+
parsedStatus ?? "",
|
|
705
|
+
title,
|
|
706
|
+
].join("|");
|
|
707
|
+
}
|
|
579
708
|
function mergeActivities(base, extra, limit) {
|
|
580
709
|
const merged = [...(base ?? []), ...(extra ?? [])].sort((a, b) => {
|
|
581
710
|
const timestampDelta = Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
@@ -584,11 +713,17 @@ function mergeActivities(base, extra, limit) {
|
|
|
584
713
|
return b.id.localeCompare(a.id);
|
|
585
714
|
});
|
|
586
715
|
const deduped = [];
|
|
587
|
-
const
|
|
716
|
+
const seenIds = new Set();
|
|
717
|
+
const seenSemantic = new Set();
|
|
588
718
|
for (const item of merged) {
|
|
589
|
-
if (
|
|
719
|
+
if (seenIds.has(item.id))
|
|
720
|
+
continue;
|
|
721
|
+
seenIds.add(item.id);
|
|
722
|
+
const sk = semanticActivityKey(item);
|
|
723
|
+
if (sk && seenSemantic.has(sk))
|
|
590
724
|
continue;
|
|
591
|
-
|
|
725
|
+
if (sk)
|
|
726
|
+
seenSemantic.add(sk);
|
|
592
727
|
deduped.push(item);
|
|
593
728
|
if (deduped.length >= limit)
|
|
594
729
|
break;
|
|
@@ -719,6 +854,9 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
719
854
|
const agentId = (node.agentId ?? "").trim() || fallbackAgent.agentId;
|
|
720
855
|
const agentName = (node.agentName ?? "").trim() || fallbackAgent.agentName;
|
|
721
856
|
const nodeStatus = (node.status ?? "").trim().toLowerCase();
|
|
857
|
+
const isTerminalNodeStatus = nodeStatus === "completed" ||
|
|
858
|
+
nodeStatus === "cancelled" ||
|
|
859
|
+
nodeStatus === "archived";
|
|
722
860
|
const isLiveLikeNodeStatus = nodeStatus === "running" ||
|
|
723
861
|
nodeStatus === "active" ||
|
|
724
862
|
nodeStatus === "in_progress" ||
|
|
@@ -726,20 +864,49 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
726
864
|
nodeStatus === "planning" ||
|
|
727
865
|
nodeStatus === "dispatching";
|
|
728
866
|
const shouldDowngradeStatusFromRuntime = isLiveLikeNodeStatus && (runtimeStatus === "queued" || runtimeStatus === "paused");
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
867
|
+
const shouldPromoteStatusFromRuntime = runtimeStatus === "completed" ||
|
|
868
|
+
runtimeStatus === "blocked" ||
|
|
869
|
+
runtimeStatus === "review" ||
|
|
870
|
+
runtimeStatus === "handoff" ||
|
|
871
|
+
(runtimeStatus === "running" &&
|
|
872
|
+
(nodeStatus === "blocked" ||
|
|
873
|
+
nodeStatus === "failed" ||
|
|
874
|
+
nodeStatus === "queued" ||
|
|
875
|
+
nodeStatus === "paused"));
|
|
876
|
+
const nextStatus = shouldDowngradeStatusFromRuntime ||
|
|
877
|
+
(!isTerminalNodeStatus && shouldPromoteStatusFromRuntime)
|
|
878
|
+
? runtimeStatus
|
|
879
|
+
: node.status;
|
|
880
|
+
const runtimeExplicitlyBlocked = runtimeStatus === "blocked" || match.phase?.toLowerCase() === "blocked";
|
|
881
|
+
const runtimeExplicitlyUnblocked = !runtimeExplicitlyBlocked && typeof match.phase === "string" && match.phase.trim().length > 0;
|
|
882
|
+
const runtimeBlockedReason = (match.lastMessage ?? "").trim();
|
|
883
|
+
const nextBlockerReason = runtimeExplicitlyBlocked
|
|
884
|
+
? runtimeBlockedReason || (node.blockerReason ?? "").trim() || null
|
|
885
|
+
: runtimeExplicitlyUnblocked
|
|
886
|
+
? null
|
|
887
|
+
: node.blockerReason ?? null;
|
|
888
|
+
const nextBlockers = runtimeExplicitlyBlocked
|
|
889
|
+
? runtimeBlockedReason
|
|
890
|
+
? [runtimeBlockedReason]
|
|
891
|
+
: Array.isArray(node.blockers)
|
|
892
|
+
? node.blockers
|
|
893
|
+
: []
|
|
894
|
+
: runtimeExplicitlyUnblocked
|
|
895
|
+
? []
|
|
896
|
+
: Array.isArray(node.blockers)
|
|
897
|
+
? node.blockers
|
|
898
|
+
: [];
|
|
733
899
|
return {
|
|
734
900
|
...node,
|
|
735
901
|
agentId: agentId || null,
|
|
736
902
|
agentName: agentName || null,
|
|
737
|
-
status:
|
|
903
|
+
status: nextStatus,
|
|
738
904
|
state: node.state ?? match.state ?? null,
|
|
739
905
|
lastEventSummary: shouldDowngradeStatusFromRuntime && runtimeStatus === "queued"
|
|
740
906
|
? node.lastEventSummary ?? "Recovered stale runtime; awaiting next dispatch."
|
|
741
907
|
: node.lastEventSummary,
|
|
742
|
-
|
|
908
|
+
blockers: nextBlockers,
|
|
909
|
+
blockerReason: nextBlockerReason,
|
|
743
910
|
runtimeClient: normalizeRuntimeSource(match.sourceClient),
|
|
744
911
|
runtimeLabel: match.displayName,
|
|
745
912
|
runtimeProvider: match.providerLogo,
|
|
@@ -749,6 +916,64 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
749
916
|
});
|
|
750
917
|
return { ...input, nodes };
|
|
751
918
|
}
|
|
919
|
+
function metadataHasStructuredScope(meta) {
|
|
920
|
+
const scalarScope = pickString(meta, [
|
|
921
|
+
"initiative_id",
|
|
922
|
+
"initiativeId",
|
|
923
|
+
"workstream_id",
|
|
924
|
+
"workstreamId",
|
|
925
|
+
"workstream_title",
|
|
926
|
+
"workstreamTitle",
|
|
927
|
+
"task_id",
|
|
928
|
+
"taskId",
|
|
929
|
+
"task_title",
|
|
930
|
+
"taskTitle",
|
|
931
|
+
"slice_run_id",
|
|
932
|
+
"sliceRunId",
|
|
933
|
+
"iwmt_id",
|
|
934
|
+
"iwmtId",
|
|
935
|
+
"milestone_id",
|
|
936
|
+
"milestoneId",
|
|
937
|
+
"milestone_title",
|
|
938
|
+
"milestoneTitle",
|
|
939
|
+
]) ?? null;
|
|
940
|
+
if (scalarScope)
|
|
941
|
+
return true;
|
|
942
|
+
const listScopeKeys = [
|
|
943
|
+
"initiative_ids",
|
|
944
|
+
"initiativeIds",
|
|
945
|
+
"workstream_ids",
|
|
946
|
+
"workstreamIds",
|
|
947
|
+
"task_ids",
|
|
948
|
+
"taskIds",
|
|
949
|
+
"milestone_ids",
|
|
950
|
+
"milestoneIds",
|
|
951
|
+
"iwmt_ids",
|
|
952
|
+
"iwmtIds",
|
|
953
|
+
];
|
|
954
|
+
for (const key of listScopeKeys) {
|
|
955
|
+
const value = meta[key];
|
|
956
|
+
if (!Array.isArray(value))
|
|
957
|
+
continue;
|
|
958
|
+
if (value.some((entry) => typeof entry === "string" && entry.trim().length > 0)) {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
function shouldInjectRuntimeInstanceAsSession(instance, runId, meta) {
|
|
965
|
+
if (instance.state !== "active")
|
|
966
|
+
return false;
|
|
967
|
+
// Synthetic hook correlation ids are telemetry-only and should never render as user-facing sessions.
|
|
968
|
+
if (runId.toLowerCase().startsWith("hook-"))
|
|
969
|
+
return false;
|
|
970
|
+
const workstreamId = instance.workstreamId?.trim() ?? "";
|
|
971
|
+
const taskId = instance.taskId?.trim() ?? "";
|
|
972
|
+
if (workstreamId.length > 0 || taskId.length > 0)
|
|
973
|
+
return true;
|
|
974
|
+
// Keep only runtime records that include structured execution scope.
|
|
975
|
+
return metadataHasStructuredScope(meta);
|
|
976
|
+
}
|
|
752
977
|
function injectRuntimeInstancesAsSessions(input, instances) {
|
|
753
978
|
if (!Array.isArray(input.nodes))
|
|
754
979
|
return input;
|
|
@@ -773,10 +998,6 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
773
998
|
continue;
|
|
774
999
|
if (existingRunIds.has(runId))
|
|
775
1000
|
continue;
|
|
776
|
-
// Only surface active runtime instances as synthetic sessions.
|
|
777
|
-
// Stale instances are reconciled onto existing sessions but shouldn't appear as fresh work.
|
|
778
|
-
if (instance.state !== "active")
|
|
779
|
-
continue;
|
|
780
1001
|
const initiativeId = instance.initiativeId?.trim() || null;
|
|
781
1002
|
const workstreamId = instance.workstreamId?.trim() || null;
|
|
782
1003
|
const runtimeClient = normalizeRuntimeSource(instance.sourceClient);
|
|
@@ -785,6 +1006,8 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
785
1006
|
const meta = instance.metadata && typeof instance.metadata === "object"
|
|
786
1007
|
? instance.metadata
|
|
787
1008
|
: {};
|
|
1009
|
+
if (!shouldInjectRuntimeInstanceAsSession(instance, runId, meta))
|
|
1010
|
+
continue;
|
|
788
1011
|
const titleHint = pickString(meta, ["workstream_title", "workstreamTitle"]) ??
|
|
789
1012
|
(workstreamId ? `Workstream ${workstreamId.slice(0, 8)}` : null);
|
|
790
1013
|
const initiativeHint = pickString(meta, ["initiative_title", "initiativeTitle"]) ??
|
|
@@ -898,18 +1121,18 @@ const CONTENT_SECURITY_POLICY = [
|
|
|
898
1121
|
"form-action 'self'",
|
|
899
1122
|
"object-src 'none'",
|
|
900
1123
|
"script-src 'self'",
|
|
901
|
-
"style-src 'self' 'unsafe-inline'",
|
|
1124
|
+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
902
1125
|
"img-src 'self' data: blob:",
|
|
903
|
-
"font-src 'self' data:",
|
|
1126
|
+
"font-src 'self' data: https://fonts.gstatic.com",
|
|
904
1127
|
"media-src 'self'",
|
|
905
|
-
"connect-src 'self' https://*.useorgx.com https://*.openclaw.ai http://127.0.0.1:* http://localhost:*",
|
|
1128
|
+
"connect-src 'self' https://*.useorgx.com https://*.openclaw.ai https://api.openai.com https://*.openai.com http://127.0.0.1:* http://localhost:*",
|
|
906
1129
|
].join("; ");
|
|
907
1130
|
const SECURITY_HEADERS = {
|
|
908
1131
|
"X-Content-Type-Options": "nosniff",
|
|
909
1132
|
"X-Frame-Options": "DENY",
|
|
910
1133
|
"Referrer-Policy": "same-origin",
|
|
911
1134
|
"X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
|
|
912
|
-
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
|
|
1135
|
+
"Permissions-Policy": "camera=(), microphone=(self), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
|
|
913
1136
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
914
1137
|
"Cross-Origin-Resource-Policy": "same-origin",
|
|
915
1138
|
"Origin-Agent-Cluster": "?1",
|
|
@@ -982,9 +1205,16 @@ function resolveSafeDistPath(subPath) {
|
|
|
982
1205
|
}
|
|
983
1206
|
return candidate;
|
|
984
1207
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1208
|
+
const PRECOMPRESSED_FILE_EXTENSIONS = new Set([
|
|
1209
|
+
".css",
|
|
1210
|
+
".html",
|
|
1211
|
+
".js",
|
|
1212
|
+
".json",
|
|
1213
|
+
".map",
|
|
1214
|
+
".svg",
|
|
1215
|
+
".txt",
|
|
1216
|
+
".xml",
|
|
1217
|
+
]);
|
|
988
1218
|
const IMMUTABLE_FILE_CACHE = new Map();
|
|
989
1219
|
const IMMUTABLE_FILE_CACHE_MAX = 128;
|
|
990
1220
|
const FILE_PREVIEW_MAX_BYTES = 1_000_000;
|
|
@@ -1064,23 +1294,109 @@ function readFilePreview(pathname, totalBytes) {
|
|
|
1064
1294
|
closeSync(fd);
|
|
1065
1295
|
}
|
|
1066
1296
|
}
|
|
1067
|
-
function
|
|
1297
|
+
function parseAcceptedEncodings(rawHeader) {
|
|
1298
|
+
const parsed = new Map();
|
|
1299
|
+
if (!rawHeader || rawHeader.trim().length === 0)
|
|
1300
|
+
return parsed;
|
|
1301
|
+
const parts = rawHeader.split(",");
|
|
1302
|
+
for (const part of parts) {
|
|
1303
|
+
const [nameRaw, ...params] = part.split(";");
|
|
1304
|
+
const name = nameRaw?.trim().toLowerCase();
|
|
1305
|
+
if (!name)
|
|
1306
|
+
continue;
|
|
1307
|
+
let q = 1;
|
|
1308
|
+
for (const param of params) {
|
|
1309
|
+
const [keyRaw, valueRaw] = param.split("=");
|
|
1310
|
+
const key = keyRaw?.trim().toLowerCase();
|
|
1311
|
+
if (key !== "q")
|
|
1312
|
+
continue;
|
|
1313
|
+
const candidate = Number.parseFloat((valueRaw ?? "").trim());
|
|
1314
|
+
if (Number.isFinite(candidate)) {
|
|
1315
|
+
q = candidate;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (q <= 0)
|
|
1319
|
+
continue;
|
|
1320
|
+
const existing = parsed.get(name);
|
|
1321
|
+
if (existing == null || q > existing) {
|
|
1322
|
+
parsed.set(name, q);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return parsed;
|
|
1326
|
+
}
|
|
1327
|
+
function resolveEncodingQuality(accepted, encoding) {
|
|
1328
|
+
if (accepted.has(encoding))
|
|
1329
|
+
return accepted.get(encoding) ?? 0;
|
|
1330
|
+
if (accepted.has("*"))
|
|
1331
|
+
return accepted.get("*") ?? 0;
|
|
1332
|
+
return 0;
|
|
1333
|
+
}
|
|
1334
|
+
function resolvePrecompressedVariant(req, filePath) {
|
|
1335
|
+
const ext = extname(filePath).toLowerCase();
|
|
1336
|
+
if (!PRECOMPRESSED_FILE_EXTENSIONS.has(ext))
|
|
1337
|
+
return null;
|
|
1338
|
+
const accepted = parseAcceptedEncodings(pickHeaderString(req.headers, ["accept-encoding"]));
|
|
1339
|
+
if (accepted.size === 0)
|
|
1340
|
+
return null;
|
|
1341
|
+
const candidates = [
|
|
1342
|
+
{
|
|
1343
|
+
encoding: "br",
|
|
1344
|
+
path: `${filePath}.br`,
|
|
1345
|
+
quality: resolveEncodingQuality(accepted, "br"),
|
|
1346
|
+
priority: 2,
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
encoding: "gzip",
|
|
1350
|
+
path: `${filePath}.gz`,
|
|
1351
|
+
quality: resolveEncodingQuality(accepted, "gzip"),
|
|
1352
|
+
priority: 1,
|
|
1353
|
+
},
|
|
1354
|
+
];
|
|
1355
|
+
candidates.sort((left, right) => {
|
|
1356
|
+
if (right.quality !== left.quality)
|
|
1357
|
+
return right.quality - left.quality;
|
|
1358
|
+
return right.priority - left.priority;
|
|
1359
|
+
});
|
|
1360
|
+
for (const candidate of candidates) {
|
|
1361
|
+
if (candidate.quality <= 0)
|
|
1362
|
+
continue;
|
|
1363
|
+
if (existsSync(candidate.path)) {
|
|
1364
|
+
return { path: candidate.path, encoding: candidate.encoding };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
function sendFile(req, res, filePath, cacheControl) {
|
|
1068
1370
|
try {
|
|
1069
1371
|
const shouldCacheImmutable = cacheControl.includes("immutable");
|
|
1372
|
+
const shouldVaryByEncoding = PRECOMPRESSED_FILE_EXTENSIONS.has(extname(filePath).toLowerCase());
|
|
1373
|
+
const precompressed = resolvePrecompressedVariant(req, filePath);
|
|
1374
|
+
const responsePath = precompressed?.path ?? filePath;
|
|
1375
|
+
const responseEncoding = precompressed?.encoding ?? null;
|
|
1376
|
+
const cacheKey = `${responsePath}|${cacheControl}`;
|
|
1070
1377
|
if (shouldCacheImmutable) {
|
|
1071
|
-
const cached = IMMUTABLE_FILE_CACHE.get(
|
|
1378
|
+
const cached = IMMUTABLE_FILE_CACHE.get(cacheKey);
|
|
1072
1379
|
if (cached) {
|
|
1073
|
-
|
|
1380
|
+
const headers = {
|
|
1074
1381
|
"Content-Type": cached.contentType,
|
|
1075
1382
|
"Cache-Control": cacheControl,
|
|
1076
1383
|
...SECURITY_HEADERS,
|
|
1077
1384
|
...CORS_HEADERS,
|
|
1385
|
+
};
|
|
1386
|
+
if (cached.contentEncoding === "br")
|
|
1387
|
+
headers["Content-Encoding"] = "br";
|
|
1388
|
+
if (cached.contentEncoding === "gzip")
|
|
1389
|
+
headers["Content-Encoding"] = "gzip";
|
|
1390
|
+
if (cached.varyAcceptEncoding)
|
|
1391
|
+
headers["Vary"] = "Accept-Encoding";
|
|
1392
|
+
res.writeHead(200, {
|
|
1393
|
+
...headers,
|
|
1078
1394
|
});
|
|
1079
1395
|
res.end(cached.content);
|
|
1080
1396
|
return;
|
|
1081
1397
|
}
|
|
1082
1398
|
}
|
|
1083
|
-
const content = readFileSync(
|
|
1399
|
+
const content = readFileSync(responsePath);
|
|
1084
1400
|
const type = contentType(filePath);
|
|
1085
1401
|
if (shouldCacheImmutable) {
|
|
1086
1402
|
if (IMMUTABLE_FILE_CACHE.size >= IMMUTABLE_FILE_CACHE_MAX) {
|
|
@@ -1088,14 +1404,26 @@ function sendFile(res, filePath, cacheControl) {
|
|
|
1088
1404
|
if (firstKey)
|
|
1089
1405
|
IMMUTABLE_FILE_CACHE.delete(firstKey);
|
|
1090
1406
|
}
|
|
1091
|
-
IMMUTABLE_FILE_CACHE.set(
|
|
1407
|
+
IMMUTABLE_FILE_CACHE.set(cacheKey, {
|
|
1408
|
+
content,
|
|
1409
|
+
contentType: type,
|
|
1410
|
+
contentEncoding: responseEncoding,
|
|
1411
|
+
varyAcceptEncoding: shouldVaryByEncoding,
|
|
1412
|
+
});
|
|
1092
1413
|
}
|
|
1093
|
-
|
|
1414
|
+
const headers = {
|
|
1094
1415
|
"Content-Type": type,
|
|
1095
1416
|
"Cache-Control": cacheControl,
|
|
1096
1417
|
...SECURITY_HEADERS,
|
|
1097
1418
|
...CORS_HEADERS,
|
|
1098
|
-
}
|
|
1419
|
+
};
|
|
1420
|
+
if (responseEncoding === "br")
|
|
1421
|
+
headers["Content-Encoding"] = "br";
|
|
1422
|
+
if (responseEncoding === "gzip")
|
|
1423
|
+
headers["Content-Encoding"] = "gzip";
|
|
1424
|
+
if (shouldVaryByEncoding)
|
|
1425
|
+
headers["Vary"] = "Accept-Encoding";
|
|
1426
|
+
res.writeHead(200, headers);
|
|
1099
1427
|
res.end(content);
|
|
1100
1428
|
}
|
|
1101
1429
|
catch {
|
|
@@ -1110,10 +1438,24 @@ function send404(res) {
|
|
|
1110
1438
|
});
|
|
1111
1439
|
res.end("Not Found");
|
|
1112
1440
|
}
|
|
1113
|
-
function
|
|
1441
|
+
function sendStaleChunkRecovery(res) {
|
|
1442
|
+
const body = [
|
|
1443
|
+
"// Recover from stale chunk references after dashboard/plugin upgrades.",
|
|
1444
|
+
"window.location.replace('/orgx/live' + window.location.search);",
|
|
1445
|
+
"export {};"
|
|
1446
|
+
].join("\n");
|
|
1447
|
+
res.writeHead(200, {
|
|
1448
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
1449
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
1450
|
+
...SECURITY_HEADERS,
|
|
1451
|
+
...CORS_HEADERS,
|
|
1452
|
+
});
|
|
1453
|
+
res.end(body);
|
|
1454
|
+
}
|
|
1455
|
+
function sendIndexHtml(req, res) {
|
|
1114
1456
|
const indexPath = join(DIST_DIR, "index.html");
|
|
1115
1457
|
if (existsSync(indexPath)) {
|
|
1116
|
-
sendFile(res, indexPath, "no-cache, no-store, must-revalidate");
|
|
1458
|
+
sendFile(req, res, indexPath, "no-cache, no-store, must-revalidate");
|
|
1117
1459
|
}
|
|
1118
1460
|
else {
|
|
1119
1461
|
res.writeHead(503, {
|
|
@@ -1307,9 +1649,60 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1307
1649
|
spawnAgentTurn,
|
|
1308
1650
|
upsertAgentRun,
|
|
1309
1651
|
});
|
|
1652
|
+
const normalizeRunnerAgentToken = (value) => {
|
|
1653
|
+
if (typeof value !== "string")
|
|
1654
|
+
return null;
|
|
1655
|
+
const trimmed = value.trim();
|
|
1656
|
+
if (!trimmed)
|
|
1657
|
+
return null;
|
|
1658
|
+
const normalized = trimmed.toLowerCase();
|
|
1659
|
+
if (normalized === "main" ||
|
|
1660
|
+
normalized === "undefined" ||
|
|
1661
|
+
normalized === "null" ||
|
|
1662
|
+
normalized === "n/a" ||
|
|
1663
|
+
normalized === "na") {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
return trimmed;
|
|
1667
|
+
};
|
|
1668
|
+
const pushRunnerAgent = (target, seen, input) => {
|
|
1669
|
+
const agentId = normalizeRunnerAgentToken(input.id ?? null);
|
|
1670
|
+
const agentName = normalizeRunnerAgentToken(input.name ?? null);
|
|
1671
|
+
if (!agentId && !agentName)
|
|
1672
|
+
return;
|
|
1673
|
+
const resolvedId = agentId ?? agentName;
|
|
1674
|
+
const dedupeKey = resolvedId.toLowerCase();
|
|
1675
|
+
if (seen.has(dedupeKey))
|
|
1676
|
+
return;
|
|
1677
|
+
seen.add(dedupeKey);
|
|
1678
|
+
target.push({
|
|
1679
|
+
id: resolvedId,
|
|
1680
|
+
name: agentName ?? resolvedId,
|
|
1681
|
+
});
|
|
1682
|
+
};
|
|
1683
|
+
const dedupeWithPrimary = (primary, extras) => {
|
|
1684
|
+
const merged = [];
|
|
1685
|
+
const seen = new Set();
|
|
1686
|
+
for (const candidate of [...primary, ...extras]) {
|
|
1687
|
+
const id = normalizeRunnerAgentToken(candidate.id);
|
|
1688
|
+
const name = normalizeRunnerAgentToken(candidate.name);
|
|
1689
|
+
if (!id && !name)
|
|
1690
|
+
continue;
|
|
1691
|
+
const resolvedId = id ?? name;
|
|
1692
|
+
const key = resolvedId.toLowerCase();
|
|
1693
|
+
if (seen.has(key))
|
|
1694
|
+
continue;
|
|
1695
|
+
seen.add(key);
|
|
1696
|
+
merged.push({
|
|
1697
|
+
id: resolvedId,
|
|
1698
|
+
name: name ?? resolvedId,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
return merged;
|
|
1702
|
+
};
|
|
1310
1703
|
const codexBinResolver = createCodexBinResolver();
|
|
1311
1704
|
const resolveCodexBinInfo = () => codexBinResolver.resolveCodexBinInfo();
|
|
1312
|
-
const { autoContinueRuns, autoContinueSliceRuns, localInitiativeStatusOverrides, writeRuntimeEvent, autoContinueTickMs: AUTO_CONTINUE_TICK_MS, defaultAutoContinueTokenBudget, setLocalInitiativeStatusOverride, clearLocalInitiativeStatusOverride, applyLocalInitiativeOverrides, applyLocalInitiativeOverrideToGraph, updateInitiativeAutoContinueState, stopAutoContinueRun, tickAutoContinueRun, tickAllAutoContinue, isInitiativeActiveStatus, runningAutoContinueForWorkstream, startAutoContinueRun, } = createAutoContinueEngine({
|
|
1705
|
+
const { autoContinueRuns, autoContinueSliceRuns, localInitiativeStatusOverrides, writeRuntimeEvent, autoContinueTickMs: AUTO_CONTINUE_TICK_MS, defaultAutoContinueTokenBudget, defaultAutoContinueMaxParallelSlices, setLocalInitiativeStatusOverride, clearLocalInitiativeStatusOverride, applyLocalInitiativeOverrides, applyLocalInitiativeOverrideToGraph, updateInitiativeAutoContinueState, stopAutoContinueRun, tickAutoContinueRun, tickAllAutoContinue, isInitiativeActiveStatus, runningAutoContinueForWorkstream, getAutoContinueLaneForWorkstream, scheduleAutoFixForWorkstream, startAutoContinueRun, } = createAutoContinueEngine({
|
|
1313
1706
|
client,
|
|
1314
1707
|
filename: __filename,
|
|
1315
1708
|
safeErrorMessage,
|
|
@@ -1327,10 +1720,194 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1327
1720
|
clearSnapshotResponseCache,
|
|
1328
1721
|
resolveByokEnvOverrides,
|
|
1329
1722
|
randomUUID,
|
|
1723
|
+
fetchKickoffContextSafe,
|
|
1724
|
+
renderKickoffMessage,
|
|
1330
1725
|
});
|
|
1331
1726
|
const nextUpQueueCache = new Map();
|
|
1332
1727
|
const nextUpQueueInFlight = new Map();
|
|
1333
|
-
const
|
|
1728
|
+
const PROJECT_INITIATIVE_IDS_CACHE_TTL_MS = 20_000;
|
|
1729
|
+
const projectInitiativeIdsCache = new Map();
|
|
1730
|
+
const commandCenterScopeCache = new Map();
|
|
1731
|
+
const nextUpQueueCacheKeyFor = (initiativeId, projectId) => {
|
|
1732
|
+
const normalizedInitiative = initiativeId?.trim() || "__all__";
|
|
1733
|
+
const normalizedProject = projectId?.trim() || "__all__";
|
|
1734
|
+
return `${normalizedProject}::${normalizedInitiative}`;
|
|
1735
|
+
};
|
|
1736
|
+
async function listInitiativeIdsForProject(input) {
|
|
1737
|
+
const projectId = input.projectId.trim();
|
|
1738
|
+
if (!projectId)
|
|
1739
|
+
return [];
|
|
1740
|
+
const cached = projectInitiativeIdsCache.get(projectId);
|
|
1741
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
1742
|
+
return [...cached.ids];
|
|
1743
|
+
}
|
|
1744
|
+
const mapInitiativeIds = (rows, opts) => {
|
|
1745
|
+
const projectScopeId = opts?.projectId?.trim() ?? "";
|
|
1746
|
+
const commandCenterId = opts?.commandCenterId?.trim() ?? "";
|
|
1747
|
+
return rows
|
|
1748
|
+
.map((entry) => {
|
|
1749
|
+
const record = entry;
|
|
1750
|
+
if (projectScopeId) {
|
|
1751
|
+
const rowProjectId = pickString(record, ["project_id", "projectId"]) ?? "";
|
|
1752
|
+
if (rowProjectId !== projectScopeId)
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
if (commandCenterId) {
|
|
1756
|
+
const rowCommandCenterId = pickString(record, [
|
|
1757
|
+
"workspace_id",
|
|
1758
|
+
"workspaceId",
|
|
1759
|
+
"command_center_id",
|
|
1760
|
+
"commandCenterId",
|
|
1761
|
+
]) ?? "";
|
|
1762
|
+
if (rowCommandCenterId !== commandCenterId)
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
return pickString(record, ["id"]);
|
|
1766
|
+
})
|
|
1767
|
+
.filter((id) => Boolean(id && id.trim().length > 0));
|
|
1768
|
+
};
|
|
1769
|
+
const cacheAndReturn = (ids) => {
|
|
1770
|
+
const normalized = dedupeStrings(ids
|
|
1771
|
+
.map((id) => id.trim())
|
|
1772
|
+
.filter((id) => id.length > 0));
|
|
1773
|
+
projectInitiativeIdsCache.set(projectId, {
|
|
1774
|
+
expiresAt: Date.now() + PROJECT_INITIATIVE_IDS_CACHE_TTL_MS,
|
|
1775
|
+
ids: normalized,
|
|
1776
|
+
});
|
|
1777
|
+
return normalized;
|
|
1778
|
+
};
|
|
1779
|
+
const isKnownCommandCenterScope = async () => {
|
|
1780
|
+
const cachedScope = commandCenterScopeCache.get(projectId);
|
|
1781
|
+
if (cachedScope && cachedScope.expiresAt > Date.now()) {
|
|
1782
|
+
return cachedScope.exists;
|
|
1783
|
+
}
|
|
1784
|
+
const cacheScope = (exists) => {
|
|
1785
|
+
commandCenterScopeCache.set(projectId, {
|
|
1786
|
+
expiresAt: Date.now() + PROJECT_INITIATIVE_IDS_CACHE_TTL_MS,
|
|
1787
|
+
exists,
|
|
1788
|
+
});
|
|
1789
|
+
return exists;
|
|
1790
|
+
};
|
|
1791
|
+
const hasId = (rows) => rows.some((entry) => {
|
|
1792
|
+
const record = entry;
|
|
1793
|
+
const id = pickString(record, ["id"]) ?? "";
|
|
1794
|
+
return id === projectId;
|
|
1795
|
+
});
|
|
1796
|
+
try {
|
|
1797
|
+
const byId = await withSoftTimeout("command center scope lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("command_center", {
|
|
1798
|
+
id: projectId,
|
|
1799
|
+
limit: 1,
|
|
1800
|
+
}));
|
|
1801
|
+
const byIdRows = Array.isArray(byId.data) ? byId.data : [];
|
|
1802
|
+
if (hasId(byIdRows))
|
|
1803
|
+
return cacheScope(true);
|
|
1804
|
+
}
|
|
1805
|
+
catch {
|
|
1806
|
+
// continue to all-command-center fallback
|
|
1807
|
+
}
|
|
1808
|
+
try {
|
|
1809
|
+
const all = await withSoftTimeout("command center catalog lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("command_center", {
|
|
1810
|
+
limit: 100,
|
|
1811
|
+
}));
|
|
1812
|
+
const allRows = Array.isArray(all.data) ? all.data : [];
|
|
1813
|
+
return cacheScope(hasId(allRows));
|
|
1814
|
+
}
|
|
1815
|
+
catch {
|
|
1816
|
+
return cacheScope(false);
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
const listInitiativesWithFilters = async (filters) => {
|
|
1820
|
+
const rows = [];
|
|
1821
|
+
const pageSize = 100;
|
|
1822
|
+
const seenIds = new Set();
|
|
1823
|
+
let offset = 0;
|
|
1824
|
+
let page = 0;
|
|
1825
|
+
while (page < PROJECT_SCOPE_MAX_INITIATIVE_PAGES) {
|
|
1826
|
+
const result = await withSoftTimeout("initiative scope lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("initiative", {
|
|
1827
|
+
...filters,
|
|
1828
|
+
limit: pageSize,
|
|
1829
|
+
offset,
|
|
1830
|
+
}));
|
|
1831
|
+
const pageRows = Array.isArray(result.data) ? result.data : [];
|
|
1832
|
+
let addedCount = 0;
|
|
1833
|
+
for (const entry of pageRows) {
|
|
1834
|
+
const record = entry;
|
|
1835
|
+
const id = pickString(record, ["id"]);
|
|
1836
|
+
if (id && seenIds.has(id))
|
|
1837
|
+
continue;
|
|
1838
|
+
if (id)
|
|
1839
|
+
seenIds.add(id);
|
|
1840
|
+
rows.push(entry);
|
|
1841
|
+
addedCount += 1;
|
|
1842
|
+
}
|
|
1843
|
+
const hasMoreFlag = Boolean(result.pagination?.has_more);
|
|
1844
|
+
const likelyMore = hasMoreFlag || pageRows.length >= pageSize;
|
|
1845
|
+
if (!likelyMore)
|
|
1846
|
+
break;
|
|
1847
|
+
if (addedCount === 0)
|
|
1848
|
+
break;
|
|
1849
|
+
offset += pageRows.length;
|
|
1850
|
+
page += 1;
|
|
1851
|
+
}
|
|
1852
|
+
return rows;
|
|
1853
|
+
};
|
|
1854
|
+
const listLiveInitiativesWithFilters = async (filters) => {
|
|
1855
|
+
// Fast path: request once without status fan-out. Upstream often returns
|
|
1856
|
+
// all relevant rows and this avoids 5x paginated round-trips.
|
|
1857
|
+
const broadRows = await listInitiativesWithFilters(filters);
|
|
1858
|
+
if (broadRows.length > 0)
|
|
1859
|
+
return broadRows;
|
|
1860
|
+
// Backward-compat fallback for upstreams that require explicit status.
|
|
1861
|
+
const rows = [];
|
|
1862
|
+
for (const status of LIVE_WORKSPACE_INITIATIVE_STATUSES) {
|
|
1863
|
+
const statusRows = await listInitiativesWithFilters({
|
|
1864
|
+
...filters,
|
|
1865
|
+
status,
|
|
1866
|
+
});
|
|
1867
|
+
rows.push(...statusRows);
|
|
1868
|
+
}
|
|
1869
|
+
return rows;
|
|
1870
|
+
};
|
|
1871
|
+
try {
|
|
1872
|
+
// Workspace selection in the plugin uses command-center IDs.
|
|
1873
|
+
// Resolve that scope first so broad project queries never leak cross-workspace items.
|
|
1874
|
+
const byCommandCenterIds = mapInitiativeIds(await listLiveInitiativesWithFilters({
|
|
1875
|
+
workspace_id: projectId,
|
|
1876
|
+
command_center_id: projectId,
|
|
1877
|
+
}), { commandCenterId: projectId });
|
|
1878
|
+
if (byCommandCenterIds.length > 0)
|
|
1879
|
+
return cacheAndReturn(byCommandCenterIds);
|
|
1880
|
+
}
|
|
1881
|
+
catch {
|
|
1882
|
+
// continue to project-id fallback
|
|
1883
|
+
}
|
|
1884
|
+
try {
|
|
1885
|
+
// Do not hard-return empty for known command-center scopes here.
|
|
1886
|
+
// Some tenants only populate project_id links, so we continue through
|
|
1887
|
+
// project-id fallbacks before concluding the scope is empty.
|
|
1888
|
+
await isKnownCommandCenterScope();
|
|
1889
|
+
}
|
|
1890
|
+
catch {
|
|
1891
|
+
// continue to project-id fallback
|
|
1892
|
+
}
|
|
1893
|
+
try {
|
|
1894
|
+
const byWorkspaceFallback = mapInitiativeIds(await listLiveInitiativesWithFilters({
|
|
1895
|
+
workspace_id: projectId,
|
|
1896
|
+
command_center_id: projectId,
|
|
1897
|
+
}), { projectId });
|
|
1898
|
+
if (byWorkspaceFallback.length > 0)
|
|
1899
|
+
return cacheAndReturn(byWorkspaceFallback);
|
|
1900
|
+
}
|
|
1901
|
+
catch {
|
|
1902
|
+
// continue to empty fallback
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
return cacheAndReturn([]);
|
|
1906
|
+
}
|
|
1907
|
+
catch {
|
|
1908
|
+
return [];
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1334
1911
|
const readNextUpQueueCache = (key, opts) => {
|
|
1335
1912
|
const entry = nextUpQueueCache.get(key);
|
|
1336
1913
|
if (!entry)
|
|
@@ -1361,9 +1938,37 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1361
1938
|
},
|
|
1362
1939
|
});
|
|
1363
1940
|
};
|
|
1941
|
+
const clearNextUpQueueCache = (initiativeId) => {
|
|
1942
|
+
const normalized = initiativeId?.trim() || null;
|
|
1943
|
+
if (!normalized) {
|
|
1944
|
+
nextUpQueueCache.clear();
|
|
1945
|
+
nextUpQueueInFlight.clear();
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
for (const key of Array.from(nextUpQueueCache.keys())) {
|
|
1949
|
+
if (key.endsWith(`::${normalized}`) || key.endsWith("::__all__")) {
|
|
1950
|
+
nextUpQueueCache.delete(key);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
for (const key of Array.from(nextUpQueueInFlight.keys())) {
|
|
1954
|
+
if (key.endsWith(`::${normalized}`) || key.endsWith("::__all__")) {
|
|
1955
|
+
nextUpQueueInFlight.delete(key);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1364
1959
|
async function buildNextUpQueueUncached(input) {
|
|
1365
1960
|
const degraded = [];
|
|
1366
1961
|
const requestedInitiativeId = input?.initiativeId?.trim() || null;
|
|
1962
|
+
const requestedProjectId = input?.projectId?.trim() || null;
|
|
1963
|
+
let allowedInitiativeIds = null;
|
|
1964
|
+
if (requestedProjectId && requestedProjectId.length > 0) {
|
|
1965
|
+
const scopedIds = await listInitiativeIdsForProject({
|
|
1966
|
+
projectId: requestedProjectId,
|
|
1967
|
+
});
|
|
1968
|
+
if (scopedIds.length > 0) {
|
|
1969
|
+
allowedInitiativeIds = new Set(scopedIds);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1367
1972
|
const pinnedQueue = readNextUpQueuePins();
|
|
1368
1973
|
const pinnedRankByKey = new Map();
|
|
1369
1974
|
const pinnedByKey = new Map();
|
|
@@ -1377,6 +1982,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1377
1982
|
preferredMilestoneId: pin.preferredMilestoneId ?? null,
|
|
1378
1983
|
});
|
|
1379
1984
|
}
|
|
1985
|
+
const suppressedKeySet = new Set();
|
|
1986
|
+
for (const suppression of pinnedQueue.suppressions ?? []) {
|
|
1987
|
+
const initiativeId = suppression.initiativeId?.trim();
|
|
1988
|
+
const workstreamId = suppression.workstreamId?.trim();
|
|
1989
|
+
if (!initiativeId || !workstreamId)
|
|
1990
|
+
continue;
|
|
1991
|
+
if (requestedInitiativeId && initiativeId !== requestedInitiativeId)
|
|
1992
|
+
continue;
|
|
1993
|
+
suppressedKeySet.add(`${initiativeId}:${workstreamId}`);
|
|
1994
|
+
}
|
|
1995
|
+
const isSuppressed = (initiativeId, workstreamId) => suppressedKeySet.has(`${initiativeId}:${workstreamId}`);
|
|
1380
1996
|
const initiativeTitleById = new Map();
|
|
1381
1997
|
const initiativeStatusById = new Map();
|
|
1382
1998
|
const initiativePriorityById = new Map();
|
|
@@ -1388,7 +2004,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1388
2004
|
initiativeTitleById.set(id, initiative.title);
|
|
1389
2005
|
initiativeStatusById.set(id, initiative.status || "active");
|
|
1390
2006
|
}
|
|
1391
|
-
const initiativeResult = await listEntitiesSafe(client, "initiative", { limit: 500 })
|
|
2007
|
+
const initiativeResult = await withSoftTimeout("initiative list", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, listEntitiesSafe(client, "initiative", { limit: 500 })).catch((err) => ({
|
|
2008
|
+
items: [],
|
|
2009
|
+
warning: `initiative unavailable (${safeErrorMessage(err)})`,
|
|
2010
|
+
}));
|
|
1392
2011
|
if (initiativeResult.warning)
|
|
1393
2012
|
degraded.push(initiativeResult.warning);
|
|
1394
2013
|
const initiatives = initiativeResult.items;
|
|
@@ -1407,6 +2026,39 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1407
2026
|
if (priority)
|
|
1408
2027
|
initiativePriorityById.set(id, priority);
|
|
1409
2028
|
}
|
|
2029
|
+
const initiativeMatchesRequestedProject = (record) => {
|
|
2030
|
+
if (!requestedProjectId)
|
|
2031
|
+
return true;
|
|
2032
|
+
const scopedValue = pickString(record, [
|
|
2033
|
+
"workspace_id",
|
|
2034
|
+
"workspaceId",
|
|
2035
|
+
"command_center_id",
|
|
2036
|
+
"commandCenterId",
|
|
2037
|
+
"project_id",
|
|
2038
|
+
"projectId",
|
|
2039
|
+
]) ?? null;
|
|
2040
|
+
if (!scopedValue)
|
|
2041
|
+
return false;
|
|
2042
|
+
return scopedValue === requestedProjectId;
|
|
2043
|
+
};
|
|
2044
|
+
if (requestedProjectId && !allowedInitiativeIds) {
|
|
2045
|
+
const metadataScopedIds = initiatives
|
|
2046
|
+
.map((entity) => {
|
|
2047
|
+
const record = entity;
|
|
2048
|
+
const id = pickString(record, ["id"]);
|
|
2049
|
+
if (!id)
|
|
2050
|
+
return null;
|
|
2051
|
+
return initiativeMatchesRequestedProject(record) ? id : null;
|
|
2052
|
+
})
|
|
2053
|
+
.filter((value) => Boolean(value));
|
|
2054
|
+
if (metadataScopedIds.length > 0) {
|
|
2055
|
+
allowedInitiativeIds = new Set(metadataScopedIds);
|
|
2056
|
+
degraded.push("workspace initiative scope lookup returned no rows; using metadata scoped initiatives.");
|
|
2057
|
+
}
|
|
2058
|
+
else {
|
|
2059
|
+
degraded.push("workspace initiative scope lookup returned no rows; local queue may be incomplete.");
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1410
2062
|
for (const [initiativeId, override] of localInitiativeStatusOverrides.entries()) {
|
|
1411
2063
|
initiativeStatusById.set(initiativeId, override.status);
|
|
1412
2064
|
}
|
|
@@ -1466,10 +2118,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1466
2118
|
const buildSessionFallbackQueue = async () => {
|
|
1467
2119
|
let sessionTree = null;
|
|
1468
2120
|
try {
|
|
1469
|
-
sessionTree = await client.getLiveSessions({
|
|
2121
|
+
sessionTree = await withSoftTimeout("live sessions", NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS, client.getLiveSessions({
|
|
1470
2122
|
initiative: requestedInitiativeId,
|
|
2123
|
+
projectId: requestedProjectId,
|
|
1471
2124
|
limit: 500,
|
|
1472
|
-
});
|
|
2125
|
+
}));
|
|
1473
2126
|
}
|
|
1474
2127
|
catch (err) {
|
|
1475
2128
|
degraded.push(`live sessions fallback unavailable (${safeErrorMessage(err)})`);
|
|
@@ -1501,6 +2154,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1501
2154
|
continue;
|
|
1502
2155
|
if (requestedInitiativeId && initiativeId !== requestedInitiativeId)
|
|
1503
2156
|
continue;
|
|
2157
|
+
if (allowedInitiativeIds && !allowedInitiativeIds.has(initiativeId))
|
|
2158
|
+
continue;
|
|
1504
2159
|
const initiativeStatus = initiativeStatusById.get(initiativeId) ?? "active";
|
|
1505
2160
|
if (!isInitiativeActiveStatus(initiativeStatus))
|
|
1506
2161
|
continue;
|
|
@@ -1508,6 +2163,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1508
2163
|
const epoch = parseEpoch(node.updatedAt ?? node.lastEventAt ?? node.startedAt);
|
|
1509
2164
|
const existing = grouped.get(key);
|
|
1510
2165
|
if (!existing) {
|
|
2166
|
+
const runnerAgents = [];
|
|
2167
|
+
const runnerAgentSeen = new Set();
|
|
2168
|
+
pushRunnerAgent(runnerAgents, runnerAgentSeen, {
|
|
2169
|
+
id: node.agentId,
|
|
2170
|
+
name: node.agentName,
|
|
2171
|
+
});
|
|
1511
2172
|
grouped.set(key, {
|
|
1512
2173
|
initiativeId,
|
|
1513
2174
|
workstreamId,
|
|
@@ -1518,6 +2179,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1518
2179
|
workstreamTitle: `Workstream ${workstreamId.slice(0, 8)}`,
|
|
1519
2180
|
statuses: new Set([node.status]),
|
|
1520
2181
|
blockers: Array.isArray(node.blockers) ? [...node.blockers] : [],
|
|
2182
|
+
runnerAgents,
|
|
2183
|
+
runnerAgentSeen,
|
|
1521
2184
|
latest: node,
|
|
1522
2185
|
latestEpoch: epoch,
|
|
1523
2186
|
});
|
|
@@ -1532,6 +2195,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1532
2195
|
existing.blockers.push(blocker);
|
|
1533
2196
|
}
|
|
1534
2197
|
}
|
|
2198
|
+
pushRunnerAgent(existing.runnerAgents, existing.runnerAgentSeen, {
|
|
2199
|
+
id: node.agentId,
|
|
2200
|
+
name: node.agentName,
|
|
2201
|
+
});
|
|
1535
2202
|
if (epoch >= existing.latestEpoch) {
|
|
1536
2203
|
existing.latest = node;
|
|
1537
2204
|
existing.latestEpoch = epoch;
|
|
@@ -1551,11 +2218,22 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1551
2218
|
: hasQueued
|
|
1552
2219
|
? "queued"
|
|
1553
2220
|
: "idle";
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
2221
|
+
const latestRunner = [];
|
|
2222
|
+
const latestRunnerSeen = new Set();
|
|
2223
|
+
pushRunnerAgent(latestRunner, latestRunnerSeen, {
|
|
2224
|
+
id: entry.latest.agentId,
|
|
2225
|
+
name: entry.latest.agentName,
|
|
2226
|
+
});
|
|
2227
|
+
const runnerAgents = latestRunner.length > 0
|
|
2228
|
+
? dedupeWithPrimary(latestRunner, entry.runnerAgents)
|
|
2229
|
+
: [...entry.runnerAgents];
|
|
2230
|
+
const primaryRunner = runnerAgents[0] ?? null;
|
|
2231
|
+
const runnerAgentId = primaryRunner?.id ?? "unassigned";
|
|
2232
|
+
const runnerAgentName = primaryRunner?.name ?? "Unassigned";
|
|
1558
2233
|
const pinKey = `${entry.initiativeId}:${entry.workstreamId}`;
|
|
2234
|
+
if (isSuppressed(entry.initiativeId, entry.workstreamId) && queueState !== "running") {
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
1559
2237
|
fallbackItems.push({
|
|
1560
2238
|
initiativeId: entry.initiativeId,
|
|
1561
2239
|
initiativeTitle: entry.initiativeTitle,
|
|
@@ -1571,10 +2249,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1571
2249
|
nextTaskDueAt: null,
|
|
1572
2250
|
runnerAgentId,
|
|
1573
2251
|
runnerAgentName,
|
|
2252
|
+
runnerAgents,
|
|
1574
2253
|
runnerSource: "fallback",
|
|
1575
2254
|
queueState,
|
|
1576
2255
|
blockReason: hasBlocked
|
|
1577
|
-
? entry.blockers[0] ??
|
|
2256
|
+
? entry.blockers[0] ??
|
|
2257
|
+
(statusValues.includes("failed") ? "Latest run failed" : "Workstream blocked")
|
|
1578
2258
|
: null,
|
|
1579
2259
|
isPinned: pinnedRankByKey.has(pinKey),
|
|
1580
2260
|
pinnedRank: pinnedRankByKey.get(pinKey) ?? null,
|
|
@@ -1591,6 +2271,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1591
2271
|
return false;
|
|
1592
2272
|
if (requestedInitiativeId && id !== requestedInitiativeId)
|
|
1593
2273
|
return false;
|
|
2274
|
+
if (!initiativeMatchesRequestedProject(record))
|
|
2275
|
+
return false;
|
|
2276
|
+
if (allowedInitiativeIds && !allowedInitiativeIds.has(id))
|
|
2277
|
+
return false;
|
|
1594
2278
|
const status = pickString(record, ["status"]);
|
|
1595
2279
|
return isInitiativeActiveStatus(status);
|
|
1596
2280
|
});
|
|
@@ -1616,6 +2300,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1616
2300
|
try {
|
|
1617
2301
|
const data = await withSoftTimeout("live agents", NEXT_UP_LIVE_AGENTS_TIMEOUT_MS, client.getLiveAgents({
|
|
1618
2302
|
initiative: requestedInitiativeId,
|
|
2303
|
+
projectId: requestedProjectId,
|
|
1619
2304
|
includeIdle: true,
|
|
1620
2305
|
}));
|
|
1621
2306
|
for (const raw of Array.isArray(data.agents) ? data.agents : []) {
|
|
@@ -1673,13 +2358,50 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1673
2358
|
return (milestone?.status?.toLowerCase() === "blocked" ||
|
|
1674
2359
|
workstream?.status?.toLowerCase() === "blocked");
|
|
1675
2360
|
};
|
|
2361
|
+
const normalizeSliceScope = (value) => {
|
|
2362
|
+
if (value === "task" || value === "milestone" || value === "workstream") {
|
|
2363
|
+
return value;
|
|
2364
|
+
}
|
|
2365
|
+
return null;
|
|
2366
|
+
};
|
|
2367
|
+
const resolveExecutionPolicyFromActiveRuns = (activeRunIds, workstreamId) => {
|
|
2368
|
+
for (const runId of activeRunIds) {
|
|
2369
|
+
const slice = autoContinueSliceRuns.get(runId);
|
|
2370
|
+
if (!slice)
|
|
2371
|
+
continue;
|
|
2372
|
+
if (typeof slice.workstreamId === "string" && slice.workstreamId.trim()) {
|
|
2373
|
+
if (slice.workstreamId.trim() !== workstreamId)
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
const domain = (slice.domain ?? "").trim();
|
|
2377
|
+
const requiredSkills = Array.isArray(slice.requiredSkills)
|
|
2378
|
+
? slice.requiredSkills.filter((skill) => typeof skill === "string" && skill.trim().length > 0)
|
|
2379
|
+
: [];
|
|
2380
|
+
if (!domain || requiredSkills.length === 0)
|
|
2381
|
+
continue;
|
|
2382
|
+
const executionPolicy = {
|
|
2383
|
+
domain,
|
|
2384
|
+
requiredSkills,
|
|
2385
|
+
};
|
|
2386
|
+
if (typeof slice.behaviorConfigId === "string" && slice.behaviorConfigId.trim()) {
|
|
2387
|
+
executionPolicy.profile = slice.behaviorConfigId.trim();
|
|
2388
|
+
}
|
|
2389
|
+
const scope = normalizeSliceScope(slice.scope ?? null);
|
|
2390
|
+
if (scope) {
|
|
2391
|
+
executionPolicy.sliceScopePreference = scope;
|
|
2392
|
+
}
|
|
2393
|
+
return executionPolicy;
|
|
2394
|
+
}
|
|
2395
|
+
return null;
|
|
2396
|
+
};
|
|
1676
2397
|
for (const workstream of workstreamNodes) {
|
|
2398
|
+
const workstreamKey = `${initiativeId}:${workstream.id}`;
|
|
1677
2399
|
const todoTasks = graph.recentTodos
|
|
1678
2400
|
.map((taskId) => nodeById.get(taskId))
|
|
1679
2401
|
.filter((node) => node?.type === "task" &&
|
|
1680
2402
|
node.workstreamId === workstream.id &&
|
|
1681
2403
|
isTodoStatus(node.status));
|
|
1682
|
-
const pinKey =
|
|
2404
|
+
const pinKey = workstreamKey;
|
|
1683
2405
|
const pin = pinnedByKey.get(pinKey) ?? null;
|
|
1684
2406
|
const preferredTask = pin?.preferredTaskId && nodeById.get(pin.preferredTaskId)
|
|
1685
2407
|
? nodeById.get(pin.preferredTaskId) ?? null
|
|
@@ -1704,12 +2426,100 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1704
2426
|
const preferredReadyTask = preferredCandidates.find((task) => taskIsReady(task) && !taskHasBlockedParent(task));
|
|
1705
2427
|
const candidateTask = preferredReadyTask ?? readyTask ?? todoTasks[0] ?? null;
|
|
1706
2428
|
const autoContinueRun = runningAutoContinueForWorkstream(initiativeId, workstream.id);
|
|
1707
|
-
|
|
2429
|
+
const autoContinueLane = getAutoContinueLaneForWorkstream(initiativeId, workstream.id);
|
|
2430
|
+
const laneState = autoContinueLane?.state ?? null;
|
|
2431
|
+
const scopedAllowedWorkstreams = Array.isArray(autoContinueRun?.allowedWorkstreamIds)
|
|
2432
|
+
? (autoContinueRun.allowedWorkstreamIds
|
|
2433
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2434
|
+
.map((id) => id.trim()))
|
|
2435
|
+
: [];
|
|
2436
|
+
const runScopedToCurrentWorkstream = scopedAllowedWorkstreams.length === 1 &&
|
|
2437
|
+
scopedAllowedWorkstreams[0] === workstream.id &&
|
|
2438
|
+
autoContinueRun?.status === "running";
|
|
2439
|
+
const activeRunIds = Array.isArray(autoContinueRun?.activeSliceRunIds)
|
|
2440
|
+
? autoContinueRun.activeSliceRunIds
|
|
2441
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2442
|
+
.map((id) => id.trim())
|
|
2443
|
+
: [];
|
|
2444
|
+
const activeTaskId = (autoContinueLane?.activeTaskIds?.[0]?.trim() ||
|
|
2445
|
+
autoContinueRun?.activeTaskId?.trim() ||
|
|
2446
|
+
null) ??
|
|
2447
|
+
null;
|
|
2448
|
+
const activeTaskNode = activeTaskId ? nodeById.get(activeTaskId) ?? null : null;
|
|
2449
|
+
const policyTask = candidateTask ??
|
|
2450
|
+
activeTaskNode ??
|
|
2451
|
+
todoTasks.find((task) => task.workstreamId === workstream.id) ??
|
|
2452
|
+
null;
|
|
2453
|
+
const derivedExecutionPolicy = policyTask
|
|
2454
|
+
? deriveExecutionPolicy(policyTask, workstream)
|
|
2455
|
+
: null;
|
|
2456
|
+
const activeExecutionPolicy = resolveExecutionPolicyFromActiveRuns(activeRunIds, workstream.id);
|
|
2457
|
+
const executionPolicy = derivedExecutionPolicy ?? activeExecutionPolicy;
|
|
2458
|
+
const runScope = normalizeSliceScope(autoContinueRun?.scope ?? null);
|
|
2459
|
+
const preferredPolicyScope = normalizeSliceScope(executionPolicy?.sliceScopePreference ?? null);
|
|
2460
|
+
const defaultScope = runScope ??
|
|
2461
|
+
(preferredPolicyScope && preferredPolicyScope !== "task"
|
|
2462
|
+
? preferredPolicyScope
|
|
2463
|
+
: pin?.preferredMilestoneId
|
|
2464
|
+
? "milestone"
|
|
2465
|
+
: "task");
|
|
2466
|
+
const scopeSelection = selectSliceTasksByScope({
|
|
2467
|
+
scope: defaultScope,
|
|
2468
|
+
workstreamId: workstream.id,
|
|
2469
|
+
milestoneId: pin?.preferredMilestoneId ?? null,
|
|
2470
|
+
recentTodos: graph.recentTodos,
|
|
2471
|
+
nodeById,
|
|
2472
|
+
includeVerification: autoContinueRun?.includeVerification ?? false,
|
|
2473
|
+
});
|
|
2474
|
+
const cappedSliceTasks = typeof executionPolicy?.maxSliceTasks === "number" &&
|
|
2475
|
+
executionPolicy.maxSliceTasks > 0
|
|
2476
|
+
? scopeSelection.tasks.slice(0, executionPolicy.maxSliceTasks)
|
|
2477
|
+
: scopeSelection.tasks;
|
|
2478
|
+
const sliceTaskIds = cappedSliceTasks.length > 0
|
|
2479
|
+
? cappedSliceTasks.map((task) => task.id)
|
|
2480
|
+
: candidateTask?.id
|
|
2481
|
+
? [candidateTask.id]
|
|
2482
|
+
: activeTaskId
|
|
2483
|
+
? [activeTaskId]
|
|
2484
|
+
: [];
|
|
2485
|
+
const sliceMilestoneId = defaultScope === "milestone"
|
|
2486
|
+
? scopeSelection.milestoneIds[0] ?? pin?.preferredMilestoneId ?? null
|
|
2487
|
+
: null;
|
|
2488
|
+
let queueState = laneState === "running"
|
|
1708
2489
|
? "running"
|
|
1709
|
-
:
|
|
1710
|
-
? "
|
|
1711
|
-
:
|
|
2490
|
+
: runScopedToCurrentWorkstream
|
|
2491
|
+
? "running"
|
|
2492
|
+
: candidateTask
|
|
2493
|
+
? "queued"
|
|
2494
|
+
: "idle";
|
|
1712
2495
|
let blockReason = null;
|
|
2496
|
+
if (laneState === "blocked") {
|
|
2497
|
+
queueState = "blocked";
|
|
2498
|
+
blockReason = autoContinueLane?.blockedReason ?? "Blocked";
|
|
2499
|
+
}
|
|
2500
|
+
else if (laneState === "waiting_dependency") {
|
|
2501
|
+
queueState = "blocked";
|
|
2502
|
+
if (Array.isArray(autoContinueLane?.waitingOnWorkstreamIds) &&
|
|
2503
|
+
autoContinueLane.waitingOnWorkstreamIds.length > 0) {
|
|
2504
|
+
const waitingTitles = autoContinueLane.waitingOnWorkstreamIds
|
|
2505
|
+
.map((id) => {
|
|
2506
|
+
const node = nodeById.get(id);
|
|
2507
|
+
return node?.type === "workstream" ? node.title : id;
|
|
2508
|
+
})
|
|
2509
|
+
.filter(Boolean);
|
|
2510
|
+
blockReason =
|
|
2511
|
+
waitingTitles.length > 0
|
|
2512
|
+
? `Waiting on ${waitingTitles.slice(0, 2).join(", ")}${waitingTitles.length > 2 ? "…" : ""}`
|
|
2513
|
+
: "Waiting on dependency workstreams";
|
|
2514
|
+
}
|
|
2515
|
+
else {
|
|
2516
|
+
blockReason = "Waiting on dependency workstreams";
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
else if (laneState === "rate_limited") {
|
|
2520
|
+
queueState = "blocked";
|
|
2521
|
+
blockReason = autoContinueLane?.blockedReason ?? "Rate-limited";
|
|
2522
|
+
}
|
|
1713
2523
|
if (!autoContinueRun && !readyTask && candidateTask) {
|
|
1714
2524
|
queueState = "blocked";
|
|
1715
2525
|
const blockedDeps = candidateTask.dependencyIds
|
|
@@ -1729,27 +2539,48 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1729
2539
|
if (!candidateTask && !autoContinueRun && !pin) {
|
|
1730
2540
|
continue;
|
|
1731
2541
|
}
|
|
2542
|
+
if (isSuppressed(initiativeId, workstream.id) && queueState !== "running") {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
1732
2545
|
runningWorkstreams.add(workstream.id);
|
|
1733
|
-
const
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
(
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
const
|
|
2546
|
+
const assignedRunnerAgents = [];
|
|
2547
|
+
const assignedRunnerSeen = new Set();
|
|
2548
|
+
for (const agent of workstream.assignedAgents) {
|
|
2549
|
+
pushRunnerAgent(assignedRunnerAgents, assignedRunnerSeen, {
|
|
2550
|
+
id: agent.id,
|
|
2551
|
+
name: agent.name,
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
const inferredRunnerAgents = [];
|
|
2555
|
+
const inferredRunnerSeen = new Set();
|
|
2556
|
+
for (const agent of graph.initiative.assignedAgents) {
|
|
2557
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2558
|
+
id: agent.id,
|
|
2559
|
+
name: agent.name,
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
for (const agent of liveAgentsByInitiative.get(initiativeId) ?? []) {
|
|
2563
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2564
|
+
id: agent.id,
|
|
2565
|
+
name: agent.name,
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
if (autoContinueRun?.agentId) {
|
|
2569
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2570
|
+
id: autoContinueRun.agentId,
|
|
2571
|
+
name: agentCatalogById.get(autoContinueRun.agentId)?.name ??
|
|
2572
|
+
autoContinueRun.agentId,
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
const runnerAgents = assignedRunnerAgents.length > 0 ? assignedRunnerAgents : inferredRunnerAgents;
|
|
2576
|
+
const runnerSource = assignedRunnerAgents.length > 0
|
|
1744
2577
|
? "assigned"
|
|
1745
|
-
:
|
|
2578
|
+
: runnerAgents.length > 0
|
|
1746
2579
|
? "inferred"
|
|
1747
2580
|
: "fallback";
|
|
1748
|
-
const
|
|
1749
|
-
const runnerAgentId =
|
|
1750
|
-
const runnerAgentName =
|
|
1751
|
-
agentCatalogById.get(runnerAgentId)?.name ??
|
|
1752
|
-
runnerAgentId;
|
|
2581
|
+
const primaryRunner = runnerAgents[0] ?? null;
|
|
2582
|
+
const runnerAgentId = primaryRunner?.id ?? "unassigned";
|
|
2583
|
+
const runnerAgentName = primaryRunner?.name ?? "Unassigned";
|
|
1753
2584
|
itemsForInitiative.push({
|
|
1754
2585
|
initiativeId,
|
|
1755
2586
|
initiativeTitle,
|
|
@@ -1758,25 +2589,50 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1758
2589
|
workstreamTitle: workstream.title,
|
|
1759
2590
|
workstreamStatus: workstream.status,
|
|
1760
2591
|
nextTaskId: candidateTask?.id ??
|
|
1761
|
-
|
|
2592
|
+
activeTaskId,
|
|
1762
2593
|
nextTaskTitle: candidateTask?.title ??
|
|
1763
|
-
(
|
|
1764
|
-
? nodeById.get(
|
|
2594
|
+
((activeTaskId)
|
|
2595
|
+
? nodeById.get(activeTaskId)?.title ?? null
|
|
1765
2596
|
: null),
|
|
1766
2597
|
nextTaskPriority: candidateTask?.priorityNum ?? null,
|
|
1767
2598
|
nextTaskDueAt: candidateTask?.dueDate ?? null,
|
|
1768
2599
|
runnerAgentId,
|
|
1769
2600
|
runnerAgentName,
|
|
2601
|
+
runnerAgents,
|
|
1770
2602
|
runnerSource,
|
|
1771
2603
|
queueState,
|
|
1772
2604
|
blockReason,
|
|
1773
2605
|
isPinned: Boolean(pin),
|
|
1774
2606
|
pinnedRank: pin ? (pinnedRankByKey.get(pinKey) ?? null) : null,
|
|
2607
|
+
sliceScope: defaultScope,
|
|
2608
|
+
sliceTaskIds,
|
|
2609
|
+
sliceTaskCount: sliceTaskIds.length,
|
|
2610
|
+
sliceMilestoneId,
|
|
2611
|
+
executionPolicy,
|
|
1775
2612
|
autoContinue: autoContinueRun
|
|
1776
2613
|
? {
|
|
1777
2614
|
status: autoContinueRun.status,
|
|
1778
2615
|
activeTaskId: autoContinueRun.activeTaskId,
|
|
1779
2616
|
activeRunId: autoContinueRun.activeRunId,
|
|
2617
|
+
activeTaskIds: Array.isArray(autoContinueRun.activeTaskIds)
|
|
2618
|
+
? autoContinueRun.activeTaskIds
|
|
2619
|
+
: [],
|
|
2620
|
+
activeRunIds: Array.isArray(autoContinueRun.activeSliceRunIds)
|
|
2621
|
+
? autoContinueRun.activeSliceRunIds
|
|
2622
|
+
: [],
|
|
2623
|
+
laneState,
|
|
2624
|
+
laneBlockedReason: autoContinueLane?.blockedReason ?? null,
|
|
2625
|
+
laneWaitingOnWorkstreamIds: Array.isArray(autoContinueLane?.waitingOnWorkstreamIds)
|
|
2626
|
+
? autoContinueLane.waitingOnWorkstreamIds
|
|
2627
|
+
: [],
|
|
2628
|
+
laneRetryAt: autoContinueLane?.retryAt ?? null,
|
|
2629
|
+
maxParallelSlices: typeof autoContinueRun.maxParallelSlices === "number"
|
|
2630
|
+
? autoContinueRun.maxParallelSlices
|
|
2631
|
+
: 1,
|
|
2632
|
+
parallelMode: (typeof autoContinueRun.parallelMode === "string" &&
|
|
2633
|
+
autoContinueRun.parallelMode.toLowerCase() === "iwmt"
|
|
2634
|
+
? "iwmt"
|
|
2635
|
+
: "iwmt"),
|
|
1780
2636
|
stopReason: autoContinueRun.stopReason,
|
|
1781
2637
|
updatedAt: autoContinueRun.updatedAt,
|
|
1782
2638
|
}
|
|
@@ -1794,6 +2650,44 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1794
2650
|
const workstream = nodeById.get(workstreamId);
|
|
1795
2651
|
if (!workstream || workstream.type !== "workstream")
|
|
1796
2652
|
continue;
|
|
2653
|
+
const lane = getAutoContinueLaneForWorkstream(initiativeId, workstream.id);
|
|
2654
|
+
if (!lane &&
|
|
2655
|
+
!(typeof run.activeRunId === "string" && run.activeRunId.trim().length > 0)) {
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
const laneState = lane?.state ?? null;
|
|
2659
|
+
const activeRunIds = Array.isArray(run.activeSliceRunIds)
|
|
2660
|
+
? run.activeSliceRunIds
|
|
2661
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2662
|
+
.map((id) => id.trim())
|
|
2663
|
+
: [];
|
|
2664
|
+
const activeTaskId = lane?.activeTaskIds?.[0] ?? run.activeTaskId;
|
|
2665
|
+
const activeTaskNode = activeTaskId ? nodeById.get(activeTaskId) ?? null : null;
|
|
2666
|
+
const executionPolicy = (activeTaskNode ? deriveExecutionPolicy(activeTaskNode, workstream) : null) ??
|
|
2667
|
+
resolveExecutionPolicyFromActiveRuns(activeRunIds, workstream.id);
|
|
2668
|
+
const sliceScope = normalizeSliceScope(run.scope ?? null) ?? "task";
|
|
2669
|
+
const sliceTaskIds = lane?.activeTaskIds?.length
|
|
2670
|
+
? lane.activeTaskIds
|
|
2671
|
+
: activeTaskId
|
|
2672
|
+
? [activeTaskId]
|
|
2673
|
+
: [];
|
|
2674
|
+
const queueState = laneState === "running"
|
|
2675
|
+
? "running"
|
|
2676
|
+
: laneState === "blocked" ||
|
|
2677
|
+
laneState === "waiting_dependency" ||
|
|
2678
|
+
laneState === "rate_limited"
|
|
2679
|
+
? "blocked"
|
|
2680
|
+
: "queued";
|
|
2681
|
+
if (isSuppressed(initiativeId, workstream.id) && queueState !== "running") {
|
|
2682
|
+
continue;
|
|
2683
|
+
}
|
|
2684
|
+
const runRunnerAgents = [];
|
|
2685
|
+
const runRunnerSeen = new Set();
|
|
2686
|
+
pushRunnerAgent(runRunnerAgents, runRunnerSeen, {
|
|
2687
|
+
id: run.agentId,
|
|
2688
|
+
name: agentCatalogById.get(run.agentId)?.name ?? run.agentId,
|
|
2689
|
+
});
|
|
2690
|
+
const runPrimaryRunner = runRunnerAgents[0] ?? null;
|
|
1797
2691
|
itemsForInitiative.push({
|
|
1798
2692
|
initiativeId,
|
|
1799
2693
|
initiativeTitle,
|
|
@@ -1801,23 +2695,52 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1801
2695
|
workstreamId: workstream.id,
|
|
1802
2696
|
workstreamTitle: workstream.title,
|
|
1803
2697
|
workstreamStatus: workstream.status,
|
|
1804
|
-
nextTaskId:
|
|
1805
|
-
nextTaskTitle:
|
|
1806
|
-
? nodeById.get(
|
|
2698
|
+
nextTaskId: activeTaskId ?? null,
|
|
2699
|
+
nextTaskTitle: activeTaskId
|
|
2700
|
+
? nodeById.get(activeTaskId)?.title ?? null
|
|
1807
2701
|
: null,
|
|
1808
2702
|
nextTaskPriority: null,
|
|
1809
2703
|
nextTaskDueAt: null,
|
|
1810
|
-
runnerAgentId:
|
|
1811
|
-
runnerAgentName:
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2704
|
+
runnerAgentId: runPrimaryRunner?.id ?? "unassigned",
|
|
2705
|
+
runnerAgentName: runPrimaryRunner?.name ?? "Unassigned",
|
|
2706
|
+
runnerAgents: runRunnerAgents,
|
|
2707
|
+
runnerSource: runPrimaryRunner ? "inferred" : "fallback",
|
|
2708
|
+
queueState,
|
|
2709
|
+
blockReason: queueState === "blocked"
|
|
2710
|
+
? lane?.blockedReason ?? "Blocked"
|
|
2711
|
+
: null,
|
|
1815
2712
|
isPinned: Boolean(pinnedByKey.get(`${initiativeId}:${workstream.id}`)),
|
|
1816
2713
|
pinnedRank: pinnedRankByKey.get(`${initiativeId}:${workstream.id}`) ?? null,
|
|
2714
|
+
sliceScope,
|
|
2715
|
+
sliceTaskIds,
|
|
2716
|
+
sliceTaskCount: sliceTaskIds.length,
|
|
2717
|
+
sliceMilestoneId: sliceScope === "milestone"
|
|
2718
|
+
? activeTaskNode?.milestoneId ?? null
|
|
2719
|
+
: null,
|
|
2720
|
+
executionPolicy,
|
|
1817
2721
|
autoContinue: {
|
|
1818
2722
|
status: run.status,
|
|
1819
2723
|
activeTaskId: run.activeTaskId,
|
|
1820
2724
|
activeRunId: run.activeRunId,
|
|
2725
|
+
activeTaskIds: Array.isArray(run.activeTaskIds)
|
|
2726
|
+
? run.activeTaskIds
|
|
2727
|
+
: [],
|
|
2728
|
+
activeRunIds: Array.isArray(run.activeSliceRunIds)
|
|
2729
|
+
? run.activeSliceRunIds
|
|
2730
|
+
: [],
|
|
2731
|
+
laneState,
|
|
2732
|
+
laneBlockedReason: lane?.blockedReason ?? null,
|
|
2733
|
+
laneWaitingOnWorkstreamIds: Array.isArray(lane?.waitingOnWorkstreamIds)
|
|
2734
|
+
? lane.waitingOnWorkstreamIds
|
|
2735
|
+
: [],
|
|
2736
|
+
laneRetryAt: lane?.retryAt ?? null,
|
|
2737
|
+
maxParallelSlices: typeof run.maxParallelSlices === "number"
|
|
2738
|
+
? run.maxParallelSlices
|
|
2739
|
+
: 1,
|
|
2740
|
+
parallelMode: typeof run.parallelMode === "string" &&
|
|
2741
|
+
run.parallelMode.toLowerCase() === "iwmt"
|
|
2742
|
+
? "iwmt"
|
|
2743
|
+
: "iwmt",
|
|
1821
2744
|
stopReason: run.stopReason,
|
|
1822
2745
|
updatedAt: run.updatedAt,
|
|
1823
2746
|
},
|
|
@@ -1839,7 +2762,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1839
2762
|
return { items, degraded };
|
|
1840
2763
|
}
|
|
1841
2764
|
async function buildNextUpQueue(input) {
|
|
1842
|
-
const key = nextUpQueueCacheKeyFor(input?.initiativeId?.trim() || null);
|
|
2765
|
+
const key = nextUpQueueCacheKeyFor(input?.initiativeId?.trim() || null, input?.projectId?.trim() || null);
|
|
1843
2766
|
const fresh = readNextUpQueueCache(key, { allowStale: false });
|
|
1844
2767
|
if (fresh)
|
|
1845
2768
|
return fresh;
|
|
@@ -1883,6 +2806,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1883
2806
|
nextUpQueueInFlight.delete(key);
|
|
1884
2807
|
}
|
|
1885
2808
|
}
|
|
2809
|
+
// Prime queue cache shortly after boot so first dashboard paint is not cold.
|
|
2810
|
+
const prewarmNextUpQueue = () => {
|
|
2811
|
+
void buildNextUpQueue({ initiativeId: null, projectId: null }).catch(() => {
|
|
2812
|
+
// best effort prewarm only
|
|
2813
|
+
});
|
|
2814
|
+
};
|
|
2815
|
+
const nextUpPrewarmTimer = setTimeout(prewarmNextUpQueue, 75);
|
|
2816
|
+
nextUpPrewarmTimer.unref?.();
|
|
1886
2817
|
const autoContinueTimer = setInterval(() => {
|
|
1887
2818
|
void tickAllAutoContinue();
|
|
1888
2819
|
}, AUTO_CONTINUE_TICK_MS);
|
|
@@ -1916,6 +2847,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1916
2847
|
formatInitiatives,
|
|
1917
2848
|
getOnboardingState: async () => getOnboardingState(await onboarding.getStatus()),
|
|
1918
2849
|
});
|
|
2850
|
+
registerUsageRoutes(apiRouter, {
|
|
2851
|
+
client,
|
|
2852
|
+
listActivityPage: ({ limit, runId, since, until, cursor }) => listActivityPage({ limit, runId, since, until, cursor }),
|
|
2853
|
+
sendJson,
|
|
2854
|
+
safeErrorMessage,
|
|
2855
|
+
});
|
|
1919
2856
|
registerAgentSuiteRoutes(apiRouter, {
|
|
1920
2857
|
pluginVersion: config.pluginVersion,
|
|
1921
2858
|
telemetryDistinctId,
|
|
@@ -1926,6 +2863,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1926
2863
|
applyOrgxAgentSuitePlan,
|
|
1927
2864
|
generateAgentSuiteOperationId,
|
|
1928
2865
|
updateSkillPackPolicy,
|
|
2866
|
+
rollbackSkillPackPolicy,
|
|
2867
|
+
fetchAgentRuntimeSettings: ({ workspaceId, projectId } = {}) => client.getClientAgentRuntimeSettings({
|
|
2868
|
+
workspaceId: workspaceId ?? projectId ?? null,
|
|
2869
|
+
}),
|
|
2870
|
+
updateAgentRuntimeSettings: (payload) => client.updateClientAgentRuntimeSettings(payload),
|
|
1929
2871
|
posthogCapture,
|
|
1930
2872
|
sendJson,
|
|
1931
2873
|
safeErrorMessage,
|
|
@@ -1944,13 +2886,20 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1944
2886
|
sendJson,
|
|
1945
2887
|
safeErrorMessage,
|
|
1946
2888
|
});
|
|
2889
|
+
registerSentinelsCatalogRoutes(apiRouter, {
|
|
2890
|
+
sendJson,
|
|
2891
|
+
safeErrorMessage,
|
|
2892
|
+
});
|
|
1947
2893
|
registerMissionControlReadRoutes(apiRouter, {
|
|
1948
2894
|
autoContinueRuns,
|
|
1949
2895
|
defaultAutoContinueTokenBudget,
|
|
2896
|
+
defaultAutoContinueMaxParallelSlices,
|
|
1950
2897
|
autoContinueTickMs: AUTO_CONTINUE_TICK_MS,
|
|
1951
2898
|
buildMissionControlGraph: (initiativeId) => buildMissionControlGraph(client, initiativeId),
|
|
1952
2899
|
applyLocalInitiativeOverrideToGraph: (graph) => applyLocalInitiativeOverrideToGraph(graph),
|
|
2900
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
1953
2901
|
buildNextUpQueue,
|
|
2902
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
1954
2903
|
sendJson,
|
|
1955
2904
|
safeErrorMessage,
|
|
1956
2905
|
});
|
|
@@ -1993,12 +2942,127 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1993
2942
|
applyLocalInitiativeOverrides,
|
|
1994
2943
|
formatInitiatives,
|
|
1995
2944
|
getSnapshot,
|
|
2945
|
+
scheduleWorkstreamReassignment: async (input) => {
|
|
2946
|
+
const initiativeId = input.initiativeId.trim();
|
|
2947
|
+
const workstreamId = input.workstreamId.trim();
|
|
2948
|
+
if (!initiativeId || !workstreamId)
|
|
2949
|
+
return null;
|
|
2950
|
+
const normalizedStatus = (input.status ?? "").trim().toLowerCase();
|
|
2951
|
+
const shouldRedispatch = normalizedStatus === "active" ||
|
|
2952
|
+
normalizedStatus === "ready" ||
|
|
2953
|
+
normalizedStatus === "queued" ||
|
|
2954
|
+
normalizedStatus === "running" ||
|
|
2955
|
+
normalizedStatus === "in_progress" ||
|
|
2956
|
+
normalizedStatus === "pending";
|
|
2957
|
+
if (!shouldRedispatch)
|
|
2958
|
+
return null;
|
|
2959
|
+
if (!isDispatchableWorkstreamStatus(normalizedStatus))
|
|
2960
|
+
return null;
|
|
2961
|
+
const liveRun = runningAutoContinueForWorkstream(initiativeId, workstreamId);
|
|
2962
|
+
return await scheduleAutoFixForWorkstream({
|
|
2963
|
+
initiativeId,
|
|
2964
|
+
workstreamId,
|
|
2965
|
+
runId: liveRun?.activeRunId ?? null,
|
|
2966
|
+
event: input.event,
|
|
2967
|
+
requestedByAgentId: "system",
|
|
2968
|
+
requestedByAgentName: "System",
|
|
2969
|
+
graceMs: 5_000,
|
|
2970
|
+
});
|
|
2971
|
+
},
|
|
1996
2972
|
sendJson,
|
|
1997
2973
|
safeErrorMessage,
|
|
1998
2974
|
});
|
|
2975
|
+
const readCachedDecisionRows = () => {
|
|
2976
|
+
const snapshots = [
|
|
2977
|
+
readSnapshotResponseCache("live-snapshot"),
|
|
2978
|
+
readSnapshotResponseCache("dashboard-bundle"),
|
|
2979
|
+
readSnapshotResponseCache("live-snapshot-v2"),
|
|
2980
|
+
];
|
|
2981
|
+
const rows = [];
|
|
2982
|
+
for (const snapshot of snapshots) {
|
|
2983
|
+
if (!snapshot || typeof snapshot !== "object")
|
|
2984
|
+
continue;
|
|
2985
|
+
const decisionsRaw = snapshot.decisions;
|
|
2986
|
+
if (!Array.isArray(decisionsRaw))
|
|
2987
|
+
continue;
|
|
2988
|
+
for (const entry of decisionsRaw) {
|
|
2989
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
2990
|
+
continue;
|
|
2991
|
+
rows.push(entry);
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
return rows;
|
|
2995
|
+
};
|
|
2996
|
+
const emitDecisionResolvedActivity = async (input) => {
|
|
2997
|
+
const ids = Array.from(new Set(input.ids
|
|
2998
|
+
.filter((id) => typeof id === "string")
|
|
2999
|
+
.map((id) => id.trim())
|
|
3000
|
+
.filter(Boolean)));
|
|
3001
|
+
if (ids.length === 0)
|
|
3002
|
+
return;
|
|
3003
|
+
const decisionById = new Map();
|
|
3004
|
+
for (const row of readCachedDecisionRows()) {
|
|
3005
|
+
const rowId = pickString(row, ["id"])?.trim() ?? "";
|
|
3006
|
+
if (!rowId || decisionById.has(rowId))
|
|
3007
|
+
continue;
|
|
3008
|
+
decisionById.set(rowId, row);
|
|
3009
|
+
}
|
|
3010
|
+
for (const decisionId of ids) {
|
|
3011
|
+
const row = decisionById.get(decisionId) ?? null;
|
|
3012
|
+
const decisionTitle = pickString(row ?? {}, ["title", "summary"]) ??
|
|
3013
|
+
`Decision ${decisionId.slice(0, 8)}`;
|
|
3014
|
+
const scopedInitiativeId = input.initiativeId ??
|
|
3015
|
+
pickString(row ?? {}, ["initiative_id", "initiativeId"]) ??
|
|
3016
|
+
null;
|
|
3017
|
+
const scopedRunId = input.sliceRunId ??
|
|
3018
|
+
pickString(row ?? {}, [
|
|
3019
|
+
"run_id",
|
|
3020
|
+
"runId",
|
|
3021
|
+
"source_run_id",
|
|
3022
|
+
"sourceRunId",
|
|
3023
|
+
"correlation_id",
|
|
3024
|
+
"correlationId",
|
|
3025
|
+
]) ??
|
|
3026
|
+
null;
|
|
3027
|
+
await emitActivitySafe({
|
|
3028
|
+
initiativeId: scopedInitiativeId,
|
|
3029
|
+
runId: scopedRunId ?? undefined,
|
|
3030
|
+
correlationId: scopedRunId ?? undefined,
|
|
3031
|
+
phase: "review",
|
|
3032
|
+
level: "info",
|
|
3033
|
+
message: `Decision ${input.action === "approve" ? "approved" : "rejected"}: ${decisionTitle}`,
|
|
3034
|
+
progressPct: 100,
|
|
3035
|
+
nextStep: input.action === "approve"
|
|
3036
|
+
? "Execution can continue with the approved direction."
|
|
3037
|
+
: "Review the rejected decision and provide revised guidance to continue safely.",
|
|
3038
|
+
metadata: {
|
|
3039
|
+
event: "decision_resolved",
|
|
3040
|
+
action: input.action,
|
|
3041
|
+
resolver: "human",
|
|
3042
|
+
decision_id: decisionId,
|
|
3043
|
+
decision_ids: ids,
|
|
3044
|
+
decision_title: decisionTitle,
|
|
3045
|
+
initiative_id: scopedInitiativeId,
|
|
3046
|
+
workstream_id: pickString(row ?? {}, ["workstream_id", "workstreamId"]) ?? null,
|
|
3047
|
+
source_run_id: scopedRunId,
|
|
3048
|
+
option_id: input.optionId ?? null,
|
|
3049
|
+
note: input.note ?? null,
|
|
3050
|
+
slice_run_id: input.sliceRunId ?? null,
|
|
3051
|
+
},
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
1999
3055
|
registerDecisionActionsRoutes(apiRouter, {
|
|
2000
3056
|
parseJsonRequest,
|
|
2001
|
-
bulkDecideDecisions: (ids, action,
|
|
3057
|
+
bulkDecideDecisions: (ids, action, input) => client.bulkDecideDecisions(ids, action, input),
|
|
3058
|
+
emitDecisionResolvedActivity: async (ids, action, input) => {
|
|
3059
|
+
await emitDecisionResolvedActivity({
|
|
3060
|
+
ids,
|
|
3061
|
+
action,
|
|
3062
|
+
note: input?.note ?? null,
|
|
3063
|
+
optionId: input?.optionId ?? null,
|
|
3064
|
+
});
|
|
3065
|
+
},
|
|
2002
3066
|
sendJson,
|
|
2003
3067
|
safeErrorMessage,
|
|
2004
3068
|
});
|
|
@@ -2009,6 +3073,147 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2009
3073
|
createRunCheckpoint: (runId, input) => client.createRunCheckpoint(runId, input),
|
|
2010
3074
|
restoreRunCheckpoint: (runId, input) => client.restoreRunCheckpoint(runId, input),
|
|
2011
3075
|
runAction: (runId, action, input) => client.runAction(runId, action, input),
|
|
3076
|
+
markRunCompleted: async (runId, input) => {
|
|
3077
|
+
const normalizedRunId = runId.trim();
|
|
3078
|
+
if (!normalizedRunId) {
|
|
3079
|
+
throw new Error("runId is required");
|
|
3080
|
+
}
|
|
3081
|
+
const nowIso = new Date().toISOString();
|
|
3082
|
+
const reason = input.reason?.trim() || null;
|
|
3083
|
+
const message = reason
|
|
3084
|
+
? `Marked completed from dashboard (${reason}).`
|
|
3085
|
+
: "Marked completed from dashboard.";
|
|
3086
|
+
const existingRun = getAgentRun(normalizedRunId);
|
|
3087
|
+
// ── Try OrgX-side completion first (for remote sessions) ────────────
|
|
3088
|
+
let remoteOk = false;
|
|
3089
|
+
try {
|
|
3090
|
+
await client.updateEntity("run", normalizedRunId, {
|
|
3091
|
+
status: "completed",
|
|
3092
|
+
phase: "completed",
|
|
3093
|
+
});
|
|
3094
|
+
remoteOk = true;
|
|
3095
|
+
}
|
|
3096
|
+
catch {
|
|
3097
|
+
// OrgX may not support updating runs directly — fall through to local
|
|
3098
|
+
}
|
|
3099
|
+
// ── Local operations (defensive — partial failures don't block) ─────
|
|
3100
|
+
let runtimeRecord = null;
|
|
3101
|
+
try {
|
|
3102
|
+
runtimeRecord = upsertRuntimeInstanceFromHook({
|
|
3103
|
+
source_client: "api",
|
|
3104
|
+
event: "session_stop",
|
|
3105
|
+
run_id: normalizedRunId,
|
|
3106
|
+
correlation_id: normalizedRunId,
|
|
3107
|
+
initiative_id: existingRun?.initiativeId ?? null,
|
|
3108
|
+
workstream_id: existingRun?.workstreamId ?? null,
|
|
3109
|
+
task_id: existingRun?.taskId ?? null,
|
|
3110
|
+
agent_id: existingRun?.agentId ?? null,
|
|
3111
|
+
phase: "completed",
|
|
3112
|
+
message,
|
|
3113
|
+
timestamp: nowIso,
|
|
3114
|
+
metadata: {
|
|
3115
|
+
source: "dashboard_manual_complete",
|
|
3116
|
+
reason,
|
|
3117
|
+
},
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
catch (err) {
|
|
3121
|
+
console.error(`[markRunCompleted] upsertRuntime failed for ${normalizedRunId}:`, err);
|
|
3122
|
+
}
|
|
3123
|
+
try {
|
|
3124
|
+
markAgentRunStopped(normalizedRunId);
|
|
3125
|
+
}
|
|
3126
|
+
catch (err) {
|
|
3127
|
+
console.error(`[markRunCompleted] markAgentRunStopped failed for ${normalizedRunId}:`, err);
|
|
3128
|
+
}
|
|
3129
|
+
if (runtimeRecord) {
|
|
3130
|
+
broadcastRuntimeSse("runtime.updated", runtimeRecord);
|
|
3131
|
+
}
|
|
3132
|
+
clearSnapshotResponseCache();
|
|
3133
|
+
try {
|
|
3134
|
+
appendActivityItems([
|
|
3135
|
+
{
|
|
3136
|
+
id: randomUUID(),
|
|
3137
|
+
type: "run_completed",
|
|
3138
|
+
title: message,
|
|
3139
|
+
description: reason,
|
|
3140
|
+
agentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3141
|
+
agentName: runtimeRecord?.agentName ?? null,
|
|
3142
|
+
requesterAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3143
|
+
requesterAgentName: runtimeRecord?.agentName ?? null,
|
|
3144
|
+
executorAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3145
|
+
executorAgentName: runtimeRecord?.agentName ?? null,
|
|
3146
|
+
runId: normalizedRunId,
|
|
3147
|
+
initiativeId: runtimeRecord?.initiativeId ?? existingRun?.initiativeId ?? null,
|
|
3148
|
+
timestamp: nowIso,
|
|
3149
|
+
phase: "completed",
|
|
3150
|
+
state: "done",
|
|
3151
|
+
summary: message,
|
|
3152
|
+
metadata: {
|
|
3153
|
+
source: "dashboard_manual_complete",
|
|
3154
|
+
reason,
|
|
3155
|
+
remoteOk,
|
|
3156
|
+
event: "dashboard_run_mark_completed",
|
|
3157
|
+
},
|
|
3158
|
+
},
|
|
3159
|
+
]);
|
|
3160
|
+
}
|
|
3161
|
+
catch (err) {
|
|
3162
|
+
console.error(`[markRunCompleted] appendActivity failed for ${normalizedRunId}:`, err);
|
|
3163
|
+
}
|
|
3164
|
+
// ── Write to outbox so snapshot merge picks up the completion ───────
|
|
3165
|
+
try {
|
|
3166
|
+
const outboxSessionId = runtimeRecord?.initiativeId ?? existingRun?.initiativeId ?? normalizedRunId;
|
|
3167
|
+
const outboxActivityItem = {
|
|
3168
|
+
id: randomUUID(),
|
|
3169
|
+
type: "run_completed",
|
|
3170
|
+
title: message,
|
|
3171
|
+
description: reason,
|
|
3172
|
+
agentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3173
|
+
agentName: runtimeRecord?.agentName ?? null,
|
|
3174
|
+
requesterAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3175
|
+
requesterAgentName: runtimeRecord?.agentName ?? null,
|
|
3176
|
+
executorAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3177
|
+
executorAgentName: runtimeRecord?.agentName ?? null,
|
|
3178
|
+
runId: normalizedRunId,
|
|
3179
|
+
initiativeId: runtimeRecord?.initiativeId ?? existingRun?.initiativeId ?? null,
|
|
3180
|
+
timestamp: nowIso,
|
|
3181
|
+
phase: "completed",
|
|
3182
|
+
state: "done",
|
|
3183
|
+
summary: message,
|
|
3184
|
+
metadata: {
|
|
3185
|
+
source: "dashboard_manual_complete",
|
|
3186
|
+
reason,
|
|
3187
|
+
remoteOk,
|
|
3188
|
+
event: "dashboard_run_mark_completed",
|
|
3189
|
+
},
|
|
3190
|
+
};
|
|
3191
|
+
await appendToOutbox(outboxSessionId, {
|
|
3192
|
+
id: outboxActivityItem.id,
|
|
3193
|
+
type: "outcome",
|
|
3194
|
+
timestamp: nowIso,
|
|
3195
|
+
payload: {
|
|
3196
|
+
runId: normalizedRunId,
|
|
3197
|
+
status: "completed",
|
|
3198
|
+
reason,
|
|
3199
|
+
remoteOk,
|
|
3200
|
+
},
|
|
3201
|
+
activityItem: outboxActivityItem,
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
catch (err) {
|
|
3205
|
+
console.error(`[markRunCompleted] outbox write failed for ${normalizedRunId}:`, err);
|
|
3206
|
+
}
|
|
3207
|
+
return {
|
|
3208
|
+
ok: true,
|
|
3209
|
+
data: {
|
|
3210
|
+
runId: normalizedRunId,
|
|
3211
|
+
action: "complete",
|
|
3212
|
+
status: "completed",
|
|
3213
|
+
remoteOk,
|
|
3214
|
+
},
|
|
3215
|
+
};
|
|
3216
|
+
},
|
|
2012
3217
|
sendJson,
|
|
2013
3218
|
safeErrorMessage,
|
|
2014
3219
|
});
|
|
@@ -2032,6 +3237,20 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2032
3237
|
sendJson,
|
|
2033
3238
|
safeErrorMessage,
|
|
2034
3239
|
});
|
|
3240
|
+
registerChatRoutes(apiRouter, {
|
|
3241
|
+
parseJsonRequest,
|
|
3242
|
+
pickString,
|
|
3243
|
+
parsePositiveInt,
|
|
3244
|
+
emitActivitySafe,
|
|
3245
|
+
sendJson,
|
|
3246
|
+
safeErrorMessage,
|
|
3247
|
+
});
|
|
3248
|
+
registerRealtimeOrchestratorRoutes(apiRouter, {
|
|
3249
|
+
parseJsonRequest,
|
|
3250
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
3251
|
+
sendJson,
|
|
3252
|
+
safeErrorMessage,
|
|
3253
|
+
});
|
|
2035
3254
|
registerMissionControlActionsRoutes(apiRouter, {
|
|
2036
3255
|
parseJsonRequest,
|
|
2037
3256
|
pickString,
|
|
@@ -2049,11 +3268,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2049
3268
|
stopAutoContinueRun,
|
|
2050
3269
|
updateInitiativeAutoContinueState,
|
|
2051
3270
|
tickAllAutoContinue,
|
|
3271
|
+
scheduleAutoFixForWorkstream,
|
|
2052
3272
|
upsertNextUpQueuePin,
|
|
2053
3273
|
removeNextUpQueuePin,
|
|
3274
|
+
suppressNextUpQueueItem,
|
|
2054
3275
|
setNextUpQueuePinOrder,
|
|
3276
|
+
clearNextUpQueueCache,
|
|
2055
3277
|
resolveAutoAssignments,
|
|
3278
|
+
buildMissionControlGraph: (initiativeId) => buildMissionControlGraph(client, initiativeId),
|
|
3279
|
+
applyLocalInitiativeOverrideToGraph: (graph) => applyLocalInitiativeOverrideToGraph(graph),
|
|
2056
3280
|
client,
|
|
3281
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
2057
3282
|
sendJson,
|
|
2058
3283
|
safeErrorMessage,
|
|
2059
3284
|
});
|
|
@@ -2095,10 +3320,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2095
3320
|
parseJsonRequest,
|
|
2096
3321
|
pickString,
|
|
2097
3322
|
summarizeActivityHeadline,
|
|
2098
|
-
getLiveAgents: ({ initiative, includeIdle }) => client.getLiveAgents({ initiative, includeIdle }),
|
|
2099
|
-
getLiveInitiatives: ({ id, limit }) => client.getLiveInitiatives({ id, limit }),
|
|
2100
|
-
getLiveDecisions: ({ status, limit }) => client.getLiveDecisions({ status, limit }),
|
|
3323
|
+
getLiveAgents: ({ initiative, projectId, includeIdle }) => client.getLiveAgents({ initiative, projectId, includeIdle }),
|
|
3324
|
+
getLiveInitiatives: ({ id, projectId, limit, offset }) => client.getLiveInitiatives({ id, projectId, limit, offset }),
|
|
3325
|
+
getLiveDecisions: ({ status, projectId, limit }) => client.getLiveDecisions({ status, projectId, limit }),
|
|
2101
3326
|
getHandoffs: () => client.getHandoffs(),
|
|
3327
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2102
3328
|
loadLocalOpenClawSnapshot,
|
|
2103
3329
|
toLocalLiveAgents,
|
|
2104
3330
|
toLocalLiveInitiatives,
|
|
@@ -2107,9 +3333,15 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2107
3333
|
sendJson,
|
|
2108
3334
|
safeErrorMessage,
|
|
2109
3335
|
});
|
|
3336
|
+
registerLiveTerminalRoutes(apiRouter, {
|
|
3337
|
+
parseJsonRequest,
|
|
3338
|
+
sendJson,
|
|
3339
|
+
safeErrorMessage,
|
|
3340
|
+
});
|
|
2110
3341
|
registerLiveLegacyRoutes(apiRouter, {
|
|
2111
|
-
getLiveSessions: ({ initiative, limit }) => client.getLiveSessions({ initiative, limit }),
|
|
2112
|
-
getLiveActivity: ({ run, since, limit }) => client.getLiveActivity({ run, since, limit }),
|
|
3342
|
+
getLiveSessions: ({ initiative, projectId, limit }) => client.getLiveSessions({ initiative, projectId, limit }),
|
|
3343
|
+
getLiveActivity: ({ run, since, projectId, limit }) => client.getLiveActivity({ run, since, projectId, limit }),
|
|
3344
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2113
3345
|
listRuntimeInstances,
|
|
2114
3346
|
injectRuntimeInstancesAsSessions,
|
|
2115
3347
|
enrichSessionsWithRuntime,
|
|
@@ -2171,16 +3403,18 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2171
3403
|
toLocalSessionTree,
|
|
2172
3404
|
toLocalLiveActivity,
|
|
2173
3405
|
toLocalLiveAgents,
|
|
2174
|
-
getLiveSessions: ({ initiative, limit }) => client.getLiveSessions({ initiative, limit }),
|
|
2175
|
-
getLiveActivity: ({ run, since, limit }) => client.getLiveActivity({ run, since, limit }),
|
|
3406
|
+
getLiveSessions: ({ initiative, projectId, limit }) => client.getLiveSessions({ initiative, projectId, limit }),
|
|
3407
|
+
getLiveActivity: ({ run, since, projectId, limit }) => client.getLiveActivity({ run, since, projectId, limit }),
|
|
2176
3408
|
getHandoffs: () => client.getHandoffs(),
|
|
2177
|
-
getLiveDecisions: ({ status, limit }) => client.getLiveDecisions({ status, limit }),
|
|
2178
|
-
getLiveAgents: ({ initiative, includeIdle }) => client.getLiveAgents({ initiative, includeIdle }),
|
|
3409
|
+
getLiveDecisions: ({ status, projectId, limit }) => client.getLiveDecisions({ status, projectId, limit }),
|
|
3410
|
+
getLiveAgents: ({ initiative, projectId, includeIdle }) => client.getLiveAgents({ initiative, projectId, includeIdle }),
|
|
3411
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2179
3412
|
mapDecisionEntity: (entry) => mapDecisionEntity(entry),
|
|
2180
3413
|
applyAgentContextsToSessionTree,
|
|
2181
3414
|
applyAgentContextsToActivity,
|
|
2182
3415
|
mergeSessionTrees,
|
|
2183
3416
|
mergeActivities,
|
|
3417
|
+
semanticActivityKey,
|
|
2184
3418
|
listRuntimeInstances,
|
|
2185
3419
|
injectRuntimeInstancesAsSessions,
|
|
2186
3420
|
enrichSessionsWithRuntime,
|
|
@@ -2196,6 +3430,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2196
3430
|
lastSnapshotActivityFingerprint = state.lastFingerprint;
|
|
2197
3431
|
lastSnapshotActivityPersistAt = state.lastPersistAt;
|
|
2198
3432
|
},
|
|
3433
|
+
parseJsonRequest,
|
|
3434
|
+
buildNextUpQueue: ({ initiativeId, projectId }) => buildNextUpQueue({ initiativeId, projectId }),
|
|
3435
|
+
bulkDecideDecisions: (ids, action, input) => client.bulkDecideDecisions(ids, action, input),
|
|
3436
|
+
emitDecisionResolvedActivity: async (input) => {
|
|
3437
|
+
await emitDecisionResolvedActivity(input);
|
|
3438
|
+
},
|
|
3439
|
+
runAction: (runId, action, input) => client.runAction(runId, action, input),
|
|
3440
|
+
listChatThreads: ({ commandCenterId, initiativeId, limit, offset }) => listChatThreads({ commandCenterId, initiativeId, limit, offset }),
|
|
2199
3441
|
sendJson,
|
|
2200
3442
|
});
|
|
2201
3443
|
registerRuntimeHookRoutes(apiRouter, {
|
|
@@ -2232,12 +3474,81 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2232
3474
|
sendJson,
|
|
2233
3475
|
safeErrorMessage,
|
|
2234
3476
|
});
|
|
3477
|
+
registerLiveTerminalRoutes(apiRouter, {
|
|
3478
|
+
parseJsonRequest,
|
|
3479
|
+
sendJson,
|
|
3480
|
+
safeErrorMessage,
|
|
3481
|
+
});
|
|
3482
|
+
registerLiveTriageRoutes(apiRouter, {
|
|
3483
|
+
parseJsonRequest,
|
|
3484
|
+
sendJson,
|
|
3485
|
+
getDecisions: (workspaceId) => {
|
|
3486
|
+
// Return cached decisions from latest snapshot, or empty array
|
|
3487
|
+
const normalizedWorkspaceId = (workspaceId ?? "").trim();
|
|
3488
|
+
const scopedKeys = normalizedWorkspaceId
|
|
3489
|
+
? [
|
|
3490
|
+
`live-snapshot:${normalizedWorkspaceId}`,
|
|
3491
|
+
`live-snapshot-v2:${normalizedWorkspaceId}`,
|
|
3492
|
+
`dashboard-bundle:${normalizedWorkspaceId}`,
|
|
3493
|
+
]
|
|
3494
|
+
: [];
|
|
3495
|
+
const fallbackKeys = ["live-snapshot", "dashboard-bundle", "live-snapshot-v2"];
|
|
3496
|
+
const keys = [...scopedKeys, ...fallbackKeys];
|
|
3497
|
+
try {
|
|
3498
|
+
for (const key of keys) {
|
|
3499
|
+
const cached = readSnapshotResponseCache(key);
|
|
3500
|
+
if (!cached || typeof cached !== "object" || !("decisions" in cached))
|
|
3501
|
+
continue;
|
|
3502
|
+
const decisions = cached.decisions;
|
|
3503
|
+
if (Array.isArray(decisions))
|
|
3504
|
+
return decisions;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
catch {
|
|
3508
|
+
// best effort
|
|
3509
|
+
}
|
|
3510
|
+
return [];
|
|
3511
|
+
},
|
|
3512
|
+
getBlockerEvents: () => {
|
|
3513
|
+
// Extract blocker events from recent activity
|
|
3514
|
+
// In future, this will read from a dedicated blocker store
|
|
3515
|
+
return [];
|
|
3516
|
+
},
|
|
3517
|
+
resolveDecisionAction: async (decisionId, action, note, optionId) => {
|
|
3518
|
+
try {
|
|
3519
|
+
await client.bulkDecideDecisions([decisionId], action, { note: note ?? undefined, optionId: optionId ?? undefined });
|
|
3520
|
+
return { ok: true };
|
|
3521
|
+
}
|
|
3522
|
+
catch (err) {
|
|
3523
|
+
return {
|
|
3524
|
+
ok: false,
|
|
3525
|
+
error: err instanceof Error ? err.message : "Decision action failed",
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
},
|
|
3529
|
+
emitDecisionResolvedActivity: async (input) => {
|
|
3530
|
+
await emitDecisionResolvedActivity(input);
|
|
3531
|
+
},
|
|
3532
|
+
});
|
|
2235
3533
|
return async function handler(req, res) {
|
|
2236
3534
|
const method = (req.method ?? "GET").toUpperCase();
|
|
2237
3535
|
const rawUrl = req.url ?? "/";
|
|
2238
3536
|
const [path, queryString] = rawUrl.split("?", 2);
|
|
2239
3537
|
const url = path;
|
|
2240
3538
|
const searchParams = new URLSearchParams(queryString ?? "");
|
|
3539
|
+
// Legacy deep-link compatibility:
|
|
3540
|
+
// Older launch paths still point at /workspace-hub. Route those into
|
|
3541
|
+
// the current dashboard entrypoint while preserving query params.
|
|
3542
|
+
if (url === "/workspace-hub" || url === "/workspace-hub/") {
|
|
3543
|
+
const suffix = queryString && queryString.trim().length > 0 ? `?${queryString}` : "";
|
|
3544
|
+
res.writeHead(302, {
|
|
3545
|
+
Location: `/orgx/live${suffix}`,
|
|
3546
|
+
...SECURITY_HEADERS,
|
|
3547
|
+
...CORS_HEADERS,
|
|
3548
|
+
});
|
|
3549
|
+
res.end();
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
2241
3552
|
// Only handle /orgx paths — return false for everything else
|
|
2242
3553
|
if (!url.startsWith("/orgx")) {
|
|
2243
3554
|
return false;
|
|
@@ -2317,9 +3628,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2317
3628
|
const cacheControl = assetExt === ".js" || assetExt === ".css"
|
|
2318
3629
|
? "no-cache"
|
|
2319
3630
|
: "public, max-age=31536000, immutable";
|
|
2320
|
-
sendFile(res, assetPath, cacheControl);
|
|
3631
|
+
sendFile(req, res, assetPath, cacheControl);
|
|
2321
3632
|
}
|
|
2322
3633
|
else {
|
|
3634
|
+
if (/^assets\/[A-Za-z0-9_-]+\.js$/i.test(subPath)) {
|
|
3635
|
+
sendStaleChunkRecovery(res);
|
|
3636
|
+
return true;
|
|
3637
|
+
}
|
|
2323
3638
|
send404(res);
|
|
2324
3639
|
}
|
|
2325
3640
|
return true;
|
|
@@ -2328,12 +3643,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2328
3643
|
if (subPath) {
|
|
2329
3644
|
const filePath = resolveSafeDistPath(subPath);
|
|
2330
3645
|
if (filePath && existsSync(filePath)) {
|
|
2331
|
-
sendFile(res, filePath, "no-cache");
|
|
3646
|
+
sendFile(req, res, filePath, "no-cache");
|
|
2332
3647
|
return true;
|
|
2333
3648
|
}
|
|
2334
3649
|
}
|
|
2335
3650
|
// SPA fallback: serve index.html for all other routes under /orgx/live
|
|
2336
|
-
sendIndexHtml(res);
|
|
3651
|
+
sendIndexHtml(req, res);
|
|
2337
3652
|
return true;
|
|
2338
3653
|
}
|
|
2339
3654
|
// Catch-all for /orgx but not /orgx/live or /orgx/api
|