@useorgx/openclaw-plugin 0.7.18 → 0.7.23
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/9gFmK3Kr.js +1 -0
- package/dashboard/dist/assets/9gFmK3Kr.js.br +0 -0
- package/dashboard/dist/assets/9gFmK3Kr.js.gz +0 -0
- package/dashboard/dist/assets/{DS79hzMu.js → BrMXbzQ-.js} +2 -2
- package/dashboard/dist/assets/BrMXbzQ-.js.br +0 -0
- package/dashboard/dist/assets/BrMXbzQ-.js.gz +0 -0
- package/dashboard/dist/assets/By0MIBj_.js +1 -0
- package/dashboard/dist/assets/By0MIBj_.js.br +0 -0
- package/dashboard/dist/assets/By0MIBj_.js.gz +0 -0
- package/dashboard/dist/assets/C1u2SGin.css +1 -0
- package/dashboard/dist/assets/C1u2SGin.css.br +0 -0
- package/dashboard/dist/assets/C1u2SGin.css.gz +0 -0
- package/dashboard/dist/assets/{467jKHFJ.js → CGJiHCIx.js} +1 -1
- package/dashboard/dist/assets/CGJiHCIx.js.br +0 -0
- package/dashboard/dist/assets/CGJiHCIx.js.gz +0 -0
- package/dashboard/dist/assets/CSd4rSuU.js +212 -0
- package/dashboard/dist/assets/CSd4rSuU.js.br +0 -0
- package/dashboard/dist/assets/CSd4rSuU.js.gz +0 -0
- package/dashboard/dist/assets/{5Ihga-4X.js → CZXS5i_5.js} +1 -1
- package/dashboard/dist/assets/CZXS5i_5.js.br +0 -0
- package/dashboard/dist/assets/CZXS5i_5.js.gz +0 -0
- package/dashboard/dist/assets/{a6qcPiWt.js → CbVWL74-.js} +1 -1
- package/dashboard/dist/assets/CbVWL74-.js.br +0 -0
- package/dashboard/dist/assets/CbVWL74-.js.gz +0 -0
- package/dashboard/dist/assets/{qDJ6rqcs.js → D-FuHfT8.js} +1 -1
- package/dashboard/dist/assets/D-FuHfT8.js.br +0 -0
- package/dashboard/dist/assets/D-FuHfT8.js.gz +0 -0
- package/dashboard/dist/assets/{BcJmNILk.js → D0PN5_vY.js} +1 -1
- package/dashboard/dist/assets/D0PN5_vY.js.br +0 -0
- package/dashboard/dist/assets/D0PN5_vY.js.gz +0 -0
- package/dashboard/dist/assets/DDCPrZRt.js +1 -0
- package/dashboard/dist/assets/DDCPrZRt.js.br +0 -0
- package/dashboard/dist/assets/DDCPrZRt.js.gz +0 -0
- package/dashboard/dist/assets/{B71dt9yu.js → DNQ-iFO2.js} +1 -1
- package/dashboard/dist/assets/DNQ-iFO2.js.br +0 -0
- package/dashboard/dist/assets/DNQ-iFO2.js.gz +0 -0
- package/dashboard/dist/assets/{PVi0vr9a.js → DhPuHPK7.js} +1 -1
- package/dashboard/dist/assets/DhPuHPK7.js.br +0 -0
- package/dashboard/dist/assets/DhPuHPK7.js.gz +0 -0
- package/dashboard/dist/assets/Dhz7qPtn.js +1 -0
- package/dashboard/dist/assets/Dhz7qPtn.js.br +0 -0
- package/dashboard/dist/assets/Dhz7qPtn.js.gz +0 -0
- package/dashboard/dist/assets/LOFrVoPD.js +1 -0
- package/dashboard/dist/assets/LOFrVoPD.js.br +0 -0
- package/dashboard/dist/assets/LOFrVoPD.js.gz +0 -0
- package/dashboard/dist/assets/OlLPtzdz.js +1 -0
- package/dashboard/dist/assets/OlLPtzdz.js.br +0 -0
- package/dashboard/dist/assets/OlLPtzdz.js.gz +0 -0
- package/dashboard/dist/assets/{sdoPH_Z1.js → RN4M9u9W.js} +2 -2
- package/dashboard/dist/assets/RN4M9u9W.js.br +0 -0
- package/dashboard/dist/assets/RN4M9u9W.js.gz +0 -0
- package/dashboard/dist/assets/VCHu272d.js +1 -0
- package/dashboard/dist/assets/VCHu272d.js.br +0 -0
- package/dashboard/dist/assets/VCHu272d.js.gz +0 -0
- package/dashboard/dist/assets/m2smti3F.js +1 -0
- package/dashboard/dist/assets/m2smti3F.js.br +0 -0
- package/dashboard/dist/assets/m2smti3F.js.gz +0 -0
- package/dashboard/dist/assets/{C3_j_W9V.js → nra1yvJX.js} +1 -1
- package/dashboard/dist/assets/nra1yvJX.js.br +0 -0
- package/dashboard/dist/assets/nra1yvJX.js.gz +0 -0
- package/dashboard/dist/assets/qLX6NZ-J.js +1 -0
- package/dashboard/dist/assets/qLX6NZ-J.js.br +0 -0
- package/dashboard/dist/assets/qLX6NZ-J.js.gz +0 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/agent-run-store.js +162 -24
- package/dist/cli/orgx.d.ts +3 -0
- package/dist/config/resolution.d.ts +7 -0
- package/dist/config/resolution.js +13 -5
- package/dist/contracts/onboarding-state.d.ts +2 -0
- package/dist/contracts/onboarding-state.js +23 -0
- package/dist/contracts/shared-types.d.ts +17 -0
- package/dist/http/helpers/auto-continue-engine.d.ts +62 -0
- package/dist/http/helpers/auto-continue-engine.js +329 -53
- package/dist/http/helpers/autopilot-runtime.js +5 -1
- package/dist/http/helpers/autopilot-slice-utils.js +25 -1
- package/dist/http/helpers/decision-mapper.d.ts +1 -0
- package/dist/http/helpers/decision-mapper.js +19 -2
- package/dist/http/helpers/dispatch-lifecycle.js +3 -0
- package/dist/http/helpers/mission-control.d.ts +1 -0
- package/dist/http/helpers/mission-control.js +5 -2
- package/dist/http/helpers/slice-run-projections.d.ts +27 -0
- package/dist/http/helpers/slice-run-projections.js +198 -10
- package/dist/http/helpers/triage-mapper.js +220 -6
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +94 -46
- package/dist/http/router.js +64 -9
- package/dist/http/routes/live-legacy.d.ts +19 -2
- package/dist/http/routes/live-legacy.js +110 -27
- package/dist/http/routes/live-snapshot.d.ts +16 -2
- package/dist/http/routes/live-snapshot.js +169 -25
- package/dist/http/routes/mission-control-actions.js +28 -0
- package/dist/http/routes/mission-control-read.d.ts +18 -0
- package/dist/http/routes/mission-control-read.js +130 -218
- package/dist/http/routes/onboarding.d.ts +1 -0
- package/dist/http/routes/onboarding.js +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +199 -123
- package/dist/outbox.d.ts +0 -2
- package/dist/outbox.js +268 -150
- package/dist/reporting/rollups.js +18 -11
- package/dist/runtime-instance-store.js +212 -58
- package/dist/stores/materialized-snapshot-store.d.ts +18 -0
- package/dist/stores/materialized-snapshot-store.js +91 -0
- package/dist/stores/sqlite-state.d.ts +6 -0
- package/dist/stores/sqlite-state.js +179 -0
- package/package.json +6 -1
- package/dashboard/dist/assets/467jKHFJ.js.br +0 -0
- package/dashboard/dist/assets/467jKHFJ.js.gz +0 -0
- package/dashboard/dist/assets/5Ihga-4X.js.br +0 -0
- package/dashboard/dist/assets/5Ihga-4X.js.gz +0 -0
- package/dashboard/dist/assets/B71dt9yu.js.br +0 -0
- package/dashboard/dist/assets/B71dt9yu.js.gz +0 -0
- package/dashboard/dist/assets/BCudUvwg.js +0 -1
- package/dashboard/dist/assets/BCudUvwg.js.br +0 -0
- package/dashboard/dist/assets/BCudUvwg.js.gz +0 -0
- package/dashboard/dist/assets/BEnI6kNR.js +0 -1
- package/dashboard/dist/assets/BEnI6kNR.js.br +0 -0
- package/dashboard/dist/assets/BEnI6kNR.js.gz +0 -0
- package/dashboard/dist/assets/BcJmNILk.js.br +0 -0
- package/dashboard/dist/assets/BcJmNILk.js.gz +0 -0
- package/dashboard/dist/assets/C-MOJWHs.js +0 -1
- package/dashboard/dist/assets/C-MOJWHs.js.br +0 -0
- package/dashboard/dist/assets/C-MOJWHs.js.gz +0 -0
- package/dashboard/dist/assets/C-XuWXGi.js +0 -1
- package/dashboard/dist/assets/C-XuWXGi.js.br +0 -0
- package/dashboard/dist/assets/C-XuWXGi.js.gz +0 -0
- package/dashboard/dist/assets/C3_j_W9V.js.br +0 -0
- package/dashboard/dist/assets/C3_j_W9V.js.gz +0 -0
- package/dashboard/dist/assets/C9-UYhBb.js +0 -1
- package/dashboard/dist/assets/C9-UYhBb.js.br +0 -0
- package/dashboard/dist/assets/C9-UYhBb.js.gz +0 -0
- package/dashboard/dist/assets/C9yV06GS.js +0 -1
- package/dashboard/dist/assets/C9yV06GS.js.br +0 -0
- package/dashboard/dist/assets/C9yV06GS.js.gz +0 -0
- package/dashboard/dist/assets/CReugbyT.js +0 -1
- package/dashboard/dist/assets/CReugbyT.js.br +0 -0
- package/dashboard/dist/assets/CReugbyT.js.gz +0 -0
- package/dashboard/dist/assets/CSDhTbKy.js +0 -1
- package/dashboard/dist/assets/CSDhTbKy.js.br +0 -0
- package/dashboard/dist/assets/CSDhTbKy.js.gz +0 -0
- package/dashboard/dist/assets/CfMS9yIf.js +0 -1
- package/dashboard/dist/assets/CfMS9yIf.js.br +0 -0
- package/dashboard/dist/assets/CfMS9yIf.js.gz +0 -0
- package/dashboard/dist/assets/D2Kqcmv9.js +0 -212
- package/dashboard/dist/assets/D2Kqcmv9.js.br +0 -0
- package/dashboard/dist/assets/D2Kqcmv9.js.gz +0 -0
- package/dashboard/dist/assets/DS79hzMu.js.br +0 -0
- package/dashboard/dist/assets/DS79hzMu.js.gz +0 -0
- package/dashboard/dist/assets/PVi0vr9a.js.br +0 -0
- package/dashboard/dist/assets/PVi0vr9a.js.gz +0 -0
- package/dashboard/dist/assets/RZkbqlJk.css +0 -1
- package/dashboard/dist/assets/RZkbqlJk.css.br +0 -0
- package/dashboard/dist/assets/RZkbqlJk.css.gz +0 -0
- package/dashboard/dist/assets/a6qcPiWt.js.br +0 -0
- package/dashboard/dist/assets/a6qcPiWt.js.gz +0 -0
- package/dashboard/dist/assets/qDJ6rqcs.js.br +0 -0
- package/dashboard/dist/assets/qDJ6rqcs.js.gz +0 -0
- package/dashboard/dist/assets/sdoPH_Z1.js.br +0 -0
- package/dashboard/dist/assets/sdoPH_Z1.js.gz +0 -0
package/dist/outbox.js
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Events are flushed on next successful sync.
|
|
5
5
|
*/
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { readFile,
|
|
7
|
+
import { readFile, mkdir, chmod, rename, unlink, readdir, appendFile, } from "node:fs/promises";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { classifyOutboxReplaySkip } from "./event-sanitization.js";
|
|
10
10
|
import { getOrgxOutboxDir } from "./paths.js";
|
|
11
|
+
import { getStateDb, readStateMeta, writeStateMeta, } from "./stores/sqlite-state.js";
|
|
11
12
|
function outboxDir() {
|
|
12
13
|
return getOrgxOutboxDir();
|
|
13
14
|
}
|
|
@@ -42,6 +43,7 @@ const OUTBOX_EVENT_TTL_MS = 7 * 24 * 60 * 60_000;
|
|
|
42
43
|
/** Hard cap per session to prevent unbounded growth if sync repeatedly fails. */
|
|
43
44
|
const OUTBOX_MAX_EVENTS_PER_SESSION = 500;
|
|
44
45
|
const DEAD_LETTER_DIRNAME = "_dead-letter";
|
|
46
|
+
const OUTBOX_IMPORT_META_KEY = "outbox_events_imported_v1";
|
|
45
47
|
async function ensureDir() {
|
|
46
48
|
const dir = outboxDir();
|
|
47
49
|
try {
|
|
@@ -62,7 +64,6 @@ function outboxPath(sessionId) {
|
|
|
62
64
|
return join(outboxDir(), `${normalizeSessionId(sessionId)}.json`);
|
|
63
65
|
}
|
|
64
66
|
async function backupCorruptOutboxFile(targetPath) {
|
|
65
|
-
// Preserve the corrupted file for debugging, but move it out of the hot path.
|
|
66
67
|
const suffix = `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
67
68
|
const backupPath = `${targetPath}.corrupt.${suffix}`;
|
|
68
69
|
try {
|
|
@@ -73,34 +74,6 @@ async function backupCorruptOutboxFile(targetPath) {
|
|
|
73
74
|
// best effort
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
async function writeFileAtomic(targetPath, content, mode) {
|
|
77
|
-
// Atomic write to avoid partial JSON files if we crash mid-write.
|
|
78
|
-
const tmpPath = `${targetPath}.tmp.${process.pid}.${randomUUID().slice(0, 8)}`;
|
|
79
|
-
await writeFile(tmpPath, content, { encoding: "utf8", mode });
|
|
80
|
-
await hardenPath(tmpPath, mode);
|
|
81
|
-
try {
|
|
82
|
-
await rename(tmpPath, targetPath);
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
// On Windows, rename can fail if the destination exists. Best-effort fallback:
|
|
86
|
-
// remove the destination and retry. This is not strictly atomic, but avoids
|
|
87
|
-
// leaving the outbox unreadable.
|
|
88
|
-
const code = err && typeof err === "object" ? err.code : null;
|
|
89
|
-
if (code === "EEXIST" || code === "EPERM" || code === "EACCES") {
|
|
90
|
-
try {
|
|
91
|
-
await unlink(targetPath);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// ignore
|
|
95
|
-
}
|
|
96
|
-
await rename(tmpPath, targetPath);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
throw err;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
await hardenPath(targetPath, mode);
|
|
103
|
-
}
|
|
104
77
|
async function appendOutboxDeadLetterRecord(sessionId, record) {
|
|
105
78
|
await ensureDir();
|
|
106
79
|
const dir = deadLetterDir();
|
|
@@ -114,15 +87,43 @@ async function appendOutboxDeadLetterRecord(sessionId, record) {
|
|
|
114
87
|
await hardenPath(targetPath, 0o600);
|
|
115
88
|
}
|
|
116
89
|
export async function appendOutboxDeadLetter(sessionId, event, reason, error) {
|
|
90
|
+
let normalizedSessionId;
|
|
91
|
+
try {
|
|
92
|
+
normalizedSessionId = normalizeSessionId(sessionId);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
117
97
|
const droppedAt = new Date().toISOString();
|
|
118
|
-
await appendOutboxDeadLetterRecord(
|
|
98
|
+
await appendOutboxDeadLetterRecord(normalizedSessionId, {
|
|
119
99
|
droppedAt,
|
|
120
|
-
queueId:
|
|
100
|
+
queueId: normalizedSessionId,
|
|
121
101
|
reason,
|
|
122
102
|
error: error ?? null,
|
|
123
103
|
event,
|
|
124
104
|
});
|
|
125
105
|
}
|
|
106
|
+
function normalizeOutboxEvent(input) {
|
|
107
|
+
const timestamp = typeof input.timestamp === "string" && Number.isFinite(Date.parse(input.timestamp))
|
|
108
|
+
? new Date(Date.parse(input.timestamp)).toISOString()
|
|
109
|
+
: new Date().toISOString();
|
|
110
|
+
return {
|
|
111
|
+
id: input.id.trim(),
|
|
112
|
+
type: input.type,
|
|
113
|
+
timestamp,
|
|
114
|
+
payload: input.payload && typeof input.payload === "object" && !Array.isArray(input.payload)
|
|
115
|
+
? input.payload
|
|
116
|
+
: {},
|
|
117
|
+
activityItem: input.activityItem,
|
|
118
|
+
replayFailures: typeof input.replayFailures === "number" && Number.isFinite(input.replayFailures)
|
|
119
|
+
? Math.max(0, Math.floor(input.replayFailures))
|
|
120
|
+
: undefined,
|
|
121
|
+
lastReplayError: typeof input.lastReplayError === "string" ? input.lastReplayError : input.lastReplayError ?? null,
|
|
122
|
+
lastReplayAt: typeof input.lastReplayAt === "string" && Number.isFinite(Date.parse(input.lastReplayAt))
|
|
123
|
+
? new Date(Date.parse(input.lastReplayAt)).toISOString()
|
|
124
|
+
: input.lastReplayAt ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
126
127
|
/** Drop stale events and enforce per-session cap. Returns true if any were removed. */
|
|
127
128
|
function pruneOutboxEvents(events) {
|
|
128
129
|
const cutoff = Date.now() - OUTBOX_EVENT_TTL_MS;
|
|
@@ -130,50 +131,67 @@ function pruneOutboxEvents(events) {
|
|
|
130
131
|
const epoch = Date.parse(e.timestamp);
|
|
131
132
|
return Number.isFinite(epoch) && epoch >= cutoff;
|
|
132
133
|
});
|
|
133
|
-
// Keep newest events if over cap.
|
|
134
134
|
const capped = fresh.length > OUTBOX_MAX_EVENTS_PER_SESSION
|
|
135
135
|
? fresh.slice(fresh.length - OUTBOX_MAX_EVENTS_PER_SESSION)
|
|
136
136
|
: fresh;
|
|
137
137
|
return { pruned: capped, changed: capped.length !== events.length };
|
|
138
138
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
function rowToEvent(row) {
|
|
140
|
+
const payload = row.payload_json && typeof row.payload_json === "string"
|
|
141
|
+
? JSON.parse(row.payload_json)
|
|
142
|
+
: {};
|
|
143
|
+
const activityItem = row.activity_item_json && typeof row.activity_item_json === "string"
|
|
144
|
+
? JSON.parse(row.activity_item_json)
|
|
145
|
+
: null;
|
|
146
|
+
if (!activityItem || typeof activityItem !== "object")
|
|
147
|
+
return null;
|
|
148
|
+
return normalizeOutboxEvent({
|
|
149
|
+
id: row.event_id,
|
|
150
|
+
type: row.event_type,
|
|
151
|
+
timestamp: row.timestamp,
|
|
152
|
+
payload: payload && typeof payload === "object" && !Array.isArray(payload)
|
|
153
|
+
? payload
|
|
154
|
+
: {},
|
|
155
|
+
activityItem: activityItem,
|
|
156
|
+
replayFailures: typeof row.replay_failures === "number" ? row.replay_failures : undefined,
|
|
157
|
+
lastReplayError: row.last_replay_error,
|
|
158
|
+
lastReplayAt: row.last_replay_at,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function writeOutboxEvents(queueId, events) {
|
|
162
|
+
const normalizedQueueId = normalizeSessionId(queueId);
|
|
163
|
+
const transaction = getStateDb().transaction((items) => {
|
|
164
|
+
getStateDb()
|
|
165
|
+
.prepare("DELETE FROM outbox_events WHERE queue_id = ?")
|
|
166
|
+
.run(normalizedQueueId);
|
|
167
|
+
const insertStatement = getStateDb().prepare(`INSERT INTO outbox_events (
|
|
168
|
+
event_id,
|
|
169
|
+
queue_id,
|
|
170
|
+
event_type,
|
|
171
|
+
timestamp,
|
|
172
|
+
payload_json,
|
|
173
|
+
activity_item_json,
|
|
174
|
+
replay_failures,
|
|
175
|
+
last_replay_error,
|
|
176
|
+
last_replay_at,
|
|
177
|
+
updated_at
|
|
178
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
179
|
+
const nowIso = new Date().toISOString();
|
|
180
|
+
for (const event of items) {
|
|
181
|
+
const normalized = normalizeOutboxEvent(event);
|
|
182
|
+
insertStatement.run(normalized.id, normalizedQueueId, normalized.type, normalized.timestamp, JSON.stringify(normalized.payload ?? {}), JSON.stringify(normalized.activityItem), normalized.replayFailures ?? 0, normalized.lastReplayError ?? null, normalized.lastReplayAt ?? null, nowIso);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
transaction(events);
|
|
186
|
+
}
|
|
187
|
+
async function readLegacyOutboxFile(queueId) {
|
|
188
|
+
const targetPath = outboxPath(queueId);
|
|
147
189
|
try {
|
|
148
190
|
const raw = await readFile(targetPath, "utf8");
|
|
149
191
|
try {
|
|
150
192
|
const parsed = JSON.parse(raw);
|
|
151
193
|
const events = Array.isArray(parsed) ? parsed : [];
|
|
152
|
-
|
|
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;
|
|
194
|
+
return events.map((event) => normalizeOutboxEvent(event));
|
|
177
195
|
}
|
|
178
196
|
catch {
|
|
179
197
|
await backupCorruptOutboxFile(targetPath);
|
|
@@ -184,6 +202,103 @@ export async function readOutbox(sessionId) {
|
|
|
184
202
|
return [];
|
|
185
203
|
}
|
|
186
204
|
}
|
|
205
|
+
async function ensureOutboxMigrated() {
|
|
206
|
+
const migrated = readStateMeta(OUTBOX_IMPORT_META_KEY);
|
|
207
|
+
if (migrated)
|
|
208
|
+
return;
|
|
209
|
+
const countRow = getStateDb()
|
|
210
|
+
.prepare("SELECT COUNT(*) AS count FROM outbox_events")
|
|
211
|
+
.get();
|
|
212
|
+
if ((countRow?.count ?? 0) > 0) {
|
|
213
|
+
writeStateMeta(OUTBOX_IMPORT_META_KEY, true);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
await ensureDir();
|
|
217
|
+
const files = await readdir(outboxDir()).catch(() => []);
|
|
218
|
+
const queueIds = files
|
|
219
|
+
.filter((file) => file.endsWith(".json"))
|
|
220
|
+
.map((file) => file.slice(0, -5))
|
|
221
|
+
.filter(Boolean);
|
|
222
|
+
for (const queueId of queueIds) {
|
|
223
|
+
const events = await readLegacyOutboxFile(queueId);
|
|
224
|
+
if (events.length === 0)
|
|
225
|
+
continue;
|
|
226
|
+
writeOutboxEvents(queueId, events);
|
|
227
|
+
}
|
|
228
|
+
writeStateMeta(OUTBOX_IMPORT_META_KEY, true);
|
|
229
|
+
}
|
|
230
|
+
function pruneQueueRows(queueId) {
|
|
231
|
+
const normalizedQueueId = normalizeSessionId(queueId);
|
|
232
|
+
const cutoffIso = new Date(Date.now() - OUTBOX_EVENT_TTL_MS).toISOString();
|
|
233
|
+
getStateDb()
|
|
234
|
+
.prepare(`DELETE FROM outbox_events
|
|
235
|
+
WHERE queue_id = ?
|
|
236
|
+
AND timestamp < ?`)
|
|
237
|
+
.run(normalizedQueueId, cutoffIso);
|
|
238
|
+
getStateDb()
|
|
239
|
+
.prepare(`DELETE FROM outbox_events
|
|
240
|
+
WHERE queue_id = ?
|
|
241
|
+
AND event_id NOT IN (
|
|
242
|
+
SELECT event_id
|
|
243
|
+
FROM outbox_events
|
|
244
|
+
WHERE queue_id = ?
|
|
245
|
+
ORDER BY timestamp DESC, updated_at DESC
|
|
246
|
+
LIMIT ?
|
|
247
|
+
)`)
|
|
248
|
+
.run(normalizedQueueId, normalizedQueueId, OUTBOX_MAX_EVENTS_PER_SESSION);
|
|
249
|
+
}
|
|
250
|
+
export async function readOutbox(sessionId) {
|
|
251
|
+
let normalizedSessionId;
|
|
252
|
+
try {
|
|
253
|
+
normalizedSessionId = normalizeSessionId(sessionId);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
await ensureOutboxMigrated();
|
|
259
|
+
const rows = getStateDb()
|
|
260
|
+
.prepare(`SELECT
|
|
261
|
+
queue_id,
|
|
262
|
+
event_id,
|
|
263
|
+
event_type,
|
|
264
|
+
timestamp,
|
|
265
|
+
payload_json,
|
|
266
|
+
activity_item_json,
|
|
267
|
+
replay_failures,
|
|
268
|
+
last_replay_error,
|
|
269
|
+
last_replay_at
|
|
270
|
+
FROM outbox_events
|
|
271
|
+
WHERE queue_id = ?
|
|
272
|
+
ORDER BY timestamp ASC, event_id ASC`)
|
|
273
|
+
.all(normalizedSessionId);
|
|
274
|
+
const events = [];
|
|
275
|
+
for (const row of rows) {
|
|
276
|
+
try {
|
|
277
|
+
const event = rowToEvent(row);
|
|
278
|
+
if (event)
|
|
279
|
+
events.push(event);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// skip malformed rows
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const { pruned, changed } = pruneOutboxEvents(events);
|
|
286
|
+
const filtered = [];
|
|
287
|
+
let filteredChanged = changed;
|
|
288
|
+
for (const event of pruned) {
|
|
289
|
+
const skipReason = classifyOutboxReplaySkip(event);
|
|
290
|
+
if (!skipReason) {
|
|
291
|
+
filtered.push(event);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
filteredChanged = true;
|
|
295
|
+
await appendOutboxDeadLetter(normalizedSessionId, event, `pruned_on_read:${skipReason}`, null);
|
|
296
|
+
}
|
|
297
|
+
if (filteredChanged) {
|
|
298
|
+
await replaceOutbox(normalizedSessionId, filtered);
|
|
299
|
+
}
|
|
300
|
+
return filtered;
|
|
301
|
+
}
|
|
187
302
|
export async function appendToOutbox(sessionId, event) {
|
|
188
303
|
let normalizedSessionId;
|
|
189
304
|
try {
|
|
@@ -192,22 +307,39 @@ export async function appendToOutbox(sessionId, event) {
|
|
|
192
307
|
catch {
|
|
193
308
|
return;
|
|
194
309
|
}
|
|
195
|
-
const
|
|
310
|
+
const normalizedEvent = normalizeOutboxEvent(event);
|
|
311
|
+
const skipReason = classifyOutboxReplaySkip(normalizedEvent);
|
|
196
312
|
if (skipReason) {
|
|
197
|
-
await appendOutboxDeadLetter(normalizedSessionId,
|
|
313
|
+
await appendOutboxDeadLetter(normalizedSessionId, normalizedEvent, `suppressed_on_append:${skipReason}`, null);
|
|
198
314
|
return;
|
|
199
315
|
}
|
|
200
|
-
await
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
316
|
+
await ensureOutboxMigrated();
|
|
317
|
+
const nowIso = new Date().toISOString();
|
|
318
|
+
getStateDb()
|
|
319
|
+
.prepare(`INSERT INTO outbox_events (
|
|
320
|
+
event_id,
|
|
321
|
+
queue_id,
|
|
322
|
+
event_type,
|
|
323
|
+
timestamp,
|
|
324
|
+
payload_json,
|
|
325
|
+
activity_item_json,
|
|
326
|
+
replay_failures,
|
|
327
|
+
last_replay_error,
|
|
328
|
+
last_replay_at,
|
|
329
|
+
updated_at
|
|
330
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
331
|
+
ON CONFLICT(event_id) DO UPDATE SET
|
|
332
|
+
queue_id = excluded.queue_id,
|
|
333
|
+
event_type = excluded.event_type,
|
|
334
|
+
timestamp = excluded.timestamp,
|
|
335
|
+
payload_json = excluded.payload_json,
|
|
336
|
+
activity_item_json = excluded.activity_item_json,
|
|
337
|
+
replay_failures = excluded.replay_failures,
|
|
338
|
+
last_replay_error = excluded.last_replay_error,
|
|
339
|
+
last_replay_at = excluded.last_replay_at,
|
|
340
|
+
updated_at = excluded.updated_at`)
|
|
341
|
+
.run(normalizedEvent.id, normalizedSessionId, normalizedEvent.type, normalizedEvent.timestamp, JSON.stringify(normalizedEvent.payload ?? {}), JSON.stringify(normalizedEvent.activityItem), normalizedEvent.replayFailures ?? 0, normalizedEvent.lastReplayError ?? null, normalizedEvent.lastReplayAt ?? null, nowIso);
|
|
342
|
+
pruneQueueRows(normalizedSessionId);
|
|
211
343
|
}
|
|
212
344
|
export async function replaceOutbox(sessionId, events) {
|
|
213
345
|
let normalizedSessionId;
|
|
@@ -217,93 +349,75 @@ export async function replaceOutbox(sessionId, events) {
|
|
|
217
349
|
catch {
|
|
218
350
|
return;
|
|
219
351
|
}
|
|
220
|
-
await
|
|
221
|
-
const targetPath = outboxPath(normalizedSessionId);
|
|
352
|
+
await ensureOutboxMigrated();
|
|
222
353
|
if (events.length === 0) {
|
|
354
|
+
getStateDb()
|
|
355
|
+
.prepare("DELETE FROM outbox_events WHERE queue_id = ?")
|
|
356
|
+
.run(normalizedSessionId);
|
|
223
357
|
try {
|
|
224
|
-
await unlink(
|
|
225
|
-
return;
|
|
358
|
+
await unlink(outboxPath(normalizedSessionId));
|
|
226
359
|
}
|
|
227
360
|
catch {
|
|
228
361
|
// File may not exist
|
|
229
|
-
return;
|
|
230
362
|
}
|
|
363
|
+
return;
|
|
231
364
|
}
|
|
232
|
-
|
|
365
|
+
writeOutboxEvents(normalizedSessionId, events);
|
|
366
|
+
pruneQueueRows(normalizedSessionId);
|
|
233
367
|
}
|
|
234
368
|
export async function readAllOutboxItems() {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// skip malformed files
|
|
369
|
+
await ensureOutboxMigrated();
|
|
370
|
+
const rows = getStateDb()
|
|
371
|
+
.prepare(`SELECT activity_item_json
|
|
372
|
+
FROM outbox_events
|
|
373
|
+
ORDER BY timestamp DESC, event_id DESC
|
|
374
|
+
LIMIT ?`)
|
|
375
|
+
.all(10_000);
|
|
376
|
+
const items = [];
|
|
377
|
+
for (const row of rows) {
|
|
378
|
+
try {
|
|
379
|
+
const parsed = JSON.parse(row.activity_item_json);
|
|
380
|
+
if (parsed && typeof parsed === "object") {
|
|
381
|
+
items.push(parsed);
|
|
251
382
|
}
|
|
252
383
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return [];
|
|
384
|
+
catch {
|
|
385
|
+
// skip malformed rows
|
|
386
|
+
}
|
|
257
387
|
}
|
|
388
|
+
return items;
|
|
258
389
|
}
|
|
259
390
|
export async function readOutboxSummary() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
pendingByQueue[queueId] = pendingByQueue[queueId] ?? 0;
|
|
286
|
-
}
|
|
391
|
+
await ensureOutboxMigrated();
|
|
392
|
+
const rows = getStateDb()
|
|
393
|
+
.prepare(`SELECT
|
|
394
|
+
queue_id,
|
|
395
|
+
COUNT(*) AS pending_count,
|
|
396
|
+
MIN(timestamp) AS oldest_event_at,
|
|
397
|
+
MAX(timestamp) AS newest_event_at
|
|
398
|
+
FROM outbox_events
|
|
399
|
+
GROUP BY queue_id`)
|
|
400
|
+
.all();
|
|
401
|
+
const pendingByQueue = {};
|
|
402
|
+
let pendingTotal = 0;
|
|
403
|
+
let oldestEventAt = null;
|
|
404
|
+
let newestEventAt = null;
|
|
405
|
+
for (const row of rows) {
|
|
406
|
+
pendingByQueue[row.queue_id] = row.pending_count;
|
|
407
|
+
pendingTotal += row.pending_count;
|
|
408
|
+
if (row.oldest_event_at && (!oldestEventAt || row.oldest_event_at < oldestEventAt)) {
|
|
409
|
+
oldestEventAt = row.oldest_event_at;
|
|
410
|
+
}
|
|
411
|
+
if (row.newest_event_at && (!newestEventAt || row.newest_event_at > newestEventAt)) {
|
|
412
|
+
newestEventAt = row.newest_event_at;
|
|
287
413
|
}
|
|
288
|
-
return {
|
|
289
|
-
pendingTotal,
|
|
290
|
-
pendingByQueue,
|
|
291
|
-
oldestEventAt: Number.isFinite(oldestEpoch)
|
|
292
|
-
? new Date(oldestEpoch).toISOString()
|
|
293
|
-
: null,
|
|
294
|
-
newestEventAt: Number.isFinite(newestEpoch)
|
|
295
|
-
? new Date(newestEpoch).toISOString()
|
|
296
|
-
: null,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
return {
|
|
301
|
-
pendingTotal: 0,
|
|
302
|
-
pendingByQueue: {},
|
|
303
|
-
oldestEventAt: null,
|
|
304
|
-
newestEventAt: null,
|
|
305
|
-
};
|
|
306
414
|
}
|
|
415
|
+
return {
|
|
416
|
+
pendingTotal,
|
|
417
|
+
pendingByQueue,
|
|
418
|
+
oldestEventAt,
|
|
419
|
+
newestEventAt,
|
|
420
|
+
};
|
|
307
421
|
}
|
|
308
422
|
export async function clearOutbox(sessionId) {
|
|
309
423
|
let normalizedSessionId;
|
|
@@ -313,6 +427,10 @@ export async function clearOutbox(sessionId) {
|
|
|
313
427
|
catch {
|
|
314
428
|
return;
|
|
315
429
|
}
|
|
430
|
+
await ensureOutboxMigrated();
|
|
431
|
+
getStateDb()
|
|
432
|
+
.prepare("DELETE FROM outbox_events WHERE queue_id = ?")
|
|
433
|
+
.run(normalizedSessionId);
|
|
316
434
|
try {
|
|
317
435
|
await unlink(outboxPath(normalizedSessionId));
|
|
318
436
|
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
export function classifyTaskState(status) {
|
|
2
2
|
const normalized = String(status ?? "").trim().toLowerCase();
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const canonical = normalized.replace(/[\s-]+/g, "_");
|
|
4
|
+
if (canonical === "done" ||
|
|
5
|
+
canonical === "completed" ||
|
|
6
|
+
canonical === "cancelled" ||
|
|
7
|
+
canonical === "archived" ||
|
|
8
|
+
canonical === "deleted") {
|
|
8
9
|
return "done";
|
|
9
10
|
}
|
|
10
|
-
if (
|
|
11
|
+
if (canonical === "blocked" ||
|
|
12
|
+
canonical === "at_risk" ||
|
|
13
|
+
canonical === "on_hold" ||
|
|
14
|
+
canonical === "onhold") {
|
|
11
15
|
return "blocked";
|
|
12
16
|
}
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
if (canonical === "in_progress" ||
|
|
18
|
+
canonical === "inprogress" ||
|
|
19
|
+
canonical === "active" ||
|
|
20
|
+
canonical === "running" ||
|
|
21
|
+
canonical === "queued" ||
|
|
22
|
+
canonical === "retry_pending" ||
|
|
23
|
+
canonical === "pending_review" ||
|
|
24
|
+
canonical === "pendingreview") {
|
|
18
25
|
return "active";
|
|
19
26
|
}
|
|
20
27
|
return "todo";
|