@adhdev/daemon-core 0.9.82-rc.352 → 0.9.82-rc.354
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/handler.d.ts +15 -0
- package/dist/commands/router.d.ts +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +558 -364
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +555 -364
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +3 -0
- package/dist/providers/cli-provider-instance.d.ts +12 -0
- package/dist/providers/manual-attendance.d.ts +63 -0
- package/dist/providers/provider-instance.d.ts +8 -0
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +33 -6
- package/src/commands/handler.ts +32 -0
- package/src/commands/router.ts +133 -33
- package/src/git/git-diff.ts +31 -14
- package/src/index.ts +5 -0
- package/src/providers/acp-provider-instance.ts +18 -1
- package/src/providers/cli-provider-instance.ts +54 -3
- package/src/providers/manual-attendance.ts +85 -0
- package/src/providers/provider-instance.ts +9 -0
|
@@ -51,6 +51,7 @@ import { normalizeContent, flattenContent, normalizeInputEnvelope } from './cont
|
|
|
51
51
|
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
52
52
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext, SessionModalState } from './provider-instance.js';
|
|
53
53
|
import { StatusMonitor } from './status-monitor.js';
|
|
54
|
+
import { ManualAttendanceTracker } from './manual-attendance.js';
|
|
54
55
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
55
56
|
import { workingDirBasename } from './working-dir.js';
|
|
56
57
|
import {
|
|
@@ -845,7 +846,12 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
845
846
|
}
|
|
846
847
|
|
|
847
848
|
// ─── Auto-approve: skip user confirmation ───
|
|
848
|
-
|
|
849
|
+
// Held while a human is actively attending this session (manual
|
|
850
|
+
// attendance) so they can decide the permission themselves; falls
|
|
851
|
+
// through to the waiting_approval manual path below. A background
|
|
852
|
+
// worker is never attended, so its delegated auto-approve fires
|
|
853
|
+
// as before.
|
|
854
|
+
if (this.settings.autoApprove !== false && !this.manualAttendance.isAttended()) {
|
|
849
855
|
const toolTitle = tc.title || tc.toolCallId || 'tool call';
|
|
850
856
|
this.log.info(`[${this.type}] Auto-approving: ${toolTitle}`);
|
|
851
857
|
this.appendSystemMessage(`Auto-approved: ${toolTitle}`);
|
|
@@ -1128,6 +1134,17 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1128
1134
|
|
|
1129
1135
|
private permissionResolvers: ((approved: boolean) => void)[] = [];
|
|
1130
1136
|
|
|
1137
|
+
// Provider-common manual-attendance signal: while a human is actively driving
|
|
1138
|
+
// this session from the dashboard, auto-approve holds so they can decide on
|
|
1139
|
+
// the permission request themselves. Background workers are never attended →
|
|
1140
|
+
// delegated auto-approve is unaffected.
|
|
1141
|
+
private readonly manualAttendance = new ManualAttendanceTracker();
|
|
1142
|
+
|
|
1143
|
+
/** @see ProviderInstance.noteManualInteraction */
|
|
1144
|
+
noteManualInteraction(now = Date.now()): void {
|
|
1145
|
+
this.manualAttendance.note(now);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1131
1148
|
async resolvePermission(approved: boolean): Promise<void> {
|
|
1132
1149
|
const resolver = this.permissionResolvers.shift();
|
|
1133
1150
|
if (resolver) {
|
|
@@ -29,6 +29,7 @@ import { mergeProviderPatchState, resolveProviderStateSurface } from './provider
|
|
|
29
29
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
30
30
|
import { buildChatMessage, buildRuntimeSystemChatMessage, isUserFacingChatMessage, normalizeChatMessages, resolveChatMessageKind, extractFinalSummaryFromMessages } from './chat-message-normalization.js';
|
|
31
31
|
import { workingDirBasename } from './working-dir.js';
|
|
32
|
+
import { ManualAttendanceTracker } from './manual-attendance.js';
|
|
32
33
|
|
|
33
34
|
type PersistableCliHistoryMessage = {
|
|
34
35
|
role: string;
|
|
@@ -416,6 +417,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
416
417
|
// a settle gate was in progress. Drives AUTO_APPROVE_GATE_HYSTERESIS_MS so a
|
|
417
418
|
// brief generating flip does not immediately wipe the settle clock.
|
|
418
419
|
private autoApproveInactiveSince = 0;
|
|
420
|
+
// Provider-common manual-attendance signal: while a human is actively driving
|
|
421
|
+
// this session from the dashboard, auto-approve holds so they can take manual
|
|
422
|
+
// control. Background mesh workers are never attended → delegated auto-approve
|
|
423
|
+
// is unaffected.
|
|
424
|
+
private readonly manualAttendance = new ManualAttendanceTracker();
|
|
419
425
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
420
426
|
private summaryMetadata: unknown = undefined;
|
|
421
427
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -828,7 +834,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
834
|
|
|
829
835
|
getHotChatSessionState(): HotChatSessionState {
|
|
830
836
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
831
|
-
const autoApproveActive = adapterStatus.status
|
|
837
|
+
const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status);
|
|
832
838
|
const autoApproveHoldIdle = this.autoApproveBusy && adapterStatus.status === 'idle';
|
|
833
839
|
const visibleStatus = autoApproveActive || autoApproveHoldIdle ? 'generating' : adapterStatus.status;
|
|
834
840
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
@@ -844,7 +850,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
844
850
|
|
|
845
851
|
getSessionModalState(sessionId?: string): SessionModalState {
|
|
846
852
|
const adapterStatus = this.adapter.getStatus({ allowParse: true });
|
|
847
|
-
const autoApproveActive = adapterStatus.status
|
|
853
|
+
const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status);
|
|
848
854
|
const autoApproveHoldIdle = this.autoApproveBusy && adapterStatus.status === 'idle';
|
|
849
855
|
const visibleStatus = autoApproveActive || autoApproveHoldIdle ? 'generating' : adapterStatus.status;
|
|
850
856
|
const dirName = workingDirBasename(this.workingDir);
|
|
@@ -947,7 +953,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
947
953
|
} catch {
|
|
948
954
|
return null;
|
|
949
955
|
}
|
|
950
|
-
|
|
956
|
+
// A session whose auto-approve is held by manual attendance IS parked on a
|
|
957
|
+
// modal awaiting the human — autoApproveEffectivelyActive folds that in, so
|
|
958
|
+
// the mesh force-inject guard correctly treats it as modal-parked.
|
|
959
|
+
if (adapterStatus.status === 'waiting_approval' && !this.autoApproveEffectivelyActive(adapterStatus.status)) {
|
|
951
960
|
return 'waiting_approval';
|
|
952
961
|
}
|
|
953
962
|
return null;
|
|
@@ -1481,6 +1490,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1481
1490
|
}
|
|
1482
1491
|
|
|
1483
1492
|
private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
|
|
1493
|
+
// Manual-attendance suppression (provider-common): when a human is
|
|
1494
|
+
// actively driving this session from the dashboard, hold auto-approve so
|
|
1495
|
+
// the modal stays visible and they can pick a button / use the controlbar
|
|
1496
|
+
// themselves. Return false (NOT auto-approving) so getState keeps the
|
|
1497
|
+
// modal surfaced. Clear any in-progress settle gate — a genuine fire
|
|
1498
|
+
// after the window lapses must re-settle from scratch — and arm a
|
|
1499
|
+
// re-check for the lapse moment, because the PTY may have gone silent and
|
|
1500
|
+
// would otherwise never re-drive this decision. Background mesh workers
|
|
1501
|
+
// are never attended, so their delegated auto-approve is untouched.
|
|
1502
|
+
if (adapterStatus?.status === 'waiting_approval'
|
|
1503
|
+
&& this.shouldAutoApprove()
|
|
1504
|
+
&& this.manualAttendance.isAttended(now)) {
|
|
1505
|
+
this.lastAutoApprovalSignature = '';
|
|
1506
|
+
this.pendingAutoApprovalSignature = '';
|
|
1507
|
+
this.pendingAutoApprovalSince = 0;
|
|
1508
|
+
this.autoApproveInactiveSince = 0;
|
|
1509
|
+
if (this.autoApproveSettleTimer) clearTimeout(this.autoApproveSettleTimer);
|
|
1510
|
+
this.autoApproveSettleTimer = setTimeout(() => {
|
|
1511
|
+
this.autoApproveSettleTimer = null;
|
|
1512
|
+
this.recheckAutoApproveSettled();
|
|
1513
|
+
}, this.manualAttendance.remainingMs(now) + 20);
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1484
1516
|
const autoApproveActive = adapterStatus?.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
1485
1517
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
1486
1518
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
@@ -2096,6 +2128,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
2096
2128
|
return false;
|
|
2097
2129
|
}
|
|
2098
2130
|
|
|
2131
|
+
/** @see ProviderInstance.noteManualInteraction */
|
|
2132
|
+
noteManualInteraction(now = Date.now()): void {
|
|
2133
|
+
this.manualAttendance.note(now);
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* Whether auto-approve should be treated as active *right now* for display
|
|
2138
|
+
* and firing decisions: the configured intent AND the user is not currently
|
|
2139
|
+
* attending this session by hand. When a human is attending, auto-approve is
|
|
2140
|
+
* held so the modal stays visible and they can drive it via the controlbar.
|
|
2141
|
+
* Provider-agnostic — the attendance signal is the command set, never any
|
|
2142
|
+
* CLI-specific modal text.
|
|
2143
|
+
*/
|
|
2144
|
+
private autoApproveEffectivelyActive(status: string | undefined, now = Date.now()): boolean {
|
|
2145
|
+
return status === 'waiting_approval'
|
|
2146
|
+
&& this.shouldAutoApprove()
|
|
2147
|
+
&& !this.manualAttendance.isAttended(now);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2099
2150
|
private recordAutoApproval(modalMessage?: string, buttonLabel?: string, now = Date.now()): void {
|
|
2100
2151
|
this.appendRuntimeSystemMessage(
|
|
2101
2152
|
formatAutoApprovalMessage(modalMessage, buttonLabel),
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ManualAttendanceTracker — provider-agnostic "is a human driving this session
|
|
3
|
+
* right now" signal, used to suppress auto-approve while the user is taking
|
|
4
|
+
* manual control of a session from the dashboard.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists
|
|
7
|
+
* ---------------
|
|
8
|
+
* When `autoApprove` is on, an approval modal is auto-dismissed within a few
|
|
9
|
+
* hundred ms of appearing. For a background mesh worker that is exactly the
|
|
10
|
+
* desired delegated behavior. But for a session the user is actively watching
|
|
11
|
+
* and operating (a base-node / foreground session), the auto-fire closes the
|
|
12
|
+
* modal before the human can pick a button — and likewise fights their use of
|
|
13
|
+
* the controlbar. The fix is to give the human a short quiet window: while they
|
|
14
|
+
* are attending the session by hand, auto-approve holds; once they go idle it
|
|
15
|
+
* resumes.
|
|
16
|
+
*
|
|
17
|
+
* The tracker holds only a timestamp. The *signal* — which commands count as
|
|
18
|
+
* "a human attending" — is decided by the caller (the command handler), and is
|
|
19
|
+
* the same set for every provider: foreground tab selection (select_session /
|
|
20
|
+
* open_panel), controlbar use (invoke_provider_script / set_mode / change_model
|
|
21
|
+
* / set_thought_level), manual approval (resolve_action) and manual terminal
|
|
22
|
+
* input (pty_input). Notably NOT send_chat, which is also how a coordinator
|
|
23
|
+
* delegates a task to a worker — counting it would wrongly suppress the
|
|
24
|
+
* worker's delegated auto-approve.
|
|
25
|
+
*
|
|
26
|
+
* Because a background worker never receives any of those attending commands,
|
|
27
|
+
* it is never "attended", so its delegated auto-approve is unaffected. The
|
|
28
|
+
* mechanism is therefore provider-common AND preserves worker auto-approve
|
|
29
|
+
* without any per-provider branching.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* How long after the last manual interaction auto-approve stays suppressed.
|
|
34
|
+
*
|
|
35
|
+
* Trade-off: long enough that after foregrounding a session's tab the user has
|
|
36
|
+
* a realistic chance to act on an incoming approval (the auto-approve settle
|
|
37
|
+
* window is only ~600ms, so a human cannot out-race it), yet short enough that
|
|
38
|
+
* a session left unattended — e.g. a worker tab the user briefly peeked at —
|
|
39
|
+
* resumes auto-approving within about a minute. Re-armed on every attending
|
|
40
|
+
* command, so a user who keeps interacting keeps the window fresh.
|
|
41
|
+
*/
|
|
42
|
+
export const AUTO_APPROVE_MANUAL_ATTENDANCE_SUPPRESS_MS = 60_000;
|
|
43
|
+
|
|
44
|
+
export class ManualAttendanceTracker {
|
|
45
|
+
private lastInteractionAt = 0;
|
|
46
|
+
|
|
47
|
+
constructor(private readonly suppressMs: number = AUTO_APPROVE_MANUAL_ATTENDANCE_SUPPRESS_MS) {}
|
|
48
|
+
|
|
49
|
+
/** Record that a human just drove this session by hand. */
|
|
50
|
+
note(now = Date.now()): void {
|
|
51
|
+
this.lastInteractionAt = now;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** True while a manual interaction is recent enough to suppress auto-approve. */
|
|
55
|
+
isAttended(now = Date.now()): boolean {
|
|
56
|
+
return this.lastInteractionAt > 0 && (now - this.lastInteractionAt) < this.suppressMs;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Milliseconds remaining in the current suppression window, or 0 when not
|
|
61
|
+
* attended. Used to re-arm a re-check timer so auto-approve fires the moment
|
|
62
|
+
* the window lapses even if the PTY/agent has since gone silent.
|
|
63
|
+
*/
|
|
64
|
+
remainingMs(now = Date.now()): number {
|
|
65
|
+
if (this.lastInteractionAt <= 0) return 0;
|
|
66
|
+
return Math.max(0, this.suppressMs - (now - this.lastInteractionAt));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The session-scoped commands that count as "a human is attending this session
|
|
72
|
+
* by hand". Shared so the command handler and any forward path agree on one
|
|
73
|
+
* definition. Deliberately excludes send_chat (coordinator task delegation) and
|
|
74
|
+
* pure read commands (read_chat / list_chats — passive polling, not driving).
|
|
75
|
+
*/
|
|
76
|
+
export const MANUAL_ATTENDANCE_COMMANDS: ReadonlySet<string> = new Set([
|
|
77
|
+
'select_session',
|
|
78
|
+
'open_panel',
|
|
79
|
+
'invoke_provider_script',
|
|
80
|
+
'set_mode',
|
|
81
|
+
'change_model',
|
|
82
|
+
'set_thought_level',
|
|
83
|
+
'resolve_action',
|
|
84
|
+
'pty_input',
|
|
85
|
+
]);
|
|
@@ -226,6 +226,15 @@ export interface ProviderInstance {
|
|
|
226
226
|
/** Refresh static provider definition/scripts without restarting the live runtime. */
|
|
227
227
|
refreshProviderDefinition?(provider: ProviderModule): void;
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Record that a human is actively attending this session by hand right now
|
|
231
|
+
* (foreground tab selection, controlbar use, manual approval, terminal
|
|
232
|
+
* input). Provider-common signal that suppresses auto-approve for a short
|
|
233
|
+
* window so the user can drive the session manually; background mesh worker
|
|
234
|
+
* sessions never receive it, so their delegated auto-approve is unaffected.
|
|
235
|
+
*/
|
|
236
|
+
noteManualInteraction?(now?: number): void;
|
|
237
|
+
|
|
229
238
|
/** cleanup */
|
|
230
239
|
dispose(): void;
|
|
231
240
|
}
|