@adhdev/daemon-core 0.9.82-rc.357 → 0.9.82-rc.358
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 +28 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -16
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/evaluator.d.ts +21 -0
- package/package.json +2 -2
- package/src/mesh/mesh-events-coordinator.ts +19 -2
- package/src/providers/spec/cli-adapter.ts +13 -6
- package/src/providers/spec/evaluator.ts +41 -4
|
@@ -22,3 +22,24 @@ export declare function extractButtonsFromRule(rule: ExtractButtons, hay: string
|
|
|
22
22
|
key: string;
|
|
23
23
|
current: boolean;
|
|
24
24
|
}[];
|
|
25
|
+
/**
|
|
26
|
+
* Reduce a top→bottom-ordered list of parsed numbered entries to only the
|
|
27
|
+
* bottom-most contiguous block — the run whose indices descend by exactly 1
|
|
28
|
+
* scanning upward from the last entry.
|
|
29
|
+
*
|
|
30
|
+
* Picker and approval choices always render as the LAST contiguous numbered
|
|
31
|
+
* block at the bottom of the modal section. The conversation history above can
|
|
32
|
+
* carry its own stray "1./2./3." numbered lists (and blockquote `>` lines), and
|
|
33
|
+
* because section anchoring can pull body lines into the modal section, a naive
|
|
34
|
+
* top-down scan would bind the low option indices to those body lines and drop
|
|
35
|
+
* the real choices (the dashboard would show, and an arrow-key picker would
|
|
36
|
+
* commit, the wrong row). Selecting the bottom block makes the on-screen picker
|
|
37
|
+
* win regardless of body content. Since pickers number their options 1..N
|
|
38
|
+
* contiguously and bodies use indices ≥ 1, the upward chain always breaks the
|
|
39
|
+
* moment it would need a "0." above the picker's "1." — so the picker block is
|
|
40
|
+
* isolated cleanly. Entries are assumed already in screen order; the returned
|
|
41
|
+
* slice keeps that order.
|
|
42
|
+
*/
|
|
43
|
+
export declare function lastContiguousNumberedBlock<T extends {
|
|
44
|
+
index: number;
|
|
45
|
+
}>(entries: T[]): T[];
|
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.358",
|
|
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.358",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -1840,7 +1840,16 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1840
1840
|
// transcript and record the genuine completion once the worker truly finishes (commonly
|
|
1841
1841
|
// after a coordinator nudge / re-dispatch). A matched queue task, or a completion with
|
|
1842
1842
|
// genuine evidence, is marked terminal as before.
|
|
1843
|
-
|
|
1843
|
+
// WARMUPGAP: a no-taskId completion from a session that holds no active assignment is a
|
|
1844
|
+
// pre-assignment warmup / ghost event (a worker spawns, idles, and emits idle→generating→
|
|
1845
|
+
// completed before any task is dispatched, with meshActiveTaskId unset so the event carries
|
|
1846
|
+
// no taskId). Letting it through would hit the session_id fallback in updateDirectDispatchStatus
|
|
1847
|
+
// and flip a sibling/stale dispatch row this event does not own — the real task later lands on
|
|
1848
|
+
// a corrupted row and never reaches completed. Skip the dispatch update for that case. A
|
|
1849
|
+
// taskId-carrying completion (real task), or any completion whose session currently holds an
|
|
1850
|
+
// active assignment (legacy/relayed worker), still flips as before.
|
|
1851
|
+
const leaveDirectDispatchActive = (!task && opts?.tentativeIfDirect === true)
|
|
1852
|
+
|| (!eventTaskId && !sessionHasActiveAssignment(args.meshId, sessionId));
|
|
1844
1853
|
if (!leaveDirectDispatchActive) {
|
|
1845
1854
|
// CANON-B: flip the exact dispatch row the completion echoed its taskId for; the
|
|
1846
1855
|
// session_id fallback (no echoed taskId) still covers legacy/relayed workers.
|
|
@@ -1958,7 +1967,15 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1958
1967
|
// sibling must keep that row 'dispatched' so its own confirm can match it; acking
|
|
1959
1968
|
// by session would mark it 'acked' prematurely and hide a genuine non-delivery.
|
|
1960
1969
|
const startedTaskId = readNonEmptyString(args.metadataEvent.taskId) || undefined;
|
|
1961
|
-
|
|
1970
|
+
// WARMUPGAP: only ack a dispatch row when the event names its task, or the session
|
|
1971
|
+
// currently holds an active assignment. A no-taskId generating_started from an
|
|
1972
|
+
// unassigned session is a pre-assignment warmup — the session_id fallback would ack a
|
|
1973
|
+
// sibling/stale dispatch row this event does not own, marking it 'acked' prematurely and
|
|
1974
|
+
// hiding a genuine non-delivery. Skip the dispatch ack for that ghost case (the delivery
|
|
1975
|
+
// acks below are bound to actual deliveries and stay a no-op for a warmup session).
|
|
1976
|
+
if (startedTaskId || sessionHasActiveAssignment(args.meshId, sessionId)) {
|
|
1977
|
+
updateDirectDispatchStatus(args.meshId, sessionId, 'acked', startedTaskId);
|
|
1978
|
+
}
|
|
1962
1979
|
const activeDeliveries = ((): { id: string; taskId: string | null }[] => {
|
|
1963
1980
|
try { return MeshRuntimeStore.getInstance().getActiveSessionDeliveries(args.meshId, sessionId); }
|
|
1964
1981
|
catch { return []; }
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
21
|
import { FsmDriver, type DashboardEvent, type ISpecDriver } from './fsm-driver.js';
|
|
22
|
+
import { lastContiguousNumberedBlock } from './evaluator.js';
|
|
22
23
|
import { executeNativeHistory } from './native-history-executor.js';
|
|
23
24
|
import * as fs from 'node:fs';
|
|
24
25
|
import type { NativeHistoryConfig, Control, ControlAction } from './types.js';
|
|
@@ -521,21 +522,27 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
521
522
|
const ec = action.extract_choices;
|
|
522
523
|
if (!ec?.pattern) return [];
|
|
523
524
|
const text = this.readScreenSectionText(ec.section);
|
|
524
|
-
|
|
525
|
-
|
|
525
|
+
// Collect EVERY matching line in screen order with no top-down de-dup.
|
|
526
|
+
// The picker section can include conversation history above it (a stray
|
|
527
|
+
// "1./2./3." list, blockquote `>` lines); a `seen.has(idx)` first-wins
|
|
528
|
+
// scan would let those body lines claim the option indices and shadow
|
|
529
|
+
// the real choices — committing the wrong model under arrow-key nav.
|
|
530
|
+
const all: Array<{ index: number; label: string; current: boolean }> = [];
|
|
526
531
|
for (const rawLine of text.split('\n')) {
|
|
527
532
|
const line = rawLine.replace(/\r$/, '');
|
|
528
533
|
const m = new RegExp(ec.pattern, ec.flags ?? '').exec(line);
|
|
529
534
|
if (!m) continue;
|
|
530
535
|
const idx = Number(m[1]);
|
|
531
|
-
if (!Number.isFinite(idx) ||
|
|
536
|
+
if (!Number.isFinite(idx) || idx <= 0) continue;
|
|
532
537
|
const label = (m[2] ?? '').replace(/\s+/g, ' ').trim();
|
|
533
538
|
if (!label) continue;
|
|
534
539
|
const current = /^\s*[❯›>]/.test(line) || /[✔✓●]\s*$/.test(label);
|
|
535
|
-
|
|
536
|
-
out.push({ index: idx, label, current });
|
|
540
|
+
all.push({ index: idx, label, current });
|
|
537
541
|
}
|
|
538
|
-
|
|
542
|
+
// Real options are the bottom-most contiguous numbered block; this also
|
|
543
|
+
// confines the `current` cursor flag to that block so a body `>` line is
|
|
544
|
+
// never mistaken for the cursor row.
|
|
545
|
+
return lastContiguousNumberedBlock(all);
|
|
539
546
|
}
|
|
540
547
|
|
|
541
548
|
/** Live text of a named screen section (or the whole screen when no
|
|
@@ -338,7 +338,10 @@ export function extractButtonsFromRule(
|
|
|
338
338
|
label += ' ' + next.trim();
|
|
339
339
|
j += 1;
|
|
340
340
|
}
|
|
341
|
-
|
|
341
|
+
// No top-down de-dup here: a stray body "1." above the modal would
|
|
342
|
+
// otherwise claim the index and shadow the real choice. Collect
|
|
343
|
+
// every match in screen order and let lastContiguousNumberedBlock
|
|
344
|
+
// pick the bottom-most option block below.
|
|
342
345
|
const key = keyTemplate.replace(/\{index\}/g, String(idx));
|
|
343
346
|
buttons.push({ index: idx, label, key, current });
|
|
344
347
|
i = j - 1;
|
|
@@ -350,17 +353,51 @@ export function extractButtonsFromRule(
|
|
|
350
353
|
const idx = Number(m[1]);
|
|
351
354
|
const label = String(m[2] ?? '').trim();
|
|
352
355
|
if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
|
|
353
|
-
if (buttons.some(b => b.index === idx)) continue;
|
|
354
356
|
const key = keyTemplate.replace(/\{index\}/g, String(idx));
|
|
355
357
|
// The matched text begins at the cursor marker (the pattern's
|
|
356
358
|
// optional `[❯›>]` prefix); flag this row as the cursor's current
|
|
357
359
|
// position so `select_mode: 'arrow_keys'` can step from it.
|
|
360
|
+
// Like the continuation path, no top-down de-dup — body numbered
|
|
361
|
+
// lines are filtered out by the bottom-block selection below.
|
|
358
362
|
buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
|
|
359
363
|
}
|
|
360
364
|
}
|
|
361
365
|
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
// Buttons were collected in screen (top→bottom) order. The real choices are
|
|
367
|
+
// the bottom-most contiguous numbered block (the modal/picker always renders
|
|
368
|
+
// them last); reduce to that block so conversation-history numbered lists
|
|
369
|
+
// pulled into the modal section can never be mistaken for options.
|
|
370
|
+
const block = lastContiguousNumberedBlock(buttons);
|
|
371
|
+
block.sort((a, b) => a.index - b.index);
|
|
372
|
+
return block;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Reduce a top→bottom-ordered list of parsed numbered entries to only the
|
|
377
|
+
* bottom-most contiguous block — the run whose indices descend by exactly 1
|
|
378
|
+
* scanning upward from the last entry.
|
|
379
|
+
*
|
|
380
|
+
* Picker and approval choices always render as the LAST contiguous numbered
|
|
381
|
+
* block at the bottom of the modal section. The conversation history above can
|
|
382
|
+
* carry its own stray "1./2./3." numbered lists (and blockquote `>` lines), and
|
|
383
|
+
* because section anchoring can pull body lines into the modal section, a naive
|
|
384
|
+
* top-down scan would bind the low option indices to those body lines and drop
|
|
385
|
+
* the real choices (the dashboard would show, and an arrow-key picker would
|
|
386
|
+
* commit, the wrong row). Selecting the bottom block makes the on-screen picker
|
|
387
|
+
* win regardless of body content. Since pickers number their options 1..N
|
|
388
|
+
* contiguously and bodies use indices ≥ 1, the upward chain always breaks the
|
|
389
|
+
* moment it would need a "0." above the picker's "1." — so the picker block is
|
|
390
|
+
* isolated cleanly. Entries are assumed already in screen order; the returned
|
|
391
|
+
* slice keeps that order.
|
|
392
|
+
*/
|
|
393
|
+
export function lastContiguousNumberedBlock<T extends { index: number }>(entries: T[]): T[] {
|
|
394
|
+
if (entries.length <= 1) return entries.slice();
|
|
395
|
+
let start = entries.length - 1;
|
|
396
|
+
for (let i = entries.length - 1; i > 0; i -= 1) {
|
|
397
|
+
if (entries[i - 1].index === entries[i].index - 1) start = i - 1;
|
|
398
|
+
else break;
|
|
399
|
+
}
|
|
400
|
+
return entries.slice(start);
|
|
364
401
|
}
|
|
365
402
|
|
|
366
403
|
/** True when a button line carries a TUI cursor marker (`❯`, `›`, `>`) before
|