@adhdev/daemon-core 0.9.82-rc.160 → 0.9.82-rc.162

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.
Files changed (63) hide show
  1. package/dist/cli-adapter-types.d.ts +14 -1
  2. package/dist/commands/mesh-coordinator.d.ts +72 -1
  3. package/dist/config/chat-history.d.ts +2 -0
  4. package/dist/config/mesh-config.d.ts +3 -0
  5. package/dist/index.d.ts +11 -0
  6. package/dist/index.js +4924 -1411
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4976 -1476
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/mesh/coordinator-prompt.d.ts +30 -0
  11. package/dist/mesh/coordinator-registry.d.ts +35 -1
  12. package/dist/providers/cli-provider-instance.d.ts +1 -1
  13. package/dist/providers/contracts.d.ts +48 -0
  14. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +1 -1
  15. package/dist/providers/native-history/claude-cli-transcript.d.ts +1 -1
  16. package/dist/providers/native-history/codex-cli-transcript.d.ts +1 -1
  17. package/dist/providers/native-history/dispatcher.d.ts +24 -0
  18. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  19. package/dist/providers/native-history/index.d.ts +2 -0
  20. package/dist/providers/spec/adapter.d.ts +56 -0
  21. package/dist/providers/spec/cli-adapter.d.ts +76 -0
  22. package/dist/providers/spec/driver.d.ts +148 -0
  23. package/dist/providers/spec/evaluator.d.ts +47 -0
  24. package/dist/providers/spec/loader.d.ts +14 -0
  25. package/dist/providers/spec/native-history-executor.d.ts +39 -0
  26. package/dist/providers/spec/route.d.ts +4 -0
  27. package/dist/providers/spec/schema.gen.d.ts +507 -0
  28. package/dist/providers/spec/types.d.ts +211 -0
  29. package/dist/repo-mesh-types.d.ts +33 -1
  30. package/dist/sessions/registry.d.ts +3 -0
  31. package/package.json +2 -1
  32. package/src/cli-adapter-types.ts +15 -1
  33. package/src/commands/chat-commands.ts +150 -12
  34. package/src/commands/cli-manager.ts +11 -0
  35. package/src/commands/mesh-coordinator.ts +235 -1
  36. package/src/commands/router.ts +238 -50
  37. package/src/config/chat-history.ts +11 -3
  38. package/src/config/mesh-config.ts +16 -1
  39. package/src/index.ts +19 -0
  40. package/src/mesh/coordinator-prompt.ts +164 -8
  41. package/src/mesh/coordinator-registry.ts +50 -4
  42. package/src/providers/cli-provider-instance.ts +8 -3
  43. package/src/providers/contracts.ts +53 -0
  44. package/src/providers/native-history/antigravity-cli-transcript.ts +2 -2
  45. package/src/providers/native-history/claude-cli-transcript.ts +1 -1
  46. package/src/providers/native-history/codex-cli-transcript.ts +1 -1
  47. package/src/providers/native-history/dispatcher.ts +227 -0
  48. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  49. package/src/providers/native-history/index.ts +7 -0
  50. package/src/providers/provider-loader.ts +126 -3
  51. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +13 -0
  52. package/src/providers/spec/adapter.ts +168 -0
  53. package/src/providers/spec/cli-adapter.ts +318 -0
  54. package/src/providers/spec/driver.ts +498 -0
  55. package/src/providers/spec/evaluator.ts +268 -0
  56. package/src/providers/spec/loader.ts +130 -0
  57. package/src/providers/spec/native-history-executor.ts +612 -0
  58. package/src/providers/spec/route.ts +51 -0
  59. package/src/providers/spec/schema.gen.ts +507 -0
  60. package/src/providers/spec/schema.json +210 -0
  61. package/src/providers/spec/types.ts +230 -0
  62. package/src/repo-mesh-types.ts +33 -1
  63. package/src/sessions/registry.ts +3 -0
@@ -8,6 +8,17 @@
8
8
  * 3. How to orchestrate work across nodes
9
9
  *
10
10
  * The prompt is generated dynamically from the current mesh state.
11
+ *
12
+ * User customization:
13
+ * ~/.adhdev/coordinator-prompts/<cliType>.md — full override
14
+ * ~/.adhdev/coordinator-prompts/<cliType>.append.md — appended to default
15
+ * ~/.adhdev/coordinator-prompts/default.md — full override (any CLI)
16
+ * ~/.adhdev/coordinator-prompts/default.append.md — appended to default (any CLI)
17
+ *
18
+ * CLI-specific files take precedence over default.* files. The override file
19
+ * still gets the node/policy facts substituted via the same {{placeholders}}
20
+ * the daemon understands; an override that doesn't reference them just gets
21
+ * a static prompt, which is also fine.
11
22
  */
12
23
  import type { LocalMeshEntry, RepoMeshStatus } from '../repo-mesh-types.js';
13
24
  export interface CoordinatorPromptContext {
@@ -16,4 +27,23 @@ export interface CoordinatorPromptContext {
16
27
  userInstruction?: string;
17
28
  coordinatorCliType?: string;
18
29
  }
30
+ /**
31
+ * Compose the final coordinator prompt from four layers, in this precedence:
32
+ *
33
+ * 1. Per-launch `extraSystemPrompt` (always appended, as "## Additional
34
+ * Context"). Never wins as a base — it's launch-scope context.
35
+ * 2. Mesh-level append (`mesh.coordinator.systemPromptAppend` or the legacy
36
+ * `systemPromptSuffix`). Stacks after whichever base won.
37
+ * 3. User-file append (`~/.adhdev/coordinator-prompts/<cli>.append.md` or
38
+ * `default.append.md`). Also stacks; same placeholder expansion as the
39
+ * override path.
40
+ * 4. Base prompt, picked in this order:
41
+ * a. `mesh.coordinator.systemPromptOverride` (mesh-level override)
42
+ * b. user-file override (`~/.adhdev/coordinator-prompts/<cli>.md` or
43
+ * `default.md`)
44
+ * c. daemon default (assembled from identity/nodes/policy/tools/…)
45
+ *
46
+ * That layering lets a user customize prompts at three increasing scopes
47
+ * (machine, mesh, single launch) without losing the daemon's stock rules.
48
+ */
19
49
  export declare function buildCoordinatorSystemPrompt(ctx: CoordinatorPromptContext): string;
@@ -12,12 +12,46 @@ export interface CoordinatorRegistryEntry {
12
12
  sessionId: string;
13
13
  workspace?: string;
14
14
  startedAt: number;
15
+ /** CLI type used to launch the coordinator (claude-cli / codex-cli / …). */
16
+ cliType?: string;
17
+ /** Final system prompt sent to the coordinator after all overrides, appends,
18
+ * and extraSystemPrompt have been applied. Surfaced via the session-info
19
+ * endpoint so users can audit exactly what prompt the agent saw. */
20
+ systemPrompt?: string;
21
+ /** Per-launch extraSystemPrompt the caller passed in, if any. Stored
22
+ * separately from `systemPrompt` so the UI can show what the user
23
+ * added vs. what the daemon's default template produced. */
24
+ extraSystemPrompt?: string;
25
+ /** How the prompt was actually injected (cli_arg / context_file / …). */
26
+ injection?: {
27
+ mode: string;
28
+ target?: string;
29
+ };
30
+ /** Path of the MCP config file the daemon wrote for this session. */
31
+ mcpConfigPath?: string;
15
32
  }
16
33
  /** Load persisted coordinator registry from disk into in-memory map. Called once on daemon boot. */
17
34
  export declare function loadMeshCoordinatorRegistry(): void;
18
35
  /** Register a coordinator session. Persists to disk immediately. */
19
36
  export declare function registerMeshCoordinator(entry: CoordinatorRegistryEntry): void;
20
- /** Remove a coordinator session by sessionId. Persists to disk. */
37
+ /** Remove a coordinator session by sessionId. Persists to disk.
38
+ *
39
+ * Also best-effort strips any context_file wrapper block we wrote into
40
+ * the workspace at launch time (AGENTS.md / GEMINI.md), because those
41
+ * files are auto-loaded by their CLIs on every subsequent launch — and
42
+ * a wrapper block surviving the coordinator session would silently
43
+ * inject the coordinator system prompt into ordinary non-coordinator
44
+ * sessions in the same workspace, which is exactly the bug we're
45
+ * fixing here.
46
+ *
47
+ * Stripping rules:
48
+ * - Look for the start sentinel anywhere in the file; if absent,
49
+ * leave the file alone (user-authored content).
50
+ * - If the wrapper block was the only thing in the file, delete the
51
+ * file outright so we don't leave behind an empty AGENTS.md.
52
+ * - Otherwise drop only the block between the sentinels and trim a
53
+ * leading/trailing blank line so we don't leave dangling separators.
54
+ */
21
55
  export declare function unregisterMeshCoordinator(sessionId: string): void;
22
56
  /** Look up a coordinator entry by session ID. Returns undefined if not registered. */
23
57
  export declare function getCoordinatorForSession(sessionId: string): CoordinatorRegistryEntry | undefined;
@@ -94,7 +94,7 @@ export declare class CliProviderInstance implements ProviderInstance {
94
94
  setPresentationMode(mode: 'terminal' | 'chat'): void;
95
95
  getPresentationMode(): 'terminal' | 'chat';
96
96
  getHotChatSessionState(): HotChatSessionState;
97
- getSessionModalState(): SessionModalState;
97
+ getSessionModalState(sessionId?: string): SessionModalState;
98
98
  updateSettings(newSettings: Record<string, any>): void;
99
99
  onEvent(event: string, data?: any): void;
100
100
  recordAcknowledgedUserInput(input: InputEnvelope | string): void;
@@ -305,7 +305,55 @@ export interface ProviderMeshCoordinatorConfig {
305
305
  /** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{workspace}}, {{serverName}}. */
306
306
  template?: string;
307
307
  };
308
+ /**
309
+ * How the coordinator system prompt reaches the launched CLI. Replaces the
310
+ * old hard-coded `if (cliType === 'claude-cli') push --append-system-prompt`
311
+ * branches in router.ts: a new CLI now ships its injection rule in its
312
+ * provider.v1.json, no daemon code change needed. Users can override the
313
+ * rendered prompt or the injection mechanism per-provider; if omitted, no
314
+ * system prompt is injected (safe default — won't crash spawn with a flag
315
+ * the CLI doesn't recognize).
316
+ */
317
+ systemPromptInjection?: MeshCoordinatorSystemPromptInjection;
308
318
  }
319
+ /**
320
+ * Declarative description of how a CLI accepts a session-scoped system prompt.
321
+ *
322
+ * Modes:
323
+ * - cli_arg → push `flag` + prompt onto spawn args (Claude)
324
+ * - config_override → push `flag` + a templated key=value config override (Codex)
325
+ * - context_file → write prompt into a workspace markdown the CLI
326
+ * auto-loads as project context (Gemini, Antigravity)
327
+ * - env_var → expose prompt to the spawned process as $name (Hermes)
328
+ *
329
+ * The prompt text is templated with `{prompt}` (raw) or `{prompt_json}`
330
+ * (JSON-encoded for embedding inside config-override strings).
331
+ */
332
+ export type MeshCoordinatorSystemPromptInjection = {
333
+ mode: 'cli_arg';
334
+ /** Spawn-args flag, e.g. '--append-system-prompt'. The prompt becomes the next argv. */
335
+ flag: string;
336
+ } | {
337
+ mode: 'config_override';
338
+ /** Spawn-args flag, e.g. '-c'. Followed by `template` with placeholders rendered. */
339
+ flag: string;
340
+ /** Template using {prompt} or {prompt_json}, e.g. 'developer_instructions={prompt_json}'. */
341
+ template: string;
342
+ } | {
343
+ mode: 'context_file';
344
+ /** Workspace-relative file path the CLI auto-loads, e.g. 'AGENTS.md' or 'GEMINI.md'. */
345
+ path: string;
346
+ /**
347
+ * Optional wrapper around the prompt. Use `{prompt}` placeholder. Existing
348
+ * wrapper-delimited blocks are replaced rather than duplicated, so re-launching
349
+ * a coordinator doesn't pile up copies. If omitted, the prompt is appended raw.
350
+ */
351
+ wrapper?: string;
352
+ } | {
353
+ mode: 'env_var';
354
+ /** Env-var name, e.g. 'HERMES_EPHEMERAL_SYSTEM_PROMPT'. */
355
+ name: string;
356
+ };
309
357
  export interface ProviderCompatibilityEntry {
310
358
  ideVersion: string;
311
359
  scriptDir: string;
@@ -86,7 +86,7 @@ export interface NativeHistorySessionMeta {
86
86
  *
87
87
  * Returns `null` when the file is missing, empty, or yields no parseable messages.
88
88
  */
89
- export declare function readSession(sessionPath: string, sessionId?: string, workspace?: string): Promise<NativeHistorySession | null>;
89
+ export declare function readSession(sessionPath: string, sessionId?: string, workspace?: string): NativeHistorySession | null;
90
90
  /**
91
91
  * List all Antigravity CLI sessions found across all watchPath locations.
92
92
  *
@@ -56,7 +56,7 @@ export interface NativeHistorySessionMeta {
56
56
  * `sessionPath` is the absolute path to a `<uuid>.jsonl` file.
57
57
  * Returns `null` when the file is missing, empty, or yields no parseable messages.
58
58
  */
59
- export declare function readSession(sessionPath: string): Promise<NativeHistorySession | null>;
59
+ export declare function readSession(sessionPath: string): NativeHistorySession | null;
60
60
  /**
61
61
  * List all Claude Code sessions under the given glob-style watchPath base dir.
62
62
  *
@@ -60,7 +60,7 @@ export interface NativeHistorySessionMeta {
60
60
  * under ~/.codex/sessions/.
61
61
  * Returns `null` when the file is missing, empty, or yields no parseable messages.
62
62
  */
63
- export declare function readSession(sessionPath: string): Promise<NativeHistorySession | null>;
63
+ export declare function readSession(sessionPath: string): NativeHistorySession | null;
64
64
  /**
65
65
  * List all Codex CLI sessions under the given watchPath base dir.
66
66
  *
@@ -0,0 +1,24 @@
1
+ export type ReaderId = 'claude-cli' | 'codex-cli' | 'antigravity-cli' | 'hermes-cli';
2
+ export interface NativeHistoryInput {
3
+ agentType?: string;
4
+ sessionId?: string;
5
+ providerSessionId?: string;
6
+ historySessionId?: string;
7
+ workspace?: string;
8
+ format?: string;
9
+ watchPath?: string;
10
+ args?: Record<string, unknown>;
11
+ }
12
+ export interface NativeHistoryResult {
13
+ messages: Array<{
14
+ role: string;
15
+ content: string;
16
+ receivedAt?: number;
17
+ kind?: string;
18
+ }>;
19
+ providerSessionId?: string;
20
+ sourcePath: string;
21
+ sourceMtimeMs: number;
22
+ nativeHistoryCoverage?: 'full' | 'partial' | 'best-effort';
23
+ }
24
+ export declare function createNativeHistoryDispatcher(reader: ReaderId): (input: NativeHistoryInput) => NativeHistoryResult | null;
@@ -0,0 +1,30 @@
1
+ export interface NativeHistoryMessage {
2
+ id: string;
3
+ role: 'user' | 'assistant' | 'system';
4
+ content: string;
5
+ receivedAt: number;
6
+ kind?: string;
7
+ }
8
+ export interface NativeHistorySession {
9
+ messages: NativeHistoryMessage[];
10
+ providerSessionId: string;
11
+ source: 'provider-native';
12
+ sourcePath: string;
13
+ sourceMtimeMs: number;
14
+ nativeHistoryCoverage: 'full';
15
+ workspace?: string;
16
+ }
17
+ export interface NativeHistorySessionMeta {
18
+ historySessionId: string;
19
+ sessionId: string;
20
+ sourcePath: string;
21
+ sourceMtimeMs: number;
22
+ messageCount: number;
23
+ firstMessageAt: number;
24
+ lastMessageAt: number;
25
+ sessionTitle?: string;
26
+ preview?: string;
27
+ workspace?: string;
28
+ }
29
+ export declare function readSession(sessionPath: string): NativeHistorySession | null;
30
+ export declare function listSessions(_watchPath: string): Promise<NativeHistorySessionMeta[]>;
@@ -9,3 +9,5 @@
9
9
  export { readSession as readClaudeCliSession, listSessions as listClaudeCliSessions, } from './claude-cli-transcript.js';
10
10
  export { readSession as readCodexCliSession, listSessions as listCodexCliSessions, } from './codex-cli-transcript.js';
11
11
  export { readSession as readAntigravityCliSession, listSessions as listAntigravityCliSessions, } from './antigravity-cli-transcript.js';
12
+ export { readSession as readHermesCliSession, listSessions as listHermesCliSessions, } from './hermes-cli-transcript.js';
13
+ export { createNativeHistoryDispatcher, type ReaderId } from './dispatcher.js';
@@ -0,0 +1,56 @@
1
+ import { type PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
2
+ export interface TerminalAdapterOpts {
3
+ binary: string;
4
+ args?: string[];
5
+ cwd: string;
6
+ env?: Record<string, string>;
7
+ cols?: number;
8
+ rows?: number;
9
+ /** Coalesce screen snapshots: emit on_screen_changed at most this often. */
10
+ screenChangeDebounceMs?: number;
11
+ /** tick() period. 0 disables ticks. */
12
+ tickIntervalMs?: number;
13
+ /**
14
+ * Optional PTY transport factory. When the daemon supplies a
15
+ * SessionHostPtyTransportFactory (standalone runs the PTY inside the
16
+ * session-host so the runtime/<sid>/snapshot endpoint can serve it),
17
+ * forward it here. Without it the PTY spawns locally inside the
18
+ * daemon process and the dashboard's terminal pane reports
19
+ * "Runtime terminal unavailable: Unknown session".
20
+ */
21
+ transportFactory?: PtyTransportFactory;
22
+ }
23
+ export interface TerminalAdapterHandlers {
24
+ init?(info: {
25
+ pid: number;
26
+ }): void;
27
+ on_pty_data?(chunk: string): void;
28
+ on_screen_changed?(snapshot: string): void;
29
+ tick?(): void;
30
+ on_exit?(info: {
31
+ exitCode: number;
32
+ }): void;
33
+ }
34
+ export declare class TerminalAdapter {
35
+ private readonly opts;
36
+ private readonly handlers;
37
+ private term;
38
+ private pty;
39
+ private factory;
40
+ private cols;
41
+ private rows;
42
+ private screenDebounceMs;
43
+ private tickIntervalMs;
44
+ private screenTimer;
45
+ private tickTimer;
46
+ private lastScreen;
47
+ constructor(opts: TerminalAdapterOpts, handlers: TerminalAdapterHandlers);
48
+ start(): void;
49
+ send_keys(s: string): void;
50
+ resize(cols: number, rows: number): void;
51
+ snapshot(): string;
52
+ kill(): void;
53
+ private onChunk;
54
+ private computeScreen;
55
+ private stopTimers;
56
+ }
@@ -0,0 +1,76 @@
1
+ import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
2
+ import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
3
+ export declare class SpecCliAdapter implements CliAdapter {
4
+ readonly cliType: string;
5
+ readonly cliName: string;
6
+ readonly workingDir: string;
7
+ private driver;
8
+ private spec;
9
+ private lastEvent;
10
+ private latestState;
11
+ private latestModal;
12
+ private statusCallback;
13
+ private ptyDataCallback;
14
+ private partialResponse;
15
+ private exited;
16
+ private spawned;
17
+ private providerSessionId;
18
+ /** Wall clock at the moment spawn() ran. Used as the cutoff for
19
+ * native-history file selection so a prior session's transcript
20
+ * can't leak into this session before the agent has written its
21
+ * own records. */
22
+ private spawnedAtMs;
23
+ /** Env vars the daemon set on the spawned child. Mesh coordinator
24
+ * points hermes at a per-coordinator HERMES_HOME so the dashboard's
25
+ * native-history reader needs that override to find the right
26
+ * state.db; without it the reader sees ~/.hermes/state.db which
27
+ * the coordinator-launched hermes never writes to. The choice to
28
+ * redirect HERMES_HOME is a workaround for an unresolved hermes
29
+ * upstream feature gap (see hermes-agent#23130 — runtime-supplied
30
+ * MCP config), so this routing keeps the dashboard honest until
31
+ * hermes ships a runtime MCP override. */
32
+ private spawnedEnv;
33
+ constructor(specPath: string, workingDir: string, cliArgs: string[], extraEnv: Record<string, string>, transportFactory?: PtyTransportFactory);
34
+ spawn(): Promise<void>;
35
+ sendMessage(text: string): Promise<void>;
36
+ getStatus(): CliAdapterStatus;
37
+ private maybeRefreshNativeHistory;
38
+ getScriptParsedStatus(): unknown;
39
+ getPartialResponse(): string;
40
+ shutdown(): void;
41
+ cancel(): void;
42
+ isProcessing(): boolean;
43
+ isReady(): boolean;
44
+ setOnStatusChange(cb: () => void): void;
45
+ setOnPtyData(cb: (data: string) => void): void;
46
+ writeRaw(data: string): void;
47
+ resize(cols: number, rows: number): void;
48
+ resolveModal(buttonIndex: number): void;
49
+ resolveAction(data: unknown): Promise<void>;
50
+ isApprovalRecentlyResolved(): boolean;
51
+ clearHistory(): void;
52
+ updateRuntimeSettings(): void;
53
+ setServerConn(): void;
54
+ /**
55
+ * Map an invokeScript(name, args) call onto a control_bar entry.
56
+ *
57
+ * scriptName is matched against control.id. The control's action.type
58
+ * drives the dispatch:
59
+ *
60
+ * send_keys → click_control (e.g. stop)
61
+ * open_picker → click_control then resolve when extract_choices
62
+ * surface; choice index comes from args.choiceIndex
63
+ * or args.choice (string label match), defaulting to 0
64
+ * attach_image → attach_image dispatch; expects args.blob (data url
65
+ * or base64) and args.mime
66
+ *
67
+ * Callers that pass an unknown control id get a { not_found } response.
68
+ * No control matched, no driver call — keeps the surface honest.
69
+ */
70
+ invokeScript(scriptName: string, args?: Record<string, unknown>): Promise<unknown>;
71
+ getDebugSnapshot(): unknown;
72
+ getRuntimeMetadata(): unknown;
73
+ updateRuntimeMeta(meta?: Record<string, unknown>): void;
74
+ refreshProviderDefinition(): void;
75
+ private handleEvent;
76
+ }
@@ -0,0 +1,148 @@
1
+ import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
2
+ import { type TraceEntry } from './evaluator.js';
3
+ export type DashboardEvent = {
4
+ kind: 'pty_data';
5
+ chunk: string;
6
+ } | {
7
+ kind: 'state_changed';
8
+ state: {
9
+ id: string;
10
+ label: string;
11
+ title: string | null;
12
+ };
13
+ modal: {
14
+ title: string | null;
15
+ buttons: {
16
+ index: number;
17
+ label: string;
18
+ }[];
19
+ } | null;
20
+ controls: {
21
+ id: string;
22
+ label: string;
23
+ action_type: string;
24
+ }[];
25
+ } | {
26
+ kind: 'notification';
27
+ id: string;
28
+ title: string;
29
+ body: string;
30
+ } | {
31
+ kind: 'delegate';
32
+ id: string;
33
+ task: string;
34
+ } | {
35
+ kind: 'spec_trace';
36
+ entries: TraceEntry[];
37
+ } | {
38
+ kind: 'exit';
39
+ exit_code: number;
40
+ } | {
41
+ kind: 'spec_error';
42
+ errors: string[];
43
+ };
44
+ export type DashboardCommand = {
45
+ kind: 'send_message';
46
+ text: string;
47
+ } | {
48
+ kind: 'pty_write';
49
+ data: string;
50
+ } | {
51
+ kind: 'click_control';
52
+ control_id: string;
53
+ payload?: unknown;
54
+ } | {
55
+ kind: 'click_modal_button';
56
+ index: number;
57
+ } | {
58
+ kind: 'attach_image';
59
+ blob: string;
60
+ mime: string;
61
+ } | {
62
+ kind: 'resize';
63
+ cols: number;
64
+ rows: number;
65
+ } | {
66
+ kind: 'cancel';
67
+ } | {
68
+ kind: 'shutdown';
69
+ };
70
+ export interface SpecDriverOpts {
71
+ specPath: string;
72
+ workingDir: string;
73
+ extraEnv?: Record<string, string>;
74
+ cols?: number;
75
+ rows?: number;
76
+ /** Set false to skip the spec.json fs.watch. */
77
+ hotReload?: boolean;
78
+ /** Set true to forward trace entries on every state_changed. */
79
+ emitTrace?: boolean;
80
+ /** Inject the daemon's PTY transport (typically SessionHostPtyTransportFactory). */
81
+ transportFactory?: PtyTransportFactory;
82
+ /**
83
+ * Extra CLI args appended to spec.spawn_args. Used by the daemon to
84
+ * pass per-launch arguments like `--session-id <uuid>` so the agent
85
+ * uses the daemon's providerSessionId instead of generating its own.
86
+ */
87
+ extraCliArgs?: string[];
88
+ }
89
+ export declare class SpecDriver {
90
+ private readonly opts;
91
+ private spec;
92
+ private adapter;
93
+ private listeners;
94
+ private currentStateId;
95
+ /** Have we ever seen the spec's idle state *after* the startup grace
96
+ * window? Until we do, the agent's startup banner may still be
97
+ * painting and any send_message we forward to the PTY will be wiped
98
+ * when the banner clears the screen. Queue + drain on first valid
99
+ * idle. */
100
+ private idleSeenOnce;
101
+ private startedAtMs;
102
+ private pendingSends;
103
+ private currentEval;
104
+ private pickerInProgress;
105
+ private delegateTimers;
106
+ /** Timestamp of the last time the evaluator returned busy. Used to debounce
107
+ * the busy → idle transition (see reevaluate). */
108
+ private lastBusyAt;
109
+ /** The exact busy state object we last saw — held alongside lastBusyAt so
110
+ * the hold can re-emit the same { id: 'busy', label, title } payload the
111
+ * dashboard already learned about. currentEval can't fill this role
112
+ * because the evaluator already moved past busy by the time the hold
113
+ * kicks in. */
114
+ private lastBusyState;
115
+ /** Timer that re-runs evaluate() once the hold window expires. Needed
116
+ * because the PTY stops emitting once the agent finishes; without an
117
+ * explicit wake-up there's nothing to trigger the busy → idle
118
+ * downshift. */
119
+ private busyExpiryTimer;
120
+ private specWatcher;
121
+ constructor(opts: SpecDriverOpts);
122
+ /** Subscribe to outbound events. Returns an unsubscribe fn. */
123
+ subscribe(listener: (ev: DashboardEvent) => void): () => void;
124
+ start(): void;
125
+ dispatch(cmd: DashboardCommand): void;
126
+ shutdown(): void;
127
+ private loadSpecOrThrow;
128
+ private buildAdapterOpts;
129
+ private armSpecWatcher;
130
+ private emitInitialState;
131
+ /** Re-arm the timer that wakes the driver up after BUSY_HOLD_MS so it
132
+ * can decide whether to downshift to idle. Always uses the most recent
133
+ * hold value so a spec hot-reload that shortens the hold takes effect
134
+ * on the next busy entry. Safe to call repeatedly; only the last call
135
+ * fires. */
136
+ private scheduleBusyExpiry;
137
+ private reevaluate;
138
+ private armOrCancelDelegateTimers;
139
+ private fireDelegate;
140
+ private handleSendMessage;
141
+ private actuallySendMessage;
142
+ private handleClickControl;
143
+ private handleClickModalButton;
144
+ private handleAttachImage;
145
+ private tryAdvancePicker;
146
+ private handleExit;
147
+ private emit;
148
+ }
@@ -0,0 +1,47 @@
1
+ import type { CliSpec } from './types.js';
2
+ export interface ResolvedSection {
3
+ id: string;
4
+ fromLine: number;
5
+ toLine: number;
6
+ text: string;
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
+ export interface TraceEntry {
31
+ kind: 'section' | 'state_match' | 'state_skip' | 'modal' | 'control' | 'notification' | 'delegate';
32
+ text: string;
33
+ }
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
+ export declare function evaluate(spec: CliSpec, screenText: string): SpecEvaluation;
@@ -0,0 +1,14 @@
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
+ /** Convenience: look up a provider's spec.json next to its provider dir. */
14
+ export declare function resolveSpecPath(providerDir: string): string;
@@ -0,0 +1,39 @@
1
+ import type { NativeHistoryConfig } from './types.js';
2
+ export interface NativeHistoryInput {
3
+ agentType?: string;
4
+ sessionId?: string;
5
+ providerSessionId?: string;
6
+ historySessionId?: string;
7
+ workspace?: string;
8
+ /** Daemon-side wall clock at the moment the session was registered.
9
+ * Native-history file lookups use this as the lower bound: any file
10
+ * whose mtime is before the current session started can't be from
11
+ * this session, so it's excluded from newest-recent matching. The
12
+ * caller (chat-history pipeline) populates this from the session
13
+ * registry; specs/executor never need to know how it's sourced. */
14
+ sessionStartedAtMs?: number;
15
+ /** Env overrides the daemon set on the spawned CLI. The mesh
16
+ * coordinator points hermes at a per-coordinator HERMES_HOME so
17
+ * the hermes process writes its state.db into a tmp directory
18
+ * instead of ~/.hermes. expandPath consults this map before
19
+ * process.env so the native-history reader follows the spawned
20
+ * child's view of HERMES_HOME / similar overrides; without it
21
+ * the reader would always look at ~/.hermes and miss every
22
+ * coordinator-session transcript. */
23
+ envOverrides?: Record<string, string>;
24
+ args?: Record<string, unknown>;
25
+ }
26
+ export interface NativeHistoryMessage {
27
+ role: 'user' | 'assistant' | 'system';
28
+ content: string;
29
+ receivedAt: number;
30
+ kind?: string;
31
+ }
32
+ export interface NativeHistoryResult {
33
+ messages: NativeHistoryMessage[];
34
+ providerSessionId?: string;
35
+ sourcePath: string;
36
+ sourceMtimeMs: number;
37
+ nativeHistoryCoverage?: 'full' | 'partial' | 'best-effort';
38
+ }
39
+ export declare function executeNativeHistory(cfg: NativeHistoryConfig, input: NativeHistoryInput): NativeHistoryResult | null;
@@ -0,0 +1,4 @@
1
+ import type { CliProviderModule } from '../../cli-adapters/provider-cli-adapter.js';
2
+ import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
3
+ import type { CliAdapter } from '../../cli-adapter-types.js';
4
+ export declare function createCliAdapter(provider: CliProviderModule, workingDir: string, cliArgs: string[], extraEnv: Record<string, string>, transportFactory?: PtyTransportFactory): CliAdapter;