@khalilgharbaoui/opencode-claude-code-plugin 0.36.5 → 0.37.0

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
@@ -5471,6 +5471,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
5471
5471
  "skipping remote MCP server with no url",
5472
5472
  "stale scratch directory sweep failed",
5473
5473
  "task_batch resolving with child results missing",
5474
+ "this opencode process is running an older plugin build than the one on disk",
5474
5475
  "timed out pending proxy call",
5475
5476
  "turn abort listener threw",
5476
5477
  "two different skills share a name; bridging the one the nearest root claimed",
@@ -5619,6 +5620,10 @@ var BUNDLE_DATA_ALLOWLIST = {
5619
5620
  toolNames: "name",
5620
5621
  cliFloor: "version",
5621
5622
  cliVersion: "version",
5623
+ // The two halves of the stale-build WARN: what this process loaded and what
5624
+ // is on disk now. Both are this package's own version strings.
5625
+ loadedVersion: "version",
5626
+ onDiskVersion: "version",
5622
5627
  opencode: "version",
5623
5628
  plugin: "version",
5624
5629
  version: "version",
@@ -5868,8 +5873,8 @@ function redactLogLine(raw, context) {
5868
5873
  if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
5869
5874
  return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
5870
5875
  }
5871
- function readLogTail(path10, maxBytes = BUNDLE_TAIL_BYTES) {
5872
- const fd = openSync(path10, "r");
5876
+ function readLogTail(path11, maxBytes = BUNDLE_TAIL_BYTES) {
5877
+ const fd = openSync(path11, "r");
5873
5878
  try {
5874
5879
  const size = fstatSync(fd).size;
5875
5880
  const length = Math.min(size, maxBytes);
@@ -6059,6 +6064,11 @@ async function fetchPlanUsage(cliPath, options = {}) {
6059
6064
  }
6060
6065
  }
6061
6066
 
6067
+ // src/stale-build.ts
6068
+ import * as fs5 from "fs";
6069
+ import * as path6 from "path";
6070
+ import { fileURLToPath as fileURLToPath2 } from "url";
6071
+
6062
6072
  // src/startup-diagnostics.ts
6063
6073
  import { execFile as execFile3 } from "child_process";
6064
6074
  import * as fs4 from "fs";
@@ -6198,6 +6208,161 @@ function logStartupDiagnostics(providers, opencodeVersion) {
6198
6208
  })();
6199
6209
  }
6200
6210
 
6211
+ // src/stale-build.ts
6212
+ var STALE_BUILD_MARKER = "\u258C **restart opencode:**";
6213
+ var STALE_BUILD_THROTTLE_MS = 6e4;
6214
+ var STALE_BUILD_SESSION_CAP = 64;
6215
+ function compareBuilds(loaded, onDisk) {
6216
+ if (!onDisk) return null;
6217
+ if (onDisk.version !== loaded.version) {
6218
+ return {
6219
+ kind: "version",
6220
+ loadedVersion: loaded.version,
6221
+ onDiskVersion: onDisk.version,
6222
+ loadedAt: loaded.loadedAt
6223
+ };
6224
+ }
6225
+ if (loaded.mtimeMs === void 0 || loaded.size === void 0) return null;
6226
+ if (onDisk.mtimeMs === loaded.mtimeMs && onDisk.size === loaded.size) return null;
6227
+ return {
6228
+ kind: "rebuilt",
6229
+ loadedVersion: loaded.version,
6230
+ onDiskVersion: onDisk.version,
6231
+ loadedAt: loaded.loadedAt,
6232
+ rebuiltAt: onDisk.mtimeMs
6233
+ };
6234
+ }
6235
+ function formatLocalMinute(ms) {
6236
+ const when = new Date(ms);
6237
+ const pad = (value) => String(value).padStart(2, "0");
6238
+ return `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())} ${pad(when.getHours())}:${pad(when.getMinutes())}`;
6239
+ }
6240
+ function formatStaleBuildNote(stale) {
6241
+ const loadedAt = formatLocalMinute(stale.loadedAt);
6242
+ if (stale.kind === "version") {
6243
+ return `${STALE_BUILD_MARKER} plugin ${stale.onDiskVersion} is on disk, but this opencode process still runs ${stale.loadedVersion}, loaded ${loadedAt}. opencode reads a plugin once at startup, so fixes since then are not active here: quit every opencode window and relaunch.`;
6244
+ }
6245
+ const rebuiltAt = stale.rebuiltAt === void 0 ? "since" : `at ${formatLocalMinute(stale.rebuiltAt)}`;
6246
+ return `${STALE_BUILD_MARKER} this opencode process loaded plugin ${stale.loadedVersion} at ${loadedAt}, and that build was rebuilt on disk ${rebuiltAt}. opencode reads a plugin once at startup, so what you just built is not running here: quit every opencode window and relaunch.`;
6247
+ }
6248
+ function describeBuildStatus(status) {
6249
+ const loadedAt = formatLocalMinute(status.loaded.loadedAt);
6250
+ switch (status.verdict) {
6251
+ case "current":
6252
+ return `${status.loaded.version}, loaded ${loadedAt}, current`;
6253
+ case "version":
6254
+ return `${status.loaded.version}, loaded ${loadedAt}; on disk ${status.onDisk?.version ?? "unknown"}. Restart opencode to run it`;
6255
+ case "rebuilt":
6256
+ return `${status.loaded.version}, loaded ${loadedAt}; the same version was rebuilt on disk ${status.onDisk ? `at ${formatLocalMinute(status.onDisk.mtimeMs)}` : "since"}. Restart opencode to run it`;
6257
+ case "unreadable":
6258
+ return `${status.loaded.version}, loaded ${loadedAt}; the build on disk could not be read`;
6259
+ }
6260
+ }
6261
+ function createStaleBuildWatch(io) {
6262
+ const throttleMs = io.throttleMs ?? STALE_BUILD_THROTTLE_MS;
6263
+ const warn = io.warn ?? ((data) => {
6264
+ log.warn(
6265
+ "this opencode process is running an older plugin build than the one on disk",
6266
+ data
6267
+ );
6268
+ });
6269
+ const claimed = /* @__PURE__ */ new Set();
6270
+ const warned = /* @__PURE__ */ new Set();
6271
+ let checkedAt;
6272
+ let cached = null;
6273
+ const read = () => {
6274
+ let onDisk;
6275
+ try {
6276
+ onDisk = io.readOnDisk();
6277
+ } catch {
6278
+ onDisk = void 0;
6279
+ }
6280
+ const stale = compareBuilds(io.loaded, onDisk);
6281
+ const verdict = !onDisk ? "unreadable" : stale ? stale.kind : "current";
6282
+ return { loaded: io.loaded, onDisk, stale, verdict };
6283
+ };
6284
+ return {
6285
+ get loaded() {
6286
+ return io.loaded;
6287
+ },
6288
+ check() {
6289
+ const now = io.now();
6290
+ if (checkedAt !== void 0 && now - checkedAt < throttleMs) return cached;
6291
+ checkedAt = now;
6292
+ const status = read();
6293
+ cached = status.stale;
6294
+ if (status.stale && status.onDisk) {
6295
+ const identity = `${status.stale.kind}:${status.onDisk.version}:${status.onDisk.mtimeMs}:${status.onDisk.size}`;
6296
+ if (!warned.has(identity)) {
6297
+ warned.add(identity);
6298
+ warn({
6299
+ kind: status.stale.kind,
6300
+ loadedVersion: status.stale.loadedVersion,
6301
+ onDiskVersion: status.stale.onDiskVersion,
6302
+ path: io.loaded.entryPath
6303
+ });
6304
+ }
6305
+ }
6306
+ return cached;
6307
+ },
6308
+ describe() {
6309
+ return read();
6310
+ },
6311
+ claimSession(sessionId) {
6312
+ if (claimed.has(sessionId)) return false;
6313
+ claimed.add(sessionId);
6314
+ while (claimed.size > STALE_BUILD_SESSION_CAP) {
6315
+ const oldest = claimed.values().next();
6316
+ if (oldest.done) break;
6317
+ claimed.delete(oldest.value);
6318
+ }
6319
+ return true;
6320
+ },
6321
+ claimedSessions() {
6322
+ return [...claimed];
6323
+ }
6324
+ };
6325
+ }
6326
+ function defaultEntryPath() {
6327
+ return fileURLToPath2(import.meta.url);
6328
+ }
6329
+ function readOnDiskBuild(entryPath) {
6330
+ try {
6331
+ const raw = fs5.readFileSync(path6.join(path6.dirname(entryPath), "..", "package.json"), "utf8");
6332
+ const version = JSON.parse(raw).version;
6333
+ if (typeof version !== "string" || version.length === 0) return void 0;
6334
+ const stat = fs5.statSync(entryPath);
6335
+ return { version, mtimeMs: stat.mtimeMs, size: stat.size };
6336
+ } catch {
6337
+ return void 0;
6338
+ }
6339
+ }
6340
+ function recordLoadedBuild() {
6341
+ const entryPath = defaultEntryPath();
6342
+ let mtimeMs;
6343
+ let size;
6344
+ try {
6345
+ const stat = fs5.statSync(entryPath);
6346
+ mtimeMs = stat.mtimeMs;
6347
+ size = stat.size;
6348
+ } catch {
6349
+ }
6350
+ return { version: pluginVersion(), entryPath, mtimeMs, size, loadedAt: Date.now() };
6351
+ }
6352
+ var loadedAtImport = recordLoadedBuild();
6353
+ var watch;
6354
+ function staleBuildWatch() {
6355
+ if (!watch) {
6356
+ const loaded = loadedAtImport;
6357
+ watch = createStaleBuildWatch({
6358
+ loaded,
6359
+ readOnDisk: () => readOnDiskBuild(loaded.entryPath),
6360
+ now: () => Date.now()
6361
+ });
6362
+ }
6363
+ return watch;
6364
+ }
6365
+
6201
6366
  // src/doctor.ts
6202
6367
  var DOCTOR_COMMAND = "claude-code-doctor";
6203
6368
  var DOCTOR_COMMAND_DESCRIPTION = "Report what the Claude Code plugin sees: versions, cwd, live processes, pending proxy calls. Add `usage` for plan windows, or `bundle` for a redacted log you can paste into an issue";
@@ -6269,6 +6434,7 @@ function formatDoctorReport(report) {
6269
6434
  lines.push("| Field | Value |");
6270
6435
  lines.push("|---|---|");
6271
6436
  lines.push(`| plugin | ${report.plugin} |`);
6437
+ lines.push(`| plugin build | ${describeBuildStatus(report.build)} |`);
6272
6438
  lines.push(`| opencode | ${report.opencode} |`);
6273
6439
  lines.push(`| claude CLI | \`${report.claudeCli.path}\` (${report.claudeCli.version}) |`);
6274
6440
  lines.push(`| cwd | \`${report.cwd.resolved}\` (${report.cwd.source}) |`);
@@ -6512,6 +6678,8 @@ async function gatherDoctorReport(options) {
6512
6678
  }) : { status: "not-requested" };
6513
6679
  return {
6514
6680
  plugin: base.plugin,
6681
+ // `describe`, not `check`: unthrottled, silent, and it claims no session.
6682
+ build: staleBuildWatch().describe(),
6515
6683
  opencode: base.opencode,
6516
6684
  claudeCli: { path: cliPath, version: cli?.raw ?? "not detected" },
6517
6685
  cwd: base.cwd,
@@ -6578,6 +6746,7 @@ async function buildDoctorReport(options) {
6578
6746
  const report = await gatherDoctorReport(options);
6579
6747
  log.info("claude-code doctor report", {
6580
6748
  plugin: report.plugin,
6749
+ verdict: report.build.verdict,
6581
6750
  opencode: report.opencode,
6582
6751
  cwd: report.cwd,
6583
6752
  processes: report.processes.length,
@@ -6601,7 +6770,7 @@ Could not build the report: ${message}`;
6601
6770
 
6602
6771
  // src/agent-models.ts
6603
6772
  import { readFile, readdir } from "fs/promises";
6604
- import path6 from "path";
6773
+ import path7 from "path";
6605
6774
 
6606
6775
  // src/models.ts
6607
6776
  var PROVIDER_ID = "claude-code";
@@ -7104,11 +7273,11 @@ async function readAgentMarkdownRecords(directories) {
7104
7273
  const name = entry.slice(0, -3);
7105
7274
  if (records[name]) continue;
7106
7275
  try {
7107
- const text = await readFile(path6.join(directory, entry), "utf8");
7276
+ const text = await readFile(path7.join(directory, entry), "utf8");
7108
7277
  records[name] = parseAgentFrontmatter(text);
7109
7278
  } catch (err) {
7110
7279
  log.debug("failed to read agent markdown", {
7111
- file: path6.join(directory, entry),
7280
+ file: path7.join(directory, entry),
7112
7281
  error: String(err)
7113
7282
  });
7114
7283
  }
@@ -7120,12 +7289,12 @@ function agentDirectories(home, projectDirectory) {
7120
7289
  const directories = [];
7121
7290
  if (projectDirectory) {
7122
7291
  for (const name of AGENT_DIR_NAMES) {
7123
- directories.push(path6.join(projectDirectory, ".opencode", name));
7292
+ directories.push(path7.join(projectDirectory, ".opencode", name));
7124
7293
  }
7125
7294
  }
7126
7295
  if (home) {
7127
7296
  for (const name of AGENT_DIR_NAMES) {
7128
- directories.push(path6.join(home, ".config", "opencode", name));
7297
+ directories.push(path7.join(home, ".config", "opencode", name));
7129
7298
  }
7130
7299
  }
7131
7300
  return directories;
@@ -7294,7 +7463,8 @@ var PLUGIN_NOTE_MARKERS = [
7294
7463
  FAILOVER_MARKER,
7295
7464
  ACCOUNT_BLOCK_MARKER,
7296
7465
  MODEL_FALLBACK_MARKER,
7297
- UNATTENDED_REPLAY_MARKER
7466
+ UNATTENDED_REPLAY_MARKER,
7467
+ STALE_BUILD_MARKER
7298
7468
  ];
7299
7469
  function isPluginNote(part) {
7300
7470
  if (!part || part.type !== "text" || typeof part.text !== "string") return false;
@@ -7681,14 +7851,14 @@ Now continuing with the current message:
7681
7851
 
7682
7852
  // src/skill-bridge.ts
7683
7853
  import * as crypto3 from "crypto";
7684
- import * as fs5 from "fs";
7854
+ import * as fs6 from "fs";
7685
7855
  import * as os3 from "os";
7686
- import * as path7 from "path";
7687
- import { fileURLToPath as fileURLToPath2 } from "url";
7856
+ import * as path8 from "path";
7857
+ import { fileURLToPath as fileURLToPath3 } from "url";
7688
7858
  function bundledSkillsDir() {
7689
7859
  try {
7690
- const here = fileURLToPath2(import.meta.url);
7691
- const dir = path7.resolve(path7.dirname(here), "..", "skills");
7860
+ const here = fileURLToPath3(import.meta.url);
7861
+ const dir = path8.resolve(path8.dirname(here), "..", "skills");
7692
7862
  return dirExists2(dir) ? dir : null;
7693
7863
  } catch {
7694
7864
  return null;
@@ -7696,16 +7866,16 @@ function bundledSkillsDir() {
7696
7866
  }
7697
7867
  function dirExists2(p) {
7698
7868
  try {
7699
- return fs5.statSync(p).isDirectory();
7869
+ return fs6.statSync(p).isDirectory();
7700
7870
  } catch {
7701
7871
  return false;
7702
7872
  }
7703
7873
  }
7704
7874
  function realPath(p) {
7705
7875
  try {
7706
- return fs5.realpathSync(p);
7876
+ return fs6.realpathSync(p);
7707
7877
  } catch {
7708
- return path7.resolve(p);
7878
+ return path8.resolve(p);
7709
7879
  }
7710
7880
  }
7711
7881
  function envEnabled(name) {
@@ -7726,10 +7896,10 @@ function declaredSkillName(source) {
7726
7896
  return SAFE_SKILL_NAME.test(name) ? name : null;
7727
7897
  }
7728
7898
  function readSkill(root, entry) {
7729
- const dir = path7.join(root, entry);
7899
+ const dir = path8.join(root, entry);
7730
7900
  let source;
7731
7901
  try {
7732
- source = fs5.readFileSync(path7.join(dir, "SKILL.md"), "utf8");
7902
+ source = fs6.readFileSync(path8.join(dir, "SKILL.md"), "utf8");
7733
7903
  } catch {
7734
7904
  return null;
7735
7905
  }
@@ -7747,47 +7917,47 @@ function skillRoots(cwd) {
7747
7917
  const roots = [];
7748
7918
  const seen = /* @__PURE__ */ new Set();
7749
7919
  const push = (p) => {
7750
- const abs = path7.resolve(p);
7920
+ const abs = path8.resolve(p);
7751
7921
  if (seen.has(abs)) return;
7752
7922
  seen.add(abs);
7753
7923
  if (dirExists2(abs)) roots.push(abs);
7754
7924
  };
7755
7925
  const home = os3.homedir();
7756
- const homeDir = home ? path7.resolve(home) : null;
7757
- let current = path7.resolve(cwd);
7926
+ const homeDir = home ? path8.resolve(home) : null;
7927
+ let current = path8.resolve(cwd);
7758
7928
  while (true) {
7759
- push(path7.join(current, ".opencode", "skills"));
7929
+ push(path8.join(current, ".opencode", "skills"));
7760
7930
  if (current !== homeDir) {
7761
- push(path7.join(current, ".claude", "skills"));
7762
- push(path7.join(current, ".agents", "skills"));
7931
+ push(path8.join(current, ".claude", "skills"));
7932
+ push(path8.join(current, ".agents", "skills"));
7763
7933
  }
7764
- const parent = path7.dirname(current);
7934
+ const parent = path8.dirname(current);
7765
7935
  if (parent === current) break;
7766
7936
  current = parent;
7767
7937
  }
7768
- if (homeDir) push(path7.join(homeDir, ".opencode", "skills"));
7938
+ if (homeDir) push(path8.join(homeDir, ".opencode", "skills"));
7769
7939
  const envDir = process.env.OPENCODE_CONFIG_DIR;
7770
7940
  if (envDir) {
7771
- push(path7.join(envDir, "skills"));
7772
- push(path7.join(envDir, "skill"));
7941
+ push(path8.join(envDir, "skills"));
7942
+ push(path8.join(envDir, "skill"));
7773
7943
  }
7774
- const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path7.join(homeDir, ".config") : null);
7944
+ const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path8.join(homeDir, ".config") : null);
7775
7945
  if (xdg) {
7776
- push(path7.join(xdg, "opencode", "skills"));
7777
- push(path7.join(xdg, "opencode", "skill"));
7946
+ push(path8.join(xdg, "opencode", "skills"));
7947
+ push(path8.join(xdg, "opencode", "skill"));
7778
7948
  }
7779
7949
  if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
7780
7950
  if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
7781
- push(path7.join(homeDir, ".claude", "skills"));
7951
+ push(path8.join(homeDir, ".claude", "skills"));
7782
7952
  }
7783
- push(path7.join(homeDir, ".agents", "skills"));
7953
+ push(path8.join(homeDir, ".agents", "skills"));
7784
7954
  }
7785
7955
  return roots;
7786
7956
  }
7787
7957
  function collectSkills(root, claimed, found) {
7788
7958
  let entries;
7789
7959
  try {
7790
- entries = fs5.readdirSync(root, { withFileTypes: true });
7960
+ entries = fs6.readdirSync(root, { withFileTypes: true });
7791
7961
  } catch {
7792
7962
  return;
7793
7963
  }
@@ -7827,23 +7997,23 @@ function discoverBundledSkills() {
7827
7997
  }
7828
7998
  function claudeConfigDir(configDir) {
7829
7999
  const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
7830
- if (explicit) return path7.resolve(expandHome(explicit));
8000
+ if (explicit) return path8.resolve(expandHome(explicit));
7831
8001
  const home = os3.homedir();
7832
- return home ? path7.resolve(home, ".claude") : null;
8002
+ return home ? path8.resolve(home, ".claude") : null;
7833
8003
  }
7834
8004
  function installedPluginSkillRoots(configDir, cwd) {
7835
8005
  if (!configDir) return [];
7836
8006
  let parsed;
7837
8007
  try {
7838
8008
  parsed = JSON.parse(
7839
- fs5.readFileSync(path7.join(configDir, "plugins", "installed_plugins.json"), "utf8")
8009
+ fs6.readFileSync(path8.join(configDir, "plugins", "installed_plugins.json"), "utf8")
7840
8010
  );
7841
8011
  } catch {
7842
8012
  return [];
7843
8013
  }
7844
8014
  const plugins = parsed?.plugins;
7845
8015
  if (!plugins || typeof plugins !== "object") return [];
7846
- const workspace = path7.resolve(cwd);
8016
+ const workspace = path8.resolve(cwd);
7847
8017
  const roots = [];
7848
8018
  for (const installs of Object.values(plugins)) {
7849
8019
  if (!Array.isArray(installs)) continue;
@@ -7851,28 +8021,28 @@ function installedPluginSkillRoots(configDir, cwd) {
7851
8021
  const entry = install;
7852
8022
  if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
7853
8023
  if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
7854
- roots.push(path7.join(entry.installPath, "skills"));
8024
+ roots.push(path8.join(entry.installPath, "skills"));
7855
8025
  }
7856
8026
  }
7857
8027
  return roots;
7858
8028
  }
7859
8029
  function coversWorkspace(projectPath, workspace) {
7860
8030
  if (typeof projectPath !== "string" || !projectPath) return false;
7861
- const root = path7.resolve(projectPath);
8031
+ const root = path8.resolve(projectPath);
7862
8032
  if (root === workspace) return true;
7863
- const rel = path7.relative(root, workspace);
7864
- return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
8033
+ const rel = path8.relative(root, workspace);
8034
+ return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
7865
8035
  }
7866
8036
  function discoverNativeClaudeSkills(opts) {
7867
8037
  const found = [];
7868
8038
  const scanned = /* @__PURE__ */ new Set();
7869
8039
  const scan = (root, scope) => {
7870
- const abs = path7.resolve(root);
8040
+ const abs = path8.resolve(root);
7871
8041
  if (scanned.has(abs)) return;
7872
8042
  scanned.add(abs);
7873
8043
  let entries;
7874
8044
  try {
7875
- entries = fs5.readdirSync(abs, { withFileTypes: true });
8045
+ entries = fs6.readdirSync(abs, { withFileTypes: true });
7876
8046
  } catch {
7877
8047
  return;
7878
8048
  }
@@ -7884,13 +8054,13 @@ function discoverNativeClaudeSkills(opts) {
7884
8054
  }
7885
8055
  };
7886
8056
  const configDir = claudeConfigDir(opts.configDir);
7887
- if (configDir) scan(path7.join(configDir, "skills"), "user");
8057
+ if (configDir) scan(path8.join(configDir, "skills"), "user");
7888
8058
  const home = os3.homedir();
7889
- const homeDir = home ? path7.resolve(home) : null;
7890
- let current = path7.resolve(opts.cwd);
8059
+ const homeDir = home ? path8.resolve(home) : null;
8060
+ let current = path8.resolve(opts.cwd);
7891
8061
  while (true) {
7892
- if (current !== homeDir) scan(path7.join(current, ".claude", "skills"), "project");
7893
- const parent = path7.dirname(current);
8062
+ if (current !== homeDir) scan(path8.join(current, ".claude", "skills"), "project");
8063
+ const parent = path8.dirname(current);
7894
8064
  if (parent === current) break;
7895
8065
  current = parent;
7896
8066
  }
@@ -7924,10 +8094,10 @@ function dropNativelyLoadedSkills(skills, native) {
7924
8094
  }
7925
8095
  function readBundledSkillInfos() {
7926
8096
  return discoverBundledSkills().flatMap((skill) => {
7927
- const file = path7.join(skill.dir, "SKILL.md");
8097
+ const file = path8.join(skill.dir, "SKILL.md");
7928
8098
  let text;
7929
8099
  try {
7930
- text = fs5.readFileSync(file, "utf8");
8100
+ text = fs6.readFileSync(file, "utf8");
7931
8101
  } catch {
7932
8102
  return [];
7933
8103
  }
@@ -7951,15 +8121,15 @@ function readBundledSkillInfos() {
7951
8121
  }
7952
8122
  function linkSkill(source, target) {
7953
8123
  try {
7954
- fs5.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
8124
+ fs6.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
7955
8125
  return;
7956
8126
  } catch {
7957
- fs5.cpSync(source, target, { recursive: true, dereference: true });
8127
+ fs6.cpSync(source, target, { recursive: true, dereference: true });
7958
8128
  }
7959
8129
  }
7960
8130
  function fileExists2(p) {
7961
8131
  try {
7962
- return fs5.statSync(p).isFile();
8132
+ return fs6.statSync(p).isFile();
7963
8133
  } catch {
7964
8134
  return false;
7965
8135
  }
@@ -7968,14 +8138,14 @@ function buildSkillPluginDir(skills) {
7968
8138
  if (skills.length === 0) return null;
7969
8139
  const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
7970
8140
  const hash = crypto3.createHash("sha256").update(fingerprint).digest("hex").slice(0, 12);
7971
- const root = path7.join(pluginTmpDir(), `skills-${hash}`);
7972
- const manifest = path7.join(root, ".claude-plugin", "plugin.json");
8141
+ const root = path8.join(pluginTmpDir(), `skills-${hash}`);
8142
+ const manifest = path8.join(root, ".claude-plugin", "plugin.json");
7973
8143
  if (fileExists2(manifest)) return root;
7974
8144
  try {
7975
- fs5.rmSync(root, { recursive: true, force: true });
7976
- fs5.mkdirSync(path7.join(root, ".claude-plugin"), { recursive: true });
7977
- fs5.mkdirSync(path7.join(root, "skills"), { recursive: true });
7978
- fs5.writeFileSync(
8145
+ fs6.rmSync(root, { recursive: true, force: true });
8146
+ fs6.mkdirSync(path8.join(root, ".claude-plugin"), { recursive: true });
8147
+ fs6.mkdirSync(path8.join(root, "skills"), { recursive: true });
8148
+ fs6.writeFileSync(
7979
8149
  manifest,
7980
8150
  JSON.stringify(
7981
8151
  {
@@ -7988,7 +8158,7 @@ function buildSkillPluginDir(skills) {
7988
8158
  { encoding: "utf8", mode: 384 }
7989
8159
  );
7990
8160
  for (const skill of skills) {
7991
- linkSkill(skill.dir, path7.join(root, "skills", skill.name));
8161
+ linkSkill(skill.dir, path8.join(root, "skills", skill.name));
7992
8162
  }
7993
8163
  } catch (err) {
7994
8164
  log.warn("failed to stage opencode skill plugin dir", {
@@ -8004,7 +8174,7 @@ function registerBundledSkillPath(config) {
8004
8174
  if (!dir) return false;
8005
8175
  config.skills ??= {};
8006
8176
  const paths = config.skills.paths ??= [];
8007
- if (paths.some((p) => path7.resolve(p) === dir)) return false;
8177
+ if (paths.some((p) => path8.resolve(p) === dir)) return false;
8008
8178
  paths.push(dir);
8009
8179
  return true;
8010
8180
  }
@@ -8055,12 +8225,12 @@ import { unlink as unlink2 } from "fs/promises";
8055
8225
 
8056
8226
  // src/claude-session-bun.ts
8057
8227
  import * as os4 from "os";
8058
- import * as fs6 from "fs";
8059
- import * as path8 from "path";
8228
+ import * as fs7 from "fs";
8229
+ import * as path9 from "path";
8060
8230
  import { execFileSync } from "child_process";
8061
8231
  import { randomUUID as randomUUID4 } from "crypto";
8062
8232
  function resolveClaude(cmd = "claude") {
8063
- if (path8.isAbsolute(cmd) && fs6.existsSync(cmd)) return cmd;
8233
+ if (path9.isAbsolute(cmd) && fs7.existsSync(cmd)) return cmd;
8064
8234
  const viaBun = Bun.which(cmd);
8065
8235
  if (viaBun) return viaBun;
8066
8236
  const isWin = os4.platform() === "win32";
@@ -8069,17 +8239,17 @@ function resolveClaude(cmd = "claude") {
8069
8239
  encoding: "utf8",
8070
8240
  stdio: ["ignore", "pipe", "ignore"]
8071
8241
  });
8072
- const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs6.existsSync(p));
8242
+ const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs7.existsSync(p));
8073
8243
  if (first) return first;
8074
8244
  } catch {
8075
8245
  }
8076
8246
  throw new Error(`Could not resolve command on PATH: ${cmd}`);
8077
8247
  }
8078
8248
  function encodeCwd(cwd) {
8079
- const resolved = path8.resolve(cwd);
8249
+ const resolved = path9.resolve(cwd);
8080
8250
  let real = resolved;
8081
8251
  try {
8082
- real = fs6.realpathSync.native(resolved);
8252
+ real = fs7.realpathSync.native(resolved);
8083
8253
  } catch {
8084
8254
  }
8085
8255
  return real.replace(/[^a-zA-Z0-9]/g, "-");
@@ -8164,12 +8334,12 @@ var TERMINAL_STOP = /* @__PURE__ */ new Set(["end_turn", "stop_sequence", "max_t
8164
8334
  var delay = (ms) => new Promise((r) => setTimeout(r, ms));
8165
8335
  function resolveConfigDir(configDir) {
8166
8336
  const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
8167
- if (!value) return path8.join(os4.homedir(), ".claude");
8337
+ if (!value) return path9.join(os4.homedir(), ".claude");
8168
8338
  if (value === "~") return os4.homedir();
8169
8339
  if (value.startsWith("~/") || value.startsWith("~\\")) {
8170
- return path8.join(os4.homedir(), value.slice(2));
8340
+ return path9.join(os4.homedir(), value.slice(2));
8171
8341
  }
8172
- return path8.resolve(value);
8342
+ return path9.resolve(value);
8173
8343
  }
8174
8344
  var ClaudeSession = class {
8175
8345
  sessionId;
@@ -8187,11 +8357,11 @@ var ClaudeSession = class {
8187
8357
  signal;
8188
8358
  o;
8189
8359
  constructor(opts = {}) {
8190
- this.cwd = path8.resolve(opts.cwd ?? process.cwd());
8360
+ this.cwd = path9.resolve(opts.cwd ?? process.cwd());
8191
8361
  this.configDir = resolveConfigDir(opts.configDir);
8192
8362
  this.signal = opts.signal;
8193
8363
  this.sessionId = randomUUID4();
8194
- this.jsonlPath = path8.join(
8364
+ this.jsonlPath = path9.join(
8195
8365
  this.configDir,
8196
8366
  "projects",
8197
8367
  encodeCwd(this.cwd),
@@ -8314,7 +8484,7 @@ var ClaudeSession = class {
8314
8484
  }
8315
8485
  readRawLines() {
8316
8486
  try {
8317
- return fs6.readFileSync(this.jsonlPath, "utf8").split("\n");
8487
+ return fs7.readFileSync(this.jsonlPath, "utf8").split("\n");
8318
8488
  } catch {
8319
8489
  return [];
8320
8490
  }
@@ -8711,13 +8881,13 @@ function spawnInteractiveProcess(opts) {
8711
8881
  }
8712
8882
 
8713
8883
  // src/prompts.ts
8714
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
8884
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
8715
8885
  import { homedir as homedir7 } from "os";
8716
8886
  import { randomUUID as randomUUID5 } from "crypto";
8717
- import { dirname as dirname5, join as join8 } from "path";
8718
- function readPromptFileIfPresent(path10) {
8887
+ import { dirname as dirname6, join as join9 } from "path";
8888
+ function readPromptFileIfPresent(path11) {
8719
8889
  try {
8720
- const content = readFileSync5(path10, "utf8").trim();
8890
+ const content = readFileSync6(path11, "utf8").trim();
8721
8891
  return content || void 0;
8722
8892
  } catch {
8723
8893
  return void 0;
@@ -8726,9 +8896,9 @@ function readPromptFileIfPresent(path10) {
8726
8896
  function nearestWorkspaceAgentsPrompt(cwd) {
8727
8897
  let dir = cwd;
8728
8898
  while (true) {
8729
- const content = readPromptFileIfPresent(join8(dir, "AGENTS.md"));
8899
+ const content = readPromptFileIfPresent(join9(dir, "AGENTS.md"));
8730
8900
  if (content) return content;
8731
- const parent = dirname5(dir);
8901
+ const parent = dirname6(dir);
8732
8902
  if (parent === dir) return void 0;
8733
8903
  dir = parent;
8734
8904
  }
@@ -8835,8 +9005,8 @@ ${options.compressionSummary.trim()}`
8835
9005
  for (const s of extraSystemContent) {
8836
9006
  if (s.trim()) parts.push(s.trim());
8837
9007
  }
8838
- const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir7(), ".config");
8839
- const globalAgents = readPromptFileIfPresent(join8(configRoot, "opencode", "AGENTS.md"));
9008
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
9009
+ const globalAgents = readPromptFileIfPresent(join9(configRoot, "opencode", "AGENTS.md"));
8840
9010
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
8841
9011
  const forwarded = extraSystemContent.join("\n\n");
8842
9012
  const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
@@ -8847,10 +9017,10 @@ ${options.compressionSummary.trim()}`
8847
9017
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
8848
9018
  const content = parts.join("\n\n");
8849
9019
  if (!content) return void 0;
8850
- const path10 = join8(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
9020
+ const path11 = join9(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
8851
9021
  try {
8852
- writeFileSync4(path10, content, { encoding: "utf8", mode: 384 });
8853
- return path10;
9022
+ writeFileSync4(path11, content, { encoding: "utf8", mode: 384 });
9023
+ return path11;
8854
9024
  } catch (err) {
8855
9025
  log.warn("failed to write system prompt file", { error: String(err) });
8856
9026
  return void 0;
@@ -11903,6 +12073,11 @@ var ClaudeCodeLanguageModel = class {
11903
12073
  // the operator is usually not even looking at.
11904
12074
  childSession: !!await fetchSessionParentId(affinity)
11905
12075
  });
12076
+ let staleBuild = null;
12077
+ if (!compactionMode && mode !== "generate" && affinity !== "default") {
12078
+ staleBuild = staleBuildWatch().check();
12079
+ if (staleBuild && await fetchSessionParentId(affinity)) staleBuild = null;
12080
+ }
11906
12081
  log.info("doStream starting", {
11907
12082
  cwd,
11908
12083
  model: effectiveModelId,
@@ -12363,6 +12538,14 @@ var ClaudeCodeLanguageModel = class {
12363
12538
  if (stoppedBeforeWork()) return;
12364
12539
  state.streamStarted = true;
12365
12540
  controller.enqueue({ type: "stream-start", warnings });
12541
+ if (staleBuild && staleBuildWatch().claimSession(affinity)) {
12542
+ controller.enqueue({
12543
+ type: "text-delta",
12544
+ id: state.startTextBlock(),
12545
+ delta: formatStaleBuildNote(staleBuild)
12546
+ });
12547
+ state.endTextBlock();
12548
+ }
12366
12549
  if (failoverNote) {
12367
12550
  controller.enqueue({
12368
12551
  type: "text-delta",
@@ -12810,34 +12993,34 @@ var ClaudeCodeLanguageModel = class {
12810
12993
  import {
12811
12994
  existsSync as existsSync3,
12812
12995
  mkdirSync as mkdirSync4,
12813
- readFileSync as readFileSync6,
12996
+ readFileSync as readFileSync7,
12814
12997
  realpathSync as realpathSync4,
12815
12998
  rmSync as rmSync3,
12816
12999
  writeFileSync as writeFileSync5
12817
13000
  } from "fs";
12818
13001
  import { homedir as homedir8 } from "os";
12819
- import { dirname as dirname6, join as join9, resolve as resolve5 } from "path";
12820
- import { fileURLToPath as fileURLToPath3 } from "url";
13002
+ import { dirname as dirname7, join as join10, resolve as resolve5 } from "path";
13003
+ import { fileURLToPath as fileURLToPath4 } from "url";
12821
13004
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
12822
13005
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
12823
13006
  var alreadyRan = false;
12824
13007
  function candidateCacheRoots() {
12825
13008
  const xdg = process.env.XDG_CACHE_HOME;
12826
13009
  return [
12827
- xdg ? join9(xdg, "opencode") : null,
12828
- join9(homedir8(), ".cache", "opencode"),
12829
- join9(homedir8(), "Library", "Caches", "opencode")
13010
+ xdg ? join10(xdg, "opencode") : null,
13011
+ join10(homedir8(), ".cache", "opencode"),
13012
+ join10(homedir8(), "Library", "Caches", "opencode")
12830
13013
  ].filter((p) => Boolean(p));
12831
13014
  }
12832
13015
  function userOpencodeJsonPath() {
12833
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(homedir8(), ".config");
12834
- return join9(xdgConfig, "opencode", "opencode.json");
13016
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(homedir8(), ".config");
13017
+ return join10(xdgConfig, "opencode", "opencode.json");
12835
13018
  }
12836
13019
  function userIntendsToUseUnscoped() {
12837
13020
  const cfg = userOpencodeJsonPath();
12838
13021
  if (!existsSync3(cfg)) return false;
12839
13022
  try {
12840
- const json = JSON.parse(readFileSync6(cfg, "utf8"));
13023
+ const json = JSON.parse(readFileSync7(cfg, "utf8"));
12841
13024
  const plugins = json.plugin;
12842
13025
  if (!Array.isArray(plugins)) return false;
12843
13026
  return plugins.some(
@@ -12849,7 +13032,7 @@ function userIntendsToUseUnscoped() {
12849
13032
  }
12850
13033
  function ourLoadedDir() {
12851
13034
  try {
12852
- const filePath = fileURLToPath3(import.meta.url);
13035
+ const filePath = fileURLToPath4(import.meta.url);
12853
13036
  return realpathSync4(resolve5(filePath, "..", ".."));
12854
13037
  } catch {
12855
13038
  return null;
@@ -12858,19 +13041,19 @@ function ourLoadedDir() {
12858
13041
  function ourVersion(dir) {
12859
13042
  if (!dir) return null;
12860
13043
  try {
12861
- const pkg = JSON.parse(readFileSync6(join9(dir, "package.json"), "utf8"));
13044
+ const pkg = JSON.parse(readFileSync7(join10(dir, "package.json"), "utf8"));
12862
13045
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
12863
13046
  } catch {
12864
13047
  return null;
12865
13048
  }
12866
13049
  }
12867
13050
  function markerPath() {
12868
- const stateRoot = process.env.XDG_STATE_HOME ?? join9(homedir8(), ".local", "state");
12869
- return join9(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
13051
+ const stateRoot = process.env.XDG_STATE_HOME ?? join10(homedir8(), ".local", "state");
13052
+ return join10(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
12870
13053
  }
12871
13054
  function markerVersion() {
12872
13055
  try {
12873
- const marker = JSON.parse(readFileSync6(markerPath(), "utf8"));
13056
+ const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
12874
13057
  return typeof marker.version === "string" ? marker.version : null;
12875
13058
  } catch {
12876
13059
  return null;
@@ -12879,7 +13062,7 @@ function markerVersion() {
12879
13062
  function writeMarker(version) {
12880
13063
  const file = markerPath();
12881
13064
  try {
12882
- mkdirSync4(dirname6(file), { recursive: true });
13065
+ mkdirSync4(dirname7(file), { recursive: true });
12883
13066
  writeFileSync5(
12884
13067
  file,
12885
13068
  JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
@@ -12915,7 +13098,7 @@ function cleanupStaleUnscopedInstall() {
12915
13098
  }
12916
13099
  function cleanupOne(cacheRoot, ourDir) {
12917
13100
  if (!existsSync3(cacheRoot)) return;
12918
- const stalePath = join9(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
13101
+ const stalePath = join10(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
12919
13102
  if (!existsSync3(stalePath)) return;
12920
13103
  let realStalePath = stalePath;
12921
13104
  try {
@@ -12923,11 +13106,11 @@ function cleanupOne(cacheRoot, ourDir) {
12923
13106
  } catch {
12924
13107
  }
12925
13108
  if (ourDir && realStalePath === ourDir) return;
12926
- const pkgJsonPath = join9(stalePath, "package.json");
13109
+ const pkgJsonPath = join10(stalePath, "package.json");
12927
13110
  if (!existsSync3(pkgJsonPath)) return;
12928
13111
  let pkg = {};
12929
13112
  try {
12930
- pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
13113
+ pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
12931
13114
  } catch {
12932
13115
  return;
12933
13116
  }
@@ -12943,10 +13126,10 @@ function cleanupOne(cacheRoot, ourDir) {
12943
13126
  });
12944
13127
  return;
12945
13128
  }
12946
- const cachePkgJson = join9(cacheRoot, "package.json");
13129
+ const cachePkgJson = join10(cacheRoot, "package.json");
12947
13130
  if (!existsSync3(cachePkgJson)) return;
12948
13131
  try {
12949
- const cfg = JSON.parse(readFileSync6(cachePkgJson, "utf8"));
13132
+ const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
12950
13133
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
12951
13134
  delete cfg.dependencies[STALE_PACKAGE_NAME];
12952
13135
  writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -12960,7 +13143,7 @@ function cleanupOne(cacheRoot, ourDir) {
12960
13143
  }
12961
13144
 
12962
13145
  // src/v2-client.ts
12963
- import path9 from "path";
13146
+ import path10 from "path";
12964
13147
  function formatAgentTypeList(agents) {
12965
13148
  const lines = agents.flatMap((agent) => {
12966
13149
  if (agent.hidden === true || agent.mode === "primary") return [];
@@ -12989,7 +13172,7 @@ function toV1Session(info) {
12989
13172
  if (!info || typeof info !== "object") return void 0;
12990
13173
  const session = info;
12991
13174
  const root = session.location?.directory;
12992
- const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path9.resolve(root, session.subpath) : root : void 0;
13175
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path10.resolve(root, session.subpath) : root : void 0;
12993
13176
  return { ...info, directory, parentID: session.parentID };
12994
13177
  }
12995
13178
  function toV1Messages(messages) {