@adhdev/daemon-core 0.9.82-rc.454 → 0.9.82-rc.456
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-refine.d.ts +137 -0
- package/dist/commands/router-worktree-cleanup.d.ts +133 -0
- package/dist/commands/router.d.ts +103 -151
- package/dist/index.js +3043 -2882
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3043 -2882
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-event-classify.d.ts +2 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +20 -0
- package/dist/providers/approval-utils.d.ts +25 -0
- package/dist/providers/cli-provider-instance.d.ts +26 -1
- package/dist/providers/manual-attendance.d.ts +16 -0
- package/dist/providers/provider-instance.d.ts +8 -1
- package/package.json +3 -3
- package/src/commands/handler.ts +6 -3
- package/src/commands/med-family/cli-agent.ts +26 -23
- package/src/commands/router-refine.ts +1698 -0
- package/src/commands/router-worktree-cleanup.ts +870 -0
- package/src/commands/router.ts +59 -2452
- package/src/mesh/mesh-active-work.ts +11 -2
- package/src/mesh/mesh-event-classify.ts +22 -0
- package/src/mesh/mesh-event-forwarding.ts +36 -0
- package/src/mesh/mesh-queue-assignment.ts +38 -11
- package/src/mesh/mesh-reconcile-loop.ts +174 -4
- package/src/mesh/mesh-runtime-store.ts +27 -13
- package/src/mesh/worktree-bootstrap-config.ts +19 -0
- package/src/providers/approval-utils.ts +42 -0
- package/src/providers/cli-provider-instance.ts +87 -6
- package/src/providers/manual-attendance.ts +20 -0
- package/src/providers/provider-instance.ts +6 -1
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +13 -2
|
@@ -3,3 +3,5 @@ export declare const EVENT_TO_LEDGER_KIND: Record<string, MeshLedgerKind>;
|
|
|
3
3
|
export declare function isMeshCoordinatorEvent(eventName: unknown): eventName is string;
|
|
4
4
|
export declare const MESH_FORCE_INJECT_EVENTS: ReadonlySet<string>;
|
|
5
5
|
export declare function shouldForceInjectMeshEvent(eventName: unknown): boolean;
|
|
6
|
+
export declare const MESH_APPROVAL_EVENTS: ReadonlySet<string>;
|
|
7
|
+
export declare function isMeshApprovalEvent(eventName: unknown): boolean;
|
|
@@ -38,6 +38,26 @@ export declare function isWorktreeBootstrapStaleRunning(node: {
|
|
|
38
38
|
};
|
|
39
39
|
workspace?: string;
|
|
40
40
|
} | undefined, nowMs?: number): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* COMPLETION-PROPAGATION F7 (C2): the single shared consume-ready / bootstrap-pending defer
|
|
43
|
+
* predicate. A task must NOT be injected into a worktree node whose bootstrap is still 'running'
|
|
44
|
+
* — the provider is not yet ready to consume input, so the inject lands in the input buffer and
|
|
45
|
+
* is silently swallowed (empty session). Both the remote dispatch guard (the router agent_command
|
|
46
|
+
* handler) and the local queue-claim gate (tryAssignQueueTask) route through THIS predicate so
|
|
47
|
+
* they agree on exactly when to defer. Returns true = defer. The stale-'running' backstop
|
|
48
|
+
* (isWorktreeBootstrapStaleRunning) is honored here too: a 'running' state far older than any real
|
|
49
|
+
* bootstrap whose worktree is git-clean is treated as silently complete (do NOT defer), so a node
|
|
50
|
+
* whose terminal stamp never reached this daemon is not stranded forever.
|
|
51
|
+
*/
|
|
52
|
+
export declare function shouldDeferDispatchForBootstrap(node: {
|
|
53
|
+
worktreeBootstrap?: {
|
|
54
|
+
status?: string;
|
|
55
|
+
startedAt?: string;
|
|
56
|
+
updatedAt?: string;
|
|
57
|
+
completedAt?: string;
|
|
58
|
+
};
|
|
59
|
+
workspace?: string;
|
|
60
|
+
} | undefined, nowMs?: number): boolean;
|
|
41
61
|
export interface WorktreeBootstrapConfigLoadResult {
|
|
42
62
|
config?: RepoMeshWorktreeBootstrapConfig;
|
|
43
63
|
source: string;
|
|
@@ -6,6 +6,31 @@ import type { ProviderModule } from './contracts.js';
|
|
|
6
6
|
* decline, which distinguishes it from a generic numbered menu or prose list.
|
|
7
7
|
*/
|
|
8
8
|
export declare function hasNegativeApprovalOption(buttons: string[] | null | undefined): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* True when a button reliably identifies a tool-CONSENT modal on its own — a
|
|
11
|
+
* scoped permission-grant affirmative such as:
|
|
12
|
+
* - "Yes, allow all edits in tmp/ during this session"
|
|
13
|
+
* - "Yes, and don't ask again for example.com"
|
|
14
|
+
* - "Yes, allow reading from etc/ from this project"
|
|
15
|
+
* - "Always allow"
|
|
16
|
+
*
|
|
17
|
+
* These options only ever appear in a genuine approval/permission prompt; a
|
|
18
|
+
* /model or /mode picker ("1. Default 2. Opus 3. Sonnet") never offers a
|
|
19
|
+
* "grant this scope" choice. They therefore serve as a SECOND reliable
|
|
20
|
+
* structural anchor alongside {@link hasNegativeApprovalOption}.
|
|
21
|
+
*
|
|
22
|
+
* Why this exists (tall-diff fallback, #137): when a Write/Edit diff is tall,
|
|
23
|
+
* the trailing decline option ("3. No") can scroll off the bottom of the
|
|
24
|
+
* captured PTY frame, leaving only "1. Yes" + "2. Yes, allow … this session".
|
|
25
|
+
* hasNegativeApprovalOption then reads false and the auto-approve gate bails —
|
|
26
|
+
* a delegated worker sits forever on a modal it could safely have approved. The
|
|
27
|
+
* grant-scope affirmative lets the gate recognize the consent modal WITHOUT
|
|
28
|
+
* seeing the off-frame decline. The gate still selects the plain "Yes"
|
|
29
|
+
* (allow-once) via pickApprovalButton, never the broader grant, and the settle
|
|
30
|
+
* gate still requires a stable modal — so a half-rendered frame never fires.
|
|
31
|
+
* Kept deliberately narrow so no picker/confirm modal can trip it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasReliableApprovalAffirmative(buttons: string[] | null | undefined): boolean;
|
|
9
34
|
export declare function getApprovalPositiveHints(provider?: Pick<ProviderModule, 'approvalPositiveHints'> | null): string[];
|
|
10
35
|
export declare function pickApprovalButton(buttons: string[] | null | undefined, provider?: Pick<ProviderModule, 'approvalPositiveHints'> | null): {
|
|
11
36
|
index: number;
|
|
@@ -115,6 +115,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
115
115
|
private autoApproveSettleTimer;
|
|
116
116
|
private autoApproveInactiveSince;
|
|
117
117
|
private autoApproveMaskSince;
|
|
118
|
+
private stalledApprovalNudgeEpisode;
|
|
118
119
|
private readonly manualAttendance;
|
|
119
120
|
private controlValues;
|
|
120
121
|
private summaryMetadata;
|
|
@@ -355,7 +356,9 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
355
356
|
get cliName(): string;
|
|
356
357
|
private shouldAutoApprove;
|
|
357
358
|
/** @see ProviderInstance.noteManualInteraction */
|
|
358
|
-
noteManualInteraction(now?: number
|
|
359
|
+
noteManualInteraction(now?: number, opts?: {
|
|
360
|
+
passive?: boolean;
|
|
361
|
+
}): void;
|
|
359
362
|
/**
|
|
360
363
|
* Whether auto-approve should be treated as active *right now* for display
|
|
361
364
|
* and firing decisions: the configured intent AND the user is not currently
|
|
@@ -366,6 +369,28 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
366
369
|
*/
|
|
367
370
|
private autoApproveEffectivelyActive;
|
|
368
371
|
private autoApproveMaskStalled;
|
|
372
|
+
/**
|
|
373
|
+
* NOTIF-APPROVAL-MASKED (Q1b): surface a delegated worker's STALLED auto-approve modal
|
|
374
|
+
* to the mesh COORDINATOR, decoupled from the dashboard visible-status mask.
|
|
375
|
+
*
|
|
376
|
+
* When auto-approve is configured but the episode never settles (modal parse miss / the
|
|
377
|
+
* settle gate never satisfied), getState()/detectStatusTransition() fold the raw
|
|
378
|
+
* `waiting_approval` into `generating` to suppress dashboard flicker — so
|
|
379
|
+
* detectStatusTransition()'s `waiting_approval` arm never runs and NO agent:waiting_approval
|
|
380
|
+
* event is emitted. The coordinator's real-time approval-nudge delivery then has no input and
|
|
381
|
+
* the worker's stuck modal is never surfaced (the live ~25s stall). The dashboard mask is
|
|
382
|
+
* intentional and stays; this emits the coordinator nudge exactly ONCE, gated on the SAME
|
|
383
|
+
* raw-waiting_approval + mask-stalled signal resolveModalParkStatus() distinguishes, the
|
|
384
|
+
* instant the mask-stall threshold trips (the same moment getState un-folds the mask).
|
|
385
|
+
*
|
|
386
|
+
* Only delegated worker sessions qualify: a foreground session has no coordinator to notify,
|
|
387
|
+
* and its own dashboard mask already reveals the modal on stall. A normally-resolving
|
|
388
|
+
* auto-approve never reaches AUTO_APPROVE_MASK_STALL_MS, so it emits nothing here; and if a
|
|
389
|
+
* masked approval clears just as this fires, rc.455's isApprovalNudgeResolved stale-drop
|
|
390
|
+
* discards the nudge coordinator-side without noise. Dedup is per-episode (keyed on the
|
|
391
|
+
* mask-clock value) so a modal that flaps between parsed/unparsed states is announced once.
|
|
392
|
+
*/
|
|
393
|
+
private maybeEmitStalledApprovalNudge;
|
|
369
394
|
private recordAutoApproval;
|
|
370
395
|
recordApprovalSelection(buttonText: string): void;
|
|
371
396
|
private formatMarkerTimestamp;
|
|
@@ -61,3 +61,19 @@ export declare class ManualAttendanceTracker {
|
|
|
61
61
|
* pure read commands (read_chat / list_chats — passive polling, not driving).
|
|
62
62
|
*/
|
|
63
63
|
export declare const MANUAL_ATTENDANCE_COMMANDS: ReadonlySet<string>;
|
|
64
|
+
/**
|
|
65
|
+
* The subset of {@link MANUAL_ATTENDANCE_COMMANDS} that are PASSIVE view-only
|
|
66
|
+
* actions — foregrounding a session's tab / opening its panel. They convey "I am
|
|
67
|
+
* looking at this session", not "I am driving it", and carry no user input.
|
|
68
|
+
*
|
|
69
|
+
* For a foreground (base-node) session these still attend: a user who
|
|
70
|
+
* foregrounds their own session should get the quiet window so an incoming
|
|
71
|
+
* approval stays visible for them to act on. But for a DELEGATED worker session
|
|
72
|
+
* a passive peek must NOT attend — a coordinator merely opening a worker's panel
|
|
73
|
+
* to watch progress would otherwise suppress that worker's delegated
|
|
74
|
+
* auto-approve for the whole window (secondary cause, #137). The per-instance
|
|
75
|
+
* hook decides: it drops a passive stamp only when the session is a delegated
|
|
76
|
+
* worker, so explicit input (controlbar / resolve_action / pty_input) still
|
|
77
|
+
* attends a worker and a foreground session is unaffected.
|
|
78
|
+
*/
|
|
79
|
+
export declare const MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS: ReadonlySet<string>;
|
|
@@ -200,8 +200,15 @@ export interface ProviderInstance {
|
|
|
200
200
|
* input). Provider-common signal that suppresses auto-approve for a short
|
|
201
201
|
* window so the user can drive the session manually; background mesh worker
|
|
202
202
|
* sessions never receive it, so their delegated auto-approve is unaffected.
|
|
203
|
+
*
|
|
204
|
+
* `opts.passive` marks a view-only action (select_session / open_panel). A
|
|
205
|
+
* delegated worker session ignores passive stamps so a coordinator merely
|
|
206
|
+
* watching its panel does not suppress its delegated auto-approve; explicit
|
|
207
|
+
* input still attends. Foreground sessions attend on passive views too.
|
|
203
208
|
*/
|
|
204
|
-
noteManualInteraction?(now?: number
|
|
209
|
+
noteManualInteraction?(now?: number, opts?: {
|
|
210
|
+
passive?: boolean;
|
|
211
|
+
}): void;
|
|
205
212
|
/** cleanup */
|
|
206
213
|
dispose(): void;
|
|
207
214
|
}
|
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.456",
|
|
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,8 +46,8 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
50
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.456",
|
|
50
|
+
"@adhdev/session-host-core": "0.9.82-rc.456",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
53
53
|
"ajv-formats": "^3.0.1",
|
package/src/commands/handler.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { reconcileIdeRuntimeSessions } from '../sessions/reconcile.js';
|
|
|
26
26
|
import { LOG } from '../logging/logger.js';
|
|
27
27
|
import { resolveLegacyProviderScript, type LegacyStringScript } from './provider-script-resolver.js';
|
|
28
28
|
import { sha256Hex } from '../system/hash.js';
|
|
29
|
-
import { MANUAL_ATTENDANCE_COMMANDS } from '../providers/manual-attendance.js';
|
|
29
|
+
import { MANUAL_ATTENDANCE_COMMANDS, MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS } from '../providers/manual-attendance.js';
|
|
30
30
|
|
|
31
31
|
// Sub-module imports
|
|
32
32
|
import * as Chat from './chat-commands.js';
|
|
@@ -393,15 +393,18 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
393
393
|
*/
|
|
394
394
|
private noteManualAttendanceIfApplicable(cmd: string, args: any): void {
|
|
395
395
|
if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
|
|
396
|
+
// Passive view-only actions (select_session / open_panel) attend a
|
|
397
|
+
// foreground session but NOT a delegated worker — the instance decides.
|
|
398
|
+
const passive = MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS.has(cmd);
|
|
396
399
|
const sessionId = this._currentRoute.session?.sessionId
|
|
397
400
|
|| (typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '');
|
|
398
401
|
if (!sessionId) return;
|
|
399
402
|
const session = this._ctx.sessionRegistry?.get(sessionId);
|
|
400
403
|
const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
|
|
401
404
|
const instance = this._ctx.instanceManager?.getInstance(instanceKey) as
|
|
402
|
-
{ noteManualInteraction?: (now?: number) => void } | undefined;
|
|
405
|
+
{ noteManualInteraction?: (now?: number, opts?: { passive?: boolean }) => void } | undefined;
|
|
403
406
|
try {
|
|
404
|
-
instance?.noteManualInteraction?.();
|
|
407
|
+
instance?.noteManualInteraction?.(undefined, { passive });
|
|
405
408
|
} catch {
|
|
406
409
|
// attendance is best-effort — never block command dispatch
|
|
407
410
|
}
|
|
@@ -114,32 +114,35 @@ export const cliAgentHandlers: Record<string, MedFamilyHandler> = {
|
|
|
114
114
|
if (isSendChat && dispatchNodeId && dispatchMeshId) {
|
|
115
115
|
try {
|
|
116
116
|
const { getMesh } = await import('../../config/mesh-config.js');
|
|
117
|
-
|
|
117
|
+
// COMPLETION-PROPAGATION F6 (C1 SSOT): read the router's synchronous inline mesh
|
|
118
|
+
// cache FIRST, falling back to getMesh only when the inline view has nothing. The
|
|
119
|
+
// inline cache is the authoritative bootstrap-status source — markWorktreeBootstrapTerminalState
|
|
120
|
+
// stamps 'complete'/'failed' into it SYNCHRONOUSLY, then persists to local config
|
|
121
|
+
// (what getMesh reads) via a DETACHED async import chain that lags. Reading getMesh
|
|
122
|
+
// first therefore observed a stale 'running' and deferred a dispatch whose worktree
|
|
123
|
+
// was already bootstrapped — the stale-'running' defer this eliminates. Inline-first
|
|
124
|
+
// makes the freshest single source of truth win for both the running and terminal states.
|
|
125
|
+
const meshObj = ctx.getCachedInlineMesh(dispatchMeshId) ?? getMesh(dispatchMeshId);
|
|
118
126
|
const nodeObj = Array.isArray(meshObj?.nodes)
|
|
119
127
|
? meshObj.nodes.find((n: any) => meshNodeIdMatches(n, dispatchNodeId))
|
|
120
128
|
: undefined;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
...(readStringValue(meshCtx?.taskId) ? { taskId: readStringValue(meshCtx?.taskId) } : {}),
|
|
139
|
-
error: `Node '${dispatchNodeId}' worktree bootstrap is still running; a task injected now would land in the session input buffer before the provider is ready to consume it and be silently lost. Dispatch deferred.`,
|
|
140
|
-
nextAction: 'Wait for the worktree_bootstrap_complete event (or poll mesh_status until the node session is ready), then re-send the task with mesh_send_task. Alternatively use mesh_enqueue_task so the queue auto-assigns it once a ready session is available.',
|
|
141
|
-
};
|
|
142
|
-
}
|
|
129
|
+
// COMPLETION-PROPAGATION F7 (C2): the single shared consume-ready/bootstrap-pending
|
|
130
|
+
// defer predicate (honors the stale-'running' git-clean backstop internally), reused
|
|
131
|
+
// by the local queue-claim gate too so remote and local dispatch agree on when to defer.
|
|
132
|
+
const { shouldDeferDispatchForBootstrap } = await import('../../mesh/worktree-bootstrap-config.js');
|
|
133
|
+
if (shouldDeferDispatchForBootstrap(nodeObj as any)) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
recoverable: true,
|
|
137
|
+
dispatched: false,
|
|
138
|
+
code: 'mesh_node_bootstrap_pending',
|
|
139
|
+
reason: 'bootstrap_still_running',
|
|
140
|
+
nodeId: dispatchNodeId,
|
|
141
|
+
meshId: dispatchMeshId,
|
|
142
|
+
...(readStringValue(meshCtx?.taskId) ? { taskId: readStringValue(meshCtx?.taskId) } : {}),
|
|
143
|
+
error: `Node '${dispatchNodeId}' worktree bootstrap is still running; a task injected now would land in the session input buffer before the provider is ready to consume it and be silently lost. Dispatch deferred.`,
|
|
144
|
+
nextAction: 'Wait for the worktree_bootstrap_complete event (or poll mesh_status until the node session is ready), then re-send the task with mesh_send_task. Alternatively use mesh_enqueue_task so the queue auto-assigns it once a ready session is available.',
|
|
145
|
+
};
|
|
143
146
|
}
|
|
144
147
|
} catch { /* best-effort — if the bootstrap probe fails, fall through and dispatch */ }
|
|
145
148
|
}
|