@adhdev/daemon-core 1.0.28-rc.6 → 1.0.28-rc.7
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/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/index.js +50 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/cli-adapters/cli-state-engine.ts +9 -1
- package/src/cli-adapters/provider-cli-adapter.ts +42 -1
- package/src/providers/cli-provider-instance.ts +55 -11
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.7",
|
|
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.7",
|
|
53
|
+
"@adhdev/session-host-core": "1.0.28-rc.7",
|
|
54
54
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
55
55
|
"ajv": "^8.20.0",
|
|
56
56
|
"ajv-formats": "^3.0.1",
|
|
@@ -1604,7 +1604,15 @@ export class CliStateEngine {
|
|
|
1604
1604
|
// background-tool race this was meant to guard against. If a provider
|
|
1605
1605
|
// really needs the gate, the manifest can set
|
|
1606
1606
|
// `requiresFinalAssistantBeforeIdle: true`.
|
|
1607
|
-
|
|
1607
|
+
//
|
|
1608
|
+
// This is a completion-timing JUDGMENT, so it goes through the shared
|
|
1609
|
+
// transcript-authority profile rather than reading the raw flag: the
|
|
1610
|
+
// 'floor' class is exactly `requiresFinalAssistantBeforeIdle && !hold`
|
|
1611
|
+
// (see resolveTranscriptAuthorityProfile), which is equivalent to the
|
|
1612
|
+
// old `!!requiresFinalAssistantBeforeIdle` for every real provider
|
|
1613
|
+
// because none declares both flags — a hold provider (antigravity)
|
|
1614
|
+
// sets holdCompletionForTranscript only, so it was already false here.
|
|
1615
|
+
const requiresFinalAssistant = resolveTranscriptAuthorityProfile(this.provider).timing === 'floor';
|
|
1608
1616
|
if (!requiresFinalAssistant) return false;
|
|
1609
1617
|
if (!this.isWaitingForResponse || !this.currentTurnScope || this.hasActionableApproval()) return false;
|
|
1610
1618
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status.trim() : '';
|
|
@@ -232,6 +232,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
232
232
|
private pendingOutboundQueue: PendingOutboundMessage[] = [];
|
|
233
233
|
private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
|
|
234
234
|
private pendingOutboundFlushInFlight = false;
|
|
235
|
+
// Stale-queue watchdog: fires STALE_QUEUE_WARN_MS after the oldest queued
|
|
236
|
+
// message was enqueued if nothing has been flushed yet.
|
|
237
|
+
private pendingOutboundStaleTimer: NodeJS.Timeout | null = null;
|
|
235
238
|
// Submit retry timer — PTY-level, not state machine
|
|
236
239
|
private submitRetryTimer: NodeJS.Timeout | null = null;
|
|
237
240
|
|
|
@@ -1715,6 +1718,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1715
1718
|
await new Promise<void>(resolve => setTimeout(resolve, FORCE_SUBMIT_SETTLE_MS));
|
|
1716
1719
|
}
|
|
1717
1720
|
|
|
1721
|
+
// Stale-queue threshold: warn if a queued message has not been flushed
|
|
1722
|
+
// within this many milliseconds (instrumentation only, no behavior change).
|
|
1723
|
+
private static readonly STALE_QUEUE_WARN_MS = 15000;
|
|
1724
|
+
|
|
1718
1725
|
private enqueuePendingOutboundMessage(text: string, reason: string, meshTaskId?: string): void {
|
|
1719
1726
|
const content = String(text || '');
|
|
1720
1727
|
const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
|
|
@@ -1722,6 +1729,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1722
1729
|
return;
|
|
1723
1730
|
}
|
|
1724
1731
|
const queuedAt = Date.now();
|
|
1732
|
+
const stableMs = this.lastScreenChangeAt ? (queuedAt - this.lastScreenChangeAt) : -1;
|
|
1733
|
+
// Diagnostic context: which gate caused the silent-queue and the full
|
|
1734
|
+
// session readiness snapshot at the moment of enqueue.
|
|
1735
|
+
const gateContext = {
|
|
1736
|
+
reason,
|
|
1737
|
+
startupParseGate: this.startupParseGate,
|
|
1738
|
+
ready: this.ready,
|
|
1739
|
+
engineStatus: this.engine.currentStatus,
|
|
1740
|
+
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
1741
|
+
stableMs,
|
|
1742
|
+
sessionId: this.engine.getTraceSessionId(),
|
|
1743
|
+
};
|
|
1725
1744
|
const message: PendingOutboundMessage = {
|
|
1726
1745
|
id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
|
|
1727
1746
|
role: 'user',
|
|
@@ -1731,8 +1750,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1731
1750
|
...(typeof meshTaskId === 'string' && meshTaskId.trim() ? { meshTaskId } : {}),
|
|
1732
1751
|
};
|
|
1733
1752
|
this.pendingOutboundQueue.push(message);
|
|
1734
|
-
LOG.info('CLI', `[${this.cliType}] queued outbound message
|
|
1753
|
+
LOG.info('CLI', `[${this.cliType}] queued outbound message; gate=${JSON.stringify(gateContext)}; queue=${this.pendingOutboundQueue.length}`);
|
|
1735
1754
|
this.onStatusChange?.();
|
|
1755
|
+
// Stale-queue watchdog: emit a warn-level log if the enqueued message
|
|
1756
|
+
// has not been flushed after STALE_QUEUE_WARN_MS. This fires once per
|
|
1757
|
+
// enqueue event (not once per message still in queue) so it does not
|
|
1758
|
+
// spam on a persistently stuck queue; it simply surfaces that the queue
|
|
1759
|
+
// is stuck and records the current gate state at warn time.
|
|
1760
|
+
if (!this.pendingOutboundStaleTimer) {
|
|
1761
|
+
this.pendingOutboundStaleTimer = setTimeout(() => {
|
|
1762
|
+
this.pendingOutboundStaleTimer = null;
|
|
1763
|
+
if (this.pendingOutboundQueue.length === 0) return;
|
|
1764
|
+
const oldest = this.pendingOutboundQueue[0];
|
|
1765
|
+
const staleSec = ((Date.now() - oldest.queuedAt) / 1000).toFixed(1);
|
|
1766
|
+
const nowStableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : -1;
|
|
1767
|
+
LOG.warn('CLI', `[${this.cliType}] STALE QUEUE: ${this.pendingOutboundQueue.length} message(s) undelivered for ${staleSec}s; gate=startupParseGate:${this.startupParseGate} ready:${this.ready} engineStatus:${this.engine.currentStatus} isWaitingForResponse:${this.engine.isWaitingForResponse} stableMs:${nowStableMs} sessionId:${this.engine.getTraceSessionId()} enqueueReason:${oldest.id}`);
|
|
1768
|
+
}, ProviderCliAdapter.STALE_QUEUE_WARN_MS);
|
|
1769
|
+
}
|
|
1736
1770
|
}
|
|
1737
1771
|
|
|
1738
1772
|
private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
|
|
@@ -1785,6 +1819,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1785
1819
|
try {
|
|
1786
1820
|
await this.sendMessageNow(next.content, false, next.meshTaskId);
|
|
1787
1821
|
this.pendingOutboundQueue.shift();
|
|
1822
|
+
// Clear stale watchdog once the queue drains.
|
|
1823
|
+
if (this.pendingOutboundQueue.length === 0 && this.pendingOutboundStaleTimer) {
|
|
1824
|
+
clearTimeout(this.pendingOutboundStaleTimer);
|
|
1825
|
+
this.pendingOutboundStaleTimer = null;
|
|
1826
|
+
}
|
|
1788
1827
|
this.onStatusChange?.();
|
|
1789
1828
|
} catch (error: any) {
|
|
1790
1829
|
LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
|
|
@@ -2174,6 +2213,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2174
2213
|
this.pendingTerminalQueryTail = '';
|
|
2175
2214
|
this.ptyOutputChunks = [];
|
|
2176
2215
|
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2216
|
+
if (this.pendingOutboundStaleTimer) { clearTimeout(this.pendingOutboundStaleTimer); this.pendingOutboundStaleTimer = null; }
|
|
2177
2217
|
this.pendingOutboundQueue = [];
|
|
2178
2218
|
this.pendingOutboundFlushInFlight = false;
|
|
2179
2219
|
if (this.ptyProcess) {
|
|
@@ -2197,6 +2237,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2197
2237
|
this.pendingTerminalQueryTail = '';
|
|
2198
2238
|
this.ptyOutputChunks = [];
|
|
2199
2239
|
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2240
|
+
if (this.pendingOutboundStaleTimer) { clearTimeout(this.pendingOutboundStaleTimer); this.pendingOutboundStaleTimer = null; }
|
|
2200
2241
|
this.pendingOutboundQueue = [];
|
|
2201
2242
|
this.pendingOutboundFlushInFlight = false;
|
|
2202
2243
|
if (this.ptyProcess) {
|
|
@@ -785,16 +785,21 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
785
785
|
}))
|
|
786
786
|
: mergedMessages;
|
|
787
787
|
|
|
788
|
-
// Dashboard-tail repair (native-source
|
|
789
|
-
// assistant answer lives only in native-history,
|
|
790
|
-
// statusMessages end on the user prompt / auto-approve system
|
|
791
|
-
// snapshot's preview / lastMessageRole / completionMarker never see
|
|
792
|
-
// answer — the session looks stuck on the user turn. We already cached the
|
|
793
|
-
// real final assistant summary at completion time (lastCompletionSummary),
|
|
794
|
-
//
|
|
795
|
-
// assistant message at/after it. Purely additive to the status view; no
|
|
796
|
-
//
|
|
797
|
-
//
|
|
788
|
+
// purpose: 'display-tail' (zero-read) — Dashboard-tail repair (native-source
|
|
789
|
+
// providers, e.g. antigravity): the assistant answer lives only in native-history,
|
|
790
|
+
// so the PTY-parsed statusMessages end on the user prompt / auto-approve system
|
|
791
|
+
// lines and the snapshot's preview / lastMessageRole / completionMarker never see
|
|
792
|
+
// the answer — the session looks stuck on the user turn. We already cached the
|
|
793
|
+
// real final assistant summary at completion time (lastCompletionSummary), so
|
|
794
|
+
// append it as the trailing assistant bubble when the current tail has no
|
|
795
|
+
// assistant message at/after it. Purely additive to the status view; no per-tick
|
|
796
|
+
// native read, no effect on providers whose PTY carries the assistant (they
|
|
797
|
+
// surface it themselves and the guard below is a no-op).
|
|
798
|
+
//
|
|
799
|
+
// authority-ok: this is a DISPLAY-ONLY tail repair, never a completion/stall/
|
|
800
|
+
// redrive verdict — it reads the pre-cached summary (zero native read) and only
|
|
801
|
+
// paints the status view. It keys off the adapter's runtime chatMessagesOwnedExternally
|
|
802
|
+
// capability (not a class predicate); a completion decision is never taken here.
|
|
798
803
|
const adapterOwnsMessagesElsewhereForTail = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
799
804
|
if (adapterOwnsMessagesElsewhereForTail && this.lastCompletionSummary) {
|
|
800
805
|
const summary = this.lastCompletionSummary;
|
|
@@ -1580,6 +1585,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1580
1585
|
private readExternalCompletionMessages(): unknown[] | null {
|
|
1581
1586
|
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1582
1587
|
if (!adapterOwnsMessagesElsewhere) return null;
|
|
1588
|
+
// authority-ok: native READ resolution, not a completion/stall/redrive verdict.
|
|
1589
|
+
// This gate only decides whether an on-disk native transcript EXISTS to read for
|
|
1590
|
+
// this session; the completion decision is made by callers over the returned
|
|
1591
|
+
// messages (completionFinalAssistantEvidence / completionFinalSummary), which
|
|
1592
|
+
// route class through resolveTranscriptAuthorityProfile.
|
|
1583
1593
|
if (!isNativeSourceCanonicalHistory(this.provider.nativeHistory)) return null;
|
|
1584
1594
|
|
|
1585
1595
|
// Resolve a CONCRETE native-history handle for this session's OWN
|
|
@@ -2613,6 +2623,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2613
2623
|
* (≥180s of PTY stasis), never on the routine 5s tick.
|
|
2614
2624
|
*/
|
|
2615
2625
|
private sampleNativeTranscriptProgress(): { msgCount: number; sourceMtimeMs: number } | null {
|
|
2626
|
+
// authority-ok: native fingerprint SAMPLING, not a stall verdict. This only
|
|
2627
|
+
// decides whether a native source exists to sample a progress fingerprint from;
|
|
2628
|
+
// the caller (checkMeshWorkerStall) makes the stall/no-progress decision over the
|
|
2629
|
+
// returned fingerprint. Non-native-source classes have nothing to sample here and
|
|
2630
|
+
// fall back to the unchanged lastOutputAt-only judgment.
|
|
2616
2631
|
if (!isNativeSourceCanonicalHistory(this.provider?.nativeHistory)) return null;
|
|
2617
2632
|
// readExternalCompletionMessages resolves this session's OWN native-source
|
|
2618
2633
|
// conversation (providerSessionId / persisted pin / floor claim) and, as a
|
|
@@ -3900,7 +3915,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
3900
3915
|
fcEvidenceSource = evidence.source;
|
|
3901
3916
|
fcFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
3902
3917
|
} catch { /* best-effort */ }
|
|
3903
|
-
|
|
3918
|
+
// SPEC-DRIVEN completion timing (mission f2f6da1b / AGY-BOOT-PHANTOM): the
|
|
3919
|
+
// startup-grace synth must enumerate the SAME transcript-authority timing
|
|
3920
|
+
// classes the genuine finalization gate does (getCompletedFinalizationBlock,
|
|
3921
|
+
// the external-native branch ~line 2120), not just 'floor' + external-native.
|
|
3922
|
+
// The 'hold' class (antigravity — holdCompletionForTranscript) was missing:
|
|
3923
|
+
// at boot its native history is often not yet written, so fcEvidenceSource is
|
|
3924
|
+
// 'unavailable' (neither 'floor' nor 'external-native'), which left
|
|
3925
|
+
// missingEvidence=false and fired a WEAK phantom completion the moment the FSM
|
|
3926
|
+
// collapsed to idle — a completion for a turn whose authoritative transcript
|
|
3927
|
+
// had not landed.
|
|
3928
|
+
const synthTiming = resolveTranscriptAuthorityProfile(this.provider).timing;
|
|
3929
|
+
const missingEvidence = (synthTiming === 'floor' || synthTiming === 'hold' || fcEvidenceSource === 'external-native') && !fcFinalSummary;
|
|
3930
|
+
// HOLD class (antigravity): idle holds for the native transcript to land. The
|
|
3931
|
+
// finalization gate never emits a completion for a hold provider without the
|
|
3932
|
+
// transcript (it returns holdForTranscript / null at ~line 2120), so the synth
|
|
3933
|
+
// must not either — even WITH mesh context. Suppress the synth and leave the
|
|
3934
|
+
// task UNMARKED so a later idle poll re-runs this path once the transcript's
|
|
3935
|
+
// final assistant lands (then fcFinalSummary is present → missingEvidence=false
|
|
3936
|
+
// → a genuine, summary-bearing emit). This is the HOLD-and-retry equivalent of
|
|
3937
|
+
// the finalization gate's non-terminal missing_final_assistant hold; it is what
|
|
3938
|
+
// stops the boot-time phantom weak completion for a mesh worker.
|
|
3939
|
+
if (missingEvidence && synthTiming === 'hold') {
|
|
3940
|
+
LOG.info('CLI', `[${this.type}] ${reason} held: hold-class transcript not yet landed (source=${fcEvidenceSource})`);
|
|
3941
|
+
return false;
|
|
3942
|
+
}
|
|
3904
3943
|
// Mirror the short-generating idle path's suppression: a provider that
|
|
3905
3944
|
// requires a final assistant (or external-native history) with NO confirmed
|
|
3906
3945
|
// summary and NO mesh context emits nothing — the session is idle with no
|
|
@@ -5119,6 +5158,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
5119
5158
|
const limit = options.full ? Number.MAX_SAFE_INTEGER : STATUS_HYDRATION_TAIL_LIMIT;
|
|
5120
5159
|
const windowTag = options.full ? 'full' : `tail:${STATUS_HYDRATION_TAIL_LIMIT}`;
|
|
5121
5160
|
|
|
5161
|
+
// authority-ok: history-hydration READ routing, not a completion verdict. Selects
|
|
5162
|
+
// the on-disk native transcript vs the materialized-mirror read path; no
|
|
5163
|
+
// completion/stall/redrive decision is taken here.
|
|
5122
5164
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
5123
5165
|
const cacheKey = [this.type, this.providerSessionId, this.workingDir, windowTag].join('\0');
|
|
5124
5166
|
const now = Date.now();
|
|
@@ -5188,6 +5230,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
5188
5230
|
// transcript so seedSessionHistory can prime dedup state. Pass full so the
|
|
5189
5231
|
// hydration read is unbounded here (and only here).
|
|
5190
5232
|
this.syncCanonicalSavedHistoryIfNeeded({ full: true });
|
|
5233
|
+
// authority-ok: history-restore READ routing, not a completion verdict — picks the
|
|
5234
|
+
// native transcript read vs the legacy chat-history read for seeding dedup state.
|
|
5191
5235
|
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory)
|
|
5192
5236
|
? readProviderChatHistory(this.type, {
|
|
5193
5237
|
canonicalHistory: this.provider.nativeHistory,
|