oh-my-opencode-slim 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.ja-JP.md +638 -0
  2. package/README.ko-KR.md +634 -0
  3. package/README.md +44 -13
  4. package/README.zh-CN.md +627 -0
  5. package/dist/cli/background-subagents.d.ts +13 -0
  6. package/dist/cli/index.js +65 -92
  7. package/dist/cli/skills.d.ts +2 -30
  8. package/dist/cli/types.d.ts +0 -1
  9. package/dist/config/fallback-chains.d.ts +1 -0
  10. package/dist/config/schema.d.ts +7 -0
  11. package/dist/hooks/auto-update-checker/checker.d.ts +7 -1
  12. package/dist/hooks/auto-update-checker/constants.d.ts +1 -0
  13. package/dist/hooks/auto-update-checker/types.d.ts +10 -0
  14. package/dist/hooks/deepwork/index.d.ts +13 -0
  15. package/dist/index.js +300 -121
  16. package/dist/tools/ast-grep/tools.d.ts +1 -1
  17. package/dist/tools/cancel-task.d.ts +16 -0
  18. package/dist/tools/preset-manager.d.ts +6 -7
  19. package/dist/tools/subtask/tools.d.ts +6 -1
  20. package/dist/tui.js +17 -62
  21. package/dist/utils/background-job-board.d.ts +90 -0
  22. package/oh-my-opencode-slim.schema.json +11 -0
  23. package/package.json +6 -3
  24. package/dist/agents/council-master.d.ts +0 -2
  25. package/dist/background/background-manager.d.ts +0 -203
  26. package/dist/background/index.d.ts +0 -3
  27. package/dist/background/multiplexer-session-manager.d.ts +0 -70
  28. package/dist/background/subagent-depth.d.ts +0 -35
  29. package/dist/cli/divoom.d.ts +0 -23
  30. package/dist/goal/index.d.ts +0 -3
  31. package/dist/goal/manager.d.ts +0 -41
  32. package/dist/goal/prompts.d.ts +0 -4
  33. package/dist/goal/store.d.ts +0 -15
  34. package/dist/goal/types.d.ts +0 -28
  35. package/dist/integrations/divoom/index.d.ts +0 -3
  36. package/dist/integrations/divoom/status-manager.d.ts +0 -31
  37. package/dist/integrations/divoom/swift-helper-source.d.ts +0 -1
  38. package/dist/integrations/divoom/swift-transport.d.ts +0 -26
  39. package/dist/integrations/divoom/types.d.ts +0 -41
  40. package/dist/tools/background.d.ts +0 -13
  41. package/dist/tools/fork/command.d.ts +0 -28
  42. package/dist/tools/fork/files.d.ts +0 -33
  43. package/dist/tools/fork/index.d.ts +0 -10
  44. package/dist/tools/fork/state.d.ts +0 -7
  45. package/dist/tools/fork/tools.d.ts +0 -23
  46. package/dist/tools/fork/vendor.d.ts +0 -28
  47. package/dist/tools/handoff/command.d.ts +0 -29
  48. package/dist/tools/handoff/files.d.ts +0 -33
  49. package/dist/tools/handoff/index.d.ts +0 -10
  50. package/dist/tools/handoff/state.d.ts +0 -7
  51. package/dist/tools/handoff/tools.d.ts +0 -23
  52. package/dist/tools/handoff/vendor.d.ts +0 -28
  53. package/dist/tools/lsp/client.d.ts +0 -81
  54. package/dist/tools/lsp/config-store.d.ts +0 -29
  55. package/dist/tools/lsp/config.d.ts +0 -5
  56. package/dist/tools/lsp/constants.d.ts +0 -24
  57. package/dist/tools/lsp/index.d.ts +0 -4
  58. package/dist/tools/lsp/tools.d.ts +0 -5
  59. package/dist/tools/lsp/types.d.ts +0 -45
  60. package/dist/tools/lsp/utils.d.ts +0 -34
  61. package/dist/utils/tmux-debug-log.d.ts +0 -2
package/dist/index.js CHANGED
@@ -18196,6 +18196,13 @@ function getCustomOpenCodeConfigDir() {
18196
18196
  const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
18197
18197
  return configDir || undefined;
18198
18198
  }
18199
+ function getConfigDir() {
18200
+ const customConfigDir = getCustomOpenCodeConfigDir();
18201
+ if (customConfigDir) {
18202
+ return customConfigDir;
18203
+ }
18204
+ return getDefaultOpenCodeConfigDir();
18205
+ }
18199
18206
  function getConfigSearchDirs() {
18200
18207
  const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
18201
18208
  return dirs.filter((dir, index) => {
@@ -18203,7 +18210,7 @@ function getConfigSearchDirs() {
18203
18210
  });
18204
18211
  }
18205
18212
  function getOpenCodeConfigPaths() {
18206
- const configDir = getDefaultOpenCodeConfigDir();
18213
+ const configDir = getConfigDir();
18207
18214
  return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
18208
18215
  }
18209
18216
 
@@ -18230,19 +18237,6 @@ var CUSTOM_SKILLS = [
18230
18237
  ];
18231
18238
 
18232
18239
  // src/cli/skills.ts
18233
- var RECOMMENDED_SKILLS = [
18234
- {
18235
- name: "agent-browser",
18236
- repo: "https://github.com/vercel-labs/agent-browser",
18237
- skillName: "agent-browser",
18238
- allowedAgents: ["designer"],
18239
- description: "High-performance browser automation",
18240
- postInstallCommands: [
18241
- "npm install -g agent-browser",
18242
- "agent-browser install"
18243
- ]
18244
- }
18245
- ];
18246
18240
  var PERMISSION_ONLY_SKILLS = [
18247
18241
  {
18248
18242
  name: "requesting-code-review",
@@ -18267,12 +18261,6 @@ function getSkillPermissionsForAgent(agentName, skillList) {
18267
18261
  }
18268
18262
  return permissions;
18269
18263
  }
18270
- for (const skill of RECOMMENDED_SKILLS) {
18271
- const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
18272
- if (isAllowed) {
18273
- permissions[skill.skillName] = "allow";
18274
- }
18275
- }
18276
18264
  for (const skill of CUSTOM_SKILLS) {
18277
18265
  const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
18278
18266
  if (isAllowed) {
@@ -18603,6 +18591,9 @@ var TodoContinuationConfigSchema = z2.object({
18603
18591
  autoEnable: z2.boolean().default(false).describe("Automatically enable auto-continue when the orchestrator session has enough todos"),
18604
18592
  autoEnableThreshold: z2.number().int().min(1).max(50).default(4).describe("Number of todos that triggers auto-enable (only used when autoEnable is true)")
18605
18593
  });
18594
+ var SubtaskConfigSchema = z2.object({
18595
+ timeoutMs: z2.number().int().min(0).max(24 * 60 * 60 * 1000).optional().describe("Subtask worker timeout in ms. 0 disables the timeout. Defaults to 300000 (5 minutes).")
18596
+ });
18606
18597
  var FailoverConfigSchema = z2.object({
18607
18598
  enabled: z2.boolean().default(true),
18608
18599
  timeoutMs: z2.number().min(0).default(15000),
@@ -18650,6 +18641,7 @@ var PluginConfigSchema = z2.object({
18650
18641
  sessionManager: SessionManagerConfigSchema.optional(),
18651
18642
  divoom: DivoomConfigSchema.optional(),
18652
18643
  todoContinuation: TodoContinuationConfigSchema.optional(),
18644
+ subtask: SubtaskConfigSchema.optional(),
18653
18645
  fallback: FailoverConfigSchema.optional(),
18654
18646
  council: CouncilConfigSchema.optional()
18655
18647
  }).superRefine((value, ctx) => {
@@ -18670,7 +18662,9 @@ function loadConfigFromPath(configPath, options) {
18670
18662
  const content = fs.readFileSync(configPath, "utf-8");
18671
18663
  let rawConfig;
18672
18664
  try {
18673
- rawConfig = JSON.parse(stripJsonComments(content));
18665
+ const stripped = stripJsonComments(content);
18666
+ const interpolated = stripped.replace(/\{env:([^}]+)\}/g, (_, varName) => process.env[varName] ?? "");
18667
+ rawConfig = JSON.parse(interpolated);
18674
18668
  } catch (error) {
18675
18669
  const message = error instanceof Error ? error.message : String(error);
18676
18670
  options?.onWarning?.({
@@ -18749,7 +18743,8 @@ function mergePluginConfigs(base, override) {
18749
18743
  sessionManager: deepMerge(base.sessionManager, override.sessionManager),
18750
18744
  divoom: deepMerge(base.divoom, override.divoom),
18751
18745
  fallback: deepMerge(base.fallback, override.fallback),
18752
- council: deepMerge(base.council, override.council)
18746
+ council: deepMerge(base.council, override.council),
18747
+ subtask: deepMerge(base.subtask, override.subtask)
18753
18748
  };
18754
18749
  }
18755
18750
  function deepMerge(base, override) {
@@ -18901,34 +18896,32 @@ function parseModelReference(model) {
18901
18896
  async function promptWithTimeout(client, args, timeoutMs, signal) {
18902
18897
  if (signal?.aborted)
18903
18898
  throw new Error("Prompt cancelled");
18904
- if (timeoutMs <= 0) {
18905
- await client.session.prompt(args);
18906
- return;
18907
- }
18908
18899
  const sessionId = args.path.id;
18900
+ const hasTimeout = timeoutMs > 0;
18909
18901
  let timer;
18910
18902
  let onAbort;
18911
18903
  try {
18912
18904
  const promptPromise = client.session.prompt(args);
18913
18905
  promptPromise.catch(() => {});
18914
- await Promise.race([
18915
- promptPromise,
18916
- new Promise((_, reject) => {
18906
+ const racers = [promptPromise];
18907
+ if (hasTimeout) {
18908
+ racers.push(new Promise((_, reject) => {
18917
18909
  timer = setTimeout(() => {
18918
18910
  reject(new OperationTimeoutError(`Prompt timed out after ${timeoutMs}ms`));
18919
18911
  }, timeoutMs);
18920
- }),
18921
- new Promise((_, reject) => {
18922
- if (!signal)
18923
- return;
18912
+ }));
18913
+ }
18914
+ if (signal) {
18915
+ racers.push(new Promise((_, reject) => {
18924
18916
  if (signal.aborted) {
18925
18917
  reject(new Error("Prompt cancelled"));
18926
18918
  return;
18927
18919
  }
18928
18920
  onAbort = () => reject(new Error("Prompt cancelled"));
18929
18921
  signal.addEventListener("abort", onAbort, { once: true });
18930
- })
18931
- ]);
18922
+ }));
18923
+ }
18924
+ await Promise.race(racers);
18932
18925
  } catch (error) {
18933
18926
  if (error instanceof OperationTimeoutError) {
18934
18927
  try {
@@ -19012,7 +19005,7 @@ var AGENT_DESCRIPTIONS = {
19012
19005
  - Permissions: Read/write files
19013
19006
  - Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
19014
19007
  - Tools/Constraints: Execution-focused—no research, no architectural decisions
19015
- - **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Writing or updating tests • Tasks that touch test files, fixtures, mocks, or test helpers. Parallelization benefits: Task involves multiple folders and multiple files modificaiton, scoping work per folder and spawning parallel @fixers for each folder.
19008
+ - **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Writing or updating tests • Tasks that touch test files, fixtures, mocks, or test helpers. Parallelization benefits: Task involves multiple folders and multiple files modification, scoping work per folder and spawning parallel @fixers for each folder.
19016
19009
  - **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to fixer > doing • Tight integration with your current work • Sequential dependencies
19017
19010
  - **Rule of thumb:** Explaining > doing? → yourself. Test file modifications and bounded implementation work usually go to @fixer. Bigger or lots of edits, splitting makes sense, parallelized by spawning @fixers per certain scope.`,
19018
19011
  council: `@council
@@ -19135,7 +19128,7 @@ relevant files. Wait for the summary, then integrate and verify it.
19135
19128
  5. Adjust if needed
19136
19129
 
19137
19130
  ### Session Reuse
19138
- - Smartly reuse an available specialist session - constext reuse saves time and tokens
19131
+ - Smartly reuse an available specialist session - context reuse saves time and tokens
19139
19132
  - When too much unrelated, and really needed, start a fresh session with the specialist
19140
19133
  - If multiple remembered sessions fit, prefer the most recently used matching session.
19141
19134
  - Prefer re-uses over creating new sessions all the time
@@ -19982,6 +19975,35 @@ function getDisabledAgents(config) {
19982
19975
  return disabled;
19983
19976
  }
19984
19977
 
19978
+ // src/config/fallback-chains.ts
19979
+ function normalizeFallbackChainsForPreset(chains, presetName) {
19980
+ const normalized = {};
19981
+ for (const [rawKey, chainModels] of Object.entries(chains)) {
19982
+ if (!chainModels?.length)
19983
+ continue;
19984
+ const separatorIndex = rawKey.indexOf(":");
19985
+ const hasPresetScope = separatorIndex !== -1;
19986
+ const scopedPreset = hasPresetScope ? rawKey.slice(0, separatorIndex) : "";
19987
+ const agentName = hasPresetScope ? rawKey.slice(separatorIndex + 1) : rawKey;
19988
+ if (!agentName)
19989
+ continue;
19990
+ if (hasPresetScope && scopedPreset !== presetName)
19991
+ continue;
19992
+ const existing = normalized[agentName] ?? [];
19993
+ const seen = new Set(existing);
19994
+ for (const chainModel of chainModels) {
19995
+ if (seen.has(chainModel))
19996
+ continue;
19997
+ seen.add(chainModel);
19998
+ existing.push(chainModel);
19999
+ }
20000
+ if (existing.length > 0) {
20001
+ normalized[agentName] = existing;
20002
+ }
20003
+ }
20004
+ return normalized;
20005
+ }
20006
+
19985
20007
  // src/config/runtime-preset.ts
19986
20008
  var activeRuntimePreset = null;
19987
20009
  function setActiveRuntimePreset(name) {
@@ -19998,10 +20020,6 @@ function setActiveRuntimePresetWithPrevious(name) {
19998
20020
  previousRuntimePreset = activeRuntimePreset;
19999
20021
  activeRuntimePreset = name;
20000
20022
  }
20001
- function rollbackRuntimePreset(previous) {
20002
- activeRuntimePreset = previous;
20003
- previousRuntimePreset = null;
20004
- }
20005
20023
 
20006
20024
  // src/utils/logger.ts
20007
20025
  import * as fs2 from "node:fs";
@@ -20014,7 +20032,7 @@ var RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
20014
20032
  var logFile = null;
20015
20033
  var writeChain = Promise.resolve();
20016
20034
  function getLogDir() {
20017
- return process.env.OPENCODE_LOG_DIR ?? path2.join(os.homedir(), ".local/share/opencode");
20035
+ return process.env.OPENCODE_LOG_DIR ?? path2.join(os.homedir(), ".local/share/opencode/log");
20018
20036
  }
20019
20037
  function cleanupOldLogs(logDir) {
20020
20038
  try {
@@ -22139,6 +22157,7 @@ import * as os3 from "node:os";
22139
22157
  import * as path6 from "node:path";
22140
22158
  var PACKAGE_NAME = "oh-my-opencode-slim";
22141
22159
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
22160
+ var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
22142
22161
  var NPM_FETCH_TIMEOUT = 5000;
22143
22162
  function getCacheDir() {
22144
22163
  if (process.platform === "win32") {
@@ -22165,6 +22184,83 @@ function isPrereleaseVersion(version) {
22165
22184
  function isDistTag(version) {
22166
22185
  return !/^\d/.test(version);
22167
22186
  }
22187
+ function parseVersion(version) {
22188
+ const normalized = version.trim().replace(/^[~^=<>\s]+/, "");
22189
+ const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?/);
22190
+ if (!match)
22191
+ return null;
22192
+ return {
22193
+ major: Number(match[1]),
22194
+ minor: Number(match[2]),
22195
+ patch: Number(match[3]),
22196
+ prerelease: match[4] ?? null
22197
+ };
22198
+ }
22199
+ function compareVersions(a, b) {
22200
+ const parsedA = parseVersion(a);
22201
+ const parsedB = parseVersion(b);
22202
+ if (!parsedA || !parsedB)
22203
+ return a.localeCompare(b);
22204
+ const parts = [
22205
+ "major",
22206
+ "minor",
22207
+ "patch"
22208
+ ];
22209
+ for (const part of parts) {
22210
+ if (parsedA[part] !== parsedB[part]) {
22211
+ return parsedA[part] - parsedB[part];
22212
+ }
22213
+ }
22214
+ if (parsedA.prerelease === parsedB.prerelease)
22215
+ return 0;
22216
+ if (!parsedA.prerelease)
22217
+ return 1;
22218
+ if (!parsedB.prerelease)
22219
+ return -1;
22220
+ return comparePrerelease(parsedA.prerelease, parsedB.prerelease);
22221
+ }
22222
+ function comparePrerelease(a, b) {
22223
+ const segmentsA = a.split(".");
22224
+ const segmentsB = b.split(".");
22225
+ const length = Math.max(segmentsA.length, segmentsB.length);
22226
+ for (let i = 0;i < length; i++) {
22227
+ const segmentA = segmentsA[i];
22228
+ const segmentB = segmentsB[i];
22229
+ if (segmentA === segmentB)
22230
+ continue;
22231
+ if (segmentA === undefined)
22232
+ return -1;
22233
+ if (segmentB === undefined)
22234
+ return 1;
22235
+ const numberA = Number(segmentA);
22236
+ const numberB = Number(segmentB);
22237
+ const numericA = Number.isInteger(numberA);
22238
+ const numericB = Number.isInteger(numberB);
22239
+ if (numericA && numericB)
22240
+ return numberA - numberB;
22241
+ if (numericA)
22242
+ return -1;
22243
+ if (numericB)
22244
+ return 1;
22245
+ const comparison = segmentA.localeCompare(segmentB);
22246
+ if (comparison !== 0)
22247
+ return comparison;
22248
+ }
22249
+ return 0;
22250
+ }
22251
+ function getPrereleaseChannel(version) {
22252
+ if (!version.prerelease)
22253
+ return null;
22254
+ return version.prerelease.split(".")[0] ?? null;
22255
+ }
22256
+ function isVersionInChannel(version, channel) {
22257
+ const parsed = parseVersion(version);
22258
+ if (!parsed)
22259
+ return false;
22260
+ if (channel === "latest")
22261
+ return parsed.prerelease === null;
22262
+ return getPrereleaseChannel(parsed) === channel;
22263
+ }
22168
22264
  function extractChannel(version) {
22169
22265
  if (!version)
22170
22266
  return "latest";
@@ -22312,6 +22408,10 @@ function getCachedVersion() {
22312
22408
  return null;
22313
22409
  }
22314
22410
  async function getLatestVersion(channel = "latest") {
22411
+ const distTags = await fetchDistTags();
22412
+ return distTags?.[channel] ?? distTags?.latest ?? null;
22413
+ }
22414
+ async function fetchDistTags() {
22315
22415
  const controller = new AbortController;
22316
22416
  const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
22317
22417
  try {
@@ -22322,13 +22422,84 @@ async function getLatestVersion(channel = "latest") {
22322
22422
  if (!response.ok)
22323
22423
  return null;
22324
22424
  const data = await response.json();
22325
- return data[channel] ?? data.latest ?? null;
22425
+ return data;
22326
22426
  } catch {
22327
22427
  return null;
22328
22428
  } finally {
22329
22429
  clearTimeout(timeoutId);
22330
22430
  }
22331
22431
  }
22432
+ async function getLatestCompatibleVersion(currentVersion, channel = "latest") {
22433
+ const current = parseVersion(currentVersion);
22434
+ if (!current) {
22435
+ const latestVersion = await getLatestVersion(channel);
22436
+ return {
22437
+ latestVersion: null,
22438
+ latestMajorVersion: latestVersion,
22439
+ blockedByMajor: latestVersion !== null,
22440
+ unsafeReason: latestVersion ? "unparseable-current-version" : undefined
22441
+ };
22442
+ }
22443
+ const controller = new AbortController;
22444
+ const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
22445
+ try {
22446
+ const response = await fetch(NPM_PACKAGE_URL, {
22447
+ signal: controller.signal,
22448
+ headers: { Accept: "application/json" }
22449
+ });
22450
+ if (!response.ok)
22451
+ return await getCompatibleFromDistTags(current, channel);
22452
+ const data = await response.json();
22453
+ const distTags = data["dist-tags"] ?? { latest: "" };
22454
+ const taggedVersion = distTags[channel] ?? distTags.latest ?? null;
22455
+ const latestMajorVersion = getBlockingMajorVersion(current, [
22456
+ taggedVersion,
22457
+ distTags.latest
22458
+ ]);
22459
+ const blockedByMajor = latestMajorVersion !== null;
22460
+ const versions = Object.keys(data.versions ?? {}).filter((version) => {
22461
+ const parsed = parseVersion(version);
22462
+ return parsed?.major === current.major && isVersionInChannel(version, channel);
22463
+ }).sort(compareVersions);
22464
+ const latestVersion = versions.at(-1) ?? null;
22465
+ return { latestVersion, latestMajorVersion, blockedByMajor };
22466
+ } catch {
22467
+ return await getCompatibleFromDistTags(current, channel);
22468
+ } finally {
22469
+ clearTimeout(timeoutId);
22470
+ }
22471
+ }
22472
+ async function getCompatibleFromDistTags(current, channel) {
22473
+ const distTags = await fetchDistTags();
22474
+ if (!distTags) {
22475
+ return {
22476
+ latestVersion: null,
22477
+ latestMajorVersion: null,
22478
+ blockedByMajor: false
22479
+ };
22480
+ }
22481
+ const latestVersion = distTags[channel] ?? distTags.latest ?? null;
22482
+ const latestMajorVersion = getBlockingMajorVersion(current, [
22483
+ latestVersion,
22484
+ distTags.latest
22485
+ ]);
22486
+ const blockedByMajor = latestMajorVersion !== null;
22487
+ const parsedLatest = latestVersion ? parseVersion(latestVersion) : null;
22488
+ const compatibleLatestVersion = parsedLatest?.major === current.major && latestVersion && isVersionInChannel(latestVersion, channel) ? latestVersion : null;
22489
+ return {
22490
+ latestVersion: compatibleLatestVersion,
22491
+ latestMajorVersion,
22492
+ blockedByMajor
22493
+ };
22494
+ }
22495
+ function getBlockingMajorVersion(current, candidates) {
22496
+ for (const candidate of candidates) {
22497
+ const parsed = candidate ? parseVersion(candidate) : null;
22498
+ if (parsed && parsed.major > current.major)
22499
+ return candidate ?? null;
22500
+ }
22501
+ return null;
22502
+ }
22332
22503
 
22333
22504
  // src/hooks/auto-update-checker/cache.ts
22334
22505
  function removeFromBunLock(installDir, packageName) {
@@ -22472,7 +22643,20 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
22472
22643
  return;
22473
22644
  }
22474
22645
  const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
22475
- const latestVersion = await getLatestVersion(channel);
22646
+ const latestInfo = await getLatestCompatibleVersion(currentVersion, channel);
22647
+ if (latestInfo.unsafeReason === "unparseable-current-version") {
22648
+ log(`[auto-update-checker] Current version is not semver; skipping auto-update: ${currentVersion}`);
22649
+ if (latestInfo.latestMajorVersion) {
22650
+ showToast(ctx, `OMO-Slim ${latestInfo.latestMajorVersion}`, `v${latestInfo.latestMajorVersion} available. Auto-update skipped because the current version could not be compared safely.`, "info", 8000);
22651
+ }
22652
+ return;
22653
+ }
22654
+ if (latestInfo.blockedByMajor && latestInfo.latestMajorVersion) {
22655
+ showMajorUpgradeToast(ctx, latestInfo.latestMajorVersion);
22656
+ log(`[auto-update-checker] Major update available; skipping auto-update: ${latestInfo.latestMajorVersion}`);
22657
+ return;
22658
+ }
22659
+ const latestVersion = latestInfo.latestVersion;
22476
22660
  if (!latestVersion) {
22477
22661
  log("[auto-update-checker] Failed to fetch latest version for channel:", channel);
22478
22662
  return;
@@ -22509,6 +22693,10 @@ Restart OpenCode to apply.`, "success", 8000);
22509
22693
  log("[auto-update-checker] bun install failed; update not installed");
22510
22694
  }
22511
22695
  }
22696
+ function showMajorUpgradeToast(ctx, version) {
22697
+ showToast(ctx, `oh-my-opencode-slim v${version} is available.`, `It requires OpenCode background subagents.
22698
+ Run: bunx oh-my-opencode-slim@latest install --background-subagents=yes`, "info", 12000);
22699
+ }
22512
22700
  async function runBunInstallSafe(installDir) {
22513
22701
  try {
22514
22702
  const proc = crossSpawn(["bun", "install"], {
@@ -23194,7 +23382,7 @@ var RATE_LIMIT_PATTERNS = [
23194
23382
  /usage limit/i,
23195
23383
  /overloaded/i,
23196
23384
  /resource.?exhausted/i,
23197
- /insufficient.?quota/i,
23385
+ /insufficient.?(quota|balance)/i,
23198
23386
  /high concurrency/i,
23199
23387
  /reduce concurrency/i
23200
23388
  ];
@@ -23270,7 +23458,7 @@ class ForegroundFallbackManager {
23270
23458
  if (!props?.sessionID || props.status?.type !== "retry")
23271
23459
  break;
23272
23460
  const msg = props.status.message?.toLowerCase() ?? "";
23273
- if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("exceededbudget") || msg.includes("over budget") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
23461
+ if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("exceededbudget") || msg.includes("over budget") || msg.includes("insufficient") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
23274
23462
  await this.tryFallback(props.sessionID);
23275
23463
  }
23276
23464
  break;
@@ -23677,14 +23865,13 @@ function createPhaseReminderHook() {
23677
23865
  if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
23678
23866
  return;
23679
23867
  }
23680
- if (originalText.includes(PHASE_REMINDER)) {
23868
+ if (lastUserMessage.parts.some((p) => p.text?.includes(PHASE_REMINDER))) {
23681
23869
  return;
23682
23870
  }
23683
- lastUserMessage.parts[textPartIndex].text = `${originalText}
23684
-
23685
- ---
23686
-
23687
- ${PHASE_REMINDER}`;
23871
+ lastUserMessage.parts.push({
23872
+ type: "text",
23873
+ text: PHASE_REMINDER
23874
+ });
23688
23875
  }
23689
23876
  };
23690
23877
  }
@@ -24329,7 +24516,7 @@ function createTaskSessionManagerHook(_ctx, options) {
24329
24516
  };
24330
24517
  }
24331
24518
  // src/hooks/todo-continuation/index.ts
24332
- import { tool } from "@opencode-ai/plugin/tool";
24519
+ import { tool } from "@opencode-ai/plugin";
24333
24520
 
24334
24521
  // src/hooks/todo-continuation/todo-hygiene.ts
24335
24522
  var TODO_HYGIENE_REMINDER = "If the active task changed or finished, update the todo list to match the current work state.";
@@ -29439,7 +29626,8 @@ class MultiplexerSessionManager {
29439
29626
  title,
29440
29627
  directory,
29441
29628
  createdAt: now,
29442
- lastSeenAt: now
29629
+ lastSeenAt: now,
29630
+ seenInStatus: false
29443
29631
  });
29444
29632
  log("[multiplexer-session-manager] pane spawned", {
29445
29633
  instanceId: this.instanceId,
@@ -29531,11 +29719,12 @@ class MultiplexerSessionManager {
29531
29719
  const isIdle = status?.type === "idle";
29532
29720
  if (status) {
29533
29721
  tracked.lastSeenAt = now;
29722
+ tracked.seenInStatus = true;
29534
29723
  tracked.missingSince = undefined;
29535
29724
  } else if (!tracked.missingSince) {
29536
29725
  tracked.missingSince = now;
29537
29726
  }
29538
- const missingTooLong = !!tracked.missingSince && now - tracked.missingSince >= SESSION_MISSING_GRACE_MS;
29727
+ const missingTooLong = tracked.seenInStatus && !!tracked.missingSince && now - tracked.missingSince >= SESSION_MISSING_GRACE_MS;
29539
29728
  const isTimedOut = now - tracked.createdAt > SESSION_TIMEOUT_MS;
29540
29729
  if (isIdle || missingTooLong || isTimedOut) {
29541
29730
  sessionsToClose.push({
@@ -29657,7 +29846,8 @@ class MultiplexerSessionManager {
29657
29846
  title: known.title,
29658
29847
  directory: known.directory,
29659
29848
  createdAt: now,
29660
- lastSeenAt: now
29849
+ lastSeenAt: now,
29850
+ seenInStatus: false
29661
29851
  });
29662
29852
  log("[multiplexer-session-manager] pane respawned on busy", {
29663
29853
  instanceId: this.instanceId,
@@ -29727,7 +29917,7 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
29727
29917
  return false;
29728
29918
  }
29729
29919
  // src/tools/ast-grep/tools.ts
29730
- import { tool as tool2 } from "@opencode-ai/plugin/tool";
29920
+ import { tool as tool2 } from "@opencode-ai/plugin";
29731
29921
 
29732
29922
  // src/tools/ast-grep/cli.ts
29733
29923
  import { existsSync as existsSync9 } from "node:fs";
@@ -30338,6 +30528,9 @@ Returns the councillor responses with a summary footer.`,
30338
30528
  });
30339
30529
  return { council_session };
30340
30530
  }
30531
+ // src/tools/preset-manager.ts
30532
+ import * as fs11 from "node:fs";
30533
+
30341
30534
  // src/tui-state.ts
30342
30535
  import * as fs10 from "node:fs";
30343
30536
  import * as os5 from "node:os";
@@ -30456,64 +30649,47 @@ function createPresetManager(ctx, config) {
30456
30649
  agentUpdates[resolvedName] = agentConfig;
30457
30650
  }
30458
30651
  }
30459
- const currentRuntimePreset = getActiveRuntimePreset();
30460
- const resetUpdates = {};
30461
- if (currentRuntimePreset && config.presets?.[currentRuntimePreset]) {
30462
- const oldPreset = config.presets[currentRuntimePreset];
30463
- for (const rawName of Object.keys(oldPreset)) {
30464
- const resolvedOld = AGENT_ALIASES[rawName] ?? rawName;
30465
- if (resolvedOld in agentUpdates)
30466
- continue;
30467
- const baseline = config.agents?.[resolvedOld];
30468
- if (baseline) {
30469
- resetUpdates[resolvedOld] = mapOverrideToAgentConfig(baseline);
30470
- }
30471
- }
30472
- }
30473
30652
  const hasAgentUpdates = Object.keys(agentUpdates).length > 0;
30474
- const allUpdates = { ...resetUpdates, ...agentUpdates };
30475
30653
  if (!hasAgentUpdates) {
30476
30654
  output.parts.push(createInternalAgentTextPart(`Preset "${presetName}" is empty (no agent overrides defined).`));
30477
30655
  return;
30478
30656
  }
30479
- const previousPreset = activePreset;
30480
30657
  setActiveRuntimePresetWithPrevious(presetName);
30481
30658
  try {
30482
- await ctx.client.config.update({
30483
- body: { agent: allUpdates }
30484
- });
30485
- const snapshot = readTuiSnapshot();
30486
- const agentModels = { ...snapshot.agentModels };
30487
- for (const [agentName, agentConfig] of Object.entries(allUpdates)) {
30488
- if (typeof agentConfig.model === "string") {
30489
- agentModels[agentName] = agentConfig.model;
30490
- }
30491
- }
30492
- recordTuiAgentModels({ agentModels });
30493
- activePreset = presetName;
30494
- const summaryParts = [];
30495
- for (const [name, cfg] of Object.entries(agentUpdates)) {
30496
- const parts = [name];
30497
- if (cfg.model)
30498
- parts.push(`model: ${cfg.model}`);
30499
- if (cfg.variant)
30500
- parts.push(`variant: ${cfg.variant}`);
30501
- if (cfg.temperature !== undefined)
30502
- parts.push(`temp: ${cfg.temperature}`);
30503
- if (cfg.options)
30504
- parts.push("options: yes");
30505
- summaryParts.push(parts.join(" → "));
30506
- }
30507
- if (Object.keys(resetUpdates).length > 0) {
30508
- summaryParts.push(`Reset to baseline: ${Object.keys(resetUpdates).join(", ")}`);
30509
- }
30510
- output.parts.push(createInternalAgentTextPart(`Switched to preset "${presetName}":
30659
+ const { userConfigPath } = findPluginConfigPaths(ctx.directory);
30660
+ if (userConfigPath) {
30661
+ const raw = fs11.readFileSync(userConfigPath, "utf-8");
30662
+ const persisted = JSON.parse(stripJsonComments(raw));
30663
+ persisted.preset = presetName;
30664
+ fs11.writeFileSync(userConfigPath, `${JSON.stringify(persisted, null, 2)}
30665
+ `);
30666
+ }
30667
+ } catch {}
30668
+ const snapshot = readTuiSnapshot();
30669
+ const agentModels = { ...snapshot.agentModels };
30670
+ for (const [agentName, agentConfig] of Object.entries(agentUpdates)) {
30671
+ if (typeof agentConfig.model === "string") {
30672
+ agentModels[agentName] = agentConfig.model;
30673
+ }
30674
+ }
30675
+ recordTuiAgentModels({ agentModels });
30676
+ activePreset = presetName;
30677
+ const summaryParts = [];
30678
+ for (const [name, cfg] of Object.entries(agentUpdates)) {
30679
+ const parts = [name];
30680
+ if (cfg.model)
30681
+ parts.push(`model: ${cfg.model}`);
30682
+ if (cfg.variant)
30683
+ parts.push(`variant: ${cfg.variant}`);
30684
+ if (cfg.temperature !== undefined)
30685
+ parts.push(`temp: ${cfg.temperature}`);
30686
+ if (cfg.options)
30687
+ parts.push("options: yes");
30688
+ summaryParts.push(parts.join(" → "));
30689
+ }
30690
+ output.parts.push(createInternalAgentTextPart(`Saved preset "${presetName}". Restart or reload OpenCode to apply it to agent configuration. The current session was not reloaded to avoid interrupting the active conversation.
30511
30691
  ${summaryParts.join(`
30512
30692
  `)}`));
30513
- } catch (err) {
30514
- rollbackRuntimePreset(previousPreset);
30515
- output.parts.push(createInternalAgentTextPart(`Failed to switch preset "${presetName}": ${String(err)}`));
30516
- }
30517
30693
  }
30518
30694
  function mapOverrideToAgentConfig(override) {
30519
30695
  const agentConfig = {};
@@ -32921,11 +33097,11 @@ function createSubtaskCommandManager(_ctx, state) {
32921
33097
  };
32922
33098
  }
32923
33099
  // src/tools/subtask/files.ts
32924
- import * as fs12 from "node:fs/promises";
33100
+ import * as fs13 from "node:fs/promises";
32925
33101
  import * as path20 from "node:path";
32926
33102
 
32927
33103
  // src/tools/subtask/vendor.ts
32928
- import * as fs11 from "node:fs/promises";
33104
+ import * as fs12 from "node:fs/promises";
32929
33105
  import * as path19 from "node:path";
32930
33106
  var DEFAULT_READ_LIMIT = 2000;
32931
33107
  var MAX_LINE_LENGTH = 2000;
@@ -32969,7 +33145,7 @@ async function isBinaryFile(filepath) {
32969
33145
  return true;
32970
33146
  }
32971
33147
  try {
32972
- const file = await fs11.open(filepath, "r");
33148
+ const file = await fs12.open(filepath, "r");
32973
33149
  try {
32974
33150
  const buffer = Buffer.alloc(SAMPLE_BYTES);
32975
33151
  const result = await file.read(buffer, 0, SAMPLE_BYTES, 0);
@@ -33055,24 +33231,24 @@ function parseFileReferences(text) {
33055
33231
  }
33056
33232
  async function buildSyntheticFileParts(directory, refs) {
33057
33233
  const parts = [];
33058
- const realDirectory = await fs12.realpath(directory);
33234
+ const realDirectory = await fs13.realpath(directory);
33059
33235
  for (const ref of refs) {
33060
33236
  const filepath = path20.resolve(directory, ref);
33061
33237
  const relative3 = path20.relative(directory, filepath);
33062
33238
  if (relative3.startsWith("..") || path20.isAbsolute(relative3))
33063
33239
  continue;
33064
33240
  try {
33065
- const realFilepath = await fs12.realpath(filepath);
33241
+ const realFilepath = await fs13.realpath(filepath);
33066
33242
  const realRelative = path20.relative(realDirectory, realFilepath);
33067
33243
  if (realRelative.startsWith("..") || path20.isAbsolute(realRelative)) {
33068
33244
  continue;
33069
33245
  }
33070
- const stats = await fs12.stat(realFilepath);
33246
+ const stats = await fs13.stat(realFilepath);
33071
33247
  if (!stats.isFile())
33072
33248
  continue;
33073
33249
  if (await isBinaryFile(realFilepath))
33074
33250
  continue;
33075
- const content = await fs12.readFile(realFilepath, "utf-8");
33251
+ const content = await fs13.readFile(realFilepath, "utf-8");
33076
33252
  parts.push({
33077
33253
  type: "text",
33078
33254
  synthetic: true,
@@ -33107,7 +33283,7 @@ function createSubtaskState() {
33107
33283
  }
33108
33284
  // src/tools/subtask/tools.ts
33109
33285
  import { tool as tool5 } from "@opencode-ai/plugin";
33110
- var SUBTASK_TIMEOUT_MS = 5 * 60 * 1000;
33286
+ var DEFAULT_SUBTASK_TIMEOUT_MS = 5 * 60 * 1000;
33111
33287
  var SUBTASK_SUMMARY_TAG_REGEX = /<\/?subtask_summary>/g;
33112
33288
  function normalizeSubtaskSummary(text) {
33113
33289
  return text.replace(SUBTASK_SUMMARY_TAG_REGEX, "").trim();
@@ -33119,8 +33295,9 @@ function getAbortSignal(context) {
33119
33295
  const signal = context.abort;
33120
33296
  return signal && typeof signal === "object" && "addEventListener" in signal && "removeEventListener" in signal && "aborted" in signal ? signal : undefined;
33121
33297
  }
33122
- function createSubtaskTool(ctx, state, depthTracker) {
33298
+ function createSubtaskTool(ctx, state, depthTracker, options = {}) {
33123
33299
  const client = ctx.client;
33300
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SUBTASK_TIMEOUT_MS;
33124
33301
  return tool5({
33125
33302
  description: "Run a child worker session and return its completion summary to the caller",
33126
33303
  args: {
@@ -33218,7 +33395,7 @@ Risks / follow-up:
33218
33395
  ...await buildSyntheticFileParts(directory, files)
33219
33396
  ]
33220
33397
  }
33221
- }, SUBTASK_TIMEOUT_MS, abortSignal);
33398
+ }, timeoutMs, abortSignal);
33222
33399
  const extraction = await extractSessionResult(client, childSessionID, {
33223
33400
  directory,
33224
33401
  includeReasoning: false
@@ -33471,11 +33648,10 @@ var OhMyOpenCodeLite = async (ctx) => {
33471
33648
  runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
33472
33649
  }
33473
33650
  }
33651
+ const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
33474
33652
  if (config.fallback?.enabled !== false) {
33475
- const chains = config.fallback?.chains ?? {};
33653
+ const chains = normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback);
33476
33654
  for (const [agentName, chainModels] of Object.entries(chains)) {
33477
- if (!chainModels?.length)
33478
- continue;
33479
33655
  const existing = runtimeChains[agentName] ?? [];
33480
33656
  const seen = new Set(existing);
33481
33657
  for (const m of chainModels) {
@@ -33587,7 +33763,9 @@ var OhMyOpenCodeLite = async (ctx) => {
33587
33763
  ...todoContinuationHook.tool,
33588
33764
  ast_grep_search,
33589
33765
  ast_grep_replace,
33590
- subtask: createSubtaskTool(ctx, subtaskState, depthTracker),
33766
+ subtask: createSubtaskTool(ctx, subtaskState, depthTracker, {
33767
+ timeoutMs: config.subtask?.timeoutMs
33768
+ }),
33591
33769
  read_session: createReadSessionTool(ctx.client, subtaskState)
33592
33770
  },
33593
33771
  mcp: mcps,
@@ -33613,8 +33791,9 @@ var OhMyOpenCodeLite = async (ctx) => {
33613
33791
  }
33614
33792
  }
33615
33793
  const configAgent = opencodeConfig.agent;
33794
+ const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
33616
33795
  const fallbackChainsEnabled = config.fallback?.enabled !== false;
33617
- const fallbackChains = fallbackChainsEnabled ? config.fallback?.chains ?? {} : {};
33796
+ const fallbackChains = fallbackChainsEnabled ? normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback) : {};
33618
33797
  const effectiveArrays = {};
33619
33798
  for (const [agentName, models] of Object.entries(modelArrayMap)) {
33620
33799
  effectiveArrays[agentName] = [...models];