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,641 @@
1
+ /**
2
+ * Boot helper: assemble a {@link SessionConductor} for a non-interactive runner.
3
+ *
4
+ * Both the oneshot and the link runners drive a live conductor; this module owns
5
+ * the one place that turns a {@link BootContext} into one, so the two runners do
6
+ * not each re-derive the model id and session options. The model is taken from
7
+ * the invocation when supplied, else resolved to the catalog default through the
8
+ * {@link ModelMatcher}; the conductor itself is built lazily so no framework agent
9
+ * is constructed until the first turn runs.
10
+ */
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { createMCPAgentToolFactory, loadMCPConfig, MCPClientPool } from "indusagi/mcp";
15
+ import {
16
+ type AgentTool,
17
+ type ApprovalResolver,
18
+ type CanUseToolFn,
19
+ type CheckpointStore,
20
+ type DelegateRunner,
21
+ type MemoryStore,
22
+ type PermissionMode,
23
+ type PermissionRule,
24
+ createSessionConductor,
25
+ isPermissionMode,
26
+ makeRule,
27
+ createPermissionGate,
28
+ } from "../../conductor/index.js";
29
+ import {
30
+ type ContextDoc,
31
+ type SkillCard,
32
+ composeBriefing,
33
+ gatherContextDocs,
34
+ gatherSkillCards,
35
+ modelInvocableCards,
36
+ joinBlocks,
37
+ bullets,
38
+ } from "../../briefing/index.js";
39
+ import {
40
+ clearStaleToolResults,
41
+ condense,
42
+ planSlice,
43
+ rehydrateRecentReads,
44
+ AUTO_CONDENSE_POLICY,
45
+ } from "../../window-budget/index.js";
46
+ import { ModelCatalog, ModelMatcher } from "../../conductor/index.js";
47
+ import { provisionDeck } from "../../capability-deck/index.js";
48
+ import { type WorkflowAgentRunner } from "../../workflow-engine/index.js";
49
+ import { PreferenceStore } from "../../settings/manager.js";
50
+ import { createDelegateRunner } from "./delegate-runner.js";
51
+ import { createCheckpointStore, CHECKPOINT_HANDLE_KEY } from "./checkpoint.js";
52
+ import { createReadStateStore, READ_STATE_HANDLE_KEY } from "./read-state.js";
53
+ import { MEMORY_HANDLE_KEY, WORKFLOW_HANDLE_KEY, DELEGATE_HANDLE_KEY } from "../../capability-deck/index.js";
54
+ import { createWorkflowAgentRunner } from "../../workflow-engine/index.js";
55
+ import { loadZohoAgentTools } from "../../integrations/zoho/index.js";
56
+ import { loadSarvamAgentTools } from "../../integrations/sarvam/index.js";
57
+ import { createAuthVault } from "../auth-vault.js";
58
+ import { TOKEN_FILE, hasValidServerToken, getServerToken } from "../server-token.js";
59
+ import { resolveServerMode, resolveServerGatewayUrls, serverTierProviders } from "./server-mode.js";
60
+ import { DiskMemoryStore, memoryDirFor, loadMemoryDoc } from "./memdir.js";
61
+ import { buildAddonHost, wrapToolsWithAddons, concatAddonTools } from "./addon-wiring.js";
62
+ import type { BootContext, Invocation } from "../contract.js";
63
+
64
+ interface AuthFileData {
65
+ [provider: string]: unknown;
66
+ }
67
+
68
+ function authenticatedProviders(ctx: BootContext): string[] {
69
+ try {
70
+ const path = ctx.workspace.authPath;
71
+ if (!existsSync(path)) return [];
72
+ const data = JSON.parse(readFileSync(path, "utf8")) as AuthFileData;
73
+ return Object.keys(data).filter((id) => {
74
+ const accounts = data[id];
75
+ return id.length > 0 && accounts !== null && typeof accounts === "object" && Object.keys(accounts as object).length > 0;
76
+ });
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+
82
+ const PREFERRED_DEFAULT: Record<string, string[]> = {
83
+ anthropic: [
84
+ "claude-sonnet-4-5",
85
+ "claude-haiku-4-5",
86
+ "claude-sonnet-4-6",
87
+ "claude-sonnet-4-0",
88
+ "claude-opus-4-5",
89
+ ],
90
+ openai: ["gpt-5.1", "gpt-4.1", "gpt-4o"],
91
+ minimax: ["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.5"],
92
+ sarvam: ["sarvam-105b", "sarvam-30b"],
93
+ };
94
+
95
+ function hasServerSessionSync(): boolean {
96
+ try {
97
+ const token = JSON.parse(readFileSync(TOKEN_FILE, "utf8")) as { expires_at?: unknown };
98
+ return typeof token.expires_at === "string" && new Date(token.expires_at).getTime() - Date.now() > 5 * 60 * 1000;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ function providerDefaultModel(catalog: ModelCatalog, provider: string): string | undefined {
105
+ const cards = catalog.byProvider(provider);
106
+ if (cards.length === 0) return undefined;
107
+ for (const bare of PREFERRED_DEFAULT[provider] ?? []) {
108
+ const hit = cards.find((c) => c.id === `${provider}/${bare}`);
109
+ if (hit) return hit.id;
110
+ }
111
+ return cards[cards.length - 1].id;
112
+ }
113
+
114
+ /**
115
+ * Resolve the model id for this run.
116
+ *
117
+ * Precedence: an explicit `--model` selector wins; otherwise a current model of a
118
+ * provider the user has authenticated for (so a fresh launch lands on a model the
119
+ * stored key can actually call); otherwise the catalog default.
120
+ *
121
+ * @param ctx the boot context carrying the parsed invocation
122
+ * @returns the canonical model id to bind the session to
123
+ */
124
+ export function resolveModelId(ctx: BootContext): string {
125
+ const explicit = ctx.invocation.modelId;
126
+ if (explicit !== undefined && explicit.trim().length > 0) {
127
+ return explicit;
128
+ }
129
+ const catalog = new ModelCatalog();
130
+ for (const provider of authenticatedProviders(ctx)) {
131
+ const id = providerDefaultModel(catalog, provider);
132
+ if (id !== undefined) return id;
133
+ }
134
+ if (hasServerSessionSync()) {
135
+ for (const provider of serverTierProviders()) {
136
+ const id = providerDefaultModel(catalog, provider);
137
+ if (id !== undefined) return id;
138
+ }
139
+ }
140
+ const card = new ModelMatcher(catalog).resolve({});
141
+ return card?.id ?? "";
142
+ }
143
+
144
+ const PROVIDER_ENV: Record<string, string> = {
145
+ anthropic: "ANTHROPIC_API_KEY",
146
+ openai: "OPENAI_API_KEY",
147
+ "azure-openai-responses": "AZURE_OPENAI_API_KEY",
148
+ google: "GEMINI_API_KEY",
149
+ groq: "GROQ_API_KEY",
150
+ cerebras: "CEREBRAS_API_KEY",
151
+ xai: "XAI_API_KEY",
152
+ openrouter: "OPENROUTER_API_KEY",
153
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
154
+ zai: "ZAI_API_KEY",
155
+ mistral: "MISTRAL_API_KEY",
156
+ minimax: "MINIMAX_API_KEY",
157
+ "minimax-cn": "MINIMAX_CN_API_KEY",
158
+ opencode: "OPENCODE_API_KEY",
159
+ sarvam: "SARVAM_API_KEY",
160
+ krutrim: "KRUTRIM_API_KEY",
161
+ nvidia: "NVIDIA_API_KEY",
162
+ };
163
+
164
+ interface AuthAccountRecord {
165
+ kind?: string;
166
+ isDefault?: boolean;
167
+ key?: string;
168
+ access?: string;
169
+ }
170
+
171
+ function primeProviderEnv(ctx: BootContext): void {
172
+ try {
173
+ const path = ctx.workspace.authPath;
174
+ if (!existsSync(path)) return;
175
+ const data = JSON.parse(readFileSync(path, "utf8")) as AuthFileData;
176
+ for (const [provider, accounts] of Object.entries(data)) {
177
+ if (provider.startsWith("_") || accounts === null || typeof accounts !== "object") continue;
178
+ const envVar = PROVIDER_ENV[provider];
179
+ if (envVar === undefined || process.env[envVar]) continue;
180
+ const list = Object.values(accounts as Record<string, AuthAccountRecord>);
181
+ const chosen = list.find((a) => a?.isDefault) ?? list[0];
182
+ const value = chosen?.kind === "apiKey" ? chosen.key : chosen?.access;
183
+ if (typeof value === "string" && value.length > 0) {
184
+ process.env[envVar] = value;
185
+ }
186
+ }
187
+ } catch {
188
+ // ignore
189
+ }
190
+ }
191
+
192
+ function buildKeyResolver(
193
+ ctx: BootContext,
194
+ vault: ReturnType<typeof createAuthVault>,
195
+ requestedAccount: string | undefined,
196
+ ) {
197
+ return async (provider: string): Promise<string | undefined> => {
198
+ try {
199
+ const candidates: (string | undefined)[] = [
200
+ requestedAccount,
201
+ await vault.defaultAccount(provider),
202
+ (await vault.listAccounts(provider))[0],
203
+ ];
204
+ for (const account of candidates) {
205
+ if (account === undefined) continue;
206
+ const key = await vault.readUsableKey(provider, account);
207
+ if (key) return key;
208
+ }
209
+ const server = await resolveServerMode(ctx, provider, vault);
210
+ if (server.serverMode && server.token) return server.token;
211
+ // also surface ambient env so providers that the user has not persisted but
212
+ // did export keep working in the local-key path
213
+ const envVar = PROVIDER_ENV[provider];
214
+ if (envVar && process.env[envVar]) return process.env[envVar];
215
+ return undefined;
216
+ } catch {
217
+ return undefined;
218
+ }
219
+ };
220
+ }
221
+
222
+ const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
223
+
224
+ function resolveThinkingLevel(raw: string | undefined): string | undefined {
225
+ return raw !== undefined && THINKING_LEVELS.has(raw) ? raw : undefined;
226
+ }
227
+
228
+ function resolveText(value: string): string {
229
+ try {
230
+ if (existsSync(value)) return readFileSync(value, "utf8");
231
+ } catch {
232
+ // ignore
233
+ }
234
+ return value;
235
+ }
236
+
237
+ const PROFILE_DIR = ".indusagi";
238
+
239
+ function skillRoots(cwd: string): Array<{ dir: string; origin: "project" | "user" }> {
240
+ return [
241
+ { dir: join(cwd, PROFILE_DIR, "skills"), origin: "project" },
242
+ { dir: join(homedir(), PROFILE_DIR, "skills"), origin: "user" },
243
+ ];
244
+ }
245
+
246
+ /**
247
+ * Gather the on-disk skill cards the model may invoke this run: walk the project
248
+ * and user `.indusagi/skills` roots and drop any card flagged
249
+ * `disable-model-invocation` (those stay loadable explicitly, but the model is
250
+ * not told about them). A filesystem walk error degrades to an empty list so a
251
+ * bad/unreadable skills dir never sinks the session.
252
+ */
253
+ export function gatherModelSkills(cwd: string): SkillCard[] {
254
+ try {
255
+ return modelInvocableCards(gatherSkillCards(skillRoots(cwd)).cards);
256
+ } catch {
257
+ return [];
258
+ }
259
+ }
260
+
261
+ const WORKFLOW_TOOL_ENABLED = false;
262
+
263
+ export function selectTools(
264
+ cwd: string,
265
+ inv: Invocation,
266
+ runner?: DelegateRunner,
267
+ memoryStore?: MemoryStore,
268
+ checkpoint?: CheckpointStore,
269
+ workflowRunner?: WorkflowAgentRunner,
270
+ ): AgentTool[] {
271
+ if (inv.noTools) return [];
272
+ const framework: Record<string, unknown> = {
273
+ ...runner ? { [DELEGATE_HANDLE_KEY]: runner } : {},
274
+ ...memoryStore ? { [MEMORY_HANDLE_KEY]: memoryStore } : {},
275
+ [READ_STATE_HANDLE_KEY]: createReadStateStore(),
276
+ ...checkpoint ? { [CHECKPOINT_HANDLE_KEY]: checkpoint } : {},
277
+ ...workflowRunner && WORKFLOW_TOOL_ENABLED ? { [WORKFLOW_HANDLE_KEY]: workflowRunner } : {},
278
+ };
279
+ const all = provisionDeck("all", {
280
+ cwd,
281
+ ...Object.keys(framework).length > 0 ? { framework } : {},
282
+ }).tools();
283
+ const visible = WORKFLOW_TOOL_ENABLED ? all : all.filter((tool) => tool.name !== "workflow");
284
+ if (inv.tools === undefined || inv.tools.length === 0) return visible;
285
+ const canon = (name: string) => name.toLowerCase().replace(/[_-]/g, "");
286
+ const allow = new Set(inv.tools.map(canon));
287
+ return visible.filter((tool) => allow.has(canon(tool.name)));
288
+ }
289
+
290
+ /**
291
+ * Compose the run's system prompt: `--system` replaces the built-in briefing,
292
+ * `--append-system` adds a trailing block, and both compose (override then
293
+ * append). With neither, it is the tool-aware built-in briefing.
294
+ */
295
+ export function composeSystem(
296
+ tools: AgentTool[],
297
+ inv: Invocation,
298
+ cwd: string,
299
+ skills: readonly SkillCard[] = [],
300
+ memoryDoc?: ContextDoc,
301
+ ): string {
302
+ const contextDocs = [
303
+ ...gatherContextDocs(cwd, homedir()),
304
+ ...memoryDoc ? [memoryDoc] : [],
305
+ ];
306
+ let base = inv.system !== undefined ? resolveText(inv.system) : composeBriefing({ tools, contextDocs, skills });
307
+ base = `${base}\n\n${renderSessionGuidanceSection(tools, skills)}`;
308
+ base = `${base}\n\n${renderEnvironmentSection(cwd, inv)}`;
309
+ if (inv && typeof inv.language === "string" && inv.language.length > 0) {
310
+ base = `${base}\n\n${renderLanguageSection(inv.language)}`;
311
+ }
312
+ if (Array.isArray(inv?.mcpInstructions) && inv.mcpInstructions.length > 0) {
313
+ base = `${base}\n\n${renderMcpInstructionsSection(inv.mcpInstructions)}`;
314
+ }
315
+ const hasZoho = Array.isArray(tools) && tools.some((t) => typeof t?.name === "string" && (t.name.startsWith("zoho") || t.name.includes("ZohoCRM") || t.name.includes("zoho__")));
316
+ if (hasZoho) {
317
+ base = `${base}\n\n## Zoho CRM (MCP)\nYou have live Zoho CRM tools (names often start with zoho_ or zoho__). Use them for CRM data — never invent record IDs. If only Leads tools are available, say so when the user asks about Deals/pipeline.`;
318
+ }
319
+ const hasSarvam = Array.isArray(tools) && tools.some((t) => typeof t?.name === "string" && (t.name.startsWith("sarvam") || t.name.includes("sarvam__") || t.name.includes("sarvam_")));
320
+ if (hasSarvam) {
321
+ base = `${base}\n\n## Sarvam AI (MCP)\nYou have live Sarvam tools for Indian-language STT, TTS, translation, transliteration, language ID, LLM, and vision. Prefer these tools for Indic language work; do not invent transcripts or translations. Audio/docs may be written under SARVAM_MCP_BASE_PATH (default ~/Desktop). Use actual tool names from your tool list (often prefixed sarvam_ or sarvam__).`;
322
+ }
323
+ return inv.appendSystem !== undefined ? `${base}\n\n${resolveText(inv.appendSystem)}` : base;
324
+ }
325
+
326
+ function renderSessionGuidanceSection(tools: AgentTool[], skills: readonly SkillCard[]): string {
327
+ const enabled = Array.isArray(tools) ? tools : [];
328
+ const hasSkills = Array.isArray(skills) && skills.length > 0 && enabled.some((t) => t.name === "task");
329
+ const items: string[] = [];
330
+ if (hasSkills) {
331
+ items.push("Use the task tool to delegate focused, self-contained sub-tasks. Subagents are valuable for parallelizing independent queries or for protecting the main context window from excessive results, but they should not be used excessively when not needed. Importantly, avoid duplicating work that subagents are already doing \u2014 if you delegate research to a subagent, do not also perform the same searches yourself.");
332
+ }
333
+ if (enabled.some((t) => t.name === "grep") || enabled.some((t) => t.name === "find")) {
334
+ items.push("For simple, directed codebase searches (e.g. for a specific file/class/function) use grep or find directly. For broader codebase exploration and deep research, use the task tool to delegate to a sub-agent.");
335
+ }
336
+ items.push(`If the user denies a tool call, do not re-attempt the exact same tool call with the same arguments. Escalate to the user with the ask_user_question tool only when you're genuinely stuck after investigation.`);
337
+ if (hasSkills) {
338
+ items.push("User-invocable skills are surfaced as slash commands (/<skill-name>). When executed, the skill gets expanded to a full prompt. Use the reader tool to load SKILL.md files when a task lines up with a skill's description.");
339
+ }
340
+ return joinBlocks(["# Session-specific guidance", bullets(items)]);
341
+ }
342
+
343
+ function renderEnvironmentSection(cwd: string, inv: Invocation): string {
344
+ const items: string[] = [];
345
+ items.push(`Primary working directory: ${cwd}`);
346
+ items.push(`Is a git repository: Yes`);
347
+ items.push(`Platform: ${process.platform}`);
348
+ items.push(`Shell: bash`);
349
+ if (inv && typeof inv.modelId === "string") {
350
+ items.push(`You are powered by the model ${inv.modelId}.`);
351
+ }
352
+ return joinBlocks([
353
+ "# Environment",
354
+ "You have been invoked in the following environment: ",
355
+ bullets(items),
356
+ ]);
357
+ }
358
+
359
+ function renderLanguageSection(language: string): string {
360
+ return `# Language\nAlways respond in ${language}. Use ${language} for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.`;
361
+ }
362
+
363
+ interface McpInstructionsEntry {
364
+ name: string;
365
+ instructions: string;
366
+ }
367
+
368
+ function renderMcpInstructionsSection(blocks: McpInstructionsEntry[]): string {
369
+ const instructionBlocks = blocks
370
+ .map((entry) => `## ${entry.name}\n${entry.instructions}`)
371
+ .join("\n\n");
372
+ return `# MCP Server Instructions\n\nThe following MCP servers have provided instructions for how to use their tools and resources:\n\n${instructionBlocks}`;
373
+ }
374
+
375
+ async function loadMcpTools(inv: Invocation, cwd: string): Promise<AgentTool[]> {
376
+ const paths = inv.mcp && inv.mcp.length > 0 ? inv.mcp : [cwd];
377
+ const tools: AgentTool[] = [];
378
+ const savedLog = console.log;
379
+ const savedError = console.error;
380
+ console.log = () => {};
381
+ console.error = () => {};
382
+ try {
383
+ for (const path of paths) {
384
+ try {
385
+ const servers = loadMCPConfig(path);
386
+ if (servers.length === 0) continue;
387
+ const pool = new MCPClientPool({ servers });
388
+ await pool.connectAll();
389
+ for (const client of pool.getAllClients()) {
390
+ const defs = await client.listTools();
391
+ for (const def of defs) tools.push(createMCPAgentToolFactory(def, client)());
392
+ }
393
+ } catch {
394
+ // swallow per-server failures to keep the session booting
395
+ }
396
+ }
397
+ } finally {
398
+ console.log = savedLog;
399
+ console.error = savedError;
400
+ }
401
+ return tools;
402
+ }
403
+
404
+ /**
405
+ * The cwd-scoped session directory under the workspace `sessions/` root.
406
+ *
407
+ * Sessions are partitioned per working directory (so `--continue` means "the most
408
+ * recent session in THIS directory"). The cwd is slugged — every non-alphanumeric
409
+ * run collapsed to a single dash — and wrapped in `--…--` markers, matching the
410
+ * on-disk layout. Both the conductor (writer) and {@link SessionLibrary} (reader)
411
+ * must agree on this, so it lives here and is shared by the repl runner.
412
+ *
413
+ * @param sessionsRoot the workspace `sessions/` directory
414
+ * @param cwd the run's working directory
415
+ */
416
+ export function sessionScopeDir(sessionsRoot: string, cwd: string): string {
417
+ const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
418
+ return join(sessionsRoot, `--${slug}--`);
419
+ }
420
+
421
+ function lastUserTurnStart(messages: Array<{ role: string }>): number {
422
+ for (let i = messages.length - 1; i >= 0; i--) {
423
+ if (messages[i].role === "user") return i;
424
+ }
425
+ return -1;
426
+ }
427
+
428
+ const condenseTranscript = async (
429
+ messages: Array<{ role: string }>,
430
+ opts?: { model?: unknown; apiKey?: string; signal?: AbortSignal; force?: boolean },
431
+ ): Promise<Array<{ role: string }>> => {
432
+ const model = opts?.model;
433
+ const deps = model ? {
434
+ model,
435
+ ...opts?.apiKey !== undefined ? { apiKey: opts.apiKey } : {},
436
+ ...opts?.signal !== undefined ? { signal: opts.signal } : {},
437
+ } : {};
438
+ const cleared = clearStaleToolResults(messages as never) as Array<{ role: string }>;
439
+ if (opts?.force) {
440
+ const cut = lastUserTurnStart(cleared);
441
+ if (cut <= 0) return messages;
442
+ const dropped = cleared.slice(0, cut);
443
+ const kept = cleared.slice(cut);
444
+ const summary = await condense(dropped, deps);
445
+ const restored = restoreUnderCountGuard(dropped, kept);
446
+ return [summary.message, ...restored, ...kept];
447
+ }
448
+ const plan = planSlice(cleared, AUTO_CONDENSE_POLICY);
449
+ if (plan.cut === 0 || plan.dropped.length === 0) return messages;
450
+ const summary = await condense(plan.dropped, deps);
451
+ const restored = restoreUnderCountGuard(plan.dropped, plan.kept);
452
+ return [summary.message, ...restored, ...plan.kept];
453
+ };
454
+
455
+ function restoreUnderCountGuard(dropped: unknown[], kept: unknown[]): unknown[] {
456
+ const headroom = dropped.length - 2;
457
+ if (headroom <= 0) return [];
458
+ return rehydrateRecentReads(dropped, kept, { maxFiles: Math.min(5, headroom) });
459
+ }
460
+
461
+ function gatherPermissionRules(store: PreferenceStore): PermissionRule[] {
462
+ const tiers = [store.projectOverrides(), store.globalDefaults()];
463
+ const rules: PermissionRule[] = [];
464
+ for (const tier of tiers) {
465
+ const perms = tier.permissions;
466
+ if (perms === undefined) continue;
467
+ for (const raw of perms.deny ?? []) rules.push(makeRule("deny", raw));
468
+ for (const raw of perms.ask ?? []) rules.push(makeRule("ask", raw));
469
+ for (const raw of perms.allow ?? []) rules.push(makeRule("allow", raw));
470
+ }
471
+ return rules;
472
+ }
473
+
474
+ function resolveDefaultMode(store: PreferenceStore): PermissionMode {
475
+ for (const tier of [store.projectOverrides(), store.globalDefaults()]) {
476
+ const mode = tier.permissions?.defaultMode;
477
+ if (mode !== undefined && isPermissionMode(mode)) return mode;
478
+ }
479
+ return "default";
480
+ }
481
+
482
+ function readOnlyNamesFromDeck(tools: AgentTool[]): Set<string> {
483
+ const names = new Set<string>();
484
+ for (const tool of tools) {
485
+ if (tool.readOnly === true) names.add(tool.name);
486
+ }
487
+ return names;
488
+ }
489
+
490
+ /**
491
+ * Build the `canUseTool` gate factory for a boot run.
492
+ *
493
+ * The factory receives a getter onto the conductor's LIVE permission mode plus the
494
+ * conductor's stable approval delegate, and returns the gate that reads them. The
495
+ * delegate is threaded straight into `requestApproval`: on a NON-interactive boot
496
+ * (oneshot / link) no resolver is ever installed, so the delegate denies and an
497
+ * `ask` decision deterministically denies with an actionable message. The
498
+ * interactive (React) console installs its overlay resolver via the conductor's
499
+ * `setApprovalResolver` AFTER mount, at which point the SAME delegate begins
500
+ * routing `ask` decisions to the overlay — no rebuild, fully additive.
501
+ *
502
+ * `rules` is the session's LIVE mutable rule list: the gate re-scans it on every
503
+ * call, and an `allow-always` approval appends into it (via `appendAllowRule`
504
+ * below), so the approval is remembered for the remainder of the session — and
505
+ * ONLY the session; nothing is ever written back to settings. Every gate the
506
+ * factory mints (and every sub-agent gate built over the same array) sees the
507
+ * appended rules immediately.
508
+ *
509
+ * Exported for the boot tests; product callers go through
510
+ * {@link buildSessionConductor}.
511
+ */
512
+ export function buildPermissionGate(
513
+ rules: PermissionRule[],
514
+ mode: PermissionMode,
515
+ tools: readonly AgentTool[],
516
+ planReachable = false,
517
+ ): ((currentMode: () => PermissionMode, requestApproval?: ApprovalResolver) => CanUseToolFn) | undefined {
518
+ if (rules.length === 0 && mode === "bypass" && !planReachable) {
519
+ return undefined;
520
+ }
521
+ const readOnlyToolNames = readOnlyNamesFromDeck(tools as AgentTool[]);
522
+ return (currentMode, requestApproval) => createPermissionGate({
523
+ rules,
524
+ mode: currentMode,
525
+ ...requestApproval !== undefined ? { requestApproval } : {},
526
+ appendAllowRule: (rule) => rules.push(rule),
527
+ readOnlyToolNames,
528
+ });
529
+ }
530
+
531
+ function resolveOpeningMode(inv: Invocation, store: PreferenceStore): PermissionMode {
532
+ const raw = inv.flags["permission-mode"];
533
+ if (typeof raw === "string" && isPermissionMode(raw)) return raw;
534
+ return resolveDefaultMode(store);
535
+ }
536
+
537
+ export async function buildSessionConductor(ctx: BootContext) {
538
+ primeProviderEnv(ctx);
539
+ const inv = ctx.invocation;
540
+ const modelId = resolveModelId(ctx);
541
+ const workspace = inv.cwd;
542
+ const cwd = workspace ?? process.cwd();
543
+ const sessionsDir = sessionScopeDir(ctx.workspace.sessionsDir, cwd);
544
+ const memDir = memoryDirFor(ctx.workspace, cwd);
545
+ const memoryStore = new DiskMemoryStore(memDir);
546
+ const memoryDoc = loadMemoryDoc(memDir);
547
+ const vault = createAuthVault(ctx.workspace.authPath);
548
+ const getApiKey = buildKeyResolver(ctx, vault, inv.account);
549
+ const gatewayBaseUrls = await resolveServerGatewayUrls(ctx, vault);
550
+ const settings = PreferenceStore.fromWorkspace(ctx.workspace, cwd);
551
+ const permissionRules = gatherPermissionRules(settings);
552
+ const permissionMode = resolveOpeningMode(inv, settings);
553
+ let liveConductor: { permissionMode(): PermissionMode } | undefined;
554
+ const subagentPolicy = {
555
+ rules: permissionRules,
556
+ mode: () => liveConductor?.permissionMode() ?? permissionMode,
557
+ };
558
+ const surveyBriefing = composeBriefing({
559
+ tools: provisionDeck("authoring", { cwd }).tools(),
560
+ });
561
+ const runner = createDelegateRunner({
562
+ modelId,
563
+ getApiKey,
564
+ system: surveyBriefing,
565
+ cwd,
566
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
567
+ permissionPolicy: subagentPolicy,
568
+ });
569
+ const workflowRunner = createWorkflowAgentRunner({
570
+ modelId,
571
+ getApiKey,
572
+ cwd,
573
+ getGatewayBaseUrls: () => resolveServerGatewayUrls(ctx, vault),
574
+ permissionPolicy: subagentPolicy,
575
+ });
576
+ const addons = await buildAddonHost(ctx);
577
+ const checkpoint = createCheckpointStore();
578
+ const deck = [
579
+ ...selectTools(cwd, inv, runner, memoryStore, checkpoint, workflowRunner),
580
+ ...(await loadMcpTools(inv, cwd)),
581
+ ...(await loadZohoAgentTools({
582
+ cwd,
583
+ closables: ctx.closables,
584
+ zohoUrl: typeof inv.zohoUrl === "string" ? inv.zohoUrl : typeof inv.flags?.["zoho-url"] === "string" ? inv.flags["zoho-url"] : process.env.ZOHO_MCP_URL,
585
+ noZoho: inv.noZoho === true || inv.flags?.["no-zoho"] === true,
586
+ forceZoho: inv.flags?.zoho === true,
587
+ })),
588
+ ...(await loadSarvamAgentTools({
589
+ cwd,
590
+ closables: ctx.closables,
591
+ sarvamKey: typeof inv.sarvamKey === "string" ? inv.sarvamKey : typeof inv.flags?.["sarvam-key"] === "string" ? inv.flags["sarvam-key"] : process.env.SARVAM_API_KEY,
592
+ noSarvam: inv.noSarvam === true || inv.flags?.["no-sarvam"] === true,
593
+ forceSarvam: inv.flags?.sarvam === true,
594
+ basePath: typeof inv.sarvamBasePath === "string" ? inv.sarvamBasePath : typeof inv.flags?.["sarvam-base-path"] === "string" ? inv.flags["sarvam-base-path"] : undefined,
595
+ })),
596
+ ];
597
+ const tools = wrapToolsWithAddons(concatAddonTools(deck, addons), addons);
598
+ const skills = gatherModelSkills(cwd);
599
+ const system = composeSystem(tools, inv, cwd, skills, memoryDoc);
600
+ const thinking = resolveThinkingLevel(inv.thinking);
601
+ const fallbackModelId = inv.fallbackModelId;
602
+ const planReachable = inv.mode === "repl";
603
+ const canUseTool = buildPermissionGate(permissionRules, permissionMode, tools, planReachable);
604
+ const conductor = createSessionConductor(
605
+ {
606
+ modelId,
607
+ tools,
608
+ system,
609
+ getApiKey,
610
+ sessionsDir,
611
+ permissionMode,
612
+ plansDir: sessionsDir,
613
+ checkpoint,
614
+ ...canUseTool !== undefined ? { canUseTool } : {},
615
+ ...thinking !== undefined ? { thinking } : {},
616
+ ...workspace ? { workspace } : {},
617
+ ...fallbackModelId !== undefined ? { fallbackModelId } : {},
618
+ ...Object.keys(gatewayBaseUrls).length > 0 ? { gatewayBaseUrls } : {},
619
+ },
620
+ { condense: condenseTranscript },
621
+ );
622
+ liveConductor = conductor;
623
+ return conductor;
624
+ }
625
+
626
+ /**
627
+ * The prompts a oneshot run submits: the positional first prompt when present,
628
+ * plus any pass-through tokens that survived flag parsing. Empty when the
629
+ * invocation carried no request text (the caller decides what to do then).
630
+ *
631
+ * @param ctx the boot context carrying the parsed invocation
632
+ */
633
+ export function oneshotPrompts(ctx: BootContext): string[] {
634
+ const prompts: string[] = [];
635
+ const head = ctx.invocation.prompt;
636
+ if (head !== undefined && head.length > 0) {
637
+ prompts.push(head);
638
+ }
639
+ return prompts;
640
+ }
641
+