@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/runtime.ts
2
- import path15 from "path";
2
+ import path16 from "path";
3
3
  import { SessionStateStoreImpl as SessionStateStoreImpl2 } from "@codelia/storage";
4
4
 
5
5
  // src/agent-factory.ts
@@ -659,10 +659,12 @@ var openBrowser = (url) => {
659
659
  // src/config.ts
660
660
  import { promises as fs2 } from "fs";
661
661
  import path2 from "path";
662
- import { configRegistry } from "@codelia/config";
662
+ import { CONFIG_GROUP_DEFAULT_WRITE_SCOPE, configRegistry } from "@codelia/config";
663
663
  import {
664
664
  appendPermissionAllowRules as appendPermissionAllowRulesAtPath,
665
- loadConfig
665
+ loadConfig,
666
+ updateModelConfig,
667
+ updateTuiConfig
666
668
  } from "@codelia/config-loader";
667
669
  import { getDefaultSystemPromptPath } from "@codelia/core";
668
670
  import { StoragePathServiceImpl } from "@codelia/storage";
@@ -672,6 +674,10 @@ var DEFAULT_SKILLS_INITIAL_MAX_ENTRIES = 200;
672
674
  var DEFAULT_SKILLS_INITIAL_MAX_BYTES = 32 * 1024;
673
675
  var DEFAULT_SKILLS_SEARCH_DEFAULT_LIMIT = 8;
674
676
  var DEFAULT_SKILLS_SEARCH_MAX_LIMIT = 50;
677
+ var DEFAULT_SEARCH_MODE = "auto";
678
+ var DEFAULT_SEARCH_NATIVE_PROVIDERS = ["openai", "anthropic"];
679
+ var DEFAULT_SEARCH_LOCAL_BACKEND = "ddg";
680
+ var DEFAULT_SEARCH_BRAVE_API_KEY_ENV = "BRAVE_SEARCH_API_KEY";
675
681
  var readEnvValue = (key) => {
676
682
  const value = process.env[key];
677
683
  if (!value) return void 0;
@@ -762,6 +768,45 @@ var resolveSkillsConfig = async (workingDir) => {
762
768
  const effective = configRegistry.resolve([globalConfig, projectConfig]);
763
769
  return normalizeSkillsConfig(effective.skills);
764
770
  };
771
+ var normalizeSearchConfig = (value) => {
772
+ const mode = value?.mode === "auto" || value?.mode === "native" || value?.mode === "local" ? value.mode : DEFAULT_SEARCH_MODE;
773
+ const providersRaw = value?.native?.providers ?? [
774
+ ...DEFAULT_SEARCH_NATIVE_PROVIDERS
775
+ ];
776
+ const providers = Array.from(
777
+ new Set(
778
+ providersRaw.map((entry) => entry.trim()).filter((entry) => entry.length > 0)
779
+ )
780
+ );
781
+ const searchContextSize = value?.native?.search_context_size;
782
+ const allowedDomains = value?.native?.allowed_domains?.length ? value.native.allowed_domains.map((entry) => entry.trim()).filter((entry) => entry.length > 0) : void 0;
783
+ const userLocation = value?.native?.user_location ? {
784
+ ...value.native.user_location.city ? { city: value.native.user_location.city } : {},
785
+ ...value.native.user_location.country ? { country: value.native.user_location.country } : {},
786
+ ...value.native.user_location.region ? { region: value.native.user_location.region } : {},
787
+ ...value.native.user_location.timezone ? { timezone: value.native.user_location.timezone } : {}
788
+ } : void 0;
789
+ const backend = value?.local?.backend === "ddg" || value?.local?.backend === "brave" ? value.local.backend : DEFAULT_SEARCH_LOCAL_BACKEND;
790
+ const braveApiKeyEnv = value?.local?.brave_api_key_env?.trim() || DEFAULT_SEARCH_BRAVE_API_KEY_ENV;
791
+ return {
792
+ mode,
793
+ native: {
794
+ providers: providers.length ? providers : [...DEFAULT_SEARCH_NATIVE_PROVIDERS],
795
+ ...searchContextSize ? { searchContextSize } : {},
796
+ ...allowedDomains && allowedDomains.length ? { allowedDomains } : {},
797
+ ...userLocation && Object.keys(userLocation).length ? { userLocation } : {}
798
+ },
799
+ local: {
800
+ backend,
801
+ braveApiKeyEnv
802
+ }
803
+ };
804
+ };
805
+ var resolveSearchConfig = async (workingDir) => {
806
+ const { globalConfig, projectConfig } = await loadConfigLayers(workingDir);
807
+ const effective = configRegistry.resolve([globalConfig, projectConfig]);
808
+ return normalizeSearchConfig(effective.search);
809
+ };
765
810
  var normalizeMcpTimeoutMs = (value) => {
766
811
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
767
812
  return DEFAULT_MCP_REQUEST_TIMEOUT_MS;
@@ -797,6 +842,44 @@ var appendPermissionAllowRules = async (workingDir, rules) => {
797
842
  const configPath = resolveProjectConfigPath(workingDir);
798
843
  await appendPermissionAllowRulesAtPath(configPath, rules);
799
844
  };
845
+ var hasDefinedGroup = (group, value) => {
846
+ switch (group) {
847
+ case "model":
848
+ return typeof value?.model?.name === "string" && value.model.name.trim().length > 0;
849
+ case "permissions":
850
+ return Array.isArray(value?.permissions?.allow) || Array.isArray(value?.permissions?.deny);
851
+ case "tui":
852
+ return typeof value?.tui?.theme === "string" && value.tui.theme.trim().length > 0;
853
+ default:
854
+ return false;
855
+ }
856
+ };
857
+ var resolveWriteTarget = async (workingDir, group) => {
858
+ const globalPath = resolveConfigPath();
859
+ const projectPath = resolveProjectConfigPath(workingDir);
860
+ const [globalConfig, projectConfig] = await Promise.all([
861
+ loadConfig(globalPath),
862
+ loadConfig(projectPath)
863
+ ]);
864
+ if (hasDefinedGroup(group, projectConfig)) {
865
+ return { scope: "project", path: projectPath };
866
+ }
867
+ if (hasDefinedGroup(group, globalConfig)) {
868
+ return { scope: "global", path: globalPath };
869
+ }
870
+ const defaultScope = CONFIG_GROUP_DEFAULT_WRITE_SCOPE[group];
871
+ return defaultScope === "project" ? { scope: "project", path: projectPath } : { scope: "global", path: globalPath };
872
+ };
873
+ var updateModel = async (workingDir, model) => {
874
+ const target = await resolveWriteTarget(workingDir, "model");
875
+ await updateModelConfig(target.path, model);
876
+ return target;
877
+ };
878
+ var updateTuiTheme = async (workingDir, theme) => {
879
+ const target = await resolveWriteTarget(workingDir, "tui");
880
+ await updateTuiConfig(target.path, { theme });
881
+ return target;
882
+ };
800
883
  var resolveReasoningEffort = (value) => {
801
884
  return resolveModelLevelOption(value, "model.reasoning");
802
885
  };
@@ -945,6 +1028,14 @@ var sendRunContext = (runId, contextLeftPercent) => {
945
1028
  };
946
1029
  send(notify);
947
1030
  };
1031
+ var sendRunDiagnostics = (params) => {
1032
+ const notify = {
1033
+ jsonrpc: "2.0",
1034
+ method: "run.diagnostics",
1035
+ params
1036
+ };
1037
+ send(notify);
1038
+ };
948
1039
 
949
1040
  // src/rpc/ui-requests.ts
950
1041
  var requestUi = async (state, method, params) => {
@@ -4190,17 +4281,176 @@ ${reason} Use offset to read beyond line ${lastReadLine}.`;
4190
4281
  }
4191
4282
  });
4192
4283
 
4193
- // src/tools/skill-load.ts
4284
+ // src/tools/search.ts
4194
4285
  import { defineTool as defineTool9 } from "@codelia/core";
4195
4286
  import { z as z10 } from "zod";
4196
- var SkillLoadInputSchema = z10.object({
4197
- name: z10.string().min(1).optional().describe("Exact skill name to load."),
4198
- path: z10.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
4287
+ var normalizeDomain = (value) => value.trim().toLowerCase().replace(/\.+$/, "");
4288
+ var shouldKeepUrl = (url, allowedDomains) => {
4289
+ if (!allowedDomains.length) return true;
4290
+ try {
4291
+ const parsed = new URL(url);
4292
+ const host = parsed.hostname.toLowerCase();
4293
+ return allowedDomains.some((domain) => {
4294
+ const normalized = normalizeDomain(domain);
4295
+ return host === normalized || host.endsWith(`.${normalized}`);
4296
+ });
4297
+ } catch {
4298
+ return false;
4299
+ }
4300
+ };
4301
+ var normalizeEntries = (entries, allowedDomains, maxResults) => {
4302
+ return entries.filter((entry) => shouldKeepUrl(entry.url, allowedDomains)).slice(0, maxResults);
4303
+ };
4304
+ var parseDdg = (payload) => {
4305
+ const results = [];
4306
+ if (!payload || typeof payload !== "object") {
4307
+ return results;
4308
+ }
4309
+ const record = payload;
4310
+ const related = Array.isArray(record.RelatedTopics) ? record.RelatedTopics : [];
4311
+ for (const topic of related) {
4312
+ if (!topic || typeof topic !== "object") {
4313
+ continue;
4314
+ }
4315
+ const typed = topic;
4316
+ if (Array.isArray(typed.Topics)) {
4317
+ for (const child of typed.Topics) {
4318
+ if (!child || typeof child !== "object") continue;
4319
+ const row = child;
4320
+ const text2 = typeof row.Text === "string" ? row.Text : "";
4321
+ const url2 = typeof row.FirstURL === "string" ? row.FirstURL : "";
4322
+ if (!text2 || !url2) continue;
4323
+ results.push({
4324
+ title: text2.split(" - ")[0] ?? text2,
4325
+ url: url2,
4326
+ snippet: text2,
4327
+ source: "ddg"
4328
+ });
4329
+ }
4330
+ continue;
4331
+ }
4332
+ const text = typeof typed.Text === "string" ? typed.Text : "";
4333
+ const url = typeof typed.FirstURL === "string" ? typed.FirstURL : "";
4334
+ if (!text || !url) continue;
4335
+ results.push({
4336
+ title: text.split(" - ")[0] ?? text,
4337
+ url,
4338
+ snippet: text,
4339
+ source: "ddg"
4340
+ });
4341
+ }
4342
+ return results;
4343
+ };
4344
+ var parseBrave = (payload) => {
4345
+ const results = [];
4346
+ if (!payload || typeof payload !== "object") {
4347
+ return results;
4348
+ }
4349
+ const record = payload;
4350
+ const web = record.web;
4351
+ if (!web || typeof web !== "object") {
4352
+ return results;
4353
+ }
4354
+ const rows = Array.isArray(web.results) ? web.results : [];
4355
+ for (const row of rows) {
4356
+ if (!row || typeof row !== "object") continue;
4357
+ const typed = row;
4358
+ const title = typeof typed.title === "string" ? typed.title : "";
4359
+ const url = typeof typed.url === "string" ? typed.url : "";
4360
+ const snippet = typeof typed.description === "string" ? typed.description : "";
4361
+ if (!title || !url) continue;
4362
+ results.push({
4363
+ title,
4364
+ url,
4365
+ snippet,
4366
+ source: "brave"
4367
+ });
4368
+ }
4369
+ return results;
4370
+ };
4371
+ var runDdgSearch = async (query, maxResults, allowedDomains) => {
4372
+ const url = new URL("https://api.duckduckgo.com/");
4373
+ url.searchParams.set("q", query);
4374
+ url.searchParams.set("format", "json");
4375
+ url.searchParams.set("no_html", "1");
4376
+ url.searchParams.set("skip_disambig", "1");
4377
+ const response = await fetch(url);
4378
+ if (!response.ok) {
4379
+ throw new Error(`ddg request failed: status=${response.status}`);
4380
+ }
4381
+ const payload = await response.json();
4382
+ const parsed = parseDdg(payload);
4383
+ return normalizeEntries(parsed, allowedDomains, maxResults);
4384
+ };
4385
+ var runBraveSearch = async (query, maxResults, allowedDomains, apiKey) => {
4386
+ const url = new URL("https://api.search.brave.com/res/v1/web/search");
4387
+ url.searchParams.set("q", query);
4388
+ url.searchParams.set("count", String(maxResults));
4389
+ const response = await fetch(url, {
4390
+ headers: {
4391
+ Accept: "application/json",
4392
+ "X-Subscription-Token": apiKey
4393
+ }
4394
+ });
4395
+ if (!response.ok) {
4396
+ throw new Error(`brave request failed: status=${response.status}`);
4397
+ }
4398
+ const payload = await response.json();
4399
+ const parsed = parseBrave(payload);
4400
+ return normalizeEntries(parsed, allowedDomains, maxResults);
4401
+ };
4402
+ var createSearchTool = (options) => defineTool9({
4403
+ name: "search",
4404
+ description: "Search the web and return concise source candidates.",
4405
+ input: z10.object({
4406
+ query: z10.string().min(1).describe("Search query."),
4407
+ max_results: z10.number().int().min(1).max(20).optional().describe("Max results. Default 5."),
4408
+ backend: z10.enum(["ddg", "brave"]).optional().describe("Search backend. Default comes from config."),
4409
+ allowed_domains: z10.array(z10.string().min(1)).optional().describe("Optional allowlist of domains.")
4410
+ }),
4411
+ execute: async (input) => {
4412
+ const backend = input.backend ?? options.defaultBackend;
4413
+ const maxResults = input.max_results ?? 5;
4414
+ const allowedDomains = input.allowed_domains?.map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
4415
+ try {
4416
+ let results;
4417
+ if (backend === "ddg") {
4418
+ results = await runDdgSearch(input.query, maxResults, allowedDomains);
4419
+ } else {
4420
+ const apiKey = process.env[options.braveApiKeyEnv]?.trim();
4421
+ if (!apiKey) {
4422
+ return `Missing ${options.braveApiKeyEnv} for brave backend.`;
4423
+ }
4424
+ results = await runBraveSearch(
4425
+ input.query,
4426
+ maxResults,
4427
+ allowedDomains,
4428
+ apiKey
4429
+ );
4430
+ }
4431
+ return {
4432
+ query: input.query,
4433
+ backend,
4434
+ count: results.length,
4435
+ results
4436
+ };
4437
+ } catch (error) {
4438
+ return `Error running search: ${String(error)}`;
4439
+ }
4440
+ }
4441
+ });
4442
+
4443
+ // src/tools/skill-load.ts
4444
+ import { defineTool as defineTool10 } from "@codelia/core";
4445
+ import { z as z11 } from "zod";
4446
+ var SkillLoadInputSchema = z11.object({
4447
+ name: z11.string().min(1).optional().describe("Exact skill name to load."),
4448
+ path: z11.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
4199
4449
  }).refine((value) => !!value.name || !!value.path, {
4200
4450
  message: "name or path is required",
4201
4451
  path: ["name"]
4202
4452
  });
4203
- var createSkillLoadTool = (skillsResolverKey) => defineTool9({
4453
+ var createSkillLoadTool = (skillsResolverKey) => defineTool10({
4204
4454
  name: "skill_load",
4205
4455
  description: "Load full SKILL.md content by exact name or path.",
4206
4456
  input: SkillLoadInputSchema,
@@ -4246,15 +4496,15 @@ var createSkillLoadTool = (skillsResolverKey) => defineTool9({
4246
4496
  });
4247
4497
 
4248
4498
  // src/tools/skill-search.ts
4249
- import { defineTool as defineTool10 } from "@codelia/core";
4250
- import { z as z11 } from "zod";
4251
- var createSkillSearchTool = (skillsResolverKey) => defineTool10({
4499
+ import { defineTool as defineTool11 } from "@codelia/core";
4500
+ import { z as z12 } from "zod";
4501
+ var createSkillSearchTool = (skillsResolverKey) => defineTool11({
4252
4502
  name: "skill_search",
4253
4503
  description: "Search installed local skills by name, description, or path.",
4254
- input: z11.object({
4255
- query: z11.string().min(1).describe("Search query text."),
4256
- limit: z11.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
4257
- scope: z11.enum(["repo", "user"]).optional().describe("Optional scope filter.")
4504
+ input: z12.object({
4505
+ query: z12.string().min(1).describe("Search query text."),
4506
+ limit: z12.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
4507
+ scope: z12.enum(["repo", "user"]).optional().describe("Optional scope filter.")
4258
4508
  }),
4259
4509
  execute: async (input, ctx) => {
4260
4510
  try {
@@ -4277,17 +4527,17 @@ var createSkillSearchTool = (skillsResolverKey) => defineTool10({
4277
4527
  });
4278
4528
 
4279
4529
  // src/tools/todo-read.ts
4280
- import { defineTool as defineTool11 } from "@codelia/core";
4281
- import { z as z12 } from "zod";
4530
+ import { defineTool as defineTool12 } from "@codelia/core";
4531
+ import { z as z13 } from "zod";
4282
4532
 
4283
4533
  // src/tools/todo-store.ts
4284
4534
  var todoStore = /* @__PURE__ */ new Map();
4285
4535
 
4286
4536
  // src/tools/todo-read.ts
4287
- var createTodoReadTool = (sandboxKey) => defineTool11({
4537
+ var createTodoReadTool = (sandboxKey) => defineTool12({
4288
4538
  name: "todo_read",
4289
4539
  description: "Read the in-session todo list.",
4290
- input: z12.object({}),
4540
+ input: z13.object({}),
4291
4541
  execute: async (_input, ctx) => {
4292
4542
  const sandbox = await getSandboxContext(ctx, sandboxKey);
4293
4543
  const todos = todoStore.get(sandbox.sessionId) ?? [];
@@ -4300,17 +4550,17 @@ var createTodoReadTool = (sandboxKey) => defineTool11({
4300
4550
  });
4301
4551
 
4302
4552
  // src/tools/todo-write.ts
4303
- import { defineTool as defineTool12 } from "@codelia/core";
4304
- import { z as z13 } from "zod";
4305
- var createTodoWriteTool = (sandboxKey) => defineTool12({
4553
+ import { defineTool as defineTool13 } from "@codelia/core";
4554
+ import { z as z14 } from "zod";
4555
+ var createTodoWriteTool = (sandboxKey) => defineTool13({
4306
4556
  name: "todo_write",
4307
4557
  description: "Replace the in-session todo list.",
4308
- input: z13.object({
4309
- todos: z13.array(
4310
- z13.object({
4311
- content: z13.string().describe("Todo item text."),
4312
- status: z13.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
4313
- activeForm: z13.string().optional().describe("Optional in-progress phrasing for UI display.")
4558
+ input: z14.object({
4559
+ todos: z14.array(
4560
+ z14.object({
4561
+ content: z14.string().describe("Todo item text."),
4562
+ status: z14.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
4563
+ activeForm: z14.string().optional().describe("Optional in-progress phrasing for UI display.")
4314
4564
  })
4315
4565
  )
4316
4566
  }),
@@ -4327,15 +4577,15 @@ var createTodoWriteTool = (sandboxKey) => defineTool12({
4327
4577
  });
4328
4578
 
4329
4579
  // src/tools/tool-output-cache.ts
4330
- import { defineTool as defineTool13 } from "@codelia/core";
4331
- import { z as z14 } from "zod";
4332
- var createToolOutputCacheTool = (store) => defineTool13({
4580
+ import { defineTool as defineTool14 } from "@codelia/core";
4581
+ import { z as z15 } from "zod";
4582
+ var createToolOutputCacheTool = (store) => defineTool14({
4333
4583
  name: "tool_output_cache",
4334
4584
  description: "Read cached tool output by ref_id.",
4335
- input: z14.object({
4336
- ref_id: z14.string().describe("Tool output reference ID."),
4337
- offset: z14.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
4338
- limit: z14.number().int().positive().optional().describe("Optional max number of lines to return.")
4585
+ input: z15.object({
4586
+ ref_id: z15.string().describe("Tool output reference ID."),
4587
+ offset: z15.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
4588
+ limit: z15.number().int().positive().optional().describe("Optional max number of lines to return.")
4339
4589
  }),
4340
4590
  execute: async (input) => {
4341
4591
  if (!store.read) {
@@ -4351,16 +4601,16 @@ var createToolOutputCacheTool = (store) => defineTool13({
4351
4601
  }
4352
4602
  }
4353
4603
  });
4354
- var createToolOutputCacheGrepTool = (store) => defineTool13({
4604
+ var createToolOutputCacheGrepTool = (store) => defineTool14({
4355
4605
  name: "tool_output_cache_grep",
4356
4606
  description: "Search cached tool output by ref_id.",
4357
- input: z14.object({
4358
- ref_id: z14.string().describe("Tool output reference ID."),
4359
- pattern: z14.string().describe("Text or regex pattern to search for."),
4360
- regex: z14.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
4361
- before: z14.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
4362
- after: z14.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
4363
- max_matches: z14.number().int().positive().optional().describe("Maximum number of matches to return.")
4607
+ input: z15.object({
4608
+ ref_id: z15.string().describe("Tool output reference ID."),
4609
+ pattern: z15.string().describe("Text or regex pattern to search for."),
4610
+ regex: z15.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
4611
+ before: z15.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
4612
+ after: z15.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
4613
+ max_matches: z15.number().int().positive().optional().describe("Maximum number of matches to return.")
4364
4614
  }),
4365
4615
  execute: async (input) => {
4366
4616
  if (!store.grep) {
@@ -4383,14 +4633,14 @@ var createToolOutputCacheGrepTool = (store) => defineTool13({
4383
4633
  // src/tools/write.ts
4384
4634
  import { promises as fs13 } from "fs";
4385
4635
  import path12 from "path";
4386
- import { defineTool as defineTool14 } from "@codelia/core";
4387
- import { z as z15 } from "zod";
4388
- var createWriteTool = (sandboxKey) => defineTool14({
4636
+ import { defineTool as defineTool15 } from "@codelia/core";
4637
+ import { z as z16 } from "zod";
4638
+ var createWriteTool = (sandboxKey) => defineTool15({
4389
4639
  name: "write",
4390
4640
  description: "Write text to a file, creating parent directories if needed.",
4391
- input: z15.object({
4392
- file_path: z15.string().describe("File path under the sandbox root."),
4393
- content: z15.string().describe("UTF-8 text content to write.")
4641
+ input: z16.object({
4642
+ file_path: z16.string().describe("File path under the sandbox root."),
4643
+ content: z16.string().describe("UTF-8 text content to write.")
4394
4644
  }),
4395
4645
  execute: async (input, ctx) => {
4396
4646
  let resolved;
@@ -4419,6 +4669,7 @@ var createTools = (sandboxKey, agentsResolverKey, skillsResolverKey, options = {
4419
4669
  createAgentsResolveTool(sandboxKey, agentsResolverKey),
4420
4670
  createSkillSearchTool(skillsResolverKey),
4421
4671
  createSkillLoadTool(skillsResolverKey),
4672
+ ...options.search ? [createSearchTool(options.search)] : [],
4422
4673
  createGlobSearchTool(sandboxKey),
4423
4674
  createGrepTool(sandboxKey),
4424
4675
  ...options.toolOutputCacheStore ? [
@@ -4467,9 +4718,9 @@ var normalizeLanguage = (value) => {
4467
4718
  };
4468
4719
  var languageFromFilePath = (filePath) => {
4469
4720
  if (!filePath) return void 0;
4470
- const path16 = filePath.trim().replace(/^["']|["']$/g, "");
4471
- if (!path16 || path16 === "/dev/null") return void 0;
4472
- const normalizedPath = path16.replace(/^a\//, "").replace(/^b\//, "");
4721
+ const path17 = filePath.trim().replace(/^["']|["']$/g, "");
4722
+ if (!path17 || path17 === "/dev/null") return void 0;
4723
+ const normalizedPath = path17.replace(/^a\//, "").replace(/^b\//, "");
4473
4724
  const lastSlash = Math.max(
4474
4725
  normalizedPath.lastIndexOf("/"),
4475
4726
  normalizedPath.lastIndexOf("\\")
@@ -4540,6 +4791,25 @@ var envTruthy = (value) => {
4540
4791
  return normalized === "1" || normalized === "true";
4541
4792
  };
4542
4793
  var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
4794
+ var isNativeSearchProvider = (provider, allowedProviders) => allowedProviders.includes(provider);
4795
+ var buildHostedSearchToolDefinitions = (provider, options) => {
4796
+ if (options.mode === "local" || !isNativeSearchProvider(provider, options.native.providers)) {
4797
+ return [];
4798
+ }
4799
+ if (provider !== "openai" && provider !== "anthropic") {
4800
+ return [];
4801
+ }
4802
+ return [
4803
+ {
4804
+ type: "hosted_search",
4805
+ name: "web_search",
4806
+ provider,
4807
+ ...options.native.searchContextSize ? { search_context_size: options.native.searchContextSize } : {},
4808
+ ...options.native.allowedDomains ? { allowed_domains: options.native.allowedDomains } : {},
4809
+ ...options.native.userLocation ? { user_location: options.native.userLocation } : {}
4810
+ }
4811
+ ];
4812
+ };
4543
4813
  var buildOpenAiClientOptions = (authResolver, auth) => {
4544
4814
  if (auth.method === "api_key") {
4545
4815
  return { apiKey: auth.api_key };
@@ -4776,7 +5046,7 @@ var createAgentFactory = (state, options = {}) => {
4776
5046
  const agentsResolverKey = createAgentsResolverKey(agentsResolver);
4777
5047
  const skillsResolverKey = createSkillsResolverKey(skillsResolver);
4778
5048
  const toolOutputCacheStore = new ToolOutputCacheStoreImpl();
4779
- const localTools = createTools(
5049
+ const baseLocalTools = createTools(
4780
5050
  sandboxKey,
4781
5051
  agentsResolverKey,
4782
5052
  skillsResolverKey,
@@ -4784,7 +5054,7 @@ var createAgentFactory = (state, options = {}) => {
4784
5054
  toolOutputCacheStore
4785
5055
  }
4786
5056
  );
4787
- const editTool = localTools.find(
5057
+ const editTool = baseLocalTools.find(
4788
5058
  (tool) => tool.definition.name === "edit"
4789
5059
  );
4790
5060
  let mcpTools = [];
@@ -4807,9 +5077,6 @@ var createAgentFactory = (state, options = {}) => {
4807
5077
  log(`failed to load mcp tools: ${String(error)}`);
4808
5078
  }
4809
5079
  }
4810
- const tools = [...localTools, ...mcpTools];
4811
- state.tools = tools;
4812
- state.toolDefinitions = tools.map((tool) => tool.definition);
4813
5080
  const baseSystemPrompt = await loadSystemPrompt(ctx.workingDir);
4814
5081
  const withAgentsContext = appendInitialAgentsContext(
4815
5082
  baseSystemPrompt,
@@ -4845,6 +5112,29 @@ var createAgentFactory = (state, options = {}) => {
4845
5112
  const authResolver = await AuthResolver.create(state, log);
4846
5113
  const provider = await authResolver.resolveProvider(modelConfig.provider);
4847
5114
  const providerAuth = await authResolver.resolveProviderAuth(provider);
5115
+ const searchConfig = await resolveSearchConfig(ctx.workingDir);
5116
+ const hostedSearchDefinitions = buildHostedSearchToolDefinitions(
5117
+ provider,
5118
+ searchConfig
5119
+ );
5120
+ if (searchConfig.mode === "native" && hostedSearchDefinitions.length === 0) {
5121
+ throw new Error(
5122
+ `search.mode=native is enabled, but native search is unavailable for provider '${provider}'.`
5123
+ );
5124
+ }
5125
+ const useLocalSearchTool = searchConfig.mode === "local" || searchConfig.mode === "auto" && hostedSearchDefinitions.length === 0;
5126
+ const localSearchTools = useLocalSearchTool ? [
5127
+ createSearchTool({
5128
+ defaultBackend: searchConfig.local.backend,
5129
+ braveApiKeyEnv: searchConfig.local.braveApiKeyEnv
5130
+ })
5131
+ ] : [];
5132
+ const tools = [...baseLocalTools, ...localSearchTools, ...mcpTools];
5133
+ state.tools = tools;
5134
+ state.toolDefinitions = [
5135
+ ...tools.map((tool) => tool.definition),
5136
+ ...hostedSearchDefinitions
5137
+ ];
4848
5138
  let llm;
4849
5139
  switch (provider) {
4850
5140
  case "openai": {
@@ -4884,11 +5174,18 @@ var createAgentFactory = (state, options = {}) => {
4884
5174
  const modelRegistry = await buildModelRegistry(llm, {
4885
5175
  strict: provider !== "openrouter"
4886
5176
  });
5177
+ const totalBudgetTrimEnabled = envTruthy(
5178
+ process.env.CODELIA_TOOL_OUTPUT_TOTAL_TRIM
5179
+ );
4887
5180
  const agent = new Agent({
4888
5181
  llm,
4889
5182
  tools,
5183
+ hostedTools: hostedSearchDefinitions,
4890
5184
  systemPrompt,
4891
5185
  modelRegistry: modelRegistry ?? DEFAULT_MODEL_REGISTRY2,
5186
+ toolOutputCache: {
5187
+ totalBudgetTrim: totalBudgetTrimEnabled
5188
+ },
4892
5189
  services: { toolOutputCacheStore },
4893
5190
  canExecuteTool: async (call, rawArgs, toolCtx) => {
4894
5191
  const decision = permissionService.evaluate(
@@ -5599,8 +5896,8 @@ var parseAuthorizationServerMetadata = (value) => {
5599
5896
  var buildAuthorizationServerMetadataUrl = (issuer) => {
5600
5897
  try {
5601
5898
  const parsed = new URL(issuer);
5602
- const path16 = parsed.pathname === "/" ? "" : parsed.pathname;
5603
- const basePath = path16.startsWith("/") ? path16 : `/${path16}`;
5899
+ const path17 = parsed.pathname === "/" ? "" : parsed.pathname;
5900
+ const basePath = path17.startsWith("/") ? path17 : `/${path17}`;
5604
5901
  const metadataPath = `/.well-known/oauth-authorization-server${basePath}`;
5605
5902
  return new URL(metadataPath, `${parsed.origin}/`).toString();
5606
5903
  } catch {
@@ -6257,9 +6554,8 @@ var McpManager = class {
6257
6554
  };
6258
6555
 
6259
6556
  // src/rpc/handlers.ts
6260
- import { updateModelConfig as updateModelConfig2 } from "@codelia/config-loader";
6261
6557
  import {
6262
- RPC_ERROR_CODE as RPC_ERROR_CODE7
6558
+ RPC_ERROR_CODE as RPC_ERROR_CODE8
6263
6559
  } from "@codelia/protocol";
6264
6560
  import {
6265
6561
  RunEventStoreFactoryImpl,
@@ -6644,7 +6940,6 @@ var createHistoryHandlers = ({
6644
6940
  };
6645
6941
 
6646
6942
  // src/rpc/model.ts
6647
- import { updateModelConfig } from "@codelia/config-loader";
6648
6943
  import {
6649
6944
  DEFAULT_MODEL_REGISTRY as DEFAULT_MODEL_REGISTRY3,
6650
6945
  listModels,
@@ -6999,12 +7294,12 @@ var createModelHandlers = ({
6999
7294
  }
7000
7295
  }
7001
7296
  try {
7002
- const configPath = resolveConfigPath();
7003
- await updateModelConfig(configPath, { provider, name });
7297
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
7298
+ const target = await updateModel(workingDir, { provider, name });
7004
7299
  state.agent = null;
7005
7300
  const result = { provider, name };
7006
7301
  sendResult(id, result);
7007
- log2(`model.set ${provider}/${name}`);
7302
+ log2(`model.set ${provider}/${name} scope=${target.scope} path=${target.path}`);
7008
7303
  } catch (error) {
7009
7304
  sendError(id, { code: RPC_ERROR_CODE3.RUNTIME_INTERNAL, message: String(error) });
7010
7305
  }
@@ -7317,6 +7612,28 @@ var buildSessionState = (sessionId, runId, messages, invokeSeq) => ({
7317
7612
  invoke_seq: invokeSeq,
7318
7613
  messages
7319
7614
  });
7615
+ var toTimestampMs = (value) => {
7616
+ const ms = Date.parse(value);
7617
+ return Number.isFinite(ms) ? ms : null;
7618
+ };
7619
+ var summarizeProviderMeta = (value) => {
7620
+ if (value === null || value === void 0) {
7621
+ return null;
7622
+ }
7623
+ if (typeof value === "string") {
7624
+ return value.length > 80 ? `${value.slice(0, 77)}...` : value;
7625
+ }
7626
+ if (Array.isArray(value)) {
7627
+ return `array(len=${value.length})`;
7628
+ }
7629
+ if (typeof value === "object") {
7630
+ const keys = Object.keys(value);
7631
+ if (keys.length === 0) return "object";
7632
+ const shown = keys.slice(0, 4).join(",");
7633
+ return keys.length > 4 ? `object(keys=${shown},...)` : `object(keys=${shown})`;
7634
+ }
7635
+ return typeof value;
7636
+ };
7320
7637
  var createRunHandlers = ({
7321
7638
  state,
7322
7639
  getAgent,
@@ -7450,12 +7767,77 @@ var createRunHandlers = ({
7450
7767
  const runAbortController = new AbortController();
7451
7768
  activeRunAbort = { runId, controller: runAbortController };
7452
7769
  const sessionStore = runEventStoreFactory.create({ runId, startedAt });
7453
- const sessionAppend = createSessionAppender(
7770
+ const sessionAppenderRaw = createSessionAppender(
7454
7771
  sessionStore,
7455
7772
  (error, record) => {
7456
7773
  log2(`session-store error (${record.type}): ${String(error)}`);
7457
7774
  }
7458
7775
  );
7776
+ const pendingLlmRequests = /* @__PURE__ */ new Map();
7777
+ const emitRunDiagnostics = (params2) => {
7778
+ if (!state.diagnosticsEnabled) return;
7779
+ try {
7780
+ sendRunDiagnostics(params2);
7781
+ } catch (error) {
7782
+ log2(`run diagnostics emit failed: ${String(error)}`);
7783
+ }
7784
+ };
7785
+ const sessionAppend = (record) => {
7786
+ if (state.diagnosticsEnabled) {
7787
+ try {
7788
+ if (record.type === "llm.request") {
7789
+ const modelName = record.model?.name ?? record.input.model ?? "unknown";
7790
+ pendingLlmRequests.set(record.seq, {
7791
+ ts: record.ts,
7792
+ provider: record.model?.provider,
7793
+ model: modelName
7794
+ });
7795
+ }
7796
+ if (record.type === "llm.response") {
7797
+ const request = pendingLlmRequests.get(record.seq);
7798
+ pendingLlmRequests.delete(record.seq);
7799
+ const usage = record.output.usage ?? null;
7800
+ const cacheReadTokens = usage?.input_cached_tokens ?? 0;
7801
+ const cacheCreationTokens = usage?.input_cache_creation_tokens ?? 0;
7802
+ const inputTokens = usage?.input_tokens ?? 0;
7803
+ const hitState = usage ? cacheReadTokens > 0 ? "hit" : "miss" : "unknown";
7804
+ const responseTsMs = toTimestampMs(record.ts);
7805
+ const requestTsMs = request ? toTimestampMs(request.ts) : null;
7806
+ const latencyMs = responseTsMs !== null && requestTsMs !== null ? Math.max(0, responseTsMs - requestTsMs) : 0;
7807
+ const model = usage?.model ?? request?.model ?? "unknown";
7808
+ const diagnostics = {
7809
+ run_id: runId,
7810
+ seq: record.seq,
7811
+ ...request?.provider ? { provider: request.provider } : {},
7812
+ model,
7813
+ request_ts: request?.ts ?? record.ts,
7814
+ response_ts: record.ts,
7815
+ latency_ms: latencyMs,
7816
+ stop_reason: record.output.stop_reason ?? null,
7817
+ usage,
7818
+ cache: {
7819
+ hit_state: hitState,
7820
+ cache_read_tokens: cacheReadTokens,
7821
+ cache_creation_tokens: cacheCreationTokens,
7822
+ cache_read_ratio: usage ? cacheReadTokens / Math.max(inputTokens, 1) : null
7823
+ },
7824
+ cost_usd: null,
7825
+ provider_meta_summary: summarizeProviderMeta(
7826
+ record.output.provider_meta
7827
+ )
7828
+ };
7829
+ emitRunDiagnostics({
7830
+ run_id: runId,
7831
+ kind: "llm_call",
7832
+ call: diagnostics
7833
+ });
7834
+ }
7835
+ } catch (error) {
7836
+ log2(`run diagnostics build failed: ${String(error)}`);
7837
+ }
7838
+ }
7839
+ sessionAppenderRaw(record);
7840
+ };
7459
7841
  state.sessionAppend = sessionAppend;
7460
7842
  const session = {
7461
7843
  run_id: runId,
@@ -7508,6 +7890,13 @@ var createRunHandlers = ({
7508
7890
  let finalResponse;
7509
7891
  let sessionSaveChain = Promise.resolve();
7510
7892
  let lastSessionSaveAt = 0;
7893
+ const emitRunSummaryDiagnostics = () => {
7894
+ emitRunDiagnostics({
7895
+ run_id: runId,
7896
+ kind: "run_summary",
7897
+ summary: runtimeAgent.getUsageSummary()
7898
+ });
7899
+ };
7511
7900
  const queueSessionSave = async (reason) => {
7512
7901
  sessionSaveChain = sessionSaveChain.then(async () => {
7513
7902
  if (!sessionId) return;
@@ -7602,6 +7991,7 @@ var createRunHandlers = ({
7602
7991
  normalizeRunHistoryAfterCancel(runId, runtimeAgent);
7603
7992
  }
7604
7993
  await queueSessionSave("terminal");
7994
+ emitRunSummaryDiagnostics();
7605
7995
  const status = state.cancelRequested ? "cancelled" : "completed";
7606
7996
  emitRunStatus(runId, status);
7607
7997
  emitRunEnd(
@@ -7614,11 +8004,13 @@ var createRunHandlers = ({
7614
8004
  if (state.cancelRequested || isAbortLikeError(err)) {
7615
8005
  normalizeRunHistoryAfterCancel(runId, runtimeAgent);
7616
8006
  await queueSessionSave("cancelled");
8007
+ emitRunSummaryDiagnostics();
7617
8008
  emitRunStatus(runId, "cancelled", err.message || "cancelled");
7618
8009
  emitRunEnd(runId, "cancelled", finalResponse);
7619
8010
  return;
7620
8011
  }
7621
8012
  await queueSessionSave("error");
8013
+ emitRunSummaryDiagnostics();
7622
8014
  emitRunStatus(runId, "error", err.message);
7623
8015
  appendSession({
7624
8016
  type: "run.error",
@@ -7802,7 +8194,315 @@ var createToolHandlers = ({
7802
8194
  };
7803
8195
  };
7804
8196
 
8197
+ // src/rpc/shell.ts
8198
+ import crypto6 from "crypto";
8199
+ import path15 from "path";
8200
+ import { spawn as spawn5 } from "child_process";
8201
+ import { ToolOutputCacheStoreImpl as ToolOutputCacheStoreImpl2 } from "@codelia/storage";
8202
+ import {
8203
+ RPC_ERROR_CODE as RPC_ERROR_CODE7
8204
+ } from "@codelia/protocol";
8205
+ var DEFAULT_EXCERPT_LINES = 80;
8206
+ var MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
8207
+ var COMMAND_PREVIEW_CHARS = 400;
8208
+ var truncateCommandPreview = (value) => {
8209
+ const trimmed = value.trim();
8210
+ if (trimmed.length <= COMMAND_PREVIEW_CHARS) return trimmed;
8211
+ return `${trimmed.slice(0, COMMAND_PREVIEW_CHARS)}...[truncated]`;
8212
+ };
8213
+ var utf8ByteLength = (value) => Buffer.byteLength(value, "utf8");
8214
+ var truncateUtf8Prefix = (value, maxBytes) => {
8215
+ if (maxBytes <= 0 || value.length === 0) return "";
8216
+ let bytes = 0;
8217
+ let out = "";
8218
+ for (const ch of value) {
8219
+ const next = utf8ByteLength(ch);
8220
+ if (bytes + next > maxBytes) break;
8221
+ out += ch;
8222
+ bytes += next;
8223
+ }
8224
+ return out;
8225
+ };
8226
+ var truncateUtf8Suffix = (value, maxBytes) => {
8227
+ if (maxBytes <= 0 || value.length === 0) return "";
8228
+ let bytes = 0;
8229
+ const chars = Array.from(value);
8230
+ const out = [];
8231
+ for (let idx = chars.length - 1; idx >= 0; idx -= 1) {
8232
+ const ch = chars[idx];
8233
+ const next = utf8ByteLength(ch);
8234
+ if (bytes + next > maxBytes) break;
8235
+ out.push(ch);
8236
+ bytes += next;
8237
+ }
8238
+ out.reverse();
8239
+ return out.join("");
8240
+ };
8241
+ var excerptByLines = (value) => {
8242
+ const lines = value.split(/\r?\n/);
8243
+ if (lines.length <= DEFAULT_EXCERPT_LINES * 2) {
8244
+ return value;
8245
+ }
8246
+ const head = lines.slice(0, DEFAULT_EXCERPT_LINES);
8247
+ const tail = lines.slice(lines.length - DEFAULT_EXCERPT_LINES);
8248
+ const omitted = lines.length - head.length - tail.length;
8249
+ return [...head, `...[${omitted} lines omitted]...`, ...tail].join("\n");
8250
+ };
8251
+ var excerptByBytes = (value, maxBytes) => {
8252
+ if (utf8ByteLength(value) <= maxBytes) return value;
8253
+ const marker = "\n...[truncated by size]...\n";
8254
+ const markerBytes = utf8ByteLength(marker);
8255
+ if (maxBytes <= markerBytes + 2) {
8256
+ return truncateUtf8Prefix(value, maxBytes);
8257
+ }
8258
+ const budget = maxBytes - markerBytes;
8259
+ const headBytes = Math.floor(budget / 2);
8260
+ const tailBytes = budget - headBytes;
8261
+ const head = truncateUtf8Prefix(value, headBytes);
8262
+ const tail = truncateUtf8Suffix(value, tailBytes);
8263
+ return `${head}${marker}${tail}`;
8264
+ };
8265
+ var needsInlineTruncation = (value) => value.split(/\r?\n/).length > DEFAULT_EXCERPT_LINES * 2 || utf8ByteLength(value) > MAX_INLINE_OUTPUT_BYTES;
8266
+ var excerptText = (value) => excerptByBytes(excerptByLines(value), MAX_INLINE_OUTPUT_BYTES);
8267
+ var isWithin2 = (basePath, candidatePath) => {
8268
+ const relative = path15.relative(basePath, candidatePath);
8269
+ return relative === "" || !relative.startsWith("..") && !path15.isAbsolute(relative);
8270
+ };
8271
+ var resolveShellCwd = (state, requestedCwd) => {
8272
+ const workingDir = state.runtimeWorkingDir ?? process.cwd();
8273
+ const rootDir = state.runtimeSandboxRoot ?? workingDir;
8274
+ if (!requestedCwd) return workingDir;
8275
+ const resolved = path15.resolve(workingDir, requestedCwd);
8276
+ if (!isWithin2(rootDir, resolved)) {
8277
+ return null;
8278
+ }
8279
+ return resolved;
8280
+ };
8281
+ var runShellWithUserShell = async (command, options) => {
8282
+ if (process.platform === "win32") {
8283
+ return runShellCommand(command, options);
8284
+ }
8285
+ const shellPath = process.env.SHELL?.trim() || "";
8286
+ if (!shellPath) {
8287
+ return runShellCommand(command, options);
8288
+ }
8289
+ return new Promise((resolve, reject) => {
8290
+ const child = spawn5(shellPath, ["-lc", command], {
8291
+ cwd: options.cwd,
8292
+ stdio: ["ignore", "pipe", "pipe"],
8293
+ signal: options.signal
8294
+ });
8295
+ let stdout = "";
8296
+ let stderr = "";
8297
+ let totalBytes = 0;
8298
+ let settled = false;
8299
+ let timedOut = false;
8300
+ let timeoutHandle;
8301
+ const finish = (handler) => {
8302
+ if (settled) return;
8303
+ settled = true;
8304
+ if (timeoutHandle) clearTimeout(timeoutHandle);
8305
+ handler();
8306
+ };
8307
+ const consumeChunk = (chunk, stream) => {
8308
+ const text = chunk.toString("utf8");
8309
+ totalBytes += Buffer.byteLength(text, "utf8");
8310
+ if (totalBytes > options.maxOutputBytes) {
8311
+ const error = Object.assign(
8312
+ new Error(
8313
+ `Command output exceeded max buffer of ${options.maxOutputBytes} bytes`
8314
+ ),
8315
+ {
8316
+ code: "MAXBUFFER",
8317
+ stdout,
8318
+ stderr,
8319
+ killed: true,
8320
+ signal: "SIGTERM"
8321
+ }
8322
+ );
8323
+ try {
8324
+ child.kill("SIGTERM");
8325
+ } catch {
8326
+ }
8327
+ finish(() => reject(error));
8328
+ return;
8329
+ }
8330
+ if (stream === "stdout") stdout += text;
8331
+ else stderr += text;
8332
+ };
8333
+ child.stdout?.on("data", (chunk) => consumeChunk(chunk, "stdout"));
8334
+ child.stderr?.on("data", (chunk) => consumeChunk(chunk, "stderr"));
8335
+ child.on("error", (error) => {
8336
+ const enriched = Object.assign(error, {
8337
+ stdout,
8338
+ stderr
8339
+ });
8340
+ finish(() => reject(enriched));
8341
+ });
8342
+ child.on("close", (code, signal) => {
8343
+ if (timedOut) {
8344
+ const timeoutError = Object.assign(
8345
+ new Error(
8346
+ `Command timed out after ${Math.trunc(options.timeoutMs / 1e3)}s`
8347
+ ),
8348
+ {
8349
+ code: "ETIMEDOUT",
8350
+ stdout,
8351
+ stderr,
8352
+ killed: true,
8353
+ signal: signal ?? "SIGTERM"
8354
+ }
8355
+ );
8356
+ finish(() => reject(timeoutError));
8357
+ return;
8358
+ }
8359
+ if (code === 0) {
8360
+ finish(() => resolve({ stdout, stderr }));
8361
+ return;
8362
+ }
8363
+ const failure = Object.assign(
8364
+ new Error(
8365
+ `Command failed with exit code ${code ?? "unknown"}${signal ? ` signal ${signal}` : ""}`
8366
+ ),
8367
+ { code, stdout, stderr, killed: false, signal }
8368
+ );
8369
+ finish(() => reject(failure));
8370
+ });
8371
+ timeoutHandle = setTimeout(() => {
8372
+ timedOut = true;
8373
+ try {
8374
+ child.kill("SIGTERM");
8375
+ } catch {
8376
+ }
8377
+ setTimeout(() => {
8378
+ try {
8379
+ if (!child.killed) child.kill("SIGKILL");
8380
+ } catch {
8381
+ }
8382
+ }, 2e3).unref();
8383
+ }, options.timeoutMs);
8384
+ });
8385
+ };
8386
+ var createShellHandlers = ({
8387
+ state,
8388
+ log: log2
8389
+ }) => {
8390
+ const outputCache = new ToolOutputCacheStoreImpl2();
8391
+ const handleShellExec = async (id, params) => {
8392
+ const command = params?.command?.trim() ?? "";
8393
+ if (!command) {
8394
+ sendError(id, {
8395
+ code: RPC_ERROR_CODE7.INVALID_PARAMS,
8396
+ message: "command is required"
8397
+ });
8398
+ return;
8399
+ }
8400
+ const requestedTimeout = params?.timeout_seconds ?? DEFAULT_TIMEOUT_SECONDS;
8401
+ if (!Number.isFinite(requestedTimeout) || requestedTimeout <= 0) {
8402
+ sendError(id, {
8403
+ code: RPC_ERROR_CODE7.INVALID_PARAMS,
8404
+ message: "timeout_seconds must be a positive number"
8405
+ });
8406
+ return;
8407
+ }
8408
+ const timeoutSeconds = Math.max(
8409
+ 1,
8410
+ Math.min(Math.trunc(requestedTimeout), MAX_TIMEOUT_SECONDS)
8411
+ );
8412
+ const cwd = resolveShellCwd(state, params?.cwd);
8413
+ if (!cwd) {
8414
+ sendError(id, {
8415
+ code: RPC_ERROR_CODE7.INVALID_PARAMS,
8416
+ message: "cwd is outside sandbox root"
8417
+ });
8418
+ return;
8419
+ }
8420
+ const startedAt = Date.now();
8421
+ const commandSummary = summarizeCommand(command);
8422
+ const commandPreview = truncateCommandPreview(command);
8423
+ log2(
8424
+ `shell.exec.start origin=ui_bang cwd=${cwd} timeout_s=${timeoutSeconds} command="${commandSummary}"`
8425
+ );
8426
+ let rawStdout = "";
8427
+ let rawStderr = "";
8428
+ let exitCode = 0;
8429
+ let signal = null;
8430
+ try {
8431
+ const result2 = await runShellWithUserShell(command, {
8432
+ cwd,
8433
+ timeoutMs: timeoutSeconds * 1e3,
8434
+ maxOutputBytes: MAX_OUTPUT_BYTES
8435
+ });
8436
+ rawStdout = result2.stdout;
8437
+ rawStderr = result2.stderr;
8438
+ } catch (error) {
8439
+ const execError = error;
8440
+ rawStdout = execError.stdout ?? "";
8441
+ rawStderr = execError.stderr ?? "";
8442
+ exitCode = typeof execError.code === "number" ? execError.code : null;
8443
+ signal = execError.signal ?? null;
8444
+ }
8445
+ const stdoutTruncated = needsInlineTruncation(rawStdout);
8446
+ const stderrTruncated = needsInlineTruncation(rawStderr);
8447
+ const stdout = stdoutTruncated ? excerptText(rawStdout) : rawStdout;
8448
+ const stderr = stderrTruncated ? excerptText(rawStderr) : rawStderr;
8449
+ const combinedTruncated = stdoutTruncated || stderrTruncated;
8450
+ let stdoutCacheId;
8451
+ let stderrCacheId;
8452
+ if (stdoutTruncated && rawStdout.trim()) {
8453
+ const saved = await outputCache.save({
8454
+ tool_call_id: `shell_exec_stdout_${crypto6.randomUUID()}`,
8455
+ tool_name: "shell.exec",
8456
+ content: rawStdout
8457
+ });
8458
+ stdoutCacheId = saved.id;
8459
+ }
8460
+ if (stderrTruncated && rawStderr.trim()) {
8461
+ const saved = await outputCache.save({
8462
+ tool_call_id: `shell_exec_stderr_${crypto6.randomUUID()}`,
8463
+ tool_name: "shell.exec",
8464
+ content: rawStderr
8465
+ });
8466
+ stderrCacheId = saved.id;
8467
+ }
8468
+ const result = {
8469
+ command_preview: commandPreview,
8470
+ exit_code: exitCode,
8471
+ signal,
8472
+ stdout,
8473
+ stderr,
8474
+ truncated: {
8475
+ stdout: stdoutTruncated,
8476
+ stderr: stderrTruncated,
8477
+ combined: combinedTruncated
8478
+ },
8479
+ duration_ms: Date.now() - startedAt,
8480
+ ...stdoutCacheId ? { stdout_cache_id: stdoutCacheId } : {},
8481
+ ...stderrCacheId ? { stderr_cache_id: stderrCacheId } : {}
8482
+ };
8483
+ sendResult(id, result);
8484
+ log2(
8485
+ `shell.exec.done origin=ui_bang duration_ms=${result.duration_ms} exit_code=${String(result.exit_code)} signal=${result.signal ?? "-"}`
8486
+ );
8487
+ };
8488
+ return {
8489
+ handleShellExec
8490
+ };
8491
+ };
8492
+
7805
8493
  // src/rpc/handlers.ts
8494
+ var SUPPORTED_TUI_THEMES = /* @__PURE__ */ new Set([
8495
+ "codelia",
8496
+ "ocean",
8497
+ "forest",
8498
+ "rose",
8499
+ "sakura",
8500
+ "mauve",
8501
+ "plum",
8502
+ "iris",
8503
+ "crimson",
8504
+ "wine"
8505
+ ]);
7806
8506
  var createRuntimeHandlers = ({
7807
8507
  state,
7808
8508
  getAgent,
@@ -7907,10 +8607,15 @@ var createRuntimeHandlers = ({
7907
8607
  log2("startup onboarding skipped (model not selected)");
7908
8608
  return;
7909
8609
  }
7910
- const configPath = resolveConfigPath();
7911
- await updateModelConfig2(configPath, { provider, name: selectedModel });
8610
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
8611
+ const modelTarget = await updateModel(workingDir, {
8612
+ provider,
8613
+ name: selectedModel
8614
+ });
7912
8615
  state.agent = null;
7913
- log2(`startup onboarding completed: ${provider}/${selectedModel}`);
8616
+ log2(
8617
+ `startup onboarding completed: ${provider}/${selectedModel} scope=${modelTarget.scope} path=${modelTarget.path}`
8618
+ );
7914
8619
  };
7915
8620
  const launchStartupOnboarding = () => {
7916
8621
  if (startupOnboardingStarted) {
@@ -7956,17 +8661,24 @@ var createRuntimeHandlers = ({
7956
8661
  state,
7957
8662
  getAgent
7958
8663
  });
8664
+ const { handleShellExec } = createShellHandlers({
8665
+ state,
8666
+ log: log2
8667
+ });
7959
8668
  const handleInitialize = (id, params) => {
7960
8669
  const result = {
7961
8670
  protocol_version: PROTOCOL_VERSION,
7962
8671
  server: { name: SERVER_NAME, version: SERVER_VERSION },
7963
8672
  server_capabilities: {
7964
8673
  supports_run_cancel: true,
8674
+ supports_run_diagnostics: true,
8675
+ supports_shell_exec: true,
7965
8676
  supports_ui_requests: true,
7966
8677
  supports_mcp_list: true,
7967
8678
  supports_skills_list: true,
7968
8679
  supports_context_inspect: true,
7969
8680
  supports_tool_call: true,
8681
+ supports_theme_set: true,
7970
8682
  supports_permission_preflight_events: true
7971
8683
  }
7972
8684
  };
@@ -7982,9 +8694,43 @@ var createRuntimeHandlers = ({
7982
8694
  `ui.context.update cwd=${params.cwd ?? "-"} file=${params.active_file?.path ?? "-"}`
7983
8695
  );
7984
8696
  };
8697
+ const handleThemeSet = async (id, params) => {
8698
+ if (state.activeRunId) {
8699
+ sendError(id, { code: RPC_ERROR_CODE8.RUNTIME_BUSY, message: "runtime busy" });
8700
+ return;
8701
+ }
8702
+ const name = params?.name?.trim().toLowerCase();
8703
+ if (!name) {
8704
+ sendError(id, { code: RPC_ERROR_CODE8.INVALID_PARAMS, message: "theme name is required" });
8705
+ return;
8706
+ }
8707
+ if (!SUPPORTED_TUI_THEMES.has(name)) {
8708
+ sendError(id, {
8709
+ code: RPC_ERROR_CODE8.INVALID_PARAMS,
8710
+ message: `unsupported theme: ${name}`
8711
+ });
8712
+ return;
8713
+ }
8714
+ const workingDir = state.lastUiContext?.cwd ?? state.runtimeWorkingDir ?? process.cwd();
8715
+ try {
8716
+ const target = await updateTuiTheme(workingDir, name);
8717
+ const result = {
8718
+ name,
8719
+ scope: target.scope,
8720
+ path: target.path
8721
+ };
8722
+ sendResult(id, result);
8723
+ log2(`theme.set ${name} scope=${target.scope} path=${target.path}`);
8724
+ } catch (error) {
8725
+ sendError(id, { code: RPC_ERROR_CODE8.RUNTIME_INTERNAL, message: String(error) });
8726
+ }
8727
+ };
7985
8728
  const handleAuthLogout = async (id, params) => {
7986
8729
  if (state.activeRunId) {
7987
- sendError(id, { code: RPC_ERROR_CODE7.RUNTIME_BUSY, message: "runtime busy" });
8730
+ sendError(id, {
8731
+ code: RPC_ERROR_CODE8.RUNTIME_BUSY,
8732
+ message: "runtime busy"
8733
+ });
7988
8734
  return;
7989
8735
  }
7990
8736
  const clearSession = params?.clear_session ?? true;
@@ -7992,7 +8738,7 @@ var createRuntimeHandlers = ({
7992
8738
  const supportsConfirm = !!state.uiCapabilities?.supports_confirm;
7993
8739
  if (!supportsConfirm) {
7994
8740
  sendError(id, {
7995
- code: RPC_ERROR_CODE7.RUNTIME_INTERNAL,
8741
+ code: RPC_ERROR_CODE8.RUNTIME_INTERNAL,
7996
8742
  message: "UI confirmation is required for logout"
7997
8743
  });
7998
8744
  return;
@@ -8034,7 +8780,7 @@ var createRuntimeHandlers = ({
8034
8780
  log2(`auth.logout session_cleared=${clearSession}`);
8035
8781
  } catch (error) {
8036
8782
  sendError(id, {
8037
- code: RPC_ERROR_CODE7.RUNTIME_INTERNAL,
8783
+ code: RPC_ERROR_CODE8.RUNTIME_INTERNAL,
8038
8784
  message: `auth logout failed: ${String(error)}`
8039
8785
  });
8040
8786
  }
@@ -8059,6 +8805,8 @@ var createRuntimeHandlers = ({
8059
8805
  return handleModelSet(req.id, req.params);
8060
8806
  case "tool.call":
8061
8807
  return handleToolCall(req.id, req.params);
8808
+ case "shell.exec":
8809
+ return handleShellExec(req.id, req.params);
8062
8810
  case "mcp.list":
8063
8811
  await mcpManager.start?.();
8064
8812
  return sendResult(
@@ -8069,8 +8817,13 @@ var createRuntimeHandlers = ({
8069
8817
  return handleSkillsList(req.id, req.params);
8070
8818
  case "context.inspect":
8071
8819
  return handleContextInspect(req.id, req.params);
8820
+ case "theme.set":
8821
+ return handleThemeSet(req.id, req.params);
8072
8822
  default:
8073
- return sendError(req.id, { code: RPC_ERROR_CODE7.METHOD_NOT_FOUND, message: "method not found" });
8823
+ return sendError(req.id, {
8824
+ code: RPC_ERROR_CODE8.METHOD_NOT_FOUND,
8825
+ message: "method not found"
8826
+ });
8074
8827
  }
8075
8828
  };
8076
8829
  const handleNotification = (note) => {
@@ -8097,7 +8850,7 @@ var createRuntimeHandlers = ({
8097
8850
  };
8098
8851
 
8099
8852
  // src/runtime-state.ts
8100
- import crypto6 from "crypto";
8853
+ import crypto7 from "crypto";
8101
8854
  var RuntimeState = class {
8102
8855
  runSeq = /* @__PURE__ */ new Map();
8103
8856
  uiRequestCounter = 0;
@@ -8120,11 +8873,12 @@ var RuntimeState = class {
8120
8873
  loadedSkillVersions = /* @__PURE__ */ new Map();
8121
8874
  runtimeWorkingDir = null;
8122
8875
  runtimeSandboxRoot = null;
8876
+ diagnosticsEnabled = false;
8123
8877
  nextRunId() {
8124
- return crypto6.randomUUID();
8878
+ return crypto7.randomUUID();
8125
8879
  }
8126
8880
  nextSessionId() {
8127
- return crypto6.randomUUID();
8881
+ return crypto7.randomUUID();
8128
8882
  }
8129
8883
  beginRun(runId, uiContext) {
8130
8884
  this.activeRunId = runId;
@@ -8213,10 +8967,16 @@ var RuntimeState = class {
8213
8967
  };
8214
8968
 
8215
8969
  // src/runtime.ts
8970
+ var envTruthy2 = (value) => {
8971
+ if (!value) return false;
8972
+ const normalized = value.trim().toLowerCase();
8973
+ return normalized === "1" || normalized === "true";
8974
+ };
8216
8975
  var startRuntime = () => {
8217
8976
  void (async () => {
8218
8977
  const state = new RuntimeState();
8219
- const workingDir = process.env.CODELIA_SANDBOX_ROOT ? path15.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
8978
+ state.diagnosticsEnabled = envTruthy2(process.env.CODELIA_DIAGNOSTICS);
8979
+ const workingDir = process.env.CODELIA_SANDBOX_ROOT ? path16.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
8220
8980
  state.runtimeWorkingDir = workingDir;
8221
8981
  state.runtimeSandboxRoot = workingDir;
8222
8982
  const sessionStateStore = new SessionStateStoreImpl2({