@adhdev/daemon-core 0.9.82-rc.137 → 0.9.82-rc.138
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/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +15 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +0 -1
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +1 -0
- package/dist/index.js +922 -328
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +922 -328
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/providers/contracts.d.ts +19 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/shared-types.d.ts +7 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +9 -0
- package/src/cli-adapters/cli-state-engine.ts +103 -6
- package/src/cli-adapters/provider-cli-adapter.ts +51 -5
- package/src/cli-adapters/provider-cli-parse.ts +3 -0
- package/src/cli-adapters/provider-cli-shared.ts +13 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +712 -381
- package/src/commands/router.ts +14 -2
- package/src/config/chat-history.ts +36 -13
- package/src/mesh/contracts.ts +329 -0
- package/src/providers/contracts.ts +19 -0
- package/src/providers/provider-loader.ts +21 -7
- package/src/providers/provider-schema.ts +10 -0
- package/src/providers/read-chat-contract.ts +74 -14
- package/src/providers/transcript-v2.ts +567 -0
- package/src/shared-types.ts +7 -0
- package/src/status/snapshot.ts +35 -11
- package/src/types.ts +5 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat source state machine.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the ad-hoc anchor + freshness + 6-trigger ladder that
|
|
5
|
+
* chat-commands.ts:1500-1800 grew over time. The machine has four states and
|
|
6
|
+
* three events; every other signal collapses to one of those three.
|
|
7
|
+
*
|
|
8
|
+
* States Meaning
|
|
9
|
+
* ─────────────────────────────────────────────────────────────────
|
|
10
|
+
* Booting No native attempt evaluated yet. PTY is the source
|
|
11
|
+
* because we have nothing else; this is not a
|
|
12
|
+
* "fallback", just the starting condition.
|
|
13
|
+
* NativeLocked Native transcript is the source. The lock holds even
|
|
14
|
+
* when PTY receives newer bytes — only NativeRegressed
|
|
15
|
+
* or NativeUnavailable can break it.
|
|
16
|
+
* PtyOnly Native is genuinely not usable (unavailable /
|
|
17
|
+
* regressed / never observed). PTY is the source.
|
|
18
|
+
* Recovering We were NativeLocked, native disappeared, but we
|
|
19
|
+
* have not yet seen enough PTY-only turns to commit
|
|
20
|
+
* to PtyOnly. Behaves like PtyOnly for the current
|
|
21
|
+
* read but a single NativeProgressed re-locks.
|
|
22
|
+
*
|
|
23
|
+
* Events Meaning
|
|
24
|
+
* ─────────────────────────────────────────────────────────────────
|
|
25
|
+
* NativeProgressed Native transcript has fresh, safely-mapped messages
|
|
26
|
+
* whose newest sequence is >= the last observed peak.
|
|
27
|
+
* NativeRegressed Native messages dropped, mapping became unsafe, or
|
|
28
|
+
* the transcript shrank. Strong unlock signal.
|
|
29
|
+
* NativeUnavailable Native transcript could not be fetched at all
|
|
30
|
+
* (read error, provider not supported, schema invalid).
|
|
31
|
+
* Soft unlock — Recovering tolerates a transient miss.
|
|
32
|
+
*
|
|
33
|
+
* Why this replaces the v1 design:
|
|
34
|
+
* - The v1 freshness check (`isNativeHistoryFreshEnough`) compared native's
|
|
35
|
+
* newest receivedAt to the PTY buffer's. PTY arrived every turn, so
|
|
36
|
+
* native looked stale by default and only the 30-minute anchor TTL kept
|
|
37
|
+
* things stable. When the anchor expired the source flipped, which is
|
|
38
|
+
* the "plipping" the user reported. The new machine does not compare
|
|
39
|
+
* PTY freshness against native at all; the only thing that unlocks a
|
|
40
|
+
* NativeLocked state is evidence that native itself moved backwards or
|
|
41
|
+
* vanished.
|
|
42
|
+
* - The 6 trigger strings (`native_history_partial`, `native_history_stale`,
|
|
43
|
+
* `native_history_not_safely_mapped`, `native_history_empty`,
|
|
44
|
+
* `native_history_error:*`, `native_history_unavailable:*`) collapse to
|
|
45
|
+
* three events. Each transition records the original trigger as `cause`
|
|
46
|
+
* for observability, so we lose nothing diagnostic.
|
|
47
|
+
* - There is no anchor store. The "lock" is a state, not a piece of
|
|
48
|
+
* mutable data living on the adapter. Callers pass the previous state in
|
|
49
|
+
* and get the next state out.
|
|
50
|
+
*/
|
|
51
|
+
import type { ChatContractVersion } from '../providers/transcript-v2.js';
|
|
52
|
+
import { CHAT_CONTRACT_VERSION_V1, CHAT_CONTRACT_VERSION_V2 } from '../providers/transcript-v2.js';
|
|
53
|
+
export type ChatSourceStateName = 'Booting' | 'NativeLocked' | 'PtyOnly' | 'Recovering';
|
|
54
|
+
export type ChatSourceSelected = 'native-history' | 'pty-parser';
|
|
55
|
+
export type ChatSourceEventKind = 'NativeProgressed' | 'NativeRegressed' | 'NativeUnavailable';
|
|
56
|
+
/**
|
|
57
|
+
* Cause codes mirror the legacy 6-trigger vocabulary so existing log
|
|
58
|
+
* pipelines and dashboards can group transitions the same way they do
|
|
59
|
+
* today. They are diagnostic, not control-flow.
|
|
60
|
+
*/
|
|
61
|
+
export type ChatSourceTransitionCause = 'initial' | 'native_progressed' | 'native_regressed_shrunk' | 'native_regressed_unsafe_mapping' | 'native_regressed_coverage_partial' | 'native_regressed_coverage_unavailable' | 'native_unavailable_read_error' | 'native_unavailable_provider_unsupported' | 'native_unavailable_empty' | 'native_unavailable_not_native_source';
|
|
62
|
+
export interface ChatSourceState {
|
|
63
|
+
readonly name: ChatSourceStateName;
|
|
64
|
+
/**
|
|
65
|
+
* Highest sequence number ever observed from native. Used as the watermark
|
|
66
|
+
* NativeProgressed has to reach (or exceed) before re-locking from
|
|
67
|
+
* Recovering. Undefined while we have never seen native.
|
|
68
|
+
*/
|
|
69
|
+
readonly nativeSequencePeak: number | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Highest providerUnitKey set we have committed to. Future progressed events
|
|
72
|
+
* must be a superset (no removals) for the lock to hold; if any prior
|
|
73
|
+
* unit key vanishes we issue NativeRegressed instead. Empty when we have
|
|
74
|
+
* never locked.
|
|
75
|
+
*/
|
|
76
|
+
readonly committedUnitKeys: ReadonlySet<string>;
|
|
77
|
+
/**
|
|
78
|
+
* Number of consecutive non-progressed observations while in Recovering.
|
|
79
|
+
* Threshold-based promotion to PtyOnly so transient misses do not commit
|
|
80
|
+
* us to PtyOnly prematurely. Always 0 outside Recovering.
|
|
81
|
+
*/
|
|
82
|
+
readonly recoveringMisses: number;
|
|
83
|
+
}
|
|
84
|
+
export declare const INITIAL_CHAT_SOURCE_STATE: ChatSourceState;
|
|
85
|
+
/**
|
|
86
|
+
* Promote from Recovering to PtyOnly after this many consecutive misses.
|
|
87
|
+
* One transient miss should not flip the source for a user who was just
|
|
88
|
+
* reading from native; three in a row signals native is genuinely gone.
|
|
89
|
+
*/
|
|
90
|
+
export declare const RECOVERING_MISS_PROMOTION_THRESHOLD = 3;
|
|
91
|
+
/**
|
|
92
|
+
* Per-message identity slice the machine needs. Producers normalise their
|
|
93
|
+
* native transcript output into this shape (v2 producers already do so via
|
|
94
|
+
* transcript-v2.ts; v1 producers go through a thin adapter — see
|
|
95
|
+
* source-resolver.ts).
|
|
96
|
+
*/
|
|
97
|
+
export interface ResolverNativeMessageIdentity {
|
|
98
|
+
providerUnitKey: string;
|
|
99
|
+
sequence: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Observation the resolver hands to the machine for a single readChat call.
|
|
103
|
+
* Three mutually exclusive shapes — represented as a discriminated union so
|
|
104
|
+
* the type system enforces honesty about what happened.
|
|
105
|
+
*/
|
|
106
|
+
export type ChatSourceObservation = {
|
|
107
|
+
kind: 'native_present';
|
|
108
|
+
contractVersion: ChatContractVersion;
|
|
109
|
+
messages: ReadonlyArray<ResolverNativeMessageIdentity>;
|
|
110
|
+
/** Producer-declared coverage. Drives Progressed vs Regressed when the
|
|
111
|
+
* transcript is non-empty but only partial. */
|
|
112
|
+
coverage: 'full' | 'tail' | 'current-turn' | 'partial';
|
|
113
|
+
/** True when the daemon can prove these native messages belong to the
|
|
114
|
+
* intended workspace+session. False is treated as regression. */
|
|
115
|
+
safeMapping: boolean;
|
|
116
|
+
} | {
|
|
117
|
+
kind: 'native_unavailable';
|
|
118
|
+
reason: 'provider_not_supported' | 'read_error' | 'empty' | 'not_native_source' | 'coverage_unavailable';
|
|
119
|
+
};
|
|
120
|
+
export interface ChatSourceTransition {
|
|
121
|
+
readonly fromState: ChatSourceStateName;
|
|
122
|
+
readonly toState: ChatSourceStateName;
|
|
123
|
+
readonly event: ChatSourceEventKind | 'NoOp';
|
|
124
|
+
readonly cause: ChatSourceTransitionCause;
|
|
125
|
+
/** Wall-clock ms at the moment of resolution. Producer time, not ordering. */
|
|
126
|
+
readonly at: number;
|
|
127
|
+
}
|
|
128
|
+
export interface ChatSourceLockState {
|
|
129
|
+
readonly locked: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* ms since epoch when the current lock began. Undefined when not locked or
|
|
132
|
+
* not yet observed. Producers may use this for "anchored Xs ago" UI but
|
|
133
|
+
* the machine itself never expires a lock based on it — only events do.
|
|
134
|
+
*/
|
|
135
|
+
readonly lockedSince?: number;
|
|
136
|
+
}
|
|
137
|
+
export interface ChatSourceDecision {
|
|
138
|
+
readonly selected: ChatSourceSelected;
|
|
139
|
+
readonly nextState: ChatSourceState;
|
|
140
|
+
readonly transition: ChatSourceTransition;
|
|
141
|
+
readonly lockState: ChatSourceLockState;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Pure transition function. Given a previous state and a single observation,
|
|
145
|
+
* returns the next state, the resulting transition record, and the selected
|
|
146
|
+
* source. No side effects. No mutation of `prev`.
|
|
147
|
+
*
|
|
148
|
+
* Lock semantics:
|
|
149
|
+
* - Booting + native_present(safeMapping, not partial-only) → NativeLocked
|
|
150
|
+
* - NativeLocked + native_present(superset of committed keys, safe) → stays NativeLocked
|
|
151
|
+
* - NativeLocked + native_present(missing committed keys OR unsafe OR partial-shrunk) → PtyOnly
|
|
152
|
+
* - NativeLocked + native_unavailable → Recovering (lock holds for the source decision but watermark survives)
|
|
153
|
+
* - Recovering + native_present(progressed past peak) → NativeLocked
|
|
154
|
+
* - Recovering + native_unavailable (RECOVERING_MISS_PROMOTION_THRESHOLD times) → PtyOnly
|
|
155
|
+
* - PtyOnly is sticky: only an explicit progressed observation that meets
|
|
156
|
+
* the original superset rule moves us back to NativeLocked. This is the
|
|
157
|
+
* intended behaviour — once we have concluded native is dead, we do not
|
|
158
|
+
* re-lock on a single transient revival.
|
|
159
|
+
*/
|
|
160
|
+
export declare function transitionChatSourceState(prev: ChatSourceState, observation: ChatSourceObservation, at: number, lockedSince: number | undefined): {
|
|
161
|
+
next: ChatSourceState;
|
|
162
|
+
transition: ChatSourceTransition;
|
|
163
|
+
selected: ChatSourceSelected;
|
|
164
|
+
lockState: ChatSourceLockState;
|
|
165
|
+
};
|
|
166
|
+
export { CHAT_CONTRACT_VERSION_V1, CHAT_CONTRACT_VERSION_V2 };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat source resolver.
|
|
3
|
+
*
|
|
4
|
+
* Facade over ChatSourceMachine. Responsibilities:
|
|
5
|
+
* - Normalise heterogeneous provider output (v1 ad-hoc identities, v2
|
|
6
|
+
* ChatMessageV2) into ResolverNativeMessageIdentity slices the machine
|
|
7
|
+
* can compare.
|
|
8
|
+
* - Hold per-session machine state in an in-memory map (no anchor disk
|
|
9
|
+
* persistence — the machine is fully recoverable from a fresh
|
|
10
|
+
* observation).
|
|
11
|
+
* - Maintain a small ring buffer of recent transitions per session so
|
|
12
|
+
* debug surfaces (web-devconsole SourceTimeline, mesh_read_debug
|
|
13
|
+
* bundles) can show *why* the source flipped, not just what it is now.
|
|
14
|
+
*
|
|
15
|
+
* What this module deliberately does NOT do:
|
|
16
|
+
* - Decide which messages to display. The machine decides only between
|
|
17
|
+
* `native-history` and `pty-parser`; selecting/merging the actual
|
|
18
|
+
* message arrays stays in chat-commands.ts (A2.2).
|
|
19
|
+
* - Mutate provider modules or CLI adapters. nativeHistoryAnchoredAt on
|
|
20
|
+
* ProviderCliAdapter is gone in A2.2; the resolver is the new owner.
|
|
21
|
+
* - Persist anything to disk. A daemon restart starts every session in
|
|
22
|
+
* `Booting`, which lock-promotes on the first valid native observation
|
|
23
|
+
* of the new process.
|
|
24
|
+
*/
|
|
25
|
+
import { INITIAL_CHAT_SOURCE_STATE, transitionChatSourceState, type ChatSourceDecision, type ChatSourceObservation, type ChatSourceState, type ChatSourceTransition } from './source-machine.js';
|
|
26
|
+
import type { ChatContractVersion } from '../providers/transcript-v2.js';
|
|
27
|
+
/** Maximum transitions retained per session for debug surfaces. */
|
|
28
|
+
export declare const TRANSITION_HISTORY_LIMIT = 25;
|
|
29
|
+
/** Composite key uniquely identifying a chat surface for state purposes.
|
|
30
|
+
* We key by (providerType, sessionId) because the same provider may run
|
|
31
|
+
* multiple sessions and each has its own native/PTY interplay. */
|
|
32
|
+
export type ChatSourceSessionKey = string;
|
|
33
|
+
export declare function chatSourceSessionKey(providerType: string, sessionId: string): ChatSourceSessionKey;
|
|
34
|
+
/**
|
|
35
|
+
* Per-session machine store. Keyed by ChatSourceSessionKey. Daemon-lifetime
|
|
36
|
+
* memory; not persisted. Tests should create their own instance to keep
|
|
37
|
+
* isolation; production has a module-level singleton (CHAT_SOURCE_REGISTRY)
|
|
38
|
+
* exposed at the bottom of this file.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ChatSourceRegistry {
|
|
41
|
+
private readonly records;
|
|
42
|
+
/** Snapshot of current state for diagnostics. Does not mutate. */
|
|
43
|
+
getState(key: ChatSourceSessionKey): ChatSourceState;
|
|
44
|
+
/** Recent transitions, newest last. Empty array when nothing has happened. */
|
|
45
|
+
getTransitions(key: ChatSourceSessionKey): ReadonlyArray<ChatSourceTransition>;
|
|
46
|
+
/** Drop a session. Caller should invoke this when the session is destroyed
|
|
47
|
+
* to avoid unbounded growth across long-lived daemons. */
|
|
48
|
+
clear(key: ChatSourceSessionKey): void;
|
|
49
|
+
/** Drop all sessions. Test helper. */
|
|
50
|
+
clearAll(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Apply one observation, returning the decision and side-effecting the
|
|
53
|
+
* stored state. The returned `nextState` is the same object now stored
|
|
54
|
+
* under `key`; callers may treat the decision as authoritative without
|
|
55
|
+
* re-reading.
|
|
56
|
+
*/
|
|
57
|
+
observe(key: ChatSourceSessionKey, observation: ChatSourceObservation, at?: number): ChatSourceDecision;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a native_present observation from a v2 read_chat payload's message
|
|
61
|
+
* array. v2 producers already provide stable identity (transcript-v2.ts
|
|
62
|
+
* invariants), so this is a thin projection.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildV2NativePresentObservation(args: {
|
|
65
|
+
messages: ReadonlyArray<{
|
|
66
|
+
providerUnitKey: string;
|
|
67
|
+
sequence: number;
|
|
68
|
+
}>;
|
|
69
|
+
coverage: 'full' | 'tail' | 'current-turn' | 'partial';
|
|
70
|
+
safeMapping: boolean;
|
|
71
|
+
}): ChatSourceObservation;
|
|
72
|
+
/**
|
|
73
|
+
* Build a native_present observation from v1 producer output. v1 messages
|
|
74
|
+
* may have no providerUnitKey or a producer-derived one that shifts across
|
|
75
|
+
* reads (index/content-hash based). We synthesise a deterministic identity
|
|
76
|
+
* from the most stable fields available so the machine has *something* to
|
|
77
|
+
* compare across reads. This is intentionally lossy — A2.3 retires v1
|
|
78
|
+
* producers and this branch goes away.
|
|
79
|
+
*
|
|
80
|
+
* The synthesised key is NOT the same as the v2 contract key; never compare
|
|
81
|
+
* v1-synthesised keys against v2 keys for the same logical message. The
|
|
82
|
+
* registry naturally separates them by ChatSourceSessionKey so cross-version
|
|
83
|
+
* leakage cannot happen in practice.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildV1NativePresentObservation(args: {
|
|
86
|
+
providerType: string;
|
|
87
|
+
sessionId: string;
|
|
88
|
+
messages: ReadonlyArray<{
|
|
89
|
+
providerUnitKey?: unknown;
|
|
90
|
+
bubbleId?: unknown;
|
|
91
|
+
id?: unknown;
|
|
92
|
+
index?: unknown;
|
|
93
|
+
role?: unknown;
|
|
94
|
+
receivedAt?: unknown;
|
|
95
|
+
timestamp?: unknown;
|
|
96
|
+
content?: unknown;
|
|
97
|
+
}>;
|
|
98
|
+
coverage: 'full' | 'tail' | 'current-turn' | 'partial';
|
|
99
|
+
safeMapping: boolean;
|
|
100
|
+
}): ChatSourceObservation;
|
|
101
|
+
export declare const CHAT_SOURCE_REGISTRY: ChatSourceRegistry;
|
|
102
|
+
export type { ChatSourceDecision, ChatSourceLockState, ChatSourceObservation, ChatSourceState, ChatSourceTransition, ChatSourceTransitionCause, ResolverNativeMessageIdentity, } from './source-machine.js';
|
|
103
|
+
export { transitionChatSourceState, INITIAL_CHAT_SOURCE_STATE };
|
|
104
|
+
export type { ChatContractVersion };
|
|
@@ -82,6 +82,19 @@ export declare class CliStateEngine {
|
|
|
82
82
|
pendingScriptStatusSince: number;
|
|
83
83
|
private pendingScriptStatusTimer;
|
|
84
84
|
private idleFinishCandidate;
|
|
85
|
+
/**
|
|
86
|
+
* `finishResponse` produces the `generating → idle` transition that
|
|
87
|
+
* coordinators interpret as "task complete". Some providers (antigravity-
|
|
88
|
+
* cli observed in the wild) briefly paint a screen that looks like an
|
|
89
|
+
* idle prompt between tool result frames while still actively running,
|
|
90
|
+
* which fired `response_finished` and broke completion semantics.
|
|
91
|
+
* We defer the actual idle transition by IDLE_CONFIRMATION_GRACE_MS and
|
|
92
|
+
* cancel it if the scripted detection re-detects generating during that
|
|
93
|
+
* window — a true completion stays idle for many seconds, so a 2-second
|
|
94
|
+
* grace is sufficient to filter the paint blip.
|
|
95
|
+
*/
|
|
96
|
+
private pendingIdleFinishTimer;
|
|
97
|
+
private pendingIdleFinishAt;
|
|
85
98
|
private statusHistory;
|
|
86
99
|
private traceEntries;
|
|
87
100
|
private traceSeq;
|
|
@@ -138,6 +151,8 @@ export declare class CliStateEngine {
|
|
|
138
151
|
private maybeScheduleProviderErrorRetry;
|
|
139
152
|
private applyIdle;
|
|
140
153
|
finishResponse(): void;
|
|
154
|
+
private scheduleIdleFinish;
|
|
155
|
+
private cancelPendingIdleFinish;
|
|
141
156
|
private armApprovalExitTimeout;
|
|
142
157
|
private armIdleFinishCandidate;
|
|
143
158
|
private shouldDeferIdleTimeoutFinish;
|
|
@@ -62,7 +62,6 @@ export declare class ProviderCliAdapter implements CliAdapter {
|
|
|
62
62
|
private pendingOutboundFlushInFlight;
|
|
63
63
|
private submitRetryTimer;
|
|
64
64
|
private resizeSuppressUntil;
|
|
65
|
-
nativeHistoryAnchoredAt: number;
|
|
66
65
|
private readonly runner;
|
|
67
66
|
/** @deprecated use runner.cliScripts for direct script access */
|
|
68
67
|
get cliScripts(): CliScripts;
|
|
@@ -23,6 +23,7 @@ export declare function buildCliParseInput(options: {
|
|
|
23
23
|
isWaitingForResponse?: boolean;
|
|
24
24
|
scope?: TurnParseScope | null;
|
|
25
25
|
runtimeSettings: Record<string, any>;
|
|
26
|
+
spawnAt?: number;
|
|
26
27
|
}): CliScriptInput;
|
|
27
28
|
export declare function summarizeCliTraceText(text: string, max?: number): string;
|
|
28
29
|
export declare function summarizeCliTraceMessages(messages: CliChatMessage[], limit?: number): {
|