@adhdev/daemon-core 1.0.28-rc.11 → 1.0.28-rc.12
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 +124 -77
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +124 -77
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +65 -28
- package/package.json +3 -3
- package/src/providers/cli-provider-instance.ts +175 -104
- package/src/providers/transcript-signal-source.ts +14 -6
|
@@ -192,7 +192,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
192
192
|
private meshStallEmittedForAnchor;
|
|
193
193
|
private meshStallTurnActiveLast;
|
|
194
194
|
private meshStallLastFiredAt;
|
|
195
|
-
private
|
|
195
|
+
private meshStallTranscriptSignalSampled;
|
|
196
196
|
private busyEpoch;
|
|
197
197
|
private fastCollapseSynthesizedTaskId;
|
|
198
198
|
private startupGraceCollapseAt;
|
|
@@ -413,10 +413,17 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
413
413
|
private completedDebounceTimer;
|
|
414
414
|
private completedDebouncePending;
|
|
415
415
|
private lastExternalCompletionProbe;
|
|
416
|
-
/** TX-FSM
|
|
417
|
-
*
|
|
418
|
-
*
|
|
416
|
+
/** TX-FSM: lazily-created transcript signal normalizer. Fed ONLY by
|
|
417
|
+
* transcript reads this instance already performs — it adds zero I/O.
|
|
418
|
+
* Stage 0: its output was a pure shadow observation for the FSM driver.
|
|
419
|
+
* Stage 1: the instance's own stall/growth-hold judgments consume the
|
|
420
|
+
* normalized snapshot too (single source of truth). */
|
|
419
421
|
private transcriptSignalSource;
|
|
422
|
+
/** TX-FSM Stage 1: the latest snapshot the source produced (set inside
|
|
423
|
+
* publishTranscriptSignalObservation). Consumed the same tick by the
|
|
424
|
+
* stall-path / growth-hold judgments via probeNativeTranscriptSignals —
|
|
425
|
+
* never treated as fresh across ticks. */
|
|
426
|
+
private lastTranscriptSignalSnapshot;
|
|
420
427
|
/**
|
|
421
428
|
* The final assistant summary of the last completed turn, cached at
|
|
422
429
|
* completion-emit time. For a native-source provider (antigravity) whose
|
|
@@ -453,15 +460,19 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
453
460
|
private spawnedEnvOverrides;
|
|
454
461
|
private readExternalCompletionMessages;
|
|
455
462
|
/**
|
|
456
|
-
* TX-FSM
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
463
|
+
* TX-FSM: normalize the transcript read that JUST happened into a
|
|
464
|
+
* SignalSnapshot. Stage 0 injected it into the FSM driver as a pure
|
|
465
|
+
* shadow observation (daemon → SpecCliAdapter → FsmDriver); Stage 1
|
|
466
|
+
* additionally caches it (lastTranscriptSignalSnapshot) so the instance's
|
|
467
|
+
* OWN stall/growth-hold judgments consume the SAME normalized snapshot
|
|
468
|
+
* instead of re-running private transcript scans. Fed ONLY by reads this
|
|
469
|
+
* method's caller already performs — it adds zero I/O, so the getState()
|
|
470
|
+
* zero-native-read invariant and the stall-path read cadence are
|
|
471
|
+
* untouched. The source update runs regardless of the adapter hook so a
|
|
472
|
+
* non-spec provider still produces the instance-side snapshot (the FSM
|
|
473
|
+
* injection is simply skipped there). Fail-open end to end — an
|
|
474
|
+
* unresolved transcript or any throw degrades to "no observation", never
|
|
475
|
+
* to a wedge.
|
|
465
476
|
*/
|
|
466
477
|
private publishTranscriptSignalObservation;
|
|
467
478
|
/**
|
|
@@ -592,23 +603,35 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
592
603
|
* starts with a clean slate (a restart is not throttled by a prior stall).
|
|
593
604
|
*/
|
|
594
605
|
private resetMeshStallEpisode;
|
|
595
|
-
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
* source
|
|
604
|
-
*
|
|
606
|
+
/** The result of one native-transcript signal probe: the normalized
|
|
607
|
+
* snapshot the shared TranscriptSignalSource produced from the read, and
|
|
608
|
+
* the very messages it was normalized from (so a judgment site can pull
|
|
609
|
+
* a payload — e.g. the final summary — from the SAME read with zero
|
|
610
|
+
* added I/O). */
|
|
611
|
+
/**
|
|
612
|
+
* TX-FSM Stage 1 — the single native-transcript signal probe (replaces
|
|
613
|
+
* the Stage-0 sampleNativeTranscriptProgress fingerprint sampler). For a
|
|
614
|
+
* native-source provider (its authoritative history is an on-disk
|
|
615
|
+
* transcript file, e.g. kimi's wire.jsonl), perform the read this
|
|
616
|
+
* judgment point already owns — SAME cadence as before, one
|
|
617
|
+
* readExternalCompletionMessages() per call, never more — and return the
|
|
618
|
+
* NORMALIZED SignalSnapshot the shared TranscriptSignalSource produced
|
|
619
|
+
* from it (publishTranscriptSignalObservation runs inside the read), plus
|
|
620
|
+
* the messages that read returned. Judgment sites (the stall watchdog's
|
|
621
|
+
* transcript-advancing axis, the completion growth-hold, the stall-path
|
|
622
|
+
* completion rescue) consume the snapshot's SIGNALS instead of running
|
|
623
|
+
* their own fingerprint/freshness/final-assistant scans — one source of
|
|
624
|
+
* truth for "what does the transcript say right now".
|
|
605
625
|
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* (
|
|
626
|
+
* Class gating goes through resolveTranscriptAuthorityProfile ONLY.
|
|
627
|
+
* Returns null for a non-native-source class (nothing to signal from) and
|
|
628
|
+
* a null snapshot when the read threw — callers keep their fail-open
|
|
629
|
+
* fallbacks ("couldn't tell" never blocks an idle verdict and never
|
|
630
|
+
* fabricates a completion). Cheap enough for the stall path: it runs
|
|
631
|
+
* only at the stall threshold (≥180s of PTY stasis) or during an armed
|
|
632
|
+
* completion-debounce retry, never on the routine 5s tick.
|
|
610
633
|
*/
|
|
611
|
-
private
|
|
634
|
+
private probeNativeTranscriptSignals;
|
|
612
635
|
/**
|
|
613
636
|
* KIMI-MESH-COMPLETION-EMIT (axis 2). Last-chance completion emit for a mesh
|
|
614
637
|
* DELEGATED worker whose PTY has already exited (e.g. killed by a false stall)
|
|
@@ -661,6 +684,20 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
661
684
|
* false for a daemon-owned provider and for a genuinely mid-turn / wedged
|
|
662
685
|
* worker with no in-turn final assistant, so a real stall still fires
|
|
663
686
|
* unchanged. Evidence bar is identical to flushMeshCompletionBeforeCleanup.
|
|
687
|
+
*
|
|
688
|
+
* TX-FSM Stage 1 (FIX 3 probe delegation): for a native-source class the
|
|
689
|
+
* completion VERDICT is the shared TranscriptSignalSource's
|
|
690
|
+
* final_assistant_present signal, normalized from the ONE transcript read
|
|
691
|
+
* the stall watchdog already performed this tick (passed in as
|
|
692
|
+
* `transcriptSignals`) — not a second private read + scan. The emit
|
|
693
|
+
* payload (finalSummary) is extracted from the SAME messages the signal
|
|
694
|
+
* was normalized from, with the SAME turn boundary the legacy path used,
|
|
695
|
+
* so the extraction re-proves the turn scope the signal checked. Fail-open
|
|
696
|
+
* both ways: no usable snapshot (read failed / unresolved / a pure-PTY
|
|
697
|
+
* class, which has no native signal by construction) → the legacy
|
|
698
|
+
* class-appropriate evidence path (completionFinalSummary), unchanged; and
|
|
699
|
+
* a present-but-unextractable signal yields false rather than a
|
|
700
|
+
* payload-less emit.
|
|
664
701
|
*/
|
|
665
702
|
private tryReconcileTranscriptCompletionForStall;
|
|
666
703
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "1.0.28-rc.
|
|
3
|
+
"version": "1.0.28-rc.12",
|
|
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",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"author": "vilmire",
|
|
50
50
|
"license": "AGPL-3.0-or-later",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@adhdev/mesh-shared": "1.0.28-rc.
|
|
53
|
-
"@adhdev/session-host-core": "1.0.28-rc.
|
|
52
|
+
"@adhdev/mesh-shared": "1.0.28-rc.12",
|
|
53
|
+
"@adhdev/session-host-core": "1.0.28-rc.12",
|
|
54
54
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
55
55
|
"ajv": "^8.20.0",
|
|
56
56
|
"ajv-formats": "^3.0.1",
|
|
@@ -19,6 +19,7 @@ import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js'
|
|
|
19
19
|
import type { MeshSendKeyItem, MeshSendKeyName } from '../cli-adapters/provider-cli-shared.js';
|
|
20
20
|
import { resolveTranscriptAuthorityProfile } from './transcript-evidence.js';
|
|
21
21
|
import { TranscriptSignalSource } from './transcript-signal-source.js';
|
|
22
|
+
import type { SignalSnapshot } from './spec/signal-envelope.js';
|
|
22
23
|
import { createCliAdapter } from './spec/route.js';
|
|
23
24
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
24
25
|
import { StatusMonitor } from './status-monitor.js';
|
|
@@ -337,18 +338,20 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
337
338
|
// MESH_WORKER_STALL_REFIRE_COOLDOWN_MS between successive emissions across
|
|
338
339
|
// anchor re-arms (the per-anchor guard only covers a single continuous stall).
|
|
339
340
|
private meshStallLastFiredAt = -1;
|
|
340
|
-
// KIMI-MESH-COMPLETION-EMIT (axis 1)
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// handling can lead to the worker being stopped mid-work → completion
|
|
350
|
-
// emitted).
|
|
351
|
-
|
|
341
|
+
// KIMI-MESH-COMPLETION-EMIT (axis 1) — TX-FSM Stage 1: whether the stall
|
|
342
|
+
// watchdog has consumed a usable native-transcript SIGNAL SNAPSHOT for the
|
|
343
|
+
// current stall episode. A pure-PTY native-source worker running a long,
|
|
344
|
+
// screen-quiet tool (no viewport bytes for minutes) looks stalled by the
|
|
345
|
+
// lastOutputAt clock even though its transcript file is still growing.
|
|
346
|
+
// Before firing the stall, the watchdog consults the shared
|
|
347
|
+
// TranscriptSignalSource's in_turn_progress signal; if the transcript is
|
|
348
|
+
// advancing, the "stall" is a false positive of PTY-render stasis, so we
|
|
349
|
+
// re-arm the anchor instead of paging the coordinator (whose no_progress
|
|
350
|
+
// handling can lead to the worker being stopped mid-work → completion
|
|
351
|
+
// never emitted). The FIRST usable sample of an episode still re-arms
|
|
352
|
+
// unconditionally (no episode-scoped baseline exists yet to prove stasis
|
|
353
|
+
// against) — the historical `!prev → advanced` semantics, preserved.
|
|
354
|
+
private meshStallTranscriptSignalSampled = false;
|
|
352
355
|
// FALSE-IDLE continuity epoch: monotonically bumped on EVERY entry into a busy
|
|
353
356
|
// phase (→generating or →waiting_approval). The completedDebouncePending snapshots
|
|
354
357
|
// this value at arm time (busyEpochAtArm); the flush guard requires it UNCHANGED
|
|
@@ -1427,10 +1430,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1427
1430
|
private completedDebounceTimer: NodeJS.Timeout | null = null;
|
|
1428
1431
|
private completedDebouncePending: CompletedDebouncePending | null = null;
|
|
1429
1432
|
private lastExternalCompletionProbe: ExternalTranscriptProbe | null = null;
|
|
1430
|
-
/** TX-FSM
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
+
/** TX-FSM: lazily-created transcript signal normalizer. Fed ONLY by
|
|
1434
|
+
* transcript reads this instance already performs — it adds zero I/O.
|
|
1435
|
+
* Stage 0: its output was a pure shadow observation for the FSM driver.
|
|
1436
|
+
* Stage 1: the instance's own stall/growth-hold judgments consume the
|
|
1437
|
+
* normalized snapshot too (single source of truth). */
|
|
1433
1438
|
private transcriptSignalSource: TranscriptSignalSource | null = null;
|
|
1439
|
+
/** TX-FSM Stage 1: the latest snapshot the source produced (set inside
|
|
1440
|
+
* publishTranscriptSignalObservation). Consumed the same tick by the
|
|
1441
|
+
* stall-path / growth-hold judgments via probeNativeTranscriptSignals —
|
|
1442
|
+
* never treated as fresh across ticks. */
|
|
1443
|
+
private lastTranscriptSignalSnapshot: SignalSnapshot | null = null;
|
|
1434
1444
|
/**
|
|
1435
1445
|
* The final assistant summary of the last completed turn, cached at
|
|
1436
1446
|
* completion-emit time. For a native-source provider (antigravity) whose
|
|
@@ -1667,19 +1677,21 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1667
1677
|
}
|
|
1668
1678
|
|
|
1669
1679
|
/**
|
|
1670
|
-
* TX-FSM
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
1680
|
+
* TX-FSM: normalize the transcript read that JUST happened into a
|
|
1681
|
+
* SignalSnapshot. Stage 0 injected it into the FSM driver as a pure
|
|
1682
|
+
* shadow observation (daemon → SpecCliAdapter → FsmDriver); Stage 1
|
|
1683
|
+
* additionally caches it (lastTranscriptSignalSnapshot) so the instance's
|
|
1684
|
+
* OWN stall/growth-hold judgments consume the SAME normalized snapshot
|
|
1685
|
+
* instead of re-running private transcript scans. Fed ONLY by reads this
|
|
1686
|
+
* method's caller already performs — it adds zero I/O, so the getState()
|
|
1687
|
+
* zero-native-read invariant and the stall-path read cadence are
|
|
1688
|
+
* untouched. The source update runs regardless of the adapter hook so a
|
|
1689
|
+
* non-spec provider still produces the instance-side snapshot (the FSM
|
|
1690
|
+
* injection is simply skipped there). Fail-open end to end — an
|
|
1691
|
+
* unresolved transcript or any throw degrades to "no observation", never
|
|
1692
|
+
* to a wedge.
|
|
1679
1693
|
*/
|
|
1680
1694
|
private publishTranscriptSignalObservation(messages: unknown[] | null, error = false): void {
|
|
1681
|
-
const adapter = this.adapter as { setSignalObservation?: (snapshot: unknown) => void } | null | undefined;
|
|
1682
|
-
if (typeof adapter?.setSignalObservation !== 'function') return; // non-spec path: no FSM consumer
|
|
1683
1695
|
try {
|
|
1684
1696
|
if (!this.transcriptSignalSource) {
|
|
1685
1697
|
this.transcriptSignalSource = new TranscriptSignalSource({
|
|
@@ -1689,7 +1701,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1689
1701
|
profile: resolveTranscriptAuthorityProfile(this.provider),
|
|
1690
1702
|
turnStartedAt: () => {
|
|
1691
1703
|
const t = (this.adapter as any)?.currentTurnStartedAt;
|
|
1692
|
-
|
|
1704
|
+
if (typeof t === 'number' && Number.isFinite(t)) return t;
|
|
1705
|
+
// Mesh fallback: for an emitsPtyTurnEvents=false worker
|
|
1706
|
+
// (idle→idle collapse) currentTurnStartedAt may never
|
|
1707
|
+
// bind; scope to the task injection instead — the SAME
|
|
1708
|
+
// boundary the stall-path rescue uses, so the signal and
|
|
1709
|
+
// the rescue's payload extraction agree on the turn.
|
|
1710
|
+
return this.meshTaskInjectedAt > 0 ? this.meshTaskInjectedAt : undefined;
|
|
1693
1711
|
},
|
|
1694
1712
|
// Reuse the exact completion machinery (I1) for the
|
|
1695
1713
|
// final_assistant_present signal rather than duplicating
|
|
@@ -1701,8 +1719,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1701
1719
|
const snapshot = this.transcriptSignalSource.update(
|
|
1702
1720
|
{ messages, probe: this.lastExternalCompletionProbe, error },
|
|
1703
1721
|
);
|
|
1704
|
-
|
|
1705
|
-
|
|
1722
|
+
this.lastTranscriptSignalSnapshot = snapshot;
|
|
1723
|
+
const adapter = this.adapter as { setSignalObservation?: (snapshot: unknown) => void } | null | undefined;
|
|
1724
|
+
if (typeof adapter?.setSignalObservation === 'function') {
|
|
1725
|
+
adapter.setSignalObservation(snapshot);
|
|
1726
|
+
}
|
|
1727
|
+
} catch { /* signal collection must never break the read path */ }
|
|
1706
1728
|
}
|
|
1707
1729
|
|
|
1708
1730
|
/**
|
|
@@ -2552,31 +2574,38 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2552
2574
|
const stalledMs = now - this.meshStallAnchorAt;
|
|
2553
2575
|
if (stalledMs < threshold) return;
|
|
2554
2576
|
|
|
2555
|
-
// KIMI-MESH-COMPLETION-EMIT (axis 1)
|
|
2556
|
-
//
|
|
2557
|
-
//
|
|
2558
|
-
//
|
|
2559
|
-
//
|
|
2560
|
-
//
|
|
2561
|
-
//
|
|
2562
|
-
//
|
|
2563
|
-
//
|
|
2564
|
-
//
|
|
2565
|
-
//
|
|
2566
|
-
//
|
|
2567
|
-
//
|
|
2568
|
-
//
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
+
// KIMI-MESH-COMPLETION-EMIT (axis 1) — TX-FSM Stage 1: transcript-aware
|
|
2578
|
+
// stall for native-source providers, now delegated to the SHARED
|
|
2579
|
+
// TranscriptSignalSource. A pure-PTY native-source worker
|
|
2580
|
+
// (transcriptAuthority=provider, e.g. kimi) running a long, screen-quiet
|
|
2581
|
+
// tool emits no viewport bytes for minutes, so the lastOutputAt clock
|
|
2582
|
+
// above reads it as stalled — but its authoritative transcript
|
|
2583
|
+
// (wire.jsonl) is still being appended. Firing monitor:no_progress here
|
|
2584
|
+
// surfaces a false stall to the coordinator whose downstream handling
|
|
2585
|
+
// can stop the worker mid-work, killing the emit window before the real
|
|
2586
|
+
// completion propagates. So before firing, consume the signal source's
|
|
2587
|
+
// in_turn_progress (transcript advanced since the previous sample OR
|
|
2588
|
+
// source mtime inside the growth-quiet window — the same liveness test
|
|
2589
|
+
// the completion growth-hold uses, normalized from the ONE read this
|
|
2590
|
+
// tick): if true, the worker is demonstrably alive — treat it as
|
|
2591
|
+
// progress. Re-arm the anchor to `now` and reset the episode so the
|
|
2592
|
+
// clock restarts from this proven-live moment; only a transcript that
|
|
2593
|
+
// is ALSO static falls through to the genuine-stall fire below. The
|
|
2594
|
+
// FIRST usable sample of an episode re-arms unconditionally — no
|
|
2595
|
+
// episode-scoped baseline exists yet to prove stasis against (the
|
|
2596
|
+
// historical `!prev → advanced` semantics). Fail-open: a non-native-
|
|
2597
|
+
// source class, an unresolved transcript, or a read error yields no
|
|
2598
|
+
// usable snapshot → fall through to the unchanged lastOutputAt-only
|
|
2599
|
+
// judgment.
|
|
2600
|
+
const transcriptSignals = this.probeNativeTranscriptSignals();
|
|
2601
|
+
if (transcriptSignals?.snapshot?.available === true) {
|
|
2602
|
+
const firstSampleThisEpisode = !this.meshStallTranscriptSignalSampled;
|
|
2603
|
+
this.meshStallTranscriptSignalSampled = true;
|
|
2604
|
+
const signalDetail = transcriptSignals.snapshot.detail;
|
|
2605
|
+
if (firstSampleThisEpisode || transcriptSignals.snapshot.signals.in_turn_progress === true) {
|
|
2577
2606
|
if (this.isMeshWorkerSession()) {
|
|
2578
2607
|
traceMeshEventDrop('mesh_worker_stall_transcript_advancing', this.meshTraceCtx('monitor:no_progress'),
|
|
2579
|
-
`msgCount=${
|
|
2608
|
+
`msgCount=${signalDetail.msgCount} sourceMtime=${signalDetail.sourceMtimeMs} (PTY quiet ${Math.round(stalledMs / 1000)}s but transcript advancing)`);
|
|
2580
2609
|
}
|
|
2581
2610
|
this.meshStallAnchorAt = now;
|
|
2582
2611
|
this.meshStallEmittedForAnchor = false;
|
|
@@ -2591,11 +2620,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2591
2620
|
// collapse) sits at a STATIC idle prompt; the status-agnostic watchdog
|
|
2592
2621
|
// would misread that finished-but-quiet state as a wedge and false-fire
|
|
2593
2622
|
// monitor:no_progress. If the class-appropriate transcript (PTY parse or
|
|
2594
|
-
// authoritative native history
|
|
2595
|
-
//
|
|
2596
|
-
//
|
|
2597
|
-
//
|
|
2598
|
-
|
|
2623
|
+
// authoritative native history) proves a finished turn, emit the missing
|
|
2624
|
+
// completion and SUPPRESS the stall. A genuinely mid-turn / wedged worker
|
|
2625
|
+
// with no in-turn final assistant falls through and the real stall fires
|
|
2626
|
+
// below unchanged. TX-FSM Stage 1: the native-source verdict consumes the
|
|
2627
|
+
// SAME snapshot axis 1 just produced — no second read.
|
|
2628
|
+
if (this.tryReconcileTranscriptCompletionForStall(observedStatus, transcriptSignals)) {
|
|
2599
2629
|
this.meshStallEmittedForAnchor = true;
|
|
2600
2630
|
return;
|
|
2601
2631
|
}
|
|
@@ -2650,49 +2680,51 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2650
2680
|
this.meshStallEmittedForAnchor = false;
|
|
2651
2681
|
this.meshStallTurnActiveLast = undefined;
|
|
2652
2682
|
this.meshStallLastFiredAt = -1;
|
|
2653
|
-
this.
|
|
2683
|
+
this.meshStallTranscriptSignalSampled = false;
|
|
2654
2684
|
}
|
|
2655
2685
|
|
|
2686
|
+
/** The result of one native-transcript signal probe: the normalized
|
|
2687
|
+
* snapshot the shared TranscriptSignalSource produced from the read, and
|
|
2688
|
+
* the very messages it was normalized from (so a judgment site can pull
|
|
2689
|
+
* a payload — e.g. the final summary — from the SAME read with zero
|
|
2690
|
+
* added I/O). */
|
|
2656
2691
|
/**
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2661
|
-
*
|
|
2662
|
-
*
|
|
2663
|
-
*
|
|
2664
|
-
*
|
|
2665
|
-
*
|
|
2692
|
+
* TX-FSM Stage 1 — the single native-transcript signal probe (replaces
|
|
2693
|
+
* the Stage-0 sampleNativeTranscriptProgress fingerprint sampler). For a
|
|
2694
|
+
* native-source provider (its authoritative history is an on-disk
|
|
2695
|
+
* transcript file, e.g. kimi's wire.jsonl), perform the read this
|
|
2696
|
+
* judgment point already owns — SAME cadence as before, one
|
|
2697
|
+
* readExternalCompletionMessages() per call, never more — and return the
|
|
2698
|
+
* NORMALIZED SignalSnapshot the shared TranscriptSignalSource produced
|
|
2699
|
+
* from it (publishTranscriptSignalObservation runs inside the read), plus
|
|
2700
|
+
* the messages that read returned. Judgment sites (the stall watchdog's
|
|
2701
|
+
* transcript-advancing axis, the completion growth-hold, the stall-path
|
|
2702
|
+
* completion rescue) consume the snapshot's SIGNALS instead of running
|
|
2703
|
+
* their own fingerprint/freshness/final-assistant scans — one source of
|
|
2704
|
+
* truth for "what does the transcript say right now".
|
|
2666
2705
|
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
2669
|
-
*
|
|
2670
|
-
* (
|
|
2706
|
+
* Class gating goes through resolveTranscriptAuthorityProfile ONLY.
|
|
2707
|
+
* Returns null for a non-native-source class (nothing to signal from) and
|
|
2708
|
+
* a null snapshot when the read threw — callers keep their fail-open
|
|
2709
|
+
* fallbacks ("couldn't tell" never blocks an idle verdict and never
|
|
2710
|
+
* fabricates a completion). Cheap enough for the stall path: it runs
|
|
2711
|
+
* only at the stall threshold (≥180s of PTY stasis) or during an armed
|
|
2712
|
+
* completion-debounce retry, never on the routine 5s tick.
|
|
2671
2713
|
*/
|
|
2672
|
-
private
|
|
2673
|
-
|
|
2674
|
-
// decides whether a native source exists to sample a progress fingerprint from;
|
|
2675
|
-
// the caller (checkMeshWorkerStall) makes the stall/no-progress decision over the
|
|
2676
|
-
// returned fingerprint. Non-native-source classes have nothing to sample here and
|
|
2677
|
-
// fall back to the unchanged lastOutputAt-only judgment.
|
|
2678
|
-
if (!isNativeSourceCanonicalHistory(this.provider?.nativeHistory)) return null;
|
|
2714
|
+
private probeNativeTranscriptSignals(): { snapshot: SignalSnapshot | null; messages: unknown[] | null } | null {
|
|
2715
|
+
if (resolveTranscriptAuthorityProfile(this.provider).class !== 'native-source') return null;
|
|
2679
2716
|
// readExternalCompletionMessages resolves this session's OWN native-source
|
|
2680
2717
|
// conversation (providerSessionId / persisted pin / floor claim) and, as a
|
|
2681
|
-
// side effect,
|
|
2682
|
-
//
|
|
2683
|
-
// one place and immune to the antigravity-style session-id quirks.
|
|
2718
|
+
// side effect, feeds the shared TranscriptSignalSource (which refreshes
|
|
2719
|
+
// this.lastTranscriptSignalSnapshot). Reusing it keeps the resolution
|
|
2720
|
+
// logic in one place and immune to the antigravity-style session-id quirks.
|
|
2684
2721
|
let messages: unknown[] | null = null;
|
|
2685
2722
|
try {
|
|
2686
2723
|
messages = this.readExternalCompletionMessages();
|
|
2687
2724
|
} catch {
|
|
2688
|
-
return null; // best-effort:
|
|
2725
|
+
return { snapshot: null, messages: null }; // best-effort: fail-open
|
|
2689
2726
|
}
|
|
2690
|
-
|
|
2691
|
-
if (!probe) return null;
|
|
2692
|
-
return {
|
|
2693
|
-
msgCount: typeof probe.msgCount === 'number' ? probe.msgCount : (Array.isArray(messages) ? messages.length : 0),
|
|
2694
|
-
sourceMtimeMs: typeof probe.sourceMtimeMs === 'number' ? probe.sourceMtimeMs : 0,
|
|
2695
|
-
};
|
|
2727
|
+
return { snapshot: this.lastTranscriptSignalSnapshot, messages };
|
|
2696
2728
|
}
|
|
2697
2729
|
|
|
2698
2730
|
/**
|
|
@@ -2795,8 +2827,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2795
2827
|
* false for a daemon-owned provider and for a genuinely mid-turn / wedged
|
|
2796
2828
|
* worker with no in-turn final assistant, so a real stall still fires
|
|
2797
2829
|
* unchanged. Evidence bar is identical to flushMeshCompletionBeforeCleanup.
|
|
2830
|
+
*
|
|
2831
|
+
* TX-FSM Stage 1 (FIX 3 probe delegation): for a native-source class the
|
|
2832
|
+
* completion VERDICT is the shared TranscriptSignalSource's
|
|
2833
|
+
* final_assistant_present signal, normalized from the ONE transcript read
|
|
2834
|
+
* the stall watchdog already performed this tick (passed in as
|
|
2835
|
+
* `transcriptSignals`) — not a second private read + scan. The emit
|
|
2836
|
+
* payload (finalSummary) is extracted from the SAME messages the signal
|
|
2837
|
+
* was normalized from, with the SAME turn boundary the legacy path used,
|
|
2838
|
+
* so the extraction re-proves the turn scope the signal checked. Fail-open
|
|
2839
|
+
* both ways: no usable snapshot (read failed / unresolved / a pure-PTY
|
|
2840
|
+
* class, which has no native signal by construction) → the legacy
|
|
2841
|
+
* class-appropriate evidence path (completionFinalSummary), unchanged; and
|
|
2842
|
+
* a present-but-unextractable signal yields false rather than a
|
|
2843
|
+
* payload-less emit.
|
|
2798
2844
|
*/
|
|
2799
|
-
private tryReconcileTranscriptCompletionForStall(
|
|
2845
|
+
private tryReconcileTranscriptCompletionForStall(
|
|
2846
|
+
observedStatus: string,
|
|
2847
|
+
transcriptSignals?: { snapshot: SignalSnapshot | null; messages: unknown[] | null } | null,
|
|
2848
|
+
): boolean {
|
|
2800
2849
|
// daemon-owned transcripts get real PTY turn events — an idle-quiet
|
|
2801
2850
|
// daemon-owned worker with no completion emit is a genuine anomaly the
|
|
2802
2851
|
// stall should surface, exactly as before this unification.
|
|
@@ -2825,14 +2874,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2825
2874
|
try {
|
|
2826
2875
|
parsedMessages = this.adapter?.getScriptParsedStatus?.()?.messages;
|
|
2827
2876
|
} catch { parsedMessages = undefined; }
|
|
2828
|
-
//
|
|
2829
|
-
//
|
|
2830
|
-
//
|
|
2831
|
-
//
|
|
2877
|
+
// TX-FSM Stage 1: a native-source class with a usable signal snapshot
|
|
2878
|
+
// takes its verdict from the SHARED signal source (final_assistant_present),
|
|
2879
|
+
// and the payload from the SAME messages — zero added I/O. Every other
|
|
2880
|
+
// case falls back to completionFinalSummary, which turn-scopes the
|
|
2881
|
+
// class-appropriate transcript (native history for a native-source
|
|
2882
|
+
// provider, the PTY parse otherwise) — a stale prior-turn tail or a
|
|
2883
|
+
// mid-turn worker with no in-turn final assistant yields '' → return
|
|
2884
|
+
// false below.
|
|
2832
2885
|
let finalSummary: string | undefined;
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2886
|
+
const signalSnapshot = transcriptSignals?.snapshot;
|
|
2887
|
+
if (profile.class === 'native-source' && signalSnapshot?.available === true) {
|
|
2888
|
+
// The signal is the verdict; the turn-scoped extraction re-proves
|
|
2889
|
+
// the scope and supplies the emit payload from the same read.
|
|
2890
|
+
if (signalSnapshot.signals.final_assistant_present !== true) return false;
|
|
2891
|
+
finalSummary = extractFinalSummaryFromMessagesAfter(
|
|
2892
|
+
(Array.isArray(transcriptSignals?.messages) ? transcriptSignals.messages : []) as any,
|
|
2893
|
+
turnStartedAt,
|
|
2894
|
+
) || undefined;
|
|
2895
|
+
} else {
|
|
2896
|
+
try {
|
|
2897
|
+
finalSummary = this.completionFinalSummary(parsedMessages, turnStartedAt);
|
|
2898
|
+
} catch { finalSummary = undefined; }
|
|
2899
|
+
}
|
|
2836
2900
|
// No in-turn final assistant evidence → not a proven turn-end. Let the real
|
|
2837
2901
|
// stall fire so a genuinely-wedged worker is still surfaced.
|
|
2838
2902
|
if (!finalSummary) return false;
|
|
@@ -3214,20 +3278,27 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3214
3278
|
// untouched; claude-cli's write-lag CANON-C immediate emit is likewise
|
|
3215
3279
|
// untouched (its block deliberately omits noExternalTranscriptSource).
|
|
3216
3280
|
if (blockReason === 'missing_final_assistant' && block.noExternalTranscriptSource === true) {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3281
|
+
// TX-FSM Stage 1: the freshness judgment is the shared signal
|
|
3282
|
+
// source's transcript_growing signal (same growth-quiet window,
|
|
3283
|
+
// normalized from the ONE re-probe this retry performs) instead
|
|
3284
|
+
// of a private mtime scan. Fail-open identically: no signal
|
|
3285
|
+
// (non-native-source / unresolved / read error / unknown mtime)
|
|
3286
|
+
// → falls through to the unchanged floor/cap logic.
|
|
3287
|
+
let growthSnapshot: SignalSnapshot | null = null;
|
|
3288
|
+
try { growthSnapshot = this.probeNativeTranscriptSignals()?.snapshot ?? null; } catch { growthSnapshot = null; }
|
|
3289
|
+
if (growthSnapshot?.available === true && growthSnapshot.signals.transcript_growing === true) {
|
|
3290
|
+
const growthDetail = growthSnapshot.detail;
|
|
3291
|
+
const mtimeAgeMs = growthDetail.ageMs ?? 0; // growing ⇒ ageMs non-null
|
|
3221
3292
|
if (pending.loggedBlockReason !== 'native_transcript_advancing') {
|
|
3222
|
-
LOG.info('CLI', `[${this.type}] holding pending completed (native_transcript_advancing: msgCount=${
|
|
3293
|
+
LOG.info('CLI', `[${this.type}] holding pending completed (native_transcript_advancing: msgCount=${growthDetail.msgCount} mtimeAge=${mtimeAgeMs}ms < ${MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS}ms) — transcript still growing, screen-idle verdict not trusted`);
|
|
3223
3294
|
if (this.isMeshWorkerSession()) {
|
|
3224
|
-
traceMeshEventDrop('completion_gate_hold', this.meshTraceCtx(), `native_transcript_advancing msgCount=${
|
|
3295
|
+
traceMeshEventDrop('completion_gate_hold', this.meshTraceCtx(), `native_transcript_advancing msgCount=${growthDetail.msgCount} mtimeAge=${mtimeAgeMs}ms`);
|
|
3225
3296
|
}
|
|
3226
3297
|
if (this.completionTraceOn()) this.recordCompletionGateTrace('hold', {
|
|
3227
3298
|
blockReason: 'native_transcript_advancing',
|
|
3228
3299
|
latestVisibleStatus,
|
|
3229
|
-
msgCount:
|
|
3230
|
-
sourceMtimeAgeMs:
|
|
3300
|
+
msgCount: growthDetail.msgCount,
|
|
3301
|
+
sourceMtimeAgeMs: mtimeAgeMs,
|
|
3231
3302
|
growthQuietMs: MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS,
|
|
3232
3303
|
waitedMs,
|
|
3233
3304
|
});
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TranscriptSignalSource — TX-FSM
|
|
2
|
+
* TranscriptSignalSource — TX-FSM transcript signal normalizer, daemon side.
|
|
3
3
|
*
|
|
4
4
|
* Owns the collection/normalization half of the dual-source redesign: given
|
|
5
5
|
* the daemon's EXISTING native-transcript reads (file discovery + session pin
|
|
6
6
|
* + parsing all stay in CliProviderInstance.readExternalCompletionMessages,
|
|
7
7
|
* which resolves providerSessionId / persisted pins / floor-claims), this
|
|
8
8
|
* source normalizes each read into the provider-agnostic SignalSnapshot
|
|
9
|
-
* envelope (spec/signal-envelope.ts)
|
|
9
|
+
* envelope (spec/signal-envelope.ts).
|
|
10
|
+
*
|
|
11
|
+
* Consumers:
|
|
12
|
+
* - Stage 0 (shadow): the snapshot is injected into the FsmDriver as a pure
|
|
13
|
+
* observation; `signal` conditions stay pass-through there (fsm-evaluator).
|
|
14
|
+
* - Stage 1 (delegation): the instance's OWN stall/growth-hold judgments
|
|
15
|
+
* (checkMeshWorkerStall's transcript-advancing axis, the TRANSCRIPT-
|
|
16
|
+
* GROWTH-HOLD, and tryReconcileTranscriptCompletionForStall — the FIX 3
|
|
17
|
+
* probe) consume the SAME normalized snapshot instead of running private
|
|
18
|
+
* transcript scans. The FSM-side consumption remains shadow/pass-through.
|
|
10
19
|
*
|
|
11
20
|
* Hard contracts:
|
|
12
21
|
* - ZERO added I/O. update() is fed by transcript reads the daemon already
|
|
@@ -16,10 +25,9 @@
|
|
|
16
25
|
* P0 choke point) — never raw isNativeSourceCanonicalHistory /
|
|
17
26
|
* isPurePtyTranscriptProvider calls, and never a provider-name branch.
|
|
18
27
|
* - Fail-open: a non-native-source class, an unresolved transcript, or a
|
|
19
|
-
* read error yields an unavailable snapshot —
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* Stage 1-3 judgment data). Nothing here feeds back into any verdict.
|
|
28
|
+
* read error yields an unavailable snapshot — consumers treat every signal
|
|
29
|
+
* as null and keep their pre-delegation fallbacks; a missing signal must
|
|
30
|
+
* never wedge a session or fabricate a completion.
|
|
23
31
|
* - No approval signal (Stage 4 scope — see signal-envelope.ts).
|
|
24
32
|
*/
|
|
25
33
|
'use strict';
|