@adhdev/daemon-core 0.9.82-rc.342 → 0.9.82-rc.343
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 +59 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -12
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/fsm-driver.d.ts +16 -0
- package/package.json +2 -2
- package/src/mesh/mesh-events-coordinator.ts +15 -1
- package/src/providers/spec/fsm-driver.ts +64 -24
|
@@ -177,6 +177,10 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
177
177
|
* after a re-prime we don't re-inject until the screen changes (which
|
|
178
178
|
* resets the stall reference) or another full stall window lapses. */
|
|
179
179
|
private lastRefocusAt;
|
|
180
|
+
/** Timer driving the win32 verification-based submit resend loop (see
|
|
181
|
+
* WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
|
|
182
|
+
* leaves idle (submitted) or the resend budget is spent. */
|
|
183
|
+
private win32SubmitTimer;
|
|
180
184
|
private currentEval;
|
|
181
185
|
private stateHistory;
|
|
182
186
|
private prevStateAt;
|
|
@@ -294,6 +298,18 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
294
298
|
private fireDelegate;
|
|
295
299
|
private handleSendMessage;
|
|
296
300
|
private actuallySendMessage;
|
|
301
|
+
/** The agent's current coarse status, derived from the FSM node we're in. */
|
|
302
|
+
private currentStatus;
|
|
303
|
+
/**
|
|
304
|
+
* win32 verification-based submit. Sends the submit key, waits a gap, and if
|
|
305
|
+
* the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
|
|
306
|
+
* a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
|
|
307
|
+
* first CR always fires (so a stale/edge status never suppresses the submit);
|
|
308
|
+
* subsequent resends are gated on still being idle, and stop the instant the
|
|
309
|
+
* agent leaves idle (submitted → generating / approval). This converges the
|
|
310
|
+
* nondeterministic multiline window without spamming Enter into the next turn.
|
|
311
|
+
*/
|
|
312
|
+
private scheduleWin32Submit;
|
|
297
313
|
private handleClickControl;
|
|
298
314
|
private handleClickModalButton;
|
|
299
315
|
private handleAttachImage;
|
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.343",
|
|
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.343",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -369,9 +369,23 @@ export function tryAssignQueueTask(
|
|
|
369
369
|
}).then(() => {
|
|
370
370
|
updateSessionDeliveryStatus(delivery.id, 'delivered');
|
|
371
371
|
}).catch((e: any) => {
|
|
372
|
+
// Mirror the remote-dispatch catch above: a local dispatch failure is most often a
|
|
373
|
+
// transient busy/refusal (e.g. the adapter rejected send_chat while mid-generation),
|
|
374
|
+
// not a permanent task failure. Marking the task terminal 'failed' here with no ledger
|
|
375
|
+
// and no retry permanently killed tasks that a later tick would have delivered fine.
|
|
376
|
+
// Return the task to 'pending' and record a retryable dispatch_failed ledger entry so
|
|
377
|
+
// the reconcile loop re-dispatches it, exactly as the remote branch does.
|
|
372
378
|
LOG.error('MeshQueue', `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
373
379
|
updateSessionDeliveryStatus(delivery.id, 'failed', { lastError: e?.message, incrementAttempt: true });
|
|
374
|
-
updateTaskStatus(meshId, task.id, '
|
|
380
|
+
updateTaskStatus(meshId, task.id, 'pending');
|
|
381
|
+
try {
|
|
382
|
+
appendLedgerEntry(meshId, {
|
|
383
|
+
kind: 'dispatch_failed' as any,
|
|
384
|
+
nodeId,
|
|
385
|
+
sessionId,
|
|
386
|
+
payload: { taskId: task.id, deliveryId: delivery.id, error: e?.message, retryable: true },
|
|
387
|
+
});
|
|
388
|
+
} catch { /* ledger write is best-effort */ }
|
|
375
389
|
});
|
|
376
390
|
|
|
377
391
|
return true;
|
|
@@ -145,13 +145,23 @@ function countNewlines(s: string): number {
|
|
|
145
145
|
|
|
146
146
|
const SUBMIT_DELAY_FLOOR_MS = 200;
|
|
147
147
|
|
|
148
|
-
// win32 ConPTY submit reliability
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
148
|
+
// win32 ConPTY submit reliability. A MULTILINE message creates an Ink
|
|
149
|
+
// paste/newline-accumulation window during which a lone CR is absorbed as a
|
|
150
|
+
// literal newline instead of submitting; the window's length is
|
|
151
|
+
// nondeterministic (observed 0–~2s, driven by ConPTY byte timing), so neither a
|
|
152
|
+
// fixed delay nor a fixed CR count reliably submits. (A/B PTY testing on win32
|
|
153
|
+
// ConPTY: single-line submits on the FIRST CR; multiline needs a *variable*
|
|
154
|
+
// number of CRs as the window expires — a fixed double-CR fails outright, and
|
|
155
|
+
// bracketed-paste wrapping does NOT help.) So we VERIFY instead of guessing:
|
|
156
|
+
// write the text, then resend the submit key on a fixed cadence until the FSM
|
|
157
|
+
// observes the agent has actually left the idle composer (status flips away from
|
|
158
|
+
// 'idle' — i.e. it submitted and is generating / showing a modal), bounded by a
|
|
159
|
+
// retry budget. Once submission is observed we stop so we don't spam Enter into
|
|
160
|
+
// the next turn. Single-line messages satisfy the check after the first CR, so
|
|
161
|
+
// their behaviour is unchanged. CRs absorbed as newlines during the window are
|
|
162
|
+
// trimmed by the TUI on submit.
|
|
163
|
+
const WIN32_SUBMIT_RESEND_GAP_MS = 350;
|
|
164
|
+
const WIN32_SUBMIT_MAX_RESENDS = 14;
|
|
155
165
|
|
|
156
166
|
export function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number {
|
|
157
167
|
const lines = countNewlines(text);
|
|
@@ -217,6 +227,10 @@ export class FsmDriver implements ISpecDriver {
|
|
|
217
227
|
* after a re-prime we don't re-inject until the screen changes (which
|
|
218
228
|
* resets the stall reference) or another full stall window lapses. */
|
|
219
229
|
private lastRefocusAt = 0;
|
|
230
|
+
/** Timer driving the win32 verification-based submit resend loop (see
|
|
231
|
+
* WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
|
|
232
|
+
* leaves idle (submitted) or the resend budget is spent. */
|
|
233
|
+
private win32SubmitTimer: ReturnType<typeof setTimeout> | null = null;
|
|
220
234
|
|
|
221
235
|
private currentEval: CurrentEval | null = null;
|
|
222
236
|
private stateHistory: HistoryEntry[] = [];
|
|
@@ -334,6 +348,7 @@ export class FsmDriver implements ISpecDriver {
|
|
|
334
348
|
this.delegateTimers.clear();
|
|
335
349
|
if (this.wakeTimer) { clearTimeout(this.wakeTimer); this.wakeTimer = null; }
|
|
336
350
|
if (this.stallTimer) { clearTimeout(this.stallTimer); this.stallTimer = null; }
|
|
351
|
+
if (this.win32SubmitTimer) { clearTimeout(this.win32SubmitTimer); this.win32SubmitTimer = null; }
|
|
337
352
|
this.specWatcher?.close();
|
|
338
353
|
this.adapter.kill();
|
|
339
354
|
}
|
|
@@ -841,25 +856,19 @@ export class FsmDriver implements ISpecDriver {
|
|
|
841
856
|
const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
|
|
842
857
|
|
|
843
858
|
// win32 ConPTY submit: the text and the submit key (CR) must NOT be
|
|
844
|
-
// combined into one PTY write
|
|
845
|
-
// write that carries text + a trailing CR as a bracketed/multi-line
|
|
846
|
-
// and absorb the CR as a literal newline
|
|
847
|
-
//
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
// paste-accumulation
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
// correctness of submission wins over the typing visual there.
|
|
859
|
+
// combined into one PTY write — Ink-based TUIs (claude-cli) treat a
|
|
860
|
+
// single write that carries text + a trailing CR as a bracketed/multi-line
|
|
861
|
+
// paste and absorb the CR as a literal newline. So we write the text on
|
|
862
|
+
// its own, then resend the submit key on a fixed cadence, VERIFYING after
|
|
863
|
+
// each that the agent actually left the idle composer (status flipped away
|
|
864
|
+
// from 'idle'). This handles the nondeterministic multiline
|
|
865
|
+
// paste-accumulation window where a variable number of CRs is needed; a
|
|
866
|
+
// fixed double-CR fails for multiline (see WIN32_SUBMIT_* above). perChar
|
|
867
|
+
// typing simulation is skipped on win32; correctness of submission wins
|
|
868
|
+
// over the typing visual there.
|
|
855
869
|
if (process.platform === 'win32') {
|
|
856
|
-
const submitTwice = (): void => {
|
|
857
|
-
this.adapter.send_keys(sm.submit_key);
|
|
858
|
-
setTimeout(() => this.adapter.send_keys(sm.submit_key), WIN32_SUBMIT_REPEAT_GAP_MS);
|
|
859
|
-
};
|
|
860
870
|
this.adapter.send_keys(text);
|
|
861
|
-
|
|
862
|
-
else submitTwice();
|
|
871
|
+
this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
|
|
863
872
|
return;
|
|
864
873
|
}
|
|
865
874
|
|
|
@@ -881,6 +890,37 @@ export class FsmDriver implements ISpecDriver {
|
|
|
881
890
|
}, perChar);
|
|
882
891
|
}
|
|
883
892
|
|
|
893
|
+
/** The agent's current coarse status, derived from the FSM node we're in. */
|
|
894
|
+
private currentStatus(): 'idle' | 'generating' | 'approval' {
|
|
895
|
+
const st = stateById(this.spec, this.currentStateId);
|
|
896
|
+
return st ? statusForState(st) : 'idle';
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* win32 verification-based submit. Sends the submit key, waits a gap, and if
|
|
901
|
+
* the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
|
|
902
|
+
* a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
|
|
903
|
+
* first CR always fires (so a stale/edge status never suppresses the submit);
|
|
904
|
+
* subsequent resends are gated on still being idle, and stop the instant the
|
|
905
|
+
* agent leaves idle (submitted → generating / approval). This converges the
|
|
906
|
+
* nondeterministic multiline window without spamming Enter into the next turn.
|
|
907
|
+
*/
|
|
908
|
+
private scheduleWin32Submit(submitKey: string, initialDelayMs: number): void {
|
|
909
|
+
if (this.win32SubmitTimer) { clearTimeout(this.win32SubmitTimer); this.win32SubmitTimer = null; }
|
|
910
|
+
const fire = (attempt: number): void => {
|
|
911
|
+
this.win32SubmitTimer = null;
|
|
912
|
+
this.adapter.send_keys(submitKey);
|
|
913
|
+
if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
|
|
914
|
+
this.win32SubmitTimer = setTimeout(() => {
|
|
915
|
+
// Left the idle composer → it submitted; stop resending.
|
|
916
|
+
if (this.currentStatus() !== 'idle') { this.win32SubmitTimer = null; return; }
|
|
917
|
+
fire(attempt + 1);
|
|
918
|
+
}, WIN32_SUBMIT_RESEND_GAP_MS);
|
|
919
|
+
};
|
|
920
|
+
if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(() => fire(0), initialDelayMs);
|
|
921
|
+
else fire(0);
|
|
922
|
+
}
|
|
923
|
+
|
|
884
924
|
private handleClickControl(controlId: string, payload?: unknown): void {
|
|
885
925
|
const ctl = (this.spec.control_bar ?? []).find(c => c.id === controlId);
|
|
886
926
|
if (!ctl) return;
|