@evomap/evolver-proxy 2.0.0-beta.1 → 2.0.0-beta.4

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 (37) hide show
  1. package/dist/bin/evolver-proxy.d.ts +67 -6
  2. package/dist/bin/evolver-proxy.js +389 -75
  3. package/dist/bin/proxySettings.d.ts +2 -0
  4. package/dist/bin/proxySettings.js +8 -1
  5. package/dist/daemon/collaborationFacade.d.ts +56 -0
  6. package/dist/daemon/collaborationFacade.js +877 -0
  7. package/dist/daemon/proxyDaemon.d.ts +3 -0
  8. package/dist/daemon/proxyDaemon.js +111 -0
  9. package/dist/daemon/selectHub.js +17 -1
  10. package/dist/llm/traceControl.js +1 -1
  11. package/dist/private/adapterLoader.d.ts +6 -1
  12. package/dist/private/adapterLoader.js +14 -3
  13. package/dist/router/messagesRoute.d.ts +13 -0
  14. package/dist/router/messagesRoute.js +56 -0
  15. package/dist/selfUpdate/executor.d.ts +10 -5
  16. package/dist/selfUpdate/executor.js +81 -6
  17. package/dist/selfUpdate/failureCodes.d.ts +6 -0
  18. package/dist/selfUpdate/failureCodes.js +6 -0
  19. package/dist/selfUpdate/index.d.ts +4 -1
  20. package/dist/selfUpdate/index.js +4 -1
  21. package/dist/selfUpdate/lastUpdate.d.ts +3 -1
  22. package/dist/selfUpdate/lastUpdate.js +37 -6
  23. package/dist/selfUpdate/releaseBinary.d.ts +10 -0
  24. package/dist/selfUpdate/releaseBinary.js +43 -6
  25. package/dist/selfUpdate/transaction.d.ts +109 -0
  26. package/dist/selfUpdate/transaction.js +1174 -0
  27. package/dist/selfUpdate/unixController.d.ts +15 -0
  28. package/dist/selfUpdate/unixController.js +186 -0
  29. package/dist/selfUpdate/version.d.ts +6 -2
  30. package/dist/selfUpdate/version.js +5 -3
  31. package/dist/selfUpdate/windowsController.d.ts +23 -0
  32. package/dist/selfUpdate/windowsController.js +274 -0
  33. package/dist/selfUpdate/windowsUpdater.d.ts +79 -0
  34. package/dist/selfUpdate/windowsUpdater.js +715 -0
  35. package/dist/sync/engine.d.ts +6 -5
  36. package/dist/sync/engine.js +102 -58
  37. package/package.json +8 -3
@@ -19,6 +19,12 @@ export interface SyncEngineDeps {
19
19
  now: () => number;
20
20
  runtimeNamespace?: string;
21
21
  leaseMs?: number;
22
+ /** Finalize durable facade state after the Hub accepted one outbound envelope. */
23
+ onOutboundSucceeded?: (envelope: Envelope, result: unknown) => void | Promise<void>;
24
+ /** Cache a durable facade terminal result after SyncEngine moved the envelope to DLQ. */
25
+ onOutboundTerminal?: (envelope: Envelope, error: unknown) => void | Promise<void>;
26
+ /** Canonicalize an inbound envelope before durable insertion. */
27
+ normalizeInboundEnvelope?: (envelope: Envelope) => Envelope;
22
28
  onOutboundFlushed?: (result: OutboundResult) => void | Promise<void>;
23
29
  env?: NodeJS.ProcessEnv;
24
30
  }
@@ -36,11 +42,6 @@ export interface InboundResult {
36
42
  nextPollAfterMs?: number;
37
43
  hasMore: boolean;
38
44
  }
39
- /**
40
- * SyncEngine(M6-2): proxy↔hub 双向同步. 移植 v1 sync/{engine,outbound,inbound} 到 TS,
41
- * hub I/O 全走 HubCapability.mailbox(非裸 hubFetch). tick 方法纯逻辑(注入 now), 可对 FakeHubCapability 确定性测;
42
- * 定时器循环(start/stop)是薄包装, 由 M6-4 daemon 装配驱动.
43
- */
44
45
  export declare class SyncEngine {
45
46
  private readonly deps;
46
47
  private lastActivityAt;
@@ -1,4 +1,5 @@
1
1
  import { mailbox, hub as hubNs } from '@evomap/evolver-core';
2
+ import { HubClientError } from '@evomap/evolver-adapter-public';
2
3
  import { normalizeProxyTraceOutboundPayload } from '../llm/traceBackfill.js';
3
4
  import { applyTraceCollectionConfig } from '../llm/traceControl.js';
4
5
  import { hubAuthFailureHint } from '../daemon/selectHub.js';
@@ -15,8 +16,12 @@ const STATE = {
15
16
  lastError: 'sync:last_error',
16
17
  authStatus: 'hub:auth_status',
17
18
  };
18
- function isTerminal(err) {
19
- return err instanceof hubNs.PublishRejectedError && err.terminal === true;
19
+ function isTerminal(err, envelopeType) {
20
+ if (err instanceof hubNs.PublishRejectedError && err.terminal === true)
21
+ return true;
22
+ return (envelopeType === 'task_claim' || envelopeType === 'task_complete')
23
+ && err instanceof HubClientError
24
+ && (err.status === 404 || err.status === 409);
20
25
  }
21
26
  function isRetryableRejection(err) {
22
27
  return err instanceof hubNs.PublishRejectedError
@@ -27,9 +32,30 @@ function isHubUnreachable(err) {
27
32
  const e = err;
28
33
  return e?.name === 'HubUnreachableError' || e?.code === 'HUB_UNREACHABLE';
29
34
  }
35
+ function isRetryableTransportError(err) {
36
+ if (isRetryableRejection(err) || isHubUnreachable(err)
37
+ || (err instanceof HubClientError && err.status === 429))
38
+ return true;
39
+ const e = err;
40
+ const rawStatus = e?.statusCode ?? e?.status;
41
+ const status = typeof rawStatus === 'number' ? rawStatus : Number(rawStatus);
42
+ if (Number.isFinite(status) && status >= 500 && status <= 599)
43
+ return true;
44
+ if (e?.retryable === true)
45
+ return true;
46
+ const signature = `${String(e?.name ?? '')} ${String(e?.code ?? '')} ${err instanceof Error ? err.message : ''}`;
47
+ return /\b5\d\d\b|ECONN|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|fetch/i.test(signature);
48
+ }
30
49
  function retryAfterMs(err) {
50
+ const body = err instanceof HubClientError && err.body && typeof err.body === 'object' && !Array.isArray(err.body)
51
+ ? err.body
52
+ : undefined;
53
+ const bodyRetryMs = Number(body?.['retry_after_ms'] ?? body?.['retryAfterMs']);
54
+ const bodyRetrySeconds = Number(body?.['retry_after'] ?? body?.['retryAfter']);
31
55
  const retry = err?.retryAfterMs
32
- ?? err?.details?.retryAfterMs;
56
+ ?? err?.details?.retryAfterMs
57
+ ?? (Number.isFinite(bodyRetryMs) ? bodyRetryMs : undefined)
58
+ ?? (Number.isFinite(bodyRetrySeconds) ? bodyRetrySeconds * 1_000 : undefined);
33
59
  return Math.max(1_000, typeof retry === 'number' && Number.isFinite(retry) ? retry : 60_000);
34
60
  }
35
61
  function errorMessage(err) {
@@ -74,6 +100,53 @@ function envelopeToAgentEvent(e) {
74
100
  * hub I/O 全走 HubCapability.mailbox(非裸 hubFetch). tick 方法纯逻辑(注入 now), 可对 FakeHubCapability 确定性测;
75
101
  * 定时器循环(start/stop)是薄包装, 由 M6-4 daemon 装配驱动.
76
102
  */
103
+ function applyMailboxPushManyOutcomes(group, outcomes, deps) {
104
+ const byId = new Map(outcomes.map((outcome) => [outcome.id, outcome]));
105
+ const result = { sent: 0, failed: 0, terminal: 0, deferred: 0, completedDuplicates: 0 };
106
+ for (const pushed of group) {
107
+ const outcome = byId.get(pushed.event.id) ?? byId.get(pushed.original.id);
108
+ if (outcome?.status === 'failed') {
109
+ const msg = redactAndTruncate(outcome.reason ?? 'mailbox_push_rejected');
110
+ const authLike = isAuthError(msg);
111
+ result.firstFailure ??= msg;
112
+ const retryable = outcome.terminal !== true
113
+ && (outcome.retryable === true || (typeof outcome.retryAfterMs === 'number' && Number.isFinite(outcome.retryAfterMs)));
114
+ if (authLike || retryable) {
115
+ const nowAfterFailure = deps.now();
116
+ const retry = Math.max(1_000, typeof outcome.retryAfterMs === 'number' && Number.isFinite(outcome.retryAfterMs) ? outcome.retryAfterMs : 60_000);
117
+ result.deferredFailure ??= { msg, retryAfterMs: retry };
118
+ if (authLike) {
119
+ result.authFailed = true;
120
+ result.authErrorMessage ??= msg;
121
+ }
122
+ deps.store.defer(pushed.original.id, msg, nowAfterFailure, retry);
123
+ for (const duplicate of pushed.duplicates)
124
+ deps.store.defer(duplicate.id, msg, nowAfterFailure, retry);
125
+ result.deferred += 1 + pushed.duplicates.length;
126
+ }
127
+ else {
128
+ const maxAttempts = outcome.terminal ? 1 : undefined;
129
+ deps.store.fail(pushed.original.id, msg, deps.now(), maxAttempts);
130
+ for (const duplicate of pushed.duplicates)
131
+ deps.store.fail(duplicate.id, msg, deps.now(), maxAttempts);
132
+ if (outcome.terminal)
133
+ result.terminal += 1 + pushed.duplicates.length;
134
+ else
135
+ result.failed += 1 + pushed.duplicates.length;
136
+ }
137
+ }
138
+ else {
139
+ deps.store.complete(pushed.original.id, deps.now());
140
+ for (const duplicate of pushed.duplicates)
141
+ deps.store.complete(duplicate.id, deps.now());
142
+ if (pushed.dedupKey)
143
+ deps.store.markProcessed(pushed.dedupKey, { type: pushed.original.type }, deps.now());
144
+ result.sent += 1;
145
+ result.completedDuplicates += pushed.duplicates.length;
146
+ }
147
+ }
148
+ return result;
149
+ }
77
150
  export class SyncEngine {
78
151
  deps;
79
152
  lastActivityAt;
@@ -133,57 +206,21 @@ export class SyncEngine {
133
206
  try {
134
207
  const result = await this.deps.hub.mailbox.pushMany(group.map((entry) => entry.event));
135
208
  if (isMailboxPushManyResult(result)) {
136
- const byId = new Map(result.outcomes.map((outcome) => [outcome.id, outcome]));
137
- let firstFailure;
138
- let deferredFailure;
139
- for (const pushed of group) {
140
- const outcome = byId.get(pushed.event.id) ?? byId.get(pushed.original.id);
141
- if (outcome?.status === 'failed') {
142
- const msg = redactAndTruncate(outcome.reason ?? 'mailbox_push_rejected');
143
- const authLike = isAuthError(msg);
144
- firstFailure ??= msg;
145
- const retryable = outcome.terminal !== true
146
- && (outcome.retryable === true || (typeof outcome.retryAfterMs === 'number' && Number.isFinite(outcome.retryAfterMs)));
147
- if (authLike || retryable) {
148
- const nowAfterFailure = this.deps.now();
149
- const retry = Math.max(1_000, typeof outcome.retryAfterMs === 'number' && Number.isFinite(outcome.retryAfterMs) ? outcome.retryAfterMs : 60_000);
150
- deferredFailure ??= { msg, retryAfterMs: retry };
151
- if (authLike) {
152
- authFailed = true;
153
- authErrorMessage ??= msg;
154
- }
155
- this.deps.store.defer(pushed.original.id, msg, nowAfterFailure, retry);
156
- for (const duplicate of pushed.duplicates)
157
- this.deps.store.defer(duplicate.id, msg, nowAfterFailure, retry);
158
- deferred += 1 + pushed.duplicates.length;
159
- }
160
- else {
161
- const maxAttempts = outcome.terminal ? 1 : undefined;
162
- this.deps.store.fail(pushed.original.id, msg, this.deps.now(), maxAttempts);
163
- for (const duplicate of pushed.duplicates)
164
- this.deps.store.fail(duplicate.id, msg, this.deps.now(), maxAttempts);
165
- if (outcome.terminal)
166
- terminal += 1 + pushed.duplicates.length;
167
- else
168
- failed += 1 + pushed.duplicates.length;
169
- }
170
- }
171
- else {
172
- this.deps.store.complete(pushed.original.id, this.deps.now());
173
- for (const duplicate of pushed.duplicates)
174
- this.deps.store.complete(duplicate.id, this.deps.now());
175
- if (pushed.dedupKey)
176
- this.deps.store.markProcessed(pushed.dedupKey, { type: pushed.original.type }, this.deps.now());
177
- sent += 1;
178
- completedDuplicates += pushed.duplicates.length;
179
- }
180
- }
181
- if (firstFailure)
182
- this.markError(`outbound: ${firstFailure}`, isAuthError(firstFailure));
183
- if (deferredFailure) {
209
+ const applied = applyMailboxPushManyOutcomes(group, result.outcomes, this.deps);
210
+ sent += applied.sent;
211
+ failed += applied.failed;
212
+ terminal += applied.terminal;
213
+ deferred += applied.deferred;
214
+ completedDuplicates += applied.completedDuplicates;
215
+ if (applied.authFailed)
216
+ authFailed = true;
217
+ authErrorMessage ??= applied.authErrorMessage;
218
+ if (applied.firstFailure)
219
+ this.markError(`outbound: ${applied.firstFailure}`, isAuthError(applied.firstFailure));
220
+ if (applied.deferredFailure) {
184
221
  const nowAfterFailure = this.deps.now();
185
222
  for (const pending of batch.slice(j)) {
186
- this.deps.store.defer(pending.id, deferredFailure.msg, nowAfterFailure, deferredFailure.retryAfterMs);
223
+ this.deps.store.defer(pending.id, applied.deferredFailure.msg, nowAfterFailure, applied.deferredFailure.retryAfterMs);
187
224
  deferred += 1;
188
225
  }
189
226
  break;
@@ -224,7 +261,7 @@ export class SyncEngine {
224
261
  authErrorMessage ??= msg;
225
262
  break;
226
263
  }
227
- else if (isTerminal(err)) {
264
+ else if (isTerminal(err, group[0].original.type)) {
228
265
  for (const pushed of group) {
229
266
  this.deps.store.fail(pushed.original.id, msg, this.deps.now(), 1);
230
267
  for (const duplicate of pushed.duplicates)
@@ -233,7 +270,7 @@ export class SyncEngine {
233
270
  }
234
271
  i = j - 1;
235
272
  }
236
- else if (isRetryableRejection(err) || isHubUnreachable(err)) {
273
+ else if (isRetryableTransportError(err)) {
237
274
  const nowAfterFailure = this.deps.now();
238
275
  const retry = retryAfterMs(err);
239
276
  for (const pushed of group) {
@@ -273,7 +310,8 @@ export class SyncEngine {
273
310
  continue;
274
311
  }
275
312
  try {
276
- await this.deps.proxyHandler(guard.envelope);
313
+ const handlerResult = await this.deps.proxyHandler(guard.envelope);
314
+ await this.deps.onOutboundSucceeded?.(e, handlerResult);
277
315
  this.deps.store.complete(e.id, this.deps.now());
278
316
  if (outboundDedupKey)
279
317
  this.deps.store.markProcessed(outboundDedupKey, { type: e.type }, this.deps.now());
@@ -294,11 +332,15 @@ export class SyncEngine {
294
332
  authErrorMessage ??= msg;
295
333
  break;
296
334
  }
297
- else if (isTerminal(err)) {
335
+ else if (isTerminal(err, e.type)) {
336
+ // A concurrent facade attempt may already have finalized this durable intent successfully.
337
+ if (this.deps.store.getById(e.id)?.status === 'done')
338
+ continue;
298
339
  this.deps.store.fail(e.id, msg, this.deps.now(), 1); // maxAttempts=1 → 直进 DLQ, 不反复打经济端点
340
+ await this.deps.onOutboundTerminal?.(e, err);
299
341
  terminal += 1;
300
342
  }
301
- else if (isRetryableRejection(err) || isHubUnreachable(err)) {
343
+ else if (isRetryableTransportError(err)) {
302
344
  const nowAfterFailure = this.deps.now();
303
345
  const retry = retryAfterMs(err);
304
346
  for (const pending of batch.slice(i)) {
@@ -347,7 +389,8 @@ export class SyncEngine {
347
389
  this.deps.store.setState(CURSOR_KEY, ev.cursor);
348
390
  continue;
349
391
  }
350
- const env = this.toEnvelope(ev);
392
+ const rawEnvelope = this.toEnvelope(ev);
393
+ const env = rawEnvelope ? (this.deps.normalizeInboundEnvelope?.(rawEnvelope) ?? rawEnvelope) : null;
351
394
  if (env) {
352
395
  this.deps.store.send(env);
353
396
  this.deps.store.markProcessed(dedupKey, { type: ev.type }, this.deps.now());
@@ -450,6 +493,7 @@ export class SyncEngine {
450
493
  try {
451
494
  return mailbox.createEnvelope({
452
495
  type: ev.type, payload: ev.payload, idempotencyKey: `inbound:${ev.id}`,
496
+ ...(ev.refId ? { replyTo: ev.refId } : {}),
453
497
  ...(this.deps.runtimeNamespace ? { runtimeNamespace: this.deps.runtimeNamespace } : {}),
454
498
  now: this.deps.now(),
455
499
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver-proxy",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "系统级 mailbox/hub 同步 daemon (Node)",
@@ -26,8 +26,12 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@aws-sdk/client-bedrock-runtime": "^3.1053.0",
29
- "@evomap/evolver-adapter-public": "2.0.0-beta.1",
30
- "@evomap/evolver-core": "2.0.0-beta.1"
29
+ "@evomap/evolver-adapter-public": "2.0.0-beta.4",
30
+ "@evomap/evolver-core": "2.0.0-beta.4"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/EvoMap/evolver.git"
31
35
  },
32
36
  "publishConfig": {
33
37
  "access": "public",
@@ -35,6 +39,7 @@
35
39
  },
36
40
  "files": [
37
41
  "dist/",
42
+ "assets/",
38
43
  "README.md",
39
44
  "package.json"
40
45
  ]