@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,53 +1,14 @@
1
- import type { CliSpec, SectionDef, Condition, ExtractTitle, ExtractButtons } from './types.js';
1
+ import type { SectionDef, Condition, ExtractTitle, ExtractButtons } from './types.js';
2
2
  export interface ResolvedSection {
3
3
  id: string;
4
4
  fromLine: number;
5
5
  toLine: number;
6
6
  text: string;
7
7
  }
8
- export interface ModalSnapshot {
9
- title: string | null;
10
- buttons: {
11
- index: number;
12
- label: string;
13
- key: string;
14
- }[];
15
- }
16
- export interface VisibleControl {
17
- id: string;
18
- label: string;
19
- actionType: 'send_keys' | 'open_picker' | 'attach_image';
20
- }
21
- export interface FiredNotification {
22
- id: string;
23
- title: string;
24
- body: string;
25
- }
26
- export interface FiredDelegate {
27
- id: string;
28
- task: string;
29
- }
30
8
  export interface TraceEntry {
31
9
  kind: 'section' | 'state_match' | 'state_skip' | 'modal' | 'control' | 'notification' | 'delegate';
32
10
  text: string;
33
11
  }
34
- export interface SpecEvaluation {
35
- state: {
36
- id: string;
37
- label: string;
38
- title: string | null;
39
- };
40
- modal: ModalSnapshot | null;
41
- controls: VisibleControl[];
42
- notifications: FiredNotification[];
43
- delegates: FiredDelegate[];
44
- sections: ResolvedSection[];
45
- trace: TraceEntry[];
46
- }
47
- /**
48
- * Resolve v3 sections{} object into an ordered array of ResolvedSection.
49
- * Two-pass: first anchor/positional, then apply `until` cross-references.
50
- */
51
12
  export declare function resolveSections(sectionsObj: Record<string, SectionDef>, lines: string[]): ResolvedSection[];
52
13
  export declare function sectionText(sections: ResolvedSection[], sectionId: string | undefined, fullScreen: string): string;
53
14
  export declare function evaluateCondition(cond: Condition, sections: ResolvedSection[], fullScreen: string, cursor: {
@@ -60,11 +21,3 @@ export declare function extractButtonsFromRule(rule: ExtractButtons, hay: string
60
21
  label: string;
61
22
  key: string;
62
23
  }[];
63
- export declare function evaluate(spec: CliSpec, screenText: string,
64
- /** Optional cursor position (0-based row and col). */
65
- cursor?: {
66
- row: number;
67
- col: number;
68
- },
69
- /** Optional previous screen lines for `changed` condition detection. */
70
- prevLines?: string[]): SpecEvaluation;
@@ -1,5 +1,126 @@
1
+ import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
2
+ import { type TraceEntry } from './evaluator.js';
1
3
  import { type TransitionEval } from './fsm-evaluator.js';
2
- import { type DashboardEvent, type DashboardCommand, type SpecDriverOpts, type ISpecDriver, type DriverHistoryEntry } from './driver.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
+ export interface DriverHistoryEntry {
72
+ stateId: string;
73
+ label: string;
74
+ at: number;
75
+ durationMs: number;
76
+ reason: string;
77
+ matchedStateId?: string;
78
+ matchedRules?: string[];
79
+ debounceKind?: string;
80
+ idleHoldMs?: number;
81
+ busyHoldMs?: number;
82
+ via?: string;
83
+ }
84
+ export interface ISpecDriver {
85
+ subscribe(listener: (ev: DashboardEvent) => void): () => void;
86
+ start(): void;
87
+ dispatch(cmd: DashboardCommand): void;
88
+ snapshot(): string;
89
+ getCursorPosition(): {
90
+ row: number;
91
+ col: number;
92
+ };
93
+ getScreen(): string;
94
+ getSpecPath(): string;
95
+ shutdown(): void;
96
+ getStateHistory(): ReadonlyArray<DriverHistoryEntry>;
97
+ getSections(): Array<{
98
+ id: string;
99
+ text: string;
100
+ }> | null;
101
+ getLastBusyAt(): number;
102
+ hasIdleHoldPending(): boolean;
103
+ getCompletionIdleDebounceState(): {
104
+ active: boolean;
105
+ ageMs: number;
106
+ holdMs: number;
107
+ forceAfterMs: number;
108
+ } | null;
109
+ getFsmDebug?(): unknown;
110
+ }
111
+ export interface SpecDriverOpts {
112
+ specPath: string;
113
+ workingDir: string;
114
+ extraEnv?: Record<string, string>;
115
+ cols?: number;
116
+ rows?: number;
117
+ hotReload?: boolean;
118
+ emitTrace?: boolean;
119
+ transportFactory?: PtyTransportFactory;
120
+ extraCliArgs?: string[];
121
+ }
122
+ export declare function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number;
123
+ export declare function guessExt(mime: string): string;
3
124
  type HistoryEntry = DriverHistoryEntry;
4
125
  export declare class FsmDriver implements ISpecDriver {
5
126
  private readonly opts;
@@ -4,13 +4,19 @@ export interface SectionPattern {
4
4
  pattern: string;
5
5
  flags?: string;
6
6
  }
7
+ /** Inline wait_for shape used by open_picker control actions. */
8
+ export interface WaitForCondition {
9
+ section?: string;
10
+ regex?: string;
11
+ flags?: string;
12
+ }
7
13
  export type ControlAction = {
8
14
  type: 'send_keys';
9
15
  keys: string;
10
16
  } | {
11
17
  type: 'open_picker';
12
18
  trigger_keys: string;
13
- wait_for: SectionRegex;
19
+ wait_for: WaitForCondition;
14
20
  extract_choices: SectionPattern;
15
21
  submit_key: string;
16
22
  } | {
@@ -36,15 +42,6 @@ export interface DelegateTrigger {
36
42
  after_duration_ms?: number;
37
43
  task_template: string;
38
44
  }
39
- /**
40
- * Native history config — three modes, picked by which fields are present:
41
- *
42
- * 1. `reader`: built-in reader id (claude-cli / codex-cli / antigravity-cli / hermes-cli).
43
- *
44
- * 2. `source`: declarative source descriptor.
45
- *
46
- * 3. `override_path`: relative path to a provider-supplied reader module.
47
- */
48
45
  export interface NativeHistoryConfig {
49
46
  reader?: 'claude-cli' | 'codex-cli' | 'antigravity-cli' | 'hermes-cli';
50
47
  source?: NativeHistorySource;
@@ -78,56 +75,6 @@ export interface NativeHistoryMessageMap {
78
75
  timestamp_ms?: string;
79
76
  kind?: string;
80
77
  }
81
- export interface Section {
82
- id: string;
83
- from_top?: Size;
84
- from_bottom?: Size;
85
- until?: {
86
- section: string;
87
- };
88
- anchor_regex?: string;
89
- anchor_flags?: string;
90
- anchor_last?: boolean;
91
- anchor_context?: {
92
- prev?: string;
93
- prev_flags?: string;
94
- next?: string;
95
- next_flags?: string;
96
- };
97
- lines?: number;
98
- until_regex?: string;
99
- until_regex_flags?: string;
100
- }
101
- /** v1 condition — flat SectionRegex object. Also used in control_bar.wait_for. */
102
- export interface SectionRegex {
103
- section?: string;
104
- regex?: string;
105
- flags?: string;
106
- cursor_row_min?: number;
107
- cursor_row_max?: number;
108
- cursor_col_min?: number;
109
- cursor_col_max?: number;
110
- /** v1 delta detection (cursor_above_lines + changed). */
111
- cursor_above_lines?: number;
112
- /** Required when cursor_above_lines is set. */
113
- changed?: true;
114
- }
115
- export interface ModalButtonsRule {
116
- section?: string;
117
- pattern?: string;
118
- flags?: string;
119
- patterns?: Array<{
120
- pattern: string;
121
- flags?: string;
122
- }>;
123
- key_for_index: string;
124
- min_count?: number;
125
- continuation_lines?: boolean;
126
- }
127
- /**
128
- * v3 section definition — keyed by id in `sections{}` object.
129
- * `until` is EITHER a section-id string (no `^` prefix) OR a regex string (starts with `^`).
130
- */
131
78
  export interface SectionDef {
132
79
  from_top?: Size;
133
80
  from_bottom?: Size;
@@ -145,7 +92,6 @@ export interface SectionDef {
145
92
  until_regex?: string;
146
93
  until_regex_flags?: string;
147
94
  }
148
- /** v3 regex condition against a section (or full screen). */
149
95
  export interface RegexCondition {
150
96
  section?: string;
151
97
  matches: string;
@@ -155,31 +101,24 @@ export interface RegexCondition {
155
101
  cursor_col_min?: number;
156
102
  cursor_col_max?: number;
157
103
  }
158
- /** v3 delta condition: N lines above cursor changed vs prevLines.
159
- * changed:false + stable_ms: region must be stable for at least N ms. */
160
104
  export interface ChangedCondition {
161
105
  cursor_above: number;
162
106
  changed: boolean;
163
- /** Only for changed:false — region must have been stable for this many ms. */
164
107
  stable_ms?: number;
165
108
  }
166
- /** v3 AND composite. */
167
109
  export interface AllCondition {
168
110
  all: Condition[];
169
111
  }
170
- /** v3 OR composite. */
171
112
  export interface AnyCondition {
172
113
  any: Condition[];
173
114
  }
174
115
  export type Condition = RegexCondition | ChangedCondition | AllCondition | AnyCondition;
175
- /** v3 extract_title config. */
176
116
  export interface ExtractTitle {
177
117
  section?: string;
178
118
  regex?: string;
179
119
  flags?: string;
180
120
  first_line?: true;
181
121
  }
182
- /** v3 extract_buttons config. */
183
122
  export interface ExtractButtons {
184
123
  section?: string;
185
124
  pattern: string;
@@ -188,124 +127,3 @@ export interface ExtractButtons {
188
127
  min_count?: number;
189
128
  continuation_lines?: boolean;
190
129
  }
191
- /** v3 state. */
192
- export interface SpecStateV3 {
193
- id: string;
194
- label: string;
195
- when: AllCondition | AnyCondition;
196
- extract?: {
197
- title?: ExtractTitle;
198
- buttons?: ExtractButtons;
199
- };
200
- }
201
- export interface CliSpec {
202
- $schema: 'adhdev:cli/spec@3';
203
- id: string;
204
- name: string;
205
- binary: string;
206
- spawn_args?: string[];
207
- env?: Record<string, string>;
208
- cli_version_range?: string;
209
- send_message: {
210
- submit_key: string;
211
- delay_ms_before_submit?: number;
212
- delay_ms_per_char?: number;
213
- };
214
- sections: Record<string, SectionDef>;
215
- states: SpecStateV3[];
216
- default_state: string;
217
- control_bar?: Control[];
218
- notifications?: NotificationRule[];
219
- delegate?: DelegateTrigger[];
220
- native_history?: NativeHistoryConfig;
221
- requiresFinalAssistantBeforeIdle?: boolean;
222
- /**
223
- * Per-spec timing knobs. Replaces v1 `debounce`.
224
- * Also accessible via the legacy `debounce` alias in the driver for
225
- * backward compat with older callers.
226
- */
227
- timing?: {
228
- busy_hold_ms?: number;
229
- idle_hold_ms?: number;
230
- startup_grace_ms?: number;
231
- /** Suppress idle downshift while the screen is actively changing.
232
- * When set, any PTY frame that changes the screen resets a timer;
233
- * idle transitions (busy_hold expiry and completion_idle_after) are
234
- * blocked until the screen has been stable for this many ms. */
235
- screen_active_hold_ms?: number;
236
- completion_marker?: {
237
- section?: string;
238
- matches: string;
239
- flags?: string;
240
- hold_ms: number;
241
- force_after_ms?: number;
242
- };
243
- };
244
- /**
245
- * Legacy alias: v1 callers (tests, tooling) may read `debounce`.
246
- * The loader populates both `timing` and `debounce` pointing at the
247
- * same object so callers that use either field see consistent values.
248
- * @deprecated Use `timing` instead.
249
- */
250
- debounce?: {
251
- busy_hold_ms?: number;
252
- idle_hold_ms?: number;
253
- startup_grace_ms?: number;
254
- screen_active_hold_ms?: number;
255
- /**
256
- * Legacy field name for completion_marker. Populated by the loader
257
- * from `timing.completion_marker` for backward compat.
258
- */
259
- completion_idle_after?: {
260
- section?: string;
261
- regex: string;
262
- flags?: string;
263
- hold_ms: number;
264
- force_after_ms?: number;
265
- };
266
- };
267
- }
268
- /** Raw v1 spec shape as loaded from JSON. Used only in migrateV1toV3. */
269
- export interface RawCliSpecV1 {
270
- $schema: 'adhdev:cli/spec@1';
271
- id: string;
272
- name: string;
273
- binary: string;
274
- spawn_args?: string[];
275
- env?: Record<string, string>;
276
- cli_version_range?: string;
277
- send_message: {
278
- submit_key: string;
279
- delay_ms_before_submit?: number;
280
- delay_ms_per_char?: number;
281
- };
282
- layout: {
283
- sections: Section[];
284
- };
285
- states: Array<{
286
- id: string;
287
- label: string;
288
- when: SectionRegex;
289
- extract_title?: SectionRegex;
290
- modal_buttons?: ModalButtonsRule;
291
- }>;
292
- default_state: string;
293
- control_bar?: Control[];
294
- notifications?: NotificationRule[];
295
- delegate?: DelegateTrigger[];
296
- native_history?: NativeHistoryConfig;
297
- requiresFinalAssistantBeforeIdle?: boolean;
298
- debounce?: {
299
- busy_hold_ms?: number;
300
- idle_hold_ms?: number;
301
- startup_grace_ms?: number;
302
- completion_idle_after?: {
303
- section?: string;
304
- regex: string;
305
- flags?: string;
306
- hold_ms: number;
307
- force_after_ms?: number;
308
- };
309
- };
310
- }
311
- export type SpecState = SpecStateV3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.220",
3
+ "version": "0.9.82-rc.221",
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",
package/src/index.ts CHANGED
@@ -497,9 +497,7 @@ export {
497
497
  type IpcStatusPayload,
498
498
  } from './ipc/local-ipc-server.js';
499
499
 
500
- // ── CLI Spec (adhdev:cli/spec@1) ──
501
- export { evaluate as evaluateSpec, evaluate } from './providers/spec/evaluator.js';
502
- export { loadSpec, resolveSpecPath } from './providers/spec/loader.js';
500
+ // ── CLI Spec (adhdev:cli/spec@4) ──
503
501
  export { createNativeHistoryDispatcher } from './providers/native-history/index.js';
504
502
  export type { ReaderId } from './providers/native-history/index.js';
505
503
  export {
@@ -507,12 +505,15 @@ export {
507
505
  readAntigravityCliSession, readHermesCliSession,
508
506
  } from './providers/native-history/index.js';
509
507
  export type {
510
- CliSpec, SpecState, ControlAction, Control,
511
- NotificationRule, DelegateTrigger, Section,
508
+ ControlAction, Control,
509
+ NotificationRule, DelegateTrigger,
512
510
  } from './providers/spec/types.js';
513
- export type { SpecEvaluation, TraceEntry } from './providers/spec/evaluator.js';
514
- export { SpecDriver } from './providers/spec/driver.js';
515
- export type { DashboardEvent, DashboardCommand, SpecDriverOpts } from './providers/spec/driver.js';
511
+ export type { TraceEntry } from './providers/spec/evaluator.js';
512
+ export { evaluateFsm } from './providers/spec/fsm-evaluator.js';
513
+ export type { FsmClock } from './providers/spec/fsm-evaluator.js';
514
+ export { validateFsmSpec } from './providers/spec/fsm-loader.js';
515
+ export { FsmDriver } from './providers/spec/fsm-driver.js';
516
+ export type { DashboardEvent, DashboardCommand, SpecDriverOpts, ISpecDriver } from './providers/spec/fsm-driver.js';
516
517
  export { TerminalAdapter } from './providers/spec/adapter.js';
517
518
  export type { TerminalAdapterOpts, TerminalAdapterHandlers } from './providers/spec/adapter.js';
518
519
 
@@ -36,7 +36,6 @@ import {
36
36
  } from './external-sources.js';
37
37
  import type { ProviderSourceMode } from '../config/config.js';
38
38
  import type { ProviderSourceConfigSnapshot, ProviderUserDirSource } from '../config/provider-source-config.js';
39
- import { loadSpec } from './spec/loader.js';
40
39
  import { executeNativeHistory } from './spec/native-history-executor.js';
41
40
  import { createNativeHistoryDispatcher, type ReaderId } from './native-history/dispatcher.js';
42
41
 
@@ -1247,20 +1246,13 @@ export class ProviderLoader {
1247
1246
  // Hand the resolved spec path off to route.ts via a hidden field
1248
1247
  // so the routing layer doesn't have to repeat the candidate walk.
1249
1248
  (resolved as any)._resolvedSpecPath = specPath;
1250
- // Extract control_bar + native_history in a schema-agnostic way.
1251
- // v3 goes through loadSpec (validates/migrates); v4 (FSM) reads the
1252
- // header fields directly from JSON since loadSpec only knows v1/v3.
1249
+ // Extract control_bar + native_history directly from the JSON header.
1253
1250
  let specControls: any[] | undefined;
1254
1251
  let nh: any | undefined;
1255
1252
  try {
1256
1253
  const rawSpec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
1257
- if (rawSpec?.$schema === 'adhdev:cli/spec@4') {
1258
- specControls = rawSpec.control_bar;
1259
- nh = rawSpec.native_history;
1260
- } else {
1261
- const r = loadSpec(specPath);
1262
- if (r.ok) { specControls = r.spec.control_bar; nh = r.spec.native_history; }
1263
- }
1254
+ specControls = rawSpec.control_bar;
1255
+ nh = rawSpec.native_history;
1264
1256
  } catch { /* unreadable spec — leave controls/native unavailable */ }
1265
1257
  // Stub each control_bar entry as a provider.scripts.<id>. The
1266
1258
  // upstream invoke_provider_script gate checks that the script
@@ -18,12 +18,9 @@
18
18
  */
19
19
  'use strict';
20
20
 
21
- import { SpecDriver, type DashboardEvent, type ISpecDriver } from './driver.js';
22
- import { FsmDriver } from './fsm-driver.js';
21
+ import { FsmDriver, type DashboardEvent, type ISpecDriver } from './fsm-driver.js';
23
22
  import { executeNativeHistory } from './native-history-executor.js';
24
- import { loadSpec } from './loader.js';
25
23
  import * as fs from 'node:fs';
26
- import type { CliSpec } from './types.js';
27
24
  import type { NativeHistoryConfig, Control } from './types.js';
28
25
  import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
29
26
  import type { ChatMessage } from '../../types.js';
@@ -39,13 +36,6 @@ import {
39
36
  type InteractivePromptResponse,
40
37
  } from '../types/interactive-prompt.js';
41
38
 
42
- /** Peek at the spec's $schema to choose the driver. Cheap header read. */
43
- function detectV4Schema(specPath: string): boolean {
44
- try {
45
- const raw = JSON.parse(fs.readFileSync(specPath, 'utf8'));
46
- return raw?.$schema === 'adhdev:cli/spec@4';
47
- } catch { return false; }
48
- }
49
39
 
50
40
  function stripAnsi(text: string): string {
51
41
  // eslint-disable-next-line no-control-regex
@@ -113,30 +103,13 @@ export class SpecCliAdapter implements CliAdapter {
113
103
  extraEnv: Record<string, string>,
114
104
  transportFactory?: PtyTransportFactory,
115
105
  ) {
116
- // Detect the spec schema version up front. v4 (FSM) and v3 (debounce)
117
- // use different drivers behind the same ISpecDriver interface.
118
- const isV4 = detectV4Schema(specPath);
119
-
120
- if (isV4) {
121
- // v4 loader runs inside FsmDriver; read the common header fields
122
- // we need here directly from the parsed JSON.
123
- const raw = JSON.parse(fs.readFileSync(specPath, 'utf8'));
124
- this.spec = {
125
- id: raw.id,
126
- name: raw.name,
127
- control_bar: raw.control_bar,
128
- native_history: raw.native_history,
129
- };
130
- } else {
131
- const res = loadSpec(specPath);
132
- if (!res.ok) throw new Error(`spec invalid (${specPath}): ${res.errors.join('; ')}`);
133
- this.spec = {
134
- id: res.spec.id,
135
- name: res.spec.name,
136
- control_bar: res.spec.control_bar,
137
- native_history: res.spec.native_history,
138
- };
139
- }
106
+ const raw = JSON.parse(fs.readFileSync(specPath, 'utf8'));
107
+ this.spec = {
108
+ id: raw.id,
109
+ name: raw.name,
110
+ control_bar: raw.control_bar,
111
+ native_history: raw.native_history,
112
+ };
140
113
  this.cliType = this.spec.id;
141
114
  this.cliName = this.spec.name;
142
115
  this.workingDir = workingDir;
@@ -148,7 +121,7 @@ export class SpecCliAdapter implements CliAdapter {
148
121
  // so the agent uses the daemon's id, otherwise (claude case) the
149
122
  // agent generates its own id and the chat-history pipeline can't
150
123
  // pair the on-disk transcript with the live session.
151
- const driverOpts = {
124
+ this.driver = new FsmDriver({
152
125
  specPath,
153
126
  workingDir,
154
127
  extraEnv,
@@ -156,8 +129,7 @@ export class SpecCliAdapter implements CliAdapter {
156
129
  emitTrace: false,
157
130
  transportFactory,
158
131
  extraCliArgs: cliArgs,
159
- };
160
- this.driver = isV4 ? new FsmDriver(driverOpts) : new SpecDriver(driverOpts);
132
+ });
161
133
  this.driver.subscribe((ev) => this.handleEvent(ev));
162
134
  }
163
135