@useorgx/openclaw-plugin 0.7.15 → 0.7.16

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 (126) hide show
  1. package/dashboard/dist/assets/8pbG6uLK.js +1 -0
  2. package/dashboard/dist/assets/8pbG6uLK.js.br +0 -0
  3. package/dashboard/dist/assets/8pbG6uLK.js.gz +0 -0
  4. package/dashboard/dist/assets/B1LENRC8.js +212 -0
  5. package/dashboard/dist/assets/B1LENRC8.js.br +0 -0
  6. package/dashboard/dist/assets/B1LENRC8.js.gz +0 -0
  7. package/dashboard/dist/assets/{gZr_xKlA.js → BM75sh1f.js} +1 -1
  8. package/dashboard/dist/assets/BM75sh1f.js.br +0 -0
  9. package/dashboard/dist/assets/BM75sh1f.js.gz +0 -0
  10. package/dashboard/dist/assets/{CJjEAGFN.js → BYVYH9CH.js} +1 -1
  11. package/dashboard/dist/assets/BYVYH9CH.js.br +0 -0
  12. package/dashboard/dist/assets/BYVYH9CH.js.gz +0 -0
  13. package/dashboard/dist/assets/BkMrrjAv.js +1 -0
  14. package/dashboard/dist/assets/BkMrrjAv.js.br +0 -0
  15. package/dashboard/dist/assets/BkMrrjAv.js.gz +0 -0
  16. package/dashboard/dist/assets/BpF7v1Dk.js +1 -0
  17. package/dashboard/dist/assets/BpF7v1Dk.js.br +0 -0
  18. package/dashboard/dist/assets/BpF7v1Dk.js.gz +0 -0
  19. package/dashboard/dist/assets/{C9fvfXmS.js → Bv_86bUY.js} +1 -1
  20. package/dashboard/dist/assets/Bv_86bUY.js.br +0 -0
  21. package/dashboard/dist/assets/Bv_86bUY.js.gz +0 -0
  22. package/dashboard/dist/assets/{CFZ4Swr5.js → C3PrI8L7.js} +1 -1
  23. package/dashboard/dist/assets/C3PrI8L7.js.br +0 -0
  24. package/dashboard/dist/assets/C3PrI8L7.js.gz +0 -0
  25. package/dashboard/dist/assets/{C91KLKit.js → C6AqbA9J.js} +1 -1
  26. package/dashboard/dist/assets/C6AqbA9J.js.br +0 -0
  27. package/dashboard/dist/assets/C6AqbA9J.js.gz +0 -0
  28. package/dashboard/dist/assets/{7DhYqBrM.js → CUXb_4F3.js} +1 -1
  29. package/dashboard/dist/assets/CUXb_4F3.js.br +0 -0
  30. package/dashboard/dist/assets/CUXb_4F3.js.gz +0 -0
  31. package/dashboard/dist/assets/{3TtV4moZ.js → Cn8sRTkO.js} +1 -1
  32. package/dashboard/dist/assets/Cn8sRTkO.js.br +0 -0
  33. package/dashboard/dist/assets/Cn8sRTkO.js.gz +0 -0
  34. package/dashboard/dist/assets/{CnPC783_.js → D5IgXoTj.js} +1 -1
  35. package/dashboard/dist/assets/D5IgXoTj.js.br +0 -0
  36. package/dashboard/dist/assets/D5IgXoTj.js.gz +0 -0
  37. package/dashboard/dist/assets/DMKyYAtD.js +1 -0
  38. package/dashboard/dist/assets/DMKyYAtD.js.br +0 -0
  39. package/dashboard/dist/assets/DMKyYAtD.js.gz +0 -0
  40. package/dashboard/dist/assets/{CGj8kRhg.js → DXzpQUC0.js} +1 -1
  41. package/dashboard/dist/assets/DXzpQUC0.js.br +0 -0
  42. package/dashboard/dist/assets/DXzpQUC0.js.gz +0 -0
  43. package/dashboard/dist/assets/JDPvhd68.js +1 -0
  44. package/dashboard/dist/assets/JDPvhd68.js.br +0 -0
  45. package/dashboard/dist/assets/JDPvhd68.js.gz +0 -0
  46. package/dashboard/dist/assets/{3VwNyxUf.js → R6N_VVqm.js} +1 -1
  47. package/dashboard/dist/assets/R6N_VVqm.js.br +0 -0
  48. package/dashboard/dist/assets/R6N_VVqm.js.gz +0 -0
  49. package/dashboard/dist/assets/{CKrH5fYO.js → cEP7N1dn.js} +1 -1
  50. package/dashboard/dist/assets/cEP7N1dn.js.br +0 -0
  51. package/dashboard/dist/assets/cEP7N1dn.js.gz +0 -0
  52. package/dashboard/dist/assets/iLnvdWmW.css +1 -0
  53. package/dashboard/dist/assets/iLnvdWmW.css.br +0 -0
  54. package/dashboard/dist/assets/iLnvdWmW.css.gz +0 -0
  55. package/dashboard/dist/assets/{Ctw95IkC.js → konqMbVI.js} +1 -1
  56. package/dashboard/dist/assets/konqMbVI.js.br +0 -0
  57. package/dashboard/dist/assets/konqMbVI.js.gz +0 -0
  58. package/dashboard/dist/assets/{T2NFtzAv.js → wc6cgXzV.js} +1 -1
  59. package/dashboard/dist/assets/wc6cgXzV.js.br +0 -0
  60. package/dashboard/dist/assets/wc6cgXzV.js.gz +0 -0
  61. package/dashboard/dist/index.html +3 -3
  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.d.ts +15 -0
  65. package/dist/http/helpers/auto-continue-engine.js +165 -0
  66. package/dist/http/helpers/mission-control.d.ts +3 -0
  67. package/dist/http/helpers/mission-control.js +38 -7
  68. package/dist/http/helpers/value-utils.d.ts +1 -1
  69. package/dist/http/helpers/value-utils.js +5 -2
  70. package/dist/http/index.js +3 -1
  71. package/dist/http/routes/chat.d.ts +1 -1
  72. package/dist/http/routes/chat.js +2 -2
  73. package/dist/http/routes/entities.js +60 -2
  74. package/dist/http/routes/entity-dynamic.js +49 -9
  75. package/dist/http/routes/live-snapshot.d.ts +10 -1
  76. package/dist/http/routes/live-snapshot.js +11 -3
  77. package/dist/http/routes/mission-control-actions.d.ts +6 -0
  78. package/dist/http/routes/mission-control-actions.js +33 -0
  79. package/package.json +1 -1
  80. package/dashboard/dist/assets/0RUEVzJa.js +0 -1
  81. package/dashboard/dist/assets/0RUEVzJa.js.br +0 -0
  82. package/dashboard/dist/assets/0RUEVzJa.js.gz +0 -0
  83. package/dashboard/dist/assets/3TtV4moZ.js.br +0 -0
  84. package/dashboard/dist/assets/3TtV4moZ.js.gz +0 -0
  85. package/dashboard/dist/assets/3VwNyxUf.js.br +0 -0
  86. package/dashboard/dist/assets/3VwNyxUf.js.gz +0 -0
  87. package/dashboard/dist/assets/7DhYqBrM.js.br +0 -0
  88. package/dashboard/dist/assets/7DhYqBrM.js.gz +0 -0
  89. package/dashboard/dist/assets/BVvffj0x.js +0 -1
  90. package/dashboard/dist/assets/BVvffj0x.js.br +0 -0
  91. package/dashboard/dist/assets/BVvffj0x.js.gz +0 -0
  92. package/dashboard/dist/assets/BiOgVMED.js +0 -1
  93. package/dashboard/dist/assets/BiOgVMED.js.br +0 -0
  94. package/dashboard/dist/assets/BiOgVMED.js.gz +0 -0
  95. package/dashboard/dist/assets/C91KLKit.js.br +0 -0
  96. package/dashboard/dist/assets/C91KLKit.js.gz +0 -0
  97. package/dashboard/dist/assets/C9fvfXmS.js.br +0 -0
  98. package/dashboard/dist/assets/C9fvfXmS.js.gz +0 -0
  99. package/dashboard/dist/assets/CFZ4Swr5.js.br +0 -0
  100. package/dashboard/dist/assets/CFZ4Swr5.js.gz +0 -0
  101. package/dashboard/dist/assets/CGj8kRhg.js.br +0 -0
  102. package/dashboard/dist/assets/CGj8kRhg.js.gz +0 -0
  103. package/dashboard/dist/assets/CJjEAGFN.js.br +0 -0
  104. package/dashboard/dist/assets/CJjEAGFN.js.gz +0 -0
  105. package/dashboard/dist/assets/CKrH5fYO.js.br +0 -0
  106. package/dashboard/dist/assets/CKrH5fYO.js.gz +0 -0
  107. package/dashboard/dist/assets/CMTTPXch.js +0 -1
  108. package/dashboard/dist/assets/CMTTPXch.js.br +0 -0
  109. package/dashboard/dist/assets/CMTTPXch.js.gz +0 -0
  110. package/dashboard/dist/assets/CnPC783_.js.br +0 -0
  111. package/dashboard/dist/assets/CnPC783_.js.gz +0 -0
  112. package/dashboard/dist/assets/Ctw95IkC.js.br +0 -0
  113. package/dashboard/dist/assets/Ctw95IkC.js.gz +0 -0
  114. package/dashboard/dist/assets/DHz-aQPw.js +0 -1
  115. package/dashboard/dist/assets/DHz-aQPw.js.br +0 -0
  116. package/dashboard/dist/assets/DHz-aQPw.js.gz +0 -0
  117. package/dashboard/dist/assets/DNX2foSJ.css +0 -1
  118. package/dashboard/dist/assets/DNX2foSJ.css.br +0 -0
  119. package/dashboard/dist/assets/DNX2foSJ.css.gz +0 -0
  120. package/dashboard/dist/assets/DxUw4FMR.js +0 -212
  121. package/dashboard/dist/assets/DxUw4FMR.js.br +0 -0
  122. package/dashboard/dist/assets/DxUw4FMR.js.gz +0 -0
  123. package/dashboard/dist/assets/T2NFtzAv.js.br +0 -0
  124. package/dashboard/dist/assets/T2NFtzAv.js.gz +0 -0
  125. package/dashboard/dist/assets/gZr_xKlA.js.br +0 -0
  126. package/dashboard/dist/assets/gZr_xKlA.js.gz +0 -0
@@ -485,6 +485,21 @@ export declare function createAutoContinueEngine(deps: CreateAutoContinueEngineD
485
485
  activeRunId: string | null;
486
486
  activeTaskTokenEstimate: number | null;
487
487
  }>;
488
+ skipCurrentWorkstream: (initiativeId: string, workstreamId: string, reason?: string) => Promise<{
489
+ ok: boolean;
490
+ skippedWorkstreamId: string;
491
+ nextWorkstreamId?: string;
492
+ nextWorkstreamTitle?: string;
493
+ }>;
494
+ getCanonicalAutopilotState: (initiativeId: string) => {
495
+ state: "idle" | "running" | "blocked" | "stopping";
496
+ reason: string | null;
497
+ activeRunId: string | null;
498
+ activeWorkstreamId: string | null;
499
+ activeWorkstreamTitle: string | null;
500
+ queueHeadTitle: string | null;
501
+ lastTransitionAt: string;
502
+ } | null;
488
503
  workstreamSessionStore: Map<string, {
489
504
  sessionId: string;
490
505
  workstreamId: string;
@@ -1554,6 +1554,20 @@ export function createAutoContinueEngine(deps) {
1554
1554
  decision_count: decisionIds.length,
1555
1555
  last_error: input.run.lastError,
1556
1556
  error_location: errorLocation,
1557
+ ...(input.reason === "blocked" || input.reason === "error"
1558
+ ? {
1559
+ blocker: {
1560
+ kind: decisionRequired ? "decision_required" : "error",
1561
+ summary: input.error ?? input.run.lastError ?? "Execution blocked",
1562
+ required_actor: decisionRequired ? "user" : "system",
1563
+ required_action: decisionRequired
1564
+ ? "Resolve the pending decision in Decisions panel"
1565
+ : "Review the error and retry",
1566
+ can_skip: true,
1567
+ skip_route: "/orgx/api/autopilot/skip",
1568
+ },
1569
+ }
1570
+ : {}),
1557
1571
  },
1558
1572
  });
1559
1573
  // Emit autopilot_transition event for state observers.
@@ -2278,12 +2292,53 @@ export function createAutoContinueEngine(deps) {
2278
2292
  ...mockMeta(slice),
2279
2293
  user_summary: userSummary,
2280
2294
  next_actions: nextActions,
2295
+ outcomes: {
2296
+ pr_url: typeof parsed?.pr_url === "string" ? parsed.pr_url : null,
2297
+ pr_number: typeof parsed?.pr_number === "number" ? parsed.pr_number : null,
2298
+ commit_sha: typeof parsed?.commit_sha === "string" ? parsed.commit_sha : null,
2299
+ commit_url: typeof parsed?.commit_url === "string" ? parsed.commit_url : null,
2300
+ tests: null,
2301
+ artifact_ids: artifacts.map((a) => a.name).filter(Boolean),
2302
+ task_updates: taskUpdates?.length ?? 0,
2303
+ },
2281
2304
  },
2282
2305
  });
2283
2306
  }
2284
2307
  catch {
2285
2308
  // best effort
2286
2309
  }
2310
+ // Emit explicit session completion event for canonical agent panel state
2311
+ if (slice.status === "completed" || reportedParsedStatus === "completed") {
2312
+ await emitActivitySafe({
2313
+ initiativeId: run.initiativeId,
2314
+ runId: slice.runId,
2315
+ correlationId: slice.runId,
2316
+ phase: "completed",
2317
+ level: "info",
2318
+ message: userSummary ?? `Completed work on ${slice.workstreamTitle ?? "task"}`,
2319
+ metadata: {
2320
+ ...buildSliceEnrichment({
2321
+ run,
2322
+ slice,
2323
+ workstreamId: slice.workstreamId,
2324
+ workstreamTitle: slice.workstreamTitle ?? null,
2325
+ domain: slice.domain,
2326
+ requiredSkills: slice.requiredSkills,
2327
+ userSummary,
2328
+ event: "session_completed",
2329
+ }),
2330
+ session_id: slice.cliSessionId ?? null,
2331
+ source_client: slice.sourceClient,
2332
+ workstream_title: slice.workstreamTitle ?? null,
2333
+ task_title: slice.workstreamTitle ?? slice.workstreamId,
2334
+ duration_ms: slice.finishedAt
2335
+ ? new Date(slice.finishedAt).getTime() - new Date(slice.startedAt).getTime()
2336
+ : null,
2337
+ outcome: reportedParsedStatus ?? slice.status,
2338
+ artifacts_produced: artifacts.length,
2339
+ },
2340
+ });
2341
+ }
2287
2342
  if (slice.status === "completed") {
2288
2343
  await emitActivitySafe({
2289
2344
  initiativeId: run.initiativeId,
@@ -2754,8 +2809,11 @@ export function createAutoContinueEngine(deps) {
2754
2809
  };
2755
2810
  // Select the next eligible workstream by scanning ordered todos.
2756
2811
  let selectedWorkstreamId = null;
2812
+ let selectedQueueRank = 0;
2757
2813
  let deferredBySpawnGuardRateLimit = 0;
2814
+ let queueScanIndex = 0;
2758
2815
  for (const taskId of graph.recentTodos) {
2816
+ queueScanIndex++;
2759
2817
  const node = nodeById.get(taskId);
2760
2818
  if (!node || node.type !== "task")
2761
2819
  continue;
@@ -2797,6 +2855,7 @@ export function createAutoContinueEngine(deps) {
2797
2855
  continue;
2798
2856
  }
2799
2857
  selectedWorkstreamId = node.workstreamId;
2858
+ selectedQueueRank = queueScanIndex + 1;
2800
2859
  break;
2801
2860
  }
2802
2861
  if (!selectedWorkstreamId) {
@@ -3675,6 +3734,10 @@ export function createAutoContinueEngine(deps) {
3675
3734
  scope_milestone_ids: slice.scopeMilestoneIds,
3676
3735
  log_path: logPath,
3677
3736
  output_path: outputPath,
3737
+ dispatch_queue_rank: selectedQueueRank > 0 ? selectedQueueRank : null,
3738
+ dispatch_workstream_title: workstreamTitle ?? null,
3739
+ dispatch_task_title: primaryTask.title ?? null,
3740
+ dispatch_selection_reason: "top_of_queue",
3678
3741
  ...mockMeta(slice),
3679
3742
  },
3680
3743
  });
@@ -3714,6 +3777,10 @@ export function createAutoContinueEngine(deps) {
3714
3777
  scope_milestone_ids: slice.scopeMilestoneIds,
3715
3778
  log_path: logPath,
3716
3779
  output_path: outputPath,
3780
+ dispatch_queue_rank: selectedQueueRank > 0 ? selectedQueueRank : null,
3781
+ dispatch_workstream_title: workstreamTitle ?? null,
3782
+ dispatch_task_title: primaryTask.title ?? null,
3783
+ dispatch_selection_reason: "top_of_queue",
3717
3784
  ...mockMeta(slice),
3718
3785
  },
3719
3786
  });
@@ -4286,6 +4353,102 @@ export function createAutoContinueEngine(deps) {
4286
4353
  }
4287
4354
  return run;
4288
4355
  }
4356
+ async function skipCurrentWorkstream(initiativeId, workstreamId, reason) {
4357
+ const run = autoContinueRuns.get(initiativeId) ?? null;
4358
+ if (!run) {
4359
+ return { ok: false, skippedWorkstreamId: workstreamId };
4360
+ }
4361
+ ensureRunInternals(run);
4362
+ if (!run.blockedWorkstreamIds.includes(workstreamId)) {
4363
+ run.blockedWorkstreamIds.push(workstreamId);
4364
+ }
4365
+ setLaneState(run, {
4366
+ workstreamId,
4367
+ state: LaneState.BLOCKED,
4368
+ activeRunId: null,
4369
+ activeTaskIds: [],
4370
+ blockedReason: reason ?? "Skipped by user",
4371
+ waitingOnWorkstreamIds: [],
4372
+ retryAt: null,
4373
+ });
4374
+ run.updatedAt = new Date().toISOString();
4375
+ try {
4376
+ await emitActivitySafe({
4377
+ initiativeId,
4378
+ runId: run.lastRunId ?? undefined,
4379
+ correlationId: run.lastRunId ?? undefined,
4380
+ phase: "review",
4381
+ level: "info",
4382
+ message: `Workstream ${workstreamId} skipped${reason ? `: ${reason}` : ""}.`,
4383
+ metadata: {
4384
+ ...buildSliceEnrichment({
4385
+ run,
4386
+ workstreamId,
4387
+ event: "autopilot_item_skipped",
4388
+ }),
4389
+ skipped_workstream_id: workstreamId,
4390
+ skip_reason: reason ?? null,
4391
+ },
4392
+ });
4393
+ }
4394
+ catch {
4395
+ // best effort
4396
+ }
4397
+ // Re-enable the run if it was stopped due to the blocked workstream.
4398
+ if (run.status === RunStatus.STOPPED && run.stopReason === "blocked") {
4399
+ run.status = RunStatus.RUNNING;
4400
+ run.stopReason = null;
4401
+ run.stoppedAt = null;
4402
+ run.stopRequested = false;
4403
+ run.lastError = null;
4404
+ }
4405
+ // Trigger the next tick to pick up a different workstream.
4406
+ try {
4407
+ await tickAutoContinueRun(run);
4408
+ }
4409
+ catch {
4410
+ // best effort
4411
+ }
4412
+ // Determine what the next workstream is, if any.
4413
+ const nextLane = Object.values(run.laneByWorkstreamId ?? {}).find((lane) => lane.state === LaneState.RUNNING && lane.workstreamId !== workstreamId) ?? null;
4414
+ return {
4415
+ ok: true,
4416
+ skippedWorkstreamId: workstreamId,
4417
+ nextWorkstreamId: nextLane?.workstreamId ?? undefined,
4418
+ nextWorkstreamTitle: undefined,
4419
+ };
4420
+ }
4421
+ function getCanonicalAutopilotState(initiativeId) {
4422
+ const run = autoContinueRuns.get(initiativeId) ?? null;
4423
+ if (!run)
4424
+ return null;
4425
+ const canonicalState = run.status === RunStatus.RUNNING
4426
+ ? "running"
4427
+ : run.status === RunStatus.STOPPING
4428
+ ? "stopping"
4429
+ : run.stopReason === "blocked" || run.stopReason === "error"
4430
+ ? "blocked"
4431
+ : "idle";
4432
+ const reason = canonicalState === "blocked"
4433
+ ? run.lastError ?? run.stopReason ?? null
4434
+ : canonicalState === "stopping"
4435
+ ? "stop_requested"
4436
+ : null;
4437
+ // Find the first active slice to identify the current workstream.
4438
+ const activeSliceRunId = (run.activeSliceRunIds ?? [])[0] ?? run.activeRunId ?? null;
4439
+ const activeSlice = activeSliceRunId
4440
+ ? autoContinueSliceRuns.get(activeSliceRunId) ?? null
4441
+ : null;
4442
+ return {
4443
+ state: canonicalState,
4444
+ reason,
4445
+ activeRunId: activeSliceRunId,
4446
+ activeWorkstreamId: activeSlice?.workstreamId ?? null,
4447
+ activeWorkstreamTitle: activeSlice?.workstreamTitle ?? null,
4448
+ queueHeadTitle: activeSlice?.workstreamTitle ?? null,
4449
+ lastTransitionAt: run.updatedAt ?? run.startedAt,
4450
+ };
4451
+ }
4289
4452
  return {
4290
4453
  autoContinueRuns,
4291
4454
  autoContinueSliceRuns,
@@ -4307,6 +4470,8 @@ export function createAutoContinueEngine(deps) {
4307
4470
  getAutoContinueLaneForWorkstream,
4308
4471
  scheduleAutoFixForWorkstream,
4309
4472
  startAutoContinueRun,
4473
+ skipCurrentWorkstream,
4474
+ getCanonicalAutopilotState,
4310
4475
  // Session store (for resume support)
4311
4476
  workstreamSessionStore,
4312
4477
  getWorkstreamSession,
@@ -87,7 +87,10 @@ export declare const DEFAULT_TOKEN_BUDGET_ASSUMPTIONS: {
87
87
  export declare function pickStringArray(record: Record<string, unknown>, keys: string[]): string[];
88
88
  export declare function dedupeStrings(items: string[]): string[];
89
89
  export declare function isTodoStatus(status: string): boolean;
90
+ export declare function isPausedStatus(status: string): boolean;
91
+ export declare function isBlockedStatus(status: string): boolean;
90
92
  export declare function isInProgressStatus(status: string): boolean;
93
+ export declare function deriveInitiativeLifecycleStatus(currentStatus: string, childStatuses: string[]): string;
91
94
  export declare function isDispatchableWorkstreamStatus(status: string): boolean;
92
95
  export declare function isDoneStatus(status: string): boolean;
93
96
  export declare function listEntitiesSafe(client: OrgXClient, type: MissionControlNodeType, filters: Record<string, unknown>): Promise<{
@@ -637,6 +637,18 @@ export function isTodoStatus(status) {
637
637
  normalized === "backlog" ||
638
638
  normalized === "pending");
639
639
  }
640
+ export function isPausedStatus(status) {
641
+ const normalized = status.toLowerCase();
642
+ return (normalized === "paused" ||
643
+ normalized === "idle" ||
644
+ normalized === "hold" ||
645
+ normalized === "on_hold" ||
646
+ normalized === "waiting");
647
+ }
648
+ export function isBlockedStatus(status) {
649
+ const normalized = status.toLowerCase();
650
+ return normalized === "blocked" || normalized === "at_risk" || normalized === "needs_input";
651
+ }
640
652
  export function isInProgressStatus(status) {
641
653
  const normalized = status.toLowerCase();
642
654
  return (normalized === "in_progress" ||
@@ -644,6 +656,30 @@ export function isInProgressStatus(status) {
644
656
  normalized === "running" ||
645
657
  normalized === "queued");
646
658
  }
659
+ export function deriveInitiativeLifecycleStatus(currentStatus, childStatuses) {
660
+ const normalizedCurrent = currentStatus.toLowerCase();
661
+ if (!childStatuses.length)
662
+ return currentStatus;
663
+ const hasInProgressChildren = childStatuses.some((status) => isInProgressStatus(status));
664
+ const hasRemainingChildren = childStatuses.some((status) => !isDoneStatus(status));
665
+ const hasBlockedChildren = childStatuses.some((status) => isBlockedStatus(status));
666
+ const hasPausedChildren = childStatuses.some((status) => isPausedStatus(status));
667
+ const hasTodoChildren = childStatuses.some((status) => isTodoStatus(status));
668
+ const isActiveLikeCurrent = normalizedCurrent === "active" ||
669
+ normalizedCurrent === "running" ||
670
+ normalizedCurrent === "in_progress" ||
671
+ normalizedCurrent === "queued";
672
+ if (!hasInProgressChildren && isActiveLikeCurrent && hasRemainingChildren) {
673
+ if (hasBlockedChildren && !hasPausedChildren && !hasTodoChildren) {
674
+ return "blocked";
675
+ }
676
+ return "paused";
677
+ }
678
+ if (hasInProgressChildren && (normalizedCurrent === "paused" || normalizedCurrent === "idle")) {
679
+ return "active";
680
+ }
681
+ return currentStatus;
682
+ }
647
683
  export function isDispatchableWorkstreamStatus(status) {
648
684
  const normalized = status.toLowerCase();
649
685
  if (!normalized)
@@ -889,13 +925,8 @@ export async function buildMissionControlGraph(client, initiativeId, options) {
889
925
  }
890
926
  }
891
927
  const taskNodesOnly = nodes.filter((node) => node.type === "task");
892
- const hasActiveTasks = taskNodesOnly.some((node) => isInProgressStatus(node.status));
893
- const hasTodoTasks = taskNodesOnly.some((node) => isTodoStatus(node.status));
894
- if (initiativeNode.status.toLowerCase() === "active" &&
895
- !hasActiveTasks &&
896
- hasTodoTasks) {
897
- initiativeNode.status = "paused";
898
- }
928
+ const lifecycleChildren = taskNodesOnly.length > 0 ? taskNodesOnly : workstreamNodes;
929
+ initiativeNode.status = deriveInitiativeLifecycleStatus(initiativeNode.status, lifecycleChildren.map((node) => node.status));
899
930
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
900
931
  const taskIsReady = (task) => task.dependencyIds.every((depId) => {
901
932
  const dependency = nodeById.get(depId);
@@ -2,5 +2,5 @@ export declare function pickString(record: Record<string, unknown>, keys: string
2
2
  export declare function pickNumber(record: Record<string, unknown>, keys: string[]): number | null;
3
3
  export declare function pickHeaderString(headers: Record<string, string | string[] | undefined>, keys: string[]): string | null;
4
4
  export declare function toIsoString(value: string | null): string | null;
5
- export declare function parsePositiveInt(raw: string | null, fallback: number): number;
5
+ export declare function parsePositiveInt(raw: string | null, fallback: number, max?: number): number;
6
6
  export declare function parseBooleanQuery(raw: string | null): boolean;
@@ -48,7 +48,7 @@ export function toIsoString(value) {
48
48
  return null;
49
49
  return new Date(parsed).toISOString();
50
50
  }
51
- export function parsePositiveInt(raw, fallback) {
51
+ export function parsePositiveInt(raw, fallback, max = Number.POSITIVE_INFINITY) {
52
52
  if (!raw)
53
53
  return fallback;
54
54
  const normalized = raw.trim();
@@ -59,7 +59,10 @@ export function parsePositiveInt(raw, fallback) {
59
59
  return fallback;
60
60
  // Offset-like parameters may intentionally allow zero when fallback is zero.
61
61
  const minimum = fallback <= 0 ? 0 : 1;
62
- return Math.max(minimum, Math.floor(parsed));
62
+ const clamped = Math.max(minimum, Math.floor(parsed));
63
+ if (!Number.isFinite(max))
64
+ return clamped;
65
+ return Math.min(clamped, Math.max(minimum, Math.floor(max)));
63
66
  }
64
67
  export function parseBooleanQuery(raw) {
65
68
  if (!raw)
@@ -1846,7 +1846,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
1846
1846
  };
1847
1847
  const codexBinResolver = createCodexBinResolver();
1848
1848
  const resolveCodexBinInfo = () => codexBinResolver.resolveCodexBinInfo();
1849
- const { autoContinueRuns, autoContinueSliceRuns, localInitiativeStatusOverrides, writeRuntimeEvent, autoContinueTickMs: AUTO_CONTINUE_TICK_MS, defaultAutoContinueTokenBudget, defaultAutoContinueMaxParallelSlices, setLocalInitiativeStatusOverride, clearLocalInitiativeStatusOverride, applyLocalInitiativeOverrides, applyLocalInitiativeOverrideToGraph, updateInitiativeAutoContinueState, stopAutoContinueRun, tickAutoContinueRun, tickAllAutoContinue, isInitiativeActiveStatus, runningAutoContinueForWorkstream, getAutoContinueLaneForWorkstream, scheduleAutoFixForWorkstream, startAutoContinueRun, } = createAutoContinueEngine({
1849
+ const { autoContinueRuns, autoContinueSliceRuns, localInitiativeStatusOverrides, writeRuntimeEvent, autoContinueTickMs: AUTO_CONTINUE_TICK_MS, defaultAutoContinueTokenBudget, defaultAutoContinueMaxParallelSlices, setLocalInitiativeStatusOverride, clearLocalInitiativeStatusOverride, applyLocalInitiativeOverrides, applyLocalInitiativeOverrideToGraph, updateInitiativeAutoContinueState, stopAutoContinueRun, tickAutoContinueRun, tickAllAutoContinue, isInitiativeActiveStatus, runningAutoContinueForWorkstream, getAutoContinueLaneForWorkstream, scheduleAutoFixForWorkstream, startAutoContinueRun, skipCurrentWorkstream, getCanonicalAutopilotState, } = createAutoContinueEngine({
1850
1850
  client,
1851
1851
  filename: __filename,
1852
1852
  safeErrorMessage,
@@ -3417,6 +3417,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3417
3417
  dispatchFallbackWorkstreamTurn,
3418
3418
  tickAutoContinueRun,
3419
3419
  stopAutoContinueRun,
3420
+ skipCurrentWorkstream,
3420
3421
  updateInitiativeAutoContinueState,
3421
3422
  tickAllAutoContinue,
3422
3423
  scheduleAutoFixForWorkstream,
@@ -3592,6 +3593,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3592
3593
  },
3593
3594
  runAction: (runId, action, input) => client.runAction(runId, action, input),
3594
3595
  listChatThreads: ({ commandCenterId, initiativeId, limit, offset }) => listChatThreads({ commandCenterId, initiativeId, limit, offset }),
3596
+ getCanonicalAutopilotState,
3595
3597
  sendJson,
3596
3598
  });
3597
3599
  registerRuntimeHookRoutes(apiRouter, {
@@ -3,7 +3,7 @@ type JsonRecord = Record<string, unknown>;
3
3
  type RegisterChatRoutesDeps<TReq, TRes> = {
4
4
  parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
5
5
  pickString: (input: Record<string, unknown>, keys: string[]) => string | null;
6
- parsePositiveInt: (raw: string | null, fallback: number) => number;
6
+ parsePositiveInt: (raw: string | null, fallback: number, max?: number) => number;
7
7
  emitActivitySafe?: (input: {
8
8
  initiativeId: string | null;
9
9
  sourceClient?: string;
@@ -161,8 +161,8 @@ export function registerChatRoutes(router, deps) {
161
161
  const initiativeId = query.get("initiative_id") ?? query.get("initiative");
162
162
  const searchQuery = query.get("query") ?? query.get("q");
163
163
  const status = query.get("status");
164
- const limit = deps.parsePositiveInt(query.get("limit"), 60);
165
- const offset = deps.parsePositiveInt(query.get("offset"), 0);
164
+ const limit = deps.parsePositiveInt(query.get("limit"), 60, 200);
165
+ const offset = deps.parsePositiveInt(query.get("offset"), 0, 10000);
166
166
  sendThreadList(deps, res, {
167
167
  commandCenterId: commandCenterId?.trim() ?? null,
168
168
  initiativeId: initiativeId?.trim() ?? null,
@@ -1,4 +1,5 @@
1
1
  import { resolveWorkspaceScope, workspaceScopeFromHeaders, } from "../helpers/workspace-scope.js";
2
+ import { deriveInitiativeLifecycleStatus } from "../helpers/mission-control.js";
2
3
  const WORKSTREAM_REASSIGNMENT_FIELDS = [
3
4
  "domain",
4
5
  "role",
@@ -38,6 +39,59 @@ function toObjectArray(input) {
38
39
  return [];
39
40
  return input.filter((item) => Boolean(item) && typeof item === "object");
40
41
  }
42
+ async function reconcileInitiativeStatusesFromWorkstreams(deps, rows, input) {
43
+ if (rows.length === 0)
44
+ return rows;
45
+ const initiativeIds = new Set(rows
46
+ .map((row) => deps.pickString(row, ["id"]))
47
+ .filter((value) => Boolean(value)));
48
+ if (initiativeIds.size === 0)
49
+ return rows;
50
+ const scopedInitiativeId = input.initiativeId?.trim() ?? "";
51
+ const scopedWorkspaceId = input.workspaceId?.trim() ?? "";
52
+ if (!scopedInitiativeId && !scopedWorkspaceId) {
53
+ // Avoid unscoped global workstream scans.
54
+ return rows;
55
+ }
56
+ const filters = { limit: 2000 };
57
+ if (scopedInitiativeId) {
58
+ filters.initiative_id = scopedInitiativeId;
59
+ }
60
+ if (scopedWorkspaceId) {
61
+ filters.workspace_id = scopedWorkspaceId;
62
+ filters.command_center_id = scopedWorkspaceId;
63
+ }
64
+ const response = await deps.client.listEntities("workstream", filters);
65
+ const workstreamRows = toObjectArray(response && typeof response === "object"
66
+ ? response.data
67
+ : []);
68
+ const childStatusesByInitiative = new Map();
69
+ for (const workstreamRow of workstreamRows) {
70
+ const initiativeId = deps.pickString(workstreamRow, ["initiative_id", "initiativeId"]);
71
+ if (!initiativeId || !initiativeIds.has(initiativeId))
72
+ continue;
73
+ const status = deps.pickString(workstreamRow, ["status"]) ?? "todo";
74
+ const statuses = childStatusesByInitiative.get(initiativeId) ?? [];
75
+ statuses.push(status);
76
+ childStatusesByInitiative.set(initiativeId, statuses);
77
+ }
78
+ return rows.map((row) => {
79
+ const initiativeId = deps.pickString(row, ["id"]);
80
+ const status = deps.pickString(row, ["status"]);
81
+ if (!initiativeId || !status)
82
+ return row;
83
+ const childStatuses = childStatusesByInitiative.get(initiativeId) ?? [];
84
+ if (childStatuses.length === 0)
85
+ return row;
86
+ const normalized = deriveInitiativeLifecycleStatus(status, childStatuses);
87
+ if (normalized === status)
88
+ return row;
89
+ return {
90
+ ...row,
91
+ status: normalized,
92
+ };
93
+ });
94
+ }
41
95
  export function registerEntitiesRoutes(router, deps) {
42
96
  router.add("POST", "entities", async ({ req, res }) => {
43
97
  try {
@@ -305,13 +359,17 @@ export function registerEntitiesRoutes(router, deps) {
305
359
  return rowScope.trim() === workspaceScopeId;
306
360
  })
307
361
  : searchedRows;
362
+ const reconciledRows = await reconcileInitiativeStatusesFromWorkstreams(deps, rows, {
363
+ initiativeId: id,
364
+ workspaceId: workspaceScopeId,
365
+ }).catch(() => rows);
308
366
  deps.sendJson(res, 200, {
309
367
  ...payload,
310
- data: deps.applyLocalInitiativeOverrides(rows),
368
+ data: deps.applyLocalInitiativeOverrides(reconciledRows),
311
369
  pagination: payload.pagination && typeof payload.pagination === "object"
312
370
  ? {
313
371
  ...payload.pagination,
314
- total: rows.length,
372
+ total: reconciledRows.length,
315
373
  }
316
374
  : payload.pagination,
317
375
  });
@@ -1,3 +1,51 @@
1
+ function resolveEntityActionStatus(entityType, entityAction) {
2
+ const normalizedType = entityType.trim().toLowerCase();
3
+ const normalizedAction = entityAction.trim().toLowerCase();
4
+ const defaultStatusMap = {
5
+ start: "in_progress",
6
+ complete: "done",
7
+ block: "blocked",
8
+ unblock: "in_progress",
9
+ pause: "paused",
10
+ resume: "active",
11
+ };
12
+ const statusMapByEntityType = {
13
+ initiative: {
14
+ start: "active",
15
+ complete: "completed",
16
+ block: "blocked",
17
+ unblock: "active",
18
+ pause: "paused",
19
+ resume: "active",
20
+ },
21
+ workstream: {
22
+ start: "active",
23
+ complete: "completed",
24
+ block: "blocked",
25
+ unblock: "active",
26
+ pause: "paused",
27
+ resume: "active",
28
+ },
29
+ milestone: {
30
+ start: "in_progress",
31
+ complete: "completed",
32
+ block: "at_risk",
33
+ unblock: "in_progress",
34
+ pause: "planned",
35
+ resume: "in_progress",
36
+ },
37
+ task: {
38
+ start: "in_progress",
39
+ complete: "done",
40
+ block: "blocked",
41
+ unblock: "in_progress",
42
+ pause: "todo",
43
+ resume: "in_progress",
44
+ },
45
+ };
46
+ const map = statusMapByEntityType[normalizedType] ?? defaultStatusMap;
47
+ return map[normalizedAction] ?? null;
48
+ }
1
49
  export function registerEntityDynamicRoutes(router, deps) {
2
50
  router.add("*", "entities/*", async ({ req, res, path }) => {
3
51
  const method = (req.method ?? "GET").toUpperCase();
@@ -137,15 +185,7 @@ export function registerEntityDynamicRoutes(router, deps) {
137
185
  }
138
186
  return;
139
187
  }
140
- const statusMap = {
141
- start: "in_progress",
142
- complete: "done",
143
- block: "blocked",
144
- unblock: "in_progress",
145
- pause: "paused",
146
- resume: "active",
147
- };
148
- const newStatus = statusMap[entityAction];
188
+ const newStatus = resolveEntityActionStatus(entityType, entityAction);
149
189
  if (!newStatus) {
150
190
  deps.sendJson(res, 400, {
151
191
  error: `Unknown entity action: ${entityAction}`,
@@ -14,7 +14,7 @@ type SnapshotPersistState = {
14
14
  lastPersistAt: number;
15
15
  };
16
16
  type LiveSnapshotRoutesDeps<TReq, TRes> = {
17
- parsePositiveInt: (raw: string | null, fallback: number) => number;
17
+ parsePositiveInt: (raw: string | null, fallback: number, max?: number) => number;
18
18
  readSnapshotResponseCache: (key: string) => Record<string, unknown> | null;
19
19
  writeSnapshotResponseCache: (key: string, payload: Record<string, unknown>) => void;
20
20
  safeErrorMessage: (err: unknown) => string;
@@ -131,6 +131,15 @@ type LiveSnapshotRoutesDeps<TReq, TRes> = {
131
131
  total: number;
132
132
  updatedAt: string;
133
133
  };
134
+ getCanonicalAutopilotState?: (initiativeId: string) => {
135
+ state: "idle" | "running" | "blocked" | "stopping";
136
+ reason: string | null;
137
+ activeRunId: string | null;
138
+ activeWorkstreamId: string | null;
139
+ activeWorkstreamTitle: string | null;
140
+ queueHeadTitle: string | null;
141
+ lastTransitionAt: string;
142
+ } | null;
134
143
  sendJson: (res: TRes, status: number, payload: unknown) => void;
135
144
  };
136
145
  export declare function registerLiveSnapshotRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: LiveSnapshotRoutesDeps<TReq, TRes>): void;
@@ -231,9 +231,9 @@ export function registerLiveSnapshotRoutes(router, deps) {
231
231
  };
232
232
  const headerScopeFromRequest = (req) => workspaceScopeFromHeaders(req?.headers);
233
233
  function parseSnapshotQuery(query, headerScope) {
234
- const sessionsLimit = deps.parsePositiveInt(query.get("sessionsLimit") ?? query.get("sessions_limit"), 320);
235
- const activityLimit = deps.parsePositiveInt(query.get("activityLimit") ?? query.get("activity_limit"), 600);
236
- const decisionsLimit = deps.parsePositiveInt(query.get("decisionsLimit") ?? query.get("decisions_limit"), 120);
234
+ const sessionsLimit = deps.parsePositiveInt(query.get("sessionsLimit") ?? query.get("sessions_limit"), 320, 1000);
235
+ const activityLimit = deps.parsePositiveInt(query.get("activityLimit") ?? query.get("activity_limit"), 600, 2000);
236
+ const decisionsLimit = deps.parsePositiveInt(query.get("decisionsLimit") ?? query.get("decisions_limit"), 120, 500);
237
237
  const initiative = query.get("initiative");
238
238
  const scope = resolveWorkspaceScope(query, headerScope, {
239
239
  allowProjectScope: false,
@@ -523,6 +523,14 @@ export function registerLiveSnapshotRoutes(router, deps) {
523
523
  projectId,
524
524
  degraded: degraded.length > 0 ? degraded : undefined,
525
525
  };
526
+ if (typeof deps.getCanonicalAutopilotState === "function" && initiative) {
527
+ try {
528
+ payload.autopilot = deps.getCanonicalAutopilotState(initiative) ?? null;
529
+ }
530
+ catch {
531
+ // best effort
532
+ }
533
+ }
526
534
  if (typeof deps.listChatThreads === "function") {
527
535
  try {
528
536
  const listed = deps.listChatThreads({
@@ -66,6 +66,12 @@ type RegisterMissionControlActionsRoutesDeps<TReq, TRes> = {
66
66
  }>;
67
67
  tickAutoContinueRun: (run: any) => Promise<void>;
68
68
  stopAutoContinueRun: (input: any) => Promise<void>;
69
+ skipCurrentWorkstream: (initiativeId: string, workstreamId: string, reason?: string) => Promise<{
70
+ ok: boolean;
71
+ skippedWorkstreamId: string;
72
+ nextWorkstreamId?: string;
73
+ nextWorkstreamTitle?: string;
74
+ }>;
69
75
  updateInitiativeAutoContinueState: (input: any) => Promise<void>;
70
76
  tickAllAutoContinue: () => Promise<void>;
71
77
  scheduleAutoFixForWorkstream: (input: {