@useorgx/openclaw-plugin 0.7.23 → 0.7.25
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.
- package/dashboard/dist/assets/{m2smti3F.js → B6VftyY6.js} +1 -1
- package/dashboard/dist/assets/B6VftyY6.js.br +0 -0
- package/dashboard/dist/assets/B6VftyY6.js.gz +0 -0
- package/dashboard/dist/assets/{D-FuHfT8.js → BANQdlC4.js} +1 -1
- package/dashboard/dist/assets/BANQdlC4.js.br +0 -0
- package/dashboard/dist/assets/BANQdlC4.js.gz +0 -0
- package/dashboard/dist/assets/{DDCPrZRt.js → BPL4CL3c.js} +1 -1
- package/dashboard/dist/assets/BPL4CL3c.js.br +0 -0
- package/dashboard/dist/assets/BPL4CL3c.js.gz +0 -0
- package/dashboard/dist/assets/{D0PN5_vY.js → BZCkOZ20.js} +1 -1
- package/dashboard/dist/assets/BZCkOZ20.js.br +0 -0
- package/dashboard/dist/assets/BZCkOZ20.js.gz +0 -0
- package/dashboard/dist/assets/{DNQ-iFO2.js → B_LdOJUa.js} +1 -1
- package/dashboard/dist/assets/B_LdOJUa.js.br +0 -0
- package/dashboard/dist/assets/B_LdOJUa.js.gz +0 -0
- package/dashboard/dist/assets/Bfp-wdwb.css +1 -0
- package/dashboard/dist/assets/Bfp-wdwb.css.br +0 -0
- package/dashboard/dist/assets/{C1u2SGin.css.gz → Bfp-wdwb.css.gz} +0 -0
- package/dashboard/dist/assets/{CZXS5i_5.js → BvFcH_Iy.js} +1 -1
- package/dashboard/dist/assets/BvFcH_Iy.js.br +0 -0
- package/dashboard/dist/assets/BvFcH_Iy.js.gz +0 -0
- package/dashboard/dist/assets/C0i7ABUU.js +212 -0
- package/dashboard/dist/assets/C0i7ABUU.js.br +0 -0
- package/dashboard/dist/assets/C0i7ABUU.js.gz +0 -0
- package/dashboard/dist/assets/CFB0MM7j.js +1 -0
- package/dashboard/dist/assets/CFB0MM7j.js.br +0 -0
- package/dashboard/dist/assets/CFB0MM7j.js.gz +0 -0
- package/dashboard/dist/assets/{OlLPtzdz.js → CQSRb1yu.js} +1 -1
- package/dashboard/dist/assets/CQSRb1yu.js.br +0 -0
- package/dashboard/dist/assets/CQSRb1yu.js.gz +0 -0
- package/dashboard/dist/assets/{CbVWL74-.js → CUoQoSm-.js} +1 -1
- package/dashboard/dist/assets/CUoQoSm-.js.br +0 -0
- package/dashboard/dist/assets/CUoQoSm-.js.gz +0 -0
- package/dashboard/dist/assets/Ckd1R1iE.js +1 -0
- package/dashboard/dist/assets/Ckd1R1iE.js.br +0 -0
- package/dashboard/dist/assets/Ckd1R1iE.js.gz +0 -0
- package/dashboard/dist/assets/{DhPuHPK7.js → CqRNb2EL.js} +1 -1
- package/dashboard/dist/assets/CqRNb2EL.js.br +0 -0
- package/dashboard/dist/assets/CqRNb2EL.js.gz +0 -0
- package/dashboard/dist/assets/{CGJiHCIx.js → DClUc9rw.js} +1 -1
- package/dashboard/dist/assets/DClUc9rw.js.br +0 -0
- package/dashboard/dist/assets/DClUc9rw.js.gz +0 -0
- package/dashboard/dist/assets/DF2PMTwT.js +1 -0
- package/dashboard/dist/assets/DF2PMTwT.js.br +0 -0
- package/dashboard/dist/assets/DF2PMTwT.js.gz +0 -0
- package/dashboard/dist/assets/{RN4M9u9W.js → DJYl7gyA.js} +1 -1
- package/dashboard/dist/assets/DJYl7gyA.js.br +0 -0
- package/dashboard/dist/assets/DJYl7gyA.js.gz +0 -0
- package/dashboard/dist/assets/{BrMXbzQ-.js → DZtNMX0t.js} +1 -1
- package/dashboard/dist/assets/DZtNMX0t.js.br +0 -0
- package/dashboard/dist/assets/DZtNMX0t.js.gz +0 -0
- package/dashboard/dist/assets/{LOFrVoPD.js → DlEa8PI0.js} +1 -1
- package/dashboard/dist/assets/DlEa8PI0.js.br +0 -0
- package/dashboard/dist/assets/DlEa8PI0.js.gz +0 -0
- package/dashboard/dist/assets/M4QxcXjh.js +1 -0
- package/dashboard/dist/assets/M4QxcXjh.js.br +0 -0
- package/dashboard/dist/assets/M4QxcXjh.js.gz +0 -0
- package/dashboard/dist/assets/{nra1yvJX.js → MrW1ixGx.js} +1 -1
- package/dashboard/dist/assets/MrW1ixGx.js.br +0 -0
- package/dashboard/dist/assets/MrW1ixGx.js.gz +0 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/activity-store.js +68 -8
- package/dist/contracts/shared-types.d.ts +28 -0
- package/dist/http/helpers/auto-continue-engine.js +235 -32
- package/dist/http/helpers/triage-mapper.js +285 -6
- package/dist/http/helpers/value-utils.d.ts +1 -0
- package/dist/http/helpers/value-utils.js +17 -0
- package/dist/http/index.js +89 -3
- package/dist/http/routes/live-triage.js +6 -1
- package/dist/http/routes/mission-control-actions.d.ts +9 -0
- package/dist/http/routes/mission-control-actions.js +157 -7
- package/dist/http/routes/mission-control-read.d.ts +9 -0
- package/dist/http/routes/mission-control-read.js +33 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/stores/sqlite-state.d.ts +1 -1
- package/dist/stores/sqlite-state.js +153 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/9gFmK3Kr.js +0 -1
- package/dashboard/dist/assets/9gFmK3Kr.js.br +0 -0
- package/dashboard/dist/assets/9gFmK3Kr.js.gz +0 -0
- package/dashboard/dist/assets/BrMXbzQ-.js.br +0 -0
- package/dashboard/dist/assets/BrMXbzQ-.js.gz +0 -0
- package/dashboard/dist/assets/C1u2SGin.css +0 -1
- package/dashboard/dist/assets/C1u2SGin.css.br +0 -0
- package/dashboard/dist/assets/CGJiHCIx.js.br +0 -0
- package/dashboard/dist/assets/CGJiHCIx.js.gz +0 -0
- package/dashboard/dist/assets/CSd4rSuU.js +0 -212
- package/dashboard/dist/assets/CSd4rSuU.js.br +0 -0
- package/dashboard/dist/assets/CSd4rSuU.js.gz +0 -0
- package/dashboard/dist/assets/CZXS5i_5.js.br +0 -0
- package/dashboard/dist/assets/CZXS5i_5.js.gz +0 -0
- package/dashboard/dist/assets/CbVWL74-.js.br +0 -0
- package/dashboard/dist/assets/CbVWL74-.js.gz +0 -0
- package/dashboard/dist/assets/D-FuHfT8.js.br +0 -0
- package/dashboard/dist/assets/D-FuHfT8.js.gz +0 -0
- package/dashboard/dist/assets/D0PN5_vY.js.br +0 -0
- package/dashboard/dist/assets/D0PN5_vY.js.gz +0 -0
- package/dashboard/dist/assets/DDCPrZRt.js.br +0 -0
- package/dashboard/dist/assets/DDCPrZRt.js.gz +0 -0
- package/dashboard/dist/assets/DNQ-iFO2.js.br +0 -0
- package/dashboard/dist/assets/DNQ-iFO2.js.gz +0 -0
- package/dashboard/dist/assets/DhPuHPK7.js.br +0 -0
- package/dashboard/dist/assets/DhPuHPK7.js.gz +0 -0
- package/dashboard/dist/assets/Dhz7qPtn.js +0 -1
- package/dashboard/dist/assets/Dhz7qPtn.js.br +0 -0
- package/dashboard/dist/assets/Dhz7qPtn.js.gz +0 -0
- package/dashboard/dist/assets/LOFrVoPD.js.br +0 -0
- package/dashboard/dist/assets/LOFrVoPD.js.gz +0 -0
- package/dashboard/dist/assets/OlLPtzdz.js.br +0 -0
- package/dashboard/dist/assets/OlLPtzdz.js.gz +0 -0
- package/dashboard/dist/assets/RN4M9u9W.js.br +0 -0
- package/dashboard/dist/assets/RN4M9u9W.js.gz +0 -0
- package/dashboard/dist/assets/VCHu272d.js +0 -1
- package/dashboard/dist/assets/VCHu272d.js.br +0 -0
- package/dashboard/dist/assets/VCHu272d.js.gz +0 -0
- package/dashboard/dist/assets/m2smti3F.js.br +0 -0
- package/dashboard/dist/assets/m2smti3F.js.gz +0 -0
- package/dashboard/dist/assets/nra1yvJX.js.br +0 -0
- package/dashboard/dist/assets/nra1yvJX.js.gz +0 -0
- package/dashboard/dist/assets/qLX6NZ-J.js +0 -1
- package/dashboard/dist/assets/qLX6NZ-J.js.br +0 -0
- package/dashboard/dist/assets/qLX6NZ-J.js.gz +0 -0
|
@@ -53,6 +53,126 @@ function countArray(record, keys) {
|
|
|
53
53
|
}
|
|
54
54
|
return 0;
|
|
55
55
|
}
|
|
56
|
+
function pickBoolean(record, keys) {
|
|
57
|
+
if (!record)
|
|
58
|
+
return null;
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
const candidate = record[key];
|
|
61
|
+
if (typeof candidate === "boolean")
|
|
62
|
+
return candidate;
|
|
63
|
+
if (typeof candidate === "string") {
|
|
64
|
+
const normalized = candidate.trim().toLowerCase();
|
|
65
|
+
if (normalized === "true" || normalized === "yes" || normalized === "1")
|
|
66
|
+
return true;
|
|
67
|
+
if (normalized === "false" || normalized === "no" || normalized === "0")
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function normalizeDecisionOptionsFromUnknown(...values) {
|
|
74
|
+
const options = [];
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const value of values) {
|
|
77
|
+
if (!Array.isArray(value))
|
|
78
|
+
continue;
|
|
79
|
+
for (const entry of value) {
|
|
80
|
+
if (typeof entry === "string") {
|
|
81
|
+
const label = entry.trim();
|
|
82
|
+
if (!label)
|
|
83
|
+
continue;
|
|
84
|
+
const key = label.toLowerCase();
|
|
85
|
+
if (seen.has(key))
|
|
86
|
+
continue;
|
|
87
|
+
seen.add(key);
|
|
88
|
+
options.push({ label });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const record = asRecord(entry);
|
|
92
|
+
if (!record)
|
|
93
|
+
continue;
|
|
94
|
+
const label = pickString(record, ["label", "title", "name", "question"]) ??
|
|
95
|
+
pickString(record, ["action", "action_type", "actionType"]);
|
|
96
|
+
if (!label)
|
|
97
|
+
continue;
|
|
98
|
+
const id = pickString(record, ["id", "option_id", "optionId"]);
|
|
99
|
+
const description = pickString(record, ["description", "summary"]);
|
|
100
|
+
const consequences = pickString(record, ["consequences", "impact"]);
|
|
101
|
+
const actionType = pickString(record, ["action_type", "actionType", "action"]);
|
|
102
|
+
const impliedStatus = pickString(record, ["implied_status", "impliedStatus", "status"]);
|
|
103
|
+
const requiresNote = pickBoolean(record, ["requires_note", "requiresNote", "note_required"]);
|
|
104
|
+
const recommended = pickBoolean(record, ["recommended", "is_recommended", "isRecommended"]);
|
|
105
|
+
const key = `${(id ?? "").toLowerCase()}|${label.toLowerCase()}`;
|
|
106
|
+
if (seen.has(key))
|
|
107
|
+
continue;
|
|
108
|
+
seen.add(key);
|
|
109
|
+
options.push({
|
|
110
|
+
...(id ? { id } : {}),
|
|
111
|
+
label,
|
|
112
|
+
...(description ? { description } : {}),
|
|
113
|
+
...(consequences ? { consequences } : {}),
|
|
114
|
+
...(actionType ? { actionType } : {}),
|
|
115
|
+
...(impliedStatus ? { impliedStatus } : {}),
|
|
116
|
+
...(typeof requiresNote === "boolean" ? { requiresNote } : {}),
|
|
117
|
+
...(typeof recommended === "boolean" ? { recommended } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return options.slice(0, 8);
|
|
122
|
+
}
|
|
123
|
+
function normalizeEvidenceFromUnknown(...values) {
|
|
124
|
+
const evidence = [];
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
for (const value of values) {
|
|
127
|
+
if (!Array.isArray(value))
|
|
128
|
+
continue;
|
|
129
|
+
for (const entry of value) {
|
|
130
|
+
const record = asRecord(entry);
|
|
131
|
+
if (!record)
|
|
132
|
+
continue;
|
|
133
|
+
const title = pickString(record, ["title", "label", "name"]) ??
|
|
134
|
+
pickString(record, ["source_pointer", "sourcePointer", "source_url", "sourceUrl"]) ??
|
|
135
|
+
"Evidence";
|
|
136
|
+
const summary = pickString(record, ["summary", "description"]);
|
|
137
|
+
const url = pickString(record, ["source_url", "sourceUrl", "url"]);
|
|
138
|
+
const pointer = pickString(record, ["source_pointer", "sourcePointer", "path"]);
|
|
139
|
+
const evidenceType = pickString(record, ["evidence_type", "evidenceType", "type"]);
|
|
140
|
+
const confidenceRaw = record.confidence ?? record.confidence_score;
|
|
141
|
+
const confidence = typeof confidenceRaw === "number" && Number.isFinite(confidenceRaw)
|
|
142
|
+
? Math.max(0, Math.min(1, confidenceRaw))
|
|
143
|
+
: null;
|
|
144
|
+
const key = `${title.toLowerCase()}|${url ?? ""}|${pointer ?? ""}`;
|
|
145
|
+
if (seen.has(key))
|
|
146
|
+
continue;
|
|
147
|
+
seen.add(key);
|
|
148
|
+
evidence.push({
|
|
149
|
+
title,
|
|
150
|
+
...(summary ? { summary } : {}),
|
|
151
|
+
...(url ? { url } : {}),
|
|
152
|
+
...(pointer ? { pointer } : {}),
|
|
153
|
+
...(evidenceType ? { evidenceType } : {}),
|
|
154
|
+
...(confidence !== null ? { confidence } : {}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return evidence.slice(0, 8);
|
|
159
|
+
}
|
|
160
|
+
function pickHierarchy(record, keys) {
|
|
161
|
+
if (!record)
|
|
162
|
+
return [];
|
|
163
|
+
for (const key of keys) {
|
|
164
|
+
const candidate = record[key];
|
|
165
|
+
if (!Array.isArray(candidate))
|
|
166
|
+
continue;
|
|
167
|
+
const normalized = candidate
|
|
168
|
+
.filter((entry) => typeof entry === "string")
|
|
169
|
+
.map((entry) => entry.trim())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
if (normalized.length > 0)
|
|
172
|
+
return normalized;
|
|
173
|
+
}
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
56
176
|
function deriveInterventionContext(input) {
|
|
57
177
|
const metadata = asRecord(input.metadata);
|
|
58
178
|
const result = asRecord(metadata?.result);
|
|
@@ -101,6 +221,28 @@ function deriveInterventionContext(input) {
|
|
|
101
221
|
countArray(metadata, ["task_updates", "taskUpdates"]);
|
|
102
222
|
const milestoneUpdateCount = countArray(result, ["milestone_updates", "milestoneUpdates"]) ||
|
|
103
223
|
countArray(metadata, ["milestone_updates", "milestoneUpdates"]);
|
|
224
|
+
const decisionPrompt = pickString(metadata, ["decision_prompt", "decisionPrompt", "question", "decision_title", "decisionTitle"]) ??
|
|
225
|
+
pickString(result, ["decision_prompt", "decisionPrompt", "question"]);
|
|
226
|
+
const decisionSummary = pickString(metadata, ["decision_summary", "decisionSummary", "summary", "context"]) ??
|
|
227
|
+
pickString(result, ["decision_summary", "decisionSummary", "summary"]);
|
|
228
|
+
const decisionOptions = normalizeDecisionOptionsFromUnknown(metadata?.decision_options, metadata?.decisionOptions, metadata?.options, result?.decision_options, result?.decisionOptions, result?.options);
|
|
229
|
+
const recommendedAction = pickString(metadata, ["recommended_action", "recommendedAction"]) ??
|
|
230
|
+
pickString(result, ["recommended_action", "recommendedAction"]) ??
|
|
231
|
+
requiredAction;
|
|
232
|
+
const scopeHierarchy = [
|
|
233
|
+
...pickHierarchy(metadata, ["scope_hierarchy", "scopeHierarchy"]),
|
|
234
|
+
...pickHierarchy(result, ["scope_hierarchy", "scopeHierarchy"]),
|
|
235
|
+
].filter((entry, index, source) => source.indexOf(entry) === index);
|
|
236
|
+
const currentRunState = pickString(metadata, ["current_run_state", "currentRunState", "runtime_state", "runtimeState", "status"]) ??
|
|
237
|
+
pickString(result, ["current_run_state", "currentRunState", "runtime_state", "runtimeState", "status"]);
|
|
238
|
+
const impactIfDelayed = pickString(metadata, ["impact_if_delayed", "impactIfDelayed"]) ??
|
|
239
|
+
pickString(result, ["impact_if_delayed", "impactIfDelayed"]);
|
|
240
|
+
const evidence = normalizeEvidenceFromUnknown(metadata?.evidence_refs, metadata?.evidenceRefs, result?.evidence_refs, result?.evidenceRefs);
|
|
241
|
+
const artifacts = pickStringArray(metadata, ["artifacts_created", "artifact_titles", "artifactTitles"]);
|
|
242
|
+
const updatesApplied = [
|
|
243
|
+
...pickStringArray(metadata, ["updates_applied", "updatesApplied"]),
|
|
244
|
+
...pickStringArray(result, ["updates_applied", "updatesApplied"]),
|
|
245
|
+
];
|
|
104
246
|
const context = {
|
|
105
247
|
blockerReason,
|
|
106
248
|
waitingOn,
|
|
@@ -114,6 +256,16 @@ function deriveInterventionContext(input) {
|
|
|
114
256
|
decisionIds: decisionIds.length > 0 ? Array.from(new Set(decisionIds)) : [],
|
|
115
257
|
taskUpdateCount: taskUpdateCount > 0 ? taskUpdateCount : undefined,
|
|
116
258
|
milestoneUpdateCount: milestoneUpdateCount > 0 ? milestoneUpdateCount : undefined,
|
|
259
|
+
decisionPrompt,
|
|
260
|
+
decisionSummary,
|
|
261
|
+
decisionOptions: decisionOptions.length > 0 ? decisionOptions : undefined,
|
|
262
|
+
recommendedAction,
|
|
263
|
+
scopeHierarchy: scopeHierarchy.length > 0 ? scopeHierarchy : undefined,
|
|
264
|
+
currentRunState,
|
|
265
|
+
impactIfDelayed,
|
|
266
|
+
artifacts: artifacts.length > 0 ? Array.from(new Set(artifacts)) : undefined,
|
|
267
|
+
evidence: evidence.length > 0 ? evidence : undefined,
|
|
268
|
+
updatesApplied: updatesApplied.length > 0 ? Array.from(new Set(updatesApplied)) : undefined,
|
|
117
269
|
};
|
|
118
270
|
const hasValue = [
|
|
119
271
|
context.blockerReason,
|
|
@@ -125,9 +277,19 @@ function deriveInterventionContext(input) {
|
|
|
125
277
|
context.retryable,
|
|
126
278
|
context.taskUpdateCount,
|
|
127
279
|
context.milestoneUpdateCount,
|
|
280
|
+
context.decisionPrompt,
|
|
281
|
+
context.decisionSummary,
|
|
282
|
+
context.recommendedAction,
|
|
283
|
+
context.currentRunState,
|
|
284
|
+
context.impactIfDelayed,
|
|
128
285
|
context.suggestedActions?.length,
|
|
129
286
|
context.nextActions?.length,
|
|
130
287
|
context.decisionIds?.length,
|
|
288
|
+
context.decisionOptions?.length,
|
|
289
|
+
context.scopeHierarchy?.length,
|
|
290
|
+
context.artifacts?.length,
|
|
291
|
+
context.evidence?.length,
|
|
292
|
+
context.updatesApplied?.length,
|
|
131
293
|
].some((entry) => {
|
|
132
294
|
if (typeof entry === "number")
|
|
133
295
|
return entry > 0;
|
|
@@ -310,6 +472,72 @@ const FAILURE_MAPPINGS = {
|
|
|
310
472
|
},
|
|
311
473
|
],
|
|
312
474
|
},
|
|
475
|
+
decision_required: {
|
|
476
|
+
kind: "decision_required",
|
|
477
|
+
severity: "high",
|
|
478
|
+
recommendedAction: "Review the options and choose the next move",
|
|
479
|
+
defaultTitle: (ctx) => `Decision required${ctx.workstreamTitle ? `: ${ctx.workstreamTitle}` : ""}`,
|
|
480
|
+
defaultSummary: (ctx) => `${ctx.workstreamTitle ?? "This workstream"} cannot continue until a decision is made. ${ctx.reason ?? "Review the recommendation and choose a direction."}`,
|
|
481
|
+
actions: () => [
|
|
482
|
+
{
|
|
483
|
+
action: "approve",
|
|
484
|
+
label: "Approve path",
|
|
485
|
+
description: "Accept the recommended option and continue",
|
|
486
|
+
consequences: "Autopilot will continue with the approved direction.",
|
|
487
|
+
requiresNote: false,
|
|
488
|
+
available: true,
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
action: "reject",
|
|
492
|
+
label: "Reject path",
|
|
493
|
+
description: "Decline this path and provide direction",
|
|
494
|
+
consequences: "The run stays paused until new direction is provided.",
|
|
495
|
+
requiresNote: true,
|
|
496
|
+
available: true,
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
action: "snooze",
|
|
500
|
+
label: "Snooze",
|
|
501
|
+
description: "Defer this intervention",
|
|
502
|
+
consequences: "This decision returns to the queue later.",
|
|
503
|
+
requiresNote: false,
|
|
504
|
+
available: true,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
review_required: {
|
|
509
|
+
kind: "review_required",
|
|
510
|
+
severity: "medium",
|
|
511
|
+
recommendedAction: "Review the update and confirm the next step",
|
|
512
|
+
defaultTitle: (ctx) => `Review required${ctx.workstreamTitle ? `: ${ctx.workstreamTitle}` : ""}`,
|
|
513
|
+
defaultSummary: (ctx) => `${ctx.workstreamTitle ?? "This workstream"} surfaced something that needs judgment before it proceeds. ${ctx.reason ?? "Review the evidence and confirm what should happen next."}`,
|
|
514
|
+
actions: () => [
|
|
515
|
+
{
|
|
516
|
+
action: "approve",
|
|
517
|
+
label: "Approve",
|
|
518
|
+
description: "Confirm the proposed next step",
|
|
519
|
+
consequences: "The run continues with the reviewed direction.",
|
|
520
|
+
requiresNote: false,
|
|
521
|
+
available: true,
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
action: "reject",
|
|
525
|
+
label: "Send back",
|
|
526
|
+
description: "Request a different approach",
|
|
527
|
+
consequences: "The run pauses until new direction is provided.",
|
|
528
|
+
requiresNote: true,
|
|
529
|
+
available: true,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
action: "snooze",
|
|
533
|
+
label: "Snooze",
|
|
534
|
+
description: "Return to this later",
|
|
535
|
+
consequences: "The review request will surface again later.",
|
|
536
|
+
requiresNote: false,
|
|
537
|
+
available: true,
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
},
|
|
313
541
|
budget_exhausted: {
|
|
314
542
|
kind: "blocked_intervention",
|
|
315
543
|
severity: "critical",
|
|
@@ -432,6 +660,19 @@ export async function mapFailureToTriageItem(input) {
|
|
|
432
660
|
if (input.outputPath) {
|
|
433
661
|
proofBundle.artifactRefs.push(input.outputPath);
|
|
434
662
|
}
|
|
663
|
+
for (const artifact of intervention?.artifacts ?? []) {
|
|
664
|
+
if (!proofBundle.artifactRefs.includes(artifact)) {
|
|
665
|
+
proofBundle.artifactRefs.push(artifact);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
for (const evidence of intervention?.evidence ?? []) {
|
|
669
|
+
if (evidence.url && !proofBundle.artifactRefs.includes(evidence.url)) {
|
|
670
|
+
proofBundle.artifactRefs.push(evidence.url);
|
|
671
|
+
}
|
|
672
|
+
if (evidence.pointer && !proofBundle.logRefs.includes(evidence.pointer)) {
|
|
673
|
+
proofBundle.logRefs.push(evidence.pointer);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
435
676
|
const impact = {
|
|
436
677
|
initiativeCount: input.initiativeId ? 1 : 0,
|
|
437
678
|
workstreamCount: input.workstreamId ? 1 : 0,
|
|
@@ -589,6 +830,41 @@ export function mapDecisionToTriageItem(decision) {
|
|
|
589
830
|
});
|
|
590
831
|
const blocking = metadata?.blocking !== false;
|
|
591
832
|
const options = Array.isArray(decision.options) ? decision.options : [];
|
|
833
|
+
const enrichedIntervention = intervention || options.length > 0 || decision.context || decision.recommendedAction || decision.evidenceRefs?.length
|
|
834
|
+
? {
|
|
835
|
+
...(intervention ?? {}),
|
|
836
|
+
decisionPrompt: intervention?.decisionPrompt ??
|
|
837
|
+
decision.title,
|
|
838
|
+
decisionSummary: intervention?.decisionSummary ??
|
|
839
|
+
decision.context ??
|
|
840
|
+
null,
|
|
841
|
+
decisionOptions: intervention?.decisionOptions && intervention.decisionOptions.length > 0
|
|
842
|
+
? intervention.decisionOptions
|
|
843
|
+
: options.map((option) => ({
|
|
844
|
+
id: option.id,
|
|
845
|
+
label: option.label,
|
|
846
|
+
description: option.description ?? null,
|
|
847
|
+
consequences: option.consequences ?? null,
|
|
848
|
+
actionType: option.actionType ?? null,
|
|
849
|
+
impliedStatus: option.impliedStatus ?? null,
|
|
850
|
+
requiresNote: option.requiresNote,
|
|
851
|
+
recommended: decision.selectedOptionId != null ? decision.selectedOptionId === option.id : false,
|
|
852
|
+
})),
|
|
853
|
+
recommendedAction: intervention?.recommendedAction ??
|
|
854
|
+
decision.recommendedAction ??
|
|
855
|
+
null,
|
|
856
|
+
evidence: intervention?.evidence && intervention.evidence.length > 0
|
|
857
|
+
? intervention.evidence
|
|
858
|
+
: (decision.evidenceRefs ?? []).map((ref) => ({
|
|
859
|
+
title: ref.title ?? ref.sourcePointer ?? ref.sourceUrl ?? "Evidence",
|
|
860
|
+
summary: ref.summary ?? null,
|
|
861
|
+
url: ref.sourceUrl ?? null,
|
|
862
|
+
pointer: ref.sourcePointer ?? null,
|
|
863
|
+
evidenceType: ref.evidenceType ?? null,
|
|
864
|
+
confidence: ref.confidence ?? null,
|
|
865
|
+
})),
|
|
866
|
+
}
|
|
867
|
+
: null;
|
|
592
868
|
const optionActions = options
|
|
593
869
|
.map((option) => {
|
|
594
870
|
const implied = (option.impliedStatus ?? "").toLowerCase();
|
|
@@ -657,7 +933,7 @@ export function mapDecisionToTriageItem(decision) {
|
|
|
657
933
|
fileChanges: [],
|
|
658
934
|
prRefs: [],
|
|
659
935
|
logRefs: [],
|
|
660
|
-
decisionRefs: Array.from(new Set([decision.id, ...(
|
|
936
|
+
decisionRefs: Array.from(new Set([decision.id, ...(enrichedIntervention?.decisionIds ?? [])].filter(Boolean))),
|
|
661
937
|
};
|
|
662
938
|
if (decision.evidenceRefs) {
|
|
663
939
|
for (const ref of decision.evidenceRefs) {
|
|
@@ -668,16 +944,19 @@ export function mapDecisionToTriageItem(decision) {
|
|
|
668
944
|
}
|
|
669
945
|
}
|
|
670
946
|
const summaryBase = (typeof decision.context === "string" && decision.context.trim()) ||
|
|
671
|
-
|
|
947
|
+
enrichedIntervention?.blockerReason ||
|
|
672
948
|
decision.title;
|
|
673
949
|
const summarySuffix = [
|
|
674
|
-
|
|
675
|
-
|
|
950
|
+
enrichedIntervention?.waitingOn ? `Waiting on ${enrichedIntervention.waitingOn}.` : null,
|
|
951
|
+
enrichedIntervention?.requiredAction ? `Required action: ${enrichedIntervention.requiredAction}.` : null,
|
|
676
952
|
]
|
|
677
953
|
.filter((entry) => Boolean(entry))
|
|
678
954
|
.join(" ");
|
|
679
955
|
const summary = summarySuffix.length > 0 ? `${summaryBase} ${summarySuffix}` : summaryBase;
|
|
680
|
-
const recommendedAction = decision.recommendedAction ??
|
|
956
|
+
const recommendedAction = decision.recommendedAction ??
|
|
957
|
+
enrichedIntervention?.recommendedAction ??
|
|
958
|
+
enrichedIntervention?.requiredAction ??
|
|
959
|
+
null;
|
|
681
960
|
return {
|
|
682
961
|
id: `triage-decision-${decision.id}`,
|
|
683
962
|
kind: mapping?.kind ?? "decision_required",
|
|
@@ -698,7 +977,7 @@ export function mapDecisionToTriageItem(decision) {
|
|
|
698
977
|
blocking,
|
|
699
978
|
recommendedAction,
|
|
700
979
|
agentId: decision.agentId ?? null,
|
|
701
|
-
intervention,
|
|
980
|
+
intervention: enrichedIntervention,
|
|
702
981
|
impact: {
|
|
703
982
|
initiativeCount: decision.initiativeId ? 1 : 0,
|
|
704
983
|
workstreamCount: decision.workstreamId ? 1 : 0,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function pickString(record: Record<string, unknown>, keys: string[]): string | null;
|
|
2
2
|
export declare function pickNumber(record: Record<string, unknown>, keys: string[]): number | null;
|
|
3
|
+
export declare function pickBoolean(record: Record<string, unknown> | null, keys: string[]): boolean | null;
|
|
3
4
|
export declare function pickHeaderString(headers: Record<string, string | string[] | undefined>, keys: string[]): string | null;
|
|
4
5
|
export declare function toIsoString(value: string | null): string | null;
|
|
5
6
|
export declare function parsePositiveInt(raw: string | null, fallback: number, max?: number): number;
|
|
@@ -23,6 +23,23 @@ export function pickNumber(record, keys) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
+
export function pickBoolean(record, keys) {
|
|
27
|
+
if (!record)
|
|
28
|
+
return null;
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
const value = record[key];
|
|
31
|
+
if (typeof value === "boolean")
|
|
32
|
+
return value;
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
const normalized = value.trim().toLowerCase();
|
|
35
|
+
if (normalized === "true" || normalized === "yes" || normalized === "1")
|
|
36
|
+
return true;
|
|
37
|
+
if (normalized === "false" || normalized === "no" || normalized === "0")
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
26
43
|
export function pickHeaderString(headers, keys) {
|
|
27
44
|
for (const key of keys) {
|
|
28
45
|
const candidates = [key, key.toLowerCase(), key.toUpperCase()];
|
package/dist/http/index.js
CHANGED
|
@@ -56,7 +56,7 @@ import { configureOpenClawProviderRouting, fetchBillingStatusSafe, isPidAlive, l
|
|
|
56
56
|
import { fetchKickoffContextSafe, renderKickoffMessage } from "./helpers/kickoff-context.js";
|
|
57
57
|
import { createDispatchLifecycle } from "./helpers/dispatch-lifecycle.js";
|
|
58
58
|
import { createRuntimeSseHub } from "./helpers/runtime-sse.js";
|
|
59
|
-
import { parseBooleanQuery, parsePositiveInt, pickHeaderString, pickNumber, pickString, } from "./helpers/value-utils.js";
|
|
59
|
+
import { parseBooleanQuery, pickBoolean, parsePositiveInt, pickHeaderString, pickNumber, pickString, } from "./helpers/value-utils.js";
|
|
60
60
|
import { registerAgentControlRoutes } from "./routes/agent-control.js";
|
|
61
61
|
import { registerAgentSuiteRoutes } from "./routes/agent-suite.js";
|
|
62
62
|
import { registerAgentsCatalogRoutes } from "./routes/agents-catalog.js";
|
|
@@ -2411,6 +2411,30 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2411
2411
|
? entry.blockers[0] ??
|
|
2412
2412
|
(statusValues.includes("failed") ? "Latest run failed" : "Workstream blocked")
|
|
2413
2413
|
: null,
|
|
2414
|
+
canStartNow: queueState === QueueState.QUEUED || queueState === QueueState.IDLE,
|
|
2415
|
+
startReasonCode: queueState === QueueState.RUNNING
|
|
2416
|
+
? "already_running"
|
|
2417
|
+
: hasBlocked
|
|
2418
|
+
? "blocked"
|
|
2419
|
+
: queueState === QueueState.QUEUED || queueState === QueueState.IDLE
|
|
2420
|
+
? "dispatchable"
|
|
2421
|
+
: "not_startable",
|
|
2422
|
+
startReasonLabel: hasBlocked
|
|
2423
|
+
? entry.blockers[0] ??
|
|
2424
|
+
(statusValues.includes("failed") ? "Latest run failed." : "Workstream blocked.")
|
|
2425
|
+
: queueState === QueueState.RUNNING
|
|
2426
|
+
? "Already running."
|
|
2427
|
+
: "Ready to start this workstream.",
|
|
2428
|
+
dispatchableTask: queueState === QueueState.QUEUED || queueState === QueueState.IDLE
|
|
2429
|
+
? {
|
|
2430
|
+
id: entry.latest.id ?? `${entry.initiativeId}:${entry.workstreamId}`,
|
|
2431
|
+
title: (entry.latest.lastEventSummary ?? "").trim() ||
|
|
2432
|
+
(entry.latest.title ?? "").trim() ||
|
|
2433
|
+
entry.workstreamTitle,
|
|
2434
|
+
scope: "workstream",
|
|
2435
|
+
milestoneId: null,
|
|
2436
|
+
}
|
|
2437
|
+
: null,
|
|
2414
2438
|
isPinned: pinnedRankByKey.has(pinKey),
|
|
2415
2439
|
pinnedRank: pinnedRankByKey.get(pinKey) ?? null,
|
|
2416
2440
|
autoContinue: null,
|
|
@@ -2503,6 +2527,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2503
2527
|
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
2504
2528
|
const workstreamNodes = graph.nodes.filter((node) => node.type === "workstream");
|
|
2505
2529
|
const runningWorkstreams = new Set();
|
|
2530
|
+
const initiativeRun = autoContinueRuns.get(initiativeId) ?? null;
|
|
2531
|
+
const initiativeActiveRunIds = Array.isArray(initiativeRun?.activeSliceRunIds)
|
|
2532
|
+
? initiativeRun.activeSliceRunIds
|
|
2533
|
+
.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
2534
|
+
.map((id) => id.trim())
|
|
2535
|
+
: typeof initiativeRun?.activeRunId === "string" && initiativeRun.activeRunId.trim().length > 0
|
|
2536
|
+
? [initiativeRun.activeRunId.trim()]
|
|
2537
|
+
: [];
|
|
2506
2538
|
const taskIsReady = (task) => task.dependencyIds.every((depId) => {
|
|
2507
2539
|
const dependency = nodeById.get(depId);
|
|
2508
2540
|
return dependency ? isDoneStatus(dependency.status) : true;
|
|
@@ -2703,6 +2735,36 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2703
2735
|
if (isSuppressed(initiativeId, workstream.id) && queueState !== QueueState.RUNNING) {
|
|
2704
2736
|
continue;
|
|
2705
2737
|
}
|
|
2738
|
+
const dispatchableTask = preferredReadyTask ?? readyTask ?? null;
|
|
2739
|
+
const initiativeHasConcurrentRun = initiativeActiveRunIds.length > 0 &&
|
|
2740
|
+
laneState !== LaneState.RUNNING &&
|
|
2741
|
+
!(autoContinueRun && autoContinueRun.status === RunStatus.RUNNING);
|
|
2742
|
+
const canStartNow = Boolean(dispatchableTask) &&
|
|
2743
|
+
queueState !== QueueState.RUNNING &&
|
|
2744
|
+
queueState !== QueueState.BLOCKED &&
|
|
2745
|
+
!initiativeHasConcurrentRun;
|
|
2746
|
+
const startReasonCode = canStartNow
|
|
2747
|
+
? "dispatchable"
|
|
2748
|
+
: queueState === QueueState.RUNNING
|
|
2749
|
+
? "already_running"
|
|
2750
|
+
: initiativeHasConcurrentRun
|
|
2751
|
+
? "initiative_run_active"
|
|
2752
|
+
: queueState === QueueState.BLOCKED
|
|
2753
|
+
? "blocked"
|
|
2754
|
+
: !dispatchableTask
|
|
2755
|
+
? "no_dispatchable_task"
|
|
2756
|
+
: "not_startable";
|
|
2757
|
+
const startReasonLabel = canStartNow
|
|
2758
|
+
? `Ready to start ${dispatchableTask?.title ?? "this workstream"}.`
|
|
2759
|
+
: initiativeHasConcurrentRun
|
|
2760
|
+
? "Autopilot is already running for this initiative."
|
|
2761
|
+
: queueState === QueueState.RUNNING
|
|
2762
|
+
? "Already running."
|
|
2763
|
+
: blockReason
|
|
2764
|
+
? blockReason
|
|
2765
|
+
: dispatchableTask
|
|
2766
|
+
? "This workstream is not ready to start."
|
|
2767
|
+
: "No dispatchable task is available right now.";
|
|
2706
2768
|
runningWorkstreams.add(workstream.id);
|
|
2707
2769
|
const assignedRunnerAgents = [];
|
|
2708
2770
|
const assignedRunnerSeen = new Set();
|
|
@@ -2763,6 +2825,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2763
2825
|
runnerSource,
|
|
2764
2826
|
queueState,
|
|
2765
2827
|
blockReason,
|
|
2828
|
+
canStartNow,
|
|
2829
|
+
startReasonCode,
|
|
2830
|
+
startReasonLabel,
|
|
2831
|
+
dispatchableTask: dispatchableTask
|
|
2832
|
+
? {
|
|
2833
|
+
id: dispatchableTask.id,
|
|
2834
|
+
title: dispatchableTask.title,
|
|
2835
|
+
scope: defaultScope,
|
|
2836
|
+
milestoneId: dispatchableTask.milestoneId ?? null,
|
|
2837
|
+
}
|
|
2838
|
+
: null,
|
|
2766
2839
|
isPinned: Boolean(pin),
|
|
2767
2840
|
pinnedRank: pin ? (pinnedRankByKey.get(pinKey) ?? null) : null,
|
|
2768
2841
|
sliceScope: defaultScope,
|
|
@@ -2870,6 +2943,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2870
2943
|
blockReason: queueState === QueueState.BLOCKED
|
|
2871
2944
|
? lane?.blockedReason ?? "Blocked"
|
|
2872
2945
|
: null,
|
|
2946
|
+
canStartNow: false,
|
|
2947
|
+
startReasonCode: queueState === QueueState.RUNNING ? "already_running" : "initiative_run_active",
|
|
2948
|
+
startReasonLabel: queueState === QueueState.RUNNING
|
|
2949
|
+
? "Already running."
|
|
2950
|
+
: "Autopilot is already running for this initiative.",
|
|
2951
|
+
dispatchableTask: null,
|
|
2873
2952
|
isPinned: Boolean(pinnedByKey.get(`${initiativeId}:${workstream.id}`)),
|
|
2874
2953
|
pinnedRank: pinnedRankByKey.get(`${initiativeId}:${workstream.id}`) ?? null,
|
|
2875
2954
|
sliceScope,
|
|
@@ -3696,13 +3775,18 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3696
3775
|
const keys = [...scopedKeys, ...fallbackKeys];
|
|
3697
3776
|
const seen = new Set();
|
|
3698
3777
|
const mapped = [];
|
|
3699
|
-
const deriveFailureType = (eventNameRaw, actionTypeRaw, reasonRaw) => {
|
|
3778
|
+
const deriveFailureType = (eventNameRaw, actionTypeRaw, reasonRaw, blockingRaw) => {
|
|
3700
3779
|
const eventName = (eventNameRaw ?? "").trim().toLowerCase();
|
|
3701
3780
|
const actionType = (actionTypeRaw ?? "").trim().toLowerCase();
|
|
3702
3781
|
const reason = (reasonRaw ?? "").trim().toLowerCase();
|
|
3703
3782
|
const signature = `${eventName} ${actionType} ${reason}`;
|
|
3704
3783
|
if (!signature.trim())
|
|
3705
3784
|
return null;
|
|
3785
|
+
if (signature.includes("question_asked") ||
|
|
3786
|
+
signature.includes("review_item_created") ||
|
|
3787
|
+
signature.includes("decision_requested")) {
|
|
3788
|
+
return blockingRaw === false ? "review_required" : "decision_required";
|
|
3789
|
+
}
|
|
3706
3790
|
if (signature.includes("status_updates_buffered"))
|
|
3707
3791
|
return "status_updates_buffered";
|
|
3708
3792
|
if (signature.includes("question_answer_failed"))
|
|
@@ -3766,8 +3850,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3766
3850
|
pickString(resultRecord ?? {}, ["error", "reason", "blocked_reason", "blockedReason", "summary"]) ??
|
|
3767
3851
|
pickString(metadataRecord, ["error", "reason", "message", "blocked_reason", "blockedReason"]) ??
|
|
3768
3852
|
pickString(activityRecord, ["description", "summary", "title"]);
|
|
3853
|
+
const blockingSignal = pickBoolean(metadataRecord, ["blocking"]) ??
|
|
3854
|
+
pickBoolean(resultRecord, ["blocking"]);
|
|
3769
3855
|
const failureType = deriveFailureType(pickString(metadataRecord, ["event", "event_name"]), pickString(metadataRecord, ["action_type", "actionType"]) ??
|
|
3770
|
-
pickString(activityRecord, ["type"]), reasonText);
|
|
3856
|
+
pickString(activityRecord, ["type"]), reasonText, blockingSignal);
|
|
3771
3857
|
if (!failureType)
|
|
3772
3858
|
continue;
|
|
3773
3859
|
const runId = pickString(metadataRecord, ["run_id", "source_run_id"]) ?? pickString(activityRecord, ["runId"]);
|
|
@@ -9,7 +9,12 @@ export function registerLiveTriageRoutes(router, deps) {
|
|
|
9
9
|
// ─── GET /live/triage ─────────────────────────────────────────────
|
|
10
10
|
router.add("GET", "live/triage", async ({ res, query }) => {
|
|
11
11
|
const workspaceId = query.get("workspace_id") || null;
|
|
12
|
-
const
|
|
12
|
+
const requestedStatus = query.get("status") || "open";
|
|
13
|
+
const statusFilter = requestedStatus === "pending"
|
|
14
|
+
? "open"
|
|
15
|
+
: requestedStatus === "closed"
|
|
16
|
+
? "resolved"
|
|
17
|
+
: requestedStatus;
|
|
13
18
|
const limitStr = query.get("limit");
|
|
14
19
|
const limit = limitStr ? Math.min(Math.max(1, parseInt(limitStr, 10) || 50), 200) : 50;
|
|
15
20
|
const degraded = [];
|
|
@@ -23,6 +23,15 @@ type NextUpQueue = {
|
|
|
23
23
|
sliceTaskIds?: string[];
|
|
24
24
|
sliceTaskCount?: number | null;
|
|
25
25
|
sliceMilestoneId?: string | null;
|
|
26
|
+
canStartNow?: boolean;
|
|
27
|
+
startReasonCode?: string | null;
|
|
28
|
+
startReasonLabel?: string | null;
|
|
29
|
+
dispatchableTask?: {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
scope: "task" | "milestone" | "workstream";
|
|
33
|
+
milestoneId?: string | null;
|
|
34
|
+
} | null;
|
|
26
35
|
executionPolicy?: {
|
|
27
36
|
domain?: string;
|
|
28
37
|
requiredSkills?: string[];
|