@adhdev/daemon-core 0.9.82-rc.363 → 0.9.82-rc.364

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.
@@ -198,6 +198,11 @@ export declare class FsmDriver implements ISpecDriver {
198
198
  private lastWin32WriteAt;
199
199
  /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
200
200
  private win32WriteTimer;
201
+ /** Timer driving the win32 verification-based modal-confirm CR resend loop (see
202
+ * scheduleWin32ModalConfirm). A lone CR that confirms an approval/picker choice
203
+ * is absorbed by ConPTY the same way a send_message submit CR is, so the confirm
204
+ * must be resent until the modal actually resolves (status leaves 'approval'). */
205
+ private win32ModalConfirmTimer;
201
206
  private currentEval;
202
207
  private stateHistory;
203
208
  private prevStateAt;
@@ -358,6 +363,28 @@ export declare class FsmDriver implements ISpecDriver {
358
363
  private scheduleWin32Submit;
359
364
  private handleClickControl;
360
365
  private handleClickModalButton;
366
+ /**
367
+ * Submit a modal-confirm key sequence (the choice key + its trailing CR).
368
+ *
369
+ * On win32 the trailing CR is the SAME lone-CR-swallow case as a send_message
370
+ * submit: ConPTY can absorb a single CR as a literal newline instead of a
371
+ * confirm, so the approval/picker modal never resolves and the FSM flaps
372
+ * approval↔busy while auto-approve keeps firing into the void (APPROVESTUCK).
373
+ * So we split any non-CR prefix (e.g. the "1" of "1\r") off, write it once, and
374
+ * resend the CR on a fixed cadence until the modal actually resolves (status
375
+ * leaves 'approval'). Non-win32 keeps the single direct write — its CR submits
376
+ * on the first try.
377
+ */
378
+ private submitModalConfirm;
379
+ /**
380
+ * win32 modal-confirm CR resend loop. Mirrors scheduleWin32Submit's phase-2
381
+ * verified resend, but gated on still being IN a modal (status 'approval')
382
+ * rather than still idle: the first CR fires immediately, then resends every
383
+ * WIN32_SUBMIT_RESEND_GAP_MS while the FSM is still showing the modal, up to
384
+ * WIN32_SUBMIT_MAX_RESENDS. The instant the modal resolves (status flips to
385
+ * generating/idle) we stop, so no stray CR leaks into the next turn's composer.
386
+ */
387
+ private scheduleWin32ModalConfirm;
361
388
  private handleAttachImage;
362
389
  private tryAdvancePicker;
363
390
  private handleExit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.363",
3
+ "version": "0.9.82-rc.364",
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.363",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.364",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -294,6 +294,11 @@ export class FsmDriver implements ISpecDriver {
294
294
  private lastWin32WriteAt = 0;
295
295
  /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
296
296
  private win32WriteTimer: ReturnType<typeof setTimeout> | null = null;
297
+ /** Timer driving the win32 verification-based modal-confirm CR resend loop (see
298
+ * scheduleWin32ModalConfirm). A lone CR that confirms an approval/picker choice
299
+ * is absorbed by ConPTY the same way a send_message submit CR is, so the confirm
300
+ * must be resent until the modal actually resolves (status leaves 'approval'). */
301
+ private win32ModalConfirmTimer: ReturnType<typeof setTimeout> | null = null;
297
302
 
298
303
  private currentEval: CurrentEval | null = null;
299
304
  private stateHistory: HistoryEntry[] = [];
@@ -413,6 +418,7 @@ export class FsmDriver implements ISpecDriver {
413
418
  if (this.stallTimer) { clearTimeout(this.stallTimer); this.stallTimer = null; }
414
419
  if (this.win32SubmitTimer) { clearTimeout(this.win32SubmitTimer); this.win32SubmitTimer = null; }
415
420
  if (this.win32WriteTimer) { clearTimeout(this.win32WriteTimer); this.win32WriteTimer = null; }
421
+ if (this.win32ModalConfirmTimer) { clearTimeout(this.win32ModalConfirmTimer); this.win32ModalConfirmTimer = null; }
416
422
  this.specWatcher?.close();
417
423
  this.adapter.kill();
418
424
  }
@@ -1140,10 +1146,58 @@ export class FsmDriver implements ISpecDriver {
1140
1146
  // `{index}\r` → `\r`.
1141
1147
  const confirm = (rule.key_for_index || '\r').replace(/\{index\}/g, '') || '\r';
1142
1148
  if (nav) this.adapter.send_keys(nav);
1143
- this.adapter.send_keys(confirm);
1149
+ this.submitModalConfirm(confirm);
1144
1150
  return;
1145
1151
  }
1146
- this.adapter.send_keys(btn.key);
1152
+ this.submitModalConfirm(btn.key);
1153
+ }
1154
+
1155
+ /**
1156
+ * Submit a modal-confirm key sequence (the choice key + its trailing CR).
1157
+ *
1158
+ * On win32 the trailing CR is the SAME lone-CR-swallow case as a send_message
1159
+ * submit: ConPTY can absorb a single CR as a literal newline instead of a
1160
+ * confirm, so the approval/picker modal never resolves and the FSM flaps
1161
+ * approval↔busy while auto-approve keeps firing into the void (APPROVESTUCK).
1162
+ * So we split any non-CR prefix (e.g. the "1" of "1\r") off, write it once, and
1163
+ * resend the CR on a fixed cadence until the modal actually resolves (status
1164
+ * leaves 'approval'). Non-win32 keeps the single direct write — its CR submits
1165
+ * on the first try.
1166
+ */
1167
+ private submitModalConfirm(keys: string): void {
1168
+ if (process.platform !== 'win32') {
1169
+ this.adapter.send_keys(keys);
1170
+ return;
1171
+ }
1172
+ const m = /^([\s\S]*?)([\r\n]+)$/.exec(keys);
1173
+ const prefix = m ? m[1] : keys;
1174
+ const cr = m ? m[2] : '';
1175
+ if (prefix) this.adapter.send_keys(prefix);
1176
+ if (!cr) return;
1177
+ this.scheduleWin32ModalConfirm(cr);
1178
+ }
1179
+
1180
+ /**
1181
+ * win32 modal-confirm CR resend loop. Mirrors scheduleWin32Submit's phase-2
1182
+ * verified resend, but gated on still being IN a modal (status 'approval')
1183
+ * rather than still idle: the first CR fires immediately, then resends every
1184
+ * WIN32_SUBMIT_RESEND_GAP_MS while the FSM is still showing the modal, up to
1185
+ * WIN32_SUBMIT_MAX_RESENDS. The instant the modal resolves (status flips to
1186
+ * generating/idle) we stop, so no stray CR leaks into the next turn's composer.
1187
+ */
1188
+ private scheduleWin32ModalConfirm(submitKey: string): void {
1189
+ if (this.win32ModalConfirmTimer) { clearTimeout(this.win32ModalConfirmTimer); this.win32ModalConfirmTimer = null; }
1190
+ const fire = (attempt: number): void => {
1191
+ this.win32ModalConfirmTimer = null;
1192
+ this.adapter.send_keys(submitKey);
1193
+ if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
1194
+ this.win32ModalConfirmTimer = setTimeout(() => {
1195
+ // Left the modal → it resolved; stop resending.
1196
+ if (this.currentStatus() !== 'approval') { this.win32ModalConfirmTimer = null; return; }
1197
+ fire(attempt + 1);
1198
+ }, WIN32_SUBMIT_RESEND_GAP_MS);
1199
+ };
1200
+ fire(0);
1147
1201
  }
1148
1202
 
1149
1203
  private handleAttachImage(blob: string, mime: string): void {