@adhdev/daemon-core 0.9.82-rc.220 → 0.9.82-rc.221

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.
@@ -1,17 +1,14 @@
1
1
  /**
2
- * Type mirror of adhdev:cli/spec@1 (v1) and adhdev:cli/spec@3 (v3).
3
- * Keep in sync with schema.gen.ts.
2
+ * Shared types for the adhdev:cli/spec@4 FSM spec system.
4
3
  *
5
- * v1 uses layout.sections[] and debounce{}.
6
- * v3 uses sections{} (object) and timing{}, with composable conditions.
7
- *
8
- * The loader auto-migrates v1 → v3. Callers receive a CliSpec (v3) after
9
- * loadSpec; the v1 raw shape is only used during migration.
4
+ * These are the building blocks reused by fsm-types.ts, evaluator.ts, and
5
+ * the rest of the spec stack. v3/v1 types have been removed all providers
6
+ * are on v4.
10
7
  */
11
8
  'use strict';
12
9
 
13
10
  // ─────────────────────────────────────────────────────────────────────────────
14
- // Shared / unchanged types
11
+ // Shared / layout types
15
12
  // ─────────────────────────────────────────────────────────────────────────────
16
13
 
17
14
  export type Size = number | string;
@@ -22,12 +19,19 @@ export interface SectionPattern {
22
19
  flags?: string;
23
20
  }
24
21
 
22
+ /** Inline wait_for shape used by open_picker control actions. */
23
+ export interface WaitForCondition {
24
+ section?: string;
25
+ regex?: string;
26
+ flags?: string;
27
+ }
28
+
25
29
  export type ControlAction =
26
30
  | { type: 'send_keys'; keys: string }
27
31
  | {
28
32
  type: 'open_picker';
29
33
  trigger_keys: string;
30
- wait_for: SectionRegex;
34
+ wait_for: WaitForCondition;
31
35
  extract_choices: SectionPattern;
32
36
  submit_key: string;
33
37
  }
@@ -58,15 +62,6 @@ export interface DelegateTrigger {
58
62
  task_template: string;
59
63
  }
60
64
 
61
- /**
62
- * Native history config — three modes, picked by which fields are present:
63
- *
64
- * 1. `reader`: built-in reader id (claude-cli / codex-cli / antigravity-cli / hermes-cli).
65
- *
66
- * 2. `source`: declarative source descriptor.
67
- *
68
- * 3. `override_path`: relative path to a provider-supplied reader module.
69
- */
70
65
  export interface NativeHistoryConfig {
71
66
  reader?: 'claude-cli' | 'codex-cli' | 'antigravity-cli' | 'hermes-cli';
72
67
  source?: NativeHistorySource;
@@ -106,21 +101,20 @@ export interface NativeHistoryMessageMap {
106
101
  }
107
102
 
108
103
  // ─────────────────────────────────────────────────────────────────────────────
109
- // v1 types (legacy — used internally during migration)
104
+ // Section definition
110
105
  // ─────────────────────────────────────────────────────────────────────────────
111
106
 
112
- export interface Section {
113
- id: string;
107
+ export interface SectionDef {
114
108
  from_top?: Size;
115
109
  from_bottom?: Size;
116
- until?: { section: string };
117
- anchor_regex?: string;
110
+ until?: string; // section id OR regex (starts with ^)
111
+ anchor?: string;
118
112
  anchor_flags?: string;
119
113
  anchor_last?: boolean;
120
114
  anchor_context?: {
121
115
  prev?: string;
122
- prev_flags?: string;
123
116
  next?: string;
117
+ prev_flags?: string;
124
118
  next_flags?: string;
125
119
  };
126
120
  lines?: number;
@@ -128,63 +122,13 @@ export interface Section {
128
122
  until_regex_flags?: string;
129
123
  }
130
124
 
131
- /** v1 condition — flat SectionRegex object. Also used in control_bar.wait_for. */
132
- export interface SectionRegex {
133
- section?: string;
134
- regex?: string;
135
- flags?: string;
136
- cursor_row_min?: number;
137
- cursor_row_max?: number;
138
- cursor_col_min?: number;
139
- cursor_col_max?: number;
140
- /** v1 delta detection (cursor_above_lines + changed). */
141
- cursor_above_lines?: number;
142
- /** Required when cursor_above_lines is set. */
143
- changed?: true;
144
- }
145
-
146
- export interface ModalButtonsRule {
147
- section?: string;
148
- pattern?: string;
149
- flags?: string;
150
- patterns?: Array<{ pattern: string; flags?: string }>;
151
- key_for_index: string;
152
- min_count?: number;
153
- continuation_lines?: boolean;
154
- }
155
-
156
125
  // ─────────────────────────────────────────────────────────────────────────────
157
- // v3 types (current)
126
+ // Conditions (shared with fsm-types.ts FsmCondition leaves)
158
127
  // ─────────────────────────────────────────────────────────────────────────────
159
128
 
160
- /**
161
- * v3 section definition — keyed by id in `sections{}` object.
162
- * `until` is EITHER a section-id string (no `^` prefix) OR a regex string (starts with `^`).
163
- */
164
- export interface SectionDef {
165
- // positional
166
- from_top?: Size;
167
- from_bottom?: Size;
168
- until?: string; // section id OR regex (starts with ^)
169
- // anchor-based
170
- anchor?: string; // regex (replaces anchor_regex)
171
- anchor_flags?: string;
172
- anchor_last?: boolean;
173
- anchor_context?: {
174
- prev?: string;
175
- next?: string;
176
- prev_flags?: string;
177
- next_flags?: string;
178
- };
179
- lines?: number;
180
- until_regex?: string; // kept for anchor-based extension
181
- until_regex_flags?: string;
182
- }
183
-
184
- /** v3 regex condition against a section (or full screen). */
185
129
  export interface RegexCondition {
186
130
  section?: string;
187
- matches: string; // regex string
131
+ matches: string;
188
132
  flags?: string;
189
133
  cursor_row_min?: number;
190
134
  cursor_row_max?: number;
@@ -192,28 +136,26 @@ export interface RegexCondition {
192
136
  cursor_col_max?: number;
193
137
  }
194
138
 
195
- /** v3 delta condition: N lines above cursor changed vs prevLines.
196
- * changed:false + stable_ms: region must be stable for at least N ms. */
197
139
  export interface ChangedCondition {
198
140
  cursor_above: number;
199
141
  changed: boolean;
200
- /** Only for changed:false — region must have been stable for this many ms. */
201
142
  stable_ms?: number;
202
143
  }
203
144
 
204
- /** v3 AND composite. */
205
145
  export interface AllCondition {
206
146
  all: Condition[];
207
147
  }
208
148
 
209
- /** v3 OR composite. */
210
149
  export interface AnyCondition {
211
150
  any: Condition[];
212
151
  }
213
152
 
214
153
  export type Condition = RegexCondition | ChangedCondition | AllCondition | AnyCondition;
215
154
 
216
- /** v3 extract_title config. */
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ // Extraction helpers
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+
217
159
  export interface ExtractTitle {
218
160
  section?: string;
219
161
  regex?: string;
@@ -221,7 +163,6 @@ export interface ExtractTitle {
221
163
  first_line?: true;
222
164
  }
223
165
 
224
- /** v3 extract_buttons config. */
225
166
  export interface ExtractButtons {
226
167
  section?: string;
227
168
  pattern: string;
@@ -230,136 +171,3 @@ export interface ExtractButtons {
230
171
  min_count?: number;
231
172
  continuation_lines?: boolean;
232
173
  }
233
-
234
- /** v3 state. */
235
- export interface SpecStateV3 {
236
- id: string;
237
- label: string;
238
- when: AllCondition | AnyCondition;
239
- extract?: {
240
- title?: ExtractTitle;
241
- buttons?: ExtractButtons;
242
- };
243
- }
244
-
245
- // ─────────────────────────────────────────────────────────────────────────────
246
- // CliSpec — v3 (the canonical runtime type after loading)
247
- // ─────────────────────────────────────────────────────────────────────────────
248
-
249
- export interface CliSpec {
250
- $schema: 'adhdev:cli/spec@3';
251
- id: string;
252
- name: string;
253
- binary: string;
254
- spawn_args?: string[];
255
- env?: Record<string, string>;
256
- cli_version_range?: string;
257
- send_message: {
258
- submit_key: string;
259
- delay_ms_before_submit?: number;
260
- delay_ms_per_char?: number;
261
- };
262
- sections: Record<string, SectionDef>;
263
- states: SpecStateV3[];
264
- default_state: string;
265
- control_bar?: Control[];
266
- notifications?: NotificationRule[];
267
- delegate?: DelegateTrigger[];
268
- native_history?: NativeHistoryConfig;
269
- requiresFinalAssistantBeforeIdle?: boolean;
270
- /**
271
- * Per-spec timing knobs. Replaces v1 `debounce`.
272
- * Also accessible via the legacy `debounce` alias in the driver for
273
- * backward compat with older callers.
274
- */
275
- timing?: {
276
- busy_hold_ms?: number;
277
- idle_hold_ms?: number;
278
- startup_grace_ms?: number;
279
- /** Suppress idle downshift while the screen is actively changing.
280
- * When set, any PTY frame that changes the screen resets a timer;
281
- * idle transitions (busy_hold expiry and completion_idle_after) are
282
- * blocked until the screen has been stable for this many ms. */
283
- screen_active_hold_ms?: number;
284
- completion_marker?: {
285
- section?: string;
286
- matches: string;
287
- flags?: string;
288
- hold_ms: number;
289
- force_after_ms?: number;
290
- };
291
- };
292
- /**
293
- * Legacy alias: v1 callers (tests, tooling) may read `debounce`.
294
- * The loader populates both `timing` and `debounce` pointing at the
295
- * same object so callers that use either field see consistent values.
296
- * @deprecated Use `timing` instead.
297
- */
298
- debounce?: {
299
- busy_hold_ms?: number;
300
- idle_hold_ms?: number;
301
- startup_grace_ms?: number;
302
- screen_active_hold_ms?: number;
303
- /**
304
- * Legacy field name for completion_marker. Populated by the loader
305
- * from `timing.completion_marker` for backward compat.
306
- */
307
- completion_idle_after?: {
308
- section?: string;
309
- regex: string;
310
- flags?: string;
311
- hold_ms: number;
312
- force_after_ms?: number;
313
- };
314
- };
315
- }
316
-
317
- // ─────────────────────────────────────────────────────────────────────────────
318
- // v1 CliSpec raw shape (for migration only — not exported as the primary type)
319
- // ─────────────────────────────────────────────────────────────────────────────
320
-
321
- /** Raw v1 spec shape as loaded from JSON. Used only in migrateV1toV3. */
322
- export interface RawCliSpecV1 {
323
- $schema: 'adhdev:cli/spec@1';
324
- id: string;
325
- name: string;
326
- binary: string;
327
- spawn_args?: string[];
328
- env?: Record<string, string>;
329
- cli_version_range?: string;
330
- send_message: {
331
- submit_key: string;
332
- delay_ms_before_submit?: number;
333
- delay_ms_per_char?: number;
334
- };
335
- layout: { sections: Section[] };
336
- states: Array<{
337
- id: string;
338
- label: string;
339
- when: SectionRegex;
340
- extract_title?: SectionRegex;
341
- modal_buttons?: ModalButtonsRule;
342
- }>;
343
- default_state: string;
344
- control_bar?: Control[];
345
- notifications?: NotificationRule[];
346
- delegate?: DelegateTrigger[];
347
- native_history?: NativeHistoryConfig;
348
- requiresFinalAssistantBeforeIdle?: boolean;
349
- debounce?: {
350
- busy_hold_ms?: number;
351
- idle_hold_ms?: number;
352
- startup_grace_ms?: number;
353
- completion_idle_after?: {
354
- section?: string;
355
- regex: string;
356
- flags?: string;
357
- hold_ms: number;
358
- force_after_ms?: number;
359
- };
360
- };
361
- }
362
-
363
- // Keep SpecState as alias for backward compat with cli-adapter.ts and other code
364
- // that imports SpecState from types.ts. In v3 we use SpecStateV3.
365
- export type SpecState = SpecStateV3;
@@ -1,287 +0,0 @@
1
- import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
2
- import { type SpecEvaluation, type TraceEntry } from './evaluator.js';
3
- import type { CliSpec } from './types.js';
4
- export type DashboardEvent = {
5
- kind: 'pty_data';
6
- chunk: string;
7
- } | {
8
- kind: 'state_changed';
9
- state: {
10
- id: string;
11
- label: string;
12
- title: string | null;
13
- };
14
- modal: {
15
- title: string | null;
16
- buttons: {
17
- index: number;
18
- label: string;
19
- }[];
20
- } | null;
21
- controls: {
22
- id: string;
23
- label: string;
24
- action_type: string;
25
- }[];
26
- } | {
27
- kind: 'notification';
28
- id: string;
29
- title: string;
30
- body: string;
31
- } | {
32
- kind: 'delegate';
33
- id: string;
34
- task: string;
35
- } | {
36
- kind: 'spec_trace';
37
- entries: TraceEntry[];
38
- } | {
39
- kind: 'exit';
40
- exit_code: number;
41
- } | {
42
- kind: 'spec_error';
43
- errors: string[];
44
- };
45
- export type DashboardCommand = {
46
- kind: 'send_message';
47
- text: string;
48
- } | {
49
- kind: 'pty_write';
50
- data: string;
51
- } | {
52
- kind: 'click_control';
53
- control_id: string;
54
- payload?: unknown;
55
- } | {
56
- kind: 'click_modal_button';
57
- index: number;
58
- } | {
59
- kind: 'attach_image';
60
- blob: string;
61
- mime: string;
62
- } | {
63
- kind: 'resize';
64
- cols: number;
65
- rows: number;
66
- } | {
67
- kind: 'cancel';
68
- } | {
69
- kind: 'shutdown';
70
- };
71
- /** One state-history entry — the union of fields produced by the v3 SpecDriver
72
- * (debounce-based) and the v4 FsmDriver (transition-based). The cli-adapter
73
- * and debug panel read this shape from either driver. */
74
- export interface DriverHistoryEntry {
75
- stateId: string;
76
- label: string;
77
- at: number;
78
- durationMs: number;
79
- reason: string;
80
- matchedStateId?: string;
81
- matchedRules?: string[];
82
- debounceKind?: string;
83
- idleHoldMs?: number;
84
- busyHoldMs?: number;
85
- /** v4: the transition that fired, e.g. "idle→busy". */
86
- via?: string;
87
- }
88
- /** The surface the cli-adapter drives. Implemented by both SpecDriver (v3,
89
- * debounce) and FsmDriver (v4, FSM). Lets the adapter hold either without
90
- * branching on the concrete type. */
91
- export interface ISpecDriver {
92
- subscribe(listener: (ev: DashboardEvent) => void): () => void;
93
- start(): void;
94
- dispatch(cmd: DashboardCommand): void;
95
- snapshot(): string;
96
- getCursorPosition(): {
97
- row: number;
98
- col: number;
99
- };
100
- getScreen(): string;
101
- getSpecPath(): string;
102
- shutdown(): void;
103
- getStateHistory(): ReadonlyArray<DriverHistoryEntry>;
104
- getSections(): Array<{
105
- id: string;
106
- text: string;
107
- }> | null;
108
- getLastBusyAt(): number;
109
- hasIdleHoldPending(): boolean;
110
- getCompletionIdleDebounceState(): {
111
- active: boolean;
112
- ageMs: number;
113
- holdMs: number;
114
- forceAfterMs: number;
115
- } | null;
116
- /** v4 only — present on FsmDriver. Returns the live transition table. */
117
- getFsmDebug?(): unknown;
118
- }
119
- export interface SpecDriverOpts {
120
- specPath: string;
121
- workingDir: string;
122
- extraEnv?: Record<string, string>;
123
- cols?: number;
124
- rows?: number;
125
- /** Set false to skip the spec.json fs.watch. */
126
- hotReload?: boolean;
127
- /** Set true to forward trace entries on every state_changed. */
128
- emitTrace?: boolean;
129
- /** Inject the daemon's PTY transport (typically SessionHostPtyTransportFactory). */
130
- transportFactory?: PtyTransportFactory;
131
- /**
132
- * Extra CLI args appended to spec.spawn_args. Used by the daemon to
133
- * pass per-launch arguments like `--session-id <uuid>` so the agent
134
- * uses the daemon's providerSessionId instead of generating its own.
135
- */
136
- extraCliArgs?: string[];
137
- }
138
- /**
139
- * Pick the effective delay (ms) to wait between writing the prompt body and
140
- * writing the submit_key. Exported for tests; production callers go through
141
- * actuallySendMessage. Floors to SUBMIT_DELAY_FLOOR_MS so specs that omit
142
- * delay_ms_before_submit (e.g. claude, antigravity) still get baseline
143
- * protection against the paste→submit race. Adds line-count bonus so
144
- * multi-line prompts get more settling time on TUIs that re-render per
145
- * embedded `\n`.
146
- */
147
- export declare function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number;
148
- export declare function matchesCompletionIdleRule(spec: CliSpec, ev: SpecEvaluation, screen: string): string | null;
149
- export declare function matchesCompletionIdleTargetState(spec: CliSpec, ev: SpecEvaluation, screen: string, cursor?: {
150
- row: number;
151
- col: number;
152
- }): boolean;
153
- export declare class SpecDriver implements ISpecDriver {
154
- private readonly opts;
155
- private spec;
156
- private adapter;
157
- private listeners;
158
- private currentStateId;
159
- /** Have we ever seen the spec's idle state *after* the startup grace
160
- * window? Until we do, the agent's startup banner may still be
161
- * painting and any send_message we forward to the PTY will be wiped
162
- * when the banner clears the screen. Queue + drain on first valid
163
- * idle. */
164
- private idleSeenOnce;
165
- private startedAtMs;
166
- private pendingSends;
167
- private currentEval;
168
- private pickerInProgress;
169
- private delegateTimers;
170
- /** Timestamp of the last time the evaluator returned busy. Used to debounce
171
- * the busy → idle transition (see reevaluate). */
172
- private lastBusyAt;
173
- /** The exact busy state object we last saw — held alongside lastBusyAt so
174
- * the hold can re-emit the same { id: 'busy', label, title } payload the
175
- * dashboard already learned about. currentEval can't fill this role
176
- * because the evaluator already moved past busy by the time the hold
177
- * kicks in. */
178
- private lastBusyState;
179
- /** Timestamp of the last time we entered a modal state (approval/picker or
180
- * any non-busy non-idle state). Used to suppress brief busy blips that
181
- * appear while the modal is still on screen — Claude Code streams body
182
- * text that transiently shows a spinner even while an approval modal is
183
- * visible, causing rapid approval→busy→approval flicker on the dashboard. */
184
- private lastModalAt;
185
- /** The modal state snapshot held across busy blips. */
186
- private lastModalState;
187
- /** Timestamp of when we last *exited* a modal state (approval/picker → idle
188
- * or directly via completion_idle_after). Used to suppress completion_idle_after
189
- * firings that were queued before the modal appeared and expire immediately
190
- * after the modal is dismissed — without this the agent appears idle even
191
- * though it is still generating. */
192
- private lastModalExitAt;
193
- private completionIdleFirstSeenAt;
194
- private completionIdleKey;
195
- /** Previous screen lines — passed to evaluate() for `changed` condition detection. */
196
- private prevScreenLines;
197
- /** Timestamp when idle was last committed (either direct or via idle_hold_ms).
198
- * Used to suppress immediate idle → busy re-entry from transient `changed`
199
- * condition blips (e.g. completion-marker counter "Completed for Xs" updating
200
- * every second, which triggers cursor_above:changed and bounces back to busy
201
- * right after an idle commit). */
202
- private lastIdleCommittedAt;
203
- /** Timestamp of the last PTY frame that changed the screen content.
204
- * Used by screen_active_hold_ms to suppress idle downshifts while
205
- * the terminal is still actively updating. */
206
- private lastScreenChangedAt;
207
- /** Per-cursor_above region last-changed timestamps for stable_ms tracking.
208
- * Key: cursor_above value. Value: last time that region changed. */
209
- private regionLastChangedAt;
210
- /** Timer that re-runs evaluate() once the hold window expires. Needed
211
- * because the PTY stops emitting once the agent finishes; without an
212
- * explicit wake-up there's nothing to trigger the busy → idle
213
- * downshift. */
214
- private busyExpiryTimer;
215
- /** Set true while reevaluate() is invoked from busyExpiryTimer callback. */
216
- private busyExpiryFired;
217
- /** Pending idle-commit timer. Armed when the evaluator first returns idle;
218
- * fires after idle_hold_ms if no non-idle reading has cancelled it. */
219
- private idleHoldTimer;
220
- /** State snapshot captured when the idle hold was armed — emitted on commit. */
221
- private pendingIdleState;
222
- private specWatcher;
223
- /** Ring buffer of committed state transitions (max 50). */
224
- private stateHistory;
225
- private prevStateAt;
226
- constructor(opts: SpecDriverOpts);
227
- /** Subscribe to outbound events. Returns an unsubscribe fn. */
228
- subscribe(listener: (ev: DashboardEvent) => void): () => void;
229
- start(): void;
230
- dispatch(cmd: DashboardCommand): void;
231
- snapshot(): string;
232
- getCursorPosition(): {
233
- row: number;
234
- col: number;
235
- };
236
- shutdown(): void;
237
- private cancelIdleHold;
238
- private pushHistory;
239
- getStateHistory(): ReadonlyArray<{
240
- stateId: string;
241
- label: string;
242
- at: number;
243
- durationMs: number;
244
- reason: string;
245
- matchedStateId?: string;
246
- matchedRules?: string[];
247
- debounceKind?: string;
248
- idleHoldMs?: number;
249
- busyHoldMs?: number;
250
- }>;
251
- getLastBusyAt(): number;
252
- hasIdleHoldPending(): boolean;
253
- getSpecPath(): string;
254
- getCompletionIdleDebounceState(): {
255
- active: boolean;
256
- ageMs: number;
257
- holdMs: number;
258
- forceAfterMs: number;
259
- } | null;
260
- getScreen(): string;
261
- getSections(): Array<{
262
- id: string;
263
- text: string;
264
- }> | null;
265
- private loadSpecOrThrow;
266
- private buildAdapterOpts;
267
- private armSpecWatcher;
268
- private emitInitialState;
269
- /** Re-arm the timer that wakes the driver up after BUSY_HOLD_MS so it
270
- * can decide whether to downshift to idle. Always uses the most recent
271
- * hold value so a spec hot-reload that shortens the hold takes effect
272
- * on the next busy entry. Safe to call repeatedly; only the last call
273
- * fires. */
274
- private scheduleBusyExpiry;
275
- private reevaluate;
276
- private armOrCancelDelegateTimers;
277
- private fireDelegate;
278
- private handleSendMessage;
279
- private actuallySendMessage;
280
- private handleClickControl;
281
- private handleClickModalButton;
282
- private handleAttachImage;
283
- private tryAdvancePicker;
284
- private handleExit;
285
- private emit;
286
- }
287
- export declare function guessExt(mime: string): string;
@@ -1,15 +0,0 @@
1
- import type { CliSpec } from './types.js';
2
- export interface SpecLoadResult {
3
- ok: true;
4
- spec: CliSpec;
5
- sourcePath: string;
6
- }
7
- export interface SpecLoadError {
8
- ok: false;
9
- errors: string[];
10
- sourcePath: string;
11
- }
12
- export declare function loadSpec(sourcePath: string): SpecLoadResult | SpecLoadError;
13
- export declare function migrateV1toV3(raw: any): any;
14
- /** Convenience: look up a provider's spec.json next to its provider dir. */
15
- export declare function resolveSpecPath(providerDir: string): string;