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

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
@@ -149,6 +149,7 @@ import path from "path";
149
149
  import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
150
150
  import { homedir } from "os";
151
151
  import { dirname, join } from "path";
152
+ import { isMainThread } from "worker_threads";
152
153
  var LEVEL_RANK = {
153
154
  debug: 0,
154
155
  info: 1,
@@ -241,15 +242,110 @@ function fmt(level, msg, data) {
241
242
  function shouldEmit(level) {
242
243
  return LEVEL_RANK[level] >= LEVEL_RANK[activeConfig.level];
243
244
  }
244
- function shouldTui(level) {
245
+ var tuiHostOverride;
246
+ function isTuiHost() {
247
+ return tuiHostOverride ?? !isMainThread;
248
+ }
249
+ var MAX_BUFFERED_SINK_ENTRIES = 50;
250
+ var MAX_TOASTED_MESSAGES = 200;
251
+ var toastBurstLimit = 3;
252
+ var toastBurstWindowMs = 2e3;
253
+ var activeSink = null;
254
+ var bufferedEntries = [];
255
+ var droppedBeforeSink = 0;
256
+ var toastedMessages = /* @__PURE__ */ new Set();
257
+ var burstWindowStart = 0;
258
+ var burstCount = 0;
259
+ var burstSuppressed = 0;
260
+ var burstSummaryTimer = null;
261
+ function setLogSink(sink) {
262
+ activeSink = sink;
263
+ if (!sink) return;
264
+ const pending = bufferedEntries;
265
+ const dropped = droppedBeforeSink;
266
+ bufferedEntries = [];
267
+ droppedBeforeSink = 0;
268
+ for (const entry of pending) deliverToSink(entry);
269
+ if (dropped > 0) {
270
+ const message = `${dropped} earlier log line(s) were dropped before opencode's log channel was available`;
271
+ deliverToSink({
272
+ level: "warn",
273
+ message,
274
+ line: fmt("WARN", message)
275
+ });
276
+ }
277
+ }
278
+ function routeToSink(entry) {
279
+ if (!activeSink) {
280
+ if (bufferedEntries.length >= MAX_BUFFERED_SINK_ENTRIES) {
281
+ droppedBeforeSink += 1;
282
+ return;
283
+ }
284
+ bufferedEntries.push(entry);
285
+ return;
286
+ }
287
+ deliverToSink(entry);
288
+ }
289
+ function deliverToSink(entry) {
290
+ const sink = activeSink;
291
+ if (!sink) return;
292
+ try {
293
+ sink.log(entry);
294
+ } catch {
295
+ }
296
+ if (entry.level !== "warn" && entry.level !== "error") return;
297
+ if (toastedMessages.has(entry.message)) return;
298
+ if (toastedMessages.size < MAX_TOASTED_MESSAGES) toastedMessages.add(entry.message);
299
+ if (!allowToastNow()) return;
300
+ sendToast(sink, entry);
301
+ }
302
+ function sendToast(sink, entry) {
303
+ try {
304
+ sink.toast(entry);
305
+ } catch {
306
+ }
307
+ }
308
+ function allowToastNow() {
309
+ const now = Date.now();
310
+ if (now - burstWindowStart >= toastBurstWindowMs) {
311
+ burstWindowStart = now;
312
+ burstCount = 0;
313
+ }
314
+ if (burstCount < toastBurstLimit) {
315
+ burstCount += 1;
316
+ return true;
317
+ }
318
+ burstSuppressed += 1;
319
+ armBurstSummary();
320
+ return false;
321
+ }
322
+ function armBurstSummary() {
323
+ if (burstSummaryTimer) return;
324
+ burstSummaryTimer = setTimeout(() => {
325
+ burstSummaryTimer = null;
326
+ const suppressed = burstSuppressed;
327
+ burstSuppressed = 0;
328
+ if (suppressed <= 0) return;
329
+ const sink = activeSink;
330
+ if (!sink) return;
331
+ const message = `${suppressed} more claude-code warning(s) were not shown; the full text is in opencode's log`;
332
+ sendToast(sink, { level: "warn", message, line: fmt("WARN", message) });
333
+ }, toastBurstWindowMs);
334
+ burstSummaryTimer.unref?.();
335
+ }
336
+ function shouldSurface(level) {
245
337
  if (level === "warn" || level === "error") return true;
246
338
  return activeConfig.mode === "debug";
247
339
  }
248
340
  function emit(level, msg, data) {
249
341
  if (!shouldEmit(level)) return;
250
342
  const line = fmt(level.toUpperCase(), msg, data);
251
- if (shouldTui(level)) {
252
- console.error(line);
343
+ if (shouldSurface(level)) {
344
+ if (isTuiHost()) {
345
+ routeToSink({ level, message: msg, data, line });
346
+ } else {
347
+ console.error(line);
348
+ }
253
349
  }
254
350
  writeToFile(line);
255
351
  }
@@ -5471,6 +5567,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
5471
5567
  "skipping remote MCP server with no url",
5472
5568
  "stale scratch directory sweep failed",
5473
5569
  "task_batch resolving with child results missing",
5570
+ "this opencode process is running an older plugin build than the one on disk",
5474
5571
  "timed out pending proxy call",
5475
5572
  "turn abort listener threw",
5476
5573
  "two different skills share a name; bridging the one the nearest root claimed",
@@ -5619,6 +5716,10 @@ var BUNDLE_DATA_ALLOWLIST = {
5619
5716
  toolNames: "name",
5620
5717
  cliFloor: "version",
5621
5718
  cliVersion: "version",
5719
+ // The two halves of the stale-build WARN: what this process loaded and what
5720
+ // is on disk now. Both are this package's own version strings.
5721
+ loadedVersion: "version",
5722
+ onDiskVersion: "version",
5622
5723
  opencode: "version",
5623
5724
  plugin: "version",
5624
5725
  version: "version",
@@ -5868,8 +5969,8 @@ function redactLogLine(raw, context) {
5868
5969
  if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
5869
5970
  return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
5870
5971
  }
5871
- function readLogTail(path10, maxBytes = BUNDLE_TAIL_BYTES) {
5872
- const fd = openSync(path10, "r");
5972
+ function readLogTail(path11, maxBytes = BUNDLE_TAIL_BYTES) {
5973
+ const fd = openSync(path11, "r");
5873
5974
  try {
5874
5975
  const size = fstatSync(fd).size;
5875
5976
  const length = Math.min(size, maxBytes);
@@ -6059,6 +6160,11 @@ async function fetchPlanUsage(cliPath, options = {}) {
6059
6160
  }
6060
6161
  }
6061
6162
 
6163
+ // src/stale-build.ts
6164
+ import * as fs5 from "fs";
6165
+ import * as path6 from "path";
6166
+ import { fileURLToPath as fileURLToPath2 } from "url";
6167
+
6062
6168
  // src/startup-diagnostics.ts
6063
6169
  import { execFile as execFile3 } from "child_process";
6064
6170
  import * as fs4 from "fs";
@@ -6198,6 +6304,161 @@ function logStartupDiagnostics(providers, opencodeVersion) {
6198
6304
  })();
6199
6305
  }
6200
6306
 
6307
+ // src/stale-build.ts
6308
+ var STALE_BUILD_MARKER = "\u258C **restart opencode:**";
6309
+ var STALE_BUILD_THROTTLE_MS = 6e4;
6310
+ var STALE_BUILD_SESSION_CAP = 64;
6311
+ function compareBuilds(loaded, onDisk) {
6312
+ if (!onDisk) return null;
6313
+ if (onDisk.version !== loaded.version) {
6314
+ return {
6315
+ kind: "version",
6316
+ loadedVersion: loaded.version,
6317
+ onDiskVersion: onDisk.version,
6318
+ loadedAt: loaded.loadedAt
6319
+ };
6320
+ }
6321
+ if (loaded.mtimeMs === void 0 || loaded.size === void 0) return null;
6322
+ if (onDisk.mtimeMs === loaded.mtimeMs && onDisk.size === loaded.size) return null;
6323
+ return {
6324
+ kind: "rebuilt",
6325
+ loadedVersion: loaded.version,
6326
+ onDiskVersion: onDisk.version,
6327
+ loadedAt: loaded.loadedAt,
6328
+ rebuiltAt: onDisk.mtimeMs
6329
+ };
6330
+ }
6331
+ function formatLocalMinute(ms) {
6332
+ const when = new Date(ms);
6333
+ const pad = (value) => String(value).padStart(2, "0");
6334
+ return `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())} ${pad(when.getHours())}:${pad(when.getMinutes())}`;
6335
+ }
6336
+ function formatStaleBuildNote(stale) {
6337
+ const loadedAt = formatLocalMinute(stale.loadedAt);
6338
+ if (stale.kind === "version") {
6339
+ 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.`;
6340
+ }
6341
+ const rebuiltAt = stale.rebuiltAt === void 0 ? "since" : `at ${formatLocalMinute(stale.rebuiltAt)}`;
6342
+ 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.`;
6343
+ }
6344
+ function describeBuildStatus(status) {
6345
+ const loadedAt = formatLocalMinute(status.loaded.loadedAt);
6346
+ switch (status.verdict) {
6347
+ case "current":
6348
+ return `${status.loaded.version}, loaded ${loadedAt}, current`;
6349
+ case "version":
6350
+ return `${status.loaded.version}, loaded ${loadedAt}; on disk ${status.onDisk?.version ?? "unknown"}. Restart opencode to run it`;
6351
+ case "rebuilt":
6352
+ 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`;
6353
+ case "unreadable":
6354
+ return `${status.loaded.version}, loaded ${loadedAt}; the build on disk could not be read`;
6355
+ }
6356
+ }
6357
+ function createStaleBuildWatch(io) {
6358
+ const throttleMs = io.throttleMs ?? STALE_BUILD_THROTTLE_MS;
6359
+ const warn = io.warn ?? ((data) => {
6360
+ log.warn(
6361
+ "this opencode process is running an older plugin build than the one on disk",
6362
+ data
6363
+ );
6364
+ });
6365
+ const claimed = /* @__PURE__ */ new Set();
6366
+ const warned = /* @__PURE__ */ new Set();
6367
+ let checkedAt;
6368
+ let cached = null;
6369
+ const read = () => {
6370
+ let onDisk;
6371
+ try {
6372
+ onDisk = io.readOnDisk();
6373
+ } catch {
6374
+ onDisk = void 0;
6375
+ }
6376
+ const stale = compareBuilds(io.loaded, onDisk);
6377
+ const verdict = !onDisk ? "unreadable" : stale ? stale.kind : "current";
6378
+ return { loaded: io.loaded, onDisk, stale, verdict };
6379
+ };
6380
+ return {
6381
+ get loaded() {
6382
+ return io.loaded;
6383
+ },
6384
+ check() {
6385
+ const now = io.now();
6386
+ if (checkedAt !== void 0 && now - checkedAt < throttleMs) return cached;
6387
+ checkedAt = now;
6388
+ const status = read();
6389
+ cached = status.stale;
6390
+ if (status.stale && status.onDisk) {
6391
+ const identity = `${status.stale.kind}:${status.onDisk.version}:${status.onDisk.mtimeMs}:${status.onDisk.size}`;
6392
+ if (!warned.has(identity)) {
6393
+ warned.add(identity);
6394
+ warn({
6395
+ kind: status.stale.kind,
6396
+ loadedVersion: status.stale.loadedVersion,
6397
+ onDiskVersion: status.stale.onDiskVersion,
6398
+ path: io.loaded.entryPath
6399
+ });
6400
+ }
6401
+ }
6402
+ return cached;
6403
+ },
6404
+ describe() {
6405
+ return read();
6406
+ },
6407
+ claimSession(sessionId) {
6408
+ if (claimed.has(sessionId)) return false;
6409
+ claimed.add(sessionId);
6410
+ while (claimed.size > STALE_BUILD_SESSION_CAP) {
6411
+ const oldest = claimed.values().next();
6412
+ if (oldest.done) break;
6413
+ claimed.delete(oldest.value);
6414
+ }
6415
+ return true;
6416
+ },
6417
+ claimedSessions() {
6418
+ return [...claimed];
6419
+ }
6420
+ };
6421
+ }
6422
+ function defaultEntryPath() {
6423
+ return fileURLToPath2(import.meta.url);
6424
+ }
6425
+ function readOnDiskBuild(entryPath) {
6426
+ try {
6427
+ const raw = fs5.readFileSync(path6.join(path6.dirname(entryPath), "..", "package.json"), "utf8");
6428
+ const version = JSON.parse(raw).version;
6429
+ if (typeof version !== "string" || version.length === 0) return void 0;
6430
+ const stat = fs5.statSync(entryPath);
6431
+ return { version, mtimeMs: stat.mtimeMs, size: stat.size };
6432
+ } catch {
6433
+ return void 0;
6434
+ }
6435
+ }
6436
+ function recordLoadedBuild() {
6437
+ const entryPath = defaultEntryPath();
6438
+ let mtimeMs;
6439
+ let size;
6440
+ try {
6441
+ const stat = fs5.statSync(entryPath);
6442
+ mtimeMs = stat.mtimeMs;
6443
+ size = stat.size;
6444
+ } catch {
6445
+ }
6446
+ return { version: pluginVersion(), entryPath, mtimeMs, size, loadedAt: Date.now() };
6447
+ }
6448
+ var loadedAtImport = recordLoadedBuild();
6449
+ var watch;
6450
+ function staleBuildWatch() {
6451
+ if (!watch) {
6452
+ const loaded = loadedAtImport;
6453
+ watch = createStaleBuildWatch({
6454
+ loaded,
6455
+ readOnDisk: () => readOnDiskBuild(loaded.entryPath),
6456
+ now: () => Date.now()
6457
+ });
6458
+ }
6459
+ return watch;
6460
+ }
6461
+
6201
6462
  // src/doctor.ts
6202
6463
  var DOCTOR_COMMAND = "claude-code-doctor";
6203
6464
  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 +6530,7 @@ function formatDoctorReport(report) {
6269
6530
  lines.push("| Field | Value |");
6270
6531
  lines.push("|---|---|");
6271
6532
  lines.push(`| plugin | ${report.plugin} |`);
6533
+ lines.push(`| plugin build | ${describeBuildStatus(report.build)} |`);
6272
6534
  lines.push(`| opencode | ${report.opencode} |`);
6273
6535
  lines.push(`| claude CLI | \`${report.claudeCli.path}\` (${report.claudeCli.version}) |`);
6274
6536
  lines.push(`| cwd | \`${report.cwd.resolved}\` (${report.cwd.source}) |`);
@@ -6512,6 +6774,8 @@ async function gatherDoctorReport(options) {
6512
6774
  }) : { status: "not-requested" };
6513
6775
  return {
6514
6776
  plugin: base.plugin,
6777
+ // `describe`, not `check`: unthrottled, silent, and it claims no session.
6778
+ build: staleBuildWatch().describe(),
6515
6779
  opencode: base.opencode,
6516
6780
  claudeCli: { path: cliPath, version: cli?.raw ?? "not detected" },
6517
6781
  cwd: base.cwd,
@@ -6578,6 +6842,7 @@ async function buildDoctorReport(options) {
6578
6842
  const report = await gatherDoctorReport(options);
6579
6843
  log.info("claude-code doctor report", {
6580
6844
  plugin: report.plugin,
6845
+ verdict: report.build.verdict,
6581
6846
  opencode: report.opencode,
6582
6847
  cwd: report.cwd,
6583
6848
  processes: report.processes.length,
@@ -6601,7 +6866,7 @@ Could not build the report: ${message}`;
6601
6866
 
6602
6867
  // src/agent-models.ts
6603
6868
  import { readFile, readdir } from "fs/promises";
6604
- import path6 from "path";
6869
+ import path7 from "path";
6605
6870
 
6606
6871
  // src/models.ts
6607
6872
  var PROVIDER_ID = "claude-code";
@@ -7104,11 +7369,11 @@ async function readAgentMarkdownRecords(directories) {
7104
7369
  const name = entry.slice(0, -3);
7105
7370
  if (records[name]) continue;
7106
7371
  try {
7107
- const text = await readFile(path6.join(directory, entry), "utf8");
7372
+ const text = await readFile(path7.join(directory, entry), "utf8");
7108
7373
  records[name] = parseAgentFrontmatter(text);
7109
7374
  } catch (err) {
7110
7375
  log.debug("failed to read agent markdown", {
7111
- file: path6.join(directory, entry),
7376
+ file: path7.join(directory, entry),
7112
7377
  error: String(err)
7113
7378
  });
7114
7379
  }
@@ -7120,12 +7385,12 @@ function agentDirectories(home, projectDirectory) {
7120
7385
  const directories = [];
7121
7386
  if (projectDirectory) {
7122
7387
  for (const name of AGENT_DIR_NAMES) {
7123
- directories.push(path6.join(projectDirectory, ".opencode", name));
7388
+ directories.push(path7.join(projectDirectory, ".opencode", name));
7124
7389
  }
7125
7390
  }
7126
7391
  if (home) {
7127
7392
  for (const name of AGENT_DIR_NAMES) {
7128
- directories.push(path6.join(home, ".config", "opencode", name));
7393
+ directories.push(path7.join(home, ".config", "opencode", name));
7129
7394
  }
7130
7395
  }
7131
7396
  return directories;
@@ -7294,7 +7559,8 @@ var PLUGIN_NOTE_MARKERS = [
7294
7559
  FAILOVER_MARKER,
7295
7560
  ACCOUNT_BLOCK_MARKER,
7296
7561
  MODEL_FALLBACK_MARKER,
7297
- UNATTENDED_REPLAY_MARKER
7562
+ UNATTENDED_REPLAY_MARKER,
7563
+ STALE_BUILD_MARKER
7298
7564
  ];
7299
7565
  function isPluginNote(part) {
7300
7566
  if (!part || part.type !== "text" || typeof part.text !== "string") return false;
@@ -7681,14 +7947,14 @@ Now continuing with the current message:
7681
7947
 
7682
7948
  // src/skill-bridge.ts
7683
7949
  import * as crypto3 from "crypto";
7684
- import * as fs5 from "fs";
7950
+ import * as fs6 from "fs";
7685
7951
  import * as os3 from "os";
7686
- import * as path7 from "path";
7687
- import { fileURLToPath as fileURLToPath2 } from "url";
7952
+ import * as path8 from "path";
7953
+ import { fileURLToPath as fileURLToPath3 } from "url";
7688
7954
  function bundledSkillsDir() {
7689
7955
  try {
7690
- const here = fileURLToPath2(import.meta.url);
7691
- const dir = path7.resolve(path7.dirname(here), "..", "skills");
7956
+ const here = fileURLToPath3(import.meta.url);
7957
+ const dir = path8.resolve(path8.dirname(here), "..", "skills");
7692
7958
  return dirExists2(dir) ? dir : null;
7693
7959
  } catch {
7694
7960
  return null;
@@ -7696,16 +7962,16 @@ function bundledSkillsDir() {
7696
7962
  }
7697
7963
  function dirExists2(p) {
7698
7964
  try {
7699
- return fs5.statSync(p).isDirectory();
7965
+ return fs6.statSync(p).isDirectory();
7700
7966
  } catch {
7701
7967
  return false;
7702
7968
  }
7703
7969
  }
7704
7970
  function realPath(p) {
7705
7971
  try {
7706
- return fs5.realpathSync(p);
7972
+ return fs6.realpathSync(p);
7707
7973
  } catch {
7708
- return path7.resolve(p);
7974
+ return path8.resolve(p);
7709
7975
  }
7710
7976
  }
7711
7977
  function envEnabled(name) {
@@ -7726,10 +7992,10 @@ function declaredSkillName(source) {
7726
7992
  return SAFE_SKILL_NAME.test(name) ? name : null;
7727
7993
  }
7728
7994
  function readSkill(root, entry) {
7729
- const dir = path7.join(root, entry);
7995
+ const dir = path8.join(root, entry);
7730
7996
  let source;
7731
7997
  try {
7732
- source = fs5.readFileSync(path7.join(dir, "SKILL.md"), "utf8");
7998
+ source = fs6.readFileSync(path8.join(dir, "SKILL.md"), "utf8");
7733
7999
  } catch {
7734
8000
  return null;
7735
8001
  }
@@ -7747,47 +8013,47 @@ function skillRoots(cwd) {
7747
8013
  const roots = [];
7748
8014
  const seen = /* @__PURE__ */ new Set();
7749
8015
  const push = (p) => {
7750
- const abs = path7.resolve(p);
8016
+ const abs = path8.resolve(p);
7751
8017
  if (seen.has(abs)) return;
7752
8018
  seen.add(abs);
7753
8019
  if (dirExists2(abs)) roots.push(abs);
7754
8020
  };
7755
8021
  const home = os3.homedir();
7756
- const homeDir = home ? path7.resolve(home) : null;
7757
- let current = path7.resolve(cwd);
8022
+ const homeDir = home ? path8.resolve(home) : null;
8023
+ let current = path8.resolve(cwd);
7758
8024
  while (true) {
7759
- push(path7.join(current, ".opencode", "skills"));
8025
+ push(path8.join(current, ".opencode", "skills"));
7760
8026
  if (current !== homeDir) {
7761
- push(path7.join(current, ".claude", "skills"));
7762
- push(path7.join(current, ".agents", "skills"));
8027
+ push(path8.join(current, ".claude", "skills"));
8028
+ push(path8.join(current, ".agents", "skills"));
7763
8029
  }
7764
- const parent = path7.dirname(current);
8030
+ const parent = path8.dirname(current);
7765
8031
  if (parent === current) break;
7766
8032
  current = parent;
7767
8033
  }
7768
- if (homeDir) push(path7.join(homeDir, ".opencode", "skills"));
8034
+ if (homeDir) push(path8.join(homeDir, ".opencode", "skills"));
7769
8035
  const envDir = process.env.OPENCODE_CONFIG_DIR;
7770
8036
  if (envDir) {
7771
- push(path7.join(envDir, "skills"));
7772
- push(path7.join(envDir, "skill"));
8037
+ push(path8.join(envDir, "skills"));
8038
+ push(path8.join(envDir, "skill"));
7773
8039
  }
7774
- const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path7.join(homeDir, ".config") : null);
8040
+ const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path8.join(homeDir, ".config") : null);
7775
8041
  if (xdg) {
7776
- push(path7.join(xdg, "opencode", "skills"));
7777
- push(path7.join(xdg, "opencode", "skill"));
8042
+ push(path8.join(xdg, "opencode", "skills"));
8043
+ push(path8.join(xdg, "opencode", "skill"));
7778
8044
  }
7779
8045
  if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
7780
8046
  if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
7781
- push(path7.join(homeDir, ".claude", "skills"));
8047
+ push(path8.join(homeDir, ".claude", "skills"));
7782
8048
  }
7783
- push(path7.join(homeDir, ".agents", "skills"));
8049
+ push(path8.join(homeDir, ".agents", "skills"));
7784
8050
  }
7785
8051
  return roots;
7786
8052
  }
7787
8053
  function collectSkills(root, claimed, found) {
7788
8054
  let entries;
7789
8055
  try {
7790
- entries = fs5.readdirSync(root, { withFileTypes: true });
8056
+ entries = fs6.readdirSync(root, { withFileTypes: true });
7791
8057
  } catch {
7792
8058
  return;
7793
8059
  }
@@ -7827,23 +8093,23 @@ function discoverBundledSkills() {
7827
8093
  }
7828
8094
  function claudeConfigDir(configDir) {
7829
8095
  const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
7830
- if (explicit) return path7.resolve(expandHome(explicit));
8096
+ if (explicit) return path8.resolve(expandHome(explicit));
7831
8097
  const home = os3.homedir();
7832
- return home ? path7.resolve(home, ".claude") : null;
8098
+ return home ? path8.resolve(home, ".claude") : null;
7833
8099
  }
7834
8100
  function installedPluginSkillRoots(configDir, cwd) {
7835
8101
  if (!configDir) return [];
7836
8102
  let parsed;
7837
8103
  try {
7838
8104
  parsed = JSON.parse(
7839
- fs5.readFileSync(path7.join(configDir, "plugins", "installed_plugins.json"), "utf8")
8105
+ fs6.readFileSync(path8.join(configDir, "plugins", "installed_plugins.json"), "utf8")
7840
8106
  );
7841
8107
  } catch {
7842
8108
  return [];
7843
8109
  }
7844
8110
  const plugins = parsed?.plugins;
7845
8111
  if (!plugins || typeof plugins !== "object") return [];
7846
- const workspace = path7.resolve(cwd);
8112
+ const workspace = path8.resolve(cwd);
7847
8113
  const roots = [];
7848
8114
  for (const installs of Object.values(plugins)) {
7849
8115
  if (!Array.isArray(installs)) continue;
@@ -7851,28 +8117,28 @@ function installedPluginSkillRoots(configDir, cwd) {
7851
8117
  const entry = install;
7852
8118
  if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
7853
8119
  if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
7854
- roots.push(path7.join(entry.installPath, "skills"));
8120
+ roots.push(path8.join(entry.installPath, "skills"));
7855
8121
  }
7856
8122
  }
7857
8123
  return roots;
7858
8124
  }
7859
8125
  function coversWorkspace(projectPath, workspace) {
7860
8126
  if (typeof projectPath !== "string" || !projectPath) return false;
7861
- const root = path7.resolve(projectPath);
8127
+ const root = path8.resolve(projectPath);
7862
8128
  if (root === workspace) return true;
7863
- const rel = path7.relative(root, workspace);
7864
- return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
8129
+ const rel = path8.relative(root, workspace);
8130
+ return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
7865
8131
  }
7866
8132
  function discoverNativeClaudeSkills(opts) {
7867
8133
  const found = [];
7868
8134
  const scanned = /* @__PURE__ */ new Set();
7869
8135
  const scan = (root, scope) => {
7870
- const abs = path7.resolve(root);
8136
+ const abs = path8.resolve(root);
7871
8137
  if (scanned.has(abs)) return;
7872
8138
  scanned.add(abs);
7873
8139
  let entries;
7874
8140
  try {
7875
- entries = fs5.readdirSync(abs, { withFileTypes: true });
8141
+ entries = fs6.readdirSync(abs, { withFileTypes: true });
7876
8142
  } catch {
7877
8143
  return;
7878
8144
  }
@@ -7884,13 +8150,13 @@ function discoverNativeClaudeSkills(opts) {
7884
8150
  }
7885
8151
  };
7886
8152
  const configDir = claudeConfigDir(opts.configDir);
7887
- if (configDir) scan(path7.join(configDir, "skills"), "user");
8153
+ if (configDir) scan(path8.join(configDir, "skills"), "user");
7888
8154
  const home = os3.homedir();
7889
- const homeDir = home ? path7.resolve(home) : null;
7890
- let current = path7.resolve(opts.cwd);
8155
+ const homeDir = home ? path8.resolve(home) : null;
8156
+ let current = path8.resolve(opts.cwd);
7891
8157
  while (true) {
7892
- if (current !== homeDir) scan(path7.join(current, ".claude", "skills"), "project");
7893
- const parent = path7.dirname(current);
8158
+ if (current !== homeDir) scan(path8.join(current, ".claude", "skills"), "project");
8159
+ const parent = path8.dirname(current);
7894
8160
  if (parent === current) break;
7895
8161
  current = parent;
7896
8162
  }
@@ -7924,10 +8190,10 @@ function dropNativelyLoadedSkills(skills, native) {
7924
8190
  }
7925
8191
  function readBundledSkillInfos() {
7926
8192
  return discoverBundledSkills().flatMap((skill) => {
7927
- const file = path7.join(skill.dir, "SKILL.md");
8193
+ const file = path8.join(skill.dir, "SKILL.md");
7928
8194
  let text;
7929
8195
  try {
7930
- text = fs5.readFileSync(file, "utf8");
8196
+ text = fs6.readFileSync(file, "utf8");
7931
8197
  } catch {
7932
8198
  return [];
7933
8199
  }
@@ -7951,15 +8217,15 @@ function readBundledSkillInfos() {
7951
8217
  }
7952
8218
  function linkSkill(source, target) {
7953
8219
  try {
7954
- fs5.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
8220
+ fs6.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
7955
8221
  return;
7956
8222
  } catch {
7957
- fs5.cpSync(source, target, { recursive: true, dereference: true });
8223
+ fs6.cpSync(source, target, { recursive: true, dereference: true });
7958
8224
  }
7959
8225
  }
7960
8226
  function fileExists2(p) {
7961
8227
  try {
7962
- return fs5.statSync(p).isFile();
8228
+ return fs6.statSync(p).isFile();
7963
8229
  } catch {
7964
8230
  return false;
7965
8231
  }
@@ -7968,14 +8234,14 @@ function buildSkillPluginDir(skills) {
7968
8234
  if (skills.length === 0) return null;
7969
8235
  const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
7970
8236
  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");
8237
+ const root = path8.join(pluginTmpDir(), `skills-${hash}`);
8238
+ const manifest = path8.join(root, ".claude-plugin", "plugin.json");
7973
8239
  if (fileExists2(manifest)) return root;
7974
8240
  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(
8241
+ fs6.rmSync(root, { recursive: true, force: true });
8242
+ fs6.mkdirSync(path8.join(root, ".claude-plugin"), { recursive: true });
8243
+ fs6.mkdirSync(path8.join(root, "skills"), { recursive: true });
8244
+ fs6.writeFileSync(
7979
8245
  manifest,
7980
8246
  JSON.stringify(
7981
8247
  {
@@ -7988,7 +8254,7 @@ function buildSkillPluginDir(skills) {
7988
8254
  { encoding: "utf8", mode: 384 }
7989
8255
  );
7990
8256
  for (const skill of skills) {
7991
- linkSkill(skill.dir, path7.join(root, "skills", skill.name));
8257
+ linkSkill(skill.dir, path8.join(root, "skills", skill.name));
7992
8258
  }
7993
8259
  } catch (err) {
7994
8260
  log.warn("failed to stage opencode skill plugin dir", {
@@ -8004,7 +8270,7 @@ function registerBundledSkillPath(config) {
8004
8270
  if (!dir) return false;
8005
8271
  config.skills ??= {};
8006
8272
  const paths = config.skills.paths ??= [];
8007
- if (paths.some((p) => path7.resolve(p) === dir)) return false;
8273
+ if (paths.some((p) => path8.resolve(p) === dir)) return false;
8008
8274
  paths.push(dir);
8009
8275
  return true;
8010
8276
  }
@@ -8055,12 +8321,12 @@ import { unlink as unlink2 } from "fs/promises";
8055
8321
 
8056
8322
  // src/claude-session-bun.ts
8057
8323
  import * as os4 from "os";
8058
- import * as fs6 from "fs";
8059
- import * as path8 from "path";
8324
+ import * as fs7 from "fs";
8325
+ import * as path9 from "path";
8060
8326
  import { execFileSync } from "child_process";
8061
8327
  import { randomUUID as randomUUID4 } from "crypto";
8062
8328
  function resolveClaude(cmd = "claude") {
8063
- if (path8.isAbsolute(cmd) && fs6.existsSync(cmd)) return cmd;
8329
+ if (path9.isAbsolute(cmd) && fs7.existsSync(cmd)) return cmd;
8064
8330
  const viaBun = Bun.which(cmd);
8065
8331
  if (viaBun) return viaBun;
8066
8332
  const isWin = os4.platform() === "win32";
@@ -8069,17 +8335,17 @@ function resolveClaude(cmd = "claude") {
8069
8335
  encoding: "utf8",
8070
8336
  stdio: ["ignore", "pipe", "ignore"]
8071
8337
  });
8072
- const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs6.existsSync(p));
8338
+ const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs7.existsSync(p));
8073
8339
  if (first) return first;
8074
8340
  } catch {
8075
8341
  }
8076
8342
  throw new Error(`Could not resolve command on PATH: ${cmd}`);
8077
8343
  }
8078
8344
  function encodeCwd(cwd) {
8079
- const resolved = path8.resolve(cwd);
8345
+ const resolved = path9.resolve(cwd);
8080
8346
  let real = resolved;
8081
8347
  try {
8082
- real = fs6.realpathSync.native(resolved);
8348
+ real = fs7.realpathSync.native(resolved);
8083
8349
  } catch {
8084
8350
  }
8085
8351
  return real.replace(/[^a-zA-Z0-9]/g, "-");
@@ -8164,12 +8430,12 @@ var TERMINAL_STOP = /* @__PURE__ */ new Set(["end_turn", "stop_sequence", "max_t
8164
8430
  var delay = (ms) => new Promise((r) => setTimeout(r, ms));
8165
8431
  function resolveConfigDir(configDir) {
8166
8432
  const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
8167
- if (!value) return path8.join(os4.homedir(), ".claude");
8433
+ if (!value) return path9.join(os4.homedir(), ".claude");
8168
8434
  if (value === "~") return os4.homedir();
8169
8435
  if (value.startsWith("~/") || value.startsWith("~\\")) {
8170
- return path8.join(os4.homedir(), value.slice(2));
8436
+ return path9.join(os4.homedir(), value.slice(2));
8171
8437
  }
8172
- return path8.resolve(value);
8438
+ return path9.resolve(value);
8173
8439
  }
8174
8440
  var ClaudeSession = class {
8175
8441
  sessionId;
@@ -8187,11 +8453,11 @@ var ClaudeSession = class {
8187
8453
  signal;
8188
8454
  o;
8189
8455
  constructor(opts = {}) {
8190
- this.cwd = path8.resolve(opts.cwd ?? process.cwd());
8456
+ this.cwd = path9.resolve(opts.cwd ?? process.cwd());
8191
8457
  this.configDir = resolveConfigDir(opts.configDir);
8192
8458
  this.signal = opts.signal;
8193
8459
  this.sessionId = randomUUID4();
8194
- this.jsonlPath = path8.join(
8460
+ this.jsonlPath = path9.join(
8195
8461
  this.configDir,
8196
8462
  "projects",
8197
8463
  encodeCwd(this.cwd),
@@ -8314,7 +8580,7 @@ var ClaudeSession = class {
8314
8580
  }
8315
8581
  readRawLines() {
8316
8582
  try {
8317
- return fs6.readFileSync(this.jsonlPath, "utf8").split("\n");
8583
+ return fs7.readFileSync(this.jsonlPath, "utf8").split("\n");
8318
8584
  } catch {
8319
8585
  return [];
8320
8586
  }
@@ -8711,13 +8977,13 @@ function spawnInteractiveProcess(opts) {
8711
8977
  }
8712
8978
 
8713
8979
  // src/prompts.ts
8714
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
8980
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
8715
8981
  import { homedir as homedir7 } from "os";
8716
8982
  import { randomUUID as randomUUID5 } from "crypto";
8717
- import { dirname as dirname5, join as join8 } from "path";
8718
- function readPromptFileIfPresent(path10) {
8983
+ import { dirname as dirname6, join as join9 } from "path";
8984
+ function readPromptFileIfPresent(path11) {
8719
8985
  try {
8720
- const content = readFileSync5(path10, "utf8").trim();
8986
+ const content = readFileSync6(path11, "utf8").trim();
8721
8987
  return content || void 0;
8722
8988
  } catch {
8723
8989
  return void 0;
@@ -8726,9 +8992,9 @@ function readPromptFileIfPresent(path10) {
8726
8992
  function nearestWorkspaceAgentsPrompt(cwd) {
8727
8993
  let dir = cwd;
8728
8994
  while (true) {
8729
- const content = readPromptFileIfPresent(join8(dir, "AGENTS.md"));
8995
+ const content = readPromptFileIfPresent(join9(dir, "AGENTS.md"));
8730
8996
  if (content) return content;
8731
- const parent = dirname5(dir);
8997
+ const parent = dirname6(dir);
8732
8998
  if (parent === dir) return void 0;
8733
8999
  dir = parent;
8734
9000
  }
@@ -8835,8 +9101,8 @@ ${options.compressionSummary.trim()}`
8835
9101
  for (const s of extraSystemContent) {
8836
9102
  if (s.trim()) parts.push(s.trim());
8837
9103
  }
8838
- const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir7(), ".config");
8839
- const globalAgents = readPromptFileIfPresent(join8(configRoot, "opencode", "AGENTS.md"));
9104
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
9105
+ const globalAgents = readPromptFileIfPresent(join9(configRoot, "opencode", "AGENTS.md"));
8840
9106
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
8841
9107
  const forwarded = extraSystemContent.join("\n\n");
8842
9108
  const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
@@ -8847,10 +9113,10 @@ ${options.compressionSummary.trim()}`
8847
9113
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
8848
9114
  const content = parts.join("\n\n");
8849
9115
  if (!content) return void 0;
8850
- const path10 = join8(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
9116
+ const path11 = join9(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
8851
9117
  try {
8852
- writeFileSync4(path10, content, { encoding: "utf8", mode: 384 });
8853
- return path10;
9118
+ writeFileSync4(path11, content, { encoding: "utf8", mode: 384 });
9119
+ return path11;
8854
9120
  } catch (err) {
8855
9121
  log.warn("failed to write system prompt file", { error: String(err) });
8856
9122
  return void 0;
@@ -11903,6 +12169,11 @@ var ClaudeCodeLanguageModel = class {
11903
12169
  // the operator is usually not even looking at.
11904
12170
  childSession: !!await fetchSessionParentId(affinity)
11905
12171
  });
12172
+ let staleBuild = null;
12173
+ if (!compactionMode && mode !== "generate" && affinity !== "default") {
12174
+ staleBuild = staleBuildWatch().check();
12175
+ if (staleBuild && await fetchSessionParentId(affinity)) staleBuild = null;
12176
+ }
11906
12177
  log.info("doStream starting", {
11907
12178
  cwd,
11908
12179
  model: effectiveModelId,
@@ -12363,6 +12634,14 @@ var ClaudeCodeLanguageModel = class {
12363
12634
  if (stoppedBeforeWork()) return;
12364
12635
  state.streamStarted = true;
12365
12636
  controller.enqueue({ type: "stream-start", warnings });
12637
+ if (staleBuild && staleBuildWatch().claimSession(affinity)) {
12638
+ controller.enqueue({
12639
+ type: "text-delta",
12640
+ id: state.startTextBlock(),
12641
+ delta: formatStaleBuildNote(staleBuild)
12642
+ });
12643
+ state.endTextBlock();
12644
+ }
12366
12645
  if (failoverNote) {
12367
12646
  controller.enqueue({
12368
12647
  type: "text-delta",
@@ -12810,34 +13089,34 @@ var ClaudeCodeLanguageModel = class {
12810
13089
  import {
12811
13090
  existsSync as existsSync3,
12812
13091
  mkdirSync as mkdirSync4,
12813
- readFileSync as readFileSync6,
13092
+ readFileSync as readFileSync7,
12814
13093
  realpathSync as realpathSync4,
12815
13094
  rmSync as rmSync3,
12816
13095
  writeFileSync as writeFileSync5
12817
13096
  } from "fs";
12818
13097
  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";
13098
+ import { dirname as dirname7, join as join10, resolve as resolve5 } from "path";
13099
+ import { fileURLToPath as fileURLToPath4 } from "url";
12821
13100
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
12822
13101
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
12823
13102
  var alreadyRan = false;
12824
13103
  function candidateCacheRoots() {
12825
13104
  const xdg = process.env.XDG_CACHE_HOME;
12826
13105
  return [
12827
- xdg ? join9(xdg, "opencode") : null,
12828
- join9(homedir8(), ".cache", "opencode"),
12829
- join9(homedir8(), "Library", "Caches", "opencode")
13106
+ xdg ? join10(xdg, "opencode") : null,
13107
+ join10(homedir8(), ".cache", "opencode"),
13108
+ join10(homedir8(), "Library", "Caches", "opencode")
12830
13109
  ].filter((p) => Boolean(p));
12831
13110
  }
12832
13111
  function userOpencodeJsonPath() {
12833
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(homedir8(), ".config");
12834
- return join9(xdgConfig, "opencode", "opencode.json");
13112
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(homedir8(), ".config");
13113
+ return join10(xdgConfig, "opencode", "opencode.json");
12835
13114
  }
12836
13115
  function userIntendsToUseUnscoped() {
12837
13116
  const cfg = userOpencodeJsonPath();
12838
13117
  if (!existsSync3(cfg)) return false;
12839
13118
  try {
12840
- const json = JSON.parse(readFileSync6(cfg, "utf8"));
13119
+ const json = JSON.parse(readFileSync7(cfg, "utf8"));
12841
13120
  const plugins = json.plugin;
12842
13121
  if (!Array.isArray(plugins)) return false;
12843
13122
  return plugins.some(
@@ -12849,7 +13128,7 @@ function userIntendsToUseUnscoped() {
12849
13128
  }
12850
13129
  function ourLoadedDir() {
12851
13130
  try {
12852
- const filePath = fileURLToPath3(import.meta.url);
13131
+ const filePath = fileURLToPath4(import.meta.url);
12853
13132
  return realpathSync4(resolve5(filePath, "..", ".."));
12854
13133
  } catch {
12855
13134
  return null;
@@ -12858,19 +13137,19 @@ function ourLoadedDir() {
12858
13137
  function ourVersion(dir) {
12859
13138
  if (!dir) return null;
12860
13139
  try {
12861
- const pkg = JSON.parse(readFileSync6(join9(dir, "package.json"), "utf8"));
13140
+ const pkg = JSON.parse(readFileSync7(join10(dir, "package.json"), "utf8"));
12862
13141
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
12863
13142
  } catch {
12864
13143
  return null;
12865
13144
  }
12866
13145
  }
12867
13146
  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");
13147
+ const stateRoot = process.env.XDG_STATE_HOME ?? join10(homedir8(), ".local", "state");
13148
+ return join10(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
12870
13149
  }
12871
13150
  function markerVersion() {
12872
13151
  try {
12873
- const marker = JSON.parse(readFileSync6(markerPath(), "utf8"));
13152
+ const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
12874
13153
  return typeof marker.version === "string" ? marker.version : null;
12875
13154
  } catch {
12876
13155
  return null;
@@ -12879,7 +13158,7 @@ function markerVersion() {
12879
13158
  function writeMarker(version) {
12880
13159
  const file = markerPath();
12881
13160
  try {
12882
- mkdirSync4(dirname6(file), { recursive: true });
13161
+ mkdirSync4(dirname7(file), { recursive: true });
12883
13162
  writeFileSync5(
12884
13163
  file,
12885
13164
  JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
@@ -12915,7 +13194,7 @@ function cleanupStaleUnscopedInstall() {
12915
13194
  }
12916
13195
  function cleanupOne(cacheRoot, ourDir) {
12917
13196
  if (!existsSync3(cacheRoot)) return;
12918
- const stalePath = join9(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
13197
+ const stalePath = join10(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
12919
13198
  if (!existsSync3(stalePath)) return;
12920
13199
  let realStalePath = stalePath;
12921
13200
  try {
@@ -12923,11 +13202,11 @@ function cleanupOne(cacheRoot, ourDir) {
12923
13202
  } catch {
12924
13203
  }
12925
13204
  if (ourDir && realStalePath === ourDir) return;
12926
- const pkgJsonPath = join9(stalePath, "package.json");
13205
+ const pkgJsonPath = join10(stalePath, "package.json");
12927
13206
  if (!existsSync3(pkgJsonPath)) return;
12928
13207
  let pkg = {};
12929
13208
  try {
12930
- pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
13209
+ pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
12931
13210
  } catch {
12932
13211
  return;
12933
13212
  }
@@ -12943,10 +13222,10 @@ function cleanupOne(cacheRoot, ourDir) {
12943
13222
  });
12944
13223
  return;
12945
13224
  }
12946
- const cachePkgJson = join9(cacheRoot, "package.json");
13225
+ const cachePkgJson = join10(cacheRoot, "package.json");
12947
13226
  if (!existsSync3(cachePkgJson)) return;
12948
13227
  try {
12949
- const cfg = JSON.parse(readFileSync6(cachePkgJson, "utf8"));
13228
+ const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
12950
13229
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
12951
13230
  delete cfg.dependencies[STALE_PACKAGE_NAME];
12952
13231
  writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -12959,8 +13238,74 @@ function cleanupOne(cacheRoot, ourDir) {
12959
13238
  }
12960
13239
  }
12961
13240
 
13241
+ // src/tui-log-sink.ts
13242
+ var LOG_SERVICE_NAME = "opencode-claude-code";
13243
+ var TOAST_TITLE = "claude-code";
13244
+ var TOAST_DURATION_MS = 1e4;
13245
+ var MAX_TOAST_CHARS = 240;
13246
+ var APP_LOG_LEVEL = {
13247
+ debug: "debug",
13248
+ info: "info",
13249
+ notice: "info",
13250
+ warn: "warn",
13251
+ error: "error"
13252
+ };
13253
+ function toastMessage(message) {
13254
+ const flat = message.replace(/\s+/g, " ").trim();
13255
+ if (flat.length <= MAX_TOAST_CHARS) return flat;
13256
+ return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
13257
+ }
13258
+ function appLogBody(entry) {
13259
+ return {
13260
+ service: LOG_SERVICE_NAME,
13261
+ level: APP_LOG_LEVEL[entry.level],
13262
+ message: entry.message,
13263
+ ...entry.data && Object.keys(entry.data).length > 0 ? { extra: entry.data } : {}
13264
+ };
13265
+ }
13266
+ function toastBody(entry) {
13267
+ return {
13268
+ title: TOAST_TITLE,
13269
+ message: toastMessage(entry.message),
13270
+ variant: entry.level === "error" ? "error" : "warning",
13271
+ duration: TOAST_DURATION_MS
13272
+ };
13273
+ }
13274
+ function fireAndForget(call) {
13275
+ try {
13276
+ const result = call();
13277
+ if (result && typeof result.catch === "function") {
13278
+ void result.catch(() => void 0);
13279
+ }
13280
+ } catch {
13281
+ }
13282
+ }
13283
+ function createOpencodeLogSink(client) {
13284
+ if (!client || typeof client !== "object") return null;
13285
+ const sdk = client;
13286
+ const canLog = typeof sdk.app?.log === "function";
13287
+ const canToast = typeof sdk.tui?.showToast === "function";
13288
+ if (!canLog && !canToast) return null;
13289
+ return {
13290
+ log(entry) {
13291
+ if (!canLog) return;
13292
+ fireAndForget(() => sdk.app.log({ body: appLogBody(entry) }));
13293
+ },
13294
+ toast(entry) {
13295
+ if (!canToast) return;
13296
+ fireAndForget(() => sdk.tui.showToast({ body: toastBody(entry) }));
13297
+ }
13298
+ };
13299
+ }
13300
+ function registerOpencodeLogSink(client) {
13301
+ const sink = createOpencodeLogSink(client);
13302
+ if (!sink) return false;
13303
+ setLogSink(sink);
13304
+ return true;
13305
+ }
13306
+
12962
13307
  // src/v2-client.ts
12963
- import path9 from "path";
13308
+ import path10 from "path";
12964
13309
  function formatAgentTypeList(agents) {
12965
13310
  const lines = agents.flatMap((agent) => {
12966
13311
  if (agent.hidden === true || agent.mode === "primary") return [];
@@ -12989,7 +13334,7 @@ function toV1Session(info) {
12989
13334
  if (!info || typeof info !== "object") return void 0;
12990
13335
  const session = info;
12991
13336
  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;
13337
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path10.resolve(root, session.subpath) : root : void 0;
12993
13338
  return { ...info, directory, parentID: session.parentID };
12994
13339
  }
12995
13340
  function toV1Messages(messages) {
@@ -13783,7 +14128,9 @@ var server = async (input) => {
13783
14128
  ensureProcessExitCleanup();
13784
14129
  const opencodeVersion = pickOpencodeVersion(input);
13785
14130
  if (input && typeof input === "object" && "client" in input) {
13786
- setOpencodeClient(input.client);
14131
+ const client = input.client;
14132
+ setOpencodeClient(client);
14133
+ registerOpencodeLogSink(client);
13787
14134
  }
13788
14135
  setOpencodeProjectDirectory(pickOpencodeDirectory(input));
13789
14136
  return {