indusagi-coding-agent 0.1.61 → 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/CHANGELOG.md +27 -0
- package/dist/entry.js +11589 -14719
- 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 +38 -6
- package/dist/types/conductor/contract.d.ts +221 -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/Banner.d.ts +28 -6
- package/dist/types/console/components/Emblem.d.ts +49 -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/components/banner-sweep.d.ts +55 -0
- package/dist/types/console/components/banner.test.d.ts +9 -0
- package/dist/types/console/contract.d.ts +41 -7
- 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/console/theme/adapter.d.ts +19 -0
- package/dist/types/console/theme/index.d.ts +1 -1
- package/dist/types/console/theme/palette.d.ts +25 -0
- package/dist/types/console/theme/tokens.d.ts +23 -1
- package/dist/types/index.d.ts +1 -1
- 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 +59 -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 +6 -0
- package/dist/types/workspace/index.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.
|
|
@@ -33,6 +33,26 @@ export interface BannerProps {
|
|
|
33
33
|
* single compact header line. Wired from the verbose / quiet-startup flag.
|
|
34
34
|
*/
|
|
35
35
|
readonly quiet?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When set, the masthead auto-condenses to a single emblem + brand + model
|
|
38
|
+
* line (the repeat-launch presentation): the user has already seen this
|
|
39
|
+
* version's full masthead, so the big wordmark is skipped. Distinct from
|
|
40
|
+
* {@link quiet}, which is the manual suppression toggle; either collapses the
|
|
41
|
+
* banner, but `compact` keeps the small emblem and the welcome line.
|
|
42
|
+
*/
|
|
43
|
+
readonly compact?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The signed-in account label, used for the personalized "Welcome back,
|
|
46
|
+
* {name}!" line. Absent → the plain "Welcome back!" fallback is shown.
|
|
47
|
+
*/
|
|
48
|
+
readonly name?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Opt-in static colour-sweep flourish: when set, the wordmark and emblem fill
|
|
51
|
+
* are tinted along a frozen primary→secondary gradient instead of the flat
|
|
52
|
+
* accent. The caller is responsible for suppressing it under reduced-motion /
|
|
53
|
+
* non-TTY; this prop is simply the resolved on/off decision.
|
|
54
|
+
*/
|
|
55
|
+
readonly sweep?: boolean;
|
|
36
56
|
/** The gathered session resources rendered as the Startup Map panel. */
|
|
37
57
|
readonly startup?: StartupMap;
|
|
38
58
|
/** Out-of-band lines drawn above the wordmark (errors, warnings, info). */
|
|
@@ -43,12 +63,14 @@ export interface BannerProps {
|
|
|
43
63
|
/**
|
|
44
64
|
* Render the console masthead.
|
|
45
65
|
*
|
|
46
|
-
* In the default (loud) mode this is the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
66
|
+
* In the default (loud) mode this is the two-tone emblem beside the block-letter
|
|
67
|
+
* wordmark, the brand / version line, the personalized welcome line, the
|
|
68
|
+
* optional notices region, the bordered Startup Map, and the changelog block.
|
|
69
|
+
* The {@link BannerProps.quiet} or {@link BannerProps.compact} flags collapse
|
|
70
|
+
* all of that to a single compact header line (emblem glyph + brand + version +
|
|
71
|
+
* model), still carrying the welcome line, the notices, and a condensed
|
|
72
|
+
* changelog so nothing important is silently dropped.
|
|
51
73
|
*
|
|
52
74
|
* @param props the wordmark context, version, session facts, and startup chrome
|
|
53
75
|
*/
|
|
54
|
-
export declare function Banner({ theme, modelId, workspace, version, verbose, quiet, startup, notices, changelog, }: BannerProps): JSX.Element;
|
|
76
|
+
export declare function Banner({ theme, modelId, workspace, version, verbose, quiet, compact, name, sweep, startup, notices, changelog, }: BannerProps): JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emblem — the two-tone block monogram that sits to the left of the wordmark
|
|
3
|
+
* (roadmap #2).
|
|
4
|
+
*
|
|
5
|
+
* Before this, the masthead was lettering alone — the "INDUS CODE" wordmark
|
|
6
|
+
* *was* the whole identity, painted in one flat accent. The emblem gives the
|
|
7
|
+
* brand a *mark*: a small fixed-width block glyph built from the box quadrant
|
|
8
|
+
* family (`▛ ▜ ▙ ▟ ▝ ▘ ▗ ▖ █ ▓`), split per row into a bright "fill" span and a
|
|
9
|
+
* dim "shadow" span so it reads as a single solid shape lit from one corner.
|
|
10
|
+
* The fill is the primary accent; the shadow is the secondary accent — the two
|
|
11
|
+
* distinct palette hues the scheme already carries — so the mark is genuinely
|
|
12
|
+
* two-tone rather than a single colour with a darker edge.
|
|
13
|
+
*
|
|
14
|
+
* It is purely presentational: a fixed glyph grid tinted through the framework
|
|
15
|
+
* {@link InkThemeAdapter} at render time, no state, no effects, no props beyond
|
|
16
|
+
* the theme and an optional row-colour override the banner uses to fold the
|
|
17
|
+
* emblem's fill into a frozen colour-sweep.
|
|
18
|
+
*
|
|
19
|
+
* Layout is a `flexDirection="column"` of one `<Box>` per row; within a row the
|
|
20
|
+
* fill and shadow are adjacent `<Text>` spans so each row's two tones share a
|
|
21
|
+
* line. The whole emblem is the same display width on every row, so it stacks
|
|
22
|
+
* into a clean column the wordmark can sit beside in a parent row Box.
|
|
23
|
+
*/
|
|
24
|
+
import type { InkThemeAdapter } from "indusagi/react-ink";
|
|
25
|
+
/** How many terminal rows the emblem occupies (its glyph-grid height). */
|
|
26
|
+
export declare const EMBLEM_HEIGHT: number;
|
|
27
|
+
/** What the {@link Emblem} renders. */
|
|
28
|
+
export interface EmblemProps {
|
|
29
|
+
/** The framework adapter that turns token roles into terminal colours. */
|
|
30
|
+
readonly theme: InkThemeAdapter;
|
|
31
|
+
/**
|
|
32
|
+
* Optional per-row fill colour (a `#rrggbb` hex), indexed by row. When supplied
|
|
33
|
+
* (the banner's frozen colour-sweep), row `i`'s fill is painted with
|
|
34
|
+
* `rowColors[i]` via chalk's hex path instead of the flat accent role; the
|
|
35
|
+
* shadow keeps the secondary accent so the mark stays two-tone. Absent rows
|
|
36
|
+
* fall back to the accent role.
|
|
37
|
+
*/
|
|
38
|
+
readonly rowColors?: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Render the two-tone block emblem.
|
|
42
|
+
*
|
|
43
|
+
* Each row paints its fill span in the accent role (or the supplied sweep colour
|
|
44
|
+
* for that row) and its shadow span in the secondary `customMessage` role, so the
|
|
45
|
+
* monogram reads as one solid mark with a lit and a shadowed face.
|
|
46
|
+
*
|
|
47
|
+
* @param props the theme adapter and an optional per-row fill-colour override
|
|
48
|
+
*/
|
|
49
|
+
export declare function Emblem({ theme, rowColors }: EmblemProps): JSX.Element;
|
|
@@ -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 {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner colour-sweep helpers — the pure, render-agnostic maths behind the
|
|
3
|
+
* optional startup flourish (roadmap #14) and the personalized welcome line
|
|
4
|
+
* (#7).
|
|
5
|
+
*
|
|
6
|
+
* Two concerns, no React or Ink, no I/O, no state:
|
|
7
|
+
*
|
|
8
|
+
* - {@link rowGradient} computes a *frozen* (single-pass, no animation timer)
|
|
9
|
+
* colour for one wordmark row by lerping between two palette hexes across
|
|
10
|
+
* the block's rows. The banner paints row `i` of `n` with this colour when
|
|
11
|
+
* the opt-in sweep is on; off, the wordmark stays a flat accent. Because it
|
|
12
|
+
* takes no clock and never schedules a frame, it is reduced-motion safe by
|
|
13
|
+
* construction — the suppression decision lives at the call site, which
|
|
14
|
+
* simply does not ask for a gradient when motion is reduced.
|
|
15
|
+
*
|
|
16
|
+
* - {@link welcomeLine} formats the "Welcome back{, name}!" greeting from the
|
|
17
|
+
* signed-in account label, with a length guard and a name-less fallback, so
|
|
18
|
+
* the banner and its tests share one formatter rather than re-deriving the
|
|
19
|
+
* string.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is referentially transparent: same inputs → same output.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Format the personalized welcome greeting.
|
|
25
|
+
*
|
|
26
|
+
* Returns `"Welcome back, {name}!"` for a usable name, and the plain
|
|
27
|
+
* `"Welcome back!"` fallback when the name is absent, blank, or too long to read
|
|
28
|
+
* as a name. The name is trimmed of surrounding whitespace before the checks so
|
|
29
|
+
* a padded label does not slip past the guards.
|
|
30
|
+
*
|
|
31
|
+
* @param name the signed-in account label, if one is known
|
|
32
|
+
* @returns the greeting string (never the tag markup — plain text)
|
|
33
|
+
*/
|
|
34
|
+
export declare function welcomeLine(name?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The frozen sweep colour for one wordmark row.
|
|
37
|
+
*
|
|
38
|
+
* Lerps from `primaryHex` (the first row) to `secondaryHex` (the last row)
|
|
39
|
+
* across the `rowCount` rows of the block, returning the colour for row
|
|
40
|
+
* `rowIndex` as a `#rrggbb` hex string. A single-row block pins to the primary;
|
|
41
|
+
* the endpoints are exact (row 0 = primary, row n-1 = secondary). If either hex
|
|
42
|
+
* cannot be parsed, the corresponding raw input hex is returned so the banner
|
|
43
|
+
* still gets a usable colour string rather than nothing.
|
|
44
|
+
*
|
|
45
|
+
* This computes a *static* gradient — there is no timer, no frame loop, no
|
|
46
|
+
* clock. The whole sweep is laid down in the one render pass, which is exactly
|
|
47
|
+
* why it is safe under reduced-motion: the flourish is colour, not movement.
|
|
48
|
+
*
|
|
49
|
+
* @param rowIndex the zero-based row being painted
|
|
50
|
+
* @param rowCount the total number of rows in the wordmark block
|
|
51
|
+
* @param primaryHex the colour of the first row (e.g. the accent)
|
|
52
|
+
* @param secondaryHex the colour of the last row (e.g. the secondary accent)
|
|
53
|
+
* @returns a `#rrggbb` hex string (or a raw input hex when one is unparseable)
|
|
54
|
+
*/
|
|
55
|
+
export declare function rowGradient(rowIndex: number, rowCount: number, primaryHex: string, secondaryHex: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner colour-sweep helpers — pure behavioural tests.
|
|
3
|
+
*
|
|
4
|
+
* These exercise the render-agnostic maths behind the masthead flourish without
|
|
5
|
+
* mounting Ink: the welcome-line formatter (#7) and the frozen colour-sweep
|
|
6
|
+
* gradient (#14). Both are referentially transparent, so the assertions are
|
|
7
|
+
* exact.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -56,13 +56,17 @@ export type { SessionSnapshot, ToolExecutionState, StatusMessage, UiDisplayBlock
|
|
|
56
56
|
/** Re-exported conductor vocabulary the console renders and drives. */
|
|
57
57
|
export type { SessionConductor, SessionSignal, ConductorState };
|
|
58
58
|
/**
|
|
59
|
-
* The
|
|
60
|
-
*
|
|
61
|
-
* Named for the time-of-day they evoke rather than a bare light/dark axis
|
|
62
|
-
*
|
|
63
|
-
* - `
|
|
59
|
+
* The built-in colour schemes the console ships with.
|
|
60
|
+
*
|
|
61
|
+
* Named for the time-of-day they evoke rather than a bare light/dark axis, with
|
|
62
|
+
* a daltonized (color-blind-friendly) variant of each:
|
|
63
|
+
* - `midnight` — a low-luminance scheme for dark terminals.
|
|
64
|
+
* - `daylight` — a high-luminance scheme for light terminals.
|
|
65
|
+
* - `midnight-cb` — the dark scheme re-derived so success vs failure separates
|
|
66
|
+
* off the red-green axis (success → blue), deuteran/protan-safe.
|
|
67
|
+
* - `daylight-cb` — the light scheme's color-blind-safe counterpart.
|
|
64
68
|
*/
|
|
65
|
-
export type ThemeScheme = "midnight" | "daylight";
|
|
69
|
+
export type ThemeScheme = "midnight" | "daylight" | "midnight-cb" | "daylight-cb";
|
|
66
70
|
/** The default scheme applied before any user preference is loaded. */
|
|
67
71
|
export declare const DEFAULT_SCHEME: ThemeScheme;
|
|
68
72
|
/**
|
|
@@ -96,6 +100,23 @@ export declare function isThemeScheme(value: string): value is ThemeScheme;
|
|
|
96
100
|
* - `caution` — warning status tone.
|
|
97
101
|
* - `alarm` — error/fault status tone.
|
|
98
102
|
* - `pending` — busy/in-flight status tone.
|
|
103
|
+
*
|
|
104
|
+
* Rich-render roles (markdown / diff / syntax highlighting). These feed the
|
|
105
|
+
* framework adapter's markdown/diff/highlight accessors so the styled
|
|
106
|
+
* transcript, colored diffs, and fenced-code highlighting recolor for free with
|
|
107
|
+
* each scheme:
|
|
108
|
+
* - `codeInline` — inline `` `code` `` foreground.
|
|
109
|
+
* - `heading` — markdown heading foreground.
|
|
110
|
+
* - `blockquoteBar` — the dim bar drawn beside a blockquote.
|
|
111
|
+
* - `diffAddedBg` — background tint for an added (`+`) diff line.
|
|
112
|
+
* - `diffRemovedBg` — background tint for a removed (`-`) diff line.
|
|
113
|
+
* - `diffAddedText` — foreground for added-line content / `+` marker.
|
|
114
|
+
* - `diffRemovedText`— foreground for removed-line content / `-` marker.
|
|
115
|
+
* - `synKeyword` — syntax scope: keywords.
|
|
116
|
+
* - `synString` — syntax scope: string literals.
|
|
117
|
+
* - `synNumber` — syntax scope: numeric literals.
|
|
118
|
+
* - `synComment` — syntax scope: comments.
|
|
119
|
+
* - `synType` — syntax scope: types / classes / built-ins.
|
|
99
120
|
*/
|
|
100
121
|
export interface ThemeTokens {
|
|
101
122
|
readonly signal: string;
|
|
@@ -111,6 +132,18 @@ export interface ThemeTokens {
|
|
|
111
132
|
readonly caution: string;
|
|
112
133
|
readonly alarm: string;
|
|
113
134
|
readonly pending: string;
|
|
135
|
+
readonly codeInline: string;
|
|
136
|
+
readonly heading: string;
|
|
137
|
+
readonly blockquoteBar: string;
|
|
138
|
+
readonly diffAddedBg: string;
|
|
139
|
+
readonly diffRemovedBg: string;
|
|
140
|
+
readonly diffAddedText: string;
|
|
141
|
+
readonly diffRemovedText: string;
|
|
142
|
+
readonly synKeyword: string;
|
|
143
|
+
readonly synString: string;
|
|
144
|
+
readonly synNumber: string;
|
|
145
|
+
readonly synComment: string;
|
|
146
|
+
readonly synType: string;
|
|
114
147
|
}
|
|
115
148
|
/** The closed set of token role names, for iteration and validation. */
|
|
116
149
|
export type ThemeToken = keyof ThemeTokens;
|
|
@@ -211,8 +244,9 @@ export interface ViewRow {
|
|
|
211
244
|
* - `signOut` — the provider sign-out confirmation.
|
|
212
245
|
* - `oauth` — the in-flight OAuth device/redirect flow.
|
|
213
246
|
* - `plugin` — a plugin-supplied select/confirm/input/custom overlay.
|
|
247
|
+
* - `approval` — a pending tool-permission prompt (allow once / always / deny).
|
|
214
248
|
*/
|
|
215
|
-
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";
|
|
216
250
|
/**
|
|
217
251
|
* The active modal plus any opaque payload the matching dialog needs.
|
|
218
252
|
*
|