@useorgx/openclaw-plugin 0.7.20 → 0.7.24

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 (165) hide show
  1. package/dashboard/dist/assets/B6VftyY6.js +1 -0
  2. package/dashboard/dist/assets/B6VftyY6.js.br +0 -0
  3. package/dashboard/dist/assets/B6VftyY6.js.gz +0 -0
  4. package/dashboard/dist/assets/{Dm0CfDGr.js → BANQdlC4.js} +1 -1
  5. package/dashboard/dist/assets/BANQdlC4.js.br +0 -0
  6. package/dashboard/dist/assets/BANQdlC4.js.gz +0 -0
  7. package/dashboard/dist/assets/{_zpQCpjm.js → BPL4CL3c.js} +1 -1
  8. package/dashboard/dist/assets/BPL4CL3c.js.br +0 -0
  9. package/dashboard/dist/assets/BPL4CL3c.js.gz +0 -0
  10. package/dashboard/dist/assets/{DXVs61e1.js → BZCkOZ20.js} +1 -1
  11. package/dashboard/dist/assets/BZCkOZ20.js.br +0 -0
  12. package/dashboard/dist/assets/BZCkOZ20.js.gz +0 -0
  13. package/dashboard/dist/assets/{BYb6DARX.js → B_LdOJUa.js} +1 -1
  14. package/dashboard/dist/assets/B_LdOJUa.js.br +0 -0
  15. package/dashboard/dist/assets/B_LdOJUa.js.gz +0 -0
  16. package/dashboard/dist/assets/Bfp-wdwb.css +1 -0
  17. package/dashboard/dist/assets/Bfp-wdwb.css.br +0 -0
  18. package/dashboard/dist/assets/Bfp-wdwb.css.gz +0 -0
  19. package/dashboard/dist/assets/{DibzNd0I.js → BvFcH_Iy.js} +1 -1
  20. package/dashboard/dist/assets/BvFcH_Iy.js.br +0 -0
  21. package/dashboard/dist/assets/BvFcH_Iy.js.gz +0 -0
  22. package/dashboard/dist/assets/By0MIBj_.js +1 -0
  23. package/dashboard/dist/assets/By0MIBj_.js.br +0 -0
  24. package/dashboard/dist/assets/By0MIBj_.js.gz +0 -0
  25. package/dashboard/dist/assets/C0i7ABUU.js +212 -0
  26. package/dashboard/dist/assets/C0i7ABUU.js.br +0 -0
  27. package/dashboard/dist/assets/C0i7ABUU.js.gz +0 -0
  28. package/dashboard/dist/assets/CFB0MM7j.js +1 -0
  29. package/dashboard/dist/assets/CFB0MM7j.js.br +0 -0
  30. package/dashboard/dist/assets/CFB0MM7j.js.gz +0 -0
  31. package/dashboard/dist/assets/CQSRb1yu.js +1 -0
  32. package/dashboard/dist/assets/CQSRb1yu.js.br +0 -0
  33. package/dashboard/dist/assets/CQSRb1yu.js.gz +0 -0
  34. package/dashboard/dist/assets/{wa4jJQK9.js → CUoQoSm-.js} +1 -1
  35. package/dashboard/dist/assets/CUoQoSm-.js.br +0 -0
  36. package/dashboard/dist/assets/CUoQoSm-.js.gz +0 -0
  37. package/dashboard/dist/assets/Ckd1R1iE.js +1 -0
  38. package/dashboard/dist/assets/Ckd1R1iE.js.br +0 -0
  39. package/dashboard/dist/assets/Ckd1R1iE.js.gz +0 -0
  40. package/dashboard/dist/assets/{BGY6oI8h.js → CqRNb2EL.js} +1 -1
  41. package/dashboard/dist/assets/CqRNb2EL.js.br +0 -0
  42. package/dashboard/dist/assets/CqRNb2EL.js.gz +0 -0
  43. package/dashboard/dist/assets/{DAr4MfFk.js → DClUc9rw.js} +1 -1
  44. package/dashboard/dist/assets/DClUc9rw.js.br +0 -0
  45. package/dashboard/dist/assets/DClUc9rw.js.gz +0 -0
  46. package/dashboard/dist/assets/DF2PMTwT.js +1 -0
  47. package/dashboard/dist/assets/DF2PMTwT.js.br +0 -0
  48. package/dashboard/dist/assets/DF2PMTwT.js.gz +0 -0
  49. package/dashboard/dist/assets/{B014hrCe.js → DJYl7gyA.js} +2 -2
  50. package/dashboard/dist/assets/DJYl7gyA.js.br +0 -0
  51. package/dashboard/dist/assets/DJYl7gyA.js.gz +0 -0
  52. package/dashboard/dist/assets/{BoDhb8_y.js → DZtNMX0t.js} +2 -2
  53. package/dashboard/dist/assets/DZtNMX0t.js.br +0 -0
  54. package/dashboard/dist/assets/DZtNMX0t.js.gz +0 -0
  55. package/dashboard/dist/assets/DlEa8PI0.js +1 -0
  56. package/dashboard/dist/assets/DlEa8PI0.js.br +0 -0
  57. package/dashboard/dist/assets/DlEa8PI0.js.gz +0 -0
  58. package/dashboard/dist/assets/M4QxcXjh.js +1 -0
  59. package/dashboard/dist/assets/M4QxcXjh.js.br +0 -0
  60. package/dashboard/dist/assets/M4QxcXjh.js.gz +0 -0
  61. package/dashboard/dist/assets/{CV0sWMbv.js → MrW1ixGx.js} +1 -1
  62. package/dashboard/dist/assets/MrW1ixGx.js.br +0 -0
  63. package/dashboard/dist/assets/MrW1ixGx.js.gz +0 -0
  64. package/dashboard/dist/index.html +2 -2
  65. package/dashboard/dist/index.html.br +0 -0
  66. package/dashboard/dist/index.html.gz +0 -0
  67. package/dist/activity-store.js +68 -8
  68. package/dist/agent-run-store.js +162 -24
  69. package/dist/cli/orgx.d.ts +3 -0
  70. package/dist/config/resolution.d.ts +7 -0
  71. package/dist/config/resolution.js +13 -5
  72. package/dist/contracts/onboarding-state.d.ts +2 -0
  73. package/dist/contracts/onboarding-state.js +23 -0
  74. package/dist/contracts/shared-types.d.ts +45 -0
  75. package/dist/http/helpers/auto-continue-engine.d.ts +23 -0
  76. package/dist/http/helpers/auto-continue-engine.js +468 -85
  77. package/dist/http/helpers/autopilot-runtime.js +5 -1
  78. package/dist/http/helpers/autopilot-slice-utils.js +25 -1
  79. package/dist/http/helpers/decision-mapper.d.ts +1 -0
  80. package/dist/http/helpers/decision-mapper.js +19 -2
  81. package/dist/http/helpers/dispatch-lifecycle.js +3 -0
  82. package/dist/http/helpers/mission-control.d.ts +1 -0
  83. package/dist/http/helpers/mission-control.js +5 -2
  84. package/dist/http/helpers/slice-run-projections.d.ts +27 -0
  85. package/dist/http/helpers/slice-run-projections.js +198 -10
  86. package/dist/http/helpers/triage-mapper.js +499 -6
  87. package/dist/http/helpers/value-utils.d.ts +1 -0
  88. package/dist/http/helpers/value-utils.js +17 -0
  89. package/dist/http/index.d.ts +1 -0
  90. package/dist/http/index.js +179 -46
  91. package/dist/http/router.js +64 -9
  92. package/dist/http/routes/live-legacy.d.ts +19 -2
  93. package/dist/http/routes/live-legacy.js +110 -27
  94. package/dist/http/routes/live-snapshot.d.ts +16 -2
  95. package/dist/http/routes/live-snapshot.js +169 -25
  96. package/dist/http/routes/live-triage.js +6 -1
  97. package/dist/http/routes/mission-control-actions.d.ts +9 -0
  98. package/dist/http/routes/mission-control-actions.js +185 -7
  99. package/dist/http/routes/mission-control-read.d.ts +13 -0
  100. package/dist/http/routes/mission-control-read.js +100 -219
  101. package/dist/http/routes/onboarding.d.ts +1 -0
  102. package/dist/http/routes/onboarding.js +17 -0
  103. package/dist/index.d.ts +5 -0
  104. package/dist/index.js +199 -123
  105. package/dist/outbox.d.ts +0 -2
  106. package/dist/outbox.js +259 -148
  107. package/dist/reporting/rollups.js +18 -11
  108. package/dist/runtime-instance-store.js +212 -58
  109. package/dist/stores/materialized-snapshot-store.d.ts +18 -0
  110. package/dist/stores/materialized-snapshot-store.js +91 -0
  111. package/dist/stores/sqlite-state.d.ts +6 -0
  112. package/dist/stores/sqlite-state.js +330 -0
  113. package/package.json +5 -1
  114. package/dashboard/dist/assets/B014hrCe.js.br +0 -0
  115. package/dashboard/dist/assets/B014hrCe.js.gz +0 -0
  116. package/dashboard/dist/assets/BCudUvwg.js +0 -1
  117. package/dashboard/dist/assets/BCudUvwg.js.br +0 -0
  118. package/dashboard/dist/assets/BCudUvwg.js.gz +0 -0
  119. package/dashboard/dist/assets/BGY6oI8h.js.br +0 -0
  120. package/dashboard/dist/assets/BGY6oI8h.js.gz +0 -0
  121. package/dashboard/dist/assets/BJI1Iy5v.css +0 -1
  122. package/dashboard/dist/assets/BJI1Iy5v.css.br +0 -0
  123. package/dashboard/dist/assets/BJI1Iy5v.css.gz +0 -0
  124. package/dashboard/dist/assets/BUvcp_7V.js +0 -1
  125. package/dashboard/dist/assets/BUvcp_7V.js.br +0 -0
  126. package/dashboard/dist/assets/BUvcp_7V.js.gz +0 -0
  127. package/dashboard/dist/assets/BV2Tf8S2.js +0 -212
  128. package/dashboard/dist/assets/BV2Tf8S2.js.br +0 -0
  129. package/dashboard/dist/assets/BV2Tf8S2.js.gz +0 -0
  130. package/dashboard/dist/assets/BYb6DARX.js.br +0 -0
  131. package/dashboard/dist/assets/BYb6DARX.js.gz +0 -0
  132. package/dashboard/dist/assets/BoDhb8_y.js.br +0 -0
  133. package/dashboard/dist/assets/BoDhb8_y.js.gz +0 -0
  134. package/dashboard/dist/assets/Bqk_l0k6.js +0 -1
  135. package/dashboard/dist/assets/Bqk_l0k6.js.br +0 -0
  136. package/dashboard/dist/assets/Bqk_l0k6.js.gz +0 -0
  137. package/dashboard/dist/assets/C-MOJWHs.js +0 -1
  138. package/dashboard/dist/assets/C-MOJWHs.js.br +0 -0
  139. package/dashboard/dist/assets/C-MOJWHs.js.gz +0 -0
  140. package/dashboard/dist/assets/CV0sWMbv.js.br +0 -0
  141. package/dashboard/dist/assets/CV0sWMbv.js.gz +0 -0
  142. package/dashboard/dist/assets/CaAkScfa.js +0 -1
  143. package/dashboard/dist/assets/CaAkScfa.js.br +0 -0
  144. package/dashboard/dist/assets/CaAkScfa.js.gz +0 -0
  145. package/dashboard/dist/assets/Ck5KlsPN.js +0 -1
  146. package/dashboard/dist/assets/Ck5KlsPN.js.br +0 -0
  147. package/dashboard/dist/assets/Ck5KlsPN.js.gz +0 -0
  148. package/dashboard/dist/assets/D2G51wQm.js +0 -1
  149. package/dashboard/dist/assets/D2G51wQm.js.br +0 -0
  150. package/dashboard/dist/assets/D2G51wQm.js.gz +0 -0
  151. package/dashboard/dist/assets/DAr4MfFk.js.br +0 -0
  152. package/dashboard/dist/assets/DAr4MfFk.js.gz +0 -0
  153. package/dashboard/dist/assets/DXVs61e1.js.br +0 -0
  154. package/dashboard/dist/assets/DXVs61e1.js.gz +0 -0
  155. package/dashboard/dist/assets/DibzNd0I.js.br +0 -0
  156. package/dashboard/dist/assets/DibzNd0I.js.gz +0 -0
  157. package/dashboard/dist/assets/Dm0CfDGr.js.br +0 -0
  158. package/dashboard/dist/assets/Dm0CfDGr.js.gz +0 -0
  159. package/dashboard/dist/assets/_zpQCpjm.js.br +0 -0
  160. package/dashboard/dist/assets/_zpQCpjm.js.gz +0 -0
  161. package/dashboard/dist/assets/uNGpYMSH.js +0 -1
  162. package/dashboard/dist/assets/uNGpYMSH.js.br +0 -0
  163. package/dashboard/dist/assets/uNGpYMSH.js.gz +0 -0
  164. package/dashboard/dist/assets/wa4jJQK9.js.br +0 -0
  165. package/dashboard/dist/assets/wa4jJQK9.js.gz +0 -0
@@ -199,7 +199,11 @@ export function createAutopilotRuntime(deps) {
199
199
  function spawnCodexSliceWorker(input) {
200
200
  ensurePrivateDirForFile(input.logPath);
201
201
  ensurePrivateDirForFile(input.outputPath);
202
- const workerKind = (process.env.ORGX_AUTOPILOT_WORKER_KIND ?? "").trim().toLowerCase();
202
+ const workerKind = (input.env.ORGX_AUTOPILOT_WORKER_KIND ??
203
+ process.env.ORGX_AUTOPILOT_WORKER_KIND ??
204
+ "")
205
+ .trim()
206
+ .toLowerCase();
203
207
  if (workerKind === "mock") {
204
208
  const scriptPath = resolve(dirname(deps.filename), "..", "..", "scripts", "mock-autopilot-slice-worker.mjs");
205
209
  const logStream = createSafeAppendStream(input.logPath);
@@ -33,7 +33,27 @@ function autopilotSliceSchema() {
33
33
  const decisionProperties = {
34
34
  question: { type: "string", minLength: 1 },
35
35
  summary: { type: ["string", "null"] },
36
- options: { type: ["array", "null"], items: { type: "string" } },
36
+ options: {
37
+ type: ["array", "null"],
38
+ items: {
39
+ type: ["string", "object"],
40
+ minLength: 1,
41
+ additionalProperties: false,
42
+ required: ["label"],
43
+ properties: {
44
+ id: { type: ["string", "null"] },
45
+ label: { type: "string", minLength: 1 },
46
+ description: { type: ["string", "null"] },
47
+ consequences: { type: ["string", "null"] },
48
+ implied_status: {
49
+ type: ["string", "null"],
50
+ enum: ["approved", "declined", "cancelled", "rejected", null],
51
+ },
52
+ action_type: { type: ["string", "null"] },
53
+ requires_note: { type: ["boolean", "null"] },
54
+ },
55
+ },
56
+ },
37
57
  urgency: {
38
58
  type: ["string", "null"],
39
59
  enum: ["low", "medium", "high", "urgent", null],
@@ -859,6 +879,8 @@ export function buildSliceOutputInstructions(input) {
859
879
  "- Artifacts must be verifiable: include URLs or local paths, plus verification steps.",
860
880
  "- Include `confidence_score` for each artifact (`0` to `1`; use `null` when unknown).",
861
881
  "- If you need a human decision, include it in decisions_needed.",
882
+ "- Prefer structured decision options objects with: id, label, description, consequences, implied_status, action_type, requires_note.",
883
+ "- String options are still accepted, but structured options are required for precise decision routing.",
862
884
  "- For every decisions_needed entry, ALWAYS set blocking explicitly (true or false).",
863
885
  "- If status is blocked, needs_decision, or error: include at least one decisions_needed entry with blocking=true.",
864
886
  "- Status/decision consistency is strict:",
@@ -959,6 +981,8 @@ export function buildWorkstreamSlicePrompt(input) {
959
981
  "- Artifacts must be verifiable: include URLs or local paths, plus verification steps.",
960
982
  "- Include `confidence_score` for each artifact (`0` to `1`; use `null` when unknown).",
961
983
  "- If you need a human decision, include it in decisions_needed.",
984
+ "- Prefer structured decision options objects with: id, label, description, consequences, implied_status, action_type, requires_note.",
985
+ "- String options are still accepted, but structured options are required for precise decision routing.",
962
986
  "- For every decisions_needed entry, ALWAYS set blocking explicitly (true or false).",
963
987
  "- If status is blocked, needs_decision, or error: include at least one decisions_needed entry with blocking=true.",
964
988
  "- Status/decision consistency is strict:",
@@ -5,6 +5,7 @@ type LiveDecisionOption = {
5
5
  id: string;
6
6
  label: string;
7
7
  description: string | null;
8
+ consequences: string | null;
8
9
  impliedStatus: LiveDecisionOptionStatus | null;
9
10
  actionType: DecisionActionType | null;
10
11
  requiresNote: boolean;
@@ -76,6 +76,7 @@ function parseDecisionOptions(record) {
76
76
  id,
77
77
  label,
78
78
  description: null,
79
+ consequences: null,
79
80
  impliedStatus: null,
80
81
  actionType: null,
81
82
  requiresNote: false,
@@ -103,6 +104,9 @@ function parseDecisionOptions(record) {
103
104
  id,
104
105
  label,
105
106
  description: typeof candidate.description === "string" ? candidate.description : null,
107
+ consequences: (typeof candidate.consequences === "string" && candidate.consequences.trim()) ||
108
+ (typeof candidate.impact === "string" && candidate.impact.trim()) ||
109
+ null,
106
110
  impliedStatus: normalizeOptionStatus(candidate.implied_status) ??
107
111
  normalizeOptionStatus(candidate.status) ??
108
112
  normalizeOptionStatus(candidate.disposition),
@@ -202,6 +206,15 @@ export function mapDecisionEntity(entity) {
202
206
  : 0);
203
207
  const options = parseDecisionOptions(record);
204
208
  const evidenceRefs = parseEvidenceRefs(record);
209
+ const metadataBlocker = asRecord(metadata?.blocker);
210
+ const envelopeBlocker = asRecord(envelope?.blocker);
211
+ const blockerContext = (metadataBlocker
212
+ ? pickString(metadataBlocker, ["description", "summary", "required_action", "requiredAction"])
213
+ : null) ??
214
+ (envelopeBlocker
215
+ ? pickString(envelopeBlocker, ["description", "summary", "required_action", "requiredAction"])
216
+ : null) ??
217
+ null;
205
218
  return {
206
219
  id: String(record.id ?? ""),
207
220
  title: pickStringFromRecords(containers, ["title", "name"]) ?? "Decision",
@@ -212,7 +225,7 @@ export function mapDecisionEntity(entity) {
212
225
  "details",
213
226
  "recommended_action",
214
227
  "recommendedAction",
215
- ]),
228
+ ]) ?? blockerContext,
216
229
  status: pickStringFromRecords(containers, ["status", "decision_status"]) ?? "pending",
217
230
  priority: pickStringFromRecords(containers, ["priority"]),
218
231
  decisionType: pickStringFromRecords(containers, ["decision_type", "decisionType"]),
@@ -239,7 +252,11 @@ export function mapDecisionEntity(entity) {
239
252
  recommendedAction: pickStringFromRecords(containers, [
240
253
  "recommended_action",
241
254
  "recommendedAction",
242
- ]),
255
+ ]) ??
256
+ (metadataBlocker
257
+ ? pickString(metadataBlocker, ["required_action", "requiredAction"])
258
+ : null) ??
259
+ null,
243
260
  occurrenceCount: pickNumberFromRecords(containers, ["occurrence_count", "occurrenceCount"]),
244
261
  firstSeenAt: toIsoString(pickStringFromRecords(containers, ["first_seen_at", "firstSeenAt"])),
245
262
  lastSeenAt: toIsoString(pickStringFromRecords(containers, ["last_seen_at", "lastSeenAt"])),
@@ -278,6 +278,9 @@ export function createDispatchLifecycle(deps) {
278
278
  if (typeof record.description === "string" && record.description.trim()) {
279
279
  optionPayload.description = record.description.trim();
280
280
  }
281
+ if (typeof record.consequences === "string" && record.consequences.trim()) {
282
+ optionPayload.consequences = record.consequences.trim();
283
+ }
281
284
  if (typeof record.implied_status === "string" && record.implied_status.trim()) {
282
285
  const implied = record.implied_status.trim().toLowerCase();
283
286
  if (implied === "approved" ||
@@ -92,6 +92,7 @@ export declare function isBlockedStatus(status: string): boolean;
92
92
  export declare function isInProgressStatus(status: string): boolean;
93
93
  export declare function deriveInitiativeLifecycleStatus(currentStatus: string, childStatuses: string[]): string;
94
94
  export declare function isDispatchableWorkstreamStatus(status: string): boolean;
95
+ export declare function normalizeMissionControlStatus(status: string | null | undefined): string;
95
96
  export declare function isDoneStatus(status: string): boolean;
96
97
  export declare function listEntitiesSafe(client: OrgXClient, type: MissionControlNodeType, filters: Record<string, unknown>): Promise<{
97
98
  items: Entity[];
@@ -681,7 +681,7 @@ export function deriveInitiativeLifecycleStatus(currentStatus, childStatuses) {
681
681
  return currentStatus;
682
682
  }
683
683
  export function isDispatchableWorkstreamStatus(status) {
684
- const normalized = status.toLowerCase();
684
+ const normalized = normalizeMissionControlStatus(status);
685
685
  if (!normalized)
686
686
  return true;
687
687
  return !(normalized === "blocked" ||
@@ -691,8 +691,11 @@ export function isDispatchableWorkstreamStatus(status) {
691
691
  normalized === "archived" ||
692
692
  normalized === "deleted");
693
693
  }
694
+ export function normalizeMissionControlStatus(status) {
695
+ return (status ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
696
+ }
694
697
  export function isDoneStatus(status) {
695
- const normalized = status.toLowerCase();
698
+ const normalized = normalizeMissionControlStatus(status);
696
699
  return (normalized === "done" ||
697
700
  normalized === "completed" ||
698
701
  normalized === "cancelled" ||
@@ -16,6 +16,31 @@ export type SliceRunDecisionOption = {
16
16
  impliedStatus: string | null;
17
17
  requiresNote: boolean;
18
18
  };
19
+ export type SliceRunPendingDecision = {
20
+ id: string;
21
+ title: string;
22
+ summary: string | null;
23
+ status: string;
24
+ blocking: boolean;
25
+ decisionType: string | null;
26
+ recommendedAction: string | null;
27
+ updatedAt: string | null;
28
+ sourceRunId: string | null;
29
+ sourceClient: string | null;
30
+ evidenceCount: number;
31
+ options: SliceRunDecisionOption[];
32
+ };
33
+ export type SliceRunBlockerSummary = {
34
+ id: string;
35
+ reason: string;
36
+ waitingOn: string | null;
37
+ requiredAction: string | null;
38
+ source: string | null;
39
+ eventType: string | null;
40
+ eventAt: string | null;
41
+ severity: "info" | "warn" | "error";
42
+ decisionIds: string[];
43
+ };
19
44
  export type SliceRunProjection = {
20
45
  id: string;
21
46
  sliceRunId: string;
@@ -38,6 +63,8 @@ export type SliceRunProjection = {
38
63
  decisionCount: number;
39
64
  blockingDecisionCount: number;
40
65
  decisionOptions: SliceRunDecisionOption[];
66
+ pendingDecisions: SliceRunPendingDecision[];
67
+ blockers: SliceRunBlockerSummary[];
41
68
  sourceClient: string | null;
42
69
  runtimeState: string | null;
43
70
  startedAt: string | null;
@@ -257,6 +257,8 @@ function createProjection(sliceRunId) {
257
257
  decisionCount: 0,
258
258
  blockingDecisionCount: 0,
259
259
  decisionOptions: [],
260
+ pendingDecisions: [],
261
+ blockers: [],
260
262
  sourceClient: null,
261
263
  runtimeState: null,
262
264
  startedAt: null,
@@ -274,6 +276,8 @@ function createProjection(sliceRunId) {
274
276
  _artifactIds: new Set(),
275
277
  _hasExplicitCompletion: false,
276
278
  _peakReportedArtifacts: 0,
279
+ _pendingDecisionById: new Map(),
280
+ _blockerByKey: new Map(),
277
281
  };
278
282
  }
279
283
  function upsertProjection(map, sliceRunId) {
@@ -403,6 +407,19 @@ function maybeAddArtifact(projection, item, metadata) {
403
407
  function mergeDecisionOptions(projection, optionsRaw) {
404
408
  if (!Array.isArray(optionsRaw))
405
409
  return;
410
+ const next = parseDecisionOptions(optionsRaw);
411
+ if (next.length === 0)
412
+ return;
413
+ const merged = new Map();
414
+ for (const option of projection.decisionOptions)
415
+ merged.set(option.id, option);
416
+ for (const option of next)
417
+ merged.set(option.id, option);
418
+ projection.decisionOptions = Array.from(merged.values()).slice(0, 8);
419
+ }
420
+ function parseDecisionOptions(optionsRaw) {
421
+ if (!Array.isArray(optionsRaw))
422
+ return [];
406
423
  const next = [];
407
424
  for (const option of optionsRaw) {
408
425
  const record = asRecord(option);
@@ -420,14 +437,169 @@ function mergeDecisionOptions(projection, optionsRaw) {
420
437
  requiresNote: metadataBoolean(record, ["requiresNote", "requires_note"]) ?? false,
421
438
  });
422
439
  }
423
- if (next.length === 0)
440
+ return next;
441
+ }
442
+ function extractDecisionIdsFromMetadata(metadata) {
443
+ if (!metadata)
444
+ return [];
445
+ const ids = new Set();
446
+ for (const id of metadataStringArray(metadata, [
447
+ "decision_id",
448
+ "decisionId",
449
+ "decision_ids",
450
+ "decisionIds",
451
+ "blocking_decision_ids",
452
+ "blockingDecisionIds",
453
+ "non_blocking_decision_ids",
454
+ "nonBlockingDecisionIds",
455
+ ])) {
456
+ ids.add(id);
457
+ }
458
+ const decisionsNeededRaw = metadata.decisions_needed ?? metadata.decisionsNeeded;
459
+ if (Array.isArray(decisionsNeededRaw)) {
460
+ for (const item of decisionsNeededRaw) {
461
+ const record = asRecord(item);
462
+ if (!record)
463
+ continue;
464
+ const directId = normalizeText(record.id) ??
465
+ normalizeText(record.decision_id) ??
466
+ normalizeText(record.decisionId);
467
+ if (directId)
468
+ ids.add(directId);
469
+ }
470
+ }
471
+ return Array.from(ids);
472
+ }
473
+ function normalizePendingDecisionStatus(value) {
474
+ return (value ?? "pending").trim().toLowerCase() || "pending";
475
+ }
476
+ function isDecisionResolvedStatus(status) {
477
+ return (status === "approved" ||
478
+ status === "declined" ||
479
+ status === "cancelled" ||
480
+ status === "resolved" ||
481
+ status === "closed");
482
+ }
483
+ function upsertPendingDecision(projection, decisionRecord, metadata) {
484
+ const decisionId = normalizeText(decisionRecord.id) ??
485
+ normalizeText(decisionRecord.decision_id) ??
486
+ normalizeText(decisionRecord.entity_id);
487
+ if (!decisionId)
424
488
  return;
425
- const merged = new Map();
426
- for (const option of projection.decisionOptions)
427
- merged.set(option.id, option);
428
- for (const option of next)
429
- merged.set(option.id, option);
430
- projection.decisionOptions = Array.from(merged.values()).slice(0, 8);
489
+ const status = normalizePendingDecisionStatus(normalizeText(decisionRecord.status));
490
+ if (isDecisionResolvedStatus(status)) {
491
+ projection._pendingDecisionById.delete(decisionId);
492
+ return;
493
+ }
494
+ const options = parseDecisionOptions(decisionRecord.options ??
495
+ decisionRecord.decision_options ??
496
+ metadata?.options ??
497
+ metadata?.decision_options);
498
+ if (options.length > 0) {
499
+ mergeDecisionOptions(projection, options);
500
+ }
501
+ const evidenceGroup = decisionRecord.evidenceRefs ??
502
+ decisionRecord.evidence_refs ??
503
+ metadata?.evidence_refs ??
504
+ metadata?.decision_evidence;
505
+ const evidenceCount = Array.isArray(evidenceGroup) ? evidenceGroup.length : 0;
506
+ const updatedAt = toIso(normalizeText(decisionRecord.updatedAt)) ??
507
+ toIso(normalizeText(decisionRecord.updated_at)) ??
508
+ toIso(normalizeText(decisionRecord.requestedAt)) ??
509
+ toIso(normalizeText(decisionRecord.requested_at)) ??
510
+ projection.updatedAt;
511
+ projection._pendingDecisionById.set(decisionId, {
512
+ id: decisionId,
513
+ title: normalizeText(decisionRecord.title) ??
514
+ normalizeText(decisionRecord.name) ??
515
+ "Pending decision",
516
+ summary: normalizeText(decisionRecord.context) ??
517
+ normalizeText(decisionRecord.summary) ??
518
+ normalizeText(decisionRecord.description) ??
519
+ normalizeText(metadata?.summary) ??
520
+ null,
521
+ status,
522
+ blocking: metadataBoolean(metadata, ["blocking"]) !== false,
523
+ decisionType: normalizeText(decisionRecord.decisionType) ??
524
+ normalizeText(decisionRecord.decision_type) ??
525
+ normalizeText(metadata?.decision_type) ??
526
+ null,
527
+ recommendedAction: normalizeText(decisionRecord.recommendedAction) ??
528
+ normalizeText(decisionRecord.recommended_action) ??
529
+ normalizeText(metadata?.recommended_action) ??
530
+ null,
531
+ updatedAt,
532
+ sourceRunId: normalizeText(decisionRecord.sourceRunId) ??
533
+ normalizeText(decisionRecord.source_run_id) ??
534
+ normalizeText(metadata?.source_run_id) ??
535
+ normalizeText(metadata?.run_id) ??
536
+ normalizeText(metadata?.correlation_id) ??
537
+ null,
538
+ sourceClient: normalizeText(decisionRecord.sourceClient) ??
539
+ normalizeText(decisionRecord.source_client) ??
540
+ normalizeText(metadata?.source_client) ??
541
+ null,
542
+ evidenceCount,
543
+ options: options.slice(0, 4),
544
+ });
545
+ }
546
+ function inferBlockerFromActivity(item, metadata, event, atIso) {
547
+ const blocker = asRecord(metadata?.blocker);
548
+ const reason = normalizeText(blocker?.description) ??
549
+ normalizeText(blocker?.summary) ??
550
+ metadataString(metadata, ["error", "reason", "blocked_reason", "blockedReason", "last_error", "lastError"]) ??
551
+ normalizeText(item.summary) ??
552
+ normalizeText(item.description) ??
553
+ null;
554
+ if (!reason)
555
+ return null;
556
+ const waitingOn = normalizeText(blocker?.waiting_on) ??
557
+ normalizeText(blocker?.required_actor) ??
558
+ metadataString(metadata, ["waiting_on", "required_actor", "requiredActor"]) ??
559
+ null;
560
+ const requiredAction = normalizeText(blocker?.required_action) ??
561
+ normalizeText(blocker?.requiredAction) ??
562
+ metadataString(metadata, ["next_step", "nextStep", "recommended_action", "recommendedAction"]) ??
563
+ null;
564
+ const source = metadataString(metadata, ["source", "source_system", "sourceSystem"]) ??
565
+ null;
566
+ const severity = item.type === "run_failed" || event.includes("failed") || event.includes("error")
567
+ ? "error"
568
+ : item.type === "blocker_created" || event.includes("blocked")
569
+ ? "warn"
570
+ : "info";
571
+ const decisionIds = extractDecisionIdsFromMetadata(metadata);
572
+ return {
573
+ id: normalizeText(item.id) ?? `${event || item.type}:${atIso ?? "unknown"}`,
574
+ reason,
575
+ waitingOn,
576
+ requiredAction,
577
+ source,
578
+ eventType: event || item.type,
579
+ eventAt: atIso,
580
+ severity,
581
+ decisionIds,
582
+ };
583
+ }
584
+ function upsertBlocker(projection, blocker) {
585
+ const key = [
586
+ blocker.reason.trim().toLowerCase(),
587
+ blocker.waitingOn?.trim().toLowerCase() ?? "",
588
+ blocker.requiredAction?.trim().toLowerCase() ?? "",
589
+ ].join("|");
590
+ const existing = projection._blockerByKey.get(key);
591
+ if (!existing) {
592
+ projection._blockerByKey.set(key, blocker);
593
+ return;
594
+ }
595
+ const existingEpoch = toEpoch(existing.eventAt);
596
+ const nextEpoch = toEpoch(blocker.eventAt);
597
+ const keep = nextEpoch >= existingEpoch ? blocker : existing;
598
+ keep.decisionIds = dedupeStrings([
599
+ ...(existing.decisionIds ?? []),
600
+ ...(blocker.decisionIds ?? []),
601
+ ]);
602
+ projection._blockerByKey.set(key, keep);
431
603
  }
432
604
  function applySessionFallback(projection, session) {
433
605
  const status = (session.status ?? "").trim().toLowerCase();
@@ -500,6 +672,10 @@ export function buildSliceRunProjections(input) {
500
672
  const projection = upsertProjection(projections, sliceRunId);
501
673
  updateProjectionContext(projection, item, metadata);
502
674
  const atIso = toIso(item.timestamp);
675
+ const blockerFromEvent = inferBlockerFromActivity(item, metadata, event, atIso);
676
+ if (blockerFromEvent) {
677
+ upsertBlocker(projection, blockerFromEvent);
678
+ }
503
679
  if (item.type === "artifact_created") {
504
680
  maybeAddArtifact(projection, item, metadata);
505
681
  if (projection.status !== "failed" && projection.status !== "archived") {
@@ -716,14 +892,20 @@ export function buildSliceRunProjections(input) {
716
892
  if (!projection)
717
893
  continue;
718
894
  const status = (normalizeText(decisionRecord?.status) ?? "pending").toLowerCase();
719
- if (status === "approved" || status === "declined" || status === "cancelled" || status === "resolved") {
720
- continue;
895
+ if (decisionRecord) {
896
+ upsertPendingDecision(projection, decisionRecord, metadata);
721
897
  }
898
+ if (isDecisionResolvedStatus(status))
899
+ continue;
722
900
  projection.decisionCount += 1;
723
901
  const isBlocking = metadataBoolean(metadata, ["blocking"]) !== false;
724
902
  if (isBlocking)
725
903
  projection.blockingDecisionCount += 1;
726
- mergeDecisionOptions(projection, decisionRecord?.options ?? null);
904
+ mergeDecisionOptions(projection, decisionRecord?.options ??
905
+ decisionRecord?.decision_options ??
906
+ metadata?.options ??
907
+ metadata?.decision_options ??
908
+ null);
727
909
  const updatedAt = toIso(normalizeText(decisionRecord?.updatedAt)) ??
728
910
  toIso(normalizeText(decisionRecord?.requestedAt)) ??
729
911
  projection.updatedAt;
@@ -851,6 +1033,12 @@ export function buildSliceRunProjections(input) {
851
1033
  decisionCount: projection.decisionCount,
852
1034
  blockingDecisionCount: projection.blockingDecisionCount,
853
1035
  decisionOptions: projection.decisionOptions,
1036
+ pendingDecisions: Array.from(projection._pendingDecisionById.values())
1037
+ .sort((a, b) => toEpoch(b.updatedAt) - toEpoch(a.updatedAt))
1038
+ .slice(0, 8),
1039
+ blockers: Array.from(projection._blockerByKey.values())
1040
+ .sort((a, b) => toEpoch(b.eventAt) - toEpoch(a.eventAt))
1041
+ .slice(0, 6),
854
1042
  sourceClient: projection.sourceClient,
855
1043
  runtimeState: projection.runtimeState,
856
1044
  startedAt: projection.startedAt,