@useorgx/openclaw-plugin 0.4.8 → 0.4.9

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 (125) hide show
  1. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  2. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  3. package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
  4. package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/activity-store.js +4 -18
  7. package/dist/agent-context-store.js +5 -25
  8. package/dist/agent-run-store.js +5 -25
  9. package/dist/agent-suite.js +1 -8
  10. package/dist/auth/flows.d.ts +47 -0
  11. package/dist/auth/flows.js +169 -0
  12. package/dist/auth-store.js +6 -26
  13. package/dist/byok-store.js +5 -19
  14. package/dist/cli/orgx.d.ts +66 -0
  15. package/dist/cli/orgx.js +91 -0
  16. package/dist/config/refresh.d.ts +32 -0
  17. package/dist/config/refresh.js +55 -0
  18. package/dist/config/resolution.d.ts +37 -0
  19. package/dist/config/resolution.js +178 -0
  20. package/dist/contracts/shared-types.d.ts +147 -0
  21. package/dist/contracts/shared-types.js +3 -0
  22. package/dist/contracts/types.d.ts +1 -134
  23. package/dist/contracts/types.js +5 -0
  24. package/dist/entities/auto-assignment.d.ts +36 -0
  25. package/dist/entities/auto-assignment.js +115 -0
  26. package/dist/entity-comment-store.js +5 -25
  27. package/dist/hash-utils.d.ts +2 -0
  28. package/dist/hash-utils.js +12 -0
  29. package/dist/http/helpers/activity-headline.d.ts +10 -0
  30. package/dist/http/helpers/activity-headline.js +192 -0
  31. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  32. package/dist/http/helpers/artifact-fallback.js +148 -0
  33. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  34. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  35. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  36. package/dist/http/helpers/autopilot-operations.js +403 -0
  37. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  38. package/dist/http/helpers/autopilot-runtime.js +319 -0
  39. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  40. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  41. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  42. package/dist/http/helpers/decision-mapper.js +44 -0
  43. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  44. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  45. package/dist/http/helpers/hash-utils.d.ts +1 -0
  46. package/dist/http/helpers/hash-utils.js +1 -0
  47. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  48. package/dist/http/helpers/kickoff-context.js +154 -0
  49. package/dist/http/helpers/mission-control.d.ts +94 -0
  50. package/dist/http/helpers/mission-control.js +894 -0
  51. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  52. package/dist/http/helpers/openclaw-cli.js +283 -0
  53. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  54. package/dist/http/helpers/runtime-sse.js +110 -0
  55. package/dist/http/helpers/value-utils.d.ts +6 -0
  56. package/dist/http/helpers/value-utils.js +67 -0
  57. package/dist/http/index.d.ts +88 -0
  58. package/dist/http/index.js +2353 -0
  59. package/dist/http/router.d.ts +23 -0
  60. package/dist/http/router.js +23 -0
  61. package/dist/http/routes/agent-control.d.ts +79 -0
  62. package/dist/http/routes/agent-control.js +684 -0
  63. package/dist/http/routes/agent-suite.d.ts +29 -0
  64. package/dist/http/routes/agent-suite.js +198 -0
  65. package/dist/http/routes/agents-catalog.d.ts +40 -0
  66. package/dist/http/routes/agents-catalog.js +83 -0
  67. package/dist/http/routes/billing.d.ts +23 -0
  68. package/dist/http/routes/billing.js +55 -0
  69. package/dist/http/routes/debug.d.ts +14 -0
  70. package/dist/http/routes/debug.js +21 -0
  71. package/dist/http/routes/decision-actions.d.ts +13 -0
  72. package/dist/http/routes/decision-actions.js +66 -0
  73. package/dist/http/routes/delegation.d.ts +19 -0
  74. package/dist/http/routes/delegation.js +32 -0
  75. package/dist/http/routes/entities.d.ts +47 -0
  76. package/dist/http/routes/entities.js +152 -0
  77. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  78. package/dist/http/routes/entity-dynamic.js +191 -0
  79. package/dist/http/routes/health.d.ts +22 -0
  80. package/dist/http/routes/health.js +49 -0
  81. package/dist/http/routes/live-legacy.d.ts +110 -0
  82. package/dist/http/routes/live-legacy.js +598 -0
  83. package/dist/http/routes/live-misc.d.ts +69 -0
  84. package/dist/http/routes/live-misc.js +206 -0
  85. package/dist/http/routes/live-snapshot.d.ts +90 -0
  86. package/dist/http/routes/live-snapshot.js +297 -0
  87. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  88. package/dist/http/routes/mission-control-actions.js +541 -0
  89. package/dist/http/routes/mission-control-read.d.ts +28 -0
  90. package/dist/http/routes/mission-control-read.js +67 -0
  91. package/dist/http/routes/onboarding.d.ts +34 -0
  92. package/dist/http/routes/onboarding.js +101 -0
  93. package/dist/http/routes/run-control.d.ts +24 -0
  94. package/dist/http/routes/run-control.js +86 -0
  95. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  96. package/dist/http/routes/runtime-hooks.js +437 -0
  97. package/dist/http/routes/settings-byok.d.ts +23 -0
  98. package/dist/http/routes/settings-byok.js +163 -0
  99. package/dist/http/routes/summary.d.ts +18 -0
  100. package/dist/http/routes/summary.js +42 -0
  101. package/dist/http/routes/work-artifacts.d.ts +9 -0
  102. package/dist/http/routes/work-artifacts.js +36 -0
  103. package/dist/http/shared-state.d.ts +16 -0
  104. package/dist/http/shared-state.js +1 -0
  105. package/dist/http-handler.d.ts +1 -88
  106. package/dist/http-handler.js +1 -10605
  107. package/dist/index.js +108 -2243
  108. package/dist/json-utils.d.ts +1 -0
  109. package/dist/json-utils.js +8 -0
  110. package/dist/next-up-queue-store.js +4 -18
  111. package/dist/runtime-instance-store.js +5 -31
  112. package/dist/services/background.d.ts +23 -0
  113. package/dist/services/background.js +23 -0
  114. package/dist/services/instrumentation.d.ts +29 -0
  115. package/dist/services/instrumentation.js +136 -0
  116. package/dist/snapshot-store.js +5 -25
  117. package/dist/stores/json-store.d.ts +11 -0
  118. package/dist/stores/json-store.js +42 -0
  119. package/dist/sync/outbox-replay.d.ts +55 -0
  120. package/dist/sync/outbox-replay.js +514 -0
  121. package/dist/tools/core-tools.d.ts +76 -0
  122. package/dist/tools/core-tools.js +1005 -0
  123. package/package.json +1 -1
  124. package/dashboard/dist/assets/BzkiMPmM.js +0 -215
  125. package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
@@ -0,0 +1,514 @@
1
+ import { registerArtifact } from "../artifacts/register-artifact.js";
2
+ import { readOutbox, readOutboxSummary, replaceOutbox } from "../outbox.js";
3
+ import { extractProgressOutboxMessage } from "../reporting/outbox-replay.js";
4
+ export function createOutboxReplayer(deps) {
5
+ const { client, logger, toErrorMessage, stableHash, resolveReportingContext, pickStringField, pickStringArrayField, toReportingPhase, parseRetroEntityType, isUuid, } = deps;
6
+ async function replayOutboxEvent(event) {
7
+ const payload = event.payload ?? {};
8
+ function normalizeRunFields(context) {
9
+ // We prefer correlation IDs for replay because many local adapters use UUID-like
10
+ // session IDs that do *not* exist as server-side run IDs.
11
+ if (context.correlationId) {
12
+ return { run_id: undefined, correlation_id: context.correlationId };
13
+ }
14
+ if (context.runId) {
15
+ return {
16
+ run_id: undefined,
17
+ correlation_id: `openclaw_run_${stableHash(context.runId).slice(0, 24)}`,
18
+ };
19
+ }
20
+ return { run_id: undefined, correlation_id: undefined };
21
+ }
22
+ if (event.type === "progress") {
23
+ const message = extractProgressOutboxMessage(payload);
24
+ if (!message) {
25
+ logger.warn?.("[orgx] Dropping invalid progress outbox event", {
26
+ eventId: event.id,
27
+ });
28
+ return;
29
+ }
30
+ const context = resolveReportingContext(payload);
31
+ if (!context.ok) {
32
+ throw new Error(context.error);
33
+ }
34
+ const rawPhase = pickStringField(payload, "phase") ?? "implementing";
35
+ const progressPct = typeof payload.progress_pct === "number"
36
+ ? payload.progress_pct
37
+ : typeof payload.progressPct === "number"
38
+ ? payload.progressPct
39
+ : undefined;
40
+ const phase = rawPhase === "intent" ||
41
+ rawPhase === "execution" ||
42
+ rawPhase === "blocked" ||
43
+ rawPhase === "review" ||
44
+ rawPhase === "handoff" ||
45
+ rawPhase === "completed"
46
+ ? rawPhase
47
+ : toReportingPhase(rawPhase, progressPct);
48
+ const metaRaw = payload.metadata;
49
+ const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
50
+ ? metaRaw
51
+ : {};
52
+ const baseMetadata = {
53
+ ...meta,
54
+ source: "orgx_openclaw_outbox_replay",
55
+ outbox_event_id: event.id,
56
+ };
57
+ let emitPayload = {
58
+ initiative_id: context.value.initiativeId,
59
+ run_id: context.value.runId,
60
+ correlation_id: context.value.correlationId,
61
+ source_client: context.value.sourceClient,
62
+ message,
63
+ phase,
64
+ progress_pct: progressPct,
65
+ level: pickStringField(payload, "level"),
66
+ next_step: pickStringField(payload, "next_step") ??
67
+ pickStringField(payload, "nextStep") ??
68
+ undefined,
69
+ metadata: baseMetadata,
70
+ };
71
+ // Locally-buffered progress events often store a local UUID in run_id. OrgX may reject
72
+ // unknown run IDs on replay; prefer a deterministic non-UUID correlation key instead.
73
+ if (emitPayload.run_id && !emitPayload.correlation_id) {
74
+ const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
75
+ emitPayload = {
76
+ ...emitPayload,
77
+ run_id: undefined,
78
+ correlation_id: replayCorrelationId,
79
+ metadata: {
80
+ ...(emitPayload.metadata ?? {}),
81
+ replay_run_id_as_correlation: true,
82
+ },
83
+ };
84
+ }
85
+ try {
86
+ await client.emitActivity(emitPayload);
87
+ }
88
+ catch (err) {
89
+ // Some locally-buffered events carry a UUID that *looks* like an OrgX run_id
90
+ // but was only ever used as a local correlation/grouping key. If OrgX
91
+ // doesn't recognize it, retry by treating it as correlation_id so OrgX can
92
+ // create/attach a run deterministically.
93
+ const msg = toErrorMessage(err);
94
+ if (emitPayload.run_id &&
95
+ /^404\\b/.test(msg) &&
96
+ /\\brun\\b/i.test(msg) &&
97
+ /not found/i.test(msg)) {
98
+ const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
99
+ await client.emitActivity({
100
+ ...emitPayload,
101
+ run_id: undefined,
102
+ correlation_id: replayCorrelationId,
103
+ metadata: {
104
+ ...(emitPayload.metadata ?? {}),
105
+ replay_run_id_as_correlation: true,
106
+ },
107
+ });
108
+ }
109
+ else {
110
+ throw err;
111
+ }
112
+ }
113
+ return;
114
+ }
115
+ if (event.type === "decision") {
116
+ const question = pickStringField(payload, "question");
117
+ if (!question) {
118
+ logger.warn?.("[orgx] Dropping invalid decision outbox event", {
119
+ eventId: event.id,
120
+ });
121
+ return;
122
+ }
123
+ const context = resolveReportingContext(payload);
124
+ if (!context.ok) {
125
+ throw new Error(context.error);
126
+ }
127
+ const runFields = normalizeRunFields({
128
+ runId: context.value.runId,
129
+ correlationId: context.value.correlationId,
130
+ });
131
+ // Payloads should include a stable idempotency_key when enqueued, but older
132
+ // events may not. Derive a deterministic fallback so outbox replay won't
133
+ // double-create the same remote decision.
134
+ const fallbackKey = stableHash(JSON.stringify({
135
+ t: "decision",
136
+ initiative_id: context.value.initiativeId,
137
+ run_id: context.value.runId ?? null,
138
+ correlation_id: context.value.correlationId ?? null,
139
+ question,
140
+ })).slice(0, 24);
141
+ const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
142
+ pickStringField(payload, "idempotencyKey") ??
143
+ `openclaw:decision:${fallbackKey}`;
144
+ await client.applyChangeset({
145
+ initiative_id: context.value.initiativeId,
146
+ run_id: runFields.run_id,
147
+ correlation_id: runFields.correlation_id,
148
+ source_client: context.value.sourceClient,
149
+ idempotency_key: resolvedIdempotencyKey,
150
+ operations: [
151
+ {
152
+ op: "decision.create",
153
+ title: question,
154
+ summary: pickStringField(payload, "context") ?? undefined,
155
+ urgency: pickStringField(payload, "urgency") ?? "medium",
156
+ options: pickStringArrayField(payload, "options"),
157
+ blocking: typeof payload.blocking === "boolean" ? payload.blocking : true,
158
+ },
159
+ ],
160
+ });
161
+ return;
162
+ }
163
+ if (event.type === "changeset") {
164
+ const context = resolveReportingContext(payload);
165
+ if (!context.ok) {
166
+ throw new Error(context.error);
167
+ }
168
+ const runFields = normalizeRunFields({
169
+ runId: context.value.runId,
170
+ correlationId: context.value.correlationId,
171
+ });
172
+ const operations = Array.isArray(payload.operations)
173
+ ? payload.operations
174
+ : [];
175
+ if (operations.length === 0) {
176
+ logger.warn?.("[orgx] Dropping invalid changeset outbox event", {
177
+ eventId: event.id,
178
+ });
179
+ return;
180
+ }
181
+ // Status updates are the most common offline replay payload, and `updateEntity`
182
+ // is the most widely supported primitive across OrgX deployments. Prefer it
183
+ // when the changeset contains only simple status mutations.
184
+ const statusOps = operations
185
+ .map((op) => {
186
+ if (!op || typeof op !== "object")
187
+ return null;
188
+ const record = op;
189
+ const kind = typeof record.op === "string" ? record.op.trim() : "";
190
+ if (kind === "task.update") {
191
+ const taskId = typeof record.task_id === "string" ? record.task_id.trim() : "";
192
+ const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
193
+ const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
194
+ const status = normalized === "completed" || normalized === "complete" || normalized === "finished"
195
+ ? "done"
196
+ : normalized === "inprogress"
197
+ ? "in_progress"
198
+ : normalized;
199
+ if (!taskId || !status)
200
+ return null;
201
+ return { type: "task", id: taskId, status };
202
+ }
203
+ if (kind === "milestone.update") {
204
+ const milestoneId = typeof record.milestone_id === "string" ? record.milestone_id.trim() : "";
205
+ const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
206
+ const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
207
+ const status = normalized === "done" || normalized === "complete" || normalized === "finished"
208
+ ? "completed"
209
+ : normalized === "inprogress"
210
+ ? "in_progress"
211
+ : normalized === "todo" || normalized === "not_started" || normalized === "pending"
212
+ ? "planned"
213
+ : normalized === "blocked" || normalized === "stuck"
214
+ ? "at_risk"
215
+ : normalized;
216
+ if (!milestoneId || !status)
217
+ return null;
218
+ return { type: "milestone", id: milestoneId, status };
219
+ }
220
+ return null;
221
+ })
222
+ .filter((item) => Boolean(item));
223
+ if (statusOps.length === operations.length) {
224
+ for (const op of statusOps) {
225
+ await client.updateEntity(op.type, op.id, { status: op.status });
226
+ }
227
+ return;
228
+ }
229
+ // Payloads should include a stable idempotency_key when enqueued, but older
230
+ // events may not. Derive a deterministic fallback so outbox replay won't
231
+ // double-create the same remote change.
232
+ const fallbackKey = stableHash(JSON.stringify({
233
+ t: "changeset",
234
+ initiative_id: context.value.initiativeId,
235
+ run_id: context.value.runId ?? null,
236
+ correlation_id: context.value.correlationId ?? null,
237
+ operations,
238
+ })).slice(0, 24);
239
+ const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
240
+ pickStringField(payload, "idempotencyKey") ??
241
+ `openclaw:changeset:${fallbackKey}`;
242
+ await client.applyChangeset({
243
+ initiative_id: context.value.initiativeId,
244
+ run_id: runFields.run_id,
245
+ correlation_id: runFields.correlation_id,
246
+ source_client: context.value.sourceClient,
247
+ idempotency_key: resolvedIdempotencyKey,
248
+ operations,
249
+ });
250
+ return;
251
+ }
252
+ if (event.type === "outcome") {
253
+ const context = resolveReportingContext(payload);
254
+ if (!context.ok) {
255
+ throw new Error(context.error);
256
+ }
257
+ const runFields = normalizeRunFields({
258
+ runId: context.value.runId,
259
+ correlationId: context.value.correlationId,
260
+ });
261
+ const executionId = pickStringField(payload, "execution_id") ??
262
+ pickStringField(payload, "executionId");
263
+ const executionType = pickStringField(payload, "execution_type") ??
264
+ pickStringField(payload, "executionType");
265
+ const agentId = pickStringField(payload, "agent_id") ??
266
+ pickStringField(payload, "agentId");
267
+ const success = typeof payload.success === "boolean"
268
+ ? payload.success
269
+ : null;
270
+ if (!executionId || !executionType || !agentId || success === null) {
271
+ logger.warn?.("[orgx] Dropping invalid outcome outbox event", {
272
+ eventId: event.id,
273
+ });
274
+ return;
275
+ }
276
+ const metaRaw = payload.metadata;
277
+ const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
278
+ ? metaRaw
279
+ : {};
280
+ await client.recordRunOutcome({
281
+ initiative_id: context.value.initiativeId,
282
+ run_id: runFields.run_id,
283
+ correlation_id: runFields.correlation_id,
284
+ source_client: context.value.sourceClient,
285
+ execution_id: executionId,
286
+ execution_type: executionType,
287
+ agent_id: agentId,
288
+ task_type: pickStringField(payload, "task_type") ??
289
+ pickStringField(payload, "taskType") ??
290
+ undefined,
291
+ domain: pickStringField(payload, "domain") ?? undefined,
292
+ started_at: pickStringField(payload, "started_at") ??
293
+ pickStringField(payload, "startedAt") ??
294
+ undefined,
295
+ completed_at: pickStringField(payload, "completed_at") ??
296
+ pickStringField(payload, "completedAt") ??
297
+ undefined,
298
+ inputs: payload.inputs && typeof payload.inputs === "object"
299
+ ? payload.inputs
300
+ : undefined,
301
+ outputs: payload.outputs && typeof payload.outputs === "object"
302
+ ? payload.outputs
303
+ : undefined,
304
+ steps: Array.isArray(payload.steps)
305
+ ? payload.steps
306
+ : undefined,
307
+ success,
308
+ quality_score: typeof payload.quality_score === "number"
309
+ ? payload.quality_score
310
+ : typeof payload.qualityScore === "number"
311
+ ? payload.qualityScore
312
+ : undefined,
313
+ duration_vs_estimate: typeof payload.duration_vs_estimate === "number"
314
+ ? payload.duration_vs_estimate
315
+ : typeof payload.durationVsEstimate === "number"
316
+ ? payload.durationVsEstimate
317
+ : undefined,
318
+ cost_vs_budget: typeof payload.cost_vs_budget === "number"
319
+ ? payload.cost_vs_budget
320
+ : typeof payload.costVsBudget === "number"
321
+ ? payload.costVsBudget
322
+ : undefined,
323
+ human_interventions: typeof payload.human_interventions === "number"
324
+ ? payload.human_interventions
325
+ : typeof payload.humanInterventions === "number"
326
+ ? payload.humanInterventions
327
+ : undefined,
328
+ user_satisfaction: typeof payload.user_satisfaction === "number"
329
+ ? payload.user_satisfaction
330
+ : typeof payload.userSatisfaction === "number"
331
+ ? payload.userSatisfaction
332
+ : undefined,
333
+ errors: Array.isArray(payload.errors)
334
+ ? payload.errors.filter((e) => typeof e === "string")
335
+ : undefined,
336
+ metadata: {
337
+ ...meta,
338
+ source: "orgx_openclaw_outbox_replay",
339
+ outbox_event_id: event.id,
340
+ },
341
+ });
342
+ return;
343
+ }
344
+ if (event.type === "retro") {
345
+ const context = resolveReportingContext(payload);
346
+ if (!context.ok) {
347
+ throw new Error(context.error);
348
+ }
349
+ const runFields = normalizeRunFields({
350
+ runId: context.value.runId,
351
+ correlationId: context.value.correlationId,
352
+ });
353
+ const retro = payload.retro && typeof payload.retro === "object" && !Array.isArray(payload.retro)
354
+ ? payload.retro
355
+ : null;
356
+ const summary = retro && typeof retro.summary === "string" ? retro.summary.trim() : "";
357
+ if (!retro || !summary) {
358
+ logger.warn?.("[orgx] Dropping invalid retro outbox event", {
359
+ eventId: event.id,
360
+ });
361
+ return;
362
+ }
363
+ const entityTypeRaw = pickStringField(payload, "entity_type") ??
364
+ pickStringField(payload, "entityType");
365
+ const parsedEntityType = parseRetroEntityType(entityTypeRaw) ?? null;
366
+ // Server-side enum parity can lag behind local clients. Only attach to the
367
+ // entity types that are guaranteed to exist today.
368
+ const entityType = parsedEntityType === "initiative" || parsedEntityType === "task"
369
+ ? parsedEntityType
370
+ : null;
371
+ const entityIdRaw = pickStringField(payload, "entity_id") ??
372
+ pickStringField(payload, "entityId") ??
373
+ null;
374
+ const entityId = isUuid(entityIdRaw ?? undefined) ? entityIdRaw : null;
375
+ await client.recordRunRetro({
376
+ initiative_id: context.value.initiativeId,
377
+ run_id: runFields.run_id,
378
+ correlation_id: runFields.correlation_id,
379
+ source_client: context.value.sourceClient,
380
+ entity_type: entityType && entityId ? entityType : undefined,
381
+ entity_id: entityType && entityId ? entityId : undefined,
382
+ title: pickStringField(payload, "title") ?? undefined,
383
+ idempotency_key: pickStringField(payload, "idempotency_key") ??
384
+ pickStringField(payload, "idempotencyKey") ??
385
+ undefined,
386
+ retro: retro,
387
+ markdown: pickStringField(payload, "markdown") ?? undefined,
388
+ });
389
+ return;
390
+ }
391
+ if (event.type === "artifact") {
392
+ // Artifacts are first-class UX loop closure (activity stream + entity modals).
393
+ // Try to persist upstream; if this fails, keep the event queued for retry.
394
+ const payload = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
395
+ ? event.payload
396
+ : {};
397
+ const name = pickStringField(payload, "name") ?? pickStringField(payload, "title") ?? "";
398
+ const artifactType = pickStringField(payload, "artifact_type") ?? "other";
399
+ const entityType = pickStringField(payload, "entity_type") ?? "";
400
+ const entityId = pickStringField(payload, "entity_id") ?? "";
401
+ const artifactId = pickStringField(payload, "artifact_id") ?? null;
402
+ const description = pickStringField(payload, "description") ?? undefined;
403
+ const externalUrl = pickStringField(payload, "url") ?? pickStringField(payload, "artifact_url") ?? null;
404
+ const content = pickStringField(payload, "content") ?? pickStringField(payload, "preview_markdown") ?? null;
405
+ const allowedEntityType = entityType === "initiative" ||
406
+ entityType === "milestone" ||
407
+ entityType === "task" ||
408
+ entityType === "decision" ||
409
+ entityType === "project"
410
+ ? entityType
411
+ : null;
412
+ if (!allowedEntityType || !entityId.trim() || !name.trim()) {
413
+ logger.warn?.("[orgx] Dropping invalid artifact outbox event", {
414
+ eventId: event.id,
415
+ entityType,
416
+ entityId,
417
+ });
418
+ return;
419
+ }
420
+ const result = await registerArtifact(client, client.getBaseUrl(), {
421
+ artifact_id: artifactId,
422
+ entity_type: allowedEntityType,
423
+ entity_id: entityId,
424
+ name: name.trim(),
425
+ artifact_type: artifactType.trim() || "other",
426
+ description,
427
+ external_url: externalUrl,
428
+ preview_markdown: content,
429
+ status: "draft",
430
+ metadata: {
431
+ source: "outbox_replay",
432
+ outbox_event_id: event.id,
433
+ ...(payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata)
434
+ ? payload.metadata
435
+ : {}),
436
+ },
437
+ validate_persistence: process.env.ORGX_VALIDATE_ARTIFACT_PERSISTENCE === "1",
438
+ });
439
+ if (!result.ok) {
440
+ throw new Error(result.persistence.last_error ?? "artifact registration failed");
441
+ }
442
+ return;
443
+ }
444
+ }
445
+ async function flushOutboxQueues() {
446
+ const attemptAt = new Date().toISOString();
447
+ deps.writeOutboxReplayState({
448
+ ...deps.readOutboxReplayState(),
449
+ status: "running",
450
+ lastReplayAttemptAt: attemptAt,
451
+ lastReplayError: null,
452
+ });
453
+ let hadReplayFailure = false;
454
+ let lastReplayError = null;
455
+ // Outbox files are keyed by *session id* (e.g. initiative/run correlation),
456
+ // not by event type.
457
+ const outboxSummary = await readOutboxSummary();
458
+ const queues = Object.entries(outboxSummary.pendingByQueue)
459
+ .filter(([, count]) => typeof count === "number" && count > 0)
460
+ .map(([queueId]) => queueId)
461
+ .sort();
462
+ for (const queue of queues) {
463
+ const pending = await readOutbox(queue);
464
+ if (pending.length === 0) {
465
+ continue;
466
+ }
467
+ const remaining = [];
468
+ for (const event of pending) {
469
+ try {
470
+ await replayOutboxEvent(event);
471
+ }
472
+ catch (err) {
473
+ hadReplayFailure = true;
474
+ lastReplayError = toErrorMessage(err);
475
+ remaining.push(event);
476
+ logger.warn?.("[orgx] Outbox replay failed", {
477
+ queue,
478
+ eventId: event.id,
479
+ error: lastReplayError,
480
+ });
481
+ }
482
+ }
483
+ await replaceOutbox(queue, remaining);
484
+ const replayedCount = pending.length - remaining.length;
485
+ if (replayedCount > 0) {
486
+ logger.info?.("[orgx] Replayed buffered outbox events", {
487
+ queue,
488
+ replayed: replayedCount,
489
+ remaining: remaining.length,
490
+ });
491
+ }
492
+ }
493
+ if (hadReplayFailure) {
494
+ deps.writeOutboxReplayState({
495
+ ...deps.readOutboxReplayState(),
496
+ status: "error",
497
+ lastReplayFailureAt: new Date().toISOString(),
498
+ lastReplayError,
499
+ });
500
+ }
501
+ else {
502
+ deps.writeOutboxReplayState({
503
+ ...deps.readOutboxReplayState(),
504
+ status: "success",
505
+ lastReplaySuccessAt: new Date().toISOString(),
506
+ lastReplayError: null,
507
+ });
508
+ }
509
+ }
510
+ return {
511
+ replayOutboxEvent,
512
+ flushOutboxQueues,
513
+ };
514
+ }
@@ -0,0 +1,76 @@
1
+ import type { OrgXClient } from "../api.js";
2
+ import type { AutoAssignedAgent } from "../entities/auto-assignment.js";
3
+ import type { RegisteredTool } from "../mcp-http-handler.js";
4
+ import type { OrgSnapshot, ReportingPhase, ReportingSourceClient } from "../types.js";
5
+ export interface ToolResult {
6
+ content: Array<{
7
+ type: "text";
8
+ text: string;
9
+ }>;
10
+ }
11
+ export type RegisterToolFn = (tool: {
12
+ name: string;
13
+ description: string;
14
+ parameters: Record<string, unknown>;
15
+ execute: (callId: string, params?: any) => Promise<ToolResult>;
16
+ }, options?: {
17
+ optional?: boolean;
18
+ }) => void;
19
+ export interface RegisterCoreToolsDeps {
20
+ registerTool: RegisterToolFn;
21
+ client: OrgXClient;
22
+ config: {
23
+ syncIntervalMs: number;
24
+ pluginVersion?: string | null;
25
+ };
26
+ getCachedSnapshot: () => OrgSnapshot | null;
27
+ getLastSnapshotAt: () => number;
28
+ doSync: () => Promise<void>;
29
+ text: (value: string) => ToolResult;
30
+ json: (label: string, data: unknown) => ToolResult;
31
+ formatSnapshot: (snapshot: OrgSnapshot) => string;
32
+ autoAssignEntityForCreate: (input: {
33
+ entityType: string;
34
+ entityId: string;
35
+ initiativeId: string | null;
36
+ title: string;
37
+ summary: string | null;
38
+ }) => Promise<{
39
+ updatedEntity?: unknown;
40
+ assignmentSource: "orchestrator" | "fallback" | "manual";
41
+ assignedAgents: AutoAssignedAgent[];
42
+ warnings: string[];
43
+ }>;
44
+ toReportingPhase: (phase: string, progressPct?: number) => ReportingPhase;
45
+ inferReportingInitiativeId: (input: Record<string, unknown>) => string | undefined;
46
+ isUuid: (value: string | undefined) => value is string;
47
+ pickNonEmptyString: (...values: Array<unknown>) => string | undefined;
48
+ resolveReportingContext: (input: Record<string, unknown>) => {
49
+ ok: true;
50
+ value: {
51
+ initiativeId: string;
52
+ runId?: string;
53
+ correlationId?: string;
54
+ sourceClient?: ReportingSourceClient;
55
+ };
56
+ } | {
57
+ ok: false;
58
+ error: string;
59
+ };
60
+ readSkillPackState: () => {
61
+ pack?: {
62
+ name?: string | null;
63
+ version?: string | null;
64
+ checksum?: string | null;
65
+ } | null;
66
+ overrides?: {
67
+ source?: string | null;
68
+ name?: string | null;
69
+ version?: string | null;
70
+ checksum?: string | null;
71
+ } | null;
72
+ etag?: string | null;
73
+ };
74
+ randomUUID?: () => string;
75
+ }
76
+ export declare function registerCoreTools(deps: RegisterCoreToolsDeps): Map<string, RegisteredTool>;