@useorgx/openclaw-plugin 0.7.20 → 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/{BoDhb8_y.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/{DAr4MfFk.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/{DibzNd0I.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/{wa4jJQK9.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/{Dm0CfDGr.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/{DXVs61e1.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/{_zpQCpjm.js → DDCPrZRt.js} +1 -1
- package/dashboard/dist/assets/DDCPrZRt.js.br +0 -0
- package/dashboard/dist/assets/DDCPrZRt.js.gz +0 -0
- package/dashboard/dist/assets/{BYb6DARX.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/{BGY6oI8h.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/{B014hrCe.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/{CV0sWMbv.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 +23 -0
- package/dist/http/helpers/auto-continue-engine.js +233 -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 +92 -45
- 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 +4 -0
- package/dist/http/routes/mission-control-read.js +67 -219
- 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 +259 -148
- 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 +5 -1
- package/dashboard/dist/assets/B014hrCe.js.br +0 -0
- package/dashboard/dist/assets/B014hrCe.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/BGY6oI8h.js.br +0 -0
- package/dashboard/dist/assets/BGY6oI8h.js.gz +0 -0
- package/dashboard/dist/assets/BJI1Iy5v.css +0 -1
- package/dashboard/dist/assets/BJI1Iy5v.css.br +0 -0
- package/dashboard/dist/assets/BJI1Iy5v.css.gz +0 -0
- package/dashboard/dist/assets/BUvcp_7V.js +0 -1
- package/dashboard/dist/assets/BUvcp_7V.js.br +0 -0
- package/dashboard/dist/assets/BUvcp_7V.js.gz +0 -0
- package/dashboard/dist/assets/BV2Tf8S2.js +0 -212
- package/dashboard/dist/assets/BV2Tf8S2.js.br +0 -0
- package/dashboard/dist/assets/BV2Tf8S2.js.gz +0 -0
- package/dashboard/dist/assets/BYb6DARX.js.br +0 -0
- package/dashboard/dist/assets/BYb6DARX.js.gz +0 -0
- package/dashboard/dist/assets/BoDhb8_y.js.br +0 -0
- package/dashboard/dist/assets/BoDhb8_y.js.gz +0 -0
- package/dashboard/dist/assets/Bqk_l0k6.js +0 -1
- package/dashboard/dist/assets/Bqk_l0k6.js.br +0 -0
- package/dashboard/dist/assets/Bqk_l0k6.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/CV0sWMbv.js.br +0 -0
- package/dashboard/dist/assets/CV0sWMbv.js.gz +0 -0
- package/dashboard/dist/assets/CaAkScfa.js +0 -1
- package/dashboard/dist/assets/CaAkScfa.js.br +0 -0
- package/dashboard/dist/assets/CaAkScfa.js.gz +0 -0
- package/dashboard/dist/assets/Ck5KlsPN.js +0 -1
- package/dashboard/dist/assets/Ck5KlsPN.js.br +0 -0
- package/dashboard/dist/assets/Ck5KlsPN.js.gz +0 -0
- package/dashboard/dist/assets/D2G51wQm.js +0 -1
- package/dashboard/dist/assets/D2G51wQm.js.br +0 -0
- package/dashboard/dist/assets/D2G51wQm.js.gz +0 -0
- package/dashboard/dist/assets/DAr4MfFk.js.br +0 -0
- package/dashboard/dist/assets/DAr4MfFk.js.gz +0 -0
- package/dashboard/dist/assets/DXVs61e1.js.br +0 -0
- package/dashboard/dist/assets/DXVs61e1.js.gz +0 -0
- package/dashboard/dist/assets/DibzNd0I.js.br +0 -0
- package/dashboard/dist/assets/DibzNd0I.js.gz +0 -0
- package/dashboard/dist/assets/Dm0CfDGr.js.br +0 -0
- package/dashboard/dist/assets/Dm0CfDGr.js.gz +0 -0
- package/dashboard/dist/assets/_zpQCpjm.js.br +0 -0
- package/dashboard/dist/assets/_zpQCpjm.js.gz +0 -0
- package/dashboard/dist/assets/uNGpYMSH.js +0 -1
- package/dashboard/dist/assets/uNGpYMSH.js.br +0 -0
- package/dashboard/dist/assets/uNGpYMSH.js.gz +0 -0
- package/dashboard/dist/assets/wa4jJQK9.js.br +0 -0
- package/dashboard/dist/assets/wa4jJQK9.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();
|
|
@@ -130,6 +103,27 @@ export async function appendOutboxDeadLetter(sessionId, event, reason, error) {
|
|
|
130
103
|
event,
|
|
131
104
|
});
|
|
132
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
|
+
}
|
|
133
127
|
/** Drop stale events and enforce per-session cap. Returns true if any were removed. */
|
|
134
128
|
function pruneOutboxEvents(events) {
|
|
135
129
|
const cutoff = Date.now() - OUTBOX_EVENT_TTL_MS;
|
|
@@ -137,50 +131,67 @@ function pruneOutboxEvents(events) {
|
|
|
137
131
|
const epoch = Date.parse(e.timestamp);
|
|
138
132
|
return Number.isFinite(epoch) && epoch >= cutoff;
|
|
139
133
|
});
|
|
140
|
-
// Keep newest events if over cap.
|
|
141
134
|
const capped = fresh.length > OUTBOX_MAX_EVENTS_PER_SESSION
|
|
142
135
|
? fresh.slice(fresh.length - OUTBOX_MAX_EVENTS_PER_SESSION)
|
|
143
136
|
: fresh;
|
|
144
137
|
return { pruned: capped, changed: capped.length !== events.length };
|
|
145
138
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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);
|
|
154
189
|
try {
|
|
155
190
|
const raw = await readFile(targetPath, "utf8");
|
|
156
191
|
try {
|
|
157
192
|
const parsed = JSON.parse(raw);
|
|
158
193
|
const events = Array.isArray(parsed) ? parsed : [];
|
|
159
|
-
|
|
160
|
-
const filtered = [];
|
|
161
|
-
let filteredChanged = changed;
|
|
162
|
-
for (const event of pruned) {
|
|
163
|
-
const skipReason = classifyOutboxReplaySkip(event);
|
|
164
|
-
if (!skipReason) {
|
|
165
|
-
filtered.push(event);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
filteredChanged = true;
|
|
169
|
-
await appendOutboxDeadLetter(sessionId, event, `pruned_on_read:${skipReason}`, null);
|
|
170
|
-
}
|
|
171
|
-
// Write back if stale events were dropped.
|
|
172
|
-
if (filteredChanged) {
|
|
173
|
-
if (filtered.length === 0) {
|
|
174
|
-
try {
|
|
175
|
-
await unlink(targetPath);
|
|
176
|
-
}
|
|
177
|
-
catch { /* ok */ }
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
await writeFileAtomic(targetPath, JSON.stringify(filtered), 0o600);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return filtered;
|
|
194
|
+
return events.map((event) => normalizeOutboxEvent(event));
|
|
184
195
|
}
|
|
185
196
|
catch {
|
|
186
197
|
await backupCorruptOutboxFile(targetPath);
|
|
@@ -191,6 +202,103 @@ export async function readOutbox(sessionId) {
|
|
|
191
202
|
return [];
|
|
192
203
|
}
|
|
193
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
|
+
}
|
|
194
302
|
export async function appendToOutbox(sessionId, event) {
|
|
195
303
|
let normalizedSessionId;
|
|
196
304
|
try {
|
|
@@ -199,22 +307,39 @@ export async function appendToOutbox(sessionId, event) {
|
|
|
199
307
|
catch {
|
|
200
308
|
return;
|
|
201
309
|
}
|
|
202
|
-
const
|
|
310
|
+
const normalizedEvent = normalizeOutboxEvent(event);
|
|
311
|
+
const skipReason = classifyOutboxReplaySkip(normalizedEvent);
|
|
203
312
|
if (skipReason) {
|
|
204
|
-
await appendOutboxDeadLetter(normalizedSessionId,
|
|
313
|
+
await appendOutboxDeadLetter(normalizedSessionId, normalizedEvent, `suppressed_on_append:${skipReason}`, null);
|
|
205
314
|
return;
|
|
206
315
|
}
|
|
207
|
-
await
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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);
|
|
218
343
|
}
|
|
219
344
|
export async function replaceOutbox(sessionId, events) {
|
|
220
345
|
let normalizedSessionId;
|
|
@@ -224,93 +349,75 @@ export async function replaceOutbox(sessionId, events) {
|
|
|
224
349
|
catch {
|
|
225
350
|
return;
|
|
226
351
|
}
|
|
227
|
-
await
|
|
228
|
-
const targetPath = outboxPath(normalizedSessionId);
|
|
352
|
+
await ensureOutboxMigrated();
|
|
229
353
|
if (events.length === 0) {
|
|
354
|
+
getStateDb()
|
|
355
|
+
.prepare("DELETE FROM outbox_events WHERE queue_id = ?")
|
|
356
|
+
.run(normalizedSessionId);
|
|
230
357
|
try {
|
|
231
|
-
await unlink(
|
|
232
|
-
return;
|
|
358
|
+
await unlink(outboxPath(normalizedSessionId));
|
|
233
359
|
}
|
|
234
360
|
catch {
|
|
235
361
|
// File may not exist
|
|
236
|
-
return;
|
|
237
362
|
}
|
|
363
|
+
return;
|
|
238
364
|
}
|
|
239
|
-
|
|
365
|
+
writeOutboxEvents(normalizedSessionId, events);
|
|
366
|
+
pruneQueueRows(normalizedSessionId);
|
|
240
367
|
}
|
|
241
368
|
export async function readAllOutboxItems() {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
// 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);
|
|
258
382
|
}
|
|
259
383
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return [];
|
|
384
|
+
catch {
|
|
385
|
+
// skip malformed rows
|
|
386
|
+
}
|
|
264
387
|
}
|
|
388
|
+
return items;
|
|
265
389
|
}
|
|
266
390
|
export async function readOutboxSummary() {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
pendingByQueue[queueId] = pendingByQueue[queueId] ?? 0;
|
|
293
|
-
}
|
|
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;
|
|
294
413
|
}
|
|
295
|
-
return {
|
|
296
|
-
pendingTotal,
|
|
297
|
-
pendingByQueue,
|
|
298
|
-
oldestEventAt: Number.isFinite(oldestEpoch)
|
|
299
|
-
? new Date(oldestEpoch).toISOString()
|
|
300
|
-
: null,
|
|
301
|
-
newestEventAt: Number.isFinite(newestEpoch)
|
|
302
|
-
? new Date(newestEpoch).toISOString()
|
|
303
|
-
: null,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
return {
|
|
308
|
-
pendingTotal: 0,
|
|
309
|
-
pendingByQueue: {},
|
|
310
|
-
oldestEventAt: null,
|
|
311
|
-
newestEventAt: null,
|
|
312
|
-
};
|
|
313
414
|
}
|
|
415
|
+
return {
|
|
416
|
+
pendingTotal,
|
|
417
|
+
pendingByQueue,
|
|
418
|
+
oldestEventAt,
|
|
419
|
+
newestEventAt,
|
|
420
|
+
};
|
|
314
421
|
}
|
|
315
422
|
export async function clearOutbox(sessionId) {
|
|
316
423
|
let normalizedSessionId;
|
|
@@ -320,6 +427,10 @@ export async function clearOutbox(sessionId) {
|
|
|
320
427
|
catch {
|
|
321
428
|
return;
|
|
322
429
|
}
|
|
430
|
+
await ensureOutboxMigrated();
|
|
431
|
+
getStateDb()
|
|
432
|
+
.prepare("DELETE FROM outbox_events WHERE queue_id = ?")
|
|
433
|
+
.run(normalizedSessionId);
|
|
323
434
|
try {
|
|
324
435
|
await unlink(outboxPath(normalizedSessionId));
|
|
325
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";
|