@adhdev/daemon-core 0.9.82-rc.355 → 0.9.82-rc.357
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter-types.d.ts +9 -0
- package/dist/index.js +177 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -76
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-coordinator.d.ts +1 -0
- package/dist/providers/spec/cli-adapter.d.ts +6 -0
- package/dist/providers/spec/evaluator.d.ts +1 -0
- package/dist/providers/spec/fsm-driver.d.ts +1 -0
- package/dist/providers/spec/fsm-types.d.ts +30 -0
- package/dist/providers/spec/types.d.ts +38 -0
- package/package.json +2 -2
- package/src/cli-adapter-types.ts +9 -0
- package/src/mesh/mesh-events-coordinator.ts +128 -63
- package/src/providers/cli-provider-instance.ts +46 -5
- package/src/providers/spec/cli-adapter.ts +61 -9
- package/src/providers/spec/evaluator.ts +14 -4
- package/src/providers/spec/fsm-driver.ts +31 -4
- package/src/providers/spec/fsm-types.ts +35 -0
- package/src/providers/spec/types.ts +32 -0
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import { evaluateFsm, type FsmClock, type TransitionEval, type FsmEvaluation } from './fsm-evaluator.js';
|
|
33
33
|
import {
|
|
34
34
|
type CliSpecV4, type FsmState, type FsmTransition,
|
|
35
|
-
initialState, stateById, statusForState, outgoingTransitions,
|
|
35
|
+
initialState, stateById, statusForState, modalKindForState, outgoingTransitions,
|
|
36
36
|
} from './fsm-types.js';
|
|
37
37
|
import { loadFsmSpec } from './fsm-loader.js';
|
|
38
38
|
import { applyPreLaunchTrust } from './pre-launch-trust.js';
|
|
@@ -44,7 +44,7 @@ import { LOG } from '../../logging/logger.js';
|
|
|
44
44
|
export type DashboardEvent =
|
|
45
45
|
| { kind: 'pty_data'; chunk: string }
|
|
46
46
|
| { kind: 'state_changed'; state: { id: string; label: string; title: string | null; status: 'idle' | 'generating' | 'approval' };
|
|
47
|
-
modal: { title: string | null; buttons: { index: number; label: string }[] } | null;
|
|
47
|
+
modal: { title: string | null; buttons: { index: number; label: string }[]; kind: 'approval' | 'picker' | 'confirm' | null } | null;
|
|
48
48
|
controls: { id: string; label: string; action_type: string }[] }
|
|
49
49
|
| { kind: 'notification'; id: string; title: string; body: string }
|
|
50
50
|
| { kind: 'delegate'; id: string; task: string }
|
|
@@ -221,7 +221,7 @@ export function guessExt(mime: string): string {
|
|
|
221
221
|
|
|
222
222
|
interface ModalSnapshot {
|
|
223
223
|
title: string | null;
|
|
224
|
-
buttons: { index: number; label: string; key: string }[];
|
|
224
|
+
buttons: { index: number; label: string; key: string; current: boolean }[];
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
interface VisibleControl {
|
|
@@ -694,7 +694,12 @@ export class FsmDriver implements ISpecDriver {
|
|
|
694
694
|
this.emit({
|
|
695
695
|
kind: 'state_changed',
|
|
696
696
|
state: next.state,
|
|
697
|
-
|
|
697
|
+
// kind is the SEMANTIC modal class (approval vs picker/confirm)
|
|
698
|
+
// derived from the FSM state, NOT from the parsed buttons — the
|
|
699
|
+
// status field already collapsed it to 'approval' so the modal is
|
|
700
|
+
// surfaced. The auto-approve worker needs the distinction back to
|
|
701
|
+
// avoid answering a /model picker on the user's behalf.
|
|
702
|
+
modal: next.modal ? { title: next.modal.title, buttons: next.modal.buttons.map(b => ({ index: b.index, label: b.label })), kind: modalKindForState(state) } : null,
|
|
698
703
|
controls: next.controls.map(c => ({ id: c.id, label: c.label, action_type: c.actionType })),
|
|
699
704
|
});
|
|
700
705
|
this.fireNotifications(state.id, title);
|
|
@@ -1079,6 +1084,28 @@ export class FsmDriver implements ISpecDriver {
|
|
|
1079
1084
|
if (!m) return;
|
|
1080
1085
|
const btn = m.buttons.find(b => b.index === index);
|
|
1081
1086
|
if (!btn) return;
|
|
1087
|
+
|
|
1088
|
+
const rule = stateById(this.spec, this.currentStateId)?.extract?.buttons;
|
|
1089
|
+
if (rule?.select_mode === 'arrow_keys') {
|
|
1090
|
+
// Cursor-list approval modal (claude-cli new TUI): number keys are
|
|
1091
|
+
// IGNORED — sending `btn.key` ("1\r") types a literal "1" into the
|
|
1092
|
+
// composer and the trailing CR submits it as a chat message. Drive
|
|
1093
|
+
// the cursor from its current row to the target row with arrows,
|
|
1094
|
+
// then confirm. The cursor opens on the first option, so when the
|
|
1095
|
+
// marker isn't detected we step down from row 1 (index - 1).
|
|
1096
|
+
const from = m.buttons.find(b => b.current)?.index ?? 1;
|
|
1097
|
+
const up = rule.cursor_keys?.up ?? '\x1b[A';
|
|
1098
|
+
const down = rule.cursor_keys?.down ?? '\x1b[B';
|
|
1099
|
+
const delta = btn.index - from;
|
|
1100
|
+
const step = delta >= 0 ? down : up;
|
|
1101
|
+
const nav = step.repeat(Math.abs(delta));
|
|
1102
|
+
// Confirm = key_for_index with the (now unused) {index} stripped:
|
|
1103
|
+
// `{index}\r` → `\r`.
|
|
1104
|
+
const confirm = (rule.key_for_index || '\r').replace(/\{index\}/g, '') || '\r';
|
|
1105
|
+
if (nav) this.adapter.send_keys(nav);
|
|
1106
|
+
this.adapter.send_keys(confirm);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1082
1109
|
this.adapter.send_keys(btn.key);
|
|
1083
1110
|
}
|
|
1084
1111
|
|
|
@@ -88,6 +88,27 @@ export interface FsmState {
|
|
|
88
88
|
/** Modal states (approval/picker) expose modal buttons in the UI and are
|
|
89
89
|
* treated as "interesting" — the dashboard surfaces them distinctly. */
|
|
90
90
|
modal?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* For a modal state, what KIND of modal it is — the semantic distinction the
|
|
93
|
+
* status field (always 'approval' for any modal, so the dashboard surfaces it)
|
|
94
|
+
* deliberately loses. The auto-approve worker uses this to decide whether it
|
|
95
|
+
* may answer the modal on the user's behalf:
|
|
96
|
+
*
|
|
97
|
+
* - 'approval' — a tool/command/trust consent prompt ("Allow Bash?",
|
|
98
|
+
* "Trust this folder?"). Auto-approve MAY fire (a background mesh worker
|
|
99
|
+
* should not stall on these).
|
|
100
|
+
* - 'picker' — a selection menu the user opened (/model, /mode, …). There
|
|
101
|
+
* is no "correct" answer to auto-pick; blindly selecting the first option
|
|
102
|
+
* silently changes the model/mode. Auto-approve must NOT fire — the user
|
|
103
|
+
* chooses.
|
|
104
|
+
* - 'confirm' — a non-consent yes/no the user must decide. Left to the user.
|
|
105
|
+
*
|
|
106
|
+
* Defaults to 'approval' for a modal state that omits it (preserves the
|
|
107
|
+
* pre-existing "auto-approve any modal" behaviour for un-migrated specs;
|
|
108
|
+
* picker/confirm states declare their kind explicitly). Non-modal states
|
|
109
|
+
* have no modal_kind.
|
|
110
|
+
*/
|
|
111
|
+
modal_kind?: 'approval' | 'picker' | 'confirm';
|
|
91
112
|
/** Status this state maps to for the dashboard/cli-adapter status field.
|
|
92
113
|
* One of: idle | generating | approval. Defaults: modal→approval,
|
|
93
114
|
* initial→idle, id==='busy'→generating, else idle. Explicit wins. */
|
|
@@ -236,3 +257,17 @@ export function statusForState(state: FsmState): 'idle' | 'generating' | 'approv
|
|
|
236
257
|
if (state.id === 'busy' || state.id === 'generating') return 'generating';
|
|
237
258
|
return 'idle';
|
|
238
259
|
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The modal kind for a state, or null when the state is not modal. A modal state
|
|
263
|
+
* that omits `modal_kind` defaults to 'approval' so the established
|
|
264
|
+
* auto-approve-any-modal behaviour is preserved for specs that have not yet
|
|
265
|
+
* declared a kind; picker/confirm states must opt out by declaring their kind.
|
|
266
|
+
* This is the value the cli-adapter carries on `activeModal.kind` and the
|
|
267
|
+
* auto-approve gate reads — see cli-provider-instance.maybeAutoApproveStatus.
|
|
268
|
+
*/
|
|
269
|
+
export function modalKindForState(state: FsmState): 'approval' | 'picker' | 'confirm' | null {
|
|
270
|
+
if (state.modal_kind) return state.modal_kind;
|
|
271
|
+
if (state.modal) return 'approval';
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
@@ -34,6 +34,21 @@ export type ControlAction =
|
|
|
34
34
|
wait_for: WaitForCondition;
|
|
35
35
|
extract_choices: SectionPattern;
|
|
36
36
|
submit_key: string;
|
|
37
|
+
/**
|
|
38
|
+
* How a parsed choice is committed once the picker is open.
|
|
39
|
+
* 'index' (default) — type the on-screen number then `submit_key`
|
|
40
|
+
* (`{index}\r`). Correct for CLIs whose picker is number-selectable
|
|
41
|
+
* (codex-cli, hermes-cli).
|
|
42
|
+
* 'arrow_keys' — the picker is a cursor list that ignores number keys
|
|
43
|
+
* (claude-cli /model): move the cursor from its current row to the
|
|
44
|
+
* target row with up/down arrows, then confirm with the `submit_key`
|
|
45
|
+
* tail (the `\r` left after stripping `{index}`). Requires the
|
|
46
|
+
* extracted choices to flag the current cursor row.
|
|
47
|
+
*/
|
|
48
|
+
select_mode?: 'index' | 'arrow_keys';
|
|
49
|
+
/** Arrow byte sequences for `select_mode: 'arrow_keys'`. Defaults to
|
|
50
|
+
* ANSI cursor up/down (`[A` / `[B`) when omitted. */
|
|
51
|
+
cursor_keys?: { up: string; down: string };
|
|
37
52
|
}
|
|
38
53
|
| {
|
|
39
54
|
type: 'attach_image';
|
|
@@ -227,4 +242,21 @@ export interface ExtractButtons {
|
|
|
227
242
|
key_for_index: string;
|
|
228
243
|
min_count?: number;
|
|
229
244
|
continuation_lines?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
* How a button is committed when its modal is resolved (auto-approve or an
|
|
247
|
+
* explicit dashboard click).
|
|
248
|
+
* 'index' (default) — send `key_for_index` with `{index}` filled in
|
|
249
|
+
* (`{index}\r` → `1\r`). Correct for modals whose buttons are
|
|
250
|
+
* number-selectable (codex, hermes, antigravity number rows).
|
|
251
|
+
* 'arrow_keys' — the modal is a cursor list that IGNORES number keys
|
|
252
|
+
* (claude-cli's new TUI approval modal): a typed `1` leaks into the
|
|
253
|
+
* composer as literal text and `\r` submits it. Instead move the cursor
|
|
254
|
+
* from its current row to the target row with up/down arrows, then
|
|
255
|
+
* confirm with the `key_for_index` tail (the `\r` left after stripping
|
|
256
|
+
* `{index}`). Mirrors the `open_picker` `select_mode` of the same name.
|
|
257
|
+
*/
|
|
258
|
+
select_mode?: 'index' | 'arrow_keys';
|
|
259
|
+
/** Arrow byte sequences for `select_mode: 'arrow_keys'`. Defaults to ANSI
|
|
260
|
+
* cursor up/down (`[A` / `[B`) when omitted. */
|
|
261
|
+
cursor_keys?: { up: string; down: string };
|
|
230
262
|
}
|