@useorgx/openclaw-plugin 0.4.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js +1 -0
- package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
- package/dashboard/dist/assets/BgOYB78t.js +4 -0
- package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
- package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
- package/dashboard/dist/assets/CE38zU4U.js +1 -0
- package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js +1 -0
- package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js +1 -0
- package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js +1 -0
- package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js +8 -0
- package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js +1 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
- package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
- package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
- package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js +213 -0
- package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js +2 -0
- package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js +1 -0
- package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
- package/dashboard/dist/assets/DW_rKUic.js +11 -0
- package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
- package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
- package/dashboard/dist/assets/DbNoijHm.js +1 -0
- package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js +2 -0
- package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js +1 -0
- package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
- package/dashboard/dist/assets/PAUiij_z.js +1 -0
- package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js +8 -0
- package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
- package/dashboard/dist/assets/h5biQs2I.css +1 -0
- package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
- package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js +1 -0
- package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
- package/dashboard/dist/assets/nByHNHoW.js +1 -0
- package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css +1 -0
- package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js +1 -0
- package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openai-mark.svg.br +0 -0
- package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
- package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
- package/dashboard/dist/index.html +7 -5
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/activity-actor-fields.js +26 -4
- package/dist/activity-store.js +34 -8
- package/dist/agent-context-store.js +79 -17
- package/dist/agent-run-store.js +44 -3
- package/dist/agent-suite.d.ts +9 -0
- package/dist/agent-suite.js +149 -9
- package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
- package/dist/artifacts/artifact-domain-schemas.js +357 -0
- package/dist/artifacts/register-artifact.d.ts +4 -3
- package/dist/artifacts/register-artifact.js +170 -57
- package/dist/chat-store.d.ts +157 -0
- package/dist/chat-store.js +586 -0
- package/dist/cli/orgx.js +11 -0
- package/dist/contracts/client.d.ts +43 -3
- package/dist/contracts/client.js +159 -30
- package/dist/contracts/retro-schema.d.ts +81 -0
- package/dist/contracts/retro-schema.js +80 -0
- package/dist/contracts/shared-types.d.ts +159 -0
- package/dist/contracts/shared-types.js +177 -1
- package/dist/contracts/skill-pack-schema.d.ts +192 -0
- package/dist/contracts/skill-pack-schema.js +180 -0
- package/dist/contracts/types.d.ts +227 -2
- package/dist/entities/auto-assignment.js +43 -17
- package/dist/event-sanitization.d.ts +11 -0
- package/dist/event-sanitization.js +113 -0
- package/dist/fs-utils.js +13 -1
- package/dist/gateway-watchdog.d.ts +5 -0
- package/dist/gateway-watchdog.js +50 -0
- package/dist/hooks/post-reporting-event.mjs +1 -5
- package/dist/http/helpers/activity-headline.js +13 -132
- package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
- package/dist/http/helpers/auto-continue-engine.js +2531 -186
- package/dist/http/helpers/autopilot-operations.d.ts +19 -0
- package/dist/http/helpers/autopilot-operations.js +182 -31
- package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
- package/dist/http/helpers/autopilot-runtime.js +308 -20
- package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
- package/dist/http/helpers/autopilot-slice-utils.js +516 -93
- package/dist/http/helpers/decision-mapper.d.ts +40 -0
- package/dist/http/helpers/decision-mapper.js +223 -7
- package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
- package/dist/http/helpers/dispatch-lifecycle.js +242 -37
- package/dist/http/helpers/kickoff-context.js +74 -0
- package/dist/http/helpers/llm-client.d.ts +47 -0
- package/dist/http/helpers/llm-client.js +256 -0
- package/dist/http/helpers/mission-control.d.ts +102 -3
- package/dist/http/helpers/mission-control.js +498 -9
- package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
- package/dist/http/helpers/sentinel-catalog.js +193 -0
- package/dist/http/helpers/session-classification.d.ts +9 -0
- package/dist/http/helpers/session-classification.js +564 -0
- package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
- package/dist/http/helpers/slice-experience-v2.js +677 -0
- package/dist/http/helpers/slice-run-projections.d.ts +72 -0
- package/dist/http/helpers/slice-run-projections.js +860 -0
- package/dist/http/helpers/triage-mapper.d.ts +43 -0
- package/dist/http/helpers/triage-mapper.js +549 -0
- package/dist/http/helpers/value-utils.js +7 -2
- package/dist/http/helpers/workspace-scope.d.ts +15 -0
- package/dist/http/helpers/workspace-scope.js +170 -0
- package/dist/http/index.js +1354 -97
- package/dist/http/routes/agent-suite.d.ts +9 -0
- package/dist/http/routes/agent-suite.js +207 -8
- package/dist/http/routes/agents-catalog.js +64 -19
- package/dist/http/routes/chat.d.ts +19 -0
- package/dist/http/routes/chat.js +522 -0
- package/dist/http/routes/decision-actions.d.ts +8 -1
- package/dist/http/routes/decision-actions.js +42 -5
- package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
- package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
- package/dist/http/routes/entities.d.ts +16 -0
- package/dist/http/routes/entities.js +294 -6
- package/dist/http/routes/live-legacy.d.ts +5 -0
- package/dist/http/routes/live-legacy.js +23 -509
- package/dist/http/routes/live-misc.d.ts +12 -0
- package/dist/http/routes/live-misc.js +251 -31
- package/dist/http/routes/live-snapshot.d.ts +48 -2
- package/dist/http/routes/live-snapshot.js +638 -19
- package/dist/http/routes/live-terminal.d.ts +11 -0
- package/dist/http/routes/live-terminal.js +261 -0
- package/dist/http/routes/live-triage.d.ts +61 -0
- package/dist/http/routes/live-triage.js +248 -0
- package/dist/http/routes/mission-control-actions.d.ts +49 -1
- package/dist/http/routes/mission-control-actions.js +1334 -84
- package/dist/http/routes/mission-control-read.d.ts +48 -3
- package/dist/http/routes/mission-control-read.js +1593 -20
- package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
- package/dist/http/routes/realtime-orchestrator.js +74 -0
- package/dist/http/routes/run-control.d.ts +5 -2
- package/dist/http/routes/run-control.js +10 -0
- package/dist/http/routes/sentinels-catalog.d.ts +7 -0
- package/dist/http/routes/sentinels-catalog.js +24 -0
- package/dist/http/routes/summary.js +10 -3
- package/dist/http/routes/usage.d.ts +24 -0
- package/dist/http/routes/usage.js +362 -0
- package/dist/http/routes/work-artifacts.js +28 -9
- package/dist/index.js +165 -27
- package/dist/local-openclaw.js +29 -6
- package/dist/mcp-client-setup.js +3 -3
- package/dist/mcp-http-handler.js +33 -59
- package/dist/next-up-queue-store.d.ts +16 -1
- package/dist/next-up-queue-store.js +89 -7
- package/dist/outbox.d.ts +5 -0
- package/dist/outbox.js +113 -9
- package/dist/paths.js +24 -5
- package/dist/reporting/rollups.d.ts +53 -0
- package/dist/reporting/rollups.js +148 -0
- package/dist/retro/domain-templates.d.ts +45 -0
- package/dist/retro/domain-templates.js +297 -0
- package/dist/retro/quality-rubric.d.ts +33 -0
- package/dist/retro/quality-rubric.js +213 -0
- package/dist/runtime-cleanup.d.ts +18 -0
- package/dist/runtime-cleanup.js +87 -0
- package/dist/services/background.d.ts +11 -0
- package/dist/services/background.js +22 -0
- package/dist/services/experiment-randomization.d.ts +21 -0
- package/dist/services/experiment-randomization.js +63 -0
- package/dist/skill-pack-state.d.ts +36 -5
- package/dist/skill-pack-state.js +273 -29
- package/dist/sync/local-agent-telemetry.d.ts +13 -0
- package/dist/sync/local-agent-telemetry.js +128 -0
- package/dist/sync/outbox-replay.js +131 -24
- package/dist/team-context-store.d.ts +23 -0
- package/dist/team-context-store.js +116 -0
- package/dist/telemetry/posthog.js +4 -2
- package/dist/tools/core-tools.d.ts +10 -14
- package/dist/tools/core-tools.js +1289 -24
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/worker-supervisor.js +23 -0
- package/package.json +14 -4
- package/dashboard/dist/assets/B3ziCA02.js +0 -8
- package/dashboard/dist/assets/B5NEElEI.css +0 -1
- package/dashboard/dist/assets/BhapSNAs.js +0 -215
- package/dashboard/dist/assets/iFdvE7lx.js +0 -1
- package/dashboard/dist/assets/jRJsmpYM.js +0 -1
- package/dashboard/dist/assets/sAhvFnpk.js +0 -4
package/dist/http/index.js
CHANGED
|
@@ -22,20 +22,21 @@ 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
29
|
import { readAgentContexts, upsertAgentContext, upsertRunContext } from "../agent-context-store.js";
|
|
30
30
|
import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from "../agent-run-store.js";
|
|
31
31
|
import { appendEntityComment, listEntityComments, mergeEntityComments, } from "../entity-comment-store.js";
|
|
32
|
+
import { listChatThreads } from "../chat-store.js";
|
|
32
33
|
import { appendActivityItems, listActivityPage, } from "../activity-store.js";
|
|
33
34
|
import { enrichActivityActorFields } from "../activity-actor-fields.js";
|
|
34
35
|
import { readByokKeys, writeByokKeys } from "../byok-store.js";
|
|
35
36
|
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan, generateAgentSuiteOperationId, } from "../agent-suite.js";
|
|
36
37
|
import { listRuntimeInstances, resolveRuntimeHookToken, upsertRuntimeInstanceFromHook, } from "../runtime-instance-store.js";
|
|
37
38
|
import { parseJsonSafe } from "../json-utils.js";
|
|
38
|
-
import { readSkillPackState, refreshSkillPackState, updateSkillPackPolicy } from "../skill-pack-state.js";
|
|
39
|
+
import { readSkillPackState, refreshSkillPackState, rollbackSkillPackPolicy, updateSkillPackPolicy, } from "../skill-pack-state.js";
|
|
39
40
|
import { posthogCapture } from "../telemetry/posthog.js";
|
|
40
41
|
import { createRouter } from "./router.js";
|
|
41
42
|
import { summarizeActivityHeadline } from "./helpers/activity-headline.js";
|
|
@@ -45,7 +46,7 @@ import { mapDecisionEntity } from "./helpers/decision-mapper.js";
|
|
|
45
46
|
import { idempotencyKey, stableHash } from "./helpers/hash-utils.js";
|
|
46
47
|
import { createCodexBinResolver, } from "./helpers/autopilot-slice-utils.js";
|
|
47
48
|
import { createLocalArtifactDetailFallbackBuilder } from "./helpers/artifact-fallback.js";
|
|
48
|
-
import { buildMissionControlGraph, dedupeStrings, isDoneStatus, isInProgressStatus, isTodoStatus, listEntitiesSafe, normalizeEntityMutationPayload, pickStringArray, resolveAutoAssignments, } from "./helpers/mission-control.js";
|
|
49
|
+
import { buildMissionControlGraph, deriveExecutionPolicy, dedupeStrings, isDispatchableWorkstreamStatus, isDoneStatus, isInProgressStatus, isTodoStatus, listEntitiesSafe, normalizeEntityMutationPayload, pickStringArray, resolveAutoAssignments, selectSliceTasksByScope, } from "./helpers/mission-control.js";
|
|
49
50
|
import { configureOpenClawProviderRouting, fetchBillingStatusSafe, isPidAlive, listOpenClawAgents, listOpenClawProviderModels, modelImpliesByok, normalizeOpenClawProvider, resolveAutoOpenClawProvider, resolveByokEnvOverrides, spawnOpenClawAgentTurn, stopDetachedProcess, } from "./helpers/openclaw-cli.js";
|
|
50
51
|
import { fetchKickoffContextSafe, renderKickoffMessage } from "./helpers/kickoff-context.js";
|
|
51
52
|
import { createDispatchLifecycle } from "./helpers/dispatch-lifecycle.js";
|
|
@@ -61,17 +62,23 @@ import { registerDebugRoutes } from "./routes/debug.js";
|
|
|
61
62
|
import { registerEntityDynamicRoutes } from "./routes/entity-dynamic.js";
|
|
62
63
|
import { registerEntitiesRoutes } from "./routes/entities.js";
|
|
63
64
|
import { registerHealthRoutes } from "./routes/health.js";
|
|
65
|
+
import { registerChatRoutes } from "./routes/chat.js";
|
|
64
66
|
import { registerLiveLegacyRoutes } from "./routes/live-legacy.js";
|
|
65
67
|
import { registerLiveMiscRoutes } from "./routes/live-misc.js";
|
|
68
|
+
import { registerLiveTerminalRoutes } from "./routes/live-terminal.js";
|
|
66
69
|
import { registerLiveSnapshotRoutes } from "./routes/live-snapshot.js";
|
|
67
70
|
import { registerMissionControlActionsRoutes } from "./routes/mission-control-actions.js";
|
|
68
71
|
import { registerMissionControlReadRoutes } from "./routes/mission-control-read.js";
|
|
69
72
|
import { registerOnboardingRoutes } from "./routes/onboarding.js";
|
|
70
73
|
import { registerRunControlRoutes } from "./routes/run-control.js";
|
|
71
74
|
import { registerRuntimeHookRoutes } from "./routes/runtime-hooks.js";
|
|
75
|
+
import { registerSentinelsCatalogRoutes } from "./routes/sentinels-catalog.js";
|
|
72
76
|
import { registerSettingsByokRoutes } from "./routes/settings-byok.js";
|
|
73
77
|
import { registerSummaryRoutes } from "./routes/summary.js";
|
|
78
|
+
import { registerUsageRoutes } from "./routes/usage.js";
|
|
74
79
|
import { registerWorkArtifactsRoutes } from "./routes/work-artifacts.js";
|
|
80
|
+
import { registerLiveTriageRoutes } from "./routes/live-triage.js";
|
|
81
|
+
import { registerRealtimeOrchestratorRoutes } from "./routes/realtime-orchestrator.js";
|
|
75
82
|
// =============================================================================
|
|
76
83
|
// Helpers
|
|
77
84
|
// =============================================================================
|
|
@@ -96,10 +103,23 @@ async function resolveSkillPackOverrides(input) {
|
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
function safeErrorMessage(err) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
106
|
+
const raw = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
107
|
+
const normalized = raw.trim().toLowerCase();
|
|
108
|
+
if (normalized.length > 0) {
|
|
109
|
+
if (normalized.includes("signal is aborted") ||
|
|
110
|
+
normalized.includes("aborterror") ||
|
|
111
|
+
normalized.includes("request cancelled") ||
|
|
112
|
+
normalized.includes("request canceled")) {
|
|
113
|
+
return "request timed out before upstream completed";
|
|
114
|
+
}
|
|
115
|
+
if (normalized.includes("timed out") || normalized.includes("timeout")) {
|
|
116
|
+
return "request timed out before upstream completed";
|
|
117
|
+
}
|
|
118
|
+
if (normalized.includes("failed to fetch") || normalized.includes("network")) {
|
|
119
|
+
return "network request failed";
|
|
120
|
+
}
|
|
121
|
+
return raw;
|
|
122
|
+
}
|
|
103
123
|
return "Unexpected error";
|
|
104
124
|
}
|
|
105
125
|
function titleCaseFromSlug(value) {
|
|
@@ -185,11 +205,21 @@ const SNAPSHOT_RESPONSE_CACHE_TTL_MS = 1_500;
|
|
|
185
205
|
const SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES = 16;
|
|
186
206
|
const SNAPSHOT_ACTIVITY_PERSIST_MIN_INTERVAL_MS = 15_000;
|
|
187
207
|
const SNAPSHOT_ACTIVITY_FINGERPRINT_DEPTH = 8;
|
|
188
|
-
const NEXT_UP_QUEUE_CACHE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_CACHE_TTL_MS",
|
|
208
|
+
const NEXT_UP_QUEUE_CACHE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_CACHE_TTL_MS", 30_000, { min: 250, max: 120_000 });
|
|
189
209
|
const NEXT_UP_QUEUE_STALE_TTL_MS = readPositiveIntEnv("ORGX_NEXT_UP_QUEUE_STALE_TTL_MS", 45_000, { min: 1_000, max: 600_000 });
|
|
190
210
|
const NEXT_UP_GRAPH_CONCURRENCY = readPositiveIntEnv("ORGX_NEXT_UP_GRAPH_CONCURRENCY", 20, { min: 1, max: 32 });
|
|
191
211
|
const NEXT_UP_LIVE_AGENTS_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_LIVE_AGENTS_TIMEOUT_MS", 1_500, { min: 200, max: 20_000 });
|
|
192
212
|
const NEXT_UP_AGENT_CATALOG_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_AGENT_CATALOG_TIMEOUT_MS", 900, { min: 100, max: 20_000 });
|
|
213
|
+
const NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS = readPositiveIntEnv("ORGX_NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS", 2_500, { min: 250, max: 30_000 });
|
|
214
|
+
const PROJECT_SCOPE_LOOKUP_TIMEOUT_MS = readPositiveIntEnv("ORGX_PROJECT_SCOPE_LOOKUP_TIMEOUT_MS", 2_500, { min: 250, max: 20_000 });
|
|
215
|
+
const PROJECT_SCOPE_MAX_INITIATIVE_PAGES = readPositiveIntEnv("ORGX_PROJECT_SCOPE_MAX_INITIATIVE_PAGES", 12, { min: 1, max: 100 });
|
|
216
|
+
const LIVE_WORKSPACE_INITIATIVE_STATUSES = [
|
|
217
|
+
"active",
|
|
218
|
+
"planning",
|
|
219
|
+
"paused",
|
|
220
|
+
"draft",
|
|
221
|
+
"in_progress",
|
|
222
|
+
];
|
|
193
223
|
let lastSnapshotActivityPersistAt = 0;
|
|
194
224
|
let lastSnapshotActivityFingerprint = "";
|
|
195
225
|
const snapshotResponseCache = new Map();
|
|
@@ -284,6 +314,9 @@ function deriveStructuredActivityBucket(input) {
|
|
|
284
314
|
"nonBlockingDecisionCount",
|
|
285
315
|
]) ?? 0;
|
|
286
316
|
if (event === "autopilot_slice_result") {
|
|
317
|
+
// Any blocked slice result needs decision-first surfacing in the Activity UX.
|
|
318
|
+
if (input.phase === "blocked")
|
|
319
|
+
return "decision";
|
|
287
320
|
if (decisionRequired || blockingDecisions > 0)
|
|
288
321
|
return "decision";
|
|
289
322
|
if (artifacts > 0)
|
|
@@ -292,6 +325,13 @@ function deriveStructuredActivityBucket(input) {
|
|
|
292
325
|
return "decision";
|
|
293
326
|
return "message";
|
|
294
327
|
}
|
|
328
|
+
if (event === "auto_continue_stopped") {
|
|
329
|
+
const stopReason = typeof metadata?.stop_reason === "string"
|
|
330
|
+
? metadata.stop_reason.trim().toLowerCase()
|
|
331
|
+
: "";
|
|
332
|
+
if (stopReason === "blocked" || stopReason === "error")
|
|
333
|
+
return "decision";
|
|
334
|
+
}
|
|
295
335
|
if (event && ACTIVITY_ARTIFACT_EVENT_HINTS.has(event))
|
|
296
336
|
return "artifact";
|
|
297
337
|
if (event && ACTIVITY_DECISION_EVENT_HINTS.has(event))
|
|
@@ -577,6 +617,81 @@ function mergeSessionTrees(base, extra) {
|
|
|
577
617
|
};
|
|
578
618
|
}
|
|
579
619
|
function mergeActivities(base, extra, limit) {
|
|
620
|
+
const asMetadataRecord = (value) => {
|
|
621
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
622
|
+
return null;
|
|
623
|
+
return value;
|
|
624
|
+
};
|
|
625
|
+
const metadataString = (metadata, keys) => {
|
|
626
|
+
if (!metadata)
|
|
627
|
+
return null;
|
|
628
|
+
for (const key of keys) {
|
|
629
|
+
const value = metadata[key];
|
|
630
|
+
if (typeof value !== "string")
|
|
631
|
+
continue;
|
|
632
|
+
const normalized = value.trim();
|
|
633
|
+
if (normalized.length > 0)
|
|
634
|
+
return normalized;
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
};
|
|
638
|
+
const semanticEvents = new Set([
|
|
639
|
+
"autopilot_slice_result",
|
|
640
|
+
"auto_continue_started",
|
|
641
|
+
"auto_continue_stopped",
|
|
642
|
+
"next_up_manual_dispatch_started",
|
|
643
|
+
"autopilot_slice_mcp_handshake_failed",
|
|
644
|
+
"autopilot_slice_timeout",
|
|
645
|
+
"autopilot_slice_log_stall",
|
|
646
|
+
"auto_continue_spawn_guard_blocked",
|
|
647
|
+
"auto_continue_spawn_guard_rate_limited",
|
|
648
|
+
"autopilot_autofix_scheduled",
|
|
649
|
+
"autopilot_autofix_executed",
|
|
650
|
+
"autopilot_autofix_skipped",
|
|
651
|
+
]);
|
|
652
|
+
const semanticActivityKey = (item) => {
|
|
653
|
+
const metadata = asMetadataRecord(item.metadata);
|
|
654
|
+
const eventRaw = metadata?.event;
|
|
655
|
+
const event = typeof eventRaw === "string" ? eventRaw.trim().toLowerCase() : "";
|
|
656
|
+
if (!event || !semanticEvents.has(event))
|
|
657
|
+
return null;
|
|
658
|
+
const runLike = (typeof item.runId === "string" && item.runId.trim().length > 0
|
|
659
|
+
? item.runId.trim()
|
|
660
|
+
: null) ??
|
|
661
|
+
metadataString(metadata, [
|
|
662
|
+
"run_id",
|
|
663
|
+
"runId",
|
|
664
|
+
"slice_run_id",
|
|
665
|
+
"sliceRunId",
|
|
666
|
+
"active_run_id",
|
|
667
|
+
"activeRunId",
|
|
668
|
+
"last_run_id",
|
|
669
|
+
"lastRunId",
|
|
670
|
+
]);
|
|
671
|
+
const correlationId = metadataString(metadata, ["correlation_id", "correlationId"]);
|
|
672
|
+
const initiativeId = (typeof item.initiativeId === "string" && item.initiativeId.trim().length > 0
|
|
673
|
+
? item.initiativeId.trim()
|
|
674
|
+
: null) ??
|
|
675
|
+
metadataString(metadata, ["initiative_id", "initiativeId"]);
|
|
676
|
+
const workstreamId = metadataString(metadata, ["workstream_id", "workstreamId"]);
|
|
677
|
+
const taskId = metadataString(metadata, ["task_id", "taskId"]);
|
|
678
|
+
const stopReason = metadataString(metadata, ["stop_reason", "stopReason"]);
|
|
679
|
+
const parsedStatus = metadataString(metadata, ["parsed_status", "parsedStatus"]);
|
|
680
|
+
const title = (item.title ?? "").trim().toLowerCase();
|
|
681
|
+
if (!runLike && !correlationId && !workstreamId && !taskId)
|
|
682
|
+
return null;
|
|
683
|
+
return [
|
|
684
|
+
event,
|
|
685
|
+
initiativeId ?? "",
|
|
686
|
+
workstreamId ?? "",
|
|
687
|
+
taskId ?? "",
|
|
688
|
+
runLike ?? "",
|
|
689
|
+
correlationId ?? "",
|
|
690
|
+
stopReason ?? "",
|
|
691
|
+
parsedStatus ?? "",
|
|
692
|
+
title,
|
|
693
|
+
].join("|");
|
|
694
|
+
};
|
|
580
695
|
const merged = [...(base ?? []), ...(extra ?? [])].sort((a, b) => {
|
|
581
696
|
const timestampDelta = Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
582
697
|
if (timestampDelta !== 0)
|
|
@@ -584,11 +699,17 @@ function mergeActivities(base, extra, limit) {
|
|
|
584
699
|
return b.id.localeCompare(a.id);
|
|
585
700
|
});
|
|
586
701
|
const deduped = [];
|
|
587
|
-
const
|
|
702
|
+
const seenIds = new Set();
|
|
703
|
+
const seenSemantic = new Set();
|
|
588
704
|
for (const item of merged) {
|
|
589
|
-
if (
|
|
705
|
+
if (seenIds.has(item.id))
|
|
706
|
+
continue;
|
|
707
|
+
seenIds.add(item.id);
|
|
708
|
+
const semanticKey = semanticActivityKey(item);
|
|
709
|
+
if (semanticKey && seenSemantic.has(semanticKey))
|
|
590
710
|
continue;
|
|
591
|
-
|
|
711
|
+
if (semanticKey)
|
|
712
|
+
seenSemantic.add(semanticKey);
|
|
592
713
|
deduped.push(item);
|
|
593
714
|
if (deduped.length >= limit)
|
|
594
715
|
break;
|
|
@@ -719,6 +840,9 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
719
840
|
const agentId = (node.agentId ?? "").trim() || fallbackAgent.agentId;
|
|
720
841
|
const agentName = (node.agentName ?? "").trim() || fallbackAgent.agentName;
|
|
721
842
|
const nodeStatus = (node.status ?? "").trim().toLowerCase();
|
|
843
|
+
const isTerminalNodeStatus = nodeStatus === "completed" ||
|
|
844
|
+
nodeStatus === "cancelled" ||
|
|
845
|
+
nodeStatus === "archived";
|
|
722
846
|
const isLiveLikeNodeStatus = nodeStatus === "running" ||
|
|
723
847
|
nodeStatus === "active" ||
|
|
724
848
|
nodeStatus === "in_progress" ||
|
|
@@ -726,20 +850,49 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
726
850
|
nodeStatus === "planning" ||
|
|
727
851
|
nodeStatus === "dispatching";
|
|
728
852
|
const shouldDowngradeStatusFromRuntime = isLiveLikeNodeStatus && (runtimeStatus === "queued" || runtimeStatus === "paused");
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
853
|
+
const shouldPromoteStatusFromRuntime = runtimeStatus === "completed" ||
|
|
854
|
+
runtimeStatus === "blocked" ||
|
|
855
|
+
runtimeStatus === "review" ||
|
|
856
|
+
runtimeStatus === "handoff" ||
|
|
857
|
+
(runtimeStatus === "running" &&
|
|
858
|
+
(nodeStatus === "blocked" ||
|
|
859
|
+
nodeStatus === "failed" ||
|
|
860
|
+
nodeStatus === "queued" ||
|
|
861
|
+
nodeStatus === "paused"));
|
|
862
|
+
const nextStatus = shouldDowngradeStatusFromRuntime ||
|
|
863
|
+
(!isTerminalNodeStatus && shouldPromoteStatusFromRuntime)
|
|
864
|
+
? runtimeStatus
|
|
865
|
+
: node.status;
|
|
866
|
+
const runtimeExplicitlyBlocked = runtimeStatus === "blocked" || match.phase?.toLowerCase() === "blocked";
|
|
867
|
+
const runtimeExplicitlyUnblocked = !runtimeExplicitlyBlocked && typeof match.phase === "string" && match.phase.trim().length > 0;
|
|
868
|
+
const runtimeBlockedReason = (match.lastMessage ?? "").trim();
|
|
869
|
+
const nextBlockerReason = runtimeExplicitlyBlocked
|
|
870
|
+
? runtimeBlockedReason || (node.blockerReason ?? "").trim() || null
|
|
871
|
+
: runtimeExplicitlyUnblocked
|
|
872
|
+
? null
|
|
873
|
+
: node.blockerReason ?? null;
|
|
874
|
+
const nextBlockers = runtimeExplicitlyBlocked
|
|
875
|
+
? runtimeBlockedReason
|
|
876
|
+
? [runtimeBlockedReason]
|
|
877
|
+
: Array.isArray(node.blockers)
|
|
878
|
+
? node.blockers
|
|
879
|
+
: []
|
|
880
|
+
: runtimeExplicitlyUnblocked
|
|
881
|
+
? []
|
|
882
|
+
: Array.isArray(node.blockers)
|
|
883
|
+
? node.blockers
|
|
884
|
+
: [];
|
|
733
885
|
return {
|
|
734
886
|
...node,
|
|
735
887
|
agentId: agentId || null,
|
|
736
888
|
agentName: agentName || null,
|
|
737
|
-
status:
|
|
889
|
+
status: nextStatus,
|
|
738
890
|
state: node.state ?? match.state ?? null,
|
|
739
891
|
lastEventSummary: shouldDowngradeStatusFromRuntime && runtimeStatus === "queued"
|
|
740
892
|
? node.lastEventSummary ?? "Recovered stale runtime; awaiting next dispatch."
|
|
741
893
|
: node.lastEventSummary,
|
|
742
|
-
|
|
894
|
+
blockers: nextBlockers,
|
|
895
|
+
blockerReason: nextBlockerReason,
|
|
743
896
|
runtimeClient: normalizeRuntimeSource(match.sourceClient),
|
|
744
897
|
runtimeLabel: match.displayName,
|
|
745
898
|
runtimeProvider: match.providerLogo,
|
|
@@ -749,6 +902,64 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
749
902
|
});
|
|
750
903
|
return { ...input, nodes };
|
|
751
904
|
}
|
|
905
|
+
function metadataHasStructuredScope(meta) {
|
|
906
|
+
const scalarScope = pickString(meta, [
|
|
907
|
+
"initiative_id",
|
|
908
|
+
"initiativeId",
|
|
909
|
+
"workstream_id",
|
|
910
|
+
"workstreamId",
|
|
911
|
+
"workstream_title",
|
|
912
|
+
"workstreamTitle",
|
|
913
|
+
"task_id",
|
|
914
|
+
"taskId",
|
|
915
|
+
"task_title",
|
|
916
|
+
"taskTitle",
|
|
917
|
+
"slice_run_id",
|
|
918
|
+
"sliceRunId",
|
|
919
|
+
"iwmt_id",
|
|
920
|
+
"iwmtId",
|
|
921
|
+
"milestone_id",
|
|
922
|
+
"milestoneId",
|
|
923
|
+
"milestone_title",
|
|
924
|
+
"milestoneTitle",
|
|
925
|
+
]) ?? null;
|
|
926
|
+
if (scalarScope)
|
|
927
|
+
return true;
|
|
928
|
+
const listScopeKeys = [
|
|
929
|
+
"initiative_ids",
|
|
930
|
+
"initiativeIds",
|
|
931
|
+
"workstream_ids",
|
|
932
|
+
"workstreamIds",
|
|
933
|
+
"task_ids",
|
|
934
|
+
"taskIds",
|
|
935
|
+
"milestone_ids",
|
|
936
|
+
"milestoneIds",
|
|
937
|
+
"iwmt_ids",
|
|
938
|
+
"iwmtIds",
|
|
939
|
+
];
|
|
940
|
+
for (const key of listScopeKeys) {
|
|
941
|
+
const value = meta[key];
|
|
942
|
+
if (!Array.isArray(value))
|
|
943
|
+
continue;
|
|
944
|
+
if (value.some((entry) => typeof entry === "string" && entry.trim().length > 0)) {
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
function shouldInjectRuntimeInstanceAsSession(instance, runId, meta) {
|
|
951
|
+
if (instance.state !== "active")
|
|
952
|
+
return false;
|
|
953
|
+
// Synthetic hook correlation ids are telemetry-only and should never render as user-facing sessions.
|
|
954
|
+
if (runId.toLowerCase().startsWith("hook-"))
|
|
955
|
+
return false;
|
|
956
|
+
const workstreamId = instance.workstreamId?.trim() ?? "";
|
|
957
|
+
const taskId = instance.taskId?.trim() ?? "";
|
|
958
|
+
if (workstreamId.length > 0 || taskId.length > 0)
|
|
959
|
+
return true;
|
|
960
|
+
// Keep only runtime records that include structured execution scope.
|
|
961
|
+
return metadataHasStructuredScope(meta);
|
|
962
|
+
}
|
|
752
963
|
function injectRuntimeInstancesAsSessions(input, instances) {
|
|
753
964
|
if (!Array.isArray(input.nodes))
|
|
754
965
|
return input;
|
|
@@ -773,10 +984,6 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
773
984
|
continue;
|
|
774
985
|
if (existingRunIds.has(runId))
|
|
775
986
|
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
987
|
const initiativeId = instance.initiativeId?.trim() || null;
|
|
781
988
|
const workstreamId = instance.workstreamId?.trim() || null;
|
|
782
989
|
const runtimeClient = normalizeRuntimeSource(instance.sourceClient);
|
|
@@ -785,6 +992,8 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
785
992
|
const meta = instance.metadata && typeof instance.metadata === "object"
|
|
786
993
|
? instance.metadata
|
|
787
994
|
: {};
|
|
995
|
+
if (!shouldInjectRuntimeInstanceAsSession(instance, runId, meta))
|
|
996
|
+
continue;
|
|
788
997
|
const titleHint = pickString(meta, ["workstream_title", "workstreamTitle"]) ??
|
|
789
998
|
(workstreamId ? `Workstream ${workstreamId.slice(0, 8)}` : null);
|
|
790
999
|
const initiativeHint = pickString(meta, ["initiative_title", "initiativeTitle"]) ??
|
|
@@ -898,18 +1107,18 @@ const CONTENT_SECURITY_POLICY = [
|
|
|
898
1107
|
"form-action 'self'",
|
|
899
1108
|
"object-src 'none'",
|
|
900
1109
|
"script-src 'self'",
|
|
901
|
-
"style-src 'self' 'unsafe-inline'",
|
|
1110
|
+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
902
1111
|
"img-src 'self' data: blob:",
|
|
903
|
-
"font-src 'self' data:",
|
|
1112
|
+
"font-src 'self' data: https://fonts.gstatic.com",
|
|
904
1113
|
"media-src 'self'",
|
|
905
|
-
"connect-src 'self' https://*.useorgx.com https://*.openclaw.ai http://127.0.0.1:* http://localhost:*",
|
|
1114
|
+
"connect-src 'self' https://*.useorgx.com https://*.openclaw.ai https://api.openai.com https://*.openai.com http://127.0.0.1:* http://localhost:*",
|
|
906
1115
|
].join("; ");
|
|
907
1116
|
const SECURITY_HEADERS = {
|
|
908
1117
|
"X-Content-Type-Options": "nosniff",
|
|
909
1118
|
"X-Frame-Options": "DENY",
|
|
910
1119
|
"Referrer-Policy": "same-origin",
|
|
911
1120
|
"X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
|
|
912
|
-
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
|
|
1121
|
+
"Permissions-Policy": "camera=(), microphone=(self), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
|
|
913
1122
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
914
1123
|
"Cross-Origin-Resource-Policy": "same-origin",
|
|
915
1124
|
"Origin-Agent-Cluster": "?1",
|
|
@@ -982,9 +1191,16 @@ function resolveSafeDistPath(subPath) {
|
|
|
982
1191
|
}
|
|
983
1192
|
return candidate;
|
|
984
1193
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1194
|
+
const PRECOMPRESSED_FILE_EXTENSIONS = new Set([
|
|
1195
|
+
".css",
|
|
1196
|
+
".html",
|
|
1197
|
+
".js",
|
|
1198
|
+
".json",
|
|
1199
|
+
".map",
|
|
1200
|
+
".svg",
|
|
1201
|
+
".txt",
|
|
1202
|
+
".xml",
|
|
1203
|
+
]);
|
|
988
1204
|
const IMMUTABLE_FILE_CACHE = new Map();
|
|
989
1205
|
const IMMUTABLE_FILE_CACHE_MAX = 128;
|
|
990
1206
|
const FILE_PREVIEW_MAX_BYTES = 1_000_000;
|
|
@@ -1064,23 +1280,109 @@ function readFilePreview(pathname, totalBytes) {
|
|
|
1064
1280
|
closeSync(fd);
|
|
1065
1281
|
}
|
|
1066
1282
|
}
|
|
1067
|
-
function
|
|
1283
|
+
function parseAcceptedEncodings(rawHeader) {
|
|
1284
|
+
const parsed = new Map();
|
|
1285
|
+
if (!rawHeader || rawHeader.trim().length === 0)
|
|
1286
|
+
return parsed;
|
|
1287
|
+
const parts = rawHeader.split(",");
|
|
1288
|
+
for (const part of parts) {
|
|
1289
|
+
const [nameRaw, ...params] = part.split(";");
|
|
1290
|
+
const name = nameRaw?.trim().toLowerCase();
|
|
1291
|
+
if (!name)
|
|
1292
|
+
continue;
|
|
1293
|
+
let q = 1;
|
|
1294
|
+
for (const param of params) {
|
|
1295
|
+
const [keyRaw, valueRaw] = param.split("=");
|
|
1296
|
+
const key = keyRaw?.trim().toLowerCase();
|
|
1297
|
+
if (key !== "q")
|
|
1298
|
+
continue;
|
|
1299
|
+
const candidate = Number.parseFloat((valueRaw ?? "").trim());
|
|
1300
|
+
if (Number.isFinite(candidate)) {
|
|
1301
|
+
q = candidate;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (q <= 0)
|
|
1305
|
+
continue;
|
|
1306
|
+
const existing = parsed.get(name);
|
|
1307
|
+
if (existing == null || q > existing) {
|
|
1308
|
+
parsed.set(name, q);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return parsed;
|
|
1312
|
+
}
|
|
1313
|
+
function resolveEncodingQuality(accepted, encoding) {
|
|
1314
|
+
if (accepted.has(encoding))
|
|
1315
|
+
return accepted.get(encoding) ?? 0;
|
|
1316
|
+
if (accepted.has("*"))
|
|
1317
|
+
return accepted.get("*") ?? 0;
|
|
1318
|
+
return 0;
|
|
1319
|
+
}
|
|
1320
|
+
function resolvePrecompressedVariant(req, filePath) {
|
|
1321
|
+
const ext = extname(filePath).toLowerCase();
|
|
1322
|
+
if (!PRECOMPRESSED_FILE_EXTENSIONS.has(ext))
|
|
1323
|
+
return null;
|
|
1324
|
+
const accepted = parseAcceptedEncodings(pickHeaderString(req.headers, ["accept-encoding"]));
|
|
1325
|
+
if (accepted.size === 0)
|
|
1326
|
+
return null;
|
|
1327
|
+
const candidates = [
|
|
1328
|
+
{
|
|
1329
|
+
encoding: "br",
|
|
1330
|
+
path: `${filePath}.br`,
|
|
1331
|
+
quality: resolveEncodingQuality(accepted, "br"),
|
|
1332
|
+
priority: 2,
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
encoding: "gzip",
|
|
1336
|
+
path: `${filePath}.gz`,
|
|
1337
|
+
quality: resolveEncodingQuality(accepted, "gzip"),
|
|
1338
|
+
priority: 1,
|
|
1339
|
+
},
|
|
1340
|
+
];
|
|
1341
|
+
candidates.sort((left, right) => {
|
|
1342
|
+
if (right.quality !== left.quality)
|
|
1343
|
+
return right.quality - left.quality;
|
|
1344
|
+
return right.priority - left.priority;
|
|
1345
|
+
});
|
|
1346
|
+
for (const candidate of candidates) {
|
|
1347
|
+
if (candidate.quality <= 0)
|
|
1348
|
+
continue;
|
|
1349
|
+
if (existsSync(candidate.path)) {
|
|
1350
|
+
return { path: candidate.path, encoding: candidate.encoding };
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
function sendFile(req, res, filePath, cacheControl) {
|
|
1068
1356
|
try {
|
|
1069
1357
|
const shouldCacheImmutable = cacheControl.includes("immutable");
|
|
1358
|
+
const shouldVaryByEncoding = PRECOMPRESSED_FILE_EXTENSIONS.has(extname(filePath).toLowerCase());
|
|
1359
|
+
const precompressed = resolvePrecompressedVariant(req, filePath);
|
|
1360
|
+
const responsePath = precompressed?.path ?? filePath;
|
|
1361
|
+
const responseEncoding = precompressed?.encoding ?? null;
|
|
1362
|
+
const cacheKey = `${responsePath}|${cacheControl}`;
|
|
1070
1363
|
if (shouldCacheImmutable) {
|
|
1071
|
-
const cached = IMMUTABLE_FILE_CACHE.get(
|
|
1364
|
+
const cached = IMMUTABLE_FILE_CACHE.get(cacheKey);
|
|
1072
1365
|
if (cached) {
|
|
1073
|
-
|
|
1366
|
+
const headers = {
|
|
1074
1367
|
"Content-Type": cached.contentType,
|
|
1075
1368
|
"Cache-Control": cacheControl,
|
|
1076
1369
|
...SECURITY_HEADERS,
|
|
1077
1370
|
...CORS_HEADERS,
|
|
1371
|
+
};
|
|
1372
|
+
if (cached.contentEncoding === "br")
|
|
1373
|
+
headers["Content-Encoding"] = "br";
|
|
1374
|
+
if (cached.contentEncoding === "gzip")
|
|
1375
|
+
headers["Content-Encoding"] = "gzip";
|
|
1376
|
+
if (cached.varyAcceptEncoding)
|
|
1377
|
+
headers["Vary"] = "Accept-Encoding";
|
|
1378
|
+
res.writeHead(200, {
|
|
1379
|
+
...headers,
|
|
1078
1380
|
});
|
|
1079
1381
|
res.end(cached.content);
|
|
1080
1382
|
return;
|
|
1081
1383
|
}
|
|
1082
1384
|
}
|
|
1083
|
-
const content = readFileSync(
|
|
1385
|
+
const content = readFileSync(responsePath);
|
|
1084
1386
|
const type = contentType(filePath);
|
|
1085
1387
|
if (shouldCacheImmutable) {
|
|
1086
1388
|
if (IMMUTABLE_FILE_CACHE.size >= IMMUTABLE_FILE_CACHE_MAX) {
|
|
@@ -1088,14 +1390,26 @@ function sendFile(res, filePath, cacheControl) {
|
|
|
1088
1390
|
if (firstKey)
|
|
1089
1391
|
IMMUTABLE_FILE_CACHE.delete(firstKey);
|
|
1090
1392
|
}
|
|
1091
|
-
IMMUTABLE_FILE_CACHE.set(
|
|
1393
|
+
IMMUTABLE_FILE_CACHE.set(cacheKey, {
|
|
1394
|
+
content,
|
|
1395
|
+
contentType: type,
|
|
1396
|
+
contentEncoding: responseEncoding,
|
|
1397
|
+
varyAcceptEncoding: shouldVaryByEncoding,
|
|
1398
|
+
});
|
|
1092
1399
|
}
|
|
1093
|
-
|
|
1400
|
+
const headers = {
|
|
1094
1401
|
"Content-Type": type,
|
|
1095
1402
|
"Cache-Control": cacheControl,
|
|
1096
1403
|
...SECURITY_HEADERS,
|
|
1097
1404
|
...CORS_HEADERS,
|
|
1098
|
-
}
|
|
1405
|
+
};
|
|
1406
|
+
if (responseEncoding === "br")
|
|
1407
|
+
headers["Content-Encoding"] = "br";
|
|
1408
|
+
if (responseEncoding === "gzip")
|
|
1409
|
+
headers["Content-Encoding"] = "gzip";
|
|
1410
|
+
if (shouldVaryByEncoding)
|
|
1411
|
+
headers["Vary"] = "Accept-Encoding";
|
|
1412
|
+
res.writeHead(200, headers);
|
|
1099
1413
|
res.end(content);
|
|
1100
1414
|
}
|
|
1101
1415
|
catch {
|
|
@@ -1110,10 +1424,24 @@ function send404(res) {
|
|
|
1110
1424
|
});
|
|
1111
1425
|
res.end("Not Found");
|
|
1112
1426
|
}
|
|
1113
|
-
function
|
|
1427
|
+
function sendStaleChunkRecovery(res) {
|
|
1428
|
+
const body = [
|
|
1429
|
+
"// Recover from stale chunk references after dashboard/plugin upgrades.",
|
|
1430
|
+
"window.location.replace('/orgx/live' + window.location.search);",
|
|
1431
|
+
"export {};"
|
|
1432
|
+
].join("\n");
|
|
1433
|
+
res.writeHead(200, {
|
|
1434
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
1435
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
1436
|
+
...SECURITY_HEADERS,
|
|
1437
|
+
...CORS_HEADERS,
|
|
1438
|
+
});
|
|
1439
|
+
res.end(body);
|
|
1440
|
+
}
|
|
1441
|
+
function sendIndexHtml(req, res) {
|
|
1114
1442
|
const indexPath = join(DIST_DIR, "index.html");
|
|
1115
1443
|
if (existsSync(indexPath)) {
|
|
1116
|
-
sendFile(res, indexPath, "no-cache, no-store, must-revalidate");
|
|
1444
|
+
sendFile(req, res, indexPath, "no-cache, no-store, must-revalidate");
|
|
1117
1445
|
}
|
|
1118
1446
|
else {
|
|
1119
1447
|
res.writeHead(503, {
|
|
@@ -1307,9 +1635,60 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1307
1635
|
spawnAgentTurn,
|
|
1308
1636
|
upsertAgentRun,
|
|
1309
1637
|
});
|
|
1638
|
+
const normalizeRunnerAgentToken = (value) => {
|
|
1639
|
+
if (typeof value !== "string")
|
|
1640
|
+
return null;
|
|
1641
|
+
const trimmed = value.trim();
|
|
1642
|
+
if (!trimmed)
|
|
1643
|
+
return null;
|
|
1644
|
+
const normalized = trimmed.toLowerCase();
|
|
1645
|
+
if (normalized === "main" ||
|
|
1646
|
+
normalized === "undefined" ||
|
|
1647
|
+
normalized === "null" ||
|
|
1648
|
+
normalized === "n/a" ||
|
|
1649
|
+
normalized === "na") {
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
return trimmed;
|
|
1653
|
+
};
|
|
1654
|
+
const pushRunnerAgent = (target, seen, input) => {
|
|
1655
|
+
const agentId = normalizeRunnerAgentToken(input.id ?? null);
|
|
1656
|
+
const agentName = normalizeRunnerAgentToken(input.name ?? null);
|
|
1657
|
+
if (!agentId && !agentName)
|
|
1658
|
+
return;
|
|
1659
|
+
const resolvedId = agentId ?? agentName;
|
|
1660
|
+
const dedupeKey = resolvedId.toLowerCase();
|
|
1661
|
+
if (seen.has(dedupeKey))
|
|
1662
|
+
return;
|
|
1663
|
+
seen.add(dedupeKey);
|
|
1664
|
+
target.push({
|
|
1665
|
+
id: resolvedId,
|
|
1666
|
+
name: agentName ?? resolvedId,
|
|
1667
|
+
});
|
|
1668
|
+
};
|
|
1669
|
+
const dedupeWithPrimary = (primary, extras) => {
|
|
1670
|
+
const merged = [];
|
|
1671
|
+
const seen = new Set();
|
|
1672
|
+
for (const candidate of [...primary, ...extras]) {
|
|
1673
|
+
const id = normalizeRunnerAgentToken(candidate.id);
|
|
1674
|
+
const name = normalizeRunnerAgentToken(candidate.name);
|
|
1675
|
+
if (!id && !name)
|
|
1676
|
+
continue;
|
|
1677
|
+
const resolvedId = id ?? name;
|
|
1678
|
+
const key = resolvedId.toLowerCase();
|
|
1679
|
+
if (seen.has(key))
|
|
1680
|
+
continue;
|
|
1681
|
+
seen.add(key);
|
|
1682
|
+
merged.push({
|
|
1683
|
+
id: resolvedId,
|
|
1684
|
+
name: name ?? resolvedId,
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
return merged;
|
|
1688
|
+
};
|
|
1310
1689
|
const codexBinResolver = createCodexBinResolver();
|
|
1311
1690
|
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({
|
|
1691
|
+
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
1692
|
client,
|
|
1314
1693
|
filename: __filename,
|
|
1315
1694
|
safeErrorMessage,
|
|
@@ -1327,10 +1706,194 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1327
1706
|
clearSnapshotResponseCache,
|
|
1328
1707
|
resolveByokEnvOverrides,
|
|
1329
1708
|
randomUUID,
|
|
1709
|
+
fetchKickoffContextSafe,
|
|
1710
|
+
renderKickoffMessage,
|
|
1330
1711
|
});
|
|
1331
1712
|
const nextUpQueueCache = new Map();
|
|
1332
1713
|
const nextUpQueueInFlight = new Map();
|
|
1333
|
-
const
|
|
1714
|
+
const PROJECT_INITIATIVE_IDS_CACHE_TTL_MS = 20_000;
|
|
1715
|
+
const projectInitiativeIdsCache = new Map();
|
|
1716
|
+
const commandCenterScopeCache = new Map();
|
|
1717
|
+
const nextUpQueueCacheKeyFor = (initiativeId, projectId) => {
|
|
1718
|
+
const normalizedInitiative = initiativeId?.trim() || "__all__";
|
|
1719
|
+
const normalizedProject = projectId?.trim() || "__all__";
|
|
1720
|
+
return `${normalizedProject}::${normalizedInitiative}`;
|
|
1721
|
+
};
|
|
1722
|
+
async function listInitiativeIdsForProject(input) {
|
|
1723
|
+
const projectId = input.projectId.trim();
|
|
1724
|
+
if (!projectId)
|
|
1725
|
+
return [];
|
|
1726
|
+
const cached = projectInitiativeIdsCache.get(projectId);
|
|
1727
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
1728
|
+
return [...cached.ids];
|
|
1729
|
+
}
|
|
1730
|
+
const mapInitiativeIds = (rows, opts) => {
|
|
1731
|
+
const projectScopeId = opts?.projectId?.trim() ?? "";
|
|
1732
|
+
const commandCenterId = opts?.commandCenterId?.trim() ?? "";
|
|
1733
|
+
return rows
|
|
1734
|
+
.map((entry) => {
|
|
1735
|
+
const record = entry;
|
|
1736
|
+
if (projectScopeId) {
|
|
1737
|
+
const rowProjectId = pickString(record, ["project_id", "projectId"]) ?? "";
|
|
1738
|
+
if (rowProjectId !== projectScopeId)
|
|
1739
|
+
return null;
|
|
1740
|
+
}
|
|
1741
|
+
if (commandCenterId) {
|
|
1742
|
+
const rowCommandCenterId = pickString(record, [
|
|
1743
|
+
"workspace_id",
|
|
1744
|
+
"workspaceId",
|
|
1745
|
+
"command_center_id",
|
|
1746
|
+
"commandCenterId",
|
|
1747
|
+
]) ?? "";
|
|
1748
|
+
if (rowCommandCenterId !== commandCenterId)
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
return pickString(record, ["id"]);
|
|
1752
|
+
})
|
|
1753
|
+
.filter((id) => Boolean(id && id.trim().length > 0));
|
|
1754
|
+
};
|
|
1755
|
+
const cacheAndReturn = (ids) => {
|
|
1756
|
+
const normalized = dedupeStrings(ids
|
|
1757
|
+
.map((id) => id.trim())
|
|
1758
|
+
.filter((id) => id.length > 0));
|
|
1759
|
+
projectInitiativeIdsCache.set(projectId, {
|
|
1760
|
+
expiresAt: Date.now() + PROJECT_INITIATIVE_IDS_CACHE_TTL_MS,
|
|
1761
|
+
ids: normalized,
|
|
1762
|
+
});
|
|
1763
|
+
return normalized;
|
|
1764
|
+
};
|
|
1765
|
+
const isKnownCommandCenterScope = async () => {
|
|
1766
|
+
const cachedScope = commandCenterScopeCache.get(projectId);
|
|
1767
|
+
if (cachedScope && cachedScope.expiresAt > Date.now()) {
|
|
1768
|
+
return cachedScope.exists;
|
|
1769
|
+
}
|
|
1770
|
+
const cacheScope = (exists) => {
|
|
1771
|
+
commandCenterScopeCache.set(projectId, {
|
|
1772
|
+
expiresAt: Date.now() + PROJECT_INITIATIVE_IDS_CACHE_TTL_MS,
|
|
1773
|
+
exists,
|
|
1774
|
+
});
|
|
1775
|
+
return exists;
|
|
1776
|
+
};
|
|
1777
|
+
const hasId = (rows) => rows.some((entry) => {
|
|
1778
|
+
const record = entry;
|
|
1779
|
+
const id = pickString(record, ["id"]) ?? "";
|
|
1780
|
+
return id === projectId;
|
|
1781
|
+
});
|
|
1782
|
+
try {
|
|
1783
|
+
const byId = await withSoftTimeout("command center scope lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("command_center", {
|
|
1784
|
+
id: projectId,
|
|
1785
|
+
limit: 1,
|
|
1786
|
+
}));
|
|
1787
|
+
const byIdRows = Array.isArray(byId.data) ? byId.data : [];
|
|
1788
|
+
if (hasId(byIdRows))
|
|
1789
|
+
return cacheScope(true);
|
|
1790
|
+
}
|
|
1791
|
+
catch {
|
|
1792
|
+
// continue to all-command-center fallback
|
|
1793
|
+
}
|
|
1794
|
+
try {
|
|
1795
|
+
const all = await withSoftTimeout("command center catalog lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("command_center", {
|
|
1796
|
+
limit: 100,
|
|
1797
|
+
}));
|
|
1798
|
+
const allRows = Array.isArray(all.data) ? all.data : [];
|
|
1799
|
+
return cacheScope(hasId(allRows));
|
|
1800
|
+
}
|
|
1801
|
+
catch {
|
|
1802
|
+
return cacheScope(false);
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
const listInitiativesWithFilters = async (filters) => {
|
|
1806
|
+
const rows = [];
|
|
1807
|
+
const pageSize = 100;
|
|
1808
|
+
const seenIds = new Set();
|
|
1809
|
+
let offset = 0;
|
|
1810
|
+
let page = 0;
|
|
1811
|
+
while (page < PROJECT_SCOPE_MAX_INITIATIVE_PAGES) {
|
|
1812
|
+
const result = await withSoftTimeout("initiative scope lookup", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, client.listEntities("initiative", {
|
|
1813
|
+
...filters,
|
|
1814
|
+
limit: pageSize,
|
|
1815
|
+
offset,
|
|
1816
|
+
}));
|
|
1817
|
+
const pageRows = Array.isArray(result.data) ? result.data : [];
|
|
1818
|
+
let addedCount = 0;
|
|
1819
|
+
for (const entry of pageRows) {
|
|
1820
|
+
const record = entry;
|
|
1821
|
+
const id = pickString(record, ["id"]);
|
|
1822
|
+
if (id && seenIds.has(id))
|
|
1823
|
+
continue;
|
|
1824
|
+
if (id)
|
|
1825
|
+
seenIds.add(id);
|
|
1826
|
+
rows.push(entry);
|
|
1827
|
+
addedCount += 1;
|
|
1828
|
+
}
|
|
1829
|
+
const hasMoreFlag = Boolean(result.pagination?.has_more);
|
|
1830
|
+
const likelyMore = hasMoreFlag || pageRows.length >= pageSize;
|
|
1831
|
+
if (!likelyMore)
|
|
1832
|
+
break;
|
|
1833
|
+
if (addedCount === 0)
|
|
1834
|
+
break;
|
|
1835
|
+
offset += pageRows.length;
|
|
1836
|
+
page += 1;
|
|
1837
|
+
}
|
|
1838
|
+
return rows;
|
|
1839
|
+
};
|
|
1840
|
+
const listLiveInitiativesWithFilters = async (filters) => {
|
|
1841
|
+
// Fast path: request once without status fan-out. Upstream often returns
|
|
1842
|
+
// all relevant rows and this avoids 5x paginated round-trips.
|
|
1843
|
+
const broadRows = await listInitiativesWithFilters(filters);
|
|
1844
|
+
if (broadRows.length > 0)
|
|
1845
|
+
return broadRows;
|
|
1846
|
+
// Backward-compat fallback for upstreams that require explicit status.
|
|
1847
|
+
const rows = [];
|
|
1848
|
+
for (const status of LIVE_WORKSPACE_INITIATIVE_STATUSES) {
|
|
1849
|
+
const statusRows = await listInitiativesWithFilters({
|
|
1850
|
+
...filters,
|
|
1851
|
+
status,
|
|
1852
|
+
});
|
|
1853
|
+
rows.push(...statusRows);
|
|
1854
|
+
}
|
|
1855
|
+
return rows;
|
|
1856
|
+
};
|
|
1857
|
+
try {
|
|
1858
|
+
// Workspace selection in the plugin uses command-center IDs.
|
|
1859
|
+
// Resolve that scope first so broad project queries never leak cross-workspace items.
|
|
1860
|
+
const byCommandCenterIds = mapInitiativeIds(await listLiveInitiativesWithFilters({
|
|
1861
|
+
workspace_id: projectId,
|
|
1862
|
+
command_center_id: projectId,
|
|
1863
|
+
}), { commandCenterId: projectId });
|
|
1864
|
+
if (byCommandCenterIds.length > 0)
|
|
1865
|
+
return cacheAndReturn(byCommandCenterIds);
|
|
1866
|
+
}
|
|
1867
|
+
catch {
|
|
1868
|
+
// continue to project-id fallback
|
|
1869
|
+
}
|
|
1870
|
+
try {
|
|
1871
|
+
// Do not hard-return empty for known command-center scopes here.
|
|
1872
|
+
// Some tenants only populate project_id links, so we continue through
|
|
1873
|
+
// project-id fallbacks before concluding the scope is empty.
|
|
1874
|
+
await isKnownCommandCenterScope();
|
|
1875
|
+
}
|
|
1876
|
+
catch {
|
|
1877
|
+
// continue to project-id fallback
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const byWorkspaceFallback = mapInitiativeIds(await listLiveInitiativesWithFilters({
|
|
1881
|
+
workspace_id: projectId,
|
|
1882
|
+
command_center_id: projectId,
|
|
1883
|
+
}), { projectId });
|
|
1884
|
+
if (byWorkspaceFallback.length > 0)
|
|
1885
|
+
return cacheAndReturn(byWorkspaceFallback);
|
|
1886
|
+
}
|
|
1887
|
+
catch {
|
|
1888
|
+
// continue to empty fallback
|
|
1889
|
+
}
|
|
1890
|
+
try {
|
|
1891
|
+
return cacheAndReturn([]);
|
|
1892
|
+
}
|
|
1893
|
+
catch {
|
|
1894
|
+
return [];
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1334
1897
|
const readNextUpQueueCache = (key, opts) => {
|
|
1335
1898
|
const entry = nextUpQueueCache.get(key);
|
|
1336
1899
|
if (!entry)
|
|
@@ -1361,9 +1924,37 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1361
1924
|
},
|
|
1362
1925
|
});
|
|
1363
1926
|
};
|
|
1927
|
+
const clearNextUpQueueCache = (initiativeId) => {
|
|
1928
|
+
const normalized = initiativeId?.trim() || null;
|
|
1929
|
+
if (!normalized) {
|
|
1930
|
+
nextUpQueueCache.clear();
|
|
1931
|
+
nextUpQueueInFlight.clear();
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
for (const key of Array.from(nextUpQueueCache.keys())) {
|
|
1935
|
+
if (key.endsWith(`::${normalized}`) || key.endsWith("::__all__")) {
|
|
1936
|
+
nextUpQueueCache.delete(key);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
for (const key of Array.from(nextUpQueueInFlight.keys())) {
|
|
1940
|
+
if (key.endsWith(`::${normalized}`) || key.endsWith("::__all__")) {
|
|
1941
|
+
nextUpQueueInFlight.delete(key);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1364
1945
|
async function buildNextUpQueueUncached(input) {
|
|
1365
1946
|
const degraded = [];
|
|
1366
1947
|
const requestedInitiativeId = input?.initiativeId?.trim() || null;
|
|
1948
|
+
const requestedProjectId = input?.projectId?.trim() || null;
|
|
1949
|
+
let allowedInitiativeIds = null;
|
|
1950
|
+
if (requestedProjectId && requestedProjectId.length > 0) {
|
|
1951
|
+
const scopedIds = await listInitiativeIdsForProject({
|
|
1952
|
+
projectId: requestedProjectId,
|
|
1953
|
+
});
|
|
1954
|
+
if (scopedIds.length > 0) {
|
|
1955
|
+
allowedInitiativeIds = new Set(scopedIds);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1367
1958
|
const pinnedQueue = readNextUpQueuePins();
|
|
1368
1959
|
const pinnedRankByKey = new Map();
|
|
1369
1960
|
const pinnedByKey = new Map();
|
|
@@ -1377,6 +1968,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1377
1968
|
preferredMilestoneId: pin.preferredMilestoneId ?? null,
|
|
1378
1969
|
});
|
|
1379
1970
|
}
|
|
1971
|
+
const suppressedKeySet = new Set();
|
|
1972
|
+
for (const suppression of pinnedQueue.suppressions ?? []) {
|
|
1973
|
+
const initiativeId = suppression.initiativeId?.trim();
|
|
1974
|
+
const workstreamId = suppression.workstreamId?.trim();
|
|
1975
|
+
if (!initiativeId || !workstreamId)
|
|
1976
|
+
continue;
|
|
1977
|
+
if (requestedInitiativeId && initiativeId !== requestedInitiativeId)
|
|
1978
|
+
continue;
|
|
1979
|
+
suppressedKeySet.add(`${initiativeId}:${workstreamId}`);
|
|
1980
|
+
}
|
|
1981
|
+
const isSuppressed = (initiativeId, workstreamId) => suppressedKeySet.has(`${initiativeId}:${workstreamId}`);
|
|
1380
1982
|
const initiativeTitleById = new Map();
|
|
1381
1983
|
const initiativeStatusById = new Map();
|
|
1382
1984
|
const initiativePriorityById = new Map();
|
|
@@ -1388,7 +1990,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1388
1990
|
initiativeTitleById.set(id, initiative.title);
|
|
1389
1991
|
initiativeStatusById.set(id, initiative.status || "active");
|
|
1390
1992
|
}
|
|
1391
|
-
const initiativeResult = await listEntitiesSafe(client, "initiative", { limit: 500 })
|
|
1993
|
+
const initiativeResult = await withSoftTimeout("initiative list", PROJECT_SCOPE_LOOKUP_TIMEOUT_MS, listEntitiesSafe(client, "initiative", { limit: 500 })).catch((err) => ({
|
|
1994
|
+
items: [],
|
|
1995
|
+
warning: `initiative unavailable (${safeErrorMessage(err)})`,
|
|
1996
|
+
}));
|
|
1392
1997
|
if (initiativeResult.warning)
|
|
1393
1998
|
degraded.push(initiativeResult.warning);
|
|
1394
1999
|
const initiatives = initiativeResult.items;
|
|
@@ -1407,6 +2012,39 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1407
2012
|
if (priority)
|
|
1408
2013
|
initiativePriorityById.set(id, priority);
|
|
1409
2014
|
}
|
|
2015
|
+
const initiativeMatchesRequestedProject = (record) => {
|
|
2016
|
+
if (!requestedProjectId)
|
|
2017
|
+
return true;
|
|
2018
|
+
const scopedValue = pickString(record, [
|
|
2019
|
+
"workspace_id",
|
|
2020
|
+
"workspaceId",
|
|
2021
|
+
"command_center_id",
|
|
2022
|
+
"commandCenterId",
|
|
2023
|
+
"project_id",
|
|
2024
|
+
"projectId",
|
|
2025
|
+
]) ?? null;
|
|
2026
|
+
if (!scopedValue)
|
|
2027
|
+
return false;
|
|
2028
|
+
return scopedValue === requestedProjectId;
|
|
2029
|
+
};
|
|
2030
|
+
if (requestedProjectId && !allowedInitiativeIds) {
|
|
2031
|
+
const metadataScopedIds = initiatives
|
|
2032
|
+
.map((entity) => {
|
|
2033
|
+
const record = entity;
|
|
2034
|
+
const id = pickString(record, ["id"]);
|
|
2035
|
+
if (!id)
|
|
2036
|
+
return null;
|
|
2037
|
+
return initiativeMatchesRequestedProject(record) ? id : null;
|
|
2038
|
+
})
|
|
2039
|
+
.filter((value) => Boolean(value));
|
|
2040
|
+
if (metadataScopedIds.length > 0) {
|
|
2041
|
+
allowedInitiativeIds = new Set(metadataScopedIds);
|
|
2042
|
+
degraded.push("workspace initiative scope lookup returned no rows; using metadata scoped initiatives.");
|
|
2043
|
+
}
|
|
2044
|
+
else {
|
|
2045
|
+
degraded.push("workspace initiative scope lookup returned no rows; local queue may be incomplete.");
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
1410
2048
|
for (const [initiativeId, override] of localInitiativeStatusOverrides.entries()) {
|
|
1411
2049
|
initiativeStatusById.set(initiativeId, override.status);
|
|
1412
2050
|
}
|
|
@@ -1420,9 +2058,6 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1420
2058
|
return 3;
|
|
1421
2059
|
};
|
|
1422
2060
|
const sortQueueItems = (a, b) => {
|
|
1423
|
-
const queueDelta = queueRank(a.queueState) - queueRank(b.queueState);
|
|
1424
|
-
if (queueDelta !== 0)
|
|
1425
|
-
return queueDelta;
|
|
1426
2061
|
const aPinnedRank = pinnedRankByKey.get(`${a.initiativeId}:${a.workstreamId}`);
|
|
1427
2062
|
const bPinnedRank = pinnedRankByKey.get(`${b.initiativeId}:${b.workstreamId}`);
|
|
1428
2063
|
if (aPinnedRank !== undefined || bPinnedRank !== undefined) {
|
|
@@ -1431,6 +2066,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1431
2066
|
if (aRank !== bRank)
|
|
1432
2067
|
return aRank - bRank;
|
|
1433
2068
|
}
|
|
2069
|
+
const queueDelta = queueRank(a.queueState) - queueRank(b.queueState);
|
|
2070
|
+
if (queueDelta !== 0)
|
|
2071
|
+
return queueDelta;
|
|
1434
2072
|
const priorityRank = (value) => {
|
|
1435
2073
|
const normalized = (value ?? "").trim().toLowerCase();
|
|
1436
2074
|
if (!normalized)
|
|
@@ -1466,10 +2104,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1466
2104
|
const buildSessionFallbackQueue = async () => {
|
|
1467
2105
|
let sessionTree = null;
|
|
1468
2106
|
try {
|
|
1469
|
-
sessionTree = await client.getLiveSessions({
|
|
2107
|
+
sessionTree = await withSoftTimeout("live sessions", NEXT_UP_LIVE_SESSIONS_TIMEOUT_MS, client.getLiveSessions({
|
|
1470
2108
|
initiative: requestedInitiativeId,
|
|
2109
|
+
projectId: requestedProjectId,
|
|
1471
2110
|
limit: 500,
|
|
1472
|
-
});
|
|
2111
|
+
}));
|
|
1473
2112
|
}
|
|
1474
2113
|
catch (err) {
|
|
1475
2114
|
degraded.push(`live sessions fallback unavailable (${safeErrorMessage(err)})`);
|
|
@@ -1501,6 +2140,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1501
2140
|
continue;
|
|
1502
2141
|
if (requestedInitiativeId && initiativeId !== requestedInitiativeId)
|
|
1503
2142
|
continue;
|
|
2143
|
+
if (allowedInitiativeIds && !allowedInitiativeIds.has(initiativeId))
|
|
2144
|
+
continue;
|
|
1504
2145
|
const initiativeStatus = initiativeStatusById.get(initiativeId) ?? "active";
|
|
1505
2146
|
if (!isInitiativeActiveStatus(initiativeStatus))
|
|
1506
2147
|
continue;
|
|
@@ -1508,6 +2149,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1508
2149
|
const epoch = parseEpoch(node.updatedAt ?? node.lastEventAt ?? node.startedAt);
|
|
1509
2150
|
const existing = grouped.get(key);
|
|
1510
2151
|
if (!existing) {
|
|
2152
|
+
const runnerAgents = [];
|
|
2153
|
+
const runnerAgentSeen = new Set();
|
|
2154
|
+
pushRunnerAgent(runnerAgents, runnerAgentSeen, {
|
|
2155
|
+
id: node.agentId,
|
|
2156
|
+
name: node.agentName,
|
|
2157
|
+
});
|
|
1511
2158
|
grouped.set(key, {
|
|
1512
2159
|
initiativeId,
|
|
1513
2160
|
workstreamId,
|
|
@@ -1518,6 +2165,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1518
2165
|
workstreamTitle: `Workstream ${workstreamId.slice(0, 8)}`,
|
|
1519
2166
|
statuses: new Set([node.status]),
|
|
1520
2167
|
blockers: Array.isArray(node.blockers) ? [...node.blockers] : [],
|
|
2168
|
+
runnerAgents,
|
|
2169
|
+
runnerAgentSeen,
|
|
1521
2170
|
latest: node,
|
|
1522
2171
|
latestEpoch: epoch,
|
|
1523
2172
|
});
|
|
@@ -1532,6 +2181,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1532
2181
|
existing.blockers.push(blocker);
|
|
1533
2182
|
}
|
|
1534
2183
|
}
|
|
2184
|
+
pushRunnerAgent(existing.runnerAgents, existing.runnerAgentSeen, {
|
|
2185
|
+
id: node.agentId,
|
|
2186
|
+
name: node.agentName,
|
|
2187
|
+
});
|
|
1535
2188
|
if (epoch >= existing.latestEpoch) {
|
|
1536
2189
|
existing.latest = node;
|
|
1537
2190
|
existing.latestEpoch = epoch;
|
|
@@ -1551,11 +2204,22 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1551
2204
|
: hasQueued
|
|
1552
2205
|
? "queued"
|
|
1553
2206
|
: "idle";
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
2207
|
+
const latestRunner = [];
|
|
2208
|
+
const latestRunnerSeen = new Set();
|
|
2209
|
+
pushRunnerAgent(latestRunner, latestRunnerSeen, {
|
|
2210
|
+
id: entry.latest.agentId,
|
|
2211
|
+
name: entry.latest.agentName,
|
|
2212
|
+
});
|
|
2213
|
+
const runnerAgents = latestRunner.length > 0
|
|
2214
|
+
? dedupeWithPrimary(latestRunner, entry.runnerAgents)
|
|
2215
|
+
: [...entry.runnerAgents];
|
|
2216
|
+
const primaryRunner = runnerAgents[0] ?? null;
|
|
2217
|
+
const runnerAgentId = primaryRunner?.id ?? "unassigned";
|
|
2218
|
+
const runnerAgentName = primaryRunner?.name ?? "Unassigned";
|
|
1558
2219
|
const pinKey = `${entry.initiativeId}:${entry.workstreamId}`;
|
|
2220
|
+
if (isSuppressed(entry.initiativeId, entry.workstreamId) && queueState !== "running") {
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
1559
2223
|
fallbackItems.push({
|
|
1560
2224
|
initiativeId: entry.initiativeId,
|
|
1561
2225
|
initiativeTitle: entry.initiativeTitle,
|
|
@@ -1571,10 +2235,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1571
2235
|
nextTaskDueAt: null,
|
|
1572
2236
|
runnerAgentId,
|
|
1573
2237
|
runnerAgentName,
|
|
2238
|
+
runnerAgents,
|
|
1574
2239
|
runnerSource: "fallback",
|
|
1575
2240
|
queueState,
|
|
1576
2241
|
blockReason: hasBlocked
|
|
1577
|
-
? entry.blockers[0] ??
|
|
2242
|
+
? entry.blockers[0] ??
|
|
2243
|
+
(statusValues.includes("failed") ? "Latest run failed" : "Workstream blocked")
|
|
1578
2244
|
: null,
|
|
1579
2245
|
isPinned: pinnedRankByKey.has(pinKey),
|
|
1580
2246
|
pinnedRank: pinnedRankByKey.get(pinKey) ?? null,
|
|
@@ -1591,6 +2257,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1591
2257
|
return false;
|
|
1592
2258
|
if (requestedInitiativeId && id !== requestedInitiativeId)
|
|
1593
2259
|
return false;
|
|
2260
|
+
if (!initiativeMatchesRequestedProject(record))
|
|
2261
|
+
return false;
|
|
2262
|
+
if (allowedInitiativeIds && !allowedInitiativeIds.has(id))
|
|
2263
|
+
return false;
|
|
1594
2264
|
const status = pickString(record, ["status"]);
|
|
1595
2265
|
return isInitiativeActiveStatus(status);
|
|
1596
2266
|
});
|
|
@@ -1616,6 +2286,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1616
2286
|
try {
|
|
1617
2287
|
const data = await withSoftTimeout("live agents", NEXT_UP_LIVE_AGENTS_TIMEOUT_MS, client.getLiveAgents({
|
|
1618
2288
|
initiative: requestedInitiativeId,
|
|
2289
|
+
projectId: requestedProjectId,
|
|
1619
2290
|
includeIdle: true,
|
|
1620
2291
|
}));
|
|
1621
2292
|
for (const raw of Array.isArray(data.agents) ? data.agents : []) {
|
|
@@ -1673,13 +2344,50 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1673
2344
|
return (milestone?.status?.toLowerCase() === "blocked" ||
|
|
1674
2345
|
workstream?.status?.toLowerCase() === "blocked");
|
|
1675
2346
|
};
|
|
2347
|
+
const normalizeSliceScope = (value) => {
|
|
2348
|
+
if (value === "task" || value === "milestone" || value === "workstream") {
|
|
2349
|
+
return value;
|
|
2350
|
+
}
|
|
2351
|
+
return null;
|
|
2352
|
+
};
|
|
2353
|
+
const resolveExecutionPolicyFromActiveRuns = (activeRunIds, workstreamId) => {
|
|
2354
|
+
for (const runId of activeRunIds) {
|
|
2355
|
+
const slice = autoContinueSliceRuns.get(runId);
|
|
2356
|
+
if (!slice)
|
|
2357
|
+
continue;
|
|
2358
|
+
if (typeof slice.workstreamId === "string" && slice.workstreamId.trim()) {
|
|
2359
|
+
if (slice.workstreamId.trim() !== workstreamId)
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
const domain = (slice.domain ?? "").trim();
|
|
2363
|
+
const requiredSkills = Array.isArray(slice.requiredSkills)
|
|
2364
|
+
? slice.requiredSkills.filter((skill) => typeof skill === "string" && skill.trim().length > 0)
|
|
2365
|
+
: [];
|
|
2366
|
+
if (!domain || requiredSkills.length === 0)
|
|
2367
|
+
continue;
|
|
2368
|
+
const executionPolicy = {
|
|
2369
|
+
domain,
|
|
2370
|
+
requiredSkills,
|
|
2371
|
+
};
|
|
2372
|
+
if (typeof slice.behaviorConfigId === "string" && slice.behaviorConfigId.trim()) {
|
|
2373
|
+
executionPolicy.profile = slice.behaviorConfigId.trim();
|
|
2374
|
+
}
|
|
2375
|
+
const scope = normalizeSliceScope(slice.scope ?? null);
|
|
2376
|
+
if (scope) {
|
|
2377
|
+
executionPolicy.sliceScopePreference = scope;
|
|
2378
|
+
}
|
|
2379
|
+
return executionPolicy;
|
|
2380
|
+
}
|
|
2381
|
+
return null;
|
|
2382
|
+
};
|
|
1676
2383
|
for (const workstream of workstreamNodes) {
|
|
2384
|
+
const workstreamKey = `${initiativeId}:${workstream.id}`;
|
|
1677
2385
|
const todoTasks = graph.recentTodos
|
|
1678
2386
|
.map((taskId) => nodeById.get(taskId))
|
|
1679
2387
|
.filter((node) => node?.type === "task" &&
|
|
1680
2388
|
node.workstreamId === workstream.id &&
|
|
1681
2389
|
isTodoStatus(node.status));
|
|
1682
|
-
const pinKey =
|
|
2390
|
+
const pinKey = workstreamKey;
|
|
1683
2391
|
const pin = pinnedByKey.get(pinKey) ?? null;
|
|
1684
2392
|
const preferredTask = pin?.preferredTaskId && nodeById.get(pin.preferredTaskId)
|
|
1685
2393
|
? nodeById.get(pin.preferredTaskId) ?? null
|
|
@@ -1704,12 +2412,100 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1704
2412
|
const preferredReadyTask = preferredCandidates.find((task) => taskIsReady(task) && !taskHasBlockedParent(task));
|
|
1705
2413
|
const candidateTask = preferredReadyTask ?? readyTask ?? todoTasks[0] ?? null;
|
|
1706
2414
|
const autoContinueRun = runningAutoContinueForWorkstream(initiativeId, workstream.id);
|
|
1707
|
-
|
|
2415
|
+
const autoContinueLane = getAutoContinueLaneForWorkstream(initiativeId, workstream.id);
|
|
2416
|
+
const laneState = autoContinueLane?.state ?? null;
|
|
2417
|
+
const scopedAllowedWorkstreams = Array.isArray(autoContinueRun?.allowedWorkstreamIds)
|
|
2418
|
+
? (autoContinueRun.allowedWorkstreamIds
|
|
2419
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2420
|
+
.map((id) => id.trim()))
|
|
2421
|
+
: [];
|
|
2422
|
+
const runScopedToCurrentWorkstream = scopedAllowedWorkstreams.length === 1 &&
|
|
2423
|
+
scopedAllowedWorkstreams[0] === workstream.id &&
|
|
2424
|
+
autoContinueRun?.status === "running";
|
|
2425
|
+
const activeRunIds = Array.isArray(autoContinueRun?.activeSliceRunIds)
|
|
2426
|
+
? autoContinueRun.activeSliceRunIds
|
|
2427
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2428
|
+
.map((id) => id.trim())
|
|
2429
|
+
: [];
|
|
2430
|
+
const activeTaskId = (autoContinueLane?.activeTaskIds?.[0]?.trim() ||
|
|
2431
|
+
autoContinueRun?.activeTaskId?.trim() ||
|
|
2432
|
+
null) ??
|
|
2433
|
+
null;
|
|
2434
|
+
const activeTaskNode = activeTaskId ? nodeById.get(activeTaskId) ?? null : null;
|
|
2435
|
+
const policyTask = candidateTask ??
|
|
2436
|
+
activeTaskNode ??
|
|
2437
|
+
todoTasks.find((task) => task.workstreamId === workstream.id) ??
|
|
2438
|
+
null;
|
|
2439
|
+
const derivedExecutionPolicy = policyTask
|
|
2440
|
+
? deriveExecutionPolicy(policyTask, workstream)
|
|
2441
|
+
: null;
|
|
2442
|
+
const activeExecutionPolicy = resolveExecutionPolicyFromActiveRuns(activeRunIds, workstream.id);
|
|
2443
|
+
const executionPolicy = derivedExecutionPolicy ?? activeExecutionPolicy;
|
|
2444
|
+
const runScope = normalizeSliceScope(autoContinueRun?.scope ?? null);
|
|
2445
|
+
const preferredPolicyScope = normalizeSliceScope(executionPolicy?.sliceScopePreference ?? null);
|
|
2446
|
+
const defaultScope = runScope ??
|
|
2447
|
+
(preferredPolicyScope && preferredPolicyScope !== "task"
|
|
2448
|
+
? preferredPolicyScope
|
|
2449
|
+
: pin?.preferredMilestoneId
|
|
2450
|
+
? "milestone"
|
|
2451
|
+
: "task");
|
|
2452
|
+
const scopeSelection = selectSliceTasksByScope({
|
|
2453
|
+
scope: defaultScope,
|
|
2454
|
+
workstreamId: workstream.id,
|
|
2455
|
+
milestoneId: pin?.preferredMilestoneId ?? null,
|
|
2456
|
+
recentTodos: graph.recentTodos,
|
|
2457
|
+
nodeById,
|
|
2458
|
+
includeVerification: autoContinueRun?.includeVerification ?? false,
|
|
2459
|
+
});
|
|
2460
|
+
const cappedSliceTasks = typeof executionPolicy?.maxSliceTasks === "number" &&
|
|
2461
|
+
executionPolicy.maxSliceTasks > 0
|
|
2462
|
+
? scopeSelection.tasks.slice(0, executionPolicy.maxSliceTasks)
|
|
2463
|
+
: scopeSelection.tasks;
|
|
2464
|
+
const sliceTaskIds = cappedSliceTasks.length > 0
|
|
2465
|
+
? cappedSliceTasks.map((task) => task.id)
|
|
2466
|
+
: candidateTask?.id
|
|
2467
|
+
? [candidateTask.id]
|
|
2468
|
+
: activeTaskId
|
|
2469
|
+
? [activeTaskId]
|
|
2470
|
+
: [];
|
|
2471
|
+
const sliceMilestoneId = defaultScope === "milestone"
|
|
2472
|
+
? scopeSelection.milestoneIds[0] ?? pin?.preferredMilestoneId ?? null
|
|
2473
|
+
: null;
|
|
2474
|
+
let queueState = laneState === "running"
|
|
1708
2475
|
? "running"
|
|
1709
|
-
:
|
|
1710
|
-
? "
|
|
1711
|
-
:
|
|
2476
|
+
: runScopedToCurrentWorkstream
|
|
2477
|
+
? "running"
|
|
2478
|
+
: candidateTask
|
|
2479
|
+
? "queued"
|
|
2480
|
+
: "idle";
|
|
1712
2481
|
let blockReason = null;
|
|
2482
|
+
if (laneState === "blocked") {
|
|
2483
|
+
queueState = "blocked";
|
|
2484
|
+
blockReason = autoContinueLane?.blockedReason ?? "Blocked";
|
|
2485
|
+
}
|
|
2486
|
+
else if (laneState === "waiting_dependency") {
|
|
2487
|
+
queueState = "blocked";
|
|
2488
|
+
if (Array.isArray(autoContinueLane?.waitingOnWorkstreamIds) &&
|
|
2489
|
+
autoContinueLane.waitingOnWorkstreamIds.length > 0) {
|
|
2490
|
+
const waitingTitles = autoContinueLane.waitingOnWorkstreamIds
|
|
2491
|
+
.map((id) => {
|
|
2492
|
+
const node = nodeById.get(id);
|
|
2493
|
+
return node?.type === "workstream" ? node.title : id;
|
|
2494
|
+
})
|
|
2495
|
+
.filter(Boolean);
|
|
2496
|
+
blockReason =
|
|
2497
|
+
waitingTitles.length > 0
|
|
2498
|
+
? `Waiting on ${waitingTitles.slice(0, 2).join(", ")}${waitingTitles.length > 2 ? "…" : ""}`
|
|
2499
|
+
: "Waiting on dependency workstreams";
|
|
2500
|
+
}
|
|
2501
|
+
else {
|
|
2502
|
+
blockReason = "Waiting on dependency workstreams";
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
else if (laneState === "rate_limited") {
|
|
2506
|
+
queueState = "blocked";
|
|
2507
|
+
blockReason = autoContinueLane?.blockedReason ?? "Rate-limited";
|
|
2508
|
+
}
|
|
1713
2509
|
if (!autoContinueRun && !readyTask && candidateTask) {
|
|
1714
2510
|
queueState = "blocked";
|
|
1715
2511
|
const blockedDeps = candidateTask.dependencyIds
|
|
@@ -1729,27 +2525,48 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1729
2525
|
if (!candidateTask && !autoContinueRun && !pin) {
|
|
1730
2526
|
continue;
|
|
1731
2527
|
}
|
|
2528
|
+
if (isSuppressed(initiativeId, workstream.id) && queueState !== "running") {
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
1732
2531
|
runningWorkstreams.add(workstream.id);
|
|
1733
|
-
const
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
(
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
const
|
|
2532
|
+
const assignedRunnerAgents = [];
|
|
2533
|
+
const assignedRunnerSeen = new Set();
|
|
2534
|
+
for (const agent of workstream.assignedAgents) {
|
|
2535
|
+
pushRunnerAgent(assignedRunnerAgents, assignedRunnerSeen, {
|
|
2536
|
+
id: agent.id,
|
|
2537
|
+
name: agent.name,
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
const inferredRunnerAgents = [];
|
|
2541
|
+
const inferredRunnerSeen = new Set();
|
|
2542
|
+
for (const agent of graph.initiative.assignedAgents) {
|
|
2543
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2544
|
+
id: agent.id,
|
|
2545
|
+
name: agent.name,
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
for (const agent of liveAgentsByInitiative.get(initiativeId) ?? []) {
|
|
2549
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2550
|
+
id: agent.id,
|
|
2551
|
+
name: agent.name,
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
if (autoContinueRun?.agentId) {
|
|
2555
|
+
pushRunnerAgent(inferredRunnerAgents, inferredRunnerSeen, {
|
|
2556
|
+
id: autoContinueRun.agentId,
|
|
2557
|
+
name: agentCatalogById.get(autoContinueRun.agentId)?.name ??
|
|
2558
|
+
autoContinueRun.agentId,
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
const runnerAgents = assignedRunnerAgents.length > 0 ? assignedRunnerAgents : inferredRunnerAgents;
|
|
2562
|
+
const runnerSource = assignedRunnerAgents.length > 0
|
|
1744
2563
|
? "assigned"
|
|
1745
|
-
:
|
|
2564
|
+
: runnerAgents.length > 0
|
|
1746
2565
|
? "inferred"
|
|
1747
2566
|
: "fallback";
|
|
1748
|
-
const
|
|
1749
|
-
const runnerAgentId =
|
|
1750
|
-
const runnerAgentName =
|
|
1751
|
-
agentCatalogById.get(runnerAgentId)?.name ??
|
|
1752
|
-
runnerAgentId;
|
|
2567
|
+
const primaryRunner = runnerAgents[0] ?? null;
|
|
2568
|
+
const runnerAgentId = primaryRunner?.id ?? "unassigned";
|
|
2569
|
+
const runnerAgentName = primaryRunner?.name ?? "Unassigned";
|
|
1753
2570
|
itemsForInitiative.push({
|
|
1754
2571
|
initiativeId,
|
|
1755
2572
|
initiativeTitle,
|
|
@@ -1758,25 +2575,50 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1758
2575
|
workstreamTitle: workstream.title,
|
|
1759
2576
|
workstreamStatus: workstream.status,
|
|
1760
2577
|
nextTaskId: candidateTask?.id ??
|
|
1761
|
-
|
|
2578
|
+
activeTaskId,
|
|
1762
2579
|
nextTaskTitle: candidateTask?.title ??
|
|
1763
|
-
(
|
|
1764
|
-
? nodeById.get(
|
|
2580
|
+
((activeTaskId)
|
|
2581
|
+
? nodeById.get(activeTaskId)?.title ?? null
|
|
1765
2582
|
: null),
|
|
1766
2583
|
nextTaskPriority: candidateTask?.priorityNum ?? null,
|
|
1767
2584
|
nextTaskDueAt: candidateTask?.dueDate ?? null,
|
|
1768
2585
|
runnerAgentId,
|
|
1769
2586
|
runnerAgentName,
|
|
2587
|
+
runnerAgents,
|
|
1770
2588
|
runnerSource,
|
|
1771
2589
|
queueState,
|
|
1772
2590
|
blockReason,
|
|
1773
2591
|
isPinned: Boolean(pin),
|
|
1774
2592
|
pinnedRank: pin ? (pinnedRankByKey.get(pinKey) ?? null) : null,
|
|
2593
|
+
sliceScope: defaultScope,
|
|
2594
|
+
sliceTaskIds,
|
|
2595
|
+
sliceTaskCount: sliceTaskIds.length,
|
|
2596
|
+
sliceMilestoneId,
|
|
2597
|
+
executionPolicy,
|
|
1775
2598
|
autoContinue: autoContinueRun
|
|
1776
2599
|
? {
|
|
1777
2600
|
status: autoContinueRun.status,
|
|
1778
2601
|
activeTaskId: autoContinueRun.activeTaskId,
|
|
1779
2602
|
activeRunId: autoContinueRun.activeRunId,
|
|
2603
|
+
activeTaskIds: Array.isArray(autoContinueRun.activeTaskIds)
|
|
2604
|
+
? autoContinueRun.activeTaskIds
|
|
2605
|
+
: [],
|
|
2606
|
+
activeRunIds: Array.isArray(autoContinueRun.activeSliceRunIds)
|
|
2607
|
+
? autoContinueRun.activeSliceRunIds
|
|
2608
|
+
: [],
|
|
2609
|
+
laneState,
|
|
2610
|
+
laneBlockedReason: autoContinueLane?.blockedReason ?? null,
|
|
2611
|
+
laneWaitingOnWorkstreamIds: Array.isArray(autoContinueLane?.waitingOnWorkstreamIds)
|
|
2612
|
+
? autoContinueLane.waitingOnWorkstreamIds
|
|
2613
|
+
: [],
|
|
2614
|
+
laneRetryAt: autoContinueLane?.retryAt ?? null,
|
|
2615
|
+
maxParallelSlices: typeof autoContinueRun.maxParallelSlices === "number"
|
|
2616
|
+
? autoContinueRun.maxParallelSlices
|
|
2617
|
+
: 1,
|
|
2618
|
+
parallelMode: (typeof autoContinueRun.parallelMode === "string" &&
|
|
2619
|
+
autoContinueRun.parallelMode.toLowerCase() === "iwmt"
|
|
2620
|
+
? "iwmt"
|
|
2621
|
+
: "iwmt"),
|
|
1780
2622
|
stopReason: autoContinueRun.stopReason,
|
|
1781
2623
|
updatedAt: autoContinueRun.updatedAt,
|
|
1782
2624
|
}
|
|
@@ -1794,6 +2636,44 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1794
2636
|
const workstream = nodeById.get(workstreamId);
|
|
1795
2637
|
if (!workstream || workstream.type !== "workstream")
|
|
1796
2638
|
continue;
|
|
2639
|
+
const lane = getAutoContinueLaneForWorkstream(initiativeId, workstream.id);
|
|
2640
|
+
if (!lane &&
|
|
2641
|
+
!(typeof run.activeRunId === "string" && run.activeRunId.trim().length > 0)) {
|
|
2642
|
+
continue;
|
|
2643
|
+
}
|
|
2644
|
+
const laneState = lane?.state ?? null;
|
|
2645
|
+
const activeRunIds = Array.isArray(run.activeSliceRunIds)
|
|
2646
|
+
? run.activeSliceRunIds
|
|
2647
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2648
|
+
.map((id) => id.trim())
|
|
2649
|
+
: [];
|
|
2650
|
+
const activeTaskId = lane?.activeTaskIds?.[0] ?? run.activeTaskId;
|
|
2651
|
+
const activeTaskNode = activeTaskId ? nodeById.get(activeTaskId) ?? null : null;
|
|
2652
|
+
const executionPolicy = (activeTaskNode ? deriveExecutionPolicy(activeTaskNode, workstream) : null) ??
|
|
2653
|
+
resolveExecutionPolicyFromActiveRuns(activeRunIds, workstream.id);
|
|
2654
|
+
const sliceScope = normalizeSliceScope(run.scope ?? null) ?? "task";
|
|
2655
|
+
const sliceTaskIds = lane?.activeTaskIds?.length
|
|
2656
|
+
? lane.activeTaskIds
|
|
2657
|
+
: activeTaskId
|
|
2658
|
+
? [activeTaskId]
|
|
2659
|
+
: [];
|
|
2660
|
+
const queueState = laneState === "running"
|
|
2661
|
+
? "running"
|
|
2662
|
+
: laneState === "blocked" ||
|
|
2663
|
+
laneState === "waiting_dependency" ||
|
|
2664
|
+
laneState === "rate_limited"
|
|
2665
|
+
? "blocked"
|
|
2666
|
+
: "queued";
|
|
2667
|
+
if (isSuppressed(initiativeId, workstream.id) && queueState !== "running") {
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
const runRunnerAgents = [];
|
|
2671
|
+
const runRunnerSeen = new Set();
|
|
2672
|
+
pushRunnerAgent(runRunnerAgents, runRunnerSeen, {
|
|
2673
|
+
id: run.agentId,
|
|
2674
|
+
name: agentCatalogById.get(run.agentId)?.name ?? run.agentId,
|
|
2675
|
+
});
|
|
2676
|
+
const runPrimaryRunner = runRunnerAgents[0] ?? null;
|
|
1797
2677
|
itemsForInitiative.push({
|
|
1798
2678
|
initiativeId,
|
|
1799
2679
|
initiativeTitle,
|
|
@@ -1801,23 +2681,52 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1801
2681
|
workstreamId: workstream.id,
|
|
1802
2682
|
workstreamTitle: workstream.title,
|
|
1803
2683
|
workstreamStatus: workstream.status,
|
|
1804
|
-
nextTaskId:
|
|
1805
|
-
nextTaskTitle:
|
|
1806
|
-
? nodeById.get(
|
|
2684
|
+
nextTaskId: activeTaskId ?? null,
|
|
2685
|
+
nextTaskTitle: activeTaskId
|
|
2686
|
+
? nodeById.get(activeTaskId)?.title ?? null
|
|
1807
2687
|
: null,
|
|
1808
2688
|
nextTaskPriority: null,
|
|
1809
2689
|
nextTaskDueAt: null,
|
|
1810
|
-
runnerAgentId:
|
|
1811
|
-
runnerAgentName:
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2690
|
+
runnerAgentId: runPrimaryRunner?.id ?? "unassigned",
|
|
2691
|
+
runnerAgentName: runPrimaryRunner?.name ?? "Unassigned",
|
|
2692
|
+
runnerAgents: runRunnerAgents,
|
|
2693
|
+
runnerSource: runPrimaryRunner ? "inferred" : "fallback",
|
|
2694
|
+
queueState,
|
|
2695
|
+
blockReason: queueState === "blocked"
|
|
2696
|
+
? lane?.blockedReason ?? "Blocked"
|
|
2697
|
+
: null,
|
|
1815
2698
|
isPinned: Boolean(pinnedByKey.get(`${initiativeId}:${workstream.id}`)),
|
|
1816
2699
|
pinnedRank: pinnedRankByKey.get(`${initiativeId}:${workstream.id}`) ?? null,
|
|
2700
|
+
sliceScope,
|
|
2701
|
+
sliceTaskIds,
|
|
2702
|
+
sliceTaskCount: sliceTaskIds.length,
|
|
2703
|
+
sliceMilestoneId: sliceScope === "milestone"
|
|
2704
|
+
? activeTaskNode?.milestoneId ?? null
|
|
2705
|
+
: null,
|
|
2706
|
+
executionPolicy,
|
|
1817
2707
|
autoContinue: {
|
|
1818
2708
|
status: run.status,
|
|
1819
2709
|
activeTaskId: run.activeTaskId,
|
|
1820
2710
|
activeRunId: run.activeRunId,
|
|
2711
|
+
activeTaskIds: Array.isArray(run.activeTaskIds)
|
|
2712
|
+
? run.activeTaskIds
|
|
2713
|
+
: [],
|
|
2714
|
+
activeRunIds: Array.isArray(run.activeSliceRunIds)
|
|
2715
|
+
? run.activeSliceRunIds
|
|
2716
|
+
: [],
|
|
2717
|
+
laneState,
|
|
2718
|
+
laneBlockedReason: lane?.blockedReason ?? null,
|
|
2719
|
+
laneWaitingOnWorkstreamIds: Array.isArray(lane?.waitingOnWorkstreamIds)
|
|
2720
|
+
? lane.waitingOnWorkstreamIds
|
|
2721
|
+
: [],
|
|
2722
|
+
laneRetryAt: lane?.retryAt ?? null,
|
|
2723
|
+
maxParallelSlices: typeof run.maxParallelSlices === "number"
|
|
2724
|
+
? run.maxParallelSlices
|
|
2725
|
+
: 1,
|
|
2726
|
+
parallelMode: typeof run.parallelMode === "string" &&
|
|
2727
|
+
run.parallelMode.toLowerCase() === "iwmt"
|
|
2728
|
+
? "iwmt"
|
|
2729
|
+
: "iwmt",
|
|
1821
2730
|
stopReason: run.stopReason,
|
|
1822
2731
|
updatedAt: run.updatedAt,
|
|
1823
2732
|
},
|
|
@@ -1839,7 +2748,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1839
2748
|
return { items, degraded };
|
|
1840
2749
|
}
|
|
1841
2750
|
async function buildNextUpQueue(input) {
|
|
1842
|
-
const key = nextUpQueueCacheKeyFor(input?.initiativeId?.trim() || null);
|
|
2751
|
+
const key = nextUpQueueCacheKeyFor(input?.initiativeId?.trim() || null, input?.projectId?.trim() || null);
|
|
1843
2752
|
const fresh = readNextUpQueueCache(key, { allowStale: false });
|
|
1844
2753
|
if (fresh)
|
|
1845
2754
|
return fresh;
|
|
@@ -1883,6 +2792,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1883
2792
|
nextUpQueueInFlight.delete(key);
|
|
1884
2793
|
}
|
|
1885
2794
|
}
|
|
2795
|
+
// Prime queue cache shortly after boot so first dashboard paint is not cold.
|
|
2796
|
+
const prewarmNextUpQueue = () => {
|
|
2797
|
+
void buildNextUpQueue({ initiativeId: null, projectId: null }).catch(() => {
|
|
2798
|
+
// best effort prewarm only
|
|
2799
|
+
});
|
|
2800
|
+
};
|
|
2801
|
+
const nextUpPrewarmTimer = setTimeout(prewarmNextUpQueue, 75);
|
|
2802
|
+
nextUpPrewarmTimer.unref?.();
|
|
1886
2803
|
const autoContinueTimer = setInterval(() => {
|
|
1887
2804
|
void tickAllAutoContinue();
|
|
1888
2805
|
}, AUTO_CONTINUE_TICK_MS);
|
|
@@ -1916,6 +2833,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1916
2833
|
formatInitiatives,
|
|
1917
2834
|
getOnboardingState: async () => getOnboardingState(await onboarding.getStatus()),
|
|
1918
2835
|
});
|
|
2836
|
+
registerUsageRoutes(apiRouter, {
|
|
2837
|
+
client,
|
|
2838
|
+
listActivityPage: ({ limit, runId, since, until, cursor }) => listActivityPage({ limit, runId, since, until, cursor }),
|
|
2839
|
+
sendJson,
|
|
2840
|
+
safeErrorMessage,
|
|
2841
|
+
});
|
|
1919
2842
|
registerAgentSuiteRoutes(apiRouter, {
|
|
1920
2843
|
pluginVersion: config.pluginVersion,
|
|
1921
2844
|
telemetryDistinctId,
|
|
@@ -1926,6 +2849,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1926
2849
|
applyOrgxAgentSuitePlan,
|
|
1927
2850
|
generateAgentSuiteOperationId,
|
|
1928
2851
|
updateSkillPackPolicy,
|
|
2852
|
+
rollbackSkillPackPolicy,
|
|
2853
|
+
fetchAgentRuntimeSettings: ({ workspaceId, projectId } = {}) => client.getClientAgentRuntimeSettings({
|
|
2854
|
+
workspaceId: workspaceId ?? projectId ?? null,
|
|
2855
|
+
}),
|
|
2856
|
+
updateAgentRuntimeSettings: (payload) => client.updateClientAgentRuntimeSettings(payload),
|
|
1929
2857
|
posthogCapture,
|
|
1930
2858
|
sendJson,
|
|
1931
2859
|
safeErrorMessage,
|
|
@@ -1944,13 +2872,20 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1944
2872
|
sendJson,
|
|
1945
2873
|
safeErrorMessage,
|
|
1946
2874
|
});
|
|
2875
|
+
registerSentinelsCatalogRoutes(apiRouter, {
|
|
2876
|
+
sendJson,
|
|
2877
|
+
safeErrorMessage,
|
|
2878
|
+
});
|
|
1947
2879
|
registerMissionControlReadRoutes(apiRouter, {
|
|
1948
2880
|
autoContinueRuns,
|
|
1949
2881
|
defaultAutoContinueTokenBudget,
|
|
2882
|
+
defaultAutoContinueMaxParallelSlices,
|
|
1950
2883
|
autoContinueTickMs: AUTO_CONTINUE_TICK_MS,
|
|
1951
2884
|
buildMissionControlGraph: (initiativeId) => buildMissionControlGraph(client, initiativeId),
|
|
1952
2885
|
applyLocalInitiativeOverrideToGraph: (graph) => applyLocalInitiativeOverrideToGraph(graph),
|
|
2886
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
1953
2887
|
buildNextUpQueue,
|
|
2888
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
1954
2889
|
sendJson,
|
|
1955
2890
|
safeErrorMessage,
|
|
1956
2891
|
});
|
|
@@ -1993,12 +2928,127 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1993
2928
|
applyLocalInitiativeOverrides,
|
|
1994
2929
|
formatInitiatives,
|
|
1995
2930
|
getSnapshot,
|
|
2931
|
+
scheduleWorkstreamReassignment: async (input) => {
|
|
2932
|
+
const initiativeId = input.initiativeId.trim();
|
|
2933
|
+
const workstreamId = input.workstreamId.trim();
|
|
2934
|
+
if (!initiativeId || !workstreamId)
|
|
2935
|
+
return null;
|
|
2936
|
+
const normalizedStatus = (input.status ?? "").trim().toLowerCase();
|
|
2937
|
+
const shouldRedispatch = normalizedStatus === "active" ||
|
|
2938
|
+
normalizedStatus === "ready" ||
|
|
2939
|
+
normalizedStatus === "queued" ||
|
|
2940
|
+
normalizedStatus === "running" ||
|
|
2941
|
+
normalizedStatus === "in_progress" ||
|
|
2942
|
+
normalizedStatus === "pending";
|
|
2943
|
+
if (!shouldRedispatch)
|
|
2944
|
+
return null;
|
|
2945
|
+
if (!isDispatchableWorkstreamStatus(normalizedStatus))
|
|
2946
|
+
return null;
|
|
2947
|
+
const liveRun = runningAutoContinueForWorkstream(initiativeId, workstreamId);
|
|
2948
|
+
return await scheduleAutoFixForWorkstream({
|
|
2949
|
+
initiativeId,
|
|
2950
|
+
workstreamId,
|
|
2951
|
+
runId: liveRun?.activeRunId ?? null,
|
|
2952
|
+
event: input.event,
|
|
2953
|
+
requestedByAgentId: "system",
|
|
2954
|
+
requestedByAgentName: "System",
|
|
2955
|
+
graceMs: 5_000,
|
|
2956
|
+
});
|
|
2957
|
+
},
|
|
1996
2958
|
sendJson,
|
|
1997
2959
|
safeErrorMessage,
|
|
1998
2960
|
});
|
|
2961
|
+
const readCachedDecisionRows = () => {
|
|
2962
|
+
const snapshots = [
|
|
2963
|
+
readSnapshotResponseCache("live-snapshot"),
|
|
2964
|
+
readSnapshotResponseCache("dashboard-bundle"),
|
|
2965
|
+
readSnapshotResponseCache("live-snapshot-v2"),
|
|
2966
|
+
];
|
|
2967
|
+
const rows = [];
|
|
2968
|
+
for (const snapshot of snapshots) {
|
|
2969
|
+
if (!snapshot || typeof snapshot !== "object")
|
|
2970
|
+
continue;
|
|
2971
|
+
const decisionsRaw = snapshot.decisions;
|
|
2972
|
+
if (!Array.isArray(decisionsRaw))
|
|
2973
|
+
continue;
|
|
2974
|
+
for (const entry of decisionsRaw) {
|
|
2975
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
2976
|
+
continue;
|
|
2977
|
+
rows.push(entry);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return rows;
|
|
2981
|
+
};
|
|
2982
|
+
const emitDecisionResolvedActivity = async (input) => {
|
|
2983
|
+
const ids = Array.from(new Set(input.ids
|
|
2984
|
+
.filter((id) => typeof id === "string")
|
|
2985
|
+
.map((id) => id.trim())
|
|
2986
|
+
.filter(Boolean)));
|
|
2987
|
+
if (ids.length === 0)
|
|
2988
|
+
return;
|
|
2989
|
+
const decisionById = new Map();
|
|
2990
|
+
for (const row of readCachedDecisionRows()) {
|
|
2991
|
+
const rowId = pickString(row, ["id"])?.trim() ?? "";
|
|
2992
|
+
if (!rowId || decisionById.has(rowId))
|
|
2993
|
+
continue;
|
|
2994
|
+
decisionById.set(rowId, row);
|
|
2995
|
+
}
|
|
2996
|
+
for (const decisionId of ids) {
|
|
2997
|
+
const row = decisionById.get(decisionId) ?? null;
|
|
2998
|
+
const decisionTitle = pickString(row ?? {}, ["title", "summary"]) ??
|
|
2999
|
+
`Decision ${decisionId.slice(0, 8)}`;
|
|
3000
|
+
const scopedInitiativeId = input.initiativeId ??
|
|
3001
|
+
pickString(row ?? {}, ["initiative_id", "initiativeId"]) ??
|
|
3002
|
+
null;
|
|
3003
|
+
const scopedRunId = input.sliceRunId ??
|
|
3004
|
+
pickString(row ?? {}, [
|
|
3005
|
+
"run_id",
|
|
3006
|
+
"runId",
|
|
3007
|
+
"source_run_id",
|
|
3008
|
+
"sourceRunId",
|
|
3009
|
+
"correlation_id",
|
|
3010
|
+
"correlationId",
|
|
3011
|
+
]) ??
|
|
3012
|
+
null;
|
|
3013
|
+
await emitActivitySafe({
|
|
3014
|
+
initiativeId: scopedInitiativeId,
|
|
3015
|
+
runId: scopedRunId ?? undefined,
|
|
3016
|
+
correlationId: scopedRunId ?? undefined,
|
|
3017
|
+
phase: "review",
|
|
3018
|
+
level: "info",
|
|
3019
|
+
message: `Decision ${input.action === "approve" ? "approved" : "rejected"}: ${decisionTitle}`,
|
|
3020
|
+
progressPct: 100,
|
|
3021
|
+
nextStep: input.action === "approve"
|
|
3022
|
+
? "Execution can continue with the approved direction."
|
|
3023
|
+
: "Review the rejected decision and provide revised guidance to continue safely.",
|
|
3024
|
+
metadata: {
|
|
3025
|
+
event: "decision_resolved",
|
|
3026
|
+
action: input.action,
|
|
3027
|
+
resolver: "human",
|
|
3028
|
+
decision_id: decisionId,
|
|
3029
|
+
decision_ids: ids,
|
|
3030
|
+
decision_title: decisionTitle,
|
|
3031
|
+
initiative_id: scopedInitiativeId,
|
|
3032
|
+
workstream_id: pickString(row ?? {}, ["workstream_id", "workstreamId"]) ?? null,
|
|
3033
|
+
source_run_id: scopedRunId,
|
|
3034
|
+
option_id: input.optionId ?? null,
|
|
3035
|
+
note: input.note ?? null,
|
|
3036
|
+
slice_run_id: input.sliceRunId ?? null,
|
|
3037
|
+
},
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
1999
3041
|
registerDecisionActionsRoutes(apiRouter, {
|
|
2000
3042
|
parseJsonRequest,
|
|
2001
|
-
bulkDecideDecisions: (ids, action,
|
|
3043
|
+
bulkDecideDecisions: (ids, action, input) => client.bulkDecideDecisions(ids, action, input),
|
|
3044
|
+
emitDecisionResolvedActivity: async (ids, action, input) => {
|
|
3045
|
+
await emitDecisionResolvedActivity({
|
|
3046
|
+
ids,
|
|
3047
|
+
action,
|
|
3048
|
+
note: input?.note ?? null,
|
|
3049
|
+
optionId: input?.optionId ?? null,
|
|
3050
|
+
});
|
|
3051
|
+
},
|
|
2002
3052
|
sendJson,
|
|
2003
3053
|
safeErrorMessage,
|
|
2004
3054
|
});
|
|
@@ -2009,6 +3059,104 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2009
3059
|
createRunCheckpoint: (runId, input) => client.createRunCheckpoint(runId, input),
|
|
2010
3060
|
restoreRunCheckpoint: (runId, input) => client.restoreRunCheckpoint(runId, input),
|
|
2011
3061
|
runAction: (runId, action, input) => client.runAction(runId, action, input),
|
|
3062
|
+
markRunCompleted: async (runId, input) => {
|
|
3063
|
+
const normalizedRunId = runId.trim();
|
|
3064
|
+
if (!normalizedRunId) {
|
|
3065
|
+
throw new Error("runId is required");
|
|
3066
|
+
}
|
|
3067
|
+
const nowIso = new Date().toISOString();
|
|
3068
|
+
const reason = input.reason?.trim() || null;
|
|
3069
|
+
const message = reason
|
|
3070
|
+
? `Marked completed from dashboard (${reason}).`
|
|
3071
|
+
: "Marked completed from dashboard.";
|
|
3072
|
+
const existingRun = getAgentRun(normalizedRunId);
|
|
3073
|
+
// ── Try OrgX-side completion first (for remote sessions) ────────────
|
|
3074
|
+
let remoteOk = false;
|
|
3075
|
+
try {
|
|
3076
|
+
await client.updateEntity("run", normalizedRunId, {
|
|
3077
|
+
status: "completed",
|
|
3078
|
+
phase: "completed",
|
|
3079
|
+
});
|
|
3080
|
+
remoteOk = true;
|
|
3081
|
+
}
|
|
3082
|
+
catch {
|
|
3083
|
+
// OrgX may not support updating runs directly — fall through to local
|
|
3084
|
+
}
|
|
3085
|
+
// ── Local operations (defensive — partial failures don't block) ─────
|
|
3086
|
+
let runtimeRecord = null;
|
|
3087
|
+
try {
|
|
3088
|
+
runtimeRecord = upsertRuntimeInstanceFromHook({
|
|
3089
|
+
source_client: "api",
|
|
3090
|
+
event: "session_stop",
|
|
3091
|
+
run_id: normalizedRunId,
|
|
3092
|
+
correlation_id: normalizedRunId,
|
|
3093
|
+
initiative_id: existingRun?.initiativeId ?? null,
|
|
3094
|
+
workstream_id: existingRun?.workstreamId ?? null,
|
|
3095
|
+
task_id: existingRun?.taskId ?? null,
|
|
3096
|
+
agent_id: existingRun?.agentId ?? null,
|
|
3097
|
+
phase: "completed",
|
|
3098
|
+
message,
|
|
3099
|
+
timestamp: nowIso,
|
|
3100
|
+
metadata: {
|
|
3101
|
+
source: "dashboard_manual_complete",
|
|
3102
|
+
reason,
|
|
3103
|
+
},
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
catch (err) {
|
|
3107
|
+
console.error(`[markRunCompleted] upsertRuntime failed for ${normalizedRunId}:`, err);
|
|
3108
|
+
}
|
|
3109
|
+
try {
|
|
3110
|
+
markAgentRunStopped(normalizedRunId);
|
|
3111
|
+
}
|
|
3112
|
+
catch (err) {
|
|
3113
|
+
console.error(`[markRunCompleted] markAgentRunStopped failed for ${normalizedRunId}:`, err);
|
|
3114
|
+
}
|
|
3115
|
+
if (runtimeRecord) {
|
|
3116
|
+
broadcastRuntimeSse("runtime.updated", runtimeRecord);
|
|
3117
|
+
}
|
|
3118
|
+
clearSnapshotResponseCache();
|
|
3119
|
+
try {
|
|
3120
|
+
appendActivityItems([
|
|
3121
|
+
{
|
|
3122
|
+
id: randomUUID(),
|
|
3123
|
+
type: "run_completed",
|
|
3124
|
+
title: message,
|
|
3125
|
+
description: reason,
|
|
3126
|
+
agentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3127
|
+
agentName: runtimeRecord?.agentName ?? null,
|
|
3128
|
+
requesterAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3129
|
+
requesterAgentName: runtimeRecord?.agentName ?? null,
|
|
3130
|
+
executorAgentId: runtimeRecord?.agentId ?? existingRun?.agentId ?? null,
|
|
3131
|
+
executorAgentName: runtimeRecord?.agentName ?? null,
|
|
3132
|
+
runId: normalizedRunId,
|
|
3133
|
+
initiativeId: runtimeRecord?.initiativeId ?? existingRun?.initiativeId ?? null,
|
|
3134
|
+
timestamp: nowIso,
|
|
3135
|
+
phase: "completed",
|
|
3136
|
+
state: "done",
|
|
3137
|
+
summary: message,
|
|
3138
|
+
metadata: {
|
|
3139
|
+
source: "dashboard_manual_complete",
|
|
3140
|
+
reason,
|
|
3141
|
+
remoteOk,
|
|
3142
|
+
event: "dashboard_run_mark_completed",
|
|
3143
|
+
},
|
|
3144
|
+
},
|
|
3145
|
+
]);
|
|
3146
|
+
}
|
|
3147
|
+
catch (err) {
|
|
3148
|
+
console.error(`[markRunCompleted] appendActivity failed for ${normalizedRunId}:`, err);
|
|
3149
|
+
}
|
|
3150
|
+
return {
|
|
3151
|
+
ok: true,
|
|
3152
|
+
data: {
|
|
3153
|
+
runId: normalizedRunId,
|
|
3154
|
+
action: "complete",
|
|
3155
|
+
status: "completed",
|
|
3156
|
+
remoteOk,
|
|
3157
|
+
},
|
|
3158
|
+
};
|
|
3159
|
+
},
|
|
2012
3160
|
sendJson,
|
|
2013
3161
|
safeErrorMessage,
|
|
2014
3162
|
});
|
|
@@ -2032,6 +3180,20 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2032
3180
|
sendJson,
|
|
2033
3181
|
safeErrorMessage,
|
|
2034
3182
|
});
|
|
3183
|
+
registerChatRoutes(apiRouter, {
|
|
3184
|
+
parseJsonRequest,
|
|
3185
|
+
pickString,
|
|
3186
|
+
parsePositiveInt,
|
|
3187
|
+
emitActivitySafe,
|
|
3188
|
+
sendJson,
|
|
3189
|
+
safeErrorMessage,
|
|
3190
|
+
});
|
|
3191
|
+
registerRealtimeOrchestratorRoutes(apiRouter, {
|
|
3192
|
+
parseJsonRequest,
|
|
3193
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
3194
|
+
sendJson,
|
|
3195
|
+
safeErrorMessage,
|
|
3196
|
+
});
|
|
2035
3197
|
registerMissionControlActionsRoutes(apiRouter, {
|
|
2036
3198
|
parseJsonRequest,
|
|
2037
3199
|
pickString,
|
|
@@ -2049,11 +3211,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2049
3211
|
stopAutoContinueRun,
|
|
2050
3212
|
updateInitiativeAutoContinueState,
|
|
2051
3213
|
tickAllAutoContinue,
|
|
3214
|
+
scheduleAutoFixForWorkstream,
|
|
2052
3215
|
upsertNextUpQueuePin,
|
|
2053
3216
|
removeNextUpQueuePin,
|
|
3217
|
+
suppressNextUpQueueItem,
|
|
2054
3218
|
setNextUpQueuePinOrder,
|
|
3219
|
+
clearNextUpQueueCache,
|
|
2055
3220
|
resolveAutoAssignments,
|
|
3221
|
+
buildMissionControlGraph: (initiativeId) => buildMissionControlGraph(client, initiativeId),
|
|
3222
|
+
applyLocalInitiativeOverrideToGraph: (graph) => applyLocalInitiativeOverrideToGraph(graph),
|
|
2056
3223
|
client,
|
|
3224
|
+
rawRequest: (requestMethod, requestPath, body) => client.rawRequest(requestMethod, requestPath, body),
|
|
2057
3225
|
sendJson,
|
|
2058
3226
|
safeErrorMessage,
|
|
2059
3227
|
});
|
|
@@ -2095,10 +3263,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2095
3263
|
parseJsonRequest,
|
|
2096
3264
|
pickString,
|
|
2097
3265
|
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 }),
|
|
3266
|
+
getLiveAgents: ({ initiative, projectId, includeIdle }) => client.getLiveAgents({ initiative, projectId, includeIdle }),
|
|
3267
|
+
getLiveInitiatives: ({ id, projectId, limit, offset }) => client.getLiveInitiatives({ id, projectId, limit, offset }),
|
|
3268
|
+
getLiveDecisions: ({ status, projectId, limit }) => client.getLiveDecisions({ status, projectId, limit }),
|
|
2101
3269
|
getHandoffs: () => client.getHandoffs(),
|
|
3270
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2102
3271
|
loadLocalOpenClawSnapshot,
|
|
2103
3272
|
toLocalLiveAgents,
|
|
2104
3273
|
toLocalLiveInitiatives,
|
|
@@ -2107,9 +3276,15 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2107
3276
|
sendJson,
|
|
2108
3277
|
safeErrorMessage,
|
|
2109
3278
|
});
|
|
3279
|
+
registerLiveTerminalRoutes(apiRouter, {
|
|
3280
|
+
parseJsonRequest,
|
|
3281
|
+
sendJson,
|
|
3282
|
+
safeErrorMessage,
|
|
3283
|
+
});
|
|
2110
3284
|
registerLiveLegacyRoutes(apiRouter, {
|
|
2111
|
-
getLiveSessions: ({ initiative, limit }) => client.getLiveSessions({ initiative, limit }),
|
|
2112
|
-
getLiveActivity: ({ run, since, limit }) => client.getLiveActivity({ run, since, limit }),
|
|
3285
|
+
getLiveSessions: ({ initiative, projectId, limit }) => client.getLiveSessions({ initiative, projectId, limit }),
|
|
3286
|
+
getLiveActivity: ({ run, since, projectId, limit }) => client.getLiveActivity({ run, since, projectId, limit }),
|
|
3287
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2113
3288
|
listRuntimeInstances,
|
|
2114
3289
|
injectRuntimeInstancesAsSessions,
|
|
2115
3290
|
enrichSessionsWithRuntime,
|
|
@@ -2171,11 +3346,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2171
3346
|
toLocalSessionTree,
|
|
2172
3347
|
toLocalLiveActivity,
|
|
2173
3348
|
toLocalLiveAgents,
|
|
2174
|
-
getLiveSessions: ({ initiative, limit }) => client.getLiveSessions({ initiative, limit }),
|
|
2175
|
-
getLiveActivity: ({ run, since, limit }) => client.getLiveActivity({ run, since, limit }),
|
|
3349
|
+
getLiveSessions: ({ initiative, projectId, limit }) => client.getLiveSessions({ initiative, projectId, limit }),
|
|
3350
|
+
getLiveActivity: ({ run, since, projectId, limit }) => client.getLiveActivity({ run, since, projectId, limit }),
|
|
2176
3351
|
getHandoffs: () => client.getHandoffs(),
|
|
2177
|
-
getLiveDecisions: ({ status, limit }) => client.getLiveDecisions({ status, limit }),
|
|
2178
|
-
getLiveAgents: ({ initiative, includeIdle }) => client.getLiveAgents({ initiative, includeIdle }),
|
|
3352
|
+
getLiveDecisions: ({ status, projectId, limit }) => client.getLiveDecisions({ status, projectId, limit }),
|
|
3353
|
+
getLiveAgents: ({ initiative, projectId, includeIdle }) => client.getLiveAgents({ initiative, projectId, includeIdle }),
|
|
3354
|
+
listInitiativeIdsForProject: ({ projectId }) => listInitiativeIdsForProject({ projectId }),
|
|
2179
3355
|
mapDecisionEntity: (entry) => mapDecisionEntity(entry),
|
|
2180
3356
|
applyAgentContextsToSessionTree,
|
|
2181
3357
|
applyAgentContextsToActivity,
|
|
@@ -2196,6 +3372,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2196
3372
|
lastSnapshotActivityFingerprint = state.lastFingerprint;
|
|
2197
3373
|
lastSnapshotActivityPersistAt = state.lastPersistAt;
|
|
2198
3374
|
},
|
|
3375
|
+
parseJsonRequest,
|
|
3376
|
+
buildNextUpQueue: ({ initiativeId, projectId }) => buildNextUpQueue({ initiativeId, projectId }),
|
|
3377
|
+
bulkDecideDecisions: (ids, action, input) => client.bulkDecideDecisions(ids, action, input),
|
|
3378
|
+
emitDecisionResolvedActivity: async (input) => {
|
|
3379
|
+
await emitDecisionResolvedActivity(input);
|
|
3380
|
+
},
|
|
3381
|
+
runAction: (runId, action, input) => client.runAction(runId, action, input),
|
|
3382
|
+
listChatThreads: ({ commandCenterId, initiativeId, limit, offset }) => listChatThreads({ commandCenterId, initiativeId, limit, offset }),
|
|
2199
3383
|
sendJson,
|
|
2200
3384
|
});
|
|
2201
3385
|
registerRuntimeHookRoutes(apiRouter, {
|
|
@@ -2232,12 +3416,81 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2232
3416
|
sendJson,
|
|
2233
3417
|
safeErrorMessage,
|
|
2234
3418
|
});
|
|
3419
|
+
registerLiveTerminalRoutes(apiRouter, {
|
|
3420
|
+
parseJsonRequest,
|
|
3421
|
+
sendJson,
|
|
3422
|
+
safeErrorMessage,
|
|
3423
|
+
});
|
|
3424
|
+
registerLiveTriageRoutes(apiRouter, {
|
|
3425
|
+
parseJsonRequest,
|
|
3426
|
+
sendJson,
|
|
3427
|
+
getDecisions: (workspaceId) => {
|
|
3428
|
+
// Return cached decisions from latest snapshot, or empty array
|
|
3429
|
+
const normalizedWorkspaceId = (workspaceId ?? "").trim();
|
|
3430
|
+
const scopedKeys = normalizedWorkspaceId
|
|
3431
|
+
? [
|
|
3432
|
+
`live-snapshot:${normalizedWorkspaceId}`,
|
|
3433
|
+
`live-snapshot-v2:${normalizedWorkspaceId}`,
|
|
3434
|
+
`dashboard-bundle:${normalizedWorkspaceId}`,
|
|
3435
|
+
]
|
|
3436
|
+
: [];
|
|
3437
|
+
const fallbackKeys = ["live-snapshot", "dashboard-bundle", "live-snapshot-v2"];
|
|
3438
|
+
const keys = [...scopedKeys, ...fallbackKeys];
|
|
3439
|
+
try {
|
|
3440
|
+
for (const key of keys) {
|
|
3441
|
+
const cached = readSnapshotResponseCache(key);
|
|
3442
|
+
if (!cached || typeof cached !== "object" || !("decisions" in cached))
|
|
3443
|
+
continue;
|
|
3444
|
+
const decisions = cached.decisions;
|
|
3445
|
+
if (Array.isArray(decisions))
|
|
3446
|
+
return decisions;
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
catch {
|
|
3450
|
+
// best effort
|
|
3451
|
+
}
|
|
3452
|
+
return [];
|
|
3453
|
+
},
|
|
3454
|
+
getBlockerEvents: () => {
|
|
3455
|
+
// Extract blocker events from recent activity
|
|
3456
|
+
// In future, this will read from a dedicated blocker store
|
|
3457
|
+
return [];
|
|
3458
|
+
},
|
|
3459
|
+
resolveDecisionAction: async (decisionId, action, note, optionId) => {
|
|
3460
|
+
try {
|
|
3461
|
+
await client.bulkDecideDecisions([decisionId], action, { note: note ?? undefined, optionId: optionId ?? undefined });
|
|
3462
|
+
return { ok: true };
|
|
3463
|
+
}
|
|
3464
|
+
catch (err) {
|
|
3465
|
+
return {
|
|
3466
|
+
ok: false,
|
|
3467
|
+
error: err instanceof Error ? err.message : "Decision action failed",
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
},
|
|
3471
|
+
emitDecisionResolvedActivity: async (input) => {
|
|
3472
|
+
await emitDecisionResolvedActivity(input);
|
|
3473
|
+
},
|
|
3474
|
+
});
|
|
2235
3475
|
return async function handler(req, res) {
|
|
2236
3476
|
const method = (req.method ?? "GET").toUpperCase();
|
|
2237
3477
|
const rawUrl = req.url ?? "/";
|
|
2238
3478
|
const [path, queryString] = rawUrl.split("?", 2);
|
|
2239
3479
|
const url = path;
|
|
2240
3480
|
const searchParams = new URLSearchParams(queryString ?? "");
|
|
3481
|
+
// Legacy deep-link compatibility:
|
|
3482
|
+
// Older launch paths still point at /workspace-hub. Route those into
|
|
3483
|
+
// the current dashboard entrypoint while preserving query params.
|
|
3484
|
+
if (url === "/workspace-hub" || url === "/workspace-hub/") {
|
|
3485
|
+
const suffix = queryString && queryString.trim().length > 0 ? `?${queryString}` : "";
|
|
3486
|
+
res.writeHead(302, {
|
|
3487
|
+
Location: `/orgx/live${suffix}`,
|
|
3488
|
+
...SECURITY_HEADERS,
|
|
3489
|
+
...CORS_HEADERS,
|
|
3490
|
+
});
|
|
3491
|
+
res.end();
|
|
3492
|
+
return true;
|
|
3493
|
+
}
|
|
2241
3494
|
// Only handle /orgx paths — return false for everything else
|
|
2242
3495
|
if (!url.startsWith("/orgx")) {
|
|
2243
3496
|
return false;
|
|
@@ -2317,9 +3570,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2317
3570
|
const cacheControl = assetExt === ".js" || assetExt === ".css"
|
|
2318
3571
|
? "no-cache"
|
|
2319
3572
|
: "public, max-age=31536000, immutable";
|
|
2320
|
-
sendFile(res, assetPath, cacheControl);
|
|
3573
|
+
sendFile(req, res, assetPath, cacheControl);
|
|
2321
3574
|
}
|
|
2322
3575
|
else {
|
|
3576
|
+
if (/^assets\/[A-Za-z0-9_-]+\.js$/i.test(subPath)) {
|
|
3577
|
+
sendStaleChunkRecovery(res);
|
|
3578
|
+
return true;
|
|
3579
|
+
}
|
|
2323
3580
|
send404(res);
|
|
2324
3581
|
}
|
|
2325
3582
|
return true;
|
|
@@ -2328,12 +3585,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2328
3585
|
if (subPath) {
|
|
2329
3586
|
const filePath = resolveSafeDistPath(subPath);
|
|
2330
3587
|
if (filePath && existsSync(filePath)) {
|
|
2331
|
-
sendFile(res, filePath, "no-cache");
|
|
3588
|
+
sendFile(req, res, filePath, "no-cache");
|
|
2332
3589
|
return true;
|
|
2333
3590
|
}
|
|
2334
3591
|
}
|
|
2335
3592
|
// SPA fallback: serve index.html for all other routes under /orgx/live
|
|
2336
|
-
sendIndexHtml(res);
|
|
3593
|
+
sendIndexHtml(req, res);
|
|
2337
3594
|
return true;
|
|
2338
3595
|
}
|
|
2339
3596
|
// Catch-all for /orgx but not /orgx/live or /orgx/api
|