@adhdev/daemon-core 0.9.82-rc.403 → 0.9.82-rc.405
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/index.js +94 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -40
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +30 -0
- package/package.json +2 -2
- package/src/providers/cli-provider-instance.ts +152 -48
|
@@ -86,6 +86,8 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
86
86
|
private lastStatus;
|
|
87
87
|
private agentReadyEmitted;
|
|
88
88
|
private generatingStartedAt;
|
|
89
|
+
private fastCollapseSynthesizedTaskId;
|
|
90
|
+
private startupGraceCollapseAt;
|
|
89
91
|
private settings;
|
|
90
92
|
private monitor;
|
|
91
93
|
private generatingDebounceTimer;
|
|
@@ -257,6 +259,34 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
257
259
|
* never claimed twice and a queued task is never double-dispatched.
|
|
258
260
|
*/
|
|
259
261
|
private emitAgentReadyOnce;
|
|
262
|
+
/**
|
|
263
|
+
* GENERATING-BOUNDARY synthetic emit for a first turn that started AND finished
|
|
264
|
+
* inside the startup-grace window without the FSM ever observing a 'generating'
|
|
265
|
+
* frame (an unobservably-fast turn). Synthesizes the agent:generating_started +
|
|
266
|
+
* agent:generating_completed pair so the mesh coordinator learns the worker went
|
|
267
|
+
* idle. Returns true iff it synthesized.
|
|
268
|
+
*
|
|
269
|
+
* Two callers share this defense line:
|
|
270
|
+
* - 'startup_grace_fast_collapse' — the starting→idle transition (the FSM whose
|
|
271
|
+
* spec lacks/hadn't-synced the starting→busy edge collapses starting→idle
|
|
272
|
+
* directly, with no intervening 'generating' frame).
|
|
273
|
+
* - 'startup_grace_idle_turn_collapse' — the idle→idle no-status-change poll
|
|
274
|
+
* (the launch settle already drained starting→idle BEFORE the first turn, so
|
|
275
|
+
* the turn runs+completes while status stays 'idle' the whole time and
|
|
276
|
+
* detectStatusTransition never re-enters its change block — the live rc.403
|
|
277
|
+
* Probe1 miss).
|
|
278
|
+
*
|
|
279
|
+
* False-positive safety (must NOT fire on a benign boot): currentTurnTaskId is
|
|
280
|
+
* set ONLY by onTurnStarted (a real turn STARTED this boot) and persists past
|
|
281
|
+
* completion, so it excludes a true idle boot (null) and a queued-pending first
|
|
282
|
+
* turn that only runs after grace (onTurnStarted not yet called → null).
|
|
283
|
+
* !hasAdapterPendingResponse() excludes a turn still mid-flight. generating
|
|
284
|
+
* never armed (generatingStartedAt===0 && !generatingDebouncePending) means the
|
|
285
|
+
* whole idle→busy→idle never happened for this turn. The once-per-turn guard
|
|
286
|
+
* (fastCollapseSynthesizedTaskId) keeps the re-polled idle-stayed caller from
|
|
287
|
+
* re-emitting every poll.
|
|
288
|
+
*/
|
|
289
|
+
private maybeSynthesizeStartupGraceCollapse;
|
|
260
290
|
private detectStatusTransition;
|
|
261
291
|
private pushEvent;
|
|
262
292
|
private flushEvents;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.405",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.405",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -140,6 +140,19 @@ const NATIVE_HISTORY_MESH_IDLE_SETTLE_MS = 4000;
|
|
|
140
140
|
// DUPLICATE_DISPATCH_WINDOW_MS (mesh-tools) so the daemon's bubble-level guard
|
|
141
141
|
// covers the same retry horizon as the MCP-level dispatch dedup.
|
|
142
142
|
const USER_INPUT_ACK_DEDUP_WINDOW_MS = 60_000;
|
|
143
|
+
// GENERATING-BOUNDARY (R4c): window — measured from the startup-grace COLLAPSE moment
|
|
144
|
+
// (startupGraceCollapseAt), not boot — inside which a "first turn that ran+completed
|
|
145
|
+
// without the FSM ever observing a 'generating' frame" is attributed to the startup-grace
|
|
146
|
+
// collapse and synthesized (reason 'startup_grace_idle_turn_collapse').
|
|
147
|
+
// The spec's startup-grace exit is elapsed_ms=8000, so the FSM spends ~8s in 'starting'
|
|
148
|
+
// before collapsing to idle; a turn can be dispatched a few seconds AFTER that collapse
|
|
149
|
+
// (the live R4b miss: collapse at boot+8s, dispatch at boot+12.4s — already past a 12s
|
|
150
|
+
// boot-anchored window before the turn even started). Anchoring on the collapse moment
|
|
151
|
+
// makes the window cover dispatch-delay + turn-duration. The strong discriminator is
|
|
152
|
+
// generatingStartedAt===0 (generating was never observed) AND a started-but-finished turn
|
|
153
|
+
// — the window only keeps the synthesized reason honest and scopes the synthesis to the
|
|
154
|
+
// boot collapse, so a much-later unobservably-fast turn is not mislabelled a startup collapse.
|
|
155
|
+
const STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS = 12_000;
|
|
143
156
|
|
|
144
157
|
/** Events that signal a dispatched mesh task has reached a terminal state.
|
|
145
158
|
* Detach the mesh assignment after emitting one of these so the worker's
|
|
@@ -464,6 +477,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
464
477
|
// first sets it; the other becomes a no-op.
|
|
465
478
|
private agentReadyEmitted = false;
|
|
466
479
|
private generatingStartedAt: number = 0;
|
|
480
|
+
// GENERATING-BOUNDARY (R4b): the per-turn taskId for which a startup-grace
|
|
481
|
+
// started+completed pair was already synthesized. Both fast-collapse callers
|
|
482
|
+
// (starting→idle transition AND the idle-stayed no-status-change poll) route
|
|
483
|
+
// through maybeSynthesizeStartupGraceCollapse; the idle-stayed caller re-polls
|
|
484
|
+
// steadily while the session sits idle, so this guard makes the synthesis fire
|
|
485
|
+
// AT MOST ONCE per collapsed turn.
|
|
486
|
+
private fastCollapseSynthesizedTaskId: string | null = null;
|
|
487
|
+
// GENERATING-BOUNDARY (R4c): the wall-clock moment the FSM's starting→idle
|
|
488
|
+
// startup-grace collapse was observed (set ONCE, on the first starting→idle
|
|
489
|
+
// transition this boot). The idle-stayed collapse window is anchored on THIS,
|
|
490
|
+
// not on instance/boot time (this.startedAt): the FSM spends the full 8s
|
|
491
|
+
// startup-grace sitting in 'starting' before collapsing, and a turn can be
|
|
492
|
+
// dispatched a few seconds AFTER the collapse — measuring the window from boot
|
|
493
|
+
// would have it already closed by the time that turn lands+completes (the live
|
|
494
|
+
// R4b miss: collapse at boot+8s, dispatch at boot+12.4s > the 12s boot window).
|
|
495
|
+
// Anchoring on the collapse moment makes the window cover dispatch-delay+turn.
|
|
496
|
+
private startupGraceCollapseAt: number | null = null;
|
|
467
497
|
private settings: Record<string, any> = {};
|
|
468
498
|
private monitor: StatusMonitor;
|
|
469
499
|
private generatingDebounceTimer: NodeJS.Timeout | null = null;
|
|
@@ -2022,6 +2052,95 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2022
2052
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
2023
2053
|
}
|
|
2024
2054
|
|
|
2055
|
+
/**
|
|
2056
|
+
* GENERATING-BOUNDARY synthetic emit for a first turn that started AND finished
|
|
2057
|
+
* inside the startup-grace window without the FSM ever observing a 'generating'
|
|
2058
|
+
* frame (an unobservably-fast turn). Synthesizes the agent:generating_started +
|
|
2059
|
+
* agent:generating_completed pair so the mesh coordinator learns the worker went
|
|
2060
|
+
* idle. Returns true iff it synthesized.
|
|
2061
|
+
*
|
|
2062
|
+
* Two callers share this defense line:
|
|
2063
|
+
* - 'startup_grace_fast_collapse' — the starting→idle transition (the FSM whose
|
|
2064
|
+
* spec lacks/hadn't-synced the starting→busy edge collapses starting→idle
|
|
2065
|
+
* directly, with no intervening 'generating' frame).
|
|
2066
|
+
* - 'startup_grace_idle_turn_collapse' — the idle→idle no-status-change poll
|
|
2067
|
+
* (the launch settle already drained starting→idle BEFORE the first turn, so
|
|
2068
|
+
* the turn runs+completes while status stays 'idle' the whole time and
|
|
2069
|
+
* detectStatusTransition never re-enters its change block — the live rc.403
|
|
2070
|
+
* Probe1 miss).
|
|
2071
|
+
*
|
|
2072
|
+
* False-positive safety (must NOT fire on a benign boot): currentTurnTaskId is
|
|
2073
|
+
* set ONLY by onTurnStarted (a real turn STARTED this boot) and persists past
|
|
2074
|
+
* completion, so it excludes a true idle boot (null) and a queued-pending first
|
|
2075
|
+
* turn that only runs after grace (onTurnStarted not yet called → null).
|
|
2076
|
+
* !hasAdapterPendingResponse() excludes a turn still mid-flight. generating
|
|
2077
|
+
* never armed (generatingStartedAt===0 && !generatingDebouncePending) means the
|
|
2078
|
+
* whole idle→busy→idle never happened for this turn. The once-per-turn guard
|
|
2079
|
+
* (fastCollapseSynthesizedTaskId) keeps the re-polled idle-stayed caller from
|
|
2080
|
+
* re-emitting every poll.
|
|
2081
|
+
*/
|
|
2082
|
+
private maybeSynthesizeStartupGraceCollapse(
|
|
2083
|
+
chatTitle: string,
|
|
2084
|
+
now: number,
|
|
2085
|
+
reason: 'startup_grace_fast_collapse' | 'startup_grace_idle_turn_collapse',
|
|
2086
|
+
): boolean {
|
|
2087
|
+
const startedTurnTaskId = typeof (this.adapter as any)?.currentTurnTaskId === 'string'
|
|
2088
|
+
&& (this.adapter as any).currentTurnTaskId.trim()
|
|
2089
|
+
? (this.adapter as any).currentTurnTaskId as string
|
|
2090
|
+
: undefined;
|
|
2091
|
+
const fastCollapsed = !!startedTurnTaskId
|
|
2092
|
+
&& !this.hasAdapterPendingResponse()
|
|
2093
|
+
&& !this.generatingStartedAt
|
|
2094
|
+
&& !this.generatingDebouncePending;
|
|
2095
|
+
if (!fastCollapsed) return false;
|
|
2096
|
+
// Once-per-turn: the idle-stayed caller re-polls steadily while the session
|
|
2097
|
+
// sits idle; without this guard it would re-emit the pair every poll.
|
|
2098
|
+
if (this.fastCollapseSynthesizedTaskId === startedTurnTaskId) return false;
|
|
2099
|
+
|
|
2100
|
+
let fcFinalSummary: string | undefined;
|
|
2101
|
+
let fcEvidenceSource: CompletionFinalAssistantEvidence['source'] = 'unavailable';
|
|
2102
|
+
try {
|
|
2103
|
+
const parsedMessages = this.adapter?.getScriptParsedStatus()?.messages;
|
|
2104
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
2105
|
+
fcEvidenceSource = evidence.source;
|
|
2106
|
+
fcFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
2107
|
+
} catch { /* best-effort */ }
|
|
2108
|
+
const missingEvidence = ((this.provider as any).requiresFinalAssistantBeforeIdle === true || fcEvidenceSource === 'external-native') && !fcFinalSummary;
|
|
2109
|
+
// Mirror the short-generating idle path's suppression: a provider that
|
|
2110
|
+
// requires a final assistant (or external-native history) with NO confirmed
|
|
2111
|
+
// summary and NO mesh context emits nothing — the session is idle with no
|
|
2112
|
+
// confirmed turn, matching startup-blip semantics. With mesh context we still
|
|
2113
|
+
// emit so the coordinator can apply its own timeout/retry logic. Left UNMARKED
|
|
2114
|
+
// so a later poll can retry once the transcript's final assistant lands.
|
|
2115
|
+
const hasMeshContext = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
|
|
2116
|
+
if (missingEvidence && !hasMeshContext) {
|
|
2117
|
+
LOG.info('CLI', `[${this.type}] ${reason} suppressed: missing final assistant evidence, no mesh context (source=${fcEvidenceSource})`);
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
// Mark BEFORE emitting so a re-entrant poll cannot double-fire.
|
|
2121
|
+
this.fastCollapseSynthesizedTaskId = startedTurnTaskId;
|
|
2122
|
+
LOG.info('CLI', `[${this.type}] ${reason}: synthesizing started+completed (taskId=${startedTurnTaskId} source=${fcEvidenceSource} hadFinalSummary=${!!fcFinalSummary})`);
|
|
2123
|
+
// Retroactive started so the started→completed pair (and the chat bubble) is
|
|
2124
|
+
// well-formed; pushEvent stamps the per-turn taskId for CANON-B ack.
|
|
2125
|
+
this.pushEvent({ event: 'agent:generating_started', chatTitle, timestamp: now });
|
|
2126
|
+
if (this.isMeshWorkerSession()) {
|
|
2127
|
+
traceMeshEventStage('fired', this.meshTraceCtx(), `${reason} (source=${fcEvidenceSource})`);
|
|
2128
|
+
}
|
|
2129
|
+
this.pushEvent({
|
|
2130
|
+
event: 'agent:generating_completed',
|
|
2131
|
+
chatTitle,
|
|
2132
|
+
duration: 0,
|
|
2133
|
+
timestamp: now,
|
|
2134
|
+
finalSummary: fcFinalSummary,
|
|
2135
|
+
completionDiagnostic: {
|
|
2136
|
+
reason,
|
|
2137
|
+
finalAssistantEvidenceSource: fcEvidenceSource,
|
|
2138
|
+
...(missingEvidence ? { blockReason: 'missing_final_assistant' } : {}),
|
|
2139
|
+
},
|
|
2140
|
+
});
|
|
2141
|
+
return true;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2025
2144
|
private detectStatusTransition(): void {
|
|
2026
2145
|
const now = Date.now();
|
|
2027
2146
|
// Status-change handling is a hot path: PTY output can fire it many times
|
|
@@ -2276,6 +2395,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2276
2395
|
}
|
|
2277
2396
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
2278
2397
|
this.emitAgentReadyOnce(chatTitle, now);
|
|
2398
|
+
// GENERATING-BOUNDARY (R4c): stamp the collapse moment ONCE so the
|
|
2399
|
+
// idle-stayed window below is anchored on the startup-grace collapse,
|
|
2400
|
+
// not on boot. Set only the first time so a later starting re-entry
|
|
2401
|
+
// cannot slide the window forward.
|
|
2402
|
+
if (this.startupGraceCollapseAt === null) this.startupGraceCollapseAt = now;
|
|
2279
2403
|
// GENERATING-BOUNDARY fast-collapse (R4, win32 startup-grace first turn):
|
|
2280
2404
|
// a turn dispatched into the startup-grace window can START and FINISH while
|
|
2281
2405
|
// the FSM is still in 'starting'. On a daemon whose claude-cli spec has NOT yet
|
|
@@ -2297,54 +2421,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2297
2421
|
// excluded so we don't fire a premature mid-turn completion; idle→busy self-
|
|
2298
2422
|
// corrects once the FSM reaches idle). We fire only when a turn started AND has
|
|
2299
2423
|
// already finished: started-this-boot && !still-in-flight.
|
|
2300
|
-
|
|
2301
|
-
&& (this.adapter as any).currentTurnTaskId.trim()
|
|
2302
|
-
? (this.adapter as any).currentTurnTaskId as string
|
|
2303
|
-
: undefined;
|
|
2304
|
-
const fastCollapsed = !!startedTurnTaskId
|
|
2305
|
-
&& !this.hasAdapterPendingResponse()
|
|
2306
|
-
&& !this.generatingStartedAt
|
|
2307
|
-
&& !this.generatingDebouncePending;
|
|
2308
|
-
if (fastCollapsed) {
|
|
2309
|
-
let fcFinalSummary: string | undefined;
|
|
2310
|
-
let fcEvidenceSource: CompletionFinalAssistantEvidence['source'] = 'unavailable';
|
|
2311
|
-
try {
|
|
2312
|
-
const parsedMessages = this.adapter?.getScriptParsedStatus()?.messages;
|
|
2313
|
-
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
2314
|
-
fcEvidenceSource = evidence.source;
|
|
2315
|
-
fcFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
2316
|
-
} catch { /* best-effort */ }
|
|
2317
|
-
const missingEvidence = ((this.provider as any).requiresFinalAssistantBeforeIdle === true || fcEvidenceSource === 'external-native') && !fcFinalSummary;
|
|
2318
|
-
// Mirror the short-generating idle path's suppression: a provider that
|
|
2319
|
-
// requires a final assistant (or external-native history) with NO confirmed
|
|
2320
|
-
// summary and NO mesh context emits nothing — the session is idle with no
|
|
2321
|
-
// confirmed turn, matching startup-blip semantics. With mesh context we still
|
|
2322
|
-
// emit so the coordinator can apply its own timeout/retry logic.
|
|
2323
|
-
const hasMeshContext = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
|
|
2324
|
-
if (missingEvidence && !hasMeshContext) {
|
|
2325
|
-
LOG.info('CLI', `[${this.type}] startup-grace fast-collapse suppressed: missing final assistant evidence, no mesh context (source=${fcEvidenceSource})`);
|
|
2326
|
-
} else {
|
|
2327
|
-
LOG.info('CLI', `[${this.type}] startup-grace fast-collapse: synthesizing started+completed (taskId=${startedTurnTaskId} source=${fcEvidenceSource} hadFinalSummary=${!!fcFinalSummary})`);
|
|
2328
|
-
// Retroactive started so the started→completed pair (and the chat bubble)
|
|
2329
|
-
// is well-formed; pushEvent stamps the per-turn taskId for CANON-B ack.
|
|
2330
|
-
this.pushEvent({ event: 'agent:generating_started', chatTitle, timestamp: now });
|
|
2331
|
-
if (this.isMeshWorkerSession()) {
|
|
2332
|
-
traceMeshEventStage('fired', this.meshTraceCtx(), `startup-grace fast-collapse (source=${fcEvidenceSource})`);
|
|
2333
|
-
}
|
|
2334
|
-
this.pushEvent({
|
|
2335
|
-
event: 'agent:generating_completed',
|
|
2336
|
-
chatTitle,
|
|
2337
|
-
duration: 0,
|
|
2338
|
-
timestamp: now,
|
|
2339
|
-
finalSummary: fcFinalSummary,
|
|
2340
|
-
completionDiagnostic: {
|
|
2341
|
-
reason: 'startup_grace_fast_collapse',
|
|
2342
|
-
finalAssistantEvidenceSource: fcEvidenceSource,
|
|
2343
|
-
...(missingEvidence ? { blockReason: 'missing_final_assistant' } : {}),
|
|
2344
|
-
},
|
|
2345
|
-
});
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2424
|
+
this.maybeSynthesizeStartupGraceCollapse(chatTitle, now, 'startup_grace_fast_collapse');
|
|
2348
2425
|
} else if (newStatus === 'error') {
|
|
2349
2426
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
2350
2427
|
this.generatingDebouncePending = null;
|
|
@@ -2373,6 +2450,33 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2373
2450
|
this.lastStatus = newStatus;
|
|
2374
2451
|
}
|
|
2375
2452
|
|
|
2453
|
+
// GENERATING-BOUNDARY idle-stayed collapse (R4b): the starting→idle
|
|
2454
|
+
// fast-collapse arm above only fires when the FIRST turn itself drives the
|
|
2455
|
+
// starting→idle transition. When the launch settle already drained
|
|
2456
|
+
// starting→idle BEFORE the first turn arrives, the session is already 'idle'
|
|
2457
|
+
// and a turn that runs+completes inside the startup-grace window — too fast
|
|
2458
|
+
// for any poll to observe a 'generating' frame — produces NO status change
|
|
2459
|
+
// at all (idle→idle). detectStatusTransition's change block above is skipped
|
|
2460
|
+
// entirely, so neither the idle→generating arm nor the starting→idle
|
|
2461
|
+
// fast-collapse arm ever runs, and the mesh coordinator never learns the
|
|
2462
|
+
// worker went idle (the live rc.403 Probe1 miss). Catch it here: an
|
|
2463
|
+
// already-idle poll (no status change), still inside the startup-grace
|
|
2464
|
+
// window, where a turn started this boot and has already finished without
|
|
2465
|
+
// ever arming generating. The same false-positive-safe discriminators apply
|
|
2466
|
+
// (currentTurnTaskId set && !pending && generating never armed), plus a
|
|
2467
|
+
// once-per-turn guard since this branch is re-polled while the session sits
|
|
2468
|
+
// idle. Normal turns that DO reach 'busy' set generatingStartedAt and are
|
|
2469
|
+
// excluded; a queued-pending first turn that only runs after grace falls
|
|
2470
|
+
// outside the window and completes normally via idle→busy→idle.
|
|
2471
|
+
if (
|
|
2472
|
+
newStatus === 'idle'
|
|
2473
|
+
&& previousStatus === 'idle'
|
|
2474
|
+
&& this.startupGraceCollapseAt !== null
|
|
2475
|
+
&& (now - this.startupGraceCollapseAt) < STARTUP_GRACE_IDLE_COLLAPSE_WINDOW_MS
|
|
2476
|
+
) {
|
|
2477
|
+
this.maybeSynthesizeStartupGraceCollapse(chatTitle, now, 'startup_grace_idle_turn_collapse');
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2376
2480
|
// Re-arm the queue-claim agent:ready on the FSM's first GENUINE ready.
|
|
2377
2481
|
//
|
|
2378
2482
|
// The boot-time starting→idle one-shot above is the historical claim
|