@useorgx/openclaw-plugin 0.7.0 → 0.7.3
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/README.md +42 -11
- package/dashboard/dist/assets/6mILZQ2a.js +1 -0
- package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
- package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
- package/dashboard/dist/assets/{DG6y9wJI.js → 8dksYiq4.js} +1 -1
- package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
- package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
- package/dashboard/dist/assets/{PAUiij_z.js → B5zYRHc3.js} +1 -1
- package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
- package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
- package/dashboard/dist/assets/{CFGKRAzG.js → B6wPWJ35.js} +1 -1
- package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
- package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
- package/dashboard/dist/assets/BWEwjt1W.js +1 -0
- package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
- package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
- package/dashboard/dist/assets/BzRbDCAD.css +1 -0
- package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
- package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
- package/dashboard/dist/assets/{DNxKz-GV.js → C8uM3AX8.js} +1 -1
- package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
- package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
- package/dashboard/dist/assets/C9jy61eu.js +212 -0
- package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
- package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
- package/dashboard/dist/assets/{CE38zU4U.js → CC63EwFD.js} +1 -1
- package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
- package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
- package/dashboard/dist/assets/{nByHNHoW.js → CZaT3ob_.js} +1 -1
- package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
- package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
- package/dashboard/dist/assets/{tS9mbYZi.js → CgaottFX.js} +1 -1
- package/dashboard/dist/assets/CgaottFX.js.br +0 -0
- package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
- package/dashboard/dist/assets/CzCxAZlW.js +1 -0
- package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
- package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js +1 -0
- package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
- package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
- package/dashboard/dist/assets/{DjcdE6jC.js → D8JNX8kq.js} +1 -1
- package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
- package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js +1 -0
- package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
- package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
- package/dashboard/dist/assets/{DbNoijHm.js → IUexzymk.js} +1 -1
- package/dashboard/dist/assets/IUexzymk.js.br +0 -0
- package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
- package/dashboard/dist/assets/{CZZTvkQZ.js → rttbDbEx.js} +1 -1
- package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
- package/dashboard/dist/assets/rttbDbEx.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/contracts/practice-exercise-schema.d.ts +216 -0
- package/dist/contracts/practice-exercise-schema.js +314 -0
- package/dist/contracts/shared-types.d.ts +2 -2
- package/dist/contracts/shared-types.js +22 -0
- package/dist/contracts/types.d.ts +20 -0
- package/dist/fs-utils.js +1 -13
- package/dist/http/helpers/auto-continue-engine.js +638 -24
- package/dist/http/helpers/autopilot-runtime.js +22 -7
- package/dist/http/helpers/autopilot-slice-utils.js +0 -2
- package/dist/http/helpers/kickoff-context.js +30 -0
- package/dist/http/helpers/slice-run-projections.js +19 -2
- package/dist/http/index.js +151 -93
- package/dist/http/routes/agent-suite.js +87 -0
- package/dist/http/routes/entities.js +1 -63
- package/dist/http/routes/live-snapshot.d.ts +1 -0
- package/dist/http/routes/live-snapshot.js +15 -4
- package/dist/http/routes/live-terminal.js +1 -108
- package/dist/http/routes/live-triage.js +1 -57
- package/dist/http/routes/mission-control-actions.js +0 -88
- package/dist/http/routes/mission-control-read.js +73 -8
- package/dist/mcp-http-handler.d.ts +3 -0
- package/dist/mcp-http-handler.js +2 -2
- package/dist/openclaw.plugin.json +1 -1
- package/dist/paths.js +14 -2
- package/dist/reporting/rollups.d.ts +0 -12
- package/dist/reporting/rollups.js +0 -35
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -3
- package/dashboard/dist/assets/BXWDRGm-.js +0 -1
- package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js +0 -1
- package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js +0 -8
- package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js +0 -213
- package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
- package/dashboard/dist/assets/DW_rKUic.js +0 -11
- package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
- package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js +0 -1
- package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
- package/dashboard/dist/assets/h5biQs2I.css +0 -1
- package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
- package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { resolveWorkspaceScope, workspaceScopeFromHeaders, } from "../helpers/workspace-scope.js";
|
|
2
|
-
import { cascadeProgressFromChildren, deriveLifecycleState, } from "../../reporting/rollups.js";
|
|
3
2
|
const WORKSTREAM_REASSIGNMENT_FIELDS = [
|
|
4
3
|
"domain",
|
|
5
4
|
"role",
|
|
@@ -196,68 +195,7 @@ export function registerEntitiesRoutes(router, deps) {
|
|
|
196
195
|
if (normalizedType === "initiative") {
|
|
197
196
|
deps.clearLocalInitiativeStatusOverride(id);
|
|
198
197
|
}
|
|
199
|
-
|
|
200
|
-
// completed/done, recompute cascaded progress for parent entities.
|
|
201
|
-
let cascadedProgress = null;
|
|
202
|
-
if (normalizedType === "task" && requestedStatus) {
|
|
203
|
-
const normalizedRequestedStatus = requestedStatus.trim().toLowerCase();
|
|
204
|
-
const isTerminal = normalizedRequestedStatus === "done" ||
|
|
205
|
-
normalizedRequestedStatus === "completed" ||
|
|
206
|
-
normalizedRequestedStatus === "cancelled" ||
|
|
207
|
-
normalizedRequestedStatus === "archived";
|
|
208
|
-
if (isTerminal) {
|
|
209
|
-
// Look up sibling tasks for the parent workstream/milestone/initiative
|
|
210
|
-
const entityRecord = entity && typeof entity === "object"
|
|
211
|
-
? entity
|
|
212
|
-
: {};
|
|
213
|
-
const parentWorkstreamId = deps.pickString(entityRecord, ["workstream_id", "workstreamId"]) ??
|
|
214
|
-
deps.pickString(normalizedUpdates, ["workstream_id", "workstreamId"]);
|
|
215
|
-
const parentInitiativeId = deps.pickString(entityRecord, ["initiative_id", "initiativeId"]) ??
|
|
216
|
-
deps.pickString(normalizedUpdates, ["initiative_id", "initiativeId"]);
|
|
217
|
-
// Fetch sibling tasks to compute cascaded progress
|
|
218
|
-
const parentScope = parentWorkstreamId
|
|
219
|
-
? { initiative_id: parentInitiativeId ?? undefined }
|
|
220
|
-
: parentInitiativeId
|
|
221
|
-
? { initiative_id: parentInitiativeId }
|
|
222
|
-
: null;
|
|
223
|
-
if (parentScope) {
|
|
224
|
-
try {
|
|
225
|
-
const siblingResult = await deps.client.listEntities("task", {
|
|
226
|
-
...parentScope,
|
|
227
|
-
limit: 500,
|
|
228
|
-
});
|
|
229
|
-
const siblingRows = toObjectArray(siblingResult && typeof siblingResult === "object"
|
|
230
|
-
? siblingResult.data
|
|
231
|
-
: []);
|
|
232
|
-
const siblingStatuses = siblingRows
|
|
233
|
-
.filter((row) => {
|
|
234
|
-
if (!parentWorkstreamId)
|
|
235
|
-
return true;
|
|
236
|
-
const rowWsId = deps.pickString(row, ["workstream_id", "workstreamId"]);
|
|
237
|
-
return rowWsId === parentWorkstreamId;
|
|
238
|
-
})
|
|
239
|
-
.map((row) => deps.pickString(row, ["status"]) ?? "unknown");
|
|
240
|
-
const parentProgress = cascadeProgressFromChildren(siblingStatuses);
|
|
241
|
-
const parentLifecycleState = deriveLifecycleState(parentWorkstreamId ? "active" : (parentInitiativeId ? "active" : "active"), siblingStatuses);
|
|
242
|
-
cascadedProgress = {
|
|
243
|
-
parentProgress,
|
|
244
|
-
parentLifecycleState,
|
|
245
|
-
taskStatuses: siblingStatuses,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
// Non-critical: cascade progress is best-effort
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
deps.sendJson(res, 200, {
|
|
255
|
-
ok: true,
|
|
256
|
-
entity,
|
|
257
|
-
reassignment,
|
|
258
|
-
initiative_reassignment: initiativeReassignment,
|
|
259
|
-
...(cascadedProgress ? { cascaded_progress: cascadedProgress } : {}),
|
|
260
|
-
});
|
|
198
|
+
deps.sendJson(res, 200, { ok: true, entity, reassignment, initiative_reassignment: initiativeReassignment });
|
|
261
199
|
}
|
|
262
200
|
catch (err) {
|
|
263
201
|
if (type?.trim().toLowerCase() === "initiative" &&
|
|
@@ -81,6 +81,7 @@ type LiveSnapshotRoutesDeps<TReq, TRes> = {
|
|
|
81
81
|
}) => LiveActivityItem[];
|
|
82
82
|
mergeSessionTrees: (base: SessionTreeResponse, extra: SessionTreeResponse) => SessionTreeResponse;
|
|
83
83
|
mergeActivities: (base: LiveActivityItem[], extra: LiveActivityItem[], limit: number) => LiveActivityItem[];
|
|
84
|
+
semanticActivityKey: (item: LiveActivityItem) => string | null;
|
|
84
85
|
listRuntimeInstances: (input: {
|
|
85
86
|
limit: number;
|
|
86
87
|
}) => RuntimeInstanceRecord[];
|
|
@@ -445,14 +445,25 @@ export function registerLiveSnapshotRoutes(router, deps) {
|
|
|
445
445
|
const buffered = await deps.readOutboxItems();
|
|
446
446
|
if (buffered.length > 0) {
|
|
447
447
|
const merged = [...activity, ...buffered]
|
|
448
|
-
.sort((a, b) =>
|
|
448
|
+
.sort((a, b) => {
|
|
449
|
+
const d = Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
450
|
+
if (d !== 0)
|
|
451
|
+
return d;
|
|
452
|
+
return b.id.localeCompare(a.id);
|
|
453
|
+
})
|
|
449
454
|
.slice(0, activityLimit);
|
|
450
455
|
const deduped = [];
|
|
451
|
-
const
|
|
456
|
+
const seenIds = new Set();
|
|
457
|
+
const seenSemantic = new Set();
|
|
452
458
|
for (const item of merged) {
|
|
453
|
-
if (
|
|
459
|
+
if (seenIds.has(item.id))
|
|
454
460
|
continue;
|
|
455
|
-
|
|
461
|
+
seenIds.add(item.id);
|
|
462
|
+
const sk = deps.semanticActivityKey(item);
|
|
463
|
+
if (sk && seenSemantic.has(sk))
|
|
464
|
+
continue;
|
|
465
|
+
if (sk)
|
|
466
|
+
seenSemantic.add(sk);
|
|
456
467
|
deduped.push(item);
|
|
457
468
|
}
|
|
458
469
|
activity = deduped;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { exec } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
import { platform } from "node:os";
|
|
4
4
|
import { join, resolve, sep } from "node:path";
|
|
5
5
|
import { getOrgxPluginConfigDir } from "../../paths.js";
|
|
6
6
|
export function escapeShellSingleQuotedArg(value) {
|
|
7
7
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
8
8
|
}
|
|
9
|
-
const DEFAULT_TAIL_LINES = 120;
|
|
10
|
-
const MAX_TAIL_LINES = 400;
|
|
11
|
-
const MAX_TAIL_BYTES = 256 * 1024;
|
|
12
9
|
function pickString(input, keys) {
|
|
13
10
|
for (const key of keys) {
|
|
14
11
|
const value = input[key];
|
|
@@ -128,111 +125,7 @@ function resolveTargetPath(payload) {
|
|
|
128
125
|
}
|
|
129
126
|
return null;
|
|
130
127
|
}
|
|
131
|
-
function parseTailLines(raw) {
|
|
132
|
-
if (!raw)
|
|
133
|
-
return DEFAULT_TAIL_LINES;
|
|
134
|
-
const parsed = Number(raw);
|
|
135
|
-
if (!Number.isFinite(parsed))
|
|
136
|
-
return DEFAULT_TAIL_LINES;
|
|
137
|
-
const normalized = Math.floor(parsed);
|
|
138
|
-
if (normalized <= 0)
|
|
139
|
-
return DEFAULT_TAIL_LINES;
|
|
140
|
-
return Math.min(MAX_TAIL_LINES, normalized);
|
|
141
|
-
}
|
|
142
|
-
function readLogTailPreview(path, lineLimit) {
|
|
143
|
-
const stats = statSync(path);
|
|
144
|
-
if (!stats.isFile()) {
|
|
145
|
-
throw new Error("Tail target is not a file.");
|
|
146
|
-
}
|
|
147
|
-
const totalBytes = Number.isFinite(stats.size) ? Math.max(0, stats.size) : 0;
|
|
148
|
-
const offsetBytes = Math.max(0, totalBytes - MAX_TAIL_BYTES);
|
|
149
|
-
const readLength = Math.max(0, totalBytes - offsetBytes);
|
|
150
|
-
if (readLength === 0) {
|
|
151
|
-
return {
|
|
152
|
-
text: "",
|
|
153
|
-
lineCount: 0,
|
|
154
|
-
truncated: false,
|
|
155
|
-
totalBytes,
|
|
156
|
-
offsetBytes,
|
|
157
|
-
updatedAt: stats.mtime.toISOString(),
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
const fd = openSync(path, "r");
|
|
161
|
-
try {
|
|
162
|
-
const buffer = Buffer.allocUnsafe(readLength);
|
|
163
|
-
let bytesRead = 0;
|
|
164
|
-
while (bytesRead < readLength) {
|
|
165
|
-
const chunk = readSync(fd, buffer, bytesRead, readLength - bytesRead, offsetBytes + bytesRead);
|
|
166
|
-
if (chunk <= 0)
|
|
167
|
-
break;
|
|
168
|
-
bytesRead += chunk;
|
|
169
|
-
}
|
|
170
|
-
const normalizedText = buffer
|
|
171
|
-
.subarray(0, Math.max(0, bytesRead))
|
|
172
|
-
.toString("utf8")
|
|
173
|
-
.replaceAll("\r\n", "\n")
|
|
174
|
-
.replaceAll("\r", "\n");
|
|
175
|
-
const allLines = normalizedText.split("\n");
|
|
176
|
-
if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
|
|
177
|
-
allLines.pop();
|
|
178
|
-
}
|
|
179
|
-
const tailLines = allLines.slice(-lineLimit);
|
|
180
|
-
const truncated = offsetBytes > 0 || allLines.length > tailLines.length;
|
|
181
|
-
return {
|
|
182
|
-
text: tailLines.join("\n"),
|
|
183
|
-
lineCount: tailLines.length,
|
|
184
|
-
truncated,
|
|
185
|
-
totalBytes,
|
|
186
|
-
offsetBytes,
|
|
187
|
-
updatedAt: stats.mtime.toISOString(),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
closeSync(fd);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
128
|
export function registerLiveTerminalRoutes(router, deps) {
|
|
195
|
-
router.add("GET", "live/terminal/tail", ({ query, res }) => {
|
|
196
|
-
try {
|
|
197
|
-
const payload = {
|
|
198
|
-
path: query.get("path"),
|
|
199
|
-
logPath: query.get("logPath"),
|
|
200
|
-
log_path: query.get("log_path"),
|
|
201
|
-
sliceRunId: query.get("sliceRunId"),
|
|
202
|
-
slice_run_id: query.get("slice_run_id"),
|
|
203
|
-
runId: query.get("runId"),
|
|
204
|
-
run_id: query.get("run_id"),
|
|
205
|
-
sessionId: query.get("sessionId"),
|
|
206
|
-
session_id: query.get("session_id"),
|
|
207
|
-
};
|
|
208
|
-
const targetPath = resolveTargetPath(payload);
|
|
209
|
-
if (!targetPath) {
|
|
210
|
-
deps.sendJson(res, 404, {
|
|
211
|
-
error: "Tail target not found. Provide runId, sliceRunId, sessionId, or logPath/path.",
|
|
212
|
-
});
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
const lineLimit = parseTailLines(query.get("lines") ?? query.get("line_count"));
|
|
216
|
-
const preview = readLogTailPreview(targetPath, lineLimit);
|
|
217
|
-
deps.sendJson(res, 200, {
|
|
218
|
-
ok: true,
|
|
219
|
-
path: targetPath,
|
|
220
|
-
lines_requested: lineLimit,
|
|
221
|
-
line_count: preview.lineCount,
|
|
222
|
-
truncated: preview.truncated,
|
|
223
|
-
bytes: preview.totalBytes,
|
|
224
|
-
offset_bytes: preview.offsetBytes,
|
|
225
|
-
updated_at: preview.updatedAt,
|
|
226
|
-
text: preview.text,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
catch (err) {
|
|
230
|
-
deps.sendJson(res, 500, { error: deps.safeErrorMessage(err) });
|
|
231
|
-
}
|
|
232
|
-
}, "Read a safe tail preview for run/session logs");
|
|
233
|
-
router.add("*", "live/terminal/tail", ({ res }) => {
|
|
234
|
-
deps.sendJson(res, 405, { error: "Use GET /orgx/api/live/terminal/tail" });
|
|
235
|
-
}, "Reject unsupported methods for live/terminal/tail");
|
|
236
129
|
router.add("POST", "live/terminal/open", async ({ req, res }) => {
|
|
237
130
|
try {
|
|
238
131
|
const payload = await deps.parseJsonRequest(req);
|
|
@@ -12,12 +12,6 @@ export function registerLiveTriageRoutes(router, deps) {
|
|
|
12
12
|
const statusFilter = query.get("status") || "open";
|
|
13
13
|
const limitStr = query.get("limit");
|
|
14
14
|
const limit = limitStr ? Math.min(Math.max(1, parseInt(limitStr, 10) || 50), 200) : 50;
|
|
15
|
-
// Noise reduction query params
|
|
16
|
-
const noiseThreshold = (query.get("noise_threshold") ?? "medium");
|
|
17
|
-
const dedupWindowStr = query.get("dedup_window");
|
|
18
|
-
const dedupWindowMs = dedupWindowStr != null
|
|
19
|
-
? Math.max(0, parseInt(dedupWindowStr, 10) || 60000)
|
|
20
|
-
: 60000;
|
|
21
15
|
const degraded = [];
|
|
22
16
|
// 1. Map existing decisions to triage items
|
|
23
17
|
let decisionItems = [];
|
|
@@ -45,57 +39,7 @@ export function registerLiveTriageRoutes(router, deps) {
|
|
|
45
39
|
if (statusFilter !== "all") {
|
|
46
40
|
allItems = allItems.filter((item) => item.status === statusFilter);
|
|
47
41
|
}
|
|
48
|
-
// 5.
|
|
49
|
-
if (noiseThreshold === "high") {
|
|
50
|
-
// Only show critical and high severity
|
|
51
|
-
allItems = allItems.filter((item) => item.severity === "critical" || item.severity === "high");
|
|
52
|
-
}
|
|
53
|
-
else if (noiseThreshold === "medium") {
|
|
54
|
-
// Suppress low-severity routine items (review_required with low severity)
|
|
55
|
-
allItems = allItems.filter((item) => {
|
|
56
|
-
if (item.severity === "low" &&
|
|
57
|
-
item.kind === "review_required") {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return true;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
// noiseThreshold === 'low' — show everything (no filtering)
|
|
64
|
-
// 6. Dedup by title within window — group items with the same title
|
|
65
|
-
// that occurred within dedupWindowMs of each other, keeping the most recent.
|
|
66
|
-
if (dedupWindowMs > 0) {
|
|
67
|
-
const seen = new Map();
|
|
68
|
-
for (const item of allItems) {
|
|
69
|
-
const key = item.title.trim().toLowerCase();
|
|
70
|
-
const ts = new Date(item.lastSeenAt).getTime();
|
|
71
|
-
const existing = seen.get(key);
|
|
72
|
-
if (!existing) {
|
|
73
|
-
seen.set(key, { item, ts, count: 1 });
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (Math.abs(existing.ts - ts) <= dedupWindowMs) {
|
|
77
|
-
existing.count += 1;
|
|
78
|
-
// Keep the more recent item
|
|
79
|
-
if (ts > existing.ts) {
|
|
80
|
-
existing.item = item;
|
|
81
|
-
existing.ts = ts;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// Outside window — keep as separate under a unique key
|
|
86
|
-
seen.set(`${key}::${item.id}`, { item, ts, count: 1 });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
allItems = Array.from(seen.values()).map((entry) => {
|
|
90
|
-
// Encode occurrence count so clients can show "N similar" badge
|
|
91
|
-
const merged = { ...entry.item };
|
|
92
|
-
if (entry.count > 1) {
|
|
93
|
-
merged.occurrenceCount = Math.max(merged.occurrenceCount, entry.count);
|
|
94
|
-
}
|
|
95
|
-
return merged;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
// 7. Sort: critical first, then by impact, then recency
|
|
42
|
+
// 5. Sort: critical first, then by impact, then recency
|
|
99
43
|
allItems.sort((a, b) => {
|
|
100
44
|
const severityOrder = {
|
|
101
45
|
critical: 0,
|
|
@@ -660,90 +660,6 @@ export function registerMissionControlActionsRoutes(router, deps) {
|
|
|
660
660
|
sendRouteException(res, "mission-control.next-up.play.handler", err);
|
|
661
661
|
}
|
|
662
662
|
}, "Mission-control next-up play");
|
|
663
|
-
router.add("POST", "mission-control/next-up/launch", async ({ req, query, res }) => {
|
|
664
|
-
try {
|
|
665
|
-
const payload = await deps.parseJsonRequest(req);
|
|
666
|
-
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
667
|
-
query.get("initiativeId") ??
|
|
668
|
-
query.get("initiative_id") ??
|
|
669
|
-
"")
|
|
670
|
-
.trim();
|
|
671
|
-
if (!initiativeId) {
|
|
672
|
-
sendRouteError(res, 400, "mission-control.next-up.launch.validation", "initiativeId is required");
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
const requestedWorkstreamIds = deps.dedupeStrings(deps.pickStringArray(payload, ["workstreamIds", "workstream_ids"]));
|
|
676
|
-
const requestedScopeRaw = deps.pickString(payload, ["scope", "sliceScope", "slice_scope"]) ??
|
|
677
|
-
query.get("scope") ??
|
|
678
|
-
null;
|
|
679
|
-
const scope = normalizeScope(requestedScopeRaw) ?? "task";
|
|
680
|
-
const ignoreSpawnGuardRateLimitRaw = payload.ignoreSpawnGuardRateLimit ??
|
|
681
|
-
payload.ignore_spawn_guard_rate_limit ??
|
|
682
|
-
null;
|
|
683
|
-
const ignoreSpawnGuardRateLimit = typeof ignoreSpawnGuardRateLimitRaw === "boolean"
|
|
684
|
-
? ignoreSpawnGuardRateLimitRaw
|
|
685
|
-
: deps.parseBooleanQuery(typeof ignoreSpawnGuardRateLimitRaw === "string"
|
|
686
|
-
? ignoreSpawnGuardRateLimitRaw
|
|
687
|
-
: null);
|
|
688
|
-
// Build the queue to discover workstreams to dispatch
|
|
689
|
-
let queue;
|
|
690
|
-
try {
|
|
691
|
-
queue = await deps.buildNextUpQueue({ initiativeId });
|
|
692
|
-
}
|
|
693
|
-
catch {
|
|
694
|
-
sendRouteError(res, 503, "mission-control.next-up.launch.queue", "Unable to load queue to determine dispatchable workstreams.");
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
// Filter to requested workstreams if specified, otherwise take all
|
|
698
|
-
const candidateItems = requestedWorkstreamIds.length > 0
|
|
699
|
-
? queue.items.filter((item) => requestedWorkstreamIds.includes(item.workstreamId))
|
|
700
|
-
: queue.items.filter((item) => item.queueState === "queued" || item.queueState === "idle");
|
|
701
|
-
if (candidateItems.length === 0) {
|
|
702
|
-
deps.sendJson(res, 200, {
|
|
703
|
-
ok: true,
|
|
704
|
-
dispatched: 0,
|
|
705
|
-
initiativeId,
|
|
706
|
-
message: "No dispatchable workstreams found in the queue.",
|
|
707
|
-
});
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
// Dispatch each candidate as a one-shot (stopAfterSlice: true)
|
|
711
|
-
let dispatched = 0;
|
|
712
|
-
const errors = [];
|
|
713
|
-
for (const item of candidateItems) {
|
|
714
|
-
try {
|
|
715
|
-
const agentId = item.runnerAgentId || "main";
|
|
716
|
-
const agentName = await deps.resolveAgentDisplayName(agentId, item.runnerAgentName ?? null);
|
|
717
|
-
const run = await deps.startAutoContinueRun({
|
|
718
|
-
initiativeId,
|
|
719
|
-
agentId,
|
|
720
|
-
agentName,
|
|
721
|
-
allowedWorkstreamIds: [item.workstreamId],
|
|
722
|
-
stopAfterSlice: true,
|
|
723
|
-
ignoreSpawnGuardRateLimit: ignoreSpawnGuardRateLimit === true,
|
|
724
|
-
scope,
|
|
725
|
-
});
|
|
726
|
-
// Fire-and-forget tick to start the actual dispatch
|
|
727
|
-
void deps.tickAutoContinueRun(run).catch(() => null);
|
|
728
|
-
dispatched += 1;
|
|
729
|
-
}
|
|
730
|
-
catch (err) {
|
|
731
|
-
errors.push(`${item.workstreamId}: ${deps.safeErrorMessage(err)}`);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
deps.clearNextUpQueueCache(initiativeId);
|
|
735
|
-
deps.sendJson(res, 200, {
|
|
736
|
-
ok: true,
|
|
737
|
-
dispatched,
|
|
738
|
-
initiativeId,
|
|
739
|
-
requested: candidateItems.length,
|
|
740
|
-
...(errors.length > 0 ? { errors } : {}),
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
catch (err) {
|
|
744
|
-
sendRouteException(res, "mission-control.next-up.launch.handler", err);
|
|
745
|
-
}
|
|
746
|
-
}, "Mission-control next-up launch (dispatch without auto-continue loop)");
|
|
747
663
|
router.add("POST", "mission-control/next-up/pin", async ({ req, query, res }) => {
|
|
748
664
|
try {
|
|
749
665
|
const payload = await deps.parseJsonRequest(req);
|
|
@@ -841,10 +757,6 @@ export function registerMissionControlActionsRoutes(router, deps) {
|
|
|
841
757
|
const workspaceId = scope.workspaceId;
|
|
842
758
|
const order = parseSliceOrderForMutation(payload?.order);
|
|
843
759
|
const canonicalOrder = order.map((sliceId) => ({ sliceId }));
|
|
844
|
-
if (canonicalOrder.length === 0) {
|
|
845
|
-
sendRouteError(res, 400, "mission-control.slices.reorder.validation", "order must contain at least one slice id");
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
760
|
const rawRequest = deps.rawRequest ??
|
|
849
761
|
(typeof deps.client?.rawRequest === "function"
|
|
850
762
|
? deps.client.rawRequest.bind(deps.client)
|
|
@@ -236,6 +236,8 @@ function canonicalReadCacheKey(input) {
|
|
|
236
236
|
input.scope ?? "__none__",
|
|
237
237
|
input.order ?? "__none__",
|
|
238
238
|
input.mixPolicy ?? "__none__",
|
|
239
|
+
input.noiseThreshold ?? "__none__",
|
|
240
|
+
input.dedupWindowMs == null ? "__none__" : String(input.dedupWindowMs),
|
|
239
241
|
input.search ?? "__none__",
|
|
240
242
|
].join("|");
|
|
241
243
|
}
|
|
@@ -343,8 +345,12 @@ function normalizeQueueState(value) {
|
|
|
343
345
|
if (normalized === "queued" || normalized === "pending" || normalized === "todo" || normalized === "ready") {
|
|
344
346
|
return "queued";
|
|
345
347
|
}
|
|
346
|
-
if (normalized === "blocked" ||
|
|
348
|
+
if (normalized === "blocked" ||
|
|
349
|
+
normalized === "waiting" ||
|
|
350
|
+
normalized === "needs_decision" ||
|
|
351
|
+
normalized === "waiting_on_decision") {
|
|
347
352
|
return "blocked";
|
|
353
|
+
}
|
|
348
354
|
if (normalized === "completed" || normalized === "done")
|
|
349
355
|
return "completed";
|
|
350
356
|
return "idle";
|
|
@@ -568,6 +574,62 @@ function normalizeQueueItems(input) {
|
|
|
568
574
|
return left.workstreamTitle.localeCompare(right.workstreamTitle);
|
|
569
575
|
});
|
|
570
576
|
}
|
|
577
|
+
function blockedItemSeverity(item) {
|
|
578
|
+
if (item.scoringTier === "urgent")
|
|
579
|
+
return "high";
|
|
580
|
+
if (item.scoringTier === "deferred")
|
|
581
|
+
return "low";
|
|
582
|
+
const reason = (item.blockReason ?? "").toLowerCase();
|
|
583
|
+
if (!reason)
|
|
584
|
+
return "medium";
|
|
585
|
+
if (/\b(critical|sev-?1|severity\s*1|outage|security|incident|prod(?:uction)?\s+down)\b/.test(reason)) {
|
|
586
|
+
return "high";
|
|
587
|
+
}
|
|
588
|
+
if (/\b(review|approval|capacity|queue|backlog|follow[\s-]?up)\b/.test(reason)) {
|
|
589
|
+
return "low";
|
|
590
|
+
}
|
|
591
|
+
return "medium";
|
|
592
|
+
}
|
|
593
|
+
function applyNextUpNoiseAndDedup(items, noiseThreshold, dedupWindowMs) {
|
|
594
|
+
const filtered = items.filter((item) => {
|
|
595
|
+
if (item.queueState !== "blocked")
|
|
596
|
+
return true;
|
|
597
|
+
const severity = blockedItemSeverity(item);
|
|
598
|
+
if (noiseThreshold === "low")
|
|
599
|
+
return true;
|
|
600
|
+
if (noiseThreshold === "high")
|
|
601
|
+
return severity === "high";
|
|
602
|
+
return severity !== "low";
|
|
603
|
+
});
|
|
604
|
+
if (dedupWindowMs <= 0)
|
|
605
|
+
return filtered;
|
|
606
|
+
const deduped = [];
|
|
607
|
+
const latestByKey = new Map();
|
|
608
|
+
for (const item of filtered) {
|
|
609
|
+
if (item.queueState !== "blocked") {
|
|
610
|
+
deduped.push(item);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const reason = (item.blockReason ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
614
|
+
if (!reason) {
|
|
615
|
+
deduped.push(item);
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const updatedAt = Date.parse(item.updatedAt ?? "");
|
|
619
|
+
if (!Number.isFinite(updatedAt)) {
|
|
620
|
+
deduped.push(item);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const dedupKey = `${item.initiativeId}:${reason}`;
|
|
624
|
+
const lastSeen = latestByKey.get(dedupKey);
|
|
625
|
+
if (typeof lastSeen === "number" && Math.abs(updatedAt - lastSeen) <= dedupWindowMs) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
latestByKey.set(dedupKey, updatedAt);
|
|
629
|
+
deduped.push(item);
|
|
630
|
+
}
|
|
631
|
+
return deduped;
|
|
632
|
+
}
|
|
571
633
|
function mapCanonicalSlicesToQueueItems(input) {
|
|
572
634
|
const queueLike = [];
|
|
573
635
|
for (const entry of input) {
|
|
@@ -600,6 +662,8 @@ function mapCanonicalSlicesToQueueItems(input) {
|
|
|
600
662
|
}
|
|
601
663
|
else if (normalizedStatus === "blocked" ||
|
|
602
664
|
normalizedStatus === "waiting_dependency" ||
|
|
665
|
+
normalizedStatus === "needs_decision" ||
|
|
666
|
+
normalizedStatus === "waiting_on_decision" ||
|
|
603
667
|
normalizedStatus === "paused" ||
|
|
604
668
|
!runnable) {
|
|
605
669
|
queueState = "blocked";
|
|
@@ -797,9 +861,6 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
797
861
|
const dedupWindowMs = dedupWindowRaw != null
|
|
798
862
|
? Math.max(0, parseInt(dedupWindowRaw, 10) || 60000)
|
|
799
863
|
: 60000;
|
|
800
|
-
// TODO: wire noiseThreshold + dedupWindowMs into triage query once server-side filtering lands
|
|
801
|
-
void noiseThreshold;
|
|
802
|
-
void dedupWindowMs;
|
|
803
864
|
const nextUpCanonicalCacheKey = canonicalReadCacheKey({
|
|
804
865
|
route: "next-up",
|
|
805
866
|
workspaceId: projectId,
|
|
@@ -811,6 +872,8 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
811
872
|
scope: requestedSliceLevelContext,
|
|
812
873
|
order: requestedOrderMode,
|
|
813
874
|
mixPolicy: requestedMixPolicy,
|
|
875
|
+
noiseThreshold,
|
|
876
|
+
dedupWindowMs,
|
|
814
877
|
search: includeLineage ? "lineage:1" : null,
|
|
815
878
|
});
|
|
816
879
|
const cachedCanonicalNextUp = readCanonicalReadCache(nextUpCanonicalCacheKey, {
|
|
@@ -868,6 +931,8 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
868
931
|
params.set("order_mode", requestedOrderMode);
|
|
869
932
|
if (includeLineage)
|
|
870
933
|
params.set("include_lineage", "1");
|
|
934
|
+
params.set("noise_threshold", noiseThreshold);
|
|
935
|
+
params.set("dedup_window", String(dedupWindowMs));
|
|
871
936
|
const canonical = await requestCanonicalWithLegacyFallback(deps, {
|
|
872
937
|
timeoutMs: CANONICAL_NEXT_UP_TIMEOUT_MS,
|
|
873
938
|
label: "canonical next-up",
|
|
@@ -881,7 +946,7 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
881
946
|
if (isCanonicalAllScopeMismatch(canonicalRecord, useAllScope)) {
|
|
882
947
|
throw new Error("canonical next-up all-workspaces scope mismatch");
|
|
883
948
|
}
|
|
884
|
-
const canonicalItems = normalizeQueueItems(canonicalRecord.items).filter((item) => includeCompleted ? true : item.queueState !== "completed");
|
|
949
|
+
const canonicalItems = applyNextUpNoiseAndDedup(normalizeQueueItems(canonicalRecord.items).filter((item) => includeCompleted ? true : item.queueState !== "completed"), noiseThreshold, dedupWindowMs);
|
|
885
950
|
const canonicalTotal = Math.max(canonicalItems.length, Math.floor(asNumber(canonicalRecord.total) ?? canonicalItems.length)) ?? canonicalItems.length;
|
|
886
951
|
const canonicalPagination = parsePaginationEnvelope(canonicalRecord.pagination, {
|
|
887
952
|
offset,
|
|
@@ -1007,7 +1072,7 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
1007
1072
|
if (isCanonicalAllScopeMismatch(canonicalSlicesRecord, useAllScope)) {
|
|
1008
1073
|
throw new Error("canonical slices all-workspaces scope mismatch");
|
|
1009
1074
|
}
|
|
1010
|
-
const bridgedItems = mapCanonicalSlicesToQueueItems(canonicalSlicesRecord.items).filter((item) => includeCompleted ? true : item.queueState !== "completed");
|
|
1075
|
+
const bridgedItems = applyNextUpNoiseAndDedup(mapCanonicalSlicesToQueueItems(canonicalSlicesRecord.items).filter((item) => includeCompleted ? true : item.queueState !== "completed"), noiseThreshold, dedupWindowMs);
|
|
1011
1076
|
if (bridgedItems.length > 0) {
|
|
1012
1077
|
const paged = applySliceSearchAndPagination({
|
|
1013
1078
|
items: bridgedItems,
|
|
@@ -1050,7 +1115,7 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
1050
1115
|
initiativeId,
|
|
1051
1116
|
projectId,
|
|
1052
1117
|
});
|
|
1053
|
-
const items = normalizeQueueItems(queue.items ?? []).filter((item) => includeCompleted ? true : item.queueState !== "completed");
|
|
1118
|
+
const items = applyNextUpNoiseAndDedup(normalizeQueueItems(queue.items ?? []).filter((item) => includeCompleted ? true : item.queueState !== "completed"), noiseThreshold, dedupWindowMs);
|
|
1054
1119
|
const paged = applySliceSearchAndPagination({
|
|
1055
1120
|
items,
|
|
1056
1121
|
searchTerm: "",
|
|
@@ -1083,7 +1148,7 @@ export function registerMissionControlReadRoutes(router, deps) {
|
|
|
1083
1148
|
initiativeId,
|
|
1084
1149
|
projectId,
|
|
1085
1150
|
});
|
|
1086
|
-
const items = normalizeQueueItems(queue.items ?? []).filter((item) => includeCompleted ? true : item.queueState !== "completed");
|
|
1151
|
+
const items = applyNextUpNoiseAndDedup(normalizeQueueItems(queue.items ?? []).filter((item) => includeCompleted ? true : item.queueState !== "completed"), noiseThreshold, dedupWindowMs);
|
|
1087
1152
|
const paged = applySliceSearchAndPagination({
|
|
1088
1153
|
items,
|
|
1089
1154
|
searchTerm: "",
|
|
@@ -45,6 +45,9 @@ export type RegisteredPrompt = {
|
|
|
45
45
|
}>;
|
|
46
46
|
messages: PromptMessage[];
|
|
47
47
|
};
|
|
48
|
+
type OrgxMcpScopeKey = "engineering" | "product" | "design" | "marketing" | "sales" | "operations" | "orchestration";
|
|
49
|
+
export declare const ORGX_BASE_TOOLS: readonly ["orgx_status", "orgx_sync", "list_agent_configs", "get_agent_config", "orgx_emit_activity", "orgx_report_progress", "update_stream_progress", "orgx_register_artifact", "orgx_request_decision", "orgx_spawn_check", "orgx_quality_score", "orgx_proof_status", "orgx_record_outcome", "orgx_get_outcome_attribution", "orgx_verify_completion"];
|
|
50
|
+
export declare const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE: Record<OrgxMcpScopeKey, string[]>;
|
|
48
51
|
export declare function createMcpHttpHandler(input: {
|
|
49
52
|
tools: Map<string, RegisteredTool>;
|
|
50
53
|
prompts?: Map<string, RegisteredPrompt>;
|
package/dist/mcp-http-handler.js
CHANGED
|
@@ -6,7 +6,7 @@ const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
|
|
|
6
6
|
// NOTE: This scopes only the tools exposed by this plugin (OrgX reporting + mutation).
|
|
7
7
|
// It cannot restrict OpenClaw-native tools (filesystem, shell, etc).
|
|
8
8
|
// Base tools available to all domain scopes
|
|
9
|
-
const ORGX_BASE_TOOLS = [
|
|
9
|
+
export const ORGX_BASE_TOOLS = [
|
|
10
10
|
"orgx_status",
|
|
11
11
|
"orgx_sync",
|
|
12
12
|
"list_agent_configs",
|
|
@@ -24,7 +24,7 @@ const ORGX_BASE_TOOLS = [
|
|
|
24
24
|
"orgx_get_outcome_attribution",
|
|
25
25
|
"orgx_verify_completion",
|
|
26
26
|
];
|
|
27
|
-
const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE = {
|
|
27
|
+
export const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE = {
|
|
28
28
|
engineering: [...ORGX_BASE_TOOLS],
|
|
29
29
|
product: [...ORGX_BASE_TOOLS],
|
|
30
30
|
design: [...ORGX_BASE_TOOLS],
|
package/dist/paths.js
CHANGED
|
@@ -4,9 +4,21 @@ function normalizeDirOverride(value) {
|
|
|
4
4
|
let trimmed = (value ?? "").trim();
|
|
5
5
|
if (!trimmed)
|
|
6
6
|
return null;
|
|
7
|
+
const startsDoubleQuoted = trimmed.startsWith('"');
|
|
8
|
+
const startsSingleQuoted = trimmed.startsWith("'");
|
|
9
|
+
const endsDoubleQuoted = trimmed.endsWith('"');
|
|
10
|
+
const endsSingleQuoted = trimmed.endsWith("'");
|
|
11
|
+
const startsQuoted = startsDoubleQuoted || startsSingleQuoted;
|
|
12
|
+
const endsQuoted = endsDoubleQuoted || endsSingleQuoted;
|
|
13
|
+
if (startsQuoted !== endsQuoted)
|
|
14
|
+
return null;
|
|
15
|
+
// Reject mismatched wrappers like `"path'` and `'path"`.
|
|
16
|
+
if ((startsDoubleQuoted && endsSingleQuoted) || (startsSingleQuoted && endsDoubleQuoted)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
7
19
|
// `.env` values are often quoted; normalize them before validation.
|
|
8
|
-
if ((
|
|
9
|
-
(
|
|
20
|
+
if ((startsDoubleQuoted && endsDoubleQuoted) ||
|
|
21
|
+
(startsSingleQuoted && endsSingleQuoted)) {
|
|
10
22
|
trimmed = trimmed.slice(1, -1).trim();
|
|
11
23
|
if (!trimmed)
|
|
12
24
|
return null;
|
|
@@ -44,18 +44,6 @@ export declare function computeTaskCompletionReadiness(input: {
|
|
|
44
44
|
qualityThreshold?: number;
|
|
45
45
|
hasOutcomeEvent?: boolean;
|
|
46
46
|
}): TaskCompletionReadinessResult;
|
|
47
|
-
export type LifecycleState = 'Queued' | 'Dispatching' | 'In Progress' | 'Blocked' | 'Completed' | 'Paused' | 'Failed';
|
|
48
|
-
/**
|
|
49
|
-
* Derive an honest lifecycle state for an entity based on its raw status
|
|
50
|
-
* and child statuses. This is the authoritative backend derivation —
|
|
51
|
-
* the dashboard mirrors this logic in status-taxonomy.ts.
|
|
52
|
-
*/
|
|
53
|
-
export declare function deriveLifecycleState(rawStatus: unknown, childStatuses?: unknown[]): LifecycleState;
|
|
54
|
-
/**
|
|
55
|
-
* Compute cascaded progress: parent progress derived from child task states.
|
|
56
|
-
* Returns a percentage (0–100) representing weighted completion.
|
|
57
|
-
*/
|
|
58
|
-
export declare function cascadeProgressFromChildren(childStatuses: unknown[]): number;
|
|
59
47
|
export type EvalPassRateDriftResult = {
|
|
60
48
|
alert: boolean;
|
|
61
49
|
baselinePassRate: number;
|