@evomap/evolver 1.89.14 → 1.89.15
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/index.js +219 -29
- package/package.json +1 -1
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/forceUpdate.js +499 -119
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/cliContracts.js +1154 -0
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationDistiller.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/issueReporter.js +86 -0
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/sanitize.js +20 -4
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/signals.js +33 -9
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +4 -4
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +233 -33
- package/src/proxy/sync/inbound.js +5 -4
- package/src/proxy/sync/outbound.js +3 -2
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
isHubUnreachableError,
|
|
12
12
|
readHubResponseJson,
|
|
13
13
|
readHubResponseText,
|
|
14
|
+
sanitizeHubResponseForLog,
|
|
14
15
|
throwIfHubUnreachableResponse,
|
|
15
16
|
} = require('../../gep/hubFetch');
|
|
16
17
|
const { getEvomapPath } = require('../../gep/paths');
|
|
@@ -367,6 +368,81 @@ function parseNodeSecretVersion(value) {
|
|
|
367
368
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
const NODE_SECRET_RE = /^[a-f0-9]{64}$/i;
|
|
372
|
+
const NODE_SECRET_SUPPRESSION_RE = /^sha256:[a-f0-9]{64}$/i;
|
|
373
|
+
const SECRET_DIVERGENCE_ERROR = 'secret_diverged_cleared';
|
|
374
|
+
const SECRET_DIVERGENCE_REASON_CODES = [
|
|
375
|
+
'node_secret_invalid',
|
|
376
|
+
'invalid_secret',
|
|
377
|
+
'rotation_requires_current_secret',
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
function validNodeSecret(value) {
|
|
381
|
+
return typeof value === 'string' && NODE_SECRET_RE.test(value);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function getEnvNodeSecret() {
|
|
385
|
+
return ((process.env.A2A_NODE_SECRET || process.env.EVOMAP_NODE_SECRET || '').trim() || null);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function fingerprintNodeSecret(secret) {
|
|
389
|
+
if (!validNodeSecret(secret)) return null;
|
|
390
|
+
const normalized = String(secret).trim().toLowerCase();
|
|
391
|
+
return 'sha256:' + crypto.createHash('sha256').update(normalized).digest('hex');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function truthyState(value) {
|
|
395
|
+
const v = String(value || '').trim().toLowerCase();
|
|
396
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function matchSecretDivergenceReason(value) {
|
|
400
|
+
const reason = String(value || '').trim().toLowerCase();
|
|
401
|
+
if (!reason) return null;
|
|
402
|
+
return SECRET_DIVERGENCE_REASON_CODES.find((code) => reason.includes(code)) || null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function extractSecretDivergenceReason(data) {
|
|
406
|
+
const candidates = [
|
|
407
|
+
data?.payload?.reason,
|
|
408
|
+
data?.payload?.error,
|
|
409
|
+
data?.reason,
|
|
410
|
+
data?.error,
|
|
411
|
+
];
|
|
412
|
+
for (const value of candidates) {
|
|
413
|
+
const reason = matchSecretDivergenceReason(value);
|
|
414
|
+
if (reason) return reason;
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function isSecretDivergenceError(error) {
|
|
420
|
+
return error === SECRET_DIVERGENCE_ERROR || Boolean(matchSecretDivergenceReason(error));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function safeHubErrorCode(data, statusCode) {
|
|
424
|
+
const candidates = [
|
|
425
|
+
data?.error,
|
|
426
|
+
data?.reason,
|
|
427
|
+
data?.payload?.error,
|
|
428
|
+
data?.payload?.reason,
|
|
429
|
+
];
|
|
430
|
+
for (const value of candidates) {
|
|
431
|
+
if (typeof value !== 'string') continue;
|
|
432
|
+
const code = value.trim();
|
|
433
|
+
if (/^[a-z][a-z0-9_:-]{0,79}$/i.test(code)) return code;
|
|
434
|
+
}
|
|
435
|
+
return `http_${statusCode}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function normalizeNodeSecretEnvSuppression(value) {
|
|
439
|
+
const raw = String(value || '').trim();
|
|
440
|
+
if (!raw) return null;
|
|
441
|
+
if (truthyState(raw)) return 'true';
|
|
442
|
+
const lower = raw.toLowerCase();
|
|
443
|
+
return NODE_SECRET_SUPPRESSION_RE.test(lower) ? lower : null;
|
|
444
|
+
}
|
|
445
|
+
|
|
370
446
|
class LifecycleManager {
|
|
371
447
|
constructor({ hubUrl, store, logger, getTaskMeta } = {}) {
|
|
372
448
|
this.hubUrl = (hubUrl || process.env.A2A_HUB_URL || '').replace(/\/+$/, '');
|
|
@@ -386,6 +462,11 @@ class LifecycleManager {
|
|
|
386
462
|
this._lastDriftCheckAt = 0;
|
|
387
463
|
this._hubUnreachableFailures = 0;
|
|
388
464
|
this._hubUnreachableUntil = 0;
|
|
465
|
+
this._envSecretSuppressionMarker = normalizeNodeSecretEnvSuppression(this.store && this.store.getState
|
|
466
|
+
? this.store.getState('node_secret_env_suppressed')
|
|
467
|
+
: null);
|
|
468
|
+
this._envSuppressionClearedForSecret = null;
|
|
469
|
+
this._suppressEnvSecret = Boolean(this._envSecretSuppressionMarker && getEnvNodeSecret());
|
|
389
470
|
|
|
390
471
|
// H4 fix: persist the legacy node_id file as soon as the in-memory
|
|
391
472
|
// node_id is known, NOT only after a successful hello(). The original
|
|
@@ -439,14 +520,13 @@ class LifecycleManager {
|
|
|
439
520
|
}
|
|
440
521
|
|
|
441
522
|
get nodeSecretVersion() {
|
|
523
|
+
this._resolveNodeSecret();
|
|
442
524
|
const storeVersion = parseNodeSecretVersion(this.store.getState('node_secret_version'));
|
|
443
525
|
const storeSecret = this.store.getState('node_secret') || null;
|
|
444
526
|
const storeSource = this.store.getState('node_secret_source') || null;
|
|
445
|
-
const envSecret = this.
|
|
446
|
-
? null
|
|
447
|
-
: ((process.env.A2A_NODE_SECRET || process.env.EVOMAP_NODE_SECRET || '').trim() || null);
|
|
527
|
+
const envSecret = this._effectiveEnvNodeSecret();
|
|
448
528
|
const envVersion = parseNodeSecretVersion(process.env.A2A_NODE_SECRET_VERSION || process.env.EVOMAP_NODE_SECRET_VERSION);
|
|
449
|
-
const validStoreSecret =
|
|
529
|
+
const validStoreSecret = validNodeSecret(storeSecret);
|
|
450
530
|
if (this._suppressEnvSecret) return validStoreSecret ? storeVersion : null;
|
|
451
531
|
if (storeSource === 'hub_rotate' && validStoreSecret) return storeVersion;
|
|
452
532
|
if (envSecret) {
|
|
@@ -487,37 +567,41 @@ class LifecycleManager {
|
|
|
487
567
|
* @returns {string|null}
|
|
488
568
|
*/
|
|
489
569
|
_resolveNodeSecret() {
|
|
490
|
-
const envSecret = this.
|
|
491
|
-
|
|
492
|
-
: ((process.env.A2A_NODE_SECRET || process.env.EVOMAP_NODE_SECRET || '').trim() || null);
|
|
570
|
+
const envSecret = this._effectiveEnvNodeSecret();
|
|
571
|
+
const envUpdatedAfterSuppression = this._envSuppressionClearedForSecret === envSecret;
|
|
493
572
|
const storeSecret = this.store.getState('node_secret') || null;
|
|
494
573
|
const storeSource = this.store.getState('node_secret_source') || null;
|
|
495
|
-
|
|
574
|
+
|
|
575
|
+
if (
|
|
576
|
+
envSecret &&
|
|
577
|
+
envUpdatedAfterSuppression &&
|
|
578
|
+
validNodeSecret(envSecret) &&
|
|
579
|
+
(!storeSecret || envSecret === storeSecret) &&
|
|
580
|
+
!(storeSecret && envSecret === storeSecret && storeSource === 'hub_rotate')
|
|
581
|
+
) {
|
|
582
|
+
this._syncEnvNodeSecretToStore(envSecret);
|
|
583
|
+
return envSecret;
|
|
584
|
+
}
|
|
496
585
|
|
|
497
586
|
if (envSecret && storeSecret && envSecret !== storeSecret) {
|
|
498
587
|
// Store value came from a successful hub rotation -> trust it.
|
|
499
588
|
// The env var is necessarily stale: it was captured by the parent
|
|
500
589
|
// shell before the rotation and a child process cannot mutate it
|
|
501
590
|
// back into its parent.
|
|
502
|
-
if (storeSource === 'hub_rotate' &&
|
|
591
|
+
if (storeSource === 'hub_rotate' && validNodeSecret(storeSecret) && !envUpdatedAfterSuppression) {
|
|
503
592
|
if (!this._storeSourceLogged) {
|
|
504
593
|
this._storeSourceLogged = true;
|
|
505
594
|
this.logger.warn(
|
|
506
595
|
'[lifecycle] A2A_NODE_SECRET env var differs from MailboxStore; ' +
|
|
507
596
|
'store value originated from a hub rotation, treating env as stale. ' +
|
|
508
|
-
'
|
|
509
|
-
'
|
|
597
|
+
'After a manual web reset, run `evolver reset-local-secret` to clear local ' +
|
|
598
|
+
'secret and env suppression state before relying only on updated env vars.'
|
|
510
599
|
);
|
|
511
600
|
}
|
|
512
601
|
return storeSecret;
|
|
513
602
|
}
|
|
514
|
-
if (
|
|
515
|
-
|
|
516
|
-
this.store.setState('node_secret', envSecret);
|
|
517
|
-
this.store.setState('node_secret_version', envVersion ? String(envVersion) : '');
|
|
518
|
-
// Mark the new store value as env-seeded so a future rotation can
|
|
519
|
-
// distinguish "operator pasted this in" from "hub returned this".
|
|
520
|
-
this.store.setState('node_secret_source', 'env_seed');
|
|
603
|
+
if (validNodeSecret(envSecret)) {
|
|
604
|
+
this._syncEnvNodeSecretToStore(envSecret);
|
|
521
605
|
if (!this._envOverrideLogged) {
|
|
522
606
|
this._envOverrideLogged = true;
|
|
523
607
|
this.logger.warn(
|
|
@@ -534,6 +618,68 @@ class LifecycleManager {
|
|
|
534
618
|
return storeSecret || envSecret || null;
|
|
535
619
|
}
|
|
536
620
|
|
|
621
|
+
_syncEnvNodeSecretToStore(envSecret) {
|
|
622
|
+
const envVersion = parseNodeSecretVersion(process.env.A2A_NODE_SECRET_VERSION || process.env.EVOMAP_NODE_SECRET_VERSION);
|
|
623
|
+
this.store.setState('node_secret', envSecret);
|
|
624
|
+
this.store.setState('node_secret_version', envVersion ? String(envVersion) : '');
|
|
625
|
+
// Mark the new store value as env-seeded so a future rotation can
|
|
626
|
+
// distinguish "operator pasted this in" from "hub returned this".
|
|
627
|
+
this.store.setState('node_secret_source', 'env_seed');
|
|
628
|
+
this._clearEnvSecretSuppression();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
_effectiveEnvNodeSecret() {
|
|
632
|
+
const envSecret = getEnvNodeSecret();
|
|
633
|
+
return this._isEnvNodeSecretSuppressed(envSecret) ? null : envSecret;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
_isEnvNodeSecretSuppressed(envSecret) {
|
|
637
|
+
this._envSuppressionClearedForSecret = null;
|
|
638
|
+
const persistedMarker = normalizeNodeSecretEnvSuppression(this.store && this.store.getState
|
|
639
|
+
? this.store.getState('node_secret_env_suppressed')
|
|
640
|
+
: null);
|
|
641
|
+
const marker = persistedMarker || this._envSecretSuppressionMarker;
|
|
642
|
+
if (!marker) {
|
|
643
|
+
this._envSecretSuppressionMarker = null;
|
|
644
|
+
this._suppressEnvSecret = false;
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
this._envSecretSuppressionMarker = marker;
|
|
648
|
+
if (marker === 'true') {
|
|
649
|
+
this._suppressEnvSecret = Boolean(envSecret);
|
|
650
|
+
return Boolean(envSecret);
|
|
651
|
+
}
|
|
652
|
+
const envFingerprint = fingerprintNodeSecret(envSecret);
|
|
653
|
+
if (!envFingerprint) {
|
|
654
|
+
this._suppressEnvSecret = false;
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (envFingerprint === marker) {
|
|
658
|
+
this._suppressEnvSecret = true;
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
this._clearEnvSecretSuppression();
|
|
662
|
+
this._envSuppressionClearedForSecret = envSecret;
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
_markCurrentEnvSecretSuppressed() {
|
|
667
|
+
const marker = fingerprintNodeSecret(getEnvNodeSecret());
|
|
668
|
+
if (!marker) {
|
|
669
|
+
this._clearEnvSecretSuppression();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
try { this.store.setState('node_secret_env_suppressed', marker); } catch { /* best-effort */ }
|
|
673
|
+
this._envSecretSuppressionMarker = marker;
|
|
674
|
+
this._suppressEnvSecret = true;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
_clearEnvSecretSuppression() {
|
|
678
|
+
try { this.store.setState('node_secret_env_suppressed', ''); } catch { /* best-effort */ }
|
|
679
|
+
this._envSecretSuppressionMarker = null;
|
|
680
|
+
this._suppressEnvSecret = false;
|
|
681
|
+
}
|
|
682
|
+
|
|
537
683
|
_buildHeaders() {
|
|
538
684
|
const headers = { 'Content-Type': 'application/json' };
|
|
539
685
|
const secret = this.nodeSecret;
|
|
@@ -607,28 +753,64 @@ class LifecycleManager {
|
|
|
607
753
|
await throwIfHubUnreachableResponse(res, 'lifecycle hello');
|
|
608
754
|
this._recordHubReachable();
|
|
609
755
|
if (!res.ok) {
|
|
610
|
-
const
|
|
611
|
-
|
|
756
|
+
const errText = await readHubResponseText(res).catch(() => '');
|
|
757
|
+
let errData = {};
|
|
758
|
+
try { errData = errText ? JSON.parse(errText) : {}; } catch (_) { /* non-JSON API error */ }
|
|
759
|
+
const errMsg = safeHubErrorCode(errData, res.status);
|
|
612
760
|
if (res.status === 429) {
|
|
613
761
|
const retryAfter = parseInt(res.headers.get('retry-after') || '3600', 10);
|
|
614
762
|
this._helloRateLimitUntil = Date.now() + retryAfter * 1000;
|
|
615
763
|
this.logger.error(`[lifecycle] hello rate limited (429): retry after ${retryAfter}s`);
|
|
616
764
|
return { ok: false, error: 'hello_rate_limited', retryAfter };
|
|
617
765
|
}
|
|
618
|
-
|
|
766
|
+
const divergenceReason = (res.status === 401 || res.status === 403)
|
|
767
|
+
? extractSecretDivergenceReason(errData)
|
|
768
|
+
: null;
|
|
769
|
+
if (divergenceReason) {
|
|
770
|
+
this.logger.warn(
|
|
771
|
+
`[lifecycle] hello rejected secret rotation (${res.status}, reason=${divergenceReason}); ` +
|
|
772
|
+
'local secret divergence detected'
|
|
773
|
+
);
|
|
774
|
+
return {
|
|
775
|
+
ok: false,
|
|
776
|
+
error: SECRET_DIVERGENCE_ERROR,
|
|
777
|
+
reason: divergenceReason,
|
|
778
|
+
statusCode: res.status,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
const safeErrText = errText ? sanitizeHubResponseForLog(errText) : errMsg;
|
|
782
|
+
this.logger.error(`[lifecycle] hello HTTP ${res.status}: ${safeErrText}`);
|
|
619
783
|
return { ok: false, error: errMsg, statusCode: res.status };
|
|
620
784
|
}
|
|
621
785
|
|
|
622
786
|
const data = await readHubResponseJson(res);
|
|
623
787
|
|
|
624
788
|
if (data?.payload?.status === 'rejected') {
|
|
625
|
-
|
|
626
|
-
|
|
789
|
+
const divergenceReason = extractSecretDivergenceReason(data);
|
|
790
|
+
if (divergenceReason) {
|
|
791
|
+
this.logger.warn(
|
|
792
|
+
`[lifecycle] hello rejected secret rotation (reason=${divergenceReason}); ` +
|
|
793
|
+
'local secret divergence detected'
|
|
794
|
+
);
|
|
795
|
+
return {
|
|
796
|
+
ok: false,
|
|
797
|
+
error: SECRET_DIVERGENCE_ERROR,
|
|
798
|
+
reason: divergenceReason,
|
|
799
|
+
response: data,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const reason = data.payload.reason || 'unknown';
|
|
803
|
+
const safeReason = sanitizeHubResponseForLog(reason);
|
|
804
|
+
this.logger.error(`[lifecycle] hello rejected: ${safeReason}`);
|
|
805
|
+
const error = String(reason || '').startsWith('node_id_already_claimed')
|
|
806
|
+
? reason
|
|
807
|
+
: 'hello_rejected';
|
|
808
|
+
return { ok: false, error, response: data };
|
|
627
809
|
}
|
|
628
810
|
|
|
629
811
|
const secret = data?.payload?.node_secret || data?.node_secret || null;
|
|
630
812
|
const secretVersion = parseNodeSecretVersion(data?.payload?.node_secret_version || data?.node_secret_version);
|
|
631
|
-
if (secret &&
|
|
813
|
+
if (secret && validNodeSecret(secret)) {
|
|
632
814
|
this.store.setState('node_secret', secret);
|
|
633
815
|
// Tag the store entry so the next process that boots into a stale
|
|
634
816
|
// shell env can recognise this value as hub-authoritative and
|
|
@@ -643,7 +825,11 @@ class LifecycleManager {
|
|
|
643
825
|
// and overwrite the freshly rotated secret with the stale one,
|
|
644
826
|
// re-creating the auth loop the previous patch fixed (see #529
|
|
645
827
|
// and the Bugbot review on PR #22).
|
|
646
|
-
|
|
828
|
+
if (getEnvNodeSecret() && getEnvNodeSecret() !== secret) {
|
|
829
|
+
this._markCurrentEnvSecretSuppressed();
|
|
830
|
+
} else {
|
|
831
|
+
this._clearEnvSecretSuppression();
|
|
832
|
+
}
|
|
647
833
|
this.logger.log('[lifecycle] new node_secret stored from hello response');
|
|
648
834
|
}
|
|
649
835
|
if (secretVersion) {
|
|
@@ -713,6 +899,7 @@ class LifecycleManager {
|
|
|
713
899
|
this._reauthInProgress = true;
|
|
714
900
|
let manualResetRequired = false;
|
|
715
901
|
let hubUnreachable = false;
|
|
902
|
+
let droppedDivergedSecret = false;
|
|
716
903
|
try {
|
|
717
904
|
for (let attempt = 1; attempt <= MAX_REAUTH_ATTEMPTS; attempt++) {
|
|
718
905
|
this.logger.warn(`[lifecycle] re-auth attempt ${attempt}/${MAX_REAUTH_ATTEMPTS}: rotating secret via hello...`);
|
|
@@ -731,6 +918,18 @@ class LifecycleManager {
|
|
|
731
918
|
hubUnreachable = true;
|
|
732
919
|
break;
|
|
733
920
|
}
|
|
921
|
+
if (isSecretDivergenceError(helloResult.error)) {
|
|
922
|
+
// The hub has explicitly rejected our current secret as diverged
|
|
923
|
+
// from its record. Drop store/env-derived auth once, then spend
|
|
924
|
+
// the second attempt on an unauthenticated rotate hello instead
|
|
925
|
+
// of backing off while still presenting the same stale Bearer.
|
|
926
|
+
if (attempt < MAX_REAUTH_ATTEMPTS && !droppedDivergedSecret) {
|
|
927
|
+
this._dropLocalSecret(SECRET_DIVERGENCE_ERROR);
|
|
928
|
+
droppedDivergedSecret = true;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
734
933
|
if (typeof helloResult.error === 'string' && helloResult.error.startsWith('node_id_already_claimed')) {
|
|
735
934
|
// Hub does not believe we own this nodeId. Our locally cached
|
|
736
935
|
// secret(s) are useless. Drop them so attempt 2 retries WITHOUT
|
|
@@ -806,10 +1005,9 @@ class LifecycleManager {
|
|
|
806
1005
|
try { this.store.setState('node_secret_version', ''); } catch { /* best-effort */ }
|
|
807
1006
|
// Clear the source tag too -- nothing is stored, nothing to attribute.
|
|
808
1007
|
try { this.store.setState('node_secret_source', ''); } catch { /* best-effort */ }
|
|
809
|
-
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
this._suppressEnvSecret = true;
|
|
1008
|
+
// Suppress only the exact env secret that was just proven stale. If no
|
|
1009
|
+
// env secret is present, do not leave a marker that blocks a future reset.
|
|
1010
|
+
this._markCurrentEnvSecretSuppressed();
|
|
813
1011
|
}
|
|
814
1012
|
|
|
815
1013
|
_emitManualResetNeeded() {
|
|
@@ -821,7 +1019,7 @@ class LifecycleManager {
|
|
|
821
1019
|
payload: {
|
|
822
1020
|
action: 'manual_secret_reset_required',
|
|
823
1021
|
message:
|
|
824
|
-
'Hub disowns this node_id (node_id_already_claimed). Local node_secret
|
|
1022
|
+
'Hub disowns this node_id (node_id_already_claimed). Local node_secret and the current node secret env value are invalid. Visit https://evomap.ai/account, click "Reset Secret" on the agent card, run `evolver reset-local-secret` to clear local secret and env suppression state, then update A2A_NODE_SECRET/EVOMAP_NODE_SECRET and restart proxy. Only changing env before clearing suppression state can keep an older local marker active.',
|
|
825
1023
|
docs_url: 'https://evomap.ai/account',
|
|
826
1024
|
},
|
|
827
1025
|
});
|
|
@@ -924,7 +1122,8 @@ class LifecycleManager {
|
|
|
924
1122
|
if (res.status === 403 || res.status === 401) {
|
|
925
1123
|
this._consecutiveFailures++;
|
|
926
1124
|
const errText = await readHubResponseText(res).catch(() => '');
|
|
927
|
-
|
|
1125
|
+
const safeErrText = sanitizeHubResponseForLog(errText);
|
|
1126
|
+
this.logger.error(`[lifecycle] heartbeat auth failed (${res.status}): ${safeErrText}`);
|
|
928
1127
|
if (!_skipReauth) {
|
|
929
1128
|
const recovered = await this.reAuthenticate();
|
|
930
1129
|
if (recovered) {
|
|
@@ -937,6 +1136,7 @@ class LifecycleManager {
|
|
|
937
1136
|
|
|
938
1137
|
if (!res.ok) {
|
|
939
1138
|
const errText = await readHubResponseText(res).catch(() => '');
|
|
1139
|
+
const safeErrText = sanitizeHubResponseForLog(errText);
|
|
940
1140
|
// 426 Upgrade Required: hub emits this when our evolver_version is
|
|
941
1141
|
// below the minimum version it requires. The body is JSON of shape
|
|
942
1142
|
// `{ error: 'evolver_min_version_required', force_update: {...} }`
|
|
@@ -961,7 +1161,7 @@ class LifecycleManager {
|
|
|
961
1161
|
_maybeTriggerForceUpdateFromHeartbeat(fu, this.logger);
|
|
962
1162
|
} else {
|
|
963
1163
|
this.logger.warn(
|
|
964
|
-
`[lifecycle] heartbeat HTTP 426 without parseable force_update payload: ${
|
|
1164
|
+
`[lifecycle] heartbeat HTTP 426 without parseable force_update payload: ${safeErrText}`
|
|
965
1165
|
);
|
|
966
1166
|
}
|
|
967
1167
|
}
|
|
@@ -1000,7 +1200,7 @@ class LifecycleManager {
|
|
|
1000
1200
|
}
|
|
1001
1201
|
}
|
|
1002
1202
|
this._consecutiveFailures++;
|
|
1003
|
-
this.logger.error(`[lifecycle] heartbeat HTTP ${res.status}: ${
|
|
1203
|
+
this.logger.error(`[lifecycle] heartbeat HTTP ${res.status}: ${safeErrText}`);
|
|
1004
1204
|
return { ok: false, error: `http_${res.status}`, statusCode: res.status };
|
|
1005
1205
|
}
|
|
1006
1206
|
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
isHubUnreachableError,
|
|
10
10
|
readHubResponseJson,
|
|
11
11
|
readHubResponseText,
|
|
12
|
+
sanitizeHubResponseForLog,
|
|
12
13
|
throwIfHubUnreachableResponse,
|
|
13
14
|
} = require('../../gep/hubFetch');
|
|
14
15
|
|
|
@@ -75,12 +76,12 @@ class InboundSync {
|
|
|
75
76
|
|
|
76
77
|
if (res.status === 403 || res.status === 401) {
|
|
77
78
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
78
|
-
throw new AuthError(`Hub ${res.status}: ${errText}`, res.status);
|
|
79
|
+
throw new AuthError(`Hub ${res.status}: ${sanitizeHubResponseForLog(errText)}`, res.status);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
if (!res.ok) {
|
|
82
83
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
83
|
-
throw new Error(`Hub returned ${res.status}: ${errText}`);
|
|
84
|
+
throw new Error(`Hub returned ${res.status}: ${sanitizeHubResponseForLog(errText)}`);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
const data = await readHubResponseJson(res);
|
|
@@ -163,11 +164,11 @@ class InboundSync {
|
|
|
163
164
|
this._recordHubReachable();
|
|
164
165
|
if (res.status === 403 || res.status === 401) {
|
|
165
166
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
166
|
-
throw new AuthError(`Hub ${res.status}: ${errText}`, res.status);
|
|
167
|
+
throw new AuthError(`Hub ${res.status}: ${sanitizeHubResponseForLog(errText)}`, res.status);
|
|
167
168
|
}
|
|
168
169
|
if (!res.ok) {
|
|
169
170
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
170
|
-
throw new Error(`Hub returned ${res.status}: ${errText}`);
|
|
171
|
+
throw new Error(`Hub returned ${res.status}: ${sanitizeHubResponseForLog(errText)}`);
|
|
171
172
|
}
|
|
172
173
|
await drainHubResponse(res);
|
|
173
174
|
return { acked: delivered.length };
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
isHubUnreachableError,
|
|
10
10
|
readHubResponseJson,
|
|
11
11
|
readHubResponseText,
|
|
12
|
+
sanitizeHubResponseForLog,
|
|
12
13
|
throwIfHubUnreachableResponse,
|
|
13
14
|
} = require('../../gep/hubFetch');
|
|
14
15
|
|
|
@@ -109,12 +110,12 @@ class OutboundSync {
|
|
|
109
110
|
|
|
110
111
|
if (res.status === 403 || res.status === 401) {
|
|
111
112
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
112
|
-
throw new AuthError(`Hub ${res.status}: ${errText}`, res.status);
|
|
113
|
+
throw new AuthError(`Hub ${res.status}: ${sanitizeHubResponseForLog(errText)}`, res.status);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
if (!res.ok) {
|
|
116
117
|
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
117
|
-
throw new Error(`Hub returned ${res.status}: ${errText}`);
|
|
118
|
+
throw new Error(`Hub returned ${res.status}: ${sanitizeHubResponseForLog(errText)}`);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
const data = await readHubResponseJson(res);
|