dsh-ntr 0.1.5 → 0.2.0

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.
@@ -2,11 +2,11 @@
2
2
  * React surface for ACP: the settings configuration section.
3
3
  *
4
4
  * Scope is deliberately narrow — this is a CONFIGURATION surface. Runtime
5
- * interaction lives in the conversation: permission asks are answered in the
6
- * chat composer through the host approval chain. The ledger card answers
7
- * whatever did NOT reach the conversation — a host without the chain, a
8
- * session with no chat identity behind it, or an ask whose native route
9
- * degraded to unavailable (see `permissionLedgerCard`).
5
+ * interaction lives in the conversation: every permission ask with a chat
6
+ * identity is answered on a card above that conversation's composer
7
+ * (`permission-dock.tsx`). The ledger card here is the cross-session mirror —
8
+ * asks whose conversation is not open, or which have no chat identity at all —
9
+ * and is where those are answered (see `permissionLedgerCard`).
10
10
  *
11
11
  * Each configured agent is one compact row (status + current model + badges)
12
12
  * that expands into the full connection form and model catalog. The row stays
@@ -15,7 +15,7 @@
15
15
  * All business state lives in the injected AcpStore.
16
16
  */
17
17
  import type { AcpStore, UiState } from './store.ts';
18
- import type { AcpAgentDef, AgentSavePayload, AgentView, DetectedAgentInfo } from '../protocol.ts';
18
+ import type { AcpAgentDef, AcpApprovalDeadlines, AgentSavePayload, AgentView, DetectedAgentInfo, PermissionTimeoutMode } from '../protocol.ts';
19
19
  interface WithStore {
20
20
  acp: AcpStore;
21
21
  }
@@ -30,22 +30,61 @@ export declare function payloadEquals(payload: AgentSavePayload, current: AcpAge
30
30
  /**
31
31
  * The settings panel's permission-ledger rule.
32
32
  *
33
- * A pending ask ALWAYS gets an answerable card here, whatever `approvalRouted`
34
- * says. That flag reports that the host HAS an approval service — it does not
35
- * promise THIS ask reached the conversation. An ask lands in `ui.permissions`
36
- * only on the ledger path (`AgentBridge.handlePermissionRequest` broadcasts
37
- * `permission/request` there and nowhere else), so a pending entry is by
38
- * definition a question the conversation is not showing. The previous rule
39
- * (`approvalRouted` → a read-only count, no buttons) hid the only answer
40
- * entry exactly when the native route had degraded to unavailable, and the
41
- * ask then hung until its timeout.
42
- *
43
- * `fellBackFromConversation` is the case the card has to explain: the host
44
- * can route asks, and this one still did not make it.
33
+ * Pending asks are shown in the conversation card while that conversation is
34
+ * open; this card is the cross-session mirror for the ones it cannot show —
35
+ * asks belonging to another conversation, and `/acp` RPC-only asks with no
36
+ * chat identity at all. It therefore appears whenever ANY ask is pending: a
37
+ * pending entry with no conversation card would otherwise have no answer
38
+ * surface anywhere.
45
39
  */
46
- export declare function permissionLedgerCard(ui: Pick<UiState, 'approvalRouted' | 'permissions'>): {
40
+ export declare function permissionLedgerCard(ui: Pick<UiState, 'permissions'>): {
47
41
  visible: boolean;
48
- fellBackFromConversation: boolean;
42
+ };
43
+ /**
44
+ * The shortest REAL wait — the first rung above 不等待.
45
+ *
46
+ * Hand-copied from the host half (`APPROVAL_DEADLINE_MIN_WAIT_MS` in
47
+ * `src/impl.ts`): the browser half may not import the host module, so the
48
+ * equivalence is pinned by a test instead (see `tests/acp-view.spec.ts`).
49
+ */
50
+ export declare const DEADLINE_MIN_WAIT_MS = 30000;
51
+ /**
52
+ * The window the card shows under a mode.
53
+ *
54
+ * 不等待 (`0`) is the **auto** mode's rung: it answers with the option the
55
+ * agent's own kinds imply, so the agent CONTINUES. `abort` has no such rung —
56
+ * refusing every ask the instant it arrives stops the agent at every gate it
57
+ * meets, which is not a timeout but a wall. So under `abort` the window floors
58
+ * to the shortest real wait, and the card never shows a zero it cannot run.
59
+ *
60
+ * `undefined` (the `wait` mode) stays undefined: there is no window at all.
61
+ */
62
+ export declare function deadlineValueFor(mode: PermissionTimeoutMode, ms: number | undefined): number | undefined;
63
+ /**
64
+ * The rung the slider shows for a window. Exact hits keep their rung; anything
65
+ * else takes the nearest one. A tie goes to the SHORTER wait: the thumb must
66
+ * never promise more patience than the window has.
67
+ */
68
+ export declare function deadlineSliderIndex(ms: number, mode?: PermissionTimeoutMode): number;
69
+ /**
70
+ * The facts the card has to show, decided from what the host reported.
71
+ *
72
+ * The card carries TWO controls, and they are orthogonal: one window (how
73
+ * long) and one mode (what happens). An override can sit on either of them,
74
+ * so each is decided on its own.
75
+ *
76
+ * `overridden` is what gates the reset button. A baseline that AGREES with the
77
+ * running value is not an override, and offering a reset for it would be a
78
+ * button that does nothing (observed live).
79
+ */
80
+ export declare function approvalDeadlineFacts(deadlines: AcpApprovalDeadlines): {
81
+ /** The config-file window the card's window outranks — undefined when
82
+ * nothing is being outranked. */
83
+ baseline: number | undefined;
84
+ /** The config-file mode the card's mode outranks, when it differs. */
85
+ modeBaseline: PermissionTimeoutMode | undefined;
86
+ /** A reset is only honest when something is actually being outranked. */
87
+ overridden: boolean;
49
88
  };
50
89
  /** Settings configuration section for ACP agents. */
51
90
  export declare function AcpSettingsSection(props: WithStore): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pending ACP permission asks for the staged DSH conversation, rendered as
3
+ * composer-width entries above the composer card (`conversation.input.dock`).
4
+ * The slot itself spans the conversation column; the dock mirrors the host's
5
+ * InputBar width formula so the cards line up with the input box.
6
+ *
7
+ * The host broadcasts every ask as `permission/request`, carrying the DSH chat
8
+ * session id when the ask belongs to a conversation. This dock is the primary
9
+ * answer surface: it renders exactly the asks whose `dshSessionId` is the
10
+ * conversation it was mounted for, and nothing otherwise. There is no
11
+ * ACP-mode gate — the frame set itself is the gate, so an ordinary
12
+ * conversation shows this card if and only if it actually has an ask.
13
+ *
14
+ * The ask's description and options are the agent's own: buttons appear in the
15
+ * agent's order with the agent's names (including "Always Allow"), and
16
+ * `kind` is not rendered. A separate cancel button answers with no option,
17
+ * which the host forwards as `cancelled`. No Enter/Escape handlers: the
18
+ * composer owns its keyboard, and these are ordinary focusable buttons.
19
+ *
20
+ * Multiple asks stack in ARRIVAL order, oldest first: the question ignored
21
+ * longest stays at the top. The list is capped in height by CSS and scrolls.
22
+ * Asks without a chat identity never appear here — they stay in the
23
+ * settings-panel ledger, the cross-session mirror.
24
+ */
25
+ import type { AcpStore } from './store.ts';
26
+ export interface PermissionDockProps {
27
+ /** Injected face: the store plus the chat session id this dock belongs to. */
28
+ acp: AcpStore;
29
+ sessionId: string;
30
+ }
31
+ export declare function PermissionDock({ acp, sessionId }: PermissionDockProps): import("react").JSX.Element | null;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Deliberately framework-free: React binds through `useSyncExternalStore`.
7
7
  */
8
- import type { AcpApprovalDeadlines, AcpConversationChoice, AcpPresetStatus, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentSavePayload, AgentTestResult, AgentView, ChatSessionView, ApprovalDeadlineKey, DetectedAgentInfo, SessionView } from '../protocol.ts';
8
+ import type { AcpApprovalDeadlines, AcpConversationChoice, AcpPresetStatus, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentSavePayload, AgentTestResult, AgentView, ChatSessionView, DetectedAgentInfo, PermissionTimeoutMode, SessionView } from '../protocol.ts';
9
9
  /** One slash command the ACP agent declared, whole row (the menu draws name, description and `input.hint`). */
10
10
  export type AgentCommand = NonNullable<ChatSessionView['availableCommands']>[number];
11
11
  /** The unary RPC face (narrowed `ctx.connection.rpc.call`). */
@@ -84,16 +84,49 @@ export type Block = {
84
84
  tone: 'info' | 'error';
85
85
  text: string;
86
86
  };
87
+ /**
88
+ * One permission ask waiting for a human. Every field is copied VERBATIM from
89
+ * the host's `permission/request` frame: the host owns what the ask is called
90
+ * (`toolName` / `reason`), which options the agent offered (order included),
91
+ * and whether the ask carries a deadline. The browser does not derive a title
92
+ * from `toolCall` and does not second-guess the agent's option labels.
93
+ */
87
94
  export interface PendingPermission {
88
95
  requestId: string;
96
+ /** ACP session id. */
89
97
  sessionId: string;
90
- title: string;
98
+ /** DSH chat session id; absent for `/acp` RPC-only sessions (settings-only ask). */
99
+ dshSessionId?: string;
100
+ toolName: string;
101
+ reason?: string;
91
102
  options: Array<{
92
103
  optionId: string;
93
104
  name: string;
94
105
  kind: string;
95
106
  }>;
107
+ timeoutMode: PermissionTimeoutMode;
108
+ /** Epoch ms; absent in `wait` mode. */
109
+ deadlineAt?: number;
96
110
  }
111
+ /** Returned by {@link deadlineRemainingLabel} once the deadline has passed. */
112
+ export declare const PERMISSION_DEADLINE_EXPIRED = "expired";
113
+ /**
114
+ * The asks that belong to one DSH chat conversation. An ask without a
115
+ * `dshSessionId` belongs to no conversation (an `/acp` RPC-only session), so it
116
+ * is deliberately absent from every conversation's dock and stays in the
117
+ * settings-panel ledger.
118
+ */
119
+ export declare function permissionsForSession(permissions: readonly PendingPermission[], dshSessionId: string): PendingPermission[];
120
+ /**
121
+ * Compact countdown for one ask, or `undefined` when there is nothing to count
122
+ * down (`wait` mode, or a frame without a deadline). Pure so the formatting is
123
+ * testable without a clock: the dock passes `Date.now()` on its tick.
124
+ *
125
+ * Past the deadline the answer is {@link PERMISSION_DEADLINE_EXPIRED} rather
126
+ * than a negative number — the host has already armed the expiry, and a card
127
+ * counting backwards would claim a deadline that no longer exists.
128
+ */
129
+ export declare function deadlineRemainingLabel(deadlineAt: number | undefined, now: number, mode: PermissionTimeoutMode): string | undefined;
97
130
  /**
98
131
  * One observed ACP transcript, keyed by ACP session id. This is the ACP view
99
132
  * tab's data source: the native chat plane can only render text/reasoning
@@ -164,17 +197,6 @@ export interface UiState {
164
197
  * that never answers, …) reads as a dead button.
165
198
  */
166
199
  probeErrors: Record<string, string>;
167
- /**
168
- * Host reports that it HAS the native approval chain (permission asks are
169
- * answered in the chat composer). This is a capability report, not a
170
- * per-ask promise: an individual ask still falls back to the settings-panel
171
- * ledger when the service is unavailable this time, or when the session has
172
- * no chat identity behind it (`AgentBridge.askNativeApproval`). The panel
173
- * therefore answers ANY pending ask, whatever this flag says — see
174
- * `permissionLedgerCard` in panel.tsx. False on an older host or when the
175
- * approval service is missing, in which case the ledger is the only path.
176
- */
177
- approvalRouted: boolean;
178
200
  /**
179
201
  * ACP agent sessions backing the chat session the composer control sits in.
180
202
  * Only meaningful together with {@link chatSessionsFor}: the control reads it
@@ -556,6 +578,14 @@ export declare class AcpStore {
556
578
  /** Mirror the running flag onto a transcript the ACP view is showing. */
557
579
  private markWatchedRunning;
558
580
  setConfigOption(configId: string, value: string): Promise<void>;
581
+ /**
582
+ * Answer one ask (`optionId` absent = cancel it). A failure is reported
583
+ * through the store's notice — the settings panel renders that — AND
584
+ * rethrown, so the conversation dock can pin the message next to the card
585
+ * that failed instead of leaving it on a settings page the user may not
586
+ * have open. Callers that cannot show it (the settings panel) swallow it
587
+ * after the notice.
588
+ */
559
589
  respondPermission(requestId: string, optionId?: string): Promise<void>;
560
590
  stopAgent(agentId: string): Promise<void>;
561
591
  /**
@@ -572,7 +602,7 @@ export declare class AcpStore {
572
602
  */
573
603
  setTagHalo(halo: AcpTagHalo): Promise<void>;
574
604
  /**
575
- * Write one approval deadline (settings card), or drop the persisted override
605
+ * Write the approval deadline (settings card), or drop the persisted override
576
606
  * with `reset` so the `cordis.patch.yml` baseline applies again.
577
607
  *
578
608
  * The host answer — the same route returns the deadlines now in force — is the
@@ -580,7 +610,14 @@ export declare class AcpStore {
580
610
  * merged with a value this browser did not know about) self-corrects instead
581
611
  * of drifting from what runs.
582
612
  */
583
- setApprovalDeadline(key: ApprovalDeadlineKey, ms: number): Promise<void>;
613
+ setApprovalDeadline(ms: number): Promise<void>;
614
+ /**
615
+ * Write the mode: what an expired deadline DOES (`abort` refuses, `auto`
616
+ * answers with the option the agent's own kinds imply, `wait` never
617
+ * expires). Same write path as the window, so the host answer stays
618
+ * the authority.
619
+ */
620
+ setApprovalTimeoutMode(mode: PermissionTimeoutMode): Promise<void>;
584
621
  /** Drop the persisted override: the config file (or the built-in default) decides again. */
585
622
  resetApprovalDeadlines(): Promise<void>;
586
623
  private writeApprovalDeadlines;
@@ -5,7 +5,7 @@
5
5
  * fakes.
6
6
  */
7
7
  import { randomUUID } from './ids.ts';
8
- import type { AcpAgentCatalog, AcpAgentDef, AcpApprovalDeadlines, AcpConversationChoice, AcpEvent, AcpMcpServerDef, AcpPluginConfig, AcpPresetStatus, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentTestResult, AgentView, ApprovalDeadlineKey, ChatSessionView, ChatTranscriptView, DetectedAgentInfo, PermissionOptionView, PermissionOutcome, RpcResult } from './protocol.ts';
8
+ import type { AcpAgentCatalog, AcpAgentDef, AcpApprovalDeadlines, AcpConversationChoice, AcpEvent, AcpMcpServerDef, AcpPluginConfig, AcpPresetStatus, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentTestResult, AgentView, ApprovalDeadlineKey, ApprovalDeadlinePatch, ChatSessionView, ChatTranscriptView, DetectedAgentInfo, PermissionOptionView, PermissionOutcome, PermissionTimeoutMode, RpcResult } from './protocol.ts';
9
9
  export { randomUUID };
10
10
  /** Structured failure the RPC layer converts to `{ok:false, error}` and `apply()` surfaces at boot. */
11
11
  export declare class AcpError extends Error {
@@ -35,6 +35,36 @@ export declare function validateTagHalo(value: unknown): AcpTagHalo;
35
35
  * longer would be a mis-typed number rather than a deliberate window.
36
36
  */
37
37
  export declare const APPROVAL_DEADLINE_MAX_MS = 86400000;
38
+ /**
39
+ * The shortest REAL wait: the first rung of the card's ladder (30 seconds).
40
+ *
41
+ * The floor for a mode that must not wait zero — see
42
+ * {@link normalizeApprovalWindow}.
43
+ */
44
+ export declare const APPROVAL_DEADLINE_MIN_WAIT_MS = 30000;
45
+ /**
46
+ * Make a window legal for the mode it runs under.
47
+ *
48
+ * `0` means 不等待 — the ask is settled the moment it arrives instead of
49
+ * waiting. That is the **auto** mode's business: `auto` answers with the
50
+ * option the agent's own kinds imply, so the agent CONTINUES, and an
51
+ * unattended deployment gets a sane default instead of a hang.
52
+ *
53
+ * Under `abort` the same zero would refuse every ask the instant it arrived:
54
+ * the agent stops at every gate it meets, which is not a timeout, it is a
55
+ * brick wall. So `abort` never offers 不等待, and a window of `0` reaching it
56
+ * (a config file, a state file written by an older build, a mode switch that
57
+ * left the window behind) normalizes to the shortest real wait.
58
+ *
59
+ * `wait` ignores the window entirely — it never expires.
60
+ */
61
+ export declare function normalizeApprovalWindow(mode: PermissionTimeoutMode, ms: number): number;
62
+ /**
63
+ * The order `auto` mode picks an option in, by the kind the agent declared:
64
+ * the broadest grant first, then the narrow one, then the refusals, then
65
+ * whatever the agent offered instead. See {@link pickAutoOption}.
66
+ */
67
+ export declare const AUTO_OPTION_KIND_PRIORITY: readonly ["allow_always", "allow_once", "reject_once", "reject_always"];
38
68
  /**
39
69
  * Validate ONE approval deadline, shared by every layer that accepts one.
40
70
  *
@@ -48,10 +78,32 @@ export declare const APPROVAL_DEADLINE_MAX_MS = 86400000;
48
78
  export declare function validateApprovalDeadlineMs(raw: unknown, label: string, code: 'bad_config' | 'bad_payload', hint: string): number;
49
79
  /**
50
80
  * Validate a settings-page deadline write. Omitted keys are simply absent from
51
- * the result — the manager merges, so a card that only touched one slider must
52
- * not reset the other. `reset` is not a deadline and is dropped here.
81
+ * the result — the manager merges, so a card that only moved the mode must not
82
+ * reset the number. `reset` is not a deadline and is dropped here.
53
83
  */
54
84
  export declare function validateApprovalDeadlines(value: unknown): Partial<Record<ApprovalDeadlineKey, number>>;
85
+ /**
86
+ * Validate the mode a write asks for. Three values, and the list is the whole
87
+ * vocabulary: an unanswered ask is either answered for you, kept waiting, or
88
+ * refused. Anything else is a typo, not a new mode.
89
+ */
90
+ export declare function validatePermissionTimeoutMode(raw: unknown, code: 'bad_config' | 'bad_payload'): PermissionTimeoutMode;
91
+ /**
92
+ * The option an expired deadline answers with in `auto` mode.
93
+ *
94
+ * The pick is by the option's KIND, because that is what the agent declared
95
+ * about the choice it is offering: `allow_always` first (one grant covers the
96
+ * rest of the run), then `allow_once` (the smaller grant), then the reject
97
+ * kinds (refuse but let the agent continue), and finally the first option
98
+ * whatever it is — an agent may offer only a custom kind, and refusing to
99
+ * answer at all would hang the turn.
100
+ *
101
+ * `undefined` means NO option could be read (no options, or none with a usable
102
+ * id). The caller then degrades to `abort`: guessing an option is worse than
103
+ * refusing, and hanging is worse than either. OmniTerm dev picks the same way
104
+ * (`pick_auto_option`, `src/acp/permission.rs`).
105
+ */
106
+ export declare function pickAutoOption(options: readonly PermissionOptionView[]): PermissionOptionView | undefined;
55
107
  /**
56
108
  * Validate one agent's `mcpServers` list (the plugin's normalized shape, NOT
57
109
  * the ACP wire shape).
@@ -509,6 +561,21 @@ export declare class EventHub {
509
561
  subscribe(sink: (event: AcpEvent) => void): () => void;
510
562
  broadcast(event: AcpEvent): void;
511
563
  }
564
+ /** Everything a `permission/request` frame carries besides the ids the ledger owns. */
565
+ export interface PendingPermissionFields {
566
+ /** DSH chat session id; absent for `/acp` RPC-only sessions. */
567
+ dshSessionId?: string;
568
+ toolCall: unknown;
569
+ /** Host-derived human name (`permissionToolName`). */
570
+ toolName: string;
571
+ /** One-line detail from the agent's declared input, when it has any. */
572
+ reason?: string;
573
+ options: PermissionOptionView[];
574
+ /** What an unanswered ask does when its deadline fires. */
575
+ timeoutMode: PermissionTimeoutMode;
576
+ /** Epoch ms of the deadline; absent in `wait` mode (no deadline). */
577
+ deadlineAt?: number;
578
+ }
512
579
  export interface PendingPermission {
513
580
  requestId: string;
514
581
  sessionId: string;
@@ -518,17 +585,12 @@ export interface PendingPermission {
518
585
  * client receives (see `permissionReplayFrames`). Absent on entries created
519
586
  * without a view — those still answer, they just cannot be re-shown.
520
587
  */
521
- view?: {
522
- toolCall: unknown;
523
- options: PermissionOptionView[];
524
- };
588
+ view?: PendingPermissionFields;
525
589
  }
526
590
  /** One outstanding ask as a reconnecting client must be re-shown it. */
527
- export interface PendingPermissionView {
591
+ export interface PendingPermissionView extends PendingPermissionFields {
528
592
  requestId: string;
529
593
  sessionId: string;
530
- toolCall: unknown;
531
- options: PermissionOptionView[];
532
594
  }
533
595
  export declare class PermissionLedger {
534
596
  private readonly pending;
@@ -551,6 +613,16 @@ export declare class PermissionLedger {
551
613
  */
552
614
  pendingViews(): PendingPermissionView[];
553
615
  }
616
+ /**
617
+ * The `permission/request` frame for one ask.
618
+ *
619
+ * ONE builder for both the live broadcast and the reconnect replay, so the two
620
+ * can never drift: a client that reconnects must receive exactly the frame the
621
+ * live one carried (its conversation card filters on `dshSessionId` and shows
622
+ * `toolName` / `deadlineAt`, so a replay missing them would silently drop the
623
+ * ask).
624
+ */
625
+ export declare function permissionRequestFrame(view: PendingPermissionView): AcpEvent;
554
626
  /**
555
627
  * Frames that re-establish a freshly connected client's view of the pending
556
628
  * permission asks: every outstanding ask re-sent as its original
@@ -558,10 +630,10 @@ export declare class PermissionLedger {
558
630
  *
559
631
  * Why a replay at all: a `permission/request` is broadcast ONCE, when the ask is
560
632
  * created. A client that connects later — page reload, second tab, stream
561
- * outage — never receives it, and for a ledger ask (the `/acp` RPC path, or a
562
- * native route that degraded) that card is the ONLY answer entry, so the ask is
563
- * invisible to exactly the client that needs it. OmniTerm closes the same hole
564
- * with `PermissionManager::pending_events()` + a `PermissionsSynced` marker on
633
+ * outage — never receives it, and that card (conversation or settings ledger)
634
+ * is the ONLY answer entry for the ask, so the ask is invisible to exactly the
635
+ * client that needs it. OmniTerm closes the same hole with
636
+ * `PermissionManager::pending_events()` + a `PermissionsSynced` marker on
565
637
  * every WS connect.
566
638
  *
567
639
  * Why the terminator is mandatory: the replay is a snapshot, so an ask resolved
@@ -607,18 +679,18 @@ export interface AcpManager {
607
679
  /** Write the halo preference (persisted with the agent registry). */
608
680
  setTagHalo?(halo: AcpTagHalo): Promise<void>;
609
681
  /**
610
- * The approval deadlines in force, with the facts that explain an override
611
- * (the `cordis.patch.yml` baseline it outranks, and a `permissionTimeoutMs`
612
- * that overrides both). `undefined` on a host half from before the card
613
- * existed, which hides the settings card instead of guessing a value.
682
+ * The approval deadline in force, with the facts that explain an override
683
+ * (the `cordis.patch.yml` baseline it outranks). `undefined` on a host half
684
+ * from before the card existed, which hides the settings card instead of
685
+ * guessing a value.
614
686
  */
615
687
  approvalDeadlines?(): AcpApprovalDeadlines | undefined;
616
688
  /**
617
- * Write one or both approval deadlines (omitted keys keep their value), or
618
- * drop the persisted override with `reset` so the config-file baseline
689
+ * Write the approval deadline, or the mode (omitted keys keep their value),
690
+ * or drop the persisted override with `reset` so the config-file baseline
619
691
  * applies again. Applies to the running bridges without a restart.
620
692
  */
621
- setApprovalDeadlines?(patch: Partial<Record<ApprovalDeadlineKey, number>>, reset?: boolean): Promise<void>;
693
+ setApprovalDeadlines?(patch: ApprovalDeadlinePatch, reset?: boolean): Promise<void>;
622
694
  detectAgents?(): Promise<DetectedAgentInfo[]>;
623
695
  saveAgent?(def: AcpAgentDef): Promise<void>;
624
696
  deleteAgent?(agentId: string): Promise<void>;
@@ -654,12 +726,6 @@ export interface AcpManager {
654
726
  * cache (newly published models appear, withdrawn ones drop out).
655
727
  */
656
728
  cachedAgentModels?(agentId: string): AgentProbeModelsResult | undefined;
657
- /**
658
- * True when ACP permission asks are routed to the host's native approval
659
- * service (answered in the chat composer) instead of the settings-panel
660
- * ledger. Read-only capability report for `agents/list`.
661
- */
662
- approvalRouting?(): boolean;
663
729
  /**
664
730
  * ACP agent sessions backing one DSH chat session (looked up by chat session
665
731
  * id), so the composer can offer the agent's OWN mode list. Empty when no