@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.
Files changed (97) hide show
  1. package/dashboard/dist/assets/BqukHQH-.js +8 -0
  2. package/dashboard/dist/assets/CE5pVdev.js +9 -0
  3. package/dashboard/dist/assets/Cpr7n8fE.js +1 -0
  4. package/dashboard/dist/assets/Nip3CrNC.js +1 -0
  5. package/dashboard/dist/assets/TN5wE36J.js +1 -0
  6. package/dashboard/dist/assets/X6IcjS74.js +212 -0
  7. package/dashboard/dist/assets/jyFhCND-.css +1 -0
  8. package/dashboard/dist/index.html +9 -6
  9. package/dist/adapters/outbox.d.ts +0 -1
  10. package/dist/adapters/outbox.js +0 -1
  11. package/dist/agent-context-store.d.ts +0 -1
  12. package/dist/agent-context-store.js +0 -1
  13. package/dist/agent-run-store.d.ts +0 -1
  14. package/dist/agent-run-store.js +0 -1
  15. package/dist/api.d.ts +0 -1
  16. package/dist/api.js +0 -1
  17. package/dist/auth-store.d.ts +0 -1
  18. package/dist/auth-store.js +0 -1
  19. package/dist/byok-store.d.ts +0 -1
  20. package/dist/byok-store.js +0 -1
  21. package/dist/contracts/client.d.ts +0 -1
  22. package/dist/contracts/client.js +0 -1
  23. package/dist/contracts/types.d.ts +33 -1
  24. package/dist/contracts/types.js +0 -1
  25. package/dist/dashboard-api.d.ts +0 -1
  26. package/dist/dashboard-api.js +0 -1
  27. package/dist/fs-utils.d.ts +0 -1
  28. package/dist/fs-utils.js +0 -1
  29. package/dist/http-handler.d.ts +0 -1
  30. package/dist/http-handler.js +1331 -111
  31. package/dist/index.d.ts +0 -1
  32. package/dist/index.js +0 -1
  33. package/dist/local-openclaw.d.ts +0 -1
  34. package/dist/local-openclaw.js +0 -1
  35. package/dist/outbox.d.ts +0 -1
  36. package/dist/outbox.js +0 -1
  37. package/dist/paths.d.ts +0 -1
  38. package/dist/paths.js +0 -1
  39. package/dist/reporting/outbox-replay.d.ts +0 -1
  40. package/dist/reporting/outbox-replay.js +0 -1
  41. package/dist/reporting/rollups.d.ts +0 -1
  42. package/dist/reporting/rollups.js +0 -1
  43. package/dist/runtime-instance-store.d.ts +62 -0
  44. package/dist/runtime-instance-store.js +362 -0
  45. package/dist/snapshot-store.d.ts +0 -1
  46. package/dist/snapshot-store.js +0 -1
  47. package/dist/types.d.ts +0 -1
  48. package/dist/types.js +0 -1
  49. package/package.json +2 -2
  50. package/dashboard/dist/assets/MissionControlView-CthHdl6R.js +0 -1
  51. package/dashboard/dist/assets/SessionInspector-Dq0Z5WMo.js +0 -1
  52. package/dashboard/dist/assets/index-CoLgC4zE.js +0 -11
  53. package/dashboard/dist/assets/index-jfEYE0kO.css +0 -1
  54. package/dashboard/dist/assets/motion-CVDprFZg.js +0 -9
  55. package/dashboard/dist/assets/react-vendor-C2t2w4r2.js +0 -32
  56. package/dashboard/dist/assets/vendor-C-AHK0Ly.js +0 -9
  57. package/dist/adapters/outbox.d.ts.map +0 -1
  58. package/dist/adapters/outbox.js.map +0 -1
  59. package/dist/agent-context-store.d.ts.map +0 -1
  60. package/dist/agent-context-store.js.map +0 -1
  61. package/dist/agent-run-store.d.ts.map +0 -1
  62. package/dist/agent-run-store.js.map +0 -1
  63. package/dist/api.d.ts.map +0 -1
  64. package/dist/api.js.map +0 -1
  65. package/dist/auth-store.d.ts.map +0 -1
  66. package/dist/auth-store.js.map +0 -1
  67. package/dist/byok-store.d.ts.map +0 -1
  68. package/dist/byok-store.js.map +0 -1
  69. package/dist/contracts/client.d.ts.map +0 -1
  70. package/dist/contracts/client.js.map +0 -1
  71. package/dist/contracts/types.d.ts.map +0 -1
  72. package/dist/contracts/types.js.map +0 -1
  73. package/dist/dashboard-api.d.ts.map +0 -1
  74. package/dist/dashboard-api.js.map +0 -1
  75. package/dist/fs-utils.d.ts.map +0 -1
  76. package/dist/fs-utils.js.map +0 -1
  77. package/dist/http-handler.d.ts.map +0 -1
  78. package/dist/http-handler.js.map +0 -1
  79. package/dist/index.d.ts.map +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/local-openclaw.d.ts.map +0 -1
  82. package/dist/local-openclaw.js.map +0 -1
  83. package/dist/mcp-apps/orgx-live.html +0 -690
  84. package/dist/outbox.d.ts.map +0 -1
  85. package/dist/outbox.js.map +0 -1
  86. package/dist/paths.d.ts.map +0 -1
  87. package/dist/paths.js.map +0 -1
  88. package/dist/reporting/outbox-replay.d.ts.map +0 -1
  89. package/dist/reporting/outbox-replay.js.map +0 -1
  90. package/dist/reporting/rollups.d.ts.map +0 -1
  91. package/dist/reporting/rollups.js.map +0 -1
  92. package/dist/snapshot-store.d.ts.map +0 -1
  93. package/dist/snapshot-store.js.map +0 -1
  94. package/dist/types.d.ts.map +0 -1
  95. package/dist/types.js.map +0 -1
  96. /package/dashboard/dist/assets/{tanstack-C-KIc3Wc.js → C-KIc3Wc.js} +0 -0
  97. /package/dashboard/dist/assets/{orgx-logo-Fm0FhtnV.png → Fm0FhtnV.png} +0 -0
@@ -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
- // best effort
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 && !isInProgressStatus(ws.status)) {
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
- let requiresPremiumAutoContinue = false;
3100
- try {
3101
- const agents = await listAgents();
3102
- const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === agentId) ??
3103
- null;
3104
- const agentModel = agentEntry && typeof agentEntry.model === "string"
3105
- ? agentEntry.model
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 now = new Date().toISOString();
3174
- const existing = autoContinueRuns.get(initiativeId) ?? null;
3175
- const run = existing ??
3176
- {
3177
- initiativeId,
3178
- agentId,
3179
- includeVerification: false,
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 entity = await client.updateEntity(entityType, entityId, {
3440
- status: "deleted",
3441
- });
3442
- sendJson(res, 200, { ok: true, entity });
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
- const entity = await client.updateEntity(entityType, entityId, {
3462
- status: newStatus,
3463
- ...(payload.force ? { force: true } : {}),
3464
- });
3465
- sendJson(res, 200, { ok: true, entity });
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
- const payload = await parseJsonRequest(req);
3956
- const type = pickString(payload, ["type"]);
3957
- const id = pickString(payload, ["id"]);
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 entity = await client.updateEntity(type, id, normalizeEntityMutationPayload(updates));
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
- sendJson(res, 200, data);
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