oh-my-opencode-slim 0.8.2 → 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
  {
@@ -3377,11 +3614,11 @@ var SUBAGENT_DELEGATION_RULES = {
3377
3614
  };
3378
3615
  var DEFAULT_MODELS = {
3379
3616
  orchestrator: undefined,
3380
- oracle: "openai/gpt-5.2-codex",
3381
- librarian: "openai/gpt-5.1-codex-mini",
3382
- explorer: "openai/gpt-5.1-codex-mini",
3383
- designer: "kimi-for-coding/k2p5",
3384
- fixer: "openai/gpt-5.1-codex-mini"
3617
+ oracle: "openai/gpt-5.4",
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
  }
@@ -18639,19 +18885,17 @@ class TmuxSessionManager {
18639
18885
  // src/hooks/auto-update-checker/cache.ts
18640
18886
  import * as fs3 from "fs";
18641
18887
  import * as path4 from "path";
18642
- // src/cli/dynamic-model-selection.ts
18643
- var FREE_BIASED_PROVIDERS = new Set(["opencode"]);
18644
18888
  // src/hooks/auto-update-checker/constants.ts
18645
- import * as os3 from "os";
18889
+ import * as os2 from "os";
18646
18890
  import * as path3 from "path";
18647
18891
  var PACKAGE_NAME = "oh-my-opencode-slim";
18648
18892
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
18649
18893
  var NPM_FETCH_TIMEOUT = 5000;
18650
18894
  function getCacheDir() {
18651
18895
  if (process.platform === "win32") {
18652
- return path3.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
18896
+ return path3.join(process.env.LOCALAPPDATA ?? os2.homedir(), "opencode");
18653
18897
  }
18654
- return path3.join(os3.homedir(), ".cache", "opencode");
18898
+ return path3.join(os2.homedir(), ".cache", "opencode");
18655
18899
  }
18656
18900
  var CACHE_DIR = getCacheDir();
18657
18901
  var INSTALLED_PACKAGE_JSON = path3.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
@@ -19188,6 +19432,216 @@ ${buildRetryGuidance(detected)}`;
19188
19432
  }
19189
19433
  };
19190
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
+ }
19191
19645
  // src/hooks/json-error-recovery/hook.ts
19192
19646
  var JSON_ERROR_TOOL_EXCLUDE_LIST = [
19193
19647
  "bash",
@@ -31658,12 +32112,12 @@ var {spawn: spawn3 } = globalThis.Bun;
31658
32112
  // src/tools/ast-grep/constants.ts
31659
32113
  import { existsSync as existsSync5, statSync as statSync2 } from "fs";
31660
32114
  import { createRequire as createRequire2 } from "module";
31661
- import { dirname as dirname2, join as join8 } from "path";
32115
+ import { dirname as dirname3, join as join8 } from "path";
31662
32116
 
31663
32117
  // src/tools/ast-grep/downloader.ts
31664
32118
  import { chmodSync, existsSync as existsSync4, mkdirSync, unlinkSync } from "fs";
31665
32119
  import { createRequire } from "module";
31666
- import { homedir as homedir4 } from "os";
32120
+ import { homedir as homedir3 } from "os";
31667
32121
  import { join as join7 } from "path";
31668
32122
  var REPO = "ast-grep/ast-grep";
31669
32123
  var DEFAULT_VERSION = "0.40.0";
@@ -31688,11 +32142,11 @@ var PLATFORM_MAP = {
31688
32142
  function getCacheDir2() {
31689
32143
  if (process.platform === "win32") {
31690
32144
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
31691
- const base2 = localAppData || join7(homedir4(), "AppData", "Local");
32145
+ const base2 = localAppData || join7(homedir3(), "AppData", "Local");
31692
32146
  return join7(base2, "oh-my-opencode-slim", "bin");
31693
32147
  }
31694
32148
  const xdgCache = process.env.XDG_CACHE_HOME;
31695
- const base = xdgCache || join7(homedir4(), ".cache");
32149
+ const base = xdgCache || join7(homedir3(), ".cache");
31696
32150
  return join7(base, "oh-my-opencode-slim", "bin");
31697
32151
  }
31698
32152
  function getBinaryName() {
@@ -31715,8 +32169,8 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
31715
32169
  if (existsSync4(binaryPath)) {
31716
32170
  return binaryPath;
31717
32171
  }
31718
- const { arch, os: os4 } = platformInfo;
31719
- const assetName = `app-${arch}-${os4}.zip`;
32172
+ const { arch, os: os3 } = platformInfo;
32173
+ const assetName = `app-${arch}-${os3}.zip`;
31720
32174
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version3}/${assetName}`;
31721
32175
  console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
31722
32176
  try {
@@ -31815,7 +32269,7 @@ function findSgCliPathSync() {
31815
32269
  try {
31816
32270
  const require2 = createRequire2(import.meta.url);
31817
32271
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
31818
- const cliDir = dirname2(cliPkgPath);
32272
+ const cliDir = dirname3(cliPkgPath);
31819
32273
  const sgPath = join8(cliDir, binaryName);
31820
32274
  if (existsSync5(sgPath) && isValidBinary(sgPath)) {
31821
32275
  return sgPath;
@@ -31826,7 +32280,7 @@ function findSgCliPathSync() {
31826
32280
  try {
31827
32281
  const require2 = createRequire2(import.meta.url);
31828
32282
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
31829
- const pkgDir = dirname2(pkgPath);
32283
+ const pkgDir = dirname3(pkgPath);
31830
32284
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
31831
32285
  const binaryPath = join8(pkgDir, astGrepName);
31832
32286
  if (existsSync5(binaryPath) && isValidBinary(binaryPath)) {
@@ -32316,7 +32770,7 @@ var {spawn: spawn4 } = globalThis.Bun;
32316
32770
  // src/tools/grep/constants.ts
32317
32771
  import { spawnSync as spawnSync2 } from "child_process";
32318
32772
  import { existsSync as existsSync8 } from "fs";
32319
- import { dirname as dirname3, join as join10 } from "path";
32773
+ import { dirname as dirname4, join as join10 } from "path";
32320
32774
 
32321
32775
  // src/tools/grep/downloader.ts
32322
32776
  import {
@@ -32361,7 +32815,7 @@ function getDataDir() {
32361
32815
  }
32362
32816
  function getOpenCodeBundledRg() {
32363
32817
  const execPath = process.execPath;
32364
- const execDir = dirname3(execPath);
32818
+ const execDir = dirname4(execPath);
32365
32819
  const isWindows = process.platform === "win32";
32366
32820
  const rgName = isWindows ? "rg.exe" : "rg";
32367
32821
  const candidates = [
@@ -32634,17 +33088,47 @@ var grep = tool({
32634
33088
  // src/tools/lsp/client.ts
32635
33089
  var import_node = __toESM(require_main(), 1);
32636
33090
  import { readFileSync as readFileSync4 } from "fs";
32637
- import { extname, resolve } from "path";
33091
+ import { extname, resolve as resolve2 } from "path";
32638
33092
  import { Readable, Writable } from "stream";
32639
33093
  import { pathToFileURL } from "url";
32640
33094
  var {spawn: spawn5 } = globalThis.Bun;
32641
33095
 
32642
33096
  // src/tools/lsp/config.ts
32643
- import { existsSync as existsSync9 } from "fs";
32644
- 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";
32645
33100
  import { join as join11 } from "path";
32646
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
+
32647
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";
32648
33132
  var SEVERITY_MAP = {
32649
33133
  1: "error",
32650
33134
  2: "warning",
@@ -32653,22 +33137,76 @@ var SEVERITY_MAP = {
32653
33137
  };
32654
33138
  var DEFAULT_MAX_REFERENCES = 200;
32655
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
+ }
32656
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
+ },
32657
33201
  typescript: {
32658
33202
  command: ["typescript-language-server", "--stdio"],
32659
- 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"])
32660
33205
  },
32661
33206
  vue: {
32662
33207
  command: ["vue-language-server", "--stdio"],
32663
- extensions: [".vue"]
32664
- },
32665
- svelte: {
32666
- command: ["svelteserver", "--stdio"],
32667
- extensions: [".svelte"]
32668
- },
32669
- astro: {
32670
- command: ["astro-ls", "--stdio"],
32671
- extensions: [".astro"]
33208
+ extensions: [".vue"],
33209
+ root: NearestRoot(LOCK_FILE_PATTERNS)
32672
33210
  },
32673
33211
  eslint: {
32674
33212
  command: ["vscode-eslint-language-server", "--stdio"],
@@ -32679,54 +33217,316 @@ var BUILTIN_SERVERS = {
32679
33217
  ".jsx",
32680
33218
  ".mjs",
32681
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",
32682
33237
  ".vue",
33238
+ ".astro",
32683
33239
  ".svelte"
32684
- ]
33240
+ ],
33241
+ root: NearestRoot([
33242
+ ".oxlintrc.json",
33243
+ ...LOCK_FILE_PATTERNS,
33244
+ "package.json"
33245
+ ])
32685
33246
  },
32686
- tailwindcss: {
32687
- command: ["tailwindcss-language-server", "--stdio"],
32688
- 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])
32689
33269
  },
32690
33270
  gopls: {
32691
33271
  command: ["gopls"],
32692
- extensions: [".go"]
33272
+ extensions: [".go"],
33273
+ root: NearestRoot(["go.work", "go.mod", "go.sum"])
32693
33274
  },
32694
- rust: {
32695
- command: ["rust-analyzer"],
32696
- extensions: [".rs"]
33275
+ ruby_lsp: {
33276
+ command: ["rubocop", "--lsp"],
33277
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
33278
+ root: NearestRoot(["Gemfile"])
32697
33279
  },
32698
- basedpyright: {
32699
- command: ["basedpyright-langserver", "--stdio"],
32700
- 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
+ ])
32701
33292
  },
32702
33293
  pyright: {
32703
33294
  command: ["pyright-langserver", "--stdio"],
32704
- 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
+ ])
32705
33304
  },
32706
- clangd: {
32707
- command: ["clangd", "--background-index"],
32708
- 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"])
32709
33309
  },
32710
33310
  zls: {
32711
33311
  command: ["zls"],
32712
- 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"])
32713
33488
  }
32714
33489
  };
32715
33490
  var LSP_INSTALL_HINTS = {
33491
+ deno: "Install Deno: https://deno.land/#installation",
32716
33492
  typescript: "npm install -g typescript-language-server typescript",
32717
33493
  vue: "npm install -g @vue/language-server",
32718
- svelte: "npm install -g svelte-language-server",
32719
- astro: "npm install -g @astrojs/language-server",
32720
33494
  eslint: "npm install -g vscode-langservers-extracted",
32721
- 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",
32722
33497
  gopls: "go install golang.org/x/tools/gopls@latest",
32723
- rust: "rustup component add rust-analyzer",
32724
- 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",
32725
33500
  pyright: "pip install pyright",
32726
- clangd: "See https://clangd.llvm.org/installation",
32727
- 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/"
32728
33528
  };
32729
- var EXT_TO_LANG = {
33529
+ var LANGUAGE_EXTENSIONS = {
32730
33530
  ".ts": "typescript",
32731
33531
  ".tsx": "typescriptreact",
32732
33532
  ".mts": "typescript",
@@ -32735,35 +33535,180 @@ var EXT_TO_LANG = {
32735
33535
  ".jsx": "javascriptreact",
32736
33536
  ".mjs": "javascript",
32737
33537
  ".cjs": "javascript",
33538
+ ".ets": "typescript",
32738
33539
  ".vue": "vue",
32739
33540
  ".svelte": "svelte",
32740
33541
  ".astro": "astro",
32741
33542
  ".html": "html",
33543
+ ".htm": "html",
33544
+ ".xml": "xml",
33545
+ ".xsl": "xsl",
32742
33546
  ".css": "css",
32743
33547
  ".scss": "scss",
33548
+ ".sass": "sass",
32744
33549
  ".less": "less",
32745
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",
32746
33559
  ".go": "go",
32747
33560
  ".rs": "rust",
32748
33561
  ".py": "python",
32749
33562
  ".pyi": "python",
33563
+ ".rb": "ruby",
33564
+ ".rake": "ruby",
33565
+ ".gemspec": "ruby",
33566
+ ".ru": "ruby",
32750
33567
  ".c": "c",
32751
33568
  ".cpp": "cpp",
32752
33569
  ".cc": "cpp",
32753
33570
  ".cxx": "cpp",
33571
+ ".c++": "cpp",
32754
33572
  ".h": "c",
32755
33573
  ".hpp": "cpp",
32756
- ".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"
32757
33657
  };
32758
33658
 
32759
33659
  // src/tools/lsp/config.ts
32760
- function findServerForExtension(ext) {
33660
+ function buildMergedServers() {
33661
+ const servers = new Map;
32761
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) {
32762
33706
  if (config3.extensions.includes(ext)) {
32763
33707
  const server = {
32764
- id,
33708
+ id: config3.id,
32765
33709
  command: config3.command,
32766
33710
  extensions: config3.extensions,
33711
+ root: config3.root,
32767
33712
  env: config3.env,
32768
33713
  initialization: config3.initialization
32769
33714
  };
@@ -32773,39 +33718,37 @@ function findServerForExtension(ext) {
32773
33718
  return {
32774
33719
  status: "not_installed",
32775
33720
  server,
32776
- 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`
32777
33722
  };
32778
33723
  }
32779
33724
  }
32780
33725
  return { status: "not_configured", extension: ext };
32781
33726
  }
32782
33727
  function getLanguageId(ext) {
32783
- return EXT_TO_LANG[ext] || "plaintext";
33728
+ return LANGUAGE_EXTENSIONS[ext] || "plaintext";
32784
33729
  }
32785
33730
  function isServerInstalled(command) {
32786
33731
  if (command.length === 0)
32787
33732
  return false;
32788
33733
  const cmd = command[0];
32789
33734
  if (cmd.includes("/") || cmd.includes("\\")) {
32790
- return existsSync9(cmd);
33735
+ return existsSync10(cmd);
32791
33736
  }
32792
33737
  const isWindows = process.platform === "win32";
32793
33738
  const ext = isWindows ? ".exe" : "";
32794
- const pathEnv = process.env.PATH || "";
32795
- const pathSeparator = isWindows ? ";" : ":";
32796
- const paths2 = pathEnv.split(pathSeparator);
32797
- for (const p of paths2) {
32798
- if (existsSync9(join11(p, cmd)) || existsSync9(join11(p, cmd + ext))) {
32799
- return true;
32800
- }
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;
32801
33748
  }
32802
33749
  const cwd = process.cwd();
32803
33750
  const localBin = join11(cwd, "node_modules", ".bin", cmd);
32804
- if (existsSync9(localBin) || existsSync9(localBin + ext)) {
32805
- return true;
32806
- }
32807
- const globalBin = join11(homedir5(), ".config", "opencode", "bin", cmd);
32808
- if (existsSync9(globalBin) || existsSync9(globalBin + ext)) {
33751
+ if (existsSync10(localBin) || existsSync10(localBin + ext)) {
32809
33752
  return true;
32810
33753
  }
32811
33754
  return false;
@@ -32818,6 +33761,7 @@ class LSPServerManager {
32818
33761
  cleanupInterval = null;
32819
33762
  IDLE_TIMEOUT = 5 * 60 * 1000;
32820
33763
  constructor() {
33764
+ log("[lsp] manager initialized");
32821
33765
  this.startCleanupTimer();
32822
33766
  this.registerProcessCleanup();
32823
33767
  }
@@ -32874,16 +33818,31 @@ class LSPServerManager {
32874
33818
  const managed = this.clients.get(key);
32875
33819
  if (managed) {
32876
33820
  if (managed.initPromise) {
33821
+ log("[lsp] getClient: waiting for init", { key, server: server.id });
32877
33822
  await managed.initPromise;
32878
33823
  }
32879
33824
  if (managed.client.isAlive()) {
32880
33825
  managed.refCount++;
32881
33826
  managed.lastUsedAt = Date.now();
33827
+ log("[lsp] getClient: reuse pooled client", {
33828
+ key,
33829
+ server: server.id,
33830
+ refCount: managed.refCount
33831
+ });
32882
33832
  return managed.client;
32883
33833
  }
33834
+ log("[lsp] getClient: client dead, recreating", {
33835
+ key,
33836
+ server: server.id
33837
+ });
32884
33838
  await managed.client.stop();
32885
33839
  this.clients.delete(key);
32886
33840
  }
33841
+ log("[lsp] getClient: creating new client", {
33842
+ key,
33843
+ server: server.id,
33844
+ root
33845
+ });
32887
33846
  const client = new LSPClient(root, server);
32888
33847
  const initPromise2 = (async () => {
32889
33848
  await client.start();
@@ -32903,7 +33862,13 @@ class LSPServerManager {
32903
33862
  m.initPromise = undefined;
32904
33863
  m.isInitializing = false;
32905
33864
  }
33865
+ log("[lsp] getClient: client ready", { key, server: server.id });
32906
33866
  } catch (err) {
33867
+ log("[lsp] getClient: init failed", {
33868
+ key,
33869
+ server: server.id,
33870
+ error: String(err)
33871
+ });
32907
33872
  this.clients.delete(key);
32908
33873
  throw err;
32909
33874
  }
@@ -32915,6 +33880,11 @@ class LSPServerManager {
32915
33880
  if (managed && managed.refCount > 0) {
32916
33881
  managed.refCount--;
32917
33882
  managed.lastUsedAt = Date.now();
33883
+ log("[lsp] releaseClient", {
33884
+ key,
33885
+ server: serverId,
33886
+ refCount: managed.refCount
33887
+ });
32918
33888
  }
32919
33889
  }
32920
33890
  isServerInitializing(root, serverId) {
@@ -32923,14 +33893,19 @@ class LSPServerManager {
32923
33893
  return managed?.isInitializing ?? false;
32924
33894
  }
32925
33895
  async stopAll() {
32926
- 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) {
32927
33900
  await managed.client.stop();
33901
+ log("[lsp] stopAll: client stopped", { key });
32928
33902
  }
32929
33903
  this.clients.clear();
32930
33904
  if (this.cleanupInterval) {
32931
33905
  clearInterval(this.cleanupInterval);
32932
33906
  this.cleanupInterval = null;
32933
33907
  }
33908
+ log("[lsp] stopAll: complete");
32934
33909
  }
32935
33910
  }
32936
33911
  var lspManager = LSPServerManager.getInstance();
@@ -32949,6 +33924,11 @@ class LSPClient {
32949
33924
  this.server = server;
32950
33925
  }
32951
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
+ });
32952
33932
  this.proc = spawn5(this.server.command, {
32953
33933
  stdin: "pipe",
32954
33934
  stdout: "pipe",
@@ -33018,13 +33998,19 @@ class LSPClient {
33018
33998
  this.processExited = true;
33019
33999
  });
33020
34000
  this.connection.listen();
33021
- await new Promise((resolve2) => setTimeout(resolve2, 100));
34001
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
33022
34002
  if (this.proc.exitCode !== null) {
33023
34003
  const stderr = this.stderrBuffer.join(`
33024
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
+ });
33025
34010
  throw new Error(`LSP server exited immediately with code ${this.proc.exitCode}` + (stderr ? `
33026
34011
  stderr: ${stderr}` : ""));
33027
34012
  }
34013
+ log("[lsp] LSPClient.start: server spawned", { server: this.server.id });
33028
34014
  }
33029
34015
  startStderrReading() {
33030
34016
  if (!this.proc)
@@ -33050,6 +34036,10 @@ stderr: ${stderr}` : ""));
33050
34036
  async initialize() {
33051
34037
  if (!this.connection)
33052
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
+ });
33053
34043
  const rootUri = pathToFileURL(this.root).href;
33054
34044
  await this.connection.sendRequest("initialize", {
33055
34045
  processId: process.pid,
@@ -33081,14 +34071,22 @@ stderr: ${stderr}` : ""));
33081
34071
  });
33082
34072
  this.connection.sendNotification("initialized");
33083
34073
  await new Promise((r) => setTimeout(r, 300));
34074
+ log("[lsp] LSPClient.initialize: complete", { server: this.server.id });
33084
34075
  }
33085
34076
  async openFile(filePath) {
33086
- const absPath = resolve(filePath);
33087
- 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 });
33088
34080
  return;
34081
+ }
33089
34082
  const text = readFileSync4(absPath, "utf-8");
33090
34083
  const ext = extname(absPath);
33091
34084
  const languageId = getLanguageId(ext);
34085
+ log("[lsp] openFile: opening document", {
34086
+ filePath: absPath,
34087
+ languageId,
34088
+ size: text.length
34089
+ });
33092
34090
  this.connection?.sendNotification("textDocument/didOpen", {
33093
34091
  textDocument: {
33094
34092
  uri: pathToFileURL(absPath).href,
@@ -33101,7 +34099,7 @@ stderr: ${stderr}` : ""));
33101
34099
  await new Promise((r) => setTimeout(r, 1000));
33102
34100
  }
33103
34101
  async definition(filePath, line, character) {
33104
- const absPath = resolve(filePath);
34102
+ const absPath = resolve2(filePath);
33105
34103
  await this.openFile(absPath);
33106
34104
  return this.connection?.sendRequest("textDocument/definition", {
33107
34105
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33109,7 +34107,7 @@ stderr: ${stderr}` : ""));
33109
34107
  });
33110
34108
  }
33111
34109
  async references(filePath, line, character, includeDeclaration = true) {
33112
- const absPath = resolve(filePath);
34110
+ const absPath = resolve2(filePath);
33113
34111
  await this.openFile(absPath);
33114
34112
  return this.connection?.sendRequest("textDocument/references", {
33115
34113
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33118,7 +34116,7 @@ stderr: ${stderr}` : ""));
33118
34116
  });
33119
34117
  }
33120
34118
  async diagnostics(filePath) {
33121
- const absPath = resolve(filePath);
34119
+ const absPath = resolve2(filePath);
33122
34120
  const uri = pathToFileURL(absPath).href;
33123
34121
  await this.openFile(absPath);
33124
34122
  await new Promise((r) => setTimeout(r, 500));
@@ -33133,7 +34131,7 @@ stderr: ${stderr}` : ""));
33133
34131
  return { items: this.diagnosticsStore.get(uri) ?? [] };
33134
34132
  }
33135
34133
  async rename(filePath, line, character, newName) {
33136
- const absPath = resolve(filePath);
34134
+ const absPath = resolve2(filePath);
33137
34135
  await this.openFile(absPath);
33138
34136
  return this.connection?.sendRequest("textDocument/rename", {
33139
34137
  textDocument: { uri: pathToFileURL(absPath).href },
@@ -33145,6 +34143,7 @@ stderr: ${stderr}` : ""));
33145
34143
  return this.proc !== null && !this.processExited && this.proc.exitCode === null;
33146
34144
  }
33147
34145
  async stop() {
34146
+ log("[lsp] LSPClient.stop: stopping", { server: this.server.id });
33148
34147
  try {
33149
34148
  if (this.connection) {
33150
34149
  await this.connection.sendRequest("shutdown");
@@ -33157,45 +34156,24 @@ stderr: ${stderr}` : ""));
33157
34156
  this.connection = null;
33158
34157
  this.processExited = true;
33159
34158
  this.diagnosticsStore.clear();
34159
+ log("[lsp] LSPClient.stop: complete", { server: this.server.id });
33160
34160
  }
33161
34161
  }
33162
34162
  // src/tools/lsp/utils.ts
33163
34163
  import {
33164
- existsSync as existsSync10,
34164
+ existsSync as existsSync11,
33165
34165
  readFileSync as readFileSync5,
33166
- statSync as statSync3,
34166
+ statSync as statSync4,
33167
34167
  unlinkSync as unlinkSync3,
33168
34168
  writeFileSync as writeFileSync3
33169
34169
  } from "fs";
33170
- 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";
33171
34171
  import { fileURLToPath as fileURLToPath2 } from "url";
33172
- function findWorkspaceRoot(filePath) {
33173
- let dir = resolve2(filePath);
33174
- try {
33175
- if (!statSync3(dir).isDirectory()) {
33176
- dir = dirname4(dir);
33177
- }
33178
- } catch {
33179
- dir = dirname4(dir);
33180
- }
33181
- const markers = [
33182
- ".git",
33183
- "package.json",
33184
- "pyproject.toml",
33185
- "Cargo.toml",
33186
- "go.mod"
33187
- ];
33188
- let prevDir = "";
33189
- while (dir !== prevDir) {
33190
- for (const marker of markers) {
33191
- if (existsSync10(join12(dir, marker))) {
33192
- return dir;
33193
- }
33194
- }
33195
- prevDir = dir;
33196
- dir = dirname4(dir);
34172
+ function findServerProjectRoot(filePath, server) {
34173
+ if (server.root) {
34174
+ return server.root(filePath) ?? dirname6(resolve3(filePath));
33197
34175
  }
33198
- return dirname4(resolve2(filePath));
34176
+ return dirname6(resolve3(filePath));
33199
34177
  }
33200
34178
  function uriToPath(uri) {
33201
34179
  return fileURLToPath2(uri);
@@ -33214,24 +34192,42 @@ function formatServerLookupError(result) {
33214
34192
  return `No LSP server configured for extension: ${result.extension}`;
33215
34193
  }
33216
34194
  async function withLspClient(filePath, fn) {
33217
- const absPath = resolve2(filePath);
34195
+ const absPath = resolve3(filePath);
33218
34196
  const ext = extname2(absPath);
33219
34197
  const result = findServerForExtension(ext);
33220
34198
  if (result.status !== "found") {
34199
+ log("[lsp] withLspClient: server not found", {
34200
+ filePath: absPath,
34201
+ extension: ext
34202
+ });
33221
34203
  throw new Error(formatServerLookupError(result));
33222
34204
  }
33223
34205
  const server = result.server;
33224
- 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
+ });
33225
34212
  const client = await lspManager.getClient(root, server);
33226
34213
  try {
33227
- return await fn(client);
34214
+ const result2 = await fn(client);
34215
+ log("[lsp] withLspClient: operation complete", { server: server.id });
34216
+ return result2;
33228
34217
  } catch (e) {
33229
34218
  if (e instanceof Error && e.message.includes("timeout")) {
33230
34219
  const isInitializing = lspManager.isServerInitializing(root, server.id);
33231
34220
  if (isInitializing) {
34221
+ log("[lsp] withLspClient: timeout during init", {
34222
+ server: server.id
34223
+ });
33232
34224
  throw new Error(`LSP server is still initializing. Please retry in a few seconds.`);
33233
34225
  }
33234
34226
  }
34227
+ log("[lsp] withLspClient: operation failed", {
34228
+ server: server.id,
34229
+ error: String(e)
34230
+ });
33235
34231
  throw e;
33236
34232
  } finally {
33237
34233
  lspManager.releaseClient(root, server.id);
@@ -33315,6 +34311,7 @@ function applyTextEditsToFile(filePath, edits) {
33315
34311
  }
33316
34312
  function applyWorkspaceEdit(edit) {
33317
34313
  if (!edit) {
34314
+ log("[lsp] applyWorkspaceEdit: no edit provided");
33318
34315
  return {
33319
34316
  success: false,
33320
34317
  filesModified: [],
@@ -33322,6 +34319,8 @@ function applyWorkspaceEdit(edit) {
33322
34319
  errors: ["No edit provided"]
33323
34320
  };
33324
34321
  }
34322
+ const changeCount = (edit.changes ? Object.keys(edit.changes).length : 0) + (edit.documentChanges ? edit.documentChanges.length : 0);
34323
+ log("[lsp] applyWorkspaceEdit: applying", { changeCount });
33325
34324
  const result = {
33326
34325
  success: true,
33327
34326
  filesModified: [],
@@ -33388,6 +34387,12 @@ function applyWorkspaceEdit(edit) {
33388
34387
  }
33389
34388
  }
33390
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
+ });
33391
34396
  return result;
33392
34397
  }
33393
34398
  function formatApplyResult(result) {
@@ -33536,6 +34541,28 @@ var OhMyOpenCodeLite = async (ctx) => {
33536
34541
  modelArrayMap[agentDef.name] = agentDef._modelArray;
33537
34542
  }
33538
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
+ }
33539
34566
  const tmuxConfig = {
33540
34567
  enabled: config3.tmux?.enabled ?? false,
33541
34568
  layout: config3.tmux?.layout ?? "main-vertical",
@@ -33562,6 +34589,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33562
34589
  const chatHeadersHook = createChatHeadersHook(ctx);
33563
34590
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
33564
34591
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
34592
+ const foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
33565
34593
  return {
33566
34594
  name: "oh-my-opencode-slim",
33567
34595
  agent: agents,
@@ -33577,43 +34605,96 @@ var OhMyOpenCodeLite = async (ctx) => {
33577
34605
  },
33578
34606
  mcp: mcps,
33579
34607
  config: async (opencodeConfig) => {
33580
- 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
+ }
33581
34613
  if (!opencodeConfig.agent) {
33582
34614
  opencodeConfig.agent = { ...agents };
33583
34615
  } else {
33584
- Object.assign(opencodeConfig.agent, agents);
34616
+ for (const [name, pluginAgent] of Object.entries(agents)) {
34617
+ const existing = opencodeConfig.agent[name];
34618
+ if (existing) {
34619
+ opencodeConfig.agent[name] = {
34620
+ ...pluginAgent,
34621
+ ...existing
34622
+ };
34623
+ } else {
34624
+ opencodeConfig.agent[name] = {
34625
+ ...pluginAgent
34626
+ };
34627
+ }
34628
+ }
33585
34629
  }
33586
34630
  const configAgent = opencodeConfig.agent;
33587
- 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) {
33588
34654
  const providerConfig = opencodeConfig.provider ?? {};
33589
- const configuredProviders = Object.keys(providerConfig);
33590
- 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;
33591
34659
  let resolved = false;
33592
- for (const modelEntry of modelArray) {
33593
- const slashIdx = modelEntry.id.indexOf("/");
33594
- if (slashIdx === -1)
33595
- continue;
33596
- const providerID = modelEntry.id.slice(0, slashIdx);
33597
- if (configuredProviders.includes(providerID)) {
33598
- const entry = configAgent[agentName];
33599
- if (entry) {
33600
- entry.model = modelEntry.id;
33601
- if (modelEntry.variant) {
33602
- 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
+ }
33603
34674
  }
34675
+ log("[plugin] resolved model fallback", {
34676
+ agent: agentName,
34677
+ model: modelEntry.id,
34678
+ variant: modelEntry.variant
34679
+ });
34680
+ resolved = true;
34681
+ break;
33604
34682
  }
33605
- log("[plugin] resolved model fallback", {
33606
- agent: agentName,
33607
- model: modelEntry.id,
33608
- variant: modelEntry.variant
33609
- });
33610
- resolved = true;
33611
- break;
33612
34683
  }
33613
34684
  }
33614
34685
  if (!resolved) {
33615
- log("[plugin] no provider match for model array", {
33616
- 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
33617
34698
  });
33618
34699
  }
33619
34700
  }
@@ -33647,6 +34728,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33647
34728
  }
33648
34729
  },
33649
34730
  event: async (input) => {
34731
+ await foregroundFallback.handleEvent(input.event);
33650
34732
  await autoUpdateChecker.event(input);
33651
34733
  await tmuxSessionManager.onSessionCreated(input.event);
33652
34734
  await backgroundManager.handleSessionStatus(input.event);