@adhdev/daemon-core 1.0.28-rc.5 → 1.0.28-rc.6
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/config/mesh-config.d.ts +2 -0
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/index.js +7150 -7079
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7153 -7082
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-completion-synthesis.d.ts +29 -0
- package/dist/mesh/mesh-node-identity.d.ts +2 -0
- package/dist/mesh/mesh-refine-gates.d.ts +32 -0
- package/dist/mesh/mesh-runtime-store.d.ts +1 -0
- package/dist/mesh/mesh-work-queue.d.ts +15 -0
- package/dist/mesh/node-facts.d.ts +17 -0
- package/dist/providers/cli-provider-instance.d.ts +30 -57
- package/dist/providers/transcript-evidence.d.ts +63 -0
- package/dist/repo-mesh-types.d.ts +23 -0
- package/package.json +5 -3
- package/src/cli-adapters/cli-state-engine.ts +4 -3
- package/src/commands/high-family/mesh-status.ts +5 -0
- package/src/commands/router-refine.ts +30 -0
- package/src/config/mesh-config.ts +7 -0
- package/src/git/git-commands.ts +11 -1
- package/src/mesh/mesh-completion-synthesis.ts +76 -50
- package/src/mesh/mesh-node-identity.ts +37 -3
- package/src/mesh/mesh-queue-assignment.ts +34 -0
- package/src/mesh/mesh-reconcile-loop.ts +62 -71
- package/src/mesh/mesh-refine-gates.ts +58 -0
- package/src/mesh/mesh-runtime-store.ts +9 -1
- package/src/mesh/mesh-work-queue.ts +20 -1
- package/src/mesh/node-facts.ts +54 -0
- package/src/providers/cli-provider-instance.ts +72 -152
- package/src/providers/transcript-evidence.ts +105 -0
- package/src/repo-mesh-types.ts +23 -0
|
@@ -616,6 +616,20 @@ export interface MeshWorkQueueEntry {
|
|
|
616
616
|
* by counting active assignments grouped by node + provider.
|
|
617
617
|
*/
|
|
618
618
|
assignedProviderType?: string;
|
|
619
|
+
/**
|
|
620
|
+
* Transcript-authority class of the claiming session's provider, stamped at
|
|
621
|
+
* claim time (P1 of the transcript-authority unification — root repo
|
|
622
|
+
* docs/design/2026-07-25-transcript-authority-unification.md). Lets the
|
|
623
|
+
* COORDINATOR side classify a remote worker (early-arm / redrive gates)
|
|
624
|
+
* without resolving the provider module locally — the structural fix for
|
|
625
|
+
* the "remote class unknowable → reprobe-only" blind spot. Absent on rows
|
|
626
|
+
* claimed by older daemons; consumers must fall back to local resolution.
|
|
627
|
+
*/
|
|
628
|
+
assignedTranscriptProfile?: {
|
|
629
|
+
class: 'native-source' | 'pure-pty' | 'daemon-owned';
|
|
630
|
+
timing: 'hold' | 'floor' | 'immediate';
|
|
631
|
+
emitsPtyTurnEvents: boolean;
|
|
632
|
+
};
|
|
619
633
|
/** Human/operator reason for terminal cancellation. */
|
|
620
634
|
cancelReason?: string;
|
|
621
635
|
cancelledAt?: string;
|
|
@@ -1165,7 +1179,12 @@ export function claimNextTask(
|
|
|
1165
1179
|
nodeId: string,
|
|
1166
1180
|
sessionId: string,
|
|
1167
1181
|
capabilityTags?: string[],
|
|
1168
|
-
opts?: {
|
|
1182
|
+
opts?: {
|
|
1183
|
+
providerType?: string;
|
|
1184
|
+
providerMaxParallel?: number;
|
|
1185
|
+
nodeIsWorktree?: boolean;
|
|
1186
|
+
assignedTranscriptProfile?: MeshWorkQueueEntry['assignedTranscriptProfile'];
|
|
1187
|
+
},
|
|
1169
1188
|
): MeshWorkQueueEntry | null {
|
|
1170
1189
|
return MeshRuntimeStore.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags, opts);
|
|
1171
1190
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildLocalNodeFacts — THE single producer of this daemon's MeshNodeFacts
|
|
3
|
+
* bundle (design: root repo docs/design/2026-07-25-deploy-lag-visibility.md).
|
|
4
|
+
*
|
|
5
|
+
* De-mirroring core: the reporter envelope (git-commands handleGitCommand)
|
|
6
|
+
* and the self/worktree node stamp (mesh-node-identity, whose local probe
|
|
7
|
+
* bypasses the envelope) BOTH call this one function, so a fact added here
|
|
8
|
+
* reaches self and remote nodes identically — the field-by-field dual-path
|
|
9
|
+
* plumbing that produced the mirror-defect class (version chip self-miss,
|
|
10
|
+
* slot-cap chip remote-miss, dead stale-build badge) cannot recur for bundle
|
|
11
|
+
* fields.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { MeshNodeFacts } from '@adhdev/mesh-shared';
|
|
15
|
+
import { getDaemonBuildInfo } from '../build-info.js';
|
|
16
|
+
|
|
17
|
+
export function buildLocalNodeFacts(deps?: {
|
|
18
|
+
providerVersions?: Record<string, string> | null;
|
|
19
|
+
machineNickname?: string | null;
|
|
20
|
+
}): MeshNodeFacts {
|
|
21
|
+
const build = (() => {
|
|
22
|
+
try {
|
|
23
|
+
const info = getDaemonBuildInfo();
|
|
24
|
+
const commit = typeof info.commit === 'string' && info.commit && info.commit !== 'unknown' ? info.commit : undefined;
|
|
25
|
+
const commitShort = typeof info.commitShort === 'string' && info.commitShort && info.commitShort !== 'unknown' ? info.commitShort : undefined;
|
|
26
|
+
const version = typeof info.version === 'string' && info.version && info.version !== 'unknown' ? info.version : undefined;
|
|
27
|
+
const builtAt = typeof info.builtAt === 'string' && info.builtAt ? info.builtAt : undefined;
|
|
28
|
+
if (!commit && !version) return undefined;
|
|
29
|
+
return {
|
|
30
|
+
...(commit ? { commit } : {}),
|
|
31
|
+
...(commitShort ? { commitShort } : {}),
|
|
32
|
+
...(version ? { version } : {}),
|
|
33
|
+
...(builtAt ? { builtAt } : {}),
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
const providerVersions = deps?.providerVersions && Object.keys(deps.providerVersions).length > 0
|
|
40
|
+
? deps.providerVersions
|
|
41
|
+
: undefined;
|
|
42
|
+
const machineNickname = typeof deps?.machineNickname === 'string' && deps.machineNickname.trim()
|
|
43
|
+
? deps.machineNickname.trim()
|
|
44
|
+
: undefined;
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: 1,
|
|
47
|
+
reportedAt: Date.now(),
|
|
48
|
+
...(build ? { daemonBuild: build } : {}),
|
|
49
|
+
...(providerVersions ? { providerVersions } : {}),
|
|
50
|
+
platform: process.platform,
|
|
51
|
+
arch: process.arch,
|
|
52
|
+
...(machineNickname ? { machineNickname } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -17,7 +17,7 @@ import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
|
17
17
|
import { shortHash } from '../system/hash.js';
|
|
18
18
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
19
19
|
import type { MeshSendKeyItem, MeshSendKeyName } from '../cli-adapters/provider-cli-shared.js';
|
|
20
|
-
import {
|
|
20
|
+
import { resolveTranscriptAuthorityProfile } from './transcript-evidence.js';
|
|
21
21
|
import { createCliAdapter } from './spec/route.js';
|
|
22
22
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
23
23
|
import { StatusMonitor } from './status-monitor.js';
|
|
@@ -2101,12 +2101,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2101
2101
|
// autonomous mesh sessions (allowMissingAssistantTimeout) so an interactive
|
|
2102
2102
|
// antigravity session, which has no coordinator to misfire at, is untouched.
|
|
2103
2103
|
//
|
|
2104
|
-
// SPEC-DRIVEN completion timing (mission f2f6da1b root 2
|
|
2105
|
-
//
|
|
2104
|
+
// SPEC-DRIVEN completion timing (mission f2f6da1b root 2, P4 profile
|
|
2105
|
+
// substitution): the HOLD class is the authority profile's timing==='hold'
|
|
2106
|
+
// (manifest holdCompletionForTranscript — antigravity-cli declares it), NOT a
|
|
2106
2107
|
// hardcoded provider name — a native-history provider whose PTY-derived idle can
|
|
2107
|
-
// precede the authoritative transcript write. write-lag (claude) and
|
|
2108
|
-
// (codex/kimi
|
|
2109
|
-
if ((this.provider
|
|
2108
|
+
// precede the authoritative transcript write. write-lag (claude, 'immediate') and
|
|
2109
|
+
// floor (codex/kimi/…, 'floor') classes fall through to the decision below.
|
|
2110
|
+
if (resolveTranscriptAuthorityProfile(this.provider).timing === 'hold') {
|
|
2110
2111
|
if (allowMissingAssistantTimeout) {
|
|
2111
2112
|
return { reason: 'missing_final_assistant', terminal: false, holdForTranscript: true };
|
|
2112
2113
|
}
|
|
@@ -2168,7 +2169,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2168
2169
|
// This is the SAME flag the sibling branch below (source !== 'external-native')
|
|
2169
2170
|
// uses for the identical noExternalTranscriptSource decision, so the two branches
|
|
2170
2171
|
// agree; and it covers third-party providers instead of a hardcoded provider name.
|
|
2171
|
-
const isWriteLagNativeSource = (this.provider
|
|
2172
|
+
const isWriteLagNativeSource = resolveTranscriptAuthorityProfile(this.provider).timing !== 'floor';
|
|
2172
2173
|
return {
|
|
2173
2174
|
reason: 'missing_final_assistant',
|
|
2174
2175
|
terminal: true,
|
|
@@ -2176,14 +2177,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2176
2177
|
...(isWriteLagNativeSource ? {} : { noExternalTranscriptSource: true }),
|
|
2177
2178
|
};
|
|
2178
2179
|
}
|
|
2179
|
-
if ((this.provider
|
|
2180
|
+
if (resolveTranscriptAuthorityProfile(this.provider).timing === 'floor') {
|
|
2180
2181
|
return { reason: 'missing_final_assistant', terminal: true, allowTimeout: allowMissingAssistantTimeout, noExternalTranscriptSource: true };
|
|
2181
2182
|
}
|
|
2182
2183
|
} else {
|
|
2183
|
-
|
|
2184
|
+
const notOwnsExternalTiming = resolveTranscriptAuthorityProfile(this.provider).timing;
|
|
2185
|
+
LOG.debug('CLI', `[${this.type}] missing_final_assistant (not ownsExternal) requiresFinalAssistant=${notOwnsExternalTiming === 'floor'}`);
|
|
2184
2186
|
return {
|
|
2185
2187
|
reason: 'missing_final_assistant',
|
|
2186
|
-
terminal:
|
|
2188
|
+
terminal: notOwnsExternalTiming === 'floor',
|
|
2187
2189
|
allowTimeout: allowMissingAssistantTimeout,
|
|
2188
2190
|
// PTY-parsed provider (codex-cli): no external transcript trails the idle
|
|
2189
2191
|
// transition, so the CANON-C decoupled emit must observe the min-elapsed floor
|
|
@@ -2525,30 +2527,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2525
2527
|
}
|
|
2526
2528
|
}
|
|
2527
2529
|
|
|
2528
|
-
//
|
|
2529
|
-
//
|
|
2530
|
-
//
|
|
2531
|
-
//
|
|
2532
|
-
//
|
|
2533
|
-
//
|
|
2534
|
-
//
|
|
2535
|
-
// native
|
|
2536
|
-
//
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
// KIMI-NATIVE-SOURCE-COMPLETION-EMIT (Fix 3): the pure-PTY check above is a no-op
|
|
2543
|
-
// for a native-source provider (kimi and kin — transcriptAuthority=provider +
|
|
2544
|
-
// nativeHistory). For that class, a finished-but-quiet idle whose transcript has
|
|
2545
|
-
// gone static (sampleNativeTranscriptProgress stopped re-arming) is otherwise
|
|
2546
|
-
// misread as a wedge. Resolve the completion from the authoritative native
|
|
2547
|
-
// transcript; if it proves a finished turn, emit the missing completion and
|
|
2548
|
-
// SUPPRESS the stall. No-op for pure-PTY providers and for a genuinely mid-turn /
|
|
2549
|
-
// wedged native-source worker with no in-turn final assistant → the real stall
|
|
2550
|
-
// fires below unchanged. Mutually exclusive with the pure-PTY path.
|
|
2551
|
-
if (this.tryReconcileNativeSourceCompletionForStall(observedStatus)) {
|
|
2530
|
+
// TRANSCRIPT-COMPLETION-STALL-RESCUE (P2 of the transcript-authority
|
|
2531
|
+
// unification — historically KIMI-PURE-PTY-COMPLETION-EMIT + KIMI-NATIVE-
|
|
2532
|
+
// SOURCE-COMPLETION-EMIT, two class-enumerated copies of the same rescue).
|
|
2533
|
+
// A finished worker whose generating_completed never emitted (idle→idle
|
|
2534
|
+
// collapse) sits at a STATIC idle prompt; the status-agnostic watchdog
|
|
2535
|
+
// would misread that finished-but-quiet state as a wedge and false-fire
|
|
2536
|
+
// monitor:no_progress. If the class-appropriate transcript (PTY parse or
|
|
2537
|
+
// authoritative native history — completionFinalSummary picks) proves a
|
|
2538
|
+
// finished turn, emit the missing completion and SUPPRESS the stall. A
|
|
2539
|
+
// genuinely mid-turn / wedged worker with no in-turn final assistant falls
|
|
2540
|
+
// through and the real stall fires below unchanged.
|
|
2541
|
+
if (this.tryReconcileTranscriptCompletionForStall(observedStatus)) {
|
|
2552
2542
|
this.meshStallEmittedForAnchor = true;
|
|
2553
2543
|
return;
|
|
2554
2544
|
}
|
|
@@ -2718,35 +2708,38 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2718
2708
|
}
|
|
2719
2709
|
|
|
2720
2710
|
/**
|
|
2721
|
-
*
|
|
2722
|
-
*
|
|
2723
|
-
*
|
|
2724
|
-
*
|
|
2725
|
-
*
|
|
2726
|
-
* instant the answer is rendered, so the status-agnostic no-progress watchdog
|
|
2727
|
-
* (checkMeshWorkerStall) reads the finished-but-quiet session as a stall and would
|
|
2728
|
-
* false-fire monitor:no_progress. The native-transcript reconcile
|
|
2729
|
-
* (sampleNativeTranscriptProgress) does NOT cover this class (no native source →
|
|
2730
|
-
* null sample), so the stall path needs its own guard.
|
|
2711
|
+
* TRANSCRIPT-COMPLETION-STALL-RESCUE (P2 of the transcript-authority
|
|
2712
|
+
* unification — root repo docs/design/2026-07-25-transcript-authority-
|
|
2713
|
+
* unification.md): stall-path completion reconcile for every class whose
|
|
2714
|
+
* turns can finish without a generating_completed emit (idle→idle collapse)
|
|
2715
|
+
* and then sit PTY-quiet at a static idle prompt.
|
|
2731
2716
|
*
|
|
2732
|
-
*
|
|
2733
|
-
*
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
2736
|
-
* the
|
|
2737
|
-
*
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2717
|
+
* Historically this was TWO class-enumerated copies — KIMI-PURE-PTY-
|
|
2718
|
+
* COMPLETION-EMIT (Fix 3) gated on isPurePtyTranscriptProvider, and
|
|
2719
|
+
* KIMI-NATIVE-SOURCE-COMPLETION-EMIT gated on isNativeSourceCanonicalHistory
|
|
2720
|
+
* — whose bodies were identical because completionFinalSummary already picks
|
|
2721
|
+
* the class-appropriate evidence source (authoritative native transcript for
|
|
2722
|
+
* a native-source provider, the PTY parse otherwise). The enumeration itself
|
|
2723
|
+
* was the recurring defect: each new class had to be remembered at this site.
|
|
2724
|
+
* The profile collapses it: any class except daemon-owned is eligible, and
|
|
2725
|
+
* the evidence bar — not the class — decides.
|
|
2740
2726
|
*
|
|
2741
|
-
*
|
|
2742
|
-
*
|
|
2743
|
-
*
|
|
2744
|
-
*
|
|
2727
|
+
* Called from checkMeshWorkerStall just before the fire. Returns true when
|
|
2728
|
+
* the session is a finished turn — idle, nothing pending, the injected
|
|
2729
|
+
* task's turn genuinely started, and an in-turn final assistant summary —
|
|
2730
|
+
* in which case it emits the missing generating_completed (idempotent: a
|
|
2731
|
+
* real/late emit writes the terminal ledger and the coordinator's reconcile
|
|
2732
|
+
* makes any duplicate a no-op) and the caller SUPPRESSES the stall. Returns
|
|
2733
|
+
* false for a daemon-owned provider and for a genuinely mid-turn / wedged
|
|
2734
|
+
* worker with no in-turn final assistant, so a real stall still fires
|
|
2735
|
+
* unchanged. Evidence bar is identical to flushMeshCompletionBeforeCleanup.
|
|
2745
2736
|
*/
|
|
2746
|
-
private
|
|
2747
|
-
//
|
|
2748
|
-
//
|
|
2749
|
-
|
|
2737
|
+
private tryReconcileTranscriptCompletionForStall(observedStatus: string): boolean {
|
|
2738
|
+
// daemon-owned transcripts get real PTY turn events — an idle-quiet
|
|
2739
|
+
// daemon-owned worker with no completion emit is a genuine anomaly the
|
|
2740
|
+
// stall should surface, exactly as before this unification.
|
|
2741
|
+
const profile = resolveTranscriptAuthorityProfile(this.provider);
|
|
2742
|
+
if (profile.class === 'daemon-owned') return false;
|
|
2750
2743
|
// A finished turn is idle with nothing pending. A generating/waiting session is
|
|
2751
2744
|
// mid-turn (the real stall path should evaluate it), so never complete it here.
|
|
2752
2745
|
if (observedStatus !== 'idle') return false;
|
|
@@ -2770,6 +2763,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2770
2763
|
try {
|
|
2771
2764
|
parsedMessages = this.adapter?.getScriptParsedStatus?.()?.messages;
|
|
2772
2765
|
} catch { parsedMessages = undefined; }
|
|
2766
|
+
// completionFinalSummary turn-scopes the class-appropriate transcript (native
|
|
2767
|
+
// history for a native-source provider, the PTY parse otherwise) — a stale
|
|
2768
|
+
// prior-turn tail or a mid-turn worker with no in-turn final assistant yields
|
|
2769
|
+
// '' → return false below.
|
|
2773
2770
|
let finalSummary: string | undefined;
|
|
2774
2771
|
try {
|
|
2775
2772
|
finalSummary = this.completionFinalSummary(parsedMessages, turnStartedAt);
|
|
@@ -2778,93 +2775,16 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2778
2775
|
// stall fire so a genuinely-wedged worker is still surfaced.
|
|
2779
2776
|
if (!finalSummary) return false;
|
|
2780
2777
|
|
|
2781
|
-
|
|
2778
|
+
// Telemetry keeps the historical per-class source strings so traces and
|
|
2779
|
+
// dashboards stay comparable across the unification.
|
|
2780
|
+
const diagnosticSource = profile.class === 'pure-pty'
|
|
2781
|
+
? 'stall_pure_pty_transcript_completion'
|
|
2782
|
+
: 'stall_native_source_transcript_completion';
|
|
2783
|
+
LOG.warn('CLI', `[${this.type}] reconciling ${profile.class} mesh completion from the stall path for session ${this.instanceId} `
|
|
2782
2784
|
+ `task=${taskId ?? '(none)'} — PTY is idle-quiet with an in-turn final assistant message but the completion `
|
|
2783
|
-
+ `event never fired
|
|
2784
|
-
if (this.isMeshWorkerSession()) {
|
|
2785
|
-
traceMeshEventStage('fired', this.meshTraceCtx(), 'stall_pure_pty_transcript_completion');
|
|
2786
|
-
}
|
|
2787
|
-
this.emitGeneratingCompleted({
|
|
2788
|
-
chatTitle: '',
|
|
2789
|
-
duration: undefined,
|
|
2790
|
-
timestamp: Date.now(),
|
|
2791
|
-
taskId,
|
|
2792
|
-
finalSummary,
|
|
2793
|
-
evidenceLevel: 'transcript',
|
|
2794
|
-
completionDiagnostic: { source: 'stall_pure_pty_transcript_completion' },
|
|
2795
|
-
});
|
|
2796
|
-
return true;
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
/**
|
|
2800
|
-
* KIMI-NATIVE-SOURCE-COMPLETION-EMIT (Fix 3): stall-path completion reconcile for a
|
|
2801
|
-
* NATIVE-SOURCE provider (transcriptAuthority=provider + nativeHistory — e.g. kimi,
|
|
2802
|
-
* whose authoritative transcript is ~/.kimi-code/.../wire.jsonl). Like the pure-PTY
|
|
2803
|
-
* class, a native-source worker that submits a prompt while already idle can collapse
|
|
2804
|
-
* idle→idle so agent:generating_completed never emits; its PTY then goes screen-quiet
|
|
2805
|
-
* the instant the answer renders, and the status-agnostic no-progress watchdog
|
|
2806
|
-
* (checkMeshWorkerStall) misreads that finished-but-static idle as a wedge and would
|
|
2807
|
-
* false-fire monitor:no_progress. sampleNativeTranscriptProgress only re-arms while the
|
|
2808
|
-
* transcript is STILL ADVANCING; once the turn is done the transcript is static and the
|
|
2809
|
-
* sample stops rescuing — so the stall path needs this decisive completion check for the
|
|
2810
|
-
* finished-and-quiet case. tryReconcilePurePtyCompletionForStall does NOT cover this
|
|
2811
|
-
* class (isPurePtyTranscriptProvider is false for a native-source provider), so it runs
|
|
2812
|
-
* as a fallback here.
|
|
2813
|
-
*
|
|
2814
|
-
* Mutually exclusive with the pure-PTY path by construction: a provider is either
|
|
2815
|
-
* native-source (this method) or pure-PTY (that method), never both. Called from
|
|
2816
|
-
* checkMeshWorkerStall only after tryReconcilePurePtyCompletionForStall returned false.
|
|
2817
|
-
*
|
|
2818
|
-
* Evidence bar is identical to the pure-PTY / pre-cleanup paths: idle with nothing
|
|
2819
|
-
* pending, the injected task's turn genuinely started, and an in-turn final assistant
|
|
2820
|
-
* summary — here resolved from the native transcript by completionFinalSummary (its
|
|
2821
|
-
* native-source branch reads readExternalCompletionMessages). No summary ⇒ no proof of
|
|
2822
|
-
* completion ⇒ return false and let the real stall fire so a genuinely-wedged worker is
|
|
2823
|
-
* still surfaced. Idempotent: a real/late emit writes the terminal ledger and the
|
|
2824
|
-
* coordinator's reconcile makes any duplicate a no-op.
|
|
2825
|
-
*/
|
|
2826
|
-
private tryReconcileNativeSourceCompletionForStall(observedStatus: string): boolean {
|
|
2827
|
-
// Only the native-source class — the pure-PTY class is handled above.
|
|
2828
|
-
if (!isNativeSourceCanonicalHistory(this.provider?.nativeHistory)) return false;
|
|
2829
|
-
// A finished turn is idle with nothing pending. A generating/waiting session is
|
|
2830
|
-
// mid-turn (the real stall path should evaluate it), so never complete it here.
|
|
2831
|
-
if (observedStatus !== 'idle') return false;
|
|
2832
|
-
if (this.hasAdapterPendingResponse()) return false;
|
|
2833
|
-
|
|
2834
|
-
const taskId = this.completingTurnTaskId();
|
|
2835
|
-
// DOUBLE-EMIT guard (COMPLETION-WEAK-REARM fix1): suppress a re-emit only when this
|
|
2836
|
-
// turn's completion already fired with GENUINE evidence. A prior WEAK emit is
|
|
2837
|
-
// re-armable once a real generating→idle transition intervened (one-shot).
|
|
2838
|
-
if (this.shouldSuppressCompletionReEmit(taskId)) {
|
|
2839
|
-
return false;
|
|
2840
|
-
}
|
|
2841
|
-
// The injected task's own turn must have genuinely started (guards against a
|
|
2842
|
-
// reused session's stale tail before this task ran).
|
|
2843
|
-
if (!this.injectedTaskHasStartedGenerating()) return false;
|
|
2844
|
-
|
|
2845
|
-
const turnStartedAt = typeof (this.adapter as any)?.currentTurnStartedAt === 'number'
|
|
2846
|
-
? (this.adapter as any).currentTurnStartedAt as number
|
|
2847
|
-
: this.meshTaskInjectedAt || undefined;
|
|
2848
|
-
let parsedMessages: unknown;
|
|
2849
|
-
try {
|
|
2850
|
-
parsedMessages = this.adapter?.getScriptParsedStatus?.()?.messages;
|
|
2851
|
-
} catch { parsedMessages = undefined; }
|
|
2852
|
-
// completionFinalSummary reads the AUTHORITATIVE native transcript (wire.jsonl) for
|
|
2853
|
-
// this native-source provider and turn-scopes it — so a stale prior-turn tail or a
|
|
2854
|
-
// mid-turn worker with no in-turn final assistant yields '' → return false below.
|
|
2855
|
-
let finalSummary: string | undefined;
|
|
2856
|
-
try {
|
|
2857
|
-
finalSummary = this.completionFinalSummary(parsedMessages, turnStartedAt);
|
|
2858
|
-
} catch { finalSummary = undefined; }
|
|
2859
|
-
// No in-turn final assistant evidence → not a proven turn-end. Let the real stall
|
|
2860
|
-
// fire so a genuinely-wedged worker is still surfaced.
|
|
2861
|
-
if (!finalSummary) return false;
|
|
2862
|
-
|
|
2863
|
-
LOG.warn('CLI', `[${this.type}] reconciling native-source mesh completion from the stall path for session ${this.instanceId} `
|
|
2864
|
-
+ `task=${taskId ?? '(none)'} — PTY is idle-quiet with an in-turn final assistant message in the native `
|
|
2865
|
-
+ `transcript but the completion event never fired; emitting it instead of a false monitor:no_progress.`);
|
|
2785
|
+
+ `event never fired; emitting it instead of a false monitor:no_progress.`);
|
|
2866
2786
|
if (this.isMeshWorkerSession()) {
|
|
2867
|
-
traceMeshEventStage('fired', this.meshTraceCtx(),
|
|
2787
|
+
traceMeshEventStage('fired', this.meshTraceCtx(), diagnosticSource);
|
|
2868
2788
|
}
|
|
2869
2789
|
this.emitGeneratingCompleted({
|
|
2870
2790
|
chatTitle: '',
|
|
@@ -2873,7 +2793,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2873
2793
|
taskId,
|
|
2874
2794
|
finalSummary,
|
|
2875
2795
|
evidenceLevel: 'transcript',
|
|
2876
|
-
completionDiagnostic: { source:
|
|
2796
|
+
completionDiagnostic: { source: diagnosticSource },
|
|
2877
2797
|
});
|
|
2878
2798
|
return true;
|
|
2879
2799
|
}
|
|
@@ -3282,7 +3202,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3282
3202
|
// HOLD completion-timing class (`holdCompletionForTranscript`, e.g. antigravity-cli) via
|
|
3283
3203
|
// its holdForTranscript block — NOT a hardcoded provider name — so claude-cli/codex-cli
|
|
3284
3204
|
// (immediate/floor classes, which never produce a holdForTranscript block) are unchanged.
|
|
3285
|
-
if ((this.provider
|
|
3205
|
+
if (resolveTranscriptAuthorityProfile(this.provider).timing === 'hold'
|
|
3286
3206
|
&& block.holdForTranscript === true
|
|
3287
3207
|
&& waitedMs < ANTIGRAVITY_HOLD_HARD_CAP_MS
|
|
3288
3208
|
&& this.antigravityHoldPtyStillActive()) {
|
|
@@ -3466,9 +3386,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3466
3386
|
}
|
|
3467
3387
|
|
|
3468
3388
|
/**
|
|
3469
|
-
* COMPLETION-WEAK-REARM (fix1): the double-emit guard shared by the
|
|
3470
|
-
* re-emit paths (flushMeshCompletionBeforeCleanup,
|
|
3471
|
-
*
|
|
3389
|
+
* COMPLETION-WEAK-REARM (fix1): the double-emit guard shared by the transcript
|
|
3390
|
+
* re-emit paths (flushMeshCompletionBeforeCleanup,
|
|
3391
|
+
* tryReconcileTranscriptCompletionForStall). Returns true when a re-emit for `taskId`
|
|
3472
3392
|
* must be SUPPRESSED because this turn's completion already fired with strong evidence.
|
|
3473
3393
|
*
|
|
3474
3394
|
* The defect this replaces: the old guard short-circuited on ANY prior emit for the
|
|
@@ -3980,7 +3900,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3980
3900
|
fcEvidenceSource = evidence.source;
|
|
3981
3901
|
fcFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
3982
3902
|
} catch { /* best-effort */ }
|
|
3983
|
-
const missingEvidence = ((this.provider
|
|
3903
|
+
const missingEvidence = (resolveTranscriptAuthorityProfile(this.provider).timing === 'floor' || fcEvidenceSource === 'external-native') && !fcFinalSummary;
|
|
3984
3904
|
// Mirror the short-generating idle path's suppression: a provider that
|
|
3985
3905
|
// requires a final assistant (or external-native history) with NO confirmed
|
|
3986
3906
|
// summary and NO mesh context emits nothing — the session is idle with no
|
|
@@ -4285,7 +4205,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
4285
4205
|
// (the mid-turn point-sample that triggered this whole false-idle bug) is treated
|
|
4286
4206
|
// as weak/held, not fired as a genuine completion. A real shortFinalSummary being
|
|
4287
4207
|
// present still clears the gate (the !shortFinalSummary guard is unchanged).
|
|
4288
|
-
const missingEvidence = ((this.provider
|
|
4208
|
+
const missingEvidence = (resolveTranscriptAuthorityProfile(this.provider).timing === 'floor'
|
|
4289
4209
|
|| shortEvidenceSource === 'external-native'
|
|
4290
4210
|
|| shortEvidenceSource === 'unavailable') && !shortFinalSummary;
|
|
4291
4211
|
if (missingEvidence) {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript authority profile — the single place that classifies a provider
|
|
3
|
+
* for completion/stall/redrive decisions.
|
|
4
|
+
*
|
|
5
|
+
* Historically five predicates coexisted (transcriptAuthority, nativeHistory
|
|
6
|
+
* presence, adapter chatMessagesOwnedExternally, isPurePtyTranscriptProvider,
|
|
7
|
+
* and the two completion-timing flags) and every judgment site combined a
|
|
8
|
+
* different subset — each recurring "pure-PTY gate excludes native-source"
|
|
9
|
+
* defect (kimi reconcile, antigravity tail/guard, codex floor, STARTED
|
|
10
|
+
* redrive) was an instance of a site enumerating only some classes. This
|
|
11
|
+
* module makes the enumeration itself the shared artifact: sites consume a
|
|
12
|
+
* profile, never the raw predicates.
|
|
13
|
+
*
|
|
14
|
+
* Design: docs/design/2026-07-25-transcript-authority-unification.md (root
|
|
15
|
+
* repo). This file is Phase P0 (layer A — pure, synchronous classification).
|
|
16
|
+
* Layer B (the evidence-query choke point) lands in later phases.
|
|
17
|
+
*
|
|
18
|
+
* Rule for NEW code: call resolveTranscriptAuthorityProfile() — do not call
|
|
19
|
+
* isPurePtyTranscriptProvider / isNativeSourceCanonicalHistory or read the
|
|
20
|
+
* timing flags directly (annotate `// authority-ok: <why>` where a raw read
|
|
21
|
+
* is genuinely about something other than completion classification).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ProviderCanonicalHistoryConfig } from './contracts.js';
|
|
25
|
+
import { isPurePtyTranscriptProvider } from '../cli-adapters/provider-cli-shared.js';
|
|
26
|
+
import { isNativeSourceCanonicalHistory } from '../config/chat-history.js';
|
|
27
|
+
|
|
28
|
+
/** Where the authoritative transcript for completion evidence lives. */
|
|
29
|
+
export type TranscriptClass = 'native-source' | 'pure-pty' | 'daemon-owned';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* When a completion may be emitted relative to the PTY idle verdict:
|
|
33
|
+
* - 'hold' — idle holds for the native transcript to land (antigravity).
|
|
34
|
+
* - 'floor' — idle without a final assistant holds under the CANON-C
|
|
35
|
+
* min-elapsed floor (codex / kimi / cursor / opencode).
|
|
36
|
+
* - 'immediate' — emit immediately; a write-lag native source upgrades the
|
|
37
|
+
* weak emit on reconcile (claude), daemon-owned emits plainly.
|
|
38
|
+
*/
|
|
39
|
+
export type CompletionTiming = 'hold' | 'floor' | 'immediate';
|
|
40
|
+
|
|
41
|
+
export interface TranscriptAuthorityProfile {
|
|
42
|
+
class: TranscriptClass;
|
|
43
|
+
timing: CompletionTiming;
|
|
44
|
+
/** transcriptAuthority === 'provider' — provider parser output is canonical. */
|
|
45
|
+
providerOwnsTranscript: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* False ⇒ this class may run whole turns without PTY generating_started /
|
|
48
|
+
* generating_completed events, so the ABSENCE of a turn-start event must
|
|
49
|
+
* never be read as "dispatch not consumed" (the STARTED-redrive rule,
|
|
50
|
+
* generalized). True only where PTY turn events are reliable.
|
|
51
|
+
*/
|
|
52
|
+
emitsPtyTurnEvents: boolean;
|
|
53
|
+
/** The provider's native history config when the class is native-source. */
|
|
54
|
+
nativeHistory?: ProviderCanonicalHistoryConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Structural input — matches how judgment sites actually hold providers
|
|
59
|
+
* (CliProviderModule / ProviderModule / nullable this.provider). Null or
|
|
60
|
+
* undefined resolves to the conservative daemon-owned/immediate default,
|
|
61
|
+
* matching the established null-provider ⇒ not-pure-PTY contract.
|
|
62
|
+
*/
|
|
63
|
+
export interface TranscriptAuthorityInput {
|
|
64
|
+
transcriptAuthority?: 'provider' | 'daemon';
|
|
65
|
+
nativeHistory?: ProviderCanonicalHistoryConfig;
|
|
66
|
+
tui?: Record<string, unknown>;
|
|
67
|
+
requiresFinalAssistantBeforeIdle?: boolean;
|
|
68
|
+
holdCompletionForTranscript?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveTranscriptAuthorityProfile(
|
|
72
|
+
provider: TranscriptAuthorityInput | null | undefined,
|
|
73
|
+
): TranscriptAuthorityProfile {
|
|
74
|
+
if (!provider) {
|
|
75
|
+
return { class: 'daemon-owned', timing: 'immediate', providerOwnsTranscript: false, emitsPtyTurnEvents: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const nativeSource = isNativeSourceCanonicalHistory(provider.nativeHistory);
|
|
79
|
+
const transcriptClass: TranscriptClass = nativeSource
|
|
80
|
+
? 'native-source'
|
|
81
|
+
: isPurePtyTranscriptProvider(provider)
|
|
82
|
+
? 'pure-pty'
|
|
83
|
+
: 'daemon-owned';
|
|
84
|
+
|
|
85
|
+
const timing: CompletionTiming = provider.holdCompletionForTranscript === true
|
|
86
|
+
? 'hold'
|
|
87
|
+
: provider.requiresFinalAssistantBeforeIdle === true
|
|
88
|
+
? 'floor'
|
|
89
|
+
: 'immediate';
|
|
90
|
+
|
|
91
|
+
// PTY turn events are unreliable for (a) pure-PTY (turns collapse
|
|
92
|
+
// idle→idle; the parser, not the PTY event stream, notices the turn) and
|
|
93
|
+
// (b) native-source classes that hold/floor precisely because their turn
|
|
94
|
+
// activity lives in the native transcript rather than the PTY.
|
|
95
|
+
const emitsPtyTurnEvents = transcriptClass === 'daemon-owned'
|
|
96
|
+
|| (transcriptClass === 'native-source' && timing === 'immediate');
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
class: transcriptClass,
|
|
100
|
+
timing,
|
|
101
|
+
providerOwnsTranscript: provider.transcriptAuthority === 'provider',
|
|
102
|
+
emitsPtyTurnEvents,
|
|
103
|
+
...(nativeSource && provider.nativeHistory ? { nativeHistory: provider.nativeHistory } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -1183,6 +1183,14 @@ export interface LocalMeshNodeEntry {
|
|
|
1183
1183
|
* first envelope carrying it is ingested.
|
|
1184
1184
|
*/
|
|
1185
1185
|
reportedMemberState?: MeshReportedMemberState;
|
|
1186
|
+
/**
|
|
1187
|
+
* Versioned per-machine runtime facts bundle (MeshNodeFacts — deploy-lag
|
|
1188
|
+
* visibility design §a). Remote nodes: ingested wholesale from the
|
|
1189
|
+
* git_status envelope's reporterNodeFacts. Self/worktree nodes: built by
|
|
1190
|
+
* the SAME producer (buildLocalNodeFacts). Relayed opaquely — surfaces
|
|
1191
|
+
* must pass the object through, never rebuild it field-by-field.
|
|
1192
|
+
*/
|
|
1193
|
+
nodeFacts?: import('@adhdev/mesh-shared').MeshNodeFacts;
|
|
1186
1194
|
/**
|
|
1187
1195
|
* The operator-set machine nickname (config.machineNickname) of the daemon
|
|
1188
1196
|
* that owns this node's workspace. The local coordinator stamps its own
|
|
@@ -1302,6 +1310,14 @@ export interface RepoMeshStatus {
|
|
|
1302
1310
|
* an optional `stats` operational rollup (durations / retries).
|
|
1303
1311
|
*/
|
|
1304
1312
|
missions?: (MeshMissionSummary | MeshMissionSlimSummary)[];
|
|
1313
|
+
/**
|
|
1314
|
+
* Preview-deploy freshness rollup (deploy-lag visibility): stamped by the
|
|
1315
|
+
* dashboard mesh_status when the repo has a preview pipeline. Shape is
|
|
1316
|
+
* daemon-defined (mesh/preview-freshness.ts) — `currentMainCommit` is the
|
|
1317
|
+
* global deploy-lag anchor the dashboard compares nodeFacts.daemonBuild
|
|
1318
|
+
* against. Omitted for repos without the pipeline and by older daemons.
|
|
1319
|
+
*/
|
|
1320
|
+
previewFreshness?: Record<string, unknown>;
|
|
1305
1321
|
/**
|
|
1306
1322
|
* MAGI cross-verification activity, reconstructed from the mesh ledger
|
|
1307
1323
|
* (magi_dispatched / magi_synthesis entries) and folded in so the dashboard's
|
|
@@ -1476,6 +1492,13 @@ export interface RepoMeshNodeStatus {
|
|
|
1476
1492
|
* (scope/isDaemonAffecting flags). Omitted when the build is current.
|
|
1477
1493
|
*/
|
|
1478
1494
|
staleDaemonBuild?: Record<string, unknown>;
|
|
1495
|
+
/**
|
|
1496
|
+
* Versioned per-machine runtime facts bundle (MeshNodeFacts). Opaque
|
|
1497
|
+
* pass-through from the node record — see the node-level field doc.
|
|
1498
|
+
* Carries the daemon build COMMIT (not just the version string), which is
|
|
1499
|
+
* the deploy-lag anchor the dashboard renders.
|
|
1500
|
+
*/
|
|
1501
|
+
nodeFacts?: import('@adhdev/mesh-shared').MeshNodeFacts;
|
|
1479
1502
|
error?: string;
|
|
1480
1503
|
}
|
|
1481
1504
|
|