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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +259 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +11 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +11 -0
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/LocalSessionOwnershipStore.d.ts +5 -0
- package/dist/core/LocalSessionOwnershipStore.d.ts.map +1 -1
- package/dist/core/LocalSessionOwnershipStore.js +13 -0
- package/dist/core/LocalSessionOwnershipStore.js.map +1 -1
- package/dist/core/ModelSwapService.d.ts +13 -0
- package/dist/core/ModelSwapService.d.ts.map +1 -1
- package/dist/core/ModelSwapService.js +11 -0
- package/dist/core/ModelSwapService.js.map +1 -1
- package/dist/core/ModelTierEscalation.d.ts +10 -0
- package/dist/core/ModelTierEscalation.d.ts.map +1 -1
- package/dist/core/ModelTierEscalation.js +4 -0
- package/dist/core/ModelTierEscalation.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +35 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ProactiveSwapMonitor.d.ts +74 -20
- package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
- package/dist/core/ProactiveSwapMonitor.js +349 -28
- package/dist/core/ProactiveSwapMonitor.js.map +1 -1
- package/dist/core/QuotaAwareScheduler.d.ts +70 -3
- package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
- package/dist/core/QuotaAwareScheduler.js +100 -16
- package/dist/core/QuotaAwareScheduler.js.map +1 -1
- package/dist/core/SessionBuildContextStore.d.ts +20 -0
- package/dist/core/SessionBuildContextStore.d.ts.map +1 -1
- package/dist/core/SessionBuildContextStore.js +20 -3
- package/dist/core/SessionBuildContextStore.js.map +1 -1
- package/dist/core/SessionManager.d.ts +33 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +105 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionOwnershipRegistry.d.ts +16 -0
- package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -1
- package/dist/core/SessionOwnershipRegistry.js +13 -0
- package/dist/core/SessionOwnershipRegistry.js.map +1 -1
- package/dist/core/SessionRefresh.d.ts +116 -1
- package/dist/core/SessionRefresh.d.ts.map +1 -1
- package/dist/core/SessionRefresh.js +280 -17
- package/dist/core/SessionRefresh.js.map +1 -1
- package/dist/core/StateManager.d.ts +20 -0
- package/dist/core/StateManager.d.ts.map +1 -1
- package/dist/core/StateManager.js +72 -4
- package/dist/core/StateManager.js.map +1 -1
- package/dist/core/SwapAntiThrash.d.ts +329 -0
- package/dist/core/SwapAntiThrash.d.ts.map +1 -0
- package/dist/core/SwapAntiThrash.js +1025 -0
- package/dist/core/SwapAntiThrash.js.map +1 -0
- package/dist/core/SwapLedger.d.ts +168 -0
- package/dist/core/SwapLedger.d.ts.map +1 -0
- package/dist/core/SwapLedger.js +275 -0
- package/dist/core/SwapLedger.js.map +1 -0
- package/dist/core/SwapWorkGate.d.ts +100 -0
- package/dist/core/SwapWorkGate.d.ts.map +1 -0
- package/dist/core/SwapWorkGate.js +175 -0
- package/dist/core/SwapWorkGate.js.map +1 -0
- package/dist/core/WriteAdmission.d.ts +277 -0
- package/dist/core/WriteAdmission.d.ts.map +1 -0
- package/dist/core/WriteAdmission.js +514 -0
- package/dist/core/WriteAdmission.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts +132 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -0
- package/dist/core/WriteDomainRegistry.js +186 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +89 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +58 -1
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +20 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +115 -3
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +3 -1
- package/upgrades/1.3.729.md +92 -0
- package/upgrades/1.3.730.md +112 -0
- package/upgrades/side-effects/standby-write-reconciliation.md +62 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;GAOG;AACH,qBAAa,6BAA6B;IACxC,OAAO,CAAC,IAAI,CAA6E;IACzF,IAAI,CAAC,UAAU,EAAE,MAAM;IAGvB,QAAQ,CAAC,SAAS,EAAE,OAAO,uBAAuB,EAAE,sBAAsB;;;;
|
|
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":"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,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,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;
|
|
1
|
+
{"version":3,"file":"SessionOwnershipRegistry.js","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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;AAC1D,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AA2B3B,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;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC/C,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,sEAAsE;AACtE,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACzD,oEAAoE;IACpE,QAAQ,EAAE,MAAM;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAClG,mFAAmF;IACnF,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,UAAU,GAAG,aAAa,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,WAAW,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;QAC9D,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;QAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,kBAAkB,CAAC,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,cAAc,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC/F,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;QAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,KAAK,IAAI,CAAC;IACX,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE;QAChC,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;KAChD,KAAK,IAAI,CAAC;IACX;2DACuD;IACvD,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAAC;IACvF,sDAAsD;IACtD,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,MAAM,MAAM,aAAa,GACrB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,cAAc,EAAE,MAAM,CAAC;IACvB;;+EAE2E;IAC3E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;wEAEoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB;iEAC6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D;kEAC8D;IAC9D,WAAW,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC;CAC1C,CAAC;AAEN,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,GACrB,yBAAyB,GACzB,qBAAqB,GAGrB,cAAc,CAAC;AAEnB,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;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;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;IA+NlE;;;;;;OAMG;YACW,eAAe;IAsH7B;;;;;;;OAOG;IACG,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;QAC1D,YAAY,EAAE,OAAO,CAAC;QACtB,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC7D,WAAW,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC;QACzC,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;IA2DT;;qFAEiF;IACjF,OAAO,CAAC,eAAe;IAwDvB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAWtB,iEAAiE;IACjE,OAAO,CAAC,aAAa;IAOrB;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAKrB"}
|
|
@@ -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
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
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,
|
|
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,
|
|
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.
|