@adhdev/daemon-core 1.0.28-rc.7 → 1.0.28-rc.9
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.d.ts +1 -1
- package/dist/index.js +251 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -45
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +5 -1
- package/dist/mesh/mesh-queue-assignment.d.ts +19 -1
- package/dist/mesh/mesh-runtime-store.d.ts +4 -0
- package/dist/providers/cli-provider-instance-types.d.ts +1 -0
- package/dist/providers/spec/fsm-driver.d.ts +13 -0
- package/dist/providers/spec/types.d.ts +25 -0
- package/package.json +3 -3
- package/src/commands/med-family/mesh-crud.ts +27 -0
- package/src/index.ts +1 -1
- package/src/mesh/mesh-ledger.ts +72 -11
- package/src/mesh/mesh-queue-assignment.ts +176 -17
- package/src/mesh/mesh-reconcile-loop.ts +34 -0
- package/src/mesh/mesh-runtime-store.ts +33 -8
- package/src/mesh/mesh-work-queue.ts +4 -0
- package/src/providers/cli-provider-instance-types.ts +25 -0
- package/src/providers/cli-provider-instance.ts +51 -0
- package/src/providers/spec/evaluator.ts +9 -1
- package/src/providers/spec/fsm-driver.ts +43 -3
- package/src/providers/spec/types.ts +25 -0
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
COMPLETED_FINALIZATION_RETRY_MS,
|
|
60
60
|
COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
61
61
|
CANON_C_MISSING_ASSISTANT_MIN_ELAPSED_MS,
|
|
62
|
+
MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS,
|
|
62
63
|
NATIVE_HISTORY_MESH_IDLE_SETTLE_MS,
|
|
63
64
|
PTY_PARSED_FINAL_ASSISTANT_QUIET_DWELL_MS,
|
|
64
65
|
ANTIGRAVITY_HOLD_QUIET_DWELL_MS,
|
|
@@ -3140,6 +3141,56 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3140
3141
|
// from ever being emitted during the valley, without depending on the valley's length.
|
|
3141
3142
|
const isTranscriptEvidenceGate = block.allowTimeout === true;
|
|
3142
3143
|
LOG.debug('CLI', `[${this.type}] finalization block: reason=${blockReason} terminal=${block.terminal} allowTimeout=${isTranscriptEvidenceGate} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
|
|
3144
|
+
// (TRANSCRIPT-GROWTH-HOLD — CODEX-FSM-DEGENERATE-STABLE RCA, upper safety net)
|
|
3145
|
+
// The FLOOR class (missing_final_assistant + noExternalTranscriptSource:
|
|
3146
|
+
// codex-cli / kimi / cursor-cli / opencode) releases its weak emit once
|
|
3147
|
+
// the idle has been CONTINUOUSLY quiet past the floor/cap — "quiet" as
|
|
3148
|
+
// judged by the SAME screen parsing whose lie armed this completion
|
|
3149
|
+
// (spinner escaped the status window, or a degenerate stable region
|
|
3150
|
+
// accumulated a false 353s "quiet"). The daemon already observes the
|
|
3151
|
+
// independent liveness signal that proves otherwise — the native
|
|
3152
|
+
// transcript advancing (the stall watchdog logs exactly this: "PTY
|
|
3153
|
+
// quiet 365s but transcript advancing") — but it was never wired into
|
|
3154
|
+
// the idle/completion judgment. Wire it here: if the provider's native
|
|
3155
|
+
// transcript file was appended within the growth-quiet window, the
|
|
3156
|
+
// turn is demonstrably alive; HOLD (re-probe each retry — the hold
|
|
3157
|
+
// releases at most MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS after
|
|
3158
|
+
// the transcript's last append) instead of emitting a completion the
|
|
3159
|
+
// coordinator can never correct.
|
|
3160
|
+
//
|
|
3161
|
+
// Conservative by construction: this only ever DELAYS an emit on
|
|
3162
|
+
// positive growth evidence. A pure-PTY provider (no native source →
|
|
3163
|
+
// sample null), an unresolvable transcript, or a transcript whose
|
|
3164
|
+
// mtime is unknown/quiet falls through to the unchanged floor/cap
|
|
3165
|
+
// logic — missing information NEVER blocks an idle verdict (no
|
|
3166
|
+
// false-busy wedge). Clean completions (in-turn final assistant
|
|
3167
|
+
// present) never reach this branch, so genuine completion latency is
|
|
3168
|
+
// untouched; claude-cli's write-lag CANON-C immediate emit is likewise
|
|
3169
|
+
// untouched (its block deliberately omits noExternalTranscriptSource).
|
|
3170
|
+
if (blockReason === 'missing_final_assistant' && block.noExternalTranscriptSource === true) {
|
|
3171
|
+
let nativeSample: { msgCount: number; sourceMtimeMs: number } | null = null;
|
|
3172
|
+
try { nativeSample = this.sampleNativeTranscriptProgress(); } catch { nativeSample = null; }
|
|
3173
|
+
const sourceMtimeMs = nativeSample?.sourceMtimeMs ?? 0;
|
|
3174
|
+
if (nativeSample && sourceMtimeMs > 0 && Date.now() - sourceMtimeMs < MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS) {
|
|
3175
|
+
if (pending.loggedBlockReason !== 'native_transcript_advancing') {
|
|
3176
|
+
LOG.info('CLI', `[${this.type}] holding pending completed (native_transcript_advancing: msgCount=${nativeSample.msgCount} mtimeAge=${Date.now() - sourceMtimeMs}ms < ${MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS}ms) — transcript still growing, screen-idle verdict not trusted`);
|
|
3177
|
+
if (this.isMeshWorkerSession()) {
|
|
3178
|
+
traceMeshEventDrop('completion_gate_hold', this.meshTraceCtx(), `native_transcript_advancing msgCount=${nativeSample.msgCount} mtimeAge=${Date.now() - sourceMtimeMs}ms`);
|
|
3179
|
+
}
|
|
3180
|
+
if (this.completionTraceOn()) this.recordCompletionGateTrace('hold', {
|
|
3181
|
+
blockReason: 'native_transcript_advancing',
|
|
3182
|
+
latestVisibleStatus,
|
|
3183
|
+
msgCount: nativeSample.msgCount,
|
|
3184
|
+
sourceMtimeAgeMs: Date.now() - sourceMtimeMs,
|
|
3185
|
+
growthQuietMs: MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS,
|
|
3186
|
+
waitedMs,
|
|
3187
|
+
});
|
|
3188
|
+
pending.loggedBlockReason = 'native_transcript_advancing';
|
|
3189
|
+
}
|
|
3190
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3143
3194
|
if (!isTranscriptEvidenceGate && (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS)) {
|
|
3144
3195
|
if (pending.loggedBlockReason !== blockReason) {
|
|
3145
3196
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
@@ -107,7 +107,9 @@ export function resolveSections(
|
|
|
107
107
|
if (candIdx !== -1 && (idx === -1 || candIdx < idx)) idx = candIdx;
|
|
108
108
|
}
|
|
109
109
|
if (idx !== -1) {
|
|
110
|
-
from =
|
|
110
|
+
from = typeof sec.above === 'number' && sec.above > 0
|
|
111
|
+
? Math.max(0, idx - Math.floor(sec.above))
|
|
112
|
+
: idx;
|
|
111
113
|
to = total;
|
|
112
114
|
if (sec.until_regex !== undefined) {
|
|
113
115
|
try {
|
|
@@ -118,6 +120,12 @@ export function resolveSections(
|
|
|
118
120
|
} else if (sec.lines !== undefined) {
|
|
119
121
|
to = Math.min(total, from + sec.lines);
|
|
120
122
|
}
|
|
123
|
+
} else if (sec.anchor_miss === 'empty') {
|
|
124
|
+
// Explicit empty-on-miss (see SectionDef.anchor_miss): the
|
|
125
|
+
// section resolves to EMPTY rather than the whole-screen
|
|
126
|
+
// fallback, so narrow-cue guards never see unrelated body.
|
|
127
|
+
from = 0;
|
|
128
|
+
to = 0;
|
|
121
129
|
}
|
|
122
130
|
} catch { /* bad anchor regex */ }
|
|
123
131
|
} else if (sec.from_top !== undefined) {
|
|
@@ -876,9 +876,33 @@ export class FsmDriver implements ISpecDriver {
|
|
|
876
876
|
curLines = currentLines;
|
|
877
877
|
prevLines = this.prevScreenLines;
|
|
878
878
|
} else {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
879
|
+
// CODEX-FSM-DEGENERATE-STABLE fix: cursor.row is the backend's RAW
|
|
880
|
+
// row coordinate (ghostty getCursorPosition(), un-normalized), while
|
|
881
|
+
// currentLines is the VIEWPORT snapshot with blank ends trimmed
|
|
882
|
+
// (ghostty-vt-backend getText → trimBlankEnds). The two coordinate
|
|
883
|
+
// spaces diverge whenever trailing blank rows are trimmed away (or
|
|
884
|
+
// the backend counts scrollback rows): cursor.row then overshoots
|
|
885
|
+
// the array and slice(start, cursor.row) returns an EMPTY window.
|
|
886
|
+
// Two empty windows compare equal on every frame, so
|
|
887
|
+
// regionLastChangedAt never advances and stable_ms accumulates
|
|
888
|
+
// forever — the FSM read a generating screen as
|
|
889
|
+
// "stable cursor_above=4 353833ms / 1500ms" and committed a false
|
|
890
|
+
// busy→idle (live RCA: generating_completed at duration=402s while
|
|
891
|
+
// the native transcript kept growing).
|
|
892
|
+
//
|
|
893
|
+
// Invariant: an unmeasurable window must NEVER read as "stable".
|
|
894
|
+
// Clamp the window end to the content length (when the cursor sits
|
|
895
|
+
// in the trimmed blank region, the lines directly above it in
|
|
896
|
+
// content terms are the content tail), and when the window is still
|
|
897
|
+
// empty (no measurable content at all) mark the region CHANGED so
|
|
898
|
+
// the stable clock restarts instead of accumulating.
|
|
899
|
+
const window = stableCursorWindow(currentLines.length, cursor.row, d.cursor_above);
|
|
900
|
+
if (!window) {
|
|
901
|
+
this.regionLastChangedAt.set(d.key, now);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
curLines = currentLines.slice(window.start, window.end);
|
|
905
|
+
prevLines = this.prevScreenLines.slice(window.start, window.end);
|
|
882
906
|
}
|
|
883
907
|
const cur = filterIgnoredLines(curLines, d.ignoreRe).join('\n');
|
|
884
908
|
const prev = filterIgnoredLines(prevLines, d.ignoreRe).join('\n');
|
|
@@ -1570,3 +1594,19 @@ export function filterIgnoredLines(lines: string[], ignoreRe: RegExp | undefined
|
|
|
1570
1594
|
if (!ignoreRe) return lines;
|
|
1571
1595
|
return lines.filter(l => !ignoreRe.test(l));
|
|
1572
1596
|
}
|
|
1597
|
+
|
|
1598
|
+
/** Compute the [start, end) line window a cursor_above stable region measures,
|
|
1599
|
+
* reconciling the backend's raw cursor row with the blank-trimmed viewport
|
|
1600
|
+
* line array (see the CODEX-FSM-DEGENERATE-STABLE note in trackRegionChanges).
|
|
1601
|
+
* The window end is clamped to the content length so an overshooting cursor
|
|
1602
|
+
* row measures the content tail instead of slicing past the array into a
|
|
1603
|
+
* permanently-empty — and therefore permanently "unchanged" — window.
|
|
1604
|
+
* Returns null when no measurable window exists (cursor at/above row 0, or no
|
|
1605
|
+
* content): the caller must treat the region as CHANGED, never stable.
|
|
1606
|
+
* Exported for unit tests. */
|
|
1607
|
+
export function stableCursorWindow(lineCount: number, cursorRow: number, cursorAbove: number): { start: number; end: number } | null {
|
|
1608
|
+
const end = Math.min(Math.max(0, cursorRow), Math.max(0, lineCount));
|
|
1609
|
+
const start = Math.max(0, end - cursorAbove);
|
|
1610
|
+
if (end <= start) return null;
|
|
1611
|
+
return { start, end };
|
|
1612
|
+
}
|
|
@@ -293,6 +293,31 @@ export interface SectionDef {
|
|
|
293
293
|
* for that anchor. A scalar `anchor` ignores array form beyond index 0.
|
|
294
294
|
*/
|
|
295
295
|
anchor_context?: AnchorContext | (AnchorContext | null)[];
|
|
296
|
+
/**
|
|
297
|
+
* Extends an anchored section N lines ABOVE its anchor line. The default
|
|
298
|
+
* anchored geometry starts the section AT the anchor line, which cannot
|
|
299
|
+
* express a status block that sits directly ABOVE its landmark (e.g.
|
|
300
|
+
* codex's live `Working (…)` spinner above the `› ` composer). With
|
|
301
|
+
* `above: K` the section starts at max(0, anchorLine - K), so the window
|
|
302
|
+
* stays pinned to the landmark regardless of output volume — unlike
|
|
303
|
+
* `from_bottom`, which counts from the LAST NON-BLANK line of a
|
|
304
|
+
* blank-trimmed viewport and lets the live status block escape the window
|
|
305
|
+
* once long output fills the tail (CODEX-FSM-DEGENERATE-STABLE RCA,
|
|
306
|
+
* defect 1: the spinner left the from_bottom:12 window while the worker
|
|
307
|
+
* was still generating).
|
|
308
|
+
*/
|
|
309
|
+
above?: number;
|
|
310
|
+
/**
|
|
311
|
+
* Anchor-miss policy. Default (absent): an anchored section whose anchor
|
|
312
|
+
* matches NOTHING falls back to the whole screen (from=0, to=total) — the
|
|
313
|
+
* historical behavior modal sections rely on ("whole-screen fallback in
|
|
314
|
+
* non-modal frames is harmless"). 'empty' resolves the section to EMPTY
|
|
315
|
+
* instead, for sections whose guard regexes must never see unrelated body
|
|
316
|
+
* text: status_tail's spinner cues, whose whole-screen fallback would
|
|
317
|
+
* re-open the SPINNER-BODY-SELFMATCH defect on any frame where the
|
|
318
|
+
* composer landmark is momentarily absent (mid-redraw).
|
|
319
|
+
*/
|
|
320
|
+
anchor_miss?: 'empty';
|
|
296
321
|
lines?: number;
|
|
297
322
|
until_regex?: string;
|
|
298
323
|
until_regex_flags?: string;
|