indusagi-coding-agent 0.2.8 → 0.2.9
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/package.json +2 -1
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completion source — structured suggestions for the composer's prompt line.
|
|
3
|
+
*/
|
|
4
|
+
import type { SlashRegistry } from "../contract.js";
|
|
5
|
+
|
|
6
|
+
export type CompletionKind = "slash" | "path" | "none";
|
|
7
|
+
|
|
8
|
+
export interface Suggestion {
|
|
9
|
+
readonly value: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly detail: string;
|
|
12
|
+
readonly isDir: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TokenSpan {
|
|
16
|
+
readonly start: number;
|
|
17
|
+
readonly end: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CompletionResult {
|
|
21
|
+
readonly kind: CompletionKind;
|
|
22
|
+
readonly span: TokenSpan;
|
|
23
|
+
readonly suggestions: readonly Suggestion[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DirEntry {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly isDir: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DirReader = (dirPath: string) => readonly DirEntry[];
|
|
32
|
+
|
|
33
|
+
function noCompletion(caret: number): CompletionResult {
|
|
34
|
+
return {
|
|
35
|
+
kind: "none",
|
|
36
|
+
span: { start: caret, end: caret },
|
|
37
|
+
suggestions: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isBoundary(ch: string) {
|
|
42
|
+
return ch === " " || ch === "\t" || ch === "\n";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function activeToken(buffer: string, caret: number): { span: TokenSpan; stem: string } {
|
|
46
|
+
let start = caret;
|
|
47
|
+
while (start > 0 && !isBoundary(buffer[start - 1])) start--;
|
|
48
|
+
let end = caret;
|
|
49
|
+
while (end < buffer.length && !isBoundary(buffer[end])) end++;
|
|
50
|
+
return { span: { start, end }, stem: buffer.slice(start, caret) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function completeSlash(stem: string, registry: SlashRegistry): Suggestion[] {
|
|
54
|
+
const needle = stem.slice(1).toLowerCase();
|
|
55
|
+
const exact: Suggestion[] = [];
|
|
56
|
+
const prefix: Suggestion[] = [];
|
|
57
|
+
for (const cmd of registry.commands) {
|
|
58
|
+
const names = [cmd.name, ...(cmd.aliases ?? [])];
|
|
59
|
+
const hit = names.find((n) => n.toLowerCase().startsWith(needle));
|
|
60
|
+
if (hit === void 0) continue;
|
|
61
|
+
const value = cmd.takesArgs ? `/${cmd.name} ` : `/${cmd.name}`;
|
|
62
|
+
const suggestion: Suggestion = {
|
|
63
|
+
value,
|
|
64
|
+
label: `/${cmd.name}`,
|
|
65
|
+
detail: cmd.summary,
|
|
66
|
+
isDir: false
|
|
67
|
+
};
|
|
68
|
+
if (hit.toLowerCase() === needle) exact.push(suggestion);
|
|
69
|
+
else prefix.push(suggestion);
|
|
70
|
+
}
|
|
71
|
+
return [...exact, ...prefix];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function splitPathStem(stem: string) {
|
|
75
|
+
const sigil = stem.startsWith("@") ? "@" : "";
|
|
76
|
+
const body = stem.slice(sigil.length);
|
|
77
|
+
const slash = body.lastIndexOf("/");
|
|
78
|
+
if (slash < 0) return { dir: ".", leaf: body, sigil };
|
|
79
|
+
return { dir: body.slice(0, slash) || ".", leaf: body.slice(slash + 1), sigil };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function completePath(stem: string, readDir: DirReader): Suggestion[] {
|
|
83
|
+
const { dir, leaf, sigil } = splitPathStem(stem);
|
|
84
|
+
const lowerLeaf = leaf.toLowerCase();
|
|
85
|
+
const prefix = dir === "." ? "" : `${dir}/`;
|
|
86
|
+
const matches = readDir(dir)
|
|
87
|
+
.filter((e) => e.name.toLowerCase().startsWith(lowerLeaf))
|
|
88
|
+
.sort((a, b) => {
|
|
89
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
90
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
91
|
+
});
|
|
92
|
+
return matches.map((e) => {
|
|
93
|
+
const tail = e.isDir ? "/" : "";
|
|
94
|
+
return {
|
|
95
|
+
value: `${sigil}${prefix}${e.name}${tail}`,
|
|
96
|
+
label: `${e.name}${tail}`,
|
|
97
|
+
detail: e.isDir ? "directory" : "file",
|
|
98
|
+
isDir: e.isDir
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function looksLikePath(stem: string) {
|
|
104
|
+
return stem.startsWith("@") || stem.includes("/");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Compute the completion offering at a caret position.
|
|
109
|
+
*/
|
|
110
|
+
export function completeAt(buffer: string, caret: number, registry: SlashRegistry, readDir: DirReader): CompletionResult {
|
|
111
|
+
const { span, stem } = activeToken(buffer, caret);
|
|
112
|
+
if (stem.startsWith("/") && span.start === 0) {
|
|
113
|
+
return { kind: "slash", span, suggestions: completeSlash(stem, registry) };
|
|
114
|
+
}
|
|
115
|
+
if (looksLikePath(stem)) {
|
|
116
|
+
return { kind: "path", span, suggestions: completePath(stem, readDir) };
|
|
117
|
+
}
|
|
118
|
+
return noCompletion(caret);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Splice an accepted suggestion back into the buffer.
|
|
123
|
+
*/
|
|
124
|
+
export function applySuggestion(buffer: string, span: TokenSpan, suggestion: Suggestion): { buffer: string; caret: number } {
|
|
125
|
+
const next = buffer.slice(0, span.start) + suggestion.value + buffer.slice(span.end);
|
|
126
|
+
return { buffer: next, caret: span.start + suggestion.value.length };
|
|
127
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory reader — the live `fs`-backed implementation of {@link DirReader}.
|
|
3
|
+
*/
|
|
4
|
+
import { readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import type { DirReader } from "./complete.js";
|
|
7
|
+
|
|
8
|
+
function entryIsDir(base: string, name: string, isDirent: boolean, isSymlink: boolean) {
|
|
9
|
+
if (isDirent) return true;
|
|
10
|
+
if (!isSymlink) return false;
|
|
11
|
+
try {
|
|
12
|
+
return statSync(resolve(base, name)).isDirectory();
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build a live {@link DirReader} rooted at `cwd`.
|
|
20
|
+
*/
|
|
21
|
+
export function createDirReader(cwd: string = process.cwd()): DirReader {
|
|
22
|
+
return (dirPath: string) => {
|
|
23
|
+
const target = resolve(cwd, dirPath);
|
|
24
|
+
try {
|
|
25
|
+
const dirents = readdirSync(target, { withFileTypes: true });
|
|
26
|
+
return dirents.map((d) => ({
|
|
27
|
+
name: d.name,
|
|
28
|
+
isDir: entryIsDir(target, d.name, d.isDirectory(), d.isSymbolicLink())
|
|
29
|
+
}));
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console input — public barrel for the pure composer-input modules.
|
|
3
|
+
*/
|
|
4
|
+
export type { KeyChord, ConsoleVerb, ConsoleIntent, ChordOutcome, ChordLatch, ChordStep } from "./keymap.js";
|
|
5
|
+
export { NO_INTENT, readKey, NO_CHORD, advanceChord } from "./keymap.js";
|
|
6
|
+
export type { PasteThresholds, PasteVault, PasteCapture } from "./paste.js";
|
|
7
|
+
export {
|
|
8
|
+
PASTE_THRESHOLDS,
|
|
9
|
+
PASTE_BURST_GAP_MS,
|
|
10
|
+
PASTE_CONTINUATION_MS,
|
|
11
|
+
PASTE_MARKER_LINE_CAP,
|
|
12
|
+
EMPTY_PASTE_VAULT,
|
|
13
|
+
pasteMarker,
|
|
14
|
+
classifyPaste,
|
|
15
|
+
coalescePaste,
|
|
16
|
+
appendToPaste,
|
|
17
|
+
pasteLines,
|
|
18
|
+
expandMarkers,
|
|
19
|
+
hasMarkers
|
|
20
|
+
} from "./paste.js";
|
|
21
|
+
export type { CompletionKind, Suggestion, TokenSpan, CompletionResult, DirEntry, DirReader } from "./complete.js";
|
|
22
|
+
export { completeAt, applySuggestion } from "./complete.js";
|
|
23
|
+
export { createDirReader } from "./dir-reader.js";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keymap — pure translation of raw terminal key events into console intents.
|
|
3
|
+
*/
|
|
4
|
+
import type { ModalKind } from "../contract.js";
|
|
5
|
+
|
|
6
|
+
export interface KeyChord {
|
|
7
|
+
readonly upArrow: boolean;
|
|
8
|
+
readonly downArrow: boolean;
|
|
9
|
+
readonly leftArrow: boolean;
|
|
10
|
+
readonly rightArrow: boolean;
|
|
11
|
+
readonly pageUp: boolean;
|
|
12
|
+
readonly pageDown: boolean;
|
|
13
|
+
readonly return: boolean;
|
|
14
|
+
readonly escape: boolean;
|
|
15
|
+
readonly ctrl: boolean;
|
|
16
|
+
readonly shift: boolean;
|
|
17
|
+
readonly tab: boolean;
|
|
18
|
+
readonly backspace: boolean;
|
|
19
|
+
readonly delete: boolean;
|
|
20
|
+
readonly meta: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ConsoleVerb =
|
|
24
|
+
| "text:type"
|
|
25
|
+
| "text:newline"
|
|
26
|
+
| "edit:erasePrev"
|
|
27
|
+
| "edit:eraseNext"
|
|
28
|
+
| "edit:clearLine"
|
|
29
|
+
| "nav:left"
|
|
30
|
+
| "nav:right"
|
|
31
|
+
| "nav:home"
|
|
32
|
+
| "nav:end"
|
|
33
|
+
| "nav:up"
|
|
34
|
+
| "nav:down"
|
|
35
|
+
| "flow:submit"
|
|
36
|
+
| "flow:dismiss"
|
|
37
|
+
| "flow:accept"
|
|
38
|
+
| "flow:interrupt"
|
|
39
|
+
| "flow:suspend"
|
|
40
|
+
| "flow:cycleModel"
|
|
41
|
+
| "model:cycleThinking"
|
|
42
|
+
| "mode:cyclePermission"
|
|
43
|
+
| "mode:togglePlan"
|
|
44
|
+
| "queue:dequeue"
|
|
45
|
+
| "queue:steer"
|
|
46
|
+
| "input:pasteImage"
|
|
47
|
+
| "input:externalEditor"
|
|
48
|
+
| "overlay:open"
|
|
49
|
+
| "view:expandTools"
|
|
50
|
+
| "view:toggleReasoning"
|
|
51
|
+
| "none";
|
|
52
|
+
|
|
53
|
+
export interface ConsoleIntent {
|
|
54
|
+
readonly verb: ConsoleVerb;
|
|
55
|
+
readonly text?: string;
|
|
56
|
+
readonly overlay?: ModalKind;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The inert intent — a keystroke the console ignores. */
|
|
60
|
+
export const NO_INTENT: ConsoleIntent = { verb: "none" };
|
|
61
|
+
|
|
62
|
+
function typed(text: string): ConsoleIntent {
|
|
63
|
+
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
64
|
+
return normalized.length === 0 ? NO_INTENT : { verb: "text:type", text: normalized };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isPrintable(input: string) {
|
|
68
|
+
if (input.length === 0) return false;
|
|
69
|
+
if (input.length > 1) return true;
|
|
70
|
+
const code = input.charCodeAt(0);
|
|
71
|
+
return code >= 32 && code !== 127;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Classify one raw key event into a single {@link ConsoleIntent}.
|
|
76
|
+
*/
|
|
77
|
+
export function readKey(input: string, key: KeyChord): ConsoleIntent {
|
|
78
|
+
if (key.ctrl && !key.meta) {
|
|
79
|
+
switch (input) {
|
|
80
|
+
case "c":
|
|
81
|
+
return { verb: "flow:interrupt" };
|
|
82
|
+
case "z":
|
|
83
|
+
return { verb: "flow:suspend" };
|
|
84
|
+
case "u":
|
|
85
|
+
return { verb: "edit:clearLine" };
|
|
86
|
+
case "a":
|
|
87
|
+
return { verb: "nav:home" };
|
|
88
|
+
case "e":
|
|
89
|
+
return { verb: "nav:end" };
|
|
90
|
+
case "j":
|
|
91
|
+
return { verb: "text:newline" };
|
|
92
|
+
case "n":
|
|
93
|
+
return { verb: "flow:cycleModel" };
|
|
94
|
+
case "p":
|
|
95
|
+
return { verb: "flow:cycleModel" };
|
|
96
|
+
case "t":
|
|
97
|
+
return { verb: "view:toggleReasoning" };
|
|
98
|
+
case "v":
|
|
99
|
+
return { verb: "input:pasteImage" };
|
|
100
|
+
case "g":
|
|
101
|
+
return { verb: "input:externalEditor" };
|
|
102
|
+
case "r":
|
|
103
|
+
return { verb: "overlay:open", overlay: "sessions" };
|
|
104
|
+
case "x":
|
|
105
|
+
return { verb: "queue:steer" };
|
|
106
|
+
case "l":
|
|
107
|
+
return { verb: "overlay:open", overlay: "models" };
|
|
108
|
+
case "o":
|
|
109
|
+
return { verb: "view:expandTools" };
|
|
110
|
+
default:
|
|
111
|
+
return NO_INTENT;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (key.meta && key.upArrow) return { verb: "queue:dequeue" };
|
|
115
|
+
if (key.return) {
|
|
116
|
+
return key.shift ? { verb: "text:newline" } : { verb: "flow:submit" };
|
|
117
|
+
}
|
|
118
|
+
if (key.escape) return { verb: "flow:dismiss" };
|
|
119
|
+
if (key.tab) return key.shift ? { verb: "mode:cyclePermission" } : { verb: "flow:accept" };
|
|
120
|
+
if (key.backspace || key.delete || input === "\x7F" || input === "\b") {
|
|
121
|
+
return { verb: "edit:erasePrev" };
|
|
122
|
+
}
|
|
123
|
+
if (key.leftArrow) return { verb: "nav:left" };
|
|
124
|
+
if (key.rightArrow) return { verb: "nav:right" };
|
|
125
|
+
if (key.upArrow) return { verb: "nav:up" };
|
|
126
|
+
if (key.downArrow) return { verb: "nav:down" };
|
|
127
|
+
if (key.pageUp) return { verb: "nav:home" };
|
|
128
|
+
if (key.pageDown) return { verb: "nav:end" };
|
|
129
|
+
return isPrintable(input) ? typed(input) : NO_INTENT;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type ChordOutcome = "dismiss×2" | "clear×2";
|
|
133
|
+
|
|
134
|
+
export interface ChordLatch {
|
|
135
|
+
readonly armed: string | null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const NO_CHORD: ChordLatch = { armed: null };
|
|
139
|
+
|
|
140
|
+
export interface ChordStep {
|
|
141
|
+
readonly next: ChordLatch;
|
|
142
|
+
readonly fired: ChordOutcome | null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const CHORD_VERBS: Partial<Record<ConsoleVerb, ChordOutcome>> = {
|
|
146
|
+
"flow:dismiss": "dismiss×2",
|
|
147
|
+
"edit:clearLine": "clear×2"
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export function advanceChord(latch: ChordLatch, intent: ConsoleIntent): ChordStep {
|
|
151
|
+
const outcome = CHORD_VERBS[intent.verb];
|
|
152
|
+
if (outcome === void 0) {
|
|
153
|
+
return { next: NO_CHORD, fired: null };
|
|
154
|
+
}
|
|
155
|
+
if (latch.armed === intent.verb) {
|
|
156
|
+
return { next: NO_CHORD, fired: outcome };
|
|
157
|
+
}
|
|
158
|
+
return { next: { armed: intent.verb }, fired: null };
|
|
159
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paste coalescing — collapse a large clipboard burst into one compact marker.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PasteThresholds {
|
|
6
|
+
readonly lines: number;
|
|
7
|
+
readonly chars: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const PASTE_THRESHOLDS: PasteThresholds = { lines: 10, chars: 1001 };
|
|
11
|
+
export const PASTE_BURST_GAP_MS = 24;
|
|
12
|
+
export const PASTE_CONTINUATION_MS = 250;
|
|
13
|
+
export const PASTE_MARKER_LINE_CAP = 1000;
|
|
14
|
+
|
|
15
|
+
/** The console's own placeholder format for a hidden paste body. */
|
|
16
|
+
export function pasteMarker(ordinal: number, lineCount: number): string {
|
|
17
|
+
return `[Pasted text #${ordinal}${lineCount > 0 ? ` +${lineCount} lines` : ""}]`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function markerScanner() {
|
|
21
|
+
return /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function lineBreaks(chunk: string) {
|
|
25
|
+
let count = 0;
|
|
26
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
27
|
+
if (chunk.charCodeAt(i) === 10) count++;
|
|
28
|
+
}
|
|
29
|
+
return count;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decide whether a pasted chunk is large enough to hide behind a marker.
|
|
34
|
+
*/
|
|
35
|
+
export function classifyPaste(chunk: string, thresholds: PasteThresholds = PASTE_THRESHOLDS): "inline" | "coalesce" {
|
|
36
|
+
if (chunk.length >= thresholds.chars) return "coalesce";
|
|
37
|
+
if (lineBreaks(chunk) >= thresholds.lines) return "coalesce";
|
|
38
|
+
return "inline";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PasteVault {
|
|
42
|
+
readonly bodies: ReadonlyMap<number, string>;
|
|
43
|
+
readonly minted: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const EMPTY_PASTE_VAULT: PasteVault = { bodies: new Map(), minted: 0 };
|
|
47
|
+
|
|
48
|
+
export interface PasteCapture {
|
|
49
|
+
readonly marker: string;
|
|
50
|
+
readonly vault: PasteVault;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Mint a marker for a large paste and record its body in a fresh vault.
|
|
55
|
+
*/
|
|
56
|
+
export function coalescePaste(body: string, vault: PasteVault = EMPTY_PASTE_VAULT): PasteCapture {
|
|
57
|
+
const ordinal = vault.minted + 1;
|
|
58
|
+
const lineCount = body.length === 0 ? 0 : lineBreaks(body) + 1;
|
|
59
|
+
const marker = pasteMarker(ordinal, lineCount);
|
|
60
|
+
const bodies = new Map(vault.bodies);
|
|
61
|
+
bodies.set(ordinal, body);
|
|
62
|
+
return { marker, vault: { bodies, minted: ordinal } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Grow an existing paste body with a follow-on chunk, reminting its marker.
|
|
67
|
+
*/
|
|
68
|
+
export function appendToPaste(ordinal: number, extra: string, vault: PasteVault): PasteCapture {
|
|
69
|
+
const body = (vault.bodies.get(ordinal) ?? "") + extra;
|
|
70
|
+
const lineCount = body.length === 0 ? 0 : lineBreaks(body) + 1;
|
|
71
|
+
const marker = pasteMarker(ordinal, lineCount);
|
|
72
|
+
const bodies = new Map(vault.bodies);
|
|
73
|
+
bodies.set(ordinal, body);
|
|
74
|
+
return { marker, vault: { bodies, minted: vault.minted } };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The line count of an ordinal's stored body.
|
|
79
|
+
*/
|
|
80
|
+
export function pasteLines(vault: PasteVault, ordinal: number): number {
|
|
81
|
+
const body = vault.bodies.get(ordinal);
|
|
82
|
+
if (body === void 0 || body.length === 0) return 0;
|
|
83
|
+
return lineBreaks(body) + 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Rewrite every paste marker in a buffer back to the text it stands for.
|
|
88
|
+
*/
|
|
89
|
+
export function expandMarkers(buffer: string, vault: PasteVault): string {
|
|
90
|
+
if (vault.bodies.size === 0) return buffer;
|
|
91
|
+
return buffer.replace(markerScanner(), (match, ordinalStr) => {
|
|
92
|
+
const ordinal = parseInt(ordinalStr, 10);
|
|
93
|
+
const body = vault.bodies.get(ordinal);
|
|
94
|
+
return body === void 0 ? match : body;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Report whether a buffer still contains any minted paste marker.
|
|
100
|
+
*/
|
|
101
|
+
export function hasMarkers(buffer: string, vault: PasteVault): boolean {
|
|
102
|
+
if (vault.bodies.size === 0) return false;
|
|
103
|
+
return markerScanner().test(buffer);
|
|
104
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console mount — render the interactive surface and resolve on exit.
|
|
3
|
+
*/
|
|
4
|
+
import { createElement } from "indusagi/react-host";
|
|
5
|
+
import { render } from "indusagi/react-host/ink";
|
|
6
|
+
import { TerminalConsole } from "./components/TerminalConsole.js";
|
|
7
|
+
import { DEFAULT_SLASH_REGISTRY } from "./slash/index.js";
|
|
8
|
+
import { resolveTheme } from "./theme/index.js";
|
|
9
|
+
import type { OverlayServices, SessionConductor, SlashRegistry } from "./contract.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for {@link mountConsole}.
|
|
13
|
+
*/
|
|
14
|
+
export interface MountConsoleOptions {
|
|
15
|
+
readonly scheme?: string | null;
|
|
16
|
+
readonly slash?: SlashRegistry;
|
|
17
|
+
readonly initialInput?: string;
|
|
18
|
+
readonly initialImages?: readonly string[];
|
|
19
|
+
readonly verbose?: boolean;
|
|
20
|
+
readonly services?: OverlayServices;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The handle a {@link mountConsole} call resolves with once the surface exits.
|
|
25
|
+
*/
|
|
26
|
+
export interface MountResult {
|
|
27
|
+
readonly clean: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Render the interactive console for a session and resolve when it exits.
|
|
32
|
+
*/
|
|
33
|
+
export async function mountConsole(
|
|
34
|
+
conductor: SessionConductor,
|
|
35
|
+
opts: MountConsoleOptions = {}
|
|
36
|
+
): Promise<MountResult> {
|
|
37
|
+
const theme = resolveTheme(opts.scheme ?? null);
|
|
38
|
+
const slash = opts.slash ?? DEFAULT_SLASH_REGISTRY;
|
|
39
|
+
let exited = false;
|
|
40
|
+
const props = {
|
|
41
|
+
conductor,
|
|
42
|
+
theme,
|
|
43
|
+
slash,
|
|
44
|
+
initialInput: opts.initialInput,
|
|
45
|
+
initialImages: opts.initialImages,
|
|
46
|
+
verbose: opts.verbose,
|
|
47
|
+
services: opts.services,
|
|
48
|
+
onExit: () => {
|
|
49
|
+
exited = true;
|
|
50
|
+
instance.unmount();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const instance = render(createElement(TerminalConsole, props));
|
|
54
|
+
await instance.waitUntilExit();
|
|
55
|
+
return { clean: exited };
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval queue — the headless, pure serializer behind the approval overlay.
|
|
3
|
+
*/
|
|
4
|
+
import type { ApprovalChoice, PermissionMode } from "../../conductor/index.js";
|
|
5
|
+
import type { ApprovalRequest } from "./approval.js";
|
|
6
|
+
|
|
7
|
+
/** One parked approval request awaiting the user's choice. */
|
|
8
|
+
export interface ApprovalEntry {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly request: ApprovalRequest;
|
|
11
|
+
readonly resolve: (choice: ApprovalChoice) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The immutable queue state. */
|
|
15
|
+
export interface ApprovalQueueState {
|
|
16
|
+
readonly entries: readonly ApprovalEntry[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The empty queue — no approval prompt pending. */
|
|
20
|
+
export const EMPTY_APPROVAL_QUEUE: ApprovalQueueState = { entries: [] };
|
|
21
|
+
|
|
22
|
+
export type ApprovalQueueEvent =
|
|
23
|
+
| { readonly type: "enqueue"; readonly entry: ApprovalEntry }
|
|
24
|
+
| { readonly type: "settle"; readonly id: string }
|
|
25
|
+
| { readonly type: "clear" };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fold one {@link ApprovalQueueEvent} over the queue, returning a fresh state.
|
|
29
|
+
*/
|
|
30
|
+
export function approvalQueueReducer(state: ApprovalQueueState, event: ApprovalQueueEvent): ApprovalQueueState {
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case "enqueue":
|
|
33
|
+
return { entries: [...state.entries, event.entry] };
|
|
34
|
+
case "settle": {
|
|
35
|
+
const next = state.entries.filter((entry) => entry.id !== event.id);
|
|
36
|
+
return next.length === state.entries.length ? state : { entries: next };
|
|
37
|
+
}
|
|
38
|
+
case "clear":
|
|
39
|
+
return state.entries.length === 0 ? state : EMPTY_APPROVAL_QUEUE;
|
|
40
|
+
default:
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The active prompt — the head entry — or `undefined` when the queue is empty.
|
|
47
|
+
*/
|
|
48
|
+
export function activeApproval(state: ApprovalQueueState): ApprovalEntry | undefined {
|
|
49
|
+
return state.entries[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether switching INTO `mode` mid-prompt should release every PARKED approval request.
|
|
54
|
+
*/
|
|
55
|
+
export function modeReleasesApprovals(mode: PermissionMode): boolean {
|
|
56
|
+
return mode === "bypass" || mode === "bypassPermissions";
|
|
57
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval overlay — the interactive tool-permission prompt.
|
|
3
|
+
*/
|
|
4
|
+
import { Box, Text } from "indusagi/react-host/ink";
|
|
5
|
+
import { ThemeDialog, type ThemeDialogItem } from "indusagi/react-ink";
|
|
6
|
+
import { jsx, jsxs } from "indusagi/react-host/jsx-runtime";
|
|
7
|
+
import type { ApprovalChoice } from "../../conductor/index.js";
|
|
8
|
+
import type { OverlayGroupProps } from "./pickers.js";
|
|
9
|
+
|
|
10
|
+
export interface ApprovalRequest {
|
|
11
|
+
readonly toolName: string;
|
|
12
|
+
readonly input: unknown;
|
|
13
|
+
readonly suggestions?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ApprovalPayload {
|
|
17
|
+
readonly request: ApprovalRequest;
|
|
18
|
+
readonly resolve: (choice: ApprovalChoice) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const APPROVAL_CHOICES: readonly (ThemeDialogItem & { id: ApprovalChoice })[] = [
|
|
22
|
+
{ id: "allow-once", label: "Allow once", description: "run this one call" },
|
|
23
|
+
{
|
|
24
|
+
id: "allow-always",
|
|
25
|
+
label: "Allow always",
|
|
26
|
+
description: "run it and remember this action for the session"
|
|
27
|
+
},
|
|
28
|
+
{ id: "deny", label: "Deny", description: "block this call" }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const DISMISS_CHOICE: ApprovalChoice = "deny";
|
|
32
|
+
|
|
33
|
+
export function choiceFromId(id: string): ApprovalChoice {
|
|
34
|
+
const match = APPROVAL_CHOICES.find((choice) => choice.id === id);
|
|
35
|
+
return (match?.id ?? DISMISS_CHOICE) as ApprovalChoice;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function readApprovalPayload(payload: unknown): ApprovalPayload | undefined {
|
|
39
|
+
if (payload === null || typeof payload !== "object") return void 0;
|
|
40
|
+
const bag = payload as any;
|
|
41
|
+
if (typeof bag.resolve !== "function") return void 0;
|
|
42
|
+
const request = bag.request;
|
|
43
|
+
if (request === null || typeof request !== "object") return void 0;
|
|
44
|
+
const req = request as any;
|
|
45
|
+
if (typeof req.toolName !== "string") return void 0;
|
|
46
|
+
const suggestions = Array.isArray(req.suggestions) ? req.suggestions.filter((s: unknown) => typeof s === "string") : void 0;
|
|
47
|
+
return {
|
|
48
|
+
request: {
|
|
49
|
+
toolName: req.toolName,
|
|
50
|
+
input: req.input,
|
|
51
|
+
...(suggestions !== void 0 && suggestions.length > 0 ? { suggestions } : {})
|
|
52
|
+
},
|
|
53
|
+
resolve: bag.resolve
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ARG_PREVIEW_LIMIT = 200;
|
|
58
|
+
|
|
59
|
+
export function summarizeInput(input: unknown): string {
|
|
60
|
+
let text: string;
|
|
61
|
+
if (input === null || input === void 0) {
|
|
62
|
+
text = "";
|
|
63
|
+
} else if (typeof input === "string") {
|
|
64
|
+
text = input;
|
|
65
|
+
} else if (typeof input === "object") {
|
|
66
|
+
const bag = input as any;
|
|
67
|
+
if (typeof bag.command === "string") {
|
|
68
|
+
text = bag.command;
|
|
69
|
+
} else {
|
|
70
|
+
try {
|
|
71
|
+
text = JSON.stringify(bag);
|
|
72
|
+
} catch {
|
|
73
|
+
text = "[unserialisable arguments]";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
text = String(input);
|
|
78
|
+
}
|
|
79
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
80
|
+
if (text.length > ARG_PREVIEW_LIMIT) {
|
|
81
|
+
return `${text.slice(0, ARG_PREVIEW_LIMIT)}…`;
|
|
82
|
+
}
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ApprovalDialog(props: { payload: ApprovalPayload; closeModal: () => void }) {
|
|
87
|
+
const { payload, closeModal } = props;
|
|
88
|
+
const { request, resolve } = payload;
|
|
89
|
+
let settled = false;
|
|
90
|
+
const settle = (choice: ApprovalChoice) => {
|
|
91
|
+
if (settled) return;
|
|
92
|
+
settled = true;
|
|
93
|
+
resolve(choice);
|
|
94
|
+
closeModal();
|
|
95
|
+
};
|
|
96
|
+
const args = summarizeInput(request.input);
|
|
97
|
+
const suggestion = request.suggestions?.[0];
|
|
98
|
+
return jsxs(Box, {
|
|
99
|
+
flexDirection: "column",
|
|
100
|
+
children: [
|
|
101
|
+
jsxs(Text, {
|
|
102
|
+
children: ["Allow ", jsx(Text, { bold: true, children: request.toolName }), " to run?"]
|
|
103
|
+
}),
|
|
104
|
+
args.length > 0 ? jsx(Text, { dimColor: true, children: args }) : null,
|
|
105
|
+
suggestion !== void 0
|
|
106
|
+
? jsxs(Text, {
|
|
107
|
+
dimColor: true,
|
|
108
|
+
children: ["Allow-always remembers: ", suggestion]
|
|
109
|
+
})
|
|
110
|
+
: null,
|
|
111
|
+
jsx(ThemeDialog, {
|
|
112
|
+
themes: APPROVAL_CHOICES.map((choice) => ({
|
|
113
|
+
id: choice.id,
|
|
114
|
+
label: choice.label,
|
|
115
|
+
description: choice.description
|
|
116
|
+
})),
|
|
117
|
+
onSelect: (id: string) => settle(choiceFromId(id)),
|
|
118
|
+
onClose: () => settle(DISMISS_CHOICE)
|
|
119
|
+
})
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function ApprovalOverlays(props: OverlayGroupProps) {
|
|
125
|
+
const { modal, closeModal } = props;
|
|
126
|
+
if (modal.kind !== "approval") return null;
|
|
127
|
+
const payload = readApprovalPayload(modal.payload);
|
|
128
|
+
if (payload === void 0) return null;
|
|
129
|
+
return jsx(ApprovalDialog, { payload, closeModal });
|
|
130
|
+
}
|