@evomap/evolver-core 2.0.0-beta.17 → 2.0.0-beta.19
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/dist/algo/candidateAssembly.js +21 -2
- package/dist/algo/cycleEngine.d.ts +12 -0
- package/dist/algo/cycleEngine.js +36 -4
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +5 -4
- package/dist/algo/geneSelection.d.ts +1 -1
- package/dist/algo/orchestrator.js +9 -2
- package/dist/assetstore/assetSidecarRecords.js +4 -0
- package/dist/assetstore/assetStoreHealth.js +41 -24
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +16 -7
- package/dist/assetstore/localJsonl.d.ts +2 -1
- package/dist/assetstore/localJsonl.js +54 -10
- package/dist/assetstore/provenance.d.ts +24 -0
- package/dist/assetstore/provenance.js +219 -12
- package/dist/assetstore/provider.d.ts +20 -1
- package/dist/assetstore/provider.js +34 -1
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
- package/dist/bootstrap/v1EnvCompat.js +256 -0
- package/dist/events/public.d.ts +1 -1
- package/dist/events/public.js +1 -1
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +18 -1
- package/dist/exec/autoExec.js +24 -9
- package/dist/exec/autonomousCycle.d.ts +19 -4
- package/dist/exec/autonomousCycle.js +63 -13
- package/dist/exec/claudeBridge.d.ts +25 -7
- package/dist/exec/claudeBridge.js +264 -29
- package/dist/exec/prompt.js +5 -1
- package/dist/exec/runnerRegistry.d.ts +68 -26
- package/dist/exec/runnerRegistry.js +307 -72
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +17 -6
- package/dist/hub/capability.d.ts +11 -1
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +10 -2
- package/dist/mailbox/ipcServer.js +163 -13
- package/dist/mailbox/store.d.ts +38 -2
- package/dist/mailbox/store.js +416 -27
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/expand.js +17 -6
- package/dist/signals/index.d.ts +2 -1
- package/dist/signals/index.js +2 -1
- package/dist/strategy/constraintAblation.js +115 -369
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/trace/index.d.ts +2 -1
- package/dist/trace/index.js +2 -1
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- package/package.json +4 -1
package/dist/mailbox/envelope.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ulid as makeUlid } from 'ulid';
|
|
2
2
|
import { specForType, assertKnownType } from './catalog.js';
|
|
3
|
-
export const
|
|
3
|
+
export const PRIORITIES = Object.freeze(['high', 'normal', 'low']);
|
|
4
|
+
export const ENVELOPE_SCHEMA_VERSION = '1.1.0';
|
|
4
5
|
/** 固定 envelope 字段集 (schema-snapshot 锁; 增删字段必须改此处). */
|
|
5
6
|
export const ENVELOPE_FIELDS = [
|
|
6
7
|
'id', 'type', 'direction', 'status', 'handler', 'payload', 'correlationId', 'replyTo',
|
|
7
8
|
'receiptId', 'idempotencyKey', 'sourceAgent', 'targetAgent', 'runtimeNamespace',
|
|
8
|
-
'attempts', 'nextRetryAt', 'ttlAt', 'createdAt', 'updatedAt', 'schemaVersion', 'feedsMaterial',
|
|
9
|
+
'priority', 'attempts', 'nextRetryAt', 'ttlAt', 'createdAt', 'updatedAt', 'schemaVersion', 'feedsMaterial',
|
|
10
|
+
'lastError',
|
|
9
11
|
];
|
|
10
12
|
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
11
13
|
/** 防原型污染 (v1 GHSA). */
|
|
@@ -22,6 +24,9 @@ export function sanitizePayload(v) {
|
|
|
22
24
|
return v;
|
|
23
25
|
}
|
|
24
26
|
const TTL_MS = { control: 60 * 60 * 1000, default: 7 * 24 * 60 * 60 * 1000 };
|
|
27
|
+
export function normalizePriority(value) {
|
|
28
|
+
return value === 'high' || value === 'low' ? value : 'normal';
|
|
29
|
+
}
|
|
25
30
|
export function createEnvelope(input) {
|
|
26
31
|
const spec = assertKnownType(input.type);
|
|
27
32
|
const id = input.id ?? makeUlid();
|
|
@@ -40,6 +45,7 @@ export function createEnvelope(input) {
|
|
|
40
45
|
sourceAgent: input.sourceAgent ?? '',
|
|
41
46
|
targetAgent: input.targetAgent ?? '',
|
|
42
47
|
runtimeNamespace: input.runtimeNamespace ?? 'default',
|
|
48
|
+
priority: normalizePriority(input.priority),
|
|
43
49
|
attempts: 0,
|
|
44
50
|
nextRetryAt: null,
|
|
45
51
|
ttlAt: now + TTL_MS[spec.ttlClass],
|
|
@@ -47,6 +53,7 @@ export function createEnvelope(input) {
|
|
|
47
53
|
updatedAt: now,
|
|
48
54
|
schemaVersion: ENVELOPE_SCHEMA_VERSION,
|
|
49
55
|
feedsMaterial: spec.feedsMaterial,
|
|
56
|
+
lastError: null,
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
export { specForType };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
2
|
import { type Envelope } from './envelope.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type MailboxStore } from './store.js';
|
|
4
4
|
export interface IpcServerOptions {
|
|
5
5
|
store: MailboxStore;
|
|
6
6
|
token: string;
|
|
7
7
|
host?: string;
|
|
8
8
|
now?: () => number;
|
|
9
|
+
/** When set, every mailbox route is pinned to this daemon namespace. */
|
|
10
|
+
runtimeNamespace?: string;
|
|
9
11
|
extraRoutes?: IpcRouteHandler[];
|
|
10
12
|
onSend?: (envelope: Envelope, result: {
|
|
11
13
|
receiptId: string;
|
|
@@ -34,6 +36,7 @@ export declare class MailboxIpcServer {
|
|
|
34
36
|
private readonly token;
|
|
35
37
|
private readonly host;
|
|
36
38
|
private readonly now;
|
|
39
|
+
private readonly runtimeNamespace;
|
|
37
40
|
private readonly extraRoutes;
|
|
38
41
|
private readonly onSend;
|
|
39
42
|
private readonly onAuthFailure;
|
|
@@ -42,5 +45,10 @@ export declare class MailboxIpcServer {
|
|
|
42
45
|
close(): Promise<void>;
|
|
43
46
|
private handle;
|
|
44
47
|
private readJson;
|
|
48
|
+
private resolveSendNamespace;
|
|
49
|
+
private resolveRuntimeNamespace;
|
|
50
|
+
private messageInRuntime;
|
|
51
|
+
private canMutateMessage;
|
|
45
52
|
private json;
|
|
46
|
-
}
|
|
53
|
+
}
|
|
54
|
+
export declare function legacyMailboxMessage(message: Envelope): Record<string, unknown>;
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { createEnvelope } from './envelope.js';
|
|
4
|
+
import { mailboxClaimOwner } from './store.js';
|
|
4
5
|
const MAX_BODY = 256 * 1024; // 256KB 上限(防本机注入超大体)
|
|
5
6
|
const LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
|
|
7
|
+
const STATUSES = new Set(['pending', 'in_flight', 'done', 'failed', 'expired']);
|
|
8
|
+
const DIRECTIONS = new Set(['outbound', 'inbound', 'local']);
|
|
9
|
+
const PRIORITIES = new Set(['high', 'normal', 'low']);
|
|
10
|
+
function serializeClaimedMessage(message) {
|
|
11
|
+
const claimToken = mailboxClaimOwner(message);
|
|
12
|
+
if (!claimToken)
|
|
13
|
+
throw new Error('claimed mailbox message is missing its claim token');
|
|
14
|
+
return { ...message, claimToken };
|
|
15
|
+
}
|
|
16
|
+
function requiredClaimToken(value) {
|
|
17
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
18
|
+
throw new Error('claimToken must be a non-empty string');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function optionalQueryInteger(url, name) {
|
|
22
|
+
const raw = url.searchParams.get(name);
|
|
23
|
+
if (raw === null)
|
|
24
|
+
return undefined;
|
|
25
|
+
const value = Number(raw);
|
|
26
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
|
|
27
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
6
31
|
/** 定长常量时间比较(防 token 计时侧信道). */
|
|
7
32
|
function tokenEq(a, b) {
|
|
8
33
|
const ba = Buffer.from(a), bb = Buffer.from(b);
|
|
@@ -20,6 +45,7 @@ export class MailboxIpcServer {
|
|
|
20
45
|
token;
|
|
21
46
|
host;
|
|
22
47
|
now;
|
|
48
|
+
runtimeNamespace;
|
|
23
49
|
extraRoutes;
|
|
24
50
|
onSend;
|
|
25
51
|
onAuthFailure;
|
|
@@ -28,6 +54,7 @@ export class MailboxIpcServer {
|
|
|
28
54
|
this.token = opts.token;
|
|
29
55
|
this.host = opts.host ?? '127.0.0.1';
|
|
30
56
|
this.now = opts.now ?? (() => Date.now());
|
|
57
|
+
this.runtimeNamespace = opts.runtimeNamespace;
|
|
31
58
|
this.extraRoutes = opts.extraRoutes ?? [];
|
|
32
59
|
this.onSend = opts.onSend;
|
|
33
60
|
this.onAuthFailure = opts.onAuthFailure;
|
|
@@ -90,41 +117,98 @@ export class MailboxIpcServer {
|
|
|
90
117
|
}
|
|
91
118
|
if (route === 'POST /mailbox/send') {
|
|
92
119
|
const body = (await this.readJson(req));
|
|
93
|
-
|
|
120
|
+
if (body.priority !== undefined && !PRIORITIES.has(body.priority)) {
|
|
121
|
+
throw new Error('priority must be high, normal, or low');
|
|
122
|
+
}
|
|
123
|
+
const legacyRef = typeof body.ref_id === 'string' && body.ref_id ? body.ref_id : undefined;
|
|
124
|
+
const runtimeNamespace = this.resolveSendNamespace(body);
|
|
125
|
+
let env = createEnvelope({
|
|
126
|
+
...body,
|
|
127
|
+
runtimeNamespace,
|
|
128
|
+
correlationId: body.correlationId ?? legacyRef,
|
|
129
|
+
now,
|
|
130
|
+
});
|
|
131
|
+
if (body.expires_at !== undefined) {
|
|
132
|
+
if (typeof body.expires_at !== 'number' || !Number.isFinite(body.expires_at)) {
|
|
133
|
+
throw new Error('expires_at must be a finite epoch-millisecond number');
|
|
134
|
+
}
|
|
135
|
+
env = { ...env, ttlAt: body.expires_at };
|
|
136
|
+
}
|
|
94
137
|
const r = this.store.send(env);
|
|
95
138
|
if (r.stored)
|
|
96
139
|
this.onSend?.(env, r);
|
|
97
|
-
return this.json(res, 200, {
|
|
140
|
+
return this.json(res, 200, {
|
|
141
|
+
id: env.id,
|
|
142
|
+
receiptId: r.receiptId,
|
|
143
|
+
stored: r.stored,
|
|
144
|
+
correlationId: env.correlationId,
|
|
145
|
+
message_id: env.id,
|
|
146
|
+
status: env.status,
|
|
147
|
+
});
|
|
98
148
|
}
|
|
99
149
|
if (route === 'POST /mailbox/claim') {
|
|
100
150
|
const b = (await this.readJson(req));
|
|
101
|
-
const
|
|
102
|
-
|
|
151
|
+
const runtimeNamespace = this.resolveRuntimeNamespace(b.runtimeNamespace);
|
|
152
|
+
const got = this.store.claim(b.handler, b.limit ?? 16, b.leaseMs ?? 30_000, now, runtimeNamespace);
|
|
153
|
+
return this.json(res, 200, { messages: got.map(serializeClaimedMessage) });
|
|
103
154
|
}
|
|
104
155
|
if (route === 'POST /mailbox/complete') {
|
|
105
156
|
const b = (await this.readJson(req));
|
|
106
|
-
this.
|
|
157
|
+
if (!this.canMutateMessage(b.id))
|
|
158
|
+
return this.json(res, 404, { error: 'not found' });
|
|
159
|
+
const claimToken = requiredClaimToken(b.claimToken);
|
|
160
|
+
if (!this.store.completeClaimed(b.id, now, claimToken)) {
|
|
161
|
+
return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
|
|
162
|
+
}
|
|
107
163
|
return this.json(res, 200, { ok: true });
|
|
108
164
|
}
|
|
109
165
|
if (route === 'POST /mailbox/fail') {
|
|
110
166
|
const b = (await this.readJson(req));
|
|
111
|
-
this.
|
|
167
|
+
if (!this.canMutateMessage(b.id))
|
|
168
|
+
return this.json(res, 404, { error: 'not found' });
|
|
169
|
+
const claimToken = requiredClaimToken(b.claimToken);
|
|
170
|
+
if (!this.store.failClaimed(b.id, b.error ?? 'ipc fail', now, claimToken)) {
|
|
171
|
+
return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
|
|
172
|
+
}
|
|
112
173
|
return this.json(res, 200, { ok: true });
|
|
113
174
|
}
|
|
114
|
-
|
|
115
|
-
|
|
175
|
+
const statusPathMatch = req.method === 'GET' ? /^\/mailbox\/status\/([^/]+)$/.exec(url.pathname) : null;
|
|
176
|
+
if (req.method === 'GET' && (url.pathname === '/mailbox/status' || statusPathMatch)) {
|
|
177
|
+
const id = statusPathMatch ? decodeURIComponent(statusPathMatch[1] ?? '') : url.searchParams.get('id') ?? '';
|
|
178
|
+
const message = this.messageInRuntime(id);
|
|
179
|
+
if (!message)
|
|
180
|
+
return this.json(res, 404, { error: 'not found' });
|
|
181
|
+
if (statusPathMatch) {
|
|
182
|
+
return this.json(res, 200, legacyMailboxMessage(message));
|
|
183
|
+
}
|
|
116
184
|
const s = this.store.getStatus(id);
|
|
117
185
|
return s ? this.json(res, 200, s) : this.json(res, 404, { error: 'not found' });
|
|
118
186
|
}
|
|
119
187
|
if (req.method === 'GET' && url.pathname === '/mailbox/list') {
|
|
120
|
-
const
|
|
188
|
+
const type = url.searchParams.get('type') ?? undefined;
|
|
189
|
+
const legacy = type !== undefined;
|
|
190
|
+
const rawStatus = url.searchParams.get('status') ?? undefined;
|
|
191
|
+
const statusValue = legacyMailboxStatus(rawStatus);
|
|
192
|
+
if (rawStatus !== undefined && statusValue === undefined)
|
|
193
|
+
throw new Error('invalid status');
|
|
194
|
+
const directionValue = url.searchParams.get('direction') ?? undefined;
|
|
195
|
+
if (directionValue !== undefined && !DIRECTIONS.has(directionValue)) {
|
|
196
|
+
throw new Error('invalid direction');
|
|
197
|
+
}
|
|
121
198
|
const got = this.store.list({
|
|
122
|
-
status:
|
|
199
|
+
status: statusValue,
|
|
123
200
|
handler: url.searchParams.get('handler') ?? undefined,
|
|
124
|
-
runtimeNamespace: url.searchParams.get('runtimeNamespace') ?? undefined,
|
|
125
|
-
|
|
201
|
+
runtimeNamespace: this.resolveRuntimeNamespace(url.searchParams.get('runtimeNamespace') ?? undefined),
|
|
202
|
+
type,
|
|
203
|
+
direction: directionValue,
|
|
204
|
+
newestFirst: legacy,
|
|
205
|
+
offset: optionalQueryInteger(url, 'offset'),
|
|
206
|
+
limit: optionalQueryInteger(url, 'limit') ?? 200,
|
|
207
|
+
});
|
|
208
|
+
return this.json(res, 200, {
|
|
209
|
+
messages: legacy ? got.map(legacyMailboxMessage) : got,
|
|
210
|
+
count: got.length,
|
|
126
211
|
});
|
|
127
|
-
return this.json(res, 200, { messages: got });
|
|
128
212
|
}
|
|
129
213
|
return this.json(res, 404, { error: 'unknown route' });
|
|
130
214
|
}
|
|
@@ -161,8 +245,74 @@ export class MailboxIpcServer {
|
|
|
161
245
|
req.on('error', reject);
|
|
162
246
|
});
|
|
163
247
|
}
|
|
248
|
+
resolveSendNamespace(body) {
|
|
249
|
+
const bound = this.runtimeNamespace;
|
|
250
|
+
if (bound === undefined)
|
|
251
|
+
return body.runtimeNamespace;
|
|
252
|
+
const requested = body.runtimeNamespace;
|
|
253
|
+
if (requested !== undefined && (typeof requested !== 'string' || requested !== bound)) {
|
|
254
|
+
throw new Error('runtimeNamespace does not match the IPC server namespace');
|
|
255
|
+
}
|
|
256
|
+
const channel = body.channel;
|
|
257
|
+
if (channel !== undefined && (typeof channel !== 'string'
|
|
258
|
+
|| (channel !== 'evomap-hub' && channel !== bound))) {
|
|
259
|
+
throw new Error('channel does not match the IPC server namespace');
|
|
260
|
+
}
|
|
261
|
+
return bound;
|
|
262
|
+
}
|
|
263
|
+
resolveRuntimeNamespace(requested) {
|
|
264
|
+
if (requested !== undefined && typeof requested !== 'string') {
|
|
265
|
+
throw new Error('runtimeNamespace must be a string');
|
|
266
|
+
}
|
|
267
|
+
const bound = this.runtimeNamespace;
|
|
268
|
+
if (bound === undefined)
|
|
269
|
+
return requested;
|
|
270
|
+
if (requested !== undefined && requested !== bound) {
|
|
271
|
+
throw new Error('runtimeNamespace does not match the IPC server namespace');
|
|
272
|
+
}
|
|
273
|
+
return bound;
|
|
274
|
+
}
|
|
275
|
+
messageInRuntime(id) {
|
|
276
|
+
const message = this.store.getById(id);
|
|
277
|
+
if (this.runtimeNamespace !== undefined && message?.runtimeNamespace !== this.runtimeNamespace)
|
|
278
|
+
return undefined;
|
|
279
|
+
return message;
|
|
280
|
+
}
|
|
281
|
+
canMutateMessage(id) {
|
|
282
|
+
return this.messageInRuntime(id) !== undefined;
|
|
283
|
+
}
|
|
164
284
|
json(res, code, body) {
|
|
165
285
|
res.writeHead(code, { 'content-type': 'application/json' });
|
|
166
286
|
res.end(JSON.stringify(body));
|
|
167
287
|
}
|
|
288
|
+
}
|
|
289
|
+
export function legacyMailboxMessage(message) {
|
|
290
|
+
return {
|
|
291
|
+
id: message.id,
|
|
292
|
+
message_id: message.id,
|
|
293
|
+
channel: message.runtimeNamespace === 'default' ? 'evomap-hub' : message.runtimeNamespace,
|
|
294
|
+
direction: message.direction,
|
|
295
|
+
type: message.type,
|
|
296
|
+
status: message.status === 'done' ? (message.direction === 'inbound' ? 'delivered' : 'synced') : message.status,
|
|
297
|
+
payload: message.payload,
|
|
298
|
+
priority: message.priority,
|
|
299
|
+
ref_id: message.replyTo ?? message.correlationId,
|
|
300
|
+
created_at: message.createdAt,
|
|
301
|
+
synced_at: message.status === 'done' ? message.updatedAt : null,
|
|
302
|
+
expires_at: message.ttlAt,
|
|
303
|
+
retry_count: message.attempts,
|
|
304
|
+
next_retry_at: message.nextRetryAt,
|
|
305
|
+
error: message.lastError,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function legacyMailboxStatus(value) {
|
|
309
|
+
if (value === undefined)
|
|
310
|
+
return undefined;
|
|
311
|
+
if (STATUSES.has(value))
|
|
312
|
+
return value;
|
|
313
|
+
if (value === 'delivered' || value === 'synced' || value === 'acked')
|
|
314
|
+
return 'done';
|
|
315
|
+
if (value === 'rejected')
|
|
316
|
+
return 'failed';
|
|
317
|
+
return undefined;
|
|
168
318
|
}
|
package/dist/mailbox/store.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { type Envelope, type Status, type CreateEnvelopeInput } from './envelope.js';
|
|
2
2
|
export declare const MAX_ATTEMPTS = 5;
|
|
3
3
|
export declare const DEFAULT_IMPORT_JSONL_MAX_LINE_BYTES: number;
|
|
4
|
+
export declare const MAILBOX_CLAIM_OWNER: unique symbol;
|
|
5
|
+
export type ClaimedEnvelope = Envelope & {
|
|
6
|
+
readonly [MAILBOX_CLAIM_OWNER]: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function mailboxClaimOwner(envelope: Envelope): string | undefined;
|
|
4
9
|
export declare function expBackoffMs(attempt: number): number;
|
|
10
|
+
/** Read one mailbox state value without creating, migrating, or taking write ownership of the database. */
|
|
11
|
+
export declare function readMailboxState(path: string, key: string): string | undefined;
|
|
5
12
|
/** mailbox sqlite 引擎 (WAL + busy_timeout). 状态机 pending→in_flight→done/failed/expired + 租约 + 重试/DLQ. */
|
|
6
13
|
export declare class MailboxStore {
|
|
7
14
|
private readonly db;
|
|
@@ -47,12 +54,31 @@ export declare class MailboxStore {
|
|
|
47
54
|
hasMessageWithIdempotencyKey(idempotencyKey: string): boolean;
|
|
48
55
|
hasMessageWithPayload(type: string, payload: unknown): boolean;
|
|
49
56
|
/** 原子 claim: pending(或租约过期的 in_flight) → in_flight + 租约. 可按 runtimeNamespace 分区. */
|
|
50
|
-
claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string):
|
|
57
|
+
claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string): ClaimedEnvelope[];
|
|
51
58
|
complete(id: string, now: number): void;
|
|
52
59
|
/** 失败: attempts<N→退避回 pending; ≥N→DLQ(不自动丢). */
|
|
53
60
|
fail(id: string, err: string, now: number, maxAttempts?: number): void;
|
|
54
61
|
/** 暂缓: transient upstream outage 不消耗 attempts, 仅设置下次可 claim 时间. */
|
|
55
62
|
defer(id: string, err: string, now: number, retryAfterMs: number): void;
|
|
63
|
+
deferUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number): boolean;
|
|
64
|
+
failUnlessProcessed(id: string, processedKey: string, err: string, now: number, maxAttempts?: number): boolean;
|
|
65
|
+
completeClaimed(id: string, now: number, claimOwner: string): boolean;
|
|
66
|
+
/** Atomically complete the current worker's lease and persist its idempotency result. */
|
|
67
|
+
completeClaimedAndMarkProcessed(id: string, processedKey: string, result: unknown, now: number, claimOwner: string): boolean;
|
|
68
|
+
renewClaim(id: string, now: number, leaseMs: number, claimOwner: string): boolean;
|
|
69
|
+
setClaimedCheckpoint(id: string, checkpoint: unknown, now: number, claimOwner: string): boolean;
|
|
70
|
+
getClaimedCheckpoint(id: string): unknown | undefined;
|
|
71
|
+
deferClaimed(id: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
|
|
72
|
+
failClaimed(id: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
73
|
+
/** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
|
|
74
|
+
deferClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
|
|
75
|
+
/** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
|
|
76
|
+
failClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
77
|
+
/** Atomically fail a non-leased intent and persist the replayable terminal result. */
|
|
78
|
+
failAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, maxAttempts?: number): boolean;
|
|
79
|
+
/** Atomically fail the current worker's leased intent and persist the replayable terminal result. */
|
|
80
|
+
failClaimedAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
81
|
+
private transitionUnlessProcessed;
|
|
56
82
|
/** DLQ 重放(人工/agent 显式, 不自动丢). */
|
|
57
83
|
replayDlq(id: string, now: number): void;
|
|
58
84
|
dlq(): Envelope[];
|
|
@@ -73,16 +99,26 @@ export declare class MailboxStore {
|
|
|
73
99
|
id: string;
|
|
74
100
|
type: string;
|
|
75
101
|
status: Status;
|
|
102
|
+
priority: Envelope['priority'];
|
|
76
103
|
attempts: number;
|
|
77
104
|
nextRetryAt: number | null;
|
|
78
105
|
ttlAt: number | null;
|
|
79
106
|
dlq: boolean;
|
|
107
|
+
lastError: string | null;
|
|
80
108
|
} | undefined;
|
|
81
109
|
/** 幂等(A13): 副作用 handler 用 idempotencyKey 去重; 命中返缓存不重跑. */
|
|
82
110
|
isProcessed(key: string): boolean;
|
|
83
111
|
getProcessed(key: string): unknown;
|
|
84
112
|
markProcessed(key: string, result: unknown, now: number): void;
|
|
113
|
+
replaceProcessed(key: string, result: unknown, now: number): void;
|
|
114
|
+
/** Persist a monotonic outcome and its lightweight concurrency marker in one SQLite transaction. */
|
|
115
|
+
replaceProcessedWithMarker(key: string, result: unknown, markerKey: string, markerResult: unknown, now: number): void;
|
|
116
|
+
/** Backfill a concurrency marker only when the durable source result still satisfies the caller's predicate. */
|
|
117
|
+
markProcessedIf(sourceKey: string, markerKey: string, markerResult: unknown, now: number, matches: (sourceResult: unknown) => boolean): boolean;
|
|
118
|
+
deleteProcessed(keys: readonly string[]): void;
|
|
119
|
+
private messageClaimOwner;
|
|
85
120
|
/** v1 messages.jsonl → sqlite 迁移(幂等可重入). */
|
|
86
|
-
importJsonl(
|
|
121
|
+
importJsonl(source: string | number): number;
|
|
122
|
+
private applyImportedV1Update;
|
|
87
123
|
close(): void;
|
|
88
124
|
}
|