@evomap/evolver-core 2.0.0-beta.8 → 2.0.0
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.d.ts +7 -2
- package/dist/algo/candidateAssembly.js +81 -13
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +20 -7
- package/dist/algo/cycleEngine.d.ts +23 -2
- package/dist/algo/cycleEngine.js +236 -36
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +5 -4
- package/dist/algo/geneSelection.d.ts +63 -5
- package/dist/algo/geneSelection.js +330 -30
- package/dist/algo/index.d.ts +2 -0
- package/dist/algo/index.js +2 -0
- package/dist/algo/orchestrator.d.ts +8 -1
- package/dist/algo/orchestrator.js +15 -3
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
- package/dist/assetstore/assetSidecarRecords.js +238 -0
- package/dist/assetstore/assetSidecarRecovery.js +2 -2
- package/dist/assetstore/assetStoreHealth.d.ts +6 -0
- package/dist/assetstore/assetStoreHealth.js +56 -14
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +27 -9
- package/dist/assetstore/assetSyncLedger.d.ts +86 -0
- package/dist/assetstore/assetSyncLedger.js +683 -4
- package/dist/assetstore/index.d.ts +1 -0
- package/dist/assetstore/index.js +1 -0
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +4 -2
- package/dist/assetstore/localJsonl.js +91 -11
- package/dist/assetstore/provenance.d.ts +80 -4
- package/dist/assetstore/provenance.js +313 -5
- package/dist/assetstore/provider.d.ts +58 -1
- package/dist/assetstore/provider.js +97 -6
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
- package/dist/bootstrap/v1EnvCompat.js +280 -0
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +1 -0
- package/dist/events/ingest.js +1 -0
- package/dist/events/paths.d.ts +3 -1
- package/dist/events/paths.js +4 -0
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +43 -2
- package/dist/exec/autoExec.js +76 -9
- package/dist/exec/autonomousCycle.d.ts +22 -4
- package/dist/exec/autonomousCycle.js +64 -13
- package/dist/exec/claudeBridge.d.ts +32 -7
- package/dist/exec/claudeBridge.js +281 -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/hooks/hooks.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 +90 -3
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -1
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/sanitize.js +3 -2
- 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 +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/selfUpdate.d.ts +8 -0
- package/dist/ops/selfUpdate.js +24 -8
- package/dist/personality/schema.d.ts +16 -16
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +3 -1
- package/dist/signals/index.js +3 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +209 -0
- package/dist/trace/learningTrace.js +276 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/sandboxRunner.d.ts +28 -0
- package/dist/verify/sandboxRunner.js +218 -19
- package/dist/verify/sandboxedValidation.d.ts +9 -0
- package/dist/verify/sandboxedValidation.js +113 -13
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- package/package.json +5 -2
|
@@ -21,7 +21,7 @@ const EXPLORE_STOPWORDS = new Set([
|
|
|
21
21
|
'agent', 'about', 'into', 'then', 'than', 'they', 'them', 'their', 'there',
|
|
22
22
|
'here', 'will', 'would', 'could', 'should', 'been', 'were', 'using', 'used',
|
|
23
23
|
'cycle', 'evolution', 'error', 'errors', 'failed', 'failure', 'null', 'undefined',
|
|
24
|
-
'true', 'false', 'console', 'return', 'function', 'const', 'value', 'result',
|
|
24
|
+
'true', 'false', 'console', 'return', 'function', 'const', 'value', 'result', 'redacted',
|
|
25
25
|
]);
|
|
26
26
|
const SENSITIVE_TOPIC_RE = /secret|token|api[_-]?key|password|passwd|credential|bearer|authorization|cookie|session[_-]?id|private[_-]?key|oauth|refresh[_-]?token/i;
|
|
27
27
|
const LONG_OPAQUE_RE = /^[a-z0-9_-]{32,}$/i;
|
|
@@ -106,8 +106,8 @@ function safeTopicToken(token) {
|
|
|
106
106
|
return false;
|
|
107
107
|
return true;
|
|
108
108
|
}
|
|
109
|
-
export function extractTopicKeywords(transcript, memory, max = 5) {
|
|
110
|
-
const text = `${String(transcript ?? '')} ${String(memory ?? '')}
|
|
109
|
+
export function extractTopicKeywords(transcript, memory, max = 5, opts = {}) {
|
|
110
|
+
const text = redactString(`${String(transcript ?? '')} ${String(memory ?? '')}`).toLowerCase();
|
|
111
111
|
const words = text.match(/[a-z][a-z0-9_-]{4,}/g) ?? [];
|
|
112
112
|
const freq = new Map();
|
|
113
113
|
for (const word of words) {
|
|
@@ -115,9 +115,11 @@ export function extractTopicKeywords(transcript, memory, max = 5) {
|
|
|
115
115
|
continue;
|
|
116
116
|
freq.set(word, (freq.get(word) ?? 0) + 1);
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
.filter(([, count]) => count >= 2)
|
|
120
|
-
|
|
118
|
+
const entries = [...freq.entries()]
|
|
119
|
+
.filter(([, count]) => count >= Math.max(1, Math.trunc(opts.minOccurrences ?? 2)));
|
|
120
|
+
if (!opts.preserveSourceOrder)
|
|
121
|
+
entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
122
|
+
return entries
|
|
121
123
|
.map(([word]) => word)
|
|
122
124
|
.slice(0, max);
|
|
123
125
|
}
|
package/dist/hub/sanitize.js
CHANGED
|
@@ -16,11 +16,12 @@ const REDACT_PATTERNS = [
|
|
|
16
16
|
/api[_-]?key[=:]\s*["']?[A-Za-z0-9-._~+/]{16,}["']?/gi,
|
|
17
17
|
/secret[=:]\s*["']?[A-Za-z0-9-._~+/]{16,}["']?/gi,
|
|
18
18
|
/password[=:]\s*["']?[^\s"',;)}\]]{6,}["']?/gi,
|
|
19
|
-
// GitHub tokens (ghp_, gho_, ghu_, ghs_, github_pat_)
|
|
19
|
+
// GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_, github_pat_)
|
|
20
20
|
/ghp_[A-Za-z0-9]{36,}/g,
|
|
21
21
|
/gho_[A-Za-z0-9]{36,}/g,
|
|
22
22
|
/ghu_[A-Za-z0-9]{36,}/g,
|
|
23
23
|
/ghs_[A-Za-z0-9]{36,}/g,
|
|
24
|
+
/ghr_[A-Za-z0-9]{36,}/g,
|
|
24
25
|
/github_pat_[A-Za-z0-9_]{22,}/g,
|
|
25
26
|
// AWS access keys
|
|
26
27
|
/AKIA[0-9A-Z]{16}/g,
|
|
@@ -139,7 +140,7 @@ const LEAK_SCANNERS = [
|
|
|
139
140
|
{ type: 'api_key', pattern: /sk-proj-[A-Za-z0-9-_]{20,}/g, suggest: 'process.env.OPENAI_API_KEY' },
|
|
140
141
|
{ type: 'api_key', pattern: /sk-ant-[A-Za-z0-9-_]{20,}/g, suggest: 'process.env.ANTHROPIC_API_KEY' },
|
|
141
142
|
{ type: 'api_key', pattern: /AKIA[0-9A-Z]{16}/g, suggest: 'process.env.AWS_ACCESS_KEY_ID' },
|
|
142
|
-
{ type: 'github_token', pattern: /
|
|
143
|
+
{ type: 'github_token', pattern: /gh(?:p|o|u|s|r)_[A-Za-z0-9]{36,}/g, suggest: 'process.env.GITHUB_TOKEN' },
|
|
143
144
|
{ type: 'github_token', pattern: /github_pat_[A-Za-z0-9_]{22,}/g, suggest: 'process.env.GITHUB_TOKEN' },
|
|
144
145
|
{ type: 'npm_token', pattern: /npm_[A-Za-z0-9]{36,}/g, suggest: 'process.env.NPM_TOKEN' },
|
|
145
146
|
{ type: 'slack_token', pattern: /xox[baprsv]-[A-Za-z0-9-]{10,}/g, suggest: 'process.env.SLACK_TOKEN' },
|
package/dist/index.d.ts
CHANGED
|
@@ -25,4 +25,6 @@ export * as shadow from './shadow/index.js';
|
|
|
25
25
|
export * as ops from './ops/index.js';
|
|
26
26
|
export * as util from './util/index.js';
|
|
27
27
|
export * as trace from './trace/index.js';
|
|
28
|
-
export * as issueReporter from './issueReporter/index.js';
|
|
28
|
+
export * as issueReporter from './issueReporter/index.js';
|
|
29
|
+
export * as feedback from './feedback/index.js';
|
|
30
|
+
export { isValidationCommandAllowed } from './verify/validation.js';
|
package/dist/index.js
CHANGED
|
@@ -25,4 +25,7 @@ export * as shadow from './shadow/index.js';
|
|
|
25
25
|
export * as ops from './ops/index.js';
|
|
26
26
|
export * as util from './util/index.js';
|
|
27
27
|
export * as trace from './trace/index.js';
|
|
28
|
-
export * as issueReporter from './issueReporter/index.js';
|
|
28
|
+
export * as issueReporter from './issueReporter/index.js';
|
|
29
|
+
export * as feedback from './feedback/index.js';
|
|
30
|
+
// Re-export commonly used validation functions at top level for convenience
|
|
31
|
+
export { isValidationCommandAllowed } from './verify/validation.js';
|
package/dist/mailbox/dispatch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mailboxClaimOwner } from './store.js';
|
|
1
2
|
/**
|
|
2
3
|
* 选择性 Material 判定(M2-7). 默认遵目录 feedsMaterial; 两类在 dispatch 决定:
|
|
3
4
|
* - asset_publish_result: 仅 rejected 进(失败=养料 #40); 通过无需.
|
|
@@ -25,10 +26,16 @@ export class Dispatcher {
|
|
|
25
26
|
}
|
|
26
27
|
async dispatchOne(e) {
|
|
27
28
|
const now = this.deps.now();
|
|
29
|
+
const claimOwner = mailboxClaimOwner(e);
|
|
30
|
+
if (!claimOwner) {
|
|
31
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial: false, note: 'claim-owner-missing' };
|
|
32
|
+
}
|
|
28
33
|
// 副作用幂等: 业务键(非默认=id)命中 → 跳过不二次执行, 直接 complete (money-safety A7/A13)
|
|
29
34
|
const sideEffecting = e.handler !== 'agent' && e.idempotencyKey !== e.id;
|
|
30
35
|
if (sideEffecting && this.deps.store.isProcessed(e.idempotencyKey)) {
|
|
31
|
-
this.deps.store.
|
|
36
|
+
if (!this.deps.store.completeClaimed(e.id, now, claimOwner)) {
|
|
37
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial: false, note: 'claim-lost' };
|
|
38
|
+
}
|
|
32
39
|
return { id: e.id, handler: e.handler, handled: true, fedMaterial: false, note: 'idempotent-skip' };
|
|
33
40
|
}
|
|
34
41
|
try {
|
|
@@ -38,14 +45,23 @@ export class Dispatcher {
|
|
|
38
45
|
await this.deps.onMaterial(e);
|
|
39
46
|
fedMaterial = true;
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
this.deps.store.
|
|
43
|
-
|
|
48
|
+
const completed = sideEffecting
|
|
49
|
+
? this.deps.store.completeClaimedAndMarkProcessed(e.id, e.idempotencyKey, result ?? null, now, claimOwner)
|
|
50
|
+
: this.deps.store.completeClaimed(e.id, now, claimOwner);
|
|
51
|
+
if (!completed) {
|
|
52
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial, note: 'claim-lost' };
|
|
53
|
+
}
|
|
44
54
|
return { id: e.id, handler: e.handler, handled: true, fedMaterial };
|
|
45
55
|
}
|
|
46
56
|
catch (err) {
|
|
47
|
-
this.deps.store.
|
|
48
|
-
return {
|
|
57
|
+
const failed = this.deps.store.failClaimed(e.id, err instanceof Error ? err.message : String(err), now, claimOwner);
|
|
58
|
+
return {
|
|
59
|
+
id: e.id,
|
|
60
|
+
handler: e.handler,
|
|
61
|
+
handled: false,
|
|
62
|
+
fedMaterial: false,
|
|
63
|
+
note: failed ? 'failed' : 'claim-lost',
|
|
64
|
+
};
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
/** 拉一批并分派. 默认 daemon 侧 core+proxy; agent 由 runtime 经 IPC 拉取. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { specForType, type Direction, type Handler } from './catalog.js';
|
|
2
2
|
export type Status = 'pending' | 'in_flight' | 'done' | 'failed' | 'expired';
|
|
3
|
-
export
|
|
3
|
+
export type Priority = 'high' | 'normal' | 'low';
|
|
4
|
+
export declare const PRIORITIES: readonly ["high", "normal", "low"];
|
|
5
|
+
export declare const ENVELOPE_SCHEMA_VERSION = "1.1.0";
|
|
4
6
|
export interface Envelope {
|
|
5
7
|
id: string;
|
|
6
8
|
type: string;
|
|
@@ -15,6 +17,7 @@ export interface Envelope {
|
|
|
15
17
|
sourceAgent: string;
|
|
16
18
|
targetAgent: string;
|
|
17
19
|
runtimeNamespace: string;
|
|
20
|
+
priority: Priority;
|
|
18
21
|
attempts: number;
|
|
19
22
|
nextRetryAt: number | null;
|
|
20
23
|
ttlAt: number | null;
|
|
@@ -22,6 +25,7 @@ export interface Envelope {
|
|
|
22
25
|
updatedAt: number;
|
|
23
26
|
schemaVersion: string;
|
|
24
27
|
feedsMaterial: boolean;
|
|
28
|
+
lastError: string | null;
|
|
25
29
|
}
|
|
26
30
|
/** 固定 envelope 字段集 (schema-snapshot 锁; 增删字段必须改此处). */
|
|
27
31
|
export declare const ENVELOPE_FIELDS: readonly (keyof Envelope)[];
|
|
@@ -37,7 +41,9 @@ export interface CreateEnvelopeInput {
|
|
|
37
41
|
sourceAgent?: string;
|
|
38
42
|
targetAgent?: string;
|
|
39
43
|
runtimeNamespace?: string;
|
|
44
|
+
priority?: unknown;
|
|
40
45
|
now?: number;
|
|
41
46
|
}
|
|
47
|
+
export declare function normalizePriority(value: unknown): Priority;
|
|
42
48
|
export declare function createEnvelope(input: CreateEnvelopeInput): Envelope;
|
|
43
49
|
export { specForType };
|
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,59 @@
|
|
|
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 DEFAULT_IMPORT_JSONL_MAX_BYTES: number;
|
|
5
|
+
export declare const DEFAULT_IMPORT_JSONL_MAX_RECORDS = 100000;
|
|
6
|
+
export declare const MAILBOX_CLAIM_OWNER: unique symbol;
|
|
7
|
+
export type ClaimedEnvelope = Envelope & {
|
|
8
|
+
readonly [MAILBOX_CLAIM_OWNER]: string;
|
|
9
|
+
};
|
|
10
|
+
export type MailboxImportRecord = Readonly<{
|
|
11
|
+
kind: 'message';
|
|
12
|
+
envelope: Readonly<Envelope>;
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
kind: 'update';
|
|
15
|
+
id: string;
|
|
16
|
+
fields: Readonly<Record<string, unknown>>;
|
|
17
|
+
}>;
|
|
18
|
+
export type PreparedMailboxImport = Readonly<{
|
|
19
|
+
records: readonly MailboxImportRecord[];
|
|
20
|
+
sourceRecords: number;
|
|
21
|
+
messageCandidates: number;
|
|
22
|
+
uniqueMessageCandidates: number;
|
|
23
|
+
updateCandidates: number;
|
|
24
|
+
}>;
|
|
25
|
+
export type MailboxImportPreview = Readonly<{
|
|
26
|
+
sourceRecords: number;
|
|
27
|
+
messageCandidates: number;
|
|
28
|
+
uniqueMessageCandidates: number;
|
|
29
|
+
updateCandidates: number;
|
|
30
|
+
insertedMessages: number;
|
|
31
|
+
updatedMessages: number;
|
|
32
|
+
managedMessages: number;
|
|
33
|
+
protectedMessages: number;
|
|
34
|
+
}>;
|
|
35
|
+
export type MailboxJsonlImportErrorCode = 'line_too_large' | 'journal_too_large' | 'too_many_records' | 'invalid_utf8' | 'invalid_json' | 'invalid_record' | 'invalid_update' | 'unsupported_operation' | 'invalid_message';
|
|
36
|
+
export declare class MailboxJsonlImportError extends Error {
|
|
37
|
+
readonly code: MailboxJsonlImportErrorCode;
|
|
38
|
+
readonly line: number;
|
|
39
|
+
constructor(code: MailboxJsonlImportErrorCode, line: number, message: string);
|
|
40
|
+
}
|
|
41
|
+
export declare function mailboxClaimOwner(envelope: Envelope): string | undefined;
|
|
4
42
|
export declare function expBackoffMs(attempt: number): number;
|
|
43
|
+
/** Read one mailbox state value without creating, migrating, or taking write ownership of the database. */
|
|
44
|
+
export declare function readMailboxState(path: string, key: string): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Parse and normalize a V1 mailbox journal without opening or mutating a V2 mailbox.
|
|
47
|
+
* The returned batch is sealed so dry-run and apply can consume the exact same records.
|
|
48
|
+
*/
|
|
49
|
+
export declare function prepareMailboxJsonlImport(source: string | number, options?: {
|
|
50
|
+
maxLineBytes?: number;
|
|
51
|
+
maxBytes?: number;
|
|
52
|
+
maxRecords?: number;
|
|
53
|
+
now?: number;
|
|
54
|
+
}): PreparedMailboxImport;
|
|
55
|
+
/** Preview a prepared import against an optional existing mailbox without creating or migrating it. */
|
|
56
|
+
export declare function previewPreparedMailboxImport(batch: PreparedMailboxImport, mailboxPath?: string): MailboxImportPreview;
|
|
5
57
|
/** mailbox sqlite 引擎 (WAL + busy_timeout). 状态机 pending→in_flight→done/failed/expired + 租约 + 重试/DLQ. */
|
|
6
58
|
export declare class MailboxStore {
|
|
7
59
|
private readonly db;
|
|
@@ -47,12 +99,31 @@ export declare class MailboxStore {
|
|
|
47
99
|
hasMessageWithIdempotencyKey(idempotencyKey: string): boolean;
|
|
48
100
|
hasMessageWithPayload(type: string, payload: unknown): boolean;
|
|
49
101
|
/** 原子 claim: pending(或租约过期的 in_flight) → in_flight + 租约. 可按 runtimeNamespace 分区. */
|
|
50
|
-
claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string):
|
|
102
|
+
claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string): ClaimedEnvelope[];
|
|
51
103
|
complete(id: string, now: number): void;
|
|
52
104
|
/** 失败: attempts<N→退避回 pending; ≥N→DLQ(不自动丢). */
|
|
53
105
|
fail(id: string, err: string, now: number, maxAttempts?: number): void;
|
|
54
106
|
/** 暂缓: transient upstream outage 不消耗 attempts, 仅设置下次可 claim 时间. */
|
|
55
107
|
defer(id: string, err: string, now: number, retryAfterMs: number): void;
|
|
108
|
+
deferUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number): boolean;
|
|
109
|
+
failUnlessProcessed(id: string, processedKey: string, err: string, now: number, maxAttempts?: number): boolean;
|
|
110
|
+
completeClaimed(id: string, now: number, claimOwner: string): boolean;
|
|
111
|
+
/** Atomically complete the current worker's lease and persist its idempotency result. */
|
|
112
|
+
completeClaimedAndMarkProcessed(id: string, processedKey: string, result: unknown, now: number, claimOwner: string): boolean;
|
|
113
|
+
renewClaim(id: string, now: number, leaseMs: number, claimOwner: string): boolean;
|
|
114
|
+
setClaimedCheckpoint(id: string, checkpoint: unknown, now: number, claimOwner: string): boolean;
|
|
115
|
+
getClaimedCheckpoint(id: string): unknown | undefined;
|
|
116
|
+
deferClaimed(id: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
|
|
117
|
+
failClaimed(id: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
118
|
+
/** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
|
|
119
|
+
deferClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
|
|
120
|
+
/** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
|
|
121
|
+
failClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
122
|
+
/** Atomically fail a non-leased intent and persist the replayable terminal result. */
|
|
123
|
+
failAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, maxAttempts?: number): boolean;
|
|
124
|
+
/** Atomically fail the current worker's leased intent and persist the replayable terminal result. */
|
|
125
|
+
failClaimedAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
|
|
126
|
+
private transitionUnlessProcessed;
|
|
56
127
|
/** DLQ 重放(人工/agent 显式, 不自动丢). */
|
|
57
128
|
replayDlq(id: string, now: number): void;
|
|
58
129
|
dlq(): Envelope[];
|
|
@@ -73,16 +144,31 @@ export declare class MailboxStore {
|
|
|
73
144
|
id: string;
|
|
74
145
|
type: string;
|
|
75
146
|
status: Status;
|
|
147
|
+
priority: Envelope['priority'];
|
|
76
148
|
attempts: number;
|
|
77
149
|
nextRetryAt: number | null;
|
|
78
150
|
ttlAt: number | null;
|
|
79
151
|
dlq: boolean;
|
|
152
|
+
lastError: string | null;
|
|
80
153
|
} | undefined;
|
|
81
154
|
/** 幂等(A13): 副作用 handler 用 idempotencyKey 去重; 命中返缓存不重跑. */
|
|
82
155
|
isProcessed(key: string): boolean;
|
|
83
156
|
getProcessed(key: string): unknown;
|
|
84
157
|
markProcessed(key: string, result: unknown, now: number): void;
|
|
85
|
-
|
|
86
|
-
|
|
158
|
+
replaceProcessed(key: string, result: unknown, now: number): void;
|
|
159
|
+
/** Persist a monotonic outcome and its lightweight concurrency marker in one SQLite transaction. */
|
|
160
|
+
replaceProcessedWithMarker(key: string, result: unknown, markerKey: string, markerResult: unknown, now: number): void;
|
|
161
|
+
/** Backfill a concurrency marker only when the durable source result still satisfies the caller's predicate. */
|
|
162
|
+
markProcessedIf(sourceKey: string, markerKey: string, markerResult: unknown, now: number, matches: (sourceResult: unknown) => boolean): boolean;
|
|
163
|
+
deleteProcessed(keys: readonly string[]): void;
|
|
164
|
+
private messageClaimOwner;
|
|
165
|
+
/** Build the target-aware plan shared by preview and apply. */
|
|
166
|
+
private buildPreparedImportPlan;
|
|
167
|
+
/** Preview the exact import result against the current mailbox snapshot without writing to it. */
|
|
168
|
+
previewPreparedImport(batch: PreparedMailboxImport): MailboxImportPreview;
|
|
169
|
+
private writeImportedEnvelopeFields;
|
|
170
|
+
/** Apply a parsed batch; planning and writes share one transaction and one normalized journal. */
|
|
171
|
+
importPrepared(batch: PreparedMailboxImport): number;
|
|
172
|
+
importJsonl(source: string | number): number;
|
|
87
173
|
close(): void;
|
|
88
174
|
}
|