oh-my-opencode-slim 0.8.3 → 0.8.4

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.
package/dist/index.js CHANGED
@@ -3037,7 +3037,7 @@ var require_main = __commonJS((exports) => {
3037
3037
  var ril_1 = require_ril();
3038
3038
  ril_1.default.install();
3039
3039
  var path6 = __require("path");
3040
- var os4 = __require("os");
3040
+ var os3 = __require("os");
3041
3041
  var crypto_1 = __require("crypto");
3042
3042
  var net_1 = __require("net");
3043
3043
  var api_1 = require_api();
@@ -3180,7 +3180,7 @@ var require_main = __commonJS((exports) => {
3180
3180
  if (XDG_RUNTIME_DIR) {
3181
3181
  result = path6.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
3182
3182
  } else {
3183
- result = path6.join(os4.tmpdir(), `vscode-${randomSuffix}.sock`);
3183
+ result = path6.join(os3.tmpdir(), `vscode-${randomSuffix}.sock`);
3184
3184
  }
3185
3185
  const limit = safeIpcPathLengths.get(process.platform);
3186
3186
  if (limit !== undefined && result.length > limit) {
@@ -3277,6 +3277,243 @@ var require_main = __commonJS((exports) => {
3277
3277
  exports.createMessageConnection = createMessageConnection;
3278
3278
  });
3279
3279
 
3280
+ // node_modules/isexe/dist/commonjs/index.min.js
3281
+ var require_index_min = __commonJS((exports) => {
3282
+ var a = (t, e) => () => (e || t((e = { exports: {} }).exports, e), e.exports);
3283
+ var _ = a((i) => {
3284
+ Object.defineProperty(i, "__esModule", { value: true });
3285
+ i.sync = i.isexe = undefined;
3286
+ var M = __require("fs"), x = __require("fs/promises"), q = async (t, e = {}) => {
3287
+ let { ignoreErrors: r = false } = e;
3288
+ try {
3289
+ return d(await (0, x.stat)(t), e);
3290
+ } catch (s) {
3291
+ let n = s;
3292
+ if (r || n.code === "EACCES")
3293
+ return false;
3294
+ throw n;
3295
+ }
3296
+ };
3297
+ i.isexe = q;
3298
+ var m = (t, e = {}) => {
3299
+ let { ignoreErrors: r = false } = e;
3300
+ try {
3301
+ return d((0, M.statSync)(t), e);
3302
+ } catch (s) {
3303
+ let n = s;
3304
+ if (r || n.code === "EACCES")
3305
+ return false;
3306
+ throw n;
3307
+ }
3308
+ };
3309
+ i.sync = m;
3310
+ var d = (t, e) => t.isFile() && A(t, e), A = (t, e) => {
3311
+ let r = e.uid ?? process.getuid?.(), s = e.groups ?? process.getgroups?.() ?? [], n = e.gid ?? process.getgid?.() ?? s[0];
3312
+ if (r === undefined || n === undefined)
3313
+ throw new Error("cannot get uid or gid");
3314
+ let u = new Set([n, ...s]), c = t.mode, S = t.uid, P = t.gid, f = parseInt("100", 8), l = parseInt("010", 8), j = parseInt("001", 8), C = f | l;
3315
+ return !!(c & j || c & l && u.has(P) || c & f && S === r || c & C && r === 0);
3316
+ };
3317
+ });
3318
+ var g = a((o) => {
3319
+ Object.defineProperty(o, "__esModule", { value: true });
3320
+ o.sync = o.isexe = undefined;
3321
+ var T = __require("fs"), I = __require("fs/promises"), D = __require("path"), F = async (t, e = {}) => {
3322
+ let { ignoreErrors: r = false } = e;
3323
+ try {
3324
+ return y(await (0, I.stat)(t), t, e);
3325
+ } catch (s) {
3326
+ let n = s;
3327
+ if (r || n.code === "EACCES")
3328
+ return false;
3329
+ throw n;
3330
+ }
3331
+ };
3332
+ o.isexe = F;
3333
+ var L = (t, e = {}) => {
3334
+ let { ignoreErrors: r = false } = e;
3335
+ try {
3336
+ return y((0, T.statSync)(t), t, e);
3337
+ } catch (s) {
3338
+ let n = s;
3339
+ if (r || n.code === "EACCES")
3340
+ return false;
3341
+ throw n;
3342
+ }
3343
+ };
3344
+ o.sync = L;
3345
+ var B = (t, e) => {
3346
+ let { pathExt: r = process.env.PATHEXT || "" } = e, s = r.split(D.delimiter);
3347
+ if (s.indexOf("") !== -1)
3348
+ return true;
3349
+ for (let n of s) {
3350
+ let u = n.toLowerCase(), c = t.substring(t.length - u.length).toLowerCase();
3351
+ if (u && c === u)
3352
+ return true;
3353
+ }
3354
+ return false;
3355
+ }, y = (t, e, r) => t.isFile() && B(e, r);
3356
+ });
3357
+ var p = a((h) => {
3358
+ Object.defineProperty(h, "__esModule", { value: true });
3359
+ });
3360
+ var v = exports && exports.__createBinding || (Object.create ? function(t, e, r, s) {
3361
+ s === undefined && (s = r);
3362
+ var n = Object.getOwnPropertyDescriptor(e, r);
3363
+ (!n || ("get" in n ? !e.__esModule : n.writable || n.configurable)) && (n = { enumerable: true, get: function() {
3364
+ return e[r];
3365
+ } }), Object.defineProperty(t, s, n);
3366
+ } : function(t, e, r, s) {
3367
+ s === undefined && (s = r), t[s] = e[r];
3368
+ });
3369
+ var G = exports && exports.__setModuleDefault || (Object.create ? function(t, e) {
3370
+ Object.defineProperty(t, "default", { enumerable: true, value: e });
3371
+ } : function(t, e) {
3372
+ t.default = e;
3373
+ });
3374
+ var w = exports && exports.__importStar || function() {
3375
+ var t = function(e) {
3376
+ return t = Object.getOwnPropertyNames || function(r) {
3377
+ var s = [];
3378
+ for (var n in r)
3379
+ Object.prototype.hasOwnProperty.call(r, n) && (s[s.length] = n);
3380
+ return s;
3381
+ }, t(e);
3382
+ };
3383
+ return function(e) {
3384
+ if (e && e.__esModule)
3385
+ return e;
3386
+ var r = {};
3387
+ if (e != null)
3388
+ for (var s = t(e), n = 0;n < s.length; n++)
3389
+ s[n] !== "default" && v(r, e, s[n]);
3390
+ return G(r, e), r;
3391
+ };
3392
+ }();
3393
+ var X = exports && exports.__exportStar || function(t, e) {
3394
+ for (var r in t)
3395
+ r !== "default" && !Object.prototype.hasOwnProperty.call(e, r) && v(e, t, r);
3396
+ };
3397
+ Object.defineProperty(exports, "__esModule", { value: true });
3398
+ exports.sync = exports.isexe = exports.posix = exports.win32 = undefined;
3399
+ var E = w(_());
3400
+ exports.posix = E;
3401
+ var O = w(g());
3402
+ exports.win32 = O;
3403
+ X(p(), exports);
3404
+ var H = process.env._ISEXE_TEST_PLATFORM_ || process.platform;
3405
+ var b = H === "win32" ? O : E;
3406
+ exports.isexe = b.isexe;
3407
+ exports.sync = b.sync;
3408
+ });
3409
+
3410
+ // node_modules/which/lib/index.js
3411
+ var require_lib = __commonJS((exports, module) => {
3412
+ var { isexe, sync: isexeSync } = require_index_min();
3413
+ var { join: join11, delimiter, sep, posix } = __require("path");
3414
+ var isWindows = process.platform === "win32";
3415
+ var rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? "" : sep}]`.replace(/(\\)/g, "\\$1"));
3416
+ var rRel = new RegExp(`^\\.${rSlash.source}`);
3417
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
3418
+ var getPathInfo = (cmd, {
3419
+ path: optPath = process.env.PATH,
3420
+ pathExt: optPathExt = process.env.PATHEXT,
3421
+ delimiter: optDelimiter = delimiter
3422
+ }) => {
3423
+ const pathEnv = cmd.match(rSlash) ? [""] : [
3424
+ ...isWindows ? [process.cwd()] : [],
3425
+ ...(optPath || "").split(optDelimiter)
3426
+ ];
3427
+ if (isWindows) {
3428
+ const pathExtExe = optPathExt || [".EXE", ".CMD", ".BAT", ".COM"].join(optDelimiter);
3429
+ const pathExt = pathExtExe.split(optDelimiter).flatMap((item) => [item, item.toLowerCase()]);
3430
+ if (cmd.includes(".") && pathExt[0] !== "") {
3431
+ pathExt.unshift("");
3432
+ }
3433
+ return { pathEnv, pathExt, pathExtExe };
3434
+ }
3435
+ return { pathEnv, pathExt: [""] };
3436
+ };
3437
+ var getPathPart = (raw, cmd) => {
3438
+ const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw;
3439
+ const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : "";
3440
+ return prefix + join11(pathPart, cmd);
3441
+ };
3442
+ var which = async (cmd, opt = {}) => {
3443
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
3444
+ const found = [];
3445
+ for (const envPart of pathEnv) {
3446
+ const p = getPathPart(envPart, cmd);
3447
+ for (const ext of pathExt) {
3448
+ const withExt = p + ext;
3449
+ const is = await isexe(withExt, { pathExt: pathExtExe, ignoreErrors: true });
3450
+ if (is) {
3451
+ if (!opt.all) {
3452
+ return withExt;
3453
+ }
3454
+ found.push(withExt);
3455
+ }
3456
+ }
3457
+ }
3458
+ if (opt.all && found.length) {
3459
+ return found;
3460
+ }
3461
+ if (opt.nothrow) {
3462
+ return null;
3463
+ }
3464
+ throw getNotFoundError(cmd);
3465
+ };
3466
+ var whichSync = (cmd, opt = {}) => {
3467
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
3468
+ const found = [];
3469
+ for (const pathEnvPart of pathEnv) {
3470
+ const p = getPathPart(pathEnvPart, cmd);
3471
+ for (const ext of pathExt) {
3472
+ const withExt = p + ext;
3473
+ const is = isexeSync(withExt, { pathExt: pathExtExe, ignoreErrors: true });
3474
+ if (is) {
3475
+ if (!opt.all) {
3476
+ return withExt;
3477
+ }
3478
+ found.push(withExt);
3479
+ }
3480
+ }
3481
+ }
3482
+ if (opt.all && found.length) {
3483
+ return found;
3484
+ }
3485
+ if (opt.nothrow) {
3486
+ return null;
3487
+ }
3488
+ throw getNotFoundError(cmd);
3489
+ };
3490
+ module.exports = which;
3491
+ which.sync = whichSync;
3492
+ });
3493
+
3494
+ // src/cli/paths.ts
3495
+ import { homedir } from "os";
3496
+ import { dirname, join } from "path";
3497
+ function getDefaultOpenCodeConfigDir() {
3498
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
3499
+ return join(userConfigDir, "opencode");
3500
+ }
3501
+ function getCustomOpenCodeConfigDir() {
3502
+ const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
3503
+ return configDir || undefined;
3504
+ }
3505
+ function getConfigDir() {
3506
+ const customConfigDir = getCustomOpenCodeConfigDir();
3507
+ if (customConfigDir) {
3508
+ return customConfigDir;
3509
+ }
3510
+ return getDefaultOpenCodeConfigDir();
3511
+ }
3512
+ function getOpenCodeConfigPaths() {
3513
+ const configDir = getDefaultOpenCodeConfigDir();
3514
+ return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
3515
+ }
3516
+
3280
3517
  // src/cli/custom-skills.ts
3281
3518
  var CUSTOM_SKILLS = [
3282
3519
  {
@@ -3378,10 +3615,10 @@ var SUBAGENT_DELEGATION_RULES = {
3378
3615
  var DEFAULT_MODELS = {
3379
3616
  orchestrator: undefined,
3380
3617
  oracle: "openai/gpt-5.4",
3381
- librarian: "openai/gpt-5-codex",
3382
- explorer: "openai/gpt-5-codex",
3383
- designer: "kimi-for-coding/k2p5",
3384
- fixer: "openai/gpt-5-codex"
3618
+ librarian: "openai/gpt-5.4-mini",
3619
+ explorer: "openai/gpt-5.4-mini",
3620
+ designer: "openai/gpt-5.4-mini",
3621
+ fixer: "openai/gpt-5.4-mini"
3385
3622
  };
3386
3623
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
3387
3624
  var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
@@ -3389,21 +3626,8 @@ var MAX_POLL_TIME_MS = 5 * 60 * 1000;
3389
3626
  var FALLBACK_FAILOVER_TIMEOUT_MS = 15000;
3390
3627
  // src/config/loader.ts
3391
3628
  import * as fs from "fs";
3392
- import * as os from "os";
3393
3629
  import * as path from "path";
3394
3630
 
3395
- // src/cli/paths.ts
3396
- import { homedir } from "os";
3397
- import { join } from "path";
3398
- function getConfigDir() {
3399
- const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
3400
- return join(userConfigDir, "opencode");
3401
- }
3402
- function getOpenCodeConfigPaths() {
3403
- const configDir = getConfigDir();
3404
- return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
3405
- }
3406
-
3407
3631
  // src/config/agent-mcps.ts
3408
3632
  var DEFAULT_AGENT_MCPS = {
3409
3633
  orchestrator: ["websearch"],
@@ -5682,7 +5906,7 @@ class Doc {
5682
5906
  var version = {
5683
5907
  major: 4,
5684
5908
  minor: 3,
5685
- patch: 5
5909
+ patch: 6
5686
5910
  };
5687
5911
 
5688
5912
  // node_modules/zod/v4/core/schemas.js
@@ -6968,7 +7192,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
6968
7192
  if (keyResult instanceof Promise) {
6969
7193
  throw new Error("Async schemas not supported in object keys currently");
6970
7194
  }
6971
- const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number");
7195
+ const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
6972
7196
  if (checkNumericKey) {
6973
7197
  const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
6974
7198
  if (retryResult instanceof Promise) {
@@ -14339,7 +14563,7 @@ function finalize(ctx, schema) {
14339
14563
  }
14340
14564
  }
14341
14565
  }
14342
- if (refSchema.$ref) {
14566
+ if (refSchema.$ref && refSeen.def) {
14343
14567
  for (const key in schema2) {
14344
14568
  if (key === "$ref" || key === "allOf")
14345
14569
  continue;
@@ -17049,10 +17273,12 @@ var BackgroundTaskConfigSchema = exports_external.object({
17049
17273
  var FailoverConfigSchema = exports_external.object({
17050
17274
  enabled: exports_external.boolean().default(true),
17051
17275
  timeoutMs: exports_external.number().min(0).default(15000),
17276
+ retryDelayMs: exports_external.number().min(0).default(500),
17052
17277
  chains: FallbackChainsSchema.default({})
17053
17278
  });
17054
17279
  var PluginConfigSchema = exports_external.object({
17055
17280
  preset: exports_external.string().optional(),
17281
+ setDefaultAgent: exports_external.boolean().optional(),
17056
17282
  scoringEngineVersion: exports_external.enum(["v1", "v2-shadow", "v2"]).optional(),
17057
17283
  balanceProviderUsage: exports_external.boolean().optional(),
17058
17284
  manualPlan: ManualPlanSchema.optional(),
@@ -17066,9 +17292,6 @@ var PluginConfigSchema = exports_external.object({
17066
17292
 
17067
17293
  // src/config/loader.ts
17068
17294
  var PROMPTS_DIR_NAME = "oh-my-opencode-slim";
17069
- function getUserConfigDir() {
17070
- return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
17071
- }
17072
17295
  function loadConfigFromPath(configPath) {
17073
17296
  try {
17074
17297
  const content = fs.readFileSync(configPath, "utf-8");
@@ -17116,7 +17339,7 @@ function deepMerge(base, override) {
17116
17339
  return result;
17117
17340
  }
17118
17341
  function loadPluginConfig(directory) {
17119
- const userConfigBasePath = path.join(getUserConfigDir(), "opencode", "oh-my-opencode-slim");
17342
+ const userConfigBasePath = path.join(getConfigDir(), "oh-my-opencode-slim");
17120
17343
  const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
17121
17344
  const userConfigPath = findConfigPath(userConfigBasePath);
17122
17345
  const projectConfigPath = findConfigPath(projectConfigBasePath);
@@ -17149,7 +17372,7 @@ function loadPluginConfig(directory) {
17149
17372
  }
17150
17373
  function loadAgentPrompt(agentName, preset) {
17151
17374
  const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
17152
- const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
17375
+ const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
17153
17376
  const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
17154
17377
  const result = {};
17155
17378
  const readFirstPrompt = (fileName, errorPrefix) => {
@@ -17700,9 +17923,9 @@ function getAgentConfigs(config2) {
17700
17923
 
17701
17924
  // src/utils/logger.ts
17702
17925
  import * as fs2 from "fs";
17703
- import * as os2 from "os";
17926
+ import * as os from "os";
17704
17927
  import * as path2 from "path";
17705
- var logFile = path2.join(os2.tmpdir(), "oh-my-opencode-slim.log");
17928
+ var logFile = path2.join(os.tmpdir(), "oh-my-opencode-slim.log");
17706
17929
  function log(message, data) {
17707
17930
  try {
17708
17931
  const timestamp = new Date().toISOString();
@@ -18191,14 +18414,23 @@ class BackgroundTaskManager {
18191
18414
  await this.client.session.prompt(args);
18192
18415
  return;
18193
18416
  }
18194
- await Promise.race([
18195
- this.client.session.prompt(args),
18196
- new Promise((_, reject) => {
18197
- setTimeout(() => {
18198
- reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
18199
- }, timeoutMs);
18200
- })
18201
- ]);
18417
+ const sessionId = args.path.id;
18418
+ let timer;
18419
+ try {
18420
+ const promptPromise = this.client.session.prompt(args);
18421
+ promptPromise.catch(() => {});
18422
+ await Promise.race([
18423
+ promptPromise,
18424
+ new Promise((_, reject) => {
18425
+ timer = setTimeout(() => {
18426
+ this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
18427
+ reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
18428
+ }, timeoutMs);
18429
+ })
18430
+ ]);
18431
+ } finally {
18432
+ clearTimeout(timer);
18433
+ }
18202
18434
  }
18203
18435
  calculateToolPermissions(agentName) {
18204
18436
  const allowedSubagents = this.getSubagentRules(agentName);
@@ -18242,11 +18474,15 @@ class BackgroundTaskManager {
18242
18474
  });
18243
18475
  const fallbackEnabled = this.config?.fallback?.enabled ?? true;
18244
18476
  const timeoutMs = fallbackEnabled ? this.config?.fallback?.timeoutMs ?? FALLBACK_FAILOVER_TIMEOUT_MS : 0;
18477
+ const retryDelayMs = this.config?.fallback?.retryDelayMs ?? 500;
18245
18478
  const chain = fallbackEnabled ? this.resolveFallbackChain(task.agent) : [];
18246
18479
  const attemptModels = chain.length > 0 ? chain : [undefined];
18247
18480
  const errors3 = [];
18248
18481
  let succeeded = false;
18249
- for (const model of attemptModels) {
18482
+ const sessionId = session.data.id;
18483
+ for (let i = 0;i < attemptModels.length; i++) {
18484
+ const model = attemptModels[i];
18485
+ const modelLabel = model ?? "default-model";
18250
18486
  try {
18251
18487
  const body = {
18252
18488
  ...basePromptBody,
@@ -18259,8 +18495,11 @@ class BackgroundTaskManager {
18259
18495
  }
18260
18496
  body.model = ref;
18261
18497
  }
18498
+ if (i > 0) {
18499
+ log(`[background-manager] fallback attempt ${i + 1}/${attemptModels.length}: ${modelLabel}`, { taskId: task.id });
18500
+ }
18262
18501
  await this.promptWithTimeout({
18263
- path: { id: session.data.id },
18502
+ path: { id: sessionId },
18264
18503
  body,
18265
18504
  query: promptQuery
18266
18505
  }, timeoutMs);
@@ -18268,10 +18507,17 @@ class BackgroundTaskManager {
18268
18507
  break;
18269
18508
  } catch (error48) {
18270
18509
  const msg = error48 instanceof Error ? error48.message : String(error48);
18271
- if (model) {
18272
- errors3.push(`${model}: ${msg}`);
18273
- } else {
18274
- errors3.push(`default-model: ${msg}`);
18510
+ errors3.push(`${modelLabel}: ${msg}`);
18511
+ log(`[background-manager] model failed: ${modelLabel} \u2014 ${msg}`, {
18512
+ taskId: task.id
18513
+ });
18514
+ if (i < attemptModels.length - 1) {
18515
+ try {
18516
+ await this.client.session.abort({
18517
+ path: { id: sessionId }
18518
+ });
18519
+ await new Promise((r) => setTimeout(r, retryDelayMs));
18520
+ } catch {}
18275
18521
  }
18276
18522
  }
18277
18523
  }
@@ -18640,16 +18886,16 @@ class TmuxSessionManager {
18640
18886
  import * as fs3 from "fs";
18641
18887
  import * as path4 from "path";
18642
18888
  // src/hooks/auto-update-checker/constants.ts
18643
- import * as os3 from "os";
18889
+ import * as os2 from "os";
18644
18890
  import * as path3 from "path";
18645
18891
  var PACKAGE_NAME = "oh-my-opencode-slim";
18646
18892
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
18647
18893
  var NPM_FETCH_TIMEOUT = 5000;
18648
18894
  function getCacheDir() {
18649
18895
  if (process.platform === "win32") {
18650
- return path3.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
18896
+ return path3.join(process.env.LOCALAPPDATA ?? os2.homedir(), "opencode");
18651
18897
  }
18652
- return path3.join(os3.homedir(), ".cache", "opencode");
18898
+ return path3.join(os2.homedir(), ".cache", "opencode");
18653
18899
  }
18654
18900
  var CACHE_DIR = getCacheDir();
18655
18901
  var INSTALLED_PACKAGE_JSON = path3.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
@@ -19186,6 +19432,216 @@ ${buildRetryGuidance(detected)}`;
19186
19432
  }
19187
19433
  };
19188
19434
  }
19435
+ // src/hooks/foreground-fallback/index.ts
19436
+ var RATE_LIMIT_PATTERNS = [
19437
+ /\b429\b/,
19438
+ /rate.?limit/i,
19439
+ /too many requests/i,
19440
+ /quota.?exceeded/i,
19441
+ /usage.?exceeded/i,
19442
+ /usage limit/i,
19443
+ /overloaded/i,
19444
+ /resource.?exhausted/i,
19445
+ /insufficient.?quota/i,
19446
+ /high concurrency/i,
19447
+ /reduce concurrency/i
19448
+ ];
19449
+ function isRateLimitError(error48) {
19450
+ if (!error48 || typeof error48 !== "object")
19451
+ return false;
19452
+ const err = error48;
19453
+ const text = [
19454
+ err.message ?? "",
19455
+ String(err.data?.statusCode ?? ""),
19456
+ err.data?.message ?? "",
19457
+ err.data?.responseBody ?? ""
19458
+ ].join(" ");
19459
+ return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
19460
+ }
19461
+ function parseModel(model) {
19462
+ const slash = model.indexOf("/");
19463
+ if (slash <= 0 || slash >= model.length - 1)
19464
+ return null;
19465
+ return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
19466
+ }
19467
+ var DEDUP_WINDOW_MS = 5000;
19468
+
19469
+ class ForegroundFallbackManager {
19470
+ client;
19471
+ chains;
19472
+ enabled;
19473
+ sessionModel = new Map;
19474
+ sessionAgent = new Map;
19475
+ sessionTried = new Map;
19476
+ inProgress = new Set;
19477
+ lastTrigger = new Map;
19478
+ constructor(client, chains, enabled) {
19479
+ this.client = client;
19480
+ this.chains = chains;
19481
+ this.enabled = enabled;
19482
+ }
19483
+ async handleEvent(rawEvent) {
19484
+ if (!this.enabled)
19485
+ return;
19486
+ const event = rawEvent;
19487
+ if (!event?.type)
19488
+ return;
19489
+ switch (event.type) {
19490
+ case "message.updated": {
19491
+ const info = event.properties?.info;
19492
+ if (!info)
19493
+ break;
19494
+ const sessionID = info.sessionID;
19495
+ if (!sessionID)
19496
+ break;
19497
+ if (typeof info.agent === "string") {
19498
+ this.sessionAgent.set(sessionID, info.agent);
19499
+ }
19500
+ if (typeof info.providerID === "string" && typeof info.modelID === "string") {
19501
+ this.sessionModel.set(sessionID, `${info.providerID}/${info.modelID}`);
19502
+ }
19503
+ if (info.error && isRateLimitError(info.error)) {
19504
+ await this.tryFallback(sessionID);
19505
+ }
19506
+ break;
19507
+ }
19508
+ case "session.error": {
19509
+ const props = event.properties;
19510
+ if (props?.sessionID && props.error && isRateLimitError(props.error)) {
19511
+ await this.tryFallback(props.sessionID);
19512
+ }
19513
+ break;
19514
+ }
19515
+ case "session.status": {
19516
+ const props = event.properties;
19517
+ if (!props?.sessionID || props.status?.type !== "retry")
19518
+ break;
19519
+ const msg = props.status.message?.toLowerCase() ?? "";
19520
+ if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
19521
+ await this.tryFallback(props.sessionID);
19522
+ }
19523
+ break;
19524
+ }
19525
+ case "subagent.session.created": {
19526
+ const props = event.properties;
19527
+ if (props?.sessionID && typeof props.agentName === "string") {
19528
+ this.sessionAgent.set(props.sessionID, props.agentName);
19529
+ }
19530
+ break;
19531
+ }
19532
+ case "session.deleted": {
19533
+ const props = event.properties;
19534
+ const id = props?.info?.id ?? props?.sessionID;
19535
+ if (id) {
19536
+ this.sessionModel.delete(id);
19537
+ this.sessionAgent.delete(id);
19538
+ this.sessionTried.delete(id);
19539
+ this.inProgress.delete(id);
19540
+ this.lastTrigger.delete(id);
19541
+ }
19542
+ break;
19543
+ }
19544
+ }
19545
+ }
19546
+ async tryFallback(sessionID) {
19547
+ if (!sessionID)
19548
+ return;
19549
+ if (this.inProgress.has(sessionID))
19550
+ return;
19551
+ const now = Date.now();
19552
+ if (now - (this.lastTrigger.get(sessionID) ?? 0) < DEDUP_WINDOW_MS)
19553
+ return;
19554
+ this.lastTrigger.set(sessionID, now);
19555
+ this.inProgress.add(sessionID);
19556
+ try {
19557
+ const currentModel = this.sessionModel.get(sessionID);
19558
+ const agentName = this.sessionAgent.get(sessionID);
19559
+ const chain = this.resolveChain(agentName, currentModel);
19560
+ if (!chain.length) {
19561
+ log("[foreground-fallback] no chain configured", { sessionID, agentName });
19562
+ return;
19563
+ }
19564
+ if (!this.sessionTried.has(sessionID)) {
19565
+ this.sessionTried.set(sessionID, new Set);
19566
+ }
19567
+ const tried = this.sessionTried.get(sessionID);
19568
+ if (currentModel)
19569
+ tried.add(currentModel);
19570
+ const nextModel = chain.find((m) => !tried.has(m));
19571
+ if (!nextModel) {
19572
+ log("[foreground-fallback] fallback chain exhausted", {
19573
+ sessionID,
19574
+ agentName,
19575
+ tried: [...tried]
19576
+ });
19577
+ return;
19578
+ }
19579
+ tried.add(nextModel);
19580
+ const ref = parseModel(nextModel);
19581
+ if (!ref) {
19582
+ log("[foreground-fallback] invalid model format", {
19583
+ sessionID,
19584
+ nextModel
19585
+ });
19586
+ return;
19587
+ }
19588
+ const result = await this.client.session.messages({
19589
+ path: { id: sessionID }
19590
+ });
19591
+ const messages = result.data ?? [];
19592
+ const lastUser = [...messages].reverse().find((m) => m.info.role === "user");
19593
+ if (!lastUser) {
19594
+ log("[foreground-fallback] no user message found", { sessionID });
19595
+ return;
19596
+ }
19597
+ try {
19598
+ await this.client.session.abort({ path: { id: sessionID } });
19599
+ } catch {}
19600
+ await new Promise((r) => setTimeout(r, 500));
19601
+ const sessionClient = this.client.session;
19602
+ await sessionClient.promptAsync({
19603
+ path: { id: sessionID },
19604
+ body: { parts: lastUser.parts, model: ref }
19605
+ });
19606
+ this.sessionModel.set(sessionID, nextModel);
19607
+ log("[foreground-fallback] switched to fallback model", {
19608
+ sessionID,
19609
+ agentName,
19610
+ from: currentModel,
19611
+ to: nextModel
19612
+ });
19613
+ } catch (err) {
19614
+ log("[foreground-fallback] fallback attempt failed", {
19615
+ sessionID,
19616
+ error: err instanceof Error ? err.message : String(err)
19617
+ });
19618
+ } finally {
19619
+ this.inProgress.delete(sessionID);
19620
+ }
19621
+ }
19622
+ resolveChain(agentName, currentModel) {
19623
+ if (agentName) {
19624
+ return this.chains[agentName] ?? [];
19625
+ }
19626
+ if (currentModel) {
19627
+ for (const chain of Object.values(this.chains)) {
19628
+ if (chain.includes(currentModel))
19629
+ return chain;
19630
+ }
19631
+ }
19632
+ const all = [];
19633
+ const seen = new Set;
19634
+ for (const chain of Object.values(this.chains)) {
19635
+ for (const m of chain) {
19636
+ if (!seen.has(m)) {
19637
+ seen.add(m);
19638
+ all.push(m);
19639
+ }
19640
+ }
19641
+ }
19642
+ return all;
19643
+ }
19644
+ }
19189
19645
  // src/hooks/json-error-recovery/hook.ts
19190
19646
  var JSON_ERROR_TOOL_EXCLUDE_LIST = [
19191
19647
  "bash",
@@ -31656,12 +32112,12 @@ var {spawn: spawn3 } = globalThis.Bun;
31656
32112
  // src/tools/ast-grep/constants.ts
31657
32113
  import { existsSync as existsSync5, statSync as statSync2 } from "fs";
31658
32114
  import { createRequire as createRequire2 } from "module";
31659
- import { dirname as dirname2, join as join8 } from "path";
32115
+ import { dirname as dirname3, join as join8 } from "path";
31660
32116
 
31661
32117
  // src/tools/ast-grep/downloader.ts
31662
32118
  import { chmodSync, existsSync as existsSync4, mkdirSync, unlinkSync } from "fs";
31663
32119
  import { createRequire } from "module";
31664
- import { homedir as homedir4 } from "os";
32120
+ import { homedir as homedir3 } from "os";
31665
32121
  import { join as join7 } from "path";
31666
32122
  var REPO = "ast-grep/ast-grep";
31667
32123
  var DEFAULT_VERSION = "0.40.0";
@@ -31686,11 +32142,11 @@ var PLATFORM_MAP = {
31686
32142
  function getCacheDir2() {
31687
32143
  if (process.platform === "win32") {
31688
32144
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
31689
- const base2 = localAppData || join7(homedir4(), "AppData", "Local");
32145
+ const base2 = localAppData || join7(homedir3(), "AppData", "Local");
31690
32146
  return join7(base2, "oh-my-opencode-slim", "bin");
31691
32147
  }
31692
32148
  const xdgCache = process.env.XDG_CACHE_HOME;
31693
- const base = xdgCache || join7(homedir4(), ".cache");
32149
+ const base = xdgCache || join7(homedir3(), ".cache");
31694
32150
  return join7(base, "oh-my-opencode-slim", "bin");
31695
32151
  }
31696
32152
  function getBinaryName() {
@@ -31713,8 +32169,8 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
31713
32169
  if (existsSync4(binaryPath)) {
31714
32170
  return binaryPath;
31715
32171
  }
31716
- const { arch, os: os4 } = platformInfo;
31717
- const assetName = `app-${arch}-${os4}.zip`;
32172
+ const { arch, os: os3 } = platformInfo;
32173
+ const assetName = `app-${arch}-${os3}.zip`;
31718
32174
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version3}/${assetName}`;
31719
32175
  console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
31720
32176
  try {
@@ -31813,7 +32269,7 @@ function findSgCliPathSync() {
31813
32269
  try {
31814
32270
  const require2 = createRequire2(import.meta.url);
31815
32271
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
31816
- const cliDir = dirname2(cliPkgPath);
32272
+ const cliDir = dirname3(cliPkgPath);
31817
32273
  const sgPath = join8(cliDir, binaryName);
31818
32274
  if (existsSync5(sgPath) && isValidBinary(sgPath)) {
31819
32275
  return sgPath;
@@ -31824,7 +32280,7 @@ function findSgCliPathSync() {
31824
32280
  try {
31825
32281
  const require2 = createRequire2(import.meta.url);
31826
32282
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
31827
- const pkgDir = dirname2(pkgPath);
32283
+ const pkgDir = dirname3(pkgPath);
31828
32284
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
31829
32285
  const binaryPath = join8(pkgDir, astGrepName);
31830
32286
  if (existsSync5(binaryPath) && isValidBinary(binaryPath)) {
@@ -32314,7 +32770,7 @@ var {spawn: spawn4 } = globalThis.Bun;
32314
32770
  // src/tools/grep/constants.ts
32315
32771
  import { spawnSync as spawnSync2 } from "child_process";
32316
32772
  import { existsSync as existsSync8 } from "fs";
32317
- import { dirname as dirname3, join as join10 } from "path";
32773
+ import { dirname as dirname4, join as join10 } from "path";
32318
32774
 
32319
32775
  // src/tools/grep/downloader.ts
32320
32776
  import {
@@ -32359,7 +32815,7 @@ function getDataDir() {
32359
32815
  }
32360
32816
  function getOpenCodeBundledRg() {
32361
32817
  const execPath = process.execPath;
32362
- const execDir = dirname3(execPath);
32818
+ const execDir = dirname4(execPath);
32363
32819
  const isWindows = process.platform === "win32";
32364
32820
  const rgName = isWindows ? "rg.exe" : "rg";
32365
32821
  const candidates = [
@@ -32632,17 +33088,47 @@ var grep = tool({
32632
33088
  // src/tools/lsp/client.ts
32633
33089
  var import_node = __toESM(require_main(), 1);
32634
33090
  import { readFileSync as readFileSync4 } from "fs";
32635
- import { extname, resolve } from "path";
33091
+ import { extname, resolve as resolve2 } from "path";
32636
33092
  import { Readable, Writable } from "stream";
32637
33093
  import { pathToFileURL } from "url";
32638
33094
  var {spawn: spawn5 } = globalThis.Bun;
32639
33095
 
32640
33096
  // src/tools/lsp/config.ts
32641
- import { existsSync as existsSync9 } from "fs";
32642
- import { homedir as homedir5 } from "os";
33097
+ var import_which = __toESM(require_lib(), 1);
33098
+ import { existsSync as existsSync10 } from "fs";
33099
+ import { homedir as homedir4 } from "os";
32643
33100
  import { join as join11 } from "path";
32644
33101
 
33102
+ // src/tools/lsp/config-store.ts
33103
+ var userConfig = new Map;
33104
+ function setUserLspConfig(config3) {
33105
+ userConfig.clear();
33106
+ if (config3) {
33107
+ for (const [id, server] of Object.entries(config3)) {
33108
+ if (server && typeof server === "object") {
33109
+ const s = server;
33110
+ userConfig.set(id, {
33111
+ id,
33112
+ command: s.command,
33113
+ extensions: s.extensions,
33114
+ disabled: s.disabled,
33115
+ env: s.env,
33116
+ initialization: s.initialization
33117
+ });
33118
+ }
33119
+ }
33120
+ }
33121
+ }
33122
+ function getAllUserLspConfigs() {
33123
+ return new Map(userConfig);
33124
+ }
33125
+ function hasUserLspConfig() {
33126
+ return userConfig.size > 0;
33127
+ }
33128
+
32645
33129
  // src/tools/lsp/constants.ts
33130
+ import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
33131
+ import { dirname as dirname5, resolve } from "path";
32646
33132
  var SEVERITY_MAP = {
32647
33133
  1: "error",
32648
33134
  2: "warning",
@@ -32651,22 +33137,76 @@ var SEVERITY_MAP = {
32651
33137
  };
32652
33138
  var DEFAULT_MAX_REFERENCES = 200;
32653
33139
  var DEFAULT_MAX_DIAGNOSTICS = 200;
33140
+ var LOCK_FILE_PATTERNS = [
33141
+ "package-lock.json",
33142
+ "bun.lockb",
33143
+ "bun.lock",
33144
+ "pnpm-lock.yaml",
33145
+ "yarn.lock"
33146
+ ];
33147
+ function* walkUpDirectories(start, stop) {
33148
+ let dir = resolve(start);
33149
+ try {
33150
+ if (!statSync3(dir).isDirectory()) {
33151
+ dir = dirname5(dir);
33152
+ }
33153
+ } catch {
33154
+ dir = dirname5(dir);
33155
+ }
33156
+ let prevDir = "";
33157
+ while (dir !== prevDir && dir !== "/") {
33158
+ yield dir;
33159
+ prevDir = dir;
33160
+ if (dir === stop)
33161
+ break;
33162
+ dir = dirname5(dir);
33163
+ }
33164
+ }
33165
+ function NearestRoot(includePatterns, excludePatterns) {
33166
+ return (file3) => {
33167
+ const cwd = process.cwd();
33168
+ if (excludePatterns) {
33169
+ for (const dir of walkUpDirectories(file3, cwd)) {
33170
+ for (const pattern of excludePatterns) {
33171
+ if (existsSync9(`${dir}/${pattern}`)) {
33172
+ return;
33173
+ }
33174
+ }
33175
+ }
33176
+ }
33177
+ for (const dir of walkUpDirectories(file3, cwd)) {
33178
+ for (const pattern of includePatterns) {
33179
+ if (pattern.includes("*")) {
33180
+ try {
33181
+ const entries = readdirSync2(dir);
33182
+ const regex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*")}$`);
33183
+ if (entries.some((entry) => regex.test(entry))) {
33184
+ return dir;
33185
+ }
33186
+ } catch {}
33187
+ } else if (existsSync9(`${dir}/${pattern}`)) {
33188
+ return dir;
33189
+ }
33190
+ }
33191
+ }
33192
+ return;
33193
+ };
33194
+ }
32654
33195
  var BUILTIN_SERVERS = {
33196
+ deno: {
33197
+ command: ["deno", "lsp"],
33198
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
33199
+ root: NearestRoot(["deno.json", "deno.jsonc"])
33200
+ },
32655
33201
  typescript: {
32656
33202
  command: ["typescript-language-server", "--stdio"],
32657
- extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]
33203
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
33204
+ root: NearestRoot(LOCK_FILE_PATTERNS, ["deno.json", "deno.jsonc"])
32658
33205
  },
32659
33206
  vue: {
32660
33207
  command: ["vue-language-server", "--stdio"],
32661
- extensions: [".vue"]
32662
- },
32663
- svelte: {
32664
- command: ["svelteserver", "--stdio"],
32665
- extensions: [".svelte"]
32666
- },
32667
- astro: {
32668
- command: ["astro-ls", "--stdio"],
32669
- extensions: [".astro"]
33208
+ extensions: [".vue"],
33209
+ root: NearestRoot(LOCK_FILE_PATTERNS)
32670
33210
  },
32671
33211
  eslint: {
32672
33212
  command: ["vscode-eslint-language-server", "--stdio"],
@@ -32677,54 +33217,316 @@ var BUILTIN_SERVERS = {
32677
33217
  ".jsx",
32678
33218
  ".mjs",
32679
33219
  ".cjs",
33220
+ ".mts",
33221
+ ".cts",
33222
+ ".vue"
33223
+ ],
33224
+ root: NearestRoot(LOCK_FILE_PATTERNS)
33225
+ },
33226
+ oxlint: {
33227
+ command: ["oxlint", "--lsp"],
33228
+ extensions: [
33229
+ ".ts",
33230
+ ".tsx",
33231
+ ".js",
33232
+ ".jsx",
33233
+ ".mjs",
33234
+ ".cjs",
33235
+ ".mts",
33236
+ ".cts",
32680
33237
  ".vue",
33238
+ ".astro",
32681
33239
  ".svelte"
32682
- ]
33240
+ ],
33241
+ root: NearestRoot([
33242
+ ".oxlintrc.json",
33243
+ ...LOCK_FILE_PATTERNS,
33244
+ "package.json"
33245
+ ])
32683
33246
  },
32684
- tailwindcss: {
32685
- command: ["tailwindcss-language-server", "--stdio"],
32686
- extensions: [".html", ".jsx", ".tsx", ".vue", ".svelte", ".astro"]
33247
+ biome: {
33248
+ command: ["biome", "lsp-proxy", "--stdio"],
33249
+ extensions: [
33250
+ ".ts",
33251
+ ".tsx",
33252
+ ".js",
33253
+ ".jsx",
33254
+ ".mjs",
33255
+ ".cjs",
33256
+ ".mts",
33257
+ ".cts",
33258
+ ".json",
33259
+ ".jsonc",
33260
+ ".vue",
33261
+ ".astro",
33262
+ ".svelte",
33263
+ ".css",
33264
+ ".graphql",
33265
+ ".gql",
33266
+ ".html"
33267
+ ],
33268
+ root: NearestRoot(["biome.json", "biome.jsonc", ...LOCK_FILE_PATTERNS])
32687
33269
  },
32688
33270
  gopls: {
32689
33271
  command: ["gopls"],
32690
- extensions: [".go"]
33272
+ extensions: [".go"],
33273
+ root: NearestRoot(["go.work", "go.mod", "go.sum"])
32691
33274
  },
32692
- rust: {
32693
- command: ["rust-analyzer"],
32694
- extensions: [".rs"]
33275
+ ruby_lsp: {
33276
+ command: ["rubocop", "--lsp"],
33277
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
33278
+ root: NearestRoot(["Gemfile"])
32695
33279
  },
32696
- basedpyright: {
32697
- command: ["basedpyright-langserver", "--stdio"],
32698
- extensions: [".py", ".pyi"]
33280
+ ty: {
33281
+ command: ["ty", "server"],
33282
+ extensions: [".py", ".pyi"],
33283
+ root: NearestRoot([
33284
+ "pyproject.toml",
33285
+ "ty.toml",
33286
+ "setup.py",
33287
+ "setup.cfg",
33288
+ "requirements.txt",
33289
+ "Pipfile",
33290
+ "pyrightconfig.json"
33291
+ ])
32699
33292
  },
32700
33293
  pyright: {
32701
33294
  command: ["pyright-langserver", "--stdio"],
32702
- extensions: [".py", ".pyi"]
33295
+ extensions: [".py", ".pyi"],
33296
+ root: NearestRoot([
33297
+ "pyproject.toml",
33298
+ "setup.py",
33299
+ "setup.cfg",
33300
+ "requirements.txt",
33301
+ "Pipfile",
33302
+ "pyrightconfig.json"
33303
+ ])
32703
33304
  },
32704
- clangd: {
32705
- command: ["clangd", "--background-index"],
32706
- extensions: [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp"]
33305
+ elixir_ls: {
33306
+ command: ["elixir-ls"],
33307
+ extensions: [".ex", ".exs"],
33308
+ root: NearestRoot(["mix.exs", "mix.lock"])
32707
33309
  },
32708
33310
  zls: {
32709
33311
  command: ["zls"],
32710
- extensions: [".zig"]
33312
+ extensions: [".zig", ".zon"],
33313
+ root: NearestRoot(["build.zig"])
33314
+ },
33315
+ csharp: {
33316
+ command: ["csharp-ls"],
33317
+ extensions: [".cs"],
33318
+ root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"])
33319
+ },
33320
+ fsharp: {
33321
+ command: ["fsautocomplete"],
33322
+ extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
33323
+ root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"])
33324
+ },
33325
+ sourcekit_lsp: {
33326
+ command: ["sourcekit-lsp"],
33327
+ extensions: [".swift", ".objc", ".objcpp"],
33328
+ root: NearestRoot(["Package.swift", "*.xcodeproj", "*.xcworkspace"])
33329
+ },
33330
+ rust: {
33331
+ command: ["rust-analyzer"],
33332
+ extensions: [".rs"],
33333
+ root: NearestRoot(["Cargo.toml", "Cargo.lock"])
33334
+ },
33335
+ clangd: {
33336
+ command: ["clangd", "--background-index", "--clang-tidy"],
33337
+ extensions: [
33338
+ ".c",
33339
+ ".cpp",
33340
+ ".cc",
33341
+ ".cxx",
33342
+ ".c++",
33343
+ ".h",
33344
+ ".hpp",
33345
+ ".hh",
33346
+ ".hxx",
33347
+ ".h++"
33348
+ ],
33349
+ root: NearestRoot([
33350
+ "compile_commands.json",
33351
+ "compile_flags.txt",
33352
+ ".clangd",
33353
+ "CMakeLists.txt",
33354
+ "Makefile"
33355
+ ])
33356
+ },
33357
+ svelte: {
33358
+ command: ["svelteserver", "--stdio"],
33359
+ extensions: [".svelte"],
33360
+ root: NearestRoot(LOCK_FILE_PATTERNS)
33361
+ },
33362
+ astro: {
33363
+ command: ["astro-ls", "--stdio"],
33364
+ extensions: [".astro"],
33365
+ root: NearestRoot(LOCK_FILE_PATTERNS)
33366
+ },
33367
+ jdtls: {
33368
+ command: ["jdtls"],
33369
+ extensions: [".java"],
33370
+ root: NearestRoot([
33371
+ "pom.xml",
33372
+ "build.gradle",
33373
+ "build.gradle.kts",
33374
+ ".project",
33375
+ ".classpath"
33376
+ ])
33377
+ },
33378
+ kotlin_ls: {
33379
+ command: ["kotlin-lsp", "--stdio"],
33380
+ extensions: [".kt", ".kts"],
33381
+ root: NearestRoot([
33382
+ "settings.gradle.kts",
33383
+ "settings.gradle",
33384
+ "gradlew",
33385
+ "build.gradle.kts",
33386
+ "build.gradle",
33387
+ "pom.xml"
33388
+ ])
33389
+ },
33390
+ yaml_ls: {
33391
+ command: ["yaml-language-server", "--stdio"],
33392
+ extensions: [".yaml", ".yml"],
33393
+ root: NearestRoot(LOCK_FILE_PATTERNS)
33394
+ },
33395
+ lua_ls: {
33396
+ command: ["lua-language-server"],
33397
+ extensions: [".lua"],
33398
+ root: NearestRoot([
33399
+ ".luarc.json",
33400
+ ".luarc.jsonc",
33401
+ ".luacheckrc",
33402
+ "stylua.toml",
33403
+ "selene.toml",
33404
+ "selene.yml"
33405
+ ])
33406
+ },
33407
+ php_intelephense: {
33408
+ command: ["intelephense", "--stdio"],
33409
+ extensions: [".php"],
33410
+ root: NearestRoot(["composer.json", "composer.lock", ".php-version"])
33411
+ },
33412
+ prisma: {
33413
+ command: ["prisma", "language-server"],
33414
+ extensions: [".prisma"],
33415
+ root: NearestRoot(["schema.prisma", "prisma/schema.prisma", "prisma"])
33416
+ },
33417
+ dart: {
33418
+ command: ["dart", "language-server", "--lsp"],
33419
+ extensions: [".dart"],
33420
+ root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"])
33421
+ },
33422
+ ocaml_lsp: {
33423
+ command: ["ocamllsp"],
33424
+ extensions: [".ml", ".mli"],
33425
+ root: NearestRoot(["dune-project", "dune-workspace", ".merlin", "opam"])
33426
+ },
33427
+ bash: {
33428
+ command: ["bash-language-server", "start"],
33429
+ extensions: [".sh", ".bash", ".zsh", ".ksh"],
33430
+ root: undefined
33431
+ },
33432
+ terraform_ls: {
33433
+ command: ["terraform-ls", "serve"],
33434
+ extensions: [".tf", ".tfvars"],
33435
+ root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"])
33436
+ },
33437
+ texlab: {
33438
+ command: ["texlab"],
33439
+ extensions: [".tex", ".bib"],
33440
+ root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"])
33441
+ },
33442
+ dockerfile: {
33443
+ command: ["docker-langserver", "--stdio"],
33444
+ extensions: [".dockerfile", "Dockerfile"],
33445
+ root: undefined
33446
+ },
33447
+ gleam: {
33448
+ command: ["gleam", "lsp"],
33449
+ extensions: [".gleam"],
33450
+ root: NearestRoot(["gleam.toml"])
33451
+ },
33452
+ clojure_lsp: {
33453
+ command: ["clojure-lsp", "listen"],
33454
+ extensions: [".clj", ".cljs", ".cljc", ".edn"],
33455
+ root: NearestRoot([
33456
+ "deps.edn",
33457
+ "project.clj",
33458
+ "shadow-cljs.edn",
33459
+ "bb.edn",
33460
+ "build.boot"
33461
+ ])
33462
+ },
33463
+ nixd: {
33464
+ command: ["nixd"],
33465
+ extensions: [".nix"],
33466
+ root: NearestRoot(["flake.nix"])
33467
+ },
33468
+ tinymist: {
33469
+ command: ["tinymist"],
33470
+ extensions: [".typ", ".typc"],
33471
+ root: NearestRoot(["typst.toml"])
33472
+ },
33473
+ haskell_language_server: {
33474
+ command: ["haskell-language-server-wrapper", "--lsp"],
33475
+ extensions: [".hs", ".lhs"],
33476
+ root: NearestRoot(["stack.yaml", "cabal.project", "hie.yaml", "*.cabal"])
33477
+ },
33478
+ julials: {
33479
+ command: [
33480
+ "julia",
33481
+ "--startup-file=no",
33482
+ "--history-file=no",
33483
+ "-e",
33484
+ "using LanguageServer; runserver()"
33485
+ ],
33486
+ extensions: [".jl"],
33487
+ root: NearestRoot(["Project.toml", "Manifest.toml", "*.jl"])
32711
33488
  }
32712
33489
  };
32713
33490
  var LSP_INSTALL_HINTS = {
33491
+ deno: "Install Deno: https://deno.land/#installation",
32714
33492
  typescript: "npm install -g typescript-language-server typescript",
32715
33493
  vue: "npm install -g @vue/language-server",
32716
- svelte: "npm install -g svelte-language-server",
32717
- astro: "npm install -g @astrojs/language-server",
32718
33494
  eslint: "npm install -g vscode-langservers-extracted",
32719
- tailwindcss: "npm install -g @tailwindcss/language-server",
33495
+ oxlint: "npm install -g oxlint or install via package manager",
33496
+ biome: "npm install -g @biomejs/biome",
32720
33497
  gopls: "go install golang.org/x/tools/gopls@latest",
32721
- rust: "rustup component add rust-analyzer",
32722
- basedpyright: "pip install basedpyright",
33498
+ ruby_lsp: "gem install rubocop (Ruby LSP runs via rubocop --lsp)",
33499
+ ty: "pip install ty or see https://github.com/astral-sh/ty",
32723
33500
  pyright: "pip install pyright",
32724
- clangd: "See https://clangd.llvm.org/installation",
32725
- zls: "See https://github.com/zigtools/zls"
33501
+ elixir_ls: "Download from https://github.com/elixir-lsp/elixir-ls/releases or build from source",
33502
+ zls: "Install via your package manager or build from source: https://github.com/zigtools/zls",
33503
+ csharp: "dotnet tool install --global csharp-ls",
33504
+ fsharp: "dotnet tool install --global fsautocomplete",
33505
+ sourcekit_lsp: "Install via Xcode or Swift toolchain (included with Xcode)",
33506
+ rust: "rustup component add rust-analyzer",
33507
+ clangd: "Install clangd via your system package manager or LLVM",
33508
+ svelte: "npm install -g svelte-language-server",
33509
+ astro: "npm install -g @astrojs/language-server",
33510
+ jdtls: "See https://github.com/eclipse-jdtls/eclipse.jdt.ls for installation",
33511
+ kotlin_ls: "Download from https://github.com/Kotlin/kotlin-lsp/releases",
33512
+ yaml_ls: "npm install -g yaml-language-server",
33513
+ lua_ls: "Download from https://github.com/LuaLS/lua-language-server/releases",
33514
+ php_intelephense: "npm install -g intelephense",
33515
+ prisma: "npm install -g @prisma/language-server or use npx",
33516
+ dart: "dart pub global activate language_server",
33517
+ ocaml_lsp: "opam install ocaml-lsp-server",
33518
+ bash: "npm install -g bash-language-server",
33519
+ terraform_ls: "Download from https://github.com/hashicorp/terraform-ls/releases or install via tfenv",
33520
+ texlab: "Download from https://github.com/latex-lsp/texlab/releases",
33521
+ dockerfile: "npm install -g dockerfile-language-server-nodejs",
33522
+ gleam: "Install Gleam: https://gleam.run/getting-started/",
33523
+ clojure_lsp: "Install via deps.edn, project.clj, or: clj -M -m clojure-lsp.main",
33524
+ nixd: "Install via nix-env or your system package manager",
33525
+ tinymist: "cargo install tinymist or download from releases",
33526
+ haskell_language_server: "Install Haskell Tool Stack or Cabal, then language-server",
33527
+ julials: "Install Julia: https://julialang.org/downloads/"
32726
33528
  };
32727
- var EXT_TO_LANG = {
33529
+ var LANGUAGE_EXTENSIONS = {
32728
33530
  ".ts": "typescript",
32729
33531
  ".tsx": "typescriptreact",
32730
33532
  ".mts": "typescript",
@@ -32733,35 +33535,180 @@ var EXT_TO_LANG = {
32733
33535
  ".jsx": "javascriptreact",
32734
33536
  ".mjs": "javascript",
32735
33537
  ".cjs": "javascript",
33538
+ ".ets": "typescript",
32736
33539
  ".vue": "vue",
32737
33540
  ".svelte": "svelte",
32738
33541
  ".astro": "astro",
32739
33542
  ".html": "html",
33543
+ ".htm": "html",
33544
+ ".xml": "xml",
33545
+ ".xsl": "xsl",
32740
33546
  ".css": "css",
32741
33547
  ".scss": "scss",
33548
+ ".sass": "sass",
32742
33549
  ".less": "less",
32743
33550
  ".json": "json",
33551
+ ".jsonc": "json",
33552
+ ".graphql": "graphql",
33553
+ ".gql": "graphql",
33554
+ ".dockerfile": "dockerfile",
33555
+ ".sh": "shellscript",
33556
+ ".bash": "shellscript",
33557
+ ".zsh": "shellscript",
33558
+ ".ksh": "shellscript",
32744
33559
  ".go": "go",
32745
33560
  ".rs": "rust",
32746
33561
  ".py": "python",
32747
33562
  ".pyi": "python",
33563
+ ".rb": "ruby",
33564
+ ".rake": "ruby",
33565
+ ".gemspec": "ruby",
33566
+ ".ru": "ruby",
32748
33567
  ".c": "c",
32749
33568
  ".cpp": "cpp",
32750
33569
  ".cc": "cpp",
32751
33570
  ".cxx": "cpp",
33571
+ ".c++": "cpp",
32752
33572
  ".h": "c",
32753
33573
  ".hpp": "cpp",
32754
- ".zig": "zig"
33574
+ ".hh": "cpp",
33575
+ ".hxx": "cpp",
33576
+ ".h++": "cpp",
33577
+ ".java": "java",
33578
+ ".kt": "kotlin",
33579
+ ".kts": "kotlin",
33580
+ ".cs": "csharp",
33581
+ ".fs": "fsharp",
33582
+ ".fsi": "fsharp",
33583
+ ".fsx": "fsharp",
33584
+ ".fsscript": "fsharp",
33585
+ ".swift": "swift",
33586
+ ".m": "objective-c",
33587
+ ".mm": "objective-cpp",
33588
+ ".zig": "zig",
33589
+ ".zon": "zig",
33590
+ ".ex": "elixir",
33591
+ ".exs": "elixir",
33592
+ ".clj": "clojure",
33593
+ ".cljs": "clojure",
33594
+ ".cljc": "clojure",
33595
+ ".edn": "clojure",
33596
+ ".hs": "haskell",
33597
+ ".lhs": "haskell",
33598
+ ".ml": "ocaml",
33599
+ ".mli": "ocaml",
33600
+ ".scala": "scala",
33601
+ ".php": "php",
33602
+ ".lua": "lua",
33603
+ ".dart": "dart",
33604
+ ".yaml": "yaml",
33605
+ ".yml": "yaml",
33606
+ ".tf": "terraform",
33607
+ ".tfvars": "terraform-vars",
33608
+ ".hcl": "hcl",
33609
+ ".nix": "nix",
33610
+ ".typ": "typst",
33611
+ ".typc": "typst",
33612
+ ".tex": "latex",
33613
+ ".latex": "latex",
33614
+ ".bib": "bibtex",
33615
+ ".bibtex": "bibtex",
33616
+ ".prisma": "prisma",
33617
+ ".jl": "julia",
33618
+ ".gleam": "gleam",
33619
+ ".md": "markdown",
33620
+ ".markdown": "markdown",
33621
+ ".d": "d",
33622
+ ".pas": "pascal",
33623
+ ".pascal": "pascal",
33624
+ ".diff": "diff",
33625
+ ".patch": "diff",
33626
+ ".erl": "erlang",
33627
+ ".hrl": "erlang",
33628
+ ".groovy": "groovy",
33629
+ ".handlebars": "handlebars",
33630
+ ".hbs": "handlebars",
33631
+ ".ini": "ini",
33632
+ ".makefile": "makefile",
33633
+ makefile: "makefile",
33634
+ ".pug": "jade",
33635
+ ".jade": "jade",
33636
+ ".r": "r",
33637
+ ".cshtml": "razor",
33638
+ ".razor": "razor",
33639
+ ".erb": "erb",
33640
+ ".html.erb": "erb",
33641
+ ".js.erb": "erb",
33642
+ ".css.erb": "erb",
33643
+ ".json.erb": "erb",
33644
+ ".shader": "shaderlab",
33645
+ ".sql": "sql",
33646
+ ".perl": "perl",
33647
+ ".pl": "perl",
33648
+ ".pm": "perl",
33649
+ ".pm6": "perl6",
33650
+ ".ps1": "powershell",
33651
+ ".psm1": "powershell",
33652
+ ".coffee": "coffeescript",
33653
+ ".bat": "bat",
33654
+ ".abap": "abap",
33655
+ ".gitcommit": "git-commit",
33656
+ ".gitrebase": "git-rebase"
32755
33657
  };
32756
33658
 
32757
33659
  // src/tools/lsp/config.ts
32758
- function findServerForExtension(ext) {
33660
+ function buildMergedServers() {
33661
+ const servers = new Map;
32759
33662
  for (const [id, config3] of Object.entries(BUILTIN_SERVERS)) {
33663
+ servers.set(id, {
33664
+ id,
33665
+ command: config3.command,
33666
+ extensions: config3.extensions,
33667
+ root: config3.root,
33668
+ env: config3.env,
33669
+ initialization: config3.initialization
33670
+ });
33671
+ }
33672
+ if (hasUserLspConfig()) {
33673
+ for (const [id, userConfig2] of getAllUserLspConfigs()) {
33674
+ if (userConfig2.disabled === true) {
33675
+ servers.delete(id);
33676
+ continue;
33677
+ }
33678
+ const existing = servers.get(id);
33679
+ if (existing) {
33680
+ servers.set(id, {
33681
+ ...existing,
33682
+ id,
33683
+ command: userConfig2.command ?? existing.command,
33684
+ extensions: userConfig2.extensions ?? existing.extensions,
33685
+ root: existing.root,
33686
+ env: userConfig2.env ?? existing.env,
33687
+ initialization: userConfig2.initialization ?? existing.initialization
33688
+ });
33689
+ } else {
33690
+ servers.set(id, {
33691
+ id,
33692
+ command: userConfig2.command ?? [],
33693
+ extensions: userConfig2.extensions ?? [],
33694
+ root: undefined,
33695
+ env: userConfig2.env,
33696
+ initialization: userConfig2.initialization
33697
+ });
33698
+ }
33699
+ }
33700
+ }
33701
+ return servers;
33702
+ }
33703
+ function findServerForExtension(ext) {
33704
+ const servers = buildMergedServers();
33705
+ for (const [, config3] of servers) {
32760
33706
  if (config3.extensions.includes(ext)) {
32761
33707
  const server = {
32762
- id,
33708
+ id: config3.id,
32763
33709
  command: config3.command,
32764
33710
  extensions: config3.extensions,
33711
+ root: config3.root,
32765
33712
  env: config3.env,
32766
33713
  initialization: config3.initialization
32767
33714
  };
@@ -32771,39 +33718,37 @@ function findServerForExtension(ext) {
32771
33718
  return {
32772
33719
  status: "not_installed",
32773
33720
  server,
32774
- installHint: LSP_INSTALL_HINTS[id] || `Install '${config3.command[0]}' and add to PATH`
33721
+ installHint: LSP_INSTALL_HINTS[config3.id] || `Install '${config3.command[0]}' and add to PATH`
32775
33722
  };
32776
33723
  }
32777
33724
  }
32778
33725
  return { status: "not_configured", extension: ext };
32779
33726
  }
32780
33727
  function getLanguageId(ext) {
32781
- return EXT_TO_LANG[ext] || "plaintext";
33728
+ return LANGUAGE_EXTENSIONS[ext] || "plaintext";
32782
33729
  }
32783
33730
  function isServerInstalled(command) {
32784
33731
  if (command.length === 0)
32785
33732
  return false;
32786
33733
  const cmd = command[0];
32787
33734
  if (cmd.includes("/") || cmd.includes("\\")) {
32788
- return existsSync9(cmd);
33735
+ return existsSync10(cmd);
32789
33736
  }
32790
33737
  const isWindows = process.platform === "win32";
32791
33738
  const ext = isWindows ? ".exe" : "";
32792
- const pathEnv = process.env.PATH || "";
32793
- const pathSeparator = isWindows ? ";" : ":";
32794
- const paths2 = pathEnv.split(pathSeparator);
32795
- for (const p of paths2) {
32796
- if (existsSync9(join11(p, cmd)) || existsSync9(join11(p, cmd + ext))) {
32797
- return true;
32798
- }
33739
+ const opencodeBin = join11(homedir4(), ".config", "opencode", "bin");
33740
+ const searchPath = (process.env.PATH ?? "") + (isWindows ? ";" : ":") + opencodeBin;
33741
+ const result = import_which.default.sync(cmd, {
33742
+ path: searchPath,
33743
+ pathExt: isWindows ? process.env.PATHEXT : undefined,
33744
+ nothrow: true
33745
+ });
33746
+ if (result !== null) {
33747
+ return true;
32799
33748
  }
32800
33749
  const cwd = process.cwd();
32801
33750
  const localBin = join11(cwd, "node_modules", ".bin", cmd);
32802
- if (existsSync9(localBin) || existsSync9(localBin + ext)) {
32803
- return true;
32804
- }
32805
- const globalBin = join11(homedir5(), ".config", "opencode", "bin", cmd);
32806
- if (existsSync9(globalBin) || existsSync9(globalBin + ext)) {
33751
+ if (existsSync10(localBin) || existsSync10(localBin + ext)) {
32807
33752
  return true;
32808
33753
  }
32809
33754
  return false;
@@ -32816,6 +33761,7 @@ class LSPServerManager {
32816
33761
  cleanupInterval = null;
32817
33762
  IDLE_TIMEOUT = 5 * 60 * 1000;
32818
33763
  constructor() {
33764
+ log("[lsp] manager initialized");
32819
33765
  this.startCleanupTimer();
32820
33766
  this.registerProcessCleanup();
32821
33767
  }
@@ -32872,16 +33818,31 @@ class LSPServerManager {
32872
33818
  const managed = this.clients.get(key);
32873
33819
  if (managed) {
32874
33820
  if (managed.initPromise) {
33821
+ log("[lsp] getClient: waiting for init", { key, server: server.id });
32875
33822
  await managed.initPromise;
32876
33823
  }
32877
33824
  if (managed.client.isAlive()) {
32878
33825
  managed.refCount++;
32879
33826
  managed.lastUsedAt = Date.now();
33827
+ log("[lsp] getClient: reuse pooled client", {
33828
+ key,
33829
+ server: server.id,
33830
+ refCount: managed.refCount
33831
+ });
32880
33832
  return managed.client;
32881
33833
  }
33834
+ log("[lsp] getClient: client dead, recreating", {
33835
+ key,
33836
+ server: server.id
33837
+ });
32882
33838
  await managed.client.stop();
32883
33839
  this.clients.delete(key);
32884
33840
  }
33841
+ log("[lsp] getClient: creating new client", {
33842
+ key,
33843
+ server: server.id,
33844
+ root
33845
+ });
32885
33846
  const client = new LSPClient(root, server);
32886
33847
  const initPromise2 = (async () => {
32887
33848
  await client.start();
@@ -32901,7 +33862,13 @@ class LSPServerManager {
32901
33862
  m.initPromise = undefined;
32902
33863
  m.isInitializing = false;
32903
33864
  }
33865
+ log("[lsp] getClient: client ready", { key, server: server.id });
32904
33866
  } catch (err) {
33867
+ log("[lsp] getClient: init failed", {
33868
+ key,
33869
+ server: server.id,
33870
+ error: String(err)
33871
+ });
32905
33872
  this.clients.delete(key);
32906
33873
  throw err;
32907
33874
  }
@@ -32913,6 +33880,11 @@ class LSPServerManager {
32913
33880
  if (managed && managed.refCount > 0) {
32914
33881
  managed.refCount--;
32915
33882
  managed.lastUsedAt = Date.now();
33883
+ log("[lsp] releaseClient", {
33884
+ key,
33885
+ server: serverId,
33886
+ refCount: managed.refCount
33887
+ });
32916
33888
  }
32917
33889
  }
32918
33890
  isServerInitializing(root, serverId) {
@@ -32921,14 +33893,19 @@ class LSPServerManager {
32921
33893
  return managed?.isInitializing ?? false;
32922
33894
  }
32923
33895
  async stopAll() {
32924
- for (const [, managed] of this.clients) {
33896
+ log("[lsp] stopAll: shutting down all clients", {
33897
+ count: this.clients.size
33898
+ });
33899
+ for (const [key, managed] of this.clients) {
32925
33900
  await managed.client.stop();
33901
+ log("[lsp] stopAll: client stopped", { key });
32926
33902
  }
32927
33903
  this.clients.clear();
32928
33904
  if (this.cleanupInterval) {
32929
33905
  clearInterval(this.cleanupInterval);
32930
33906
  this.cleanupInterval = null;
32931
33907
  }
33908
+ log("[lsp] stopAll: complete");
32932
33909
  }
32933
33910
  }
32934
33911
  var lspManager = LSPServerManager.getInstance();
@@ -32947,6 +33924,11 @@ class LSPClient {
32947
33924
  this.server = server;
32948
33925
  }
32949
33926
  async start() {
33927
+ log("[lsp] LSPClient.start: spawning server", {
33928
+ server: this.server.id,
33929
+ command: this.server.command.join(" "),
33930
+ root: this.root
33931
+ });
32950
33932
  this.proc = spawn5(this.server.command, {
32951
33933
  stdin: "pipe",
32952
33934
  stdout: "pipe",
@@ -33016,13 +33998,19 @@ class LSPClient {
33016
33998
  this.processExited = true;
33017
33999
  });
33018
34000
  this.connection.listen();
33019
- await new Promise((resolve2) => setTimeout(resolve2, 100));
34001
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
33020
34002
  if (this.proc.exitCode !== null) {
33021
34003
  const stderr = this.stderrBuffer.join(`
33022
34004
  `);
34005
+ log("[lsp] LSPClient.start: server exited immediately", {
34006
+ server: this.server.id,
34007
+ exitCode: this.proc.exitCode,
34008
+ stderr: stderr.slice(0, 500)
34009
+ });
33023
34010
  throw new Error(`LSP server exited immediately with code ${this.proc.exitCode}` + (stderr ? `
33024
34011
  stderr: ${stderr}` : ""));
33025
34012
  }
34013
+ log("[lsp] LSPClient.start: server spawned", { server: this.server.id });
33026
34014
  }
33027
34015
  startStderrReading() {
33028
34016
  if (!this.proc)
@@ -33048,6 +34036,10 @@ stderr: ${stderr}` : ""));
33048
34036
  async initialize() {
33049
34037
  if (!this.connection)
33050
34038
  throw new Error("LSP connection not established");
34039
+ log("[lsp] LSPClient.initialize: sending initialize request", {
34040
+ server: this.server.id,
34041
+ root: this.root
34042
+ });
33051
34043
  const rootUri = pathToFileURL(this.root).href;
33052
34044
  await this.connection.sendRequest("initialize", {
33053
34045
  processId: process.pid,
@@ -33079,14 +34071,22 @@ stderr: ${stderr}` : ""));
33079
34071
  });
33080
34072
  this.connection.sendNotification("initialized");
33081
34073
  await new Promise((r) => setTimeout(r, 300));
34074
+ log("[lsp] LSPClient.initialize: complete", { server: this.server.id });
33082
34075
  }
33083
34076
  async openFile(filePath) {
33084
- const absPath = resolve(filePath);
33085
- if (this.openedFiles.has(absPath))
34077
+ const absPath = resolve2(filePath);
34078
+ if (this.openedFiles.has(absPath)) {
34079
+ log("[lsp] openFile: already open, skipping", { filePath: absPath });
33086
34080
  return;
34081
+ }
33087
34082
  const text = readFileSync4(absPath, "utf-8");
33088
34083
  const ext = extname(absPath);
33089
34084
  const languageId = getLanguageId(ext);
34085
+ log("[lsp] openFile: opening document", {
34086
+ filePath: absPath,
34087
+ languageId,
34088
+ size: text.length
34089
+ });
33090
34090
  this.connection?.sendNotification("textDocument/didOpen", {
33091
34091
  textDocument: {
33092
34092
  uri: pathToFileURL(absPath).href,
@@ -33099,7 +34099,7 @@ stderr: ${stderr}` : ""));
33099
34099
  await new Promise((r) => setTimeout(r, 1000));
33100
34100
  }
33101
34101
  async definition(filePath, line, character) {
33102
- const absPath = resolve(filePath);
34102
+ const absPath = resolve2(filePath);
33103
34103
  await this.openFile(absPath);
33104
34104
  return this.connection?.sendRequest("textDocument/definition", {
33105
34105
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33107,7 +34107,7 @@ stderr: ${stderr}` : ""));
33107
34107
  });
33108
34108
  }
33109
34109
  async references(filePath, line, character, includeDeclaration = true) {
33110
- const absPath = resolve(filePath);
34110
+ const absPath = resolve2(filePath);
33111
34111
  await this.openFile(absPath);
33112
34112
  return this.connection?.sendRequest("textDocument/references", {
33113
34113
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33116,7 +34116,7 @@ stderr: ${stderr}` : ""));
33116
34116
  });
33117
34117
  }
33118
34118
  async diagnostics(filePath) {
33119
- const absPath = resolve(filePath);
34119
+ const absPath = resolve2(filePath);
33120
34120
  const uri = pathToFileURL(absPath).href;
33121
34121
  await this.openFile(absPath);
33122
34122
  await new Promise((r) => setTimeout(r, 500));
@@ -33131,7 +34131,7 @@ stderr: ${stderr}` : ""));
33131
34131
  return { items: this.diagnosticsStore.get(uri) ?? [] };
33132
34132
  }
33133
34133
  async rename(filePath, line, character, newName) {
33134
- const absPath = resolve(filePath);
34134
+ const absPath = resolve2(filePath);
33135
34135
  await this.openFile(absPath);
33136
34136
  return this.connection?.sendRequest("textDocument/rename", {
33137
34137
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33143,6 +34143,7 @@ stderr: ${stderr}` : ""));
33143
34143
  return this.proc !== null && !this.processExited && this.proc.exitCode === null;
33144
34144
  }
33145
34145
  async stop() {
34146
+ log("[lsp] LSPClient.stop: stopping", { server: this.server.id });
33146
34147
  try {
33147
34148
  if (this.connection) {
33148
34149
  await this.connection.sendRequest("shutdown");
@@ -33155,45 +34156,24 @@ stderr: ${stderr}` : ""));
33155
34156
  this.connection = null;
33156
34157
  this.processExited = true;
33157
34158
  this.diagnosticsStore.clear();
34159
+ log("[lsp] LSPClient.stop: complete", { server: this.server.id });
33158
34160
  }
33159
34161
  }
33160
34162
  // src/tools/lsp/utils.ts
33161
34163
  import {
33162
- existsSync as existsSync10,
34164
+ existsSync as existsSync11,
33163
34165
  readFileSync as readFileSync5,
33164
- statSync as statSync3,
34166
+ statSync as statSync4,
33165
34167
  unlinkSync as unlinkSync3,
33166
34168
  writeFileSync as writeFileSync3
33167
34169
  } from "fs";
33168
- import { dirname as dirname4, extname as extname2, join as join12, resolve as resolve2 } from "path";
34170
+ import { dirname as dirname6, extname as extname2, join as join12, resolve as resolve3 } from "path";
33169
34171
  import { fileURLToPath as fileURLToPath2 } from "url";
33170
- function findWorkspaceRoot(filePath) {
33171
- let dir = resolve2(filePath);
33172
- try {
33173
- if (!statSync3(dir).isDirectory()) {
33174
- dir = dirname4(dir);
33175
- }
33176
- } catch {
33177
- dir = dirname4(dir);
33178
- }
33179
- const markers = [
33180
- ".git",
33181
- "package.json",
33182
- "pyproject.toml",
33183
- "Cargo.toml",
33184
- "go.mod"
33185
- ];
33186
- let prevDir = "";
33187
- while (dir !== prevDir) {
33188
- for (const marker of markers) {
33189
- if (existsSync10(join12(dir, marker))) {
33190
- return dir;
33191
- }
33192
- }
33193
- prevDir = dir;
33194
- dir = dirname4(dir);
34172
+ function findServerProjectRoot(filePath, server) {
34173
+ if (server.root) {
34174
+ return server.root(filePath) ?? dirname6(resolve3(filePath));
33195
34175
  }
33196
- return dirname4(resolve2(filePath));
34176
+ return dirname6(resolve3(filePath));
33197
34177
  }
33198
34178
  function uriToPath(uri) {
33199
34179
  return fileURLToPath2(uri);
@@ -33212,24 +34192,42 @@ function formatServerLookupError(result) {
33212
34192
  return `No LSP server configured for extension: ${result.extension}`;
33213
34193
  }
33214
34194
  async function withLspClient(filePath, fn) {
33215
- const absPath = resolve2(filePath);
34195
+ const absPath = resolve3(filePath);
33216
34196
  const ext = extname2(absPath);
33217
34197
  const result = findServerForExtension(ext);
33218
34198
  if (result.status !== "found") {
34199
+ log("[lsp] withLspClient: server not found", {
34200
+ filePath: absPath,
34201
+ extension: ext
34202
+ });
33219
34203
  throw new Error(formatServerLookupError(result));
33220
34204
  }
33221
34205
  const server = result.server;
33222
- const root = findWorkspaceRoot(absPath);
34206
+ const root = findServerProjectRoot(absPath, server) ?? dirname6(absPath);
34207
+ log("[lsp] withLspClient: acquiring client", {
34208
+ filePath: absPath,
34209
+ server: server.id,
34210
+ root
34211
+ });
33223
34212
  const client = await lspManager.getClient(root, server);
33224
34213
  try {
33225
- return await fn(client);
34214
+ const result2 = await fn(client);
34215
+ log("[lsp] withLspClient: operation complete", { server: server.id });
34216
+ return result2;
33226
34217
  } catch (e) {
33227
34218
  if (e instanceof Error && e.message.includes("timeout")) {
33228
34219
  const isInitializing = lspManager.isServerInitializing(root, server.id);
33229
34220
  if (isInitializing) {
34221
+ log("[lsp] withLspClient: timeout during init", {
34222
+ server: server.id
34223
+ });
33230
34224
  throw new Error(`LSP server is still initializing. Please retry in a few seconds.`);
33231
34225
  }
33232
34226
  }
34227
+ log("[lsp] withLspClient: operation failed", {
34228
+ server: server.id,
34229
+ error: String(e)
34230
+ });
33233
34231
  throw e;
33234
34232
  } finally {
33235
34233
  lspManager.releaseClient(root, server.id);
@@ -33313,6 +34311,7 @@ function applyTextEditsToFile(filePath, edits) {
33313
34311
  }
33314
34312
  function applyWorkspaceEdit(edit) {
33315
34313
  if (!edit) {
34314
+ log("[lsp] applyWorkspaceEdit: no edit provided");
33316
34315
  return {
33317
34316
  success: false,
33318
34317
  filesModified: [],
@@ -33320,6 +34319,8 @@ function applyWorkspaceEdit(edit) {
33320
34319
  errors: ["No edit provided"]
33321
34320
  };
33322
34321
  }
34322
+ const changeCount = (edit.changes ? Object.keys(edit.changes).length : 0) + (edit.documentChanges ? edit.documentChanges.length : 0);
34323
+ log("[lsp] applyWorkspaceEdit: applying", { changeCount });
33323
34324
  const result = {
33324
34325
  success: true,
33325
34326
  filesModified: [],
@@ -33386,6 +34387,12 @@ function applyWorkspaceEdit(edit) {
33386
34387
  }
33387
34388
  }
33388
34389
  }
34390
+ log("[lsp] applyWorkspaceEdit: complete", {
34391
+ success: result.success,
34392
+ filesModified: result.filesModified.length,
34393
+ totalEdits: result.totalEdits,
34394
+ errors: result.errors.length
34395
+ });
33389
34396
  return result;
33390
34397
  }
33391
34398
  function formatApplyResult(result) {
@@ -33534,6 +34541,28 @@ var OhMyOpenCodeLite = async (ctx) => {
33534
34541
  modelArrayMap[agentDef.name] = agentDef._modelArray;
33535
34542
  }
33536
34543
  }
34544
+ const runtimeChains = {};
34545
+ for (const agentDef of agentDefs) {
34546
+ if (agentDef._modelArray?.length) {
34547
+ runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
34548
+ }
34549
+ }
34550
+ if (config3.fallback?.enabled !== false) {
34551
+ const chains = config3.fallback?.chains ?? {};
34552
+ for (const [agentName, chainModels] of Object.entries(chains)) {
34553
+ if (!chainModels?.length)
34554
+ continue;
34555
+ const existing = runtimeChains[agentName] ?? [];
34556
+ const seen = new Set(existing);
34557
+ for (const m of chainModels) {
34558
+ if (!seen.has(m)) {
34559
+ seen.add(m);
34560
+ existing.push(m);
34561
+ }
34562
+ }
34563
+ runtimeChains[agentName] = existing;
34564
+ }
34565
+ }
33537
34566
  const tmuxConfig = {
33538
34567
  enabled: config3.tmux?.enabled ?? false,
33539
34568
  layout: config3.tmux?.layout ?? "main-vertical",
@@ -33560,6 +34589,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33560
34589
  const chatHeadersHook = createChatHeadersHook(ctx);
33561
34590
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
33562
34591
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
34592
+ const foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
33563
34593
  return {
33564
34594
  name: "oh-my-opencode-slim",
33565
34595
  agent: agents,
@@ -33575,7 +34605,11 @@ var OhMyOpenCodeLite = async (ctx) => {
33575
34605
  },
33576
34606
  mcp: mcps,
33577
34607
  config: async (opencodeConfig) => {
33578
- opencodeConfig.default_agent = "orchestrator";
34608
+ const lspConfig = opencodeConfig.lsp;
34609
+ setUserLspConfig(lspConfig);
34610
+ if (config3.setDefaultAgent !== false && !opencodeConfig.default_agent) {
34611
+ opencodeConfig.default_agent = "orchestrator";
34612
+ }
33579
34613
  if (!opencodeConfig.agent) {
33580
34614
  opencodeConfig.agent = { ...agents };
33581
34615
  } else {
@@ -33594,36 +34628,73 @@ var OhMyOpenCodeLite = async (ctx) => {
33594
34628
  }
33595
34629
  }
33596
34630
  const configAgent = opencodeConfig.agent;
33597
- if (Object.keys(modelArrayMap).length > 0) {
34631
+ const fallbackChainsEnabled = config3.fallback?.enabled !== false;
34632
+ const fallbackChains = fallbackChainsEnabled ? config3.fallback?.chains ?? {} : {};
34633
+ const effectiveArrays = {};
34634
+ for (const [agentName, models] of Object.entries(modelArrayMap)) {
34635
+ effectiveArrays[agentName] = [...models];
34636
+ }
34637
+ for (const [agentName, chainModels] of Object.entries(fallbackChains)) {
34638
+ if (!chainModels || chainModels.length === 0)
34639
+ continue;
34640
+ if (!effectiveArrays[agentName]) {
34641
+ const entry = configAgent[agentName];
34642
+ const currentModel = typeof entry?.model === "string" ? entry.model : undefined;
34643
+ effectiveArrays[agentName] = currentModel ? [{ id: currentModel }] : [];
34644
+ }
34645
+ const seen = new Set(effectiveArrays[agentName].map((m) => m.id));
34646
+ for (const chainModel of chainModels) {
34647
+ if (!seen.has(chainModel)) {
34648
+ seen.add(chainModel);
34649
+ effectiveArrays[agentName].push({ id: chainModel });
34650
+ }
34651
+ }
34652
+ }
34653
+ if (Object.keys(effectiveArrays).length > 0) {
33598
34654
  const providerConfig = opencodeConfig.provider ?? {};
33599
- const configuredProviders = Object.keys(providerConfig);
33600
- for (const [agentName, modelArray] of Object.entries(modelArrayMap)) {
34655
+ const hasProviderConfig = Object.keys(providerConfig).length > 0;
34656
+ for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
34657
+ if (modelArray.length === 0)
34658
+ continue;
33601
34659
  let resolved = false;
33602
- for (const modelEntry of modelArray) {
33603
- const slashIdx = modelEntry.id.indexOf("/");
33604
- if (slashIdx === -1)
33605
- continue;
33606
- const providerID = modelEntry.id.slice(0, slashIdx);
33607
- if (configuredProviders.includes(providerID)) {
33608
- const entry = configAgent[agentName];
33609
- if (entry) {
33610
- entry.model = modelEntry.id;
33611
- if (modelEntry.variant) {
33612
- entry.variant = modelEntry.variant;
34660
+ if (hasProviderConfig) {
34661
+ const configuredProviders = Object.keys(providerConfig);
34662
+ for (const modelEntry of modelArray) {
34663
+ const slashIdx = modelEntry.id.indexOf("/");
34664
+ if (slashIdx === -1)
34665
+ continue;
34666
+ const providerID = modelEntry.id.slice(0, slashIdx);
34667
+ if (configuredProviders.includes(providerID)) {
34668
+ const entry = configAgent[agentName];
34669
+ if (entry) {
34670
+ entry.model = modelEntry.id;
34671
+ if (modelEntry.variant) {
34672
+ entry.variant = modelEntry.variant;
34673
+ }
33613
34674
  }
34675
+ log("[plugin] resolved model fallback", {
34676
+ agent: agentName,
34677
+ model: modelEntry.id,
34678
+ variant: modelEntry.variant
34679
+ });
34680
+ resolved = true;
34681
+ break;
33614
34682
  }
33615
- log("[plugin] resolved model fallback", {
33616
- agent: agentName,
33617
- model: modelEntry.id,
33618
- variant: modelEntry.variant
33619
- });
33620
- resolved = true;
33621
- break;
33622
34683
  }
33623
34684
  }
33624
34685
  if (!resolved) {
33625
- log("[plugin] no provider match for model array", {
33626
- agent: agentName
34686
+ const firstModel = modelArray[0];
34687
+ const entry = configAgent[agentName];
34688
+ if (entry) {
34689
+ entry.model = firstModel.id;
34690
+ if (firstModel.variant) {
34691
+ entry.variant = firstModel.variant;
34692
+ }
34693
+ }
34694
+ log("[plugin] resolved model from array (no provider config)", {
34695
+ agent: agentName,
34696
+ model: firstModel.id,
34697
+ variant: firstModel.variant
33627
34698
  });
33628
34699
  }
33629
34700
  }
@@ -33657,6 +34728,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33657
34728
  }
33658
34729
  },
33659
34730
  event: async (input) => {
34731
+ await foregroundFallback.handleEvent(input.event);
33660
34732
  await autoUpdateChecker.event(input);
33661
34733
  await tmuxSessionManager.onSessionCreated(input.event);
33662
34734
  await backgroundManager.handleSessionStatus(input.event);