@adhdev/daemon-core 0.9.82-rc.295 → 0.9.82-rc.296
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/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -8
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/cli-adapter.d.ts +23 -0
- package/package.json +2 -2
- package/src/providers/spec/cli-adapter.ts +50 -3
- package/src/providers/types/interactive-prompt.ts +12 -1
|
@@ -118,6 +118,15 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
118
118
|
* spirit as the approval FSM's `approvalCooldown` modal-lost hysteresis.
|
|
119
119
|
*/
|
|
120
120
|
private static readonly INTERACTIVE_PROMPT_LOST_GRACE_MS;
|
|
121
|
+
/**
|
|
122
|
+
* Multi-question TUI capture: after Tabbing to a page, re-snapshot at this
|
|
123
|
+
* interval until its checkbox glyph column has settled, up to the timeout.
|
|
124
|
+
* The interval matches the legacy single fixed wait (120ms); the timeout
|
|
125
|
+
* bounds total capture time so a single-select page (which never shows
|
|
126
|
+
* glyphs) doesn't stall the capture indefinitely.
|
|
127
|
+
*/
|
|
128
|
+
private static readonly CLAUDE_TUI_PAGE_POLL_INTERVAL_MS;
|
|
129
|
+
private static readonly CLAUDE_TUI_PAGE_SETTLE_TIMEOUT_MS;
|
|
121
130
|
/**
|
|
122
131
|
* Clear a held interactive prompt once the user has resolved it directly
|
|
123
132
|
* in the terminal (the choice picker leaves the screen without going
|
|
@@ -156,6 +165,20 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
156
165
|
*/
|
|
157
166
|
private maybeUpgradeClaudeTuiMultiSelect;
|
|
158
167
|
private readClaudeTuiHeaders;
|
|
168
|
+
/**
|
|
169
|
+
* Snapshot the currently-focused claude TUI page, polling until its
|
|
170
|
+
* option-row checkbox glyph column has settled (or a bounded timeout).
|
|
171
|
+
*
|
|
172
|
+
* Why poll: right after a Tab keypress the newly-focused page's glyph
|
|
173
|
+
* column has not redrawn yet, so an immediate snapshot shows the question +
|
|
174
|
+
* option labels but NO per-option checkbox markers — freezing that page as
|
|
175
|
+
* single-select. A fixed delay either races (too short) or is wasteful (too
|
|
176
|
+
* long). Instead we re-snapshot at a fixed interval and stop as soon as the
|
|
177
|
+
* frame shows multi-select glyphs, falling back to the last frame at the
|
|
178
|
+
* timeout. Single-select pages never show glyphs, so they always poll to the
|
|
179
|
+
* timeout — bounded small to keep capture snappy.
|
|
180
|
+
*/
|
|
181
|
+
private snapshotSettledClaudeTuiPage;
|
|
159
182
|
private captureClaudeTuiPrompt;
|
|
160
183
|
getDebugState(): Record<string, any>;
|
|
161
184
|
getTraceState(limit?: number): Record<string, any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.296",
|
|
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",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.296",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -552,6 +552,16 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
552
552
|
*/
|
|
553
553
|
private static readonly INTERACTIVE_PROMPT_LOST_GRACE_MS = 1500;
|
|
554
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Multi-question TUI capture: after Tabbing to a page, re-snapshot at this
|
|
557
|
+
* interval until its checkbox glyph column has settled, up to the timeout.
|
|
558
|
+
* The interval matches the legacy single fixed wait (120ms); the timeout
|
|
559
|
+
* bounds total capture time so a single-select page (which never shows
|
|
560
|
+
* glyphs) doesn't stall the capture indefinitely.
|
|
561
|
+
*/
|
|
562
|
+
private static readonly CLAUDE_TUI_PAGE_POLL_INTERVAL_MS = 120;
|
|
563
|
+
private static readonly CLAUDE_TUI_PAGE_SETTLE_TIMEOUT_MS = 600;
|
|
564
|
+
|
|
555
565
|
/**
|
|
556
566
|
* Clear a held interactive prompt once the user has resolved it directly
|
|
557
567
|
* in the terminal (the choice picker leaves the screen without going
|
|
@@ -686,16 +696,53 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
686
696
|
return headers;
|
|
687
697
|
}
|
|
688
698
|
|
|
699
|
+
/**
|
|
700
|
+
* Snapshot the currently-focused claude TUI page, polling until its
|
|
701
|
+
* option-row checkbox glyph column has settled (or a bounded timeout).
|
|
702
|
+
*
|
|
703
|
+
* Why poll: right after a Tab keypress the newly-focused page's glyph
|
|
704
|
+
* column has not redrawn yet, so an immediate snapshot shows the question +
|
|
705
|
+
* option labels but NO per-option checkbox markers — freezing that page as
|
|
706
|
+
* single-select. A fixed delay either races (too short) or is wasteful (too
|
|
707
|
+
* long). Instead we re-snapshot at a fixed interval and stop as soon as the
|
|
708
|
+
* frame shows multi-select glyphs, falling back to the last frame at the
|
|
709
|
+
* timeout. Single-select pages never show glyphs, so they always poll to the
|
|
710
|
+
* timeout — bounded small to keep capture snappy.
|
|
711
|
+
*/
|
|
712
|
+
private async snapshotSettledClaudeTuiPage(): Promise<string> {
|
|
713
|
+
let screenText = this.driver.snapshot();
|
|
714
|
+
const deadline = Date.now() + SpecCliAdapter.CLAUDE_TUI_PAGE_SETTLE_TIMEOUT_MS;
|
|
715
|
+
while (!detectClaudeTuiMultiSelect(screenText) && Date.now() < deadline) {
|
|
716
|
+
await new Promise(resolve => setTimeout(resolve, SpecCliAdapter.CLAUDE_TUI_PAGE_POLL_INTERVAL_MS));
|
|
717
|
+
screenText = this.driver.snapshot();
|
|
718
|
+
}
|
|
719
|
+
return screenText;
|
|
720
|
+
}
|
|
721
|
+
|
|
689
722
|
private async captureClaudeTuiPrompt(firstScreen: string, headers: string[]): Promise<void> {
|
|
690
723
|
const pages: ClaudeInteractiveTuiPage[] = [{ screenText: firstScreen, header: headers[0] }];
|
|
724
|
+
// Forward pass: Tab to each page 2..N and snapshot once its glyph column
|
|
725
|
+
// has settled, so pages 2+ capture their checkbox markers (not a racy
|
|
726
|
+
// pre-redraw frame).
|
|
691
727
|
for (let index = 1; index < headers.length; index += 1) {
|
|
692
728
|
this.driver.dispatch({ kind: 'pty_write', data: '\t' });
|
|
693
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
694
|
-
pages.push({ screenText: this.
|
|
729
|
+
await new Promise(resolve => setTimeout(resolve, SpecCliAdapter.CLAUDE_TUI_PAGE_POLL_INTERVAL_MS));
|
|
730
|
+
pages.push({ screenText: await this.snapshotSettledClaudeTuiPage(), header: headers[index] });
|
|
695
731
|
}
|
|
732
|
+
// Return pass: Shift-Tab back through pages N..2. As we land on each page
|
|
733
|
+
// again re-read it and OR-in any now-visible multi-select glyphs — a
|
|
734
|
+
// second chance to repair a page whose forward-pass frame was still racy.
|
|
696
735
|
for (let index = headers.length - 1; index > 0; index -= 1) {
|
|
697
736
|
this.driver.dispatch({ kind: 'pty_write', data: '\x1b[Z' });
|
|
698
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
737
|
+
await new Promise(resolve => setTimeout(resolve, SpecCliAdapter.CLAUDE_TUI_PAGE_POLL_INTERVAL_MS));
|
|
738
|
+
const reread = await this.snapshotSettledClaudeTuiPage();
|
|
739
|
+
// The page we just Shift-Tab'd ONTO is index-1 (we move backwards).
|
|
740
|
+
const landed = pages[index - 1];
|
|
741
|
+
if (landed
|
|
742
|
+
&& !detectClaudeTuiMultiSelect(landed.screenText)
|
|
743
|
+
&& detectClaudeTuiMultiSelect(reread)) {
|
|
744
|
+
landed.screenText = reread;
|
|
745
|
+
}
|
|
699
746
|
}
|
|
700
747
|
|
|
701
748
|
const prompt = detectClaudeAskUserQuestionPromptFromTuiPages(pages, {
|
|
@@ -421,7 +421,18 @@ export function buildClaudeInteractiveTuiAnswerSteps(
|
|
|
421
421
|
if (!answer) throw new Error(`Missing answer for ${question.questionId}`);
|
|
422
422
|
const freeformText = answer.freeformText?.trim() ?? '';
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
// Defensive multi-select fallback: a checkbox picker is the ONLY way an
|
|
425
|
+
// answer can carry more than one selected label, so treat any such answer
|
|
426
|
+
// as multi-select even when `question.multiSelect` was captured as false.
|
|
427
|
+
// This guards the multi-question capture race: pages 2..N can freeze as
|
|
428
|
+
// single-select (their glyph column hadn't redrawn at Tab-snapshot time),
|
|
429
|
+
// and the old single-select branch would then either throw on 2+ checked
|
|
430
|
+
// boxes or emit a bare digit (cursor move, no toggle) for 1 box — silently
|
|
431
|
+
// dropping that page's selection. Keying off the answer's label count makes
|
|
432
|
+
// the keystroke protocol correct regardless of the captured flag.
|
|
433
|
+
const treatAsMultiSelect = question.multiSelect || answer.selectedLabels.length > 1;
|
|
434
|
+
|
|
435
|
+
if (treatAsMultiSelect) {
|
|
425
436
|
// Multi-select: Claude TUI renders each option as a checkbox and the
|
|
426
437
|
// footer reads "Space to select". A numeric digit jumps the cursor to
|
|
427
438
|
// that option; Space toggles its checkbox. So for every selected label
|