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,165 @@
1
+ /**
2
+ * AgentsView — the drill-in sub-agent view.
3
+ */
4
+ import { useState } from "indusagi/react-host";
5
+ import { Box, Text, useInput } from "indusagi/react-host/ink";
6
+ import { wrapSelectionIndex, type InkThemeAdapter } from "indusagi/react-ink";
7
+ import { jsx, jsxs } from "indusagi/react-host/jsx-runtime";
8
+ import type { AgentRun } from "./BackgroundAgents.js";
9
+
10
+ function formatTokens(tokens: number): string {
11
+ return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : String(tokens);
12
+ }
13
+
14
+ function clampLine(text: string, width: number): string {
15
+ const flat = text.replace(/\s+/g, " ").trim();
16
+ if (flat.length <= width) return flat;
17
+ if (width <= 1) return flat.slice(0, width);
18
+ return `${flat.slice(0, width - 1)}…`;
19
+ }
20
+
21
+ interface ActivityLine {
22
+ tone: "tool" | "result" | "info";
23
+ text: string;
24
+ }
25
+
26
+ function runStatusLine(run: AgentRun, now: number, tokens?: number): string {
27
+ let head: string;
28
+ if (run.status === "error") head = "failed";
29
+ else if (run.status === "done") head = "done";
30
+ else if (run.startedAt > 0 && now >= run.startedAt) {
31
+ head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
32
+ } else head = "working";
33
+ const tok = tokens !== void 0 && tokens > 0 ? ` · ↓ ${formatTokens(tokens)} tokens` : "";
34
+ return `${head}${tok}`;
35
+ }
36
+
37
+ const NAME_WIDTH = 12;
38
+
39
+ function ActivityRow({ theme, line }: { theme: InkThemeAdapter; line: ActivityLine }) {
40
+ if (line.tone === "tool") {
41
+ return jsxs(Box, {
42
+ children: [
43
+ jsx(Text, { children: theme.color("success", "● ") }),
44
+ jsx(Text, { children: theme.color("text", clampLine(line.text, 96)) })
45
+ ]
46
+ });
47
+ }
48
+ if (line.tone === "result") {
49
+ return jsx(Box, { marginLeft: 2, children: jsx(Text, { children: theme.dim(`└ ${clampLine(line.text, 92)}`) }) });
50
+ }
51
+ return jsxs(Box, {
52
+ children: [
53
+ jsx(Text, { children: theme.color("accent", "● ") }),
54
+ jsx(Text, { children: theme.color("text", clampLine(line.text, 96)) })
55
+ ]
56
+ });
57
+ }
58
+
59
+ export interface AgentsViewProps {
60
+ readonly theme: InkThemeAdapter;
61
+ readonly runs: readonly AgentRun[];
62
+ readonly activity: Readonly<Record<string, readonly ActivityLine[]>>;
63
+ readonly tokens: Readonly<Record<string, number>>;
64
+ readonly now?: number;
65
+ readonly onExit: () => void;
66
+ }
67
+
68
+ export function AgentsView({ theme, runs, activity, tokens, now, onExit }: AgentsViewProps) {
69
+ const clock = now ?? Date.now();
70
+ const [selectedIndex, setSelectedIndex] = useState(runs.length > 0 ? 1 : 0);
71
+ const rowCount = runs.length + 1;
72
+ const index = Math.min(selectedIndex, rowCount - 1);
73
+ const viewed = index > 0 ? runs[index - 1] : void 0;
74
+ useInput((input, key) => {
75
+ if (key.escape || input === "x") return onExit();
76
+ if (key.upArrow) return setSelectedIndex((i) => wrapSelectionIndex(Math.min(i, rowCount - 1), -1, rowCount));
77
+ if (key.downArrow) return setSelectedIndex((i) => wrapSelectionIndex(Math.min(i, rowCount - 1), 1, rowCount));
78
+ if (key.return && index === 0) onExit();
79
+ });
80
+ const lines = viewed ? activity[viewed.id] ?? [] : [];
81
+ return jsxs(Box, {
82
+ flexDirection: "column",
83
+ marginTop: 1,
84
+ children: [
85
+ viewed
86
+ ? jsxs(Box, {
87
+ flexDirection: "column",
88
+ children: [
89
+ jsxs(Box, {
90
+ marginBottom: 1,
91
+ children: [
92
+ jsx(Text, { children: theme.color("accent", "✶ ") }),
93
+ jsx(Text, { bold: true, children: theme.color("accent", viewed.name) }),
94
+ jsx(Text, { children: theme.dim(` ${runStatusLine(viewed, clock, tokens[viewed.id])}`) })
95
+ ]
96
+ }),
97
+ lines.length === 0
98
+ ? jsx(Text, { children: theme.muted(" Waiting for the first step…") })
99
+ : lines.map((line, i) => jsx(ActivityRow, { theme, line }, i)),
100
+ jsx(Box, {
101
+ marginTop: 1,
102
+ children: jsx(Text, { wrap: "truncate", children: theme.muted(`└ ${clampLine(viewed.objective, 100)}`) })
103
+ })
104
+ ]
105
+ })
106
+ : jsxs(Box, {
107
+ children: [
108
+ jsx(Text, { children: theme.muted("Select a sub-agent to view its live activity, or press Enter on ") }),
109
+ jsx(Text, { bold: true, children: theme.color("text", "main") }),
110
+ jsx(Text, { children: theme.muted(" to return.") })
111
+ ]
112
+ }),
113
+ jsxs(Box, {
114
+ flexDirection: "column",
115
+ marginTop: 1,
116
+ borderStyle: "single",
117
+ borderColor: theme.colors["borderMuted"],
118
+ borderBottom: false,
119
+ borderLeft: false,
120
+ borderRight: false,
121
+ paddingTop: 0,
122
+ children: [
123
+ jsxs(Box, {
124
+ children: [
125
+ jsx(Text, { children: index === 0 ? theme.color("accent", "› ") : " " }),
126
+ jsx(Text, { children: index === 0 ? theme.color("text", "● ") : theme.dim("○ ") }),
127
+ jsx(Text, { bold: index === 0, children: theme.color("text", "main") })
128
+ ]
129
+ }),
130
+ runs.map((run, i) => {
131
+ const selected = index === i + 1;
132
+ const dot =
133
+ run.status === "running"
134
+ ? theme.color("accent", "◐")
135
+ : run.status === "error"
136
+ ? theme.color("error", "●")
137
+ : theme.color("success", "●");
138
+ return jsxs(
139
+ Box,
140
+ {
141
+ children: [
142
+ jsx(Text, { children: selected ? theme.color("accent", "› ") : " " }),
143
+ jsx(Box, { width: 2, flexShrink: 0, children: jsx(Text, { children: dot }) }),
144
+ jsx(Box, {
145
+ width: NAME_WIDTH,
146
+ flexShrink: 0,
147
+ children: jsx(Text, { bold: selected, children: theme.color("accent", clampLine(run.name, NAME_WIDTH - 1)) })
148
+ }),
149
+ jsx(Box, {
150
+ flexGrow: 1,
151
+ flexShrink: 1,
152
+ children: jsx(Text, { wrap: "truncate", children: theme.muted(clampLine(run.objective, 56)) })
153
+ }),
154
+ jsx(Text, { children: theme.dim(` ${runStatusLine(run, clock, tokens[run.id])}`) })
155
+ ]
156
+ },
157
+ run.id
158
+ );
159
+ })
160
+ ]
161
+ }),
162
+ jsx(Box, { marginTop: 1, children: jsx(Text, { children: theme.dim("↑/↓ select · Enter to view · Enter on main / Esc to return") }) })
163
+ ]
164
+ });
165
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * BackgroundAgents — the live sub-agent panel pinned below the input.
3
+ */
4
+ import { Box, Text } from "indusagi/react-host/ink";
5
+ import { splitAssistantMessage, type InkThemeAdapter } from "indusagi/react-ink";
6
+ import { jsx, jsxs } from "indusagi/react-host/jsx-runtime";
7
+ import type { AgentMessage } from "indusagi/agent";
8
+
9
+ /** The live status of one delegated sub-agent. */
10
+ export type AgentRunStatus = "running" | "done" | "error";
11
+
12
+ /** One delegated sub-agent run, projected from the conversation. */
13
+ export interface AgentRun {
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly objective: string;
17
+ readonly status: AgentRunStatus;
18
+ readonly startedAt: number;
19
+ }
20
+
21
+ function readString(args: any, key: string): string | undefined {
22
+ const value = args[key];
23
+ if (typeof value !== "string") return void 0;
24
+ const trimmed = value.trim();
25
+ return trimmed.length > 0 ? trimmed : void 0;
26
+ }
27
+
28
+ /**
29
+ * Derive the delegated sub-agent runs from the conversation.
30
+ */
31
+ export function deriveAgentRuns(messages: readonly AgentMessage[]): AgentRun[] {
32
+ const runs: AgentRun[] = [];
33
+ const status = new Map<string, AgentRunStatus>();
34
+ for (const message of messages) {
35
+ if (message.role === "assistant") {
36
+ const { toolCalls } = splitAssistantMessage(message as any, false);
37
+ for (const call of toolCalls) {
38
+ if (call.name !== "task") continue;
39
+ const args = call.arguments && typeof call.arguments === "object" ? call.arguments : {};
40
+ runs.push({
41
+ id: call.id,
42
+ name: readString(args, "agent") ?? "subagent",
43
+ objective: readString(args, "objective") ?? "",
44
+ status: "running",
45
+ startedAt: typeof message.timestamp === "number" ? message.timestamp : 0
46
+ });
47
+ }
48
+ continue;
49
+ }
50
+ if (message.role === "toolResult") {
51
+ const result = message as any;
52
+ if (typeof result.toolCallId === "string") {
53
+ status.set(result.toolCallId, result.isError ? "error" : "done");
54
+ }
55
+ }
56
+ }
57
+ return runs.map((run) => ({ ...run, status: status.get(run.id) ?? run.status }));
58
+ }
59
+
60
+ const MAX_VISIBLE_RUNS = 8;
61
+ const NAME_WIDTH = 12;
62
+
63
+ function clamp(text: string, width: number): string {
64
+ if (text.length <= width) return text;
65
+ if (width <= 1) return text.slice(0, width);
66
+ return `${text.slice(0, width - 1)}…`;
67
+ }
68
+
69
+ const OBJECTIVE_WIDTH = 64;
70
+
71
+ function oneLine(text: string): string {
72
+ return text.replace(/\s+/g, " ").trim();
73
+ }
74
+
75
+ function formatTokens(tokens: number): string {
76
+ if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
77
+ return String(tokens);
78
+ }
79
+
80
+ function statusText(run: AgentRun, now: number, tokens?: number): string {
81
+ let head: string;
82
+ if (run.status === "error") head = "failed";
83
+ else if (run.status === "done") head = "done";
84
+ else if (run.startedAt > 0 && now >= run.startedAt) {
85
+ head = `${Math.max(0, Math.round((now - run.startedAt) / 1e3))}s`;
86
+ } else head = "running";
87
+ if (tokens !== void 0 && tokens > 0) {
88
+ return `${head} · ↓ ${formatTokens(tokens)} tokens`;
89
+ }
90
+ return head;
91
+ }
92
+
93
+ export interface BackgroundAgentsProps {
94
+ readonly theme: InkThemeAdapter;
95
+ readonly runs: readonly AgentRun[];
96
+ readonly tokens?: Readonly<Record<string, number>>;
97
+ readonly now?: number;
98
+ }
99
+
100
+ /**
101
+ * Render the background-agents panel, or nothing when no run is in flight.
102
+ */
103
+ export function BackgroundAgents({ theme, runs, tokens, now }: BackgroundAgentsProps) {
104
+ const clock = now ?? Date.now();
105
+ const runningCount = runs.reduce((n, run) => (run.status === "running" ? n + 1 : n), 0);
106
+ if (runningCount === 0) return null;
107
+ const visible = runs.slice(-MAX_VISIBLE_RUNS);
108
+ return jsxs(Box, {
109
+ flexDirection: "column",
110
+ marginTop: 1,
111
+ children: [
112
+ jsxs(Box, {
113
+ children: [
114
+ jsx(Text, { children: theme.color("text", "● ") }),
115
+ jsx(Text, { bold: true, children: theme.color("text", "main") })
116
+ ]
117
+ }),
118
+ visible.map((run) => {
119
+ const dot =
120
+ run.status === "running"
121
+ ? theme.color("accent", "◐")
122
+ : run.status === "error"
123
+ ? theme.color("error", "●")
124
+ : theme.color("success", "●");
125
+ return jsxs(
126
+ Box,
127
+ {
128
+ children: [
129
+ jsx(Box, { width: 2, flexShrink: 0, children: jsx(Text, { children: dot }) }),
130
+ jsx(Box, {
131
+ width: NAME_WIDTH,
132
+ flexShrink: 0,
133
+ children: jsx(Text, { bold: true, children: theme.color("accent", clamp(run.name, NAME_WIDTH - 1)) })
134
+ }),
135
+ jsx(Box, {
136
+ flexGrow: 1,
137
+ flexShrink: 1,
138
+ children: jsx(Text, { wrap: "truncate", children: theme.muted(clamp(oneLine(run.objective), OBJECTIVE_WIDTH)) })
139
+ }),
140
+ jsx(Text, { children: theme.dim(` ${statusText(run, clock, tokens?.[run.id])}`) })
141
+ ]
142
+ },
143
+ run.id
144
+ );
145
+ }),
146
+ jsxs(Box, {
147
+ marginTop: 1,
148
+ children: [
149
+ jsx(Text, { children: theme.color("accent", "✶ ") }),
150
+ jsx(Text, { children: theme.muted(`Waiting for ${runningCount} background agent${runningCount === 1 ? "" : "s"} to finish`) })
151
+ ]
152
+ })
153
+ ]
154
+ });
155
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Banner — the "IndusCode Saffron" masthead the console renders above the transcript.
3
+ */
4
+ import { Fragment, jsx, jsxs } from "indusagi/react-host/jsx-runtime";
5
+ import { Box, Text } from "indusagi/react-host/ink";
6
+ import { ChangelogBlock } from "indusagi/react-ink";
7
+ import { homedir } from "node:os";
8
+ import { basename } from "node:path";
9
+ import type { InkThemeAdapter } from "indusagi/react-ink";
10
+ import type { StartupChangelog, StartupMap, StartupNotice } from "../startup.js";
11
+ import { MASCOTS, WHATS_NEW, pickWelcomeVariant, mascotRows, type MascotFace } from "./welcome.js";
12
+ import { welcomeLine } from "./banner-sweep.js";
13
+
14
+ const BRAND = "IndusCode";
15
+ const COMPACT_EMBLEM = "▛▜▙▟";
16
+
17
+ const WORDMARK_ROWS: readonly string[] = [
18
+ "██╗███╗ ██╗██████╗ ██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗",
19
+ "██║████╗ ██║██╔══██╗██║ ██║██╔════╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
20
+ "██║██╔██╗ ██║██║ ██║██║ ██║███████╗ ██║ ██║ ██║██║ ██║███████╗",
21
+ "██║██║╚██╗██║██║ ██║██║ ██║╚════██║ ██║ ██║ ██║██║ ██║╚════██║",
22
+ "██║██║ ╚████║██████╔╝╚██████╔╝███████║ ╚██████╗╚██████╔╝██████╔╝███████║",
23
+ "╚═╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"
24
+ ];
25
+
26
+ function StartupNotices({ theme, notices }: { theme: InkThemeAdapter; notices: readonly StartupNotice[] }) {
27
+ if (notices.length === 0) return null;
28
+ return jsx(Box, {
29
+ flexDirection: "column",
30
+ marginBottom: 1,
31
+ children: notices.map((notice, index) => jsx(Text, { children: paintNotice(theme, notice) }, index))
32
+ });
33
+ }
34
+
35
+ function paintNotice(theme: InkThemeAdapter, notice: StartupNotice) {
36
+ switch (notice.kind) {
37
+ case "error":
38
+ return theme.color("error", notice.text);
39
+ case "warning":
40
+ return theme.color("warning", notice.text);
41
+ case "info":
42
+ default:
43
+ return theme.muted(notice.text);
44
+ }
45
+ }
46
+
47
+ function StartupChangelogBlock({ theme, changelog }: { theme: InkThemeAdapter; changelog: StartupChangelog }) {
48
+ if (changelog.mode === "full" && changelog.markdown !== void 0) {
49
+ return jsx(ChangelogBlock, { markdown: changelog.markdown, theme, title: "What is new" });
50
+ }
51
+ if (changelog.mode === "condensed") {
52
+ return jsxs(Box, {
53
+ marginTop: 1,
54
+ children: [
55
+ jsx(Text, { children: theme.muted(`Updated to v${changelog.version}. Use `) }),
56
+ jsx(Text, { children: theme.color("accent", "/changelog") }),
57
+ jsx(Text, { children: theme.muted(" to view the full changelog.") })
58
+ ]
59
+ });
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function WelcomeLine({ theme, name, tone = "accent" }: { theme: InkThemeAdapter; name?: string; tone?: "accent" | "muted" }) {
65
+ const paint = (text: string) => (tone === "muted" ? theme.muted(text) : theme.color("accent", text));
66
+ const line = welcomeLine(name);
67
+ if (name !== void 0 && line !== "Welcome back!") {
68
+ return jsxs(Box, {
69
+ children: [
70
+ jsx(Text, { children: paint("Welcome back, ") }),
71
+ jsx(Text, { bold: true, children: name.trim() }),
72
+ jsx(Text, { children: paint("!") })
73
+ ]
74
+ });
75
+ }
76
+ return jsx(Box, { children: jsx(Text, { children: paint(line) }) });
77
+ }
78
+
79
+ function ShellLine({ theme, workspace, branch }: { theme: InkThemeAdapter; workspace: string; branch?: string | null }) {
80
+ const dir = basename(workspace);
81
+ return jsxs(Box, {
82
+ children: [
83
+ jsx(Text, { children: theme.color("customMessage", "➜") }),
84
+ jsx(Text, { children: " " }),
85
+ jsx(Text, { bold: true, children: theme.color("text", dir) }),
86
+ branch !== void 0 && branch !== null && branch.length > 0
87
+ ? jsxs(Fragment, {
88
+ children: [
89
+ jsx(Text, { children: " " }),
90
+ jsx(Text, { children: theme.color("accent", "git:(") }),
91
+ jsx(Text, { children: theme.color("customMessage", branch) }),
92
+ jsx(Text, { children: theme.color("accent", ")") })
93
+ ]
94
+ })
95
+ : null,
96
+ jsx(Text, { children: theme.dim(" indus") })
97
+ ]
98
+ });
99
+ }
100
+
101
+ function Wordmark({ theme }: { theme: InkThemeAdapter }) {
102
+ return jsx(Box, {
103
+ flexDirection: "column",
104
+ children: WORDMARK_ROWS.map((line, index) => jsx(Text, { children: theme.color("accent", line) }, index))
105
+ });
106
+ }
107
+
108
+ function SessionFact({ theme, label, value }: { theme: InkThemeAdapter; label: string; value: string }) {
109
+ return jsxs(Box, {
110
+ children: [
111
+ jsx(Text, { children: theme.dim(`${label.padEnd(7)} `) }),
112
+ jsx(Text, { children: theme.muted(value) })
113
+ ]
114
+ });
115
+ }
116
+
117
+ function paintMascot(theme: InkThemeAdapter, tone: string, text: string) {
118
+ switch (tone) {
119
+ case "head":
120
+ return theme.color("accent", text);
121
+ case "mouth":
122
+ return theme.color("customMessage", text);
123
+ case "eye":
124
+ case "feet":
125
+ default:
126
+ return theme.dim(text);
127
+ }
128
+ }
129
+
130
+ function Mascot({ theme, face }: { theme: InkThemeAdapter; face: MascotFace }) {
131
+ return jsx(Box, {
132
+ flexDirection: "column",
133
+ alignItems: "center",
134
+ marginY: 1,
135
+ children: mascotRows(face).map((row, rowIndex) =>
136
+ jsx(
137
+ Box,
138
+ {
139
+ children: row.map((seg, segIndex) =>
140
+ jsx(Text, { children: paintMascot(theme, seg.tone, seg.text) }, segIndex)
141
+ )
142
+ },
143
+ rowIndex
144
+ )
145
+ )
146
+ });
147
+ }
148
+
149
+ function shortenWorkspace(workspace: string): string {
150
+ const home = homedir();
151
+ if (home.length > 0 && workspace.startsWith(home)) {
152
+ return `~${workspace.slice(home.length)}`;
153
+ }
154
+ return workspace;
155
+ }
156
+
157
+ export interface BannerProps {
158
+ readonly theme: InkThemeAdapter;
159
+ readonly modelId: string;
160
+ readonly workspace: string;
161
+ readonly version: string;
162
+ readonly started?: boolean;
163
+ readonly branch?: string | null;
164
+ readonly verbose?: boolean;
165
+ readonly quiet?: boolean;
166
+ readonly compact?: boolean;
167
+ readonly name?: string;
168
+ readonly sweep?: boolean;
169
+ readonly variantSeed?: number;
170
+ readonly whatsNew?: readonly string[];
171
+ readonly startup?: StartupMap;
172
+ readonly notices?: readonly StartupNotice[];
173
+ readonly changelog?: StartupChangelog;
174
+ }
175
+
176
+ export function Banner(props: BannerProps) {
177
+ const {
178
+ theme,
179
+ modelId,
180
+ workspace,
181
+ version,
182
+ started,
183
+ branch,
184
+ quiet,
185
+ compact,
186
+ name,
187
+ variantSeed,
188
+ whatsNew,
189
+ notices,
190
+ changelog
191
+ } = props;
192
+ const noticeList = notices ?? [];
193
+ if (quiet || compact) {
194
+ return jsxs(Box, {
195
+ flexDirection: "column",
196
+ marginBottom: 1,
197
+ children: [
198
+ jsx(StartupNotices, { theme, notices: noticeList }),
199
+ jsxs(Box, {
200
+ children: [
201
+ compact
202
+ ? jsxs(Fragment, {
203
+ children: [
204
+ jsx(Text, { children: theme.color("accent", COMPACT_EMBLEM.slice(0, 2)) }),
205
+ jsx(Text, { children: theme.color("customMessage", COMPACT_EMBLEM.slice(2)) }),
206
+ jsx(Text, { children: " " })
207
+ ]
208
+ })
209
+ : null,
210
+ jsx(Text, { bold: true, children: theme.color("accent", BRAND) }),
211
+ jsx(Text, { children: " " }),
212
+ jsx(Text, { children: theme.muted(`v${version}`) }),
213
+ jsx(Text, { children: " " }),
214
+ jsx(Text, { children: theme.dim(modelId) })
215
+ ]
216
+ }),
217
+ jsx(WelcomeLine, { theme, name, tone: "muted" }),
218
+ changelog ? jsx(StartupChangelogBlock, { theme, changelog }) : null
219
+ ]
220
+ });
221
+ }
222
+ if (started) {
223
+ return jsxs(Box, {
224
+ flexDirection: "column",
225
+ marginBottom: 1,
226
+ children: [
227
+ jsx(StartupNotices, { theme, notices: noticeList }),
228
+ jsx(ShellLine, { theme, workspace, branch }),
229
+ jsx(Box, { marginTop: 1, children: jsx(Wordmark, { theme }) }),
230
+ jsxs(Box, {
231
+ marginTop: 1,
232
+ flexDirection: "column",
233
+ children: [
234
+ jsxs(Box, {
235
+ children: [
236
+ jsx(Text, { bold: true, children: theme.color("accent", "indus console") }),
237
+ jsx(Text, { children: " " }),
238
+ jsx(Text, { children: theme.dim(`v${version}`) })
239
+ ]
240
+ }),
241
+ jsx(WelcomeLine, { theme, name, tone: "accent" })
242
+ ]
243
+ }),
244
+ jsxs(Box, {
245
+ flexDirection: "column",
246
+ borderStyle: "round",
247
+ borderColor: theme.colors["accent"],
248
+ paddingX: 1,
249
+ marginTop: 1,
250
+ children: [
251
+ jsx(Text, { bold: true, children: theme.color("accent", "Session") }),
252
+ jsx(SessionFact, { theme, label: "model", value: modelId }),
253
+ jsx(SessionFact, { theme, label: "cwd", value: shortenWorkspace(workspace) })
254
+ ]
255
+ }),
256
+ changelog ? jsx(StartupChangelogBlock, { theme, changelog }) : null
257
+ ]
258
+ });
259
+ }
260
+ const variant = pickWelcomeVariant(variantSeed ?? 0);
261
+ const greeting = welcomeLine(name);
262
+ const news = whatsNew && whatsNew.length > 0 ? whatsNew : WHATS_NEW;
263
+ const metaLine = modelId;
264
+ return jsxs(Box, {
265
+ flexDirection: "column",
266
+ marginBottom: 1,
267
+ children: [
268
+ jsx(StartupNotices, { theme, notices: noticeList }),
269
+ jsxs(Box, {
270
+ flexDirection: "column",
271
+ borderStyle: "round",
272
+ borderColor: theme.colors["accent"],
273
+ paddingX: 2,
274
+ paddingY: 1,
275
+ children: [
276
+ jsxs(Box, {
277
+ children: [
278
+ jsx(Text, { children: theme.color("customMessage", "● ") }),
279
+ jsx(Text, { bold: true, children: theme.color("accent", `${BRAND} `) }),
280
+ jsx(Text, { children: theme.dim(`v${version}`) })
281
+ ]
282
+ }),
283
+ jsxs(Box, {
284
+ marginTop: 1,
285
+ flexDirection: "row",
286
+ children: [
287
+ jsxs(Box, {
288
+ flexDirection: "column",
289
+ alignItems: "center",
290
+ width: 32,
291
+ flexShrink: 0,
292
+ marginRight: 2,
293
+ children: [
294
+ jsx(Text, { bold: true, children: theme.color("text", greeting) }),
295
+ jsx(Mascot, { theme, face: variant.mascot }),
296
+ jsx(Text, { children: theme.dim(metaLine) }),
297
+ jsx(Text, { children: theme.dim(shortenWorkspace(workspace)) })
298
+ ]
299
+ }),
300
+ jsxs(Box, {
301
+ flexDirection: "column",
302
+ flexGrow: 1,
303
+ borderStyle: "single",
304
+ borderColor: theme.colors["borderMuted"],
305
+ borderTop: false,
306
+ borderRight: false,
307
+ borderBottom: false,
308
+ paddingLeft: 2,
309
+ children: [
310
+ jsx(Text, { bold: true, children: theme.color("accent", "Tips for getting started") }),
311
+ jsx(Text, { children: theme.color("text", variant.tip) }),
312
+ jsx(Box, { marginTop: 1, children: jsx(Text, { bold: true, children: theme.color("accent", "What's new") }) }),
313
+ news.map((line, index) =>
314
+ jsxs(Box, {
315
+ flexDirection: "row",
316
+ children: [
317
+ jsx(Box, { width: 2, flexShrink: 0, children: jsx(Text, { children: theme.color("customMessage", "▸") }) }),
318
+ jsx(Box, { flexGrow: 1, children: jsx(Text, { children: theme.color("text", line) }) })
319
+ ]
320
+ }, index)
321
+ ),
322
+ jsx(Box, { marginTop: 1, children: jsx(Text, { italic: true, children: theme.muted("/release-notes for more") }) })
323
+ ]
324
+ })
325
+ ]
326
+ })
327
+ ]
328
+ }),
329
+ changelog ? jsx(StartupChangelogBlock, { theme, changelog }) : null
330
+ ]
331
+ });
332
+ }
333
+ // Keep MASCOTS imported as a side-effect of this file even if not used directly
334
+ void MASCOTS;