oh-my-opencode-slim 2.0.2 → 2.0.3

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
@@ -30,8 +30,160 @@ var __toESM = (mod, isNodeMode, target) => {
30
30
  return to;
31
31
  };
32
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, {
40
+ get: all[name],
41
+ enumerable: true,
42
+ configurable: true,
43
+ set: __exportSetter.bind(all, name)
44
+ });
45
+ };
46
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
33
47
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
34
48
 
49
+ // src/utils/compat.ts
50
+ import { spawn as nodeSpawn } from "node:child_process";
51
+ import { writeFile as fsWriteFile } from "node:fs/promises";
52
+ function collectStream(stream) {
53
+ if (!stream)
54
+ return () => Promise.resolve("");
55
+ const chunks = [];
56
+ stream.on("data", (chunk) => chunks.push(chunk));
57
+ return () => new Promise((resolve, reject) => {
58
+ if (!stream.readable) {
59
+ resolve(Buffer.concat(chunks).toString("utf-8"));
60
+ return;
61
+ }
62
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
63
+ stream.on("error", reject);
64
+ });
65
+ }
66
+ function crossSpawn(command, options) {
67
+ const [cmd, ...args] = command;
68
+ const proc = nodeSpawn(cmd, args, {
69
+ stdio: [
70
+ options?.stdin ?? "ignore",
71
+ options?.stdout ?? "pipe",
72
+ options?.stderr ?? "pipe"
73
+ ],
74
+ cwd: options?.cwd,
75
+ env: options?.env
76
+ });
77
+ const stdoutCollector = collectStream(proc.stdout);
78
+ const stderrCollector = collectStream(proc.stderr);
79
+ const exited = new Promise((resolve, reject) => {
80
+ proc.on("error", reject);
81
+ proc.on("close", (code) => resolve(code ?? 1));
82
+ });
83
+ return {
84
+ proc,
85
+ stdout: stdoutCollector,
86
+ stderr: stderrCollector,
87
+ exited,
88
+ kill: (signal) => proc.kill(signal),
89
+ get exitCode() {
90
+ return proc.exitCode;
91
+ }
92
+ };
93
+ }
94
+ async function crossWrite(path, data) {
95
+ await fsWriteFile(path, Buffer.from(data));
96
+ }
97
+ var init_compat = () => {};
98
+
99
+ // src/utils/zip-extractor.ts
100
+ var exports_zip_extractor = {};
101
+ __export(exports_zip_extractor, {
102
+ extractZip: () => extractZip
103
+ });
104
+ import { spawnSync } from "node:child_process";
105
+ import { release } from "node:os";
106
+ function getWindowsBuildNumber() {
107
+ if (process.platform !== "win32")
108
+ return null;
109
+ const parts = release().split(".");
110
+ if (parts.length >= 3) {
111
+ const build = parseInt(parts[2], 10);
112
+ if (!Number.isNaN(build))
113
+ return build;
114
+ }
115
+ return null;
116
+ }
117
+ function isPwshAvailable() {
118
+ if (process.platform !== "win32")
119
+ return false;
120
+ const result = spawnSync("where", ["pwsh"], {
121
+ stdio: ["ignore", "pipe", "pipe"]
122
+ });
123
+ return result.status === 0;
124
+ }
125
+ function escapePowerShellPath(path4) {
126
+ return path4.replace(/'/g, "''");
127
+ }
128
+ function getWindowsZipExtractor() {
129
+ const buildNumber = getWindowsBuildNumber();
130
+ if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
131
+ return "tar";
132
+ }
133
+ if (isPwshAvailable()) {
134
+ return "pwsh";
135
+ }
136
+ return "powershell";
137
+ }
138
+ async function extractZip(archivePath, destDir) {
139
+ let proc;
140
+ if (process.platform === "win32") {
141
+ const extractor = getWindowsZipExtractor();
142
+ switch (extractor) {
143
+ case "tar":
144
+ proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
145
+ stdout: "ignore",
146
+ stderr: "pipe"
147
+ });
148
+ break;
149
+ case "pwsh":
150
+ proc = crossSpawn([
151
+ "pwsh",
152
+ "-Command",
153
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
154
+ ], {
155
+ stdout: "ignore",
156
+ stderr: "pipe"
157
+ });
158
+ break;
159
+ default:
160
+ proc = crossSpawn([
161
+ "powershell",
162
+ "-Command",
163
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
164
+ ], {
165
+ stdout: "ignore",
166
+ stderr: "pipe"
167
+ });
168
+ break;
169
+ }
170
+ } else {
171
+ proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
172
+ stdout: "ignore",
173
+ stderr: "pipe"
174
+ });
175
+ }
176
+ const exitCode = await proc.exited;
177
+ if (exitCode !== 0) {
178
+ const stderr = await proc.stderr();
179
+ throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
180
+ }
181
+ }
182
+ var WINDOWS_BUILD_WITH_TAR = 17134;
183
+ var init_zip_extractor = __esm(() => {
184
+ init_compat();
185
+ });
186
+
35
187
  // node_modules/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability.js
36
188
  var require_Readability = __commonJS((exports, module) => {
37
189
  function Readability(doc, options) {
@@ -6246,33 +6398,33 @@ var require_URL = __commonJS((exports, module) => {
6246
6398
  else
6247
6399
  return basepath.substring(0, lastslash + 1) + refpath;
6248
6400
  }
6249
- function remove_dot_segments(path18) {
6250
- if (!path18)
6251
- return path18;
6401
+ function remove_dot_segments(path19) {
6402
+ if (!path19)
6403
+ return path19;
6252
6404
  var output = "";
6253
- while (path18.length > 0) {
6254
- if (path18 === "." || path18 === "..") {
6255
- path18 = "";
6405
+ while (path19.length > 0) {
6406
+ if (path19 === "." || path19 === "..") {
6407
+ path19 = "";
6256
6408
  break;
6257
6409
  }
6258
- var twochars = path18.substring(0, 2);
6259
- var threechars = path18.substring(0, 3);
6260
- var fourchars = path18.substring(0, 4);
6410
+ var twochars = path19.substring(0, 2);
6411
+ var threechars = path19.substring(0, 3);
6412
+ var fourchars = path19.substring(0, 4);
6261
6413
  if (threechars === "../") {
6262
- path18 = path18.substring(3);
6414
+ path19 = path19.substring(3);
6263
6415
  } else if (twochars === "./") {
6264
- path18 = path18.substring(2);
6416
+ path19 = path19.substring(2);
6265
6417
  } else if (threechars === "/./") {
6266
- path18 = "/" + path18.substring(3);
6267
- } else if (twochars === "/." && path18.length === 2) {
6268
- path18 = "/";
6269
- } else if (fourchars === "/../" || threechars === "/.." && path18.length === 3) {
6270
- path18 = "/" + path18.substring(4);
6418
+ path19 = "/" + path19.substring(3);
6419
+ } else if (twochars === "/." && path19.length === 2) {
6420
+ path19 = "/";
6421
+ } else if (fourchars === "/../" || threechars === "/.." && path19.length === 3) {
6422
+ path19 = "/" + path19.substring(4);
6271
6423
  output = output.replace(/\/?[^\/]*$/, "");
6272
6424
  } else {
6273
- var segment = path18.match(/(\/?([^\/]*))/)[0];
6425
+ var segment = path19.match(/(\/?([^\/]*))/)[0];
6274
6426
  output += segment;
6275
- path18 = path18.substring(segment.length);
6427
+ path19 = path19.substring(segment.length);
6276
6428
  }
6277
6429
  }
6278
6430
  return output;
@@ -18150,14 +18302,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18150
18302
  } else if (node.nodeType === 1) {
18151
18303
  replacement = replacementForNode.call(self, node);
18152
18304
  }
18153
- return join16(output, replacement);
18305
+ return join17(output, replacement);
18154
18306
  }, "");
18155
18307
  }
18156
18308
  function postProcess(output) {
18157
18309
  var self = this;
18158
18310
  this.rules.forEach(function(rule) {
18159
18311
  if (typeof rule.append === "function") {
18160
- output = join16(output, rule.append(self.options));
18312
+ output = join17(output, rule.append(self.options));
18161
18313
  }
18162
18314
  });
18163
18315
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -18170,7 +18322,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18170
18322
  content = content.trim();
18171
18323
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
18172
18324
  }
18173
- function join16(output, replacement) {
18325
+ function join17(output, replacement) {
18174
18326
  var s1 = trimTrailingNewlines(output);
18175
18327
  var s2 = trimLeadingNewlines(replacement);
18176
18328
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -18417,54 +18569,8 @@ var CouncilConfigSchema = z.object({
18417
18569
  import * as fs from "node:fs";
18418
18570
  import * as path from "node:path";
18419
18571
 
18420
- // src/utils/compat.ts
18421
- import { spawn as nodeSpawn } from "node:child_process";
18422
- import { writeFile as fsWriteFile } from "node:fs/promises";
18423
- function collectStream(stream) {
18424
- if (!stream)
18425
- return () => Promise.resolve("");
18426
- const chunks = [];
18427
- stream.on("data", (chunk) => chunks.push(chunk));
18428
- return () => new Promise((resolve, reject) => {
18429
- if (!stream.readable) {
18430
- resolve(Buffer.concat(chunks).toString("utf-8"));
18431
- return;
18432
- }
18433
- stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
18434
- stream.on("error", reject);
18435
- });
18436
- }
18437
- function crossSpawn(command, options) {
18438
- const [cmd, ...args] = command;
18439
- const proc = nodeSpawn(cmd, args, {
18440
- stdio: [
18441
- options?.stdin ?? "ignore",
18442
- options?.stdout ?? "pipe",
18443
- options?.stderr ?? "pipe"
18444
- ],
18445
- cwd: options?.cwd,
18446
- env: options?.env
18447
- });
18448
- const stdoutCollector = collectStream(proc.stdout);
18449
- const stderrCollector = collectStream(proc.stderr);
18450
- const exited = new Promise((resolve, reject) => {
18451
- proc.on("error", reject);
18452
- proc.on("close", (code) => resolve(code ?? 1));
18453
- });
18454
- return {
18455
- proc,
18456
- stdout: stdoutCollector,
18457
- stderr: stderrCollector,
18458
- exited,
18459
- kill: (signal) => proc.kill(signal),
18460
- get exitCode() {
18461
- return proc.exitCode;
18462
- }
18463
- };
18464
- }
18465
- async function crossWrite(path, data) {
18466
- await fsWriteFile(path, Buffer.from(data));
18467
- }
18572
+ // src/cli/config-io.ts
18573
+ init_compat();
18468
18574
 
18469
18575
  // src/config/agent-mcps.ts
18470
18576
  var DEFAULT_AGENT_MCPS = {
@@ -18604,9 +18710,28 @@ var FailoverConfigSchema = z2.object({
18604
18710
  }).strict();
18605
18711
  var CompanionConfigSchema = z2.object({
18606
18712
  enabled: z2.boolean().optional(),
18713
+ binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
18607
18714
  position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
18608
- size: z2.enum(["small", "medium", "large"]).optional()
18715
+ size: z2.enum(["small", "medium", "large"]).optional(),
18716
+ gifPack: z2.enum(["default"]).optional().describe("Bundled companion animation pack to use."),
18717
+ loopStyle: z2.enum(["classic", "smooth"]).optional().describe("Companion animation playback style: classic loops or smooth ping-pong playback."),
18718
+ speed: z2.number().min(0.25).max(4).optional().describe("Companion animation playback speed multiplier. Defaults to 1."),
18719
+ debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
18609
18720
  });
18721
+ var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
18722
+ var AcpAgentConfigSchema = z2.object({
18723
+ command: z2.string().min(1),
18724
+ args: z2.array(z2.string()).default([]),
18725
+ env: z2.record(z2.string(), z2.string()).default({}),
18726
+ cwd: z2.string().min(1).optional(),
18727
+ description: z2.string().min(1).optional(),
18728
+ prompt: z2.string().min(1).optional(),
18729
+ orchestratorPrompt: z2.string().min(1).optional(),
18730
+ wrapperModel: ProviderModelIdSchema.optional(),
18731
+ timeoutMs: z2.number().int().min(1000).max(900000).default(300000),
18732
+ permissionMode: AcpAgentPermissionModeSchema.default("ask")
18733
+ }).strict();
18734
+ var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
18610
18735
  function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
18611
18736
  for (const [name, override] of Object.entries(overrides)) {
18612
18737
  const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
@@ -18644,7 +18769,8 @@ var PluginConfigSchema = z2.object({
18644
18769
  backgroundJobs: BackgroundJobsConfigSchema.optional(),
18645
18770
  fallback: FailoverConfigSchema.optional(),
18646
18771
  council: CouncilConfigSchema.optional(),
18647
- companion: CompanionConfigSchema.optional()
18772
+ companion: CompanionConfigSchema.optional(),
18773
+ acpAgents: AcpAgentsConfigSchema.optional()
18648
18774
  }).superRefine((value, ctx) => {
18649
18775
  if (value.agents) {
18650
18776
  validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
@@ -18744,6 +18870,7 @@ function mergePluginConfigs(base, override) {
18744
18870
  backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
18745
18871
  fallback: deepMerge(base.fallback, override.fallback),
18746
18872
  council: deepMerge(base.council, override.council),
18873
+ acpAgents: deepMerge(base.acpAgents, override.acpAgents),
18747
18874
  companion: deepMerge(base.companion, override.companion)
18748
18875
  };
18749
18876
  }
@@ -18797,8 +18924,13 @@ function loadPluginConfig(directory, options) {
18797
18924
  if (config.companion) {
18798
18925
  config.companion = {
18799
18926
  enabled: config.companion.enabled ?? false,
18927
+ binaryPath: config.companion.binaryPath,
18800
18928
  position: config.companion.position ?? "bottom-right",
18801
- size: config.companion.size ?? "medium"
18929
+ size: config.companion.size ?? "medium",
18930
+ gifPack: config.companion.gifPack ?? "default",
18931
+ loopStyle: config.companion.loopStyle ?? "classic",
18932
+ speed: config.companion.speed ?? 1,
18933
+ debug: config.companion.debug ?? false
18802
18934
  };
18803
18935
  }
18804
18936
  return config;
@@ -18859,6 +18991,9 @@ function getCustomAgentNames(config) {
18859
18991
  return !ALL_AGENT_NAMES.includes(name);
18860
18992
  });
18861
18993
  }
18994
+ function getAcpAgentNames(config) {
18995
+ return Object.keys(config?.acpAgents ?? {});
18996
+ }
18862
18997
  // src/utils/session.ts
18863
18998
  var SESSION_ABORT_TIMEOUT_MS = 1000;
18864
18999
 
@@ -19734,6 +19869,39 @@ function normalizeDisplayName(displayName) {
19734
19869
  const trimmed = displayName.trim();
19735
19870
  return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
19736
19871
  }
19872
+ function buildAcpAgentDefinition(name, config) {
19873
+ const description = config.description ?? `External ACP agent '${name}' via ${config.command}`;
19874
+ const prompt = config.prompt ?? [
19875
+ `You are the ${name} ACP wrapper agent.`,
19876
+ "",
19877
+ "Your only job is to send the user task to the configured external ACP agent using the acp_run tool, then return the ACP agent result.",
19878
+ `Always call acp_run with agent: ${JSON.stringify(name)} and pass the full user task as prompt.`,
19879
+ "Do not edit files yourself unless the ACP result explicitly asks you to report a local follow-up to the orchestrator."
19880
+ ].join(`
19881
+ `);
19882
+ return {
19883
+ name,
19884
+ description,
19885
+ config: {
19886
+ model: config.wrapperModel ?? DEFAULT_MODELS.fixer ?? DEFAULT_MODELS.librarian ?? DEFAULT_MODELS.orchestrator ?? DEFAULT_MODELS.oracle,
19887
+ temperature: 0,
19888
+ prompt,
19889
+ permission: {
19890
+ read: "deny",
19891
+ edit: "deny",
19892
+ bash: "deny",
19893
+ task: "deny",
19894
+ glob: "deny",
19895
+ grep: "deny",
19896
+ list: "deny",
19897
+ webfetch: "deny",
19898
+ question: "deny",
19899
+ skill: "deny",
19900
+ acp_run: "allow"
19901
+ }
19902
+ }
19903
+ };
19904
+ }
19737
19905
  function isSafeDisplayName(displayName) {
19738
19906
  return SAFE_AGENT_ALIAS_RE.test(displayName);
19739
19907
  }
@@ -19873,6 +20041,24 @@ function createAgents(config) {
19873
20041
  buildCustomAgentDefinition(name, override, customPrompts.prompt, customPrompts.appendPrompt)
19874
20042
  ];
19875
20043
  });
20044
+ const acpAgentNames = getAcpAgentNames(config).map(normalizeCustomAgentName).filter((name) => name.length > 0).filter((name) => {
20045
+ if (!SAFE_AGENT_ALIAS_RE.test(name)) {
20046
+ throw new Error(`ACP agent name '${name}' must match /^[a-z][a-z0-9_-]*$/i`);
20047
+ }
20048
+ if (isKnownAgentName(name) || AGENT_ALIASES[name] !== undefined) {
20049
+ throw new Error(`ACP agent '${name}' conflicts with a built-in agent name or alias`);
20050
+ }
20051
+ if (customAgentNames.includes(name)) {
20052
+ throw new Error(`ACP agent '${name}' conflicts with a custom agent of the same name`);
20053
+ }
20054
+ return !disabled.has(name);
20055
+ });
20056
+ const protoAcpAgents = acpAgentNames.map((name) => {
20057
+ const acp = config?.acpAgents?.[name];
20058
+ if (!acp)
20059
+ throw new Error(`ACP agent '${name}' is missing config`);
20060
+ return buildAcpAgentDefinition(name, acp);
20061
+ });
19876
20062
  const builtInSubAgents = protoSubAgents.map((agent) => {
19877
20063
  const override = getAgentOverride(config, agent.name);
19878
20064
  if (override) {
@@ -19896,7 +20082,15 @@ function createAgents(config) {
19896
20082
  applyDefaultPermissions(agent, override?.skills);
19897
20083
  return agent;
19898
20084
  });
19899
- const allSubAgents = [...builtInSubAgents, ...customSubAgents];
20085
+ const acpSubAgents = protoAcpAgents.map((agent) => {
20086
+ applyDefaultPermissions(agent);
20087
+ return agent;
20088
+ });
20089
+ const allSubAgents = [
20090
+ ...builtInSubAgents,
20091
+ ...customSubAgents,
20092
+ ...acpSubAgents
20093
+ ];
19900
20094
  const orchestratorOverride = getAgentOverride(config, "orchestrator");
19901
20095
  const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
19902
20096
  const orchestratorPrompts = loadAgentPrompt("orchestrator", config?.preset);
@@ -19918,6 +20112,20 @@ function createAgents(config) {
19918
20112
  const override = getAgentOverride(config, agent.name);
19919
20113
  return override?.orchestratorPrompt;
19920
20114
  }).filter((prompt) => Boolean(prompt));
20115
+ const acpOrchestratorPrompts = acpSubAgents.map((agent) => {
20116
+ const acp = config?.acpAgents?.[agent.name];
20117
+ if (acp?.orchestratorPrompt)
20118
+ return acp.orchestratorPrompt;
20119
+ return [
20120
+ `@${agent.name}`,
20121
+ `- Lane: External ACP-connected agent (${acp?.command ?? "unknown command"})`,
20122
+ `- Role: ${agent.description ?? `External ACP agent ${agent.name}`}`,
20123
+ "- **Delegate when:** The user explicitly asks for this ACP-backed agent, or the task matches its role and benefits from software/subscription-specific capabilities outside OpenCode.",
20124
+ "- **Do not delegate when:** The built-in specialists can handle the task more directly or local file ownership would conflict with another writer lane.",
20125
+ "- **Result handling:** Treat returned output as external-agent work. Reconcile any reported file changes before continuing."
20126
+ ].join(`
20127
+ `);
20128
+ });
19921
20129
  const usedDisplayNames = new Set;
19922
20130
  for (const [, displayName] of displayNameMap) {
19923
20131
  const normalizedDisplayName = normalizeDisplayName(displayName);
@@ -19930,13 +20138,17 @@ function createAgents(config) {
19930
20138
  usedDisplayNames.add(normalizedDisplayName);
19931
20139
  }
19932
20140
  for (const displayName of usedDisplayNames) {
19933
- if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName)) {
20141
+ if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName) || acpAgentNames.includes(displayName)) {
19934
20142
  throw new Error(`displayName '${displayName}' conflicts with an agent name`);
19935
20143
  }
19936
20144
  }
19937
20145
  injectDisplayNames(orchestrator, displayNameMap);
19938
- if (customOrchestratorPrompts.length > 0) {
19939
- const rewrittenPrompts = customOrchestratorPrompts.map((promptText) => {
20146
+ const extraOrchestratorPrompts = [
20147
+ ...customOrchestratorPrompts,
20148
+ ...acpOrchestratorPrompts
20149
+ ];
20150
+ if (extraOrchestratorPrompts.length > 0) {
20151
+ const rewrittenPrompts = extraOrchestratorPrompts.map((promptText) => {
19940
20152
  let text = promptText;
19941
20153
  for (const [internalName, displayName] of displayNameMap) {
19942
20154
  text = text.replace(new RegExp(`@${escapeRegExp(internalName)}\\b`, "g"), `@${normalizeDisplayName(displayName)}`);
@@ -20008,6 +20220,7 @@ import {
20008
20220
  mkdirSync as mkdirSync2,
20009
20221
  readFileSync as readFileSync2,
20010
20222
  renameSync,
20223
+ rmSync,
20011
20224
  writeFileSync
20012
20225
  } from "node:fs";
20013
20226
  import * as os2 from "node:os";
@@ -20096,11 +20309,15 @@ function stateFilePath() {
20096
20309
  const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
20097
20310
  return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
20098
20311
  }
20099
- function binaryPath() {
20312
+ function defaultBinaryPath() {
20100
20313
  const xdg = process.env.XDG_DATA_HOME?.trim();
20101
20314
  const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
20102
20315
  const binaryName = os2.platform() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
20103
- const bin = path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", binaryName);
20316
+ return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", binaryName);
20317
+ }
20318
+ function resolveCompanionBinaryPath(config) {
20319
+ const configured = config?.binaryPath?.trim();
20320
+ const bin = configured || defaultBinaryPath();
20104
20321
  return existsSync2(bin) ? bin : null;
20105
20322
  }
20106
20323
  function readState() {
@@ -20113,17 +20330,43 @@ function readState() {
20113
20330
  } catch {}
20114
20331
  return { version: 1, sessions: [] };
20115
20332
  }
20116
- function writeState(state) {
20333
+ function writeState(mutator) {
20117
20334
  const file = stateFilePath();
20118
20335
  try {
20119
20336
  mkdirSync2(path3.dirname(file), { recursive: true });
20120
- const tmp = `${file}.tmp`;
20121
- writeFileSync(tmp, JSON.stringify(state));
20122
- renameSync(tmp, file);
20337
+ const release = acquireStateLock(file);
20338
+ try {
20339
+ const state = readState();
20340
+ mutator(state);
20341
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
20342
+ writeFileSync(tmp, JSON.stringify(state));
20343
+ renameSync(tmp, file);
20344
+ } finally {
20345
+ release();
20346
+ }
20123
20347
  } catch (err) {
20124
20348
  log("[companion] write failed", String(err));
20125
20349
  }
20126
20350
  }
20351
+ function acquireStateLock(file) {
20352
+ const lock = `${file}.lock`;
20353
+ for (let attempt = 0;attempt < 40; attempt++) {
20354
+ try {
20355
+ mkdirSync2(lock);
20356
+ return () => {
20357
+ try {
20358
+ rmSync(lock, { recursive: true, force: true });
20359
+ } catch {}
20360
+ };
20361
+ } catch (err) {
20362
+ const code = err.code;
20363
+ if (code !== "EEXIST")
20364
+ throw err;
20365
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
20366
+ }
20367
+ }
20368
+ throw new Error("timed out waiting for companion state lock");
20369
+ }
20127
20370
 
20128
20371
  class CompanionManager {
20129
20372
  id;
@@ -20139,12 +20382,11 @@ class CompanionManager {
20139
20382
  onLoad() {
20140
20383
  if (this.config?.enabled !== true) {
20141
20384
  try {
20142
- const state = readState();
20143
- const filtered = state.sessions.filter((s) => s.session_id !== this.id);
20144
- if (filtered.length !== state.sessions.length) {
20145
- state.sessions = filtered;
20146
- writeState(state);
20147
- }
20385
+ if (!existsSync2(stateFilePath()))
20386
+ return;
20387
+ writeState((state) => {
20388
+ state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20389
+ });
20148
20390
  } catch {}
20149
20391
  return;
20150
20392
  }
@@ -20196,9 +20438,9 @@ class CompanionManager {
20196
20438
  onExit() {
20197
20439
  if (this.config?.enabled !== true)
20198
20440
  return;
20199
- const state = readState();
20200
- state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20201
- writeState(state);
20441
+ writeState((state) => {
20442
+ state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20443
+ });
20202
20444
  }
20203
20445
  activeAgents() {
20204
20446
  const agents = Array.from(this.busyAgentSessions.values());
@@ -20214,28 +20456,41 @@ class CompanionManager {
20214
20456
  if (this.config?.enabled !== true)
20215
20457
  return;
20216
20458
  try {
20217
- const state = readState();
20218
20459
  const entry = {
20219
20460
  session_id: this.id,
20220
20461
  cwd: this.cwd,
20221
20462
  active_agents: this.activeAgents(),
20222
20463
  status: this.status,
20223
- pid: process.pid
20224
- };
20225
- const idx = state.sessions.findIndex((s) => s.session_id === this.id);
20226
- if (idx >= 0) {
20227
- state.sessions[idx] = entry;
20228
- } else {
20229
- state.sessions.push(entry);
20230
- }
20231
- if (this.config) {
20232
- state.config = {
20464
+ pid: process.pid,
20465
+ config: this.config ? {
20233
20466
  enabled: this.config.enabled ?? false,
20234
20467
  position: this.config.position ?? "bottom-right",
20235
- size: this.config.size ?? "medium"
20236
- };
20237
- }
20238
- writeState(state);
20468
+ size: this.config.size ?? "medium",
20469
+ gifPack: this.config.gifPack ?? "default",
20470
+ loopStyle: this.config.loopStyle ?? "classic",
20471
+ speed: this.config.speed ?? 1,
20472
+ debug: this.config.debug ?? false
20473
+ } : undefined
20474
+ };
20475
+ writeState((state) => {
20476
+ const idx = state.sessions.findIndex((s) => s.session_id === this.id);
20477
+ if (idx >= 0) {
20478
+ state.sessions[idx] = entry;
20479
+ } else {
20480
+ state.sessions.push(entry);
20481
+ }
20482
+ if (this.config) {
20483
+ state.config = {
20484
+ enabled: this.config.enabled ?? false,
20485
+ position: this.config.position ?? "bottom-right",
20486
+ size: this.config.size ?? "medium",
20487
+ gifPack: this.config.gifPack ?? "default",
20488
+ loopStyle: this.config.loopStyle ?? "classic",
20489
+ speed: this.config.speed ?? 1,
20490
+ debug: this.config.debug ?? false
20491
+ };
20492
+ }
20493
+ });
20239
20494
  } catch (err) {
20240
20495
  log("[companion] flush failed", String(err));
20241
20496
  }
@@ -20243,24 +20498,358 @@ class CompanionManager {
20243
20498
  spawnIfAvailable() {
20244
20499
  if (this.config?.enabled !== true)
20245
20500
  return;
20246
- const bin = binaryPath();
20501
+ const bin = resolveCompanionBinaryPath(this.config);
20247
20502
  if (!bin) {
20248
- const xdg = process.env.XDG_DATA_HOME?.trim();
20249
- const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
20250
- const expected = path3.join(base, "o‍pencode", "storage", "oh-my-o‍pencode-slim", "bin", "oh-my-o‍pencode-slim-companion");
20503
+ const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
20251
20504
  log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
20252
20505
  return;
20253
20506
  }
20254
20507
  try {
20255
- const child = spawn(bin, [], { detached: true, stdio: "ignore" });
20508
+ const child = spawn(bin, [], {
20509
+ detached: true,
20510
+ env: {
20511
+ ...process.env,
20512
+ OH_MY_OPENCODE_SLIM_COMPANION_SESSION_ID: this.id,
20513
+ ...this.config.debug === true ? { OH_MY_OPENCODE_SLIM_COMPANION_DEBUG: "1" } : {}
20514
+ },
20515
+ stdio: "ignore"
20516
+ });
20256
20517
  child.unref();
20257
- log("[companion] spawned", bin);
20518
+ log("[companion] spawned", JSON.stringify({
20519
+ bin,
20520
+ sessionId: this.id,
20521
+ debug: this.config.debug === true
20522
+ }));
20258
20523
  } catch (err) {
20259
20524
  log("[companion] spawn failed", String(err));
20260
20525
  }
20261
20526
  }
20262
20527
  }
20263
20528
 
20529
+ // src/companion/updater.ts
20530
+ init_compat();
20531
+ import { createHash } from "node:crypto";
20532
+ import {
20533
+ chmodSync,
20534
+ copyFileSync,
20535
+ existsSync as existsSync3,
20536
+ mkdirSync as mkdirSync3,
20537
+ mkdtempSync,
20538
+ readFileSync as readFileSync3,
20539
+ renameSync as renameSync2,
20540
+ rmSync as rmSync2,
20541
+ statSync as statSync2,
20542
+ writeFileSync as writeFileSync2
20543
+ } from "node:fs";
20544
+ import { homedir as homedir4, platform as platform2, tmpdir } from "node:os";
20545
+ import * as path4 from "node:path";
20546
+ import { setTimeout as delay } from "node:timers/promises";
20547
+ var DOWNLOAD_TIMEOUT_MS = 30000;
20548
+ var LOCK_TIMEOUT_MS = 2000;
20549
+ var STALE_LOCK_MS = 5 * 60000;
20550
+ var FIRST_METADATA_VERSION = "0.1.2";
20551
+ var COMPANION_MANIFEST = {
20552
+ version: "0.1.3",
20553
+ tag: "companion-v0.1.3",
20554
+ repo: "alvinunreal/oh-my-opencode-slim",
20555
+ checksums: {
20556
+ "oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
20557
+ "oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
20558
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
20559
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
20560
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
20561
+ }
20562
+ };
20563
+ function getCompanionTarget() {
20564
+ const p = process.platform;
20565
+ const a = process.arch;
20566
+ if (p === "darwin") {
20567
+ if (a === "arm64")
20568
+ return "aarch64-apple-darwin";
20569
+ if (a === "x64")
20570
+ return "x86_64-apple-darwin";
20571
+ } else if (p === "linux") {
20572
+ if (a === "x64")
20573
+ return "x86_64-unknown-linux-gnu";
20574
+ if (a === "arm64")
20575
+ return "aarch64-unknown-linux-gnu";
20576
+ } else if (p === "win32") {
20577
+ if (a === "x64")
20578
+ return "x86_64-pc-windows-msvc";
20579
+ }
20580
+ return null;
20581
+ }
20582
+ function getCompanionBinaryPath() {
20583
+ const xdg = process.env.XDG_DATA_HOME?.trim();
20584
+ const base = xdg && path4.isAbsolute(xdg) ? xdg : path4.join(homedir4(), ".local", "share");
20585
+ return path4.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", platform2() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
20586
+ }
20587
+ function loadCompanionManifestFromPackageRoot(packageRoot) {
20588
+ const manifestPath = path4.join(packageRoot, "src", "companion", "companion-manifest.json");
20589
+ try {
20590
+ const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
20591
+ if (parsed.version && parsed.tag && parsed.repo) {
20592
+ return {
20593
+ version: parsed.version,
20594
+ tag: parsed.tag,
20595
+ repo: parsed.repo,
20596
+ checksums: parsed.checksums
20597
+ };
20598
+ }
20599
+ } catch {}
20600
+ return null;
20601
+ }
20602
+ async function ensureCompanionVersion(options) {
20603
+ const { config, dryRun = false } = options;
20604
+ const manifest = options.manifest ?? COMPANION_MANIFEST;
20605
+ const binaryPath = getCompanionBinaryPath();
20606
+ if (config?.enabled !== true) {
20607
+ return { status: "skipped", reason: "disabled", binaryPath };
20608
+ }
20609
+ if (config.binaryPath?.trim()) {
20610
+ return { status: "skipped", reason: "custom-binary", binaryPath };
20611
+ }
20612
+ const target = getCompanionTarget();
20613
+ if (!target) {
20614
+ return {
20615
+ status: "failed",
20616
+ binaryPath,
20617
+ error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
20618
+ };
20619
+ }
20620
+ const current = readInstallMetadata(binaryPath);
20621
+ if (existsSync3(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
20622
+ const archiveName = companionArchiveName(manifest.version, target);
20623
+ writeInstallMetadata(binaryPath, {
20624
+ version: manifest.version,
20625
+ tag: manifest.tag,
20626
+ target,
20627
+ installedAt: new Date().toISOString(),
20628
+ archiveName,
20629
+ checksum: manifest.checksums?.[archiveName]
20630
+ });
20631
+ return { status: "current", binaryPath, version: manifest.version };
20632
+ }
20633
+ if (existsSync3(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
20634
+ return { status: "current", binaryPath, version: current.version };
20635
+ }
20636
+ if (dryRun) {
20637
+ return { status: "installed", binaryPath, version: manifest.version };
20638
+ }
20639
+ return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
20640
+ const lockedCurrent = readInstallMetadata(binaryPath);
20641
+ if (existsSync3(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
20642
+ const archiveName = companionArchiveName(manifest.version, target);
20643
+ writeInstallMetadata(binaryPath, {
20644
+ version: manifest.version,
20645
+ tag: manifest.tag,
20646
+ target,
20647
+ installedAt: new Date().toISOString(),
20648
+ archiveName,
20649
+ checksum: manifest.checksums?.[archiveName]
20650
+ });
20651
+ return { status: "current", binaryPath, version: manifest.version };
20652
+ }
20653
+ if (existsSync3(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
20654
+ return {
20655
+ status: "current",
20656
+ binaryPath,
20657
+ version: lockedCurrent.version
20658
+ };
20659
+ }
20660
+ return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
20661
+ });
20662
+ }
20663
+ async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
20664
+ const isWindows = process.platform === "win32";
20665
+ const archiveName = companionArchiveName(manifest.version, target, isWindows);
20666
+ const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
20667
+ const expectedChecksum = manifest.checksums?.[archiveName];
20668
+ if (!expectedChecksum) {
20669
+ return {
20670
+ status: "failed",
20671
+ binaryPath: finalBinaryPath,
20672
+ error: `Missing SHA256 checksum for companion archive: ${archiveName}`
20673
+ };
20674
+ }
20675
+ let buffer;
20676
+ const controller = new AbortController;
20677
+ const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
20678
+ try {
20679
+ const res = await fetch(downloadUrl, { signal: controller.signal });
20680
+ if (!res.ok) {
20681
+ return {
20682
+ status: "failed",
20683
+ binaryPath: finalBinaryPath,
20684
+ error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
20685
+ };
20686
+ }
20687
+ buffer = await res.arrayBuffer();
20688
+ } catch (err) {
20689
+ return {
20690
+ status: "failed",
20691
+ binaryPath: finalBinaryPath,
20692
+ error: `Failed to fetch companion archive: ${formatError(err)}`
20693
+ };
20694
+ } finally {
20695
+ clearTimeout(timeout);
20696
+ }
20697
+ const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
20698
+ if (checksum !== expectedChecksum) {
20699
+ return {
20700
+ status: "failed",
20701
+ binaryPath: finalBinaryPath,
20702
+ error: "Companion archive checksum mismatch"
20703
+ };
20704
+ }
20705
+ let tempDir = "";
20706
+ try {
20707
+ tempDir = mkdtempSync(path4.join(tmpdir(), "companion-install-"));
20708
+ const archivePath = path4.join(tempDir, archiveName);
20709
+ writeFileSync2(archivePath, Buffer.from(buffer));
20710
+ const extractedDir = path4.join(tempDir, "extracted");
20711
+ mkdirSync3(extractedDir, { recursive: true });
20712
+ if (isWindows) {
20713
+ const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
20714
+ await extractZip2(archivePath, extractedDir);
20715
+ } else {
20716
+ const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
20717
+ const exitCode = await proc.exited;
20718
+ if (exitCode !== 0) {
20719
+ const stderr = await proc.stderr();
20720
+ return {
20721
+ status: "failed",
20722
+ binaryPath: finalBinaryPath,
20723
+ error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
20724
+ };
20725
+ }
20726
+ }
20727
+ const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
20728
+ const extractedBinaryPath = path4.join(extractedDir, binaryName);
20729
+ if (!existsSync3(extractedBinaryPath)) {
20730
+ return {
20731
+ status: "failed",
20732
+ binaryPath: finalBinaryPath,
20733
+ error: `Binary ${binaryName} not found in extracted archive`
20734
+ };
20735
+ }
20736
+ const binDir = path4.dirname(finalBinaryPath);
20737
+ mkdirSync3(binDir, { recursive: true });
20738
+ const tmpFinalPath = `${finalBinaryPath}.tmp`;
20739
+ copyFileSync(extractedBinaryPath, tmpFinalPath);
20740
+ if (!isWindows) {
20741
+ chmodSync(tmpFinalPath, 493);
20742
+ }
20743
+ renameSync2(tmpFinalPath, finalBinaryPath);
20744
+ writeInstallMetadata(finalBinaryPath, {
20745
+ version: manifest.version,
20746
+ tag: manifest.tag,
20747
+ target,
20748
+ installedAt: new Date().toISOString(),
20749
+ archiveName,
20750
+ checksum
20751
+ });
20752
+ return {
20753
+ status: "installed",
20754
+ binaryPath: finalBinaryPath,
20755
+ version: manifest.version
20756
+ };
20757
+ } catch (err) {
20758
+ return {
20759
+ status: "failed",
20760
+ binaryPath: finalBinaryPath,
20761
+ error: `Failed to install companion: ${formatError(err)}`
20762
+ };
20763
+ } finally {
20764
+ if (tempDir) {
20765
+ try {
20766
+ rmSync2(tempDir, { recursive: true, force: true });
20767
+ } catch {}
20768
+ }
20769
+ }
20770
+ }
20771
+ function readInstallMetadata(binaryPath) {
20772
+ try {
20773
+ const parsed = JSON.parse(readFileSync3(metadataPath(binaryPath), "utf8"));
20774
+ if (parsed?.version && parsed.tag && parsed.target) {
20775
+ return parsed;
20776
+ }
20777
+ } catch {}
20778
+ return null;
20779
+ }
20780
+ function writeInstallMetadata(binaryPath, metadata) {
20781
+ writeFileSync2(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
20782
+ }
20783
+ function metadataPath(binaryPath) {
20784
+ return `${binaryPath}.json`;
20785
+ }
20786
+ async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
20787
+ const lock = `${binaryPath}.lock`;
20788
+ const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
20789
+ const staleAfterMs = staleMs ?? STALE_LOCK_MS;
20790
+ mkdirSync3(path4.dirname(binaryPath), { recursive: true });
20791
+ while (Date.now() <= deadline) {
20792
+ try {
20793
+ mkdirSync3(lock);
20794
+ try {
20795
+ return await run();
20796
+ } finally {
20797
+ try {
20798
+ rmSync2(lock, { recursive: true, force: true });
20799
+ } catch {}
20800
+ }
20801
+ } catch (err) {
20802
+ const code = err.code;
20803
+ if (code !== "EEXIST")
20804
+ throw err;
20805
+ try {
20806
+ const ageMs = Date.now() - statSync2(lock).mtimeMs;
20807
+ if (ageMs > staleAfterMs) {
20808
+ rmSync2(lock, { recursive: true, force: true });
20809
+ log("[companion] removed stale install lock", lock);
20810
+ continue;
20811
+ }
20812
+ } catch (statErr) {
20813
+ const statCode = statErr.code;
20814
+ if (statCode !== "ENOENT")
20815
+ throw statErr;
20816
+ }
20817
+ await delay(25);
20818
+ }
20819
+ }
20820
+ log("[companion] install lock timed out", lock);
20821
+ return {
20822
+ status: "failed",
20823
+ binaryPath,
20824
+ error: "Timed out waiting for companion install lock"
20825
+ };
20826
+ }
20827
+ function companionArchiveName(version, target, isWindows = process.platform === "win32") {
20828
+ const ext = isWindows ? "zip" : "tar.gz";
20829
+ return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
20830
+ }
20831
+ function compareSemver(a, b) {
20832
+ const left = parseSemver(a);
20833
+ const right = parseSemver(b);
20834
+ if (!left || !right)
20835
+ return a.localeCompare(b);
20836
+ for (let i = 0;i < 3; i++) {
20837
+ const diff = left[i] - right[i];
20838
+ if (diff !== 0)
20839
+ return diff;
20840
+ }
20841
+ return 0;
20842
+ }
20843
+ function parseSemver(version) {
20844
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
20845
+ if (!match)
20846
+ return null;
20847
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
20848
+ }
20849
+ function formatError(err) {
20850
+ return err instanceof Error ? err.message : String(err);
20851
+ }
20852
+
20264
20853
  // src/config/runtime-preset.ts
20265
20854
  var activeRuntimePreset = null;
20266
20855
  function setActiveRuntimePreset(name) {
@@ -20597,7 +21186,7 @@ function ensureApplyPatchError(error, context) {
20597
21186
 
20598
21187
  // src/hooks/apply-patch/execution-context.ts
20599
21188
  import * as fs3 from "node:fs/promises";
20600
- import path4 from "node:path";
21189
+ import path5 from "node:path";
20601
21190
 
20602
21191
  // src/hooks/apply-patch/codec.ts
20603
21192
  function normalizeLineEndings(text) {
@@ -21466,7 +22055,7 @@ function isMissingPathError(error) {
21466
22055
  }
21467
22056
  async function real(target) {
21468
22057
  const parts = [];
21469
- let current = path4.resolve(target);
22058
+ let current = path5.resolve(target);
21470
22059
  while (true) {
21471
22060
  const exact = await fs3.realpath(current).catch((error) => {
21472
22061
  if (isMissingPathError(error)) {
@@ -21475,19 +22064,19 @@ async function real(target) {
21475
22064
  throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
21476
22065
  });
21477
22066
  if (exact) {
21478
- return parts.length === 0 ? exact : path4.join(exact, ...parts.reverse());
22067
+ return parts.length === 0 ? exact : path5.join(exact, ...parts.reverse());
21479
22068
  }
21480
- const parent = path4.dirname(current);
22069
+ const parent = path5.dirname(current);
21481
22070
  if (parent === current) {
21482
- return parts.length === 0 ? current : path4.join(current, ...parts.reverse());
22071
+ return parts.length === 0 ? current : path5.join(current, ...parts.reverse());
21483
22072
  }
21484
- parts.push(path4.basename(current));
22073
+ parts.push(path5.basename(current));
21485
22074
  current = parent;
21486
22075
  }
21487
22076
  }
21488
22077
  function inside(root, target) {
21489
- const rel = path4.relative(root, target);
21490
- return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
22078
+ const rel = path5.relative(root, target);
22079
+ return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
21491
22080
  }
21492
22081
  function createPathGuardContext(root, worktree) {
21493
22082
  return {
@@ -21497,7 +22086,7 @@ function createPathGuardContext(root, worktree) {
21497
22086
  };
21498
22087
  }
21499
22088
  async function realCached(ctx, target) {
21500
- const resolvedTarget = path4.resolve(target);
22089
+ const resolvedTarget = path5.resolve(target);
21501
22090
  let pending = ctx.realCache.get(resolvedTarget);
21502
22091
  if (!pending) {
21503
22092
  pending = real(resolvedTarget);
@@ -21548,22 +22137,22 @@ async function assertRegularFile(ctx, filePath, verb) {
21548
22137
  function collectPatchTargets(root, hunks) {
21549
22138
  const targets = new Set;
21550
22139
  for (const hunk of hunks) {
21551
- targets.add(path4.resolve(root, hunk.path));
22140
+ targets.add(path5.resolve(root, hunk.path));
21552
22141
  if (hunk.type === "update" && hunk.move_path) {
21553
- targets.add(path4.resolve(root, hunk.move_path));
22142
+ targets.add(path5.resolve(root, hunk.move_path));
21554
22143
  }
21555
22144
  }
21556
22145
  return [...targets];
21557
22146
  }
21558
22147
  function toRelativePatchPath(root, target) {
21559
- const relative = path4.relative(root, target);
22148
+ const relative = path5.relative(root, target);
21560
22149
  return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
21561
22150
  }
21562
22151
  function normalizePatchPath(root, value) {
21563
- return path4.isAbsolute(value) ? toRelativePatchPath(root, path4.resolve(value)) : value;
22152
+ return path5.isAbsolute(value) ? toRelativePatchPath(root, path5.resolve(value)) : value;
21564
22153
  }
21565
22154
  function normalizePatchPaths(root, hunks) {
21566
- const resolvedRoot = path4.resolve(root);
22155
+ const resolvedRoot = path5.resolve(root);
21567
22156
  const normalized = [];
21568
22157
  let changed = false;
21569
22158
  for (const hunk of hunks) {
@@ -21687,7 +22276,7 @@ function stageAddedText(contents) {
21687
22276
  `;
21688
22277
  }
21689
22278
  // src/hooks/apply-patch/rewrite.ts
21690
- import path5 from "node:path";
22279
+ import path6 from "node:path";
21691
22280
  function normalizeTextLineEndings(text) {
21692
22281
  return text.replace(/\r\n/g, `
21693
22282
  `).replace(/\r/g, `
@@ -21844,7 +22433,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
21844
22433
  const dependencyGroups = new Map;
21845
22434
  for (const hunk of hunks) {
21846
22435
  if (hunk.type === "add") {
21847
- const filePath2 = path5.resolve(root, hunk.path);
22436
+ const filePath2 = path6.resolve(root, hunk.path);
21848
22437
  await assertPreparedPathMissing(filePath2, "add");
21849
22438
  rewritten.push(hunk);
21850
22439
  clearDependencyGroup(filePath2);
@@ -21866,20 +22455,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
21866
22455
  continue;
21867
22456
  }
21868
22457
  if (hunk.type === "delete") {
21869
- const filePath2 = path5.resolve(root, hunk.path);
22458
+ const filePath2 = path6.resolve(root, hunk.path);
21870
22459
  await getPreparedFileState(filePath2, "delete");
21871
22460
  clearDependencyGroup(filePath2);
21872
22461
  rewritten.push(hunk);
21873
22462
  staged.set(filePath2, { exists: false, derived: true });
21874
22463
  continue;
21875
22464
  }
21876
- const filePath = path5.resolve(root, hunk.path);
22465
+ const filePath = path6.resolve(root, hunk.path);
21877
22466
  const currentDependency = dependencyGroups.get(filePath);
21878
22467
  const current = await getPreparedFileState(filePath, "update");
21879
22468
  if (!current.exists) {
21880
22469
  throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
21881
22470
  }
21882
- const movePath = hunk.move_path ? path5.resolve(root, hunk.move_path) : undefined;
22471
+ const movePath = hunk.move_path ? path6.resolve(root, hunk.move_path) : undefined;
21883
22472
  if (movePath && movePath !== filePath) {
21884
22473
  await assertPreparedPathMissing(movePath, "move");
21885
22474
  }
@@ -22045,31 +22634,34 @@ function createApplyPatchHook(ctx) {
22045
22634
  };
22046
22635
  }
22047
22636
  // src/hooks/auto-update-checker/index.ts
22048
- import * as path10 from "node:path";
22637
+ import * as path11 from "node:path";
22638
+ init_compat();
22049
22639
 
22050
22640
  // src/hooks/auto-update-checker/cache.ts
22051
22641
  import * as fs5 from "node:fs";
22052
- import * as path8 from "node:path";
22642
+ import * as path9 from "node:path";
22643
+ // src/cli/system.ts
22644
+ init_compat();
22053
22645
  // src/hooks/auto-update-checker/checker.ts
22054
22646
  import * as fs4 from "node:fs";
22055
- import * as path7 from "node:path";
22647
+ import * as path8 from "node:path";
22056
22648
  import { fileURLToPath } from "node:url";
22057
22649
 
22058
22650
  // src/hooks/auto-update-checker/constants.ts
22059
22651
  import * as os3 from "node:os";
22060
- import * as path6 from "node:path";
22652
+ import * as path7 from "node:path";
22061
22653
  var PACKAGE_NAME = "oh-my-opencode-slim";
22062
22654
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
22063
22655
  var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
22064
22656
  var NPM_FETCH_TIMEOUT = 5000;
22065
22657
  function getCacheDir() {
22066
22658
  if (process.platform === "win32") {
22067
- return path6.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
22659
+ return path7.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
22068
22660
  }
22069
- return path6.join(os3.homedir(), ".cache", "opencode");
22661
+ return path7.join(os3.homedir(), ".cache", "opencode");
22070
22662
  }
22071
22663
  var CACHE_DIR = getCacheDir();
22072
- var INSTALLED_PACKAGE_JSON = path6.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
22664
+ var INSTALLED_PACKAGE_JSON = path7.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
22073
22665
  var configPaths = getOpenCodeConfigPaths();
22074
22666
  var USER_OPENCODE_CONFIG = configPaths[0];
22075
22667
  var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
@@ -22181,8 +22773,8 @@ function extractChannel(version) {
22181
22773
  }
22182
22774
  function getConfigPaths(directory) {
22183
22775
  return [
22184
- path7.join(directory, ".opencode", "opencode.json"),
22185
- path7.join(directory, ".opencode", "opencode.jsonc"),
22776
+ path8.join(directory, ".opencode", "opencode.json"),
22777
+ path8.join(directory, ".opencode", "opencode.jsonc"),
22186
22778
  USER_OPENCODE_CONFIG,
22187
22779
  USER_OPENCODE_CONFIG_JSONC
22188
22780
  ];
@@ -22211,9 +22803,9 @@ function getLocalDevPath(directory) {
22211
22803
  function findPackageJsonUp(startPath) {
22212
22804
  try {
22213
22805
  const stat2 = fs4.statSync(startPath);
22214
- let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
22806
+ let dir = stat2.isDirectory() ? startPath : path8.dirname(startPath);
22215
22807
  for (let i = 0;i < 10; i++) {
22216
- const pkgPath = path7.join(dir, "package.json");
22808
+ const pkgPath = path8.join(dir, "package.json");
22217
22809
  if (fs4.existsSync(pkgPath)) {
22218
22810
  try {
22219
22811
  const content = fs4.readFileSync(pkgPath, "utf-8");
@@ -22222,7 +22814,7 @@ function findPackageJsonUp(startPath) {
22222
22814
  return pkgPath;
22223
22815
  } catch {}
22224
22816
  }
22225
- const parent = path7.dirname(dir);
22817
+ const parent = path8.dirname(dir);
22226
22818
  if (parent === dir)
22227
22819
  break;
22228
22820
  dir = parent;
@@ -22247,7 +22839,7 @@ function getLocalDevVersion(directory) {
22247
22839
  }
22248
22840
  function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
22249
22841
  try {
22250
- const currentDir = path7.dirname(fileURLToPath(currentModuleUrl));
22842
+ const currentDir = path8.dirname(fileURLToPath(currentModuleUrl));
22251
22843
  return findPackageJsonUp(currentDir);
22252
22844
  } catch (err) {
22253
22845
  log("[auto-update-checker] Failed to resolve runtime package path:", err);
@@ -22406,7 +22998,7 @@ function getBlockingMajorVersion(current, candidates) {
22406
22998
 
22407
22999
  // src/hooks/auto-update-checker/cache.ts
22408
23000
  function removeFromBunLock(installDir, packageName) {
22409
- const lockPath = path8.join(installDir, "bun.lock");
23001
+ const lockPath = path9.join(installDir, "bun.lock");
22410
23002
  if (!fs5.existsSync(lockPath))
22411
23003
  return false;
22412
23004
  try {
@@ -22457,7 +23049,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
22457
23049
  }
22458
23050
  }
22459
23051
  function removeInstalledPackage(installDir, packageName) {
22460
- const pkgDir = path8.join(installDir, "node_modules", packageName);
23052
+ const pkgDir = path9.join(installDir, "node_modules", packageName);
22461
23053
  if (!fs5.existsSync(pkgDir))
22462
23054
  return false;
22463
23055
  fs5.rmSync(pkgDir, { recursive: true, force: true });
@@ -22466,18 +23058,18 @@ function removeInstalledPackage(installDir, packageName) {
22466
23058
  }
22467
23059
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
22468
23060
  if (runtimePackageJsonPath) {
22469
- const packageDir = path8.dirname(runtimePackageJsonPath);
22470
- const nodeModulesDir = path8.dirname(packageDir);
22471
- if (path8.basename(packageDir) === PACKAGE_NAME && path8.basename(nodeModulesDir) === "node_modules") {
22472
- const installDir = path8.dirname(nodeModulesDir);
22473
- const packageJsonPath = path8.join(installDir, "package.json");
23061
+ const packageDir = path9.dirname(runtimePackageJsonPath);
23062
+ const nodeModulesDir = path9.dirname(packageDir);
23063
+ if (path9.basename(packageDir) === PACKAGE_NAME && path9.basename(nodeModulesDir) === "node_modules") {
23064
+ const installDir = path9.dirname(nodeModulesDir);
23065
+ const packageJsonPath = path9.join(installDir, "package.json");
22474
23066
  if (fs5.existsSync(packageJsonPath)) {
22475
23067
  return { installDir, packageJsonPath };
22476
23068
  }
22477
23069
  }
22478
23070
  return null;
22479
23071
  }
22480
- const legacyPackageJsonPath = path8.join(CACHE_DIR, "package.json");
23072
+ const legacyPackageJsonPath = path9.join(CACHE_DIR, "package.json");
22481
23073
  if (fs5.existsSync(legacyPackageJsonPath)) {
22482
23074
  return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
22483
23075
  }
@@ -22508,40 +23100,40 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
22508
23100
 
22509
23101
  // src/hooks/auto-update-checker/skill-sync.ts
22510
23102
  import {
22511
- copyFileSync,
22512
- existsSync as existsSync5,
23103
+ copyFileSync as copyFileSync2,
23104
+ existsSync as existsSync6,
22513
23105
  lstatSync,
22514
- mkdirSync as mkdirSync3,
22515
- mkdtempSync,
23106
+ mkdirSync as mkdirSync4,
23107
+ mkdtempSync as mkdtempSync2,
22516
23108
  readdirSync as readdirSync2,
22517
- renameSync as renameSync2,
22518
- rmSync as rmSync2
23109
+ renameSync as renameSync3,
23110
+ rmSync as rmSync4
22519
23111
  } from "node:fs";
22520
- import * as path9 from "node:path";
23112
+ import * as path10 from "node:path";
22521
23113
  function copyDirRecursive(src, dest) {
22522
23114
  const stat2 = lstatSync(src);
22523
23115
  if (stat2.isSymbolicLink()) {
22524
23116
  return;
22525
23117
  }
22526
23118
  if (stat2.isDirectory()) {
22527
- mkdirSync3(dest, { recursive: true });
23119
+ mkdirSync4(dest, { recursive: true });
22528
23120
  const entries = readdirSync2(src);
22529
23121
  for (const entry of entries) {
22530
- copyDirRecursive(path9.join(src, entry), path9.join(dest, entry));
23122
+ copyDirRecursive(path10.join(src, entry), path10.join(dest, entry));
22531
23123
  }
22532
23124
  } else if (stat2.isFile()) {
22533
- const destDir = path9.dirname(dest);
22534
- if (!existsSync5(destDir)) {
22535
- mkdirSync3(destDir, { recursive: true });
23125
+ const destDir = path10.dirname(dest);
23126
+ if (!existsSync6(destDir)) {
23127
+ mkdirSync4(destDir, { recursive: true });
22536
23128
  }
22537
- copyFileSync(src, dest);
23129
+ copyFileSync2(src, dest);
22538
23130
  }
22539
23131
  }
22540
23132
  function syncBundledSkillsFromPackage(packageRoot) {
22541
23133
  const installed = [];
22542
23134
  const skippedExisting = [];
22543
23135
  const failed = [];
22544
- const sourceSkillsDir = path9.join(packageRoot, "src", "skills");
23136
+ const sourceSkillsDir = path10.join(packageRoot, "src", "skills");
22545
23137
  try {
22546
23138
  const stat2 = lstatSync(sourceSkillsDir);
22547
23139
  if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
@@ -22552,10 +23144,10 @@ function syncBundledSkillsFromPackage(packageRoot) {
22552
23144
  log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
22553
23145
  return { installed, skippedExisting, failed };
22554
23146
  }
22555
- const destSkillsDir = path9.join(getConfigDir(), "skills");
23147
+ const destSkillsDir = path10.join(getConfigDir(), "skills");
22556
23148
  try {
22557
- if (!existsSync5(destSkillsDir)) {
22558
- mkdirSync3(destSkillsDir, { recursive: true });
23149
+ if (!existsSync6(destSkillsDir)) {
23150
+ mkdirSync4(destSkillsDir, { recursive: true });
22559
23151
  }
22560
23152
  } catch (err) {
22561
23153
  log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
@@ -22568,7 +23160,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22568
23160
  return { installed, skippedExisting, failed };
22569
23161
  }
22570
23162
  for (const entry of entries) {
22571
- const entryPath = path9.join(sourceSkillsDir, entry);
23163
+ const entryPath = path10.join(sourceSkillsDir, entry);
22572
23164
  try {
22573
23165
  if (entry.startsWith(".")) {
22574
23166
  continue;
@@ -22577,7 +23169,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22577
23169
  if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
22578
23170
  continue;
22579
23171
  }
22580
- const skillMdPath = path9.join(entryPath, "SKILL.md");
23172
+ const skillMdPath = path10.join(entryPath, "SKILL.md");
22581
23173
  try {
22582
23174
  const skillMdStat = lstatSync(skillMdPath);
22583
23175
  if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
@@ -22586,7 +23178,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22586
23178
  } catch {
22587
23179
  continue;
22588
23180
  }
22589
- const destPath = path9.join(destSkillsDir, entry);
23181
+ const destPath = path10.join(destSkillsDir, entry);
22590
23182
  let destExists = false;
22591
23183
  try {
22592
23184
  lstatSync(destPath);
@@ -22597,7 +23189,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22597
23189
  skippedExisting.push(entry);
22598
23190
  continue;
22599
23191
  }
22600
- const stagingDir = mkdtempSync(path9.join(destSkillsDir, `.sync-staging-${entry}-`));
23192
+ const stagingDir = mkdtempSync2(path10.join(destSkillsDir, `.sync-staging-${entry}-`));
22601
23193
  try {
22602
23194
  copyDirRecursive(entryPath, stagingDir);
22603
23195
  let destExistsLate = false;
@@ -22609,7 +23201,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22609
23201
  log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
22610
23202
  skippedExisting.push(entry);
22611
23203
  } else {
22612
- renameSync2(stagingDir, destPath);
23204
+ renameSync3(stagingDir, destPath);
22613
23205
  installed.push(entry);
22614
23206
  log(`[skill-sync] Successfully synced skill: ${entry}`);
22615
23207
  }
@@ -22618,8 +23210,8 @@ function syncBundledSkillsFromPackage(packageRoot) {
22618
23210
  failed.push(entry);
22619
23211
  } finally {
22620
23212
  try {
22621
- if (existsSync5(stagingDir)) {
22622
- rmSync2(stagingDir, { recursive: true, force: true });
23213
+ if (existsSync6(stagingDir)) {
23214
+ rmSync4(stagingDir, { recursive: true, force: true });
22623
23215
  }
22624
23216
  } catch (err) {
22625
23217
  log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
@@ -22635,7 +23227,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22635
23227
 
22636
23228
  // src/hooks/auto-update-checker/index.ts
22637
23229
  function createAutoUpdateCheckerHook(ctx, options = {}) {
22638
- const { autoUpdate = true } = options;
23230
+ const { autoUpdate = true, companion } = options;
22639
23231
  let hasChecked = false;
22640
23232
  return {
22641
23233
  event: ({ event }) => {
@@ -22653,14 +23245,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
22653
23245
  log("[auto-update-checker] Local development mode");
22654
23246
  return;
22655
23247
  }
22656
- runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
23248
+ runBackgroundUpdateCheck(ctx, autoUpdate, companion).catch((err) => {
22657
23249
  log("[auto-update-checker] Background update check failed:", err);
22658
23250
  });
22659
23251
  }, 0);
22660
23252
  }
22661
23253
  };
22662
23254
  }
22663
- async function runBackgroundUpdateCheck(ctx, autoUpdate) {
23255
+ async function runBackgroundUpdateCheck(ctx, autoUpdate, companion) {
22664
23256
  const pluginInfo = findPluginEntry(ctx.directory);
22665
23257
  if (!pluginInfo) {
22666
23258
  log("[auto-update-checker] Plugin not found in config");
@@ -22716,8 +23308,10 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
22716
23308
  const installSuccess = await runBunInstallSafe(installDir);
22717
23309
  if (installSuccess) {
22718
23310
  let installedSkills = [];
23311
+ let companionUpdated = false;
23312
+ let companionWillRetry = false;
23313
+ const packageRoot = path11.join(installDir, "node_modules", PACKAGE_NAME);
22719
23314
  try {
22720
- const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
22721
23315
  const syncResult = syncBundledSkillsFromPackage(packageRoot);
22722
23316
  installedSkills = syncResult.installed;
22723
23317
  if (syncResult.failed.length > 0) {
@@ -22729,14 +23323,38 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
22729
23323
  } catch (err) {
22730
23324
  log("[auto-update-checker] Skill sync failed silently:", err);
22731
23325
  }
22732
- let message = `v${currentVersion} → v${latestVersion}
22733
- Restart OpenCode to apply.`;
23326
+ if (companion?.enabled === true) {
23327
+ try {
23328
+ const manifest = loadCompanionManifestFromPackageRoot(packageRoot);
23329
+ const companionResult = await ensureCompanionVersion({
23330
+ config: companion,
23331
+ manifest: manifest ?? undefined
23332
+ });
23333
+ if (companionResult.status === "installed") {
23334
+ companionUpdated = true;
23335
+ } else if (companionResult.status === "failed") {
23336
+ companionWillRetry = true;
23337
+ log("[auto-update-checker] Companion update failed; will retry on restart:", companionResult.error);
23338
+ } else if (companionResult.status === "skipped") {
23339
+ log("[auto-update-checker] Companion update skipped:", companionResult.reason);
23340
+ }
23341
+ } catch (err) {
23342
+ companionWillRetry = true;
23343
+ log("[auto-update-checker] Companion update failed silently; will retry on restart:", err);
23344
+ }
23345
+ }
23346
+ const messageLines = [`v${currentVersion} → v${latestVersion}`];
22734
23347
  if (installedSkills.length > 0) {
22735
- message = `v${currentVersion} → v${latestVersion}
22736
- Added bundled skills: ${installedSkills.join(", ")}
22737
- Restart OpenCode to apply.`;
23348
+ messageLines.push(`Added bundled skills: ${installedSkills.join(", ")}`);
23349
+ }
23350
+ if (companionUpdated) {
23351
+ messageLines.push("Companion updated.");
23352
+ } else if (companionWillRetry) {
23353
+ messageLines.push("Companion update will retry on restart.");
22738
23354
  }
22739
- showToast(ctx, "OMO-Slim Updated!", message, "success", 8000);
23355
+ messageLines.push("Restart OpenCode to apply.");
23356
+ showToast(ctx, "OMO-Slim Updated!", messageLines.join(`
23357
+ `), "success", 8000);
22740
23358
  log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
22741
23359
  } else {
22742
23360
  showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
@@ -23119,7 +23737,7 @@ class BackgroundJobBoard {
23119
23737
  existing.set(file.path, { ...file });
23120
23738
  }
23121
23739
  }
23122
- const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, this.readContextMaxFiles + 1);
23740
+ const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lineCount - a.lineCount || b.lastReadAt - a.lastReadAt || a.path.localeCompare(b.path)).slice(0, this.readContextMaxFiles + 1);
23123
23741
  this.jobs.set(taskID, { ...job, contextFiles });
23124
23742
  }
23125
23743
  list(parentSessionID) {
@@ -23264,86 +23882,10 @@ function hasInternalInitiatorMarker(part) {
23264
23882
  }
23265
23883
  return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
23266
23884
  }
23267
- // src/utils/zip-extractor.ts
23268
- import { spawnSync } from "node:child_process";
23269
- import { release } from "node:os";
23270
- var WINDOWS_BUILD_WITH_TAR = 17134;
23271
- function getWindowsBuildNumber() {
23272
- if (process.platform !== "win32")
23273
- return null;
23274
- const parts = release().split(".");
23275
- if (parts.length >= 3) {
23276
- const build = parseInt(parts[2], 10);
23277
- if (!Number.isNaN(build))
23278
- return build;
23279
- }
23280
- return null;
23281
- }
23282
- function isPwshAvailable() {
23283
- if (process.platform !== "win32")
23284
- return false;
23285
- const result = spawnSync("where", ["pwsh"], {
23286
- stdio: ["ignore", "pipe", "pipe"]
23287
- });
23288
- return result.status === 0;
23289
- }
23290
- function escapePowerShellPath(path11) {
23291
- return path11.replace(/'/g, "''");
23292
- }
23293
- function getWindowsZipExtractor() {
23294
- const buildNumber = getWindowsBuildNumber();
23295
- if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
23296
- return "tar";
23297
- }
23298
- if (isPwshAvailable()) {
23299
- return "pwsh";
23300
- }
23301
- return "powershell";
23302
- }
23303
- async function extractZip(archivePath, destDir) {
23304
- let proc;
23305
- if (process.platform === "win32") {
23306
- const extractor = getWindowsZipExtractor();
23307
- switch (extractor) {
23308
- case "tar":
23309
- proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
23310
- stdout: "ignore",
23311
- stderr: "pipe"
23312
- });
23313
- break;
23314
- case "pwsh":
23315
- proc = crossSpawn([
23316
- "pwsh",
23317
- "-Command",
23318
- `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
23319
- ], {
23320
- stdout: "ignore",
23321
- stderr: "pipe"
23322
- });
23323
- break;
23324
- default:
23325
- proc = crossSpawn([
23326
- "powershell",
23327
- "-Command",
23328
- `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
23329
- ], {
23330
- stdout: "ignore",
23331
- stderr: "pipe"
23332
- });
23333
- break;
23334
- }
23335
- } else {
23336
- proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
23337
- stdout: "ignore",
23338
- stderr: "pipe"
23339
- });
23340
- }
23341
- const exitCode = await proc.exited;
23342
- if (exitCode !== 0) {
23343
- const stderr = await proc.stderr();
23344
- throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
23345
- }
23346
- }
23885
+
23886
+ // src/utils/index.ts
23887
+ init_zip_extractor();
23888
+
23347
23889
  // src/hooks/chat-headers.ts
23348
23890
  var INTERNAL_MARKER_CACHE_LIMIT = 1000;
23349
23891
  var internalMarkerCache = new Map;
@@ -23848,17 +24390,17 @@ class ForegroundFallbackManager {
23848
24390
  }
23849
24391
  }
23850
24392
  // src/hooks/image-hook.ts
23851
- import { createHash } from "node:crypto";
24393
+ import { createHash as createHash2 } from "node:crypto";
23852
24394
  import {
23853
- existsSync as existsSync6,
23854
- mkdirSync as mkdirSync4,
24395
+ existsSync as existsSync7,
24396
+ mkdirSync as mkdirSync5,
23855
24397
  readdirSync as readdirSync3,
23856
24398
  rmdirSync,
23857
- statSync as statSync3,
24399
+ statSync as statSync4,
23858
24400
  unlinkSync as unlinkSync2,
23859
- writeFileSync as writeFileSync4
24401
+ writeFileSync as writeFileSync5
23860
24402
  } from "node:fs";
23861
- import { basename as basename2, extname, join as join10 } from "node:path";
24403
+ import { basename as basename2, extname, join as join11 } from "node:path";
23862
24404
  var lastCleanupByDir = new Map;
23863
24405
  var CLEANUP_INTERVAL = 10 * 60 * 1000;
23864
24406
  function isImagePart(p) {
@@ -23906,12 +24448,12 @@ function cleanupAllSessions(saveDir) {
23906
24448
  const dirsToScan = [];
23907
24449
  try {
23908
24450
  for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
23909
- const fp = join10(saveDir, entry.name);
24451
+ const fp = join11(saveDir, entry.name);
23910
24452
  if (entry.isDirectory()) {
23911
24453
  dirsToScan.push(fp);
23912
24454
  } else {
23913
24455
  try {
23914
- if (now - statSync3(fp).mtimeMs > maxAge)
24456
+ if (now - statSync4(fp).mtimeMs > maxAge)
23915
24457
  unlinkSync2(fp);
23916
24458
  } catch {}
23917
24459
  }
@@ -23923,9 +24465,9 @@ function cleanupAllSessions(saveDir) {
23923
24465
  let allRemoved = true;
23924
24466
  for (const f of readdirSync3(dir)) {
23925
24467
  isEmpty = false;
23926
- const fp = join10(dir, f);
24468
+ const fp = join11(dir, f);
23927
24469
  try {
23928
- if (now - statSync3(fp).mtimeMs > maxAge) {
24470
+ if (now - statSync4(fp).mtimeMs > maxAge) {
23929
24471
  unlinkSync2(fp);
23930
24472
  } else {
23931
24473
  allRemoved = false;
@@ -23945,20 +24487,20 @@ function cleanupAllSessions(saveDir) {
23945
24487
  function writeUniqueFile(dir, name, data, log2) {
23946
24488
  const ext = extname(name);
23947
24489
  const base = basename2(name, ext) || name;
23948
- let candidate = join10(dir, name);
23949
- if (existsSync6(candidate)) {
24490
+ let candidate = join11(dir, name);
24491
+ if (existsSync7(candidate)) {
23950
24492
  return candidate;
23951
24493
  }
23952
24494
  let counter = 0;
23953
24495
  const MAX_ATTEMPTS = 1000;
23954
24496
  for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
23955
24497
  try {
23956
- writeFileSync4(candidate, data, { flag: "wx" });
24498
+ writeFileSync5(candidate, data, { flag: "wx" });
23957
24499
  return candidate;
23958
24500
  } catch (e) {
23959
24501
  if (e instanceof Error && e.code === "EEXIST") {
23960
24502
  counter += 1;
23961
- candidate = join10(dir, `${base}-${counter}${ext}`);
24503
+ candidate = join11(dir, `${base}-${counter}${ext}`);
23962
24504
  continue;
23963
24505
  }
23964
24506
  log2(`[image-hook] failed to save image: ${e}`);
@@ -23982,17 +24524,17 @@ function processImageAttachments(args) {
23982
24524
  messagesWithImages.push({ msg, imageParts });
23983
24525
  }
23984
24526
  }
23985
- const saveDir = join10(workDir, ".opencode", "images");
24527
+ const saveDir = join11(workDir, ".opencode", "images");
23986
24528
  if (messagesWithImages.length === 0) {
23987
- if (existsSync6(saveDir))
24529
+ if (existsSync7(saveDir))
23988
24530
  cleanupAllSessions(saveDir);
23989
24531
  return;
23990
24532
  }
23991
- const gitignorePath = join10(workDir, ".opencode", ".gitignore");
24533
+ const gitignorePath = join11(workDir, ".opencode", ".gitignore");
23992
24534
  try {
23993
- mkdirSync4(saveDir, { recursive: true });
23994
- if (!existsSync6(gitignorePath))
23995
- writeFileSync4(gitignorePath, `*
24535
+ mkdirSync5(saveDir, { recursive: true });
24536
+ if (!existsSync7(gitignorePath))
24537
+ writeFileSync5(gitignorePath, `*
23996
24538
  `);
23997
24539
  } catch (e) {
23998
24540
  log2(`[image-hook] failed to create image directory: ${e}`);
@@ -24000,9 +24542,9 @@ function processImageAttachments(args) {
24000
24542
  cleanupAllSessions(saveDir);
24001
24543
  for (const { msg, imageParts } of messagesWithImages) {
24002
24544
  const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
24003
- const targetDir = sessionSubdir ? join10(saveDir, sessionSubdir) : saveDir;
24545
+ const targetDir = sessionSubdir ? join11(saveDir, sessionSubdir) : saveDir;
24004
24546
  try {
24005
- mkdirSync4(targetDir, { recursive: true });
24547
+ mkdirSync5(targetDir, { recursive: true });
24006
24548
  } catch (e) {
24007
24549
  log2(`[image-hook] failed to create target image directory: ${e}`);
24008
24550
  }
@@ -24013,7 +24555,7 @@ function processImageAttachments(args) {
24013
24555
  if (url) {
24014
24556
  const decoded = decodeDataUrl(url);
24015
24557
  if (decoded) {
24016
- const hash = createHash("sha1").update(decoded.data).digest("hex").slice(0, 8);
24558
+ const hash = createHash2("sha1").update(decoded.data).digest("hex").slice(0, 8);
24017
24559
  const sanitizedFilename = filename ? sanitizeFilename(filename) : undefined;
24018
24560
  const baseName = sanitizedFilename ? sanitizedFilename.replace(/\.[^.]+$/, "") || "image" : "image";
24019
24561
  const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
@@ -24204,7 +24746,7 @@ function createReflectCommandHook() {
24204
24746
  };
24205
24747
  }
24206
24748
  // src/hooks/task-session-manager/index.ts
24207
- import path11 from "node:path";
24749
+ import path12 from "node:path";
24208
24750
  var AGENT_NAME_SET = new Set([
24209
24751
  "orchestrator",
24210
24752
  "oracle",
@@ -24258,8 +24800,8 @@ function extractTaskSummary(output) {
24258
24800
  return summary?.trim() || undefined;
24259
24801
  }
24260
24802
  function normalizePath(root, file) {
24261
- const relative = path11.relative(root, file);
24262
- if (!relative || relative.startsWith("..") || path11.isAbsolute(relative)) {
24803
+ const relative = path12.relative(root, file);
24804
+ if (!relative || relative.startsWith("..") || path12.isAbsolute(relative)) {
24263
24805
  return file;
24264
24806
  }
24265
24807
  return relative;
@@ -24825,7 +25367,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
24825
25367
  `);
24826
25368
  }
24827
25369
  // src/interview/manager.ts
24828
- import path15 from "node:path";
25370
+ import path16 from "node:path";
24829
25371
 
24830
25372
  // src/interview/dashboard.ts
24831
25373
  import crypto from "node:crypto";
@@ -24835,27 +25377,27 @@ import {
24835
25377
  createServer
24836
25378
  } from "node:http";
24837
25379
  import os4 from "node:os";
24838
- import path13 from "node:path";
25380
+ import path14 from "node:path";
24839
25381
  import { URL as URL2 } from "node:url";
24840
25382
 
24841
25383
  // src/interview/document.ts
24842
25384
  import * as fsSync from "node:fs";
24843
25385
  import * as fs6 from "node:fs/promises";
24844
- import * as path12 from "node:path";
25386
+ import * as path13 from "node:path";
24845
25387
  var DEFAULT_OUTPUT_FOLDER = "interview";
24846
25388
  function normalizeOutputFolder(outputFolder) {
24847
25389
  const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
24848
25390
  return normalized || DEFAULT_OUTPUT_FOLDER;
24849
25391
  }
24850
25392
  function createInterviewDirectoryPath(directory, outputFolder) {
24851
- return path12.join(directory, normalizeOutputFolder(outputFolder));
25393
+ return path13.join(directory, normalizeOutputFolder(outputFolder));
24852
25394
  }
24853
25395
  function createInterviewFilePath(directory, outputFolder, idea) {
24854
25396
  const fileName = `${slugify(idea) || "interview"}.md`;
24855
- return path12.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
25397
+ return path13.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
24856
25398
  }
24857
25399
  function relativeInterviewPath(directory, filePath) {
24858
- return path12.relative(directory, filePath) || path12.basename(filePath);
25400
+ return path13.relative(directory, filePath) || path13.basename(filePath);
24859
25401
  }
24860
25402
  function resolveExistingInterviewPath(directory, outputFolder, value) {
24861
25403
  const trimmed = value.trim();
@@ -24864,22 +25406,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
24864
25406
  }
24865
25407
  const outputDir = createInterviewDirectoryPath(directory, outputFolder);
24866
25408
  const candidates = new Set;
24867
- const resolvedRoot = path12.resolve(directory);
24868
- if (path12.isAbsolute(trimmed)) {
25409
+ const resolvedRoot = path13.resolve(directory);
25410
+ if (path13.isAbsolute(trimmed)) {
24869
25411
  candidates.add(trimmed);
24870
25412
  } else {
24871
- candidates.add(path12.resolve(directory, trimmed));
24872
- candidates.add(path12.join(outputDir, trimmed));
25413
+ candidates.add(path13.resolve(directory, trimmed));
25414
+ candidates.add(path13.join(outputDir, trimmed));
24873
25415
  if (!trimmed.endsWith(".md")) {
24874
- candidates.add(path12.join(outputDir, `${trimmed}.md`));
25416
+ candidates.add(path13.join(outputDir, `${trimmed}.md`));
24875
25417
  }
24876
25418
  }
24877
25419
  for (const candidate of candidates) {
24878
- if (path12.extname(candidate) !== ".md") {
25420
+ if (path13.extname(candidate) !== ".md") {
24879
25421
  continue;
24880
25422
  }
24881
- const resolved = path12.resolve(candidate);
24882
- if (!resolved.startsWith(resolvedRoot + path12.sep) && resolved !== resolvedRoot) {
25423
+ const resolved = path13.resolve(candidate);
25424
+ if (!resolved.startsWith(resolvedRoot + path13.sep) && resolved !== resolvedRoot) {
24883
25425
  continue;
24884
25426
  }
24885
25427
  if (fsSync.existsSync(candidate)) {
@@ -24959,7 +25501,7 @@ function parseFrontmatter(content) {
24959
25501
  return result;
24960
25502
  }
24961
25503
  async function ensureInterviewFile(record) {
24962
- await fs6.mkdir(path12.dirname(record.markdownPath), { recursive: true });
25504
+ await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
24963
25505
  try {
24964
25506
  await fs6.access(record.markdownPath);
24965
25507
  } catch {
@@ -26629,12 +27171,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
26629
27171
 
26630
27172
  // src/interview/dashboard.ts
26631
27173
  function getAuthFilePath(port) {
26632
- const dataHome = process.env.XDG_DATA_HOME || path13.join(os4.homedir(), ".local", "share");
26633
- return path13.join(dataHome, "opencode", `.dashboard-${port}.json`);
27174
+ const dataHome = process.env.XDG_DATA_HOME || path14.join(os4.homedir(), ".local", "share");
27175
+ return path14.join(dataHome, "opencode", `.dashboard-${port}.json`);
26634
27176
  }
26635
27177
  function writeAuthFile(port, token) {
26636
27178
  const filePath = getAuthFilePath(port);
26637
- const dir = path13.dirname(filePath);
27179
+ const dir = path14.dirname(filePath);
26638
27180
  try {
26639
27181
  fsSync2.mkdirSync(dir, { recursive: true });
26640
27182
  } catch {}
@@ -26771,7 +27313,7 @@ function createDashboardServer(config) {
26771
27313
  const directories = getKnownDirectories();
26772
27314
  const items = [];
26773
27315
  for (const dir of directories) {
26774
- const interviewDir = path13.join(dir, config.outputFolder);
27316
+ const interviewDir = path14.join(dir, config.outputFolder);
26775
27317
  let entries;
26776
27318
  try {
26777
27319
  entries = await fs7.readdir(interviewDir);
@@ -26783,7 +27325,7 @@ function createDashboardServer(config) {
26783
27325
  continue;
26784
27326
  let content;
26785
27327
  try {
26786
- content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
27328
+ content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
26787
27329
  } catch {
26788
27330
  continue;
26789
27331
  }
@@ -26809,7 +27351,7 @@ function createDashboardServer(config) {
26809
27351
  const directories = getKnownDirectories();
26810
27352
  let rebuilt = 0;
26811
27353
  for (const dir of directories) {
26812
- const interviewDir = path13.join(dir, config.outputFolder);
27354
+ const interviewDir = path14.join(dir, config.outputFolder);
26813
27355
  let entries;
26814
27356
  try {
26815
27357
  entries = await fs7.readdir(interviewDir);
@@ -26821,7 +27363,7 @@ function createDashboardServer(config) {
26821
27363
  continue;
26822
27364
  let content;
26823
27365
  try {
26824
- content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
27366
+ content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
26825
27367
  } catch {
26826
27368
  continue;
26827
27369
  }
@@ -26847,7 +27389,7 @@ function createDashboardServer(config) {
26847
27389
  questions: [],
26848
27390
  pendingAnswers: null,
26849
27391
  lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
26850
- filePath: path13.join(interviewDir, entry),
27392
+ filePath: path14.join(interviewDir, entry),
26851
27393
  nudgeAction: null
26852
27394
  });
26853
27395
  if (!sessions.has(fm.sessionID)) {
@@ -27111,7 +27653,7 @@ function createDashboardServer(config) {
27111
27653
  const dirs = getKnownDirectories();
27112
27654
  for (const dir of dirs) {
27113
27655
  const slug = extractResumeSlug(interviewId);
27114
- const candidate = path13.join(dir, config.outputFolder, `${slug}.md`);
27656
+ const candidate = path14.join(dir, config.outputFolder, `${slug}.md`);
27115
27657
  try {
27116
27658
  document = await fs7.readFile(candidate, "utf8");
27117
27659
  markdownPath = candidate;
@@ -27639,7 +28181,7 @@ function createInterviewServer(deps) {
27639
28181
  // src/interview/service.ts
27640
28182
  import { spawn as spawn2 } from "node:child_process";
27641
28183
  import * as fs8 from "node:fs/promises";
27642
- import * as path14 from "node:path";
28184
+ import * as path15 from "node:path";
27643
28185
 
27644
28186
  // src/interview/types.ts
27645
28187
  import { z as z3 } from "zod";
@@ -27827,13 +28369,13 @@ function shouldAutoOpenBrowser(config, env) {
27827
28369
  return requested && !isAutomatedRuntime(env);
27828
28370
  }
27829
28371
  function openBrowser(url) {
27830
- const platform2 = process.platform;
28372
+ const platform3 = process.platform;
27831
28373
  let command;
27832
28374
  let args;
27833
- if (platform2 === "darwin") {
28375
+ if (platform3 === "darwin") {
27834
28376
  command = "open";
27835
28377
  args = [url];
27836
- } else if (platform2 === "win32") {
28378
+ } else if (platform3 === "win32") {
27837
28379
  command = "cmd";
27838
28380
  args = ["/c", "start", "", url];
27839
28381
  } else {
@@ -27906,12 +28448,12 @@ function createInterviewService(ctx, config, deps) {
27906
28448
  if (!newSlug) {
27907
28449
  return;
27908
28450
  }
27909
- const currentFileName = path14.basename(interview.markdownPath, ".md");
28451
+ const currentFileName = path15.basename(interview.markdownPath, ".md");
27910
28452
  if (currentFileName === newSlug) {
27911
28453
  return;
27912
28454
  }
27913
- const dir = path14.dirname(interview.markdownPath);
27914
- const newPath = path14.join(dir, `${newSlug}.md`);
28455
+ const dir = path15.dirname(interview.markdownPath);
28456
+ const newPath = path15.join(dir, `${newSlug}.md`);
27915
28457
  try {
27916
28458
  await fs8.access(newPath);
27917
28459
  return;
@@ -27987,9 +28529,9 @@ function createInterviewService(ctx, config, deps) {
27987
28529
  const messages = await loadMessages(sessionID);
27988
28530
  const title = extractTitle(document);
27989
28531
  const record = {
27990
- id: `${Date.now()}-${++idCounter}-${slugify(path14.basename(markdownPath, ".md")) || "interview"}`,
28532
+ id: `${Date.now()}-${++idCounter}-${slugify(path15.basename(markdownPath, ".md")) || "interview"}`,
27991
28533
  sessionID,
27992
- idea: title || path14.basename(markdownPath, ".md"),
28534
+ idea: title || path15.basename(markdownPath, ".md"),
27993
28535
  markdownPath,
27994
28536
  createdAt: nowIso(),
27995
28537
  status: "active",
@@ -28216,7 +28758,7 @@ function createInterviewService(ctx, config, deps) {
28216
28758
  return fileCache.items;
28217
28759
  }
28218
28760
  const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
28219
- const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path14.resolve(i.markdownPath)));
28761
+ const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path15.resolve(i.markdownPath)));
28220
28762
  let entries;
28221
28763
  try {
28222
28764
  entries = await fs8.readdir(outputDir);
@@ -28227,8 +28769,8 @@ function createInterviewService(ctx, config, deps) {
28227
28769
  for (const entry of entries) {
28228
28770
  if (!entry.endsWith(".md"))
28229
28771
  continue;
28230
- const fullPath = path14.join(outputDir, entry);
28231
- if (activePaths.has(path14.resolve(fullPath)))
28772
+ const fullPath = path15.join(outputDir, entry);
28773
+ if (activePaths.has(path15.resolve(fullPath)))
28232
28774
  continue;
28233
28775
  let content;
28234
28776
  try {
@@ -28327,7 +28869,7 @@ function createInterviewManager(ctx, config) {
28327
28869
  const outputFolder = interviewConfig?.outputFolder ?? "interview";
28328
28870
  if (!dashboardEnabled) {
28329
28871
  const service2 = createInterviewService(ctx, interviewConfig);
28330
- const resolvedOutputPath = path15.join(ctx.directory, outputFolder);
28872
+ const resolvedOutputPath = path16.join(ctx.directory, outputFolder);
28331
28873
  const server = createInterviewServer({
28332
28874
  getState: async (interviewId) => service2.getInterviewState(interviewId),
28333
28875
  listInterviewFiles: async () => service2.listInterviewFiles(),
@@ -28432,7 +28974,7 @@ function createInterviewManager(ctx, config) {
28432
28974
  listInterviews: () => service.listInterviews(),
28433
28975
  submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
28434
28976
  handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
28435
- outputFolder: path15.join(ctx.directory, outputFolder),
28977
+ outputFolder: path16.join(ctx.directory, outputFolder),
28436
28978
  port: 0
28437
28979
  });
28438
28980
  service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
@@ -28700,6 +29242,7 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
28700
29242
  }
28701
29243
 
28702
29244
  // src/multiplexer/tmux/index.ts
29245
+ init_compat();
28703
29246
  var TMUX_LAYOUT_DEBOUNCE_MS = 150;
28704
29247
 
28705
29248
  class TmuxMultiplexer {
@@ -28917,23 +29460,23 @@ class TmuxMultiplexer {
28917
29460
  return null;
28918
29461
  }
28919
29462
  const stdout = await proc.stdout();
28920
- const path16 = stdout.trim().split(`
29463
+ const path17 = stdout.trim().split(`
28921
29464
  `)[0];
28922
- if (!path16) {
29465
+ if (!path17) {
28923
29466
  log("[tmux] findBinary: no path in output");
28924
29467
  return null;
28925
29468
  }
28926
- const verifyProc = crossSpawn([path16, "-V"], {
29469
+ const verifyProc = crossSpawn([path17, "-V"], {
28927
29470
  stdout: "pipe",
28928
29471
  stderr: "pipe"
28929
29472
  });
28930
29473
  const verifyExit = await verifyProc.exited;
28931
29474
  if (verifyExit !== 0) {
28932
- log("[tmux] findBinary: tmux -V failed", { path: path16, verifyExit });
29475
+ log("[tmux] findBinary: tmux -V failed", { path: path17, verifyExit });
28933
29476
  return null;
28934
29477
  }
28935
- log("[tmux] findBinary: found", { path: path16 });
28936
- return path16;
29478
+ log("[tmux] findBinary: found", { path: path17 });
29479
+ return path17;
28937
29480
  } catch (err) {
28938
29481
  log("[tmux] findBinary: exception", { error: String(err) });
28939
29482
  return null;
@@ -28945,6 +29488,8 @@ function quoteShellArg(value) {
28945
29488
  }
28946
29489
 
28947
29490
  // src/multiplexer/zellij/index.ts
29491
+ init_compat();
29492
+
28948
29493
  class ZellijMultiplexer {
28949
29494
  paneMode;
28950
29495
  type = "zellij";
@@ -29884,22 +30429,359 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
29884
30429
  }
29885
30430
  return false;
29886
30431
  }
29887
- // src/tools/ast-grep/tools.ts
30432
+ // src/tools/acp-run.ts
30433
+ import { spawn as spawn3 } from "node:child_process";
30434
+ import { createInterface } from "node:readline";
29888
30435
  import { tool } from "@opencode-ai/plugin";
30436
+ var z4 = tool.schema;
30437
+
30438
+ class AcpClient {
30439
+ name;
30440
+ config;
30441
+ cwd;
30442
+ ask;
30443
+ child;
30444
+ next = 1;
30445
+ pending = new Map;
30446
+ chunks = [];
30447
+ errors = [];
30448
+ sessionId;
30449
+ lastUpdate = Date.now();
30450
+ authMethods = [];
30451
+ active = false;
30452
+ activeRequests = 0;
30453
+ constructor(name, config, cwd, ask) {
30454
+ this.name = name;
30455
+ this.config = config;
30456
+ this.cwd = cwd;
30457
+ this.ask = ask;
30458
+ this.child = spawn3(config.command, config.args, {
30459
+ cwd,
30460
+ env: { ...process.env, ...config.env },
30461
+ stdio: "pipe"
30462
+ });
30463
+ this.child.stderr.on("data", (chunk) => {
30464
+ this.errors.push(String(chunk));
30465
+ });
30466
+ this.child.stdin.on("error", (error) => {
30467
+ this.errors.push(String(error));
30468
+ this.rejectPending(error);
30469
+ });
30470
+ this.child.on("error", (error) => {
30471
+ this.rejectPending(error);
30472
+ });
30473
+ this.child.on("exit", (code, signal) => {
30474
+ if (this.pending.size === 0)
30475
+ return;
30476
+ this.rejectPending(new Error(`ACP agent '${name}' exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"})`));
30477
+ });
30478
+ createInterface({ input: this.child.stdout }).on("line", (line) => {
30479
+ this.receive(line).catch((error) => {
30480
+ this.errors.push(String(error));
30481
+ });
30482
+ });
30483
+ }
30484
+ async run(prompt) {
30485
+ const init = await this.request("initialize", {
30486
+ protocolVersion: 1,
30487
+ clientCapabilities: {},
30488
+ clientInfo: {
30489
+ name: "oh-my-opencode-slim",
30490
+ title: "oh-my-opencode-slim ACP bridge"
30491
+ }
30492
+ });
30493
+ this.authMethods = readAuthMethods(init);
30494
+ const created = await this.newSession();
30495
+ const sessionId = readSessionId(created);
30496
+ this.sessionId = sessionId;
30497
+ this.active = true;
30498
+ await this.request("session/prompt", {
30499
+ sessionId,
30500
+ prompt: [{ type: "text", text: prompt }]
30501
+ });
30502
+ await this.drain();
30503
+ this.active = false;
30504
+ return this.output();
30505
+ }
30506
+ async newSession() {
30507
+ try {
30508
+ return await this.request("session/new", {
30509
+ cwd: this.cwd,
30510
+ mcpServers: []
30511
+ });
30512
+ } catch (error) {
30513
+ if (!isAuthError(error) || this.authMethods.length === 0)
30514
+ throw error;
30515
+ const method = this.authMethods[0];
30516
+ if (typeof method.id !== "string")
30517
+ throw error;
30518
+ await this.request("authenticate", { methodId: method.id });
30519
+ return await this.request("session/new", {
30520
+ cwd: this.cwd,
30521
+ mcpServers: []
30522
+ });
30523
+ }
30524
+ }
30525
+ close() {
30526
+ if (this.active && this.sessionId && !this.child.killed) {
30527
+ this.notify("session/cancel", { sessionId: this.sessionId });
30528
+ }
30529
+ if (!this.child.killed)
30530
+ this.child.kill("SIGTERM");
30531
+ }
30532
+ request(method, params) {
30533
+ const id = this.next++;
30534
+ const payload = { jsonrpc: "2.0", id, method, params };
30535
+ return new Promise((resolve3, reject) => {
30536
+ this.pending.set(id, { resolve: resolve3, reject });
30537
+ this.child.stdin.write(`${JSON.stringify(payload)}
30538
+ `, (error) => {
30539
+ if (!error)
30540
+ return;
30541
+ this.pending.delete(id);
30542
+ reject(error);
30543
+ });
30544
+ });
30545
+ }
30546
+ notify(method, params) {
30547
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
30548
+ `);
30549
+ }
30550
+ async drain() {
30551
+ this.lastUpdate = Date.now();
30552
+ while (this.activeRequests > 0 || Date.now() - this.lastUpdate < 100) {
30553
+ await new Promise((resolve3) => setTimeout(resolve3, 25));
30554
+ }
30555
+ }
30556
+ async receive(line) {
30557
+ if (!line.trim())
30558
+ return;
30559
+ let message;
30560
+ try {
30561
+ message = JSON.parse(line);
30562
+ } catch {
30563
+ const error = new Error(`ACP agent '${this.name}' wrote non-JSON stdout: ${line.slice(0, 200)}`);
30564
+ this.errors.push(error.message);
30565
+ this.rejectPending(error);
30566
+ this.close();
30567
+ return;
30568
+ }
30569
+ if ("id" in message && (("result" in message) || ("error" in message))) {
30570
+ const pending = this.pending.get(message.id);
30571
+ if (!pending)
30572
+ return;
30573
+ this.pending.delete(message.id);
30574
+ if (message.error) {
30575
+ pending.reject(rpcError(message.error));
30576
+ return;
30577
+ }
30578
+ pending.resolve(message.result);
30579
+ return;
30580
+ }
30581
+ if ("id" in message && "method" in message) {
30582
+ this.activeRequests++;
30583
+ try {
30584
+ await this.handleRequest(message);
30585
+ } finally {
30586
+ this.activeRequests--;
30587
+ this.lastUpdate = Date.now();
30588
+ }
30589
+ return;
30590
+ }
30591
+ if ("method" in message)
30592
+ this.handleNotification(message);
30593
+ }
30594
+ rejectPending(error) {
30595
+ for (const item of this.pending.values())
30596
+ item.reject(error);
30597
+ this.pending.clear();
30598
+ }
30599
+ async handleRequest(message) {
30600
+ if (message.method === "session/request_permission") {
30601
+ const title = readPermissionTitle(message.params);
30602
+ try {
30603
+ if (this.config.permissionMode === "ask") {
30604
+ await this.ask(title, message.params ?? {});
30605
+ }
30606
+ const optionId = selectPermissionOption(message.params, this.config.permissionMode);
30607
+ if (!optionId)
30608
+ throw new Error("ACP permission request had no usable option");
30609
+ this.reply(message.id, {
30610
+ outcome: { outcome: "selected", optionId }
30611
+ });
30612
+ } catch {
30613
+ const optionId = selectPermissionOption(message.params, "reject");
30614
+ if (optionId) {
30615
+ this.reply(message.id, {
30616
+ outcome: { outcome: "selected", optionId }
30617
+ });
30618
+ return;
30619
+ }
30620
+ this.reply(message.id, { outcome: { outcome: "cancelled" } });
30621
+ }
30622
+ return;
30623
+ }
30624
+ this.replyError(message.id, `Unsupported ACP client method: ${message.method}`);
30625
+ }
30626
+ handleNotification(message) {
30627
+ if (message.method !== "session/update")
30628
+ return;
30629
+ this.lastUpdate = Date.now();
30630
+ const update = message.params?.update;
30631
+ if (!isRecord2(update))
30632
+ return;
30633
+ collectText(update, this.chunks);
30634
+ }
30635
+ reply(id, result) {
30636
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
30637
+ `);
30638
+ }
30639
+ replyError(id, message) {
30640
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message } })}
30641
+ `);
30642
+ }
30643
+ output() {
30644
+ const text = this.chunks.join("").trim();
30645
+ if (text)
30646
+ return text;
30647
+ const err = this.errors.join("").trim();
30648
+ return err ? `ACP agent '${this.name}' completed without text output. stderr:
30649
+ ${err}` : `ACP agent '${this.name}' completed without text output.`;
30650
+ }
30651
+ }
30652
+ function createAcpRunTool(agents = {}) {
30653
+ return tool({
30654
+ description: "Run a configured external ACP-compatible coding agent and return its streamed result. Use for configured ACP agents such as Claude Code ACP, Gemini ACP, or custom ACP servers.",
30655
+ args: {
30656
+ agent: z4.string().describe("Configured ACP agent name"),
30657
+ prompt: z4.string().describe("Task or question to send to the ACP agent"),
30658
+ cwd: z4.string().optional().describe("Optional absolute working directory override"),
30659
+ timeout_ms: z4.number().int().min(1000).max(900000).optional().describe("Optional timeout override in milliseconds")
30660
+ },
30661
+ async execute(args, ctx) {
30662
+ if (ctx.agent !== args.agent) {
30663
+ throw new Error(`acp_run for '${args.agent}' can only be used by @${args.agent}`);
30664
+ }
30665
+ const config = agents[args.agent];
30666
+ if (!config) {
30667
+ throw new Error(`Unknown ACP agent '${args.agent}'. Configured agents: ${Object.keys(agents).join(", ") || "(none)"}`);
30668
+ }
30669
+ const cwd = args.cwd ?? config.cwd ?? ctx.directory;
30670
+ if (!cwd)
30671
+ throw new Error("acp_run requires a working directory");
30672
+ await ctx.ask({
30673
+ permission: "bash",
30674
+ patterns: [`${config.command} ${config.args.join(" ")}`.trim()],
30675
+ always: [],
30676
+ metadata: {
30677
+ agent: args.agent,
30678
+ cwd,
30679
+ command: config.command,
30680
+ args: config.args
30681
+ }
30682
+ });
30683
+ const client = new AcpClient(args.agent, config, cwd, async (title, metadata) => {
30684
+ if (config.permissionMode === "reject")
30685
+ return;
30686
+ await ctx.ask({
30687
+ permission: "bash",
30688
+ patterns: [`acp:${args.agent}:${title}`],
30689
+ always: [],
30690
+ metadata
30691
+ });
30692
+ });
30693
+ const timeoutMs = args.timeout_ms ?? config.timeoutMs;
30694
+ let timer;
30695
+ const timeout = new Promise((_, reject) => timer = setTimeout(() => reject(new Error(`ACP agent '${args.agent}' timed out after ${timeoutMs}ms`)), timeoutMs));
30696
+ const abort = () => client.close();
30697
+ ctx.abort.addEventListener("abort", abort, { once: true });
30698
+ try {
30699
+ return await Promise.race([client.run(args.prompt), timeout]);
30700
+ } finally {
30701
+ if (timer)
30702
+ clearTimeout(timer);
30703
+ ctx.abort.removeEventListener("abort", abort);
30704
+ client.close();
30705
+ }
30706
+ }
30707
+ });
30708
+ }
30709
+ function readSessionId(value) {
30710
+ if (!isRecord2(value) || typeof value.sessionId !== "string") {
30711
+ throw new Error("ACP agent did not return a sessionId");
30712
+ }
30713
+ return value.sessionId;
30714
+ }
30715
+ function readAuthMethods(value) {
30716
+ if (!isRecord2(value) || !Array.isArray(value.authMethods))
30717
+ return [];
30718
+ const methods = value.authMethods;
30719
+ return methods.filter(isRecord2);
30720
+ }
30721
+ function rpcError(error) {
30722
+ const err = new Error(error.message ?? "ACP request failed");
30723
+ err.code = error.code;
30724
+ err.data = error.data;
30725
+ return err;
30726
+ }
30727
+ function isAuthError(error) {
30728
+ if (!(error instanceof Error))
30729
+ return false;
30730
+ const meta = error;
30731
+ return meta.code === -32001 || error.message.toLowerCase().includes("auth_required") || error.message.toLowerCase().includes("auth required");
30732
+ }
30733
+ function isRecord2(value) {
30734
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30735
+ }
30736
+ function readPermissionTitle(params) {
30737
+ const tool2 = isRecord2(params?.toolCall) ? params.toolCall : undefined;
30738
+ if (typeof tool2?.title === "string")
30739
+ return tool2.title;
30740
+ if (typeof params?.permission === "string")
30741
+ return params.permission;
30742
+ return "ACP permission request";
30743
+ }
30744
+ function selectPermissionOption(params, mode) {
30745
+ const options = Array.isArray(params?.options) ? params.options : [];
30746
+ const choices = options.filter(isRecord2).filter((item) => typeof item.optionId === "string");
30747
+ const reject = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("reject"));
30748
+ if (mode === "reject")
30749
+ return reject?.optionId;
30750
+ const allow = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("allow"));
30751
+ return allow?.optionId ?? reject?.optionId;
30752
+ }
30753
+ function collectText(update, chunks) {
30754
+ if (update.sessionUpdate !== "agent_message_chunk")
30755
+ return;
30756
+ const text = readText(update.delta) ?? readText(update.content);
30757
+ if (text)
30758
+ chunks.push(text);
30759
+ }
30760
+ function readText(value) {
30761
+ if (typeof value === "string")
30762
+ return value;
30763
+ if (isRecord2(value) && typeof value.text === "string")
30764
+ return value.text;
30765
+ return;
30766
+ }
30767
+ // src/tools/ast-grep/tools.ts
30768
+ import { tool as tool2 } from "@opencode-ai/plugin";
29889
30769
 
29890
30770
  // src/tools/ast-grep/cli.ts
29891
- import { existsSync as existsSync10 } from "node:fs";
30771
+ init_compat();
30772
+ import { existsSync as existsSync11 } from "node:fs";
29892
30773
 
29893
30774
  // src/tools/ast-grep/constants.ts
29894
- import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
30775
+ import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
29895
30776
  import { createRequire as createRequire3 } from "node:module";
29896
- import { dirname as dirname8, join as join14 } from "node:path";
30777
+ import { dirname as dirname9, join as join15 } from "node:path";
29897
30778
 
29898
30779
  // src/tools/ast-grep/downloader.ts
29899
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "node:fs";
30780
+ import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
29900
30781
  import { createRequire as createRequire2 } from "node:module";
29901
- import { homedir as homedir5 } from "node:os";
29902
- import { join as join13 } from "node:path";
30782
+ import { homedir as homedir6 } from "node:os";
30783
+ import { join as join14 } from "node:path";
30784
+ init_compat();
29903
30785
  var REPO = "ast-grep/ast-grep";
29904
30786
  var DEFAULT_VERSION = "0.40.0";
29905
30787
  function getAstGrepVersion() {
@@ -29923,19 +30805,19 @@ var PLATFORM_MAP = {
29923
30805
  function getCacheDir2() {
29924
30806
  if (process.platform === "win32") {
29925
30807
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
29926
- const base2 = localAppData || join13(homedir5(), "AppData", "Local");
29927
- return join13(base2, "oh-my-opencode-slim", "bin");
30808
+ const base2 = localAppData || join14(homedir6(), "AppData", "Local");
30809
+ return join14(base2, "oh-my-opencode-slim", "bin");
29928
30810
  }
29929
30811
  const xdgCache = process.env.XDG_CACHE_HOME;
29930
- const base = xdgCache || join13(homedir5(), ".cache");
29931
- return join13(base, "oh-my-opencode-slim", "bin");
30812
+ const base = xdgCache || join14(homedir6(), ".cache");
30813
+ return join14(base, "oh-my-opencode-slim", "bin");
29932
30814
  }
29933
30815
  function getBinaryName() {
29934
30816
  return process.platform === "win32" ? "sg.exe" : "sg";
29935
30817
  }
29936
30818
  function getCachedBinaryPath() {
29937
- const binaryPath2 = join13(getCacheDir2(), getBinaryName());
29938
- return existsSync8(binaryPath2) ? binaryPath2 : null;
30819
+ const binaryPath = join14(getCacheDir2(), getBinaryName());
30820
+ return existsSync9(binaryPath) ? binaryPath : null;
29939
30821
  }
29940
30822
  async function downloadAstGrep(version = DEFAULT_VERSION) {
29941
30823
  const platformKey = `${process.platform}-${process.arch}`;
@@ -29946,34 +30828,34 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
29946
30828
  }
29947
30829
  const cacheDir = getCacheDir2();
29948
30830
  const binaryName = getBinaryName();
29949
- const binaryPath2 = join13(cacheDir, binaryName);
29950
- if (existsSync8(binaryPath2)) {
29951
- return binaryPath2;
30831
+ const binaryPath = join14(cacheDir, binaryName);
30832
+ if (existsSync9(binaryPath)) {
30833
+ return binaryPath;
29952
30834
  }
29953
30835
  const { arch, os: os5 } = platformInfo;
29954
30836
  const assetName = `app-${arch}-${os5}.zip`;
29955
30837
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
29956
30838
  console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
29957
30839
  try {
29958
- if (!existsSync8(cacheDir)) {
29959
- mkdirSync6(cacheDir, { recursive: true });
30840
+ if (!existsSync9(cacheDir)) {
30841
+ mkdirSync7(cacheDir, { recursive: true });
29960
30842
  }
29961
30843
  const response = await fetch(downloadUrl, { redirect: "follow" });
29962
30844
  if (!response.ok) {
29963
30845
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
29964
30846
  }
29965
- const archivePath = join13(cacheDir, assetName);
30847
+ const archivePath = join14(cacheDir, assetName);
29966
30848
  const arrayBuffer = await response.arrayBuffer();
29967
30849
  await crossWrite(archivePath, arrayBuffer);
29968
30850
  await extractZip(archivePath, cacheDir);
29969
- if (existsSync8(archivePath)) {
30851
+ if (existsSync9(archivePath)) {
29970
30852
  unlinkSync4(archivePath);
29971
30853
  }
29972
- if (process.platform !== "win32" && existsSync8(binaryPath2)) {
29973
- chmodSync(binaryPath2, 493);
30854
+ if (process.platform !== "win32" && existsSync9(binaryPath)) {
30855
+ chmodSync2(binaryPath, 493);
29974
30856
  }
29975
30857
  console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
29976
- return binaryPath2;
30858
+ return binaryPath;
29977
30859
  } catch (err) {
29978
30860
  console.error(`[oh-my-opencode-slim] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`);
29979
30861
  return null;
@@ -30021,13 +30903,13 @@ var CLI_LANGUAGES = [
30021
30903
  var MIN_BINARY_SIZE = 1e4;
30022
30904
  function isValidBinary(filePath) {
30023
30905
  try {
30024
- return statSync4(filePath).size > MIN_BINARY_SIZE;
30906
+ return statSync5(filePath).size > MIN_BINARY_SIZE;
30025
30907
  } catch {
30026
30908
  return false;
30027
30909
  }
30028
30910
  }
30029
30911
  function getPlatformPackageName() {
30030
- const platform2 = process.platform;
30912
+ const platform3 = process.platform;
30031
30913
  const arch = process.arch;
30032
30914
  const platformMap = {
30033
30915
  "darwin-arm64": "@ast-grep/cli-darwin-arm64",
@@ -30038,7 +30920,7 @@ function getPlatformPackageName() {
30038
30920
  "win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
30039
30921
  "win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
30040
30922
  };
30041
- return platformMap[`${platform2}-${arch}`] ?? null;
30923
+ return platformMap[`${platform3}-${arch}`] ?? null;
30042
30924
  }
30043
30925
  var resolvedCliPath = null;
30044
30926
  function findSgCliPathSync() {
@@ -30050,9 +30932,9 @@ function findSgCliPathSync() {
30050
30932
  try {
30051
30933
  const require2 = createRequire3(import.meta.url);
30052
30934
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
30053
- const cliDir = dirname8(cliPkgPath);
30054
- const sgPath = join14(cliDir, binaryName);
30055
- if (existsSync9(sgPath) && isValidBinary(sgPath)) {
30935
+ const cliDir = dirname9(cliPkgPath);
30936
+ const sgPath = join15(cliDir, binaryName);
30937
+ if (existsSync10(sgPath) && isValidBinary(sgPath)) {
30056
30938
  return sgPath;
30057
30939
  }
30058
30940
  } catch {}
@@ -30061,19 +30943,19 @@ function findSgCliPathSync() {
30061
30943
  try {
30062
30944
  const require2 = createRequire3(import.meta.url);
30063
30945
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
30064
- const pkgDir = dirname8(pkgPath);
30946
+ const pkgDir = dirname9(pkgPath);
30065
30947
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
30066
- const binaryPath2 = join14(pkgDir, astGrepName);
30067
- if (existsSync9(binaryPath2) && isValidBinary(binaryPath2)) {
30068
- return binaryPath2;
30948
+ const binaryPath = join15(pkgDir, astGrepName);
30949
+ if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
30950
+ return binaryPath;
30069
30951
  }
30070
30952
  } catch {}
30071
30953
  }
30072
30954
  if (process.platform === "darwin") {
30073
30955
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
30074
- for (const path16 of homebrewPaths) {
30075
- if (existsSync9(path16) && isValidBinary(path16)) {
30076
- return path16;
30956
+ for (const path17 of homebrewPaths) {
30957
+ if (existsSync10(path17) && isValidBinary(path17)) {
30958
+ return path17;
30077
30959
  }
30078
30960
  }
30079
30961
  }
@@ -30090,8 +30972,8 @@ function getSgCliPath() {
30090
30972
  }
30091
30973
  return "sg";
30092
30974
  }
30093
- function setSgCliPath(path16) {
30094
- resolvedCliPath = path16;
30975
+ function setSgCliPath(path17) {
30976
+ resolvedCliPath = path17;
30095
30977
  }
30096
30978
  var DEFAULT_TIMEOUT_MS = 300000;
30097
30979
  var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
@@ -30101,7 +30983,7 @@ var DEFAULT_MAX_MATCHES = 500;
30101
30983
  var initPromise = null;
30102
30984
  async function getAstGrepPath() {
30103
30985
  const currentPath = getSgCliPath();
30104
- if (currentPath !== "sg" && existsSync10(currentPath)) {
30986
+ if (currentPath !== "sg" && existsSync11(currentPath)) {
30105
30987
  return currentPath;
30106
30988
  }
30107
30989
  if (initPromise) {
@@ -30109,7 +30991,7 @@ async function getAstGrepPath() {
30109
30991
  }
30110
30992
  initPromise = (async () => {
30111
30993
  const syncPath = findSgCliPathSync();
30112
- if (syncPath && existsSync10(syncPath)) {
30994
+ if (syncPath && existsSync11(syncPath)) {
30113
30995
  setSgCliPath(syncPath);
30114
30996
  return syncPath;
30115
30997
  }
@@ -30148,7 +31030,7 @@ async function runSg(options) {
30148
31030
  const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
30149
31031
  args.push(...paths2);
30150
31032
  let cliPath = getSgCliPath();
30151
- if (!existsSync10(cliPath) && cliPath !== "sg") {
31033
+ if (!existsSync11(cliPath) && cliPath !== "sg") {
30152
31034
  const downloadedPath = await getAstGrepPath();
30153
31035
  if (downloadedPath) {
30154
31036
  cliPath = downloadedPath;
@@ -30364,14 +31246,14 @@ function showOutputToUser(context, output) {
30364
31246
  const ctx = context;
30365
31247
  ctx.metadata?.({ metadata: { output } });
30366
31248
  }
30367
- var ast_grep_search = tool({
31249
+ var ast_grep_search = tool2({
30368
31250
  description: "Search code patterns across filesystem using AST-aware matching. Supports 25 languages. " + "Use meta-variables: $VAR (single node), $$$ (multiple nodes). " + "IMPORTANT: Patterns must be complete AST nodes (valid code). " + "For functions, include params and body: 'export async function $NAME($$$) { $$$ }' not 'export async function $NAME'. " + "Examples: 'console.log($MSG)', 'def $FUNC($$$):', 'async function $NAME($$$)'",
30369
31251
  args: {
30370
- pattern: tool.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
30371
- lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
30372
- paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search (default: ['.'])"),
30373
- globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
30374
- context: tool.schema.number().optional().describe("Context lines around match")
31252
+ pattern: tool2.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
31253
+ lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
31254
+ paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search (default: ['.'])"),
31255
+ globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
31256
+ context: tool2.schema.number().optional().describe("Context lines around match")
30375
31257
  },
30376
31258
  execute: async (args, context) => {
30377
31259
  try {
@@ -30400,15 +31282,15 @@ ${hint}`;
30400
31282
  }
30401
31283
  }
30402
31284
  });
30403
- var ast_grep_replace = tool({
31285
+ var ast_grep_replace = tool2({
30404
31286
  description: "Replace code patterns across filesystem with AST-aware rewriting. " + "Dry-run by default. Use meta-variables in rewrite to preserve matched content. " + "Example: pattern='console.log($MSG)' rewrite='logger.info($MSG)'",
30405
31287
  args: {
30406
- pattern: tool.schema.string().describe("AST pattern to match"),
30407
- rewrite: tool.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
30408
- lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
30409
- paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search"),
30410
- globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs"),
30411
- dryRun: tool.schema.boolean().optional().describe("Preview changes without applying (default: true)")
31288
+ pattern: tool2.schema.string().describe("AST pattern to match"),
31289
+ rewrite: tool2.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
31290
+ lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
31291
+ paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search"),
31292
+ globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs"),
31293
+ dryRun: tool2.schema.boolean().optional().describe("Preview changes without applying (default: true)")
30412
31294
  },
30413
31295
  execute: async (args, context) => {
30414
31296
  try {
@@ -30432,20 +31314,20 @@ var ast_grep_replace = tool({
30432
31314
  });
30433
31315
  // src/tools/cancel-task.ts
30434
31316
  import {
30435
- tool as tool2
31317
+ tool as tool3
30436
31318
  } from "@opencode-ai/plugin";
30437
- var z4 = tool2.schema;
31319
+ var z5 = tool3.schema;
30438
31320
 
30439
31321
  class SessionStillRunningError extends Error {
30440
31322
  }
30441
31323
  function createCancelTaskTool(options) {
30442
- const cancel_task = tool2({
31324
+ const cancel_task = tool3({
30443
31325
  description: `Cancel a tracked background specialist task.
30444
31326
 
30445
31327
  Use only for obsolete, wrong, conflicting, or user-requested cancellation. Accepts either the native task_id/session ID or the parent-scoped alias shown in the Background Job Board. Cancellation is not rollback: if cancelling a writer, inspect and reconcile partial file changes before replacing the lane.`,
30446
31328
  args: {
30447
- task_id: z4.string().describe("Tracked background task ID or Background Job Board alias"),
30448
- reason: z4.string().optional().describe("Short cancellation reason")
31329
+ task_id: z5.string().describe("Tracked background task ID or Background Job Board alias"),
31330
+ reason: z5.string().optional().describe("Short cancellation reason")
30449
31331
  },
30450
31332
  async execute(args, toolContext) {
30451
31333
  const parentSessionID = toolContext?.sessionID;
@@ -30658,7 +31540,7 @@ async function abortAndVerifySession(options, taskID) {
30658
31540
  attempts,
30659
31541
  status: statusSnapshot.status
30660
31542
  });
30661
- await delay(retryIntervalMs);
31543
+ await delay2(retryIntervalMs);
30662
31544
  continue;
30663
31545
  }
30664
31546
  stableStoppedSince ??= Date.now();
@@ -30671,7 +31553,7 @@ async function abortAndVerifySession(options, taskID) {
30671
31553
  });
30672
31554
  return;
30673
31555
  }
30674
- await delay(retryIntervalMs);
31556
+ await delay2(retryIntervalMs);
30675
31557
  }
30676
31558
  log("[cancel-task] abort verification timed out", {
30677
31559
  taskID,
@@ -30739,13 +31621,13 @@ async function deleteAndVerifySession(options, taskID, reason) {
30739
31621
  });
30740
31622
  if (status.status === "busy" || status.status === "retry") {
30741
31623
  stableStoppedSince = undefined;
30742
- await delay(retryIntervalMs);
31624
+ await delay2(retryIntervalMs);
30743
31625
  continue;
30744
31626
  }
30745
31627
  stableStoppedSince ??= Date.now();
30746
31628
  if (Date.now() - stableStoppedSince >= stableStoppedMs)
30747
31629
  return;
30748
- await delay(retryIntervalMs);
31630
+ await delay2(retryIntervalMs);
30749
31631
  }
30750
31632
  throw new SessionStillRunningError(`Session delete returned but task did not stay stopped: ${taskID} (${lastStatus ?? "unknown"})`);
30751
31633
  }
@@ -30784,7 +31666,7 @@ async function getSessionStatus(client, taskID) {
30784
31666
  return { status: undefined, source: "lookup-error", keys: [] };
30785
31667
  }
30786
31668
  }
30787
- function delay(ms) {
31669
+ function delay2(ms) {
30788
31670
  return new Promise((resolve3) => setTimeout(resolve3, ms));
30789
31671
  }
30790
31672
  function isSessionID(value) {
@@ -30826,9 +31708,9 @@ function unknownTaskOutput(taskID, message) {
30826
31708
  }
30827
31709
  // src/tools/council.ts
30828
31710
  import {
30829
- tool as tool3
31711
+ tool as tool4
30830
31712
  } from "@opencode-ai/plugin";
30831
- var z5 = tool3.schema;
31713
+ var z6 = tool4.schema;
30832
31714
  function formatModelComposition(councillorResults) {
30833
31715
  return councillorResults.map((cr) => {
30834
31716
  const shortModel = shortModelLabel(cr.model);
@@ -30836,15 +31718,15 @@ function formatModelComposition(councillorResults) {
30836
31718
  }).join(", ");
30837
31719
  }
30838
31720
  function createCouncilTool(_ctx, councilManager) {
30839
- const council_session = tool3({
31721
+ const council_session = tool4({
30840
31722
  description: `Launch a multi-LLM council session for consensus-based analysis.
30841
31723
 
30842
31724
  Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
30843
31725
 
30844
31726
  Returns the councillor responses with a summary footer.`,
30845
31727
  args: {
30846
- prompt: z5.string().describe("The prompt to send to all councillors"),
30847
- preset: z5.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
31728
+ prompt: z6.string().describe("The prompt to send to all councillors"),
31729
+ preset: z6.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
30848
31730
  },
30849
31731
  async execute(args, toolContext) {
30850
31732
  if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
@@ -30896,14 +31778,14 @@ import * as fs10 from "node:fs";
30896
31778
  // src/tui-state.ts
30897
31779
  import * as fs9 from "node:fs";
30898
31780
  import * as os5 from "node:os";
30899
- import * as path16 from "node:path";
31781
+ import * as path17 from "node:path";
30900
31782
  var STATE_DIR = "oh-my-opencode-slim";
30901
31783
  var STATE_FILE = "tui-state.json";
30902
31784
  function dataDir() {
30903
- return process.env.XDG_DATA_HOME ?? path16.join(os5.homedir(), ".local", "share");
31785
+ return process.env.XDG_DATA_HOME ?? path17.join(os5.homedir(), ".local", "share");
30904
31786
  }
30905
31787
  function getTuiStatePath() {
30906
- return path16.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
31788
+ return path17.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
30907
31789
  }
30908
31790
  function emptySnapshot() {
30909
31791
  return {
@@ -30939,7 +31821,7 @@ async function readTuiSnapshotAsync() {
30939
31821
  function writeTuiSnapshot(snapshot) {
30940
31822
  try {
30941
31823
  const filePath = getTuiStatePath();
30942
- fs9.mkdirSync(path16.dirname(filePath), { recursive: true });
31824
+ fs9.mkdirSync(path17.dirname(filePath), { recursive: true });
30943
31825
  fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
30944
31826
  `);
30945
31827
  } catch {}
@@ -31139,14 +32021,14 @@ var BINARY_PREFIXES = [
31139
32021
  var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs pages. Supports llms.txt probing, content-focused HTML extraction, metadata, redirects, and an optional prompt processed by a cheap secondary model.";
31140
32022
  // src/tools/smartfetch/tool.ts
31141
32023
  import os6 from "node:os";
31142
- import path20 from "node:path";
32024
+ import path21 from "node:path";
31143
32025
  import {
31144
- tool as tool4
32026
+ tool as tool5
31145
32027
  } from "@opencode-ai/plugin";
31146
32028
 
31147
32029
  // src/tools/smartfetch/binary.ts
31148
32030
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
31149
- import path17 from "node:path";
32031
+ import path18 from "node:path";
31150
32032
  function extensionForMime(contentType) {
31151
32033
  const mime = contentType.split(";")[0]?.trim().toLowerCase();
31152
32034
  const map = {
@@ -31167,10 +32049,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
31167
32049
  async function saveBinary(binaryDir, data, contentType, filename) {
31168
32050
  await mkdir2(binaryDir, { recursive: true });
31169
32051
  const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
31170
- const parsed = path17.parse(initialName);
32052
+ const parsed = path18.parse(initialName);
31171
32053
  for (let attempt = 0;attempt < 1000; attempt++) {
31172
32054
  const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
31173
- const file = path17.join(binaryDir, candidateName);
32055
+ const file = path18.join(binaryDir, candidateName);
31174
32056
  try {
31175
32057
  await writeFile2(file, data, { flag: "wx" });
31176
32058
  return file;
@@ -31309,7 +32191,7 @@ var M = class u2 {
31309
32191
  return this.#S;
31310
32192
  }
31311
32193
  constructor(e) {
31312
- let { max: t = 0, ttl: i, ttlResolution: s = 1, ttlAutopurge: n, updateAgeOnGet: o, updateAgeOnHas: r, allowStale: h, dispose: l, onInsert: c, disposeAfter: f, noDisposeOnSet: g, noUpdateTTL: p, maxSize: T = 0, maxEntrySize: w = 0, sizeCalculation: y, fetchMethod: a, memoMethod: m, noDeleteOnFetchRejection: _, noDeleteOnStaleGet: b, allowStaleOnFetchRejection: d, allowStaleOnFetchAbort: A, ignoreFetchAbort: z6, perf: x } = e;
32194
+ let { max: t = 0, ttl: i, ttlResolution: s = 1, ttlAutopurge: n, updateAgeOnGet: o, updateAgeOnHas: r, allowStale: h, dispose: l, onInsert: c, disposeAfter: f, noDisposeOnSet: g, noUpdateTTL: p, maxSize: T = 0, maxEntrySize: w = 0, sizeCalculation: y, fetchMethod: a, memoMethod: m, noDeleteOnFetchRejection: _, noDeleteOnStaleGet: b, allowStaleOnFetchRejection: d, allowStaleOnFetchAbort: A, ignoreFetchAbort: z7, perf: x } = e;
31313
32195
  if (x !== undefined && typeof x?.now != "function")
31314
32196
  throw new TypeError("perf option must have a now() method if specified");
31315
32197
  if (this.#m = x ?? C, t !== 0 && !F(t))
@@ -31327,7 +32209,7 @@ var M = class u2 {
31327
32209
  throw new TypeError("memoMethod must be a function if defined");
31328
32210
  if (this.#U = m, a !== undefined && typeof a != "function")
31329
32211
  throw new TypeError("fetchMethod must be a function if specified");
31330
- if (this.#M = a, this.#W = !!a, this.#s = new Map, this.#i = Array.from({ length: t }).fill(undefined), this.#t = Array.from({ length: t }).fill(undefined), this.#a = new v(t), this.#c = new v(t), this.#l = 0, this.#h = 0, this.#y = R.create(t), this.#n = 0, this.#b = 0, typeof l == "function" && (this.#w = l), typeof c == "function" && (this.#x = c), typeof f == "function" ? (this.#S = f, this.#r = []) : (this.#S = undefined, this.#r = undefined), this.#T = !!this.#w, this.#j = !!this.#x, this.#f = !!this.#S, this.noDisposeOnSet = !!g, this.noUpdateTTL = !!p, this.noDeleteOnFetchRejection = !!_, this.allowStaleOnFetchRejection = !!d, this.allowStaleOnFetchAbort = !!A, this.ignoreFetchAbort = !!z6, this.maxEntrySize !== 0) {
32212
+ if (this.#M = a, this.#W = !!a, this.#s = new Map, this.#i = Array.from({ length: t }).fill(undefined), this.#t = Array.from({ length: t }).fill(undefined), this.#a = new v(t), this.#c = new v(t), this.#l = 0, this.#h = 0, this.#y = R.create(t), this.#n = 0, this.#b = 0, typeof l == "function" && (this.#w = l), typeof c == "function" && (this.#x = c), typeof f == "function" ? (this.#S = f, this.#r = []) : (this.#S = undefined, this.#r = undefined), this.#T = !!this.#w, this.#j = !!this.#x, this.#f = !!this.#S, this.noDisposeOnSet = !!g, this.noUpdateTTL = !!p, this.noDeleteOnFetchRejection = !!_, this.allowStaleOnFetchRejection = !!d, this.allowStaleOnFetchAbort = !!A, this.ignoreFetchAbort = !!z7, this.maxEntrySize !== 0) {
31331
32213
  if (this.#u !== 0 && !F(this.#u))
31332
32214
  throw new TypeError("maxSize must be a positive integer if specified");
31333
32215
  if (!F(this.maxEntrySize))
@@ -31707,8 +32589,8 @@ var M = class u2 {
31707
32589
  let A = this.#p(b);
31708
32590
  if (!y && !A)
31709
32591
  return a && (a.fetch = "hit"), this.#L(b), s && this.#D(b), a && this.#E(a, b), d;
31710
- let z6 = this.#P(e, b, _, w), v = z6.__staleWhileFetching !== undefined && i;
31711
- return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z6.__staleWhileFetching : z6.__returned = z6;
32592
+ let z7 = this.#P(e, b, _, w), v = z7.__staleWhileFetching !== undefined && i;
32593
+ return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z7.__staleWhileFetching : z7.__returned = z7;
31712
32594
  }
31713
32595
  }
31714
32596
  forceFetch(e, t = {}) {
@@ -31824,7 +32706,7 @@ var M = class u2 {
31824
32706
  };
31825
32707
 
31826
32708
  // src/tools/smartfetch/network.ts
31827
- import path18 from "node:path";
32709
+ import path19 from "node:path";
31828
32710
 
31829
32711
  // src/tools/smartfetch/utils.ts
31830
32712
  var import_readability = __toESM(require_readability(), 1);
@@ -32549,7 +33431,7 @@ function inferFilenameFromUrl(url) {
32549
33431
  function truncateFilename(name, maxLength = 180) {
32550
33432
  if (name.length <= maxLength)
32551
33433
  return name;
32552
- const parsed = path18.parse(name);
33434
+ const parsed = path19.parse(name);
32553
33435
  const ext = parsed.ext || "";
32554
33436
  const baseLimit = Math.max(1, maxLength - ext.length);
32555
33437
  return `${parsed.name.slice(0, baseLimit)}${ext}`;
@@ -32719,9 +33601,9 @@ function isInvalidLlmsResult(fetchResult) {
32719
33601
  }
32720
33602
 
32721
33603
  // src/tools/smartfetch/secondary-model.ts
32722
- import { existsSync as existsSync11 } from "node:fs";
33604
+ import { existsSync as existsSync12 } from "node:fs";
32723
33605
  import { readFile as readFile4 } from "node:fs/promises";
32724
- import path19 from "node:path";
33606
+ import path20 from "node:path";
32725
33607
  function parseModelRef(value) {
32726
33608
  if (!value)
32727
33609
  return;
@@ -32747,8 +33629,8 @@ function pickAgentModelRef(value) {
32747
33629
  }
32748
33630
  function findPreferredOpenCodeConfigPath(baseDir) {
32749
33631
  for (const file of ["opencode.jsonc", "opencode.json"]) {
32750
- const fullPath = path19.join(baseDir, file);
32751
- if (existsSync11(fullPath))
33632
+ const fullPath = path20.join(baseDir, file);
33633
+ if (existsSync12(fullPath))
32752
33634
  return fullPath;
32753
33635
  }
32754
33636
  return;
@@ -32764,7 +33646,7 @@ async function readOpenCodeConfigFile(configPath) {
32764
33646
  }
32765
33647
  }
32766
33648
  async function readEffectiveOpenCodeConfig(directory) {
32767
- const projectDir = path19.join(directory, ".opencode");
33649
+ const projectDir = path20.join(directory, ".opencode");
32768
33650
  const userDirs = getConfigSearchDirs();
32769
33651
  const projectPath = findPreferredOpenCodeConfigPath(projectDir);
32770
33652
  const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
@@ -32845,6 +33727,29 @@ function isUsableSecondaryText(text) {
32845
33727
  return false;
32846
33728
  return true;
32847
33729
  }
33730
+ var SESSION_DELETE_RETRIES = 3;
33731
+ var SESSION_DELETE_RETRY_DELAY_MS = 500;
33732
+ var SECONDARY_MODEL_TIMEOUT_MS = 30000;
33733
+ var _testConfig = {
33734
+ deleteRetryDelayMs: SESSION_DELETE_RETRY_DELAY_MS
33735
+ };
33736
+ async function deleteSessionSafely(client, sessionId, directory) {
33737
+ for (let attempt = 1;attempt <= SESSION_DELETE_RETRIES; attempt++) {
33738
+ try {
33739
+ await client.session.delete({
33740
+ path: { id: sessionId },
33741
+ query: { directory }
33742
+ });
33743
+ return;
33744
+ } catch (error) {
33745
+ if (attempt >= SESSION_DELETE_RETRIES) {
33746
+ console.warn(`[smartfetch] Failed to clean up secondary session ${sessionId} ` + `after ${SESSION_DELETE_RETRIES} attempts: ` + (error instanceof Error ? error.message : String(error)));
33747
+ return;
33748
+ }
33749
+ await new Promise((resolve3) => setTimeout(resolve3, _testConfig.deleteRetryDelayMs));
33750
+ }
33751
+ }
33752
+ }
32848
33753
  async function runSecondaryModel(client, directory, model, prompt, content) {
32849
33754
  const session2 = await client.session.create({
32850
33755
  responseStyle: "data",
@@ -32871,23 +33776,26 @@ Note: only the first ${inputChars} characters of a longer fetched document were
32871
33776
  const toolIDsData = toolIDsResponse;
32872
33777
  const toolIDs = Array.isArray(toolIDsData.data) ? toolIDsData.data : Array.isArray(toolIDsResponse) ? toolIDsResponse : [];
32873
33778
  const disabledTools = Object.fromEntries((toolIDs || []).map((id) => [id, false]));
32874
- const result = await client.session.prompt({
32875
- responseStyle: "data",
32876
- throwOnError: true,
32877
- path: { id: sessionId },
32878
- query: { directory },
32879
- body: {
32880
- model,
32881
- system: "Answer only from the supplied content. Do not use tools or outside knowledge.",
32882
- tools: disabledTools,
32883
- parts: [
32884
- {
32885
- type: "text",
32886
- text: buildPrompt(truncatedContent, effectivePrompt)
32887
- }
32888
- ]
32889
- }
32890
- });
33779
+ const result = await Promise.race([
33780
+ client.session.prompt({
33781
+ responseStyle: "data",
33782
+ throwOnError: true,
33783
+ path: { id: sessionId },
33784
+ query: { directory },
33785
+ body: {
33786
+ model,
33787
+ system: "Answer only from the supplied content. Do not use tools or outside knowledge.",
33788
+ tools: disabledTools,
33789
+ parts: [
33790
+ {
33791
+ type: "text",
33792
+ text: buildPrompt(truncatedContent, effectivePrompt)
33793
+ }
33794
+ ]
33795
+ }
33796
+ }),
33797
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Secondary model timed out")), SECONDARY_MODEL_TIMEOUT_MS))
33798
+ ]);
32891
33799
  const parts = result?.data?.parts ?? result?.parts ?? [];
32892
33800
  const text = parts.map((part) => part?.type === "text" ? part.text || "" : "").join("").trim();
32893
33801
  return {
@@ -32897,12 +33805,7 @@ Note: only the first ${inputChars} characters of a longer fetched document were
32897
33805
  sourceChars
32898
33806
  };
32899
33807
  } finally {
32900
- await client.session.delete({
32901
- path: { id: sessionId },
32902
- query: { directory }
32903
- }).catch(() => {
32904
- return;
32905
- });
33808
+ await deleteSessionSafely(client, sessionId, directory);
32906
33809
  }
32907
33810
  }
32908
33811
  async function runSecondaryModelWithFallback(client, directory, models, prompt, content) {
@@ -32923,20 +33826,20 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
32923
33826
  }
32924
33827
 
32925
33828
  // src/tools/smartfetch/tool.ts
32926
- var z6 = tool4.schema;
33829
+ var z7 = tool5.schema;
32927
33830
  function createWebfetchTool(pluginCtx, options = {}) {
32928
- const binaryDir = options.binaryDir || path20.join(os6.tmpdir(), "opencode-smartfetch");
32929
- return tool4({
33831
+ const binaryDir = options.binaryDir || path21.join(os6.tmpdir(), "opencode-smartfetch");
33832
+ return tool5({
32930
33833
  description: WEBFETCH_DESCRIPTION,
32931
33834
  args: {
32932
- url: z6.httpUrl(),
32933
- format: z6.enum(["text", "markdown", "html"]).default("markdown"),
32934
- timeout: z6.number().positive().max(MAX_TIMEOUT_SECONDS).optional().describe("Timeout in seconds, max 120."),
32935
- prompt: z6.string().optional().describe("Optional extraction task to run on the fetched content using a cheap secondary model."),
32936
- extract_main: z6.boolean().default(true),
32937
- prefer_llms_txt: z6.enum(["auto", "always", "never"]).default("auto"),
32938
- include_metadata: z6.boolean().default(true),
32939
- save_binary: z6.boolean().default(false).describe("Save binary payload to disk when it fits within the active download limit.")
33835
+ url: z7.httpUrl(),
33836
+ format: z7.enum(["text", "markdown", "html"]).default("markdown"),
33837
+ timeout: z7.number().positive().max(MAX_TIMEOUT_SECONDS).optional().describe("Timeout in seconds, max 120."),
33838
+ prompt: z7.string().optional().describe("Optional extraction task to run on the fetched content using a cheap secondary model."),
33839
+ extract_main: z7.boolean().default(true),
33840
+ prefer_llms_txt: z7.enum(["auto", "always", "never"]).default("auto"),
33841
+ include_metadata: z7.boolean().default(true),
33842
+ save_binary: z7.boolean().default(false).describe("Save binary payload to disk when it fits within the active download limit.")
32940
33843
  },
32941
33844
  async execute(args, ctx) {
32942
33845
  const secondaryModels = await readSecondaryModelFromConfig(ctx.directory || pluginCtx.directory);
@@ -33481,7 +34384,7 @@ async function appLog(ctx, level, message) {
33481
34384
  }
33482
34385
  var HEALTH_CHECK = {
33483
34386
  minAgents: 5,
33484
- minTools: 5,
34387
+ minTools: 4,
33485
34388
  minMcps: 1
33486
34389
  };
33487
34390
  async function probeJSDOM() {
@@ -33526,6 +34429,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33526
34429
  let companionManager;
33527
34430
  let councilTools;
33528
34431
  let cancelTaskTools;
34432
+ let acpRunTools;
33529
34433
  let webfetch;
33530
34434
  let rewriteDisplayNameMentions;
33531
34435
  let toolCount = 0;
@@ -33570,6 +34474,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33570
34474
  depthTracker = new SubagentDepthTracker;
33571
34475
  councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
33572
34476
  mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
34477
+ acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
33573
34478
  webfetch = createWebfetchTool(ctx);
33574
34479
  backgroundJobBoard = new BackgroundJobBoard({
33575
34480
  maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
@@ -33578,7 +34483,8 @@ var OhMyOpenCodeLite = async (ctx) => {
33578
34483
  });
33579
34484
  multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
33580
34485
  autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
33581
- autoUpdate: config.autoUpdate ?? true
34486
+ autoUpdate: config.autoUpdate ?? true,
34487
+ companion: config.companion
33582
34488
  });
33583
34489
  phaseReminderHook = createPhaseReminderHook();
33584
34490
  filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
@@ -33608,7 +34514,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33608
34514
  backgroundJobBoard,
33609
34515
  shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
33610
34516
  });
33611
- toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + 1 + 2;
34517
+ toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + Object.keys(acpRunTools).length + 1 + 2;
33612
34518
  } catch (err) {
33613
34519
  log("[plugin] FATAL: init failed", String(err));
33614
34520
  await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
@@ -33644,6 +34550,22 @@ var OhMyOpenCodeLite = async (ctx) => {
33644
34550
  appLog(ctx, "warn", msg).catch(() => {});
33645
34551
  }
33646
34552
  });
34553
+ if (config.companion?.enabled === true) {
34554
+ try {
34555
+ const companionResult = await ensureCompanionVersion({
34556
+ config: config.companion,
34557
+ downloadTimeoutMs: 3000,
34558
+ lockTimeoutMs: 500
34559
+ });
34560
+ if (companionResult.status === "installed") {
34561
+ log("[companion] updated before startup", companionResult.version);
34562
+ } else if (companionResult.status === "failed") {
34563
+ log("[companion] startup update failed", companionResult.error);
34564
+ }
34565
+ } catch (err) {
34566
+ log("[companion] startup update failed", String(err));
34567
+ }
34568
+ }
33647
34569
  companionManager.onLoad();
33648
34570
  return {
33649
34571
  name: "oh-my-opencode-slim",
@@ -33651,6 +34573,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33651
34573
  tool: {
33652
34574
  ...councilTools,
33653
34575
  ...cancelTaskTools,
34576
+ ...acpRunTools,
33654
34577
  webfetch,
33655
34578
  ast_grep_search,
33656
34579
  ast_grep_replace