indusagi-coding-agent 0.1.62 → 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.
- package/dist/entry.js +5395 -1509
- package/dist/types/boot/contract.d.ts +2 -0
- package/dist/types/boot/runners/addon-wiring.d.ts +103 -0
- package/dist/types/boot/runners/addon-wiring.test.d.ts +19 -0
- package/dist/types/boot/runners/checkpoint.d.ts +133 -0
- package/dist/types/boot/runners/checkpoint.test.d.ts +12 -0
- package/dist/types/boot/runners/delegate-runner.d.ts +83 -0
- package/dist/types/boot/runners/delegate-runner.test.d.ts +13 -0
- package/dist/types/boot/runners/memdir.d.ts +103 -0
- package/dist/types/boot/runners/memdir.test.d.ts +12 -0
- package/dist/types/boot/runners/read-state.d.ts +82 -0
- package/dist/types/boot/runners/read-state.test.d.ts +10 -0
- package/dist/types/boot/runners/session.d.ts +37 -2
- package/dist/types/boot/runners/session.test.d.ts +10 -0
- package/dist/types/briefing/context-docs.d.ts +38 -0
- package/dist/types/briefing/context-docs.test.d.ts +18 -0
- package/dist/types/briefing/index.d.ts +2 -0
- package/dist/types/capability-deck/cards/index.d.ts +6 -0
- package/dist/types/capability-deck/cards/memory-card.d.ts +9 -10
- package/dist/types/capability-deck/cards/plan-file.d.ts +56 -0
- package/dist/types/capability-deck/cards/plan-tools.d.ts +97 -0
- package/dist/types/capability-deck/cards/plan-tools.test.d.ts +9 -0
- package/dist/types/capability-deck/checkpoint.int.test.d.ts +25 -0
- package/dist/types/capability-deck/index.d.ts +1 -1
- package/dist/types/capability-deck/read-edit-gate.int.test.d.ts +21 -0
- package/dist/types/conductor/bash-guard.d.ts +106 -0
- package/dist/types/conductor/bash-guard.test.d.ts +17 -0
- package/dist/types/conductor/conductor.d.ts +37 -6
- package/dist/types/conductor/contract.d.ts +214 -2
- package/dist/types/conductor/diagnostics.d.ts +183 -0
- package/dist/types/conductor/diagnostics.test.d.ts +10 -0
- package/dist/types/conductor/index.d.ts +4 -1
- package/dist/types/conductor/permission-gate.integration.test.d.ts +22 -0
- package/dist/types/conductor/permission-wiring.test.d.ts +14 -0
- package/dist/types/conductor/permissions.d.ts +217 -0
- package/dist/types/conductor/permissions.test.d.ts +12 -0
- package/dist/types/conductor/plan-mode.integration.test.d.ts +23 -0
- package/dist/types/conductor/post-edit-diagnostics.test.d.ts +13 -0
- package/dist/types/conductor/transcript-store/serialize.test.d.ts +10 -0
- package/dist/types/conductor/transcript-store/store.d.ts +18 -0
- package/dist/types/console/components/StatusBar.d.ts +14 -3
- package/dist/types/console/components/WorkingIndicator.d.ts +44 -0
- package/dist/types/console/components/WorkingIndicator.test.d.ts +9 -0
- package/dist/types/console/contract.d.ts +2 -1
- package/dist/types/console/input/keymap.d.ts +10 -1
- package/dist/types/console/overlays/approval-queue.d.ts +71 -0
- package/dist/types/console/overlays/approval.d.ts +104 -0
- package/dist/types/console/overlays/approval.test.d.ts +17 -0
- package/dist/types/console/overlays/host.d.ts +4 -3
- package/dist/types/console/overlays/index.d.ts +2 -0
- package/dist/types/launch/contract.d.ts +2 -0
- package/dist/types/launch/index.d.ts +1 -1
- package/dist/types/launch/oauth.d.ts +13 -0
- package/dist/types/settings/contract.d.ts +47 -0
- package/dist/types/settings/index.d.ts +2 -2
- package/dist/types/window-budget/condenser.d.ts +15 -1
- package/dist/types/window-budget/index.d.ts +3 -1
- package/dist/types/window-budget/microcompact.d.ts +68 -0
- package/dist/types/window-budget/microcompact.test.d.ts +16 -0
- package/dist/types/window-budget/rehydrate.d.ts +56 -0
- package/dist/types/workspace/brand.d.ts +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission rule engine + the `canUseTool` gate seam.
|
|
3
|
+
*
|
|
4
|
+
* This module is the product-side brain of the permission stack. It is split in
|
|
5
|
+
* two layers:
|
|
6
|
+
*
|
|
7
|
+
* 1. **A pure rule engine** — {@link resolveRuleDecision} maps a single tool
|
|
8
|
+
* call `(toolName, input, mode, rules)` to a {@link PermissionBehavior}
|
|
9
|
+
* (`allow` / `ask` / `deny`). It is total, synchronous, and side-effect free
|
|
10
|
+
* so the precedence (deny > ask > mode auto-allow > allow) is exhaustively
|
|
11
|
+
* unit-testable. Rule strings are induscode-style: a bare tool name
|
|
12
|
+
* (`"Bash"`) or a tool name with an argument specifier
|
|
13
|
+
* (`"Bash(npm run test:*)"`).
|
|
14
|
+
*
|
|
15
|
+
* 2. **A gate factory** — {@link createPermissionGate} turns that engine into a
|
|
16
|
+
* framework {@link CanUseToolFn}: the hard async hook the framework agent
|
|
17
|
+
* awaits immediately before every tool runs. An `ask` decision is routed to
|
|
18
|
+
* an OPTIONAL host-supplied approval resolver; when the resolver is absent
|
|
19
|
+
* (a non-interactive boot) `ask` denies with a clear message. An
|
|
20
|
+
* `allow-always` approval appends a session-scoped allow rule so later
|
|
21
|
+
* identical calls auto-allow.
|
|
22
|
+
*
|
|
23
|
+
* Backwards-compatibility: the framework `canUseTool` hook is itself optional, so
|
|
24
|
+
* a session that never builds a gate keeps today's allow-all behavior. A gate
|
|
25
|
+
* built from an empty rule set in `default` mode allows read-only tools and asks
|
|
26
|
+
* for everything else — and, with no resolver, the `ask` denials are explicit
|
|
27
|
+
* rather than silent.
|
|
28
|
+
*/
|
|
29
|
+
import type { PermissionMode } from "../settings";
|
|
30
|
+
/** Re-exported so consumers of the engine get the mode vocabulary in one import. */
|
|
31
|
+
export type { PermissionMode };
|
|
32
|
+
/** The verdict the rule engine renders for a single tool call. */
|
|
33
|
+
export type PermissionBehavior = "allow" | "ask" | "deny";
|
|
34
|
+
/**
|
|
35
|
+
* One parsed permission rule: a behavior plus the tool it targets, with an
|
|
36
|
+
* optional argument specifier (`ruleContent`) that further narrows the match.
|
|
37
|
+
*/
|
|
38
|
+
export interface PermissionRule {
|
|
39
|
+
/** What happens when this rule matches a tool call. */
|
|
40
|
+
readonly ruleBehavior: PermissionBehavior;
|
|
41
|
+
/** The tool (and optional argument specifier) this rule selects. */
|
|
42
|
+
readonly ruleValue: {
|
|
43
|
+
/** The canonical tool name, e.g. `"Bash"` / `"bash"` (matched case-insensitively). */
|
|
44
|
+
readonly toolName: string;
|
|
45
|
+
/** An optional argument specifier, e.g. `"npm run test:*"`. Absent = bare match. */
|
|
46
|
+
readonly ruleContent?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The framework's tool-permission verdict. Mirrors `indusagi/agent`'s
|
|
51
|
+
* `PermissionDecision`; re-declared here so the product engine never imports the
|
|
52
|
+
* framework just for a structural type (the gate is assignable to the
|
|
53
|
+
* framework's `CanUseToolFn` because the shapes coincide).
|
|
54
|
+
*/
|
|
55
|
+
export type PermissionDecision = {
|
|
56
|
+
behavior: "allow";
|
|
57
|
+
updatedInput?: unknown;
|
|
58
|
+
} | {
|
|
59
|
+
behavior: "deny";
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The hard permission gate the framework agent awaits before each tool executes.
|
|
64
|
+
* Structurally identical to `indusagi/agent`'s `CanUseToolFn`, so a value of this
|
|
65
|
+
* type is accepted by `new Agent({ canUseTool })` with no cast.
|
|
66
|
+
*/
|
|
67
|
+
export type CanUseToolFn = (toolName: string, input: unknown, opts: {
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}) => Promise<PermissionDecision>;
|
|
70
|
+
/**
|
|
71
|
+
* The outcome of the host approval prompt raised for an `ask` decision:
|
|
72
|
+
* - `allow-once` — run this single call, do not remember.
|
|
73
|
+
* - `allow-always` — run it and append a session allow rule for the tool.
|
|
74
|
+
* - `deny` — block this call.
|
|
75
|
+
*/
|
|
76
|
+
export type ApprovalChoice = "allow-once" | "allow-always" | "deny";
|
|
77
|
+
/**
|
|
78
|
+
* The OPTIONAL host approval resolver. When present, an `ask` decision awaits it;
|
|
79
|
+
* the host (an interactive overlay) returns the user's choice. It MUST resolve to
|
|
80
|
+
* `"deny"` on abort so a cancelled turn never hangs on a pending prompt. When the
|
|
81
|
+
* resolver is absent (non-interactive boot / oneshot / link), an `ask` decision
|
|
82
|
+
* deterministically denies.
|
|
83
|
+
*/
|
|
84
|
+
export type ApprovalResolver = (toolName: string, input: unknown, opts: {
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}) => Promise<ApprovalChoice>;
|
|
87
|
+
/**
|
|
88
|
+
* Tool names known to only inspect state (never mutate). Mirrors the framework's
|
|
89
|
+
* `READ_ONLY_TOOL_NAMES`; matched case-insensitively. A tool the framework marks
|
|
90
|
+
* `readOnly: true` is also auto-allowed via {@link createPermissionGate}'s
|
|
91
|
+
* `readOnlyToolNames` option, so this set is the static fallback for callers that
|
|
92
|
+
* only know names.
|
|
93
|
+
*/
|
|
94
|
+
export declare const READ_ONLY_TOOL_NAMES: ReadonlySet<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Tool names that mutate the workspace by editing or writing files. Auto-allowed
|
|
97
|
+
* under `acceptEdits`, and (together with every other non-read-only tool) denied
|
|
98
|
+
* under `plan`.
|
|
99
|
+
*/
|
|
100
|
+
export declare const EDIT_TOOL_NAMES: ReadonlySet<string>;
|
|
101
|
+
/** Whether `toolName` is a statically-known read-only tool. */
|
|
102
|
+
export declare function isReadOnlyToolName(toolName: string, extra?: ReadonlySet<string>): boolean;
|
|
103
|
+
/** Whether `toolName` is an edit/write tool (auto-allowed under `acceptEdits`). */
|
|
104
|
+
export declare function isEditToolName(toolName: string): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Parse an induscode-style rule string into its tool name and optional argument
|
|
107
|
+
* specifier.
|
|
108
|
+
*
|
|
109
|
+
* - `"Bash"` → `{ toolName: "Bash" }`
|
|
110
|
+
* - `"Bash(npm run test:*)"` → `{ toolName: "Bash", ruleContent: "npm run test:*" }`
|
|
111
|
+
* - `"mcp__server"` → `{ toolName: "mcp__server" }`
|
|
112
|
+
*
|
|
113
|
+
* Whitespace around the tool name is trimmed; an empty argument specifier
|
|
114
|
+
* (`"Bash()"`) is treated as a bare rule.
|
|
115
|
+
*/
|
|
116
|
+
export declare function parseRule(raw: string): {
|
|
117
|
+
toolName: string;
|
|
118
|
+
ruleContent?: string;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Build a {@link PermissionRule} from a behavior and an induscode-style rule
|
|
122
|
+
* string. The single place a raw settings list entry becomes a typed rule.
|
|
123
|
+
*/
|
|
124
|
+
export declare function makeRule(ruleBehavior: PermissionBehavior, raw: string): PermissionRule;
|
|
125
|
+
/**
|
|
126
|
+
* Whether a tool call matches a rule (the ANY-match semantics used for deny/ask).
|
|
127
|
+
*
|
|
128
|
+
* The tool name must match (case-insensitive), with an `mcp__server` rule also
|
|
129
|
+
* matching every `mcp__server__tool` under it (the induscode MCP-wildcard
|
|
130
|
+
* convention). A bare rule (no specifier) matches any arguments.
|
|
131
|
+
*
|
|
132
|
+
* When the rule carries an argument specifier:
|
|
133
|
+
* - for the **shell** tool the command is split into its constituent
|
|
134
|
+
* sub-commands and the specifier is tested against EACH — a match on ANY
|
|
135
|
+
* sub-command counts (so `Bash(rm:*)` denies `git status && rm x`);
|
|
136
|
+
* - for any other tool the specifier is tested against the single rendered
|
|
137
|
+
* subject (the command field, or a JSON encoding).
|
|
138
|
+
*
|
|
139
|
+
* The any-match semantics are correct for `deny` and `ask` (a single offending
|
|
140
|
+
* sub-command should trigger them). The `allow` branch needs ALL sub-commands
|
|
141
|
+
* covered instead — see {@link bashAllowCoversAll}.
|
|
142
|
+
*/
|
|
143
|
+
export declare function toolMatchesRule(toolName: string, input: unknown, rule: PermissionRule): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Resolve the behavior for a single tool call against the rule set and mode.
|
|
146
|
+
*
|
|
147
|
+
* Precedence, highest first:
|
|
148
|
+
* 1. **deny rules** — any matching `deny` rule blocks, regardless of mode. Deny
|
|
149
|
+
* wins across every tier (the caller concatenates the tiers before passing
|
|
150
|
+
* the list in, and this scans all deny rules first).
|
|
151
|
+
* 2. **plan mode** — denies any mutating (non-read-only) tool outright.
|
|
152
|
+
* 3. **ask rules** — a matching `ask` rule forces a prompt (unless a deny
|
|
153
|
+
* already fired). Mode auto-allow does NOT override an explicit ask.
|
|
154
|
+
* 4. **bypass mode** — allows everything not already denied/asked.
|
|
155
|
+
* 5. **read-only auto-allow** — a known read-only tool allows in any mode.
|
|
156
|
+
* 6. **acceptEdits mode** — auto-allows edit/write tools.
|
|
157
|
+
* 7. **allow rules** — a matching `allow` rule (or, for the shell tool, the
|
|
158
|
+
* allow rule SET) allows.
|
|
159
|
+
* 8. **fallthrough** — `ask` (the safe default: prompt, then deny when no
|
|
160
|
+
* resolver is wired).
|
|
161
|
+
*
|
|
162
|
+
* The catastrophic-command blocklist sits ABOVE all of this (step 0): a shell
|
|
163
|
+
* command on the built-in blocklist (`rm -rf /`, fork bombs, `curl | sh`, …) is
|
|
164
|
+
* denied regardless of rules or mode — even `bypass` cannot run it. See
|
|
165
|
+
* {@link evaluateCatastrophic}.
|
|
166
|
+
*
|
|
167
|
+
* Shell handling threads through the per-sub-command matching: a compound
|
|
168
|
+
* command (`a && b; c | d`) is split into its constituent commands and each rule
|
|
169
|
+
* is evaluated against each — a deny/ask on ANY sub-command fires, while
|
|
170
|
+
* auto-allow requires the allow rule set to cover EVERY sub-command.
|
|
171
|
+
*
|
|
172
|
+
* Pure and total: no I/O, no async, deterministic for a given input.
|
|
173
|
+
*/
|
|
174
|
+
export declare function resolveRuleDecision(toolName: string, input: unknown, rules: readonly PermissionRule[], mode: PermissionMode, readOnlyExtra?: ReadonlySet<string>): PermissionBehavior;
|
|
175
|
+
/** Configuration for {@link createPermissionGate}. */
|
|
176
|
+
export interface PermissionGateConfig {
|
|
177
|
+
/**
|
|
178
|
+
* The ordered rule set, already concatenated across the project/global tiers.
|
|
179
|
+
* Deny rules are scanned first so a deny anywhere in the list wins.
|
|
180
|
+
*/
|
|
181
|
+
readonly rules: readonly PermissionRule[];
|
|
182
|
+
/** A live getter for the current permission mode (so a mode switch is honored). */
|
|
183
|
+
readonly mode: () => PermissionMode;
|
|
184
|
+
/**
|
|
185
|
+
* The OPTIONAL host approval resolver consulted on an `ask` decision. Absent on
|
|
186
|
+
* a non-interactive boot — then `ask` deterministically denies.
|
|
187
|
+
*/
|
|
188
|
+
readonly requestApproval?: ApprovalResolver;
|
|
189
|
+
/**
|
|
190
|
+
* Append a session-scoped allow rule when the host returns `allow-always`. The
|
|
191
|
+
* conductor owns the mutable rule list and passes a setter here so the new rule
|
|
192
|
+
* is visible to subsequent calls within the session.
|
|
193
|
+
*/
|
|
194
|
+
readonly appendAllowRule?: (rule: PermissionRule) => void;
|
|
195
|
+
/**
|
|
196
|
+
* Tool names (beyond {@link READ_ONLY_TOOL_NAMES}) to treat as read-only —
|
|
197
|
+
* derived from the deck's `readOnly: true` tool flags so MCP/custom read-only
|
|
198
|
+
* tools also auto-allow.
|
|
199
|
+
*/
|
|
200
|
+
readonly readOnlyToolNames?: ReadonlySet<string>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Build a framework {@link CanUseToolFn} from the rule engine + the current mode.
|
|
204
|
+
*
|
|
205
|
+
* The returned gate:
|
|
206
|
+
* - resolves the behavior via {@link resolveRuleDecision},
|
|
207
|
+
* - on `allow` proceeds (the framework keeps the validated args),
|
|
208
|
+
* - on `deny` short-circuits with a clear message (the framework turns it into
|
|
209
|
+
* an `isError` tool result so the model sees why),
|
|
210
|
+
* - on `ask` awaits {@link PermissionGateConfig.requestApproval} when present —
|
|
211
|
+
* `allow-once` proceeds, `allow-always` proceeds and appends a session allow
|
|
212
|
+
* rule, `deny` blocks — and DENIES when no resolver is wired (the safe
|
|
213
|
+
* non-interactive default).
|
|
214
|
+
*
|
|
215
|
+
* The result is assignable to `indusagi/agent`'s `CanUseToolFn`.
|
|
216
|
+
*/
|
|
217
|
+
export declare function createPermissionGate(config: PermissionGateConfig): CanUseToolFn;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission rule engine + gate — unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Exhaustively pins the pure decision ({@link resolveRuleDecision}) precedence
|
|
5
|
+
* (deny > plan-deny > ask > bypass > read-only > acceptEdits > allow > ask
|
|
6
|
+
* fallthrough), the induscode rule parsing/matching ({@link parseRule},
|
|
7
|
+
* {@link toolMatchesRule}, including argument specifiers and the `mcp__` wildcard),
|
|
8
|
+
* the mode behaviors, and the {@link createPermissionGate} `ask` routing — a host
|
|
9
|
+
* resolver returns allow-once / allow-always (which appends a session allow rule)
|
|
10
|
+
* / deny, and an ABSENT resolver denies. No framework, no network.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-mode round-trip — integration across the gate + the conductor handshake.
|
|
3
|
+
*
|
|
4
|
+
* Two proofs, against the REAL wired pieces (the rule engine's gate + the live
|
|
5
|
+
* conductor), not mocks of an unwired module:
|
|
6
|
+
*
|
|
7
|
+
* 1. **The gate denies edits in plan mode.** A `createPermissionGate` whose mode
|
|
8
|
+
* getter reads the conductor's LIVE `permissionMode()` allows an edit in
|
|
9
|
+
* `default` mode, denies it once the conductor enters plan mode
|
|
10
|
+
* (`togglePlanMode(true)` / `setPermissionMode('plan')`), and allows it again
|
|
11
|
+
* once an approved exit restores the prior mode. This is the exact gate the
|
|
12
|
+
* conductor threads into the framework `Agent`, so the deny lands where tools
|
|
13
|
+
* execute.
|
|
14
|
+
*
|
|
15
|
+
* 2. **ExitPlanMode approval restores the mode, persists the plan, and resumes.**
|
|
16
|
+
* A scripted fake agent emits a `tool_execution_end` carrying the
|
|
17
|
+
* `{ exitPlan: true, plan }` detail the conductor intercepts. With an approval
|
|
18
|
+
* resolver installed that approves, a turn run while in plan mode: restores
|
|
19
|
+
* the captured pre-plan mode, writes the plan to a file under `plansDir`, and
|
|
20
|
+
* enqueues the approved plan as a follow-up so the model proceeds from it. A
|
|
21
|
+
* rejecting resolver keeps the session in plan mode and writes nothing.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor-integration tests for the post-edit live-diagnostics seam.
|
|
3
|
+
*
|
|
4
|
+
* Drives a scripted {@link AgentLike} that emits a `tool_execution_start` for an
|
|
5
|
+
* `edit`/`write` tool during `prompt`, with an injected {@link DiagnosticsEngine}
|
|
6
|
+
* over a scripted in-memory runner (no real tsc spawn). Verifies:
|
|
7
|
+
* - a turn that introduces a NEW diagnostic enqueues the summary follow-up;
|
|
8
|
+
* - a clean edit (no new diagnostics) enqueues nothing;
|
|
9
|
+
* - re-editing a file re-surfaces a previously-cleared diagnostic;
|
|
10
|
+
* - a non-TS edit never triggers the checker;
|
|
11
|
+
* - the injected follow-up turn (which edits nothing) does not loop.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript serialization — head usage round-trip.
|
|
3
|
+
*
|
|
4
|
+
* Focused coverage for the cost-cmd durability seam: the head line now carries
|
|
5
|
+
* the session's cumulative {@link Usage}, and {@link encodeHead} →
|
|
6
|
+
* {@link parseSessionText} must round-trip it faithfully so resume can restore the
|
|
7
|
+
* running token/cost total instead of seeding zero. Heads without usage (legacy
|
|
8
|
+
* files, fresh sessions) must still parse cleanly with `usage` absent.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import type { AgentMessage, SessionHead, TranscriptEntry, TranscriptRole } from "../contract";
|
|
29
29
|
import { TRANSCRIPT_SCHEMA } from "../contract";
|
|
30
|
+
import type { Usage } from "indusagi/ai";
|
|
30
31
|
/**
|
|
31
32
|
* Where a transcript's NDJSON log lives. The store appends lines and, on branch,
|
|
32
33
|
* rewrites the whole file; a backend supplies those three primitives plus a way
|
|
@@ -101,6 +102,13 @@ export declare class TranscriptStore {
|
|
|
101
102
|
get sessionId(): string;
|
|
102
103
|
/** The current head (session id + active leaf). */
|
|
103
104
|
get head(): SessionHead;
|
|
105
|
+
/**
|
|
106
|
+
* The cumulative session usage last persisted onto the head, or `undefined`
|
|
107
|
+
* when none has been written (a fresh session, or a legacy file without a
|
|
108
|
+
* usage-bearing head). `resume` reads this to restore the running token/cost
|
|
109
|
+
* total instead of seeding zero.
|
|
110
|
+
*/
|
|
111
|
+
get usage(): Usage | undefined;
|
|
104
112
|
/** Number of nodes currently in the transcript. */
|
|
105
113
|
get size(): number;
|
|
106
114
|
/** Backend location this session persists to (for diagnostics/UI). */
|
|
@@ -116,6 +124,16 @@ export declare class TranscriptStore {
|
|
|
116
124
|
* a settled turn.
|
|
117
125
|
*/
|
|
118
126
|
append(content: AgentMessage, role?: TranscriptRole, meta?: Readonly<Record<string, unknown>>): Promise<TranscriptEntry>;
|
|
127
|
+
/**
|
|
128
|
+
* Persist the session's cumulative usage onto the head and rewrite the backing
|
|
129
|
+
* file so the running token/cost total survives a reload. Distinct from
|
|
130
|
+
* {@link append}, which only appends an entry line and never rewrites the head:
|
|
131
|
+
* usage durability MUST flow through here so the re-emitted head line carries
|
|
132
|
+
* the latest tally. Leaves the node tree and active leaf untouched.
|
|
133
|
+
*
|
|
134
|
+
* @param usage the cumulative usage to pin on the head
|
|
135
|
+
*/
|
|
136
|
+
persistUsage(usage: Usage): Promise<void>;
|
|
119
137
|
/**
|
|
120
138
|
* Repoint the head at an earlier node, forking a branch. The next {@link append}
|
|
121
139
|
* becomes a child of `id`; existing nodes are untouched. Rewrites the head line.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* (Renamed surface: the console's status strip.)
|
|
11
11
|
*/
|
|
12
12
|
import { type InkThemeAdapter, type SessionSnapshot, type StatusMessage } from "indusagi/react-ink";
|
|
13
|
+
import type { PermissionMode } from "../../settings";
|
|
13
14
|
/** What the {@link StatusBar} renders. */
|
|
14
15
|
export interface StatusBarProps {
|
|
15
16
|
/** The framework adapter that turns token roles into terminal colours. */
|
|
@@ -22,10 +23,20 @@ export interface StatusBarProps {
|
|
|
22
23
|
readonly providerCount: number;
|
|
23
24
|
/** The transient status toast, when one is showing. */
|
|
24
25
|
readonly status?: StatusMessage;
|
|
26
|
+
/**
|
|
27
|
+
* The active permission mode. When supplied, a small badge is rendered above the
|
|
28
|
+
* footer (parallel to the transient status toast) so the user can see at a glance
|
|
29
|
+
* which of the four cycled modes is live: `default` (tools prompt as usual),
|
|
30
|
+
* `acceptEdits` (edits auto-approved), `plan` (mutating tools blocked), or
|
|
31
|
+
* `bypass` (every tool auto-allowed — drawn in an alert colour). The
|
|
32
|
+
* `bypassPermissions` alias renders as `bypass`. Omitted renders nothing extra.
|
|
33
|
+
*/
|
|
34
|
+
readonly permissionMode?: PermissionMode;
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
|
-
* Render the toast row above the footer strip
|
|
37
|
+
* Render the toast row above the footer strip, plus an optional permission-mode
|
|
38
|
+
* badge when a non-default mode is active.
|
|
28
39
|
*
|
|
29
|
-
* @param props the snapshot + status to display
|
|
40
|
+
* @param props the snapshot + status (+ optional permission mode) to display
|
|
30
41
|
*/
|
|
31
|
-
export declare function StatusBar({ theme, snapshot, branch, providerCount, status, }: StatusBarProps): JSX.Element;
|
|
42
|
+
export declare function StatusBar({ theme, snapshot, branch, providerCount, status, permissionMode, }: StatusBarProps): JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkingIndicator — the live "agent is working" affordance.
|
|
3
|
+
*
|
|
4
|
+
* While a turn is in flight (`busy`) this draws an animated braille spinner, a
|
|
5
|
+
* label, an elapsed-seconds clock, and an "esc to interrupt" hint. The label is
|
|
6
|
+
* a rotating whimsical word (changing every few seconds, induscode-style) for
|
|
7
|
+
* the general thinking/streaming case, and a concrete phase label while the
|
|
8
|
+
* agent runs tools or compacts context.
|
|
9
|
+
*
|
|
10
|
+
* It renders nothing when idle, and self-drives its animation via a short
|
|
11
|
+
* interval that ticks ONLY while busy (torn down the moment `busy` clears), so an
|
|
12
|
+
* idle console does no spinner work. It is a sibling row above the
|
|
13
|
+
* {@link Composer} and does NOT consume the sticky status toast — so a previous
|
|
14
|
+
* toast (e.g. the permission-mode line) can never mask that the agent is working.
|
|
15
|
+
*/
|
|
16
|
+
import type { InkThemeAdapter } from "indusagi/react-ink";
|
|
17
|
+
/**
|
|
18
|
+
* The rotating "thinking" words shown while the agent reasons/streams. Whimsical
|
|
19
|
+
* present participles, in the spirit of induscode's spinner — purely cosmetic.
|
|
20
|
+
*/
|
|
21
|
+
export declare const WORKING_WORDS: readonly ["Cogitating", "Pondering", "Noodling", "Simmering", "Brewing", "Percolating", "Conjuring", "Tinkering", "Wrangling", "Synthesizing", "Computing", "Musing", "Scheming", "Hatching", "Whirring", "Crunching", "Spelunking", "Untangling", "Marinating", "Ruminating", "Architecting", "Finagling", "Concocting", "Deliberating", "Orchestrating", "Calibrating", "Channeling", "Vibing", "Forging", "Assembling"];
|
|
22
|
+
/** Pick a working word by (wrapping, non-negative) index. Exported for testing. */
|
|
23
|
+
export declare function pickWord(index: number): string;
|
|
24
|
+
/** What the {@link WorkingIndicator} renders. */
|
|
25
|
+
export interface WorkingIndicatorProps {
|
|
26
|
+
/** The framework adapter that turns token roles into terminal colours. */
|
|
27
|
+
readonly theme: InkThemeAdapter;
|
|
28
|
+
/** Whether a turn is in flight. When false the indicator renders nothing. */
|
|
29
|
+
readonly busy: boolean;
|
|
30
|
+
/** The conductor snapshot phase, used to pick a concrete label when relevant. */
|
|
31
|
+
readonly phase?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The concrete label for the phases where a specific word is more useful than a
|
|
35
|
+
* whimsical one. Returns null for the general thinking/streaming case (the caller
|
|
36
|
+
* then shows a rotating {@link WORKING_WORDS} word instead). Exported for testing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function phaseLabel(phase: string | undefined): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Render the animated working row, or nothing when idle.
|
|
41
|
+
*
|
|
42
|
+
* @param props the theme, the busy flag, and the snapshot phase
|
|
43
|
+
*/
|
|
44
|
+
export declare function WorkingIndicator({ theme, busy, phase, }: WorkingIndicatorProps): JSX.Element | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkingIndicator — unit tests for the pure label/word logic.
|
|
3
|
+
*
|
|
4
|
+
* The animated render is driven by an Ink-only timer that is impractical to
|
|
5
|
+
* assert headlessly; the load-bearing pure logic is {@link phaseLabel} (concrete
|
|
6
|
+
* labels for tool/compaction phases) and {@link pickWord} (the rotating whimsical
|
|
7
|
+
* word for the general thinking case).
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -244,8 +244,9 @@ export interface ViewRow {
|
|
|
244
244
|
* - `signOut` — the provider sign-out confirmation.
|
|
245
245
|
* - `oauth` — the in-flight OAuth device/redirect flow.
|
|
246
246
|
* - `plugin` — a plugin-supplied select/confirm/input/custom overlay.
|
|
247
|
+
* - `approval` — a pending tool-permission prompt (allow once / always / deny).
|
|
247
248
|
*/
|
|
248
|
-
export type ModalKind = "none" | "settings" | "models" | "scopedModels" | "theme" | "sessions" | "tree" | "userTurns" | "signIn" | "signOut" | "oauth" | "plugin";
|
|
249
|
+
export type ModalKind = "none" | "settings" | "models" | "scopedModels" | "theme" | "sessions" | "tree" | "userTurns" | "signIn" | "signOut" | "oauth" | "plugin" | "approval";
|
|
249
250
|
/**
|
|
250
251
|
* The active modal plus any opaque payload the matching dialog needs.
|
|
251
252
|
*
|
|
@@ -84,6 +84,15 @@ export interface KeyChord {
|
|
|
84
84
|
* Model / reasoning
|
|
85
85
|
* - `model:cycleThinking` — advance the reasoning-effort ladder one step.
|
|
86
86
|
*
|
|
87
|
+
* Permission mode
|
|
88
|
+
* - `mode:cyclePermission` — cycle through ALL permission modes (Shift+Tab),
|
|
89
|
+
* advancing one step per press: default → acceptEdits → plan → bypass →
|
|
90
|
+
* (wrap) default. In `plan` mode every mutating tool is blocked; in `bypass`
|
|
91
|
+
* every tool is auto-allowed.
|
|
92
|
+
* - `mode:togglePlan` — toggle read-only plan mode on/off. Drives `/plan`; no
|
|
93
|
+
* longer bound to Shift+Tab (which now cycles all modes). In plan mode every
|
|
94
|
+
* mutating tool is blocked at the permission gate.
|
|
95
|
+
*
|
|
87
96
|
* Queue
|
|
88
97
|
* - `queue:dequeue` — pull the most recently queued input back into the
|
|
89
98
|
* composer so it can be edited or dropped.
|
|
@@ -106,7 +115,7 @@ export interface KeyChord {
|
|
|
106
115
|
* Inert
|
|
107
116
|
* - `none` — the keystroke carries no console meaning.
|
|
108
117
|
*/
|
|
109
|
-
export type ConsoleVerb = "text:type" | "text:newline" | "edit:erasePrev" | "edit:eraseNext" | "edit:clearLine" | "nav:left" | "nav:right" | "nav:home" | "nav:end" | "nav:up" | "nav:down" | "flow:submit" | "flow:dismiss" | "flow:accept" | "flow:interrupt" | "flow:suspend" | "flow:cycleModel" | "model:cycleThinking" | "queue:dequeue" | "input:pasteImage" | "input:externalEditor" | "overlay:open" | "view:expandTools" | "view:toggleReasoning" | "none";
|
|
118
|
+
export type ConsoleVerb = "text:type" | "text:newline" | "edit:erasePrev" | "edit:eraseNext" | "edit:clearLine" | "nav:left" | "nav:right" | "nav:home" | "nav:end" | "nav:up" | "nav:down" | "flow:submit" | "flow:dismiss" | "flow:accept" | "flow:interrupt" | "flow:suspend" | "flow:cycleModel" | "model:cycleThinking" | "mode:cyclePermission" | "mode:togglePlan" | "queue:dequeue" | "input:pasteImage" | "input:externalEditor" | "overlay:open" | "view:expandTools" | "view:toggleReasoning" | "none";
|
|
110
119
|
/**
|
|
111
120
|
* A classified keystroke: a {@link ConsoleVerb} plus the small payload a verb
|
|
112
121
|
* may carry — the literal text for a `text:type` insert, or the target overlay
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval queue — the headless, pure serializer behind the approval overlay.
|
|
3
|
+
*
|
|
4
|
+
* The permission gate may reach an `ask` decision while another approval prompt is
|
|
5
|
+
* already on screen (e.g. a turn that batches several tool calls). The UI shows ONE
|
|
6
|
+
* prompt at a time, so the pending requests are held in an ordered queue: each
|
|
7
|
+
* `enqueue` parks a request, the HEAD is the prompt currently shown, and `settle`
|
|
8
|
+
* resolves the head's parked promise then advances to the next. This module is a
|
|
9
|
+
* pure reducer over an immutable {@link ApprovalQueueState} plus a couple of tiny
|
|
10
|
+
* helpers — no React, no Ink, no timers — so the queue logic is unit-testable in
|
|
11
|
+
* isolation (the overlay component only renders the head and dispatches settles).
|
|
12
|
+
*
|
|
13
|
+
* Each queued entry carries the parked promise's `resolve` (the gate awaits it) and
|
|
14
|
+
* the request itself. Settling resolves with an {@link ApprovalChoice}; an
|
|
15
|
+
* abort/clear settles every outstanding entry with `"deny"` so a cancelled turn
|
|
16
|
+
* never hangs on a pending prompt.
|
|
17
|
+
*/
|
|
18
|
+
import type { ApprovalChoice } from "../../conductor";
|
|
19
|
+
import type { ApprovalRequest } from "./approval";
|
|
20
|
+
/** One parked approval request awaiting the user's choice. */
|
|
21
|
+
export interface ApprovalEntry {
|
|
22
|
+
/** A stable id, unique within the queue's lifetime, used to settle the right one. */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** The pending request the prompt renders. */
|
|
25
|
+
readonly request: ApprovalRequest;
|
|
26
|
+
/** Fulfil the gate's parked promise with the user's choice. Called exactly once. */
|
|
27
|
+
readonly resolve: (choice: ApprovalChoice) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The immutable queue state. The array is ordered oldest-first; the head (index 0)
|
|
31
|
+
* is the request currently prompted. An empty queue means no prompt is showing.
|
|
32
|
+
*/
|
|
33
|
+
export interface ApprovalQueueState {
|
|
34
|
+
/** The parked entries, oldest first; `[0]` is the active prompt. */
|
|
35
|
+
readonly entries: readonly ApprovalEntry[];
|
|
36
|
+
}
|
|
37
|
+
/** The empty queue — no approval prompt pending. */
|
|
38
|
+
export declare const EMPTY_APPROVAL_QUEUE: ApprovalQueueState;
|
|
39
|
+
/**
|
|
40
|
+
* The closed action union the queue reducer folds:
|
|
41
|
+
* - `enqueue` — park a new request at the tail.
|
|
42
|
+
* - `settle` — resolve the entry matching `id` with `choice` and drop it.
|
|
43
|
+
* - `clear` — drop EVERY entry (the caller resolves each with `"deny"`).
|
|
44
|
+
*/
|
|
45
|
+
export type ApprovalQueueEvent = {
|
|
46
|
+
readonly type: "enqueue";
|
|
47
|
+
readonly entry: ApprovalEntry;
|
|
48
|
+
} | {
|
|
49
|
+
readonly type: "settle";
|
|
50
|
+
readonly id: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly type: "clear";
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Fold one {@link ApprovalQueueEvent} over the queue, returning a fresh state.
|
|
56
|
+
*
|
|
57
|
+
* Pure and total: it never resolves a promise itself (the caller does that around
|
|
58
|
+
* a `settle`/`clear`), it only mutates the ordered entry list. `enqueue` appends;
|
|
59
|
+
* `settle` removes the entry with the matching `id` (a no-op when absent, e.g. a
|
|
60
|
+
* double-settle); `clear` empties the queue.
|
|
61
|
+
*
|
|
62
|
+
* @param state the prior queue state
|
|
63
|
+
* @param event the action to apply
|
|
64
|
+
*/
|
|
65
|
+
export declare function approvalQueueReducer(state: ApprovalQueueState, event: ApprovalQueueEvent): ApprovalQueueState;
|
|
66
|
+
/**
|
|
67
|
+
* The active prompt — the head entry — or `undefined` when the queue is empty.
|
|
68
|
+
*
|
|
69
|
+
* @param state the queue state
|
|
70
|
+
*/
|
|
71
|
+
export declare function activeApproval(state: ApprovalQueueState): ApprovalEntry | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval overlay — the interactive tool-permission prompt.
|
|
3
|
+
*
|
|
4
|
+
* This group owns the single modal kind raised when the permission gate reaches an
|
|
5
|
+
* `ask` decision for a tool call: it shows the pending request (tool name, a short
|
|
6
|
+
* rendering of the arguments, and any suggested allow-rule) and lets the user pick
|
|
7
|
+
* one of three outcomes — allow this one call, allow it for the rest of the session
|
|
8
|
+
* (which the gate turns into a session allow-rule), or deny it. It is mounted
|
|
9
|
+
* unconditionally by {@link import("./host").OverlayHost}, which routes by
|
|
10
|
+
* {@link ModalState.kind}; this component renders a dialog body only for its own
|
|
11
|
+
* `approval` kind and is otherwise inert.
|
|
12
|
+
*
|
|
13
|
+
* The wiring is a parked promise: when the React layer builds the conductor's
|
|
14
|
+
* approval resolver it returns a promise and stashes the request (plus the
|
|
15
|
+
* promise's `resolve`) under {@link ModalState.payload}. This overlay reads that
|
|
16
|
+
* payload, renders the choices, and on a pick calls `payload.resolve(choice)` then
|
|
17
|
+
* closes the modal — handing control back to the gate, which proceeds or denies. An
|
|
18
|
+
* Esc (the dialog's own close) resolves `"deny"` so a dismissed prompt never hangs
|
|
19
|
+
* the in-flight turn. The actual select UI reuses the framework `ThemeDialog`
|
|
20
|
+
* (a labelled, navigable pick-from-list with descriptions), so the overlay needs no
|
|
21
|
+
* bespoke key handling.
|
|
22
|
+
*
|
|
23
|
+
* React/Ink are obtained the same way the other overlays do (default React export
|
|
24
|
+
* from `indusagi/react-host`, `Box`/`Text` from `indusagi/react-host/ink`).
|
|
25
|
+
*/
|
|
26
|
+
import { type ThemeDialogItem } from "indusagi/react-ink";
|
|
27
|
+
import type { ApprovalChoice } from "../../conductor";
|
|
28
|
+
import type { OverlayGroupProps } from "./pickers";
|
|
29
|
+
/**
|
|
30
|
+
* The pending tool-permission request a prompt is raised for. Mirrors the
|
|
31
|
+
* arguments the conductor's approval resolver is handed for an `ask` decision.
|
|
32
|
+
*/
|
|
33
|
+
export interface ApprovalRequest {
|
|
34
|
+
/** The tool the model asked to run (e.g. `"Bash"`, `"Edit"`). */
|
|
35
|
+
readonly toolName: string;
|
|
36
|
+
/** The validated arguments the tool would run with. */
|
|
37
|
+
readonly input: unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Optional human-readable allow-rule suggestions the prompt surfaces (e.g.
|
|
40
|
+
* `"Bash(npm run test:*)"`), so the user knows what an "allow always" remembers.
|
|
41
|
+
*/
|
|
42
|
+
readonly suggestions?: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The opaque {@link ModalState.payload} the `approval` overlay narrows: the pending
|
|
46
|
+
* request plus the resolver of the parked approval promise. The overlay calls
|
|
47
|
+
* `resolve(choice)` exactly once, then closes the modal.
|
|
48
|
+
*/
|
|
49
|
+
export interface ApprovalPayload {
|
|
50
|
+
/** The pending request to render. */
|
|
51
|
+
readonly request: ApprovalRequest;
|
|
52
|
+
/** Fulfil the parked approval promise with the user's choice. */
|
|
53
|
+
readonly resolve: (choice: ApprovalChoice) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The three offerable outcomes, in listing order. The `id` is the
|
|
57
|
+
* {@link ApprovalChoice} the gate consumes; `label`/`description` are what the
|
|
58
|
+
* prompt shows. Kept as data (not an `if`-ladder) so the choice→resolution mapping
|
|
59
|
+
* is a pure table the headless test asserts against.
|
|
60
|
+
*/
|
|
61
|
+
export declare const APPROVAL_CHOICES: readonly (ThemeDialogItem & {
|
|
62
|
+
id: ApprovalChoice;
|
|
63
|
+
})[];
|
|
64
|
+
/** The {@link ApprovalChoice} a dismissed (Esc) prompt resolves to. */
|
|
65
|
+
export declare const DISMISS_CHOICE: ApprovalChoice;
|
|
66
|
+
/**
|
|
67
|
+
* Map a selected dialog `id` back to the {@link ApprovalChoice} the gate consumes.
|
|
68
|
+
*
|
|
69
|
+
* Pure and total: a recognised id maps to itself; anything unrecognised falls back
|
|
70
|
+
* to {@link DISMISS_CHOICE} (`"deny"`) so a stray selection can never silently
|
|
71
|
+
* allow a tool. This is the unit the headless test pins.
|
|
72
|
+
*
|
|
73
|
+
* @param id the `id` the {@link ThemeDialog} reported on select
|
|
74
|
+
*/
|
|
75
|
+
export declare function choiceFromId(id: string): ApprovalChoice;
|
|
76
|
+
/**
|
|
77
|
+
* Narrow the opaque modal payload into a typed {@link ApprovalPayload}, or
|
|
78
|
+
* `undefined` when it is not a well-formed approval request (a defensive guard —
|
|
79
|
+
* the overlay renders nothing rather than crash on a malformed payload).
|
|
80
|
+
*
|
|
81
|
+
* @param payload the opaque {@link ModalState.payload}
|
|
82
|
+
*/
|
|
83
|
+
export declare function readApprovalPayload(payload: unknown): ApprovalPayload | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Render a tool call's arguments as a single short, human-readable line for the
|
|
86
|
+
* prompt body. A shell `command` (or any `command` field) shows verbatim;
|
|
87
|
+
* otherwise a compact JSON encoding is used. Long values are elided so the prompt
|
|
88
|
+
* never floods the terminal.
|
|
89
|
+
*
|
|
90
|
+
* @param input the validated tool arguments
|
|
91
|
+
*/
|
|
92
|
+
export declare function summarizeInput(input: unknown): string;
|
|
93
|
+
/**
|
|
94
|
+
* The approval overlay group: the single `approval` modal kind.
|
|
95
|
+
*
|
|
96
|
+
* Renders the prompt body when the active modal is `approval` and its payload is a
|
|
97
|
+
* well-formed {@link ApprovalPayload}; every other kind (and a malformed payload)
|
|
98
|
+
* yields nothing. Unlike the session/auth groups this does NOT require the runtime
|
|
99
|
+
* services bundle — the payload carries everything the prompt needs — so it works
|
|
100
|
+
* on any mount path that wired a resolver.
|
|
101
|
+
*
|
|
102
|
+
* @param props the forwarded overlay props (modal, services, theme, dispatch, closer)
|
|
103
|
+
*/
|
|
104
|
+
export declare function ApprovalOverlays(props: OverlayGroupProps): JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval overlay — headless unit tests for the pure logic behind the prompt.
|
|
3
|
+
*
|
|
4
|
+
* Rendering an Ink dialog headlessly is brittle, so these tests pin the two pure
|
|
5
|
+
* pieces the interactive overlay is built from — exactly the bar the task sets:
|
|
6
|
+
*
|
|
7
|
+
* 1. The choice→resolution mapping ({@link choiceFromId}) + the payload narrowing
|
|
8
|
+
* ({@link readApprovalPayload}) + the argument summary ({@link summarizeInput}):
|
|
9
|
+
* a selected dialog row maps to the {@link ApprovalChoice} the gate consumes, a
|
|
10
|
+
* malformed payload is rejected, and a stray id falls back to `deny`.
|
|
11
|
+
* 2. The queue reducer ({@link approvalQueueReducer}) + head selector
|
|
12
|
+
* ({@link activeApproval}): requests serialise oldest-first, a settle drops the
|
|
13
|
+
* matching entry (and a double-settle is inert), and a clear empties the queue.
|
|
14
|
+
*
|
|
15
|
+
* No React, no Ink, no conductor — just the exported functions.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* owns a disjoint slice of the kind space and renders a body only for its own
|
|
11
11
|
* kinds:
|
|
12
12
|
*
|
|
13
|
-
* - {@link PickerOverlays}
|
|
14
|
-
* - {@link SessionOverlays}
|
|
15
|
-
* - {@link AuthOverlays}
|
|
13
|
+
* - {@link PickerOverlays} — `models`, `scopedModels`, `theme`, `settings`.
|
|
14
|
+
* - {@link SessionOverlays} — `sessions`, `tree`, `userTurns`.
|
|
15
|
+
* - {@link AuthOverlays} — `signIn`, `signOut`, `oauth`, `plugin`.
|
|
16
|
+
* - {@link ApprovalOverlays} — `approval` (the tool-permission prompt).
|
|
16
17
|
*
|
|
17
18
|
* When no overlay is raised (`kind === "none"`) the host renders nothing, so the
|
|
18
19
|
* composer keeps focus and the groups are never mounted needlessly.
|