@codelia/runtime 0.1.13 → 0.1.15

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 (3) hide show
  1. package/dist/index.cjs +847 -91
  2. package/dist/index.js +837 -77
  3. package/package.json +9 -9
package/dist/index.cjs CHANGED
@@ -23,12 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/runtime.ts
26
- var import_node_path15 = __toESM(require("path"), 1);
27
- var import_storage9 = require("@codelia/storage");
26
+ var import_node_path16 = __toESM(require("path"), 1);
27
+ var import_storage10 = require("@codelia/storage");
28
28
 
29
29
  // src/agent-factory.ts
30
30
  var import_node_fs14 = require("fs");
31
- var import_core17 = require("@codelia/core");
31
+ var import_core18 = require("@codelia/core");
32
32
  var import_storage5 = require("@codelia/storage");
33
33
 
34
34
  // src/agents/context.ts
@@ -688,6 +688,10 @@ var DEFAULT_SKILLS_INITIAL_MAX_ENTRIES = 200;
688
688
  var DEFAULT_SKILLS_INITIAL_MAX_BYTES = 32 * 1024;
689
689
  var DEFAULT_SKILLS_SEARCH_DEFAULT_LIMIT = 8;
690
690
  var DEFAULT_SKILLS_SEARCH_MAX_LIMIT = 50;
691
+ var DEFAULT_SEARCH_MODE = "auto";
692
+ var DEFAULT_SEARCH_NATIVE_PROVIDERS = ["openai", "anthropic"];
693
+ var DEFAULT_SEARCH_LOCAL_BACKEND = "ddg";
694
+ var DEFAULT_SEARCH_BRAVE_API_KEY_ENV = "BRAVE_SEARCH_API_KEY";
691
695
  var readEnvValue = (key) => {
692
696
  const value = process.env[key];
693
697
  if (!value) return void 0;
@@ -778,6 +782,45 @@ var resolveSkillsConfig = async (workingDir) => {
778
782
  const effective = import_config.configRegistry.resolve([globalConfig, projectConfig]);
779
783
  return normalizeSkillsConfig(effective.skills);
780
784
  };
785
+ var normalizeSearchConfig = (value) => {
786
+ const mode = value?.mode === "auto" || value?.mode === "native" || value?.mode === "local" ? value.mode : DEFAULT_SEARCH_MODE;
787
+ const providersRaw = value?.native?.providers ?? [
788
+ ...DEFAULT_SEARCH_NATIVE_PROVIDERS
789
+ ];
790
+ const providers = Array.from(
791
+ new Set(
792
+ providersRaw.map((entry) => entry.trim()).filter((entry) => entry.length > 0)
793
+ )
794
+ );
795
+ const searchContextSize = value?.native?.search_context_size;
796
+ const allowedDomains = value?.native?.allowed_domains?.length ? value.native.allowed_domains.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : void 0;
797
+ const userLocation = value?.native?.user_location ? {
798
+ ...value.native.user_location.city ? { city: value.native.user_location.city } : {},
799
+ ...value.native.user_location.country ? { country: value.native.user_location.country } : {},
800
+ ...value.native.user_location.region ? { region: value.native.user_location.region } : {},
801
+ ...value.native.user_location.timezone ? { timezone: value.native.user_location.timezone } : {}
802
+ } : void 0;
803
+ const backend = value?.local?.backend === "ddg" || value?.local?.backend === "brave" ? value.local.backend : DEFAULT_SEARCH_LOCAL_BACKEND;
804
+ const braveApiKeyEnv = value?.local?.brave_api_key_env?.trim() || DEFAULT_SEARCH_BRAVE_API_KEY_ENV;
805
+ return {
806
+ mode,
807
+ native: {
808
+ providers: providers.length ? providers : [...DEFAULT_SEARCH_NATIVE_PROVIDERS],
809
+ ...searchContextSize ? { searchContextSize } : {},
810
+ ...allowedDomains && allowedDomains.length ? { allowedDomains } : {},
811
+ ...userLocation && Object.keys(userLocation).length ? { userLocation } : {}
812
+ },
813
+ local: {
814
+ backend,
815
+ braveApiKeyEnv
816
+ }
817
+ };
818
+ };
819
+ var resolveSearchConfig = async (workingDir) => {
820
+ const { globalConfig, projectConfig } = await loadConfigLayers(workingDir);
821
+ const effective = import_config.configRegistry.resolve([globalConfig, projectConfig]);
822
+ return normalizeSearchConfig(effective.search);
823
+ };
781
824
  var normalizeMcpTimeoutMs = (value) => {
782
825
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
783
826
  return DEFAULT_MCP_REQUEST_TIMEOUT_MS;
@@ -813,6 +856,44 @@ var appendPermissionAllowRules = async (workingDir, rules) => {
813
856
  const configPath = resolveProjectConfigPath(workingDir);
814
857
  await (0, import_config_loader.appendPermissionAllowRules)(configPath, rules);
815
858
  };
859
+ var hasDefinedGroup = (group, value) => {
860
+ switch (group) {
861
+ case "model":
862
+ return typeof value?.model?.name === "string" && value.model.name.trim().length > 0;
863
+ case "permissions":
864
+ return Array.isArray(value?.permissions?.allow) || Array.isArray(value?.permissions?.deny);
865
+ case "tui":
866
+ return typeof value?.tui?.theme === "string" && value.tui.theme.trim().length > 0;
867
+ default:
868
+ return false;
869
+ }
870
+ };
871
+ var resolveWriteTarget = async (workingDir, group) => {
872
+ const globalPath = resolveConfigPath();
873
+ const projectPath = resolveProjectConfigPath(workingDir);
874
+ const [globalConfig, projectConfig] = await Promise.all([
875
+ (0, import_config_loader.loadConfig)(globalPath),
876
+ (0, import_config_loader.loadConfig)(projectPath)
877
+ ]);
878
+ if (hasDefinedGroup(group, projectConfig)) {
879
+ return { scope: "project", path: projectPath };
880
+ }
881
+ if (hasDefinedGroup(group, globalConfig)) {
882
+ return { scope: "global", path: globalPath };
883
+ }
884
+ const defaultScope = import_config.CONFIG_GROUP_DEFAULT_WRITE_SCOPE[group];
885
+ return defaultScope === "project" ? { scope: "project", path: projectPath } : { scope: "global", path: globalPath };
886
+ };
887
+ var updateModel = async (workingDir, model) => {
888
+ const target = await resolveWriteTarget(workingDir, "model");
889
+ await (0, import_config_loader.updateModelConfig)(target.path, model);
890
+ return target;
891
+ };
892
+ var updateTuiTheme = async (workingDir, theme) => {
893
+ const target = await resolveWriteTarget(workingDir, "tui");
894
+ await (0, import_config_loader.updateTuiConfig)(target.path, { theme });
895
+ return target;
896
+ };
816
897
  var resolveReasoningEffort = (value) => {
817
898
  return resolveModelLevelOption(value, "model.reasoning");
818
899
  };
@@ -961,6 +1042,14 @@ var sendRunContext = (runId, contextLeftPercent) => {
961
1042
  };
962
1043
  send(notify);
963
1044
  };
1045
+ var sendRunDiagnostics = (params) => {
1046
+ const notify = {
1047
+ jsonrpc: "2.0",
1048
+ method: "run.diagnostics",
1049
+ params
1050
+ };
1051
+ send(notify);
1052
+ };
964
1053
 
965
1054
  // src/rpc/ui-requests.ts
966
1055
  var requestUi = async (state, method, params) => {
@@ -4206,17 +4295,176 @@ ${reason} Use offset to read beyond line ${lastReadLine}.`;
4206
4295
  }
4207
4296
  });
4208
4297
 
4209
- // src/tools/skill-load.ts
4298
+ // src/tools/search.ts
4210
4299
  var import_core11 = require("@codelia/core");
4211
4300
  var import_zod10 = require("zod");
4212
- var SkillLoadInputSchema = import_zod10.z.object({
4213
- name: import_zod10.z.string().min(1).optional().describe("Exact skill name to load."),
4214
- path: import_zod10.z.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
4301
+ var normalizeDomain = (value) => value.trim().toLowerCase().replace(/\.+$/, "");
4302
+ var shouldKeepUrl = (url, allowedDomains) => {
4303
+ if (!allowedDomains.length) return true;
4304
+ try {
4305
+ const parsed = new URL(url);
4306
+ const host = parsed.hostname.toLowerCase();
4307
+ return allowedDomains.some((domain) => {
4308
+ const normalized = normalizeDomain(domain);
4309
+ return host === normalized || host.endsWith(`.${normalized}`);
4310
+ });
4311
+ } catch {
4312
+ return false;
4313
+ }
4314
+ };
4315
+ var normalizeEntries = (entries, allowedDomains, maxResults) => {
4316
+ return entries.filter((entry) => shouldKeepUrl(entry.url, allowedDomains)).slice(0, maxResults);
4317
+ };
4318
+ var parseDdg = (payload) => {
4319
+ const results = [];
4320
+ if (!payload || typeof payload !== "object") {
4321
+ return results;
4322
+ }
4323
+ const record = payload;
4324
+ const related = Array.isArray(record.RelatedTopics) ? record.RelatedTopics : [];
4325
+ for (const topic of related) {
4326
+ if (!topic || typeof topic !== "object") {
4327
+ continue;
4328
+ }
4329
+ const typed = topic;
4330
+ if (Array.isArray(typed.Topics)) {
4331
+ for (const child of typed.Topics) {
4332
+ if (!child || typeof child !== "object") continue;
4333
+ const row = child;
4334
+ const text2 = typeof row.Text === "string" ? row.Text : "";
4335
+ const url2 = typeof row.FirstURL === "string" ? row.FirstURL : "";
4336
+ if (!text2 || !url2) continue;
4337
+ results.push({
4338
+ title: text2.split(" - ")[0] ?? text2,
4339
+ url: url2,
4340
+ snippet: text2,
4341
+ source: "ddg"
4342
+ });
4343
+ }
4344
+ continue;
4345
+ }
4346
+ const text = typeof typed.Text === "string" ? typed.Text : "";
4347
+ const url = typeof typed.FirstURL === "string" ? typed.FirstURL : "";
4348
+ if (!text || !url) continue;
4349
+ results.push({
4350
+ title: text.split(" - ")[0] ?? text,
4351
+ url,
4352
+ snippet: text,
4353
+ source: "ddg"
4354
+ });
4355
+ }
4356
+ return results;
4357
+ };
4358
+ var parseBrave = (payload) => {
4359
+ const results = [];
4360
+ if (!payload || typeof payload !== "object") {
4361
+ return results;
4362
+ }
4363
+ const record = payload;
4364
+ const web = record.web;
4365
+ if (!web || typeof web !== "object") {
4366
+ return results;
4367
+ }
4368
+ const rows = Array.isArray(web.results) ? web.results : [];
4369
+ for (const row of rows) {
4370
+ if (!row || typeof row !== "object") continue;
4371
+ const typed = row;
4372
+ const title = typeof typed.title === "string" ? typed.title : "";
4373
+ const url = typeof typed.url === "string" ? typed.url : "";
4374
+ const snippet = typeof typed.description === "string" ? typed.description : "";
4375
+ if (!title || !url) continue;
4376
+ results.push({
4377
+ title,
4378
+ url,
4379
+ snippet,
4380
+ source: "brave"
4381
+ });
4382
+ }
4383
+ return results;
4384
+ };
4385
+ var runDdgSearch = async (query, maxResults, allowedDomains) => {
4386
+ const url = new URL("https://api.duckduckgo.com/");
4387
+ url.searchParams.set("q", query);
4388
+ url.searchParams.set("format", "json");
4389
+ url.searchParams.set("no_html", "1");
4390
+ url.searchParams.set("skip_disambig", "1");
4391
+ const response = await fetch(url);
4392
+ if (!response.ok) {
4393
+ throw new Error(`ddg request failed: status=${response.status}`);
4394
+ }
4395
+ const payload = await response.json();
4396
+ const parsed = parseDdg(payload);
4397
+ return normalizeEntries(parsed, allowedDomains, maxResults);
4398
+ };
4399
+ var runBraveSearch = async (query, maxResults, allowedDomains, apiKey) => {
4400
+ const url = new URL("https://api.search.brave.com/res/v1/web/search");
4401
+ url.searchParams.set("q", query);
4402
+ url.searchParams.set("count", String(maxResults));
4403
+ const response = await fetch(url, {
4404
+ headers: {
4405
+ Accept: "application/json",
4406
+ "X-Subscription-Token": apiKey
4407
+ }
4408
+ });
4409
+ if (!response.ok) {
4410
+ throw new Error(`brave request failed: status=${response.status}`);
4411
+ }
4412
+ const payload = await response.json();
4413
+ const parsed = parseBrave(payload);
4414
+ return normalizeEntries(parsed, allowedDomains, maxResults);
4415
+ };
4416
+ var createSearchTool = (options) => (0, import_core11.defineTool)({
4417
+ name: "search",
4418
+ description: "Search the web and return concise source candidates.",
4419
+ input: import_zod10.z.object({
4420
+ query: import_zod10.z.string().min(1).describe("Search query."),
4421
+ max_results: import_zod10.z.number().int().min(1).max(20).optional().describe("Max results. Default 5."),
4422
+ backend: import_zod10.z.enum(["ddg", "brave"]).optional().describe("Search backend. Default comes from config."),
4423
+ allowed_domains: import_zod10.z.array(import_zod10.z.string().min(1)).optional().describe("Optional allowlist of domains.")
4424
+ }),
4425
+ execute: async (input) => {
4426
+ const backend = input.backend ?? options.defaultBackend;
4427
+ const maxResults = input.max_results ?? 5;
4428
+ const allowedDomains = input.allowed_domains?.map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
4429
+ try {
4430
+ let results;
4431
+ if (backend === "ddg") {
4432
+ results = await runDdgSearch(input.query, maxResults, allowedDomains);
4433
+ } else {
4434
+ const apiKey = process.env[options.braveApiKeyEnv]?.trim();
4435
+ if (!apiKey) {
4436
+ return `Missing ${options.braveApiKeyEnv} for brave backend.`;
4437
+ }
4438
+ results = await runBraveSearch(
4439
+ input.query,
4440
+ maxResults,
4441
+ allowedDomains,
4442
+ apiKey
4443
+ );
4444
+ }
4445
+ return {
4446
+ query: input.query,
4447
+ backend,
4448
+ count: results.length,
4449
+ results
4450
+ };
4451
+ } catch (error) {
4452
+ return `Error running search: ${String(error)}`;
4453
+ }
4454
+ }
4455
+ });
4456
+
4457
+ // src/tools/skill-load.ts
4458
+ var import_core12 = require("@codelia/core");
4459
+ var import_zod11 = require("zod");
4460
+ var SkillLoadInputSchema = import_zod11.z.object({
4461
+ name: import_zod11.z.string().min(1).optional().describe("Exact skill name to load."),
4462
+ path: import_zod11.z.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
4215
4463
  }).refine((value) => !!value.name || !!value.path, {
4216
4464
  message: "name or path is required",
4217
4465
  path: ["name"]
4218
4466
  });
4219
- var createSkillLoadTool = (skillsResolverKey) => (0, import_core11.defineTool)({
4467
+ var createSkillLoadTool = (skillsResolverKey) => (0, import_core12.defineTool)({
4220
4468
  name: "skill_load",
4221
4469
  description: "Load full SKILL.md content by exact name or path.",
4222
4470
  input: SkillLoadInputSchema,
@@ -4262,15 +4510,15 @@ var createSkillLoadTool = (skillsResolverKey) => (0, import_core11.defineTool)({
4262
4510
  });
4263
4511
 
4264
4512
  // src/tools/skill-search.ts
4265
- var import_core12 = require("@codelia/core");
4266
- var import_zod11 = require("zod");
4267
- var createSkillSearchTool = (skillsResolverKey) => (0, import_core12.defineTool)({
4513
+ var import_core13 = require("@codelia/core");
4514
+ var import_zod12 = require("zod");
4515
+ var createSkillSearchTool = (skillsResolverKey) => (0, import_core13.defineTool)({
4268
4516
  name: "skill_search",
4269
4517
  description: "Search installed local skills by name, description, or path.",
4270
- input: import_zod11.z.object({
4271
- query: import_zod11.z.string().min(1).describe("Search query text."),
4272
- limit: import_zod11.z.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
4273
- scope: import_zod11.z.enum(["repo", "user"]).optional().describe("Optional scope filter.")
4518
+ input: import_zod12.z.object({
4519
+ query: import_zod12.z.string().min(1).describe("Search query text."),
4520
+ limit: import_zod12.z.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
4521
+ scope: import_zod12.z.enum(["repo", "user"]).optional().describe("Optional scope filter.")
4274
4522
  }),
4275
4523
  execute: async (input, ctx) => {
4276
4524
  try {
@@ -4293,17 +4541,17 @@ var createSkillSearchTool = (skillsResolverKey) => (0, import_core12.defineTool)
4293
4541
  });
4294
4542
 
4295
4543
  // src/tools/todo-read.ts
4296
- var import_core13 = require("@codelia/core");
4297
- var import_zod12 = require("zod");
4544
+ var import_core14 = require("@codelia/core");
4545
+ var import_zod13 = require("zod");
4298
4546
 
4299
4547
  // src/tools/todo-store.ts
4300
4548
  var todoStore = /* @__PURE__ */ new Map();
4301
4549
 
4302
4550
  // src/tools/todo-read.ts
4303
- var createTodoReadTool = (sandboxKey) => (0, import_core13.defineTool)({
4551
+ var createTodoReadTool = (sandboxKey) => (0, import_core14.defineTool)({
4304
4552
  name: "todo_read",
4305
4553
  description: "Read the in-session todo list.",
4306
- input: import_zod12.z.object({}),
4554
+ input: import_zod13.z.object({}),
4307
4555
  execute: async (_input, ctx) => {
4308
4556
  const sandbox = await getSandboxContext(ctx, sandboxKey);
4309
4557
  const todos = todoStore.get(sandbox.sessionId) ?? [];
@@ -4316,17 +4564,17 @@ var createTodoReadTool = (sandboxKey) => (0, import_core13.defineTool)({
4316
4564
  });
4317
4565
 
4318
4566
  // src/tools/todo-write.ts
4319
- var import_core14 = require("@codelia/core");
4320
- var import_zod13 = require("zod");
4321
- var createTodoWriteTool = (sandboxKey) => (0, import_core14.defineTool)({
4567
+ var import_core15 = require("@codelia/core");
4568
+ var import_zod14 = require("zod");
4569
+ var createTodoWriteTool = (sandboxKey) => (0, import_core15.defineTool)({
4322
4570
  name: "todo_write",
4323
4571
  description: "Replace the in-session todo list.",
4324
- input: import_zod13.z.object({
4325
- todos: import_zod13.z.array(
4326
- import_zod13.z.object({
4327
- content: import_zod13.z.string().describe("Todo item text."),
4328
- status: import_zod13.z.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
4329
- activeForm: import_zod13.z.string().optional().describe("Optional in-progress phrasing for UI display.")
4572
+ input: import_zod14.z.object({
4573
+ todos: import_zod14.z.array(
4574
+ import_zod14.z.object({
4575
+ content: import_zod14.z.string().describe("Todo item text."),
4576
+ status: import_zod14.z.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
4577
+ activeForm: import_zod14.z.string().optional().describe("Optional in-progress phrasing for UI display.")
4330
4578
  })
4331
4579
  )
4332
4580
  }),
@@ -4343,15 +4591,15 @@ var createTodoWriteTool = (sandboxKey) => (0, import_core14.defineTool)({
4343
4591
  });
4344
4592
 
4345
4593
  // src/tools/tool-output-cache.ts
4346
- var import_core15 = require("@codelia/core");
4347
- var import_zod14 = require("zod");
4348
- var createToolOutputCacheTool = (store) => (0, import_core15.defineTool)({
4594
+ var import_core16 = require("@codelia/core");
4595
+ var import_zod15 = require("zod");
4596
+ var createToolOutputCacheTool = (store) => (0, import_core16.defineTool)({
4349
4597
  name: "tool_output_cache",
4350
4598
  description: "Read cached tool output by ref_id.",
4351
- input: import_zod14.z.object({
4352
- ref_id: import_zod14.z.string().describe("Tool output reference ID."),
4353
- offset: import_zod14.z.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
4354
- limit: import_zod14.z.number().int().positive().optional().describe("Optional max number of lines to return.")
4599
+ input: import_zod15.z.object({
4600
+ ref_id: import_zod15.z.string().describe("Tool output reference ID."),
4601
+ offset: import_zod15.z.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
4602
+ limit: import_zod15.z.number().int().positive().optional().describe("Optional max number of lines to return.")
4355
4603
  }),
4356
4604
  execute: async (input) => {
4357
4605
  if (!store.read) {
@@ -4367,16 +4615,16 @@ var createToolOutputCacheTool = (store) => (0, import_core15.defineTool)({
4367
4615
  }
4368
4616
  }
4369
4617
  });
4370
- var createToolOutputCacheGrepTool = (store) => (0, import_core15.defineTool)({
4618
+ var createToolOutputCacheGrepTool = (store) => (0, import_core16.defineTool)({
4371
4619
  name: "tool_output_cache_grep",
4372
4620
  description: "Search cached tool output by ref_id.",
4373
- input: import_zod14.z.object({
4374
- ref_id: import_zod14.z.string().describe("Tool output reference ID."),
4375
- pattern: import_zod14.z.string().describe("Text or regex pattern to search for."),
4376
- regex: import_zod14.z.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
4377
- before: import_zod14.z.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
4378
- after: import_zod14.z.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
4379
- max_matches: import_zod14.z.number().int().positive().optional().describe("Maximum number of matches to return.")
4621
+ input: import_zod15.z.object({
4622
+ ref_id: import_zod15.z.string().describe("Tool output reference ID."),
4623
+ pattern: import_zod15.z.string().describe("Text or regex pattern to search for."),
4624
+ regex: import_zod15.z.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
4625
+ before: import_zod15.z.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
4626
+ after: import_zod15.z.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
4627
+ max_matches: import_zod15.z.number().int().positive().optional().describe("Maximum number of matches to return.")
4380
4628
  }),
4381
4629
  execute: async (input) => {
4382
4630
  if (!store.grep) {
@@ -4399,14 +4647,14 @@ var createToolOutputCacheGrepTool = (store) => (0, import_core15.defineTool)({
4399
4647
  // src/tools/write.ts
4400
4648
  var import_node_fs13 = require("fs");
4401
4649
  var import_node_path12 = __toESM(require("path"), 1);
4402
- var import_core16 = require("@codelia/core");
4403
- var import_zod15 = require("zod");
4404
- var createWriteTool = (sandboxKey) => (0, import_core16.defineTool)({
4650
+ var import_core17 = require("@codelia/core");
4651
+ var import_zod16 = require("zod");
4652
+ var createWriteTool = (sandboxKey) => (0, import_core17.defineTool)({
4405
4653
  name: "write",
4406
4654
  description: "Write text to a file, creating parent directories if needed.",
4407
- input: import_zod15.z.object({
4408
- file_path: import_zod15.z.string().describe("File path under the sandbox root."),
4409
- content: import_zod15.z.string().describe("UTF-8 text content to write.")
4655
+ input: import_zod16.z.object({
4656
+ file_path: import_zod16.z.string().describe("File path under the sandbox root."),
4657
+ content: import_zod16.z.string().describe("UTF-8 text content to write.")
4410
4658
  }),
4411
4659
  execute: async (input, ctx) => {
4412
4660
  let resolved;
@@ -4435,6 +4683,7 @@ var createTools = (sandboxKey, agentsResolverKey, skillsResolverKey, options = {
4435
4683
  createAgentsResolveTool(sandboxKey, agentsResolverKey),
4436
4684
  createSkillSearchTool(skillsResolverKey),
4437
4685
  createSkillLoadTool(skillsResolverKey),
4686
+ ...options.search ? [createSearchTool(options.search)] : [],
4438
4687
  createGlobSearchTool(sandboxKey),
4439
4688
  createGrepTool(sandboxKey),
4440
4689
  ...options.toolOutputCacheStore ? [
@@ -4483,9 +4732,9 @@ var normalizeLanguage = (value) => {
4483
4732
  };
4484
4733
  var languageFromFilePath = (filePath) => {
4485
4734
  if (!filePath) return void 0;
4486
- const path16 = filePath.trim().replace(/^["']|["']$/g, "");
4487
- if (!path16 || path16 === "/dev/null") return void 0;
4488
- const normalizedPath = path16.replace(/^a\//, "").replace(/^b\//, "");
4735
+ const path17 = filePath.trim().replace(/^["']|["']$/g, "");
4736
+ if (!path17 || path17 === "/dev/null") return void 0;
4737
+ const normalizedPath = path17.replace(/^a\//, "").replace(/^b\//, "");
4489
4738
  const lastSlash = Math.max(
4490
4739
  normalizedPath.lastIndexOf("/"),
4491
4740
  normalizedPath.lastIndexOf("\\")
@@ -4556,6 +4805,25 @@ var envTruthy = (value) => {
4556
4805
  return normalized === "1" || normalized === "true";
4557
4806
  };
4558
4807
  var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
4808
+ var isNativeSearchProvider = (provider, allowedProviders) => allowedProviders.includes(provider);
4809
+ var buildHostedSearchToolDefinitions = (provider, options) => {
4810
+ if (options.mode === "local" || !isNativeSearchProvider(provider, options.native.providers)) {
4811
+ return [];
4812
+ }
4813
+ if (provider !== "openai" && provider !== "anthropic") {
4814
+ return [];
4815
+ }
4816
+ return [
4817
+ {
4818
+ type: "hosted_search",
4819
+ name: "web_search",
4820
+ provider,
4821
+ ...options.native.searchContextSize ? { search_context_size: options.native.searchContextSize } : {},
4822
+ ...options.native.allowedDomains ? { allowed_domains: options.native.allowedDomains } : {},
4823
+ ...options.native.userLocation ? { user_location: options.native.userLocation } : {}
4824
+ }
4825
+ ];
4826
+ };
4559
4827
  var buildOpenAiClientOptions = (authResolver, auth) => {
4560
4828
  if (auth.method === "api_key") {
4561
4829
  return { apiKey: auth.api_key };
@@ -4792,7 +5060,7 @@ var createAgentFactory = (state, options = {}) => {
4792
5060
  const agentsResolverKey = createAgentsResolverKey(agentsResolver);
4793
5061
  const skillsResolverKey = createSkillsResolverKey(skillsResolver);
4794
5062
  const toolOutputCacheStore = new import_storage5.ToolOutputCacheStoreImpl();
4795
- const localTools = createTools(
5063
+ const baseLocalTools = createTools(
4796
5064
  sandboxKey,
4797
5065
  agentsResolverKey,
4798
5066
  skillsResolverKey,
@@ -4800,7 +5068,7 @@ var createAgentFactory = (state, options = {}) => {
4800
5068
  toolOutputCacheStore
4801
5069
  }
4802
5070
  );
4803
- const editTool = localTools.find(
5071
+ const editTool = baseLocalTools.find(
4804
5072
  (tool) => tool.definition.name === "edit"
4805
5073
  );
4806
5074
  let mcpTools = [];
@@ -4823,9 +5091,6 @@ var createAgentFactory = (state, options = {}) => {
4823
5091
  (0, import_logger.log)(`failed to load mcp tools: ${String(error)}`);
4824
5092
  }
4825
5093
  }
4826
- const tools = [...localTools, ...mcpTools];
4827
- state.tools = tools;
4828
- state.toolDefinitions = tools.map((tool) => tool.definition);
4829
5094
  const baseSystemPrompt = await loadSystemPrompt(ctx.workingDir);
4830
5095
  const withAgentsContext = appendInitialAgentsContext(
4831
5096
  baseSystemPrompt,
@@ -4861,12 +5126,35 @@ var createAgentFactory = (state, options = {}) => {
4861
5126
  const authResolver = await AuthResolver.create(state, import_logger.log);
4862
5127
  const provider = await authResolver.resolveProvider(modelConfig.provider);
4863
5128
  const providerAuth = await authResolver.resolveProviderAuth(provider);
5129
+ const searchConfig = await resolveSearchConfig(ctx.workingDir);
5130
+ const hostedSearchDefinitions = buildHostedSearchToolDefinitions(
5131
+ provider,
5132
+ searchConfig
5133
+ );
5134
+ if (searchConfig.mode === "native" && hostedSearchDefinitions.length === 0) {
5135
+ throw new Error(
5136
+ `search.mode=native is enabled, but native search is unavailable for provider '${provider}'.`
5137
+ );
5138
+ }
5139
+ const useLocalSearchTool = searchConfig.mode === "local" || searchConfig.mode === "auto" && hostedSearchDefinitions.length === 0;
5140
+ const localSearchTools = useLocalSearchTool ? [
5141
+ createSearchTool({
5142
+ defaultBackend: searchConfig.local.backend,
5143
+ braveApiKeyEnv: searchConfig.local.braveApiKeyEnv
5144
+ })
5145
+ ] : [];
5146
+ const tools = [...baseLocalTools, ...localSearchTools, ...mcpTools];
5147
+ state.tools = tools;
5148
+ state.toolDefinitions = [
5149
+ ...tools.map((tool) => tool.definition),
5150
+ ...hostedSearchDefinitions
5151
+ ];
4864
5152
  let llm;
4865
5153
  switch (provider) {
4866
5154
  case "openai": {
4867
5155
  const reasoningEffort = resolveReasoningEffort(modelConfig.reasoning);
4868
5156
  const textVerbosity = resolveTextVerbosity(modelConfig.verbosity);
4869
- llm = new import_core17.ChatOpenAI({
5157
+ llm = new import_core18.ChatOpenAI({
4870
5158
  clientOptions: buildOpenAiClientOptions(authResolver, providerAuth),
4871
5159
  ...modelConfig.name ? { model: modelConfig.name } : {},
4872
5160
  ...reasoningEffort ? { reasoningEffort } : {},
@@ -4877,7 +5165,7 @@ var createAgentFactory = (state, options = {}) => {
4877
5165
  case "openrouter": {
4878
5166
  const reasoningEffort = resolveReasoningEffort(modelConfig.reasoning);
4879
5167
  const textVerbosity = resolveTextVerbosity(modelConfig.verbosity);
4880
- llm = new import_core17.ChatOpenAI({
5168
+ llm = new import_core18.ChatOpenAI({
4881
5169
  clientOptions: buildOpenRouterClientOptions(providerAuth),
4882
5170
  ...modelConfig.name ? { model: modelConfig.name } : {},
4883
5171
  ...reasoningEffort ? { reasoningEffort } : {},
@@ -4886,7 +5174,7 @@ var createAgentFactory = (state, options = {}) => {
4886
5174
  break;
4887
5175
  }
4888
5176
  case "anthropic": {
4889
- llm = new import_core17.ChatAnthropic({
5177
+ llm = new import_core18.ChatAnthropic({
4890
5178
  clientOptions: {
4891
5179
  apiKey: requireApiKeyAuth("Anthropic", providerAuth)
4892
5180
  },
@@ -4900,11 +5188,18 @@ var createAgentFactory = (state, options = {}) => {
4900
5188
  const modelRegistry = await buildModelRegistry(llm, {
4901
5189
  strict: provider !== "openrouter"
4902
5190
  });
4903
- const agent = new import_core17.Agent({
5191
+ const totalBudgetTrimEnabled = envTruthy(
5192
+ process.env.CODELIA_TOOL_OUTPUT_TOTAL_TRIM
5193
+ );
5194
+ const agent = new import_core18.Agent({
4904
5195
  llm,
4905
5196
  tools,
5197
+ hostedTools: hostedSearchDefinitions,
4906
5198
  systemPrompt,
4907
- modelRegistry: modelRegistry ?? import_core17.DEFAULT_MODEL_REGISTRY,
5199
+ modelRegistry: modelRegistry ?? import_core18.DEFAULT_MODEL_REGISTRY,
5200
+ toolOutputCache: {
5201
+ totalBudgetTrim: totalBudgetTrimEnabled
5202
+ },
4908
5203
  services: { toolOutputCacheStore },
4909
5204
  canExecuteTool: async (call, rawArgs, toolCtx) => {
4910
5205
  const decision = permissionService.evaluate(
@@ -5606,8 +5901,8 @@ var parseAuthorizationServerMetadata = (value) => {
5606
5901
  var buildAuthorizationServerMetadataUrl = (issuer) => {
5607
5902
  try {
5608
5903
  const parsed = new URL(issuer);
5609
- const path16 = parsed.pathname === "/" ? "" : parsed.pathname;
5610
- const basePath = path16.startsWith("/") ? path16 : `/${path16}`;
5904
+ const path17 = parsed.pathname === "/" ? "" : parsed.pathname;
5905
+ const basePath = path17.startsWith("/") ? path17 : `/${path17}`;
5611
5906
  const metadataPath = `/.well-known/oauth-authorization-server${basePath}`;
5612
5907
  return new URL(metadataPath, `${parsed.origin}/`).toString();
5613
5908
  } catch {
@@ -6264,9 +6559,8 @@ var McpManager = class {
6264
6559
  };
6265
6560
 
6266
6561
  // src/rpc/handlers.ts
6267
- var import_config_loader3 = require("@codelia/config-loader");
6268
- var import_protocol8 = require("@codelia/protocol");
6269
- var import_storage8 = require("@codelia/storage");
6562
+ var import_protocol9 = require("@codelia/protocol");
6563
+ var import_storage9 = require("@codelia/storage");
6270
6564
 
6271
6565
  // src/constants.ts
6272
6566
  var SERVER_NAME = "codelia-runtime";
@@ -6642,8 +6936,7 @@ var createHistoryHandlers = ({
6642
6936
  };
6643
6937
 
6644
6938
  // src/rpc/model.ts
6645
- var import_config_loader2 = require("@codelia/config-loader");
6646
- var import_core18 = require("@codelia/core");
6939
+ var import_core19 = require("@codelia/core");
6647
6940
  var import_model_metadata2 = require("@codelia/model-metadata");
6648
6941
  var import_protocol4 = require("@codelia/protocol");
6649
6942
  var isSupportedProvider = (provider) => provider === "openai" || provider === "anthropic" || provider === "openrouter";
@@ -6887,7 +7180,7 @@ var buildProviderModelList = async ({
6887
7180
  }
6888
7181
  }
6889
7182
  const models = sortModelsByReleaseDate(
6890
- (0, import_core18.listModels)(import_core18.DEFAULT_MODEL_REGISTRY, provider).map((model) => model.id),
7183
+ (0, import_core19.listModels)(import_core19.DEFAULT_MODEL_REGISTRY, provider).map((model) => model.id),
6891
7184
  provider,
6892
7185
  providerEntries
6893
7186
  );
@@ -6984,19 +7277,19 @@ var createModelHandlers = ({
6984
7277
  return;
6985
7278
  }
6986
7279
  if (provider !== "openrouter") {
6987
- const spec = (0, import_core18.resolveModel)(import_core18.DEFAULT_MODEL_REGISTRY, name, provider);
7280
+ const spec = (0, import_core19.resolveModel)(import_core19.DEFAULT_MODEL_REGISTRY, name, provider);
6988
7281
  if (!spec) {
6989
7282
  sendError(id, { code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS, message: `unknown model: ${name}` });
6990
7283
  return;
6991
7284
  }
6992
7285
  }
6993
7286
  try {
6994
- const configPath = resolveConfigPath();
6995
- await (0, import_config_loader2.updateModelConfig)(configPath, { provider, name });
7287
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
7288
+ const target = await updateModel(workingDir, { provider, name });
6996
7289
  state.agent = null;
6997
7290
  const result = { provider, name };
6998
7291
  sendResult(id, result);
6999
- log2(`model.set ${provider}/${name}`);
7292
+ log2(`model.set ${provider}/${name} scope=${target.scope} path=${target.path}`);
7000
7293
  } catch (error) {
7001
7294
  sendError(id, { code: import_protocol4.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
7002
7295
  }
@@ -7008,7 +7301,7 @@ var createModelHandlers = ({
7008
7301
  var import_protocol5 = require("@codelia/protocol");
7009
7302
 
7010
7303
  // src/rpc/run-debug.ts
7011
- var import_core19 = require("@codelia/core");
7304
+ var import_core20 = require("@codelia/core");
7012
7305
  var DEBUG_MAX_CONTENT_CHARS = 2e3;
7013
7306
  var DEBUG_MAX_LOG_CHARS = 2e4;
7014
7307
  var DEBUG_MAX_EVENT_RESULT_CHARS = 500;
@@ -7025,7 +7318,7 @@ var stringifyUnknown = (value) => {
7025
7318
  }
7026
7319
  };
7027
7320
  var contentToDebugText = (content, maxChars) => {
7028
- const text = (0, import_core19.stringifyContent)(content, {
7321
+ const text = (0, import_core20.stringifyContent)(content, {
7029
7322
  mode: "log",
7030
7323
  joiner: "\n",
7031
7324
  includeOtherPayload: true
@@ -7307,6 +7600,28 @@ var buildSessionState = (sessionId, runId, messages, invokeSeq) => ({
7307
7600
  invoke_seq: invokeSeq,
7308
7601
  messages
7309
7602
  });
7603
+ var toTimestampMs = (value) => {
7604
+ const ms = Date.parse(value);
7605
+ return Number.isFinite(ms) ? ms : null;
7606
+ };
7607
+ var summarizeProviderMeta = (value) => {
7608
+ if (value === null || value === void 0) {
7609
+ return null;
7610
+ }
7611
+ if (typeof value === "string") {
7612
+ return value.length > 80 ? `${value.slice(0, 77)}...` : value;
7613
+ }
7614
+ if (Array.isArray(value)) {
7615
+ return `array(len=${value.length})`;
7616
+ }
7617
+ if (typeof value === "object") {
7618
+ const keys = Object.keys(value);
7619
+ if (keys.length === 0) return "object";
7620
+ const shown = keys.slice(0, 4).join(",");
7621
+ return keys.length > 4 ? `object(keys=${shown},...)` : `object(keys=${shown})`;
7622
+ }
7623
+ return typeof value;
7624
+ };
7310
7625
  var createRunHandlers = ({
7311
7626
  state,
7312
7627
  getAgent,
@@ -7440,12 +7755,77 @@ var createRunHandlers = ({
7440
7755
  const runAbortController = new AbortController();
7441
7756
  activeRunAbort = { runId, controller: runAbortController };
7442
7757
  const sessionStore = runEventStoreFactory.create({ runId, startedAt });
7443
- const sessionAppend = createSessionAppender(
7758
+ const sessionAppenderRaw = createSessionAppender(
7444
7759
  sessionStore,
7445
7760
  (error, record) => {
7446
7761
  log2(`session-store error (${record.type}): ${String(error)}`);
7447
7762
  }
7448
7763
  );
7764
+ const pendingLlmRequests = /* @__PURE__ */ new Map();
7765
+ const emitRunDiagnostics = (params2) => {
7766
+ if (!state.diagnosticsEnabled) return;
7767
+ try {
7768
+ sendRunDiagnostics(params2);
7769
+ } catch (error) {
7770
+ log2(`run diagnostics emit failed: ${String(error)}`);
7771
+ }
7772
+ };
7773
+ const sessionAppend = (record) => {
7774
+ if (state.diagnosticsEnabled) {
7775
+ try {
7776
+ if (record.type === "llm.request") {
7777
+ const modelName = record.model?.name ?? record.input.model ?? "unknown";
7778
+ pendingLlmRequests.set(record.seq, {
7779
+ ts: record.ts,
7780
+ provider: record.model?.provider,
7781
+ model: modelName
7782
+ });
7783
+ }
7784
+ if (record.type === "llm.response") {
7785
+ const request = pendingLlmRequests.get(record.seq);
7786
+ pendingLlmRequests.delete(record.seq);
7787
+ const usage = record.output.usage ?? null;
7788
+ const cacheReadTokens = usage?.input_cached_tokens ?? 0;
7789
+ const cacheCreationTokens = usage?.input_cache_creation_tokens ?? 0;
7790
+ const inputTokens = usage?.input_tokens ?? 0;
7791
+ const hitState = usage ? cacheReadTokens > 0 ? "hit" : "miss" : "unknown";
7792
+ const responseTsMs = toTimestampMs(record.ts);
7793
+ const requestTsMs = request ? toTimestampMs(request.ts) : null;
7794
+ const latencyMs = responseTsMs !== null && requestTsMs !== null ? Math.max(0, responseTsMs - requestTsMs) : 0;
7795
+ const model = usage?.model ?? request?.model ?? "unknown";
7796
+ const diagnostics = {
7797
+ run_id: runId,
7798
+ seq: record.seq,
7799
+ ...request?.provider ? { provider: request.provider } : {},
7800
+ model,
7801
+ request_ts: request?.ts ?? record.ts,
7802
+ response_ts: record.ts,
7803
+ latency_ms: latencyMs,
7804
+ stop_reason: record.output.stop_reason ?? null,
7805
+ usage,
7806
+ cache: {
7807
+ hit_state: hitState,
7808
+ cache_read_tokens: cacheReadTokens,
7809
+ cache_creation_tokens: cacheCreationTokens,
7810
+ cache_read_ratio: usage ? cacheReadTokens / Math.max(inputTokens, 1) : null
7811
+ },
7812
+ cost_usd: null,
7813
+ provider_meta_summary: summarizeProviderMeta(
7814
+ record.output.provider_meta
7815
+ )
7816
+ };
7817
+ emitRunDiagnostics({
7818
+ run_id: runId,
7819
+ kind: "llm_call",
7820
+ call: diagnostics
7821
+ });
7822
+ }
7823
+ } catch (error) {
7824
+ log2(`run diagnostics build failed: ${String(error)}`);
7825
+ }
7826
+ }
7827
+ sessionAppenderRaw(record);
7828
+ };
7449
7829
  state.sessionAppend = sessionAppend;
7450
7830
  const session = {
7451
7831
  run_id: runId,
@@ -7498,6 +7878,13 @@ var createRunHandlers = ({
7498
7878
  let finalResponse;
7499
7879
  let sessionSaveChain = Promise.resolve();
7500
7880
  let lastSessionSaveAt = 0;
7881
+ const emitRunSummaryDiagnostics = () => {
7882
+ emitRunDiagnostics({
7883
+ run_id: runId,
7884
+ kind: "run_summary",
7885
+ summary: runtimeAgent.getUsageSummary()
7886
+ });
7887
+ };
7501
7888
  const queueSessionSave = async (reason) => {
7502
7889
  sessionSaveChain = sessionSaveChain.then(async () => {
7503
7890
  if (!sessionId) return;
@@ -7592,6 +7979,7 @@ var createRunHandlers = ({
7592
7979
  normalizeRunHistoryAfterCancel(runId, runtimeAgent);
7593
7980
  }
7594
7981
  await queueSessionSave("terminal");
7982
+ emitRunSummaryDiagnostics();
7595
7983
  const status = state.cancelRequested ? "cancelled" : "completed";
7596
7984
  emitRunStatus(runId, status);
7597
7985
  emitRunEnd(
@@ -7604,11 +7992,13 @@ var createRunHandlers = ({
7604
7992
  if (state.cancelRequested || isAbortLikeError(err)) {
7605
7993
  normalizeRunHistoryAfterCancel(runId, runtimeAgent);
7606
7994
  await queueSessionSave("cancelled");
7995
+ emitRunSummaryDiagnostics();
7607
7996
  emitRunStatus(runId, "cancelled", err.message || "cancelled");
7608
7997
  emitRunEnd(runId, "cancelled", finalResponse);
7609
7998
  return;
7610
7999
  }
7611
8000
  await queueSessionSave("error");
8001
+ emitRunSummaryDiagnostics();
7612
8002
  emitRunStatus(runId, "error", err.message);
7613
8003
  appendSession({
7614
8004
  type: "run.error",
@@ -7788,7 +8178,313 @@ var createToolHandlers = ({
7788
8178
  };
7789
8179
  };
7790
8180
 
8181
+ // src/rpc/shell.ts
8182
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
8183
+ var import_node_path15 = __toESM(require("path"), 1);
8184
+ var import_node_child_process5 = require("child_process");
8185
+ var import_storage8 = require("@codelia/storage");
8186
+ var import_protocol8 = require("@codelia/protocol");
8187
+ var DEFAULT_EXCERPT_LINES = 80;
8188
+ var MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
8189
+ var COMMAND_PREVIEW_CHARS = 400;
8190
+ var truncateCommandPreview = (value) => {
8191
+ const trimmed = value.trim();
8192
+ if (trimmed.length <= COMMAND_PREVIEW_CHARS) return trimmed;
8193
+ return `${trimmed.slice(0, COMMAND_PREVIEW_CHARS)}...[truncated]`;
8194
+ };
8195
+ var utf8ByteLength = (value) => Buffer.byteLength(value, "utf8");
8196
+ var truncateUtf8Prefix = (value, maxBytes) => {
8197
+ if (maxBytes <= 0 || value.length === 0) return "";
8198
+ let bytes = 0;
8199
+ let out = "";
8200
+ for (const ch of value) {
8201
+ const next = utf8ByteLength(ch);
8202
+ if (bytes + next > maxBytes) break;
8203
+ out += ch;
8204
+ bytes += next;
8205
+ }
8206
+ return out;
8207
+ };
8208
+ var truncateUtf8Suffix = (value, maxBytes) => {
8209
+ if (maxBytes <= 0 || value.length === 0) return "";
8210
+ let bytes = 0;
8211
+ const chars = Array.from(value);
8212
+ const out = [];
8213
+ for (let idx = chars.length - 1; idx >= 0; idx -= 1) {
8214
+ const ch = chars[idx];
8215
+ const next = utf8ByteLength(ch);
8216
+ if (bytes + next > maxBytes) break;
8217
+ out.push(ch);
8218
+ bytes += next;
8219
+ }
8220
+ out.reverse();
8221
+ return out.join("");
8222
+ };
8223
+ var excerptByLines = (value) => {
8224
+ const lines = value.split(/\r?\n/);
8225
+ if (lines.length <= DEFAULT_EXCERPT_LINES * 2) {
8226
+ return value;
8227
+ }
8228
+ const head = lines.slice(0, DEFAULT_EXCERPT_LINES);
8229
+ const tail = lines.slice(lines.length - DEFAULT_EXCERPT_LINES);
8230
+ const omitted = lines.length - head.length - tail.length;
8231
+ return [...head, `...[${omitted} lines omitted]...`, ...tail].join("\n");
8232
+ };
8233
+ var excerptByBytes = (value, maxBytes) => {
8234
+ if (utf8ByteLength(value) <= maxBytes) return value;
8235
+ const marker = "\n...[truncated by size]...\n";
8236
+ const markerBytes = utf8ByteLength(marker);
8237
+ if (maxBytes <= markerBytes + 2) {
8238
+ return truncateUtf8Prefix(value, maxBytes);
8239
+ }
8240
+ const budget = maxBytes - markerBytes;
8241
+ const headBytes = Math.floor(budget / 2);
8242
+ const tailBytes = budget - headBytes;
8243
+ const head = truncateUtf8Prefix(value, headBytes);
8244
+ const tail = truncateUtf8Suffix(value, tailBytes);
8245
+ return `${head}${marker}${tail}`;
8246
+ };
8247
+ var needsInlineTruncation = (value) => value.split(/\r?\n/).length > DEFAULT_EXCERPT_LINES * 2 || utf8ByteLength(value) > MAX_INLINE_OUTPUT_BYTES;
8248
+ var excerptText = (value) => excerptByBytes(excerptByLines(value), MAX_INLINE_OUTPUT_BYTES);
8249
+ var isWithin2 = (basePath, candidatePath) => {
8250
+ const relative = import_node_path15.default.relative(basePath, candidatePath);
8251
+ return relative === "" || !relative.startsWith("..") && !import_node_path15.default.isAbsolute(relative);
8252
+ };
8253
+ var resolveShellCwd = (state, requestedCwd) => {
8254
+ const workingDir = state.runtimeWorkingDir ?? process.cwd();
8255
+ const rootDir = state.runtimeSandboxRoot ?? workingDir;
8256
+ if (!requestedCwd) return workingDir;
8257
+ const resolved = import_node_path15.default.resolve(workingDir, requestedCwd);
8258
+ if (!isWithin2(rootDir, resolved)) {
8259
+ return null;
8260
+ }
8261
+ return resolved;
8262
+ };
8263
+ var runShellWithUserShell = async (command, options) => {
8264
+ if (process.platform === "win32") {
8265
+ return runShellCommand(command, options);
8266
+ }
8267
+ const shellPath = process.env.SHELL?.trim() || "";
8268
+ if (!shellPath) {
8269
+ return runShellCommand(command, options);
8270
+ }
8271
+ return new Promise((resolve, reject) => {
8272
+ const child = (0, import_node_child_process5.spawn)(shellPath, ["-lc", command], {
8273
+ cwd: options.cwd,
8274
+ stdio: ["ignore", "pipe", "pipe"],
8275
+ signal: options.signal
8276
+ });
8277
+ let stdout = "";
8278
+ let stderr = "";
8279
+ let totalBytes = 0;
8280
+ let settled = false;
8281
+ let timedOut = false;
8282
+ let timeoutHandle;
8283
+ const finish = (handler) => {
8284
+ if (settled) return;
8285
+ settled = true;
8286
+ if (timeoutHandle) clearTimeout(timeoutHandle);
8287
+ handler();
8288
+ };
8289
+ const consumeChunk = (chunk, stream) => {
8290
+ const text = chunk.toString("utf8");
8291
+ totalBytes += Buffer.byteLength(text, "utf8");
8292
+ if (totalBytes > options.maxOutputBytes) {
8293
+ const error = Object.assign(
8294
+ new Error(
8295
+ `Command output exceeded max buffer of ${options.maxOutputBytes} bytes`
8296
+ ),
8297
+ {
8298
+ code: "MAXBUFFER",
8299
+ stdout,
8300
+ stderr,
8301
+ killed: true,
8302
+ signal: "SIGTERM"
8303
+ }
8304
+ );
8305
+ try {
8306
+ child.kill("SIGTERM");
8307
+ } catch {
8308
+ }
8309
+ finish(() => reject(error));
8310
+ return;
8311
+ }
8312
+ if (stream === "stdout") stdout += text;
8313
+ else stderr += text;
8314
+ };
8315
+ child.stdout?.on("data", (chunk) => consumeChunk(chunk, "stdout"));
8316
+ child.stderr?.on("data", (chunk) => consumeChunk(chunk, "stderr"));
8317
+ child.on("error", (error) => {
8318
+ const enriched = Object.assign(error, {
8319
+ stdout,
8320
+ stderr
8321
+ });
8322
+ finish(() => reject(enriched));
8323
+ });
8324
+ child.on("close", (code, signal) => {
8325
+ if (timedOut) {
8326
+ const timeoutError = Object.assign(
8327
+ new Error(
8328
+ `Command timed out after ${Math.trunc(options.timeoutMs / 1e3)}s`
8329
+ ),
8330
+ {
8331
+ code: "ETIMEDOUT",
8332
+ stdout,
8333
+ stderr,
8334
+ killed: true,
8335
+ signal: signal ?? "SIGTERM"
8336
+ }
8337
+ );
8338
+ finish(() => reject(timeoutError));
8339
+ return;
8340
+ }
8341
+ if (code === 0) {
8342
+ finish(() => resolve({ stdout, stderr }));
8343
+ return;
8344
+ }
8345
+ const failure = Object.assign(
8346
+ new Error(
8347
+ `Command failed with exit code ${code ?? "unknown"}${signal ? ` signal ${signal}` : ""}`
8348
+ ),
8349
+ { code, stdout, stderr, killed: false, signal }
8350
+ );
8351
+ finish(() => reject(failure));
8352
+ });
8353
+ timeoutHandle = setTimeout(() => {
8354
+ timedOut = true;
8355
+ try {
8356
+ child.kill("SIGTERM");
8357
+ } catch {
8358
+ }
8359
+ setTimeout(() => {
8360
+ try {
8361
+ if (!child.killed) child.kill("SIGKILL");
8362
+ } catch {
8363
+ }
8364
+ }, 2e3).unref();
8365
+ }, options.timeoutMs);
8366
+ });
8367
+ };
8368
+ var createShellHandlers = ({
8369
+ state,
8370
+ log: log2
8371
+ }) => {
8372
+ const outputCache = new import_storage8.ToolOutputCacheStoreImpl();
8373
+ const handleShellExec = async (id, params) => {
8374
+ const command = params?.command?.trim() ?? "";
8375
+ if (!command) {
8376
+ sendError(id, {
8377
+ code: import_protocol8.RPC_ERROR_CODE.INVALID_PARAMS,
8378
+ message: "command is required"
8379
+ });
8380
+ return;
8381
+ }
8382
+ const requestedTimeout = params?.timeout_seconds ?? DEFAULT_TIMEOUT_SECONDS;
8383
+ if (!Number.isFinite(requestedTimeout) || requestedTimeout <= 0) {
8384
+ sendError(id, {
8385
+ code: import_protocol8.RPC_ERROR_CODE.INVALID_PARAMS,
8386
+ message: "timeout_seconds must be a positive number"
8387
+ });
8388
+ return;
8389
+ }
8390
+ const timeoutSeconds = Math.max(
8391
+ 1,
8392
+ Math.min(Math.trunc(requestedTimeout), MAX_TIMEOUT_SECONDS)
8393
+ );
8394
+ const cwd = resolveShellCwd(state, params?.cwd);
8395
+ if (!cwd) {
8396
+ sendError(id, {
8397
+ code: import_protocol8.RPC_ERROR_CODE.INVALID_PARAMS,
8398
+ message: "cwd is outside sandbox root"
8399
+ });
8400
+ return;
8401
+ }
8402
+ const startedAt = Date.now();
8403
+ const commandSummary = summarizeCommand(command);
8404
+ const commandPreview = truncateCommandPreview(command);
8405
+ log2(
8406
+ `shell.exec.start origin=ui_bang cwd=${cwd} timeout_s=${timeoutSeconds} command="${commandSummary}"`
8407
+ );
8408
+ let rawStdout = "";
8409
+ let rawStderr = "";
8410
+ let exitCode = 0;
8411
+ let signal = null;
8412
+ try {
8413
+ const result2 = await runShellWithUserShell(command, {
8414
+ cwd,
8415
+ timeoutMs: timeoutSeconds * 1e3,
8416
+ maxOutputBytes: MAX_OUTPUT_BYTES
8417
+ });
8418
+ rawStdout = result2.stdout;
8419
+ rawStderr = result2.stderr;
8420
+ } catch (error) {
8421
+ const execError = error;
8422
+ rawStdout = execError.stdout ?? "";
8423
+ rawStderr = execError.stderr ?? "";
8424
+ exitCode = typeof execError.code === "number" ? execError.code : null;
8425
+ signal = execError.signal ?? null;
8426
+ }
8427
+ const stdoutTruncated = needsInlineTruncation(rawStdout);
8428
+ const stderrTruncated = needsInlineTruncation(rawStderr);
8429
+ const stdout = stdoutTruncated ? excerptText(rawStdout) : rawStdout;
8430
+ const stderr = stderrTruncated ? excerptText(rawStderr) : rawStderr;
8431
+ const combinedTruncated = stdoutTruncated || stderrTruncated;
8432
+ let stdoutCacheId;
8433
+ let stderrCacheId;
8434
+ if (stdoutTruncated && rawStdout.trim()) {
8435
+ const saved = await outputCache.save({
8436
+ tool_call_id: `shell_exec_stdout_${import_node_crypto6.default.randomUUID()}`,
8437
+ tool_name: "shell.exec",
8438
+ content: rawStdout
8439
+ });
8440
+ stdoutCacheId = saved.id;
8441
+ }
8442
+ if (stderrTruncated && rawStderr.trim()) {
8443
+ const saved = await outputCache.save({
8444
+ tool_call_id: `shell_exec_stderr_${import_node_crypto6.default.randomUUID()}`,
8445
+ tool_name: "shell.exec",
8446
+ content: rawStderr
8447
+ });
8448
+ stderrCacheId = saved.id;
8449
+ }
8450
+ const result = {
8451
+ command_preview: commandPreview,
8452
+ exit_code: exitCode,
8453
+ signal,
8454
+ stdout,
8455
+ stderr,
8456
+ truncated: {
8457
+ stdout: stdoutTruncated,
8458
+ stderr: stderrTruncated,
8459
+ combined: combinedTruncated
8460
+ },
8461
+ duration_ms: Date.now() - startedAt,
8462
+ ...stdoutCacheId ? { stdout_cache_id: stdoutCacheId } : {},
8463
+ ...stderrCacheId ? { stderr_cache_id: stderrCacheId } : {}
8464
+ };
8465
+ sendResult(id, result);
8466
+ log2(
8467
+ `shell.exec.done origin=ui_bang duration_ms=${result.duration_ms} exit_code=${String(result.exit_code)} signal=${result.signal ?? "-"}`
8468
+ );
8469
+ };
8470
+ return {
8471
+ handleShellExec
8472
+ };
8473
+ };
8474
+
7791
8475
  // src/rpc/handlers.ts
8476
+ var SUPPORTED_TUI_THEMES = /* @__PURE__ */ new Set([
8477
+ "codelia",
8478
+ "ocean",
8479
+ "forest",
8480
+ "rose",
8481
+ "sakura",
8482
+ "mauve",
8483
+ "plum",
8484
+ "iris",
8485
+ "crimson",
8486
+ "wine"
8487
+ ]);
7792
8488
  var createRuntimeHandlers = ({
7793
8489
  state,
7794
8490
  getAgent,
@@ -7798,14 +8494,14 @@ var createRuntimeHandlers = ({
7798
8494
  runEventStoreFactory: injectedRunEventStoreFactory,
7799
8495
  buildProviderModelList: injectedBuildProviderModelList
7800
8496
  }) => {
7801
- const sessionStateStore = injectedSessionStateStore ?? new import_storage8.SessionStateStoreImpl({
8497
+ const sessionStateStore = injectedSessionStateStore ?? new import_storage9.SessionStateStoreImpl({
7802
8498
  onError: (error, context) => {
7803
8499
  log2(
7804
8500
  `session-state ${context.action} error${context.detail ? ` (${context.detail})` : ""}: ${String(error)}`
7805
8501
  );
7806
8502
  }
7807
8503
  });
7808
- const runEventStoreFactory = injectedRunEventStoreFactory ?? new import_storage8.RunEventStoreFactoryImpl();
8504
+ const runEventStoreFactory = injectedRunEventStoreFactory ?? new import_storage9.RunEventStoreFactoryImpl();
7809
8505
  const mcpManager = injectedMcpManager ?? {
7810
8506
  start: async () => void 0,
7811
8507
  list: () => ({ servers: [] })
@@ -7893,10 +8589,15 @@ var createRuntimeHandlers = ({
7893
8589
  log2("startup onboarding skipped (model not selected)");
7894
8590
  return;
7895
8591
  }
7896
- const configPath = resolveConfigPath();
7897
- await (0, import_config_loader3.updateModelConfig)(configPath, { provider, name: selectedModel });
8592
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
8593
+ const modelTarget = await updateModel(workingDir, {
8594
+ provider,
8595
+ name: selectedModel
8596
+ });
7898
8597
  state.agent = null;
7899
- log2(`startup onboarding completed: ${provider}/${selectedModel}`);
8598
+ log2(
8599
+ `startup onboarding completed: ${provider}/${selectedModel} scope=${modelTarget.scope} path=${modelTarget.path}`
8600
+ );
7900
8601
  };
7901
8602
  const launchStartupOnboarding = () => {
7902
8603
  if (startupOnboardingStarted) {
@@ -7942,17 +8643,24 @@ var createRuntimeHandlers = ({
7942
8643
  state,
7943
8644
  getAgent
7944
8645
  });
8646
+ const { handleShellExec } = createShellHandlers({
8647
+ state,
8648
+ log: log2
8649
+ });
7945
8650
  const handleInitialize = (id, params) => {
7946
8651
  const result = {
7947
8652
  protocol_version: PROTOCOL_VERSION,
7948
8653
  server: { name: SERVER_NAME, version: SERVER_VERSION },
7949
8654
  server_capabilities: {
7950
8655
  supports_run_cancel: true,
8656
+ supports_run_diagnostics: true,
8657
+ supports_shell_exec: true,
7951
8658
  supports_ui_requests: true,
7952
8659
  supports_mcp_list: true,
7953
8660
  supports_skills_list: true,
7954
8661
  supports_context_inspect: true,
7955
8662
  supports_tool_call: true,
8663
+ supports_theme_set: true,
7956
8664
  supports_permission_preflight_events: true
7957
8665
  }
7958
8666
  };
@@ -7968,9 +8676,43 @@ var createRuntimeHandlers = ({
7968
8676
  `ui.context.update cwd=${params.cwd ?? "-"} file=${params.active_file?.path ?? "-"}`
7969
8677
  );
7970
8678
  };
8679
+ const handleThemeSet = async (id, params) => {
8680
+ if (state.activeRunId) {
8681
+ sendError(id, { code: import_protocol9.RPC_ERROR_CODE.RUNTIME_BUSY, message: "runtime busy" });
8682
+ return;
8683
+ }
8684
+ const name = params?.name?.trim().toLowerCase();
8685
+ if (!name) {
8686
+ sendError(id, { code: import_protocol9.RPC_ERROR_CODE.INVALID_PARAMS, message: "theme name is required" });
8687
+ return;
8688
+ }
8689
+ if (!SUPPORTED_TUI_THEMES.has(name)) {
8690
+ sendError(id, {
8691
+ code: import_protocol9.RPC_ERROR_CODE.INVALID_PARAMS,
8692
+ message: `unsupported theme: ${name}`
8693
+ });
8694
+ return;
8695
+ }
8696
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
8697
+ try {
8698
+ const target = await updateTuiTheme(workingDir, name);
8699
+ const result = {
8700
+ name,
8701
+ scope: target.scope,
8702
+ path: target.path
8703
+ };
8704
+ sendResult(id, result);
8705
+ log2(`theme.set ${name} scope=${target.scope} path=${target.path}`);
8706
+ } catch (error) {
8707
+ sendError(id, { code: import_protocol9.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
8708
+ }
8709
+ };
7971
8710
  const handleAuthLogout = async (id, params) => {
7972
8711
  if (state.activeRunId) {
7973
- sendError(id, { code: import_protocol8.RPC_ERROR_CODE.RUNTIME_BUSY, message: "runtime busy" });
8712
+ sendError(id, {
8713
+ code: import_protocol9.RPC_ERROR_CODE.RUNTIME_BUSY,
8714
+ message: "runtime busy"
8715
+ });
7974
8716
  return;
7975
8717
  }
7976
8718
  const clearSession = params?.clear_session ?? true;
@@ -7978,7 +8720,7 @@ var createRuntimeHandlers = ({
7978
8720
  const supportsConfirm = !!state.uiCapabilities?.supports_confirm;
7979
8721
  if (!supportsConfirm) {
7980
8722
  sendError(id, {
7981
- code: import_protocol8.RPC_ERROR_CODE.RUNTIME_INTERNAL,
8723
+ code: import_protocol9.RPC_ERROR_CODE.RUNTIME_INTERNAL,
7982
8724
  message: "UI confirmation is required for logout"
7983
8725
  });
7984
8726
  return;
@@ -8020,7 +8762,7 @@ var createRuntimeHandlers = ({
8020
8762
  log2(`auth.logout session_cleared=${clearSession}`);
8021
8763
  } catch (error) {
8022
8764
  sendError(id, {
8023
- code: import_protocol8.RPC_ERROR_CODE.RUNTIME_INTERNAL,
8765
+ code: import_protocol9.RPC_ERROR_CODE.RUNTIME_INTERNAL,
8024
8766
  message: `auth logout failed: ${String(error)}`
8025
8767
  });
8026
8768
  }
@@ -8045,6 +8787,8 @@ var createRuntimeHandlers = ({
8045
8787
  return handleModelSet(req.id, req.params);
8046
8788
  case "tool.call":
8047
8789
  return handleToolCall(req.id, req.params);
8790
+ case "shell.exec":
8791
+ return handleShellExec(req.id, req.params);
8048
8792
  case "mcp.list":
8049
8793
  await mcpManager.start?.();
8050
8794
  return sendResult(
@@ -8055,8 +8799,13 @@ var createRuntimeHandlers = ({
8055
8799
  return handleSkillsList(req.id, req.params);
8056
8800
  case "context.inspect":
8057
8801
  return handleContextInspect(req.id, req.params);
8802
+ case "theme.set":
8803
+ return handleThemeSet(req.id, req.params);
8058
8804
  default:
8059
- return sendError(req.id, { code: import_protocol8.RPC_ERROR_CODE.METHOD_NOT_FOUND, message: "method not found" });
8805
+ return sendError(req.id, {
8806
+ code: import_protocol9.RPC_ERROR_CODE.METHOD_NOT_FOUND,
8807
+ message: "method not found"
8808
+ });
8060
8809
  }
8061
8810
  };
8062
8811
  const handleNotification = (note) => {
@@ -8083,7 +8832,7 @@ var createRuntimeHandlers = ({
8083
8832
  };
8084
8833
 
8085
8834
  // src/runtime-state.ts
8086
- var import_node_crypto6 = __toESM(require("crypto"), 1);
8835
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
8087
8836
  var RuntimeState = class {
8088
8837
  runSeq = /* @__PURE__ */ new Map();
8089
8838
  uiRequestCounter = 0;
@@ -8106,11 +8855,12 @@ var RuntimeState = class {
8106
8855
  loadedSkillVersions = /* @__PURE__ */ new Map();
8107
8856
  runtimeWorkingDir = null;
8108
8857
  runtimeSandboxRoot = null;
8858
+ diagnosticsEnabled = false;
8109
8859
  nextRunId() {
8110
- return import_node_crypto6.default.randomUUID();
8860
+ return import_node_crypto7.default.randomUUID();
8111
8861
  }
8112
8862
  nextSessionId() {
8113
- return import_node_crypto6.default.randomUUID();
8863
+ return import_node_crypto7.default.randomUUID();
8114
8864
  }
8115
8865
  beginRun(runId, uiContext) {
8116
8866
  this.activeRunId = runId;
@@ -8199,13 +8949,19 @@ var RuntimeState = class {
8199
8949
  };
8200
8950
 
8201
8951
  // src/runtime.ts
8952
+ var envTruthy2 = (value) => {
8953
+ if (!value) return false;
8954
+ const normalized = value.trim().toLowerCase();
8955
+ return normalized === "1" || normalized === "true";
8956
+ };
8202
8957
  var startRuntime = () => {
8203
8958
  void (async () => {
8204
8959
  const state = new RuntimeState();
8205
- const workingDir = process.env.CODELIA_SANDBOX_ROOT ? import_node_path15.default.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
8960
+ state.diagnosticsEnabled = envTruthy2(process.env.CODELIA_DIAGNOSTICS);
8961
+ const workingDir = process.env.CODELIA_SANDBOX_ROOT ? import_node_path16.default.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
8206
8962
  state.runtimeWorkingDir = workingDir;
8207
8963
  state.runtimeSandboxRoot = workingDir;
8208
- const sessionStateStore = new import_storage9.SessionStateStoreImpl({
8964
+ const sessionStateStore = new import_storage10.SessionStateStoreImpl({
8209
8965
  onError: (error, context) => {
8210
8966
  (0, import_logger.log)(
8211
8967
  `Error: session-state ${context.action} error${context.detail ? ` (${context.detail})` : ""}: ${String(error)}`