indusagi-coding-agent 0.2.5 → 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.
Files changed (224) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +9 -5
  3. package/dist/entry.js +14267 -24282
  4. package/dist/guardrails.js +924 -7867
  5. package/dist/index.js +13706 -23775
  6. package/package.json +3 -2
  7. package/src/_decls/entry.ts +18 -0
  8. package/src/_decls/guardrails.ts +35 -0
  9. package/src/_decls/index.ts +26 -0
  10. package/src/addons/contract.ts +236 -0
  11. package/src/addons/dispatch/event-dispatcher.ts +164 -0
  12. package/src/addons/dispatch/index.ts +25 -0
  13. package/src/addons/dispatch/tool-interceptor.ts +208 -0
  14. package/src/addons/host.ts +225 -0
  15. package/src/addons/index.ts +112 -0
  16. package/src/addons/manifest.ts +158 -0
  17. package/src/addons/sandbox.ts +170 -0
  18. package/src/addons/surface.ts +78 -0
  19. package/src/boot/auth-vault.ts +195 -0
  20. package/src/boot/boot.ts +138 -0
  21. package/src/boot/contract.ts +238 -0
  22. package/src/boot/heap.ts +59 -0
  23. package/src/boot/index.ts +28 -0
  24. package/src/boot/invocation.ts +93 -0
  25. package/src/boot/runners/addon-wiring.ts +153 -0
  26. package/src/boot/runners/checkpoint.ts +169 -0
  27. package/src/boot/runners/delegate-runner.ts +294 -0
  28. package/src/boot/runners/index.ts +13 -0
  29. package/src/boot/runners/link-runner.ts +45 -0
  30. package/src/boot/runners/memdir.ts +168 -0
  31. package/src/boot/runners/oneshot-runner.ts +58 -0
  32. package/src/boot/runners/read-state.ts +90 -0
  33. package/src/boot/runners/registry.ts +42 -0
  34. package/src/boot/runners/repl-runner.ts +143 -0
  35. package/src/boot/runners/server-mode.ts +121 -0
  36. package/src/boot/runners/session.ts +641 -0
  37. package/src/boot/server-token.ts +148 -0
  38. package/src/boot/stages.ts +167 -0
  39. package/src/boot/upgrade/apply.ts +94 -0
  40. package/src/boot/upgrade/index.ts +13 -0
  41. package/src/boot/upgrade/upgrades.ts +289 -0
  42. package/src/briefing/compose.ts +150 -0
  43. package/src/briefing/context-docs.ts +19 -0
  44. package/src/briefing/contract.ts +717 -0
  45. package/src/briefing/index.ts +31 -0
  46. package/src/briefing/macros.ts +97 -0
  47. package/src/briefing/skills.ts +47 -0
  48. package/src/capability-deck/bridge-ledger/index.ts +27 -0
  49. package/src/capability-deck/bridge-ledger/key.ts +67 -0
  50. package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
  51. package/src/capability-deck/bridge-ledger/network.ts +117 -0
  52. package/src/capability-deck/builtin-bridge.ts +312 -0
  53. package/src/capability-deck/cards/bg-process-card.ts +335 -0
  54. package/src/capability-deck/cards/index.ts +115 -0
  55. package/src/capability-deck/cards/memory-card.ts +146 -0
  56. package/src/capability-deck/cards/plan-file.ts +97 -0
  57. package/src/capability-deck/cards/plan-tools.ts +185 -0
  58. package/src/capability-deck/cards/saas-card.ts +183 -0
  59. package/src/capability-deck/cards/task-card.ts +207 -0
  60. package/src/capability-deck/cards/todo-card.ts +168 -0
  61. package/src/capability-deck/cards/workflow-card.ts +247 -0
  62. package/src/capability-deck/contract.ts +388 -0
  63. package/src/capability-deck/index.ts +48 -0
  64. package/src/capability-deck/manifest.ts +109 -0
  65. package/src/capability-deck/provision.ts +169 -0
  66. package/src/channels/contract.ts +191 -0
  67. package/src/channels/framer.ts +50 -0
  68. package/src/channels/index.ts +101 -0
  69. package/src/channels/link/dialog.ts +129 -0
  70. package/src/channels/link/driver.ts +190 -0
  71. package/src/channels/link/index.ts +34 -0
  72. package/src/channels/link/server.ts +134 -0
  73. package/src/channels/oneshot.ts +90 -0
  74. package/src/channels/ops.ts +65 -0
  75. package/src/channels/session-ops.ts +81 -0
  76. package/src/conductor/bash-guard.ts +599 -0
  77. package/src/conductor/catalog/catalog.ts +116 -0
  78. package/src/conductor/catalog/index.ts +8 -0
  79. package/src/conductor/catalog/matcher.ts +134 -0
  80. package/src/conductor/conductor.ts +234 -0
  81. package/src/conductor/contract.ts +842 -0
  82. package/src/conductor/diagnostics.ts +227 -0
  83. package/src/conductor/index.ts +33 -0
  84. package/src/conductor/permissions.ts +588 -0
  85. package/src/conductor/quota-error.ts +49 -0
  86. package/src/conductor/signal-hub/hub.ts +46 -0
  87. package/src/conductor/signal-hub/index.ts +2 -0
  88. package/src/conductor/signal-hub/translate.test.ts +81 -0
  89. package/src/conductor/signal-hub/translate.ts +74 -0
  90. package/src/conductor/skill-parse/index.ts +2 -0
  91. package/src/conductor/skill-parse/parse.ts +108 -0
  92. package/src/conductor/transcript-store/index.ts +22 -0
  93. package/src/conductor/transcript-store/serialize.ts +116 -0
  94. package/src/conductor/transcript-store/store.ts +205 -0
  95. package/src/console/auth-status.ts +56 -0
  96. package/src/console/components/AgentsView.ts +165 -0
  97. package/src/console/components/BackgroundAgents.ts +155 -0
  98. package/src/console/components/Banner.ts +334 -0
  99. package/src/console/components/Composer.ts +94 -0
  100. package/src/console/components/StatusBar.ts +49 -0
  101. package/src/console/components/TerminalConsole.ts +1090 -0
  102. package/src/console/components/WorkingIndicator.ts +98 -0
  103. package/src/console/components/banner-sweep.ts +24 -0
  104. package/src/console/components/welcome.ts +74 -0
  105. package/src/console/contract.ts +630 -0
  106. package/src/console/index.ts +34 -0
  107. package/src/console/input/complete.ts +127 -0
  108. package/src/console/input/dir-reader.ts +34 -0
  109. package/src/console/input/index.ts +23 -0
  110. package/src/console/input/keymap.ts +159 -0
  111. package/src/console/input/paste.ts +104 -0
  112. package/src/console/mount.ts +56 -0
  113. package/src/console/overlays/approval-queue.ts +57 -0
  114. package/src/console/overlays/approval.ts +130 -0
  115. package/src/console/overlays/auth.ts +342 -0
  116. package/src/console/overlays/boards.ts +308 -0
  117. package/src/console/overlays/host.ts +36 -0
  118. package/src/console/overlays/index.ts +26 -0
  119. package/src/console/overlays/pickers.ts +258 -0
  120. package/src/console/overlays/sessions.ts +190 -0
  121. package/src/console/reducer.ts +182 -0
  122. package/src/console/slash/builtins.ts +81 -0
  123. package/src/console/slash/commands/dynamic.ts +83 -0
  124. package/src/console/slash/commands/integrations.ts +695 -0
  125. package/src/console/slash/commands/shared.ts +75 -0
  126. package/src/console/slash/commands/transcript.ts +263 -0
  127. package/src/console/slash/commands/workbench.ts +246 -0
  128. package/src/console/slash/index.ts +15 -0
  129. package/src/console/slash/registry.ts +70 -0
  130. package/src/console/slash/resolve.ts +63 -0
  131. package/src/console/startup.ts +209 -0
  132. package/src/console/theme/adapter.ts +45 -0
  133. package/src/console/theme/index.ts +7 -0
  134. package/src/console/theme/palette.ts +68 -0
  135. package/src/console/theme/resolve.ts +39 -0
  136. package/src/console/theme/tokens.ts +71 -0
  137. package/src/entry.ts +55 -0
  138. package/src/guardrails.ts +37 -0
  139. package/src/index.ts +18 -0
  140. package/src/insight/channel.ts +88 -0
  141. package/src/insight/contract.ts +185 -0
  142. package/src/insight/index.ts +110 -0
  143. package/src/insight/recorder.ts +213 -0
  144. package/src/insight/redaction.ts +157 -0
  145. package/src/insight/replay.ts +158 -0
  146. package/src/insight/sampling.ts +70 -0
  147. package/src/insight/serialize.ts +50 -0
  148. package/src/insight/sinks/console.ts +64 -0
  149. package/src/insight/sinks/file.ts +40 -0
  150. package/src/insight/sinks/index.ts +24 -0
  151. package/src/insight/sinks/stream.ts +54 -0
  152. package/src/integrations/sarvam/attach.ts +239 -0
  153. package/src/integrations/sarvam/config.ts +156 -0
  154. package/src/integrations/sarvam/index.ts +25 -0
  155. package/src/integrations/sarvam/sarvam.test.ts +60 -0
  156. package/src/integrations/sarvam/types.ts +27 -0
  157. package/src/integrations/zoho/attach.ts +342 -0
  158. package/src/integrations/zoho/config.ts +125 -0
  159. package/src/integrations/zoho/index.ts +27 -0
  160. package/src/integrations/zoho/types.ts +21 -0
  161. package/src/integrations/zoho/zoho.test.ts +50 -0
  162. package/src/kit/clipboard-image.ts +107 -0
  163. package/src/kit/external-editor.ts +48 -0
  164. package/src/kit/image.ts +59 -0
  165. package/src/kit/index.ts +51 -0
  166. package/src/kit/shell.ts +19 -0
  167. package/src/kit/tool-fetch.ts +85 -0
  168. package/src/launch/catalog.ts +148 -0
  169. package/src/launch/contract.ts +187 -0
  170. package/src/launch/credentials.ts +625 -0
  171. package/src/launch/index.ts +98 -0
  172. package/src/launch/invocation/attachments.ts +179 -0
  173. package/src/launch/invocation/flags.ts +196 -0
  174. package/src/launch/invocation/index.ts +25 -0
  175. package/src/launch/invocation/read.ts +260 -0
  176. package/src/launch/invocation/usage.ts +67 -0
  177. package/src/launch/login.ts +324 -0
  178. package/src/launch/oauth.test.ts +18 -0
  179. package/src/launch/oauth.ts +203 -0
  180. package/src/launch/packages.ts +194 -0
  181. package/src/launch/pickers.ts +189 -0
  182. package/src/runtime-bridge/bridges/_drive.ts +96 -0
  183. package/src/runtime-bridge/bridges/builtins.ts +68 -0
  184. package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
  185. package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
  186. package/src/runtime-bridge/bridges/index.ts +33 -0
  187. package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
  188. package/src/runtime-bridge/broker.ts +227 -0
  189. package/src/runtime-bridge/contract.ts +122 -0
  190. package/src/runtime-bridge/index.ts +79 -0
  191. package/src/runtime-bridge/sink.ts +180 -0
  192. package/src/sessions/contract.ts +81 -0
  193. package/src/sessions/index.ts +13 -0
  194. package/src/sessions/library.ts +229 -0
  195. package/src/settings/contract.ts +114 -0
  196. package/src/settings/index.ts +32 -0
  197. package/src/settings/manager.ts +117 -0
  198. package/src/transcript-export/index.ts +45 -0
  199. package/src/transcript-export/publish.ts +260 -0
  200. package/src/transcript-export/sgr.ts +315 -0
  201. package/src/transcript-export/template.ts +272 -0
  202. package/src/transcript-export/theme-bridge.ts +150 -0
  203. package/src/window-budget/budget/estimate.ts +135 -0
  204. package/src/window-budget/budget/gate.ts +33 -0
  205. package/src/window-budget/budget/index.ts +16 -0
  206. package/src/window-budget/budget/slice.ts +56 -0
  207. package/src/window-budget/condenser.ts +58 -0
  208. package/src/window-budget/contract.ts +184 -0
  209. package/src/window-budget/index.ts +19 -0
  210. package/src/window-budget/microcompact.ts +95 -0
  211. package/src/window-budget/rehydrate.ts +136 -0
  212. package/src/window-budget/summarize/condense.ts +103 -0
  213. package/src/window-budget/summarize/index.ts +14 -0
  214. package/src/window-budget/summarize/prompt.ts +149 -0
  215. package/src/workflow-engine/agent-runner.ts +181 -0
  216. package/src/workflow-engine/display.ts +224 -0
  217. package/src/workflow-engine/engine.ts +294 -0
  218. package/src/workflow-engine/index.ts +23 -0
  219. package/src/workflow-engine/parse.ts +172 -0
  220. package/src/workflow-engine/structured-output.ts +35 -0
  221. package/src/workspace/brand.ts +29 -0
  222. package/src/workspace/index.ts +18 -0
  223. package/src/workspace/locator.ts +103 -0
  224. package/src/workspace/runtime-detect.ts +64 -0
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Overlays subsystem — public barrel.
3
+ */
4
+ export { OverlayHost, type OverlayHostProps } from "./host.js";
5
+ export { PickerOverlays, type OverlayGroupProps } from "./pickers.js";
6
+ export { SessionOverlays } from "./sessions.js";
7
+ export { AuthOverlays } from "./auth.js";
8
+ export {
9
+ ApprovalOverlays,
10
+ choiceFromId,
11
+ readApprovalPayload,
12
+ summarizeInput,
13
+ APPROVAL_CHOICES,
14
+ DISMISS_CHOICE,
15
+ type ApprovalRequest,
16
+ type ApprovalPayload
17
+ } from "./approval.js";
18
+ export {
19
+ approvalQueueReducer,
20
+ activeApproval,
21
+ modeReleasesApprovals,
22
+ EMPTY_APPROVAL_QUEUE,
23
+ type ApprovalEntry,
24
+ type ApprovalQueueState,
25
+ type ApprovalQueueEvent
26
+ } from "./approval-queue.js";
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Picker overlays — the selection dialogs raised over the transcript.
3
+ */
4
+ import { useEffect, useState } from "indusagi/react-host";
5
+ import { ScopedModelsDialog, ThemeDialog, type Model } from "indusagi/react-ink";
6
+ import { jsx } from "indusagi/react-host/jsx-runtime";
7
+ import type { ConsoleDispatch, InkThemeAdapter, ModalState, OverlayServices } from "../contract.js";
8
+ import { DEFAULT_SCHEME, isThemeScheme } from "../contract.js";
9
+ import { useAuthenticatedProviders } from "../auth-status.js";
10
+ import { isDeliveryMode, DELIVERY_MODES } from "../../settings/index.js";
11
+ import { THEME_SCHEMES } from "../theme/index.js";
12
+ import { ModelBoard, SettingsBoard } from "./boards.js";
13
+
14
+ const TOGGLE_VALUES = ["on", "off"];
15
+
16
+ function refToModel(ref: any): Model<any> {
17
+ return {
18
+ id: ref.id,
19
+ name: ref.name,
20
+ api: "",
21
+ provider: ref.provider,
22
+ baseUrl: "",
23
+ reasoning: ref.reasoning,
24
+ input: ["text"],
25
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
26
+ contextWindow: 0,
27
+ maxTokens: 0
28
+ };
29
+ }
30
+
31
+ function listModels(services: OverlayServices): Model<any>[] {
32
+ try {
33
+ return services.conductor.availableModels().map(refToModel);
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+
39
+ function ModelPicker(props: OverlayGroupProps) {
40
+ const { services, theme, closeModal } = props;
41
+ const authed = useAuthenticatedProviders(services);
42
+ if (!services) return null;
43
+ const all = listModels(services);
44
+ const models = authed === null ? all : all.filter((model) => authed.has(model.provider));
45
+ return jsx(ModelBoard, {
46
+ theme,
47
+ models,
48
+ onSelect: (model: Model<any>) => {
49
+ try {
50
+ services.conductor.selectModel(model.id);
51
+ } catch {
52
+ // ignore
53
+ }
54
+ closeModal();
55
+ },
56
+ onClose: closeModal
57
+ });
58
+ }
59
+
60
+ function readScopedPayload(payload: unknown): { focus?: string; intent?: string } {
61
+ if (payload === null || typeof payload !== "object") return {};
62
+ const record = payload as any;
63
+ const out: any = {};
64
+ if (record.focus === "summary") out.focus = "summary";
65
+ if (record.intent === "reset") out.intent = "reset";
66
+ return out;
67
+ }
68
+
69
+ function ScopedModelPicker(props: OverlayGroupProps) {
70
+ const { services, dispatch, closeModal, modal } = props;
71
+ const payload = readScopedPayload(modal.payload);
72
+ useEffect(() => {
73
+ if (!services || payload.intent !== "reset") return;
74
+ try {
75
+ services.settings.set("enabledModels", []);
76
+ services.settings.save();
77
+ dispatch({
78
+ type: "status:set",
79
+ status: { kind: "info", text: "Per-scope model overrides cleared." }
80
+ });
81
+ } catch {
82
+ dispatch({
83
+ type: "status:set",
84
+ status: { kind: "error", text: "Could not clear per-scope model overrides." }
85
+ });
86
+ }
87
+ closeModal();
88
+ }, []);
89
+ if (!services) return null;
90
+ if (payload.intent === "reset") return null;
91
+ const models = listModels(services);
92
+ let enabled: string[] = [];
93
+ try {
94
+ enabled = services.settings.get("enabledModels");
95
+ } catch {
96
+ enabled = [];
97
+ }
98
+ const selectedIds = enabled.length > 0 ? enabled : models.map((model) => model.id);
99
+ return jsx(ScopedModelsDialog, {
100
+ models,
101
+ selectedIds,
102
+ onSave: (ids: string[]) => {
103
+ try {
104
+ const all = models.map((model) => model.id);
105
+ const next = ids.length === all.length ? [] : ids;
106
+ services.settings.set("enabledModels", next);
107
+ services.settings.save();
108
+ } catch {
109
+ // ignore
110
+ }
111
+ closeModal();
112
+ },
113
+ onClose: closeModal
114
+ });
115
+ }
116
+
117
+ function currentScheme(services: OverlayServices): string {
118
+ try {
119
+ const stored = services.settings.get("colourScheme");
120
+ if (typeof stored === "string" && isThemeScheme(stored)) return stored;
121
+ } catch {
122
+ // ignore
123
+ }
124
+ return DEFAULT_SCHEME;
125
+ }
126
+
127
+ function ThemePicker(props: OverlayGroupProps) {
128
+ const { services, dispatch, closeModal } = props;
129
+ const [originalScheme] = useState(() => (services ? currentScheme(services) : null));
130
+ if (!services) return null;
131
+ const revert = () => {
132
+ if (originalScheme) dispatch({ type: "scheme:set", scheme: originalScheme });
133
+ closeModal();
134
+ };
135
+ return jsx(ThemeDialog, {
136
+ themes: [...THEME_SCHEMES],
137
+ onSelect: (name: string) => {
138
+ if (isThemeScheme(name)) {
139
+ dispatch({ type: "scheme:set", scheme: name });
140
+ try {
141
+ services.settings.set("colourScheme", name);
142
+ services.settings.save();
143
+ } catch {
144
+ // ignore
145
+ }
146
+ }
147
+ closeModal();
148
+ },
149
+ onClose: revert
150
+ });
151
+ }
152
+
153
+ function SettingsPicker(props: OverlayGroupProps) {
154
+ const { services, theme, dispatch, closeModal } = props;
155
+ if (!services) return null;
156
+ const settings = services.settings;
157
+ const [, setVersion] = useState(0);
158
+ const bump = () => setVersion((n) => n + 1);
159
+ const onOff = (flag: boolean) => (flag ? "on" : "off");
160
+ const toggleRow = (id: string, key: string, label: string, description: string, live?: () => void) => ({
161
+ id,
162
+ label,
163
+ description,
164
+ value: onOff(settings.get(key) as boolean),
165
+ values: [...TOGGLE_VALUES],
166
+ onChange: (value: string) => {
167
+ settings.set(key, value === "on");
168
+ settings.save();
169
+ live?.();
170
+ bump();
171
+ }
172
+ });
173
+ const deliveryRow = (id: string, key: string, label: string, description: string) => ({
174
+ id,
175
+ label,
176
+ description,
177
+ value: settings.get(key) as string,
178
+ values: [...DELIVERY_MODES],
179
+ onChange: (value: string) => {
180
+ if (isDeliveryMode(value)) {
181
+ settings.set(key, value);
182
+ settings.save();
183
+ }
184
+ bump();
185
+ }
186
+ });
187
+ const items: any[] = [];
188
+ try {
189
+ items.push({
190
+ id: "colour-scheme",
191
+ label: "Colour scheme",
192
+ description: "The terminal colour scheme the console renders in.",
193
+ value: settings.get("colourScheme") as string,
194
+ values: [...THEME_SCHEMES],
195
+ onChange: (value: string) => {
196
+ settings.set("colourScheme", value);
197
+ settings.save();
198
+ if (isThemeScheme(value)) dispatch({ type: "scheme:set", scheme: value });
199
+ bump();
200
+ }
201
+ });
202
+ items.push(toggleRow("show-images", "showImages", "Show images", "Render inline image content in the transcript.", () => dispatch({ type: "toggle:images" })));
203
+ items.push(toggleRow("image-auto-resize", "imageAutoResize", "Auto-resize images", "Shrink oversized images to fit before handing them to a provider."));
204
+ items.push(toggleRow("block-images", "blockImages", "Block images", "Withhold image content from providers entirely."));
205
+ items.push(toggleRow("show-reasoning", "showReasoning", "Show reasoning", "Stream the model's reasoning / thinking text as it arrives.", () => dispatch({ type: "toggle:reasoning" })));
206
+ items.push(toggleRow("skill-commands", "enableSkillCommands", "Skill commands", "Surface discovered skills as their own slash commands."));
207
+ items.push(deliveryRow("steering-mode", "steeringMode", "Steering mode", "Whether queued steering corrections release one at a time or all at once."));
208
+ items.push(deliveryRow("follow-up-mode", "followUpMode", "Follow-up mode", "Whether queued follow-up prompts release one at a time or all at once."));
209
+ items.push(toggleRow("auto-compact", "autoCompact", "Auto-compact", "Condense the transcript automatically as it nears the window."));
210
+ items.push(toggleRow("collapse-changelog", "collapseChangelog", "Collapse changelog", "Prefer a condensed changelog after the console updates itself."));
211
+ items.push(toggleRow("quiet-startup", "quietStartup", "Quiet startup", "Suppress the banner and tips shown on a normal interactive launch."));
212
+ items.push(toggleRow("logo-sweep", "logoSweep", "Logo sweep", "Tint the startup wordmark and emblem along a static colour gradient."));
213
+ items.push(toggleRow("reduced-motion", "reducedMotion", "Reduced motion", "Suppress motion-flavoured flourishes such as the logo colour sweep."));
214
+ items.push(toggleRow("hardware-cursor", "showHardwareCursor", "Hardware cursor", "Reveal the terminal's own cursor instead of the software-drawn caret."));
215
+ items.push({
216
+ id: "editor-padding",
217
+ label: "Editor padding",
218
+ description: "Horizontal padding, in columns, around the prompt editor.",
219
+ value: String(settings.get("editorPaddingX")),
220
+ values: ["0", "1", "2", "3"],
221
+ onChange: (value: string) => {
222
+ const parsed = Number.parseInt(value, 10);
223
+ if (Number.isFinite(parsed)) {
224
+ settings.set("editorPaddingX", parsed);
225
+ settings.save();
226
+ }
227
+ bump();
228
+ }
229
+ });
230
+ } catch {
231
+ return null;
232
+ }
233
+ return jsx(SettingsBoard, { theme, items, onClose: closeModal });
234
+ }
235
+
236
+ /** The props every overlay group receives. */
237
+ export interface OverlayGroupProps {
238
+ readonly modal: ModalState;
239
+ readonly services?: OverlayServices;
240
+ readonly theme: InkThemeAdapter;
241
+ readonly dispatch: ConsoleDispatch;
242
+ readonly closeModal: () => void;
243
+ }
244
+
245
+ export function PickerOverlays(props: OverlayGroupProps) {
246
+ switch (props.modal.kind) {
247
+ case "models":
248
+ return jsx(ModelPicker, { ...props });
249
+ case "scopedModels":
250
+ return jsx(ScopedModelPicker, { ...props });
251
+ case "theme":
252
+ return jsx(ThemePicker, { ...props });
253
+ case "settings":
254
+ return jsx(SettingsPicker, { ...props });
255
+ default:
256
+ return null;
257
+ }
258
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Session overlays — the transcript navigation dialogs.
3
+ */
4
+ import { useEffect, useState } from "indusagi/react-host";
5
+ import { SessionDialog, TreeDialog, UserMessageDialog } from "indusagi/react-ink";
6
+ import { jsx } from "indusagi/react-host/jsx-runtime";
7
+ import type { OverlayGroupProps } from "./pickers.js";
8
+
9
+ function toSessionInfo(row: any) {
10
+ const lastModified = row.lastModified ?? 0;
11
+ return {
12
+ id: row.id,
13
+ path: row.path,
14
+ name: row.name,
15
+ modified: new Date(lastModified),
16
+ lastModified,
17
+ size: row.size,
18
+ messageCount: row.messageCount,
19
+ firstMessage: row.preview
20
+ };
21
+ }
22
+
23
+ function SessionPicker(props: OverlayGroupProps) {
24
+ const { services, closeModal } = props;
25
+ const [sessions, setSessions] = useState<any[]>([]);
26
+ useEffect(() => {
27
+ let live = true;
28
+ void (async () => {
29
+ try {
30
+ const rows = await services!.sessions.list();
31
+ if (live) setSessions(rows.map(toSessionInfo));
32
+ } catch {
33
+ if (live) setSessions([]);
34
+ }
35
+ })();
36
+ return () => {
37
+ live = false;
38
+ };
39
+ }, [services]);
40
+ const currentPath = services!.sessions.pathOf(services!.conductor.snapshot().head.sessionId);
41
+ return jsx(SessionDialog, {
42
+ currentSessions: sessions,
43
+ currentSessionPath: currentPath,
44
+ onSelect: async (session: any) => {
45
+ try {
46
+ await services!.conductor.resume(session.id);
47
+ } catch {
48
+ // ignore
49
+ }
50
+ closeModal();
51
+ },
52
+ onLoadAllSessions: async () => {
53
+ try {
54
+ const rows = await services!.sessions.list({ deep: true });
55
+ const mapped = rows.map(toSessionInfo);
56
+ setSessions(mapped);
57
+ return mapped;
58
+ } catch {
59
+ return [];
60
+ }
61
+ },
62
+ onDeleteSession: async (session: any) => {
63
+ try {
64
+ await services!.sessions.remove(session.id);
65
+ setSessions((prev) => prev.filter((row) => row.id !== session.id));
66
+ } catch {
67
+ // ignore
68
+ }
69
+ },
70
+ onRenameSession: async (session: any, name: string) => {
71
+ try {
72
+ await services!.sessions.rename(session.id, name);
73
+ const rows = await services!.sessions.list();
74
+ setSessions(rows.map(toSessionInfo));
75
+ } catch {
76
+ // ignore
77
+ }
78
+ },
79
+ onClose: closeModal
80
+ });
81
+ }
82
+
83
+ function BranchTree(props: OverlayGroupProps) {
84
+ const { services, closeModal } = props;
85
+ const [items, setItems] = useState<any[]>([]);
86
+ useEffect(() => {
87
+ let live = true;
88
+ void (async () => {
89
+ try {
90
+ const sessionId = services!.conductor.snapshot().head.sessionId;
91
+ const store = await services!.sessions.open(sessionId);
92
+ if (store === null) {
93
+ if (live) setItems([]);
94
+ return;
95
+ }
96
+ const nodes = services!.sessions.tree(store).map((node: any) => ({
97
+ id: node.id,
98
+ label: node.label,
99
+ depth: node.depth,
100
+ isLeaf: node.isLeaf,
101
+ isCurrent: node.isCurrent
102
+ }));
103
+ if (live) setItems(nodes);
104
+ } catch {
105
+ if (live) setItems([]);
106
+ }
107
+ })();
108
+ return () => {
109
+ live = false;
110
+ };
111
+ }, [services]);
112
+ return jsx(TreeDialog, {
113
+ items,
114
+ onSelect: async (node: any) => {
115
+ if (node.id !== null) {
116
+ try {
117
+ await services!.conductor.navigateTree(node.id);
118
+ const info = await services!.conductor.codeRestoreInfo(node.id);
119
+ if (info.canRestore) {
120
+ await services!.conductor.restoreCode(node.id);
121
+ }
122
+ } catch {
123
+ // ignore
124
+ }
125
+ }
126
+ closeModal();
127
+ },
128
+ onClose: closeModal
129
+ });
130
+ }
131
+
132
+ function PriorTurns(props: OverlayGroupProps) {
133
+ const { services, closeModal } = props;
134
+ const [items, setItems] = useState<any[]>([]);
135
+ useEffect(() => {
136
+ let live = true;
137
+ void (async () => {
138
+ try {
139
+ const sessionId = services!.conductor.snapshot().head.sessionId;
140
+ const store = await services!.sessions.open(sessionId);
141
+ if (store === null) {
142
+ if (live) setItems([]);
143
+ return;
144
+ }
145
+ const turns = services!.sessions.priorTurns(store).map((turn: any) => ({
146
+ entryId: turn.entryId,
147
+ text: turn.text,
148
+ preview: turn.preview
149
+ }));
150
+ if (live) setItems(turns);
151
+ } catch {
152
+ if (live) setItems([]);
153
+ }
154
+ })();
155
+ return () => {
156
+ live = false;
157
+ };
158
+ }, [services]);
159
+ return jsx(UserMessageDialog, {
160
+ items,
161
+ onSelect: async (turn: any) => {
162
+ try {
163
+ await services!.conductor.fork(turn.entryId);
164
+ const info = await services!.conductor.codeRestoreInfo(turn.entryId);
165
+ if (info.canRestore) {
166
+ await services!.conductor.restoreCode(turn.entryId);
167
+ }
168
+ } catch {
169
+ // ignore
170
+ }
171
+ closeModal();
172
+ },
173
+ onClose: closeModal
174
+ });
175
+ }
176
+
177
+ export function SessionOverlays(props: OverlayGroupProps) {
178
+ const { modal, services, closeModal } = props;
179
+ if (services === void 0) return null;
180
+ switch (modal.kind) {
181
+ case "sessions":
182
+ return jsx(SessionPicker, { ...props, services, closeModal });
183
+ case "tree":
184
+ return jsx(BranchTree, { ...props, services, closeModal });
185
+ case "userTurns":
186
+ return jsx(PriorTurns, { ...props, services, closeModal });
187
+ default:
188
+ return null;
189
+ }
190
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Console reducer — the single pure fold over {@link ConsoleState}.
3
+ *
4
+ * The whole terminal surface is reduced from one immutable {@link ConsoleState}
5
+ * value, mutated only by dispatching a {@link ConsoleEvent} into
6
+ * {@link consoleReducer}. This module is the one place those transitions live:
7
+ * it is a pure function (no React, no I/O, no timers) that, given a prior state
8
+ * and an event, returns a *fresh* state — it never mutates its input. That makes
9
+ * every transition unit-testable in isolation and keeps the surface component a
10
+ * thin shell that only wires events in and renders the result out.
11
+ */
12
+ import { EMPTY_CONSOLE_STATE, NO_MODAL, transitionModal, type ConsoleEvent, type ConsoleState } from "./contract.js";
13
+
14
+ function clamp(offset: number, max: number) {
15
+ if (offset < 0) return 0;
16
+ if (offset > max) return max;
17
+ return offset;
18
+ }
19
+
20
+ function spliceAt(text: string, at: number, insert: string) {
21
+ return text.slice(0, at) + insert + text.slice(at);
22
+ }
23
+
24
+ function withBuffer(state: ConsoleState, text: string, caret: number) {
25
+ return { ...state, buffer: text, caret: clamp(caret, text.length) };
26
+ }
27
+
28
+ function setBuffer(state: ConsoleState, text: string) {
29
+ return withBuffer(state, text, text.length);
30
+ }
31
+
32
+ function insertBuffer(state: ConsoleState, text: string) {
33
+ if (text.length === 0) return state;
34
+ const at = clamp(state.caret, state.buffer.length);
35
+ return withBuffer(state, spliceAt(state.buffer, at, text), at + text.length);
36
+ }
37
+
38
+ function erasePrev(state: ConsoleState) {
39
+ const at = clamp(state.caret, state.buffer.length);
40
+ if (at === 0) return state;
41
+ const next = state.buffer.slice(0, at - 1) + state.buffer.slice(at);
42
+ return withBuffer(state, next, at - 1);
43
+ }
44
+
45
+ function eraseNext(state: ConsoleState) {
46
+ const at = clamp(state.caret, state.buffer.length);
47
+ if (at >= state.buffer.length) return state;
48
+ const next = state.buffer.slice(0, at) + state.buffer.slice(at + 1);
49
+ return withBuffer(state, next, at);
50
+ }
51
+
52
+ function pushHistory(state: ConsoleState, text: string) {
53
+ const trimmed = text.trim();
54
+ const base = { ...state, historyAt: null, stash: "" };
55
+ if (trimmed.length === 0) return base;
56
+ const last = state.history[state.history.length - 1];
57
+ if (last === text) return base;
58
+ return { ...base, history: [...state.history, text] };
59
+ }
60
+
61
+ function historyBack(state: ConsoleState) {
62
+ if (state.history.length === 0) return state;
63
+ const atEnd = state.historyAt === null;
64
+ const index = atEnd ? state.history.length - 1 : Math.max(0, state.historyAt - 1);
65
+ const stash = atEnd ? state.buffer : state.stash;
66
+ const recalled = state.history[index];
67
+ return { ...withBuffer(state, recalled, recalled.length), historyAt: index, stash };
68
+ }
69
+
70
+ function historyForward(state: ConsoleState) {
71
+ if (state.historyAt === null) return state;
72
+ const next = state.historyAt + 1;
73
+ if (next >= state.history.length) {
74
+ const stash = state.stash;
75
+ return { ...withBuffer(state, stash, stash.length), historyAt: null, stash: "" };
76
+ }
77
+ const recalled = state.history[next];
78
+ return { ...withBuffer(state, recalled, recalled.length), historyAt: next };
79
+ }
80
+
81
+ function appendRow(state: ConsoleState, row: ConsoleState["rows"][number]) {
82
+ return { ...state, rows: [...state.rows, row] };
83
+ }
84
+
85
+ function patchRow(state: ConsoleState, id: string, text: string) {
86
+ let found = false;
87
+ const rows = state.rows.map((row) => {
88
+ if (row.id !== id) return row;
89
+ found = true;
90
+ return { ...row, text };
91
+ });
92
+ return found ? { ...state, rows } : state;
93
+ }
94
+
95
+ /**
96
+ * Fold a {@link ConsoleEvent} into a {@link ConsoleState}, returning a fresh state.
97
+ */
98
+ export function consoleReducer(state: ConsoleState, event: ConsoleEvent): ConsoleState {
99
+ switch (event.type) {
100
+ // Composer buffer & caret
101
+ case "buffer:set":
102
+ return setBuffer(state, event.text);
103
+ case "buffer:insert":
104
+ return insertBuffer(state, event.text);
105
+ case "buffer:replaceSpan": {
106
+ const at = state.buffer.indexOf(event.find);
107
+ if (at < 0) return state;
108
+ const buffer = state.buffer.slice(0, at) + event.replaceWith + state.buffer.slice(at + event.find.length);
109
+ const spanEnd = at + event.find.length;
110
+ const caret = state.caret >= spanEnd
111
+ ? state.caret + (event.replaceWith.length - event.find.length)
112
+ : state.caret > at
113
+ ? at + event.replaceWith.length
114
+ : state.caret;
115
+ return { ...state, buffer, caret: clamp(caret, buffer.length) };
116
+ }
117
+ case "buffer:erasePrev":
118
+ return erasePrev(state);
119
+ case "buffer:eraseNext":
120
+ return eraseNext(state);
121
+ case "caret:move":
122
+ return { ...state, caret: clamp(state.caret + event.delta, state.buffer.length) };
123
+ case "caret:jump":
124
+ return { ...state, caret: clamp(event.offset, state.buffer.length) };
125
+ // History ring
126
+ case "history:push":
127
+ return pushHistory(state, event.text);
128
+ case "history:back":
129
+ return historyBack(state);
130
+ case "history:forward":
131
+ return historyForward(state);
132
+ // Transcript view
133
+ case "rows:set":
134
+ return { ...state, rows: [...event.rows] };
135
+ case "rows:append":
136
+ return appendRow(state, event.row);
137
+ case "rows:patch":
138
+ return patchRow(state, event.id, event.text);
139
+ case "block:append":
140
+ return { ...state, blocks: [...state.blocks, event.block] };
141
+ case "blocks:clear":
142
+ return state.blocks.length === 0 ? state : { ...state, blocks: [] };
143
+ // Overlays, status, theme, toggles, busy
144
+ case "modal:open":
145
+ return { ...state, modal: transitionModal(event.kind, event.payload) };
146
+ case "modal:close":
147
+ return { ...state, modal: NO_MODAL };
148
+ case "status:set":
149
+ return { ...state, status: event.status };
150
+ case "status:clear": {
151
+ if (state.status === void 0) return state;
152
+ const { status: _drop, ...rest } = state;
153
+ return rest;
154
+ }
155
+ case "scheme:set":
156
+ return { ...state, scheme: event.scheme };
157
+ case "toggle:reasoning":
158
+ return { ...state, showReasoning: !state.showReasoning };
159
+ case "toggle:images":
160
+ return { ...state, showImages: !state.showImages };
161
+ case "busy:set": {
162
+ if (event.busy) {
163
+ const { status: _drop, ...rest } = state;
164
+ return { ...rest, busy: true };
165
+ }
166
+ return { ...state, busy: false };
167
+ }
168
+ case "tick":
169
+ return { ...state, tick: state.tick + 1 };
170
+ default: {
171
+ const _never = event;
172
+ return _never;
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Build the initial {@link ConsoleState} a console mounts with.
179
+ */
180
+ export function initConsoleState(overrides?: Partial<ConsoleState>): ConsoleState {
181
+ return overrides === void 0 ? EMPTY_CONSOLE_STATE : { ...EMPTY_CONSOLE_STATE, ...overrides };
182
+ }