@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.
- package/dashboard/dist/assets/B5NEElEI.css +1 -0
- package/dashboard/dist/assets/BhapSNAs.js +215 -0
- package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
- package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/activity-store.js +4 -18
- package/dist/agent-context-store.js +5 -25
- package/dist/agent-run-store.js +5 -25
- package/dist/agent-suite.js +1 -8
- package/dist/auth/flows.d.ts +47 -0
- package/dist/auth/flows.js +169 -0
- package/dist/auth-store.js +6 -26
- package/dist/byok-store.js +5 -19
- package/dist/cli/orgx.d.ts +66 -0
- package/dist/cli/orgx.js +91 -0
- package/dist/config/refresh.d.ts +32 -0
- package/dist/config/refresh.js +55 -0
- package/dist/config/resolution.d.ts +37 -0
- package/dist/config/resolution.js +178 -0
- package/dist/contracts/shared-types.d.ts +147 -0
- package/dist/contracts/shared-types.js +3 -0
- package/dist/contracts/types.d.ts +1 -134
- package/dist/contracts/types.js +5 -0
- package/dist/entities/auto-assignment.d.ts +36 -0
- package/dist/entities/auto-assignment.js +115 -0
- package/dist/entity-comment-store.js +5 -25
- package/dist/hash-utils.d.ts +2 -0
- package/dist/hash-utils.js +12 -0
- package/dist/http/helpers/activity-headline.d.ts +10 -0
- package/dist/http/helpers/activity-headline.js +192 -0
- package/dist/http/helpers/artifact-fallback.d.ts +13 -0
- package/dist/http/helpers/artifact-fallback.js +148 -0
- package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
- package/dist/http/helpers/auto-continue-engine.js +1218 -0
- package/dist/http/helpers/autopilot-operations.d.ts +157 -0
- package/dist/http/helpers/autopilot-operations.js +403 -0
- package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
- package/dist/http/helpers/autopilot-runtime.js +319 -0
- package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
- package/dist/http/helpers/autopilot-slice-utils.js +476 -0
- package/dist/http/helpers/decision-mapper.d.ts +12 -0
- package/dist/http/helpers/decision-mapper.js +44 -0
- package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
- package/dist/http/helpers/dispatch-lifecycle.js +604 -0
- package/dist/http/helpers/hash-utils.d.ts +1 -0
- package/dist/http/helpers/hash-utils.js +1 -0
- package/dist/http/helpers/kickoff-context.d.ts +12 -0
- package/dist/http/helpers/kickoff-context.js +154 -0
- package/dist/http/helpers/mission-control.d.ts +94 -0
- package/dist/http/helpers/mission-control.js +894 -0
- package/dist/http/helpers/openclaw-cli.d.ts +37 -0
- package/dist/http/helpers/openclaw-cli.js +283 -0
- package/dist/http/helpers/runtime-sse.d.ts +20 -0
- package/dist/http/helpers/runtime-sse.js +110 -0
- package/dist/http/helpers/value-utils.d.ts +6 -0
- package/dist/http/helpers/value-utils.js +67 -0
- package/dist/http/index.d.ts +88 -0
- package/dist/http/index.js +2353 -0
- package/dist/http/router.d.ts +23 -0
- package/dist/http/router.js +23 -0
- package/dist/http/routes/agent-control.d.ts +79 -0
- package/dist/http/routes/agent-control.js +684 -0
- package/dist/http/routes/agent-suite.d.ts +29 -0
- package/dist/http/routes/agent-suite.js +198 -0
- package/dist/http/routes/agents-catalog.d.ts +40 -0
- package/dist/http/routes/agents-catalog.js +83 -0
- package/dist/http/routes/billing.d.ts +23 -0
- package/dist/http/routes/billing.js +55 -0
- package/dist/http/routes/debug.d.ts +14 -0
- package/dist/http/routes/debug.js +21 -0
- package/dist/http/routes/decision-actions.d.ts +13 -0
- package/dist/http/routes/decision-actions.js +66 -0
- package/dist/http/routes/delegation.d.ts +19 -0
- package/dist/http/routes/delegation.js +32 -0
- package/dist/http/routes/entities.d.ts +47 -0
- package/dist/http/routes/entities.js +152 -0
- package/dist/http/routes/entity-dynamic.d.ts +25 -0
- package/dist/http/routes/entity-dynamic.js +191 -0
- package/dist/http/routes/health.d.ts +22 -0
- package/dist/http/routes/health.js +49 -0
- package/dist/http/routes/live-legacy.d.ts +110 -0
- package/dist/http/routes/live-legacy.js +598 -0
- package/dist/http/routes/live-misc.d.ts +69 -0
- package/dist/http/routes/live-misc.js +206 -0
- package/dist/http/routes/live-snapshot.d.ts +90 -0
- package/dist/http/routes/live-snapshot.js +297 -0
- package/dist/http/routes/mission-control-actions.d.ts +83 -0
- package/dist/http/routes/mission-control-actions.js +541 -0
- package/dist/http/routes/mission-control-read.d.ts +28 -0
- package/dist/http/routes/mission-control-read.js +67 -0
- package/dist/http/routes/onboarding.d.ts +34 -0
- package/dist/http/routes/onboarding.js +101 -0
- package/dist/http/routes/run-control.d.ts +24 -0
- package/dist/http/routes/run-control.js +86 -0
- package/dist/http/routes/runtime-hooks.d.ts +69 -0
- package/dist/http/routes/runtime-hooks.js +437 -0
- package/dist/http/routes/settings-byok.d.ts +23 -0
- package/dist/http/routes/settings-byok.js +163 -0
- package/dist/http/routes/summary.d.ts +18 -0
- package/dist/http/routes/summary.js +42 -0
- package/dist/http/routes/work-artifacts.d.ts +9 -0
- package/dist/http/routes/work-artifacts.js +36 -0
- package/dist/http/shared-state.d.ts +16 -0
- package/dist/http/shared-state.js +1 -0
- package/dist/http-handler.d.ts +1 -88
- package/dist/http-handler.js +1 -10605
- package/dist/index.js +108 -2243
- package/dist/json-utils.d.ts +1 -0
- package/dist/json-utils.js +8 -0
- package/dist/next-up-queue-store.js +4 -18
- package/dist/runtime-instance-store.js +5 -31
- package/dist/services/background.d.ts +23 -0
- package/dist/services/background.js +23 -0
- package/dist/services/instrumentation.d.ts +29 -0
- package/dist/services/instrumentation.js +136 -0
- package/dist/snapshot-store.js +5 -25
- package/dist/stores/json-store.d.ts +11 -0
- package/dist/stores/json-store.js +42 -0
- package/dist/sync/outbox-replay.d.ts +55 -0
- package/dist/sync/outbox-replay.js +514 -0
- package/dist/tools/core-tools.d.ts +76 -0
- package/dist/tools/core-tools.js +1005 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/BzkiMPmM.js +0 -215
- 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>;
|