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
@@ -30,6 +30,11 @@ import { type OwnershipAction, type OwnershipReason, type SessionOwnershipRecord
30
30
  */
31
31
  export declare class InMemorySessionOwnershipStore {
32
32
  private recs;
33
+ /** Interface-level commit hook (standby-write-reconciliation §3.2, round-2
34
+ * S4): fired at THIS substrate's own mutation point (`casWrite` — it has no
35
+ * `persist()` funnel), so a WriteAdmission ownership index warmed against
36
+ * this store is never warm-once-then-permanently-stale. */
37
+ onCommit?: (record: import('./SessionOwnership.js').SessionOwnershipRecord) => void;
33
38
  read(sessionKey: string): SessionOwnershipRecord | null;
34
39
  casWrite(candidate: import('./SessionOwnership.js').SessionOwnershipRecord): {
35
40
  ok: boolean;
@@ -57,6 +62,17 @@ export interface SessionOwnershipStore {
57
62
  * implement it.
58
63
  */
59
64
  all?(): SessionOwnershipRecord[];
65
+ /**
66
+ * Interface-level commit hook (standby-write-reconciliation §3.2, round-2
67
+ * S4): assignable listener each substrate fires at its OWN mutation point —
68
+ * `LocalSessionOwnershipStore` inside `persist()` (after `cache.set`),
69
+ * `InMemorySessionOwnershipStore` inside `casWrite()` at its `recs.set`.
70
+ * BOTH mutation paths (`registry.cas()` and `OwnershipApplier`) funnel
71
+ * through the store's commit point, so a consumer (the WriteAdmission
72
+ * ownership index) can never miss a transition the local store saw.
73
+ * A listener throw must never fail the CAS (substrates guard the call).
74
+ */
75
+ onCommit?: (record: SessionOwnershipRecord) => void;
60
76
  }
61
77
  export interface SessionOwnershipRegistryDeps {
62
78
  store: SessionOwnershipStore;
@@ -1 +1 @@
1
- {"version":3,"file":"SessionOwnershipRegistry.d.ts","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SessionOwnershipRegistry.d.ts","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,11 @@ import { applyOwnershipAction, ownershipNonceKey, } from './SessionOwnership.js'
30
30
  */
31
31
  export class InMemorySessionOwnershipStore {
32
32
  recs = new Map();
33
+ /** Interface-level commit hook (standby-write-reconciliation §3.2, round-2
34
+ * S4): fired at THIS substrate's own mutation point (`casWrite` — it has no
35
+ * `persist()` funnel), so a WriteAdmission ownership index warmed against
36
+ * this store is never warm-once-then-permanently-stale. */
37
+ onCommit;
33
38
  read(sessionKey) {
34
39
  return this.recs.get(sessionKey) ?? null;
35
40
  }
@@ -44,6 +49,14 @@ export class InMemorySessionOwnershipStore {
44
49
  // and proposes an epoch ≤ the landed one).
45
50
  if (candidate.ownershipEpoch > curEpoch) {
46
51
  this.recs.set(candidate.sessionKey, candidate);
52
+ try {
53
+ this.onCommit?.(candidate);
54
+ }
55
+ catch {
56
+ /* @silent-fallback-ok — the commit hook is an observability consumer
57
+ (WriteAdmission ownership index); a listener throw must never fail the
58
+ ownership CAS itself. The index's own ingest never throws by design. */
59
+ }
47
60
  return { ok: true, observed: candidate };
48
61
  }
49
62
  return { ok: false, observed: current };
@@ -1 +1 @@
1
- {"version":3,"file":"SessionOwnershipRegistry.js","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SessionOwnershipRegistry.js","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,oBAAoB,EACpB,iBAAiB,GAIlB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,OAAO,6BAA6B;IAChC,IAAI,GAAG,IAAI,GAAG,EAAkE,CAAC;IACzF;;;gEAG4D;IAC5D,QAAQ,CAA4E;IACpF,IAAI,CAAC,UAAkB;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IACD,QAAQ,CAAC,SAAiE;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QAC5D,MAAM,QAAQ,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC;QAC9C,4EAA4E;QAC5E,0EAA0E;QAC1E,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,2CAA2C;QAC3C,IAAI,SAAS,CAAC,cAAc,GAAG,QAAQ,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAC/C,IAAI,CAAC;gBACH,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP;;0FAE0E;YAC5E,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC1C,CAAC;IACD,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;CACF;AAwDD,MAAM,OAAO,wBAAwB;IAClB,CAAC,CAA+B;IACzC,YAAY,GAAG,CAAC,CAAC;IACjB,mBAAmB,GAAG,CAAC,CAAC;IAEhC,YAAY,IAAkC;QAC5C,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IACO,GAAG;QACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,UAAkB;QACrB,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACH,GAAG;QACD,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC;IACpC,CAAC;IAED,wEAAwE;IACxE,OAAO,CAAC,UAAkB;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;QAC/C,OAAO,CAAC,CAAC,cAAc,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,UAAkB;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,cAAc,CAAC;QACpD,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc;YAAE,OAAO,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,GAAG,CAAC,MAAuB,EAAE,GAA0D;QACrF,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC;YACH,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC,CAAC,mNAAmN;QAC7N,CAAC;QACD,MAAM,CAAC,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/H,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAErE,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAC7F,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAE9F,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,4BAA4B,GAAG,CAAC,UAAU,qBAAqB,GAAG,CAAC,QAAQ,EAAE,cAAc,IAAI,CAAC,EAAE,CAAC,CAAC;YACpH,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,2FAA2F;IAC3F,mBAAmB;QACjB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO;QACL,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC5F,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,aAAqB,EACrB,kBAA0B,EAC1B,OAA4C,EAAE;IAE9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACvE,mFAAmF;IACnF,MAAM,SAAS,GAAG,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -35,6 +35,7 @@ import type { SessionManager } from './SessionManager.js';
35
35
  import type { StateManager } from './StateManager.js';
36
36
  import type { TelegramAdapter } from '../messaging/TelegramAdapter.js';
37
37
  import type { TopicResumeMap } from './TopicResumeMap.js';
38
+ import { type MitigationInbound, type SwapWorkGateCallerClass, type WorkProbeResult } from './SwapWorkGate.js';
38
39
  export declare function ensureResumeTranscriptInConfigHome(uuid: string, targetConfigHome: string): boolean;
39
40
  export interface SessionRefreshDeps {
40
41
  sessionManager: SessionManager;
@@ -82,12 +83,73 @@ export interface SessionRefreshDeps {
82
83
  maxPerWindow: number;
83
84
  windowMs: number;
84
85
  };
86
+ /**
87
+ * swap-continuity-antithrash §4.2 — the in-flight work gate, bound at THIS
88
+ * primitive (the one funnel every session-killing account/model/refresh
89
+ * mutation flows through). Constructor-injected on purpose: §7.1 marks
90
+ * `swapContinuity.enabled` restart-required; the numeric knobs + dryRun are
91
+ * read live via `getKnobs`. Absent/null ⇒ byte-for-byte today's behavior.
92
+ */
93
+ workGateCtx?: SwapContinuityGateContext | null;
85
94
  /** Injectable clock for tests. Defaults to Date.now. */
86
95
  clock?: () => number;
87
96
  }
97
+ /** The work-gate context SessionRefresh consumes (§4.2/§4.3/§4.5). */
98
+ export interface SwapContinuityGateContext {
99
+ /** SwapWorkGate.probe — the stateless busy predicate. */
100
+ probe: (tmuxSession: string) => Promise<WorkProbeResult>;
101
+ /** Live knobs (dryRun + grace bounds read per evaluation, §7.1). */
102
+ getKnobs: () => {
103
+ enabled: boolean;
104
+ dryRun: boolean;
105
+ reactiveGraceMs: number;
106
+ recheckMs: number;
107
+ };
108
+ /** Swap-ledger hooks (optional — present when the anti-thrash engine is wired). */
109
+ recordProceeded?: (args: {
110
+ session: string;
111
+ kind: 'reactive' | 'interactive';
112
+ callerClass: string;
113
+ nowMs: number;
114
+ from: string;
115
+ to?: string;
116
+ reason: 'busy-turn' | 'busy-subagents' | 'busy-indeterminate';
117
+ inFlight: {
118
+ turn: boolean;
119
+ subagents: number;
120
+ };
121
+ subagentLeg: 'ok' | 'absent' | 'indeterminate';
122
+ killedSubagents?: number;
123
+ killedSubagentList?: Array<{
124
+ agentType: string;
125
+ ageMinutes: number;
126
+ transcriptPath?: string;
127
+ }>;
128
+ inbound: 'reinjected' | 'none' | 'unknown';
129
+ force?: boolean;
130
+ }) => void;
131
+ recordInteractiveRefusal?: (args: {
132
+ session: string;
133
+ nowMs: number;
134
+ inFlight: {
135
+ turn: boolean;
136
+ subagents: number;
137
+ };
138
+ subagentLeg: 'ok' | 'absent' | 'indeterminate';
139
+ }) => void;
140
+ /** §4.3(2) — the last unanswered inbound for a topic (Q4: in-memory map in
141
+ * v1; 'unknown' = map unavailable / post-restart). */
142
+ resolveUnansweredInbound?: (topicId: number) => MitigationInbound | 'none' | 'unknown';
143
+ /** Injectable sleep for tests (grace-loop pacing). */
144
+ wait?: (ms: number) => Promise<void>;
145
+ }
88
146
  export type RefreshResult = {
89
147
  ok: true;
90
148
  newSessionName: string;
149
+ /** §4.3 — this refresh proceeded OVER busy work with the mitigation
150
+ * payload attached (reactive-after-grace or force). Lets the swap
151
+ * orchestration avoid double-recording (the proceeded row is written). */
152
+ proceededOverBusy?: boolean;
91
153
  /** Telegram topic id, or (Slack) the stable NEGATIVE synthetic id —
92
154
  * same hash the rest of the system uses to bridge Slack channels into
93
155
  * numeric topic space. Kept required for back-compat consumers. */
@@ -101,8 +163,18 @@ export type RefreshResult = {
101
163
  ok: false;
102
164
  code: RefreshFailureCode;
103
165
  message: string;
166
+ /** §4.5 session-busy refusal payload (counts and ages only — no titles,
167
+ * no transcript paths, no message content on the wire). */
168
+ turnInFlight?: boolean;
169
+ subagents?: Array<{
170
+ agentType: string;
171
+ ageMinutes: number;
172
+ }>;
173
+ /** 'absent' = no readable claudeSessionId (R5-M1) — `subagents` is then
174
+ * OMITTED: unreadable is never rendered as an empty list. */
175
+ subagentLeg?: 'absent' | 'indeterminate';
104
176
  };
105
- export type RefreshFailureCode = 'rate_limited' | 'session_not_found' | 'not_telegram_bound' | 'no_telegram_adapter' | 'slack_respawner_unwired' | 'refresh_in_progress';
177
+ export type RefreshFailureCode = 'rate_limited' | 'session_not_found' | 'not_telegram_bound' | 'no_telegram_adapter' | 'slack_respawner_unwired' | 'refresh_in_progress' | 'session-busy';
106
178
  export interface RefreshOptions {
107
179
  sessionName: string;
108
180
  followUpPrompt?: string;
@@ -128,6 +200,20 @@ export interface RefreshOptions {
128
200
  */
129
201
  configHome?: string;
130
202
  accountId?: string;
203
+ /**
204
+ * swap-continuity-antithrash §4.2 — the work-gate caller class. Set ONLY by
205
+ * server-internal call sites (invariant I11 — no route ever populates it
206
+ * from request input; a wire-derived 'recovery' would be a gate bypass by
207
+ * construction). Absent/unlisted callers default to 'interactive-refresh'
208
+ * (the safest class: nothing is killed, the caller is told why).
209
+ */
210
+ callerClass?: SwapWorkGateCallerClass;
211
+ /**
212
+ * §4.5 — interactive override of the work gate ONLY (never the rate guard).
213
+ * Bearer-level authority, recorded as such in the ledger. A `force` over
214
+ * busy work proceeds WITH the §4.3 mitigation payload attached.
215
+ */
216
+ force?: boolean;
131
217
  }
132
218
  export declare class SessionRefresh {
133
219
  private readonly deps;
@@ -152,6 +238,35 @@ export declare class SessionRefresh {
152
238
  * unexpected internal errors from the respawner callback.
153
239
  */
154
240
  refreshSession(opts: RefreshOptions): Promise<RefreshResult>;
241
+ /**
242
+ * The §4.2 work-gate consult. Returns:
243
+ * - `{}` — proceed normally (gate absent/dark/idle/dry-run/recovery)
244
+ * - `{ refusal }` — structured refusal (nothing killed)
245
+ * - `{ mitigationBlock }` — proceed OVER busy work with the §4.3
246
+ * mitigation payload (reactive-after-grace or force)
247
+ */
248
+ private consultWorkGate;
249
+ /**
250
+ * §4.5 pre-202 interactive busy precheck — lets `/sessions/refresh` answer
251
+ * the truth SYNCHRONOUSLY (HTTP 409 `session-busy`) instead of
252
+ * accepting-then-failing. Returns null when the gate is absent/dark/dry-run
253
+ * or the session is idle (the route then 202s and schedules the refresh; the
254
+ * in-funnel gate remains the authority for the raced window). A busy verdict
255
+ * records the §6.1 interactive refusal row.
256
+ */
257
+ precheckInteractiveBusy(sessionName: string): Promise<{
258
+ turnInFlight: boolean;
259
+ subagents?: Array<{
260
+ agentType: string;
261
+ ageMinutes: number;
262
+ }>;
263
+ subagentLeg?: 'absent' | 'indeterminate';
264
+ message: string;
265
+ } | null>;
266
+ /** Build + record the §4.3 mitigation payload. Additive to the respawn and
267
+ * never gates it — a failure here logs and proceeds (the kill is already
268
+ * justified when we reach here; the mitigation must not become a new wedge). */
269
+ private buildMitigation;
155
270
  /**
156
271
  * Returns true if a fresh refresh is allowed under the rolling window cap.
157
272
  * Pure read of the recent-refresh ledger; does not mutate.
@@ -1 +1 @@
1
- {"version":3,"file":"SessionRefresh.d.ts","sourceRoot":"","sources":["../../src/core/SessionRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA2B1D,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CA8BlG;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,SAAS,EAAE,CACT,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,SAAS;IAClC;8EAC0E;IAC1E,WAAW,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KACtD,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,6EAA6E;IAC7E,SAAS,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,aAAa,GACrB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,cAAc,EAAE,MAAM,CAAC;IACvB;;wEAEoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB;iEAC6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,GACrB,yBAAyB,GACzB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IACpE;;oDAEgD;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;gBAEvC,IAAI,EAAE,kBAAkB;IAOpC;;;;;;;;;;OAUG;IACG,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA0MlE;;;OAGG;IACH,OAAO,CAAC,cAAc;IAWtB,iEAAiE;IACjE,OAAO,CAAC,aAAa;IAOrB;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAKrB"}
1
+ {"version":3,"file":"SessionRefresh.d.ts","sourceRoot":"","sources":["../../src/core/SessionRefresh.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,7 @@ import fs from 'node:fs';
34
34
  import path from 'node:path';
35
35
  import { ensureInteractiveReady } from './ensureInteractiveReady.js';
36
36
  import { slackConversationKey, slackRoutingKeySyntheticId, } from './slackRefreshBinding.js';
37
+ import { buildMitigationPayload, } from './SwapWorkGate.js';
37
38
  /**
38
39
  * Account-swap conversation continuity. Claude stores conversation transcripts
39
40
  * PER CONFIG HOME (`<CLAUDE_CONFIG_DIR>/projects/<projectDir>/<uuid>.jsonl`), so a
@@ -204,22 +205,43 @@ export class SessionRefresh {
204
205
  message: `A refresh is already in progress for "${sessionName}".`,
205
206
  };
206
207
  }
207
- // ── rate guard ─────────────────────────────────────────────────────
208
- if (!this.checkRateLimit(sessionName)) {
209
- this.logRateLimit(sessionName, reason);
210
- return {
211
- ok: false,
212
- code: 'rate_limited',
213
- message: `Refresh rate limit exceeded (${this.maxPerWindow} per ${Math.round(this.windowMs / 60000)} minutes) for session "${sessionName}".`,
214
- };
215
- }
216
- // ── attempt ────────────────────────────────────────────────────────
217
- // Record the attempt against the rate guard window BEFORE the work —
218
- // we count attempts, not successes, so a flapping respawner can't
219
- // bypass the cap. Also mark in-flight so a parallel call refuses.
220
- this.recordRefresh(sessionName);
208
+ // Mark in-flight BEFORE the work gate: the reactive grace loop can hold
209
+ // this call open for up to reactiveGraceMs, and a parallel refresh racing
210
+ // through that window must refuse rather than double-kill.
221
211
  this.inFlight.add(sessionName);
212
+ let followUpWithMitigations = followUpPrompt;
213
+ let proceededOverBusy = false;
222
214
  try {
215
+ // ── work gate (swap-continuity-antithrash §4.2) — BEFORE the rate
216
+ // guard: a deferred or refused attempt consumes ZERO rate budget,
217
+ // otherwise a busy session's own deferrals would exhaust the budget
218
+ // and starve the eventual legitimate swap. ──
219
+ const gate = await this.consultWorkGate(opts, stateSession, topicId);
220
+ if (gate.refusal)
221
+ return gate.refusal;
222
+ if (gate.mitigationBlock !== undefined) {
223
+ proceededOverBusy = true;
224
+ if (gate.mitigationBlock.length > 0) {
225
+ followUpWithMitigations = [followUpPrompt, gate.mitigationBlock]
226
+ .filter((s) => !!s && s.length > 0)
227
+ .join('\n\n');
228
+ }
229
+ }
230
+ // ── rate guard ───────────────────────────────────────────────────
231
+ if (!this.checkRateLimit(sessionName)) {
232
+ this.logRateLimit(sessionName, reason);
233
+ return {
234
+ ok: false,
235
+ code: 'rate_limited',
236
+ message: `Refresh rate limit exceeded (${this.maxPerWindow} per ${Math.round(this.windowMs / 60000)} minutes) for session "${sessionName}".`,
237
+ };
238
+ }
239
+ // ── attempt ──────────────────────────────────────────────────────
240
+ // Record the attempt against the rate guard window BEFORE the work —
241
+ // we count attempts, not successes, so a flapping respawner can't
242
+ // bypass the cap. (`force` overrides ONLY the work gate, never the
243
+ // rate guard — a forced refresh still consumes rate budget, §4.5.)
244
+ this.recordRefresh(sessionName);
223
245
  // Kill via sessionManager so the beforeSessionKill listener fires
224
246
  // and persists the resume UUID using session.claudeSessionId. This
225
247
  // replaces the previous SessionRefresh-side findUuidForSession+save
@@ -284,7 +306,7 @@ export class SessionRefresh {
284
306
  // then re-register the routing-key → session binding. Guarded non-null
285
307
  // by the slack_respawner_unwired refusal in detect.
286
308
  if (topicId === null && slackRoutingKey !== null) {
287
- const newSessionName = await this.deps.slackRespawner(sessionName, slackRoutingKey, followUpPrompt, accountSwap);
309
+ const newSessionName = await this.deps.slackRespawner(sessionName, slackRoutingKey, followUpWithMitigations, accountSwap);
288
310
  const conversationKey = slackConversationKey(slackRoutingKey);
289
311
  // ── verify + finalize (Slack) ──────────────────────────────────
290
312
  console.log(`[SessionRefresh] Refreshed "${sessionName}" → "${newSessionName}" (${conversationKey})${reason ? ` reason="${reason}"` : ''}`);
@@ -294,17 +316,258 @@ export class SessionRefresh {
294
316
  topicId: slackRoutingKeySyntheticId(slackRoutingKey),
295
317
  platform: 'slack',
296
318
  conversationKey,
319
+ ...(proceededOverBusy ? { proceededOverBusy: true } : {}),
297
320
  };
298
321
  }
299
- const newSessionName = await this.deps.respawner(sessionName, topicId, followUpPrompt, accountSwap);
322
+ const newSessionName = await this.deps.respawner(sessionName, topicId, followUpWithMitigations, accountSwap);
300
323
  // ── verify + finalize ────────────────────────────────────────────
301
324
  console.log(`[SessionRefresh] Refreshed "${sessionName}" → "${newSessionName}" (topic ${topicId})${reason ? ` reason="${reason}"` : ''}`);
302
- return { ok: true, newSessionName, topicId: topicId };
325
+ return { ok: true, newSessionName, topicId: topicId, ...(proceededOverBusy ? { proceededOverBusy: true } : {}) };
303
326
  }
304
327
  finally {
305
328
  this.inFlight.delete(sessionName);
306
329
  }
307
330
  }
331
+ /**
332
+ * The §4.2 work-gate consult. Returns:
333
+ * - `{}` — proceed normally (gate absent/dark/idle/dry-run/recovery)
334
+ * - `{ refusal }` — structured refusal (nothing killed)
335
+ * - `{ mitigationBlock }` — proceed OVER busy work with the §4.3
336
+ * mitigation payload (reactive-after-grace or force)
337
+ */
338
+ async consultWorkGate(opts, stateSession, topicId) {
339
+ const ctx = this.deps.workGateCtx;
340
+ if (!ctx)
341
+ return {};
342
+ let knobs;
343
+ try {
344
+ knobs = ctx.getKnobs();
345
+ }
346
+ catch {
347
+ // @silent-fallback-ok: a broken knob getter reads as gate-dark — the
348
+ // work gate is ADDITIVE and must never block a refresh (§4.5).
349
+ return {};
350
+ }
351
+ if (!knobs.enabled)
352
+ return {};
353
+ const callerClass = opts.callerClass ?? 'interactive-refresh';
354
+ // Recovery is EXEMPT by explicit class (§4.2): the session is wedged, its
355
+ // "work" is not progressing; gating recovery on a broken pane's
356
+ // indicators would deadlock recovery.
357
+ if (callerClass === 'recovery')
358
+ return {};
359
+ let probe;
360
+ try {
361
+ probe = await ctx.probe(opts.sessionName);
362
+ }
363
+ catch {
364
+ // @silent-fallback-ok: probe machinery itself failed — I7: uncertainty
365
+ // resolves BUSY, the safe direction (the refresh is refused/deferred,
366
+ // never a wrong kill); the refusal itself is the loud surface.
367
+ probe = {
368
+ busy: true,
369
+ turnLeg: 'indeterminate',
370
+ subagentLeg: 'indeterminate',
371
+ turnInFlight: false,
372
+ subagents: null,
373
+ reason: 'busy-indeterminate',
374
+ };
375
+ }
376
+ if (!probe.busy)
377
+ return {};
378
+ const busyReason = probe.reason ?? 'busy-indeterminate';
379
+ if (knobs.dryRun) {
380
+ // Rung-2 soak: log the would-decision, change nothing.
381
+ const would = callerClass === 'proactive-swap' ? 'WOULD-DEFER' : callerClass === 'reactive-swap' || opts.force ? 'WOULD-MITIGATE' : 'WOULD-REFUSE';
382
+ console.log(`[SwapWorkGate] ${would} session=${opts.sessionName} caller=${callerClass} reason=${busyReason} (dryRun — no behavior change)`);
383
+ return {};
384
+ }
385
+ if (callerClass === 'proactive-swap') {
386
+ // The monitor owns the deferral lifecycle; the funnel's job is that
387
+ // nothing is killed. The structured refusal reads as "defer" upstream.
388
+ return {
389
+ refusal: {
390
+ ok: false,
391
+ code: 'session-busy',
392
+ message: `Session "${opts.sessionName}" has in-flight work (${busyReason}) — proactive swap deferred, retried next tick.`,
393
+ },
394
+ };
395
+ }
396
+ if (callerClass === 'interactive-refresh' && !opts.force) {
397
+ // §4.5 pre-202 structured refusal — the caller decides: wait, or
398
+ // re-issue with force:true. Counts and ages only on the wire.
399
+ try {
400
+ ctx.recordInteractiveRefusal?.({
401
+ session: opts.sessionName,
402
+ nowMs: this.clock(),
403
+ inFlight: { turn: probe.turnInFlight, subagents: probe.subagents?.length ?? 0 },
404
+ subagentLeg: probe.subagentLeg,
405
+ });
406
+ }
407
+ catch {
408
+ /* ledger hooks are additive — never block the refusal itself */
409
+ }
410
+ const refusal = {
411
+ ok: false,
412
+ code: 'session-busy',
413
+ message: `Session "${opts.sessionName}" has in-flight work (${busyReason}). Wait for it to land, or re-issue with force:true (the kill then carries the mitigation payload).`,
414
+ turnInFlight: probe.turnInFlight,
415
+ };
416
+ if (probe.subagentLeg === 'ok') {
417
+ // agentType + ageMinutes only — never titles/paths/content (§4.5).
418
+ refusal.subagents = (probe.subagents ?? []).map((s) => ({ agentType: s.agentType, ageMinutes: s.ageMinutes }));
419
+ }
420
+ else {
421
+ // Unreadable is never rendered as an empty list (R5-M1).
422
+ refusal.subagentLeg = probe.subagentLeg;
423
+ }
424
+ return { refusal };
425
+ }
426
+ if (callerClass === 'reactive-swap') {
427
+ // §4.2: bounded grace — re-check every recheckMs, execute at the FIRST
428
+ // not-busy observation, never sitting out the full grace; at the
429
+ // deadline the swap proceeds WITH mitigations (any new turn on a walled
430
+ // account is failing anyway). Never refused: deferring long has no
431
+ // upside; the grace only absorbs a mid-write tool call.
432
+ const wait = ctx.wait ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
433
+ const deadline = this.clock() + Math.max(0, knobs.reactiveGraceMs);
434
+ const recheck = Math.max(250, knobs.recheckMs);
435
+ while (this.clock() < deadline) {
436
+ await wait(Math.min(recheck, Math.max(1, deadline - this.clock())));
437
+ try {
438
+ const p = await ctx.probe(opts.sessionName);
439
+ probe = p;
440
+ if (!p.busy)
441
+ return {}; // work landed — a clean swap, no mitigations
442
+ }
443
+ catch {
444
+ /* stays busy-for-grace (I7) — the deadline still bounds the wait */
445
+ }
446
+ }
447
+ return { mitigationBlock: this.buildMitigation(opts, stateSession, topicId, probe, false) };
448
+ }
449
+ // interactive-refresh + force: proceed with the mitigation toll (§4.5).
450
+ return { mitigationBlock: this.buildMitigation(opts, stateSession, topicId, probe, true) };
451
+ }
452
+ /**
453
+ * §4.5 pre-202 interactive busy precheck — lets `/sessions/refresh` answer
454
+ * the truth SYNCHRONOUSLY (HTTP 409 `session-busy`) instead of
455
+ * accepting-then-failing. Returns null when the gate is absent/dark/dry-run
456
+ * or the session is idle (the route then 202s and schedules the refresh; the
457
+ * in-funnel gate remains the authority for the raced window). A busy verdict
458
+ * records the §6.1 interactive refusal row.
459
+ */
460
+ async precheckInteractiveBusy(sessionName) {
461
+ const ctx = this.deps.workGateCtx;
462
+ if (!ctx)
463
+ return null;
464
+ let knobs;
465
+ try {
466
+ knobs = ctx.getKnobs();
467
+ }
468
+ catch {
469
+ // @silent-fallback-ok: a broken knob getter reads as gate-dark — the
470
+ // work gate is ADDITIVE and must never block a refresh (§4.5).
471
+ return null;
472
+ }
473
+ if (!knobs.enabled || knobs.dryRun)
474
+ return null;
475
+ let probe;
476
+ try {
477
+ probe = await ctx.probe(sessionName);
478
+ }
479
+ catch {
480
+ // @silent-fallback-ok: probe machinery failed — I7: uncertainty resolves
481
+ // BUSY, the safe direction (the caller gets the 409/refusal, never a
482
+ // wrong kill); the refusal itself is the loud surface.
483
+ probe = {
484
+ busy: true,
485
+ turnLeg: 'indeterminate',
486
+ subagentLeg: 'indeterminate',
487
+ turnInFlight: false,
488
+ subagents: null,
489
+ reason: 'busy-indeterminate',
490
+ };
491
+ }
492
+ if (!probe.busy)
493
+ return null;
494
+ try {
495
+ ctx.recordInteractiveRefusal?.({
496
+ session: sessionName,
497
+ nowMs: this.clock(),
498
+ inFlight: { turn: probe.turnInFlight, subagents: probe.subagents?.length ?? 0 },
499
+ subagentLeg: probe.subagentLeg,
500
+ });
501
+ }
502
+ catch {
503
+ /* ledger hooks are additive — never block the refusal itself */
504
+ }
505
+ const busyReason = probe.reason ?? 'busy-indeterminate';
506
+ const out = {
507
+ turnInFlight: probe.turnInFlight,
508
+ message: `Session "${sessionName}" has in-flight work (${busyReason}). Wait for it to land, or re-issue with force:true (the kill then carries the mitigation payload).`,
509
+ };
510
+ if (probe.subagentLeg === 'ok') {
511
+ // Counts and ages only — never titles/paths/content (§4.5).
512
+ out.subagents = (probe.subagents ?? []).map((s) => ({ agentType: s.agentType, ageMinutes: s.ageMinutes }));
513
+ }
514
+ else {
515
+ // Unreadable is never rendered as an empty list (R5-M1).
516
+ out.subagentLeg = probe.subagentLeg;
517
+ }
518
+ return out;
519
+ }
520
+ /** Build + record the §4.3 mitigation payload. Additive to the respawn and
521
+ * never gates it — a failure here logs and proceeds (the kill is already
522
+ * justified when we reach here; the mitigation must not become a new wedge). */
523
+ buildMitigation(opts, stateSession, topicId, probe, forced) {
524
+ try {
525
+ const ctx = this.deps.workGateCtx;
526
+ // Unreadable ≠ zero (R5-M1): a null enumeration renders the honesty
527
+ // line, never an implicit empty list.
528
+ const killed = probe.subagentLeg === 'ok' ? (probe.subagents ?? []) : null;
529
+ let inbound = 'unknown';
530
+ if (topicId !== null && ctx.resolveUnansweredInbound) {
531
+ try {
532
+ inbound = ctx.resolveUnansweredInbound(topicId);
533
+ }
534
+ catch {
535
+ inbound = 'unknown';
536
+ }
537
+ }
538
+ const busyReason = probe.reason ?? 'busy-indeterminate';
539
+ const inboundState = inbound === 'none' ? 'none' : inbound === 'unknown' ? 'unknown' : 'reinjected';
540
+ try {
541
+ ctx.recordProceeded?.({
542
+ session: opts.sessionName,
543
+ kind: forced ? 'interactive' : 'reactive',
544
+ callerClass: forced ? 'interactive-refresh' : 'reactive-swap',
545
+ nowMs: this.clock(),
546
+ from: stateSession.subscriptionAccountId ?? '',
547
+ ...(opts.accountId ? { to: opts.accountId } : {}),
548
+ reason: busyReason,
549
+ inFlight: { turn: probe.turnInFlight, subagents: probe.subagents?.length ?? 0 },
550
+ subagentLeg: probe.subagentLeg,
551
+ ...(killed !== null ? { killedSubagents: killed.length, killedSubagentList: killed } : {}),
552
+ inbound: inboundState,
553
+ ...(forced ? { force: true } : {}),
554
+ });
555
+ }
556
+ catch {
557
+ /* @silent-fallback-ok: ledger hooks never gate the respawn — the
558
+ PROCEEDED console line below is the loud surface for this path */
559
+ }
560
+ console.log(`[SwapWorkGate] PROCEEDED-WITH-MITIGATIONS session=${opts.sessionName} caller=${forced ? 'interactive-refresh' : 'reactive-swap'} ` +
561
+ `killedSubagents=${killed !== null ? killed.length : 'unreadable'} inbound=${inboundState}`);
562
+ return buildMitigationPayload({ killedSubagents: killed, inbound });
563
+ }
564
+ catch {
565
+ // @silent-fallback-ok: mitigations are best-effort and never gate the
566
+ // respawn — the console.warn here IS the report; the respawn proceeds.
567
+ console.warn(`[SwapWorkGate] mitigation payload failed for session=${opts.sessionName} — proceeding without it (mitigations never gate the respawn)`);
568
+ return '';
569
+ }
570
+ }
308
571
  /**
309
572
  * Returns true if a fresh refresh is allowed under the rolling window cap.
310
573
  * Pure read of the recent-refresh ledger; does not mutate.