oh-my-opencode-slim 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
 
@@ -19116,6 +19251,11 @@ Build a short work graph before dispatching:
19116
19251
  - Advisory ownership for write-capable lanes
19117
19252
  - Verification/review lanes that run after implementation
19118
19253
 
19254
+ ### Todo Continuity
19255
+ - When the user adds a new task while a todo list exists, append the new task to the end of the existing todo list instead of replacing the list.
19256
+ - Preserve existing todo order, statuses, and priorities unless the user explicitly asks to reprioritize, cancel, or replace them.
19257
+ - Finish the current in-progress task before starting the newly appended task unless the current task is blocked or the user explicitly overrides the order.
19258
+
19119
19259
  Can tasks be split into background specialist work?
19120
19260
  ${enabledParallelExamples}
19121
19261
 
@@ -19123,6 +19263,7 @@ Balance: respect dependencies, avoid parallelizing what must be sequential, and
19123
19263
 
19124
19264
  ### Background Task Discipline
19125
19265
  - Prefer \`task(..., background: true)\` for delegated work that can run independently.
19266
+ - Launch specialist agents in the background by default so the orchestrator stays unblocked and can reconcile results when they return.
19126
19267
  - Track each task's specialist, objective, task/session ID, and file/topic ownership.
19127
19268
  - Continue orchestration only on non-overlapping work; otherwise briefly report what was launched and stop.
19128
19269
  - Before local edits or another writer task, compare against running task scopes.
@@ -19734,6 +19875,39 @@ function normalizeDisplayName(displayName) {
19734
19875
  const trimmed = displayName.trim();
19735
19876
  return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
19736
19877
  }
19878
+ function buildAcpAgentDefinition(name, config) {
19879
+ const description = config.description ?? `External ACP agent '${name}' via ${config.command}`;
19880
+ const prompt = config.prompt ?? [
19881
+ `You are the ${name} ACP wrapper agent.`,
19882
+ "",
19883
+ "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.",
19884
+ `Always call acp_run with agent: ${JSON.stringify(name)} and pass the full user task as prompt.`,
19885
+ "Do not edit files yourself unless the ACP result explicitly asks you to report a local follow-up to the orchestrator."
19886
+ ].join(`
19887
+ `);
19888
+ return {
19889
+ name,
19890
+ description,
19891
+ config: {
19892
+ model: config.wrapperModel ?? DEFAULT_MODELS.fixer ?? DEFAULT_MODELS.librarian ?? DEFAULT_MODELS.orchestrator ?? DEFAULT_MODELS.oracle,
19893
+ temperature: 0,
19894
+ prompt,
19895
+ permission: {
19896
+ read: "deny",
19897
+ edit: "deny",
19898
+ bash: "deny",
19899
+ task: "deny",
19900
+ glob: "deny",
19901
+ grep: "deny",
19902
+ list: "deny",
19903
+ webfetch: "deny",
19904
+ question: "deny",
19905
+ skill: "deny",
19906
+ acp_run: "allow"
19907
+ }
19908
+ }
19909
+ };
19910
+ }
19737
19911
  function isSafeDisplayName(displayName) {
19738
19912
  return SAFE_AGENT_ALIAS_RE.test(displayName);
19739
19913
  }
@@ -19873,6 +20047,24 @@ function createAgents(config) {
19873
20047
  buildCustomAgentDefinition(name, override, customPrompts.prompt, customPrompts.appendPrompt)
19874
20048
  ];
19875
20049
  });
20050
+ const acpAgentNames = getAcpAgentNames(config).map(normalizeCustomAgentName).filter((name) => name.length > 0).filter((name) => {
20051
+ if (!SAFE_AGENT_ALIAS_RE.test(name)) {
20052
+ throw new Error(`ACP agent name '${name}' must match /^[a-z][a-z0-9_-]*$/i`);
20053
+ }
20054
+ if (isKnownAgentName(name) || AGENT_ALIASES[name] !== undefined) {
20055
+ throw new Error(`ACP agent '${name}' conflicts with a built-in agent name or alias`);
20056
+ }
20057
+ if (customAgentNames.includes(name)) {
20058
+ throw new Error(`ACP agent '${name}' conflicts with a custom agent of the same name`);
20059
+ }
20060
+ return !disabled.has(name);
20061
+ });
20062
+ const protoAcpAgents = acpAgentNames.map((name) => {
20063
+ const acp = config?.acpAgents?.[name];
20064
+ if (!acp)
20065
+ throw new Error(`ACP agent '${name}' is missing config`);
20066
+ return buildAcpAgentDefinition(name, acp);
20067
+ });
19876
20068
  const builtInSubAgents = protoSubAgents.map((agent) => {
19877
20069
  const override = getAgentOverride(config, agent.name);
19878
20070
  if (override) {
@@ -19896,7 +20088,15 @@ function createAgents(config) {
19896
20088
  applyDefaultPermissions(agent, override?.skills);
19897
20089
  return agent;
19898
20090
  });
19899
- const allSubAgents = [...builtInSubAgents, ...customSubAgents];
20091
+ const acpSubAgents = protoAcpAgents.map((agent) => {
20092
+ applyDefaultPermissions(agent);
20093
+ return agent;
20094
+ });
20095
+ const allSubAgents = [
20096
+ ...builtInSubAgents,
20097
+ ...customSubAgents,
20098
+ ...acpSubAgents
20099
+ ];
19900
20100
  const orchestratorOverride = getAgentOverride(config, "orchestrator");
19901
20101
  const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
19902
20102
  const orchestratorPrompts = loadAgentPrompt("orchestrator", config?.preset);
@@ -19918,6 +20118,20 @@ function createAgents(config) {
19918
20118
  const override = getAgentOverride(config, agent.name);
19919
20119
  return override?.orchestratorPrompt;
19920
20120
  }).filter((prompt) => Boolean(prompt));
20121
+ const acpOrchestratorPrompts = acpSubAgents.map((agent) => {
20122
+ const acp = config?.acpAgents?.[agent.name];
20123
+ if (acp?.orchestratorPrompt)
20124
+ return acp.orchestratorPrompt;
20125
+ return [
20126
+ `@${agent.name}`,
20127
+ `- Lane: External ACP-connected agent (${acp?.command ?? "unknown command"})`,
20128
+ `- Role: ${agent.description ?? `External ACP agent ${agent.name}`}`,
20129
+ "- **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.",
20130
+ "- **Do not delegate when:** The built-in specialists can handle the task more directly or local file ownership would conflict with another writer lane.",
20131
+ "- **Result handling:** Treat returned output as external-agent work. Reconcile any reported file changes before continuing."
20132
+ ].join(`
20133
+ `);
20134
+ });
19921
20135
  const usedDisplayNames = new Set;
19922
20136
  for (const [, displayName] of displayNameMap) {
19923
20137
  const normalizedDisplayName = normalizeDisplayName(displayName);
@@ -19930,13 +20144,17 @@ function createAgents(config) {
19930
20144
  usedDisplayNames.add(normalizedDisplayName);
19931
20145
  }
19932
20146
  for (const displayName of usedDisplayNames) {
19933
- if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName)) {
20147
+ if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName) || acpAgentNames.includes(displayName)) {
19934
20148
  throw new Error(`displayName '${displayName}' conflicts with an agent name`);
19935
20149
  }
19936
20150
  }
19937
20151
  injectDisplayNames(orchestrator, displayNameMap);
19938
- if (customOrchestratorPrompts.length > 0) {
19939
- const rewrittenPrompts = customOrchestratorPrompts.map((promptText) => {
20152
+ const extraOrchestratorPrompts = [
20153
+ ...customOrchestratorPrompts,
20154
+ ...acpOrchestratorPrompts
20155
+ ];
20156
+ if (extraOrchestratorPrompts.length > 0) {
20157
+ const rewrittenPrompts = extraOrchestratorPrompts.map((promptText) => {
19940
20158
  let text = promptText;
19941
20159
  for (const [internalName, displayName] of displayNameMap) {
19942
20160
  text = text.replace(new RegExp(`@${escapeRegExp(internalName)}\\b`, "g"), `@${normalizeDisplayName(displayName)}`);
@@ -20008,6 +20226,7 @@ import {
20008
20226
  mkdirSync as mkdirSync2,
20009
20227
  readFileSync as readFileSync2,
20010
20228
  renameSync,
20229
+ rmSync,
20011
20230
  writeFileSync
20012
20231
  } from "node:fs";
20013
20232
  import * as os2 from "node:os";
@@ -20096,11 +20315,15 @@ function stateFilePath() {
20096
20315
  const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
20097
20316
  return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
20098
20317
  }
20099
- function binaryPath() {
20318
+ function defaultBinaryPath() {
20100
20319
  const xdg = process.env.XDG_DATA_HOME?.trim();
20101
20320
  const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
20102
20321
  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);
20322
+ return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", binaryName);
20323
+ }
20324
+ function resolveCompanionBinaryPath(config) {
20325
+ const configured = config?.binaryPath?.trim();
20326
+ const bin = configured || defaultBinaryPath();
20104
20327
  return existsSync2(bin) ? bin : null;
20105
20328
  }
20106
20329
  function readState() {
@@ -20113,17 +20336,43 @@ function readState() {
20113
20336
  } catch {}
20114
20337
  return { version: 1, sessions: [] };
20115
20338
  }
20116
- function writeState(state) {
20339
+ function writeState(mutator) {
20117
20340
  const file = stateFilePath();
20118
20341
  try {
20119
20342
  mkdirSync2(path3.dirname(file), { recursive: true });
20120
- const tmp = `${file}.tmp`;
20121
- writeFileSync(tmp, JSON.stringify(state));
20122
- renameSync(tmp, file);
20343
+ const release = acquireStateLock(file);
20344
+ try {
20345
+ const state = readState();
20346
+ mutator(state);
20347
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
20348
+ writeFileSync(tmp, JSON.stringify(state));
20349
+ renameSync(tmp, file);
20350
+ } finally {
20351
+ release();
20352
+ }
20123
20353
  } catch (err) {
20124
20354
  log("[companion] write failed", String(err));
20125
20355
  }
20126
20356
  }
20357
+ function acquireStateLock(file) {
20358
+ const lock = `${file}.lock`;
20359
+ for (let attempt = 0;attempt < 40; attempt++) {
20360
+ try {
20361
+ mkdirSync2(lock);
20362
+ return () => {
20363
+ try {
20364
+ rmSync(lock, { recursive: true, force: true });
20365
+ } catch {}
20366
+ };
20367
+ } catch (err) {
20368
+ const code = err.code;
20369
+ if (code !== "EEXIST")
20370
+ throw err;
20371
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
20372
+ }
20373
+ }
20374
+ throw new Error("timed out waiting for companion state lock");
20375
+ }
20127
20376
 
20128
20377
  class CompanionManager {
20129
20378
  id;
@@ -20131,6 +20380,7 @@ class CompanionManager {
20131
20380
  status = "idle";
20132
20381
  busyAgentSessions = new Map;
20133
20382
  config;
20383
+ companionProcess = null;
20134
20384
  constructor(sessionId, cwd, config) {
20135
20385
  this.id = sessionId;
20136
20386
  this.cwd = cwd;
@@ -20139,12 +20389,11 @@ class CompanionManager {
20139
20389
  onLoad() {
20140
20390
  if (this.config?.enabled !== true) {
20141
20391
  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
- }
20392
+ if (!existsSync2(stateFilePath()))
20393
+ return;
20394
+ writeState((state) => {
20395
+ state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20396
+ });
20148
20397
  } catch {}
20149
20398
  return;
20150
20399
  }
@@ -20196,9 +20445,15 @@ class CompanionManager {
20196
20445
  onExit() {
20197
20446
  if (this.config?.enabled !== true)
20198
20447
  return;
20199
- const state = readState();
20200
- state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20201
- writeState(state);
20448
+ if (this.companionProcess) {
20449
+ try {
20450
+ this.companionProcess.kill();
20451
+ } catch {}
20452
+ this.companionProcess = null;
20453
+ }
20454
+ writeState((state) => {
20455
+ state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
20456
+ });
20202
20457
  }
20203
20458
  activeAgents() {
20204
20459
  const agents = Array.from(this.busyAgentSessions.values());
@@ -20214,28 +20469,41 @@ class CompanionManager {
20214
20469
  if (this.config?.enabled !== true)
20215
20470
  return;
20216
20471
  try {
20217
- const state = readState();
20218
20472
  const entry = {
20219
20473
  session_id: this.id,
20220
20474
  cwd: this.cwd,
20221
20475
  active_agents: this.activeAgents(),
20222
20476
  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 = {
20477
+ pid: process.pid,
20478
+ config: this.config ? {
20233
20479
  enabled: this.config.enabled ?? false,
20234
20480
  position: this.config.position ?? "bottom-right",
20235
- size: this.config.size ?? "medium"
20236
- };
20237
- }
20238
- writeState(state);
20481
+ size: this.config.size ?? "medium",
20482
+ gifPack: this.config.gifPack ?? "default",
20483
+ loopStyle: this.config.loopStyle ?? "classic",
20484
+ speed: this.config.speed ?? 1,
20485
+ debug: this.config.debug ?? false
20486
+ } : undefined
20487
+ };
20488
+ writeState((state) => {
20489
+ const idx = state.sessions.findIndex((s) => s.session_id === this.id);
20490
+ if (idx >= 0) {
20491
+ state.sessions[idx] = entry;
20492
+ } else {
20493
+ state.sessions.push(entry);
20494
+ }
20495
+ if (this.config) {
20496
+ state.config = {
20497
+ enabled: this.config.enabled ?? false,
20498
+ position: this.config.position ?? "bottom-right",
20499
+ size: this.config.size ?? "medium",
20500
+ gifPack: this.config.gifPack ?? "default",
20501
+ loopStyle: this.config.loopStyle ?? "classic",
20502
+ speed: this.config.speed ?? 1,
20503
+ debug: this.config.debug ?? false
20504
+ };
20505
+ }
20506
+ });
20239
20507
  } catch (err) {
20240
20508
  log("[companion] flush failed", String(err));
20241
20509
  }
@@ -20243,24 +20511,359 @@ class CompanionManager {
20243
20511
  spawnIfAvailable() {
20244
20512
  if (this.config?.enabled !== true)
20245
20513
  return;
20246
- const bin = binaryPath();
20514
+ const bin = resolveCompanionBinaryPath(this.config);
20247
20515
  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");
20516
+ const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
20251
20517
  log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
20252
20518
  return;
20253
20519
  }
20254
20520
  try {
20255
- const child = spawn(bin, [], { detached: true, stdio: "ignore" });
20521
+ const child = spawn(bin, [], {
20522
+ detached: true,
20523
+ env: {
20524
+ ...process.env,
20525
+ OH_MY_OPENCODE_SLIM_COMPANION_SESSION_ID: this.id,
20526
+ ...this.config.debug === true ? { OH_MY_OPENCODE_SLIM_COMPANION_DEBUG: "1" } : {}
20527
+ },
20528
+ stdio: "ignore"
20529
+ });
20530
+ this.companionProcess = child;
20256
20531
  child.unref();
20257
- log("[companion] spawned", bin);
20532
+ log("[companion] spawned", JSON.stringify({
20533
+ bin,
20534
+ sessionId: this.id,
20535
+ debug: this.config.debug === true
20536
+ }));
20258
20537
  } catch (err) {
20259
20538
  log("[companion] spawn failed", String(err));
20260
20539
  }
20261
20540
  }
20262
20541
  }
20263
20542
 
20543
+ // src/companion/updater.ts
20544
+ init_compat();
20545
+ import { createHash } from "node:crypto";
20546
+ import {
20547
+ chmodSync,
20548
+ copyFileSync,
20549
+ existsSync as existsSync3,
20550
+ mkdirSync as mkdirSync3,
20551
+ mkdtempSync,
20552
+ readFileSync as readFileSync3,
20553
+ renameSync as renameSync2,
20554
+ rmSync as rmSync2,
20555
+ statSync as statSync2,
20556
+ writeFileSync as writeFileSync2
20557
+ } from "node:fs";
20558
+ import { homedir as homedir4, platform as platform2, tmpdir } from "node:os";
20559
+ import * as path4 from "node:path";
20560
+ import { setTimeout as delay } from "node:timers/promises";
20561
+ var DOWNLOAD_TIMEOUT_MS = 30000;
20562
+ var LOCK_TIMEOUT_MS = 2000;
20563
+ var STALE_LOCK_MS = 5 * 60000;
20564
+ var FIRST_METADATA_VERSION = "0.1.2";
20565
+ var COMPANION_MANIFEST = {
20566
+ version: "0.1.3",
20567
+ tag: "companion-v0.1.3",
20568
+ repo: "alvinunreal/oh-my-opencode-slim",
20569
+ checksums: {
20570
+ "oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
20571
+ "oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
20572
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
20573
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
20574
+ "oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
20575
+ }
20576
+ };
20577
+ function getCompanionTarget() {
20578
+ const p = process.platform;
20579
+ const a = process.arch;
20580
+ if (p === "darwin") {
20581
+ if (a === "arm64")
20582
+ return "aarch64-apple-darwin";
20583
+ if (a === "x64")
20584
+ return "x86_64-apple-darwin";
20585
+ } else if (p === "linux") {
20586
+ if (a === "x64")
20587
+ return "x86_64-unknown-linux-gnu";
20588
+ if (a === "arm64")
20589
+ return "aarch64-unknown-linux-gnu";
20590
+ } else if (p === "win32") {
20591
+ if (a === "x64")
20592
+ return "x86_64-pc-windows-msvc";
20593
+ }
20594
+ return null;
20595
+ }
20596
+ function getCompanionBinaryPath() {
20597
+ const xdg = process.env.XDG_DATA_HOME?.trim();
20598
+ const base = xdg && path4.isAbsolute(xdg) ? xdg : path4.join(homedir4(), ".local", "share");
20599
+ return path4.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", platform2() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
20600
+ }
20601
+ function loadCompanionManifestFromPackageRoot(packageRoot) {
20602
+ const manifestPath = path4.join(packageRoot, "src", "companion", "companion-manifest.json");
20603
+ try {
20604
+ const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
20605
+ if (parsed.version && parsed.tag && parsed.repo) {
20606
+ return {
20607
+ version: parsed.version,
20608
+ tag: parsed.tag,
20609
+ repo: parsed.repo,
20610
+ checksums: parsed.checksums
20611
+ };
20612
+ }
20613
+ } catch {}
20614
+ return null;
20615
+ }
20616
+ async function ensureCompanionVersion(options) {
20617
+ const { config, dryRun = false } = options;
20618
+ const manifest = options.manifest ?? COMPANION_MANIFEST;
20619
+ const binaryPath = getCompanionBinaryPath();
20620
+ if (config?.enabled !== true) {
20621
+ return { status: "skipped", reason: "disabled", binaryPath };
20622
+ }
20623
+ if (config.binaryPath?.trim()) {
20624
+ return { status: "skipped", reason: "custom-binary", binaryPath };
20625
+ }
20626
+ const target = getCompanionTarget();
20627
+ if (!target) {
20628
+ return {
20629
+ status: "failed",
20630
+ binaryPath,
20631
+ error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
20632
+ };
20633
+ }
20634
+ const current = readInstallMetadata(binaryPath);
20635
+ if (existsSync3(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
20636
+ const archiveName = companionArchiveName(manifest.version, target);
20637
+ writeInstallMetadata(binaryPath, {
20638
+ version: manifest.version,
20639
+ tag: manifest.tag,
20640
+ target,
20641
+ installedAt: new Date().toISOString(),
20642
+ archiveName,
20643
+ checksum: manifest.checksums?.[archiveName]
20644
+ });
20645
+ return { status: "current", binaryPath, version: manifest.version };
20646
+ }
20647
+ if (existsSync3(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
20648
+ return { status: "current", binaryPath, version: current.version };
20649
+ }
20650
+ if (dryRun) {
20651
+ return { status: "installed", binaryPath, version: manifest.version };
20652
+ }
20653
+ return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
20654
+ const lockedCurrent = readInstallMetadata(binaryPath);
20655
+ if (existsSync3(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
20656
+ const archiveName = companionArchiveName(manifest.version, target);
20657
+ writeInstallMetadata(binaryPath, {
20658
+ version: manifest.version,
20659
+ tag: manifest.tag,
20660
+ target,
20661
+ installedAt: new Date().toISOString(),
20662
+ archiveName,
20663
+ checksum: manifest.checksums?.[archiveName]
20664
+ });
20665
+ return { status: "current", binaryPath, version: manifest.version };
20666
+ }
20667
+ if (existsSync3(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
20668
+ return {
20669
+ status: "current",
20670
+ binaryPath,
20671
+ version: lockedCurrent.version
20672
+ };
20673
+ }
20674
+ return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
20675
+ });
20676
+ }
20677
+ async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
20678
+ const isWindows = process.platform === "win32";
20679
+ const archiveName = companionArchiveName(manifest.version, target, isWindows);
20680
+ const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
20681
+ const expectedChecksum = manifest.checksums?.[archiveName];
20682
+ if (!expectedChecksum) {
20683
+ return {
20684
+ status: "failed",
20685
+ binaryPath: finalBinaryPath,
20686
+ error: `Missing SHA256 checksum for companion archive: ${archiveName}`
20687
+ };
20688
+ }
20689
+ let buffer;
20690
+ const controller = new AbortController;
20691
+ const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
20692
+ try {
20693
+ const res = await fetch(downloadUrl, { signal: controller.signal });
20694
+ if (!res.ok) {
20695
+ return {
20696
+ status: "failed",
20697
+ binaryPath: finalBinaryPath,
20698
+ error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
20699
+ };
20700
+ }
20701
+ buffer = await res.arrayBuffer();
20702
+ } catch (err) {
20703
+ return {
20704
+ status: "failed",
20705
+ binaryPath: finalBinaryPath,
20706
+ error: `Failed to fetch companion archive: ${formatError(err)}`
20707
+ };
20708
+ } finally {
20709
+ clearTimeout(timeout);
20710
+ }
20711
+ const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
20712
+ if (checksum !== expectedChecksum) {
20713
+ return {
20714
+ status: "failed",
20715
+ binaryPath: finalBinaryPath,
20716
+ error: "Companion archive checksum mismatch"
20717
+ };
20718
+ }
20719
+ let tempDir = "";
20720
+ try {
20721
+ tempDir = mkdtempSync(path4.join(tmpdir(), "companion-install-"));
20722
+ const archivePath = path4.join(tempDir, archiveName);
20723
+ writeFileSync2(archivePath, Buffer.from(buffer));
20724
+ const extractedDir = path4.join(tempDir, "extracted");
20725
+ mkdirSync3(extractedDir, { recursive: true });
20726
+ if (isWindows) {
20727
+ const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
20728
+ await extractZip2(archivePath, extractedDir);
20729
+ } else {
20730
+ const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
20731
+ const exitCode = await proc.exited;
20732
+ if (exitCode !== 0) {
20733
+ const stderr = await proc.stderr();
20734
+ return {
20735
+ status: "failed",
20736
+ binaryPath: finalBinaryPath,
20737
+ error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
20738
+ };
20739
+ }
20740
+ }
20741
+ const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
20742
+ const extractedBinaryPath = path4.join(extractedDir, binaryName);
20743
+ if (!existsSync3(extractedBinaryPath)) {
20744
+ return {
20745
+ status: "failed",
20746
+ binaryPath: finalBinaryPath,
20747
+ error: `Binary ${binaryName} not found in extracted archive`
20748
+ };
20749
+ }
20750
+ const binDir = path4.dirname(finalBinaryPath);
20751
+ mkdirSync3(binDir, { recursive: true });
20752
+ const tmpFinalPath = `${finalBinaryPath}.tmp`;
20753
+ copyFileSync(extractedBinaryPath, tmpFinalPath);
20754
+ if (!isWindows) {
20755
+ chmodSync(tmpFinalPath, 493);
20756
+ }
20757
+ renameSync2(tmpFinalPath, finalBinaryPath);
20758
+ writeInstallMetadata(finalBinaryPath, {
20759
+ version: manifest.version,
20760
+ tag: manifest.tag,
20761
+ target,
20762
+ installedAt: new Date().toISOString(),
20763
+ archiveName,
20764
+ checksum
20765
+ });
20766
+ return {
20767
+ status: "installed",
20768
+ binaryPath: finalBinaryPath,
20769
+ version: manifest.version
20770
+ };
20771
+ } catch (err) {
20772
+ return {
20773
+ status: "failed",
20774
+ binaryPath: finalBinaryPath,
20775
+ error: `Failed to install companion: ${formatError(err)}`
20776
+ };
20777
+ } finally {
20778
+ if (tempDir) {
20779
+ try {
20780
+ rmSync2(tempDir, { recursive: true, force: true });
20781
+ } catch {}
20782
+ }
20783
+ }
20784
+ }
20785
+ function readInstallMetadata(binaryPath) {
20786
+ try {
20787
+ const parsed = JSON.parse(readFileSync3(metadataPath(binaryPath), "utf8"));
20788
+ if (parsed?.version && parsed.tag && parsed.target) {
20789
+ return parsed;
20790
+ }
20791
+ } catch {}
20792
+ return null;
20793
+ }
20794
+ function writeInstallMetadata(binaryPath, metadata) {
20795
+ writeFileSync2(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
20796
+ }
20797
+ function metadataPath(binaryPath) {
20798
+ return `${binaryPath}.json`;
20799
+ }
20800
+ async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
20801
+ const lock = `${binaryPath}.lock`;
20802
+ const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
20803
+ const staleAfterMs = staleMs ?? STALE_LOCK_MS;
20804
+ mkdirSync3(path4.dirname(binaryPath), { recursive: true });
20805
+ while (Date.now() <= deadline) {
20806
+ try {
20807
+ mkdirSync3(lock);
20808
+ try {
20809
+ return await run();
20810
+ } finally {
20811
+ try {
20812
+ rmSync2(lock, { recursive: true, force: true });
20813
+ } catch {}
20814
+ }
20815
+ } catch (err) {
20816
+ const code = err.code;
20817
+ if (code !== "EEXIST")
20818
+ throw err;
20819
+ try {
20820
+ const ageMs = Date.now() - statSync2(lock).mtimeMs;
20821
+ if (ageMs > staleAfterMs) {
20822
+ rmSync2(lock, { recursive: true, force: true });
20823
+ log("[companion] removed stale install lock", lock);
20824
+ continue;
20825
+ }
20826
+ } catch (statErr) {
20827
+ const statCode = statErr.code;
20828
+ if (statCode !== "ENOENT")
20829
+ throw statErr;
20830
+ }
20831
+ await delay(25);
20832
+ }
20833
+ }
20834
+ log("[companion] install lock timed out", lock);
20835
+ return {
20836
+ status: "failed",
20837
+ binaryPath,
20838
+ error: "Timed out waiting for companion install lock"
20839
+ };
20840
+ }
20841
+ function companionArchiveName(version, target, isWindows = process.platform === "win32") {
20842
+ const ext = isWindows ? "zip" : "tar.gz";
20843
+ return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
20844
+ }
20845
+ function compareSemver(a, b) {
20846
+ const left = parseSemver(a);
20847
+ const right = parseSemver(b);
20848
+ if (!left || !right)
20849
+ return a.localeCompare(b);
20850
+ for (let i = 0;i < 3; i++) {
20851
+ const diff = left[i] - right[i];
20852
+ if (diff !== 0)
20853
+ return diff;
20854
+ }
20855
+ return 0;
20856
+ }
20857
+ function parseSemver(version) {
20858
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
20859
+ if (!match)
20860
+ return null;
20861
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
20862
+ }
20863
+ function formatError(err) {
20864
+ return err instanceof Error ? err.message : String(err);
20865
+ }
20866
+
20264
20867
  // src/config/runtime-preset.ts
20265
20868
  var activeRuntimePreset = null;
20266
20869
  function setActiveRuntimePreset(name) {
@@ -20597,7 +21200,7 @@ function ensureApplyPatchError(error, context) {
20597
21200
 
20598
21201
  // src/hooks/apply-patch/execution-context.ts
20599
21202
  import * as fs3 from "node:fs/promises";
20600
- import path4 from "node:path";
21203
+ import path5 from "node:path";
20601
21204
 
20602
21205
  // src/hooks/apply-patch/codec.ts
20603
21206
  function normalizeLineEndings(text) {
@@ -21466,7 +22069,7 @@ function isMissingPathError(error) {
21466
22069
  }
21467
22070
  async function real(target) {
21468
22071
  const parts = [];
21469
- let current = path4.resolve(target);
22072
+ let current = path5.resolve(target);
21470
22073
  while (true) {
21471
22074
  const exact = await fs3.realpath(current).catch((error) => {
21472
22075
  if (isMissingPathError(error)) {
@@ -21475,19 +22078,19 @@ async function real(target) {
21475
22078
  throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
21476
22079
  });
21477
22080
  if (exact) {
21478
- return parts.length === 0 ? exact : path4.join(exact, ...parts.reverse());
22081
+ return parts.length === 0 ? exact : path5.join(exact, ...parts.reverse());
21479
22082
  }
21480
- const parent = path4.dirname(current);
22083
+ const parent = path5.dirname(current);
21481
22084
  if (parent === current) {
21482
- return parts.length === 0 ? current : path4.join(current, ...parts.reverse());
22085
+ return parts.length === 0 ? current : path5.join(current, ...parts.reverse());
21483
22086
  }
21484
- parts.push(path4.basename(current));
22087
+ parts.push(path5.basename(current));
21485
22088
  current = parent;
21486
22089
  }
21487
22090
  }
21488
22091
  function inside(root, target) {
21489
- const rel = path4.relative(root, target);
21490
- return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
22092
+ const rel = path5.relative(root, target);
22093
+ return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
21491
22094
  }
21492
22095
  function createPathGuardContext(root, worktree) {
21493
22096
  return {
@@ -21497,7 +22100,7 @@ function createPathGuardContext(root, worktree) {
21497
22100
  };
21498
22101
  }
21499
22102
  async function realCached(ctx, target) {
21500
- const resolvedTarget = path4.resolve(target);
22103
+ const resolvedTarget = path5.resolve(target);
21501
22104
  let pending = ctx.realCache.get(resolvedTarget);
21502
22105
  if (!pending) {
21503
22106
  pending = real(resolvedTarget);
@@ -21548,22 +22151,22 @@ async function assertRegularFile(ctx, filePath, verb) {
21548
22151
  function collectPatchTargets(root, hunks) {
21549
22152
  const targets = new Set;
21550
22153
  for (const hunk of hunks) {
21551
- targets.add(path4.resolve(root, hunk.path));
22154
+ targets.add(path5.resolve(root, hunk.path));
21552
22155
  if (hunk.type === "update" && hunk.move_path) {
21553
- targets.add(path4.resolve(root, hunk.move_path));
22156
+ targets.add(path5.resolve(root, hunk.move_path));
21554
22157
  }
21555
22158
  }
21556
22159
  return [...targets];
21557
22160
  }
21558
22161
  function toRelativePatchPath(root, target) {
21559
- const relative = path4.relative(root, target);
22162
+ const relative = path5.relative(root, target);
21560
22163
  return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
21561
22164
  }
21562
22165
  function normalizePatchPath(root, value) {
21563
- return path4.isAbsolute(value) ? toRelativePatchPath(root, path4.resolve(value)) : value;
22166
+ return path5.isAbsolute(value) ? toRelativePatchPath(root, path5.resolve(value)) : value;
21564
22167
  }
21565
22168
  function normalizePatchPaths(root, hunks) {
21566
- const resolvedRoot = path4.resolve(root);
22169
+ const resolvedRoot = path5.resolve(root);
21567
22170
  const normalized = [];
21568
22171
  let changed = false;
21569
22172
  for (const hunk of hunks) {
@@ -21687,7 +22290,7 @@ function stageAddedText(contents) {
21687
22290
  `;
21688
22291
  }
21689
22292
  // src/hooks/apply-patch/rewrite.ts
21690
- import path5 from "node:path";
22293
+ import path6 from "node:path";
21691
22294
  function normalizeTextLineEndings(text) {
21692
22295
  return text.replace(/\r\n/g, `
21693
22296
  `).replace(/\r/g, `
@@ -21844,7 +22447,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
21844
22447
  const dependencyGroups = new Map;
21845
22448
  for (const hunk of hunks) {
21846
22449
  if (hunk.type === "add") {
21847
- const filePath2 = path5.resolve(root, hunk.path);
22450
+ const filePath2 = path6.resolve(root, hunk.path);
21848
22451
  await assertPreparedPathMissing(filePath2, "add");
21849
22452
  rewritten.push(hunk);
21850
22453
  clearDependencyGroup(filePath2);
@@ -21866,20 +22469,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
21866
22469
  continue;
21867
22470
  }
21868
22471
  if (hunk.type === "delete") {
21869
- const filePath2 = path5.resolve(root, hunk.path);
22472
+ const filePath2 = path6.resolve(root, hunk.path);
21870
22473
  await getPreparedFileState(filePath2, "delete");
21871
22474
  clearDependencyGroup(filePath2);
21872
22475
  rewritten.push(hunk);
21873
22476
  staged.set(filePath2, { exists: false, derived: true });
21874
22477
  continue;
21875
22478
  }
21876
- const filePath = path5.resolve(root, hunk.path);
22479
+ const filePath = path6.resolve(root, hunk.path);
21877
22480
  const currentDependency = dependencyGroups.get(filePath);
21878
22481
  const current = await getPreparedFileState(filePath, "update");
21879
22482
  if (!current.exists) {
21880
22483
  throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
21881
22484
  }
21882
- const movePath = hunk.move_path ? path5.resolve(root, hunk.move_path) : undefined;
22485
+ const movePath = hunk.move_path ? path6.resolve(root, hunk.move_path) : undefined;
21883
22486
  if (movePath && movePath !== filePath) {
21884
22487
  await assertPreparedPathMissing(movePath, "move");
21885
22488
  }
@@ -22045,31 +22648,34 @@ function createApplyPatchHook(ctx) {
22045
22648
  };
22046
22649
  }
22047
22650
  // src/hooks/auto-update-checker/index.ts
22048
- import * as path10 from "node:path";
22651
+ import * as path11 from "node:path";
22652
+ init_compat();
22049
22653
 
22050
22654
  // src/hooks/auto-update-checker/cache.ts
22051
22655
  import * as fs5 from "node:fs";
22052
- import * as path8 from "node:path";
22656
+ import * as path9 from "node:path";
22657
+ // src/cli/system.ts
22658
+ init_compat();
22053
22659
  // src/hooks/auto-update-checker/checker.ts
22054
22660
  import * as fs4 from "node:fs";
22055
- import * as path7 from "node:path";
22661
+ import * as path8 from "node:path";
22056
22662
  import { fileURLToPath } from "node:url";
22057
22663
 
22058
22664
  // src/hooks/auto-update-checker/constants.ts
22059
22665
  import * as os3 from "node:os";
22060
- import * as path6 from "node:path";
22666
+ import * as path7 from "node:path";
22061
22667
  var PACKAGE_NAME = "oh-my-opencode-slim";
22062
22668
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
22063
22669
  var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
22064
22670
  var NPM_FETCH_TIMEOUT = 5000;
22065
22671
  function getCacheDir() {
22066
22672
  if (process.platform === "win32") {
22067
- return path6.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
22673
+ return path7.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
22068
22674
  }
22069
- return path6.join(os3.homedir(), ".cache", "opencode");
22675
+ return path7.join(os3.homedir(), ".cache", "opencode");
22070
22676
  }
22071
22677
  var CACHE_DIR = getCacheDir();
22072
- var INSTALLED_PACKAGE_JSON = path6.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
22678
+ var INSTALLED_PACKAGE_JSON = path7.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
22073
22679
  var configPaths = getOpenCodeConfigPaths();
22074
22680
  var USER_OPENCODE_CONFIG = configPaths[0];
22075
22681
  var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
@@ -22181,8 +22787,8 @@ function extractChannel(version) {
22181
22787
  }
22182
22788
  function getConfigPaths(directory) {
22183
22789
  return [
22184
- path7.join(directory, ".opencode", "opencode.json"),
22185
- path7.join(directory, ".opencode", "opencode.jsonc"),
22790
+ path8.join(directory, ".opencode", "opencode.json"),
22791
+ path8.join(directory, ".opencode", "opencode.jsonc"),
22186
22792
  USER_OPENCODE_CONFIG,
22187
22793
  USER_OPENCODE_CONFIG_JSONC
22188
22794
  ];
@@ -22211,9 +22817,9 @@ function getLocalDevPath(directory) {
22211
22817
  function findPackageJsonUp(startPath) {
22212
22818
  try {
22213
22819
  const stat2 = fs4.statSync(startPath);
22214
- let dir = stat2.isDirectory() ? startPath : path7.dirname(startPath);
22820
+ let dir = stat2.isDirectory() ? startPath : path8.dirname(startPath);
22215
22821
  for (let i = 0;i < 10; i++) {
22216
- const pkgPath = path7.join(dir, "package.json");
22822
+ const pkgPath = path8.join(dir, "package.json");
22217
22823
  if (fs4.existsSync(pkgPath)) {
22218
22824
  try {
22219
22825
  const content = fs4.readFileSync(pkgPath, "utf-8");
@@ -22222,7 +22828,7 @@ function findPackageJsonUp(startPath) {
22222
22828
  return pkgPath;
22223
22829
  } catch {}
22224
22830
  }
22225
- const parent = path7.dirname(dir);
22831
+ const parent = path8.dirname(dir);
22226
22832
  if (parent === dir)
22227
22833
  break;
22228
22834
  dir = parent;
@@ -22247,7 +22853,7 @@ function getLocalDevVersion(directory) {
22247
22853
  }
22248
22854
  function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
22249
22855
  try {
22250
- const currentDir = path7.dirname(fileURLToPath(currentModuleUrl));
22856
+ const currentDir = path8.dirname(fileURLToPath(currentModuleUrl));
22251
22857
  return findPackageJsonUp(currentDir);
22252
22858
  } catch (err) {
22253
22859
  log("[auto-update-checker] Failed to resolve runtime package path:", err);
@@ -22406,7 +23012,7 @@ function getBlockingMajorVersion(current, candidates) {
22406
23012
 
22407
23013
  // src/hooks/auto-update-checker/cache.ts
22408
23014
  function removeFromBunLock(installDir, packageName) {
22409
- const lockPath = path8.join(installDir, "bun.lock");
23015
+ const lockPath = path9.join(installDir, "bun.lock");
22410
23016
  if (!fs5.existsSync(lockPath))
22411
23017
  return false;
22412
23018
  try {
@@ -22457,7 +23063,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
22457
23063
  }
22458
23064
  }
22459
23065
  function removeInstalledPackage(installDir, packageName) {
22460
- const pkgDir = path8.join(installDir, "node_modules", packageName);
23066
+ const pkgDir = path9.join(installDir, "node_modules", packageName);
22461
23067
  if (!fs5.existsSync(pkgDir))
22462
23068
  return false;
22463
23069
  fs5.rmSync(pkgDir, { recursive: true, force: true });
@@ -22466,18 +23072,18 @@ function removeInstalledPackage(installDir, packageName) {
22466
23072
  }
22467
23073
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
22468
23074
  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");
23075
+ const packageDir = path9.dirname(runtimePackageJsonPath);
23076
+ const nodeModulesDir = path9.dirname(packageDir);
23077
+ if (path9.basename(packageDir) === PACKAGE_NAME && path9.basename(nodeModulesDir) === "node_modules") {
23078
+ const installDir = path9.dirname(nodeModulesDir);
23079
+ const packageJsonPath = path9.join(installDir, "package.json");
22474
23080
  if (fs5.existsSync(packageJsonPath)) {
22475
23081
  return { installDir, packageJsonPath };
22476
23082
  }
22477
23083
  }
22478
23084
  return null;
22479
23085
  }
22480
- const legacyPackageJsonPath = path8.join(CACHE_DIR, "package.json");
23086
+ const legacyPackageJsonPath = path9.join(CACHE_DIR, "package.json");
22481
23087
  if (fs5.existsSync(legacyPackageJsonPath)) {
22482
23088
  return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
22483
23089
  }
@@ -22508,40 +23114,40 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
22508
23114
 
22509
23115
  // src/hooks/auto-update-checker/skill-sync.ts
22510
23116
  import {
22511
- copyFileSync,
22512
- existsSync as existsSync5,
23117
+ copyFileSync as copyFileSync2,
23118
+ existsSync as existsSync6,
22513
23119
  lstatSync,
22514
- mkdirSync as mkdirSync3,
22515
- mkdtempSync,
23120
+ mkdirSync as mkdirSync4,
23121
+ mkdtempSync as mkdtempSync2,
22516
23122
  readdirSync as readdirSync2,
22517
- renameSync as renameSync2,
22518
- rmSync as rmSync2
23123
+ renameSync as renameSync3,
23124
+ rmSync as rmSync4
22519
23125
  } from "node:fs";
22520
- import * as path9 from "node:path";
23126
+ import * as path10 from "node:path";
22521
23127
  function copyDirRecursive(src, dest) {
22522
23128
  const stat2 = lstatSync(src);
22523
23129
  if (stat2.isSymbolicLink()) {
22524
23130
  return;
22525
23131
  }
22526
23132
  if (stat2.isDirectory()) {
22527
- mkdirSync3(dest, { recursive: true });
23133
+ mkdirSync4(dest, { recursive: true });
22528
23134
  const entries = readdirSync2(src);
22529
23135
  for (const entry of entries) {
22530
- copyDirRecursive(path9.join(src, entry), path9.join(dest, entry));
23136
+ copyDirRecursive(path10.join(src, entry), path10.join(dest, entry));
22531
23137
  }
22532
23138
  } else if (stat2.isFile()) {
22533
- const destDir = path9.dirname(dest);
22534
- if (!existsSync5(destDir)) {
22535
- mkdirSync3(destDir, { recursive: true });
23139
+ const destDir = path10.dirname(dest);
23140
+ if (!existsSync6(destDir)) {
23141
+ mkdirSync4(destDir, { recursive: true });
22536
23142
  }
22537
- copyFileSync(src, dest);
23143
+ copyFileSync2(src, dest);
22538
23144
  }
22539
23145
  }
22540
23146
  function syncBundledSkillsFromPackage(packageRoot) {
22541
23147
  const installed = [];
22542
23148
  const skippedExisting = [];
22543
23149
  const failed = [];
22544
- const sourceSkillsDir = path9.join(packageRoot, "src", "skills");
23150
+ const sourceSkillsDir = path10.join(packageRoot, "src", "skills");
22545
23151
  try {
22546
23152
  const stat2 = lstatSync(sourceSkillsDir);
22547
23153
  if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
@@ -22552,10 +23158,10 @@ function syncBundledSkillsFromPackage(packageRoot) {
22552
23158
  log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
22553
23159
  return { installed, skippedExisting, failed };
22554
23160
  }
22555
- const destSkillsDir = path9.join(getConfigDir(), "skills");
23161
+ const destSkillsDir = path10.join(getConfigDir(), "skills");
22556
23162
  try {
22557
- if (!existsSync5(destSkillsDir)) {
22558
- mkdirSync3(destSkillsDir, { recursive: true });
23163
+ if (!existsSync6(destSkillsDir)) {
23164
+ mkdirSync4(destSkillsDir, { recursive: true });
22559
23165
  }
22560
23166
  } catch (err) {
22561
23167
  log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
@@ -22568,7 +23174,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22568
23174
  return { installed, skippedExisting, failed };
22569
23175
  }
22570
23176
  for (const entry of entries) {
22571
- const entryPath = path9.join(sourceSkillsDir, entry);
23177
+ const entryPath = path10.join(sourceSkillsDir, entry);
22572
23178
  try {
22573
23179
  if (entry.startsWith(".")) {
22574
23180
  continue;
@@ -22577,7 +23183,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22577
23183
  if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
22578
23184
  continue;
22579
23185
  }
22580
- const skillMdPath = path9.join(entryPath, "SKILL.md");
23186
+ const skillMdPath = path10.join(entryPath, "SKILL.md");
22581
23187
  try {
22582
23188
  const skillMdStat = lstatSync(skillMdPath);
22583
23189
  if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
@@ -22586,7 +23192,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22586
23192
  } catch {
22587
23193
  continue;
22588
23194
  }
22589
- const destPath = path9.join(destSkillsDir, entry);
23195
+ const destPath = path10.join(destSkillsDir, entry);
22590
23196
  let destExists = false;
22591
23197
  try {
22592
23198
  lstatSync(destPath);
@@ -22597,7 +23203,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22597
23203
  skippedExisting.push(entry);
22598
23204
  continue;
22599
23205
  }
22600
- const stagingDir = mkdtempSync(path9.join(destSkillsDir, `.sync-staging-${entry}-`));
23206
+ const stagingDir = mkdtempSync2(path10.join(destSkillsDir, `.sync-staging-${entry}-`));
22601
23207
  try {
22602
23208
  copyDirRecursive(entryPath, stagingDir);
22603
23209
  let destExistsLate = false;
@@ -22609,7 +23215,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22609
23215
  log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
22610
23216
  skippedExisting.push(entry);
22611
23217
  } else {
22612
- renameSync2(stagingDir, destPath);
23218
+ renameSync3(stagingDir, destPath);
22613
23219
  installed.push(entry);
22614
23220
  log(`[skill-sync] Successfully synced skill: ${entry}`);
22615
23221
  }
@@ -22618,8 +23224,8 @@ function syncBundledSkillsFromPackage(packageRoot) {
22618
23224
  failed.push(entry);
22619
23225
  } finally {
22620
23226
  try {
22621
- if (existsSync5(stagingDir)) {
22622
- rmSync2(stagingDir, { recursive: true, force: true });
23227
+ if (existsSync6(stagingDir)) {
23228
+ rmSync4(stagingDir, { recursive: true, force: true });
22623
23229
  }
22624
23230
  } catch (err) {
22625
23231
  log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
@@ -22635,7 +23241,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
22635
23241
 
22636
23242
  // src/hooks/auto-update-checker/index.ts
22637
23243
  function createAutoUpdateCheckerHook(ctx, options = {}) {
22638
- const { autoUpdate = true } = options;
23244
+ const { autoUpdate = true, companion } = options;
22639
23245
  let hasChecked = false;
22640
23246
  return {
22641
23247
  event: ({ event }) => {
@@ -22653,14 +23259,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
22653
23259
  log("[auto-update-checker] Local development mode");
22654
23260
  return;
22655
23261
  }
22656
- runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
23262
+ runBackgroundUpdateCheck(ctx, autoUpdate, companion).catch((err) => {
22657
23263
  log("[auto-update-checker] Background update check failed:", err);
22658
23264
  });
22659
23265
  }, 0);
22660
23266
  }
22661
23267
  };
22662
23268
  }
22663
- async function runBackgroundUpdateCheck(ctx, autoUpdate) {
23269
+ async function runBackgroundUpdateCheck(ctx, autoUpdate, companion) {
22664
23270
  const pluginInfo = findPluginEntry(ctx.directory);
22665
23271
  if (!pluginInfo) {
22666
23272
  log("[auto-update-checker] Plugin not found in config");
@@ -22716,8 +23322,10 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
22716
23322
  const installSuccess = await runBunInstallSafe(installDir);
22717
23323
  if (installSuccess) {
22718
23324
  let installedSkills = [];
23325
+ let companionUpdated = false;
23326
+ let companionWillRetry = false;
23327
+ const packageRoot = path11.join(installDir, "node_modules", PACKAGE_NAME);
22719
23328
  try {
22720
- const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
22721
23329
  const syncResult = syncBundledSkillsFromPackage(packageRoot);
22722
23330
  installedSkills = syncResult.installed;
22723
23331
  if (syncResult.failed.length > 0) {
@@ -22729,14 +23337,38 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
22729
23337
  } catch (err) {
22730
23338
  log("[auto-update-checker] Skill sync failed silently:", err);
22731
23339
  }
22732
- let message = `v${currentVersion} → v${latestVersion}
22733
- Restart OpenCode to apply.`;
23340
+ if (companion?.enabled === true) {
23341
+ try {
23342
+ const manifest = loadCompanionManifestFromPackageRoot(packageRoot);
23343
+ const companionResult = await ensureCompanionVersion({
23344
+ config: companion,
23345
+ manifest: manifest ?? undefined
23346
+ });
23347
+ if (companionResult.status === "installed") {
23348
+ companionUpdated = true;
23349
+ } else if (companionResult.status === "failed") {
23350
+ companionWillRetry = true;
23351
+ log("[auto-update-checker] Companion update failed; will retry on restart:", companionResult.error);
23352
+ } else if (companionResult.status === "skipped") {
23353
+ log("[auto-update-checker] Companion update skipped:", companionResult.reason);
23354
+ }
23355
+ } catch (err) {
23356
+ companionWillRetry = true;
23357
+ log("[auto-update-checker] Companion update failed silently; will retry on restart:", err);
23358
+ }
23359
+ }
23360
+ const messageLines = [`v${currentVersion} → v${latestVersion}`];
22734
23361
  if (installedSkills.length > 0) {
22735
- message = `v${currentVersion} → v${latestVersion}
22736
- Added bundled skills: ${installedSkills.join(", ")}
22737
- Restart OpenCode to apply.`;
23362
+ messageLines.push(`Added bundled skills: ${installedSkills.join(", ")}`);
23363
+ }
23364
+ if (companionUpdated) {
23365
+ messageLines.push("Companion updated.");
23366
+ } else if (companionWillRetry) {
23367
+ messageLines.push("Companion update will retry on restart.");
22738
23368
  }
22739
- showToast(ctx, "OMO-Slim Updated!", message, "success", 8000);
23369
+ messageLines.push("Restart OpenCode to apply.");
23370
+ showToast(ctx, "OMO-Slim Updated!", messageLines.join(`
23371
+ `), "success", 8000);
22740
23372
  log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
22741
23373
  } else {
22742
23374
  showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
@@ -23119,7 +23751,7 @@ class BackgroundJobBoard {
23119
23751
  existing.set(file.path, { ...file });
23120
23752
  }
23121
23753
  }
23122
- const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, this.readContextMaxFiles + 1);
23754
+ 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
23755
  this.jobs.set(taskID, { ...job, contextFiles });
23124
23756
  }
23125
23757
  list(parentSessionID) {
@@ -23264,86 +23896,10 @@ function hasInternalInitiatorMarker(part) {
23264
23896
  }
23265
23897
  return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
23266
23898
  }
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
- }
23899
+
23900
+ // src/utils/index.ts
23901
+ init_zip_extractor();
23902
+
23347
23903
  // src/hooks/chat-headers.ts
23348
23904
  var INTERNAL_MARKER_CACHE_LIMIT = 1000;
23349
23905
  var internalMarkerCache = new Map;
@@ -23848,17 +24404,17 @@ class ForegroundFallbackManager {
23848
24404
  }
23849
24405
  }
23850
24406
  // src/hooks/image-hook.ts
23851
- import { createHash } from "node:crypto";
24407
+ import { createHash as createHash2 } from "node:crypto";
23852
24408
  import {
23853
- existsSync as existsSync6,
23854
- mkdirSync as mkdirSync4,
24409
+ existsSync as existsSync7,
24410
+ mkdirSync as mkdirSync5,
23855
24411
  readdirSync as readdirSync3,
23856
24412
  rmdirSync,
23857
- statSync as statSync3,
24413
+ statSync as statSync4,
23858
24414
  unlinkSync as unlinkSync2,
23859
- writeFileSync as writeFileSync4
24415
+ writeFileSync as writeFileSync5
23860
24416
  } from "node:fs";
23861
- import { basename as basename2, extname, join as join10 } from "node:path";
24417
+ import { basename as basename2, extname, join as join11 } from "node:path";
23862
24418
  var lastCleanupByDir = new Map;
23863
24419
  var CLEANUP_INTERVAL = 10 * 60 * 1000;
23864
24420
  function isImagePart(p) {
@@ -23906,12 +24462,12 @@ function cleanupAllSessions(saveDir) {
23906
24462
  const dirsToScan = [];
23907
24463
  try {
23908
24464
  for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
23909
- const fp = join10(saveDir, entry.name);
24465
+ const fp = join11(saveDir, entry.name);
23910
24466
  if (entry.isDirectory()) {
23911
24467
  dirsToScan.push(fp);
23912
24468
  } else {
23913
24469
  try {
23914
- if (now - statSync3(fp).mtimeMs > maxAge)
24470
+ if (now - statSync4(fp).mtimeMs > maxAge)
23915
24471
  unlinkSync2(fp);
23916
24472
  } catch {}
23917
24473
  }
@@ -23923,9 +24479,9 @@ function cleanupAllSessions(saveDir) {
23923
24479
  let allRemoved = true;
23924
24480
  for (const f of readdirSync3(dir)) {
23925
24481
  isEmpty = false;
23926
- const fp = join10(dir, f);
24482
+ const fp = join11(dir, f);
23927
24483
  try {
23928
- if (now - statSync3(fp).mtimeMs > maxAge) {
24484
+ if (now - statSync4(fp).mtimeMs > maxAge) {
23929
24485
  unlinkSync2(fp);
23930
24486
  } else {
23931
24487
  allRemoved = false;
@@ -23945,20 +24501,20 @@ function cleanupAllSessions(saveDir) {
23945
24501
  function writeUniqueFile(dir, name, data, log2) {
23946
24502
  const ext = extname(name);
23947
24503
  const base = basename2(name, ext) || name;
23948
- let candidate = join10(dir, name);
23949
- if (existsSync6(candidate)) {
24504
+ let candidate = join11(dir, name);
24505
+ if (existsSync7(candidate)) {
23950
24506
  return candidate;
23951
24507
  }
23952
24508
  let counter = 0;
23953
24509
  const MAX_ATTEMPTS = 1000;
23954
24510
  for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
23955
24511
  try {
23956
- writeFileSync4(candidate, data, { flag: "wx" });
24512
+ writeFileSync5(candidate, data, { flag: "wx" });
23957
24513
  return candidate;
23958
24514
  } catch (e) {
23959
24515
  if (e instanceof Error && e.code === "EEXIST") {
23960
24516
  counter += 1;
23961
- candidate = join10(dir, `${base}-${counter}${ext}`);
24517
+ candidate = join11(dir, `${base}-${counter}${ext}`);
23962
24518
  continue;
23963
24519
  }
23964
24520
  log2(`[image-hook] failed to save image: ${e}`);
@@ -23982,17 +24538,17 @@ function processImageAttachments(args) {
23982
24538
  messagesWithImages.push({ msg, imageParts });
23983
24539
  }
23984
24540
  }
23985
- const saveDir = join10(workDir, ".opencode", "images");
24541
+ const saveDir = join11(workDir, ".opencode", "images");
23986
24542
  if (messagesWithImages.length === 0) {
23987
- if (existsSync6(saveDir))
24543
+ if (existsSync7(saveDir))
23988
24544
  cleanupAllSessions(saveDir);
23989
24545
  return;
23990
24546
  }
23991
- const gitignorePath = join10(workDir, ".opencode", ".gitignore");
24547
+ const gitignorePath = join11(workDir, ".opencode", ".gitignore");
23992
24548
  try {
23993
- mkdirSync4(saveDir, { recursive: true });
23994
- if (!existsSync6(gitignorePath))
23995
- writeFileSync4(gitignorePath, `*
24549
+ mkdirSync5(saveDir, { recursive: true });
24550
+ if (!existsSync7(gitignorePath))
24551
+ writeFileSync5(gitignorePath, `*
23996
24552
  `);
23997
24553
  } catch (e) {
23998
24554
  log2(`[image-hook] failed to create image directory: ${e}`);
@@ -24000,9 +24556,9 @@ function processImageAttachments(args) {
24000
24556
  cleanupAllSessions(saveDir);
24001
24557
  for (const { msg, imageParts } of messagesWithImages) {
24002
24558
  const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
24003
- const targetDir = sessionSubdir ? join10(saveDir, sessionSubdir) : saveDir;
24559
+ const targetDir = sessionSubdir ? join11(saveDir, sessionSubdir) : saveDir;
24004
24560
  try {
24005
- mkdirSync4(targetDir, { recursive: true });
24561
+ mkdirSync5(targetDir, { recursive: true });
24006
24562
  } catch (e) {
24007
24563
  log2(`[image-hook] failed to create target image directory: ${e}`);
24008
24564
  }
@@ -24013,7 +24569,7 @@ function processImageAttachments(args) {
24013
24569
  if (url) {
24014
24570
  const decoded = decodeDataUrl(url);
24015
24571
  if (decoded) {
24016
- const hash = createHash("sha1").update(decoded.data).digest("hex").slice(0, 8);
24572
+ const hash = createHash2("sha1").update(decoded.data).digest("hex").slice(0, 8);
24017
24573
  const sanitizedFilename = filename ? sanitizeFilename(filename) : undefined;
24018
24574
  const baseName = sanitizedFilename ? sanitizedFilename.replace(/\.[^.]+$/, "") || "image" : "image";
24019
24575
  const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
@@ -24204,18 +24760,7 @@ function createReflectCommandHook() {
24204
24760
  };
24205
24761
  }
24206
24762
  // src/hooks/task-session-manager/index.ts
24207
- import path11 from "node:path";
24208
- var AGENT_NAME_SET = new Set([
24209
- "orchestrator",
24210
- "oracle",
24211
- "designer",
24212
- "explorer",
24213
- "librarian",
24214
- "fixer",
24215
- "observer",
24216
- "council",
24217
- "councillor"
24218
- ]);
24763
+ import path12 from "node:path";
24219
24764
  var MAX_PENDING_TASK_CALLS = 100;
24220
24765
  var BACKGROUND_JOB_BOARD_SENTINEL = "SENTINEL: background-job-board-v2";
24221
24766
  var BACKGROUND_COMPLETION_COMPLETED = /^Background task completed: /;
@@ -24247,9 +24792,6 @@ function createOccurrenceId(part, message, partIndex) {
24247
24792
  const hash = djb2Hash(`${sessionID}:${content}`);
24248
24793
  return `anon:${hash}`;
24249
24794
  }
24250
- function isAgentName(value) {
24251
- return typeof value === "string" && AGENT_NAME_SET.has(value);
24252
- }
24253
24795
  function extractPath(output) {
24254
24796
  return /<path>([^<]+)<\/path>/.exec(output)?.[1];
24255
24797
  }
@@ -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;
@@ -24530,16 +25072,17 @@ function createTaskSessionManagerHook(_ctx, options) {
24530
25072
  if (!isRecord(output.args))
24531
25073
  return;
24532
25074
  const args = output.args;
24533
- if (!isAgentName(args.subagent_type)) {
25075
+ if (typeof args.subagent_type !== "string" || args.subagent_type.trim() === "") {
24534
25076
  if (typeof args.task_id === "string" && args.task_id.trim() !== "") {
24535
25077
  delete args.task_id;
24536
25078
  }
24537
25079
  return;
24538
25080
  }
25081
+ const agentType = args.subagent_type.trim();
24539
25082
  const label = deriveTaskSessionLabel({
24540
25083
  description: typeof args.description === "string" ? args.description : undefined,
24541
25084
  prompt: typeof args.prompt === "string" ? args.prompt : undefined,
24542
- agentType: args.subagent_type
25085
+ agentType
24543
25086
  });
24544
25087
  const pendingCall = {
24545
25088
  callId: pendingCallId({
@@ -24547,7 +25090,7 @@ function createTaskSessionManagerHook(_ctx, options) {
24547
25090
  sessionID: input.sessionID
24548
25091
  }),
24549
25092
  parentSessionId: input.sessionID,
24550
- agentType: args.subagent_type,
25093
+ agentType,
24551
25094
  label
24552
25095
  };
24553
25096
  rememberPendingCall(pendingCall);
@@ -24555,7 +25098,7 @@ function createTaskSessionManagerHook(_ctx, options) {
24555
25098
  return;
24556
25099
  }
24557
25100
  const requested = args.task_id.trim();
24558
- const remembered = backgroundJobBoard.resolveReusable(input.sessionID, requested, args.subagent_type);
25101
+ const remembered = backgroundJobBoard.resolveReusable(input.sessionID, requested, agentType);
24559
25102
  if (!remembered) {
24560
25103
  if (RAW_SESSION_ID_PATTERN.test(requested)) {
24561
25104
  pendingCall.resumedTaskId = requested;
@@ -24825,7 +25368,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
24825
25368
  `);
24826
25369
  }
24827
25370
  // src/interview/manager.ts
24828
- import path15 from "node:path";
25371
+ import path16 from "node:path";
24829
25372
 
24830
25373
  // src/interview/dashboard.ts
24831
25374
  import crypto from "node:crypto";
@@ -24835,27 +25378,27 @@ import {
24835
25378
  createServer
24836
25379
  } from "node:http";
24837
25380
  import os4 from "node:os";
24838
- import path13 from "node:path";
25381
+ import path14 from "node:path";
24839
25382
  import { URL as URL2 } from "node:url";
24840
25383
 
24841
25384
  // src/interview/document.ts
24842
25385
  import * as fsSync from "node:fs";
24843
25386
  import * as fs6 from "node:fs/promises";
24844
- import * as path12 from "node:path";
25387
+ import * as path13 from "node:path";
24845
25388
  var DEFAULT_OUTPUT_FOLDER = "interview";
24846
25389
  function normalizeOutputFolder(outputFolder) {
24847
25390
  const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
24848
25391
  return normalized || DEFAULT_OUTPUT_FOLDER;
24849
25392
  }
24850
25393
  function createInterviewDirectoryPath(directory, outputFolder) {
24851
- return path12.join(directory, normalizeOutputFolder(outputFolder));
25394
+ return path13.join(directory, normalizeOutputFolder(outputFolder));
24852
25395
  }
24853
25396
  function createInterviewFilePath(directory, outputFolder, idea) {
24854
25397
  const fileName = `${slugify(idea) || "interview"}.md`;
24855
- return path12.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
25398
+ return path13.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
24856
25399
  }
24857
25400
  function relativeInterviewPath(directory, filePath) {
24858
- return path12.relative(directory, filePath) || path12.basename(filePath);
25401
+ return path13.relative(directory, filePath) || path13.basename(filePath);
24859
25402
  }
24860
25403
  function resolveExistingInterviewPath(directory, outputFolder, value) {
24861
25404
  const trimmed = value.trim();
@@ -24864,22 +25407,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
24864
25407
  }
24865
25408
  const outputDir = createInterviewDirectoryPath(directory, outputFolder);
24866
25409
  const candidates = new Set;
24867
- const resolvedRoot = path12.resolve(directory);
24868
- if (path12.isAbsolute(trimmed)) {
25410
+ const resolvedRoot = path13.resolve(directory);
25411
+ if (path13.isAbsolute(trimmed)) {
24869
25412
  candidates.add(trimmed);
24870
25413
  } else {
24871
- candidates.add(path12.resolve(directory, trimmed));
24872
- candidates.add(path12.join(outputDir, trimmed));
25414
+ candidates.add(path13.resolve(directory, trimmed));
25415
+ candidates.add(path13.join(outputDir, trimmed));
24873
25416
  if (!trimmed.endsWith(".md")) {
24874
- candidates.add(path12.join(outputDir, `${trimmed}.md`));
25417
+ candidates.add(path13.join(outputDir, `${trimmed}.md`));
24875
25418
  }
24876
25419
  }
24877
25420
  for (const candidate of candidates) {
24878
- if (path12.extname(candidate) !== ".md") {
25421
+ if (path13.extname(candidate) !== ".md") {
24879
25422
  continue;
24880
25423
  }
24881
- const resolved = path12.resolve(candidate);
24882
- if (!resolved.startsWith(resolvedRoot + path12.sep) && resolved !== resolvedRoot) {
25424
+ const resolved = path13.resolve(candidate);
25425
+ if (!resolved.startsWith(resolvedRoot + path13.sep) && resolved !== resolvedRoot) {
24883
25426
  continue;
24884
25427
  }
24885
25428
  if (fsSync.existsSync(candidate)) {
@@ -24959,7 +25502,7 @@ function parseFrontmatter(content) {
24959
25502
  return result;
24960
25503
  }
24961
25504
  async function ensureInterviewFile(record) {
24962
- await fs6.mkdir(path12.dirname(record.markdownPath), { recursive: true });
25505
+ await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
24963
25506
  try {
24964
25507
  await fs6.access(record.markdownPath);
24965
25508
  } catch {
@@ -26629,12 +27172,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
26629
27172
 
26630
27173
  // src/interview/dashboard.ts
26631
27174
  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`);
27175
+ const dataHome = process.env.XDG_DATA_HOME || path14.join(os4.homedir(), ".local", "share");
27176
+ return path14.join(dataHome, "opencode", `.dashboard-${port}.json`);
26634
27177
  }
26635
27178
  function writeAuthFile(port, token) {
26636
27179
  const filePath = getAuthFilePath(port);
26637
- const dir = path13.dirname(filePath);
27180
+ const dir = path14.dirname(filePath);
26638
27181
  try {
26639
27182
  fsSync2.mkdirSync(dir, { recursive: true });
26640
27183
  } catch {}
@@ -26771,7 +27314,7 @@ function createDashboardServer(config) {
26771
27314
  const directories = getKnownDirectories();
26772
27315
  const items = [];
26773
27316
  for (const dir of directories) {
26774
- const interviewDir = path13.join(dir, config.outputFolder);
27317
+ const interviewDir = path14.join(dir, config.outputFolder);
26775
27318
  let entries;
26776
27319
  try {
26777
27320
  entries = await fs7.readdir(interviewDir);
@@ -26783,7 +27326,7 @@ function createDashboardServer(config) {
26783
27326
  continue;
26784
27327
  let content;
26785
27328
  try {
26786
- content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
27329
+ content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
26787
27330
  } catch {
26788
27331
  continue;
26789
27332
  }
@@ -26809,7 +27352,7 @@ function createDashboardServer(config) {
26809
27352
  const directories = getKnownDirectories();
26810
27353
  let rebuilt = 0;
26811
27354
  for (const dir of directories) {
26812
- const interviewDir = path13.join(dir, config.outputFolder);
27355
+ const interviewDir = path14.join(dir, config.outputFolder);
26813
27356
  let entries;
26814
27357
  try {
26815
27358
  entries = await fs7.readdir(interviewDir);
@@ -26821,7 +27364,7 @@ function createDashboardServer(config) {
26821
27364
  continue;
26822
27365
  let content;
26823
27366
  try {
26824
- content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
27367
+ content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
26825
27368
  } catch {
26826
27369
  continue;
26827
27370
  }
@@ -26847,7 +27390,7 @@ function createDashboardServer(config) {
26847
27390
  questions: [],
26848
27391
  pendingAnswers: null,
26849
27392
  lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
26850
- filePath: path13.join(interviewDir, entry),
27393
+ filePath: path14.join(interviewDir, entry),
26851
27394
  nudgeAction: null
26852
27395
  });
26853
27396
  if (!sessions.has(fm.sessionID)) {
@@ -27111,7 +27654,7 @@ function createDashboardServer(config) {
27111
27654
  const dirs = getKnownDirectories();
27112
27655
  for (const dir of dirs) {
27113
27656
  const slug = extractResumeSlug(interviewId);
27114
- const candidate = path13.join(dir, config.outputFolder, `${slug}.md`);
27657
+ const candidate = path14.join(dir, config.outputFolder, `${slug}.md`);
27115
27658
  try {
27116
27659
  document = await fs7.readFile(candidate, "utf8");
27117
27660
  markdownPath = candidate;
@@ -27639,7 +28182,7 @@ function createInterviewServer(deps) {
27639
28182
  // src/interview/service.ts
27640
28183
  import { spawn as spawn2 } from "node:child_process";
27641
28184
  import * as fs8 from "node:fs/promises";
27642
- import * as path14 from "node:path";
28185
+ import * as path15 from "node:path";
27643
28186
 
27644
28187
  // src/interview/types.ts
27645
28188
  import { z as z3 } from "zod";
@@ -27827,13 +28370,13 @@ function shouldAutoOpenBrowser(config, env) {
27827
28370
  return requested && !isAutomatedRuntime(env);
27828
28371
  }
27829
28372
  function openBrowser(url) {
27830
- const platform2 = process.platform;
28373
+ const platform3 = process.platform;
27831
28374
  let command;
27832
28375
  let args;
27833
- if (platform2 === "darwin") {
28376
+ if (platform3 === "darwin") {
27834
28377
  command = "open";
27835
28378
  args = [url];
27836
- } else if (platform2 === "win32") {
28379
+ } else if (platform3 === "win32") {
27837
28380
  command = "cmd";
27838
28381
  args = ["/c", "start", "", url];
27839
28382
  } else {
@@ -27906,12 +28449,12 @@ function createInterviewService(ctx, config, deps) {
27906
28449
  if (!newSlug) {
27907
28450
  return;
27908
28451
  }
27909
- const currentFileName = path14.basename(interview.markdownPath, ".md");
28452
+ const currentFileName = path15.basename(interview.markdownPath, ".md");
27910
28453
  if (currentFileName === newSlug) {
27911
28454
  return;
27912
28455
  }
27913
- const dir = path14.dirname(interview.markdownPath);
27914
- const newPath = path14.join(dir, `${newSlug}.md`);
28456
+ const dir = path15.dirname(interview.markdownPath);
28457
+ const newPath = path15.join(dir, `${newSlug}.md`);
27915
28458
  try {
27916
28459
  await fs8.access(newPath);
27917
28460
  return;
@@ -27987,9 +28530,9 @@ function createInterviewService(ctx, config, deps) {
27987
28530
  const messages = await loadMessages(sessionID);
27988
28531
  const title = extractTitle(document);
27989
28532
  const record = {
27990
- id: `${Date.now()}-${++idCounter}-${slugify(path14.basename(markdownPath, ".md")) || "interview"}`,
28533
+ id: `${Date.now()}-${++idCounter}-${slugify(path15.basename(markdownPath, ".md")) || "interview"}`,
27991
28534
  sessionID,
27992
- idea: title || path14.basename(markdownPath, ".md"),
28535
+ idea: title || path15.basename(markdownPath, ".md"),
27993
28536
  markdownPath,
27994
28537
  createdAt: nowIso(),
27995
28538
  status: "active",
@@ -28216,7 +28759,7 @@ function createInterviewService(ctx, config, deps) {
28216
28759
  return fileCache.items;
28217
28760
  }
28218
28761
  const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
28219
- const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path14.resolve(i.markdownPath)));
28762
+ const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path15.resolve(i.markdownPath)));
28220
28763
  let entries;
28221
28764
  try {
28222
28765
  entries = await fs8.readdir(outputDir);
@@ -28227,8 +28770,8 @@ function createInterviewService(ctx, config, deps) {
28227
28770
  for (const entry of entries) {
28228
28771
  if (!entry.endsWith(".md"))
28229
28772
  continue;
28230
- const fullPath = path14.join(outputDir, entry);
28231
- if (activePaths.has(path14.resolve(fullPath)))
28773
+ const fullPath = path15.join(outputDir, entry);
28774
+ if (activePaths.has(path15.resolve(fullPath)))
28232
28775
  continue;
28233
28776
  let content;
28234
28777
  try {
@@ -28327,7 +28870,7 @@ function createInterviewManager(ctx, config) {
28327
28870
  const outputFolder = interviewConfig?.outputFolder ?? "interview";
28328
28871
  if (!dashboardEnabled) {
28329
28872
  const service2 = createInterviewService(ctx, interviewConfig);
28330
- const resolvedOutputPath = path15.join(ctx.directory, outputFolder);
28873
+ const resolvedOutputPath = path16.join(ctx.directory, outputFolder);
28331
28874
  const server = createInterviewServer({
28332
28875
  getState: async (interviewId) => service2.getInterviewState(interviewId),
28333
28876
  listInterviewFiles: async () => service2.listInterviewFiles(),
@@ -28432,7 +28975,7 @@ function createInterviewManager(ctx, config) {
28432
28975
  listInterviews: () => service.listInterviews(),
28433
28976
  submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
28434
28977
  handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
28435
- outputFolder: path15.join(ctx.directory, outputFolder),
28978
+ outputFolder: path16.join(ctx.directory, outputFolder),
28436
28979
  port: 0
28437
28980
  });
28438
28981
  service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
@@ -28700,6 +29243,7 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
28700
29243
  }
28701
29244
 
28702
29245
  // src/multiplexer/tmux/index.ts
29246
+ init_compat();
28703
29247
  var TMUX_LAYOUT_DEBOUNCE_MS = 150;
28704
29248
 
28705
29249
  class TmuxMultiplexer {
@@ -28917,23 +29461,23 @@ class TmuxMultiplexer {
28917
29461
  return null;
28918
29462
  }
28919
29463
  const stdout = await proc.stdout();
28920
- const path16 = stdout.trim().split(`
29464
+ const path17 = stdout.trim().split(`
28921
29465
  `)[0];
28922
- if (!path16) {
29466
+ if (!path17) {
28923
29467
  log("[tmux] findBinary: no path in output");
28924
29468
  return null;
28925
29469
  }
28926
- const verifyProc = crossSpawn([path16, "-V"], {
29470
+ const verifyProc = crossSpawn([path17, "-V"], {
28927
29471
  stdout: "pipe",
28928
29472
  stderr: "pipe"
28929
29473
  });
28930
29474
  const verifyExit = await verifyProc.exited;
28931
29475
  if (verifyExit !== 0) {
28932
- log("[tmux] findBinary: tmux -V failed", { path: path16, verifyExit });
29476
+ log("[tmux] findBinary: tmux -V failed", { path: path17, verifyExit });
28933
29477
  return null;
28934
29478
  }
28935
- log("[tmux] findBinary: found", { path: path16 });
28936
- return path16;
29479
+ log("[tmux] findBinary: found", { path: path17 });
29480
+ return path17;
28937
29481
  } catch (err) {
28938
29482
  log("[tmux] findBinary: exception", { error: String(err) });
28939
29483
  return null;
@@ -28945,6 +29489,8 @@ function quoteShellArg(value) {
28945
29489
  }
28946
29490
 
28947
29491
  // src/multiplexer/zellij/index.ts
29492
+ init_compat();
29493
+
28948
29494
  class ZellijMultiplexer {
28949
29495
  paneMode;
28950
29496
  type = "zellij";
@@ -29884,22 +30430,359 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
29884
30430
  }
29885
30431
  return false;
29886
30432
  }
29887
- // src/tools/ast-grep/tools.ts
30433
+ // src/tools/acp-run.ts
30434
+ import { spawn as spawn3 } from "node:child_process";
30435
+ import { createInterface } from "node:readline";
29888
30436
  import { tool } from "@opencode-ai/plugin";
30437
+ var z4 = tool.schema;
30438
+
30439
+ class AcpClient {
30440
+ name;
30441
+ config;
30442
+ cwd;
30443
+ ask;
30444
+ child;
30445
+ next = 1;
30446
+ pending = new Map;
30447
+ chunks = [];
30448
+ errors = [];
30449
+ sessionId;
30450
+ lastUpdate = Date.now();
30451
+ authMethods = [];
30452
+ active = false;
30453
+ activeRequests = 0;
30454
+ constructor(name, config, cwd, ask) {
30455
+ this.name = name;
30456
+ this.config = config;
30457
+ this.cwd = cwd;
30458
+ this.ask = ask;
30459
+ this.child = spawn3(config.command, config.args, {
30460
+ cwd,
30461
+ env: { ...process.env, ...config.env },
30462
+ stdio: "pipe"
30463
+ });
30464
+ this.child.stderr.on("data", (chunk) => {
30465
+ this.errors.push(String(chunk));
30466
+ });
30467
+ this.child.stdin.on("error", (error) => {
30468
+ this.errors.push(String(error));
30469
+ this.rejectPending(error);
30470
+ });
30471
+ this.child.on("error", (error) => {
30472
+ this.rejectPending(error);
30473
+ });
30474
+ this.child.on("exit", (code, signal) => {
30475
+ if (this.pending.size === 0)
30476
+ return;
30477
+ this.rejectPending(new Error(`ACP agent '${name}' exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"})`));
30478
+ });
30479
+ createInterface({ input: this.child.stdout }).on("line", (line) => {
30480
+ this.receive(line).catch((error) => {
30481
+ this.errors.push(String(error));
30482
+ });
30483
+ });
30484
+ }
30485
+ async run(prompt) {
30486
+ const init = await this.request("initialize", {
30487
+ protocolVersion: 1,
30488
+ clientCapabilities: {},
30489
+ clientInfo: {
30490
+ name: "oh-my-opencode-slim",
30491
+ title: "oh-my-opencode-slim ACP bridge"
30492
+ }
30493
+ });
30494
+ this.authMethods = readAuthMethods(init);
30495
+ const created = await this.newSession();
30496
+ const sessionId = readSessionId(created);
30497
+ this.sessionId = sessionId;
30498
+ this.active = true;
30499
+ await this.request("session/prompt", {
30500
+ sessionId,
30501
+ prompt: [{ type: "text", text: prompt }]
30502
+ });
30503
+ await this.drain();
30504
+ this.active = false;
30505
+ return this.output();
30506
+ }
30507
+ async newSession() {
30508
+ try {
30509
+ return await this.request("session/new", {
30510
+ cwd: this.cwd,
30511
+ mcpServers: []
30512
+ });
30513
+ } catch (error) {
30514
+ if (!isAuthError(error) || this.authMethods.length === 0)
30515
+ throw error;
30516
+ const method = this.authMethods[0];
30517
+ if (typeof method.id !== "string")
30518
+ throw error;
30519
+ await this.request("authenticate", { methodId: method.id });
30520
+ return await this.request("session/new", {
30521
+ cwd: this.cwd,
30522
+ mcpServers: []
30523
+ });
30524
+ }
30525
+ }
30526
+ close() {
30527
+ if (this.active && this.sessionId && !this.child.killed) {
30528
+ this.notify("session/cancel", { sessionId: this.sessionId });
30529
+ }
30530
+ if (!this.child.killed)
30531
+ this.child.kill("SIGTERM");
30532
+ }
30533
+ request(method, params) {
30534
+ const id = this.next++;
30535
+ const payload = { jsonrpc: "2.0", id, method, params };
30536
+ return new Promise((resolve3, reject) => {
30537
+ this.pending.set(id, { resolve: resolve3, reject });
30538
+ this.child.stdin.write(`${JSON.stringify(payload)}
30539
+ `, (error) => {
30540
+ if (!error)
30541
+ return;
30542
+ this.pending.delete(id);
30543
+ reject(error);
30544
+ });
30545
+ });
30546
+ }
30547
+ notify(method, params) {
30548
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
30549
+ `);
30550
+ }
30551
+ async drain() {
30552
+ this.lastUpdate = Date.now();
30553
+ while (this.activeRequests > 0 || Date.now() - this.lastUpdate < 100) {
30554
+ await new Promise((resolve3) => setTimeout(resolve3, 25));
30555
+ }
30556
+ }
30557
+ async receive(line) {
30558
+ if (!line.trim())
30559
+ return;
30560
+ let message;
30561
+ try {
30562
+ message = JSON.parse(line);
30563
+ } catch {
30564
+ const error = new Error(`ACP agent '${this.name}' wrote non-JSON stdout: ${line.slice(0, 200)}`);
30565
+ this.errors.push(error.message);
30566
+ this.rejectPending(error);
30567
+ this.close();
30568
+ return;
30569
+ }
30570
+ if ("id" in message && (("result" in message) || ("error" in message))) {
30571
+ const pending = this.pending.get(message.id);
30572
+ if (!pending)
30573
+ return;
30574
+ this.pending.delete(message.id);
30575
+ if (message.error) {
30576
+ pending.reject(rpcError(message.error));
30577
+ return;
30578
+ }
30579
+ pending.resolve(message.result);
30580
+ return;
30581
+ }
30582
+ if ("id" in message && "method" in message) {
30583
+ this.activeRequests++;
30584
+ try {
30585
+ await this.handleRequest(message);
30586
+ } finally {
30587
+ this.activeRequests--;
30588
+ this.lastUpdate = Date.now();
30589
+ }
30590
+ return;
30591
+ }
30592
+ if ("method" in message)
30593
+ this.handleNotification(message);
30594
+ }
30595
+ rejectPending(error) {
30596
+ for (const item of this.pending.values())
30597
+ item.reject(error);
30598
+ this.pending.clear();
30599
+ }
30600
+ async handleRequest(message) {
30601
+ if (message.method === "session/request_permission") {
30602
+ const title = readPermissionTitle(message.params);
30603
+ try {
30604
+ if (this.config.permissionMode === "ask") {
30605
+ await this.ask(title, message.params ?? {});
30606
+ }
30607
+ const optionId = selectPermissionOption(message.params, this.config.permissionMode);
30608
+ if (!optionId)
30609
+ throw new Error("ACP permission request had no usable option");
30610
+ this.reply(message.id, {
30611
+ outcome: { outcome: "selected", optionId }
30612
+ });
30613
+ } catch {
30614
+ const optionId = selectPermissionOption(message.params, "reject");
30615
+ if (optionId) {
30616
+ this.reply(message.id, {
30617
+ outcome: { outcome: "selected", optionId }
30618
+ });
30619
+ return;
30620
+ }
30621
+ this.reply(message.id, { outcome: { outcome: "cancelled" } });
30622
+ }
30623
+ return;
30624
+ }
30625
+ this.replyError(message.id, `Unsupported ACP client method: ${message.method}`);
30626
+ }
30627
+ handleNotification(message) {
30628
+ if (message.method !== "session/update")
30629
+ return;
30630
+ this.lastUpdate = Date.now();
30631
+ const update = message.params?.update;
30632
+ if (!isRecord2(update))
30633
+ return;
30634
+ collectText(update, this.chunks);
30635
+ }
30636
+ reply(id, result) {
30637
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
30638
+ `);
30639
+ }
30640
+ replyError(id, message) {
30641
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message } })}
30642
+ `);
30643
+ }
30644
+ output() {
30645
+ const text = this.chunks.join("").trim();
30646
+ if (text)
30647
+ return text;
30648
+ const err = this.errors.join("").trim();
30649
+ return err ? `ACP agent '${this.name}' completed without text output. stderr:
30650
+ ${err}` : `ACP agent '${this.name}' completed without text output.`;
30651
+ }
30652
+ }
30653
+ function createAcpRunTool(agents = {}) {
30654
+ return tool({
30655
+ 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.",
30656
+ args: {
30657
+ agent: z4.string().describe("Configured ACP agent name"),
30658
+ prompt: z4.string().describe("Task or question to send to the ACP agent"),
30659
+ cwd: z4.string().optional().describe("Optional absolute working directory override"),
30660
+ timeout_ms: z4.number().int().min(1000).max(900000).optional().describe("Optional timeout override in milliseconds")
30661
+ },
30662
+ async execute(args, ctx) {
30663
+ if (ctx.agent !== args.agent) {
30664
+ throw new Error(`acp_run for '${args.agent}' can only be used by @${args.agent}`);
30665
+ }
30666
+ const config = agents[args.agent];
30667
+ if (!config) {
30668
+ throw new Error(`Unknown ACP agent '${args.agent}'. Configured agents: ${Object.keys(agents).join(", ") || "(none)"}`);
30669
+ }
30670
+ const cwd = args.cwd ?? config.cwd ?? ctx.directory;
30671
+ if (!cwd)
30672
+ throw new Error("acp_run requires a working directory");
30673
+ await ctx.ask({
30674
+ permission: "bash",
30675
+ patterns: [`${config.command} ${config.args.join(" ")}`.trim()],
30676
+ always: [],
30677
+ metadata: {
30678
+ agent: args.agent,
30679
+ cwd,
30680
+ command: config.command,
30681
+ args: config.args
30682
+ }
30683
+ });
30684
+ const client = new AcpClient(args.agent, config, cwd, async (title, metadata) => {
30685
+ if (config.permissionMode === "reject")
30686
+ return;
30687
+ await ctx.ask({
30688
+ permission: "bash",
30689
+ patterns: [`acp:${args.agent}:${title}`],
30690
+ always: [],
30691
+ metadata
30692
+ });
30693
+ });
30694
+ const timeoutMs = args.timeout_ms ?? config.timeoutMs;
30695
+ let timer;
30696
+ const timeout = new Promise((_, reject) => timer = setTimeout(() => reject(new Error(`ACP agent '${args.agent}' timed out after ${timeoutMs}ms`)), timeoutMs));
30697
+ const abort = () => client.close();
30698
+ ctx.abort.addEventListener("abort", abort, { once: true });
30699
+ try {
30700
+ return await Promise.race([client.run(args.prompt), timeout]);
30701
+ } finally {
30702
+ if (timer)
30703
+ clearTimeout(timer);
30704
+ ctx.abort.removeEventListener("abort", abort);
30705
+ client.close();
30706
+ }
30707
+ }
30708
+ });
30709
+ }
30710
+ function readSessionId(value) {
30711
+ if (!isRecord2(value) || typeof value.sessionId !== "string") {
30712
+ throw new Error("ACP agent did not return a sessionId");
30713
+ }
30714
+ return value.sessionId;
30715
+ }
30716
+ function readAuthMethods(value) {
30717
+ if (!isRecord2(value) || !Array.isArray(value.authMethods))
30718
+ return [];
30719
+ const methods = value.authMethods;
30720
+ return methods.filter(isRecord2);
30721
+ }
30722
+ function rpcError(error) {
30723
+ const err = new Error(error.message ?? "ACP request failed");
30724
+ err.code = error.code;
30725
+ err.data = error.data;
30726
+ return err;
30727
+ }
30728
+ function isAuthError(error) {
30729
+ if (!(error instanceof Error))
30730
+ return false;
30731
+ const meta = error;
30732
+ return meta.code === -32001 || error.message.toLowerCase().includes("auth_required") || error.message.toLowerCase().includes("auth required");
30733
+ }
30734
+ function isRecord2(value) {
30735
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30736
+ }
30737
+ function readPermissionTitle(params) {
30738
+ const tool2 = isRecord2(params?.toolCall) ? params.toolCall : undefined;
30739
+ if (typeof tool2?.title === "string")
30740
+ return tool2.title;
30741
+ if (typeof params?.permission === "string")
30742
+ return params.permission;
30743
+ return "ACP permission request";
30744
+ }
30745
+ function selectPermissionOption(params, mode) {
30746
+ const options = Array.isArray(params?.options) ? params.options : [];
30747
+ const choices = options.filter(isRecord2).filter((item) => typeof item.optionId === "string");
30748
+ const reject = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("reject"));
30749
+ if (mode === "reject")
30750
+ return reject?.optionId;
30751
+ const allow = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("allow"));
30752
+ return allow?.optionId ?? reject?.optionId;
30753
+ }
30754
+ function collectText(update, chunks) {
30755
+ if (update.sessionUpdate !== "agent_message_chunk")
30756
+ return;
30757
+ const text = readText(update.delta) ?? readText(update.content);
30758
+ if (text)
30759
+ chunks.push(text);
30760
+ }
30761
+ function readText(value) {
30762
+ if (typeof value === "string")
30763
+ return value;
30764
+ if (isRecord2(value) && typeof value.text === "string")
30765
+ return value.text;
30766
+ return;
30767
+ }
30768
+ // src/tools/ast-grep/tools.ts
30769
+ import { tool as tool2 } from "@opencode-ai/plugin";
29889
30770
 
29890
30771
  // src/tools/ast-grep/cli.ts
29891
- import { existsSync as existsSync10 } from "node:fs";
30772
+ init_compat();
30773
+ import { existsSync as existsSync11 } from "node:fs";
29892
30774
 
29893
30775
  // src/tools/ast-grep/constants.ts
29894
- import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
30776
+ import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
29895
30777
  import { createRequire as createRequire3 } from "node:module";
29896
- import { dirname as dirname8, join as join14 } from "node:path";
30778
+ import { dirname as dirname9, join as join15 } from "node:path";
29897
30779
 
29898
30780
  // src/tools/ast-grep/downloader.ts
29899
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "node:fs";
30781
+ import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
29900
30782
  import { createRequire as createRequire2 } from "node:module";
29901
- import { homedir as homedir5 } from "node:os";
29902
- import { join as join13 } from "node:path";
30783
+ import { homedir as homedir6 } from "node:os";
30784
+ import { join as join14 } from "node:path";
30785
+ init_compat();
29903
30786
  var REPO = "ast-grep/ast-grep";
29904
30787
  var DEFAULT_VERSION = "0.40.0";
29905
30788
  function getAstGrepVersion() {
@@ -29923,19 +30806,19 @@ var PLATFORM_MAP = {
29923
30806
  function getCacheDir2() {
29924
30807
  if (process.platform === "win32") {
29925
30808
  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");
30809
+ const base2 = localAppData || join14(homedir6(), "AppData", "Local");
30810
+ return join14(base2, "oh-my-opencode-slim", "bin");
29928
30811
  }
29929
30812
  const xdgCache = process.env.XDG_CACHE_HOME;
29930
- const base = xdgCache || join13(homedir5(), ".cache");
29931
- return join13(base, "oh-my-opencode-slim", "bin");
30813
+ const base = xdgCache || join14(homedir6(), ".cache");
30814
+ return join14(base, "oh-my-opencode-slim", "bin");
29932
30815
  }
29933
30816
  function getBinaryName() {
29934
30817
  return process.platform === "win32" ? "sg.exe" : "sg";
29935
30818
  }
29936
30819
  function getCachedBinaryPath() {
29937
- const binaryPath2 = join13(getCacheDir2(), getBinaryName());
29938
- return existsSync8(binaryPath2) ? binaryPath2 : null;
30820
+ const binaryPath = join14(getCacheDir2(), getBinaryName());
30821
+ return existsSync9(binaryPath) ? binaryPath : null;
29939
30822
  }
29940
30823
  async function downloadAstGrep(version = DEFAULT_VERSION) {
29941
30824
  const platformKey = `${process.platform}-${process.arch}`;
@@ -29946,34 +30829,34 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
29946
30829
  }
29947
30830
  const cacheDir = getCacheDir2();
29948
30831
  const binaryName = getBinaryName();
29949
- const binaryPath2 = join13(cacheDir, binaryName);
29950
- if (existsSync8(binaryPath2)) {
29951
- return binaryPath2;
30832
+ const binaryPath = join14(cacheDir, binaryName);
30833
+ if (existsSync9(binaryPath)) {
30834
+ return binaryPath;
29952
30835
  }
29953
30836
  const { arch, os: os5 } = platformInfo;
29954
30837
  const assetName = `app-${arch}-${os5}.zip`;
29955
30838
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
29956
30839
  console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
29957
30840
  try {
29958
- if (!existsSync8(cacheDir)) {
29959
- mkdirSync6(cacheDir, { recursive: true });
30841
+ if (!existsSync9(cacheDir)) {
30842
+ mkdirSync7(cacheDir, { recursive: true });
29960
30843
  }
29961
30844
  const response = await fetch(downloadUrl, { redirect: "follow" });
29962
30845
  if (!response.ok) {
29963
30846
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
29964
30847
  }
29965
- const archivePath = join13(cacheDir, assetName);
30848
+ const archivePath = join14(cacheDir, assetName);
29966
30849
  const arrayBuffer = await response.arrayBuffer();
29967
30850
  await crossWrite(archivePath, arrayBuffer);
29968
30851
  await extractZip(archivePath, cacheDir);
29969
- if (existsSync8(archivePath)) {
30852
+ if (existsSync9(archivePath)) {
29970
30853
  unlinkSync4(archivePath);
29971
30854
  }
29972
- if (process.platform !== "win32" && existsSync8(binaryPath2)) {
29973
- chmodSync(binaryPath2, 493);
30855
+ if (process.platform !== "win32" && existsSync9(binaryPath)) {
30856
+ chmodSync2(binaryPath, 493);
29974
30857
  }
29975
30858
  console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
29976
- return binaryPath2;
30859
+ return binaryPath;
29977
30860
  } catch (err) {
29978
30861
  console.error(`[oh-my-opencode-slim] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`);
29979
30862
  return null;
@@ -30021,13 +30904,13 @@ var CLI_LANGUAGES = [
30021
30904
  var MIN_BINARY_SIZE = 1e4;
30022
30905
  function isValidBinary(filePath) {
30023
30906
  try {
30024
- return statSync4(filePath).size > MIN_BINARY_SIZE;
30907
+ return statSync5(filePath).size > MIN_BINARY_SIZE;
30025
30908
  } catch {
30026
30909
  return false;
30027
30910
  }
30028
30911
  }
30029
30912
  function getPlatformPackageName() {
30030
- const platform2 = process.platform;
30913
+ const platform3 = process.platform;
30031
30914
  const arch = process.arch;
30032
30915
  const platformMap = {
30033
30916
  "darwin-arm64": "@ast-grep/cli-darwin-arm64",
@@ -30038,7 +30921,7 @@ function getPlatformPackageName() {
30038
30921
  "win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
30039
30922
  "win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
30040
30923
  };
30041
- return platformMap[`${platform2}-${arch}`] ?? null;
30924
+ return platformMap[`${platform3}-${arch}`] ?? null;
30042
30925
  }
30043
30926
  var resolvedCliPath = null;
30044
30927
  function findSgCliPathSync() {
@@ -30050,9 +30933,9 @@ function findSgCliPathSync() {
30050
30933
  try {
30051
30934
  const require2 = createRequire3(import.meta.url);
30052
30935
  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)) {
30936
+ const cliDir = dirname9(cliPkgPath);
30937
+ const sgPath = join15(cliDir, binaryName);
30938
+ if (existsSync10(sgPath) && isValidBinary(sgPath)) {
30056
30939
  return sgPath;
30057
30940
  }
30058
30941
  } catch {}
@@ -30061,19 +30944,19 @@ function findSgCliPathSync() {
30061
30944
  try {
30062
30945
  const require2 = createRequire3(import.meta.url);
30063
30946
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
30064
- const pkgDir = dirname8(pkgPath);
30947
+ const pkgDir = dirname9(pkgPath);
30065
30948
  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;
30949
+ const binaryPath = join15(pkgDir, astGrepName);
30950
+ if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
30951
+ return binaryPath;
30069
30952
  }
30070
30953
  } catch {}
30071
30954
  }
30072
30955
  if (process.platform === "darwin") {
30073
30956
  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;
30957
+ for (const path17 of homebrewPaths) {
30958
+ if (existsSync10(path17) && isValidBinary(path17)) {
30959
+ return path17;
30077
30960
  }
30078
30961
  }
30079
30962
  }
@@ -30090,8 +30973,8 @@ function getSgCliPath() {
30090
30973
  }
30091
30974
  return "sg";
30092
30975
  }
30093
- function setSgCliPath(path16) {
30094
- resolvedCliPath = path16;
30976
+ function setSgCliPath(path17) {
30977
+ resolvedCliPath = path17;
30095
30978
  }
30096
30979
  var DEFAULT_TIMEOUT_MS = 300000;
30097
30980
  var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
@@ -30101,7 +30984,7 @@ var DEFAULT_MAX_MATCHES = 500;
30101
30984
  var initPromise = null;
30102
30985
  async function getAstGrepPath() {
30103
30986
  const currentPath = getSgCliPath();
30104
- if (currentPath !== "sg" && existsSync10(currentPath)) {
30987
+ if (currentPath !== "sg" && existsSync11(currentPath)) {
30105
30988
  return currentPath;
30106
30989
  }
30107
30990
  if (initPromise) {
@@ -30109,7 +30992,7 @@ async function getAstGrepPath() {
30109
30992
  }
30110
30993
  initPromise = (async () => {
30111
30994
  const syncPath = findSgCliPathSync();
30112
- if (syncPath && existsSync10(syncPath)) {
30995
+ if (syncPath && existsSync11(syncPath)) {
30113
30996
  setSgCliPath(syncPath);
30114
30997
  return syncPath;
30115
30998
  }
@@ -30148,7 +31031,7 @@ async function runSg(options) {
30148
31031
  const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
30149
31032
  args.push(...paths2);
30150
31033
  let cliPath = getSgCliPath();
30151
- if (!existsSync10(cliPath) && cliPath !== "sg") {
31034
+ if (!existsSync11(cliPath) && cliPath !== "sg") {
30152
31035
  const downloadedPath = await getAstGrepPath();
30153
31036
  if (downloadedPath) {
30154
31037
  cliPath = downloadedPath;
@@ -30364,14 +31247,14 @@ function showOutputToUser(context, output) {
30364
31247
  const ctx = context;
30365
31248
  ctx.metadata?.({ metadata: { output } });
30366
31249
  }
30367
- var ast_grep_search = tool({
31250
+ var ast_grep_search = tool2({
30368
31251
  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
31252
  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")
31253
+ pattern: tool2.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
31254
+ lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
31255
+ paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search (default: ['.'])"),
31256
+ globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
31257
+ context: tool2.schema.number().optional().describe("Context lines around match")
30375
31258
  },
30376
31259
  execute: async (args, context) => {
30377
31260
  try {
@@ -30400,15 +31283,15 @@ ${hint}`;
30400
31283
  }
30401
31284
  }
30402
31285
  });
30403
- var ast_grep_replace = tool({
31286
+ var ast_grep_replace = tool2({
30404
31287
  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
31288
  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)")
31289
+ pattern: tool2.schema.string().describe("AST pattern to match"),
31290
+ rewrite: tool2.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
31291
+ lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
31292
+ paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search"),
31293
+ globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs"),
31294
+ dryRun: tool2.schema.boolean().optional().describe("Preview changes without applying (default: true)")
30412
31295
  },
30413
31296
  execute: async (args, context) => {
30414
31297
  try {
@@ -30432,20 +31315,20 @@ var ast_grep_replace = tool({
30432
31315
  });
30433
31316
  // src/tools/cancel-task.ts
30434
31317
  import {
30435
- tool as tool2
31318
+ tool as tool3
30436
31319
  } from "@opencode-ai/plugin";
30437
- var z4 = tool2.schema;
31320
+ var z5 = tool3.schema;
30438
31321
 
30439
31322
  class SessionStillRunningError extends Error {
30440
31323
  }
30441
31324
  function createCancelTaskTool(options) {
30442
- const cancel_task = tool2({
31325
+ const cancel_task = tool3({
30443
31326
  description: `Cancel a tracked background specialist task.
30444
31327
 
30445
31328
  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
31329
  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")
31330
+ task_id: z5.string().describe("Tracked background task ID or Background Job Board alias"),
31331
+ reason: z5.string().optional().describe("Short cancellation reason")
30449
31332
  },
30450
31333
  async execute(args, toolContext) {
30451
31334
  const parentSessionID = toolContext?.sessionID;
@@ -30658,7 +31541,7 @@ async function abortAndVerifySession(options, taskID) {
30658
31541
  attempts,
30659
31542
  status: statusSnapshot.status
30660
31543
  });
30661
- await delay(retryIntervalMs);
31544
+ await delay2(retryIntervalMs);
30662
31545
  continue;
30663
31546
  }
30664
31547
  stableStoppedSince ??= Date.now();
@@ -30671,7 +31554,7 @@ async function abortAndVerifySession(options, taskID) {
30671
31554
  });
30672
31555
  return;
30673
31556
  }
30674
- await delay(retryIntervalMs);
31557
+ await delay2(retryIntervalMs);
30675
31558
  }
30676
31559
  log("[cancel-task] abort verification timed out", {
30677
31560
  taskID,
@@ -30739,13 +31622,13 @@ async function deleteAndVerifySession(options, taskID, reason) {
30739
31622
  });
30740
31623
  if (status.status === "busy" || status.status === "retry") {
30741
31624
  stableStoppedSince = undefined;
30742
- await delay(retryIntervalMs);
31625
+ await delay2(retryIntervalMs);
30743
31626
  continue;
30744
31627
  }
30745
31628
  stableStoppedSince ??= Date.now();
30746
31629
  if (Date.now() - stableStoppedSince >= stableStoppedMs)
30747
31630
  return;
30748
- await delay(retryIntervalMs);
31631
+ await delay2(retryIntervalMs);
30749
31632
  }
30750
31633
  throw new SessionStillRunningError(`Session delete returned but task did not stay stopped: ${taskID} (${lastStatus ?? "unknown"})`);
30751
31634
  }
@@ -30784,7 +31667,7 @@ async function getSessionStatus(client, taskID) {
30784
31667
  return { status: undefined, source: "lookup-error", keys: [] };
30785
31668
  }
30786
31669
  }
30787
- function delay(ms) {
31670
+ function delay2(ms) {
30788
31671
  return new Promise((resolve3) => setTimeout(resolve3, ms));
30789
31672
  }
30790
31673
  function isSessionID(value) {
@@ -30826,9 +31709,9 @@ function unknownTaskOutput(taskID, message) {
30826
31709
  }
30827
31710
  // src/tools/council.ts
30828
31711
  import {
30829
- tool as tool3
31712
+ tool as tool4
30830
31713
  } from "@opencode-ai/plugin";
30831
- var z5 = tool3.schema;
31714
+ var z6 = tool4.schema;
30832
31715
  function formatModelComposition(councillorResults) {
30833
31716
  return councillorResults.map((cr) => {
30834
31717
  const shortModel = shortModelLabel(cr.model);
@@ -30836,15 +31719,15 @@ function formatModelComposition(councillorResults) {
30836
31719
  }).join(", ");
30837
31720
  }
30838
31721
  function createCouncilTool(_ctx, councilManager) {
30839
- const council_session = tool3({
31722
+ const council_session = tool4({
30840
31723
  description: `Launch a multi-LLM council session for consensus-based analysis.
30841
31724
 
30842
31725
  Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
30843
31726
 
30844
31727
  Returns the councillor responses with a summary footer.`,
30845
31728
  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.')
31729
+ prompt: z6.string().describe("The prompt to send to all councillors"),
31730
+ preset: z6.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
30848
31731
  },
30849
31732
  async execute(args, toolContext) {
30850
31733
  if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
@@ -30896,14 +31779,14 @@ import * as fs10 from "node:fs";
30896
31779
  // src/tui-state.ts
30897
31780
  import * as fs9 from "node:fs";
30898
31781
  import * as os5 from "node:os";
30899
- import * as path16 from "node:path";
31782
+ import * as path17 from "node:path";
30900
31783
  var STATE_DIR = "oh-my-opencode-slim";
30901
31784
  var STATE_FILE = "tui-state.json";
30902
31785
  function dataDir() {
30903
- return process.env.XDG_DATA_HOME ?? path16.join(os5.homedir(), ".local", "share");
31786
+ return process.env.XDG_DATA_HOME ?? path17.join(os5.homedir(), ".local", "share");
30904
31787
  }
30905
31788
  function getTuiStatePath() {
30906
- return path16.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
31789
+ return path17.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
30907
31790
  }
30908
31791
  function emptySnapshot() {
30909
31792
  return {
@@ -30939,7 +31822,7 @@ async function readTuiSnapshotAsync() {
30939
31822
  function writeTuiSnapshot(snapshot) {
30940
31823
  try {
30941
31824
  const filePath = getTuiStatePath();
30942
- fs9.mkdirSync(path16.dirname(filePath), { recursive: true });
31825
+ fs9.mkdirSync(path17.dirname(filePath), { recursive: true });
30943
31826
  fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
30944
31827
  `);
30945
31828
  } catch {}
@@ -31139,14 +32022,14 @@ var BINARY_PREFIXES = [
31139
32022
  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
32023
  // src/tools/smartfetch/tool.ts
31141
32024
  import os6 from "node:os";
31142
- import path20 from "node:path";
32025
+ import path21 from "node:path";
31143
32026
  import {
31144
- tool as tool4
32027
+ tool as tool5
31145
32028
  } from "@opencode-ai/plugin";
31146
32029
 
31147
32030
  // src/tools/smartfetch/binary.ts
31148
32031
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
31149
- import path17 from "node:path";
32032
+ import path18 from "node:path";
31150
32033
  function extensionForMime(contentType) {
31151
32034
  const mime = contentType.split(";")[0]?.trim().toLowerCase();
31152
32035
  const map = {
@@ -31167,10 +32050,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
31167
32050
  async function saveBinary(binaryDir, data, contentType, filename) {
31168
32051
  await mkdir2(binaryDir, { recursive: true });
31169
32052
  const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
31170
- const parsed = path17.parse(initialName);
32053
+ const parsed = path18.parse(initialName);
31171
32054
  for (let attempt = 0;attempt < 1000; attempt++) {
31172
32055
  const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
31173
- const file = path17.join(binaryDir, candidateName);
32056
+ const file = path18.join(binaryDir, candidateName);
31174
32057
  try {
31175
32058
  await writeFile2(file, data, { flag: "wx" });
31176
32059
  return file;
@@ -31309,7 +32192,7 @@ var M = class u2 {
31309
32192
  return this.#S;
31310
32193
  }
31311
32194
  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;
32195
+ 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
32196
  if (x !== undefined && typeof x?.now != "function")
31314
32197
  throw new TypeError("perf option must have a now() method if specified");
31315
32198
  if (this.#m = x ?? C, t !== 0 && !F(t))
@@ -31327,7 +32210,7 @@ var M = class u2 {
31327
32210
  throw new TypeError("memoMethod must be a function if defined");
31328
32211
  if (this.#U = m, a !== undefined && typeof a != "function")
31329
32212
  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) {
32213
+ 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
32214
  if (this.#u !== 0 && !F(this.#u))
31332
32215
  throw new TypeError("maxSize must be a positive integer if specified");
31333
32216
  if (!F(this.maxEntrySize))
@@ -31707,8 +32590,8 @@ var M = class u2 {
31707
32590
  let A = this.#p(b);
31708
32591
  if (!y && !A)
31709
32592
  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;
32593
+ let z7 = this.#P(e, b, _, w), v = z7.__staleWhileFetching !== undefined && i;
32594
+ return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z7.__staleWhileFetching : z7.__returned = z7;
31712
32595
  }
31713
32596
  }
31714
32597
  forceFetch(e, t = {}) {
@@ -31824,7 +32707,7 @@ var M = class u2 {
31824
32707
  };
31825
32708
 
31826
32709
  // src/tools/smartfetch/network.ts
31827
- import path18 from "node:path";
32710
+ import path19 from "node:path";
31828
32711
 
31829
32712
  // src/tools/smartfetch/utils.ts
31830
32713
  var import_readability = __toESM(require_readability(), 1);
@@ -32549,7 +33432,7 @@ function inferFilenameFromUrl(url) {
32549
33432
  function truncateFilename(name, maxLength = 180) {
32550
33433
  if (name.length <= maxLength)
32551
33434
  return name;
32552
- const parsed = path18.parse(name);
33435
+ const parsed = path19.parse(name);
32553
33436
  const ext = parsed.ext || "";
32554
33437
  const baseLimit = Math.max(1, maxLength - ext.length);
32555
33438
  return `${parsed.name.slice(0, baseLimit)}${ext}`;
@@ -32719,9 +33602,9 @@ function isInvalidLlmsResult(fetchResult) {
32719
33602
  }
32720
33603
 
32721
33604
  // src/tools/smartfetch/secondary-model.ts
32722
- import { existsSync as existsSync11 } from "node:fs";
33605
+ import { existsSync as existsSync12 } from "node:fs";
32723
33606
  import { readFile as readFile4 } from "node:fs/promises";
32724
- import path19 from "node:path";
33607
+ import path20 from "node:path";
32725
33608
  function parseModelRef(value) {
32726
33609
  if (!value)
32727
33610
  return;
@@ -32747,8 +33630,8 @@ function pickAgentModelRef(value) {
32747
33630
  }
32748
33631
  function findPreferredOpenCodeConfigPath(baseDir) {
32749
33632
  for (const file of ["opencode.jsonc", "opencode.json"]) {
32750
- const fullPath = path19.join(baseDir, file);
32751
- if (existsSync11(fullPath))
33633
+ const fullPath = path20.join(baseDir, file);
33634
+ if (existsSync12(fullPath))
32752
33635
  return fullPath;
32753
33636
  }
32754
33637
  return;
@@ -32764,7 +33647,7 @@ async function readOpenCodeConfigFile(configPath) {
32764
33647
  }
32765
33648
  }
32766
33649
  async function readEffectiveOpenCodeConfig(directory) {
32767
- const projectDir = path19.join(directory, ".opencode");
33650
+ const projectDir = path20.join(directory, ".opencode");
32768
33651
  const userDirs = getConfigSearchDirs();
32769
33652
  const projectPath = findPreferredOpenCodeConfigPath(projectDir);
32770
33653
  const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
@@ -32845,6 +33728,29 @@ function isUsableSecondaryText(text) {
32845
33728
  return false;
32846
33729
  return true;
32847
33730
  }
33731
+ var SESSION_DELETE_RETRIES = 3;
33732
+ var SESSION_DELETE_RETRY_DELAY_MS = 500;
33733
+ var SECONDARY_MODEL_TIMEOUT_MS = 30000;
33734
+ var _testConfig = {
33735
+ deleteRetryDelayMs: SESSION_DELETE_RETRY_DELAY_MS
33736
+ };
33737
+ async function deleteSessionSafely(client, sessionId, directory) {
33738
+ for (let attempt = 1;attempt <= SESSION_DELETE_RETRIES; attempt++) {
33739
+ try {
33740
+ await client.session.delete({
33741
+ path: { id: sessionId },
33742
+ query: { directory }
33743
+ });
33744
+ return;
33745
+ } catch (error) {
33746
+ if (attempt >= SESSION_DELETE_RETRIES) {
33747
+ console.warn(`[smartfetch] Failed to clean up secondary session ${sessionId} ` + `after ${SESSION_DELETE_RETRIES} attempts: ` + (error instanceof Error ? error.message : String(error)));
33748
+ return;
33749
+ }
33750
+ await new Promise((resolve3) => setTimeout(resolve3, _testConfig.deleteRetryDelayMs));
33751
+ }
33752
+ }
33753
+ }
32848
33754
  async function runSecondaryModel(client, directory, model, prompt, content) {
32849
33755
  const session2 = await client.session.create({
32850
33756
  responseStyle: "data",
@@ -32871,23 +33777,26 @@ Note: only the first ${inputChars} characters of a longer fetched document were
32871
33777
  const toolIDsData = toolIDsResponse;
32872
33778
  const toolIDs = Array.isArray(toolIDsData.data) ? toolIDsData.data : Array.isArray(toolIDsResponse) ? toolIDsResponse : [];
32873
33779
  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
- });
33780
+ const result = await Promise.race([
33781
+ client.session.prompt({
33782
+ responseStyle: "data",
33783
+ throwOnError: true,
33784
+ path: { id: sessionId },
33785
+ query: { directory },
33786
+ body: {
33787
+ model,
33788
+ system: "Answer only from the supplied content. Do not use tools or outside knowledge.",
33789
+ tools: disabledTools,
33790
+ parts: [
33791
+ {
33792
+ type: "text",
33793
+ text: buildPrompt(truncatedContent, effectivePrompt)
33794
+ }
33795
+ ]
33796
+ }
33797
+ }),
33798
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Secondary model timed out")), SECONDARY_MODEL_TIMEOUT_MS))
33799
+ ]);
32891
33800
  const parts = result?.data?.parts ?? result?.parts ?? [];
32892
33801
  const text = parts.map((part) => part?.type === "text" ? part.text || "" : "").join("").trim();
32893
33802
  return {
@@ -32897,12 +33806,7 @@ Note: only the first ${inputChars} characters of a longer fetched document were
32897
33806
  sourceChars
32898
33807
  };
32899
33808
  } finally {
32900
- await client.session.delete({
32901
- path: { id: sessionId },
32902
- query: { directory }
32903
- }).catch(() => {
32904
- return;
32905
- });
33809
+ await deleteSessionSafely(client, sessionId, directory);
32906
33810
  }
32907
33811
  }
32908
33812
  async function runSecondaryModelWithFallback(client, directory, models, prompt, content) {
@@ -32923,20 +33827,20 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
32923
33827
  }
32924
33828
 
32925
33829
  // src/tools/smartfetch/tool.ts
32926
- var z6 = tool4.schema;
33830
+ var z7 = tool5.schema;
32927
33831
  function createWebfetchTool(pluginCtx, options = {}) {
32928
- const binaryDir = options.binaryDir || path20.join(os6.tmpdir(), "opencode-smartfetch");
32929
- return tool4({
33832
+ const binaryDir = options.binaryDir || path21.join(os6.tmpdir(), "opencode-smartfetch");
33833
+ return tool5({
32930
33834
  description: WEBFETCH_DESCRIPTION,
32931
33835
  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.")
33836
+ url: z7.httpUrl(),
33837
+ format: z7.enum(["text", "markdown", "html"]).default("markdown"),
33838
+ timeout: z7.number().positive().max(MAX_TIMEOUT_SECONDS).optional().describe("Timeout in seconds, max 120."),
33839
+ prompt: z7.string().optional().describe("Optional extraction task to run on the fetched content using a cheap secondary model."),
33840
+ extract_main: z7.boolean().default(true),
33841
+ prefer_llms_txt: z7.enum(["auto", "always", "never"]).default("auto"),
33842
+ include_metadata: z7.boolean().default(true),
33843
+ save_binary: z7.boolean().default(false).describe("Save binary payload to disk when it fits within the active download limit.")
32940
33844
  },
32941
33845
  async execute(args, ctx) {
32942
33846
  const secondaryModels = await readSecondaryModelFromConfig(ctx.directory || pluginCtx.directory);
@@ -33481,7 +34385,7 @@ async function appLog(ctx, level, message) {
33481
34385
  }
33482
34386
  var HEALTH_CHECK = {
33483
34387
  minAgents: 5,
33484
- minTools: 5,
34388
+ minTools: 4,
33485
34389
  minMcps: 1
33486
34390
  };
33487
34391
  async function probeJSDOM() {
@@ -33526,6 +34430,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33526
34430
  let companionManager;
33527
34431
  let councilTools;
33528
34432
  let cancelTaskTools;
34433
+ let acpRunTools;
33529
34434
  let webfetch;
33530
34435
  let rewriteDisplayNameMentions;
33531
34436
  let toolCount = 0;
@@ -33570,6 +34475,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33570
34475
  depthTracker = new SubagentDepthTracker;
33571
34476
  councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
33572
34477
  mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
34478
+ acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
33573
34479
  webfetch = createWebfetchTool(ctx);
33574
34480
  backgroundJobBoard = new BackgroundJobBoard({
33575
34481
  maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
@@ -33578,7 +34484,8 @@ var OhMyOpenCodeLite = async (ctx) => {
33578
34484
  });
33579
34485
  multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
33580
34486
  autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
33581
- autoUpdate: config.autoUpdate ?? true
34487
+ autoUpdate: config.autoUpdate ?? true,
34488
+ companion: config.companion
33582
34489
  });
33583
34490
  phaseReminderHook = createPhaseReminderHook();
33584
34491
  filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
@@ -33608,7 +34515,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33608
34515
  backgroundJobBoard,
33609
34516
  shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
33610
34517
  });
33611
- toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + 1 + 2;
34518
+ toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + Object.keys(acpRunTools).length + 1 + 2;
33612
34519
  } catch (err) {
33613
34520
  log("[plugin] FATAL: init failed", String(err));
33614
34521
  await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
@@ -33644,6 +34551,22 @@ var OhMyOpenCodeLite = async (ctx) => {
33644
34551
  appLog(ctx, "warn", msg).catch(() => {});
33645
34552
  }
33646
34553
  });
34554
+ if (config.companion?.enabled === true) {
34555
+ try {
34556
+ const companionResult = await ensureCompanionVersion({
34557
+ config: config.companion,
34558
+ downloadTimeoutMs: 3000,
34559
+ lockTimeoutMs: 500
34560
+ });
34561
+ if (companionResult.status === "installed") {
34562
+ log("[companion] updated before startup", companionResult.version);
34563
+ } else if (companionResult.status === "failed") {
34564
+ log("[companion] startup update failed", companionResult.error);
34565
+ }
34566
+ } catch (err) {
34567
+ log("[companion] startup update failed", String(err));
34568
+ }
34569
+ }
33647
34570
  companionManager.onLoad();
33648
34571
  return {
33649
34572
  name: "oh-my-opencode-slim",
@@ -33651,6 +34574,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33651
34574
  tool: {
33652
34575
  ...councilTools,
33653
34576
  ...cancelTaskTools,
34577
+ ...acpRunTools,
33654
34578
  webfetch,
33655
34579
  ast_grep_search,
33656
34580
  ast_grep_replace