@adhdev/daemon-core 0.9.82-rc.351 → 0.9.82-rc.353
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/commands/router.d.ts +12 -0
- package/dist/commands/upgrade-helper.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +815 -500
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +812 -500
- package/dist/index.mjs.map +1 -1
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-runtime-store.d.ts +9 -0
- package/dist/mesh/mesh-work-queue.d.ts +28 -0
- package/dist/providers/approval-utils.d.ts +7 -0
- package/dist/providers/cli-provider-instance.d.ts +15 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +1 -0
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +13 -4
- package/src/commands/router.ts +133 -33
- package/src/commands/upgrade-helper.ts +57 -14
- package/src/index.ts +5 -0
- package/src/logging/command-log.ts +7 -5
- package/src/logging/logger.ts +12 -6
- package/src/mesh/mesh-events-coordinator.ts +165 -84
- package/src/mesh/mesh-events-pending.ts +14 -15
- package/src/mesh/mesh-ledger.ts +1 -0
- package/src/mesh/mesh-reconcile-loop.ts +67 -7
- package/src/mesh/mesh-runtime-store.ts +17 -0
- package/src/mesh/mesh-work-queue.ts +89 -0
- package/src/providers/approval-utils.d.ts +1 -0
- package/src/providers/approval-utils.ts +10 -0
- package/src/providers/cli-provider-instance.ts +73 -19
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +51 -0
|
@@ -376,6 +376,21 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
376
376
|
*/
|
|
377
377
|
private static readonly AUTO_APPROVE_SETTLE_MS = 600;
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Busy-side hysteresis for the settle gate. A momentary `generating` flip
|
|
381
|
+
* while the SAME approval modal's button block is still on screen (its
|
|
382
|
+
* question line scrolled out of the captured frame, only the buttons + a
|
|
383
|
+
* residual `esc to interrupt` spinner remain) briefly reports
|
|
384
|
+
* status!=waiting_approval. Without hysteresis that flip wipes the settle
|
|
385
|
+
* clock, and the modal→generating→modal flap restarts the 600ms window
|
|
386
|
+
* every time so auto-approve never fires. We keep the in-progress settle
|
|
387
|
+
* gate warm across an inactive blip up to this bound; only once the modal
|
|
388
|
+
* has genuinely stayed gone this long (a real resolution → idle) is the
|
|
389
|
+
* gate cleared. Bounded so a genuinely new, later approval still re-settles
|
|
390
|
+
* from scratch rather than firing on a stale timestamp.
|
|
391
|
+
*/
|
|
392
|
+
private static readonly AUTO_APPROVE_GATE_HYSTERESIS_MS = 1500;
|
|
393
|
+
|
|
379
394
|
private adapter: ProviderCliAdapter;
|
|
380
395
|
private context: InstanceContext | null = null;
|
|
381
396
|
private events: ProviderEvent[] = [];
|
|
@@ -397,6 +412,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
397
412
|
private pendingAutoApprovalSignature = '';
|
|
398
413
|
private pendingAutoApprovalSince = 0;
|
|
399
414
|
private autoApproveSettleTimer: NodeJS.Timeout | null = null;
|
|
415
|
+
// Wall-clock when auto-approve first observed status!=waiting_approval while
|
|
416
|
+
// a settle gate was in progress. Drives AUTO_APPROVE_GATE_HYSTERESIS_MS so a
|
|
417
|
+
// brief generating flip does not immediately wipe the settle clock.
|
|
418
|
+
private autoApproveInactiveSince = 0;
|
|
400
419
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
401
420
|
private summaryMetadata: unknown = undefined;
|
|
402
421
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -1471,13 +1490,37 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1471
1490
|
// still inside the short busy window.
|
|
1472
1491
|
if (!autoApproveActive) {
|
|
1473
1492
|
this.lastAutoApprovalSignature = '';
|
|
1493
|
+
// Hysteresis: if a settle gate is mid-progress, a momentary
|
|
1494
|
+
// status!=waiting_approval blip (a generating flip while the same
|
|
1495
|
+
// modal's button block is still on screen) must NOT wipe the settle
|
|
1496
|
+
// clock — otherwise the modal→generating→modal flap restarts the
|
|
1497
|
+
// 600ms window every time and auto-approve never fires. Keep the
|
|
1498
|
+
// gate warm for AUTO_APPROVE_GATE_HYSTERESIS_MS; re-arm a timer so
|
|
1499
|
+
// that if the modal does NOT come back the gate is cleared on the
|
|
1500
|
+
// re-check (a genuine resolution → idle frees the gate normally).
|
|
1501
|
+
if (this.pendingAutoApprovalSince) {
|
|
1502
|
+
if (!this.autoApproveInactiveSince) this.autoApproveInactiveSince = now;
|
|
1503
|
+
const goneForMs = now - this.autoApproveInactiveSince;
|
|
1504
|
+
if (goneForMs < CliProviderInstance.AUTO_APPROVE_GATE_HYSTERESIS_MS) {
|
|
1505
|
+
if (this.autoApproveSettleTimer) clearTimeout(this.autoApproveSettleTimer);
|
|
1506
|
+
this.autoApproveSettleTimer = setTimeout(() => {
|
|
1507
|
+
this.autoApproveSettleTimer = null;
|
|
1508
|
+
this.recheckAutoApproveSettled();
|
|
1509
|
+
}, CliProviderInstance.AUTO_APPROVE_GATE_HYSTERESIS_MS - goneForMs + 20);
|
|
1510
|
+
return autoApproveActive;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1474
1513
|
// Clear the settle gate so the next approval starts its own quiet
|
|
1475
1514
|
// window from scratch (a stale timestamp would let it fire instantly).
|
|
1476
1515
|
this.pendingAutoApprovalSignature = '';
|
|
1477
1516
|
this.pendingAutoApprovalSince = 0;
|
|
1517
|
+
this.autoApproveInactiveSince = 0;
|
|
1478
1518
|
if (this.autoApproveSettleTimer) { clearTimeout(this.autoApproveSettleTimer); this.autoApproveSettleTimer = null; }
|
|
1479
1519
|
return autoApproveActive;
|
|
1480
1520
|
}
|
|
1521
|
+
// Active approval observed — reset the inactivity tracker so a later
|
|
1522
|
+
// blip starts its hysteresis window fresh.
|
|
1523
|
+
this.autoApproveInactiveSince = 0;
|
|
1481
1524
|
const modal = adapterStatus.activeModal;
|
|
1482
1525
|
// (fix) Do not auto-approve when no concrete modal/buttons are present.
|
|
1483
1526
|
// Claude TUI flaps between paints; without this guard adapterStatus
|
|
@@ -1497,34 +1540,44 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1497
1540
|
// surface the modal so the user can decide.
|
|
1498
1541
|
return autoApproveActive;
|
|
1499
1542
|
}
|
|
1500
|
-
//
|
|
1501
|
-
//
|
|
1502
|
-
//
|
|
1503
|
-
//
|
|
1543
|
+
// Modal *identity* signature — the question/button set only, NO volatile
|
|
1544
|
+
// counters. This is what the settle gate tracks: the FSM bumps
|
|
1545
|
+
// approvalEntrySeq on every fresh waiting_approval entry, and a
|
|
1546
|
+
// modal→generating→modal flap (the question line scrolled out of the
|
|
1547
|
+
// captured frame while the button block stays) re-enters and bumps it
|
|
1548
|
+
// again. Folding that seq into the settle signature made the 600ms
|
|
1549
|
+
// settle clock restart on every flap, so the modal never stayed stable
|
|
1550
|
+
// long enough to fire — the gate was never satisfied. Identity excludes
|
|
1551
|
+
// the seq so button/seq flap of the SAME modal keeps one settle clock.
|
|
1552
|
+
const modalSignature = [
|
|
1553
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1554
|
+
buttons.join('|'),
|
|
1555
|
+
buttonIndex,
|
|
1556
|
+
].join('::');
|
|
1557
|
+
// Busy-window re-entry guard still needs the seq: two DISTINCT
|
|
1558
|
+
// back-to-back approvals can carry identical message/buttons (common
|
|
1559
|
+
// with claude-cli). Without the seq their busy signatures collide and
|
|
1560
|
+
// the 5s busy-window guard below would swallow the second auto-approve,
|
|
1504
1561
|
// leaving it stuck. The seq is bumped by the FSM on every fresh
|
|
1505
|
-
// waiting_approval entry, so a new approval always yields a new
|
|
1562
|
+
// waiting_approval entry, so a new approval always yields a new busy
|
|
1506
1563
|
// signature and fires through.
|
|
1507
1564
|
const approvalEntrySeq = typeof adapterStatus?.approvalEntrySeq === 'number'
|
|
1508
1565
|
? adapterStatus.approvalEntrySeq
|
|
1509
1566
|
: 0;
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
].join('::');
|
|
1516
|
-
// Already fired for this exact modal and still inside the busy window —
|
|
1517
|
-
// nothing to do (re-entry guard for repeated snapshots of one modal).
|
|
1518
|
-
if (this.autoApproveBusy && signature === this.lastAutoApprovalSignature) {
|
|
1567
|
+
const busySignature = `${approvalEntrySeq}::${modalSignature}`;
|
|
1568
|
+
// Already fired for this exact modal entry and still inside the busy
|
|
1569
|
+
// window — nothing to do (re-entry guard for repeated snapshots of one
|
|
1570
|
+
// modal).
|
|
1571
|
+
if (this.autoApproveBusy && busySignature === this.lastAutoApprovalSignature) {
|
|
1519
1572
|
return autoApproveActive;
|
|
1520
1573
|
}
|
|
1521
1574
|
|
|
1522
|
-
// Settle gate: only fire once this
|
|
1575
|
+
// Settle gate: only fire once this modal identity has been stable for
|
|
1523
1576
|
// AUTO_APPROVE_SETTLE_MS. A still-streaming prompt mutates its
|
|
1524
|
-
// message/buttons each frame → new
|
|
1577
|
+
// message/buttons each frame → new identity → clock restarts, so we
|
|
1525
1578
|
// never approve a half-rendered prompt (the "resolves too fast" bug).
|
|
1526
|
-
if (
|
|
1527
|
-
this.pendingAutoApprovalSignature =
|
|
1579
|
+
if (modalSignature !== this.pendingAutoApprovalSignature) {
|
|
1580
|
+
this.pendingAutoApprovalSignature = modalSignature;
|
|
1528
1581
|
this.pendingAutoApprovalSince = now;
|
|
1529
1582
|
}
|
|
1530
1583
|
const settledForMs = now - this.pendingAutoApprovalSince;
|
|
@@ -1543,9 +1596,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1543
1596
|
// Settled — fire the approve key.
|
|
1544
1597
|
if (this.autoApproveSettleTimer) { clearTimeout(this.autoApproveSettleTimer); this.autoApproveSettleTimer = null; }
|
|
1545
1598
|
this.autoApproveBusy = true;
|
|
1546
|
-
this.lastAutoApprovalSignature =
|
|
1599
|
+
this.lastAutoApprovalSignature = busySignature;
|
|
1547
1600
|
this.pendingAutoApprovalSignature = '';
|
|
1548
1601
|
this.pendingAutoApprovalSince = 0;
|
|
1602
|
+
this.autoApproveInactiveSince = 0;
|
|
1549
1603
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
1550
1604
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
1551
1605
|
this.autoApproveBusy = false;
|
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
applyVisibleRegion,
|
|
29
29
|
type VisibleRegionSpec,
|
|
30
30
|
} from './visible-region.js';
|
|
31
|
+
import {
|
|
32
|
+
pickApprovalButton,
|
|
33
|
+
hasNegativeApprovalOption,
|
|
34
|
+
} from '../../../../approval-utils.js';
|
|
31
35
|
|
|
32
36
|
// ─── Primitive spec shapes (mirror the JSON schemas) ───────────────────
|
|
33
37
|
|
|
@@ -69,6 +73,7 @@ interface ModalSpec {
|
|
|
69
73
|
questionVariants?: Array<{ regex: string; flags?: string; label?: string }>;
|
|
70
74
|
buttonPattern: string;
|
|
71
75
|
buttonFlags?: string;
|
|
76
|
+
buttonLabelGroup?: number;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
export type DispatchGroup =
|
|
@@ -163,6 +168,48 @@ function settledPromptMatches(spec: SettledPromptSpec, input: CliStatusInput): b
|
|
|
163
168
|
return footers.every((f) => f.test(text));
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Extract approval-button labels from every line matching the modal spec's
|
|
173
|
+
* `buttonPattern`. Mirrors buildParseApprovalFromTui's per-line extraction but
|
|
174
|
+
* scans the whole screen with no question-line anchor, so the cue survives the
|
|
175
|
+
* question line scrolling out of the captured frame during long runs of
|
|
176
|
+
* consecutive approvals.
|
|
177
|
+
*/
|
|
178
|
+
function extractButtonLabels(spec: ModalSpec, text: string): string[] {
|
|
179
|
+
if (!text) return [];
|
|
180
|
+
const flags = spec.buttonFlags && spec.buttonFlags.includes('m')
|
|
181
|
+
? spec.buttonFlags
|
|
182
|
+
: `${spec.buttonFlags ?? ''}m`;
|
|
183
|
+
const buttonRe = compile(spec.buttonPattern, flags);
|
|
184
|
+
const labelGroup = Number.isInteger(spec.buttonLabelGroup) && (spec.buttonLabelGroup ?? 0) > 0
|
|
185
|
+
? spec.buttonLabelGroup!
|
|
186
|
+
: 1;
|
|
187
|
+
const out: string[] = [];
|
|
188
|
+
for (const line of text.split('\n')) {
|
|
189
|
+
buttonRe.lastIndex = 0;
|
|
190
|
+
const m = buttonRe.exec(line);
|
|
191
|
+
if (!m) continue;
|
|
192
|
+
const captured = m[labelGroup] ?? (labelGroup === 1 && m.length > 2 ? m[m.length - 1] : undefined);
|
|
193
|
+
if (captured && captured.trim()) out.push(captured.trim());
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* (fixB ①) The approval's selectable button block is itself a modal cue.
|
|
200
|
+
* Anchored on approval *verbs* so it generalizes across CLIs without trusting
|
|
201
|
+
* any single spec's `buttonPattern` specificity: a real approval modal offers
|
|
202
|
+
* BOTH an affirmative option (Yes/Allow/Continue/…) and a decline (No/Deny/
|
|
203
|
+
* Cancel/Skip/…). A generic numbered menu, a single prose "1. Yes …" line, or
|
|
204
|
+
* an assistant enumeration lacks that pair and does not fire the cue.
|
|
205
|
+
*/
|
|
206
|
+
function buttonBlockApprovalCue(spec: ModalSpec, text: string): boolean {
|
|
207
|
+
const labels = extractButtonLabels(spec, text);
|
|
208
|
+
if (labels.length < 2) return false;
|
|
209
|
+
if (pickApprovalButton(labels).index < 0) return false;
|
|
210
|
+
return hasNegativeApprovalOption(labels);
|
|
211
|
+
}
|
|
212
|
+
|
|
166
213
|
function modalMatches(spec: ModalSpec, input: CliStatusInput): boolean {
|
|
167
214
|
// Status-level modal detection is cue-only — does the question appear at all?
|
|
168
215
|
// Button extraction lives in buildParseApprovalFromTui.
|
|
@@ -173,6 +220,10 @@ function modalMatches(spec: ModalSpec, input: CliStatusInput): boolean {
|
|
|
173
220
|
const re = compile(variant.regex, variant.flags ?? 'i');
|
|
174
221
|
if (re.test(text)) return true;
|
|
175
222
|
}
|
|
223
|
+
// The question line can scroll out of the captured frame while the button
|
|
224
|
+
// block (and a residual spinner) remain. Hold the modal cue on the button
|
|
225
|
+
// block alone so waiting_approval does not flap to generating mid-approval.
|
|
226
|
+
if (buttonBlockApprovalCue(spec, text)) return true;
|
|
176
227
|
return false;
|
|
177
228
|
}
|
|
178
229
|
|