@adhdev/daemon-core 0.9.82-rc.359 → 0.9.82-rc.360
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 +36 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +36 -20
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/fsm-driver.d.ts +10 -7
- package/package.json +2 -2
- package/src/providers/spec/fsm-driver.ts +64 -27
|
@@ -337,13 +337,16 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
337
337
|
/**
|
|
338
338
|
* win32 submit. Two phases:
|
|
339
339
|
*
|
|
340
|
-
* Phase 1 (
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
340
|
+
* Phase 1 (echo-gate): hold the first CR until the body text is CONFIRMED in the
|
|
341
|
+
* composer (its whitespace-collapsed tail appears in the rendered screen) AND the
|
|
342
|
+
* PTY output is then quiet for WIN32_SUBMIT_SETTLE_MS (the full, possibly multi-KB
|
|
343
|
+
* / multiline body has finished arriving). This replaces a bare output-quiet
|
|
344
|
+
* settle: an early write that races claude's boot is buffered, not dropped, so the
|
|
345
|
+
* screen can go quiet with the body NOT YET in the composer — a quiet-only gate
|
|
346
|
+
* would then fire a CR into an empty composer (no submit). Waiting on the echo
|
|
347
|
+
* closes that. Honors an initial minimum delay and a generous WIN32_ECHO_MAX_WAIT_MS
|
|
348
|
+
* last-resort blind fire so a body that truly never confirms still submits (carried
|
|
349
|
+
* by phase 2) rather than hanging. A settled session echoes immediately → no delay.
|
|
347
350
|
*
|
|
348
351
|
* Phase 2 (verified resend — unchanged): send the submit key, wait a gap, and
|
|
349
352
|
* if the FSM is still 'idle' (the CR was absorbed as a multiline-paste
|
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.360",
|
|
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.360",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -163,16 +163,11 @@ const SUBMIT_DELAY_FLOOR_MS = 200;
|
|
|
163
163
|
// trimmed by the TUI on submit.
|
|
164
164
|
const WIN32_SUBMIT_RESEND_GAP_MS = 350;
|
|
165
165
|
const WIN32_SUBMIT_MAX_RESENDS = 14;
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// one settles almost immediately. WIN32_SUBMIT_MAX_SETTLE_WAIT_MS bounds the wait
|
|
171
|
-
// so a perpetually-noisy screen can never hang the submit. This is what stops a
|
|
172
|
-
// blind fixed-delay CR from submitting a half-arrived prompt and dropping its
|
|
173
|
-
// leading lines. The phase-2 verified-resend loop (below) is unchanged.
|
|
166
|
+
// Quiet window the win32 echo-gate (below) requires AFTER the body is seen in the
|
|
167
|
+
// composer, so a CR fires only once the FULL (possibly multi-KB / multiline) body has
|
|
168
|
+
// finished arriving and echoing — not mid-arrival. POLL_MS is the gate's recheck
|
|
169
|
+
// cadence while it waits for the body to echo.
|
|
174
170
|
const WIN32_SUBMIT_SETTLE_MS = 500;
|
|
175
|
-
const WIN32_SUBMIT_MAX_SETTLE_WAIT_MS = 10_000;
|
|
176
171
|
const WIN32_SUBMIT_SETTLE_POLL_MS = 120;
|
|
177
172
|
// Defensive paced PTY write. A single unbounded ConPTY write can overflow the
|
|
178
173
|
// input pipe and drop leading bytes; split a large body into bounded chunks with a
|
|
@@ -180,6 +175,25 @@ const WIN32_SUBMIT_SETTLE_POLL_MS = 120;
|
|
|
180
175
|
// write in one shot.
|
|
181
176
|
const WIN32_PTY_WRITE_CHUNK_CHARS = 1024;
|
|
182
177
|
const WIN32_PTY_WRITE_CHUNK_GAP_MS = 8;
|
|
178
|
+
// Echo-gate for the win32 FIRST submit CR (supersedes the bare output-quiet settle).
|
|
179
|
+
// The body write can race claude-cli's boot — its stdin reader is not wired until the
|
|
180
|
+
// composer renders (~5–7s in, later under load), so a too-early write is buffered and
|
|
181
|
+
// the output goes quiet with a still-EMPTY composer; a quiet-only gate then fires a CR
|
|
182
|
+
// into nothing. Instead, hold the CR until the body text has actually ECHOED into the
|
|
183
|
+
// composer (effect-confirmed, not readiness-signal-guessed) — the buffered write lands
|
|
184
|
+
// once claude wires up, just late. WIN32_ECHO_MAX_WAIT_MS bounds the wait so a body
|
|
185
|
+
// that truly never confirms still fires a blind CR (resend net) rather than hanging; it
|
|
186
|
+
// is set generously so even a slow/contended boot lands its body before the blind fire.
|
|
187
|
+
const WIN32_ECHO_PROBE_CHARS = 16;
|
|
188
|
+
const WIN32_ECHO_MAX_WAIT_MS = 20_000;
|
|
189
|
+
|
|
190
|
+
/** Collapse a string to its non-whitespace characters for echo comparison: the
|
|
191
|
+
* composer wraps, indents, and prefixes the body (with the `❯ ` prompt), so a raw
|
|
192
|
+
* substring test against the rendered screen fails. Stripping all whitespace makes
|
|
193
|
+
* "❯ hello world" reliably contain the probe "helloworld". */
|
|
194
|
+
function normalizeForEcho(s: string): string {
|
|
195
|
+
return s.replace(/\s+/g, '');
|
|
196
|
+
}
|
|
183
197
|
|
|
184
198
|
export function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number {
|
|
185
199
|
const lines = countNewlines(text);
|
|
@@ -927,7 +941,7 @@ export class FsmDriver implements ISpecDriver {
|
|
|
927
941
|
// over the typing visual there.
|
|
928
942
|
if (process.platform === 'win32') {
|
|
929
943
|
this.writeWin32Body(text);
|
|
930
|
-
this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
|
|
944
|
+
this.scheduleWin32Submit(sm.submit_key, beforeSubmit, text);
|
|
931
945
|
return;
|
|
932
946
|
}
|
|
933
947
|
|
|
@@ -1000,13 +1014,16 @@ export class FsmDriver implements ISpecDriver {
|
|
|
1000
1014
|
/**
|
|
1001
1015
|
* win32 submit. Two phases:
|
|
1002
1016
|
*
|
|
1003
|
-
* Phase 1 (
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1017
|
+
* Phase 1 (echo-gate): hold the first CR until the body text is CONFIRMED in the
|
|
1018
|
+
* composer (its whitespace-collapsed tail appears in the rendered screen) AND the
|
|
1019
|
+
* PTY output is then quiet for WIN32_SUBMIT_SETTLE_MS (the full, possibly multi-KB
|
|
1020
|
+
* / multiline body has finished arriving). This replaces a bare output-quiet
|
|
1021
|
+
* settle: an early write that races claude's boot is buffered, not dropped, so the
|
|
1022
|
+
* screen can go quiet with the body NOT YET in the composer — a quiet-only gate
|
|
1023
|
+
* would then fire a CR into an empty composer (no submit). Waiting on the echo
|
|
1024
|
+
* closes that. Honors an initial minimum delay and a generous WIN32_ECHO_MAX_WAIT_MS
|
|
1025
|
+
* last-resort blind fire so a body that truly never confirms still submits (carried
|
|
1026
|
+
* by phase 2) rather than hanging. A settled session echoes immediately → no delay.
|
|
1010
1027
|
*
|
|
1011
1028
|
* Phase 2 (verified resend — unchanged): send the submit key, wait a gap, and
|
|
1012
1029
|
* if the FSM is still 'idle' (the CR was absorbed as a multiline-paste
|
|
@@ -1015,10 +1032,21 @@ export class FsmDriver implements ISpecDriver {
|
|
|
1015
1032
|
* idle and stop the instant the agent leaves idle (submitted → generating /
|
|
1016
1033
|
* approval). This preserves the win32 lone-CR-swallow handling.
|
|
1017
1034
|
*/
|
|
1018
|
-
private scheduleWin32Submit(submitKey: string, initialDelayMs: number): void {
|
|
1035
|
+
private scheduleWin32Submit(submitKey: string, initialDelayMs: number, body: string): void {
|
|
1019
1036
|
if (this.win32SubmitTimer) { clearTimeout(this.win32SubmitTimer); this.win32SubmitTimer = null; }
|
|
1020
1037
|
const startedAt = Date.now();
|
|
1021
1038
|
|
|
1039
|
+
// Distinctive whitespace-collapsed TAIL of the body — what we look for in the
|
|
1040
|
+
// composer to confirm the body landed. The tail (not the head) because a long
|
|
1041
|
+
// multiline body scrolls its leading lines out of the composer viewport while
|
|
1042
|
+
// the cursor — and therefore the body's end — stays visible. Empty body →
|
|
1043
|
+
// nothing to confirm.
|
|
1044
|
+
const probe = normalizeForEcho(body).slice(-WIN32_ECHO_PROBE_CHARS);
|
|
1045
|
+
const bodyEchoed = (): boolean => {
|
|
1046
|
+
if (!probe) return true;
|
|
1047
|
+
return normalizeForEcho(this.adapter.snapshot()).includes(probe);
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1022
1050
|
const fire = (attempt: number): void => {
|
|
1023
1051
|
this.win32SubmitTimer = null;
|
|
1024
1052
|
this.adapter.send_keys(submitKey);
|
|
@@ -1030,21 +1058,30 @@ export class FsmDriver implements ISpecDriver {
|
|
|
1030
1058
|
}, WIN32_SUBMIT_RESEND_GAP_MS);
|
|
1031
1059
|
};
|
|
1032
1060
|
|
|
1033
|
-
|
|
1061
|
+
// Echo-gate: hold the first CR until the body is CONFIRMED in the composer, not
|
|
1062
|
+
// merely until output goes quiet — a quiet screen with a not-yet-arrived body is
|
|
1063
|
+
// the exact empty-submit failure this replaces. The body is NOT re-written: on
|
|
1064
|
+
// the session-host IPC transport an early write is buffered, not dropped, so it
|
|
1065
|
+
// lands once claude's stdin reader wires up (just late on a slow/contended boot);
|
|
1066
|
+
// re-writing would risk a duplicated body when several buffered writes drain
|
|
1067
|
+
// together. So we simply WAIT for the echo. The hard upper bound only fires a
|
|
1068
|
+
// blind CR if the body never confirms at all (resend net carries it), set
|
|
1069
|
+
// generously so a slow boot still lands its body first.
|
|
1070
|
+
const waitForEcho = (): void => {
|
|
1034
1071
|
this.win32SubmitTimer = null;
|
|
1035
1072
|
const now = Date.now();
|
|
1036
1073
|
const quietFor = now - this.lastWin32InputActivityAt();
|
|
1037
1074
|
const waited = now - startedAt;
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
this.win32SubmitTimer = setTimeout(
|
|
1075
|
+
const settled = quietFor >= WIN32_SUBMIT_SETTLE_MS;
|
|
1076
|
+
// Body present in the composer AND output quiet (full body arrived) → submit.
|
|
1077
|
+
if (bodyEchoed() && settled) { fire(0); return; }
|
|
1078
|
+
// Last-resort blind fire so a body that truly never confirms cannot hang.
|
|
1079
|
+
if (waited >= WIN32_ECHO_MAX_WAIT_MS) { fire(0); return; }
|
|
1080
|
+
this.win32SubmitTimer = setTimeout(waitForEcho, WIN32_SUBMIT_SETTLE_POLL_MS);
|
|
1044
1081
|
};
|
|
1045
1082
|
|
|
1046
|
-
if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(
|
|
1047
|
-
else
|
|
1083
|
+
if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(waitForEcho, initialDelayMs);
|
|
1084
|
+
else waitForEcho();
|
|
1048
1085
|
}
|
|
1049
1086
|
|
|
1050
1087
|
private handleClickControl(controlId: string, payload?: unknown): void {
|