@useorgx/openclaw-plugin 0.7.3 → 0.7.5

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 (123) hide show
  1. package/dashboard/dist/assets/{CZaT3ob_.js → 77gGFBt6.js} +1 -1
  2. package/dashboard/dist/assets/77gGFBt6.js.br +0 -0
  3. package/dashboard/dist/assets/77gGFBt6.js.gz +0 -0
  4. package/dashboard/dist/assets/BBpTN_SR.js +1 -0
  5. package/dashboard/dist/assets/BBpTN_SR.js.br +0 -0
  6. package/dashboard/dist/assets/BBpTN_SR.js.gz +0 -0
  7. package/dashboard/dist/assets/BTAEErUY.js +1 -0
  8. package/dashboard/dist/assets/BTAEErUY.js.br +0 -0
  9. package/dashboard/dist/assets/BTAEErUY.js.gz +0 -0
  10. package/dashboard/dist/assets/BVShoyjA.js +1 -0
  11. package/dashboard/dist/assets/BVShoyjA.js.br +0 -0
  12. package/dashboard/dist/assets/BVShoyjA.js.gz +0 -0
  13. package/dashboard/dist/assets/{CC63EwFD.js → BgcAY5rE.js} +1 -1
  14. package/dashboard/dist/assets/BgcAY5rE.js.br +0 -0
  15. package/dashboard/dist/assets/BgcAY5rE.js.gz +0 -0
  16. package/dashboard/dist/assets/{rttbDbEx.js → C-PAoJF-.js} +1 -1
  17. package/dashboard/dist/assets/C-PAoJF-.js.br +0 -0
  18. package/dashboard/dist/assets/C-PAoJF-.js.gz +0 -0
  19. package/dashboard/dist/assets/C0nA-iUG.js +1 -0
  20. package/dashboard/dist/assets/C0nA-iUG.js.br +0 -0
  21. package/dashboard/dist/assets/C0nA-iUG.js.gz +0 -0
  22. package/dashboard/dist/assets/C6GO-FKy.js +1 -0
  23. package/dashboard/dist/assets/C6GO-FKy.js.br +0 -0
  24. package/dashboard/dist/assets/C6GO-FKy.js.gz +0 -0
  25. package/dashboard/dist/assets/CFwPph5U.js +1 -0
  26. package/dashboard/dist/assets/CFwPph5U.js.br +0 -0
  27. package/dashboard/dist/assets/CFwPph5U.js.gz +0 -0
  28. package/dashboard/dist/assets/CPjsbbgZ.js +212 -0
  29. package/dashboard/dist/assets/CPjsbbgZ.js.br +0 -0
  30. package/dashboard/dist/assets/CPjsbbgZ.js.gz +0 -0
  31. package/dashboard/dist/assets/{IUexzymk.js → CSr2ZnTV.js} +1 -1
  32. package/dashboard/dist/assets/CSr2ZnTV.js.br +0 -0
  33. package/dashboard/dist/assets/CSr2ZnTV.js.gz +0 -0
  34. package/dashboard/dist/assets/CgQDT6yL.js +1 -0
  35. package/dashboard/dist/assets/CgQDT6yL.js.br +0 -0
  36. package/dashboard/dist/assets/CgQDT6yL.js.gz +0 -0
  37. package/dashboard/dist/assets/{B6wPWJ35.js → CnitK1MX.js} +1 -1
  38. package/dashboard/dist/assets/CnitK1MX.js.br +0 -0
  39. package/dashboard/dist/assets/CnitK1MX.js.gz +0 -0
  40. package/dashboard/dist/assets/{CgaottFX.js → D7DHFX0D.js} +1 -1
  41. package/dashboard/dist/assets/D7DHFX0D.js.br +0 -0
  42. package/dashboard/dist/assets/D7DHFX0D.js.gz +0 -0
  43. package/dashboard/dist/assets/DEip7uko.js +1 -0
  44. package/dashboard/dist/assets/DEip7uko.js.br +0 -0
  45. package/dashboard/dist/assets/DEip7uko.js.gz +0 -0
  46. package/dashboard/dist/assets/DHUSLc01.css +1 -0
  47. package/dashboard/dist/assets/DHUSLc01.css.br +0 -0
  48. package/dashboard/dist/assets/DHUSLc01.css.gz +0 -0
  49. package/dashboard/dist/assets/{B5zYRHc3.js → DOFL9l8s.js} +1 -1
  50. package/dashboard/dist/assets/DOFL9l8s.js.br +0 -0
  51. package/dashboard/dist/assets/DOFL9l8s.js.gz +0 -0
  52. package/dashboard/dist/assets/{8dksYiq4.js → DpuQm1oF.js} +1 -1
  53. package/dashboard/dist/assets/DpuQm1oF.js.br +0 -0
  54. package/dashboard/dist/assets/DpuQm1oF.js.gz +0 -0
  55. package/dashboard/dist/assets/{D8JNX8kq.js → DxKG5zy8.js} +2 -2
  56. package/dashboard/dist/assets/DxKG5zy8.js.br +0 -0
  57. package/dashboard/dist/assets/DxKG5zy8.js.gz +0 -0
  58. package/dashboard/dist/assets/tcEHYcbW.js +1 -0
  59. package/dashboard/dist/assets/tcEHYcbW.js.br +0 -0
  60. package/dashboard/dist/assets/tcEHYcbW.js.gz +0 -0
  61. package/dashboard/dist/index.html +2 -2
  62. package/dashboard/dist/index.html.br +0 -0
  63. package/dashboard/dist/index.html.gz +0 -0
  64. package/dist/http/helpers/auto-continue-engine.js +29 -11
  65. package/dist/http/helpers/autopilot-slice-utils.js +7 -10
  66. package/dist/http/helpers/openclaw-cli.js +2 -2
  67. package/dist/http/index.js +161 -4
  68. package/dist/http/routes/live-legacy.d.ts +2 -0
  69. package/dist/http/routes/live-legacy.js +186 -3
  70. package/dist/http/routes/live-misc.d.ts +1 -0
  71. package/dist/http/routes/live-misc.js +76 -0
  72. package/dist/http/routes/mission-control-read.d.ts +4 -0
  73. package/dist/http/routes/mission-control-read.js +114 -63
  74. package/dist/http/routes/usage.js +15 -3
  75. package/dist/json-utils.js +5 -1
  76. package/dist/openclaw-settings.js +3 -2
  77. package/dist/sync/outbox-replay.js +17 -8
  78. package/package.json +6 -1
  79. package/dashboard/dist/assets/6mILZQ2a.js +0 -1
  80. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  81. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  82. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  83. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  84. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  85. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  86. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  87. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  88. package/dashboard/dist/assets/BWEwjt1W.js +0 -1
  89. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  90. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  91. package/dashboard/dist/assets/BzRbDCAD.css +0 -1
  92. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  93. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  94. package/dashboard/dist/assets/C8uM3AX8.js +0 -1
  95. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  96. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  97. package/dashboard/dist/assets/C9jy61eu.js +0 -212
  98. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  99. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  100. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  101. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  102. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  103. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  104. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  105. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  106. package/dashboard/dist/assets/CzCxAZlW.js +0 -1
  107. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  108. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  109. package/dashboard/dist/assets/D3iMTYEj.js +0 -1
  110. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  111. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  112. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  113. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  114. package/dashboard/dist/assets/DnA8dpj6.js +0 -1
  115. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  116. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  117. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  118. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  119. package/dashboard/dist/assets/ic2FaMnh.js +0 -1
  120. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  121. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  122. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  123. package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
@@ -18,10 +18,12 @@
18
18
  * /orgx/api/runs/:id/actions/:action → run control action
19
19
  */
20
20
  import { readFileSync, existsSync, readdirSync, statSync, openSync, readSync, closeSync, } from "node:fs";
21
+ import { execFile } from "node:child_process";
21
22
  import { homedir } from "node:os";
22
23
  import { join, dirname, extname, normalize, resolve, relative, sep } from "node:path";
23
24
  import { fileURLToPath } from "node:url";
24
25
  import { randomUUID } from "node:crypto";
26
+ import { promisify } from "node:util";
25
27
  import { readNextUpQueuePins, removeNextUpQueuePin, setNextUpQueuePinOrder, suppressNextUpQueueItem, upsertNextUpQueuePin, } from "../next-up-queue-store.js";
26
28
  import { formatStatus, formatAgents, formatActivity, formatInitiatives, getOnboardingState, } from "../dashboard-api.js";
27
29
  import { loadLocalOpenClawSnapshot, loadLocalTurnDetail, toLocalLiveActivity, toLocalLiveAgents, toLocalLiveInitiatives, toLocalSessionTree, } from "../local-openclaw.js";
@@ -1219,6 +1221,8 @@ const IMMUTABLE_FILE_CACHE = new Map();
1219
1221
  const IMMUTABLE_FILE_CACHE_MAX = 128;
1220
1222
  const FILE_PREVIEW_MAX_BYTES = 1_000_000;
1221
1223
  const FILE_PREVIEW_MAX_DIR_ENTRIES = 300;
1224
+ const AUTOPILOT_LOGS_DIR = join(homedir(), ".config", "useorgx", "openclaw-plugin", "autopilot-logs");
1225
+ const execFileAsync = promisify(execFile);
1222
1226
  function sendJson(res, status, data) {
1223
1227
  const body = JSON.stringify(data);
1224
1228
  res.writeHead(status, {
@@ -1270,6 +1274,50 @@ function resolveFilesystemOpenPath(rawPath) {
1270
1274
  }
1271
1275
  return resolve(process.cwd(), value);
1272
1276
  }
1277
+ function resolveAutopilotLogCandidates(runId) {
1278
+ const sanitizedRunId = runId.trim().replaceAll(/[\\/]/g, "");
1279
+ if (!sanitizedRunId)
1280
+ return [];
1281
+ const directCandidates = [
1282
+ join(AUTOPILOT_LOGS_DIR, `${sanitizedRunId}.log`),
1283
+ join(AUTOPILOT_LOGS_DIR, `${sanitizedRunId}.output.json`),
1284
+ ];
1285
+ if (!existsSync(AUTOPILOT_LOGS_DIR)) {
1286
+ return directCandidates;
1287
+ }
1288
+ const discovered = readdirSync(AUTOPILOT_LOGS_DIR)
1289
+ .filter((entry) => entry.startsWith(`${sanitizedRunId}.`))
1290
+ .map((entry) => join(AUTOPILOT_LOGS_DIR, entry))
1291
+ .filter((entry) => entry.endsWith(".log") || entry.endsWith(".output.json"));
1292
+ return dedupeStrings([...directCandidates, ...discovered]);
1293
+ }
1294
+ async function openPathInTerminal(pathname) {
1295
+ const resolvedPath = resolve(pathname);
1296
+ if (process.platform === "darwin") {
1297
+ const escapedPath = resolvedPath
1298
+ .replaceAll("\\", "\\\\")
1299
+ .replaceAll('"', '\\"');
1300
+ await execFileAsync("osascript", [
1301
+ "-e",
1302
+ 'tell application "Terminal" to activate',
1303
+ "-e",
1304
+ `tell application "Terminal" to do script "tail -f \\"${escapedPath}\\""`,
1305
+ ]);
1306
+ return;
1307
+ }
1308
+ if (process.platform === "win32") {
1309
+ await execFileAsync("cmd", ["/c", "start", "", resolvedPath]);
1310
+ return;
1311
+ }
1312
+ try {
1313
+ await execFileAsync("gnome-terminal", ["--", "tail", "-f", resolvedPath]);
1314
+ return;
1315
+ }
1316
+ catch {
1317
+ // Fallback to generic opener when no terminal app is available.
1318
+ }
1319
+ await execFileAsync("xdg-open", [resolvedPath]);
1320
+ }
1273
1321
  function readFilePreview(pathname, totalBytes) {
1274
1322
  if (totalBytes <= 0) {
1275
1323
  return { previewBuffer: Buffer.alloc(0), truncated: false };
@@ -3328,6 +3376,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3328
3376
  loadLocalOpenClawSnapshot,
3329
3377
  toLocalLiveAgents,
3330
3378
  toLocalLiveInitiatives,
3379
+ buildMissionControlGraph: (initiativeId) => buildMissionControlGraph(client, initiativeId),
3331
3380
  localInitiativeStatusOverrides,
3332
3381
  mapDecisionEntity,
3333
3382
  sendJson,
@@ -3369,6 +3418,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3369
3418
  readFilePreview,
3370
3419
  filePreviewMaxBytes: FILE_PREVIEW_MAX_BYTES,
3371
3420
  filePreviewMaxDirEntries: FILE_PREVIEW_MAX_DIR_ENTRIES,
3421
+ resolveAutopilotLogCandidates,
3422
+ openPathInTerminal,
3372
3423
  securityHeaders: SECURITY_HEADERS,
3373
3424
  corsHeaders: CORS_HEADERS,
3374
3425
  config: {
@@ -3509,10 +3560,116 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3509
3560
  }
3510
3561
  return [];
3511
3562
  },
3512
- getBlockerEvents: () => {
3513
- // Extract blocker events from recent activity
3514
- // In future, this will read from a dedicated blocker store
3515
- return [];
3563
+ getBlockerEvents: (workspaceId) => {
3564
+ const normalizedWorkspaceId = (workspaceId ?? "").trim();
3565
+ const scopedKeys = normalizedWorkspaceId
3566
+ ? [
3567
+ `live-snapshot:${normalizedWorkspaceId}`,
3568
+ `live-snapshot-v2:${normalizedWorkspaceId}`,
3569
+ `dashboard-bundle:${normalizedWorkspaceId}`,
3570
+ ]
3571
+ : [];
3572
+ const fallbackKeys = ["live-snapshot", "dashboard-bundle", "live-snapshot-v2"];
3573
+ const keys = [...scopedKeys, ...fallbackKeys];
3574
+ const seen = new Set();
3575
+ const mapped = [];
3576
+ const deriveFailureType = (eventNameRaw, actionTypeRaw) => {
3577
+ const eventName = (eventNameRaw ?? "").trim().toLowerCase();
3578
+ const actionType = (actionTypeRaw ?? "").trim().toLowerCase();
3579
+ const signature = `${eventName} ${actionType}`;
3580
+ if (!signature.trim())
3581
+ return null;
3582
+ if (signature.includes("status_updates_buffered"))
3583
+ return "status_updates_buffered";
3584
+ if (signature.includes("question_answer_failed"))
3585
+ return "question_answer_failed";
3586
+ if (signature.includes("spawn_guard_rate_limited"))
3587
+ return "spawn_guard_rate_limited";
3588
+ if (signature.includes("spawn_guard_blocked"))
3589
+ return "spawn_guard_blocked";
3590
+ if (signature.includes("mcp_handshake_failure") || signature.includes("mcp_handshake")) {
3591
+ return "mcp_handshake_failure";
3592
+ }
3593
+ if (signature.includes("worker_exit_no_output") ||
3594
+ signature.includes("worker_exit_without_structured_output") ||
3595
+ signature.includes("no_structured_output")) {
3596
+ return "worker_exit_no_output";
3597
+ }
3598
+ if (signature.includes("workspace_conflict"))
3599
+ return "workspace_conflict";
3600
+ if (signature.includes("budget_exhausted"))
3601
+ return "budget_exhausted";
3602
+ if (signature.includes("stale_blocked_workstream"))
3603
+ return "stale_blocked_workstream";
3604
+ return null;
3605
+ };
3606
+ try {
3607
+ for (const key of keys) {
3608
+ const cached = readSnapshotResponseCache(key);
3609
+ if (!cached || typeof cached !== "object")
3610
+ continue;
3611
+ const activityRaw = cached.activity;
3612
+ if (!Array.isArray(activityRaw))
3613
+ continue;
3614
+ for (const entry of activityRaw) {
3615
+ if (!entry || typeof entry !== "object")
3616
+ continue;
3617
+ const activityRecord = entry;
3618
+ const metadataRecord = activityRecord.metadata && typeof activityRecord.metadata === "object"
3619
+ ? activityRecord.metadata
3620
+ : {};
3621
+ const failureType = deriveFailureType(pickString(metadataRecord, ["event", "event_name"]), pickString(metadataRecord, ["action_type", "actionType"]));
3622
+ if (!failureType)
3623
+ continue;
3624
+ const runId = pickString(metadataRecord, ["run_id", "source_run_id"]) ?? pickString(activityRecord, ["runId"]);
3625
+ const workstreamId = pickString(metadataRecord, ["workstream_id", "source_stream_id"]) ??
3626
+ pickString(activityRecord, ["workstreamId"]);
3627
+ const taskId = pickString(metadataRecord, ["task_id"]) ?? pickString(activityRecord, ["taskId"]);
3628
+ const initiativeId = pickString(metadataRecord, ["initiative_id"]) ??
3629
+ pickString(activityRecord, ["initiativeId"]);
3630
+ const timestamp = pickString(activityRecord, ["timestamp"]) ??
3631
+ pickString(metadataRecord, ["timestamp", "created_at"]);
3632
+ const dedupeId = pickString(activityRecord, ["id"]) ??
3633
+ `${failureType}:${initiativeId ?? "unknown"}:${workstreamId ?? "unknown"}:${runId ?? "unknown"}:${timestamp ?? "unknown"}`;
3634
+ if (seen.has(dedupeId))
3635
+ continue;
3636
+ seen.add(dedupeId);
3637
+ mapped.push({
3638
+ id: dedupeId,
3639
+ failureType,
3640
+ reason: pickString(metadataRecord, ["error", "reason", "message"]) ??
3641
+ pickString(activityRecord, ["description", "summary", "title"]),
3642
+ provider: pickString(metadataRecord, ["provider"]),
3643
+ initiativeId,
3644
+ initiativeTitle: pickString(metadataRecord, ["initiative_title"]) ??
3645
+ pickString(activityRecord, ["initiativeTitle"]),
3646
+ workstreamId,
3647
+ workstreamTitle: pickString(metadataRecord, ["workstream_title"]) ??
3648
+ pickString(activityRecord, ["workstreamTitle"]),
3649
+ taskId,
3650
+ taskTitle: pickString(metadataRecord, ["task_title"]) ?? pickString(activityRecord, ["taskTitle"]),
3651
+ agentId: pickString(metadataRecord, ["agent_id", "executor_agent_id"]) ??
3652
+ pickString(activityRecord, ["executorAgentId", "agentId"]),
3653
+ domain: pickString(metadataRecord, ["domain", "executor_domain"]),
3654
+ sourceSystem: pickString(metadataRecord, ["source_system", "source"]) ?? "openclaw",
3655
+ runId,
3656
+ logPath: pickString(metadataRecord, ["log_path"]),
3657
+ outputPath: pickString(metadataRecord, ["output_path"]),
3658
+ metadata: metadataRecord,
3659
+ timestamp: timestamp ?? undefined,
3660
+ });
3661
+ }
3662
+ }
3663
+ }
3664
+ catch {
3665
+ // best effort
3666
+ }
3667
+ mapped.sort((a, b) => {
3668
+ const aTs = Date.parse(String(a.timestamp ?? "")) || 0;
3669
+ const bTs = Date.parse(String(b.timestamp ?? "")) || 0;
3670
+ return bTs - aTs;
3671
+ });
3672
+ return mapped;
3516
3673
  },
3517
3674
  resolveDecisionAction: async (decisionId, action, note, optionId) => {
3518
3675
  try {
@@ -101,6 +101,8 @@ type RegisterLiveLegacyRoutesDeps<TRes extends RouteResLike> = {
101
101
  };
102
102
  filePreviewMaxBytes: number;
103
103
  filePreviewMaxDirEntries: number;
104
+ resolveAutopilotLogCandidates?: (runId: string) => string[];
105
+ openPathInTerminal?: (path: string) => Promise<void>;
104
106
  securityHeaders: Record<string, string>;
105
107
  corsHeaders: Record<string, string>;
106
108
  config: {
@@ -1,4 +1,30 @@
1
1
  export function registerLiveLegacyRoutes(router, deps) {
2
+ function toContextBundle(value) {
3
+ return {
4
+ agents: value.agents ?? {},
5
+ runs: value.runs ?? {},
6
+ };
7
+ }
8
+ function parseWorkspaceScope(query) {
9
+ const candidates = [
10
+ query.get("workspace_id"),
11
+ query.get("workspaceId"),
12
+ query.get("command_center_id"),
13
+ query.get("commandCenterId"),
14
+ query.get("center"),
15
+ query.get("project_id"),
16
+ query.get("projectId"),
17
+ ];
18
+ for (const candidate of candidates) {
19
+ if (typeof candidate !== "string")
20
+ continue;
21
+ const normalized = candidate.trim();
22
+ if (!normalized || normalized.toLowerCase() === "all")
23
+ continue;
24
+ return normalized;
25
+ }
26
+ return null;
27
+ }
2
28
  const sendDeprecated = (res, endpoint, replacement) => {
3
29
  deps.sendJson(res, 410, {
4
30
  error: `${endpoint} is deprecated`,
@@ -14,9 +40,69 @@ export function registerLiveLegacyRoutes(router, deps) {
14
40
  router.add("GET", "live/sessions", async ({ query, res }) => renderLiveSessions(query, res), "Legacy live sessions endpoint");
15
41
  router.add("HEAD", "live/sessions", async ({ query, res }) => renderLiveSessions(query, res), "Legacy live sessions endpoint (HEAD)");
16
42
  async function renderLiveActivityPage(query, res) {
17
- sendDeprecated(res, "/orgx/api/live/activity/page", "/orgx/api/live/snapshot-v2");
18
- void query;
19
- return;
43
+ const run = query.get("run");
44
+ const since = query.get("since");
45
+ const until = query.get("until");
46
+ const cursor = query.get("cursor");
47
+ const projectId = parseWorkspaceScope(query);
48
+ const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
49
+ const limit = Number.isFinite(limitRaw)
50
+ ? Math.max(1, Math.floor(Number(limitRaw)))
51
+ : 200;
52
+ let page = deps.listActivityPage({
53
+ limit,
54
+ runId: run,
55
+ since,
56
+ until,
57
+ cursor,
58
+ });
59
+ {
60
+ const ctx = toContextBundle(deps.readAgentContexts());
61
+ page = {
62
+ ...page,
63
+ activities: deps.applyAgentContextsToActivity(page.activities, ctx),
64
+ };
65
+ }
66
+ const warmKey = `${projectId ?? ""}::${run ?? ""}::${since ?? ""}::${until ?? ""}`;
67
+ const lastWarmAt = deps.activityWarmByKey.get(warmKey) ?? 0;
68
+ const shouldWarm = Date.now() - lastWarmAt > deps.activityWarmThrottleMs &&
69
+ (cursor === null || cursor === "" || page.activities.length < limit);
70
+ if (shouldWarm) {
71
+ deps.activityWarmByKey.set(warmKey, Date.now());
72
+ try {
73
+ const warmLimit = Math.max(800, Math.min(6_000, limit * 10));
74
+ const data = await deps.getLiveActivity({
75
+ run,
76
+ since,
77
+ projectId,
78
+ limit: warmLimit,
79
+ });
80
+ const remote = Array.isArray(data.activities) ? data.activities : [];
81
+ {
82
+ const ctx = toContextBundle(deps.readAgentContexts());
83
+ const withContexts = deps.applyAgentContextsToActivity(remote, ctx);
84
+ deps.appendActivityItems(withContexts);
85
+ }
86
+ page = deps.listActivityPage({
87
+ limit,
88
+ runId: run,
89
+ since,
90
+ until,
91
+ cursor,
92
+ });
93
+ {
94
+ const ctx = toContextBundle(deps.readAgentContexts());
95
+ page = {
96
+ ...page,
97
+ activities: deps.applyAgentContextsToActivity(page.activities, ctx),
98
+ };
99
+ }
100
+ }
101
+ catch {
102
+ // best effort
103
+ }
104
+ }
105
+ deps.sendJson(res, 200, page);
20
106
  }
21
107
  router.add("GET", "live/activity/page", async ({ query, res }) => renderLiveActivityPage(query, res), "Paginated live activity");
22
108
  router.add("HEAD", "live/activity/page", async ({ query, res }) => renderLiveActivityPage(query, res), "Paginated live activity (HEAD)");
@@ -101,6 +187,64 @@ export function registerLiveLegacyRoutes(router, deps) {
101
187
  router.add("*", "live/filesystem/open", ({ res }) => {
102
188
  deps.sendJson(res, 405, { error: "Use GET /orgx/api/live/filesystem/open?path=..." });
103
189
  }, "Reject unsupported methods for live/filesystem/open");
190
+ router.add("POST", "live/terminal/open", async ({ body, res }) => {
191
+ const payload = body && typeof body === "object" && !Array.isArray(body)
192
+ ? body
193
+ : {};
194
+ const runId = typeof payload.runId === "string" ? payload.runId.trim() : "";
195
+ const rawPath = typeof payload.path === "string" ? payload.path.trim() : "";
196
+ if (!runId && !rawPath) {
197
+ deps.sendJson(res, 400, {
198
+ ok: false,
199
+ error: "runId or path is required",
200
+ });
201
+ return;
202
+ }
203
+ let resolvedPath = "";
204
+ if (rawPath) {
205
+ resolvedPath = deps.resolveFilesystemOpenPath(rawPath);
206
+ }
207
+ else if (runId) {
208
+ const candidates = deps.resolveAutopilotLogCandidates
209
+ ? deps.resolveAutopilotLogCandidates(runId)
210
+ : [];
211
+ resolvedPath =
212
+ candidates.find((candidate) => deps.existsSync(candidate)) ?? "";
213
+ }
214
+ if (!resolvedPath || !deps.existsSync(resolvedPath)) {
215
+ deps.sendJson(res, 404, {
216
+ ok: false,
217
+ error: "Terminal target not found",
218
+ });
219
+ return;
220
+ }
221
+ if (!deps.openPathInTerminal) {
222
+ deps.sendJson(res, 501, {
223
+ ok: false,
224
+ error: "Terminal open is unavailable in this runtime.",
225
+ });
226
+ return;
227
+ }
228
+ try {
229
+ await deps.openPathInTerminal(resolvedPath);
230
+ deps.sendJson(res, 200, {
231
+ ok: true,
232
+ path: resolvedPath,
233
+ });
234
+ }
235
+ catch (err) {
236
+ deps.sendJson(res, 500, {
237
+ ok: false,
238
+ error: deps.safeErrorMessage(err),
239
+ });
240
+ }
241
+ }, "Open run logs in local terminal");
242
+ router.add("*", "live/terminal/open", ({ res }) => {
243
+ deps.sendJson(res, 405, {
244
+ ok: false,
245
+ error: "Use POST /orgx/api/live/terminal/open",
246
+ });
247
+ }, "Reject unsupported methods for live/terminal/open");
104
248
  async function renderLiveStream(query, req, res) {
105
249
  sendDeprecated(res, "/orgx/api/live/stream", "/orgx/api/live/snapshot-v2");
106
250
  void query;
@@ -109,4 +253,43 @@ export function registerLiveLegacyRoutes(router, deps) {
109
253
  }
110
254
  router.add("GET", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream");
111
255
  router.add("HEAD", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream (HEAD)");
256
+ router.add("POST", "live/terminal/open", async ({ body, res }) => {
257
+ try {
258
+ const payload = (typeof body === "string" ? JSON.parse(body) : body);
259
+ const runId = payload?.runId;
260
+ if (!runId) {
261
+ deps.sendJson(res, 400, { error: "runId is required" });
262
+ return;
263
+ }
264
+ const os = await import("os");
265
+ const cp = await import("child_process");
266
+ const path = await import("path");
267
+ const homedir = os.homedir();
268
+ const logPath = path.join(homedir, ".config", "useorgx", "openclaw-plugin", "autopilot-logs", `${runId}.log`);
269
+ if (!deps.existsSync(logPath)) {
270
+ deps.sendJson(res, 404, { error: `Log file not found: ${logPath}` });
271
+ return;
272
+ }
273
+ const shellPath = logPath.replaceAll("'", "'\\''");
274
+ let command = "";
275
+ if (os.platform() === "darwin") {
276
+ command = `osascript -e 'tell app "Terminal" to do script "tail -f \\"${shellPath}\\""'`;
277
+ }
278
+ else if (os.platform() === "win32") {
279
+ command = `start cmd.exe /k "tail -f \\"${shellPath}\\""`;
280
+ }
281
+ else {
282
+ command = `gnome-terminal -- bash -c "tail -f \\"${shellPath}\\"; exec bash"`;
283
+ }
284
+ cp.exec(command, (error) => {
285
+ if (error) {
286
+ console.error("Failed to open terminal:", error);
287
+ }
288
+ });
289
+ deps.sendJson(res, 200, { success: true });
290
+ }
291
+ catch (err) {
292
+ deps.sendJson(res, 500, { error: deps.safeErrorMessage(err) });
293
+ }
294
+ }, "Open run session in native terminal");
112
295
  }
@@ -71,6 +71,7 @@ type RegisterLiveMiscRoutesDeps<TReq, TRes> = {
71
71
  status: string;
72
72
  updatedAt: string;
73
73
  }>;
74
+ buildMissionControlGraph?: (initiativeId: string) => Promise<unknown>;
74
75
  mapDecisionEntity: (entry: Entity) => {
75
76
  waitingMinutes: number;
76
77
  };
@@ -1,4 +1,5 @@
1
1
  import { resolveWorkspaceScope, workspaceScopeFromHeaders, } from "../helpers/workspace-scope.js";
2
+ import { summarizeTaskStatuses } from "../../reporting/rollups.js";
2
3
  function asRecord(input) {
3
4
  if (!input || typeof input !== "object" || Array.isArray(input))
4
5
  return null;
@@ -423,4 +424,79 @@ export function registerLiveMiscRoutes(router, deps) {
423
424
  }
424
425
  router.add("GET", "handoffs", async ({ res }) => renderHandoffs(res), "Get handoffs");
425
426
  router.add("HEAD", "handoffs", async ({ res }) => renderHandoffs(res), "Get handoffs (HEAD)");
427
+ router.add("GET", "live/initiatives/summary", async ({ query, res, req }) => {
428
+ try {
429
+ const scope = resolveWorkspaceScope(query, workspaceScopeFromHeaders(req?.headers), { allowProjectScope: false });
430
+ if (scope.error) {
431
+ deps.sendJson(res, 400, { error: scope.error });
432
+ return;
433
+ }
434
+ const workspaceId = scope.workspaceId;
435
+ const projectInitiatives = await resolveProjectInitiativeSet(workspaceId);
436
+ // Load initiatives
437
+ const local = deps.toLocalLiveInitiatives(await deps.loadLocalOpenClawSnapshot(240));
438
+ let initiatives = local.initiatives;
439
+ if (projectInitiatives) {
440
+ initiatives = initiatives.filter((item) => projectInitiatives.has(item.id));
441
+ }
442
+ const perInitiative = [];
443
+ let totalTasks = 0;
444
+ let doneTasks = 0;
445
+ let blockedCount = 0;
446
+ let activeCount = 0;
447
+ let totalActiveAgents = 0;
448
+ for (const initiative of initiatives) {
449
+ totalActiveAgents += initiative.activeAgents;
450
+ // Try to get task statuses from graph
451
+ let taskStatuses = [];
452
+ if (deps.buildMissionControlGraph) {
453
+ try {
454
+ const graphRaw = await deps.buildMissionControlGraph(initiative.id);
455
+ const graph = asRecord(graphRaw);
456
+ const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
457
+ for (const nodeEntry of nodes) {
458
+ const node = asRecord(nodeEntry);
459
+ if (!node)
460
+ continue;
461
+ const type = pickStringFromRecord(node, ["type"]);
462
+ if (type !== "task")
463
+ continue;
464
+ const status = pickStringFromRecord(node, ["status"]) ?? "todo";
465
+ taskStatuses.push(status);
466
+ }
467
+ }
468
+ catch {
469
+ // Graph unavailable — fall back to empty counts
470
+ }
471
+ }
472
+ const counts = summarizeTaskStatuses(taskStatuses);
473
+ const progressPct = counts.total > 0 ? Math.round((counts.done / counts.total) * 100) : 0;
474
+ perInitiative.push({
475
+ id: initiative.id,
476
+ taskCounts: counts,
477
+ progressPct,
478
+ });
479
+ totalTasks += counts.total;
480
+ doneTasks += counts.done;
481
+ blockedCount += counts.blocked;
482
+ activeCount += counts.active;
483
+ }
484
+ const completionPercent = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;
485
+ deps.sendJson(res, 200, {
486
+ initiatives: perInitiative,
487
+ aggregate: {
488
+ totalTasks,
489
+ doneTasks,
490
+ blockedCount,
491
+ activeAgents: totalActiveAgents,
492
+ completionPercent,
493
+ },
494
+ });
495
+ }
496
+ catch (err) {
497
+ deps.sendJson(res, 500, {
498
+ error: deps.safeErrorMessage(err),
499
+ });
500
+ }
501
+ }, "Get live initiatives summary");
426
502
  }
@@ -41,6 +41,10 @@ type NextUpQueueItem = {
41
41
  pinnedRank?: number | null;
42
42
  compositeScore?: number;
43
43
  scoringTier?: "urgent" | "ready" | "waiting" | "deferred";
44
+ objectiveScore?: number;
45
+ roiPerToken?: number;
46
+ expectedTokens?: number;
47
+ expectedValueUsd?: number;
44
48
  updatedAt?: string | null;
45
49
  };
46
50
  type SliceRunnerAgent = {