@useorgx/openclaw-plugin 0.4.9 → 0.7.0

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 (222) hide show
  1. package/README.md +35 -0
  2. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  3. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  4. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  5. package/dashboard/dist/assets/BXWDRGm-.js +1 -0
  6. package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
  7. package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
  8. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  9. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  10. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  11. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  12. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  13. package/dashboard/dist/assets/CE38zU4U.js +1 -0
  14. package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
  15. package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
  16. package/dashboard/dist/assets/CFGKRAzG.js +1 -0
  17. package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
  18. package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
  19. package/dashboard/dist/assets/CGGR2GZh.js +1 -0
  20. package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
  21. package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
  22. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  23. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  24. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  25. package/dashboard/dist/assets/CPFiTmlw.js +8 -0
  26. package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
  27. package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
  28. package/dashboard/dist/assets/CZZTvkQZ.js +1 -0
  29. package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
  30. package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
  31. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  32. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  33. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  34. package/dashboard/dist/assets/D-bf6hEI.js +213 -0
  35. package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
  36. package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
  37. package/dashboard/dist/assets/DG6y9wJI.js +2 -0
  38. package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
  39. package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
  40. package/dashboard/dist/assets/DNxKz-GV.js +1 -0
  41. package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
  42. package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
  43. package/dashboard/dist/assets/DW_rKUic.js +11 -0
  44. package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
  45. package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
  46. package/dashboard/dist/assets/DbNoijHm.js +1 -0
  47. package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
  48. package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
  49. package/dashboard/dist/assets/DjcdE6jC.js +2 -0
  50. package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
  51. package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
  52. package/dashboard/dist/assets/FZYuCDnt.js +1 -0
  53. package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
  54. package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
  55. package/dashboard/dist/assets/PAUiij_z.js +1 -0
  56. package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
  57. package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
  58. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  59. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  60. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  61. package/dashboard/dist/assets/h5biQs2I.css +1 -0
  62. package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
  63. package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
  64. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  65. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  66. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  67. package/dashboard/dist/assets/nByHNHoW.js +1 -0
  68. package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
  69. package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
  70. package/dashboard/dist/assets/qm8xLgv-.css +1 -0
  71. package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
  72. package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
  73. package/dashboard/dist/assets/tS9mbYZi.js +1 -0
  74. package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
  75. package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
  76. package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
  77. package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
  78. package/dashboard/dist/brand/openai-mark.svg.br +0 -0
  79. package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
  80. package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
  81. package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
  82. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  83. package/dashboard/dist/index.html +7 -5
  84. package/dashboard/dist/index.html.br +0 -0
  85. package/dashboard/dist/index.html.gz +0 -0
  86. package/dist/activity-actor-fields.js +26 -4
  87. package/dist/activity-store.js +34 -8
  88. package/dist/agent-context-store.js +79 -17
  89. package/dist/agent-run-store.js +44 -3
  90. package/dist/agent-suite.d.ts +9 -0
  91. package/dist/agent-suite.js +149 -9
  92. package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
  93. package/dist/artifacts/artifact-domain-schemas.js +357 -0
  94. package/dist/artifacts/register-artifact.d.ts +4 -3
  95. package/dist/artifacts/register-artifact.js +170 -57
  96. package/dist/chat-store.d.ts +157 -0
  97. package/dist/chat-store.js +586 -0
  98. package/dist/cli/orgx.js +11 -0
  99. package/dist/contracts/client.d.ts +43 -3
  100. package/dist/contracts/client.js +159 -30
  101. package/dist/contracts/retro-schema.d.ts +81 -0
  102. package/dist/contracts/retro-schema.js +80 -0
  103. package/dist/contracts/shared-types.d.ts +159 -0
  104. package/dist/contracts/shared-types.js +177 -1
  105. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  106. package/dist/contracts/skill-pack-schema.js +180 -0
  107. package/dist/contracts/types.d.ts +227 -2
  108. package/dist/entities/auto-assignment.js +43 -17
  109. package/dist/event-sanitization.d.ts +11 -0
  110. package/dist/event-sanitization.js +113 -0
  111. package/dist/fs-utils.js +13 -1
  112. package/dist/gateway-watchdog.d.ts +5 -0
  113. package/dist/gateway-watchdog.js +50 -0
  114. package/dist/hooks/post-reporting-event.mjs +1 -5
  115. package/dist/http/helpers/activity-headline.js +13 -132
  116. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  117. package/dist/http/helpers/auto-continue-engine.js +2531 -186
  118. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  119. package/dist/http/helpers/autopilot-operations.js +182 -31
  120. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  121. package/dist/http/helpers/autopilot-runtime.js +308 -20
  122. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  123. package/dist/http/helpers/autopilot-slice-utils.js +516 -93
  124. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  125. package/dist/http/helpers/decision-mapper.js +223 -7
  126. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  127. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  128. package/dist/http/helpers/kickoff-context.js +74 -0
  129. package/dist/http/helpers/llm-client.d.ts +47 -0
  130. package/dist/http/helpers/llm-client.js +256 -0
  131. package/dist/http/helpers/mission-control.d.ts +102 -3
  132. package/dist/http/helpers/mission-control.js +498 -9
  133. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  134. package/dist/http/helpers/sentinel-catalog.js +193 -0
  135. package/dist/http/helpers/session-classification.d.ts +9 -0
  136. package/dist/http/helpers/session-classification.js +564 -0
  137. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  138. package/dist/http/helpers/slice-experience-v2.js +677 -0
  139. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  140. package/dist/http/helpers/slice-run-projections.js +860 -0
  141. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  142. package/dist/http/helpers/triage-mapper.js +549 -0
  143. package/dist/http/helpers/value-utils.js +7 -2
  144. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  145. package/dist/http/helpers/workspace-scope.js +170 -0
  146. package/dist/http/index.js +1354 -97
  147. package/dist/http/routes/agent-suite.d.ts +9 -0
  148. package/dist/http/routes/agent-suite.js +207 -8
  149. package/dist/http/routes/agents-catalog.js +64 -19
  150. package/dist/http/routes/chat.d.ts +19 -0
  151. package/dist/http/routes/chat.js +522 -0
  152. package/dist/http/routes/decision-actions.d.ts +8 -1
  153. package/dist/http/routes/decision-actions.js +42 -5
  154. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  155. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  156. package/dist/http/routes/entities.d.ts +16 -0
  157. package/dist/http/routes/entities.js +294 -6
  158. package/dist/http/routes/live-legacy.d.ts +5 -0
  159. package/dist/http/routes/live-legacy.js +23 -509
  160. package/dist/http/routes/live-misc.d.ts +12 -0
  161. package/dist/http/routes/live-misc.js +251 -31
  162. package/dist/http/routes/live-snapshot.d.ts +48 -2
  163. package/dist/http/routes/live-snapshot.js +638 -19
  164. package/dist/http/routes/live-terminal.d.ts +11 -0
  165. package/dist/http/routes/live-terminal.js +261 -0
  166. package/dist/http/routes/live-triage.d.ts +61 -0
  167. package/dist/http/routes/live-triage.js +248 -0
  168. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  169. package/dist/http/routes/mission-control-actions.js +1334 -84
  170. package/dist/http/routes/mission-control-read.d.ts +48 -3
  171. package/dist/http/routes/mission-control-read.js +1593 -20
  172. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  173. package/dist/http/routes/realtime-orchestrator.js +74 -0
  174. package/dist/http/routes/run-control.d.ts +5 -2
  175. package/dist/http/routes/run-control.js +10 -0
  176. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  177. package/dist/http/routes/sentinels-catalog.js +24 -0
  178. package/dist/http/routes/summary.js +10 -3
  179. package/dist/http/routes/usage.d.ts +24 -0
  180. package/dist/http/routes/usage.js +362 -0
  181. package/dist/http/routes/work-artifacts.js +28 -9
  182. package/dist/index.js +165 -27
  183. package/dist/local-openclaw.js +29 -6
  184. package/dist/mcp-client-setup.js +3 -3
  185. package/dist/mcp-http-handler.js +33 -59
  186. package/dist/next-up-queue-store.d.ts +16 -1
  187. package/dist/next-up-queue-store.js +89 -7
  188. package/dist/outbox.d.ts +5 -0
  189. package/dist/outbox.js +113 -9
  190. package/dist/paths.js +24 -5
  191. package/dist/reporting/rollups.d.ts +53 -0
  192. package/dist/reporting/rollups.js +148 -0
  193. package/dist/retro/domain-templates.d.ts +45 -0
  194. package/dist/retro/domain-templates.js +297 -0
  195. package/dist/retro/quality-rubric.d.ts +33 -0
  196. package/dist/retro/quality-rubric.js +213 -0
  197. package/dist/runtime-cleanup.d.ts +18 -0
  198. package/dist/runtime-cleanup.js +87 -0
  199. package/dist/services/background.d.ts +11 -0
  200. package/dist/services/background.js +22 -0
  201. package/dist/services/experiment-randomization.d.ts +21 -0
  202. package/dist/services/experiment-randomization.js +63 -0
  203. package/dist/skill-pack-state.d.ts +36 -5
  204. package/dist/skill-pack-state.js +273 -29
  205. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  206. package/dist/sync/local-agent-telemetry.js +128 -0
  207. package/dist/sync/outbox-replay.js +131 -24
  208. package/dist/team-context-store.d.ts +23 -0
  209. package/dist/team-context-store.js +116 -0
  210. package/dist/telemetry/posthog.js +4 -2
  211. package/dist/tools/core-tools.d.ts +10 -14
  212. package/dist/tools/core-tools.js +1289 -24
  213. package/dist/types.d.ts +2 -0
  214. package/dist/types.js +2 -0
  215. package/dist/worker-supervisor.js +23 -0
  216. package/package.json +14 -4
  217. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  218. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  219. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  220. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  221. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  222. package/dashboard/dist/assets/sAhvFnpk.js +0 -4
@@ -3,6 +3,7 @@ import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
3
3
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
4
4
  import { ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
5
5
  const MAX_PINS = 240;
6
+ const MAX_SUPPRESSIONS = 2_000;
6
7
  function storeDir() {
7
8
  return getOrgxPluginConfigDir();
8
9
  }
@@ -28,31 +29,56 @@ function normalizeEntry(input) {
28
29
  updatedAt: input.updatedAt,
29
30
  };
30
31
  }
32
+ function normalizeSuppression(input) {
33
+ return {
34
+ initiativeId: input.initiativeId.trim(),
35
+ workstreamId: input.workstreamId.trim(),
36
+ createdAt: input.createdAt,
37
+ updatedAt: input.updatedAt,
38
+ };
39
+ }
40
+ function entryKey(input) {
41
+ return `${input.initiativeId}:${input.workstreamId}`;
42
+ }
43
+ function createEmptyStore() {
44
+ return {
45
+ version: 2,
46
+ updatedAt: new Date().toISOString(),
47
+ pins: [],
48
+ suppressions: [],
49
+ };
50
+ }
31
51
  export function readNextUpQueuePins() {
32
52
  const file = storeFile();
33
53
  try {
34
54
  if (!existsSync(file)) {
35
- return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
55
+ return createEmptyStore();
36
56
  }
37
57
  const raw = readFileSync(file, "utf8");
38
58
  const parsed = parseJsonSafe(raw);
39
59
  if (!parsed || typeof parsed !== "object") {
40
60
  backupCorruptFileSync(file);
41
- return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
61
+ return createEmptyStore();
42
62
  }
43
63
  const pins = Array.isArray(parsed.pins) ? parsed.pins : [];
64
+ const suppressions = Array.isArray(parsed.suppressions)
65
+ ? parsed.suppressions
66
+ : [];
44
67
  return {
45
- version: 1,
68
+ version: 2,
46
69
  updatedAt: typeof parsed.updatedAt === "string"
47
70
  ? parsed.updatedAt
48
71
  : new Date().toISOString(),
49
72
  pins: pins
50
73
  .filter((entry) => Boolean(entry && typeof entry === "object"))
51
74
  .map((entry) => normalizeEntry(entry)),
75
+ suppressions: suppressions
76
+ .filter((entry) => Boolean(entry && typeof entry === "object"))
77
+ .map((entry) => normalizeSuppression(entry)),
52
78
  };
53
79
  }
54
80
  catch {
55
- return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
81
+ return createEmptyStore();
56
82
  }
57
83
  }
58
84
  export function upsertNextUpQueuePin(input) {
@@ -65,7 +91,7 @@ export function upsertNextUpQueuePin(input) {
65
91
  const now = new Date().toISOString();
66
92
  const next = readNextUpQueuePins();
67
93
  const key = `${initiativeId}:${workstreamId}`;
68
- const existing = next.pins.find((pin) => `${pin.initiativeId}:${pin.workstreamId}` === key);
94
+ const existing = next.pins.find((pin) => entryKey(pin) === key);
69
95
  const updated = normalizeEntry({
70
96
  initiativeId,
71
97
  workstreamId,
@@ -74,7 +100,8 @@ export function upsertNextUpQueuePin(input) {
74
100
  createdAt: existing?.createdAt ?? now,
75
101
  updatedAt: now,
76
102
  });
77
- next.pins = [updated, ...next.pins.filter((pin) => `${pin.initiativeId}:${pin.workstreamId}` !== key)].slice(0, MAX_PINS);
103
+ next.pins = [updated, ...next.pins.filter((pin) => entryKey(pin) !== key)].slice(0, MAX_PINS);
104
+ next.suppressions = next.suppressions.filter((suppression) => entryKey(suppression) !== key);
78
105
  next.updatedAt = now;
79
106
  try {
80
107
  writeJsonFileAtomicSync(storeFile(), next, 0o600);
@@ -93,7 +120,7 @@ export function removeNextUpQueuePin(input) {
93
120
  ensureStoreDir();
94
121
  const next = readNextUpQueuePins();
95
122
  const key = `${initiativeId}:${workstreamId}`;
96
- const filtered = next.pins.filter((pin) => `${pin.initiativeId}:${pin.workstreamId}` !== key);
123
+ const filtered = next.pins.filter((pin) => entryKey(pin) !== key);
97
124
  if (filtered.length === next.pins.length)
98
125
  return next;
99
126
  next.pins = filtered;
@@ -144,6 +171,9 @@ export function setNextUpQueuePinOrder(input) {
144
171
  ordered.push(pin);
145
172
  }
146
173
  next.pins = ordered.slice(0, MAX_PINS);
174
+ if (seen.size > 0 && next.suppressions.length > 0) {
175
+ next.suppressions = next.suppressions.filter((suppression) => !seen.has(entryKey(suppression)));
176
+ }
147
177
  next.updatedAt = now;
148
178
  try {
149
179
  writeJsonFileAtomicSync(storeFile(), next, 0o600);
@@ -153,3 +183,55 @@ export function setNextUpQueuePinOrder(input) {
153
183
  }
154
184
  return next;
155
185
  }
186
+ export function suppressNextUpQueueItem(input) {
187
+ const initiativeId = input.initiativeId.trim();
188
+ const workstreamId = input.workstreamId.trim();
189
+ if (!initiativeId || !workstreamId) {
190
+ return readNextUpQueuePins();
191
+ }
192
+ ensureStoreDir();
193
+ const now = new Date().toISOString();
194
+ const next = readNextUpQueuePins();
195
+ const key = `${initiativeId}:${workstreamId}`;
196
+ const existing = next.suppressions.find((suppression) => entryKey(suppression) === key);
197
+ next.suppressions = [
198
+ normalizeSuppression({
199
+ initiativeId,
200
+ workstreamId,
201
+ createdAt: existing?.createdAt ?? now,
202
+ updatedAt: now,
203
+ }),
204
+ ...next.suppressions.filter((suppression) => entryKey(suppression) !== key),
205
+ ].slice(0, MAX_SUPPRESSIONS);
206
+ next.pins = next.pins.filter((pin) => entryKey(pin) !== key);
207
+ next.updatedAt = now;
208
+ try {
209
+ writeJsonFileAtomicSync(storeFile(), next, 0o600);
210
+ }
211
+ catch {
212
+ // best effort
213
+ }
214
+ return next;
215
+ }
216
+ export function unsuppressNextUpQueueItem(input) {
217
+ const initiativeId = input.initiativeId.trim();
218
+ const workstreamId = input.workstreamId.trim();
219
+ if (!initiativeId || !workstreamId) {
220
+ return readNextUpQueuePins();
221
+ }
222
+ ensureStoreDir();
223
+ const next = readNextUpQueuePins();
224
+ const key = `${initiativeId}:${workstreamId}`;
225
+ const filtered = next.suppressions.filter((suppression) => entryKey(suppression) !== key);
226
+ if (filtered.length === next.suppressions.length)
227
+ return next;
228
+ next.suppressions = filtered;
229
+ next.updatedAt = new Date().toISOString();
230
+ try {
231
+ writeJsonFileAtomicSync(storeFile(), next, 0o600);
232
+ }
233
+ catch {
234
+ // best effort
235
+ }
236
+ return next;
237
+ }
package/dist/outbox.d.ts CHANGED
@@ -11,6 +11,10 @@ export interface OutboxEvent {
11
11
  payload: Record<string, unknown>;
12
12
  /** Converted to a LiveActivityItem for dashboard display. */
13
13
  activityItem: LiveActivityItem;
14
+ /** Internal replay diagnostics for bounded retry/dead-letter handling. */
15
+ replayFailures?: number;
16
+ lastReplayError?: string | null;
17
+ lastReplayAt?: string | null;
14
18
  }
15
19
  export interface OutboxSummary {
16
20
  pendingTotal: number;
@@ -18,6 +22,7 @@ export interface OutboxSummary {
18
22
  oldestEventAt: string | null;
19
23
  newestEventAt: string | null;
20
24
  }
25
+ export declare function appendOutboxDeadLetter(sessionId: string, event: OutboxEvent, reason: string, error?: string | null): Promise<void>;
21
26
  export declare function readOutbox(sessionId: string): Promise<OutboxEvent[]>;
22
27
  export declare function appendToOutbox(sessionId: string, event: OutboxEvent): Promise<void>;
23
28
  export declare function replaceOutbox(sessionId: string, events: OutboxEvent[]): Promise<void>;
package/dist/outbox.js CHANGED
@@ -4,8 +4,9 @@
4
4
  * Events are flushed on next successful sync.
5
5
  */
6
6
  import { join } from "node:path";
7
- import { readFile, writeFile, mkdir, chmod, rename, unlink, readdir, } from "node:fs/promises";
7
+ import { readFile, writeFile, mkdir, chmod, rename, unlink, readdir, appendFile, } from "node:fs/promises";
8
8
  import { randomUUID } from "node:crypto";
9
+ import { classifyOutboxReplaySkip } from "./event-sanitization.js";
9
10
  import { getOrgxOutboxDir } from "./paths.js";
10
11
  function outboxDir() {
11
12
  return getOrgxOutboxDir();
@@ -36,6 +37,11 @@ async function hardenPath(path, mode) {
36
37
  // best effort
37
38
  }
38
39
  }
40
+ /** Events older than 7 days are stale — sync will never recover them. */
41
+ const OUTBOX_EVENT_TTL_MS = 7 * 24 * 60 * 60_000;
42
+ /** Hard cap per session to prevent unbounded growth if sync repeatedly fails. */
43
+ const OUTBOX_MAX_EVENTS_PER_SESSION = 500;
44
+ const DEAD_LETTER_DIRNAME = "_dead-letter";
39
45
  async function ensureDir() {
40
46
  const dir = outboxDir();
41
47
  try {
@@ -46,6 +52,12 @@ async function ensureDir() {
46
52
  // Directory may already exist
47
53
  }
48
54
  }
55
+ function deadLetterDir() {
56
+ return join(outboxDir(), DEAD_LETTER_DIRNAME);
57
+ }
58
+ function deadLetterPath(sessionId) {
59
+ return join(deadLetterDir(), `${normalizeSessionId(sessionId)}.jsonl`);
60
+ }
49
61
  function outboxPath(sessionId) {
50
62
  return join(outboxDir(), `${normalizeSessionId(sessionId)}.json`);
51
63
  }
@@ -89,13 +101,79 @@ async function writeFileAtomic(targetPath, content, mode) {
89
101
  }
90
102
  await hardenPath(targetPath, mode);
91
103
  }
104
+ async function appendOutboxDeadLetterRecord(sessionId, record) {
105
+ await ensureDir();
106
+ const dir = deadLetterDir();
107
+ await mkdir(dir, { recursive: true, mode: 0o700 });
108
+ await hardenPath(dir, 0o700);
109
+ const targetPath = deadLetterPath(sessionId);
110
+ await appendFile(targetPath, `${JSON.stringify(record)}\n`, {
111
+ encoding: "utf8",
112
+ mode: 0o600,
113
+ });
114
+ await hardenPath(targetPath, 0o600);
115
+ }
116
+ export async function appendOutboxDeadLetter(sessionId, event, reason, error) {
117
+ const droppedAt = new Date().toISOString();
118
+ await appendOutboxDeadLetterRecord(sessionId, {
119
+ droppedAt,
120
+ queueId: normalizeSessionId(sessionId),
121
+ reason,
122
+ error: error ?? null,
123
+ event,
124
+ });
125
+ }
126
+ /** Drop stale events and enforce per-session cap. Returns true if any were removed. */
127
+ function pruneOutboxEvents(events) {
128
+ const cutoff = Date.now() - OUTBOX_EVENT_TTL_MS;
129
+ const fresh = events.filter((e) => {
130
+ const epoch = Date.parse(e.timestamp);
131
+ return Number.isFinite(epoch) && epoch >= cutoff;
132
+ });
133
+ // Keep newest events if over cap.
134
+ const capped = fresh.length > OUTBOX_MAX_EVENTS_PER_SESSION
135
+ ? fresh.slice(fresh.length - OUTBOX_MAX_EVENTS_PER_SESSION)
136
+ : fresh;
137
+ return { pruned: capped, changed: capped.length !== events.length };
138
+ }
92
139
  export async function readOutbox(sessionId) {
93
- const targetPath = outboxPath(sessionId);
140
+ let targetPath;
141
+ try {
142
+ targetPath = outboxPath(sessionId);
143
+ }
144
+ catch {
145
+ return [];
146
+ }
94
147
  try {
95
148
  const raw = await readFile(targetPath, "utf8");
96
149
  try {
97
150
  const parsed = JSON.parse(raw);
98
- return Array.isArray(parsed) ? parsed : [];
151
+ const events = Array.isArray(parsed) ? parsed : [];
152
+ const { pruned, changed } = pruneOutboxEvents(events);
153
+ const filtered = [];
154
+ let filteredChanged = changed;
155
+ for (const event of pruned) {
156
+ const skipReason = classifyOutboxReplaySkip(event);
157
+ if (!skipReason) {
158
+ filtered.push(event);
159
+ continue;
160
+ }
161
+ filteredChanged = true;
162
+ await appendOutboxDeadLetter(sessionId, event, `pruned_on_read:${skipReason}`, null);
163
+ }
164
+ // Write back if stale events were dropped.
165
+ if (filteredChanged) {
166
+ if (filtered.length === 0) {
167
+ try {
168
+ await unlink(targetPath);
169
+ }
170
+ catch { /* ok */ }
171
+ }
172
+ else {
173
+ await writeFileAtomic(targetPath, JSON.stringify(filtered), 0o600);
174
+ }
175
+ }
176
+ return filtered;
99
177
  }
100
178
  catch {
101
179
  await backupCorruptOutboxFile(targetPath);
@@ -107,9 +185,21 @@ export async function readOutbox(sessionId) {
107
185
  }
108
186
  }
109
187
  export async function appendToOutbox(sessionId, event) {
188
+ let normalizedSessionId;
189
+ try {
190
+ normalizedSessionId = normalizeSessionId(sessionId);
191
+ }
192
+ catch {
193
+ return;
194
+ }
195
+ const skipReason = classifyOutboxReplaySkip(event);
196
+ if (skipReason) {
197
+ await appendOutboxDeadLetter(normalizedSessionId, event, `suppressed_on_append:${skipReason}`, null);
198
+ return;
199
+ }
110
200
  await ensureDir();
111
- const targetPath = outboxPath(sessionId);
112
- const existing = await readOutbox(sessionId);
201
+ const targetPath = outboxPath(normalizedSessionId);
202
+ const existing = await readOutbox(normalizedSessionId);
113
203
  const idx = existing.findIndex((item) => item.id === event.id);
114
204
  if (idx >= 0) {
115
205
  existing[idx] = event;
@@ -117,11 +207,18 @@ export async function appendToOutbox(sessionId, event) {
117
207
  else {
118
208
  existing.push(event);
119
209
  }
120
- await writeFileAtomic(targetPath, JSON.stringify(existing, null, 2), 0o600);
210
+ await writeFileAtomic(targetPath, JSON.stringify(existing), 0o600);
121
211
  }
122
212
  export async function replaceOutbox(sessionId, events) {
213
+ let normalizedSessionId;
214
+ try {
215
+ normalizedSessionId = normalizeSessionId(sessionId);
216
+ }
217
+ catch {
218
+ return;
219
+ }
123
220
  await ensureDir();
124
- const targetPath = outboxPath(sessionId);
221
+ const targetPath = outboxPath(normalizedSessionId);
125
222
  if (events.length === 0) {
126
223
  try {
127
224
  await unlink(targetPath);
@@ -132,7 +229,7 @@ export async function replaceOutbox(sessionId, events) {
132
229
  return;
133
230
  }
134
231
  }
135
- await writeFileAtomic(targetPath, JSON.stringify(events, null, 2), 0o600);
232
+ await writeFileAtomic(targetPath, JSON.stringify(events), 0o600);
136
233
  }
137
234
  export async function readAllOutboxItems() {
138
235
  try {
@@ -209,8 +306,15 @@ export async function readOutboxSummary() {
209
306
  }
210
307
  }
211
308
  export async function clearOutbox(sessionId) {
309
+ let normalizedSessionId;
310
+ try {
311
+ normalizedSessionId = normalizeSessionId(sessionId);
312
+ }
313
+ catch {
314
+ return;
315
+ }
212
316
  try {
213
- await unlink(outboxPath(sessionId));
317
+ await unlink(outboxPath(normalizedSessionId));
214
318
  }
215
319
  catch {
216
320
  // File may not exist
package/dist/paths.js CHANGED
@@ -1,13 +1,32 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join, resolve } from "node:path";
3
3
  function normalizeDirOverride(value) {
4
- const trimmed = (value ?? "").trim();
4
+ let trimmed = (value ?? "").trim();
5
5
  if (!trimmed)
6
6
  return null;
7
- if (trimmed.includes("\0"))
7
+ // `.env` values are often quoted; normalize them before validation.
8
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
9
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
10
+ trimmed = trimmed.slice(1, -1).trim();
11
+ if (!trimmed)
12
+ return null;
13
+ }
14
+ // Reject control characters to avoid malformed or ambiguous filesystem paths.
15
+ if (/[\u0000-\u001f\u007f]/.test(trimmed))
16
+ return null;
17
+ // Reject common escaped/encoded null-byte sequences as well.
18
+ if (/\\0|\\x00|\\u0000|%00/i.test(trimmed))
8
19
  return null;
9
20
  return trimmed;
10
21
  }
22
+ function resolveOverridePath(override) {
23
+ if (override === "~")
24
+ return homedir();
25
+ if (override.startsWith("~/") || override.startsWith("~\\")) {
26
+ return resolve(homedir(), override.slice(2));
27
+ }
28
+ return resolve(override);
29
+ }
11
30
  /**
12
31
  * Root directory for persistent OrgX plugin files.
13
32
  *
@@ -17,7 +36,7 @@ function normalizeDirOverride(value) {
17
36
  export function getOrgxPluginConfigDir() {
18
37
  const override = normalizeDirOverride(process.env.ORGX_OPENCLAW_PLUGIN_CONFIG_DIR);
19
38
  if (override)
20
- return resolve(override);
39
+ return resolveOverridePath(override);
21
40
  return join(homedir(), ".config", "useorgx", "openclaw-plugin");
22
41
  }
23
42
  export function getOrgxPluginConfigPath(filename) {
@@ -32,7 +51,7 @@ export function getOrgxPluginConfigPath(filename) {
32
51
  export function getOpenClawDir() {
33
52
  const override = normalizeDirOverride(process.env.OPENCLAW_HOME);
34
53
  if (override)
35
- return resolve(override);
54
+ return resolveOverridePath(override);
36
55
  return join(homedir(), ".openclaw");
37
56
  }
38
57
  /**
@@ -44,6 +63,6 @@ export function getOpenClawDir() {
44
63
  export function getOrgxOutboxDir() {
45
64
  const override = normalizeDirOverride(process.env.ORGX_OUTBOX_DIR);
46
65
  if (override)
47
- return resolve(override);
66
+ return resolveOverridePath(override);
48
67
  return join(getOpenClawDir(), "orgx-outbox");
49
68
  }
@@ -18,3 +18,56 @@ export declare function computeWorkstreamRollup(taskStatuses?: unknown[]): TaskS
18
18
  status: WorkstreamRollupStatus;
19
19
  progressPct: number;
20
20
  };
21
+ export type TaskCompletionReadiness = "ready" | "needs_proof" | "needs_review";
22
+ export interface TaskCompletionReadinessResult {
23
+ ready: boolean;
24
+ status: TaskCompletionReadiness;
25
+ hasArtifact: boolean;
26
+ hasSchemaValidatedArtifact: boolean;
27
+ hasQualityScore: boolean;
28
+ qualityScore: number | null;
29
+ qualityThreshold: number;
30
+ missingItems: string[];
31
+ warnings: string[];
32
+ }
33
+ /**
34
+ * Evaluate whether a task has sufficient proof chain evidence to be marked
35
+ * complete. In phase 1 this is advisory (warn-only); callers decide whether
36
+ * to hard-block or soft-warn based on workspace config.
37
+ */
38
+ export declare function computeTaskCompletionReadiness(input: {
39
+ artifacts?: Array<{
40
+ schema_validated?: boolean;
41
+ atomic_unit_type?: string;
42
+ }>;
43
+ qualityScore?: number | null;
44
+ qualityThreshold?: number;
45
+ hasOutcomeEvent?: boolean;
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
+ export type EvalPassRateDriftResult = {
60
+ alert: boolean;
61
+ baselinePassRate: number;
62
+ rollingPassRate7d: number;
63
+ dropPct: number;
64
+ thresholdDropPct: number;
65
+ baselineSamples: number;
66
+ rollingSamples: number;
67
+ };
68
+ export declare function detectEvalPassRateDrift(input: {
69
+ passRates: unknown[];
70
+ thresholdDropPct?: number;
71
+ rollingWindowDays?: number;
72
+ baselineWindowDays?: number;
73
+ }): EvalPassRateDriftResult | null;
@@ -82,3 +82,151 @@ export function computeWorkstreamRollup(taskStatuses = []) {
82
82
  progressPct,
83
83
  };
84
84
  }
85
+ function normalizePassRate(value) {
86
+ const n = typeof value === "number" ? value : Number(value);
87
+ if (!Number.isFinite(n))
88
+ return null;
89
+ if (n < 0)
90
+ return 0;
91
+ if (n <= 1)
92
+ return n;
93
+ if (n <= 100)
94
+ return n / 100;
95
+ return 1;
96
+ }
97
+ function average(values) {
98
+ if (values.length <= 0)
99
+ return 0;
100
+ const total = values.reduce((sum, value) => sum + value, 0);
101
+ return total / values.length;
102
+ }
103
+ /**
104
+ * Evaluate whether a task has sufficient proof chain evidence to be marked
105
+ * complete. In phase 1 this is advisory (warn-only); callers decide whether
106
+ * to hard-block or soft-warn based on workspace config.
107
+ */
108
+ export function computeTaskCompletionReadiness(input) {
109
+ const artifacts = Array.isArray(input.artifacts) ? input.artifacts : [];
110
+ const qualityThreshold = typeof input.qualityThreshold === "number" && Number.isFinite(input.qualityThreshold)
111
+ ? input.qualityThreshold
112
+ : 4;
113
+ const qualityScore = typeof input.qualityScore === "number" && Number.isFinite(input.qualityScore)
114
+ ? input.qualityScore
115
+ : null;
116
+ const hasArtifact = artifacts.length > 0;
117
+ const hasSchemaValidatedArtifact = artifacts.some((a) => a.schema_validated === true && typeof a.atomic_unit_type === "string");
118
+ const hasQualityScore = qualityScore !== null;
119
+ const qualityMeetsThreshold = qualityScore !== null && qualityScore >= qualityThreshold;
120
+ const missingItems = [];
121
+ const warnings = [];
122
+ if (!hasArtifact) {
123
+ missingItems.push("No artifact registered for this task.");
124
+ }
125
+ else if (!hasSchemaValidatedArtifact) {
126
+ warnings.push("Artifact(s) present but none pass domain schema validation.");
127
+ }
128
+ if (!hasQualityScore) {
129
+ missingItems.push("No quality score recorded.");
130
+ }
131
+ else if (!qualityMeetsThreshold) {
132
+ missingItems.push(`Quality score ${qualityScore} below threshold ${qualityThreshold}.`);
133
+ }
134
+ if (input.hasOutcomeEvent === false) {
135
+ warnings.push("No outcome event recorded (L5 Impact not met).");
136
+ }
137
+ const ready = hasArtifact && hasQualityScore && qualityMeetsThreshold;
138
+ let status;
139
+ if (ready) {
140
+ status = "ready";
141
+ }
142
+ else if (hasArtifact && hasQualityScore) {
143
+ status = "needs_review";
144
+ }
145
+ else {
146
+ status = "needs_proof";
147
+ }
148
+ return {
149
+ ready,
150
+ status,
151
+ hasArtifact,
152
+ hasSchemaValidatedArtifact,
153
+ hasQualityScore,
154
+ qualityScore,
155
+ qualityThreshold,
156
+ missingItems,
157
+ warnings,
158
+ };
159
+ }
160
+ /**
161
+ * Derive an honest lifecycle state for an entity based on its raw status
162
+ * and child statuses. This is the authoritative backend derivation —
163
+ * the dashboard mirrors this logic in status-taxonomy.ts.
164
+ */
165
+ export function deriveLifecycleState(rawStatus, childStatuses) {
166
+ const s = classifyTaskState(rawStatus);
167
+ if (s === 'done')
168
+ return 'Completed';
169
+ if (s === 'blocked')
170
+ return 'Blocked';
171
+ if (!childStatuses || childStatuses.length === 0) {
172
+ if (s === 'active')
173
+ return 'In Progress';
174
+ return 'Queued';
175
+ }
176
+ const counts = summarizeTaskStatuses(childStatuses);
177
+ if (counts.total > 0 && counts.done >= counts.total)
178
+ return 'Completed';
179
+ if (counts.blocked > 0 && counts.active === 0 && counts.done < counts.total)
180
+ return 'Blocked';
181
+ if (counts.active > 0 || counts.done > 0)
182
+ return 'In Progress';
183
+ return 'Queued';
184
+ }
185
+ /**
186
+ * Compute cascaded progress: parent progress derived from child task states.
187
+ * Returns a percentage (0–100) representing weighted completion.
188
+ */
189
+ export function cascadeProgressFromChildren(childStatuses) {
190
+ if (!Array.isArray(childStatuses) || childStatuses.length === 0)
191
+ return 0;
192
+ const counts = summarizeTaskStatuses(childStatuses);
193
+ return toPercent(counts.done, counts.total);
194
+ }
195
+ export function detectEvalPassRateDrift(input) {
196
+ const thresholdDropPct = typeof input.thresholdDropPct === "number" && Number.isFinite(input.thresholdDropPct) && input.thresholdDropPct >= 0
197
+ ? input.thresholdDropPct
198
+ : 5;
199
+ const rollingWindowDays = typeof input.rollingWindowDays === "number" && Number.isFinite(input.rollingWindowDays) && input.rollingWindowDays > 0
200
+ ? Math.floor(input.rollingWindowDays)
201
+ : 7;
202
+ const baselineWindowDays = typeof input.baselineWindowDays === "number" &&
203
+ Number.isFinite(input.baselineWindowDays) &&
204
+ input.baselineWindowDays > 0
205
+ ? Math.floor(input.baselineWindowDays)
206
+ : rollingWindowDays;
207
+ const normalizedPassRates = (Array.isArray(input.passRates) ? input.passRates : [])
208
+ .map(normalizePassRate)
209
+ .filter((value) => value != null);
210
+ const requiredSamples = rollingWindowDays + baselineWindowDays;
211
+ if (normalizedPassRates.length < requiredSamples)
212
+ return null;
213
+ const rollingStart = normalizedPassRates.length - rollingWindowDays;
214
+ const baselineEnd = rollingStart;
215
+ const baselineStart = Math.max(0, baselineEnd - baselineWindowDays);
216
+ const rollingSlice = normalizedPassRates.slice(rollingStart);
217
+ const baselineSlice = normalizedPassRates.slice(baselineStart, baselineEnd);
218
+ if (rollingSlice.length <= 0 || baselineSlice.length <= 0)
219
+ return null;
220
+ const baselinePassRate = average(baselineSlice);
221
+ const rollingPassRate7d = average(rollingSlice);
222
+ const dropPct = Number(((baselinePassRate - rollingPassRate7d) * 100).toFixed(2));
223
+ return {
224
+ alert: dropPct > thresholdDropPct,
225
+ baselinePassRate: Number(baselinePassRate.toFixed(4)),
226
+ rollingPassRate7d: Number(rollingPassRate7d.toFixed(4)),
227
+ dropPct,
228
+ thresholdDropPct,
229
+ baselineSamples: baselineSlice.length,
230
+ rollingSamples: rollingSlice.length,
231
+ };
232
+ }
@@ -0,0 +1,45 @@
1
+ type RetroFollowUp = {
2
+ title: string;
3
+ priority?: "p0" | "p1" | "p2";
4
+ reason?: string;
5
+ };
6
+ export type OrgxAgentRetroDomain = "engineering" | "product" | "design" | "marketing" | "sales" | "operations" | "orchestration" | "general";
7
+ export declare function buildRetroTemplateForAgent(input: {
8
+ agentId: string | null | undefined;
9
+ success: boolean;
10
+ taskId: string | null | undefined;
11
+ runId: string;
12
+ errorMessage: string | null | undefined;
13
+ }): {
14
+ domain: OrgxAgentRetroDomain;
15
+ summary: string;
16
+ whatWentWell: string[];
17
+ whatWentWrong: string[];
18
+ decisions: string[];
19
+ followUps: RetroFollowUp[];
20
+ };
21
+ /**
22
+ * Generate a structured retro using an LLM, falling back to the heuristic
23
+ * template produced by {@link buildRetroTemplateForAgent}.
24
+ *
25
+ * Caller: src/index.ts (~line 971) — `buildRetroTemplateForAgent` is invoked
26
+ * inside the session-stopped handler. Migrate that call site to use this
27
+ * function when ready to enable LLM-powered retros.
28
+ */
29
+ export declare function buildRetroWithLlm(input: {
30
+ agentId: string | null | undefined;
31
+ success: boolean;
32
+ taskId: string | null | undefined;
33
+ runId: string;
34
+ errorMessage: string | null | undefined;
35
+ executionContext?: string | null;
36
+ }): Promise<{
37
+ domain: OrgxAgentRetroDomain;
38
+ summary: string;
39
+ whatWentWell: string[];
40
+ whatWentWrong: string[];
41
+ decisions: string[];
42
+ followUps: RetroFollowUp[];
43
+ source: "llm" | "heuristic";
44
+ }>;
45
+ export {};