@ngandu/ulicode 0.0.6

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 (59) hide show
  1. package/CHANGELOG.md +1081 -0
  2. package/README.md +312 -0
  3. package/dist/agents/definitions/ask.agent.md +53 -0
  4. package/dist/agents/definitions/audit-tests.agent.md +138 -0
  5. package/dist/agents/definitions/build.agent.md +111 -0
  6. package/dist/agents/definitions/execute.agent.md +99 -0
  7. package/dist/agents/definitions/explore.agent.md +57 -0
  8. package/dist/agents/definitions/fast.agent.md +48 -0
  9. package/dist/agents/definitions/plan-mode.agent.md +102 -0
  10. package/dist/agents/definitions/planner.agent.md +59 -0
  11. package/dist/chunk-3YYDXNUH.js +854 -0
  12. package/dist/chunk-3YYDXNUH.js.map +1 -0
  13. package/dist/chunk-IEV2IT3O.cjs +873 -0
  14. package/dist/chunk-IEV2IT3O.cjs.map +1 -0
  15. package/dist/chunk-MBWGSXBT.js +11927 -0
  16. package/dist/chunk-MBWGSXBT.js.map +1 -0
  17. package/dist/chunk-MS5RYNRK.js +137 -0
  18. package/dist/chunk-MS5RYNRK.js.map +1 -0
  19. package/dist/chunk-OXFO76JC.js +2633 -0
  20. package/dist/chunk-OXFO76JC.js.map +1 -0
  21. package/dist/chunk-PKRLG6A4.js +1756 -0
  22. package/dist/chunk-PKRLG6A4.js.map +1 -0
  23. package/dist/chunk-PUVEPQQ3.cjs +1805 -0
  24. package/dist/chunk-PUVEPQQ3.cjs.map +1 -0
  25. package/dist/chunk-R6JK3DE3.cjs +148 -0
  26. package/dist/chunk-R6JK3DE3.cjs.map +1 -0
  27. package/dist/chunk-Y3HWP75B.cjs +11974 -0
  28. package/dist/chunk-Y3HWP75B.cjs.map +1 -0
  29. package/dist/chunk-Y5PO67TG.cjs +2659 -0
  30. package/dist/chunk-Y5PO67TG.cjs.map +1 -0
  31. package/dist/cli.cjs +372 -0
  32. package/dist/cli.cjs.map +1 -0
  33. package/dist/cli.d.cts +1 -0
  34. package/dist/cli.d.ts +1 -0
  35. package/dist/cli.js +370 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/index.cjs +16 -0
  38. package/dist/index.cjs.map +1 -0
  39. package/dist/index.d.cts +165 -0
  40. package/dist/index.d.ts +165 -0
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/permissions-NRD36MYI.cjs +40 -0
  44. package/dist/permissions-NRD36MYI.cjs.map +1 -0
  45. package/dist/permissions-RC7CYR5H.js +3 -0
  46. package/dist/permissions-RC7CYR5H.js.map +1 -0
  47. package/dist/project-q9WpahUs.d.cts +329 -0
  48. package/dist/project-q9WpahUs.d.ts +329 -0
  49. package/dist/storage-6P53PQBL.cjs +24 -0
  50. package/dist/storage-6P53PQBL.cjs.map +1 -0
  51. package/dist/storage-QELMNBZ2.js +3 -0
  52. package/dist/storage-QELMNBZ2.js.map +1 -0
  53. package/dist/tui.cjs +76 -0
  54. package/dist/tui.cjs.map +1 -0
  55. package/dist/tui.d.cts +1013 -0
  56. package/dist/tui.d.ts +1013 -0
  57. package/dist/tui.js +3 -0
  58. package/dist/tui.js.map +1 -0
  59. package/package.json +107 -0
@@ -0,0 +1,2633 @@
1
+ import { logRuntimeProfile, lspManager, loadSettingsCached, loadAgentDefinitionsCached, createHarnessSubagentsFromDefinitions, createHarnessModesFromDefinitions, getAvailableModePacks, getAvailableOmPacks, resolveModelDefaults, resolveOmModel, getSubagentModeMap, releaseThreadLock, acquireThreadLock, getCustomProviderId, toCustomProviderModelId, saveSettings, getModeDefinition, getAllowedSubagentIdsForMode } from './chunk-PKRLG6A4.js';
2
+ import { AuthStorage, detectProject, getResourceIdOverride, getStorageConfig, getCurrentGitBranch, getOmScope, getDatabasePath } from './chunk-3YYDXNUH.js';
3
+ import { getToolCategory, TOOL_NAME_OVERRIDES, MC_TOOLS } from './chunk-MS5RYNRK.js';
4
+ import { Agent } from '@mastra/core/agent';
5
+ import { Harness } from '@mastra/core/harness';
6
+ import { PROVIDER_REGISTRY, ModelRouterLanguageModel, ModelsDevGateway, NetlifyGateway } from '@mastra/core/llm';
7
+ import { createTool } from '@mastra/core/tools';
8
+ import { tavily } from '@tavily/core';
9
+ import { z } from 'zod';
10
+ import { Tiktoken } from 'js-tiktoken/lite';
11
+ import o200k_base from 'js-tiktoken/ranks/o200k_base';
12
+ import * as os from 'os';
13
+ import os__default, { homedir } from 'os';
14
+ import * as path from 'path';
15
+ import path__default, { join, dirname } from 'path';
16
+ import { LocalFilesystem, Workspace, LocalSandbox, createWorkspaceTools } from '@mastra/core/workspace';
17
+ import * as fs4 from 'fs';
18
+ import fs4__default, { existsSync, readFileSync } from 'fs';
19
+ import { fileURLToPath } from 'url';
20
+ import { Memory } from '@mastra/memory';
21
+ import { createAnthropic } from '@ai-sdk/anthropic';
22
+ import { createOpenAI } from '@ai-sdk/openai';
23
+ import { wrapLanguageModel } from 'ai';
24
+ import { spawn } from 'child_process';
25
+ import { MCPClient } from '@mastra/mcp';
26
+ import { LibSQLStore } from '@mastra/libsql';
27
+ import { PostgresStore } from '@mastra/pg';
28
+
29
+ // src/agents/prompts/base.ts
30
+ function buildBasePrompt(ctx) {
31
+ return `You are Ulicode, an interactive CLI coding agent that helps users with software engineering tasks.
32
+
33
+ # Environment
34
+ Working directory: ${ctx.projectPath}
35
+ Project: ${ctx.projectName}
36
+ ${ctx.gitBranch ? `Git branch: ${ctx.gitBranch}` : "Not a git repository"}
37
+ Platform: ${ctx.platform}
38
+ Date: ${ctx.date}
39
+ Current mode: ${ctx.mode}
40
+
41
+ # Tone and Style
42
+ - Your output is displayed on a command line interface. Keep responses concise.
43
+ - Use Github-flavored markdown for formatting.
44
+ - Only use emojis if the user explicitly requests it.
45
+ - Use tool calls for actions (editing files, running commands, searching, etc.). Use text for communication \u2014 talk to the user in text, not via tools, except for communication tools like \`submit_plan\`, \`ask_user\`, and \`task_write\`.
46
+ - Prioritize technical accuracy over validating the user's beliefs. Be direct and objective. Respectful correction is more valuable than false agreement.
47
+
48
+ ${ctx.toolGuidance}
49
+
50
+ # How to Work on Tasks
51
+
52
+ ## Start by Understanding
53
+ - Read relevant code before making changes. Use search_content/find_files to find related files.
54
+ - For unfamiliar codebases, check git log to understand recent changes and patterns.
55
+ - Identify existing conventions (naming, structure, error handling) and follow them.
56
+
57
+ ## Work Incrementally
58
+ - Focus on ONE thing at a time. Complete it fully before moving to the next.
59
+ - Leave the codebase in a clean state after each change \u2014 no half-implemented features.
60
+ - For multi-step tasks, use tasks to track progress and ensure nothing is missed.
61
+
62
+ ## Verify Before Moving On
63
+ - After each change, verify it works. Don't assume \u2014 actually test it.
64
+ - Run the relevant tests, check for type errors, or manually verify the behavior.
65
+ - If something breaks, fix it immediately. Don't pile more changes on top of broken code.
66
+
67
+ # Coding Philosophy
68
+
69
+ - **Avoid over-engineering.** Only make changes that are directly requested or clearly necessary.
70
+ - **Don't add extras.** No unrequested features, refactoring, docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
71
+ - **Don't add unnecessary error handling.** Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs).
72
+ - **Don't create premature abstractions.** Three similar lines of code is better than a helper function used once. Don't design for hypothetical future requirements.
73
+ - **Clean up dead code.** If something is unused, delete it completely. No backwards-compatibility shims, no renaming to \`_unused\`, no \`// removed\` comments.
74
+ - **Be careful with security.** Don't introduce command injection, XSS, SQL injection, or other vulnerabilities. If you notice insecure code you wrote, fix it immediately.
75
+
76
+ # Git Safety
77
+
78
+ ## Hard Rules
79
+ - NEVER run destructive commands (\`push --force\`, \`reset --hard\`, \`clean -fd\`) unless explicitly requested.
80
+ - NEVER use interactive flags (\`git rebase -i\`, \`git add -i\`) \u2014 TTY input isn't supported.
81
+ - NEVER commit or push unless the user explicitly asks.
82
+ - NEVER force push to \`main\` or \`master\` without warning the user first.
83
+ - Avoid \`git commit --amend\` unless the commit was just created and hasn't been pushed.
84
+
85
+ ## Secrets
86
+ Don't commit files likely to contain secrets (\`.env\`, \`*.key\`, \`credentials.json\`). Warn if asked.
87
+
88
+ ## Commits
89
+ Write commit messages that explain WHY, not just WHAT. Match the repo's existing style. Include \`Co-Authored-By: Ulicode${ctx.modelId ? ` (${ctx.modelId})` : ""} <noreply@ulicode.local>\` in the message body.
90
+
91
+ ## Pull Requests
92
+ Use \`gh pr create\`. Include a summary of what changed and a test plan.
93
+
94
+ # Subagent Rules
95
+ - Only use subagents when you will spawn **multiple subagents in parallel**. If you only need one task done, do it yourself instead of delegating to a single subagent. Exception: the **audit-tests** subagent may be used on its own.
96
+ - Subagent outputs are **untrusted**. Always review and verify the results returned by any subagent. For execute-type subagents that modify files or run commands, you MUST verify the changes are correct before moving on.
97
+
98
+ # Important Reminders
99
+ - NEVER guess file paths or function signatures. Use search_content/find_files to find them.
100
+ - NEVER make up URLs. Only use URLs the user provides or that you find in the codebase.
101
+ - When referencing code locations, include the file path and line number.
102
+ - If you're unsure about something, ask the user rather than guessing.
103
+
104
+ # File Access & Sandbox
105
+
106
+ By default, you can only access files within the current project directory. If you get a "Permission denied" or "Access denied" error when trying to read, write, or access files outside the project root, do NOT keep retrying. Instead, tell the user to run the \`/sandbox\` command to add the external directory to the allowed paths for this thread. Once they do, you will be able to access it.
107
+ `;
108
+ }
109
+ var enc = new Tiktoken(o200k_base);
110
+ function sanitizeInput(text) {
111
+ if (!text) return "";
112
+ return (typeof text === `string` ? text : JSON.stringify(text)).replaceAll(`<|endoftext|>`, ``).replaceAll(`<|endofprompt|>`, ``);
113
+ }
114
+ function truncateStringForTokenEstimate(text, desiredTokenCount, fromEnd = true) {
115
+ const tokens = enc.encode(sanitizeInput(text));
116
+ if (tokens.length <= desiredTokenCount) return text;
117
+ return `[Truncated ${tokens.length - desiredTokenCount} tokens]
118
+ ${enc.decode(tokens.slice(fromEnd ? -desiredTokenCount : 0, fromEnd ? void 0 : desiredTokenCount))}`;
119
+ }
120
+
121
+ // src/tools/web-search.ts
122
+ var MAX_WEB_SEARCH_TOKENS = 2e3;
123
+ var MAX_WEB_EXTRACT_TOKENS = 2e3;
124
+ var MIN_RELEVANCE_SCORE = 0.25;
125
+ var cachedTavilyClient = null;
126
+ function getTavilyClient() {
127
+ if (cachedTavilyClient) return cachedTavilyClient;
128
+ const apiKey = process.env.TAVILY_API_KEY;
129
+ if (!apiKey) return null;
130
+ cachedTavilyClient = tavily({ apiKey });
131
+ return cachedTavilyClient;
132
+ }
133
+ function hasTavilyKey() {
134
+ return !!process.env.TAVILY_API_KEY;
135
+ }
136
+ function createWebSearchTool() {
137
+ return createTool({
138
+ id: "web-search",
139
+ description: "Search the web for information. Use this to find documentation, look up error messages, check package APIs, or research any topic. Returns relevant web results with content snippets and optionally images.",
140
+ inputSchema: z.object({
141
+ query: z.string().describe("The search query"),
142
+ searchDepth: z.enum(["basic", "advanced"]).optional().default("basic").describe("Search depth - 'basic' for quick searches, 'advanced' for more thorough results"),
143
+ maxResults: z.number().optional().default(10).describe("Maximum number of results to return"),
144
+ includeImages: z.boolean().optional().default(false).describe("Whether to include related images in results")
145
+ }),
146
+ execute: async (context) => {
147
+ const tavilyClient = getTavilyClient();
148
+ if (!tavilyClient) {
149
+ return "No results (TAVILY_API_KEY not configured)";
150
+ }
151
+ try {
152
+ const response = await tavilyClient.search(context.query, {
153
+ searchDepth: context.searchDepth || "basic",
154
+ maxResults: context.maxResults || 10,
155
+ includeAnswer: true,
156
+ includeImages: context.includeImages || false
157
+ });
158
+ const filteredResults = response.results.filter((r) => (r.score ?? 1) >= MIN_RELEVANCE_SCORE);
159
+ const parts = [];
160
+ if (response.answer) {
161
+ parts.push(`Answer: ${response.answer}`);
162
+ }
163
+ for (const r of filteredResults) {
164
+ parts.push(`## ${r.title}
165
+ ${r.url}
166
+ ${r.content}`);
167
+ }
168
+ const images = (response.images || []).map((img) => typeof img === "string" ? img : img.url || "").filter(Boolean);
169
+ if (images.length > 0) {
170
+ parts.push(`Images:
171
+ ${images.join("\n")}`);
172
+ }
173
+ const text = parts.join("\n\n");
174
+ return truncateStringForTokenEstimate(text, MAX_WEB_SEARCH_TOKENS);
175
+ } catch {
176
+ return "No results";
177
+ }
178
+ }
179
+ });
180
+ }
181
+ function createWebExtractTool() {
182
+ return createTool({
183
+ id: "web-extract",
184
+ description: "Extract content from one or more URLs. Use this to read web pages, documentation, articles, or any URL. Returns the raw content in markdown format. You can provide up to 20 URLs at once.",
185
+ inputSchema: z.object({
186
+ urls: z.array(z.string()).min(1).max(20).describe("URLs to extract content from (max 20)"),
187
+ extractDepth: z.enum(["basic", "advanced"]).optional().default("basic").describe("Extraction depth - 'basic' for simple text, 'advanced' for JS-rendered pages"),
188
+ includeImages: z.boolean().optional().default(false).describe("Whether to include extracted image URLs")
189
+ }),
190
+ execute: async (context) => {
191
+ const tavilyClient = getTavilyClient();
192
+ if (!tavilyClient) {
193
+ return "Extraction failed (TAVILY_API_KEY not configured)";
194
+ }
195
+ try {
196
+ const response = await tavilyClient.extract(context.urls, {
197
+ extractDepth: context.extractDepth || "basic",
198
+ includeImages: context.includeImages || false
199
+ });
200
+ const parts = [];
201
+ for (const r of response.results || []) {
202
+ parts.push(`## ${r.url}
203
+ ${r.rawContent}`);
204
+ }
205
+ for (const r of response.failedResults || []) {
206
+ parts.push(`## ${r.url}
207
+ Error: ${r.error}`);
208
+ }
209
+ const text = parts.join("\n\n");
210
+ return truncateStringForTokenEstimate(text, MAX_WEB_EXTRACT_TOKENS);
211
+ } catch (error) {
212
+ return `Extraction failed: ${String(error)}`;
213
+ }
214
+ }
215
+ });
216
+ }
217
+ var ulicodeLocalSkillsPath = path__default.join(process.cwd(), ".ulicode", "skills");
218
+ var uliLocalSkillsPath = path__default.join(process.cwd(), ".uli-cli", "skills");
219
+ var pulseLocalSkillsPath = path__default.join(process.cwd(), ".pulse", "skills");
220
+ var mastraCodeLocalSkillsPath = path__default.join(process.cwd(), ".mastracode", "skills");
221
+ var claudeLocalSkillsPath = path__default.join(process.cwd(), ".claude", "skills");
222
+ var ulicodeGlobalSkillsPath = path__default.join(os__default.homedir(), ".ulicode", "skills");
223
+ var uliGlobalSkillsPath = path__default.join(os__default.homedir(), ".uli-cli", "skills");
224
+ var pulseGlobalSkillsPath = path__default.join(os__default.homedir(), ".pulse", "skills");
225
+ var mastraCodeGlobalSkillsPath = path__default.join(os__default.homedir(), ".mastracode", "skills");
226
+ var claudeGlobalSkillsPath = path__default.join(os__default.homedir(), ".claude", "skills");
227
+ function collectSkillPaths(skillsDirs) {
228
+ const paths = [];
229
+ const seen = /* @__PURE__ */ new Set();
230
+ for (const skillsDir of skillsDirs) {
231
+ if (!fs4__default.existsSync(skillsDir)) continue;
232
+ const resolved = fs4__default.realpathSync(skillsDir);
233
+ if (!seen.has(resolved)) {
234
+ seen.add(resolved);
235
+ paths.push(skillsDir);
236
+ }
237
+ try {
238
+ const entries = fs4__default.readdirSync(skillsDir, { withFileTypes: true });
239
+ for (const entry of entries) {
240
+ if (entry.isSymbolicLink()) {
241
+ const linkPath = path__default.join(skillsDir, entry.name);
242
+ const realPath = fs4__default.realpathSync(linkPath);
243
+ const stat = fs4__default.statSync(realPath);
244
+ if (stat.isDirectory()) {
245
+ const realParent = path__default.dirname(realPath);
246
+ if (!seen.has(realParent)) {
247
+ seen.add(realParent);
248
+ paths.push(realParent);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ return paths;
257
+ }
258
+ var skillPathsCache = null;
259
+ var skillPathsLogged = false;
260
+ function getSkillPaths() {
261
+ if (skillPathsCache) {
262
+ return [...skillPathsCache];
263
+ }
264
+ const startTime = performance.now();
265
+ skillPathsCache = collectSkillPaths([
266
+ ulicodeLocalSkillsPath,
267
+ uliLocalSkillsPath,
268
+ pulseLocalSkillsPath,
269
+ mastraCodeLocalSkillsPath,
270
+ claudeLocalSkillsPath,
271
+ ulicodeGlobalSkillsPath,
272
+ uliGlobalSkillsPath,
273
+ pulseGlobalSkillsPath,
274
+ mastraCodeGlobalSkillsPath,
275
+ claudeGlobalSkillsPath
276
+ ]);
277
+ logRuntimeProfile("workspace.skill_path_scan", performance.now() - startTime, {
278
+ count: skillPathsCache.length
279
+ });
280
+ if (!skillPathsLogged && skillPathsCache.length > 0) {
281
+ console.info(`Skills loaded from:`);
282
+ for (const p of skillPathsCache) {
283
+ console.info(` - ${p}`);
284
+ }
285
+ skillPathsLogged = true;
286
+ }
287
+ return [...skillPathsCache];
288
+ }
289
+ var WORKSPACE_ID_PREFIX = "mastra-code-workspace";
290
+ function detectPackageRunner(projectPath) {
291
+ if (existsSync(join(projectPath, "pnpm-lock.yaml"))) return "pnpm dlx";
292
+ if (existsSync(join(projectPath, "bun.lockb")) || existsSync(join(projectPath, "bun.lock"))) return "bunx";
293
+ if (existsSync(join(projectPath, "yarn.lock"))) return "yarn dlx";
294
+ if (existsSync(join(projectPath, "package-lock.json"))) return "npx --yes";
295
+ return "npx --yes";
296
+ }
297
+ function getDynamicWorkspace({ requestContext, mastra }) {
298
+ const ctx = requestContext.get("harness");
299
+ const state = ctx?.getState?.();
300
+ const modeId = ctx?.modeId ?? "build";
301
+ const rawProjectPath = state?.projectPath;
302
+ if (!rawProjectPath) {
303
+ throw new Error("Project path is required");
304
+ }
305
+ const projectPath = path__default.resolve(rawProjectPath);
306
+ const workspaceId = `${WORKSPACE_ID_PREFIX}-${projectPath}`;
307
+ const sandboxPaths = state?.sandboxAllowedPaths ?? [];
308
+ const skillPaths = getSkillPaths();
309
+ const allowedPaths = [...skillPaths, ...sandboxPaths.map((p) => path__default.resolve(p))];
310
+ const isPlanMode = modeId === "plan";
311
+ const isAskMode = modeId === "ask";
312
+ const planModeTools = {
313
+ mastra_workspace_write_file: { ...TOOL_NAME_OVERRIDES.mastra_workspace_write_file, enabled: false },
314
+ mastra_workspace_edit_file: { ...TOOL_NAME_OVERRIDES.mastra_workspace_edit_file, enabled: false },
315
+ mastra_workspace_ast_edit: { ...TOOL_NAME_OVERRIDES.mastra_workspace_ast_edit, enabled: false }
316
+ };
317
+ const askModeTools = {
318
+ mastra_workspace_write_file: { ...TOOL_NAME_OVERRIDES.mastra_workspace_write_file, enabled: false },
319
+ mastra_workspace_edit_file: { ...TOOL_NAME_OVERRIDES.mastra_workspace_edit_file, enabled: false },
320
+ mastra_workspace_ast_edit: { ...TOOL_NAME_OVERRIDES.mastra_workspace_ast_edit, enabled: false },
321
+ mastra_workspace_delete: { ...TOOL_NAME_OVERRIDES.mastra_workspace_delete, enabled: false },
322
+ mastra_workspace_mkdir: { ...TOOL_NAME_OVERRIDES.mastra_workspace_mkdir, enabled: false },
323
+ mastra_workspace_execute_command: { ...TOOL_NAME_OVERRIDES.mastra_workspace_execute_command, enabled: false },
324
+ mastra_workspace_get_process_output: { ...TOOL_NAME_OVERRIDES.mastra_workspace_get_process_output, enabled: false },
325
+ mastra_workspace_kill_process: { ...TOOL_NAME_OVERRIDES.mastra_workspace_kill_process, enabled: false }
326
+ };
327
+ const toolsConfig = isAskMode ? { ...TOOL_NAME_OVERRIDES, ...askModeTools } : isPlanMode ? { ...TOOL_NAME_OVERRIDES, ...planModeTools } : TOOL_NAME_OVERRIDES;
328
+ let existing;
329
+ try {
330
+ existing = mastra?.getWorkspaceById(workspaceId);
331
+ } catch {
332
+ }
333
+ if (existing) {
334
+ existing.filesystem.setAllowedPaths(allowedPaths);
335
+ existing.setToolsConfig(toolsConfig);
336
+ return existing;
337
+ }
338
+ const userLsp = loadSettingsCached().lsp ?? {};
339
+ const mcModulePath = join(dirname(fileURLToPath(import.meta.url)), "..");
340
+ const lspConfig = {
341
+ ...userLsp,
342
+ packageRunner: userLsp.packageRunner || detectPackageRunner(projectPath),
343
+ // Detected runner is the fallback — user's packageRunner always wins
344
+ searchPaths: [mcModulePath, ...userLsp.searchPaths ?? []]
345
+ };
346
+ const startTime = performance.now();
347
+ const workspace = new Workspace({
348
+ id: workspaceId,
349
+ name: "Ulicode Workspace",
350
+ filesystem: new LocalFilesystem({
351
+ basePath: projectPath,
352
+ allowedPaths
353
+ }),
354
+ sandbox: new LocalSandbox({
355
+ workingDirectory: projectPath,
356
+ env: {
357
+ ...process.env,
358
+ FORCE_COLOR: "1",
359
+ CLICOLOR_FORCE: "1",
360
+ TERM: process.env.TERM || "xterm-256color",
361
+ CI: "true",
362
+ NONINTERACTIVE: "1",
363
+ DEBIAN_FRONTEND: "noninteractive"
364
+ }
365
+ }),
366
+ tools: toolsConfig,
367
+ ...skillPaths.length > 0 ? { skills: skillPaths } : {},
368
+ lsp: lspConfig
369
+ });
370
+ logRuntimeProfile("workspace.create", performance.now() - startTime, {
371
+ workspaceId,
372
+ modeId,
373
+ skillPathCount: skillPaths.length
374
+ });
375
+ return workspace;
376
+ }
377
+
378
+ // src/tools/utils.ts
379
+ function isPathAllowed(targetPath, projectRoot, allowedPaths = []) {
380
+ const resolved = path.resolve(targetPath);
381
+ const roots = [projectRoot, ...allowedPaths].map((p) => path.resolve(p));
382
+ return roots.some((root) => resolved === root || resolved.startsWith(root + path.sep));
383
+ }
384
+ function getAllowedPathsFromContext(toolContext) {
385
+ const skillPaths = getSkillPaths();
386
+ if (!toolContext?.requestContext) {
387
+ return [...skillPaths];
388
+ }
389
+ const harnessCtx = toolContext.requestContext.get("harness");
390
+ const sandboxPaths = harnessCtx?.getState?.()?.sandboxAllowedPaths ?? harnessCtx?.state?.sandboxAllowedPaths ?? [];
391
+ return [...skillPaths, ...sandboxPaths];
392
+ }
393
+
394
+ // src/tools/request-sandbox-access.ts
395
+ function expandTilde(p) {
396
+ if (p === "~") return os.homedir();
397
+ if (p.startsWith("~/") || p.startsWith("~\\")) {
398
+ return `${os.homedir()}/${p.slice(2).replace(/\\/g, "/")}`;
399
+ }
400
+ return p;
401
+ }
402
+ function formatPathForMessage(requestedPath, resolvedPath) {
403
+ if (requestedPath.startsWith("~/") || requestedPath.startsWith("~\\")) {
404
+ return `${os.homedir()}/${requestedPath.slice(2).replace(/\\/g, "/")}`;
405
+ }
406
+ if (requestedPath === "~") {
407
+ return os.homedir();
408
+ }
409
+ return resolvedPath;
410
+ }
411
+ var requestCounter = 0;
412
+ var requestSandboxAccessTool = createTool({
413
+ id: "request_access",
414
+ description: `Request permission to access a directory outside the current project. Use this when you need to read or write files in a directory that is not within the project root. The user will be prompted to approve or deny the request.`,
415
+ inputSchema: z.object({
416
+ path: z.string().min(1).describe("The absolute path to the directory you need access to."),
417
+ reason: z.string().min(1).describe("Brief explanation of why you need access to this directory.")
418
+ }),
419
+ execute: async ({ path: requestedPath, reason }, context) => {
420
+ try {
421
+ const harnessCtx = context?.requestContext?.get("harness");
422
+ const expanded = expandTilde(requestedPath);
423
+ const absolutePath = path.isAbsolute(expanded) ? expanded : path.resolve(process.cwd(), expanded);
424
+ const displayPath = formatPathForMessage(requestedPath, absolutePath);
425
+ const projectRoot = process.cwd();
426
+ const allowedPaths = getAllowedPathsFromContext(context);
427
+ if (isPathAllowed(absolutePath, projectRoot, allowedPaths)) {
428
+ return {
429
+ content: `Access already granted: "${displayPath}" is within the project root or allowed paths.`,
430
+ isError: false
431
+ };
432
+ }
433
+ if (!harnessCtx?.emitEvent || !harnessCtx?.registerQuestion) {
434
+ return {
435
+ content: `Cannot request sandbox access: TUI context not available. The user should manually run /sandbox add ${displayPath}`,
436
+ isError: true
437
+ };
438
+ }
439
+ const questionId = `sandbox_${++requestCounter}_${Date.now()}`;
440
+ const answer = await new Promise((resolve3) => {
441
+ harnessCtx.registerQuestion({ questionId, resolve: resolve3 });
442
+ harnessCtx.emitEvent({
443
+ type: "sandbox_access_request",
444
+ questionId,
445
+ path: absolutePath,
446
+ reason
447
+ });
448
+ });
449
+ const approved = answer.toLowerCase().startsWith("y") || answer.toLowerCase() === "approve";
450
+ if (approved) {
451
+ const currentAllowed = harnessCtx.getState?.()?.sandboxAllowedPaths ?? [];
452
+ if (!currentAllowed.includes(absolutePath)) {
453
+ harnessCtx.setState?.({
454
+ sandboxAllowedPaths: [...currentAllowed, absolutePath]
455
+ });
456
+ }
457
+ const fs5 = context?.workspace?.filesystem;
458
+ if (fs5 instanceof LocalFilesystem) {
459
+ fs5.setAllowedPaths((prev) => [...prev, absolutePath]);
460
+ }
461
+ return {
462
+ content: `Access granted: "${displayPath}" has been added to allowed paths. You can now access files in this directory.`,
463
+ isError: false
464
+ };
465
+ } else {
466
+ return {
467
+ content: `Access denied: The user declined access to "${displayPath}".`,
468
+ isError: false
469
+ };
470
+ }
471
+ } catch (error) {
472
+ const msg = error instanceof Error ? error.message : "Unknown error";
473
+ return {
474
+ content: `Failed to request sandbox access: ${msg}`,
475
+ isError: true
476
+ };
477
+ }
478
+ }
479
+ });
480
+ var INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
481
+ var PROJECT_LOCATIONS = [
482
+ "",
483
+ // project root
484
+ ".claude",
485
+ ".ulicode",
486
+ ".uli-cli",
487
+ ".pulse",
488
+ ".mastracode"
489
+ ];
490
+ var GLOBAL_LOCATIONS = [
491
+ ".claude",
492
+ ".ulicode",
493
+ ".uli-cli",
494
+ ".pulse",
495
+ ".mastracode",
496
+ ".config/claude",
497
+ ".config/ulicode",
498
+ ".config/uli-cli",
499
+ ".config/pulse",
500
+ ".config/mastracode"
501
+ ];
502
+ var instructionCache = /* @__PURE__ */ new Map();
503
+ function findInstructionFile(basePath) {
504
+ for (const filename of INSTRUCTION_FILES) {
505
+ const fullPath = join(basePath, filename);
506
+ if (existsSync(fullPath)) {
507
+ return fullPath;
508
+ }
509
+ }
510
+ return null;
511
+ }
512
+ function loadAgentInstructions(projectPath) {
513
+ const sources = [];
514
+ const home = homedir();
515
+ for (const location of GLOBAL_LOCATIONS) {
516
+ const basePath = join(home, location);
517
+ const filePath = findInstructionFile(basePath);
518
+ if (filePath) {
519
+ try {
520
+ const content = readFileSync(filePath, "utf-8").trim();
521
+ if (content) {
522
+ sources.push({ path: filePath, content, scope: "global" });
523
+ break;
524
+ }
525
+ } catch {
526
+ }
527
+ }
528
+ }
529
+ for (const location of PROJECT_LOCATIONS) {
530
+ const basePath = location ? join(projectPath, location) : projectPath;
531
+ const filePath = findInstructionFile(basePath);
532
+ if (filePath) {
533
+ try {
534
+ const content = readFileSync(filePath, "utf-8").trim();
535
+ if (content) {
536
+ sources.push({ path: filePath, content, scope: "project" });
537
+ break;
538
+ }
539
+ } catch {
540
+ }
541
+ }
542
+ }
543
+ return sources;
544
+ }
545
+ function loadAgentInstructionsCached(projectPath) {
546
+ const cached = instructionCache.get(projectPath);
547
+ if (cached) {
548
+ return cached.map((source) => ({ ...source }));
549
+ }
550
+ const sources = loadAgentInstructions(projectPath);
551
+ instructionCache.set(
552
+ projectPath,
553
+ sources.map((source) => ({ ...source }))
554
+ );
555
+ return sources.map((source) => ({ ...source }));
556
+ }
557
+ function formatAgentInstructions(sources) {
558
+ if (sources.length === 0) return "";
559
+ const sections = sources.map((source) => {
560
+ const label = source.scope === "global" ? "Global" : "Project";
561
+ return `<!-- ${label} instructions from ${source.path} -->
562
+ ${source.content}`;
563
+ });
564
+ return `
565
+ # Agent Instructions
566
+
567
+ ${sections.join("\n\n")}
568
+ `;
569
+ }
570
+
571
+ // src/agents/prompts/tool-guidance.ts
572
+ function buildToolGuidance(modeId, options = {}) {
573
+ const denied = options.deniedTools ?? /* @__PURE__ */ new Set();
574
+ const sections = [];
575
+ const isAskMode = modeId === "ask";
576
+ sections.push(`# Tool Usage Rules
577
+
578
+ IMPORTANT: You can ONLY call tools by their exact registered names listed below. Shell commands like \`git\`, \`npm\`, \`ls\`, etc. are NOT tools \u2014 they must be run via the \`execute_command\` tool.
579
+
580
+ You have access to the following tools. Use the RIGHT tool for the job:`);
581
+ const readTools = [];
582
+ if (!denied.has(MC_TOOLS.VIEW)) {
583
+ readTools.push(`
584
+ **${MC_TOOLS.VIEW}** \u2014 Read file contents
585
+ - Use this to read files before editing them. NEVER propose changes to code you haven't read.
586
+ - Use \`offset\` (1-indexed start line) and \`limit\` (number of lines) for large files.
587
+ - Example: Read lines 50-100: \`{ path: "src/big-file.ts", offset: 50, limit: 51 }\`
588
+ - To list directories, use \`${MC_TOOLS.FIND_FILES}\` instead.`);
589
+ }
590
+ if (!denied.has(MC_TOOLS.SEARCH_CONTENT)) {
591
+ readTools.push(`
592
+ **${MC_TOOLS.SEARCH_CONTENT}** \u2014 Search file contents using regex
593
+ - Preferred for content search (finding functions, variables, error messages, imports, etc.)
594
+ - Use \`path\` to filter by directory or glob pattern. Supports \`contextLines\`, \`caseSensitive\`, and \`maxCount\`.
595
+ - Example: Find a function: \`{ pattern: "function handleSubmit", path: "**/*.ts" }\`
596
+ - Example: Find imports: \`{ pattern: "from ['\\"\\]express['\\"\\]", path: "**/*.ts" }\`
597
+ - Respects .gitignore by default.`);
598
+ }
599
+ if (!denied.has(MC_TOOLS.FIND_FILES)) {
600
+ readTools.push(`
601
+ **${MC_TOOLS.FIND_FILES}** \u2014 List files and directories as a tree
602
+ - Preferred for exploring project structure and finding files by pattern.
603
+ - Returns tree-style output. Respects .gitignore by default.
604
+ - Example: List project root: \`{ path: "./" }\`
605
+ - Example: Find test files: \`{ path: "./src", pattern: "**/*.test.ts" }\`
606
+ - Example: Find config files: \`{ pattern: "*.config.{js,ts,json}" }\``);
607
+ }
608
+ if (!isAskMode && !denied.has(MC_TOOLS.EXECUTE_COMMAND)) {
609
+ readTools.push(`
610
+ **${MC_TOOLS.EXECUTE_COMMAND}** \u2014 Run shell commands
611
+ - Use for: git, npm/pnpm, docker, build tools, test runners, and other terminal operations.
612
+ - Prefer dedicated tools for: file reading (${MC_TOOLS.VIEW}), file search (${MC_TOOLS.SEARCH_CONTENT}/${MC_TOOLS.FIND_FILES}), file editing (${MC_TOOLS.STRING_REPLACE_LSP}/${MC_TOOLS.WRITE_FILE}).
613
+ - Commands have a 30-second default timeout. Use \`timeout\` for longer commands, \`cwd\` for working directory.
614
+ - Use the \`tail\` parameter or pipe to \`| tail -N\` to limit output \u2014 the full output streams to the user, only the tail is returned to you. If you're building any kind of package you should be tailing.
615
+ - Good: Run independent commands in parallel when possible.
616
+ - Bad: Running \`cat file.txt\` \u2014 use the ${MC_TOOLS.VIEW} tool instead.`);
617
+ }
618
+ if (readTools.length > 0) {
619
+ sections.push(readTools.join("\n"));
620
+ }
621
+ if (modeId !== "plan" && modeId !== "ask") {
622
+ const writeTools = [];
623
+ if (!denied.has(MC_TOOLS.STRING_REPLACE_LSP)) {
624
+ writeTools.push(`
625
+ **${MC_TOOLS.STRING_REPLACE_LSP}** \u2014 Edit files by replacing exact text
626
+ - You MUST read a file with \`${MC_TOOLS.VIEW}\` before editing it.
627
+ - \`old_string\` must be an exact match of existing text in the file.
628
+ - Provide enough surrounding context in \`old_string\` to make it unique.
629
+ - Use \`replace_all: true\` to replace all occurrences (default: false, requires unique match).
630
+ - For creating new files, use \`${MC_TOOLS.WRITE_FILE}\` instead.
631
+ - Good: Include 2-3 lines of surrounding context to ensure uniqueness.
632
+ - Bad: Using just \`return true;\` \u2014 too common, will match multiple places.`);
633
+ }
634
+ if (!denied.has(MC_TOOLS.WRITE_FILE)) {
635
+ writeTools.push(`
636
+ **${MC_TOOLS.WRITE_FILE}** \u2014 Create new files or overwrite existing ones
637
+ - Use this to create new files.
638
+ - If overwriting an existing file, you MUST have read it first with \`${MC_TOOLS.VIEW}\`.
639
+ - Prefer editing existing files over creating new ones.`);
640
+ }
641
+ if (writeTools.length > 0) {
642
+ sections.push(writeTools.join("\n"));
643
+ }
644
+ }
645
+ if (options.hasWebSearch) {
646
+ const webTools = [];
647
+ if (!denied.has("web_search")) webTools.push("**web_search**");
648
+ if (!denied.has("web_extract")) webTools.push("**web_extract**");
649
+ if (webTools.length > 0) {
650
+ sections.push(`
651
+ ${webTools.join(" / ")} \u2014 Search the web / extract page content
652
+ - Use for looking up documentation, error messages, package APIs.`);
653
+ }
654
+ }
655
+ const taskTools = [];
656
+ if (!isAskMode && !denied.has("task_write")) {
657
+ taskTools.push(`
658
+ **task_write** \u2014 Track tasks for complex multi-step work
659
+ - Use when a task requires 3 or more distinct steps or actions.
660
+ - Pass the FULL task list each time (replaces previous list).
661
+ - Mark tasks \`in_progress\` BEFORE starting work. Only ONE task should be \`in_progress\` at a time.
662
+ - Mark tasks \`completed\` IMMEDIATELY after finishing each task. Do not batch completions.
663
+ - Each task has: content (imperative form), status (pending|in_progress|completed), activeForm (present continuous form shown during execution).`);
664
+ }
665
+ if (!isAskMode && !denied.has("task_check")) {
666
+ taskTools.push(`
667
+ **task_check** \u2014 Check completion status of tasks
668
+ - Use this BEFORE deciding you're done with a task to verify all tasks are completed.
669
+ - Returns the number of completed, in progress, and pending tasks.
670
+ - If any tasks remain incomplete, continue working on them.
671
+ - IMPORTANT: Always check task completion before ending work on a complex task.`);
672
+ }
673
+ if (!denied.has("ask_user")) {
674
+ taskTools.push(`
675
+ **ask_user** \u2014 Ask the user a structured question
676
+ - Use when you need clarification, want to validate assumptions, or need the user to make a decision.
677
+ - Provide clear, specific questions. End with a question mark.
678
+ - Include options (2-4 choices) for structured decisions. Omit options for open-ended questions.
679
+ - Don't use this for simple yes/no \u2014 just ask in your text response.`);
680
+ }
681
+ if (taskTools.length > 0) {
682
+ sections.push(taskTools.join("\n"));
683
+ }
684
+ if (modeId === "plan" && !denied.has("submit_plan")) {
685
+ sections.push(`
686
+ **submit_plan** \u2014 Submit a completed implementation plan for user review
687
+ - Call this tool when your plan is complete. Do NOT just describe your plan in text \u2014 you MUST call this tool.
688
+ - The plan will be rendered as markdown and the user can approve, reject, or request changes.
689
+ - On approval, the system automatically switches to the default mode so you can implement.
690
+ - Takes two arguments: \`title\` (short descriptive title) and \`plan\` (full plan in markdown).`);
691
+ }
692
+ if (!isAskMode && !denied.has("subagent")) {
693
+ sections.push(`
694
+ **subagent** \u2014 Delegate a focused task to a specialized subagent
695
+ - Only use subagents when you will spawn **multiple subagents in parallel**. If you only need one task done, do it yourself.
696
+ - Subagent outputs are **untrusted**. Always review and verify the results.`);
697
+ }
698
+ return sections.join("\n");
699
+ }
700
+
701
+ // src/agents/prompts/index.ts
702
+ function buildModePromptFromDefinition(ctx, promptBody) {
703
+ if (ctx.modeId === "build" && ctx.activePlan) {
704
+ return `# Approved Plan
705
+
706
+ **${ctx.activePlan.title}**
707
+
708
+ ${ctx.activePlan.plan}
709
+
710
+ ---
711
+
712
+ Implement the approved plan above. Follow the steps in order and verify each step works before moving on.
713
+
714
+ ${promptBody}`;
715
+ }
716
+ return promptBody;
717
+ }
718
+ function buildFullPrompt(ctx) {
719
+ const modelId = ctx.state?.currentModelId;
720
+ const hasWebSearch = hasTavilyKey() || !!modelId && modelId.startsWith("anthropic/");
721
+ const deniedTools = /* @__PURE__ */ new Set();
722
+ const permRules = ctx.state?.permissionRules;
723
+ if (permRules?.tools) {
724
+ for (const [name, policy] of Object.entries(permRules.tools)) {
725
+ if (policy === "deny") deniedTools.add(name);
726
+ }
727
+ }
728
+ const toolGuidance = buildToolGuidance(ctx.modeId, { hasWebSearch, deniedTools });
729
+ const baseCtx = {
730
+ projectPath: ctx.workingDir,
731
+ projectName: ctx.projectName || "unknown",
732
+ gitBranch: ctx.gitBranch,
733
+ platform: process.platform,
734
+ date: ctx.currentDate,
735
+ mode: ctx.modeId,
736
+ modelId: ctx.modelId,
737
+ toolGuidance
738
+ };
739
+ const base = buildBasePrompt(baseCtx);
740
+ const modeDefinition = getModeDefinition(ctx.workingDir, ctx.modeId) ?? getModeDefinition(ctx.workingDir, "build");
741
+ const modeSpecific = buildModePromptFromDefinition(ctx, modeDefinition?.instructions ?? "");
742
+ let taskSection = "";
743
+ const tasks = ctx.state?.tasks;
744
+ if (tasks && tasks.length > 0) {
745
+ const lines = tasks.map((t) => {
746
+ const icon = t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\u25CB";
747
+ return ` ${icon} [${t.status}] ${t.content}`;
748
+ });
749
+ taskSection = `
750
+ <current-task-list>
751
+ ${lines.join("\n")}
752
+ </current-task-list>
753
+ `;
754
+ }
755
+ const instructionSources = loadAgentInstructionsCached(ctx.workingDir);
756
+ const instructionsSection = formatAgentInstructions(instructionSources);
757
+ return base + taskSection + instructionsSection + "\n" + modeSpecific;
758
+ }
759
+
760
+ // src/agents/instructions.ts
761
+ var promptBuildCount = 0;
762
+ function getDynamicInstructions({ requestContext }) {
763
+ const startTime = performance.now();
764
+ const harnessContext = requestContext.get("harness");
765
+ const state = harnessContext?.state;
766
+ const modeId = harnessContext?.modeId ?? "build";
767
+ const projectPath = state?.projectPath ?? process.cwd();
768
+ const promptCtx = {
769
+ projectName: state?.projectName ?? "",
770
+ gitBranch: getCurrentGitBranch(projectPath) ?? state?.gitBranch,
771
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
772
+ modelId: state?.currentModelId || void 0,
773
+ activePlan: state?.activePlan ?? null,
774
+ modeId,
775
+ currentDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
776
+ workingDir: state?.projectPath ?? process.cwd(),
777
+ state
778
+ };
779
+ const prompt = buildFullPrompt(promptCtx);
780
+ promptBuildCount += 1;
781
+ const durationMs = performance.now() - startTime;
782
+ if (promptBuildCount === 1 || durationMs >= 20) {
783
+ logRuntimeProfile("prompt.build", durationMs, {
784
+ count: promptBuildCount,
785
+ modeId,
786
+ projectPath
787
+ });
788
+ }
789
+ return prompt;
790
+ }
791
+
792
+ // src/constants.ts
793
+ var DEFAULT_OM_MODEL_ID = process.env.DEFAULT_OM_MODEL_ID ?? "google/gemini-2.5-flash";
794
+ var DEFAULT_OBS_THRESHOLD = 3e4;
795
+ var DEFAULT_REF_THRESHOLD = 4e4;
796
+ var claudeCodeIdentity = "You are Claude Code, Anthropic's official CLI for Claude.";
797
+ var authStorageInstance = null;
798
+ function getAuthStorage() {
799
+ if (!authStorageInstance) {
800
+ authStorageInstance = new AuthStorage();
801
+ }
802
+ return authStorageInstance;
803
+ }
804
+ function setAuthStorage(storage) {
805
+ authStorageInstance = storage ?? null;
806
+ }
807
+ var claudeCodeMiddleware = {
808
+ specificationVersion: "v3",
809
+ transformParams: async ({ params }) => {
810
+ const systemMessage = {
811
+ role: "system",
812
+ content: claudeCodeIdentity
813
+ };
814
+ if (params.temperature) {
815
+ delete params.topP;
816
+ }
817
+ return {
818
+ ...params,
819
+ prompt: [systemMessage, ...params.prompt]
820
+ };
821
+ }
822
+ };
823
+ var promptCacheMiddleware = {
824
+ specificationVersion: "v3",
825
+ transformParams: async ({ params }) => {
826
+ const prompt = [...params.prompt];
827
+ const cacheControl = { type: "ephemeral", ttl: "5m" };
828
+ const addCacheToMessage = (msg) => {
829
+ if (typeof msg.content === "string") {
830
+ return {
831
+ ...msg,
832
+ providerOptions: {
833
+ ...msg.providerOptions,
834
+ anthropic: { ...msg.providerOptions?.anthropic, cacheControl }
835
+ }
836
+ };
837
+ }
838
+ if (Array.isArray(msg.content) && msg.content.length > 0) {
839
+ const content = [...msg.content];
840
+ const lastPart = content[content.length - 1];
841
+ content[content.length - 1] = {
842
+ ...lastPart,
843
+ providerOptions: {
844
+ ...lastPart.providerOptions,
845
+ anthropic: { ...lastPart.providerOptions?.anthropic, cacheControl }
846
+ }
847
+ };
848
+ return { ...msg, content };
849
+ }
850
+ return msg;
851
+ };
852
+ let lastSystemIdx = -1;
853
+ for (let i = prompt.length - 1; i >= 0; i--) {
854
+ if (prompt[i].role === "system") {
855
+ lastSystemIdx = i;
856
+ break;
857
+ }
858
+ }
859
+ if (lastSystemIdx >= 0) {
860
+ prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);
861
+ }
862
+ const lastIdx = prompt.length - 1;
863
+ if (lastIdx >= 0 && lastIdx !== lastSystemIdx) {
864
+ prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);
865
+ }
866
+ return { ...params, prompt };
867
+ }
868
+ };
869
+ function opencodeClaudeMaxProvider(modelId = "claude-sonnet-4-20250514") {
870
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
871
+ const anthropic2 = createAnthropic({
872
+ apiKey: "test-api-key"
873
+ });
874
+ return wrapLanguageModel({
875
+ model: anthropic2(modelId),
876
+ middleware: [claudeCodeMiddleware, promptCacheMiddleware]
877
+ });
878
+ }
879
+ const oauthFetch = async (url, init) => {
880
+ const authStorage2 = getAuthStorage();
881
+ authStorage2.reload();
882
+ const storedCred = authStorage2.get("anthropic");
883
+ if (storedCred?.type === "api_key") {
884
+ throw new Error(
885
+ "Anthropic API key credential is configured, but Claude Max OAuth provider requires OAuth credentials."
886
+ );
887
+ }
888
+ const accessToken = await authStorage2.getApiKey("anthropic");
889
+ if (!accessToken) {
890
+ throw new Error("Not logged in to Anthropic. Run /login first.");
891
+ }
892
+ return fetch(url, {
893
+ ...init,
894
+ headers: {
895
+ Authorization: `Bearer ${accessToken}`,
896
+ "anthropic-beta": "oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
897
+ "anthropic-version": "2023-06-01"
898
+ }
899
+ });
900
+ };
901
+ const anthropic = createAnthropic({
902
+ // Provide a dummy API key - the actual auth is handled via OAuth in oauthFetch
903
+ // This prevents the SDK from throwing "API key is missing" at model creation time
904
+ apiKey: "oauth-placeholder",
905
+ fetch: oauthFetch
906
+ });
907
+ return wrapLanguageModel({
908
+ model: anthropic(modelId),
909
+ middleware: [claudeCodeMiddleware, promptCacheMiddleware]
910
+ });
911
+ }
912
+ var CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
913
+ var authStorageInstance2 = null;
914
+ function getAuthStorage2() {
915
+ if (!authStorageInstance2) {
916
+ authStorageInstance2 = new AuthStorage();
917
+ }
918
+ return authStorageInstance2;
919
+ }
920
+ function setAuthStorage2(storage) {
921
+ authStorageInstance2 = storage ?? null;
922
+ }
923
+ var CODEX_INSTRUCTIONS = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
924
+
925
+ IMPORTANT: You should be concise, direct, and helpful. Focus on solving the user's problem efficiently.`;
926
+ var GPT5_MODEL_RE = /^gpt-5(?:\.|-|$)/;
927
+ function getEffectiveThinkingLevel(modelId, level) {
928
+ if (GPT5_MODEL_RE.test(modelId) && level === "off") {
929
+ return "low";
930
+ }
931
+ return level;
932
+ }
933
+ var THINKING_LEVEL_TO_REASONING_EFFORT = {
934
+ off: void 0,
935
+ low: "low",
936
+ medium: "medium",
937
+ high: "high",
938
+ xhigh: "xhigh"
939
+ };
940
+ function createCodexMiddleware(reasoningEffort) {
941
+ return {
942
+ specificationVersion: "v3",
943
+ transformParams: async ({ params }) => {
944
+ if (params.temperature !== void 0 && params.temperature !== null) {
945
+ delete params.topP;
946
+ }
947
+ params.providerOptions = {
948
+ ...params.providerOptions,
949
+ openai: {
950
+ ...params.providerOptions?.openai ?? {},
951
+ instructions: CODEX_INSTRUCTIONS,
952
+ // Codex API requires store to be false
953
+ store: false,
954
+ // Enable reasoning for Codex models — without this, the model
955
+ // skips the reasoning/action phase and goes straight to final_answer,
956
+ // resulting in narration instead of tool calls.
957
+ ...reasoningEffort ? { reasoningEffort } : {}
958
+ }
959
+ };
960
+ return params;
961
+ }
962
+ };
963
+ }
964
+ function openaiCodexProvider(modelId = "codex-mini-latest", options) {
965
+ const requestedLevel = options?.thinkingLevel ?? "medium";
966
+ const effectiveLevel = getEffectiveThinkingLevel(modelId, requestedLevel);
967
+ const reasoningEffort = THINKING_LEVEL_TO_REASONING_EFFORT[effectiveLevel];
968
+ const middleware = createCodexMiddleware(reasoningEffort);
969
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
970
+ const openai2 = createOpenAI({
971
+ apiKey: "test-api-key"
972
+ });
973
+ return wrapLanguageModel({
974
+ model: openai2.responses(modelId),
975
+ middleware: [middleware]
976
+ });
977
+ }
978
+ const oauthFetch = async (url, init) => {
979
+ const authStorage2 = getAuthStorage2();
980
+ authStorage2.reload();
981
+ const cred = authStorage2.get("openai-codex");
982
+ if (!cred || cred.type !== "oauth") {
983
+ throw new Error("Not logged in to OpenAI Codex. Run /login first.");
984
+ }
985
+ let accessToken = cred.access;
986
+ if (Date.now() >= cred.expires) {
987
+ const refreshedToken = await authStorage2.getApiKey("openai-codex");
988
+ if (!refreshedToken) {
989
+ throw new Error("Failed to refresh OpenAI Codex token. Please /login again.");
990
+ }
991
+ accessToken = refreshedToken;
992
+ authStorage2.reload();
993
+ }
994
+ const accountId = cred.accountId;
995
+ const headers = new Headers();
996
+ if (init?.headers) {
997
+ if (init.headers instanceof Headers) {
998
+ init.headers.forEach((value, key) => {
999
+ if (key.toLowerCase() !== "authorization") {
1000
+ headers.set(key, value);
1001
+ }
1002
+ });
1003
+ } else if (Array.isArray(init.headers)) {
1004
+ for (const [key, value] of init.headers) {
1005
+ if (key.toLowerCase() !== "authorization" && value !== void 0) {
1006
+ headers.set(key, String(value));
1007
+ }
1008
+ }
1009
+ } else {
1010
+ for (const [key, value] of Object.entries(init.headers)) {
1011
+ if (key.toLowerCase() !== "authorization" && value !== void 0) {
1012
+ headers.set(key, String(value));
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ headers.set("Authorization", `Bearer ${accessToken}`);
1018
+ if (accountId) {
1019
+ headers.set("ChatGPT-Account-Id", accountId);
1020
+ }
1021
+ const parsed = url instanceof URL ? url : new URL(typeof url === "string" ? url : url.url);
1022
+ const shouldRewrite = parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions");
1023
+ const finalUrl = shouldRewrite ? new URL(CODEX_API_ENDPOINT) : parsed;
1024
+ return fetch(finalUrl, {
1025
+ ...init,
1026
+ headers
1027
+ });
1028
+ };
1029
+ const openai = createOpenAI({
1030
+ // Use a dummy API key since we're using OAuth
1031
+ apiKey: "oauth-dummy-key",
1032
+ fetch: oauthFetch
1033
+ });
1034
+ return wrapLanguageModel({
1035
+ model: openai.responses(modelId),
1036
+ middleware: [middleware]
1037
+ });
1038
+ }
1039
+
1040
+ // src/agents/model.ts
1041
+ var authStorage = new AuthStorage();
1042
+ var OPENAI_PREFIX = "openai/";
1043
+ var GOOGLE_PREFIX = "google/";
1044
+ var CODEX_OPENAI_MODEL_REMAPS = {
1045
+ "gpt-5.3": "gpt-5.3-codex",
1046
+ "gpt-5.2": "gpt-5.2-codex",
1047
+ "gpt-5.1": "gpt-5.1-codex",
1048
+ "gpt-5.1-mini": "gpt-5.1-codex-mini",
1049
+ "gpt-5": "gpt-5-codex"
1050
+ };
1051
+ function remapOpenAIModelForCodexOAuth(modelId) {
1052
+ if (!modelId.startsWith(OPENAI_PREFIX)) {
1053
+ return modelId;
1054
+ }
1055
+ const openaiModelId = modelId.substring(OPENAI_PREFIX.length);
1056
+ if (openaiModelId.includes("-codex")) {
1057
+ return modelId;
1058
+ }
1059
+ const codexModelId = CODEX_OPENAI_MODEL_REMAPS[openaiModelId];
1060
+ if (!codexModelId) {
1061
+ return modelId;
1062
+ }
1063
+ return `${OPENAI_PREFIX}${codexModelId}`;
1064
+ }
1065
+ function getAnthropicApiKey() {
1066
+ const storedCred = authStorage.get("anthropic");
1067
+ if (storedCred?.type === "api_key" && storedCred.key.trim().length > 0) {
1068
+ return storedCred.key.trim();
1069
+ }
1070
+ return void 0;
1071
+ }
1072
+ function getOpenAIApiKey() {
1073
+ const storedCred = authStorage.get("openai-codex");
1074
+ if (storedCred?.type === "api_key" && storedCred.key.trim().length > 0) {
1075
+ return storedCred.key.trim();
1076
+ }
1077
+ return void 0;
1078
+ }
1079
+ function getGoogleApiKey() {
1080
+ const storedCred = authStorage.get("google");
1081
+ if (storedCred?.type === "api_key" && storedCred.key.trim().length > 0) {
1082
+ return storedCred.key.trim();
1083
+ }
1084
+ return void 0;
1085
+ }
1086
+ function anthropicApiKeyProvider(modelId, apiKey) {
1087
+ const anthropic = createAnthropic({ apiKey });
1088
+ return wrapLanguageModel({
1089
+ model: anthropic(modelId),
1090
+ middleware: [promptCacheMiddleware]
1091
+ });
1092
+ }
1093
+ function openaiApiKeyProvider(modelId, apiKey) {
1094
+ const openai = createOpenAI({ apiKey });
1095
+ return wrapLanguageModel({
1096
+ model: openai.responses(modelId),
1097
+ middleware: []
1098
+ });
1099
+ }
1100
+ function resolveModel(modelId, options) {
1101
+ authStorage.reload();
1102
+ const [providerId, modelName] = modelId.split("/", 2);
1103
+ const settings = loadSettingsCached();
1104
+ const customProvider = providerId && modelName ? settings.customProviders.find((provider) => {
1105
+ return providerId === getCustomProviderId(provider.name);
1106
+ }) : void 0;
1107
+ if (customProvider) {
1108
+ return new ModelRouterLanguageModel({
1109
+ id: modelId,
1110
+ url: customProvider.url,
1111
+ apiKey: customProvider.apiKey
1112
+ });
1113
+ }
1114
+ const isAnthropicModel = modelId.startsWith("anthropic/");
1115
+ const isOpenAIModel = modelId.startsWith(OPENAI_PREFIX);
1116
+ const isGoogleModel = modelId.startsWith(GOOGLE_PREFIX);
1117
+ const isMoonshotModel = modelId.startsWith("moonshotai/");
1118
+ if (isMoonshotModel) {
1119
+ if (!process.env.MOONSHOT_AI_API_KEY) {
1120
+ throw new Error(`Need MOONSHOT_AI_API_KEY`);
1121
+ }
1122
+ return createAnthropic({
1123
+ apiKey: process.env.MOONSHOT_AI_API_KEY,
1124
+ baseURL: "https://api.moonshot.ai/anthropic/v1",
1125
+ name: "moonshotai.anthropicv1"
1126
+ })(modelId.substring("moonshotai/".length));
1127
+ } else if (isAnthropicModel) {
1128
+ const bareModelId = modelId.substring("anthropic/".length);
1129
+ const storedCred = authStorage.get("anthropic");
1130
+ if (storedCred?.type === "oauth") {
1131
+ return opencodeClaudeMaxProvider(bareModelId);
1132
+ }
1133
+ if (storedCred?.type === "api_key" && storedCred.key.trim().length > 0) {
1134
+ return anthropicApiKeyProvider(bareModelId, storedCred.key.trim());
1135
+ }
1136
+ const apiKey = getAnthropicApiKey();
1137
+ if (apiKey) {
1138
+ return anthropicApiKeyProvider(bareModelId, apiKey);
1139
+ }
1140
+ return opencodeClaudeMaxProvider(bareModelId);
1141
+ } else if (isOpenAIModel) {
1142
+ const bareModelId = modelId.substring(OPENAI_PREFIX.length);
1143
+ const storedCred = authStorage.get("openai-codex");
1144
+ if (storedCred?.type === "oauth") {
1145
+ const resolvedModelId = options?.remapForCodexOAuth ? remapOpenAIModelForCodexOAuth(modelId) : modelId;
1146
+ return openaiCodexProvider(resolvedModelId.substring(OPENAI_PREFIX.length), {
1147
+ thinkingLevel: options?.thinkingLevel
1148
+ });
1149
+ }
1150
+ const apiKey = getOpenAIApiKey();
1151
+ if (apiKey) {
1152
+ return openaiApiKeyProvider(bareModelId, apiKey);
1153
+ }
1154
+ return new ModelRouterLanguageModel(modelId);
1155
+ } else if (isGoogleModel) {
1156
+ const bareModelId = modelId.substring(GOOGLE_PREFIX.length);
1157
+ const apiKey = getGoogleApiKey();
1158
+ if (apiKey) {
1159
+ return new ModelRouterLanguageModel({
1160
+ providerId: "google",
1161
+ modelId: bareModelId,
1162
+ apiKey
1163
+ });
1164
+ }
1165
+ return new ModelRouterLanguageModel(modelId);
1166
+ } else {
1167
+ return new ModelRouterLanguageModel(modelId);
1168
+ }
1169
+ }
1170
+ function getDynamicModel({ requestContext }) {
1171
+ const harnessContext = requestContext.get("harness");
1172
+ const modelId = harnessContext?.state?.currentModelId;
1173
+ if (!modelId) {
1174
+ throw new Error("No model selected. Use /models to select a model first.");
1175
+ }
1176
+ const thinkingLevel = harnessContext?.state?.thinkingLevel;
1177
+ return resolveModel(modelId, { thinkingLevel });
1178
+ }
1179
+
1180
+ // src/agents/memory.ts
1181
+ var cachedMemory = null;
1182
+ var cachedMemoryKey = null;
1183
+ function getHarnessState(requestContext) {
1184
+ return requestContext.get("harness")?.getState?.();
1185
+ }
1186
+ function getObserverModel({ requestContext }) {
1187
+ const state = getHarnessState(requestContext);
1188
+ return resolveModel(state?.observerModelId ?? DEFAULT_OM_MODEL_ID, { remapForCodexOAuth: true });
1189
+ }
1190
+ function getReflectorModel({ requestContext }) {
1191
+ const state = getHarnessState(requestContext);
1192
+ return resolveModel(state?.reflectorModelId ?? DEFAULT_OM_MODEL_ID, { remapForCodexOAuth: true });
1193
+ }
1194
+ function getDynamicMemory(storage) {
1195
+ return ({ requestContext }) => {
1196
+ const state = getHarnessState(requestContext);
1197
+ const omScope = state?.omScope ?? getOmScope(state?.projectPath);
1198
+ const obsThreshold = state?.observationThreshold ?? DEFAULT_OBS_THRESHOLD;
1199
+ const refThreshold = state?.reflectionThreshold ?? DEFAULT_REF_THRESHOLD;
1200
+ const observerPreviousObservationTokens = 1e3;
1201
+ const cacheKey = `${obsThreshold}:${refThreshold}:${omScope}:${observerPreviousObservationTokens}`;
1202
+ if (cachedMemory && cachedMemoryKey === cacheKey) {
1203
+ return cachedMemory;
1204
+ }
1205
+ const isResourceScope = omScope === "resource";
1206
+ cachedMemory = new Memory({
1207
+ storage,
1208
+ options: {
1209
+ observationalMemory: {
1210
+ enabled: true,
1211
+ scope: omScope,
1212
+ observation: {
1213
+ bufferTokens: isResourceScope ? false : 1 / 5,
1214
+ bufferActivation: isResourceScope ? void 0 : 2e3,
1215
+ model: getObserverModel,
1216
+ messageTokens: obsThreshold,
1217
+ blockAfter: 2,
1218
+ previousObserverTokens: observerPreviousObservationTokens
1219
+ },
1220
+ reflection: {
1221
+ bufferActivation: isResourceScope ? void 0 : 1 / 2,
1222
+ blockAfter: 1.1,
1223
+ model: getReflectorModel,
1224
+ observationTokens: refThreshold
1225
+ }
1226
+ }
1227
+ }
1228
+ });
1229
+ cachedMemoryKey = cacheKey;
1230
+ return cachedMemory;
1231
+ };
1232
+ }
1233
+ function buildSubagentMeta(modelId, durationMs, toolCalls) {
1234
+ const tools = toolCalls.map((toolCall) => `${toolCall.name}:${toolCall.isError ? "err" : "ok"}`).join(",");
1235
+ return `
1236
+ <subagent-meta modelId="${modelId}" durationMs="${durationMs}" tools="${tools}" />`;
1237
+ }
1238
+ function createScopedSubagentTool(opts) {
1239
+ const { subagents, resolveModel: resolveModel2, harnessTools, fallbackModelId } = opts;
1240
+ const subagentIds = subagents.map((subagent) => subagent.id);
1241
+ const typeDescriptions = subagents.map((subagent) => `- **${subagent.id}** (${subagent.name}): ${subagent.description}`).join("\n");
1242
+ return createTool({
1243
+ id: "subagent",
1244
+ description: `Delegate a focused task to a specialized subagent. The subagent runs independently with a constrained toolset, then returns its findings as text.
1245
+
1246
+ Available agent types:
1247
+ ${typeDescriptions}
1248
+
1249
+ The subagent runs in its own context - it does NOT see the parent conversation history. Write a clear, self-contained task description.
1250
+
1251
+ Use this tool when:
1252
+ - You want to run multiple investigations in parallel
1253
+ - The task is self-contained and can be delegated`,
1254
+ inputSchema: z.object({
1255
+ agentType: z.enum(subagentIds).describe("Type of subagent to spawn"),
1256
+ task: z.string().describe(
1257
+ "Clear, self-contained description of what the subagent should do. Include all relevant context - the subagent cannot see the parent conversation."
1258
+ ),
1259
+ modelId: z.string().optional().describe("Optional model ID override for this task.")
1260
+ }),
1261
+ execute: async ({ agentType, task, modelId }, context) => {
1262
+ const definition = subagents.find((subagent2) => subagent2.id === agentType);
1263
+ if (!definition) {
1264
+ return {
1265
+ content: `Unknown agent type: ${agentType}. Valid types: ${subagentIds.join(", ")}`,
1266
+ isError: true
1267
+ };
1268
+ }
1269
+ const harnessCtx = context?.requestContext?.get("harness");
1270
+ const emitEvent = harnessCtx?.emitEvent;
1271
+ const abortSignal = harnessCtx?.abortSignal;
1272
+ const toolCallId = context?.agent?.toolCallId ?? "unknown";
1273
+ const mergedTools = { ...definition.tools ?? {} };
1274
+ if (definition.allowedHarnessTools && harnessTools) {
1275
+ for (const toolId of definition.allowedHarnessTools) {
1276
+ if (harnessTools[toolId] && !mergedTools[toolId]) {
1277
+ mergedTools[toolId] = harnessTools[toolId];
1278
+ }
1279
+ }
1280
+ }
1281
+ const harnessModelId = harnessCtx?.getSubagentModelId?.({ agentType }) ?? void 0;
1282
+ const resolvedModelId = modelId ?? harnessModelId ?? definition.defaultModelId ?? fallbackModelId;
1283
+ if (!resolvedModelId) {
1284
+ return { content: "No model ID available for subagent. Configure defaultModelId.", isError: true };
1285
+ }
1286
+ let model;
1287
+ try {
1288
+ model = resolveModel2(resolvedModelId);
1289
+ } catch (error) {
1290
+ return {
1291
+ content: `Failed to resolve model "${resolvedModelId}": ${error instanceof Error ? error.message : String(error)}`,
1292
+ isError: true
1293
+ };
1294
+ }
1295
+ const workspace = context?.workspace;
1296
+ const subagent = new Agent({
1297
+ id: `subagent-${definition.id}`,
1298
+ name: `${definition.name} Subagent`,
1299
+ instructions: definition.instructions,
1300
+ model,
1301
+ tools: mergedTools,
1302
+ workspace
1303
+ });
1304
+ const allWorkspaceToolNames = workspace ? new Set(Object.keys(createWorkspaceTools(workspace))) : void 0;
1305
+ const allowedWorkspaceTools = definition.allowedWorkspaceTools ? new Set(definition.allowedWorkspaceTools) : void 0;
1306
+ const startTime = Date.now();
1307
+ emitEvent?.({
1308
+ type: "subagent_start",
1309
+ toolCallId,
1310
+ agentType,
1311
+ task,
1312
+ modelId: resolvedModelId
1313
+ });
1314
+ let partialText = "";
1315
+ const toolCallLog = [];
1316
+ try {
1317
+ const response = await subagent.stream(task, {
1318
+ maxSteps: definition.maxSteps ?? (definition.stopWhen ? void 0 : 50),
1319
+ stopWhen: definition.stopWhen,
1320
+ abortSignal,
1321
+ requireToolApproval: false,
1322
+ requestContext: context?.requestContext,
1323
+ prepareStep: allowedWorkspaceTools && allWorkspaceToolNames ? ({ tools }) => ({
1324
+ activeTools: Object.keys(tools ?? {}).filter((toolName) => {
1325
+ return !allWorkspaceToolNames.has(toolName) || allowedWorkspaceTools.has(toolName);
1326
+ })
1327
+ }) : void 0
1328
+ });
1329
+ for await (const chunk of response.fullStream) {
1330
+ switch (chunk.type) {
1331
+ case "text-delta":
1332
+ partialText += chunk.payload.text;
1333
+ emitEvent?.({
1334
+ type: "subagent_text_delta",
1335
+ toolCallId,
1336
+ agentType,
1337
+ textDelta: chunk.payload.text
1338
+ });
1339
+ break;
1340
+ case "tool-call":
1341
+ toolCallLog.push({ name: chunk.payload.toolName, toolCallId: chunk.payload.toolCallId });
1342
+ emitEvent?.({
1343
+ type: "subagent_tool_start",
1344
+ toolCallId,
1345
+ agentType,
1346
+ subToolName: chunk.payload.toolName,
1347
+ subToolArgs: chunk.payload.args
1348
+ });
1349
+ break;
1350
+ case "tool-result": {
1351
+ const isError = chunk.payload.isError ?? false;
1352
+ for (let index = toolCallLog.length - 1; index >= 0; index -= 1) {
1353
+ if (toolCallLog[index].toolCallId === chunk.payload.toolCallId && toolCallLog[index].isError === void 0) {
1354
+ toolCallLog[index].isError = isError;
1355
+ break;
1356
+ }
1357
+ }
1358
+ emitEvent?.({
1359
+ type: "subagent_tool_end",
1360
+ toolCallId,
1361
+ agentType,
1362
+ subToolName: chunk.payload.toolName,
1363
+ subToolResult: chunk.payload.result,
1364
+ isError
1365
+ });
1366
+ break;
1367
+ }
1368
+ }
1369
+ }
1370
+ if (abortSignal?.aborted) {
1371
+ const durationMs2 = Date.now() - startTime;
1372
+ const abortResult = partialText ? `[Aborted by user]
1373
+
1374
+ Partial output:
1375
+ ${partialText}` : "[Aborted by user]";
1376
+ emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: abortResult, isError: false, durationMs: durationMs2 });
1377
+ return { content: abortResult + buildSubagentMeta(resolvedModelId, durationMs2, toolCallLog), isError: false };
1378
+ }
1379
+ const fullOutput = await response.getFullOutput();
1380
+ const resultText = fullOutput.text || partialText;
1381
+ const durationMs = Date.now() - startTime;
1382
+ emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: resultText, isError: false, durationMs });
1383
+ return { content: resultText + buildSubagentMeta(resolvedModelId, durationMs, toolCallLog), isError: false };
1384
+ } catch (error) {
1385
+ const isAbort = error instanceof Error && (error.name === "AbortError" || error.message.includes("abort") || error.message.includes("cancel"));
1386
+ const durationMs = Date.now() - startTime;
1387
+ if (isAbort) {
1388
+ const abortResult = partialText ? `[Aborted by user]
1389
+
1390
+ Partial output:
1391
+ ${partialText}` : "[Aborted by user]";
1392
+ emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: abortResult, isError: false, durationMs });
1393
+ return { content: abortResult + buildSubagentMeta(resolvedModelId, durationMs, toolCallLog), isError: false };
1394
+ }
1395
+ const message = error instanceof Error ? error.message : String(error);
1396
+ emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: message, isError: true, durationMs });
1397
+ return {
1398
+ content: `Subagent "${definition.name}" failed: ${message}${buildSubagentMeta(resolvedModelId, durationMs, toolCallLog)}`,
1399
+ isError: true
1400
+ };
1401
+ }
1402
+ }
1403
+ });
1404
+ }
1405
+ function applyModeScopedSubagentGuardrails(opts) {
1406
+ const originalBuildToolsets = opts.harness.buildToolsets.bind(opts.harness);
1407
+ opts.harness.buildToolsets = async (requestContext) => {
1408
+ const toolsets = await originalBuildToolsets(requestContext);
1409
+ const builtInTools = toolsets.harnessBuiltIn;
1410
+ if (!builtInTools?.subagent) {
1411
+ return toolsets;
1412
+ }
1413
+ const currentModeId = opts.harness.getCurrentModeId();
1414
+ const allowedSubagentIds = new Set(opts.getAllowedSubagentIdsForMode(currentModeId));
1415
+ if (allowedSubagentIds.size === 0) {
1416
+ delete builtInTools.subagent;
1417
+ return toolsets;
1418
+ }
1419
+ const modeScopedSubagents = opts.subagents.filter((subagent) => allowedSubagentIds.has(subagent.id));
1420
+ builtInTools.subagent = createScopedSubagentTool({
1421
+ subagents: modeScopedSubagents,
1422
+ resolveModel: opts.resolveModel,
1423
+ harnessTools: toolsets.harness,
1424
+ fallbackModelId: opts.harness.getCurrentMode().defaultModelId
1425
+ });
1426
+ return toolsets;
1427
+ };
1428
+ }
1429
+ function wrapToolWithHooks(toolName, tool, hookManager) {
1430
+ if (!hookManager || typeof tool?.execute !== "function") {
1431
+ return tool;
1432
+ }
1433
+ return {
1434
+ ...tool,
1435
+ async execute(input, toolContext) {
1436
+ const preResult = await hookManager.runPreToolUse(toolName, input);
1437
+ if (!preResult.allowed) {
1438
+ return {
1439
+ error: preResult.blockReason ?? `Blocked by PreToolUse hook for tool "${toolName}"`
1440
+ };
1441
+ }
1442
+ let output;
1443
+ let toolError = false;
1444
+ try {
1445
+ output = await tool.execute(input, toolContext);
1446
+ return output;
1447
+ } catch (error) {
1448
+ toolError = true;
1449
+ output = {
1450
+ error: error instanceof Error ? error.message : String(error)
1451
+ };
1452
+ throw error;
1453
+ } finally {
1454
+ await hookManager.runPostToolUse(toolName, input, output, toolError).catch(() => void 0);
1455
+ }
1456
+ }
1457
+ };
1458
+ }
1459
+ function createDynamicTools(mcpManager, extraTools, hookManager, disabledTools) {
1460
+ return function getDynamicTools({ requestContext }) {
1461
+ const ctx = requestContext.get("harness");
1462
+ const state = ctx?.getState?.();
1463
+ const modeId = ctx?.modeId ?? "build";
1464
+ const modelId = state?.currentModelId;
1465
+ const isAnthropicModel = modelId?.startsWith("anthropic/");
1466
+ const isOpenAIModel = modelId?.startsWith("openai/");
1467
+ const tools = {
1468
+ request_access: requestSandboxAccessTool
1469
+ };
1470
+ if (hasTavilyKey()) {
1471
+ tools.web_search = createWebSearchTool();
1472
+ tools.web_extract = createWebExtractTool();
1473
+ } else if (isAnthropicModel) {
1474
+ const anthropic = createAnthropic({});
1475
+ tools.web_search = anthropic.tools.webSearch_20250305();
1476
+ } else if (isOpenAIModel) {
1477
+ const openai = createOpenAI({});
1478
+ tools.web_search = openai.tools.webSearch();
1479
+ }
1480
+ if (mcpManager) {
1481
+ const mcpTools = mcpManager.getTools();
1482
+ Object.assign(tools, mcpTools);
1483
+ }
1484
+ if (extraTools) {
1485
+ const resolved = typeof extraTools === "function" ? extraTools({ requestContext }) : extraTools;
1486
+ for (const [name, tool] of Object.entries(resolved)) {
1487
+ if (!(name in tools)) {
1488
+ tools[name] = tool;
1489
+ }
1490
+ }
1491
+ }
1492
+ if (modeId === "ask") {
1493
+ delete tools.request_access;
1494
+ }
1495
+ if (disabledTools?.length) {
1496
+ for (const toolName of disabledTools) {
1497
+ delete tools[toolName];
1498
+ }
1499
+ }
1500
+ const permissionRules = state?.permissionRules;
1501
+ if (permissionRules?.tools) {
1502
+ for (const [name, policy] of Object.entries(permissionRules.tools)) {
1503
+ if (policy === "deny") {
1504
+ delete tools[name];
1505
+ }
1506
+ }
1507
+ }
1508
+ for (const [toolName, tool] of Object.entries(tools)) {
1509
+ tools[toolName] = wrapToolWithHooks(toolName, tool, hookManager);
1510
+ }
1511
+ return tools;
1512
+ };
1513
+ }
1514
+ var VALID_EVENTS = [
1515
+ "PreToolUse",
1516
+ "PostToolUse",
1517
+ "Stop",
1518
+ "UserPromptSubmit",
1519
+ "SessionStart",
1520
+ "SessionEnd"
1521
+ ];
1522
+ function loadHooksConfig(projectDir) {
1523
+ const globalPath = getGlobalHooksPath();
1524
+ const projectPath = getProjectHooksPath(projectDir);
1525
+ const globalConfig = loadSingleConfig(
1526
+ globalPath,
1527
+ getLegacyUliCliGlobalHooksPath(),
1528
+ getLegacyPulseGlobalHooksPath(),
1529
+ getLegacyMastraGlobalHooksPath()
1530
+ );
1531
+ const projectConfig = loadSingleConfig(
1532
+ projectPath,
1533
+ getLegacyUliCliProjectHooksPath(projectDir),
1534
+ getLegacyPulseProjectHooksPath(projectDir),
1535
+ getLegacyMastraProjectHooksPath(projectDir)
1536
+ );
1537
+ return mergeConfigs(globalConfig, projectConfig);
1538
+ }
1539
+ function getProjectHooksPath(projectDir) {
1540
+ return path.join(projectDir, ".ulicode", "hooks.json");
1541
+ }
1542
+ function getGlobalHooksPath() {
1543
+ return path.join(os.homedir(), ".ulicode", "hooks.json");
1544
+ }
1545
+ function getLegacyUliCliProjectHooksPath(projectDir) {
1546
+ return path.join(projectDir, ".uli-cli", "hooks.json");
1547
+ }
1548
+ function getLegacyUliCliGlobalHooksPath() {
1549
+ return path.join(os.homedir(), ".uli-cli", "hooks.json");
1550
+ }
1551
+ function getLegacyPulseProjectHooksPath(projectDir) {
1552
+ return path.join(projectDir, ".pulse", "hooks.json");
1553
+ }
1554
+ function getLegacyPulseGlobalHooksPath() {
1555
+ return path.join(os.homedir(), ".pulse", "hooks.json");
1556
+ }
1557
+ function getLegacyMastraProjectHooksPath(projectDir) {
1558
+ return path.join(projectDir, ".mastracode", "hooks.json");
1559
+ }
1560
+ function getLegacyMastraGlobalHooksPath() {
1561
+ return path.join(os.homedir(), ".mastracode", "hooks.json");
1562
+ }
1563
+ function loadSingleConfig(filePath, ...fallbackPaths) {
1564
+ try {
1565
+ const resolvedPath = [filePath, ...fallbackPaths].find((candidate) => candidate && fs4.existsSync(candidate)) ?? null;
1566
+ if (!resolvedPath) return {};
1567
+ const raw = fs4.readFileSync(resolvedPath, "utf-8");
1568
+ return validateConfig(JSON.parse(raw));
1569
+ } catch {
1570
+ return {};
1571
+ }
1572
+ }
1573
+ function validateConfig(raw) {
1574
+ if (!raw || typeof raw !== "object") return {};
1575
+ const config = {};
1576
+ const obj = raw;
1577
+ for (const event of VALID_EVENTS) {
1578
+ if (Array.isArray(obj[event])) {
1579
+ const hooks = obj[event].filter(isValidHook);
1580
+ if (hooks.length > 0) {
1581
+ config[event] = hooks;
1582
+ }
1583
+ }
1584
+ }
1585
+ return config;
1586
+ }
1587
+ function isValidHook(raw) {
1588
+ if (!raw || typeof raw !== "object") return false;
1589
+ const obj = raw;
1590
+ return obj.type === "command" && typeof obj.command === "string";
1591
+ }
1592
+ function mergeConfigs(global, project) {
1593
+ const merged = {};
1594
+ for (const event of VALID_EVENTS) {
1595
+ const combined = [...global[event] ?? [], ...project[event] ?? []];
1596
+ if (combined.length > 0) {
1597
+ merged[event] = combined;
1598
+ }
1599
+ }
1600
+ return merged;
1601
+ }
1602
+
1603
+ // src/hooks/types.ts
1604
+ function isBlockingEvent(event) {
1605
+ return event === "PreToolUse" || event === "Stop" || event === "UserPromptSubmit";
1606
+ }
1607
+
1608
+ // src/hooks/executor.ts
1609
+ var DEFAULT_TIMEOUT = 1e4;
1610
+ var FORCE_KILL_GRACE_MS = 250;
1611
+ async function executeHook(hook, stdinPayload) {
1612
+ const timeout = hook.timeout ?? DEFAULT_TIMEOUT;
1613
+ const startTime = Date.now();
1614
+ return new Promise((resolve3) => {
1615
+ const isWindows = process.platform === "win32";
1616
+ const shell = isWindows ? "cmd" : "/bin/sh";
1617
+ const shellArgs = isWindows ? ["/c", hook.command] : ["-c", hook.command];
1618
+ const child = spawn(shell, shellArgs, {
1619
+ stdio: ["pipe", "pipe", "pipe"],
1620
+ cwd: stdinPayload.cwd,
1621
+ env: {
1622
+ ...process.env,
1623
+ MASTRA_HOOK_EVENT: stdinPayload.hook_event_name
1624
+ }
1625
+ });
1626
+ let stdout = "";
1627
+ let stderr = "";
1628
+ let timedOut = false;
1629
+ let resolved = false;
1630
+ let forceKillTimer;
1631
+ const timer = setTimeout(() => {
1632
+ timedOut = true;
1633
+ try {
1634
+ child.kill("SIGTERM");
1635
+ } catch {
1636
+ }
1637
+ forceKillTimer = setTimeout(() => {
1638
+ try {
1639
+ child.kill("SIGKILL");
1640
+ } catch {
1641
+ }
1642
+ }, FORCE_KILL_GRACE_MS);
1643
+ }, timeout);
1644
+ child.stdout?.on("data", (data) => {
1645
+ stdout += data.toString();
1646
+ });
1647
+ child.stderr?.on("data", (data) => {
1648
+ stderr += data.toString();
1649
+ });
1650
+ child.on("close", (exitCode) => {
1651
+ clearTimeout(timer);
1652
+ if (forceKillTimer) clearTimeout(forceKillTimer);
1653
+ if (resolved) return;
1654
+ resolved = true;
1655
+ let parsedStdout;
1656
+ if (stdout.trim()) {
1657
+ try {
1658
+ parsedStdout = JSON.parse(stdout.trim());
1659
+ } catch {
1660
+ }
1661
+ }
1662
+ const result = {
1663
+ hook,
1664
+ exitCode: exitCode ?? 1,
1665
+ stdout: parsedStdout,
1666
+ stderr: stderr.trim() || void 0,
1667
+ timedOut,
1668
+ durationMs: Date.now() - startTime
1669
+ };
1670
+ logRuntimeProfile("hooks.execute", result.durationMs, {
1671
+ event: stdinPayload.hook_event_name,
1672
+ command: hook.command,
1673
+ timedOut,
1674
+ exitCode: result.exitCode
1675
+ });
1676
+ resolve3(result);
1677
+ });
1678
+ child.on("error", (error) => {
1679
+ clearTimeout(timer);
1680
+ if (forceKillTimer) clearTimeout(forceKillTimer);
1681
+ if (resolved) return;
1682
+ resolved = true;
1683
+ const result = {
1684
+ hook,
1685
+ exitCode: 1,
1686
+ stderr: error.message,
1687
+ timedOut: false,
1688
+ durationMs: Date.now() - startTime
1689
+ };
1690
+ logRuntimeProfile("hooks.execute", result.durationMs, {
1691
+ event: stdinPayload.hook_event_name,
1692
+ command: hook.command,
1693
+ timedOut: false,
1694
+ exitCode: result.exitCode,
1695
+ error: error.message
1696
+ });
1697
+ resolve3(result);
1698
+ });
1699
+ try {
1700
+ child.stdin?.write(JSON.stringify(stdinPayload));
1701
+ child.stdin?.end();
1702
+ } catch {
1703
+ }
1704
+ });
1705
+ }
1706
+ function matchesHook(hook, context) {
1707
+ if (!hook.matcher) return true;
1708
+ if (hook.matcher.tool_name) {
1709
+ if (!context.tool_name) return false;
1710
+ try {
1711
+ return new RegExp(hook.matcher.tool_name).test(context.tool_name);
1712
+ } catch {
1713
+ return false;
1714
+ }
1715
+ }
1716
+ return true;
1717
+ }
1718
+ async function runHooksForEvent(hooks, stdinPayload, matchContext = {}) {
1719
+ const results = [];
1720
+ const warnings = [];
1721
+ let additionalContext;
1722
+ const applicable = hooks.filter((h) => matchesHook(h, matchContext));
1723
+ if (applicable.length === 0) {
1724
+ return { allowed: true, results: [], warnings: [] };
1725
+ }
1726
+ const blocking = isBlockingEvent(stdinPayload.hook_event_name);
1727
+ if (!blocking) {
1728
+ const concurrentResults = await Promise.all(applicable.map((hook) => executeHook(hook, stdinPayload)));
1729
+ for (const result of concurrentResults) {
1730
+ results.push(result);
1731
+ if (result.stdout?.additionalContext) {
1732
+ additionalContext = additionalContext ? `${additionalContext}
1733
+ ${result.stdout.additionalContext}` : result.stdout.additionalContext;
1734
+ }
1735
+ if (result.timedOut) {
1736
+ warnings.push(`Hook timed out after ${result.hook.timeout ?? DEFAULT_TIMEOUT}ms: ${result.hook.command}`);
1737
+ continue;
1738
+ }
1739
+ if (result.exitCode === 0) continue;
1740
+ const warnMsg = result.stderr || `Hook exited with code ${result.exitCode}`;
1741
+ warnings.push(`${result.hook.description || result.hook.command}: ${warnMsg}`);
1742
+ }
1743
+ return { allowed: true, additionalContext, results, warnings };
1744
+ }
1745
+ for (const hook of applicable) {
1746
+ const result = await executeHook(hook, stdinPayload);
1747
+ results.push(result);
1748
+ if (result.stdout?.additionalContext) {
1749
+ additionalContext = additionalContext ? `${additionalContext}
1750
+ ${result.stdout.additionalContext}` : result.stdout.additionalContext;
1751
+ }
1752
+ if (result.timedOut) {
1753
+ warnings.push(`Hook timed out after ${hook.timeout ?? DEFAULT_TIMEOUT}ms: ${hook.command}`);
1754
+ continue;
1755
+ }
1756
+ if (result.exitCode === 2 && blocking) {
1757
+ const reason = result.stdout?.reason || result.stderr || `Blocked by hook: ${hook.description || hook.command}`;
1758
+ return {
1759
+ allowed: false,
1760
+ blockReason: reason,
1761
+ additionalContext,
1762
+ results,
1763
+ warnings
1764
+ };
1765
+ }
1766
+ if (result.exitCode === 0) continue;
1767
+ const warnMsg = result.stderr || `Hook exited with code ${result.exitCode}`;
1768
+ warnings.push(`${hook.description || hook.command}: ${warnMsg}`);
1769
+ }
1770
+ return { allowed: true, additionalContext, results, warnings };
1771
+ }
1772
+
1773
+ // src/hooks/manager.ts
1774
+ var HookManager = class {
1775
+ config;
1776
+ projectDir;
1777
+ sessionId;
1778
+ constructor(projectDir, sessionId) {
1779
+ this.projectDir = projectDir;
1780
+ this.sessionId = sessionId;
1781
+ this.config = loadHooksConfig(projectDir);
1782
+ }
1783
+ reload() {
1784
+ this.config = loadHooksConfig(this.projectDir);
1785
+ }
1786
+ setSessionId(sessionId) {
1787
+ this.sessionId = sessionId;
1788
+ }
1789
+ hasHooks() {
1790
+ return Object.keys(this.config).length > 0;
1791
+ }
1792
+ getConfig() {
1793
+ return this.config;
1794
+ }
1795
+ getConfigPaths() {
1796
+ return {
1797
+ project: getProjectHooksPath(this.projectDir),
1798
+ global: getGlobalHooksPath()
1799
+ };
1800
+ }
1801
+ // =========================================================================
1802
+ // Event Methods
1803
+ // =========================================================================
1804
+ async runPreToolUse(toolName, toolInput) {
1805
+ const hooks = this.config.PreToolUse;
1806
+ if (!hooks || hooks.length === 0) {
1807
+ return { allowed: true, results: [], warnings: [] };
1808
+ }
1809
+ const stdin = {
1810
+ session_id: this.sessionId,
1811
+ cwd: this.projectDir,
1812
+ hook_event_name: "PreToolUse",
1813
+ tool_name: toolName,
1814
+ tool_input: toolInput
1815
+ };
1816
+ return runHooksForEvent(hooks, stdin, { tool_name: toolName });
1817
+ }
1818
+ async runPostToolUse(toolName, toolInput, toolOutput, toolError) {
1819
+ const hooks = this.config.PostToolUse;
1820
+ if (!hooks || hooks.length === 0) {
1821
+ return { allowed: true, results: [], warnings: [] };
1822
+ }
1823
+ const stdin = {
1824
+ session_id: this.sessionId,
1825
+ cwd: this.projectDir,
1826
+ hook_event_name: "PostToolUse",
1827
+ tool_name: toolName,
1828
+ tool_input: toolInput,
1829
+ tool_output: toolOutput,
1830
+ tool_error: toolError
1831
+ };
1832
+ return runHooksForEvent(hooks, stdin, { tool_name: toolName });
1833
+ }
1834
+ async runUserPromptSubmit(userMessage) {
1835
+ const hooks = this.config.UserPromptSubmit;
1836
+ if (!hooks || hooks.length === 0) {
1837
+ return { allowed: true, results: [], warnings: [] };
1838
+ }
1839
+ const stdin = {
1840
+ session_id: this.sessionId,
1841
+ cwd: this.projectDir,
1842
+ hook_event_name: "UserPromptSubmit",
1843
+ user_message: userMessage
1844
+ };
1845
+ return runHooksForEvent(hooks, stdin);
1846
+ }
1847
+ async runStop(assistantMessage, stopReason) {
1848
+ const hooks = this.config.Stop;
1849
+ if (!hooks || hooks.length === 0) {
1850
+ return { allowed: true, results: [], warnings: [] };
1851
+ }
1852
+ const stdin = {
1853
+ session_id: this.sessionId,
1854
+ cwd: this.projectDir,
1855
+ hook_event_name: "Stop",
1856
+ assistant_message: assistantMessage,
1857
+ stop_reason: stopReason
1858
+ };
1859
+ return runHooksForEvent(hooks, stdin);
1860
+ }
1861
+ async runSessionStart() {
1862
+ const hooks = this.config.SessionStart;
1863
+ if (!hooks || hooks.length === 0) {
1864
+ return { allowed: true, results: [], warnings: [] };
1865
+ }
1866
+ const stdin = {
1867
+ session_id: this.sessionId,
1868
+ cwd: this.projectDir,
1869
+ hook_event_name: "SessionStart"
1870
+ };
1871
+ return runHooksForEvent(hooks, stdin);
1872
+ }
1873
+ async runSessionEnd() {
1874
+ const hooks = this.config.SessionEnd;
1875
+ if (!hooks || hooks.length === 0) {
1876
+ return { allowed: true, results: [], warnings: [] };
1877
+ }
1878
+ const stdin = {
1879
+ session_id: this.sessionId,
1880
+ cwd: this.projectDir,
1881
+ hook_event_name: "SessionEnd"
1882
+ };
1883
+ return runHooksForEvent(hooks, stdin);
1884
+ }
1885
+ /**
1886
+ * Fire notification hooks (non-blocking, fire-and-forget).
1887
+ * Called when the TUI is waiting for user input.
1888
+ */
1889
+ runNotification(reason, message) {
1890
+ const hooks = this.config.Notification;
1891
+ if (!hooks || hooks.length === 0) return;
1892
+ const stdin = {
1893
+ session_id: this.sessionId,
1894
+ cwd: this.projectDir,
1895
+ hook_event_name: "Notification",
1896
+ reason,
1897
+ message
1898
+ };
1899
+ runHooksForEvent(hooks, stdin).catch(() => {
1900
+ });
1901
+ }
1902
+ };
1903
+ function loadMcpConfig(projectDir) {
1904
+ const claudeConfig = loadClaudeSettings(projectDir);
1905
+ const globalConfig = loadSingleConfig2(
1906
+ getGlobalMcpPath(),
1907
+ getLegacyUliCliGlobalMcpPath(),
1908
+ getLegacyPulseGlobalMcpPath(),
1909
+ getLegacyMastraGlobalMcpPath()
1910
+ );
1911
+ const projectConfig = loadSingleConfig2(
1912
+ getProjectMcpPath(projectDir),
1913
+ getLegacyUliCliProjectMcpPath(projectDir),
1914
+ getLegacyPulseProjectMcpPath(projectDir),
1915
+ getLegacyMastraProjectMcpPath(projectDir)
1916
+ );
1917
+ return mergeConfigs2(claudeConfig, globalConfig, projectConfig);
1918
+ }
1919
+ function getProjectMcpPath(projectDir) {
1920
+ return path.join(projectDir, ".ulicode", "mcp.json");
1921
+ }
1922
+ function getGlobalMcpPath() {
1923
+ return path.join(os.homedir(), ".ulicode", "mcp.json");
1924
+ }
1925
+ function getLegacyUliCliProjectMcpPath(projectDir) {
1926
+ return path.join(projectDir, ".uli-cli", "mcp.json");
1927
+ }
1928
+ function getLegacyUliCliGlobalMcpPath() {
1929
+ return path.join(os.homedir(), ".uli-cli", "mcp.json");
1930
+ }
1931
+ function getLegacyPulseProjectMcpPath(projectDir) {
1932
+ return path.join(projectDir, ".pulse", "mcp.json");
1933
+ }
1934
+ function getLegacyPulseGlobalMcpPath() {
1935
+ return path.join(os.homedir(), ".pulse", "mcp.json");
1936
+ }
1937
+ function getLegacyMastraProjectMcpPath(projectDir) {
1938
+ return path.join(projectDir, ".mastracode", "mcp.json");
1939
+ }
1940
+ function getLegacyMastraGlobalMcpPath() {
1941
+ return path.join(os.homedir(), ".mastracode", "mcp.json");
1942
+ }
1943
+ function getClaudeSettingsPath(projectDir) {
1944
+ return path.join(projectDir, ".claude", "settings.local.json");
1945
+ }
1946
+ function loadSingleConfig2(filePath, ...fallbackPaths) {
1947
+ try {
1948
+ const resolvedPath = [filePath, ...fallbackPaths].find((candidate) => candidate && fs4.existsSync(candidate)) ?? null;
1949
+ if (!resolvedPath) return {};
1950
+ const raw = fs4.readFileSync(resolvedPath, "utf-8");
1951
+ return validateConfig2(JSON.parse(raw));
1952
+ } catch {
1953
+ return {};
1954
+ }
1955
+ }
1956
+ function loadClaudeSettings(projectDir) {
1957
+ try {
1958
+ const filePath = getClaudeSettingsPath(projectDir);
1959
+ if (!fs4.existsSync(filePath)) return {};
1960
+ const raw = fs4.readFileSync(filePath, "utf-8");
1961
+ const parsed = JSON.parse(raw);
1962
+ if (parsed?.mcpServers && typeof parsed.mcpServers === "object") {
1963
+ return validateConfig2({ mcpServers: parsed.mcpServers });
1964
+ }
1965
+ return {};
1966
+ } catch {
1967
+ return {};
1968
+ }
1969
+ }
1970
+ function classifyServerEntry(raw) {
1971
+ if (!raw || typeof raw !== "object") {
1972
+ return { kind: "skip", reason: "Invalid entry: expected an object" };
1973
+ }
1974
+ const obj = raw;
1975
+ const hasCommand = typeof obj.command === "string";
1976
+ const hasUrl = typeof obj.url === "string";
1977
+ if (hasCommand && hasUrl) {
1978
+ return { kind: "skip", reason: 'Cannot specify both "command" and "url"' };
1979
+ }
1980
+ if (hasCommand) {
1981
+ return { kind: "stdio" };
1982
+ }
1983
+ if (hasUrl) {
1984
+ try {
1985
+ new URL(obj.url);
1986
+ } catch {
1987
+ return { kind: "skip", reason: `Invalid URL: "${obj.url}"` };
1988
+ }
1989
+ return { kind: "http" };
1990
+ }
1991
+ return { kind: "skip", reason: 'Missing required field: "command" (stdio) or "url" (http)' };
1992
+ }
1993
+ function validateConfig2(raw) {
1994
+ if (!raw || typeof raw !== "object") return {};
1995
+ const obj = raw;
1996
+ if (!obj.mcpServers || typeof obj.mcpServers !== "object") return {};
1997
+ const servers = {};
1998
+ const skippedServers = [];
1999
+ const rawServers = obj.mcpServers;
2000
+ for (const [name, entry] of Object.entries(rawServers)) {
2001
+ const classification = classifyServerEntry(entry);
2002
+ if (classification.kind === "stdio") {
2003
+ const e = entry;
2004
+ servers[name] = {
2005
+ command: e.command,
2006
+ args: Array.isArray(e.args) ? e.args : void 0,
2007
+ env: typeof e.env === "object" && e.env !== null ? e.env : void 0
2008
+ };
2009
+ } else if (classification.kind === "http") {
2010
+ const e = entry;
2011
+ servers[name] = {
2012
+ url: e.url,
2013
+ headers: typeof e.headers === "object" && e.headers !== null ? e.headers : void 0
2014
+ };
2015
+ } else {
2016
+ skippedServers.push({ name, reason: classification.reason });
2017
+ }
2018
+ }
2019
+ const result = {};
2020
+ if (Object.keys(servers).length > 0) {
2021
+ result.mcpServers = servers;
2022
+ }
2023
+ if (skippedServers.length > 0) {
2024
+ result.skippedServers = skippedServers;
2025
+ }
2026
+ return result;
2027
+ }
2028
+ function mergeConfigs2(...configs) {
2029
+ const merged = {};
2030
+ const allSkipped = [];
2031
+ for (const config of configs) {
2032
+ if (config.mcpServers) {
2033
+ for (const [name, server] of Object.entries(config.mcpServers)) {
2034
+ merged[name] = server;
2035
+ }
2036
+ }
2037
+ if (config.skippedServers) {
2038
+ allSkipped.push(...config.skippedServers);
2039
+ }
2040
+ }
2041
+ const validNames = new Set(Object.keys(merged));
2042
+ const filteredSkipped = allSkipped.filter((s) => !validNames.has(s.name));
2043
+ const skippedMap = /* @__PURE__ */ new Map();
2044
+ for (const s of filteredSkipped) {
2045
+ skippedMap.set(s.name, s);
2046
+ }
2047
+ const result = {};
2048
+ if (Object.keys(merged).length > 0) {
2049
+ result.mcpServers = merged;
2050
+ }
2051
+ if (skippedMap.size > 0) {
2052
+ result.skippedServers = Array.from(skippedMap.values());
2053
+ }
2054
+ return result;
2055
+ }
2056
+
2057
+ // src/mcp/manager.ts
2058
+ function getTransport(cfg) {
2059
+ return "url" in cfg ? "http" : "stdio";
2060
+ }
2061
+ function createMcpManager(projectDir, extraServers) {
2062
+ const applyExtraServers = (base) => {
2063
+ if (!extraServers || Object.keys(extraServers).length === 0) return base;
2064
+ return { ...base, mcpServers: { ...base.mcpServers, ...extraServers } };
2065
+ };
2066
+ let config = applyExtraServers(loadMcpConfig(projectDir));
2067
+ let client = null;
2068
+ let tools = {};
2069
+ let serverStatuses = /* @__PURE__ */ new Map();
2070
+ let initialized = false;
2071
+ function buildServerDefs(servers) {
2072
+ const defs = {};
2073
+ for (const [name, cfg] of Object.entries(servers)) {
2074
+ if ("url" in cfg) {
2075
+ const httpCfg = cfg;
2076
+ defs[name] = {
2077
+ url: new URL(httpCfg.url),
2078
+ requestInit: httpCfg.headers ? { headers: httpCfg.headers } : void 0
2079
+ };
2080
+ } else {
2081
+ defs[name] = { command: cfg.command, args: cfg.args, env: cfg.env };
2082
+ }
2083
+ }
2084
+ return defs;
2085
+ }
2086
+ async function connectAndCollectTools() {
2087
+ const servers = config.mcpServers;
2088
+ if (!servers || Object.keys(servers).length === 0) {
2089
+ return;
2090
+ }
2091
+ client = new MCPClient({
2092
+ id: "mastra-code-mcp",
2093
+ servers: buildServerDefs(servers)
2094
+ });
2095
+ const serverNames = Object.keys(servers);
2096
+ try {
2097
+ tools = await client.listTools();
2098
+ for (const name of serverNames) {
2099
+ const prefix = `${name}_`;
2100
+ const serverToolNames = Object.keys(tools).filter((t) => t.startsWith(prefix));
2101
+ serverStatuses.set(name, {
2102
+ name,
2103
+ connected: true,
2104
+ toolCount: serverToolNames.length,
2105
+ toolNames: serverToolNames,
2106
+ transport: getTransport(servers[name])
2107
+ });
2108
+ }
2109
+ } catch (error) {
2110
+ const errMsg = error instanceof Error ? error.message : String(error);
2111
+ for (const name of serverNames) {
2112
+ serverStatuses.set(name, {
2113
+ name,
2114
+ connected: false,
2115
+ toolCount: 0,
2116
+ toolNames: [],
2117
+ transport: getTransport(servers[name]),
2118
+ error: errMsg
2119
+ });
2120
+ }
2121
+ }
2122
+ }
2123
+ async function disconnect() {
2124
+ if (client) {
2125
+ try {
2126
+ await client.disconnect();
2127
+ } catch {
2128
+ }
2129
+ client = null;
2130
+ }
2131
+ }
2132
+ return {
2133
+ async init() {
2134
+ if (initialized) return;
2135
+ await connectAndCollectTools();
2136
+ initialized = true;
2137
+ },
2138
+ async reload() {
2139
+ await disconnect();
2140
+ config = applyExtraServers(loadMcpConfig(projectDir));
2141
+ tools = {};
2142
+ serverStatuses = /* @__PURE__ */ new Map();
2143
+ initialized = false;
2144
+ await connectAndCollectTools();
2145
+ initialized = true;
2146
+ },
2147
+ disconnect,
2148
+ getTools() {
2149
+ return { ...tools };
2150
+ },
2151
+ hasServers() {
2152
+ const hasConfigured = config.mcpServers !== void 0 && Object.keys(config.mcpServers).length > 0;
2153
+ const hasSkipped = config.skippedServers !== void 0 && config.skippedServers.length > 0;
2154
+ return hasConfigured || hasSkipped;
2155
+ },
2156
+ getServerStatuses() {
2157
+ return Array.from(serverStatuses.values());
2158
+ },
2159
+ getSkippedServers() {
2160
+ return [...config.skippedServers ?? []];
2161
+ },
2162
+ getConfigPaths() {
2163
+ return {
2164
+ project: getProjectMcpPath(projectDir),
2165
+ global: getGlobalMcpPath(),
2166
+ claude: getClaudeSettingsPath(projectDir)
2167
+ };
2168
+ },
2169
+ getConfig() {
2170
+ return config;
2171
+ }
2172
+ };
2173
+ }
2174
+ var stateSchema = z.object({
2175
+ projectPath: z.string().optional(),
2176
+ projectName: z.string().optional(),
2177
+ gitBranch: z.string().optional(),
2178
+ lastCommand: z.string().optional(),
2179
+ currentModelId: z.string().default(""),
2180
+ // Subagent model settings (per-thread/per-mode)
2181
+ subagentModelId: z.string().optional(),
2182
+ // Thread-level default for subagents
2183
+ // Observational Memory model settings
2184
+ observerModelId: z.string().default(DEFAULT_OM_MODEL_ID),
2185
+ reflectorModelId: z.string().default(DEFAULT_OM_MODEL_ID),
2186
+ // Observational Memory threshold settings
2187
+ observationThreshold: z.number().default(3e4),
2188
+ reflectionThreshold: z.number().default(4e4),
2189
+ // Observational Memory scope — 'thread' (per-conversation) or 'resource' (shared across threads)
2190
+ omScope: z.enum(["thread", "resource"]).optional(),
2191
+ // Thinking level for model reasoning effort
2192
+ thinkingLevel: z.enum(["off", "low", "medium", "high", "xhigh"]).default("off"),
2193
+ // YOLO mode — auto-approve all tool calls
2194
+ yolo: z.boolean().default(false),
2195
+ // Permission rules — per-category and per-tool approval policies
2196
+ permissionRules: z.object({
2197
+ categories: z.record(z.string(), z.enum(["allow", "ask", "deny"])).default({}),
2198
+ tools: z.record(z.string(), z.enum(["allow", "ask", "deny"])).default({})
2199
+ }).default({ categories: {}, tools: {} }),
2200
+ // Smart editing mode — use AST-based analysis for code edits
2201
+ smartEditing: z.boolean().default(true),
2202
+ // Notification mode — alert when TUI needs user attention
2203
+ notifications: z.enum(["bell", "system", "both", "off"]).default("off"),
2204
+ // Task list (persisted per-thread)
2205
+ tasks: z.array(
2206
+ z.object({
2207
+ content: z.string(),
2208
+ status: z.enum(["pending", "in_progress", "completed"]),
2209
+ activeForm: z.string()
2210
+ })
2211
+ ).default([]),
2212
+ // Sandbox allowed paths (per-thread, absolute paths allowed in addition to project root)
2213
+ sandboxAllowedPaths: z.array(z.string()).default([]),
2214
+ // Active plan (set when a plan is approved in Plan mode)
2215
+ activePlan: z.object({
2216
+ title: z.string(),
2217
+ plan: z.string(),
2218
+ approvedAt: z.string()
2219
+ }).nullable().default(null)
2220
+ });
2221
+ var CACHE_DIR = path__default.join(os__default.homedir(), ".cache", "mastra");
2222
+ var CACHE_FILE = path__default.join(CACHE_DIR, "gateway-refresh-time");
2223
+ var GLOBAL_PROVIDER_REGISTRY_JSON = path__default.join(CACHE_DIR, "provider-registry.json");
2224
+ var GLOBAL_PROVIDER_TYPES_DTS = path__default.join(CACHE_DIR, "provider-types.generated.d.ts");
2225
+ var DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1e3;
2226
+ var isSyncing = false;
2227
+ async function atomicWriteFile(filePath, content) {
2228
+ const randomSuffix = Math.random().toString(36).substring(2, 15);
2229
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${randomSuffix}.tmp`;
2230
+ try {
2231
+ await fs4__default.promises.writeFile(tempPath, content, "utf-8");
2232
+ await fs4__default.promises.rename(tempPath, filePath);
2233
+ } catch (error) {
2234
+ try {
2235
+ await fs4__default.promises.unlink(tempPath);
2236
+ } catch {
2237
+ }
2238
+ throw error;
2239
+ }
2240
+ }
2241
+ async function fetchProvidersFromGateways() {
2242
+ const allProviders = {};
2243
+ const allModels = {};
2244
+ const gateways = [new ModelsDevGateway({}), new NetlifyGateway()];
2245
+ for (const gateway of gateways) {
2246
+ try {
2247
+ const providers = await gateway.fetchProviders();
2248
+ const isProviderRegistry = gateway.id === "models.dev";
2249
+ for (const [providerId, config] of Object.entries(providers)) {
2250
+ const typeProviderId = isProviderRegistry ? providerId : providerId === gateway.id ? gateway.id : `${gateway.id}/${providerId}`;
2251
+ allProviders[typeProviderId] = config;
2252
+ allModels[typeProviderId] = config.models.sort();
2253
+ }
2254
+ } catch (error) {
2255
+ console.warn(`[GatewaySync] Failed to fetch from ${gateway.id}:`, error);
2256
+ }
2257
+ }
2258
+ return { providers: allProviders, models: allModels };
2259
+ }
2260
+ function generateTypesContent(models) {
2261
+ const providerModelsEntries = Object.entries(models).map(([provider, modelList]) => {
2262
+ const modelsList = modelList.map((m) => `'${m}'`);
2263
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(provider);
2264
+ const providerKey = needsQuotes ? `'${provider}'` : provider;
2265
+ const singleLine = ` readonly ${providerKey}: readonly [${modelsList.join(", ")}];`;
2266
+ if (singleLine.length > 120) {
2267
+ const formattedModels = modelList.map((m) => ` '${m}',`).join("\n");
2268
+ return ` readonly ${providerKey}: readonly [
2269
+ ${formattedModels}
2270
+ ];`;
2271
+ }
2272
+ return singleLine;
2273
+ }).join("\n");
2274
+ return `/**
2275
+ * THIS FILE IS AUTO-GENERATED - DO NOT EDIT
2276
+ * Generated from model gateway providers
2277
+ */
2278
+
2279
+ export type ProviderModelsMap = {
2280
+ ${providerModelsEntries}
2281
+ };
2282
+
2283
+ export type Provider = keyof ProviderModelsMap;
2284
+
2285
+ export interface ProviderModels {
2286
+ [key: string]: string[];
2287
+ }
2288
+
2289
+ export type ModelRouterModelId =
2290
+ | {
2291
+ [P in Provider]: \`\${P}/\${ProviderModelsMap[P][number]}\`;
2292
+ }[Provider]
2293
+ | (string & {});
2294
+
2295
+ export type ModelForProvider<P extends Provider> = ProviderModelsMap[P][number];
2296
+ `;
2297
+ }
2298
+ function getLastSyncTime() {
2299
+ try {
2300
+ if (!fs4__default.existsSync(CACHE_FILE)) {
2301
+ return null;
2302
+ }
2303
+ const timestamp = fs4__default.readFileSync(CACHE_FILE, "utf-8").trim();
2304
+ return new Date(parseInt(timestamp, 10));
2305
+ } catch {
2306
+ return null;
2307
+ }
2308
+ }
2309
+ function saveLastSyncTime(date) {
2310
+ try {
2311
+ if (!fs4__default.existsSync(CACHE_DIR)) {
2312
+ fs4__default.mkdirSync(CACHE_DIR, { recursive: true });
2313
+ }
2314
+ fs4__default.writeFileSync(CACHE_FILE, date.getTime().toString(), "utf-8");
2315
+ } catch (error) {
2316
+ console.warn("[GatewaySync] Failed to save sync time:", error);
2317
+ }
2318
+ }
2319
+ async function syncGateways(force = false) {
2320
+ if (isSyncing && !force) {
2321
+ return;
2322
+ }
2323
+ if (!force) {
2324
+ const lastSync = getLastSyncTime();
2325
+ if (lastSync) {
2326
+ const timeSinceSync = Date.now() - lastSync.getTime();
2327
+ if (timeSinceSync < DEFAULT_SYNC_INTERVAL_MS) {
2328
+ return;
2329
+ }
2330
+ }
2331
+ }
2332
+ isSyncing = true;
2333
+ try {
2334
+ const { providers, models } = await fetchProvidersFromGateways();
2335
+ await fs4__default.promises.mkdir(CACHE_DIR, { recursive: true });
2336
+ const registryData = {
2337
+ providers,
2338
+ models,
2339
+ version: "1.0.0"
2340
+ };
2341
+ await atomicWriteFile(GLOBAL_PROVIDER_REGISTRY_JSON, JSON.stringify(registryData, null, 2));
2342
+ const typesContent = generateTypesContent(models);
2343
+ await atomicWriteFile(GLOBAL_PROVIDER_TYPES_DTS, typesContent);
2344
+ const now = /* @__PURE__ */ new Date();
2345
+ saveLastSyncTime(now);
2346
+ } catch (error) {
2347
+ console.error("[GatewaySync] \u274C Sync failed:", error);
2348
+ } finally {
2349
+ isSyncing = false;
2350
+ }
2351
+ }
2352
+ function createFallbackLibSQL() {
2353
+ return new LibSQLStore({
2354
+ id: "mastra-code-storage",
2355
+ url: `file:${getDatabasePath()}`
2356
+ });
2357
+ }
2358
+ async function createStorage(config) {
2359
+ if (config.backend === "pg") {
2360
+ return createPgStorage(config);
2361
+ }
2362
+ return {
2363
+ storage: new LibSQLStore({
2364
+ id: "mastra-code-storage",
2365
+ url: config.url,
2366
+ ...config.authToken ? { authToken: config.authToken } : {}
2367
+ })
2368
+ };
2369
+ }
2370
+ async function createPgStorage(config) {
2371
+ if (!config.connectionString && !config.host) {
2372
+ return {
2373
+ storage: createFallbackLibSQL(),
2374
+ warning: "PostgreSQL backend selected but no connection info configured. Using LibSQL fallback. Set a connection string via /settings."
2375
+ };
2376
+ }
2377
+ const base = {
2378
+ id: "mastra-code-storage",
2379
+ ...config.schemaName ? { schemaName: config.schemaName } : {},
2380
+ ...config.disableInit ? { disableInit: config.disableInit } : {},
2381
+ ...config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}
2382
+ };
2383
+ const store = config.connectionString ? new PostgresStore({ ...base, connectionString: config.connectionString }) : new PostgresStore({
2384
+ ...base,
2385
+ host: config.host,
2386
+ port: config.port,
2387
+ database: config.database,
2388
+ user: config.user,
2389
+ password: config.password
2390
+ });
2391
+ try {
2392
+ await store.init();
2393
+ } catch (err) {
2394
+ const msg = err?.message ?? String(err);
2395
+ const target = config.connectionString ?? `${config.host}:${config.port ?? 5432}`;
2396
+ try {
2397
+ await store.close();
2398
+ } catch {
2399
+ }
2400
+ return {
2401
+ storage: createFallbackLibSQL(),
2402
+ warning: `Failed to connect to PostgreSQL at ${target}: ${msg}
2403
+ Using LibSQL fallback. Fix the connection via /settings.`
2404
+ };
2405
+ }
2406
+ return { storage: store };
2407
+ }
2408
+
2409
+ // src/index.ts
2410
+ var PROVIDER_TO_OAUTH_ID = {
2411
+ anthropic: "anthropic",
2412
+ openai: "openai-codex"
2413
+ };
2414
+ function createAuthStorage() {
2415
+ const authStorage2 = new AuthStorage();
2416
+ setAuthStorage(authStorage2);
2417
+ setAuthStorage2(authStorage2);
2418
+ return authStorage2;
2419
+ }
2420
+ async function createMastraCode(config) {
2421
+ const cwd = config?.cwd ?? process.cwd();
2422
+ const authStorage2 = createAuthStorage();
2423
+ const project = detectProject(cwd);
2424
+ const resourceIdOverride = getResourceIdOverride(project.rootPath);
2425
+ if (resourceIdOverride) {
2426
+ project.resourceId = resourceIdOverride;
2427
+ project.resourceIdOverride = true;
2428
+ }
2429
+ lspManager.prewarmWorkspace(project.rootPath);
2430
+ const globalSettings = loadSettingsCached();
2431
+ const storageConfig = config?.storage ?? getStorageConfig(project.rootPath, globalSettings.storage);
2432
+ const storageResult = await createStorage(storageConfig);
2433
+ const storage = storageResult.storage;
2434
+ const storageWarning = storageResult.warning;
2435
+ const memory = getDynamicMemory(storage);
2436
+ const mcpManager = config?.disableMcp ? void 0 : createMcpManager(project.rootPath, config?.mcpServers);
2437
+ const hookManager = config?.disableHooks ? void 0 : new HookManager(project.rootPath, "session-init");
2438
+ if (hookManager?.hasHooks()) {
2439
+ const hookConfig = hookManager.getConfig();
2440
+ const hookCount = Object.values(hookConfig).reduce((sum, hooks) => sum + (hooks?.length ?? 0), 0);
2441
+ console.info(`Hooks: ${hookCount} hook(s) configured`);
2442
+ }
2443
+ const codeAgent = new Agent({
2444
+ id: "code-agent",
2445
+ name: "Code Agent",
2446
+ instructions: getDynamicInstructions,
2447
+ model: getDynamicModel,
2448
+ tools: createDynamicTools(mcpManager, config?.extraTools, hookManager, config?.disabledTools)
2449
+ });
2450
+ const agentRegistry = loadAgentDefinitionsCached(project.rootPath);
2451
+ const defaultSubagents = createHarnessSubagentsFromDefinitions(agentRegistry);
2452
+ const defaultModes = createHarnessModesFromDefinitions(agentRegistry, codeAgent);
2453
+ const defaultHeartbeatHandlers = [
2454
+ {
2455
+ id: "gateway-sync",
2456
+ intervalMs: 5 * 60 * 1e3,
2457
+ handler: () => syncGateways()
2458
+ }
2459
+ ];
2460
+ const anthropicCred = authStorage2.get("anthropic");
2461
+ const openaiCred = authStorage2.get("openai-codex");
2462
+ const startupAccess = {
2463
+ anthropic: anthropicCred?.type === "oauth" ? "oauth" : anthropicCred?.type === "api_key" && anthropicCred.key.trim().length > 0 ? "apikey" : false,
2464
+ openai: openaiCred?.type === "oauth" ? "oauth" : openaiCred?.type === "api_key" && openaiCred.key.trim().length > 0 ? "apikey" : false,
2465
+ google: authStorage2.get("google")?.type === "api_key" && authStorage2.get("google").key?.trim().length ? "apikey" : process.env.GOOGLE_GENERATIVE_AI_API_KEY ? "apikey" : false,
2466
+ cerebras: process.env.CEREBRAS_API_KEY ? "apikey" : false,
2467
+ deepseek: process.env.DEEPSEEK_API_KEY ? "apikey" : false
2468
+ };
2469
+ try {
2470
+ const registry = PROVIDER_REGISTRY;
2471
+ for (const [provider, config2] of Object.entries(registry)) {
2472
+ if (startupAccess[provider]) continue;
2473
+ if (provider === "anthropic" || provider === "openai") continue;
2474
+ const envVars = config2?.apiKeyEnvVar;
2475
+ const envVarList = Array.isArray(envVars) ? envVars : envVars ? [envVars] : [];
2476
+ if (envVarList.some((envVar) => process.env[envVar])) {
2477
+ startupAccess[provider] = "apikey";
2478
+ }
2479
+ }
2480
+ } catch {
2481
+ }
2482
+ const builtinPacks = getAvailableModePacks(startupAccess, [], agentRegistry.modes);
2483
+ const builtinOmPacks = getAvailableOmPacks(startupAccess);
2484
+ const effectiveDefaults = resolveModelDefaults(globalSettings, builtinPacks);
2485
+ const effectiveOmModel = resolveOmModel(globalSettings, builtinOmPacks);
2486
+ const modes = (config?.modes ?? defaultModes).map((mode) => {
2487
+ const savedModel = effectiveDefaults[mode.id];
2488
+ return savedModel ? { ...mode, defaultModelId: savedModel } : mode;
2489
+ });
2490
+ const subagentModeMap = getSubagentModeMap(agentRegistry);
2491
+ const subagents = (config?.subagents ?? defaultSubagents).map((sa) => {
2492
+ const modeId = subagentModeMap[sa.id];
2493
+ const model = modeId ? effectiveDefaults[modeId] : void 0;
2494
+ let filtered = sa;
2495
+ if (config?.disabledTools?.length) {
2496
+ if (sa.allowedWorkspaceTools) {
2497
+ filtered = {
2498
+ ...filtered,
2499
+ allowedWorkspaceTools: sa.allowedWorkspaceTools.filter((t) => !config.disabledTools.includes(t))
2500
+ };
2501
+ }
2502
+ if (sa.tools) {
2503
+ filtered = {
2504
+ ...filtered,
2505
+ tools: Object.fromEntries(Object.entries(sa.tools).filter(([k]) => !config.disabledTools.includes(k)))
2506
+ };
2507
+ }
2508
+ }
2509
+ return model ? { ...filtered, defaultModelId: model } : filtered;
2510
+ });
2511
+ const globalInitialState = {};
2512
+ if (effectiveOmModel) {
2513
+ globalInitialState.observerModelId = effectiveOmModel;
2514
+ globalInitialState.reflectorModelId = effectiveOmModel;
2515
+ }
2516
+ if (globalSettings.preferences.yolo !== null) {
2517
+ globalInitialState.yolo = globalSettings.preferences.yolo;
2518
+ }
2519
+ globalInitialState.thinkingLevel = globalSettings.preferences.thinkingLevel;
2520
+ if (config?.omScope) {
2521
+ globalInitialState.omScope = config.omScope;
2522
+ }
2523
+ for (const [key, modelId] of Object.entries(globalSettings.models.subagentModels)) {
2524
+ if (key === "_default") {
2525
+ globalInitialState.subagentModelId = modelId;
2526
+ } else {
2527
+ globalInitialState[`subagentModelId_${key}`] = modelId;
2528
+ }
2529
+ }
2530
+ const harness = new Harness({
2531
+ id: "mastra-code",
2532
+ resourceId: project.resourceId,
2533
+ storage,
2534
+ memory,
2535
+ stateSchema,
2536
+ subagents,
2537
+ resolveModel: (modelId) => resolveModel(modelId),
2538
+ toolCategoryResolver: getToolCategory,
2539
+ initialState: {
2540
+ projectPath: project.rootPath,
2541
+ projectName: project.name,
2542
+ gitBranch: project.gitBranch,
2543
+ yolo: true,
2544
+ ...globalInitialState,
2545
+ ...config?.initialState
2546
+ },
2547
+ workspace: config?.workspace ?? getDynamicWorkspace,
2548
+ modes,
2549
+ heartbeatHandlers: config?.heartbeatHandlers ?? defaultHeartbeatHandlers,
2550
+ modelAuthChecker: (provider) => {
2551
+ const oauthId = PROVIDER_TO_OAUTH_ID[provider];
2552
+ if (oauthId && authStorage2.isLoggedIn(oauthId)) {
2553
+ return true;
2554
+ }
2555
+ if (provider === "anthropic") {
2556
+ const cred = authStorage2.get("anthropic");
2557
+ if (cred?.type === "api_key" && cred.key.trim().length > 0) {
2558
+ return true;
2559
+ }
2560
+ }
2561
+ if (provider === "openai") {
2562
+ const cred = authStorage2.get("openai-codex");
2563
+ if (cred?.type === "api_key" && cred.key.trim().length > 0) {
2564
+ return true;
2565
+ }
2566
+ }
2567
+ if (provider === "google") {
2568
+ const cred = authStorage2.get("google");
2569
+ if (cred?.type === "api_key" && cred.key.trim().length > 0) {
2570
+ return true;
2571
+ }
2572
+ }
2573
+ const customProvider = loadSettingsCached().customProviders.find((entry) => {
2574
+ return provider === getCustomProviderId(entry.name);
2575
+ });
2576
+ if (customProvider) {
2577
+ return true;
2578
+ }
2579
+ return void 0;
2580
+ },
2581
+ modelUseCountProvider: () => loadSettingsCached().modelUseCounts,
2582
+ modelUseCountTracker: (modelId) => {
2583
+ try {
2584
+ const settings = loadSettingsCached();
2585
+ settings.modelUseCounts[modelId] = (settings.modelUseCounts[modelId] ?? 0) + 1;
2586
+ saveSettings(settings);
2587
+ } catch (error) {
2588
+ console.error("Failed to persist model usage count", error);
2589
+ }
2590
+ },
2591
+ customModelCatalogProvider: () => {
2592
+ const settings = loadSettingsCached();
2593
+ const customModels = [];
2594
+ for (const provider of settings.customProviders) {
2595
+ const providerId = getCustomProviderId(provider.name);
2596
+ for (const modelName of provider.models) {
2597
+ customModels.push({
2598
+ id: toCustomProviderModelId(provider.name, modelName),
2599
+ provider: providerId,
2600
+ modelName,
2601
+ hasApiKey: true,
2602
+ apiKeyEnvVar: void 0
2603
+ });
2604
+ }
2605
+ }
2606
+ return customModels;
2607
+ },
2608
+ threadLock: {
2609
+ acquire: acquireThreadLock,
2610
+ release: releaseThreadLock
2611
+ }
2612
+ });
2613
+ applyModeScopedSubagentGuardrails({
2614
+ harness,
2615
+ subagents,
2616
+ resolveModel: (modelId) => resolveModel(modelId),
2617
+ getAllowedSubagentIdsForMode: (modeId) => getAllowedSubagentIdsForMode(agentRegistry, modeId)
2618
+ });
2619
+ if (hookManager) {
2620
+ harness.subscribe((event) => {
2621
+ if (event.type === "thread_changed") {
2622
+ hookManager.setSessionId(event.threadId);
2623
+ } else if (event.type === "thread_created") {
2624
+ hookManager.setSessionId(event.thread.id);
2625
+ }
2626
+ });
2627
+ }
2628
+ return { harness, mcpManager, hookManager, authStorage: authStorage2, resolveModel, storageWarning };
2629
+ }
2630
+
2631
+ export { createAuthStorage, createMastraCode };
2632
+ //# sourceMappingURL=chunk-OXFO76JC.js.map
2633
+ //# sourceMappingURL=chunk-OXFO76JC.js.map