@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22

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.
Files changed (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. package/package.json +12 -4
@@ -1,6 +1,6 @@
1
1
  import type { Envelope } from './envelope.js';
2
2
  import type { Handler } from './catalog.js';
3
- import type { MailboxStore } from './store.js';
3
+ import { type MailboxStore } from './store.js';
4
4
  export interface DispatchResult {
5
5
  id: string;
6
6
  handler: Handler;
@@ -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.complete(e.id, now);
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
- if (sideEffecting)
42
- this.deps.store.markProcessed(e.idempotencyKey, result ?? null, now);
43
- this.deps.store.complete(e.id, now);
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.fail(e.id, err instanceof Error ? err.message : String(err), now);
48
- return { id: e.id, handler: e.handler, handled: false, fedMaterial: false, note: 'failed' };
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 declare const ENVELOPE_SCHEMA_VERSION = "1.0.0";
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 };
@@ -1,11 +1,13 @@
1
1
  import { ulid as makeUlid } from 'ulid';
2
2
  import { specForType, assertKnownType } from './catalog.js';
3
- export const ENVELOPE_SCHEMA_VERSION = '1.0.0';
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 { MailboxStore } from './store.js';
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;
@@ -20,6 +22,8 @@ export interface IpcRouteContext {
20
22
  route: string;
21
23
  now: number;
22
24
  store: MailboxStore;
25
+ /** 请求连接中止或响应提前关闭时触发;长任务应将其传给可取消的宿主执行器。 */
26
+ signal?: AbortSignal;
23
27
  readJson: () => Promise<unknown>;
24
28
  json: (code: number, body: unknown) => void;
25
29
  }
@@ -34,6 +38,7 @@ export declare class MailboxIpcServer {
34
38
  private readonly token;
35
39
  private readonly host;
36
40
  private readonly now;
41
+ private readonly runtimeNamespace;
37
42
  private readonly extraRoutes;
38
43
  private readonly onSend;
39
44
  private readonly onAuthFailure;
@@ -42,5 +47,10 @@ export declare class MailboxIpcServer {
42
47
  close(): Promise<void>;
43
48
  private handle;
44
49
  private readJson;
50
+ private resolveSendNamespace;
51
+ private resolveRuntimeNamespace;
52
+ private messageInRuntime;
53
+ private canMutateMessage;
45
54
  private json;
46
- }
55
+ }
56
+ 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;
@@ -57,6 +84,20 @@ export class MailboxIpcServer {
57
84
  return new Promise((resolve, reject) => this.server.close((e) => (e ? reject(e) : resolve())));
58
85
  }
59
86
  async handle(req, res) {
87
+ const requestAbort = new AbortController();
88
+ const abortRequest = () => {
89
+ if (!requestAbort.signal.aborted)
90
+ requestAbort.abort(new Error('ipc_request_aborted'));
91
+ };
92
+ // 请求/响应 close 既可能表示客户端提前断开,也可能只是正常响应结束。
93
+ // 只有响应尚未完成时才传播取消,避免正常返回把后台验证误判为客户端取消。
94
+ const abortIfResponseOpen = () => {
95
+ if (!res.writableEnded && !res.writableFinished)
96
+ abortRequest();
97
+ };
98
+ req.once('aborted', abortIfResponseOpen);
99
+ req.socket.once('close', abortIfResponseOpen);
100
+ res.once('close', abortIfResponseOpen);
60
101
  try {
61
102
  const ra = req.socket.remoteAddress ?? '';
62
103
  if (!LOOPBACK.has(ra))
@@ -82,6 +123,7 @@ export class MailboxIpcServer {
82
123
  route,
83
124
  now,
84
125
  store: this.store,
126
+ signal: requestAbort.signal,
85
127
  readJson: () => this.readJson(req),
86
128
  json: (code, body) => this.json(res, code, body),
87
129
  });
@@ -90,47 +132,109 @@ export class MailboxIpcServer {
90
132
  }
91
133
  if (route === 'POST /mailbox/send') {
92
134
  const body = (await this.readJson(req));
93
- const env = createEnvelope({ ...body, now });
135
+ if (body.priority !== undefined && !PRIORITIES.has(body.priority)) {
136
+ throw new Error('priority must be high, normal, or low');
137
+ }
138
+ const legacyRef = typeof body.ref_id === 'string' && body.ref_id ? body.ref_id : undefined;
139
+ const runtimeNamespace = this.resolveSendNamespace(body);
140
+ let env = createEnvelope({
141
+ ...body,
142
+ runtimeNamespace,
143
+ correlationId: body.correlationId ?? legacyRef,
144
+ now,
145
+ });
146
+ if (body.expires_at !== undefined) {
147
+ if (typeof body.expires_at !== 'number' || !Number.isFinite(body.expires_at)) {
148
+ throw new Error('expires_at must be a finite epoch-millisecond number');
149
+ }
150
+ env = { ...env, ttlAt: body.expires_at };
151
+ }
94
152
  const r = this.store.send(env);
95
153
  if (r.stored)
96
154
  this.onSend?.(env, r);
97
- return this.json(res, 200, { id: env.id, receiptId: r.receiptId, stored: r.stored, correlationId: env.correlationId });
155
+ return this.json(res, 200, {
156
+ id: env.id,
157
+ receiptId: r.receiptId,
158
+ stored: r.stored,
159
+ correlationId: env.correlationId,
160
+ message_id: env.id,
161
+ status: env.status,
162
+ });
98
163
  }
99
164
  if (route === 'POST /mailbox/claim') {
100
165
  const b = (await this.readJson(req));
101
- const got = this.store.claim(b.handler, b.limit ?? 16, b.leaseMs ?? 30_000, now, b.runtimeNamespace);
102
- return this.json(res, 200, { messages: got });
166
+ const runtimeNamespace = this.resolveRuntimeNamespace(b.runtimeNamespace);
167
+ const got = this.store.claim(b.handler, b.limit ?? 16, b.leaseMs ?? 30_000, now, runtimeNamespace);
168
+ return this.json(res, 200, { messages: got.map(serializeClaimedMessage) });
103
169
  }
104
170
  if (route === 'POST /mailbox/complete') {
105
171
  const b = (await this.readJson(req));
106
- this.store.complete(b.id, now);
172
+ if (!this.canMutateMessage(b.id))
173
+ return this.json(res, 404, { error: 'not found' });
174
+ const claimToken = requiredClaimToken(b.claimToken);
175
+ if (!this.store.completeClaimed(b.id, now, claimToken)) {
176
+ return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
177
+ }
107
178
  return this.json(res, 200, { ok: true });
108
179
  }
109
180
  if (route === 'POST /mailbox/fail') {
110
181
  const b = (await this.readJson(req));
111
- this.store.fail(b.id, b.error ?? 'ipc fail', now);
182
+ if (!this.canMutateMessage(b.id))
183
+ return this.json(res, 404, { error: 'not found' });
184
+ const claimToken = requiredClaimToken(b.claimToken);
185
+ if (!this.store.failClaimed(b.id, b.error ?? 'ipc fail', now, claimToken)) {
186
+ return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
187
+ }
112
188
  return this.json(res, 200, { ok: true });
113
189
  }
114
- if (req.method === 'GET' && url.pathname === '/mailbox/status') {
115
- const id = url.searchParams.get('id') ?? '';
190
+ const statusPathMatch = req.method === 'GET' ? /^\/mailbox\/status\/([^/]+)$/.exec(url.pathname) : null;
191
+ if (req.method === 'GET' && (url.pathname === '/mailbox/status' || statusPathMatch)) {
192
+ const id = statusPathMatch ? decodeURIComponent(statusPathMatch[1] ?? '') : url.searchParams.get('id') ?? '';
193
+ const message = this.messageInRuntime(id);
194
+ if (!message)
195
+ return this.json(res, 404, { error: 'not found' });
196
+ if (statusPathMatch) {
197
+ return this.json(res, 200, legacyMailboxMessage(message));
198
+ }
116
199
  const s = this.store.getStatus(id);
117
200
  return s ? this.json(res, 200, s) : this.json(res, 404, { error: 'not found' });
118
201
  }
119
202
  if (req.method === 'GET' && url.pathname === '/mailbox/list') {
120
- const status = url.searchParams.get('status') ?? undefined;
203
+ const type = url.searchParams.get('type') ?? undefined;
204
+ const legacy = type !== undefined;
205
+ const rawStatus = url.searchParams.get('status') ?? undefined;
206
+ const statusValue = legacyMailboxStatus(rawStatus);
207
+ if (rawStatus !== undefined && statusValue === undefined)
208
+ throw new Error('invalid status');
209
+ const directionValue = url.searchParams.get('direction') ?? undefined;
210
+ if (directionValue !== undefined && !DIRECTIONS.has(directionValue)) {
211
+ throw new Error('invalid direction');
212
+ }
121
213
  const got = this.store.list({
122
- status: status,
214
+ status: statusValue,
123
215
  handler: url.searchParams.get('handler') ?? undefined,
124
- runtimeNamespace: url.searchParams.get('runtimeNamespace') ?? undefined,
125
- limit: Number(url.searchParams.get('limit') ?? 200),
216
+ runtimeNamespace: this.resolveRuntimeNamespace(url.searchParams.get('runtimeNamespace') ?? undefined),
217
+ type,
218
+ direction: directionValue,
219
+ newestFirst: legacy,
220
+ offset: optionalQueryInteger(url, 'offset'),
221
+ limit: optionalQueryInteger(url, 'limit') ?? 200,
222
+ });
223
+ return this.json(res, 200, {
224
+ messages: legacy ? got.map(legacyMailboxMessage) : got,
225
+ count: got.length,
126
226
  });
127
- return this.json(res, 200, { messages: got });
128
227
  }
129
228
  return this.json(res, 404, { error: 'unknown route' });
130
229
  }
131
230
  catch (e) {
132
231
  return this.json(res, 400, { error: e instanceof Error ? e.message : String(e), code: 'invalid_request' });
133
232
  }
233
+ finally {
234
+ req.off('aborted', abortIfResponseOpen);
235
+ req.socket.off('close', abortIfResponseOpen);
236
+ res.off('close', abortIfResponseOpen);
237
+ }
134
238
  }
135
239
  readJson(req) {
136
240
  return new Promise((resolve, reject) => {
@@ -161,8 +265,74 @@ export class MailboxIpcServer {
161
265
  req.on('error', reject);
162
266
  });
163
267
  }
268
+ resolveSendNamespace(body) {
269
+ const bound = this.runtimeNamespace;
270
+ if (bound === undefined)
271
+ return body.runtimeNamespace;
272
+ const requested = body.runtimeNamespace;
273
+ if (requested !== undefined && (typeof requested !== 'string' || requested !== bound)) {
274
+ throw new Error('runtimeNamespace does not match the IPC server namespace');
275
+ }
276
+ const channel = body.channel;
277
+ if (channel !== undefined && (typeof channel !== 'string'
278
+ || (channel !== 'evomap-hub' && channel !== bound))) {
279
+ throw new Error('channel does not match the IPC server namespace');
280
+ }
281
+ return bound;
282
+ }
283
+ resolveRuntimeNamespace(requested) {
284
+ if (requested !== undefined && typeof requested !== 'string') {
285
+ throw new Error('runtimeNamespace must be a string');
286
+ }
287
+ const bound = this.runtimeNamespace;
288
+ if (bound === undefined)
289
+ return requested;
290
+ if (requested !== undefined && requested !== bound) {
291
+ throw new Error('runtimeNamespace does not match the IPC server namespace');
292
+ }
293
+ return bound;
294
+ }
295
+ messageInRuntime(id) {
296
+ const message = this.store.getById(id);
297
+ if (this.runtimeNamespace !== undefined && message?.runtimeNamespace !== this.runtimeNamespace)
298
+ return undefined;
299
+ return message;
300
+ }
301
+ canMutateMessage(id) {
302
+ return this.messageInRuntime(id) !== undefined;
303
+ }
164
304
  json(res, code, body) {
165
305
  res.writeHead(code, { 'content-type': 'application/json' });
166
306
  res.end(JSON.stringify(body));
167
307
  }
308
+ }
309
+ export function legacyMailboxMessage(message) {
310
+ return {
311
+ id: message.id,
312
+ message_id: message.id,
313
+ channel: message.runtimeNamespace === 'default' ? 'evomap-hub' : message.runtimeNamespace,
314
+ direction: message.direction,
315
+ type: message.type,
316
+ status: message.status === 'done' ? (message.direction === 'inbound' ? 'delivered' : 'synced') : message.status,
317
+ payload: message.payload,
318
+ priority: message.priority,
319
+ ref_id: message.replyTo ?? message.correlationId,
320
+ created_at: message.createdAt,
321
+ synced_at: message.status === 'done' ? message.updatedAt : null,
322
+ expires_at: message.ttlAt,
323
+ retry_count: message.attempts,
324
+ next_retry_at: message.nextRetryAt,
325
+ error: message.lastError,
326
+ };
327
+ }
328
+ function legacyMailboxStatus(value) {
329
+ if (value === undefined)
330
+ return undefined;
331
+ if (STATUSES.has(value))
332
+ return value;
333
+ if (value === 'delivered' || value === 'synced' || value === 'acked')
334
+ return 'done';
335
+ if (value === 'rejected')
336
+ return 'failed';
337
+ return undefined;
168
338
  }
@@ -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): Envelope[];
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
- /** v1 messages.jsonl sqlite 迁移(幂等可重入). */
86
- importJsonl(path: string): number;
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
  }