instar 1.3.728 → 1.3.730

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 (103) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +259 -2
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +11 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/FileClassifier.d.ts.map +1 -1
  8. package/dist/core/FileClassifier.js +11 -0
  9. package/dist/core/FileClassifier.js.map +1 -1
  10. package/dist/core/LocalSessionOwnershipStore.d.ts +5 -0
  11. package/dist/core/LocalSessionOwnershipStore.d.ts.map +1 -1
  12. package/dist/core/LocalSessionOwnershipStore.js +13 -0
  13. package/dist/core/LocalSessionOwnershipStore.js.map +1 -1
  14. package/dist/core/ModelSwapService.d.ts +13 -0
  15. package/dist/core/ModelSwapService.d.ts.map +1 -1
  16. package/dist/core/ModelSwapService.js +11 -0
  17. package/dist/core/ModelSwapService.js.map +1 -1
  18. package/dist/core/ModelTierEscalation.d.ts +10 -0
  19. package/dist/core/ModelTierEscalation.d.ts.map +1 -1
  20. package/dist/core/ModelTierEscalation.js +4 -0
  21. package/dist/core/ModelTierEscalation.js.map +1 -1
  22. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  23. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  24. package/dist/core/PostUpdateMigrator.js +35 -0
  25. package/dist/core/PostUpdateMigrator.js.map +1 -1
  26. package/dist/core/ProactiveSwapMonitor.d.ts +74 -20
  27. package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
  28. package/dist/core/ProactiveSwapMonitor.js +349 -28
  29. package/dist/core/ProactiveSwapMonitor.js.map +1 -1
  30. package/dist/core/QuotaAwareScheduler.d.ts +70 -3
  31. package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
  32. package/dist/core/QuotaAwareScheduler.js +100 -16
  33. package/dist/core/QuotaAwareScheduler.js.map +1 -1
  34. package/dist/core/SessionBuildContextStore.d.ts +20 -0
  35. package/dist/core/SessionBuildContextStore.d.ts.map +1 -1
  36. package/dist/core/SessionBuildContextStore.js +20 -3
  37. package/dist/core/SessionBuildContextStore.js.map +1 -1
  38. package/dist/core/SessionManager.d.ts +33 -0
  39. package/dist/core/SessionManager.d.ts.map +1 -1
  40. package/dist/core/SessionManager.js +105 -0
  41. package/dist/core/SessionManager.js.map +1 -1
  42. package/dist/core/SessionOwnershipRegistry.d.ts +16 -0
  43. package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -1
  44. package/dist/core/SessionOwnershipRegistry.js +13 -0
  45. package/dist/core/SessionOwnershipRegistry.js.map +1 -1
  46. package/dist/core/SessionRefresh.d.ts +116 -1
  47. package/dist/core/SessionRefresh.d.ts.map +1 -1
  48. package/dist/core/SessionRefresh.js +280 -17
  49. package/dist/core/SessionRefresh.js.map +1 -1
  50. package/dist/core/StateManager.d.ts +20 -0
  51. package/dist/core/StateManager.d.ts.map +1 -1
  52. package/dist/core/StateManager.js +72 -4
  53. package/dist/core/StateManager.js.map +1 -1
  54. package/dist/core/SwapAntiThrash.d.ts +329 -0
  55. package/dist/core/SwapAntiThrash.d.ts.map +1 -0
  56. package/dist/core/SwapAntiThrash.js +1025 -0
  57. package/dist/core/SwapAntiThrash.js.map +1 -0
  58. package/dist/core/SwapLedger.d.ts +168 -0
  59. package/dist/core/SwapLedger.d.ts.map +1 -0
  60. package/dist/core/SwapLedger.js +275 -0
  61. package/dist/core/SwapLedger.js.map +1 -0
  62. package/dist/core/SwapWorkGate.d.ts +100 -0
  63. package/dist/core/SwapWorkGate.d.ts.map +1 -0
  64. package/dist/core/SwapWorkGate.js +175 -0
  65. package/dist/core/SwapWorkGate.js.map +1 -0
  66. package/dist/core/WriteAdmission.d.ts +277 -0
  67. package/dist/core/WriteAdmission.d.ts.map +1 -0
  68. package/dist/core/WriteAdmission.js +514 -0
  69. package/dist/core/WriteAdmission.js.map +1 -0
  70. package/dist/core/WriteDomainRegistry.d.ts +132 -0
  71. package/dist/core/WriteDomainRegistry.d.ts.map +1 -0
  72. package/dist/core/WriteDomainRegistry.js +186 -0
  73. package/dist/core/WriteDomainRegistry.js.map +1 -0
  74. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  75. package/dist/core/devGatedFeatures.js +12 -0
  76. package/dist/core/devGatedFeatures.js.map +1 -1
  77. package/dist/core/types.d.ts +89 -1
  78. package/dist/core/types.d.ts.map +1 -1
  79. package/dist/core/types.js.map +1 -1
  80. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  81. package/dist/monitoring/guardManifest.js +58 -1
  82. package/dist/monitoring/guardManifest.js.map +1 -1
  83. package/dist/scaffold/templates.d.ts.map +1 -1
  84. package/dist/scaffold/templates.js +3 -1
  85. package/dist/scaffold/templates.js.map +1 -1
  86. package/dist/server/AgentServer.d.ts +3 -0
  87. package/dist/server/AgentServer.d.ts.map +1 -1
  88. package/dist/server/AgentServer.js +20 -0
  89. package/dist/server/AgentServer.js.map +1 -1
  90. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  91. package/dist/server/CapabilityIndex.js +1 -0
  92. package/dist/server/CapabilityIndex.js.map +1 -1
  93. package/dist/server/routes.d.ts +6 -0
  94. package/dist/server/routes.d.ts.map +1 -1
  95. package/dist/server/routes.js +115 -3
  96. package/dist/server/routes.js.map +1 -1
  97. package/package.json +1 -1
  98. package/src/data/builtin-manifest.json +65 -65
  99. package/src/scaffold/templates.ts +3 -1
  100. package/upgrades/1.3.729.md +92 -0
  101. package/upgrades/1.3.730.md +112 -0
  102. package/upgrades/side-effects/standby-write-reconciliation.md +62 -0
  103. package/upgrades/side-effects/swap-continuity-antithrash.md +118 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+CH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAi/CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA68jBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAu/CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2skBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -26,6 +26,7 @@ import { SpawningTopicsRegistry } from '../core/SpawningTopicsRegistry.js';
26
26
  import { TopicReachabilityVerifier } from '../monitoring/TopicReachabilityVerifier.js';
27
27
  import { SingleInstanceLock, installReleaseHandlers } from '../core/SingleInstanceLock.js';
28
28
  import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGate.js';
29
+ import { resolveAntiThrashKnobs, readingValidity } from '../core/SwapAntiThrash.js';
29
30
  import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
30
31
  import { PrHandLease } from '../core/PrHandLease.js';
31
32
  import { claimSuspensionExcludesPin } from '../core/TopicClaimAnnotationStore.js';
@@ -760,6 +761,12 @@ let _quotaAwareScheduler = null;
760
761
  // Subscription & Auth Standard P1.3 — pre-limit proactive swap monitor. Null
761
762
  // until wired (requires the scheduler; gated behind subscriptionPool.proactiveSwap).
762
763
  let _proactiveSwapMonitor = null;
764
+ // swap-continuity-antithrash §3/§4 — the anti-thrash spine. The ledger + engine
765
+ // load and hydrate UNCONDITIONALLY at boot (R4-L3: reactive rows append
766
+ // regardless of antiThrash.enabled, so the index is warm at a mid-run flag
767
+ // flip). Null only when construction itself failed (non-fatal).
768
+ let _swapAntiThrashEngine = null;
769
+ let _swapWorkGate = null;
763
770
  async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory, userProfile, precomputedContext,
764
771
  /** Subscription & Auth Standard P1.3 (additive): launch under this account's
765
772
  * config home + record its id (the quota-aware account-swap mechanism).
@@ -5013,6 +5020,12 @@ export async function startServer(options) {
5013
5020
  // idle-monitor's rateLimitedAtIdle hand-off. `enabled` OMITTED from defaults ⇒
5014
5021
  // dev-agent live / dark-fleet.
5015
5022
  idleThrottleSettleGate: resolveDevAgentGate(config.monitoring?.idleThrottleSettleGate?.enabled, config),
5023
+ // Standby-write reconciliation §3.3 (round-2 L3): per-machine
5024
+ // SessionBuildContextStore re-key. The id comes from the coordinator/
5025
+ // mesh identity via this LATE-RESOLVING getter (the mesh id is assigned
5026
+ // further down the boot, after SessionManager exists); identity-less
5027
+ // installs resolve null → the literal 'local' inside the store.
5028
+ buildContextMachineId: () => _meshSelfId,
5016
5029
  });
5017
5030
  // Wire the SAME TTL-cached SDK-credit reader PR1's routing policy uses, so
5018
5031
  // the reroute 'auto' decision and the intelligence-funnel routing share one
@@ -14956,6 +14969,94 @@ export async function startServer(options) {
14956
14969
  divergenceChecker = undefined;
14957
14970
  }
14958
14971
  }
14972
+ // ── Swap-continuity anti-thrash spine (swap-continuity-antithrash §3/§4) ──
14973
+ // SwapLedger + SwapAntiThrashEngine + SwapWorkGate. Constructed and
14974
+ // hydrated UNCONDITIONALLY (R4-L3): the ledger loads at every boot
14975
+ // regardless of antiThrash.enabled so the in-memory index (dwell /
14976
+ // reversal / breaker / frequency) is warm the moment the flag flips on
14977
+ // mid-run — the §7.1 half-enable trap closed for this key. All antiThrash
14978
+ // knobs are read LIVE per tick via the getter (the config object is
14979
+ // shared + mutated in place by config PATCHes).
14980
+ const antiThrashKnobsGetter = () => resolveAntiThrashKnobs(config.subscriptionPool?.proactiveSwap?.antiThrash, {
14981
+ thresholdPct: config.subscriptionPool?.proactiveSwap?.thresholdPct,
14982
+ tickMs: config.subscriptionPool?.proactiveSwap?.tickMs,
14983
+ });
14984
+ // swapContinuity (Piece 2, the work gate): the `enabled` KEY IS OMITTED
14985
+ // from shipped config on purpose — omission routes it through the
14986
+ // dev-agent gate (live on a dev agent, dark on the fleet; the #1001
14987
+ // anti-mechanism). `enabled` is RESTART-REQUIRED (§7.1 — the gate wiring
14988
+ // into SessionRefresh is constructor-injected); dryRun + numeric knobs
14989
+ // are read live per evaluation via the getters below.
14990
+ const swapContinuityEnabled = resolveDevAgentGate(config.subscriptionPool?.swapContinuity?.enabled, config);
14991
+ const swapContinuityKnobs = () => {
14992
+ const c = config.subscriptionPool?.swapContinuity;
14993
+ return {
14994
+ enabled: swapContinuityEnabled,
14995
+ dryRun: c?.dryRun ?? true,
14996
+ deferralCeilingMs: c?.deferralCeilingMs ?? 1_800_000,
14997
+ reactiveGraceMs: c?.reactiveGraceMs ?? 120_000,
14998
+ recheckMs: c?.recheckMs ?? 10_000,
14999
+ };
15000
+ };
15001
+ try {
15002
+ const { SwapLedger } = await import('../core/SwapLedger.js');
15003
+ const { SwapAntiThrashEngine, retentionBoundMs, crossKnobWarnings } = await import('../core/SwapAntiThrash.js');
15004
+ const { SwapWorkGate } = await import('../core/SwapWorkGate.js');
15005
+ const swapLedger = new SwapLedger({
15006
+ filePath: path.join(config.stateDir, 'state', 'swap-ledger.jsonl'),
15007
+ windowMs: () => retentionBoundMs(antiThrashKnobsGetter()),
15008
+ // Counts supplied at outage-resume time (the ledger-lost refusals the
15009
+ // writer itself could not record — §3.5/I5).
15010
+ outageRefusalCount: () => _swapAntiThrashEngine?.ledgerLostRefusalCount() ?? 0,
15011
+ machineId: _meshSelfId ?? 'local',
15012
+ logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15013
+ });
15014
+ _swapAntiThrashEngine = new SwapAntiThrashEngine({
15015
+ ledger: swapLedger,
15016
+ getKnobs: antiThrashKnobsGetter,
15017
+ machineId: _meshSelfId ?? 'local',
15018
+ // P17: episode-deduped attention (the engine dedupes on episodeId;
15019
+ // the id passed here is the stable per-episode dedupe key).
15020
+ raiseAttention: (id, title, body) => {
15021
+ void telegram
15022
+ ?.createAttentionItem({
15023
+ id,
15024
+ title,
15025
+ summary: body,
15026
+ category: 'subscription-pool',
15027
+ priority: 'HIGH',
15028
+ sourceContext: 'subscription-pool:swap-antithrash',
15029
+ })
15030
+ .catch(() => {
15031
+ /* @silent-fallback-ok: attention is best-effort; the ledger row is the durable trace */
15032
+ });
15033
+ },
15034
+ logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15035
+ });
15036
+ _swapAntiThrashEngine.hydrate();
15037
+ // Cross-knob coherence warnings (§7, R4-L2 — warn-only, once at boot).
15038
+ // 15 min = the QuotaPoller's default cadence (pollIntervalMs ?? 15*60_000).
15039
+ for (const w of crossKnobWarnings(antiThrashKnobsGetter(), 15 * 60_000))
15040
+ console.warn(w);
15041
+ _swapWorkGate = new SwapWorkGate({
15042
+ checkSessionWorkState: (tmux) => sessionManager.checkSessionWorkState(tmux),
15043
+ getClaudeSessionId: (tmux) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === tmux)?.claudeSessionId ?? null,
15044
+ hasActiveSubagents: (id) => subagentTracker.hasActiveSubagents(id),
15045
+ getActiveSubagents: (id) => subagentTracker.getActiveSubagents(id).map((r) => ({
15046
+ agentType: r.agentType,
15047
+ startedAt: r.startedAt,
15048
+ ...(r.transcriptPath ? { transcriptPath: r.transcriptPath } : {}),
15049
+ })),
15050
+ });
15051
+ }
15052
+ catch (err) {
15053
+ // @silent-fallback-ok: cascade-isolation — a spine construction failure
15054
+ // must never 503 the server; behavior degrades to pre-brake semantics
15055
+ // (the legacy path) and re-attempts next boot.
15056
+ console.warn('[instar] swap-continuity anti-thrash spine init failed (non-fatal):', err);
15057
+ _swapAntiThrashEngine = null;
15058
+ _swapWorkGate = null;
15059
+ }
14959
15060
  // SessionRefresh — agent-initiated session respawn. Requires a Telegram
14960
15061
  // adapter (v1 scope: Telegram-bound sessions only). The respawner closure
14961
15062
  // captures `topicMemory` by reference, so even if topicMemory is wired up
@@ -14973,6 +15074,43 @@ export async function startServer(options) {
14973
15074
  state,
14974
15075
  telegram: telegramRef,
14975
15076
  topicResumeMap: _topicResumeMap,
15077
+ // swap-continuity-antithrash §4.2 — the in-flight work gate, bound at
15078
+ // THIS primitive (the funnel every session-killing mutation flows
15079
+ // through). Constructor-injected on purpose: `swapContinuity.enabled`
15080
+ // is restart-required (§7.1); dryRun + numeric knobs read live via
15081
+ // getKnobs. Null when the feature is dark on this install ⇒
15082
+ // byte-for-byte today's behavior (unconditional kill).
15083
+ workGateCtx: swapContinuityEnabled && _swapWorkGate
15084
+ ? {
15085
+ probe: (tmux) => _swapWorkGate.probe(tmux),
15086
+ getKnobs: () => {
15087
+ const k = swapContinuityKnobs();
15088
+ return { enabled: k.enabled, dryRun: k.dryRun, reactiveGraceMs: k.reactiveGraceMs, recheckMs: k.recheckMs };
15089
+ },
15090
+ recordProceeded: (args) => _swapAntiThrashEngine?.recordProceeded(args),
15091
+ recordInteractiveRefusal: (args) => _swapAntiThrashEngine?.recordInteractiveRefusal(args),
15092
+ // §4.3(2) — Q4: v1 reads the in-memory exactly-once map (set on
15093
+ // inbound, cleared on reply); 'unknown' = the map/ledger is
15094
+ // unavailable (post-restart, or exactly-once ingress dark).
15095
+ // `messageLedger`/`currentInboundByTopic` are declared later in
15096
+ // this boot function and captured by reference — every call
15097
+ // happens post-boot, so the binding is initialized by then.
15098
+ resolveUnansweredInbound: (topicId) => {
15099
+ if (!messageLedger || !currentInboundByTopic)
15100
+ return 'unknown';
15101
+ const dedupeKey = currentInboundByTopic.get(String(topicId));
15102
+ if (!dedupeKey)
15103
+ return 'none';
15104
+ const entry = messageLedger.get(dedupeKey);
15105
+ if (!entry || entry.inputSnapshot === null)
15106
+ return 'unknown';
15107
+ const from = entry.senderEnvelope?.username ??
15108
+ entry.senderEnvelope?.firstName ??
15109
+ (entry.senderEnvelope?.userId !== undefined ? String(entry.senderEnvelope.userId) : 'unknown sender');
15110
+ return { body: entry.inputSnapshot, from, at: entry.receivedAt };
15111
+ },
15112
+ }
15113
+ : null,
14976
15114
  // §10.5 Slack binding — SlackAdapter satisfies SlackRefreshBinding
14977
15115
  // structurally (getChannelForSession / removeChannelResume /
14978
15116
  // resolveChannelForSessionFromDisk). Null ⇒ Telegram-only, unchanged.
@@ -15036,6 +15174,30 @@ export async function startServer(options) {
15036
15174
  _quotaAwareScheduler = new QuotaAwareScheduler({
15037
15175
  listAccounts: () => subscriptionPool.list(),
15038
15176
  softThresholdPct: config.subscriptionPool?.swapSoftThresholdPct,
15177
+ // swap-continuity-antithrash — the anti-thrash observation + execute-time
15178
+ // revalidation hooks. Reactive decision behavior is untouched (I6):
15179
+ // these only OBSERVE reactive swaps (dwell clock-start, hop alerts,
15180
+ // failed rows) and revalidate PROACTIVE explicit-target calls (§3.3).
15181
+ antiThrash: _swapAntiThrashEngine
15182
+ ? {
15183
+ readingValid: (acct, nowMs) => readingValidity(acct, nowMs, antiThrashKnobsGetter().quotaFreshnessMs).valid,
15184
+ getKnobs: () => {
15185
+ const k = antiThrashKnobsGetter();
15186
+ return { thresholdPct: k.thresholdPct, targetHeadroomPct: k.targetHeadroomPct, minImprovementPct: k.minImprovementPct };
15187
+ },
15188
+ resolveCurrentAccountId: (sessionName) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === sessionName)?.subscriptionAccountId ?? null,
15189
+ onReactiveExecuted: (a) => _swapAntiThrashEngine?.recordReactiveExecuted(a),
15190
+ onReactiveFailed: (a) => _swapAntiThrashEngine?.recordExecFailure({
15191
+ session: a.session,
15192
+ from: a.from,
15193
+ ...(a.to ? { to: a.to } : {}),
15194
+ kind: 'reactive',
15195
+ errorClass: a.errorClass,
15196
+ nowMs: a.nowMs,
15197
+ }),
15198
+ onReactiveRateCapRefusal: (s, n) => _swapAntiThrashEngine?.noteReactiveRateCapRefusal(s, n),
15199
+ }
15200
+ : undefined,
15039
15201
  refreshFn: async (o) => {
15040
15202
  if (!_sessionRefresh)
15041
15203
  return false;
@@ -15044,8 +15206,13 @@ export async function startServer(options) {
15044
15206
  reason: o.reason,
15045
15207
  configHome: o.configHome,
15046
15208
  accountId: o.accountId,
15209
+ // I11: callerClass set ONLY server-internally — the scheduler is
15210
+ // the one caller that knows its lane (proactive funnel vs reactive).
15211
+ callerClass: o.callerClass,
15047
15212
  });
15048
- return r.ok;
15213
+ // Object form: surfaces the structured refusal code so the
15214
+ // anti-thrash observation layer can classify failures (§3.1/§3.6).
15215
+ return r.ok ? { ok: true } : { ok: false, code: r.code };
15049
15216
  },
15050
15217
  onNoAlternate: (sessionName, exhaustedAccountId) => {
15051
15218
  void telegramRef.createAttentionItem({
@@ -15127,6 +15294,18 @@ export async function startServer(options) {
15127
15294
  maxSwapsPerCycle: proactiveCfg.maxSwapsPerCycle,
15128
15295
  cooldownMs: proactiveCfg.cooldownMs,
15129
15296
  tickMs: proactiveCfg.tickMs,
15297
+ // swap-continuity-antithrash §3 — the brake engine at the proactive
15298
+ // DECISION chokepoint. Knobs read LIVE per tick (§7.1). dryRun keeps
15299
+ // the legacy decision path byte-identical while the engine logs
15300
+ // would-refuse shadow rows (the rung-2 soak).
15301
+ antiThrash: _swapAntiThrashEngine
15302
+ ? { engine: _swapAntiThrashEngine, getKnobs: antiThrashKnobsGetter }
15303
+ : undefined,
15304
+ // §4 — the in-flight work gate (proactive arm: defer, ceiling-drop).
15305
+ // The monitor owns the deferral lifecycle; the gate is stateless.
15306
+ workGate: _swapWorkGate
15307
+ ? { probe: (s) => _swapWorkGate.probe(s), getContinuity: swapContinuityKnobs }
15308
+ : undefined,
15130
15309
  logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15131
15310
  });
15132
15311
  _proactiveSwapMonitor.start();
@@ -16367,6 +16546,12 @@ export async function startServer(options) {
16367
16546
  * no-op (the spawn still runs; the reconciler/applier converge — FD11).
16368
16547
  */
16369
16548
  let _claimOwnershipForAutonomousSpawn = null;
16549
+ // Standby-write reconciliation (§3.2): the pool block's ownership store is
16550
+ // captured here so the WriteAdmission constructed just before AgentServer
16551
+ // can boot-warm its ownership index from it + register the interface-level
16552
+ // onCommit hook. Stays null on pool-dark installs (the index warms empty;
16553
+ // scoped domains collapse to the legacy lease boolean anyway).
16554
+ let _writeAdmissionOwnershipStore = null;
16370
16555
  try {
16371
16556
  const meshMod = await import('../core/MeshRpc.js');
16372
16557
  const idMod = await import('../core/MachineIdentity.js');
@@ -16568,6 +16753,7 @@ export async function startServer(options) {
16568
16753
  epochFloorOf: (sk) => ownershipEpochFloor?.(sk) ?? 0,
16569
16754
  logger: (m) => console.log(pc.dim(` ${m}`)),
16570
16755
  });
16756
+ _writeAdmissionOwnershipStore = ownershipStore;
16571
16757
  const ownReg = sessionOwnershipRegistry;
16572
16758
  // WS1.1: expose a read-only ownership lookup to the drain's
16573
16759
  // spawn-boundary re-check (module-scope; the drain closure is defined
@@ -19890,10 +20076,81 @@ export async function startServer(options) {
19890
20076
  console.warn(pc.yellow(` Rope-health monitor init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`));
19891
20077
  ropeHealthMonitor = null;
19892
20078
  }
20079
+ // ── Standby-Write Reconciliation + Typed Refusal (docs/specs/standby-write-reconciliation.md) ──
20080
+ // Ownership-scoped write admission replacing the blanket lease-boolean
20081
+ // standby guard. Dev-gated dark (`enabled` OMITTED from defaults →
20082
+ // resolveDevAgentGate) + dryRun-FIRST: while dry the LEGACY blanket guard
20083
+ // keeps enforcing and this layer only logs would-verdicts (§9.6). The
20084
+ // attach below is ONE-WAY and lands BEFORE routes are wired (AgentServer
20085
+ // is constructed just after), so no HTTP mutating route can ever observe a
20086
+ // half-attached state (§3.2 pre-construction window).
20087
+ let writeAdmission = null;
20088
+ if (resolveDevAgentGate(config.multiMachine?.writeAdmission?.enabled, config)) {
20089
+ try {
20090
+ const waMod = await import('../core/WriteAdmission.js');
20091
+ const regMod = await import('../core/WriteDomainRegistry.js');
20092
+ const waMachineId = _meshSelfId ?? coordinator.identity?.machineId ?? null;
20093
+ const waCfg = config.multiMachine?.writeAdmission;
20094
+ writeAdmission = new waMod.WriteAdmission({
20095
+ thisMachineId: waMachineId,
20096
+ isReadOnly: () => state.readOnly,
20097
+ isPoolActive: () => state.sessionPoolActive,
20098
+ registry: regMod.buildWriteDomainRegistry({ machineId: waMachineId }),
20099
+ dryRun: waCfg?.dryRun ?? true,
20100
+ refusalAggregateThreshold: waCfg?.refusalAggregateThreshold ?? 5,
20101
+ // In-memory session→topic binding ONLY (I2 — the disk fallback is
20102
+ // forbidden on the admission path).
20103
+ resolveTopicForSession: (sessionId) => telegram?.getTopicForSession(sessionId) ?? null,
20104
+ // I7 — local knowledge only: we positively know the holder only when
20105
+ // it is us; a refusal body's leaseHolder is otherwise null.
20106
+ leaseHolder: () => (coordinator.holdsLease() ? waMachineId : null),
20107
+ raiseAttention: telegram
20108
+ ? (item) => {
20109
+ void telegram.createAttentionItem({
20110
+ id: item.id,
20111
+ title: item.title,
20112
+ summary: item.body,
20113
+ category: 'write-admission',
20114
+ priority: 'NORMAL',
20115
+ sourceContext: 'write-admission',
20116
+ }).catch(() => { });
20117
+ }
20118
+ : undefined,
20119
+ logDir: path.join(config.stateDir, 'logs'),
20120
+ }, _writeAdmissionOwnershipStore);
20121
+ state.attachWriteAdmission(writeAdmission);
20122
+ // One-time legacy-key cleanup (§3.3): the pre-re-key shared
20123
+ // `session-build-context` kv entry is removed on the LEASE HOLDER only
20124
+ // (a standby's delete would be refused by the cluster-shared guard —
20125
+ // correctly). Entries self-drain via the 6h max age regardless, so a
20126
+ // machine that never holds the lease loses nothing.
20127
+ try {
20128
+ if (coordinator.holdsLease() && state.get('session-build-context') !== null) {
20129
+ state.delete('session-build-context');
20130
+ console.log(pc.dim(' [write-admission] legacy session-build-context kv entry cleaned up (lease holder, one-time)'));
20131
+ }
20132
+ }
20133
+ catch {
20134
+ /* @silent-fallback-ok — the legacy key is inert and self-draining (6h max age);
20135
+ a failed cleanup only delays its disappearance and retries at the next boot. */
20136
+ }
20137
+ console.log(pc.dim(` Write-admission: live (${writeAdmission.mode()}) — machine ${waMachineId ?? 'local'}, legacy guard ${writeAdmission.isLive ? 'subsumed' : 'still enforcing'}`));
20138
+ }
20139
+ catch (err) {
20140
+ // @silent-fallback-ok — reported via console.warn; init failure leaves the
20141
+ // layer null → route 503s + the legacy blanket guard keeps enforcing
20142
+ // (§5 fail-toward-today), never blocks boot.
20143
+ console.warn(pc.yellow(` Write-admission init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`));
20144
+ writeAdmission = null;
20145
+ }
20146
+ }
20147
+ else {
20148
+ console.log(pc.dim(' Write-admission: dark (multiMachine.writeAdmission resolves off on this agent)'));
20149
+ }
19893
20150
  const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
19894
20151
  peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
19895
20152
  } if (peers.length === 0)
19896
- return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
20153
+ return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
19897
20154
  // wired before the server existed; from here on inbound binds use the
19898
20155
  // server's own store instance.
19899
20156
  _agentServerRef = server;