@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.
Files changed (124) hide show
  1. package/README.md +42 -11
  2. package/dashboard/dist/assets/6mILZQ2a.js +1 -0
  3. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  4. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  5. package/dashboard/dist/assets/{DG6y9wJI.js → 8dksYiq4.js} +1 -1
  6. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  7. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  8. package/dashboard/dist/assets/{PAUiij_z.js → B5zYRHc3.js} +1 -1
  9. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  10. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  11. package/dashboard/dist/assets/{CFGKRAzG.js → B6wPWJ35.js} +1 -1
  12. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  13. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  14. package/dashboard/dist/assets/BWEwjt1W.js +1 -0
  15. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  16. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  17. package/dashboard/dist/assets/BzRbDCAD.css +1 -0
  18. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  19. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  20. package/dashboard/dist/assets/{DNxKz-GV.js → C8uM3AX8.js} +1 -1
  21. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  22. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  23. package/dashboard/dist/assets/C9jy61eu.js +212 -0
  24. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  25. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  26. package/dashboard/dist/assets/{CE38zU4U.js → CC63EwFD.js} +1 -1
  27. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  28. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  29. package/dashboard/dist/assets/{nByHNHoW.js → CZaT3ob_.js} +1 -1
  30. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  31. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  32. package/dashboard/dist/assets/{tS9mbYZi.js → CgaottFX.js} +1 -1
  33. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  34. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  35. package/dashboard/dist/assets/CzCxAZlW.js +1 -0
  36. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  37. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  38. package/dashboard/dist/assets/D3iMTYEj.js +1 -0
  39. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  40. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  41. package/dashboard/dist/assets/{DjcdE6jC.js → D8JNX8kq.js} +1 -1
  42. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  43. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  44. package/dashboard/dist/assets/DnA8dpj6.js +1 -0
  45. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  46. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  47. package/dashboard/dist/assets/{DbNoijHm.js → IUexzymk.js} +1 -1
  48. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  49. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  50. package/dashboard/dist/assets/{CZZTvkQZ.js → rttbDbEx.js} +1 -1
  51. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  52. package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
  53. package/dashboard/dist/index.html +2 -2
  54. package/dashboard/dist/index.html.br +0 -0
  55. package/dashboard/dist/index.html.gz +0 -0
  56. package/dist/contracts/practice-exercise-schema.d.ts +216 -0
  57. package/dist/contracts/practice-exercise-schema.js +314 -0
  58. package/dist/contracts/shared-types.d.ts +2 -2
  59. package/dist/contracts/shared-types.js +22 -0
  60. package/dist/contracts/types.d.ts +20 -0
  61. package/dist/fs-utils.js +1 -13
  62. package/dist/http/helpers/auto-continue-engine.js +638 -24
  63. package/dist/http/helpers/autopilot-runtime.js +22 -7
  64. package/dist/http/helpers/autopilot-slice-utils.js +0 -2
  65. package/dist/http/helpers/kickoff-context.js +30 -0
  66. package/dist/http/helpers/slice-run-projections.js +19 -2
  67. package/dist/http/index.js +151 -93
  68. package/dist/http/routes/agent-suite.js +87 -0
  69. package/dist/http/routes/entities.js +1 -63
  70. package/dist/http/routes/live-snapshot.d.ts +1 -0
  71. package/dist/http/routes/live-snapshot.js +15 -4
  72. package/dist/http/routes/live-terminal.js +1 -108
  73. package/dist/http/routes/live-triage.js +1 -57
  74. package/dist/http/routes/mission-control-actions.js +0 -88
  75. package/dist/http/routes/mission-control-read.js +73 -8
  76. package/dist/mcp-http-handler.d.ts +3 -0
  77. package/dist/mcp-http-handler.js +2 -2
  78. package/dist/openclaw.plugin.json +1 -1
  79. package/dist/paths.js +14 -2
  80. package/dist/reporting/rollups.d.ts +0 -12
  81. package/dist/reporting/rollups.js +0 -35
  82. package/openclaw.plugin.json +1 -1
  83. package/package.json +7 -3
  84. package/dashboard/dist/assets/BXWDRGm-.js +0 -1
  85. package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
  86. package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
  87. package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
  88. package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
  89. package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
  90. package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
  91. package/dashboard/dist/assets/CGGR2GZh.js +0 -1
  92. package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
  93. package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
  94. package/dashboard/dist/assets/CPFiTmlw.js +0 -8
  95. package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
  96. package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
  97. package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
  98. package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
  99. package/dashboard/dist/assets/D-bf6hEI.js +0 -213
  100. package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
  101. package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
  102. package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
  103. package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
  104. package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
  105. package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
  106. package/dashboard/dist/assets/DW_rKUic.js +0 -11
  107. package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
  108. package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
  109. package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
  110. package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
  111. package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
  112. package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
  113. package/dashboard/dist/assets/FZYuCDnt.js +0 -1
  114. package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
  115. package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
  116. package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
  117. package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
  118. package/dashboard/dist/assets/h5biQs2I.css +0 -1
  119. package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
  120. package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
  121. package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
  122. package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
  123. package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
  124. 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
- // Post-status-transition hook: when a task status changes to
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) => Date.parse(b.timestamp) - Date.parse(a.timestamp))
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 seen = new Set();
456
+ const seenIds = new Set();
457
+ const seenSemantic = new Set();
452
458
  for (const item of merged) {
453
- if (seen.has(item.id))
459
+ if (seenIds.has(item.id))
454
460
  continue;
455
- seen.add(item.id);
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 { closeSync, existsSync, openSync, readSync, statSync } from "node:fs";
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. Apply noise threshold suppress low-severity items based on threshold
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" || normalized === "waiting")
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>;
@@ -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],
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "orgx",
2
+ "id": "openclaw-plugin",
3
3
  "name": "OrgX Integration",
4
4
  "version": "1.0.2",
5
5
  "description": "Connects Clawdbot to OrgX for agent orchestration, quality gates, and model routing",
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 ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
9
- (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
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;