@useorgx/openclaw-plugin 0.3.2 → 0.4.1
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/dashboard/dist/assets/BqukHQH-.js +8 -0
- package/dashboard/dist/assets/CE5pVdev.js +9 -0
- package/dashboard/dist/assets/Cpr7n8fE.js +1 -0
- package/dashboard/dist/assets/Nip3CrNC.js +1 -0
- package/dashboard/dist/assets/TN5wE36J.js +1 -0
- package/dashboard/dist/assets/X6IcjS74.js +212 -0
- package/dashboard/dist/assets/jyFhCND-.css +1 -0
- package/dashboard/dist/index.html +9 -6
- package/dist/adapters/outbox.d.ts +0 -1
- package/dist/adapters/outbox.js +0 -1
- package/dist/agent-context-store.d.ts +0 -1
- package/dist/agent-context-store.js +0 -1
- package/dist/agent-run-store.d.ts +0 -1
- package/dist/agent-run-store.js +0 -1
- package/dist/api.d.ts +0 -1
- package/dist/api.js +0 -1
- package/dist/auth-store.d.ts +0 -1
- package/dist/auth-store.js +0 -1
- package/dist/byok-store.d.ts +0 -1
- package/dist/byok-store.js +0 -1
- package/dist/contracts/client.d.ts +0 -1
- package/dist/contracts/client.js +0 -1
- package/dist/contracts/types.d.ts +33 -1
- package/dist/contracts/types.js +0 -1
- package/dist/dashboard-api.d.ts +0 -1
- package/dist/dashboard-api.js +0 -1
- package/dist/fs-utils.d.ts +0 -1
- package/dist/fs-utils.js +0 -1
- package/dist/http-handler.d.ts +0 -1
- package/dist/http-handler.js +1331 -111
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/local-openclaw.d.ts +0 -1
- package/dist/local-openclaw.js +0 -1
- package/dist/outbox.d.ts +0 -1
- package/dist/outbox.js +0 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -1
- package/dist/reporting/outbox-replay.d.ts +0 -1
- package/dist/reporting/outbox-replay.js +0 -1
- package/dist/reporting/rollups.d.ts +0 -1
- package/dist/reporting/rollups.js +0 -1
- package/dist/runtime-instance-store.d.ts +62 -0
- package/dist/runtime-instance-store.js +362 -0
- package/dist/snapshot-store.d.ts +0 -1
- package/dist/snapshot-store.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +2 -2
- package/dashboard/dist/assets/MissionControlView-CthHdl6R.js +0 -1
- package/dashboard/dist/assets/SessionInspector-Dq0Z5WMo.js +0 -1
- package/dashboard/dist/assets/index-CoLgC4zE.js +0 -11
- package/dashboard/dist/assets/index-jfEYE0kO.css +0 -1
- package/dashboard/dist/assets/motion-CVDprFZg.js +0 -9
- package/dashboard/dist/assets/react-vendor-C2t2w4r2.js +0 -32
- package/dashboard/dist/assets/vendor-C-AHK0Ly.js +0 -9
- package/dist/adapters/outbox.d.ts.map +0 -1
- package/dist/adapters/outbox.js.map +0 -1
- package/dist/agent-context-store.d.ts.map +0 -1
- package/dist/agent-context-store.js.map +0 -1
- package/dist/agent-run-store.d.ts.map +0 -1
- package/dist/agent-run-store.js.map +0 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/auth-store.d.ts.map +0 -1
- package/dist/auth-store.js.map +0 -1
- package/dist/byok-store.d.ts.map +0 -1
- package/dist/byok-store.js.map +0 -1
- package/dist/contracts/client.d.ts.map +0 -1
- package/dist/contracts/client.js.map +0 -1
- package/dist/contracts/types.d.ts.map +0 -1
- package/dist/contracts/types.js.map +0 -1
- package/dist/dashboard-api.d.ts.map +0 -1
- package/dist/dashboard-api.js.map +0 -1
- package/dist/fs-utils.d.ts.map +0 -1
- package/dist/fs-utils.js.map +0 -1
- package/dist/http-handler.d.ts.map +0 -1
- package/dist/http-handler.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/local-openclaw.d.ts.map +0 -1
- package/dist/local-openclaw.js.map +0 -1
- package/dist/mcp-apps/orgx-live.html +0 -690
- package/dist/outbox.d.ts.map +0 -1
- package/dist/outbox.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/reporting/outbox-replay.d.ts.map +0 -1
- package/dist/reporting/outbox-replay.js.map +0 -1
- package/dist/reporting/rollups.d.ts.map +0 -1
- package/dist/reporting/rollups.js.map +0 -1
- package/dist/snapshot-store.d.ts.map +0 -1
- package/dist/snapshot-store.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dashboard/dist/assets/{tanstack-C-KIc3Wc.js → C-KIc3Wc.js} +0 -0
- /package/dashboard/dist/assets/{orgx-logo-Fm0FhtnV.png → Fm0FhtnV.png} +0 -0
package/dist/http-handler.js
CHANGED
|
@@ -23,11 +23,13 @@ import { spawn } from "node:child_process";
|
|
|
23
23
|
import { createHash, randomUUID } from "node:crypto";
|
|
24
24
|
import { formatStatus, formatAgents, formatActivity, formatInitiatives, getOnboardingState, } from "./dashboard-api.js";
|
|
25
25
|
import { loadLocalOpenClawSnapshot, loadLocalTurnDetail, toLocalLiveActivity, toLocalLiveAgents, toLocalLiveInitiatives, toLocalSessionTree, } from "./local-openclaw.js";
|
|
26
|
+
import { appendToOutbox } from "./outbox.js";
|
|
26
27
|
import { defaultOutboxAdapter } from "./adapters/outbox.js";
|
|
27
28
|
import { readAgentContexts, upsertAgentContext } from "./agent-context-store.js";
|
|
28
29
|
import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from "./agent-run-store.js";
|
|
29
30
|
import { readByokKeys, writeByokKeys } from "./byok-store.js";
|
|
30
31
|
import { computeMilestoneRollup, computeWorkstreamRollup, } from "./reporting/rollups.js";
|
|
32
|
+
import { listRuntimeInstances, resolveRuntimeHookToken, upsertRuntimeInstanceFromHook, } from "./runtime-instance-store.js";
|
|
31
33
|
// =============================================================================
|
|
32
34
|
// Helpers
|
|
33
35
|
// =============================================================================
|
|
@@ -38,6 +40,10 @@ function safeErrorMessage(err) {
|
|
|
38
40
|
return err;
|
|
39
41
|
return "Unexpected error";
|
|
40
42
|
}
|
|
43
|
+
function isUnauthorizedOrgxError(err) {
|
|
44
|
+
const message = safeErrorMessage(err).toLowerCase();
|
|
45
|
+
return message.includes("401") || message.includes("unauthorized");
|
|
46
|
+
}
|
|
41
47
|
function isUserScopedApiKey(apiKey) {
|
|
42
48
|
return apiKey.trim().toLowerCase().startsWith("oxk_");
|
|
43
49
|
}
|
|
@@ -477,6 +483,110 @@ function mergeActivities(base, extra, limit) {
|
|
|
477
483
|
}
|
|
478
484
|
return deduped;
|
|
479
485
|
}
|
|
486
|
+
function normalizeRuntimeSourceForReporting(value) {
|
|
487
|
+
if (value === "codex")
|
|
488
|
+
return "codex";
|
|
489
|
+
if (value === "claude-code")
|
|
490
|
+
return "claude-code";
|
|
491
|
+
if (value === "api")
|
|
492
|
+
return "api";
|
|
493
|
+
return "openclaw";
|
|
494
|
+
}
|
|
495
|
+
function normalizeHookPhase(value) {
|
|
496
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
497
|
+
if (normalized === "intent")
|
|
498
|
+
return "intent";
|
|
499
|
+
if (normalized === "execution")
|
|
500
|
+
return "execution";
|
|
501
|
+
if (normalized === "blocked")
|
|
502
|
+
return "blocked";
|
|
503
|
+
if (normalized === "review")
|
|
504
|
+
return "review";
|
|
505
|
+
if (normalized === "handoff")
|
|
506
|
+
return "handoff";
|
|
507
|
+
if (normalized === "completed")
|
|
508
|
+
return "completed";
|
|
509
|
+
return "execution";
|
|
510
|
+
}
|
|
511
|
+
function normalizeRuntimeSource(value) {
|
|
512
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
513
|
+
if (normalized === "openclaw")
|
|
514
|
+
return "openclaw";
|
|
515
|
+
if (normalized === "codex")
|
|
516
|
+
return "codex";
|
|
517
|
+
if (normalized === "claude-code")
|
|
518
|
+
return "claude-code";
|
|
519
|
+
if (normalized === "api")
|
|
520
|
+
return "api";
|
|
521
|
+
return "unknown";
|
|
522
|
+
}
|
|
523
|
+
function runtimeMatchMaps(instances) {
|
|
524
|
+
const byRunId = new Map();
|
|
525
|
+
const byAgentInitiative = new Map();
|
|
526
|
+
for (const instance of instances) {
|
|
527
|
+
if (instance.runId && !byRunId.has(instance.runId)) {
|
|
528
|
+
byRunId.set(instance.runId, instance);
|
|
529
|
+
}
|
|
530
|
+
const agentId = instance.agentId?.trim() ?? "";
|
|
531
|
+
const initiativeId = instance.initiativeId?.trim() ?? "";
|
|
532
|
+
if (!agentId || !initiativeId)
|
|
533
|
+
continue;
|
|
534
|
+
const key = `${agentId}:${initiativeId}`;
|
|
535
|
+
if (!byAgentInitiative.has(key)) {
|
|
536
|
+
byAgentInitiative.set(key, instance);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { byRunId, byAgentInitiative };
|
|
540
|
+
}
|
|
541
|
+
function enrichSessionsWithRuntime(input, instances) {
|
|
542
|
+
if (!Array.isArray(input.nodes) || input.nodes.length === 0)
|
|
543
|
+
return input;
|
|
544
|
+
if (instances.length === 0)
|
|
545
|
+
return input;
|
|
546
|
+
const { byRunId, byAgentInitiative } = runtimeMatchMaps(instances);
|
|
547
|
+
const nodes = input.nodes.map((node) => {
|
|
548
|
+
const byRun = node.runId ? byRunId.get(node.runId) ?? null : null;
|
|
549
|
+
const byAgent = !byRun && node.agentId && node.initiativeId
|
|
550
|
+
? byAgentInitiative.get(`${node.agentId}:${node.initiativeId}`) ?? null
|
|
551
|
+
: null;
|
|
552
|
+
const match = byRun ?? byAgent;
|
|
553
|
+
if (!match)
|
|
554
|
+
return node;
|
|
555
|
+
return {
|
|
556
|
+
...node,
|
|
557
|
+
runtimeClient: normalizeRuntimeSource(match.sourceClient),
|
|
558
|
+
runtimeLabel: match.displayName,
|
|
559
|
+
runtimeProvider: match.providerLogo,
|
|
560
|
+
instanceId: match.id,
|
|
561
|
+
lastHeartbeatAt: match.lastHeartbeatAt ?? null,
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
return { ...input, nodes };
|
|
565
|
+
}
|
|
566
|
+
function enrichActivityWithRuntime(input, instances) {
|
|
567
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
568
|
+
return [];
|
|
569
|
+
if (instances.length === 0)
|
|
570
|
+
return input;
|
|
571
|
+
const { byRunId, byAgentInitiative } = runtimeMatchMaps(instances);
|
|
572
|
+
return input.map((item) => {
|
|
573
|
+
const byRun = item.runId ? byRunId.get(item.runId) ?? null : null;
|
|
574
|
+
const byAgent = !byRun && item.agentId && item.initiativeId
|
|
575
|
+
? byAgentInitiative.get(`${item.agentId}:${item.initiativeId}`) ?? null
|
|
576
|
+
: null;
|
|
577
|
+
const match = byRun ?? byAgent;
|
|
578
|
+
if (!match)
|
|
579
|
+
return item;
|
|
580
|
+
return {
|
|
581
|
+
...item,
|
|
582
|
+
runtimeClient: normalizeRuntimeSource(match.sourceClient),
|
|
583
|
+
runtimeLabel: match.displayName,
|
|
584
|
+
runtimeProvider: match.providerLogo,
|
|
585
|
+
instanceId: match.id,
|
|
586
|
+
lastHeartbeatAt: match.lastHeartbeatAt ?? null,
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
}
|
|
480
590
|
const ACTIVITY_HEADLINE_TIMEOUT_MS = 4_000;
|
|
481
591
|
const ACTIVITY_HEADLINE_CACHE_TTL_MS = 12 * 60 * 60_000;
|
|
482
592
|
const ACTIVITY_HEADLINE_CACHE_MAX = 1_000;
|
|
@@ -695,14 +805,33 @@ function contentType(filePath) {
|
|
|
695
805
|
// =============================================================================
|
|
696
806
|
const CORS_HEADERS = {
|
|
697
807
|
"Access-Control-Allow-Methods": "GET, POST, PATCH, OPTIONS",
|
|
698
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-OrgX-Api-Key, X-API-Key, X-OrgX-User-Id",
|
|
808
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-OrgX-Api-Key, X-API-Key, X-OrgX-User-Id, X-OrgX-Hook-Token, X-Hook-Token",
|
|
699
809
|
Vary: "Origin",
|
|
700
810
|
};
|
|
811
|
+
const CONTENT_SECURITY_POLICY = [
|
|
812
|
+
"default-src 'self'",
|
|
813
|
+
"base-uri 'self'",
|
|
814
|
+
"frame-ancestors 'none'",
|
|
815
|
+
"form-action 'self'",
|
|
816
|
+
"object-src 'none'",
|
|
817
|
+
"script-src 'self'",
|
|
818
|
+
"style-src 'self' 'unsafe-inline'",
|
|
819
|
+
"img-src 'self' data: blob:",
|
|
820
|
+
"font-src 'self' data:",
|
|
821
|
+
"media-src 'self'",
|
|
822
|
+
"connect-src 'self' https://*.useorgx.com https://*.openclaw.ai http://127.0.0.1:* http://localhost:*",
|
|
823
|
+
].join("; ");
|
|
701
824
|
const SECURITY_HEADERS = {
|
|
702
825
|
"X-Content-Type-Options": "nosniff",
|
|
703
826
|
"X-Frame-Options": "DENY",
|
|
704
827
|
"Referrer-Policy": "same-origin",
|
|
828
|
+
"X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
|
|
829
|
+
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
|
|
830
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
705
831
|
"Cross-Origin-Resource-Policy": "same-origin",
|
|
832
|
+
"Origin-Agent-Cluster": "?1",
|
|
833
|
+
"X-Permitted-Cross-Domain-Policies": "none",
|
|
834
|
+
"Content-Security-Policy": CONTENT_SECURITY_POLICY,
|
|
706
835
|
};
|
|
707
836
|
function normalizeHost(value) {
|
|
708
837
|
return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
@@ -1481,6 +1610,17 @@ function isInProgressStatus(status) {
|
|
|
1481
1610
|
normalized === "running" ||
|
|
1482
1611
|
normalized === "queued");
|
|
1483
1612
|
}
|
|
1613
|
+
function isDispatchableWorkstreamStatus(status) {
|
|
1614
|
+
const normalized = status.toLowerCase();
|
|
1615
|
+
if (!normalized)
|
|
1616
|
+
return true;
|
|
1617
|
+
return !(normalized === "blocked" ||
|
|
1618
|
+
normalized === "done" ||
|
|
1619
|
+
normalized === "completed" ||
|
|
1620
|
+
normalized === "cancelled" ||
|
|
1621
|
+
normalized === "archived" ||
|
|
1622
|
+
normalized === "deleted");
|
|
1623
|
+
}
|
|
1484
1624
|
function isDoneStatus(status) {
|
|
1485
1625
|
const normalized = status.toLowerCase();
|
|
1486
1626
|
return (normalized === "done" ||
|
|
@@ -1933,7 +2073,56 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1933
2073
|
});
|
|
1934
2074
|
}
|
|
1935
2075
|
catch {
|
|
1936
|
-
//
|
|
2076
|
+
// Fall back to local outbox so activity is still visible in Mission Control/Activity.
|
|
2077
|
+
try {
|
|
2078
|
+
const timestamp = new Date().toISOString();
|
|
2079
|
+
const runId = input.runId?.trim() ||
|
|
2080
|
+
input.correlationId?.trim() ||
|
|
2081
|
+
null;
|
|
2082
|
+
const activityItem = {
|
|
2083
|
+
id: randomUUID(),
|
|
2084
|
+
type: input.phase === "completed"
|
|
2085
|
+
? "run_completed"
|
|
2086
|
+
: input.phase === "blocked"
|
|
2087
|
+
? "run_failed"
|
|
2088
|
+
: "run_started",
|
|
2089
|
+
title: message,
|
|
2090
|
+
description: input.nextStep ?? null,
|
|
2091
|
+
agentId: (typeof input.metadata?.agent_id === "string"
|
|
2092
|
+
? input.metadata.agent_id
|
|
2093
|
+
: null) ?? null,
|
|
2094
|
+
agentName: (typeof input.metadata?.agent_name === "string"
|
|
2095
|
+
? input.metadata.agent_name
|
|
2096
|
+
: null) ?? null,
|
|
2097
|
+
runId,
|
|
2098
|
+
initiativeId,
|
|
2099
|
+
timestamp,
|
|
2100
|
+
phase: input.phase,
|
|
2101
|
+
summary: message,
|
|
2102
|
+
metadata: {
|
|
2103
|
+
...(input.metadata ?? {}),
|
|
2104
|
+
source: "openclaw_local_fallback",
|
|
2105
|
+
},
|
|
2106
|
+
};
|
|
2107
|
+
await appendToOutbox(initiativeId, {
|
|
2108
|
+
id: randomUUID(),
|
|
2109
|
+
type: "progress",
|
|
2110
|
+
timestamp,
|
|
2111
|
+
payload: {
|
|
2112
|
+
phase: input.phase,
|
|
2113
|
+
message,
|
|
2114
|
+
level: input.level ?? "info",
|
|
2115
|
+
runId,
|
|
2116
|
+
initiativeId,
|
|
2117
|
+
nextStep: input.nextStep ?? null,
|
|
2118
|
+
metadata: input.metadata ?? null,
|
|
2119
|
+
},
|
|
2120
|
+
activityItem,
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
catch {
|
|
2124
|
+
// best effort
|
|
2125
|
+
}
|
|
1937
2126
|
}
|
|
1938
2127
|
}
|
|
1939
2128
|
async function syncParentRollupsForTask(input) {
|
|
@@ -2008,8 +2197,71 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2008
2197
|
}
|
|
2009
2198
|
}
|
|
2010
2199
|
const autoContinueRuns = new Map();
|
|
2200
|
+
const localInitiativeStatusOverrides = new Map();
|
|
2011
2201
|
let autoContinueTickInFlight = false;
|
|
2012
2202
|
const AUTO_CONTINUE_TICK_MS = 2_500;
|
|
2203
|
+
const setLocalInitiativeStatusOverride = (initiativeId, status) => {
|
|
2204
|
+
const normalizedId = initiativeId.trim();
|
|
2205
|
+
if (!normalizedId)
|
|
2206
|
+
return;
|
|
2207
|
+
localInitiativeStatusOverrides.set(normalizedId, {
|
|
2208
|
+
status,
|
|
2209
|
+
updatedAt: new Date().toISOString(),
|
|
2210
|
+
});
|
|
2211
|
+
};
|
|
2212
|
+
const clearLocalInitiativeStatusOverride = (initiativeId) => {
|
|
2213
|
+
const normalizedId = initiativeId.trim();
|
|
2214
|
+
if (!normalizedId)
|
|
2215
|
+
return;
|
|
2216
|
+
localInitiativeStatusOverrides.delete(normalizedId);
|
|
2217
|
+
};
|
|
2218
|
+
const applyLocalInitiativeOverrides = (rows) => {
|
|
2219
|
+
const seenIds = new Set();
|
|
2220
|
+
const next = rows.map((row) => {
|
|
2221
|
+
const id = pickString(row, ["id"]);
|
|
2222
|
+
if (!id)
|
|
2223
|
+
return row;
|
|
2224
|
+
seenIds.add(id);
|
|
2225
|
+
const override = localInitiativeStatusOverrides.get(id);
|
|
2226
|
+
if (!override)
|
|
2227
|
+
return row;
|
|
2228
|
+
return {
|
|
2229
|
+
...row,
|
|
2230
|
+
status: override.status,
|
|
2231
|
+
updated_at: pickString(row, ["updated_at", "updatedAt"]) ?? override.updatedAt,
|
|
2232
|
+
};
|
|
2233
|
+
});
|
|
2234
|
+
for (const [id, override] of localInitiativeStatusOverrides.entries()) {
|
|
2235
|
+
if (seenIds.has(id))
|
|
2236
|
+
continue;
|
|
2237
|
+
next.push({
|
|
2238
|
+
id,
|
|
2239
|
+
title: `Initiative ${id.slice(0, 8)}`,
|
|
2240
|
+
name: `Initiative ${id.slice(0, 8)}`,
|
|
2241
|
+
summary: null,
|
|
2242
|
+
status: override.status,
|
|
2243
|
+
progress_pct: null,
|
|
2244
|
+
created_at: override.updatedAt,
|
|
2245
|
+
updated_at: override.updatedAt,
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
return next;
|
|
2249
|
+
};
|
|
2250
|
+
const applyLocalInitiativeOverrideToGraph = (graph) => {
|
|
2251
|
+
const override = localInitiativeStatusOverrides.get(graph.initiative.id) ?? null;
|
|
2252
|
+
if (!override)
|
|
2253
|
+
return graph;
|
|
2254
|
+
return {
|
|
2255
|
+
...graph,
|
|
2256
|
+
initiative: {
|
|
2257
|
+
...graph.initiative,
|
|
2258
|
+
status: override.status,
|
|
2259
|
+
},
|
|
2260
|
+
nodes: graph.nodes.map((node) => node.type === "initiative" && node.id === graph.initiative.id
|
|
2261
|
+
? { ...node, status: override.status }
|
|
2262
|
+
: node),
|
|
2263
|
+
};
|
|
2264
|
+
};
|
|
2013
2265
|
function normalizeTokenBudget(value, fallback) {
|
|
2014
2266
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2015
2267
|
return Math.max(1_000, Math.round(value));
|
|
@@ -2209,6 +2461,59 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2209
2461
|
// best effort
|
|
2210
2462
|
}
|
|
2211
2463
|
}
|
|
2464
|
+
async function dispatchFallbackWorkstreamTurn(input) {
|
|
2465
|
+
const now = new Date().toISOString();
|
|
2466
|
+
const sessionId = randomUUID();
|
|
2467
|
+
const message = [
|
|
2468
|
+
`Initiative: ${input.initiativeTitle}`,
|
|
2469
|
+
`Workstream: ${input.workstreamTitle}`,
|
|
2470
|
+
"",
|
|
2471
|
+
"Continue this workstream from the latest context.",
|
|
2472
|
+
"Identify and execute the next concrete task, then provide a concise progress summary.",
|
|
2473
|
+
].join("\n");
|
|
2474
|
+
await emitActivitySafe({
|
|
2475
|
+
initiativeId: input.initiativeId,
|
|
2476
|
+
correlationId: sessionId,
|
|
2477
|
+
phase: "execution",
|
|
2478
|
+
level: "info",
|
|
2479
|
+
message: `Next Up dispatched ${input.workstreamTitle}.`,
|
|
2480
|
+
metadata: {
|
|
2481
|
+
event: "next_up_manual_dispatch_started",
|
|
2482
|
+
agent_id: input.agentId,
|
|
2483
|
+
session_id: sessionId,
|
|
2484
|
+
workstream_id: input.workstreamId,
|
|
2485
|
+
workstream_title: input.workstreamTitle,
|
|
2486
|
+
fallback: true,
|
|
2487
|
+
},
|
|
2488
|
+
});
|
|
2489
|
+
upsertAgentContext({
|
|
2490
|
+
agentId: input.agentId,
|
|
2491
|
+
initiativeId: input.initiativeId,
|
|
2492
|
+
initiativeTitle: input.initiativeTitle,
|
|
2493
|
+
workstreamId: input.workstreamId,
|
|
2494
|
+
taskId: null,
|
|
2495
|
+
});
|
|
2496
|
+
const spawned = spawnAgentTurn({
|
|
2497
|
+
agentId: input.agentId,
|
|
2498
|
+
sessionId,
|
|
2499
|
+
message,
|
|
2500
|
+
});
|
|
2501
|
+
upsertAgentRun({
|
|
2502
|
+
runId: sessionId,
|
|
2503
|
+
agentId: input.agentId,
|
|
2504
|
+
pid: spawned.pid,
|
|
2505
|
+
message,
|
|
2506
|
+
provider: null,
|
|
2507
|
+
model: null,
|
|
2508
|
+
initiativeId: input.initiativeId,
|
|
2509
|
+
initiativeTitle: input.initiativeTitle,
|
|
2510
|
+
workstreamId: input.workstreamId,
|
|
2511
|
+
taskId: null,
|
|
2512
|
+
startedAt: now,
|
|
2513
|
+
status: "running",
|
|
2514
|
+
});
|
|
2515
|
+
return { sessionId, pid: spawned.pid };
|
|
2516
|
+
}
|
|
2212
2517
|
async function tickAutoContinueRun(run) {
|
|
2213
2518
|
if (run.status !== "running" && run.status !== "stopping")
|
|
2214
2519
|
return;
|
|
@@ -2317,7 +2622,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2317
2622
|
// 3) Pick next-up task and dispatch.
|
|
2318
2623
|
let graph;
|
|
2319
2624
|
try {
|
|
2320
|
-
graph = await buildMissionControlGraph(client, run.initiativeId);
|
|
2625
|
+
graph = applyLocalInitiativeOverrideToGraph(await buildMissionControlGraph(client, run.initiativeId));
|
|
2321
2626
|
}
|
|
2322
2627
|
catch (err) {
|
|
2323
2628
|
await stopAutoContinueRun({
|
|
@@ -2364,7 +2669,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2364
2669
|
}
|
|
2365
2670
|
if (node.workstreamId) {
|
|
2366
2671
|
const ws = nodeById.get(node.workstreamId);
|
|
2367
|
-
if (ws && !
|
|
2672
|
+
if (ws && !isDispatchableWorkstreamStatus(ws.status)) {
|
|
2368
2673
|
continue;
|
|
2369
2674
|
}
|
|
2370
2675
|
}
|
|
@@ -2407,6 +2712,21 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2407
2712
|
]
|
|
2408
2713
|
.filter((line) => typeof line === "string")
|
|
2409
2714
|
.join("\n");
|
|
2715
|
+
if (nextTaskNode.workstreamId) {
|
|
2716
|
+
const workstreamNode = nodeById.get(nextTaskNode.workstreamId);
|
|
2717
|
+
if (workstreamNode &&
|
|
2718
|
+
!isInProgressStatus(workstreamNode.status) &&
|
|
2719
|
+
isDispatchableWorkstreamStatus(workstreamNode.status)) {
|
|
2720
|
+
try {
|
|
2721
|
+
await client.updateEntity("workstream", workstreamNode.id, {
|
|
2722
|
+
status: "active",
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
catch {
|
|
2726
|
+
// best effort
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2410
2730
|
try {
|
|
2411
2731
|
await client.updateEntity("task", nextTaskNode.id, {
|
|
2412
2732
|
status: "in_progress",
|
|
@@ -2514,6 +2834,528 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2514
2834
|
autoContinueTickInFlight = false;
|
|
2515
2835
|
}
|
|
2516
2836
|
}
|
|
2837
|
+
function isInitiativeActiveStatus(status) {
|
|
2838
|
+
const normalized = (status ?? "").trim().toLowerCase();
|
|
2839
|
+
if (!normalized)
|
|
2840
|
+
return false;
|
|
2841
|
+
return !(normalized === "completed" ||
|
|
2842
|
+
normalized === "done" ||
|
|
2843
|
+
normalized === "archived" ||
|
|
2844
|
+
normalized === "deleted" ||
|
|
2845
|
+
normalized === "cancelled");
|
|
2846
|
+
}
|
|
2847
|
+
function runningAutoContinueForWorkstream(initiativeId, workstreamId) {
|
|
2848
|
+
const run = autoContinueRuns.get(initiativeId) ?? null;
|
|
2849
|
+
if (!run)
|
|
2850
|
+
return null;
|
|
2851
|
+
if (run.status !== "running" && run.status !== "stopping")
|
|
2852
|
+
return null;
|
|
2853
|
+
if (!Array.isArray(run.allowedWorkstreamIds) || run.allowedWorkstreamIds.length === 0) {
|
|
2854
|
+
return run;
|
|
2855
|
+
}
|
|
2856
|
+
return run.allowedWorkstreamIds.includes(workstreamId) ? run : null;
|
|
2857
|
+
}
|
|
2858
|
+
async function resolveAutoContinueUpgradeGate(agentId) {
|
|
2859
|
+
let requiresPremiumAutoContinue = false;
|
|
2860
|
+
try {
|
|
2861
|
+
const agents = await listAgents();
|
|
2862
|
+
const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === agentId) ??
|
|
2863
|
+
null;
|
|
2864
|
+
const agentModel = agentEntry && typeof agentEntry.model === "string"
|
|
2865
|
+
? agentEntry.model
|
|
2866
|
+
: null;
|
|
2867
|
+
requiresPremiumAutoContinue = modelImpliesByok(agentModel);
|
|
2868
|
+
}
|
|
2869
|
+
catch {
|
|
2870
|
+
// ignore
|
|
2871
|
+
}
|
|
2872
|
+
if (!requiresPremiumAutoContinue)
|
|
2873
|
+
return null;
|
|
2874
|
+
const billingStatus = await fetchBillingStatusSafe(client);
|
|
2875
|
+
if (!billingStatus || billingStatus.plan !== "free")
|
|
2876
|
+
return null;
|
|
2877
|
+
const pricingUrl = `${client.getBaseUrl().replace(/\/+$/, "")}/pricing`;
|
|
2878
|
+
return {
|
|
2879
|
+
code: "upgrade_required",
|
|
2880
|
+
error: "Auto-continue for BYOK agents requires a paid OrgX plan. Upgrade, then retry.",
|
|
2881
|
+
currentPlan: billingStatus.plan,
|
|
2882
|
+
requiredPlan: "starter",
|
|
2883
|
+
actions: {
|
|
2884
|
+
checkout: "/orgx/api/billing/checkout",
|
|
2885
|
+
portal: "/orgx/api/billing/portal",
|
|
2886
|
+
pricing: pricingUrl,
|
|
2887
|
+
},
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
async function startAutoContinueRun(input) {
|
|
2891
|
+
const now = new Date().toISOString();
|
|
2892
|
+
const existing = autoContinueRuns.get(input.initiativeId) ?? null;
|
|
2893
|
+
const run = existing ??
|
|
2894
|
+
{
|
|
2895
|
+
initiativeId: input.initiativeId,
|
|
2896
|
+
agentId: input.agentId,
|
|
2897
|
+
includeVerification: false,
|
|
2898
|
+
allowedWorkstreamIds: null,
|
|
2899
|
+
tokenBudget: defaultAutoContinueTokenBudget(),
|
|
2900
|
+
tokensUsed: 0,
|
|
2901
|
+
status: "running",
|
|
2902
|
+
stopReason: null,
|
|
2903
|
+
stopRequested: false,
|
|
2904
|
+
startedAt: now,
|
|
2905
|
+
stoppedAt: null,
|
|
2906
|
+
updatedAt: now,
|
|
2907
|
+
lastError: null,
|
|
2908
|
+
lastTaskId: null,
|
|
2909
|
+
lastRunId: null,
|
|
2910
|
+
activeTaskId: null,
|
|
2911
|
+
activeRunId: null,
|
|
2912
|
+
activeTaskTokenEstimate: null,
|
|
2913
|
+
};
|
|
2914
|
+
run.agentId = input.agentId;
|
|
2915
|
+
run.includeVerification = input.includeVerification;
|
|
2916
|
+
run.allowedWorkstreamIds = input.allowedWorkstreamIds;
|
|
2917
|
+
run.tokenBudget = normalizeTokenBudget(input.tokenBudget, run.tokenBudget || defaultAutoContinueTokenBudget());
|
|
2918
|
+
run.status = "running";
|
|
2919
|
+
run.stopReason = null;
|
|
2920
|
+
run.stopRequested = false;
|
|
2921
|
+
run.startedAt = now;
|
|
2922
|
+
run.stoppedAt = null;
|
|
2923
|
+
run.updatedAt = now;
|
|
2924
|
+
run.lastError = null;
|
|
2925
|
+
autoContinueRuns.set(input.initiativeId, run);
|
|
2926
|
+
try {
|
|
2927
|
+
await client.updateEntity("initiative", input.initiativeId, { status: "active" });
|
|
2928
|
+
}
|
|
2929
|
+
catch {
|
|
2930
|
+
// best effort
|
|
2931
|
+
}
|
|
2932
|
+
try {
|
|
2933
|
+
await updateInitiativeAutoContinueState({
|
|
2934
|
+
initiativeId: input.initiativeId,
|
|
2935
|
+
run,
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
catch {
|
|
2939
|
+
// best effort
|
|
2940
|
+
}
|
|
2941
|
+
return run;
|
|
2942
|
+
}
|
|
2943
|
+
async function buildNextUpQueue(input) {
|
|
2944
|
+
const degraded = [];
|
|
2945
|
+
const requestedInitiativeId = input?.initiativeId?.trim() || null;
|
|
2946
|
+
const initiativeTitleById = new Map();
|
|
2947
|
+
const initiativeStatusById = new Map();
|
|
2948
|
+
const initiativePriorityById = new Map();
|
|
2949
|
+
const snapshotInitiatives = formatInitiatives(getSnapshot());
|
|
2950
|
+
for (const initiative of snapshotInitiatives) {
|
|
2951
|
+
const id = initiative.id?.trim();
|
|
2952
|
+
if (!id)
|
|
2953
|
+
continue;
|
|
2954
|
+
initiativeTitleById.set(id, initiative.title);
|
|
2955
|
+
initiativeStatusById.set(id, initiative.status || "active");
|
|
2956
|
+
}
|
|
2957
|
+
const initiativeResult = await listEntitiesSafe(client, "initiative", { limit: 500 });
|
|
2958
|
+
if (initiativeResult.warning)
|
|
2959
|
+
degraded.push(initiativeResult.warning);
|
|
2960
|
+
const initiatives = initiativeResult.items;
|
|
2961
|
+
for (const entity of initiatives) {
|
|
2962
|
+
const record = entity;
|
|
2963
|
+
const id = pickString(record, ["id"]);
|
|
2964
|
+
if (!id)
|
|
2965
|
+
continue;
|
|
2966
|
+
const title = pickString(record, ["title", "name"]);
|
|
2967
|
+
const status = pickString(record, ["status"]);
|
|
2968
|
+
const priority = pickString(record, ["priority", "priority_label", "priorityLabel"]);
|
|
2969
|
+
if (title)
|
|
2970
|
+
initiativeTitleById.set(id, title);
|
|
2971
|
+
if (status)
|
|
2972
|
+
initiativeStatusById.set(id, status);
|
|
2973
|
+
if (priority)
|
|
2974
|
+
initiativePriorityById.set(id, priority);
|
|
2975
|
+
}
|
|
2976
|
+
for (const [initiativeId, override] of localInitiativeStatusOverrides.entries()) {
|
|
2977
|
+
initiativeStatusById.set(initiativeId, override.status);
|
|
2978
|
+
}
|
|
2979
|
+
const queueRank = (state) => {
|
|
2980
|
+
if (state === "running")
|
|
2981
|
+
return 0;
|
|
2982
|
+
if (state === "queued")
|
|
2983
|
+
return 1;
|
|
2984
|
+
if (state === "blocked")
|
|
2985
|
+
return 2;
|
|
2986
|
+
return 3;
|
|
2987
|
+
};
|
|
2988
|
+
const sortQueueItems = (a, b) => {
|
|
2989
|
+
const queueDelta = queueRank(a.queueState) - queueRank(b.queueState);
|
|
2990
|
+
if (queueDelta !== 0)
|
|
2991
|
+
return queueDelta;
|
|
2992
|
+
const priorityRank = (value) => {
|
|
2993
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
2994
|
+
if (!normalized)
|
|
2995
|
+
return 4;
|
|
2996
|
+
if (normalized === "critical" || normalized === "p0" || normalized === "urgent")
|
|
2997
|
+
return 0;
|
|
2998
|
+
if (normalized === "high" || normalized === "p1")
|
|
2999
|
+
return 1;
|
|
3000
|
+
if (normalized === "medium" || normalized === "normal" || normalized === "p2")
|
|
3001
|
+
return 2;
|
|
3002
|
+
if (normalized === "low" || normalized === "p3")
|
|
3003
|
+
return 3;
|
|
3004
|
+
return 4;
|
|
3005
|
+
};
|
|
3006
|
+
const aInitiativePriority = priorityRank(initiativePriorityById.get(a.initiativeId));
|
|
3007
|
+
const bInitiativePriority = priorityRank(initiativePriorityById.get(b.initiativeId));
|
|
3008
|
+
if (aInitiativePriority !== bInitiativePriority) {
|
|
3009
|
+
return aInitiativePriority - bInitiativePriority;
|
|
3010
|
+
}
|
|
3011
|
+
const aPriority = typeof a.nextTaskPriority === "number" ? a.nextTaskPriority : 999;
|
|
3012
|
+
const bPriority = typeof b.nextTaskPriority === "number" ? b.nextTaskPriority : 999;
|
|
3013
|
+
if (aPriority !== bPriority)
|
|
3014
|
+
return aPriority - bPriority;
|
|
3015
|
+
const aDue = a.nextTaskDueAt ? Date.parse(a.nextTaskDueAt) : Number.POSITIVE_INFINITY;
|
|
3016
|
+
const bDue = b.nextTaskDueAt ? Date.parse(b.nextTaskDueAt) : Number.POSITIVE_INFINITY;
|
|
3017
|
+
if (aDue !== bDue)
|
|
3018
|
+
return aDue - bDue;
|
|
3019
|
+
const init = a.initiativeTitle.localeCompare(b.initiativeTitle);
|
|
3020
|
+
if (init !== 0)
|
|
3021
|
+
return init;
|
|
3022
|
+
return a.workstreamTitle.localeCompare(b.workstreamTitle);
|
|
3023
|
+
};
|
|
3024
|
+
const buildSessionFallbackQueue = async () => {
|
|
3025
|
+
let sessionTree = null;
|
|
3026
|
+
try {
|
|
3027
|
+
sessionTree = await client.getLiveSessions({
|
|
3028
|
+
initiative: requestedInitiativeId,
|
|
3029
|
+
limit: 500,
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
catch (err) {
|
|
3033
|
+
degraded.push(`live sessions fallback unavailable (${safeErrorMessage(err)})`);
|
|
3034
|
+
}
|
|
3035
|
+
if (!sessionTree) {
|
|
3036
|
+
try {
|
|
3037
|
+
const localTree = toLocalSessionTree(await loadLocalOpenClawSnapshot(400), 400);
|
|
3038
|
+
sessionTree = applyAgentContextsToSessionTree(localTree, readAgentContexts().agents);
|
|
3039
|
+
}
|
|
3040
|
+
catch (err) {
|
|
3041
|
+
degraded.push(`local sessions fallback unavailable (${safeErrorMessage(err)})`);
|
|
3042
|
+
return [];
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
sessionTree = applyAgentContextsToSessionTree(sessionTree, readAgentContexts().agents);
|
|
3046
|
+
const grouped = new Map();
|
|
3047
|
+
const parseEpoch = (value) => {
|
|
3048
|
+
const parsed = value ? Date.parse(value) : Number.NaN;
|
|
3049
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
3050
|
+
};
|
|
3051
|
+
for (const node of sessionTree.nodes ?? []) {
|
|
3052
|
+
const initiativeId = (node.initiativeId ?? "").trim();
|
|
3053
|
+
const workstreamId = (node.workstreamId ?? "").trim();
|
|
3054
|
+
if (!initiativeId || !workstreamId)
|
|
3055
|
+
continue;
|
|
3056
|
+
if (requestedInitiativeId && initiativeId !== requestedInitiativeId)
|
|
3057
|
+
continue;
|
|
3058
|
+
const initiativeStatus = initiativeStatusById.get(initiativeId) ?? "active";
|
|
3059
|
+
if (!isInitiativeActiveStatus(initiativeStatus))
|
|
3060
|
+
continue;
|
|
3061
|
+
const key = `${initiativeId}:${workstreamId}`;
|
|
3062
|
+
const epoch = parseEpoch(node.updatedAt ?? node.lastEventAt ?? node.startedAt);
|
|
3063
|
+
const existing = grouped.get(key);
|
|
3064
|
+
if (!existing) {
|
|
3065
|
+
grouped.set(key, {
|
|
3066
|
+
initiativeId,
|
|
3067
|
+
workstreamId,
|
|
3068
|
+
initiativeTitle: initiativeTitleById.get(initiativeId) ??
|
|
3069
|
+
node.groupLabel ??
|
|
3070
|
+
initiativeId,
|
|
3071
|
+
initiativeStatus,
|
|
3072
|
+
workstreamTitle: `Workstream ${workstreamId.slice(0, 8)}`,
|
|
3073
|
+
statuses: new Set([node.status]),
|
|
3074
|
+
blockers: Array.isArray(node.blockers) ? [...node.blockers] : [],
|
|
3075
|
+
latest: node,
|
|
3076
|
+
latestEpoch: epoch,
|
|
3077
|
+
});
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
existing.statuses.add(node.status);
|
|
3081
|
+
if (Array.isArray(node.blockers)) {
|
|
3082
|
+
for (const blocker of node.blockers) {
|
|
3083
|
+
if (typeof blocker !== "string" || blocker.trim().length === 0)
|
|
3084
|
+
continue;
|
|
3085
|
+
if (!existing.blockers.includes(blocker))
|
|
3086
|
+
existing.blockers.push(blocker);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
if (epoch >= existing.latestEpoch) {
|
|
3090
|
+
existing.latest = node;
|
|
3091
|
+
existing.latestEpoch = epoch;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
const fallbackItems = [];
|
|
3095
|
+
for (const entry of grouped.values()) {
|
|
3096
|
+
const statusValues = Array.from(entry.statuses).map((status) => status.toLowerCase());
|
|
3097
|
+
const hasBlocked = statusValues.some((status) => status === "blocked" || status === "failed") ||
|
|
3098
|
+
entry.blockers.length > 0;
|
|
3099
|
+
const hasRunning = statusValues.some((status) => isInProgressStatus(status));
|
|
3100
|
+
const hasQueued = statusValues.some((status) => status === "queued" || status === "pending");
|
|
3101
|
+
const queueState = hasRunning
|
|
3102
|
+
? "running"
|
|
3103
|
+
: hasBlocked
|
|
3104
|
+
? "blocked"
|
|
3105
|
+
: hasQueued
|
|
3106
|
+
? "queued"
|
|
3107
|
+
: "idle";
|
|
3108
|
+
const runnerAgentId = (entry.latest.agentId ?? "").trim() || "main";
|
|
3109
|
+
const runnerAgentName = (entry.latest.agentName ?? "").trim() ||
|
|
3110
|
+
initiativeTitleById.get(`agent:${runnerAgentId}`) ||
|
|
3111
|
+
runnerAgentId;
|
|
3112
|
+
fallbackItems.push({
|
|
3113
|
+
initiativeId: entry.initiativeId,
|
|
3114
|
+
initiativeTitle: entry.initiativeTitle,
|
|
3115
|
+
initiativeStatus: entry.initiativeStatus,
|
|
3116
|
+
workstreamId: entry.workstreamId,
|
|
3117
|
+
workstreamTitle: entry.workstreamTitle,
|
|
3118
|
+
workstreamStatus: hasBlocked ? "blocked" : hasRunning ? "active" : hasQueued ? "queued" : "idle",
|
|
3119
|
+
nextTaskId: entry.latest.id ?? null,
|
|
3120
|
+
nextTaskTitle: (entry.latest.lastEventSummary ?? "").trim() ||
|
|
3121
|
+
(entry.latest.title ?? "").trim() ||
|
|
3122
|
+
null,
|
|
3123
|
+
nextTaskPriority: null,
|
|
3124
|
+
nextTaskDueAt: null,
|
|
3125
|
+
runnerAgentId,
|
|
3126
|
+
runnerAgentName,
|
|
3127
|
+
runnerSource: "fallback",
|
|
3128
|
+
queueState,
|
|
3129
|
+
blockReason: hasBlocked
|
|
3130
|
+
? entry.blockers[0] ?? (statusValues.includes("failed") ? "Latest run failed" : "Workstream blocked")
|
|
3131
|
+
: null,
|
|
3132
|
+
autoContinue: null,
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
fallbackItems.sort(sortQueueItems);
|
|
3136
|
+
return fallbackItems;
|
|
3137
|
+
};
|
|
3138
|
+
const scopedInitiatives = initiatives.filter((entity) => {
|
|
3139
|
+
const record = entity;
|
|
3140
|
+
const id = pickString(record, ["id"]);
|
|
3141
|
+
if (!id)
|
|
3142
|
+
return false;
|
|
3143
|
+
if (requestedInitiativeId && id !== requestedInitiativeId)
|
|
3144
|
+
return false;
|
|
3145
|
+
const status = pickString(record, ["status"]);
|
|
3146
|
+
return isInitiativeActiveStatus(status);
|
|
3147
|
+
});
|
|
3148
|
+
const agentCatalogById = new Map();
|
|
3149
|
+
try {
|
|
3150
|
+
const catalog = await listAgents();
|
|
3151
|
+
for (const entry of catalog) {
|
|
3152
|
+
if (!entry || typeof entry !== "object")
|
|
3153
|
+
continue;
|
|
3154
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
3155
|
+
if (!id)
|
|
3156
|
+
continue;
|
|
3157
|
+
const name = typeof entry.name === "string" && entry.name.trim().length > 0
|
|
3158
|
+
? entry.name.trim()
|
|
3159
|
+
: id;
|
|
3160
|
+
agentCatalogById.set(id, { id, name });
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
catch (err) {
|
|
3164
|
+
degraded.push(`agent catalog unavailable (${safeErrorMessage(err)})`);
|
|
3165
|
+
}
|
|
3166
|
+
const liveAgentsByInitiative = new Map();
|
|
3167
|
+
try {
|
|
3168
|
+
const data = await client.getLiveAgents({
|
|
3169
|
+
initiative: requestedInitiativeId,
|
|
3170
|
+
includeIdle: true,
|
|
3171
|
+
});
|
|
3172
|
+
for (const raw of Array.isArray(data.agents) ? data.agents : []) {
|
|
3173
|
+
if (!raw || typeof raw !== "object")
|
|
3174
|
+
continue;
|
|
3175
|
+
const row = raw;
|
|
3176
|
+
const initiativeId = pickString(row, ["initiativeId", "initiative_id"]);
|
|
3177
|
+
if (!initiativeId)
|
|
3178
|
+
continue;
|
|
3179
|
+
const id = pickString(row, ["id", "agentId", "agent_id"]) ??
|
|
3180
|
+
pickString(row, ["name", "agentName", "agent_name"]) ??
|
|
3181
|
+
"";
|
|
3182
|
+
const name = pickString(row, ["name", "agentName", "agent_name"]) ??
|
|
3183
|
+
id;
|
|
3184
|
+
if (!id || !name)
|
|
3185
|
+
continue;
|
|
3186
|
+
const list = liveAgentsByInitiative.get(initiativeId) ?? [];
|
|
3187
|
+
list.push({
|
|
3188
|
+
id,
|
|
3189
|
+
name,
|
|
3190
|
+
domain: pickString(row, ["domain", "role"]),
|
|
3191
|
+
});
|
|
3192
|
+
liveAgentsByInitiative.set(initiativeId, list);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
catch (err) {
|
|
3196
|
+
degraded.push(`live agents unavailable (${safeErrorMessage(err)})`);
|
|
3197
|
+
}
|
|
3198
|
+
const items = [];
|
|
3199
|
+
for (const initiativeEntity of scopedInitiatives) {
|
|
3200
|
+
const initiativeRecord = initiativeEntity;
|
|
3201
|
+
const initiativeId = pickString(initiativeRecord, ["id"]);
|
|
3202
|
+
if (!initiativeId)
|
|
3203
|
+
continue;
|
|
3204
|
+
const initiativeTitle = pickString(initiativeRecord, ["title", "name"]) ?? initiativeId;
|
|
3205
|
+
const initiativeStatus = pickString(initiativeRecord, ["status"]) ?? "active";
|
|
3206
|
+
let graph;
|
|
3207
|
+
try {
|
|
3208
|
+
graph = applyLocalInitiativeOverrideToGraph(await buildMissionControlGraph(client, initiativeId));
|
|
3209
|
+
}
|
|
3210
|
+
catch (err) {
|
|
3211
|
+
degraded.push(`graph unavailable for ${initiativeId} (${safeErrorMessage(err)})`);
|
|
3212
|
+
continue;
|
|
3213
|
+
}
|
|
3214
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
3215
|
+
const workstreamNodes = graph.nodes.filter((node) => node.type === "workstream");
|
|
3216
|
+
const runningWorkstreams = new Set();
|
|
3217
|
+
const taskIsReady = (task) => task.dependencyIds.every((depId) => {
|
|
3218
|
+
const dependency = nodeById.get(depId);
|
|
3219
|
+
return dependency ? isDoneStatus(dependency.status) : true;
|
|
3220
|
+
});
|
|
3221
|
+
const taskHasBlockedParent = (task) => {
|
|
3222
|
+
const milestone = task.milestoneId ? nodeById.get(task.milestoneId) ?? null : null;
|
|
3223
|
+
const workstream = task.workstreamId ? nodeById.get(task.workstreamId) ?? null : null;
|
|
3224
|
+
return (milestone?.status?.toLowerCase() === "blocked" ||
|
|
3225
|
+
workstream?.status?.toLowerCase() === "blocked");
|
|
3226
|
+
};
|
|
3227
|
+
for (const workstream of workstreamNodes) {
|
|
3228
|
+
const todoTasks = graph.recentTodos
|
|
3229
|
+
.map((taskId) => nodeById.get(taskId))
|
|
3230
|
+
.filter((node) => node?.type === "task" &&
|
|
3231
|
+
node.workstreamId === workstream.id &&
|
|
3232
|
+
isTodoStatus(node.status));
|
|
3233
|
+
const readyTask = todoTasks.find((task) => taskIsReady(task) && !taskHasBlockedParent(task));
|
|
3234
|
+
const candidateTask = readyTask ?? todoTasks[0] ?? null;
|
|
3235
|
+
const autoContinueRun = runningAutoContinueForWorkstream(initiativeId, workstream.id);
|
|
3236
|
+
let queueState = autoContinueRun ? "running" : "queued";
|
|
3237
|
+
let blockReason = null;
|
|
3238
|
+
if (!autoContinueRun && !readyTask && candidateTask) {
|
|
3239
|
+
queueState = "blocked";
|
|
3240
|
+
const blockedDeps = candidateTask.dependencyIds
|
|
3241
|
+
.map((depId) => nodeById.get(depId))
|
|
3242
|
+
.filter((dependency) => Boolean(dependency && !isDoneStatus(dependency.status)))
|
|
3243
|
+
.map((dependency) => dependency.title);
|
|
3244
|
+
if (blockedDeps.length > 0) {
|
|
3245
|
+
blockReason = `Waiting on ${blockedDeps.slice(0, 2).join(", ")}${blockedDeps.length > 2 ? "…" : ""}`;
|
|
3246
|
+
}
|
|
3247
|
+
else if (taskHasBlockedParent(candidateTask)) {
|
|
3248
|
+
blockReason = "Parent milestone or workstream is blocked";
|
|
3249
|
+
}
|
|
3250
|
+
else if (!taskIsReady(candidateTask)) {
|
|
3251
|
+
blockReason = "Task prerequisites are not complete";
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
if (!candidateTask && !autoContinueRun) {
|
|
3255
|
+
continue;
|
|
3256
|
+
}
|
|
3257
|
+
runningWorkstreams.add(workstream.id);
|
|
3258
|
+
const assignedAgent = workstream.assignedAgents[0] ?? null;
|
|
3259
|
+
const inferredAgent = graph.initiative.assignedAgents[0] ??
|
|
3260
|
+
liveAgentsByInitiative.get(initiativeId)?.[0] ??
|
|
3261
|
+
(autoContinueRun?.agentId
|
|
3262
|
+
? {
|
|
3263
|
+
id: autoContinueRun.agentId,
|
|
3264
|
+
name: agentCatalogById.get(autoContinueRun.agentId)?.name ?? autoContinueRun.agentId,
|
|
3265
|
+
domain: null,
|
|
3266
|
+
}
|
|
3267
|
+
: null);
|
|
3268
|
+
const runnerSource = assignedAgent
|
|
3269
|
+
? "assigned"
|
|
3270
|
+
: inferredAgent
|
|
3271
|
+
? "inferred"
|
|
3272
|
+
: "fallback";
|
|
3273
|
+
const resolvedRunner = assignedAgent ?? inferredAgent;
|
|
3274
|
+
const runnerAgentId = resolvedRunner?.id ?? autoContinueRun?.agentId ?? "main";
|
|
3275
|
+
const runnerAgentName = resolvedRunner?.name ??
|
|
3276
|
+
agentCatalogById.get(runnerAgentId)?.name ??
|
|
3277
|
+
runnerAgentId;
|
|
3278
|
+
items.push({
|
|
3279
|
+
initiativeId,
|
|
3280
|
+
initiativeTitle,
|
|
3281
|
+
initiativeStatus,
|
|
3282
|
+
workstreamId: workstream.id,
|
|
3283
|
+
workstreamTitle: workstream.title,
|
|
3284
|
+
workstreamStatus: workstream.status,
|
|
3285
|
+
nextTaskId: candidateTask?.id ??
|
|
3286
|
+
(autoContinueRun?.activeTaskId?.trim() || null),
|
|
3287
|
+
nextTaskTitle: candidateTask?.title ??
|
|
3288
|
+
(autoContinueRun?.activeTaskId
|
|
3289
|
+
? nodeById.get(autoContinueRun.activeTaskId)?.title ?? null
|
|
3290
|
+
: null),
|
|
3291
|
+
nextTaskPriority: candidateTask?.priorityNum ?? null,
|
|
3292
|
+
nextTaskDueAt: candidateTask?.dueDate ?? null,
|
|
3293
|
+
runnerAgentId,
|
|
3294
|
+
runnerAgentName,
|
|
3295
|
+
runnerSource,
|
|
3296
|
+
queueState,
|
|
3297
|
+
blockReason,
|
|
3298
|
+
autoContinue: autoContinueRun
|
|
3299
|
+
? {
|
|
3300
|
+
status: autoContinueRun.status,
|
|
3301
|
+
activeTaskId: autoContinueRun.activeTaskId,
|
|
3302
|
+
activeRunId: autoContinueRun.activeRunId,
|
|
3303
|
+
stopReason: autoContinueRun.stopReason,
|
|
3304
|
+
updatedAt: autoContinueRun.updatedAt,
|
|
3305
|
+
}
|
|
3306
|
+
: null,
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
const run = autoContinueRuns.get(initiativeId);
|
|
3310
|
+
if (run &&
|
|
3311
|
+
(run.status === "running" || run.status === "stopping") &&
|
|
3312
|
+
Array.isArray(run.allowedWorkstreamIds) &&
|
|
3313
|
+
run.allowedWorkstreamIds.length > 0) {
|
|
3314
|
+
for (const workstreamId of run.allowedWorkstreamIds) {
|
|
3315
|
+
if (runningWorkstreams.has(workstreamId))
|
|
3316
|
+
continue;
|
|
3317
|
+
const workstream = nodeById.get(workstreamId);
|
|
3318
|
+
if (!workstream || workstream.type !== "workstream")
|
|
3319
|
+
continue;
|
|
3320
|
+
items.push({
|
|
3321
|
+
initiativeId,
|
|
3322
|
+
initiativeTitle,
|
|
3323
|
+
initiativeStatus,
|
|
3324
|
+
workstreamId: workstream.id,
|
|
3325
|
+
workstreamTitle: workstream.title,
|
|
3326
|
+
workstreamStatus: workstream.status,
|
|
3327
|
+
nextTaskId: run.activeTaskId,
|
|
3328
|
+
nextTaskTitle: run.activeTaskId
|
|
3329
|
+
? nodeById.get(run.activeTaskId)?.title ?? null
|
|
3330
|
+
: null,
|
|
3331
|
+
nextTaskPriority: null,
|
|
3332
|
+
nextTaskDueAt: null,
|
|
3333
|
+
runnerAgentId: run.agentId,
|
|
3334
|
+
runnerAgentName: agentCatalogById.get(run.agentId)?.name ?? run.agentId,
|
|
3335
|
+
runnerSource: "inferred",
|
|
3336
|
+
queueState: "running",
|
|
3337
|
+
blockReason: null,
|
|
3338
|
+
autoContinue: {
|
|
3339
|
+
status: run.status,
|
|
3340
|
+
activeTaskId: run.activeTaskId,
|
|
3341
|
+
activeRunId: run.activeRunId,
|
|
3342
|
+
stopReason: run.stopReason,
|
|
3343
|
+
updatedAt: run.updatedAt,
|
|
3344
|
+
},
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
if (items.length === 0) {
|
|
3350
|
+
const fallbackItems = await buildSessionFallbackQueue();
|
|
3351
|
+
if (fallbackItems.length > 0) {
|
|
3352
|
+
degraded.push("Using session-derived Next Up fallback.");
|
|
3353
|
+
items.push(...fallbackItems);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
items.sort(sortQueueItems);
|
|
3357
|
+
return { items, degraded };
|
|
3358
|
+
}
|
|
2517
3359
|
const autoContinueTimer = setInterval(() => {
|
|
2518
3360
|
void tickAllAutoContinue();
|
|
2519
3361
|
}, AUTO_CONTINUE_TICK_MS);
|
|
@@ -2558,6 +3400,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2558
3400
|
const runCheckpointRestoreMatch = route.match(/^runs\/([^/]+)\/checkpoints\/([^/]+)\/restore$/);
|
|
2559
3401
|
const isDelegationPreflight = route === "delegation/preflight";
|
|
2560
3402
|
const isMissionControlAutoAssignmentRoute = route === "mission-control/assignments/auto";
|
|
3403
|
+
const isMissionControlNextUpPlayRoute = route === "mission-control/next-up/play";
|
|
2561
3404
|
const isMissionControlAutoContinueStartRoute = route === "mission-control/auto-continue/start";
|
|
2562
3405
|
const isMissionControlAutoContinueStopRoute = route === "mission-control/auto-continue/stop";
|
|
2563
3406
|
const isEntitiesRoute = route === "entities";
|
|
@@ -3071,6 +3914,135 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3071
3914
|
}
|
|
3072
3915
|
return true;
|
|
3073
3916
|
}
|
|
3917
|
+
if (method === "POST" && isMissionControlNextUpPlayRoute) {
|
|
3918
|
+
try {
|
|
3919
|
+
const payload = await parseJsonRequest(req);
|
|
3920
|
+
const initiativeId = (pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
3921
|
+
searchParams.get("initiativeId") ??
|
|
3922
|
+
searchParams.get("initiative_id") ??
|
|
3923
|
+
"")
|
|
3924
|
+
.trim();
|
|
3925
|
+
const workstreamId = (pickString(payload, ["workstreamId", "workstream_id"]) ??
|
|
3926
|
+
searchParams.get("workstreamId") ??
|
|
3927
|
+
searchParams.get("workstream_id") ??
|
|
3928
|
+
"")
|
|
3929
|
+
.trim();
|
|
3930
|
+
if (!initiativeId || !workstreamId) {
|
|
3931
|
+
sendJson(res, 400, {
|
|
3932
|
+
ok: false,
|
|
3933
|
+
error: "initiativeId and workstreamId are required",
|
|
3934
|
+
});
|
|
3935
|
+
return true;
|
|
3936
|
+
}
|
|
3937
|
+
let agentIdRaw = (pickString(payload, ["agentId", "agent_id"]) ??
|
|
3938
|
+
searchParams.get("agentId") ??
|
|
3939
|
+
searchParams.get("agent_id") ??
|
|
3940
|
+
"")
|
|
3941
|
+
.trim();
|
|
3942
|
+
const queue = await buildNextUpQueue({ initiativeId });
|
|
3943
|
+
const matchedQueueItem = queue.items.find((item) => item.workstreamId === workstreamId) ?? null;
|
|
3944
|
+
if (!agentIdRaw && matchedQueueItem?.runnerAgentId) {
|
|
3945
|
+
agentIdRaw = matchedQueueItem.runnerAgentId;
|
|
3946
|
+
}
|
|
3947
|
+
const agentId = agentIdRaw || "main";
|
|
3948
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) {
|
|
3949
|
+
sendJson(res, 400, {
|
|
3950
|
+
ok: false,
|
|
3951
|
+
error: "agentId must be a simple identifier (letters, numbers, _ or -).",
|
|
3952
|
+
});
|
|
3953
|
+
return true;
|
|
3954
|
+
}
|
|
3955
|
+
const upgradeGate = await resolveAutoContinueUpgradeGate(agentId);
|
|
3956
|
+
if (upgradeGate) {
|
|
3957
|
+
sendJson(res, 402, {
|
|
3958
|
+
ok: false,
|
|
3959
|
+
...upgradeGate,
|
|
3960
|
+
});
|
|
3961
|
+
return true;
|
|
3962
|
+
}
|
|
3963
|
+
const tokenBudget = pickNumber(payload, [
|
|
3964
|
+
"tokenBudget",
|
|
3965
|
+
"token_budget",
|
|
3966
|
+
"tokenBudgetTokens",
|
|
3967
|
+
"token_budget_tokens",
|
|
3968
|
+
"maxTokens",
|
|
3969
|
+
"max_tokens",
|
|
3970
|
+
]) ??
|
|
3971
|
+
searchParams.get("tokenBudget") ??
|
|
3972
|
+
searchParams.get("token_budget") ??
|
|
3973
|
+
searchParams.get("tokenBudgetTokens") ??
|
|
3974
|
+
searchParams.get("token_budget_tokens") ??
|
|
3975
|
+
searchParams.get("maxTokens") ??
|
|
3976
|
+
searchParams.get("max_tokens") ??
|
|
3977
|
+
null;
|
|
3978
|
+
const includeVerificationRaw = payload.includeVerification ??
|
|
3979
|
+
payload.include_verification ??
|
|
3980
|
+
searchParams.get("includeVerification") ??
|
|
3981
|
+
searchParams.get("include_verification") ??
|
|
3982
|
+
null;
|
|
3983
|
+
const includeVerification = typeof includeVerificationRaw === "boolean"
|
|
3984
|
+
? includeVerificationRaw
|
|
3985
|
+
: parseBooleanQuery(typeof includeVerificationRaw === "string"
|
|
3986
|
+
? includeVerificationRaw
|
|
3987
|
+
: null);
|
|
3988
|
+
const run = await startAutoContinueRun({
|
|
3989
|
+
initiativeId,
|
|
3990
|
+
agentId,
|
|
3991
|
+
tokenBudget,
|
|
3992
|
+
includeVerification,
|
|
3993
|
+
allowedWorkstreamIds: [workstreamId],
|
|
3994
|
+
});
|
|
3995
|
+
// Play should feel immediate. Run one dispatch tick synchronously so the
|
|
3996
|
+
// user gets an actual launch (or a concrete error) in this response.
|
|
3997
|
+
await tickAutoContinueRun(run);
|
|
3998
|
+
let fallbackDispatch = null;
|
|
3999
|
+
if (!run.activeRunId &&
|
|
4000
|
+
matchedQueueItem &&
|
|
4001
|
+
matchedQueueItem.runnerSource === "fallback") {
|
|
4002
|
+
fallbackDispatch = await dispatchFallbackWorkstreamTurn({
|
|
4003
|
+
initiativeId,
|
|
4004
|
+
initiativeTitle: matchedQueueItem.initiativeTitle,
|
|
4005
|
+
workstreamId,
|
|
4006
|
+
workstreamTitle: matchedQueueItem.workstreamTitle,
|
|
4007
|
+
agentId,
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
const dispatchMode = run.activeRunId
|
|
4011
|
+
? "task"
|
|
4012
|
+
: fallbackDispatch
|
|
4013
|
+
? "fallback"
|
|
4014
|
+
: "none";
|
|
4015
|
+
if (dispatchMode === "none") {
|
|
4016
|
+
const reason = run.stopReason === "blocked"
|
|
4017
|
+
? "No dispatchable task is ready for this workstream yet."
|
|
4018
|
+
: run.stopReason === "completed"
|
|
4019
|
+
? "No queued task is available for this workstream."
|
|
4020
|
+
: "Unable to dispatch this workstream right now.";
|
|
4021
|
+
sendJson(res, 409, {
|
|
4022
|
+
ok: false,
|
|
4023
|
+
error: reason,
|
|
4024
|
+
run,
|
|
4025
|
+
initiativeId,
|
|
4026
|
+
workstreamId,
|
|
4027
|
+
agentId,
|
|
4028
|
+
});
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
sendJson(res, 200, {
|
|
4032
|
+
ok: true,
|
|
4033
|
+
run,
|
|
4034
|
+
initiativeId,
|
|
4035
|
+
workstreamId,
|
|
4036
|
+
agentId,
|
|
4037
|
+
dispatchMode,
|
|
4038
|
+
sessionId: run.activeRunId ?? fallbackDispatch?.sessionId ?? null,
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
catch (err) {
|
|
4042
|
+
sendJson(res, 500, { ok: false, error: safeErrorMessage(err) });
|
|
4043
|
+
}
|
|
4044
|
+
return true;
|
|
4045
|
+
}
|
|
3074
4046
|
if (method === "POST" && isMissionControlAutoContinueStartRoute) {
|
|
3075
4047
|
try {
|
|
3076
4048
|
const payload = await parseJsonRequest(req);
|
|
@@ -3096,37 +4068,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3096
4068
|
});
|
|
3097
4069
|
return true;
|
|
3098
4070
|
}
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
: null;
|
|
3107
|
-
requiresPremiumAutoContinue = modelImpliesByok(agentModel);
|
|
3108
|
-
}
|
|
3109
|
-
catch {
|
|
3110
|
-
// ignore
|
|
3111
|
-
}
|
|
3112
|
-
if (requiresPremiumAutoContinue) {
|
|
3113
|
-
const billingStatus = await fetchBillingStatusSafe(client);
|
|
3114
|
-
if (billingStatus && billingStatus.plan === "free") {
|
|
3115
|
-
const pricingUrl = `${client.getBaseUrl().replace(/\/+$/, "")}/pricing`;
|
|
3116
|
-
sendJson(res, 402, {
|
|
3117
|
-
ok: false,
|
|
3118
|
-
code: "upgrade_required",
|
|
3119
|
-
error: "Auto-continue for BYOK agents requires a paid OrgX plan. Upgrade, then retry.",
|
|
3120
|
-
currentPlan: billingStatus.plan,
|
|
3121
|
-
requiredPlan: "starter",
|
|
3122
|
-
actions: {
|
|
3123
|
-
checkout: "/orgx/api/billing/checkout",
|
|
3124
|
-
portal: "/orgx/api/billing/portal",
|
|
3125
|
-
pricing: pricingUrl,
|
|
3126
|
-
},
|
|
3127
|
-
});
|
|
3128
|
-
return true;
|
|
3129
|
-
}
|
|
4071
|
+
const upgradeGate = await resolveAutoContinueUpgradeGate(agentId);
|
|
4072
|
+
if (upgradeGate) {
|
|
4073
|
+
sendJson(res, 402, {
|
|
4074
|
+
ok: false,
|
|
4075
|
+
...upgradeGate,
|
|
4076
|
+
});
|
|
4077
|
+
return true;
|
|
3130
4078
|
}
|
|
3131
4079
|
const tokenBudget = pickNumber(payload, [
|
|
3132
4080
|
"tokenBudget",
|
|
@@ -3170,53 +4118,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3170
4118
|
.filter(Boolean),
|
|
3171
4119
|
]);
|
|
3172
4120
|
const allowedWorkstreamIds = workstreamFilter.length > 0 ? workstreamFilter : null;
|
|
3173
|
-
const
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
allowedWorkstreamIds: null,
|
|
3181
|
-
tokenBudget: defaultAutoContinueTokenBudget(),
|
|
3182
|
-
tokensUsed: 0,
|
|
3183
|
-
status: "running",
|
|
3184
|
-
stopReason: null,
|
|
3185
|
-
stopRequested: false,
|
|
3186
|
-
startedAt: now,
|
|
3187
|
-
stoppedAt: null,
|
|
3188
|
-
updatedAt: now,
|
|
3189
|
-
lastError: null,
|
|
3190
|
-
lastTaskId: null,
|
|
3191
|
-
lastRunId: null,
|
|
3192
|
-
activeTaskId: null,
|
|
3193
|
-
activeRunId: null,
|
|
3194
|
-
activeTaskTokenEstimate: null,
|
|
3195
|
-
};
|
|
3196
|
-
run.agentId = agentId;
|
|
3197
|
-
run.includeVerification = includeVerification;
|
|
3198
|
-
run.allowedWorkstreamIds = allowedWorkstreamIds;
|
|
3199
|
-
run.tokenBudget = normalizeTokenBudget(tokenBudget, run.tokenBudget || defaultAutoContinueTokenBudget());
|
|
3200
|
-
run.status = "running";
|
|
3201
|
-
run.stopReason = null;
|
|
3202
|
-
run.stopRequested = false;
|
|
3203
|
-
run.startedAt = now;
|
|
3204
|
-
run.stoppedAt = null;
|
|
3205
|
-
run.updatedAt = now;
|
|
3206
|
-
run.lastError = null;
|
|
3207
|
-
autoContinueRuns.set(initiativeId, run);
|
|
3208
|
-
try {
|
|
3209
|
-
await client.updateEntity("initiative", initiativeId, { status: "active" });
|
|
3210
|
-
}
|
|
3211
|
-
catch {
|
|
3212
|
-
// best effort
|
|
3213
|
-
}
|
|
3214
|
-
try {
|
|
3215
|
-
await updateInitiativeAutoContinueState({ initiativeId, run });
|
|
3216
|
-
}
|
|
3217
|
-
catch {
|
|
3218
|
-
// best effort
|
|
3219
|
-
}
|
|
4121
|
+
const run = await startAutoContinueRun({
|
|
4122
|
+
initiativeId,
|
|
4123
|
+
agentId,
|
|
4124
|
+
tokenBudget,
|
|
4125
|
+
includeVerification,
|
|
4126
|
+
allowedWorkstreamIds,
|
|
4127
|
+
});
|
|
3220
4128
|
sendJson(res, 200, { ok: true, run });
|
|
3221
4129
|
}
|
|
3222
4130
|
catch (err) {
|
|
@@ -3435,11 +4343,38 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3435
4343
|
const entityAction = decodeURIComponent(entityActionMatch[3]);
|
|
3436
4344
|
const payload = await parseJsonRequest(req);
|
|
3437
4345
|
if (entityAction === "delete") {
|
|
3438
|
-
// Delete via status update
|
|
3439
|
-
const
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
4346
|
+
// Delete via status update. Initiatives use `archived` in OrgX.
|
|
4347
|
+
const deleteStatus = entityType.trim().toLowerCase() === "initiative"
|
|
4348
|
+
? "archived"
|
|
4349
|
+
: "deleted";
|
|
4350
|
+
try {
|
|
4351
|
+
const entity = await client.updateEntity(entityType, entityId, {
|
|
4352
|
+
status: deleteStatus,
|
|
4353
|
+
});
|
|
4354
|
+
if (entityType.trim().toLowerCase() === "initiative") {
|
|
4355
|
+
clearLocalInitiativeStatusOverride(entityId);
|
|
4356
|
+
}
|
|
4357
|
+
sendJson(res, 200, { ok: true, entity, deletedAsStatus: deleteStatus });
|
|
4358
|
+
}
|
|
4359
|
+
catch (err) {
|
|
4360
|
+
if (entityType.trim().toLowerCase() === "initiative" &&
|
|
4361
|
+
isUnauthorizedOrgxError(err)) {
|
|
4362
|
+
setLocalInitiativeStatusOverride(entityId, deleteStatus);
|
|
4363
|
+
sendJson(res, 200, {
|
|
4364
|
+
ok: true,
|
|
4365
|
+
localFallback: true,
|
|
4366
|
+
warning: safeErrorMessage(err),
|
|
4367
|
+
entity: {
|
|
4368
|
+
id: entityId,
|
|
4369
|
+
type: entityType,
|
|
4370
|
+
status: deleteStatus,
|
|
4371
|
+
},
|
|
4372
|
+
deletedAsStatus: deleteStatus,
|
|
4373
|
+
});
|
|
4374
|
+
return true;
|
|
4375
|
+
}
|
|
4376
|
+
throw err;
|
|
4377
|
+
}
|
|
3443
4378
|
}
|
|
3444
4379
|
else {
|
|
3445
4380
|
// Map action to status update
|
|
@@ -3458,11 +4393,34 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3458
4393
|
});
|
|
3459
4394
|
return true;
|
|
3460
4395
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
4396
|
+
try {
|
|
4397
|
+
const entity = await client.updateEntity(entityType, entityId, {
|
|
4398
|
+
status: newStatus,
|
|
4399
|
+
...(payload.force ? { force: true } : {}),
|
|
4400
|
+
});
|
|
4401
|
+
if (entityType.trim().toLowerCase() === "initiative") {
|
|
4402
|
+
clearLocalInitiativeStatusOverride(entityId);
|
|
4403
|
+
}
|
|
4404
|
+
sendJson(res, 200, { ok: true, entity });
|
|
4405
|
+
}
|
|
4406
|
+
catch (err) {
|
|
4407
|
+
if (entityType.trim().toLowerCase() === "initiative" &&
|
|
4408
|
+
isUnauthorizedOrgxError(err)) {
|
|
4409
|
+
setLocalInitiativeStatusOverride(entityId, newStatus);
|
|
4410
|
+
sendJson(res, 200, {
|
|
4411
|
+
ok: true,
|
|
4412
|
+
localFallback: true,
|
|
4413
|
+
warning: safeErrorMessage(err),
|
|
4414
|
+
entity: {
|
|
4415
|
+
id: entityId,
|
|
4416
|
+
type: entityType,
|
|
4417
|
+
status: newStatus,
|
|
4418
|
+
},
|
|
4419
|
+
});
|
|
4420
|
+
return true;
|
|
4421
|
+
}
|
|
4422
|
+
throw err;
|
|
4423
|
+
}
|
|
3466
4424
|
}
|
|
3467
4425
|
}
|
|
3468
4426
|
catch (err) {
|
|
@@ -3479,6 +4437,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3479
4437
|
!(runActionMatch && method === "POST") &&
|
|
3480
4438
|
!(isDelegationPreflight && method === "POST") &&
|
|
3481
4439
|
!(isMissionControlAutoAssignmentRoute && method === "POST") &&
|
|
4440
|
+
!(isMissionControlNextUpPlayRoute && method === "POST") &&
|
|
3482
4441
|
!(isEntitiesRoute && method === "POST") &&
|
|
3483
4442
|
!(isEntitiesRoute && method === "PATCH") &&
|
|
3484
4443
|
!(entityActionMatch && method === "POST") &&
|
|
@@ -3660,6 +4619,108 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3660
4619
|
case "onboarding":
|
|
3661
4620
|
sendJson(res, 200, getOnboardingState(await onboarding.getStatus()));
|
|
3662
4621
|
return true;
|
|
4622
|
+
case "hooks/runtime": {
|
|
4623
|
+
if (method !== "POST") {
|
|
4624
|
+
sendJson(res, 405, { ok: false, error: "Use POST /orgx/api/hooks/runtime" });
|
|
4625
|
+
return true;
|
|
4626
|
+
}
|
|
4627
|
+
const expectedHookToken = resolveRuntimeHookToken();
|
|
4628
|
+
const providedHookToken = pickHeaderString(req.headers, ["x-orgx-hook-token", "x-hook-token"]) ??
|
|
4629
|
+
searchParams.get("hook_token") ??
|
|
4630
|
+
searchParams.get("token");
|
|
4631
|
+
if (!providedHookToken || providedHookToken.trim() !== expectedHookToken) {
|
|
4632
|
+
sendJson(res, 401, {
|
|
4633
|
+
ok: false,
|
|
4634
|
+
error: "Invalid hook token",
|
|
4635
|
+
});
|
|
4636
|
+
return true;
|
|
4637
|
+
}
|
|
4638
|
+
try {
|
|
4639
|
+
const payloadRecord = await parseJsonRequest(req);
|
|
4640
|
+
const payload = {
|
|
4641
|
+
source_client: pickString(payloadRecord, ["source_client", "sourceClient"]) ??
|
|
4642
|
+
"unknown",
|
|
4643
|
+
event: pickString(payloadRecord, ["event", "hook_event"]) ?? "heartbeat",
|
|
4644
|
+
run_id: pickString(payloadRecord, ["run_id", "runId", "session_id", "sessionId"]),
|
|
4645
|
+
correlation_id: pickString(payloadRecord, ["correlation_id", "correlationId"]),
|
|
4646
|
+
initiative_id: pickString(payloadRecord, ["initiative_id", "initiativeId"]),
|
|
4647
|
+
workstream_id: pickString(payloadRecord, ["workstream_id", "workstreamId"]),
|
|
4648
|
+
task_id: pickString(payloadRecord, ["task_id", "taskId"]),
|
|
4649
|
+
agent_id: pickString(payloadRecord, ["agent_id", "agentId"]),
|
|
4650
|
+
agent_name: pickString(payloadRecord, ["agent_name", "agentName"]),
|
|
4651
|
+
phase: pickString(payloadRecord, ["phase"]),
|
|
4652
|
+
progress_pct: pickNumber(payloadRecord, ["progress_pct", "progressPct"]) ??
|
|
4653
|
+
null,
|
|
4654
|
+
message: pickString(payloadRecord, ["message", "summary"]),
|
|
4655
|
+
metadata: payloadRecord.metadata && typeof payloadRecord.metadata === "object"
|
|
4656
|
+
? payloadRecord.metadata
|
|
4657
|
+
: null,
|
|
4658
|
+
timestamp: pickString(payloadRecord, ["timestamp", "time", "ts"]),
|
|
4659
|
+
};
|
|
4660
|
+
const instance = upsertRuntimeInstanceFromHook(payload);
|
|
4661
|
+
const fallbackPhaseByEvent = {
|
|
4662
|
+
session_start: "intent",
|
|
4663
|
+
heartbeat: "execution",
|
|
4664
|
+
progress: "execution",
|
|
4665
|
+
task_update: "execution",
|
|
4666
|
+
session_stop: "completed",
|
|
4667
|
+
error: "blocked",
|
|
4668
|
+
};
|
|
4669
|
+
const phase = normalizeHookPhase(payload.phase ??
|
|
4670
|
+
fallbackPhaseByEvent[instance.event] ??
|
|
4671
|
+
"execution");
|
|
4672
|
+
const level = instance.event === "error" ? "error" : phase === "blocked" ? "warn" : "info";
|
|
4673
|
+
const message = payload.message ??
|
|
4674
|
+
`${instance.displayName} ${instance.event.replace(/_/g, " ")}`;
|
|
4675
|
+
let forwarded = false;
|
|
4676
|
+
let forwardError = null;
|
|
4677
|
+
if (instance.initiativeId) {
|
|
4678
|
+
try {
|
|
4679
|
+
await client.emitActivity({
|
|
4680
|
+
initiative_id: instance.initiativeId,
|
|
4681
|
+
run_id: instance.runId ?? undefined,
|
|
4682
|
+
correlation_id: instance.runId
|
|
4683
|
+
? undefined
|
|
4684
|
+
: (instance.correlationId ?? undefined),
|
|
4685
|
+
source_client: normalizeRuntimeSourceForReporting(instance.sourceClient),
|
|
4686
|
+
message,
|
|
4687
|
+
phase,
|
|
4688
|
+
progress_pct: instance.progressPct ?? undefined,
|
|
4689
|
+
level,
|
|
4690
|
+
metadata: {
|
|
4691
|
+
source: "runtime_hook_relay",
|
|
4692
|
+
hook_event: instance.event,
|
|
4693
|
+
instance_id: instance.id,
|
|
4694
|
+
runtime_client: instance.sourceClient,
|
|
4695
|
+
task_id: instance.taskId,
|
|
4696
|
+
workstream_id: instance.workstreamId,
|
|
4697
|
+
...(instance.metadata ?? {}),
|
|
4698
|
+
},
|
|
4699
|
+
});
|
|
4700
|
+
forwarded = true;
|
|
4701
|
+
}
|
|
4702
|
+
catch (err) {
|
|
4703
|
+
forwardError = safeErrorMessage(err);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
sendJson(res, 200, {
|
|
4707
|
+
ok: true,
|
|
4708
|
+
instance_id: instance.id,
|
|
4709
|
+
state: instance.state,
|
|
4710
|
+
last_seen_at: instance.lastHeartbeatAt ?? instance.lastEventAt,
|
|
4711
|
+
run_id: instance.runId ?? null,
|
|
4712
|
+
forwarded,
|
|
4713
|
+
forward_error: forwardError,
|
|
4714
|
+
});
|
|
4715
|
+
}
|
|
4716
|
+
catch (err) {
|
|
4717
|
+
sendJson(res, 500, {
|
|
4718
|
+
ok: false,
|
|
4719
|
+
error: safeErrorMessage(err),
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
return true;
|
|
4723
|
+
}
|
|
3663
4724
|
case "mission-control/auto-continue/status": {
|
|
3664
4725
|
const initiativeId = searchParams.get("initiative_id") ??
|
|
3665
4726
|
searchParams.get("initiativeId") ??
|
|
@@ -3893,7 +4954,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3893
4954
|
return true;
|
|
3894
4955
|
}
|
|
3895
4956
|
try {
|
|
3896
|
-
const graph = await buildMissionControlGraph(client, initiativeId.trim());
|
|
4957
|
+
const graph = applyLocalInitiativeOverrideToGraph(await buildMissionControlGraph(client, initiativeId.trim()));
|
|
3897
4958
|
sendJson(res, 200, graph);
|
|
3898
4959
|
}
|
|
3899
4960
|
catch (err) {
|
|
@@ -3903,6 +4964,29 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3903
4964
|
}
|
|
3904
4965
|
return true;
|
|
3905
4966
|
}
|
|
4967
|
+
case "mission-control/next-up": {
|
|
4968
|
+
const initiativeIdRaw = searchParams.get("initiative_id") ??
|
|
4969
|
+
searchParams.get("initiativeId") ??
|
|
4970
|
+
"";
|
|
4971
|
+
const initiativeId = initiativeIdRaw.trim() || null;
|
|
4972
|
+
try {
|
|
4973
|
+
const queue = await buildNextUpQueue({ initiativeId });
|
|
4974
|
+
sendJson(res, 200, {
|
|
4975
|
+
ok: true,
|
|
4976
|
+
generatedAt: new Date().toISOString(),
|
|
4977
|
+
total: queue.items.length,
|
|
4978
|
+
items: queue.items,
|
|
4979
|
+
degraded: queue.degraded,
|
|
4980
|
+
});
|
|
4981
|
+
}
|
|
4982
|
+
catch (err) {
|
|
4983
|
+
sendJson(res, 500, {
|
|
4984
|
+
ok: false,
|
|
4985
|
+
error: safeErrorMessage(err),
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4988
|
+
return true;
|
|
4989
|
+
}
|
|
3906
4990
|
case "entities": {
|
|
3907
4991
|
if (method === "POST") {
|
|
3908
4992
|
try {
|
|
@@ -3951,10 +5035,15 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3951
5035
|
return true;
|
|
3952
5036
|
}
|
|
3953
5037
|
if (method === "PATCH") {
|
|
5038
|
+
let payload = {};
|
|
5039
|
+
let type = null;
|
|
5040
|
+
let id = null;
|
|
5041
|
+
let requestedStatus = null;
|
|
3954
5042
|
try {
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
5043
|
+
payload = await parseJsonRequest(req);
|
|
5044
|
+
type = pickString(payload, ["type"]);
|
|
5045
|
+
id = pickString(payload, ["id"]);
|
|
5046
|
+
requestedStatus = pickString(payload, ["status"]);
|
|
3958
5047
|
if (!type || !id) {
|
|
3959
5048
|
sendJson(res, 400, {
|
|
3960
5049
|
error: "Both 'type' and 'id' are required for PATCH.",
|
|
@@ -3964,37 +5053,91 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3964
5053
|
const updates = { ...payload };
|
|
3965
5054
|
delete updates.type;
|
|
3966
5055
|
delete updates.id;
|
|
3967
|
-
const
|
|
5056
|
+
const normalizedType = type.trim().toLowerCase();
|
|
5057
|
+
const normalizedUpdates = normalizeEntityMutationPayload(updates);
|
|
5058
|
+
const entity = await client.updateEntity(type, id, normalizedUpdates);
|
|
5059
|
+
if (normalizedType === "initiative") {
|
|
5060
|
+
clearLocalInitiativeStatusOverride(id);
|
|
5061
|
+
}
|
|
3968
5062
|
sendJson(res, 200, { ok: true, entity });
|
|
3969
5063
|
}
|
|
3970
5064
|
catch (err) {
|
|
5065
|
+
if (type?.trim().toLowerCase() === "initiative" &&
|
|
5066
|
+
id &&
|
|
5067
|
+
requestedStatus &&
|
|
5068
|
+
isUnauthorizedOrgxError(err)) {
|
|
5069
|
+
setLocalInitiativeStatusOverride(id, requestedStatus);
|
|
5070
|
+
sendJson(res, 200, {
|
|
5071
|
+
ok: true,
|
|
5072
|
+
localFallback: true,
|
|
5073
|
+
warning: safeErrorMessage(err),
|
|
5074
|
+
entity: {
|
|
5075
|
+
id,
|
|
5076
|
+
type,
|
|
5077
|
+
status: requestedStatus,
|
|
5078
|
+
},
|
|
5079
|
+
});
|
|
5080
|
+
return true;
|
|
5081
|
+
}
|
|
3971
5082
|
sendJson(res, 500, {
|
|
3972
5083
|
error: safeErrorMessage(err),
|
|
3973
5084
|
});
|
|
3974
5085
|
}
|
|
3975
5086
|
return true;
|
|
3976
5087
|
}
|
|
5088
|
+
const type = searchParams.get("type");
|
|
5089
|
+
if (!type) {
|
|
5090
|
+
sendJson(res, 400, {
|
|
5091
|
+
error: "Query parameter 'type' is required for GET /entities.",
|
|
5092
|
+
});
|
|
5093
|
+
return true;
|
|
5094
|
+
}
|
|
5095
|
+
const status = searchParams.get("status") ?? undefined;
|
|
5096
|
+
const initiativeId = searchParams.get("initiative_id") ?? undefined;
|
|
5097
|
+
const limit = searchParams.get("limit")
|
|
5098
|
+
? Number(searchParams.get("limit"))
|
|
5099
|
+
: undefined;
|
|
3977
5100
|
try {
|
|
3978
|
-
const type = searchParams.get("type");
|
|
3979
|
-
if (!type) {
|
|
3980
|
-
sendJson(res, 400, {
|
|
3981
|
-
error: "Query parameter 'type' is required for GET /entities.",
|
|
3982
|
-
});
|
|
3983
|
-
return true;
|
|
3984
|
-
}
|
|
3985
|
-
const status = searchParams.get("status") ?? undefined;
|
|
3986
|
-
const initiativeId = searchParams.get("initiative_id") ?? undefined;
|
|
3987
|
-
const limit = searchParams.get("limit")
|
|
3988
|
-
? Number(searchParams.get("limit"))
|
|
3989
|
-
: undefined;
|
|
3990
5101
|
const data = await client.listEntities(type, {
|
|
3991
5102
|
status,
|
|
3992
5103
|
initiative_id: initiativeId,
|
|
3993
5104
|
limit: Number.isFinite(limit) ? limit : undefined,
|
|
3994
5105
|
});
|
|
5106
|
+
if (type.trim().toLowerCase() === "initiative") {
|
|
5107
|
+
const payload = data;
|
|
5108
|
+
const rows = Array.isArray(payload.data)
|
|
5109
|
+
? payload.data.filter((row) => Boolean(row && typeof row === "object"))
|
|
5110
|
+
: [];
|
|
5111
|
+
sendJson(res, 200, {
|
|
5112
|
+
...payload,
|
|
5113
|
+
data: applyLocalInitiativeOverrides(rows),
|
|
5114
|
+
});
|
|
5115
|
+
return true;
|
|
5116
|
+
}
|
|
3995
5117
|
sendJson(res, 200, data);
|
|
3996
5118
|
}
|
|
3997
5119
|
catch (err) {
|
|
5120
|
+
if (type.trim().toLowerCase() === "initiative" &&
|
|
5121
|
+
isUnauthorizedOrgxError(err)) {
|
|
5122
|
+
const snapshotInitiatives = formatInitiatives(getSnapshot())
|
|
5123
|
+
.map((item) => ({
|
|
5124
|
+
id: item.id,
|
|
5125
|
+
title: item.title,
|
|
5126
|
+
name: item.title,
|
|
5127
|
+
summary: null,
|
|
5128
|
+
status: item.status,
|
|
5129
|
+
progress_pct: item.progress ?? null,
|
|
5130
|
+
created_at: null,
|
|
5131
|
+
updated_at: null,
|
|
5132
|
+
}))
|
|
5133
|
+
.filter((item) => initiativeId ? item.id === initiativeId : true);
|
|
5134
|
+
sendJson(res, 200, {
|
|
5135
|
+
data: applyLocalInitiativeOverrides(snapshotInitiatives),
|
|
5136
|
+
localFallback: true,
|
|
5137
|
+
warning: safeErrorMessage(err),
|
|
5138
|
+
});
|
|
5139
|
+
return true;
|
|
5140
|
+
}
|
|
3998
5141
|
sendJson(res, 500, {
|
|
3999
5142
|
error: safeErrorMessage(err),
|
|
4000
5143
|
});
|
|
@@ -4252,12 +5395,22 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4252
5395
|
catch (err) {
|
|
4253
5396
|
degraded.push(`outbox unavailable (${safeErrorMessage(err)})`);
|
|
4254
5397
|
}
|
|
5398
|
+
let runtimeInstances = listRuntimeInstances({ limit: 320 });
|
|
5399
|
+
if (initiative && initiative.trim().length > 0) {
|
|
5400
|
+
runtimeInstances = runtimeInstances.filter((instance) => instance.initiativeId === initiative);
|
|
5401
|
+
}
|
|
5402
|
+
if (run && run.trim().length > 0) {
|
|
5403
|
+
runtimeInstances = runtimeInstances.filter((instance) => instance.runId === run || instance.correlationId === run);
|
|
5404
|
+
}
|
|
5405
|
+
sessions = enrichSessionsWithRuntime(sessions, runtimeInstances);
|
|
5406
|
+
activity = enrichActivityWithRuntime(activity, runtimeInstances);
|
|
4255
5407
|
sendJson(res, 200, {
|
|
4256
5408
|
sessions,
|
|
4257
5409
|
activity,
|
|
4258
5410
|
handoffs,
|
|
4259
5411
|
decisions,
|
|
4260
5412
|
agents,
|
|
5413
|
+
runtimeInstances,
|
|
4261
5414
|
outbox: outboxStatus,
|
|
4262
5415
|
generatedAt: new Date().toISOString(),
|
|
4263
5416
|
degraded: degraded.length > 0 ? degraded : undefined,
|
|
@@ -4472,7 +5625,30 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4472
5625
|
id,
|
|
4473
5626
|
limit: Number.isFinite(limit) ? limit : undefined,
|
|
4474
5627
|
});
|
|
4475
|
-
|
|
5628
|
+
const payload = data;
|
|
5629
|
+
const initiatives = Array.isArray(payload.initiatives)
|
|
5630
|
+
? payload.initiatives.map((entry) => {
|
|
5631
|
+
if (!entry || typeof entry !== "object")
|
|
5632
|
+
return entry;
|
|
5633
|
+
const row = entry;
|
|
5634
|
+
const initiativeId = pickString(row, ["id"]);
|
|
5635
|
+
if (!initiativeId)
|
|
5636
|
+
return entry;
|
|
5637
|
+
const override = localInitiativeStatusOverrides.get(initiativeId) ?? null;
|
|
5638
|
+
if (!override)
|
|
5639
|
+
return entry;
|
|
5640
|
+
return {
|
|
5641
|
+
...row,
|
|
5642
|
+
status: override.status,
|
|
5643
|
+
updatedAt: pickString(row, ["updatedAt", "updated_at"]) ??
|
|
5644
|
+
override.updatedAt,
|
|
5645
|
+
};
|
|
5646
|
+
})
|
|
5647
|
+
: payload.initiatives;
|
|
5648
|
+
sendJson(res, 200, {
|
|
5649
|
+
...payload,
|
|
5650
|
+
initiatives,
|
|
5651
|
+
});
|
|
4476
5652
|
}
|
|
4477
5653
|
catch (err) {
|
|
4478
5654
|
try {
|
|
@@ -4486,9 +5662,49 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4486
5662
|
if (id && id.trim().length > 0) {
|
|
4487
5663
|
initiatives = initiatives.filter((item) => item.id === id);
|
|
4488
5664
|
}
|
|
5665
|
+
initiatives = initiatives.map((item) => {
|
|
5666
|
+
const override = localInitiativeStatusOverrides.get(item.id) ?? null;
|
|
5667
|
+
if (!override)
|
|
5668
|
+
return item;
|
|
5669
|
+
return {
|
|
5670
|
+
...item,
|
|
5671
|
+
status: override.status,
|
|
5672
|
+
updatedAt: item.updatedAt ?? override.updatedAt,
|
|
5673
|
+
};
|
|
5674
|
+
});
|
|
5675
|
+
const requestedId = id?.trim() ?? "";
|
|
5676
|
+
if (requestedId.length > 0) {
|
|
5677
|
+
const override = localInitiativeStatusOverrides.get(requestedId) ?? null;
|
|
5678
|
+
if (override && !initiatives.some((item) => item.id === requestedId)) {
|
|
5679
|
+
initiatives.push({
|
|
5680
|
+
id: requestedId,
|
|
5681
|
+
title: `Initiative ${requestedId.slice(0, 8)}`,
|
|
5682
|
+
status: override.status,
|
|
5683
|
+
updatedAt: override.updatedAt,
|
|
5684
|
+
sessionCount: 0,
|
|
5685
|
+
activeAgents: 0,
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
else {
|
|
5690
|
+
for (const [initiativeId, override] of localInitiativeStatusOverrides.entries()) {
|
|
5691
|
+
if (initiatives.some((item) => item.id === initiativeId))
|
|
5692
|
+
continue;
|
|
5693
|
+
initiatives.push({
|
|
5694
|
+
id: initiativeId,
|
|
5695
|
+
title: `Initiative ${initiativeId.slice(0, 8)}`,
|
|
5696
|
+
status: override.status,
|
|
5697
|
+
updatedAt: override.updatedAt,
|
|
5698
|
+
sessionCount: 0,
|
|
5699
|
+
activeAgents: 0,
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
4489
5703
|
sendJson(res, 200, {
|
|
4490
5704
|
initiatives: initiatives.slice(0, limit),
|
|
4491
5705
|
total: initiatives.length,
|
|
5706
|
+
localFallback: true,
|
|
5707
|
+
warning: safeErrorMessage(err),
|
|
4492
5708
|
});
|
|
4493
5709
|
}
|
|
4494
5710
|
catch (localErr) {
|
|
@@ -4735,6 +5951,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4735
5951
|
// Requests under /orgx/live
|
|
4736
5952
|
if (url === "/orgx/live" || url.startsWith("/orgx/live/")) {
|
|
4737
5953
|
const subPath = url.replace(/^\/orgx\/live\/?/, "");
|
|
5954
|
+
// Never expose source maps in shipped plugin dashboards.
|
|
5955
|
+
if (/\.map$/i.test(subPath)) {
|
|
5956
|
+
send404(res);
|
|
5957
|
+
return true;
|
|
5958
|
+
}
|
|
4738
5959
|
// Static assets: /orgx/live/assets/* → dashboard/dist/assets/*
|
|
4739
5960
|
// Hashed filenames get long-lived cache
|
|
4740
5961
|
if (subPath.startsWith("assets/")) {
|
|
@@ -4780,4 +6001,3 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4780
6001
|
return true;
|
|
4781
6002
|
};
|
|
4782
6003
|
}
|
|
4783
|
-
//# sourceMappingURL=http-handler.js.map
|