@useorgx/openclaw-plugin 0.7.11 → 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 (198) hide show
  1. package/README.md +94 -122
  2. package/dashboard/dist/assets/8pbG6uLK.js +1 -0
  3. package/dashboard/dist/assets/8pbG6uLK.js.br +0 -0
  4. package/dashboard/dist/assets/8pbG6uLK.js.gz +0 -0
  5. package/dashboard/dist/assets/B1LENRC8.js +212 -0
  6. package/dashboard/dist/assets/B1LENRC8.js.br +0 -0
  7. package/dashboard/dist/assets/B1LENRC8.js.gz +0 -0
  8. package/dashboard/dist/assets/{tcEHYcbW.js → BCudUvwg.js} +1 -1
  9. package/dashboard/dist/assets/BCudUvwg.js.br +0 -0
  10. package/dashboard/dist/assets/BCudUvwg.js.gz +0 -0
  11. package/dashboard/dist/assets/{Du1wfrXa.js → BM75sh1f.js} +2 -2
  12. package/dashboard/dist/assets/BM75sh1f.js.br +0 -0
  13. package/dashboard/dist/assets/BM75sh1f.js.gz +0 -0
  14. package/dashboard/dist/assets/BV0BcV1u.js +53 -0
  15. package/dashboard/dist/assets/BV0BcV1u.js.br +0 -0
  16. package/dashboard/dist/assets/BV0BcV1u.js.gz +0 -0
  17. package/dashboard/dist/assets/BYVYH9CH.js +1 -0
  18. package/dashboard/dist/assets/BYVYH9CH.js.br +0 -0
  19. package/dashboard/dist/assets/BYVYH9CH.js.gz +0 -0
  20. package/dashboard/dist/assets/BjK42gtU.js +1 -0
  21. package/dashboard/dist/assets/BjK42gtU.js.br +0 -0
  22. package/dashboard/dist/assets/BjK42gtU.js.gz +0 -0
  23. package/dashboard/dist/assets/BkMrrjAv.js +1 -0
  24. package/dashboard/dist/assets/BkMrrjAv.js.br +0 -0
  25. package/dashboard/dist/assets/BkMrrjAv.js.gz +0 -0
  26. package/dashboard/dist/assets/BpF7v1Dk.js +1 -0
  27. package/dashboard/dist/assets/BpF7v1Dk.js.br +0 -0
  28. package/dashboard/dist/assets/BpF7v1Dk.js.gz +0 -0
  29. package/dashboard/dist/assets/{AqVoI3SF.js → Bv_86bUY.js} +1 -1
  30. package/dashboard/dist/assets/Bv_86bUY.js.br +0 -0
  31. package/dashboard/dist/assets/Bv_86bUY.js.gz +0 -0
  32. package/dashboard/dist/assets/C-MOJWHs.js +1 -0
  33. package/dashboard/dist/assets/C-MOJWHs.js.br +0 -0
  34. package/dashboard/dist/assets/C-MOJWHs.js.gz +0 -0
  35. package/dashboard/dist/assets/C3PrI8L7.js +1 -0
  36. package/dashboard/dist/assets/C3PrI8L7.js.br +0 -0
  37. package/dashboard/dist/assets/C3PrI8L7.js.gz +0 -0
  38. package/dashboard/dist/assets/{CD-q5mdP.js → C6AqbA9J.js} +1 -1
  39. package/dashboard/dist/assets/C6AqbA9J.js.br +0 -0
  40. package/dashboard/dist/assets/C6AqbA9J.js.gz +0 -0
  41. package/dashboard/dist/assets/CSlBSRyv.js +1 -0
  42. package/dashboard/dist/assets/CSlBSRyv.js.br +0 -0
  43. package/dashboard/dist/assets/CSlBSRyv.js.gz +0 -0
  44. package/dashboard/dist/assets/{beHYBbh6.js → CUXb_4F3.js} +2 -2
  45. package/dashboard/dist/assets/CUXb_4F3.js.br +0 -0
  46. package/dashboard/dist/assets/CUXb_4F3.js.gz +0 -0
  47. package/dashboard/dist/assets/Cn8sRTkO.js +1 -0
  48. package/dashboard/dist/assets/Cn8sRTkO.js.br +0 -0
  49. package/dashboard/dist/assets/Cn8sRTkO.js.gz +0 -0
  50. package/dashboard/dist/assets/D5IgXoTj.js +1 -0
  51. package/dashboard/dist/assets/D5IgXoTj.js.br +0 -0
  52. package/dashboard/dist/assets/D5IgXoTj.js.gz +0 -0
  53. package/dashboard/dist/assets/DMKyYAtD.js +1 -0
  54. package/dashboard/dist/assets/DMKyYAtD.js.br +0 -0
  55. package/dashboard/dist/assets/DMKyYAtD.js.gz +0 -0
  56. package/dashboard/dist/assets/{DCP-C7fn.js → DXzpQUC0.js} +1 -1
  57. package/dashboard/dist/assets/DXzpQUC0.js.br +0 -0
  58. package/dashboard/dist/assets/DXzpQUC0.js.gz +0 -0
  59. package/dashboard/dist/assets/Dj2k1r16.js +8 -0
  60. package/dashboard/dist/assets/Dj2k1r16.js.br +0 -0
  61. package/dashboard/dist/assets/Dj2k1r16.js.gz +0 -0
  62. package/dashboard/dist/assets/JDPvhd68.js +1 -0
  63. package/dashboard/dist/assets/JDPvhd68.js.br +0 -0
  64. package/dashboard/dist/assets/JDPvhd68.js.gz +0 -0
  65. package/dashboard/dist/assets/R6N_VVqm.js +1 -0
  66. package/dashboard/dist/assets/R6N_VVqm.js.br +0 -0
  67. package/dashboard/dist/assets/R6N_VVqm.js.gz +0 -0
  68. package/dashboard/dist/assets/cEP7N1dn.js +1 -0
  69. package/dashboard/dist/assets/cEP7N1dn.js.br +0 -0
  70. package/dashboard/dist/assets/cEP7N1dn.js.gz +0 -0
  71. package/dashboard/dist/assets/cX2e-TLi.js +1 -0
  72. package/dashboard/dist/assets/cX2e-TLi.js.br +0 -0
  73. package/dashboard/dist/assets/cX2e-TLi.js.gz +0 -0
  74. package/dashboard/dist/assets/eeHXe_OQ.js +9 -0
  75. package/dashboard/dist/assets/eeHXe_OQ.js.br +0 -0
  76. package/dashboard/dist/assets/eeHXe_OQ.js.gz +0 -0
  77. package/dashboard/dist/assets/iLnvdWmW.css +1 -0
  78. package/dashboard/dist/assets/iLnvdWmW.css.br +0 -0
  79. package/dashboard/dist/assets/iLnvdWmW.css.gz +0 -0
  80. package/dashboard/dist/assets/konqMbVI.js +1 -0
  81. package/dashboard/dist/assets/konqMbVI.js.br +0 -0
  82. package/dashboard/dist/assets/konqMbVI.js.gz +0 -0
  83. package/dashboard/dist/assets/wc6cgXzV.js +1 -0
  84. package/dashboard/dist/assets/wc6cgXzV.js.br +0 -0
  85. package/dashboard/dist/assets/wc6cgXzV.js.gz +0 -0
  86. package/dashboard/dist/brand/control-tower.png +0 -0
  87. package/dashboard/dist/brand/design-codex.png +0 -0
  88. package/dashboard/dist/brand/engineering-autopilot.png +0 -0
  89. package/dashboard/dist/brand/launch-captain.png +0 -0
  90. package/dashboard/dist/brand/orgx-logo.png +0 -0
  91. package/dashboard/dist/brand/pipeline-intelligence.png +0 -0
  92. package/dashboard/dist/brand/product-orchestrator.png +0 -0
  93. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  94. package/dashboard/dist/index.html +8 -6
  95. package/dashboard/dist/index.html.br +0 -0
  96. package/dashboard/dist/index.html.gz +0 -0
  97. package/dist/hash-utils.d.ts +1 -0
  98. package/dist/hash-utils.js +4 -0
  99. package/dist/http/helpers/auto-continue-engine.d.ts +15 -0
  100. package/dist/http/helpers/auto-continue-engine.js +289 -67
  101. package/dist/http/helpers/autopilot-slice-utils.js +112 -66
  102. package/dist/http/helpers/hash-utils.d.ts +1 -1
  103. package/dist/http/helpers/hash-utils.js +1 -1
  104. package/dist/http/helpers/mission-control.d.ts +3 -0
  105. package/dist/http/helpers/mission-control.js +43 -9
  106. package/dist/http/helpers/queue-constants.d.ts +37 -0
  107. package/dist/http/helpers/queue-constants.js +34 -0
  108. package/dist/http/helpers/slice-experience-v2.js +2 -5
  109. package/dist/http/helpers/slice-run-projections.js +2 -5
  110. package/dist/http/helpers/value-utils.d.ts +1 -1
  111. package/dist/http/helpers/value-utils.js +5 -2
  112. package/dist/http/helpers/workspace-scope.js +4 -3
  113. package/dist/http/index.js +104 -61
  114. package/dist/http/routes/chat.d.ts +1 -1
  115. package/dist/http/routes/chat.js +3 -23
  116. package/dist/http/routes/entities.js +60 -2
  117. package/dist/http/routes/entity-dynamic.js +49 -9
  118. package/dist/http/routes/live-snapshot.d.ts +10 -1
  119. package/dist/http/routes/live-snapshot.js +15 -26
  120. package/dist/http/routes/mission-control-actions.d.ts +6 -0
  121. package/dist/http/routes/mission-control-actions.js +35 -18
  122. package/dist/http/routes/mission-control-read.js +4 -107
  123. package/dist/lib/type-coercion.d.ts +10 -0
  124. package/dist/lib/type-coercion.js +82 -0
  125. package/dist/mcp-http-handler.js +14 -2
  126. package/dist/openclaw.plugin.json +1 -1
  127. package/dist/services/experiment-randomization.js +9 -2
  128. package/openclaw.plugin.json +1 -1
  129. package/package.json +3 -2
  130. package/dashboard/dist/assets/AqVoI3SF.js.br +0 -0
  131. package/dashboard/dist/assets/AqVoI3SF.js.gz +0 -0
  132. package/dashboard/dist/assets/BC4WvnHJ.js +0 -1
  133. package/dashboard/dist/assets/BC4WvnHJ.js.br +0 -0
  134. package/dashboard/dist/assets/BC4WvnHJ.js.gz +0 -0
  135. package/dashboard/dist/assets/BG5mwTkg.js +0 -1
  136. package/dashboard/dist/assets/BG5mwTkg.js.br +0 -0
  137. package/dashboard/dist/assets/BG5mwTkg.js.gz +0 -0
  138. package/dashboard/dist/assets/BJgZIVUQ.js +0 -53
  139. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  140. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  141. package/dashboard/dist/assets/BNh-XYPV.js +0 -1
  142. package/dashboard/dist/assets/BNh-XYPV.js.br +0 -0
  143. package/dashboard/dist/assets/BNh-XYPV.js.gz +0 -0
  144. package/dashboard/dist/assets/BTAEErUY.js +0 -1
  145. package/dashboard/dist/assets/BTAEErUY.js.br +0 -0
  146. package/dashboard/dist/assets/BTAEErUY.js.gz +0 -0
  147. package/dashboard/dist/assets/BepW_590.js +0 -1
  148. package/dashboard/dist/assets/BepW_590.js.br +0 -0
  149. package/dashboard/dist/assets/BepW_590.js.gz +0 -0
  150. package/dashboard/dist/assets/BerAfzjq.js +0 -1
  151. package/dashboard/dist/assets/BerAfzjq.js.br +0 -0
  152. package/dashboard/dist/assets/BerAfzjq.js.gz +0 -0
  153. package/dashboard/dist/assets/Bp3N-QL5.js +0 -212
  154. package/dashboard/dist/assets/Bp3N-QL5.js.br +0 -0
  155. package/dashboard/dist/assets/Bp3N-QL5.js.gz +0 -0
  156. package/dashboard/dist/assets/C-KIc3Wc.js +0 -1
  157. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  158. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  159. package/dashboard/dist/assets/C3dZRz9P.css +0 -1
  160. package/dashboard/dist/assets/C3dZRz9P.css.br +0 -0
  161. package/dashboard/dist/assets/C3dZRz9P.css.gz +0 -0
  162. package/dashboard/dist/assets/CD-q5mdP.js.br +0 -0
  163. package/dashboard/dist/assets/CD-q5mdP.js.gz +0 -0
  164. package/dashboard/dist/assets/CL_wXqR7.js +0 -1
  165. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  166. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  167. package/dashboard/dist/assets/CdvjC9G9.js +0 -1
  168. package/dashboard/dist/assets/CdvjC9G9.js.br +0 -0
  169. package/dashboard/dist/assets/CdvjC9G9.js.gz +0 -0
  170. package/dashboard/dist/assets/Ck2agw-s.js +0 -1
  171. package/dashboard/dist/assets/Ck2agw-s.js.br +0 -0
  172. package/dashboard/dist/assets/Ck2agw-s.js.gz +0 -0
  173. package/dashboard/dist/assets/CxQ08qFN.js +0 -9
  174. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  175. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  176. package/dashboard/dist/assets/D2CH1H6k.js +0 -1
  177. package/dashboard/dist/assets/D2CH1H6k.js.br +0 -0
  178. package/dashboard/dist/assets/D2CH1H6k.js.gz +0 -0
  179. package/dashboard/dist/assets/D9esz7jd.js +0 -1
  180. package/dashboard/dist/assets/D9esz7jd.js.br +0 -0
  181. package/dashboard/dist/assets/D9esz7jd.js.gz +0 -0
  182. package/dashboard/dist/assets/DCP-C7fn.js.br +0 -0
  183. package/dashboard/dist/assets/DCP-C7fn.js.gz +0 -0
  184. package/dashboard/dist/assets/DJASCd69.js +0 -1
  185. package/dashboard/dist/assets/DJASCd69.js.br +0 -0
  186. package/dashboard/dist/assets/DJASCd69.js.gz +0 -0
  187. package/dashboard/dist/assets/Dm9AybAp.js +0 -1
  188. package/dashboard/dist/assets/Dm9AybAp.js.br +0 -0
  189. package/dashboard/dist/assets/Dm9AybAp.js.gz +0 -0
  190. package/dashboard/dist/assets/Du1wfrXa.js.br +0 -0
  191. package/dashboard/dist/assets/Du1wfrXa.js.gz +0 -0
  192. package/dashboard/dist/assets/beHYBbh6.js.br +0 -0
  193. package/dashboard/dist/assets/beHYBbh6.js.gz +0 -0
  194. package/dashboard/dist/assets/cNrhgGc1.js +0 -8
  195. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  196. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  197. package/dashboard/dist/assets/tcEHYcbW.js.br +0 -0
  198. package/dashboard/dist/assets/tcEHYcbW.js.gz +0 -0
@@ -15,6 +15,7 @@ import { buildMissionControlGraph, DEFAULT_TOKEN_BUDGET_ASSUMPTIONS, dedupeStrin
15
15
  import { createAutopilotRuntime } from "./autopilot-runtime.js";
16
16
  import { buildScopeDirective, buildSliceOutputInstructions, buildWorkstreamSlicePrompt, createCodexBinResolver, ensureAutopilotSliceSchemaPath, extractSessionIdFromLog, extractSessionIdFromOutput, fileUpdatedAtEpochMs, parseSliceResult, readFileTailSafe, readSliceOutputFile, } from "./autopilot-slice-utils.js";
17
17
  import { pickString } from "./value-utils.js";
18
+ import { LaneState, RunStatus, } from "./queue-constants.js";
18
19
  function resolveAutopilotDefaultCwd(filename) {
19
20
  let cursor = dirname(filename);
20
21
  for (let i = 0; i < 12; i += 1) {
@@ -1029,7 +1030,7 @@ export function createAutoContinueEngine(deps) {
1029
1030
  }
1030
1031
  const existing = run.laneByWorkstreamId[normalizedWorkstreamId] ?? {
1031
1032
  workstreamId: normalizedWorkstreamId,
1032
- state: "idle",
1033
+ state: LaneState.IDLE,
1033
1034
  activeRunId: null,
1034
1035
  activeTaskIds: [],
1035
1036
  blockedReason: null,
@@ -1081,12 +1082,12 @@ export function createAutoContinueEngine(deps) {
1081
1082
  if (lane && lane.activeRunId === sliceRunId) {
1082
1083
  setLaneState(run, {
1083
1084
  workstreamId: normalizedWorkstreamId,
1084
- state: lane.state === "blocked" ? "blocked" : "idle",
1085
+ state: lane.state === LaneState.BLOCKED ? "blocked" : "idle",
1085
1086
  activeRunId: null,
1086
1087
  activeTaskIds: [],
1087
1088
  retryAt: lane.retryAt ?? null,
1088
1089
  waitingOnWorkstreamIds: lane.waitingOnWorkstreamIds ?? [],
1089
- blockedReason: lane.state === "blocked" ? lane.blockedReason : null,
1090
+ blockedReason: lane.state === LaneState.BLOCKED ? lane.blockedReason : null,
1090
1091
  });
1091
1092
  }
1092
1093
  }
@@ -1389,7 +1390,7 @@ export function createAutoContinueEngine(deps) {
1389
1390
  updated_at: lane.updatedAt,
1390
1391
  }));
1391
1392
  const patch = {
1392
- auto_continue_enabled: input.run.status === "running" || input.run.status === "stopping",
1393
+ auto_continue_enabled: input.run.status === RunStatus.RUNNING || input.run.status === RunStatus.STOPPING,
1393
1394
  auto_continue_status: input.run.status,
1394
1395
  auto_continue_stop_reason: input.run.stopReason,
1395
1396
  auto_continue_started_at: input.run.startedAt,
@@ -1428,7 +1429,7 @@ export function createAutoContinueEngine(deps) {
1428
1429
  const now = new Date().toISOString();
1429
1430
  ensureRunInternals(input.run);
1430
1431
  const activeRunIds = listActiveSliceRunIds(input.run);
1431
- input.run.status = "stopped";
1432
+ input.run.status = RunStatus.STOPPED;
1432
1433
  input.run.stopReason = input.reason;
1433
1434
  input.run.stoppedAt = now;
1434
1435
  input.run.updatedAt = now;
@@ -1442,7 +1443,7 @@ export function createAutoContinueEngine(deps) {
1442
1443
  if (lane.activeRunId || lane.activeTaskIds.length > 0) {
1443
1444
  setLaneState(input.run, {
1444
1445
  workstreamId: lane.workstreamId,
1445
- state: lane.state === "blocked" ? "blocked" : "idle",
1446
+ state: lane.state === LaneState.BLOCKED ? "blocked" : "idle",
1446
1447
  activeRunId: null,
1447
1448
  activeTaskIds: [],
1448
1449
  });
@@ -1553,6 +1554,20 @@ export function createAutoContinueEngine(deps) {
1553
1554
  decision_count: decisionIds.length,
1554
1555
  last_error: input.run.lastError,
1555
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
+ : {}),
1556
1571
  },
1557
1572
  });
1558
1573
  // Emit autopilot_transition event for state observers.
@@ -1572,7 +1587,7 @@ export function createAutoContinueEngine(deps) {
1572
1587
  event: "autopilot_transition",
1573
1588
  actionType: "run_state_transition",
1574
1589
  }),
1575
- old_state: "running",
1590
+ old_state: LaneState.RUNNING,
1576
1591
  new_state: input.reason === "completed" || input.reason === "stopped" ? "idle" : input.reason === "blocked" ? "blocked" : input.reason === "error" ? "error" : "idle",
1577
1592
  reason: input.reason,
1578
1593
  workspace_id: input.run.allowedWorkstreamIds?.[0] ?? null,
@@ -1774,7 +1789,7 @@ export function createAutoContinueEngine(deps) {
1774
1789
  });
1775
1790
  setLaneState(run, {
1776
1791
  workstreamId: slice.workstreamId,
1777
- state: "blocked",
1792
+ state: LaneState.BLOCKED,
1778
1793
  activeRunId: null,
1779
1794
  activeTaskIds: [],
1780
1795
  blockedReason: slice.lastError,
@@ -1816,6 +1831,9 @@ export function createAutoContinueEngine(deps) {
1816
1831
  clearAutoContinueSliceTransientState(slice.runId);
1817
1832
  const event = killDecision.kind === "timeout" ? "autopilot_slice_timeout" : "autopilot_slice_log_stall";
1818
1833
  const humanLabel = killDecision.kind === "timeout" ? "timed out" : "stalled";
1834
+ const stallDecisionTitle = killDecision.kind === "timeout"
1835
+ ? `Autopilot slice timed out: ${slice.workstreamTitle ?? slice.workstreamId}`
1836
+ : `Autopilot slice stalled: ${slice.workstreamTitle ?? slice.workstreamId}`;
1819
1837
  await emitActivitySafe({
1820
1838
  initiativeId: run.initiativeId,
1821
1839
  runId: slice.runId,
@@ -1849,7 +1867,7 @@ export function createAutoContinueEngine(deps) {
1849
1867
  const decisionResult = await requestDecisionQueued({
1850
1868
  initiativeId: run.initiativeId,
1851
1869
  correlationId: slice.runId,
1852
- title: `Agent ${humanLabel === "timed out" ? "ran out of time" : "stopped making progress"}: ${slice.workstreamTitle ?? slice.workstreamId}`,
1870
+ title: stallDecisionTitle,
1853
1871
  summary: humanizeSliceFailureSummary(slice.lastError ?? `Autopilot slice ${humanLabel}`),
1854
1872
  urgency: "high",
1855
1873
  options: [
@@ -1902,7 +1920,7 @@ export function createAutoContinueEngine(deps) {
1902
1920
  });
1903
1921
  setLaneState(run, {
1904
1922
  workstreamId: slice.workstreamId,
1905
- state: "blocked",
1923
+ state: LaneState.BLOCKED,
1906
1924
  activeRunId: null,
1907
1925
  activeTaskIds: [],
1908
1926
  blockedReason: slice.lastError,
@@ -1968,13 +1986,25 @@ export function createAutoContinueEngine(deps) {
1968
1986
  const decisions = allDecisions.filter((item) => !isParserSyntheticFallbackDecision(item));
1969
1987
  const normalizedBlockingDecisionCount = allDecisions.filter((item) => typeof item.blocking === "boolean" ? item.blocking : defaultDecisionBlocking).length;
1970
1988
  const normalizedNonBlockingDecisionCount = Math.max(0, allDecisions.length - normalizedBlockingDecisionCount);
1971
- const effectiveParsedStatus = parsedStatus === "completed" && normalizedBlockingDecisionCount > 0
1989
+ const operationalParsedStatus = parsedStatus === "completed" && normalizedBlockingDecisionCount > 0
1972
1990
  ? "needs_decision"
1973
1991
  : parsedStatus;
1992
+ const parsedSummarySignal = String(parsed?.summary ?? "").toLowerCase();
1993
+ const parsedLooksLikeNoOutcomeCompletion = operationalParsedStatus === "error" &&
1994
+ (parsedSummarySignal.includes("without verifiable outcomes") ||
1995
+ parsedSummarySignal.includes("without output") ||
1996
+ parsedSummarySignal.includes("without artifacts") ||
1997
+ parsedSummarySignal.includes("did not report artifacts") ||
1998
+ (parsedSummarySignal.includes("did not report") &&
1999
+ parsedSummarySignal.includes("status updates")) ||
2000
+ parsedSummarySignal.includes("produced nothing"));
2001
+ const reportedParsedStatus = parsedLooksLikeNoOutcomeCompletion
2002
+ ? "completed"
2003
+ : operationalParsedStatus;
1974
2004
  slice.status =
1975
- effectiveParsedStatus === "completed"
2005
+ operationalParsedStatus === "completed"
1976
2006
  ? "completed"
1977
- : effectiveParsedStatus === "blocked" || effectiveParsedStatus === "needs_decision"
2007
+ : operationalParsedStatus === "blocked" || operationalParsedStatus === "needs_decision"
1978
2008
  ? "blocked"
1979
2009
  : "error";
1980
2010
  slice.finishedAt = now;
@@ -2054,7 +2084,7 @@ export function createAutoContinueEngine(deps) {
2054
2084
  : [];
2055
2085
  const resultEnvelope = {
2056
2086
  summary: userSummary,
2057
- parsed_status: effectiveParsedStatus,
2087
+ parsed_status: reportedParsedStatus,
2058
2088
  task_updates: taskUpdates,
2059
2089
  milestone_updates: milestoneUpdates,
2060
2090
  next_actions: nextActions,
@@ -2245,7 +2275,7 @@ export function createAutoContinueEngine(deps) {
2245
2275
  correlation_id: slice.runId,
2246
2276
  requested_by_agent_id: run.agentId,
2247
2277
  requested_by_agent_name: run.agentName,
2248
- status: effectiveParsedStatus,
2278
+ status: reportedParsedStatus,
2249
2279
  artifacts: artifacts.length,
2250
2280
  decisions: allDecisions.length,
2251
2281
  blocking_decisions: normalizedBlockingDecisionCount,
@@ -2262,12 +2292,53 @@ export function createAutoContinueEngine(deps) {
2262
2292
  ...mockMeta(slice),
2263
2293
  user_summary: userSummary,
2264
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
+ },
2265
2304
  },
2266
2305
  });
2267
2306
  }
2268
2307
  catch {
2269
2308
  // best effort
2270
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
+ }
2271
2342
  if (slice.status === "completed") {
2272
2343
  await emitActivitySafe({
2273
2344
  initiativeId: run.initiativeId,
@@ -2289,7 +2360,7 @@ export function createAutoContinueEngine(deps) {
2289
2360
  userSummary,
2290
2361
  event: "autopilot_slice_handoff",
2291
2362
  extra: {
2292
- parsed_status: effectiveParsedStatus,
2363
+ parsed_status: reportedParsedStatus,
2293
2364
  artifacts: artifacts.length,
2294
2365
  decisions: decisions.length,
2295
2366
  decision_ids: decisionIds,
@@ -2335,7 +2406,7 @@ export function createAutoContinueEngine(deps) {
2335
2406
  behavior_config_hash: slice.behaviorConfigHash,
2336
2407
  policy_source: slice.behaviorPolicySource,
2337
2408
  behavior_automation_level: slice.behaviorAutomationLevel,
2338
- parsed_status: effectiveParsedStatus,
2409
+ parsed_status: reportedParsedStatus,
2339
2410
  has_output: Boolean(parsed),
2340
2411
  artifacts: artifacts.length,
2341
2412
  decisions: allDecisions.length,
@@ -2344,7 +2415,7 @@ export function createAutoContinueEngine(deps) {
2344
2415
  decision_ids: decisionIds,
2345
2416
  blocking_decision_ids: Array.from(new Set(blockingDecisionIds)),
2346
2417
  non_blocking_decision_ids: Array.from(new Set(nonBlockingDecisionIds)),
2347
- decision_required: blockingDecisionQueued || effectiveParsedStatus === "needs_decision",
2418
+ decision_required: blockingDecisionQueued || operationalParsedStatus === "needs_decision",
2348
2419
  status_updates_applied: statusUpdateResult.applied,
2349
2420
  status_updates_buffered: statusUpdateResult.buffered,
2350
2421
  reported_skill_evidence_count: skillEvidence.length,
@@ -2382,20 +2453,63 @@ export function createAutoContinueEngine(deps) {
2382
2453
  queued: false,
2383
2454
  decisionIds: [],
2384
2455
  };
2456
+ const fallbackRawError = parsed?.summary ??
2457
+ slice.lastError ??
2458
+ (slice.status === "blocked"
2459
+ ? "Execution is blocked and needs intervention."
2460
+ : "Agent process exited without a valid output contract.");
2461
+ const fallbackHumanized = humanizeSliceFailure(fallbackRawError);
2462
+ const fallbackErrorSignal = [
2463
+ parsed?.summary ?? null,
2464
+ slice.lastError ?? null,
2465
+ fallbackRawError,
2466
+ ]
2467
+ .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
2468
+ .join(" ")
2469
+ .toLowerCase();
2470
+ const looksLikeNoOutcome = fallbackErrorSignal.includes("without verifiable outcomes") ||
2471
+ fallbackErrorSignal.includes("without output") ||
2472
+ fallbackErrorSignal.includes("without artifacts") ||
2473
+ fallbackErrorSignal.includes("did not report artifacts") ||
2474
+ (fallbackErrorSignal.includes("did not report") &&
2475
+ fallbackErrorSignal.includes("status updates")) ||
2476
+ fallbackErrorSignal.includes("produced nothing");
2477
+ const looksLikeStall = fallbackErrorSignal.includes("stall") ||
2478
+ fallbackErrorSignal.includes("stopped making progress");
2479
+ const looksLikeTimeout = fallbackErrorSignal.includes("timeout") ||
2480
+ fallbackErrorSignal.includes("timed out") ||
2481
+ fallbackErrorSignal.includes("ran out of time");
2482
+ const blockedLike = slice.status === "blocked" ||
2483
+ looksLikeNoOutcome ||
2484
+ looksLikeStall ||
2485
+ looksLikeTimeout;
2486
+ const decisionConflictSource = looksLikeNoOutcome
2487
+ ? "slice_completed_without_outcome"
2488
+ : looksLikeTimeout
2489
+ ? "slice_timeout"
2490
+ : looksLikeStall
2491
+ ? "slice_stall_no_output"
2492
+ : blockedLike
2493
+ ? "slice_missing_blocking_decision"
2494
+ : "slice_invalid_output";
2495
+ const fallbackDecisionTitle = looksLikeNoOutcome
2496
+ ? `Autopilot slice needs verification: ${slice.workstreamTitle ?? slice.workstreamId}`
2497
+ : looksLikeStall
2498
+ ? `Autopilot slice stalled: ${slice.workstreamTitle ?? slice.workstreamId}`
2499
+ : looksLikeTimeout
2500
+ ? `Autopilot slice timed out: ${slice.workstreamTitle ?? slice.workstreamId}`
2501
+ : blockedLike
2502
+ ? `Autopilot slice blocked: ${slice.workstreamTitle ?? slice.workstreamId}`
2503
+ : `Autopilot slice failed: ${slice.workstreamTitle ?? slice.workstreamId}`;
2504
+ const fallbackDecisionSummary = looksLikeNoOutcome
2505
+ ? "The slice reported completion but did not produce artifacts or status updates. Decide whether to retry, request stronger output, or mark tasks manually."
2506
+ : fallbackHumanized.explanation;
2385
2507
  if (!blockingDecisionQueued) {
2386
- const blockedLike = slice.status === "blocked";
2387
- const fallbackRawError = parsed?.summary ?? slice.lastError ??
2388
- (blockedLike
2389
- ? "Execution is blocked and needs intervention."
2390
- : "Agent process exited without a valid output contract.");
2391
- const fallbackHumanized = humanizeSliceFailure(fallbackRawError);
2392
2508
  fallbackDecisionResult = await requestDecisionQueued({
2393
2509
  initiativeId: run.initiativeId,
2394
2510
  correlationId: slice.runId,
2395
- title: blockedLike
2396
- ? `Agent needs your help: ${slice.workstreamTitle ?? slice.workstreamId}`
2397
- : `${fallbackHumanized.headline}: ${slice.workstreamTitle ?? slice.workstreamId}`,
2398
- summary: fallbackHumanized.explanation,
2511
+ title: fallbackDecisionTitle,
2512
+ summary: fallbackDecisionSummary,
2399
2513
  urgency: "high",
2400
2514
  options: [
2401
2515
  "Retry this workstream slice",
@@ -2403,18 +2517,20 @@ export function createAutoContinueEngine(deps) {
2403
2517
  "Skip this workstream for now",
2404
2518
  ],
2405
2519
  blocking: true,
2406
- decisionType: blockedLike ? "autopilot_blocked_without_decision" : "autopilot_failure",
2520
+ decisionType: looksLikeNoOutcome
2521
+ ? "autopilot_completed_without_outcome"
2522
+ : blockedLike
2523
+ ? "autopilot_blocked_without_decision"
2524
+ : "autopilot_failure",
2407
2525
  workstreamId: slice.workstreamId,
2408
2526
  agentId: slice.agentId,
2409
2527
  sourceSystem: "orgx-autopilot",
2410
- conflictSource: blockedLike
2411
- ? "slice_missing_blocking_decision"
2412
- : "slice_invalid_output",
2528
+ conflictSource: decisionConflictSource,
2413
2529
  dedupeKey: [
2414
2530
  "autopilot",
2415
2531
  run.initiativeId,
2416
2532
  slice.workstreamId,
2417
- blockedLike ? "slice_missing_blocking_decision" : "slice_invalid_output",
2533
+ decisionConflictSource,
2418
2534
  ].join(":"),
2419
2535
  recommendedAction: nextActions[0] ??
2420
2536
  "Review the output contract and logs, then retry or pause autopilot until the blocker is resolved.",
@@ -2422,19 +2538,17 @@ export function createAutoContinueEngine(deps) {
2422
2538
  sourceRef: {
2423
2539
  run_id: slice.runId,
2424
2540
  workstream_id: slice.workstreamId,
2425
- parsed_status: effectiveParsedStatus,
2541
+ parsed_status: reportedParsedStatus,
2426
2542
  },
2427
2543
  evidenceRefs: [
2428
2544
  {
2429
2545
  evidence_type: "slice_output_validation",
2430
2546
  title: "Slice output requires fallback decision",
2431
- summary: parsed?.summary ??
2432
- slice.lastError ??
2433
- "Slice did not provide a blocking decision payload.",
2547
+ summary: fallbackDecisionSummary,
2434
2548
  source_pointer: slice.outputPath,
2435
2549
  payload: {
2436
2550
  log_path: slice.logPath,
2437
- parsed_status: effectiveParsedStatus,
2551
+ parsed_status: reportedParsedStatus,
2438
2552
  },
2439
2553
  },
2440
2554
  ...artifactEvidenceRefs,
@@ -2443,12 +2557,12 @@ export function createAutoContinueEngine(deps) {
2443
2557
  }
2444
2558
  setLaneState(run, {
2445
2559
  workstreamId: slice.workstreamId,
2446
- state: "blocked",
2560
+ state: LaneState.BLOCKED,
2447
2561
  activeRunId: null,
2448
2562
  activeTaskIds: [],
2449
2563
  blockedReason: parsed?.summary ??
2450
2564
  slice.lastError ??
2451
- `Slice returned status: ${effectiveParsedStatus}`,
2565
+ `Slice returned status: ${reportedParsedStatus}`,
2452
2566
  waitingOnWorkstreamIds: [],
2453
2567
  retryAt: null,
2454
2568
  });
@@ -2457,10 +2571,8 @@ export function createAutoContinueEngine(deps) {
2457
2571
  }
2458
2572
  await stopAutoContinueRun({
2459
2573
  run,
2460
- reason: slice.status === "error" ? "error" : "blocked",
2461
- error: parsed?.summary ??
2462
- slice.lastError ??
2463
- `Slice returned status: ${effectiveParsedStatus}`,
2574
+ reason: blockedLike ? "blocked" : "error",
2575
+ error: fallbackRawError,
2464
2576
  decisionRequired: blockingDecisionQueued || fallbackDecisionResult.queued,
2465
2577
  decisionIds: Array.from(new Set([...decisionIds, ...fallbackDecisionResult.decisionIds])),
2466
2578
  });
@@ -2533,7 +2645,7 @@ export function createAutoContinueEngine(deps) {
2533
2645
  });
2534
2646
  setLaneState(run, {
2535
2647
  workstreamId: slice.workstreamId,
2536
- state: "blocked",
2648
+ state: LaneState.BLOCKED,
2537
2649
  activeRunId: null,
2538
2650
  activeTaskIds: [],
2539
2651
  blockedReason: slice.lastError ??
@@ -2567,7 +2679,7 @@ export function createAutoContinueEngine(deps) {
2567
2679
  });
2568
2680
  setLaneState(run, {
2569
2681
  workstreamId: slice.workstreamId,
2570
- state: "completed",
2682
+ state: LaneState.COMPLETED,
2571
2683
  activeRunId: null,
2572
2684
  activeTaskIds: [],
2573
2685
  blockedReason: null,
@@ -2645,7 +2757,7 @@ export function createAutoContinueEngine(deps) {
2645
2757
  }
2646
2758
  syncLegacyRunPointers(run);
2647
2759
  if (run.stopRequested) {
2648
- run.status = "stopping";
2760
+ run.status = RunStatus.STOPPING;
2649
2761
  run.updatedAt = now;
2650
2762
  await stopAutoContinueRun({ run, reason: "stopped" });
2651
2763
  return;
@@ -2697,8 +2809,11 @@ export function createAutoContinueEngine(deps) {
2697
2809
  };
2698
2810
  // Select the next eligible workstream by scanning ordered todos.
2699
2811
  let selectedWorkstreamId = null;
2812
+ let selectedQueueRank = 0;
2700
2813
  let deferredBySpawnGuardRateLimit = 0;
2814
+ let queueScanIndex = 0;
2701
2815
  for (const taskId of graph.recentTodos) {
2816
+ queueScanIndex++;
2702
2817
  const node = nodeById.get(taskId);
2703
2818
  if (!node || node.type !== "task")
2704
2819
  continue;
@@ -2740,6 +2855,7 @@ export function createAutoContinueEngine(deps) {
2740
2855
  continue;
2741
2856
  }
2742
2857
  selectedWorkstreamId = node.workstreamId;
2858
+ selectedQueueRank = queueScanIndex + 1;
2743
2859
  break;
2744
2860
  }
2745
2861
  if (!selectedWorkstreamId) {
@@ -2777,7 +2893,7 @@ export function createAutoContinueEngine(deps) {
2777
2893
  for (const [workstreamId, waitingOnWorkstreamIds] of waitingByWorkstream.entries()) {
2778
2894
  setLaneState(run, {
2779
2895
  workstreamId,
2780
- state: "waiting_dependency",
2896
+ state: LaneState.WAITING_DEPENDENCY,
2781
2897
  activeRunId: null,
2782
2898
  activeTaskIds: [],
2783
2899
  blockedReason: null,
@@ -3028,7 +3144,7 @@ export function createAutoContinueEngine(deps) {
3028
3144
  }
3029
3145
  setLaneState(run, {
3030
3146
  workstreamId: selectedWorkstreamId,
3031
- state: "blocked",
3147
+ state: LaneState.BLOCKED,
3032
3148
  activeRunId: null,
3033
3149
  activeTaskIds: [],
3034
3150
  blockedReason,
@@ -3123,7 +3239,7 @@ export function createAutoContinueEngine(deps) {
3123
3239
  }
3124
3240
  setLaneState(run, {
3125
3241
  workstreamId: selectedWorkstreamId,
3126
- state: "blocked",
3242
+ state: LaneState.BLOCKED,
3127
3243
  activeRunId: null,
3128
3244
  activeTaskIds: [],
3129
3245
  blockedReason,
@@ -3188,7 +3304,7 @@ export function createAutoContinueEngine(deps) {
3188
3304
  });
3189
3305
  setLaneState(run, {
3190
3306
  workstreamId: selectedWorkstreamId,
3191
- state: "rate_limited",
3307
+ state: LaneState.RATE_LIMITED,
3192
3308
  activeRunId: null,
3193
3309
  activeTaskIds: [],
3194
3310
  blockedReason,
@@ -3271,7 +3387,7 @@ export function createAutoContinueEngine(deps) {
3271
3387
  run.updatedAt = now;
3272
3388
  setLaneState(run, {
3273
3389
  workstreamId: selectedWorkstreamId,
3274
- state: "idle",
3390
+ state: LaneState.IDLE,
3275
3391
  activeRunId: null,
3276
3392
  activeTaskIds: [],
3277
3393
  blockedReason: null,
@@ -3379,7 +3495,7 @@ export function createAutoContinueEngine(deps) {
3379
3495
  }
3380
3496
  setLaneState(run, {
3381
3497
  workstreamId: selectedWorkstreamId,
3382
- state: "blocked",
3498
+ state: LaneState.BLOCKED,
3383
3499
  activeRunId: null,
3384
3500
  activeTaskIds: [],
3385
3501
  blockedReason,
@@ -3566,7 +3682,7 @@ export function createAutoContinueEngine(deps) {
3566
3682
  behaviorAutomationLevel,
3567
3683
  sourceClient: executorSourceClient,
3568
3684
  pid: spawned.pid,
3569
- status: "running",
3685
+ status: RunStatus.RUNNING,
3570
3686
  startedAt: now,
3571
3687
  finishedAt: null,
3572
3688
  updatedAt: now,
@@ -3618,6 +3734,10 @@ export function createAutoContinueEngine(deps) {
3618
3734
  scope_milestone_ids: slice.scopeMilestoneIds,
3619
3735
  log_path: logPath,
3620
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",
3621
3741
  ...mockMeta(slice),
3622
3742
  },
3623
3743
  });
@@ -3657,6 +3777,10 @@ export function createAutoContinueEngine(deps) {
3657
3777
  scope_milestone_ids: slice.scopeMilestoneIds,
3658
3778
  log_path: logPath,
3659
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",
3660
3784
  ...mockMeta(slice),
3661
3785
  },
3662
3786
  });
@@ -3684,7 +3808,7 @@ export function createAutoContinueEngine(deps) {
3684
3808
  run.activeTaskIds = dedupeStrings([...run.activeTaskIds, ...slice.taskIds]);
3685
3809
  setLaneState(run, {
3686
3810
  workstreamId: selectedWorkstreamId,
3687
- state: "running",
3811
+ state: LaneState.RUNNING,
3688
3812
  activeRunId: sliceRunId,
3689
3813
  activeTaskIds: slice.taskIds,
3690
3814
  blockedReason: null,
@@ -3763,16 +3887,16 @@ export function createAutoContinueEngine(deps) {
3763
3887
  }
3764
3888
  const lane = run.laneByWorkstreamId[workstreamId] ?? null;
3765
3889
  if (lane &&
3766
- (lane.state === "running" ||
3767
- lane.state === "blocked" ||
3768
- lane.state === "waiting_dependency" ||
3769
- lane.state === "rate_limited")) {
3890
+ (lane.state === LaneState.RUNNING ||
3891
+ lane.state === LaneState.BLOCKED ||
3892
+ lane.state === LaneState.WAITING_DEPENDENCY ||
3893
+ lane.state === LaneState.RATE_LIMITED)) {
3770
3894
  return run;
3771
3895
  }
3772
3896
  if (Array.isArray(run.allowedWorkstreamIds) &&
3773
3897
  run.allowedWorkstreamIds.length > 0 &&
3774
3898
  run.allowedWorkstreamIds.includes(workstreamId) &&
3775
- (run.status === "running" || run.status === "stopping")) {
3899
+ (run.status === RunStatus.RUNNING || run.status === RunStatus.STOPPING)) {
3776
3900
  return run;
3777
3901
  }
3778
3902
  return null;
@@ -3856,13 +3980,13 @@ export function createAutoContinueEngine(deps) {
3856
3980
  const existingRun = autoContinueRuns.get(initiativeId) ?? null;
3857
3981
  if (existingRun &&
3858
3982
  (existingRun.stopRequested ||
3859
- existingRun.status === "stopping" ||
3983
+ existingRun.status === RunStatus.STOPPING ||
3860
3984
  existingRun.stopReason === "stopped")) {
3861
3985
  await emitSkip("paused_by_user");
3862
3986
  return;
3863
3987
  }
3864
3988
  if (existingRun &&
3865
- (existingRun.status === "running" || existingRun.status === "stopping") &&
3989
+ (existingRun.status === RunStatus.RUNNING || existingRun.status === RunStatus.STOPPING) &&
3866
3990
  listActiveSliceRunIds(existingRun).length > 0) {
3867
3991
  const activeRunIds = listActiveSliceRunIds(existingRun);
3868
3992
  await emitSkip("already_running", {
@@ -4075,7 +4199,7 @@ export function createAutoContinueEngine(deps) {
4075
4199
  async function startAutoContinueRun(input) {
4076
4200
  const now = new Date().toISOString();
4077
4201
  const existing = autoContinueRuns.get(input.initiativeId) ?? null;
4078
- const existingIsLive = existing?.status === "running" || existing?.status === "stopping";
4202
+ const existingIsLive = existing?.status === RunStatus.RUNNING || existing?.status === RunStatus.STOPPING;
4079
4203
  const run = existing ??
4080
4204
  {
4081
4205
  initiativeId: input.initiativeId,
@@ -4090,7 +4214,7 @@ export function createAutoContinueEngine(deps) {
4090
4214
  scope: "task",
4091
4215
  tokenBudget: defaultAutoContinueTokenBudget(),
4092
4216
  tokensUsed: 0,
4093
- status: "running",
4217
+ status: RunStatus.RUNNING,
4094
4218
  stopReason: null,
4095
4219
  stopRequested: false,
4096
4220
  startedAt: now,
@@ -4133,7 +4257,7 @@ export function createAutoContinueEngine(deps) {
4133
4257
  ? normalizeTokenBudget(run.tokenBudget, defaultAutoContinueTokenBudget())
4134
4258
  : defaultAutoContinueTokenBudget();
4135
4259
  }
4136
- run.status = "running";
4260
+ run.status = RunStatus.RUNNING;
4137
4261
  run.stopReason = null;
4138
4262
  run.stopRequested = false;
4139
4263
  run.stoppedAt = null;
@@ -4216,8 +4340,8 @@ export function createAutoContinueEngine(deps) {
4216
4340
  event: "autopilot_transition",
4217
4341
  actionType: "run_state_transition",
4218
4342
  }),
4219
- old_state: "idle",
4220
- new_state: "running",
4343
+ old_state: LaneState.IDLE,
4344
+ new_state: LaneState.RUNNING,
4221
4345
  reason: "started",
4222
4346
  workspace_id: run.allowedWorkstreamIds?.[0] ?? null,
4223
4347
  },
@@ -4229,6 +4353,102 @@ export function createAutoContinueEngine(deps) {
4229
4353
  }
4230
4354
  return run;
4231
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
+ }
4232
4452
  return {
4233
4453
  autoContinueRuns,
4234
4454
  autoContinueSliceRuns,
@@ -4250,6 +4470,8 @@ export function createAutoContinueEngine(deps) {
4250
4470
  getAutoContinueLaneForWorkstream,
4251
4471
  scheduleAutoFixForWorkstream,
4252
4472
  startAutoContinueRun,
4473
+ skipCurrentWorkstream,
4474
+ getCanonicalAutopilotState,
4253
4475
  // Session store (for resume support)
4254
4476
  workstreamSessionStore,
4255
4477
  getWorkstreamSession,