@khalilgharbaoui/opencode-claude-code-plugin 0.36.4 → 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/README.md +4 -3
- package/dist/index.js +386 -133
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +15 -0
package/dist/index.js
CHANGED
|
@@ -808,6 +808,13 @@ function formatSilentTurnNote(hadReasoning) {
|
|
|
808
808
|
const what = hadReasoning ? "Claude Code finished the turn after thinking but never wrote an answer" : "Claude Code finished the turn without writing anything or calling a tool";
|
|
809
809
|
return `
|
|
810
810
|
${SILENT_TURN_MARKER} ${what}, so there is no reply above. Nothing failed and nothing is pending: send the message again, or rephrase it.
|
|
811
|
+
`;
|
|
812
|
+
}
|
|
813
|
+
var UNATTENDED_REPLAY_MARKER = "\u258C **between turns:**";
|
|
814
|
+
function formatUnattendedReplayNote(dropped) {
|
|
815
|
+
const lost = dropped > 0 ? ` ${dropped} earlier line${dropped === 1 ? "" : "s"} were dropped.` : "";
|
|
816
|
+
return `${UNATTENDED_REPLAY_MARKER} The Claude Code CLI wrote this after the previous turn had already finished, so it is not an answer to the message above.${lost}
|
|
817
|
+
|
|
811
818
|
`;
|
|
812
819
|
}
|
|
813
820
|
var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
|
|
@@ -5464,6 +5471,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5464
5471
|
"skipping remote MCP server with no url",
|
|
5465
5472
|
"stale scratch directory sweep failed",
|
|
5466
5473
|
"task_batch resolving with child results missing",
|
|
5474
|
+
"this opencode process is running an older plugin build than the one on disk",
|
|
5467
5475
|
"timed out pending proxy call",
|
|
5468
5476
|
"turn abort listener threw",
|
|
5469
5477
|
"two different skills share a name; bridging the one the nearest root claimed",
|
|
@@ -5612,6 +5620,10 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
5612
5620
|
toolNames: "name",
|
|
5613
5621
|
cliFloor: "version",
|
|
5614
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",
|
|
5615
5627
|
opencode: "version",
|
|
5616
5628
|
plugin: "version",
|
|
5617
5629
|
version: "version",
|
|
@@ -5861,8 +5873,8 @@ function redactLogLine(raw, context) {
|
|
|
5861
5873
|
if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
|
|
5862
5874
|
return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
|
|
5863
5875
|
}
|
|
5864
|
-
function readLogTail(
|
|
5865
|
-
const fd = openSync(
|
|
5876
|
+
function readLogTail(path11, maxBytes = BUNDLE_TAIL_BYTES) {
|
|
5877
|
+
const fd = openSync(path11, "r");
|
|
5866
5878
|
try {
|
|
5867
5879
|
const size = fstatSync(fd).size;
|
|
5868
5880
|
const length = Math.min(size, maxBytes);
|
|
@@ -6052,6 +6064,11 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
6052
6064
|
}
|
|
6053
6065
|
}
|
|
6054
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
|
+
|
|
6055
6072
|
// src/startup-diagnostics.ts
|
|
6056
6073
|
import { execFile as execFile3 } from "child_process";
|
|
6057
6074
|
import * as fs4 from "fs";
|
|
@@ -6191,6 +6208,161 @@ function logStartupDiagnostics(providers, opencodeVersion) {
|
|
|
6191
6208
|
})();
|
|
6192
6209
|
}
|
|
6193
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
|
+
|
|
6194
6366
|
// src/doctor.ts
|
|
6195
6367
|
var DOCTOR_COMMAND = "claude-code-doctor";
|
|
6196
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";
|
|
@@ -6262,6 +6434,7 @@ function formatDoctorReport(report) {
|
|
|
6262
6434
|
lines.push("| Field | Value |");
|
|
6263
6435
|
lines.push("|---|---|");
|
|
6264
6436
|
lines.push(`| plugin | ${report.plugin} |`);
|
|
6437
|
+
lines.push(`| plugin build | ${describeBuildStatus(report.build)} |`);
|
|
6265
6438
|
lines.push(`| opencode | ${report.opencode} |`);
|
|
6266
6439
|
lines.push(`| claude CLI | \`${report.claudeCli.path}\` (${report.claudeCli.version}) |`);
|
|
6267
6440
|
lines.push(`| cwd | \`${report.cwd.resolved}\` (${report.cwd.source}) |`);
|
|
@@ -6505,6 +6678,8 @@ async function gatherDoctorReport(options) {
|
|
|
6505
6678
|
}) : { status: "not-requested" };
|
|
6506
6679
|
return {
|
|
6507
6680
|
plugin: base.plugin,
|
|
6681
|
+
// `describe`, not `check`: unthrottled, silent, and it claims no session.
|
|
6682
|
+
build: staleBuildWatch().describe(),
|
|
6508
6683
|
opencode: base.opencode,
|
|
6509
6684
|
claudeCli: { path: cliPath, version: cli?.raw ?? "not detected" },
|
|
6510
6685
|
cwd: base.cwd,
|
|
@@ -6571,6 +6746,7 @@ async function buildDoctorReport(options) {
|
|
|
6571
6746
|
const report = await gatherDoctorReport(options);
|
|
6572
6747
|
log.info("claude-code doctor report", {
|
|
6573
6748
|
plugin: report.plugin,
|
|
6749
|
+
verdict: report.build.verdict,
|
|
6574
6750
|
opencode: report.opencode,
|
|
6575
6751
|
cwd: report.cwd,
|
|
6576
6752
|
processes: report.processes.length,
|
|
@@ -6594,7 +6770,7 @@ Could not build the report: ${message}`;
|
|
|
6594
6770
|
|
|
6595
6771
|
// src/agent-models.ts
|
|
6596
6772
|
import { readFile, readdir } from "fs/promises";
|
|
6597
|
-
import
|
|
6773
|
+
import path7 from "path";
|
|
6598
6774
|
|
|
6599
6775
|
// src/models.ts
|
|
6600
6776
|
var PROVIDER_ID = "claude-code";
|
|
@@ -7097,11 +7273,11 @@ async function readAgentMarkdownRecords(directories) {
|
|
|
7097
7273
|
const name = entry.slice(0, -3);
|
|
7098
7274
|
if (records[name]) continue;
|
|
7099
7275
|
try {
|
|
7100
|
-
const text = await readFile(
|
|
7276
|
+
const text = await readFile(path7.join(directory, entry), "utf8");
|
|
7101
7277
|
records[name] = parseAgentFrontmatter(text);
|
|
7102
7278
|
} catch (err) {
|
|
7103
7279
|
log.debug("failed to read agent markdown", {
|
|
7104
|
-
file:
|
|
7280
|
+
file: path7.join(directory, entry),
|
|
7105
7281
|
error: String(err)
|
|
7106
7282
|
});
|
|
7107
7283
|
}
|
|
@@ -7113,12 +7289,12 @@ function agentDirectories(home, projectDirectory) {
|
|
|
7113
7289
|
const directories = [];
|
|
7114
7290
|
if (projectDirectory) {
|
|
7115
7291
|
for (const name of AGENT_DIR_NAMES) {
|
|
7116
|
-
directories.push(
|
|
7292
|
+
directories.push(path7.join(projectDirectory, ".opencode", name));
|
|
7117
7293
|
}
|
|
7118
7294
|
}
|
|
7119
7295
|
if (home) {
|
|
7120
7296
|
for (const name of AGENT_DIR_NAMES) {
|
|
7121
|
-
directories.push(
|
|
7297
|
+
directories.push(path7.join(home, ".config", "opencode", name));
|
|
7122
7298
|
}
|
|
7123
7299
|
}
|
|
7124
7300
|
return directories;
|
|
@@ -7286,7 +7462,9 @@ var PLUGIN_NOTE_MARKERS = [
|
|
|
7286
7462
|
SILENT_TURN_MARKER,
|
|
7287
7463
|
FAILOVER_MARKER,
|
|
7288
7464
|
ACCOUNT_BLOCK_MARKER,
|
|
7289
|
-
MODEL_FALLBACK_MARKER
|
|
7465
|
+
MODEL_FALLBACK_MARKER,
|
|
7466
|
+
UNATTENDED_REPLAY_MARKER,
|
|
7467
|
+
STALE_BUILD_MARKER
|
|
7290
7468
|
];
|
|
7291
7469
|
function isPluginNote(part) {
|
|
7292
7470
|
if (!part || part.type !== "text" || typeof part.text !== "string") return false;
|
|
@@ -7673,14 +7851,14 @@ Now continuing with the current message:
|
|
|
7673
7851
|
|
|
7674
7852
|
// src/skill-bridge.ts
|
|
7675
7853
|
import * as crypto3 from "crypto";
|
|
7676
|
-
import * as
|
|
7854
|
+
import * as fs6 from "fs";
|
|
7677
7855
|
import * as os3 from "os";
|
|
7678
|
-
import * as
|
|
7679
|
-
import { fileURLToPath as
|
|
7856
|
+
import * as path8 from "path";
|
|
7857
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7680
7858
|
function bundledSkillsDir() {
|
|
7681
7859
|
try {
|
|
7682
|
-
const here =
|
|
7683
|
-
const dir =
|
|
7860
|
+
const here = fileURLToPath3(import.meta.url);
|
|
7861
|
+
const dir = path8.resolve(path8.dirname(here), "..", "skills");
|
|
7684
7862
|
return dirExists2(dir) ? dir : null;
|
|
7685
7863
|
} catch {
|
|
7686
7864
|
return null;
|
|
@@ -7688,16 +7866,16 @@ function bundledSkillsDir() {
|
|
|
7688
7866
|
}
|
|
7689
7867
|
function dirExists2(p) {
|
|
7690
7868
|
try {
|
|
7691
|
-
return
|
|
7869
|
+
return fs6.statSync(p).isDirectory();
|
|
7692
7870
|
} catch {
|
|
7693
7871
|
return false;
|
|
7694
7872
|
}
|
|
7695
7873
|
}
|
|
7696
7874
|
function realPath(p) {
|
|
7697
7875
|
try {
|
|
7698
|
-
return
|
|
7876
|
+
return fs6.realpathSync(p);
|
|
7699
7877
|
} catch {
|
|
7700
|
-
return
|
|
7878
|
+
return path8.resolve(p);
|
|
7701
7879
|
}
|
|
7702
7880
|
}
|
|
7703
7881
|
function envEnabled(name) {
|
|
@@ -7718,10 +7896,10 @@ function declaredSkillName(source) {
|
|
|
7718
7896
|
return SAFE_SKILL_NAME.test(name) ? name : null;
|
|
7719
7897
|
}
|
|
7720
7898
|
function readSkill(root, entry) {
|
|
7721
|
-
const dir =
|
|
7899
|
+
const dir = path8.join(root, entry);
|
|
7722
7900
|
let source;
|
|
7723
7901
|
try {
|
|
7724
|
-
source =
|
|
7902
|
+
source = fs6.readFileSync(path8.join(dir, "SKILL.md"), "utf8");
|
|
7725
7903
|
} catch {
|
|
7726
7904
|
return null;
|
|
7727
7905
|
}
|
|
@@ -7739,47 +7917,47 @@ function skillRoots(cwd) {
|
|
|
7739
7917
|
const roots = [];
|
|
7740
7918
|
const seen = /* @__PURE__ */ new Set();
|
|
7741
7919
|
const push = (p) => {
|
|
7742
|
-
const abs =
|
|
7920
|
+
const abs = path8.resolve(p);
|
|
7743
7921
|
if (seen.has(abs)) return;
|
|
7744
7922
|
seen.add(abs);
|
|
7745
7923
|
if (dirExists2(abs)) roots.push(abs);
|
|
7746
7924
|
};
|
|
7747
7925
|
const home = os3.homedir();
|
|
7748
|
-
const homeDir = home ?
|
|
7749
|
-
let current =
|
|
7926
|
+
const homeDir = home ? path8.resolve(home) : null;
|
|
7927
|
+
let current = path8.resolve(cwd);
|
|
7750
7928
|
while (true) {
|
|
7751
|
-
push(
|
|
7929
|
+
push(path8.join(current, ".opencode", "skills"));
|
|
7752
7930
|
if (current !== homeDir) {
|
|
7753
|
-
push(
|
|
7754
|
-
push(
|
|
7931
|
+
push(path8.join(current, ".claude", "skills"));
|
|
7932
|
+
push(path8.join(current, ".agents", "skills"));
|
|
7755
7933
|
}
|
|
7756
|
-
const parent =
|
|
7934
|
+
const parent = path8.dirname(current);
|
|
7757
7935
|
if (parent === current) break;
|
|
7758
7936
|
current = parent;
|
|
7759
7937
|
}
|
|
7760
|
-
if (homeDir) push(
|
|
7938
|
+
if (homeDir) push(path8.join(homeDir, ".opencode", "skills"));
|
|
7761
7939
|
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
7762
7940
|
if (envDir) {
|
|
7763
|
-
push(
|
|
7764
|
-
push(
|
|
7941
|
+
push(path8.join(envDir, "skills"));
|
|
7942
|
+
push(path8.join(envDir, "skill"));
|
|
7765
7943
|
}
|
|
7766
|
-
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ?
|
|
7944
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path8.join(homeDir, ".config") : null);
|
|
7767
7945
|
if (xdg) {
|
|
7768
|
-
push(
|
|
7769
|
-
push(
|
|
7946
|
+
push(path8.join(xdg, "opencode", "skills"));
|
|
7947
|
+
push(path8.join(xdg, "opencode", "skill"));
|
|
7770
7948
|
}
|
|
7771
7949
|
if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
|
|
7772
7950
|
if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
|
|
7773
|
-
push(
|
|
7951
|
+
push(path8.join(homeDir, ".claude", "skills"));
|
|
7774
7952
|
}
|
|
7775
|
-
push(
|
|
7953
|
+
push(path8.join(homeDir, ".agents", "skills"));
|
|
7776
7954
|
}
|
|
7777
7955
|
return roots;
|
|
7778
7956
|
}
|
|
7779
7957
|
function collectSkills(root, claimed, found) {
|
|
7780
7958
|
let entries;
|
|
7781
7959
|
try {
|
|
7782
|
-
entries =
|
|
7960
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
7783
7961
|
} catch {
|
|
7784
7962
|
return;
|
|
7785
7963
|
}
|
|
@@ -7819,23 +7997,23 @@ function discoverBundledSkills() {
|
|
|
7819
7997
|
}
|
|
7820
7998
|
function claudeConfigDir(configDir) {
|
|
7821
7999
|
const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
7822
|
-
if (explicit) return
|
|
8000
|
+
if (explicit) return path8.resolve(expandHome(explicit));
|
|
7823
8001
|
const home = os3.homedir();
|
|
7824
|
-
return home ?
|
|
8002
|
+
return home ? path8.resolve(home, ".claude") : null;
|
|
7825
8003
|
}
|
|
7826
8004
|
function installedPluginSkillRoots(configDir, cwd) {
|
|
7827
8005
|
if (!configDir) return [];
|
|
7828
8006
|
let parsed;
|
|
7829
8007
|
try {
|
|
7830
8008
|
parsed = JSON.parse(
|
|
7831
|
-
|
|
8009
|
+
fs6.readFileSync(path8.join(configDir, "plugins", "installed_plugins.json"), "utf8")
|
|
7832
8010
|
);
|
|
7833
8011
|
} catch {
|
|
7834
8012
|
return [];
|
|
7835
8013
|
}
|
|
7836
8014
|
const plugins = parsed?.plugins;
|
|
7837
8015
|
if (!plugins || typeof plugins !== "object") return [];
|
|
7838
|
-
const workspace =
|
|
8016
|
+
const workspace = path8.resolve(cwd);
|
|
7839
8017
|
const roots = [];
|
|
7840
8018
|
for (const installs of Object.values(plugins)) {
|
|
7841
8019
|
if (!Array.isArray(installs)) continue;
|
|
@@ -7843,28 +8021,28 @@ function installedPluginSkillRoots(configDir, cwd) {
|
|
|
7843
8021
|
const entry = install;
|
|
7844
8022
|
if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
|
|
7845
8023
|
if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
|
|
7846
|
-
roots.push(
|
|
8024
|
+
roots.push(path8.join(entry.installPath, "skills"));
|
|
7847
8025
|
}
|
|
7848
8026
|
}
|
|
7849
8027
|
return roots;
|
|
7850
8028
|
}
|
|
7851
8029
|
function coversWorkspace(projectPath, workspace) {
|
|
7852
8030
|
if (typeof projectPath !== "string" || !projectPath) return false;
|
|
7853
|
-
const root =
|
|
8031
|
+
const root = path8.resolve(projectPath);
|
|
7854
8032
|
if (root === workspace) return true;
|
|
7855
|
-
const rel =
|
|
7856
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
8033
|
+
const rel = path8.relative(root, workspace);
|
|
8034
|
+
return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
|
|
7857
8035
|
}
|
|
7858
8036
|
function discoverNativeClaudeSkills(opts) {
|
|
7859
8037
|
const found = [];
|
|
7860
8038
|
const scanned = /* @__PURE__ */ new Set();
|
|
7861
8039
|
const scan = (root, scope) => {
|
|
7862
|
-
const abs =
|
|
8040
|
+
const abs = path8.resolve(root);
|
|
7863
8041
|
if (scanned.has(abs)) return;
|
|
7864
8042
|
scanned.add(abs);
|
|
7865
8043
|
let entries;
|
|
7866
8044
|
try {
|
|
7867
|
-
entries =
|
|
8045
|
+
entries = fs6.readdirSync(abs, { withFileTypes: true });
|
|
7868
8046
|
} catch {
|
|
7869
8047
|
return;
|
|
7870
8048
|
}
|
|
@@ -7876,13 +8054,13 @@ function discoverNativeClaudeSkills(opts) {
|
|
|
7876
8054
|
}
|
|
7877
8055
|
};
|
|
7878
8056
|
const configDir = claudeConfigDir(opts.configDir);
|
|
7879
|
-
if (configDir) scan(
|
|
8057
|
+
if (configDir) scan(path8.join(configDir, "skills"), "user");
|
|
7880
8058
|
const home = os3.homedir();
|
|
7881
|
-
const homeDir = home ?
|
|
7882
|
-
let current =
|
|
8059
|
+
const homeDir = home ? path8.resolve(home) : null;
|
|
8060
|
+
let current = path8.resolve(opts.cwd);
|
|
7883
8061
|
while (true) {
|
|
7884
|
-
if (current !== homeDir) scan(
|
|
7885
|
-
const parent =
|
|
8062
|
+
if (current !== homeDir) scan(path8.join(current, ".claude", "skills"), "project");
|
|
8063
|
+
const parent = path8.dirname(current);
|
|
7886
8064
|
if (parent === current) break;
|
|
7887
8065
|
current = parent;
|
|
7888
8066
|
}
|
|
@@ -7916,10 +8094,10 @@ function dropNativelyLoadedSkills(skills, native) {
|
|
|
7916
8094
|
}
|
|
7917
8095
|
function readBundledSkillInfos() {
|
|
7918
8096
|
return discoverBundledSkills().flatMap((skill) => {
|
|
7919
|
-
const file =
|
|
8097
|
+
const file = path8.join(skill.dir, "SKILL.md");
|
|
7920
8098
|
let text;
|
|
7921
8099
|
try {
|
|
7922
|
-
text =
|
|
8100
|
+
text = fs6.readFileSync(file, "utf8");
|
|
7923
8101
|
} catch {
|
|
7924
8102
|
return [];
|
|
7925
8103
|
}
|
|
@@ -7943,15 +8121,15 @@ function readBundledSkillInfos() {
|
|
|
7943
8121
|
}
|
|
7944
8122
|
function linkSkill(source, target) {
|
|
7945
8123
|
try {
|
|
7946
|
-
|
|
8124
|
+
fs6.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
7947
8125
|
return;
|
|
7948
8126
|
} catch {
|
|
7949
|
-
|
|
8127
|
+
fs6.cpSync(source, target, { recursive: true, dereference: true });
|
|
7950
8128
|
}
|
|
7951
8129
|
}
|
|
7952
8130
|
function fileExists2(p) {
|
|
7953
8131
|
try {
|
|
7954
|
-
return
|
|
8132
|
+
return fs6.statSync(p).isFile();
|
|
7955
8133
|
} catch {
|
|
7956
8134
|
return false;
|
|
7957
8135
|
}
|
|
@@ -7960,14 +8138,14 @@ function buildSkillPluginDir(skills) {
|
|
|
7960
8138
|
if (skills.length === 0) return null;
|
|
7961
8139
|
const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
|
|
7962
8140
|
const hash = crypto3.createHash("sha256").update(fingerprint).digest("hex").slice(0, 12);
|
|
7963
|
-
const root =
|
|
7964
|
-
const manifest =
|
|
8141
|
+
const root = path8.join(pluginTmpDir(), `skills-${hash}`);
|
|
8142
|
+
const manifest = path8.join(root, ".claude-plugin", "plugin.json");
|
|
7965
8143
|
if (fileExists2(manifest)) return root;
|
|
7966
8144
|
try {
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
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(
|
|
7971
8149
|
manifest,
|
|
7972
8150
|
JSON.stringify(
|
|
7973
8151
|
{
|
|
@@ -7980,7 +8158,7 @@ function buildSkillPluginDir(skills) {
|
|
|
7980
8158
|
{ encoding: "utf8", mode: 384 }
|
|
7981
8159
|
);
|
|
7982
8160
|
for (const skill of skills) {
|
|
7983
|
-
linkSkill(skill.dir,
|
|
8161
|
+
linkSkill(skill.dir, path8.join(root, "skills", skill.name));
|
|
7984
8162
|
}
|
|
7985
8163
|
} catch (err) {
|
|
7986
8164
|
log.warn("failed to stage opencode skill plugin dir", {
|
|
@@ -7996,7 +8174,7 @@ function registerBundledSkillPath(config) {
|
|
|
7996
8174
|
if (!dir) return false;
|
|
7997
8175
|
config.skills ??= {};
|
|
7998
8176
|
const paths = config.skills.paths ??= [];
|
|
7999
|
-
if (paths.some((p) =>
|
|
8177
|
+
if (paths.some((p) => path8.resolve(p) === dir)) return false;
|
|
8000
8178
|
paths.push(dir);
|
|
8001
8179
|
return true;
|
|
8002
8180
|
}
|
|
@@ -8047,12 +8225,12 @@ import { unlink as unlink2 } from "fs/promises";
|
|
|
8047
8225
|
|
|
8048
8226
|
// src/claude-session-bun.ts
|
|
8049
8227
|
import * as os4 from "os";
|
|
8050
|
-
import * as
|
|
8051
|
-
import * as
|
|
8228
|
+
import * as fs7 from "fs";
|
|
8229
|
+
import * as path9 from "path";
|
|
8052
8230
|
import { execFileSync } from "child_process";
|
|
8053
8231
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8054
8232
|
function resolveClaude(cmd = "claude") {
|
|
8055
|
-
if (
|
|
8233
|
+
if (path9.isAbsolute(cmd) && fs7.existsSync(cmd)) return cmd;
|
|
8056
8234
|
const viaBun = Bun.which(cmd);
|
|
8057
8235
|
if (viaBun) return viaBun;
|
|
8058
8236
|
const isWin = os4.platform() === "win32";
|
|
@@ -8061,17 +8239,17 @@ function resolveClaude(cmd = "claude") {
|
|
|
8061
8239
|
encoding: "utf8",
|
|
8062
8240
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8063
8241
|
});
|
|
8064
|
-
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) =>
|
|
8242
|
+
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs7.existsSync(p));
|
|
8065
8243
|
if (first) return first;
|
|
8066
8244
|
} catch {
|
|
8067
8245
|
}
|
|
8068
8246
|
throw new Error(`Could not resolve command on PATH: ${cmd}`);
|
|
8069
8247
|
}
|
|
8070
8248
|
function encodeCwd(cwd) {
|
|
8071
|
-
const resolved =
|
|
8249
|
+
const resolved = path9.resolve(cwd);
|
|
8072
8250
|
let real = resolved;
|
|
8073
8251
|
try {
|
|
8074
|
-
real =
|
|
8252
|
+
real = fs7.realpathSync.native(resolved);
|
|
8075
8253
|
} catch {
|
|
8076
8254
|
}
|
|
8077
8255
|
return real.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -8156,12 +8334,12 @@ var TERMINAL_STOP = /* @__PURE__ */ new Set(["end_turn", "stop_sequence", "max_t
|
|
|
8156
8334
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8157
8335
|
function resolveConfigDir(configDir) {
|
|
8158
8336
|
const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
8159
|
-
if (!value) return
|
|
8337
|
+
if (!value) return path9.join(os4.homedir(), ".claude");
|
|
8160
8338
|
if (value === "~") return os4.homedir();
|
|
8161
8339
|
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
8162
|
-
return
|
|
8340
|
+
return path9.join(os4.homedir(), value.slice(2));
|
|
8163
8341
|
}
|
|
8164
|
-
return
|
|
8342
|
+
return path9.resolve(value);
|
|
8165
8343
|
}
|
|
8166
8344
|
var ClaudeSession = class {
|
|
8167
8345
|
sessionId;
|
|
@@ -8179,11 +8357,11 @@ var ClaudeSession = class {
|
|
|
8179
8357
|
signal;
|
|
8180
8358
|
o;
|
|
8181
8359
|
constructor(opts = {}) {
|
|
8182
|
-
this.cwd =
|
|
8360
|
+
this.cwd = path9.resolve(opts.cwd ?? process.cwd());
|
|
8183
8361
|
this.configDir = resolveConfigDir(opts.configDir);
|
|
8184
8362
|
this.signal = opts.signal;
|
|
8185
8363
|
this.sessionId = randomUUID4();
|
|
8186
|
-
this.jsonlPath =
|
|
8364
|
+
this.jsonlPath = path9.join(
|
|
8187
8365
|
this.configDir,
|
|
8188
8366
|
"projects",
|
|
8189
8367
|
encodeCwd(this.cwd),
|
|
@@ -8306,7 +8484,7 @@ var ClaudeSession = class {
|
|
|
8306
8484
|
}
|
|
8307
8485
|
readRawLines() {
|
|
8308
8486
|
try {
|
|
8309
|
-
return
|
|
8487
|
+
return fs7.readFileSync(this.jsonlPath, "utf8").split("\n");
|
|
8310
8488
|
} catch {
|
|
8311
8489
|
return [];
|
|
8312
8490
|
}
|
|
@@ -8703,13 +8881,13 @@ function spawnInteractiveProcess(opts) {
|
|
|
8703
8881
|
}
|
|
8704
8882
|
|
|
8705
8883
|
// src/prompts.ts
|
|
8706
|
-
import { readFileSync as
|
|
8884
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
8707
8885
|
import { homedir as homedir7 } from "os";
|
|
8708
8886
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
8709
|
-
import { dirname as
|
|
8710
|
-
function readPromptFileIfPresent(
|
|
8887
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8888
|
+
function readPromptFileIfPresent(path11) {
|
|
8711
8889
|
try {
|
|
8712
|
-
const content =
|
|
8890
|
+
const content = readFileSync6(path11, "utf8").trim();
|
|
8713
8891
|
return content || void 0;
|
|
8714
8892
|
} catch {
|
|
8715
8893
|
return void 0;
|
|
@@ -8718,9 +8896,9 @@ function readPromptFileIfPresent(path10) {
|
|
|
8718
8896
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
8719
8897
|
let dir = cwd;
|
|
8720
8898
|
while (true) {
|
|
8721
|
-
const content = readPromptFileIfPresent(
|
|
8899
|
+
const content = readPromptFileIfPresent(join9(dir, "AGENTS.md"));
|
|
8722
8900
|
if (content) return content;
|
|
8723
|
-
const parent =
|
|
8901
|
+
const parent = dirname6(dir);
|
|
8724
8902
|
if (parent === dir) return void 0;
|
|
8725
8903
|
dir = parent;
|
|
8726
8904
|
}
|
|
@@ -8827,8 +9005,8 @@ ${options.compressionSummary.trim()}`
|
|
|
8827
9005
|
for (const s of extraSystemContent) {
|
|
8828
9006
|
if (s.trim()) parts.push(s.trim());
|
|
8829
9007
|
}
|
|
8830
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
8831
|
-
const globalAgents = readPromptFileIfPresent(
|
|
9008
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
|
|
9009
|
+
const globalAgents = readPromptFileIfPresent(join9(configRoot, "opencode", "AGENTS.md"));
|
|
8832
9010
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
8833
9011
|
const forwarded = extraSystemContent.join("\n\n");
|
|
8834
9012
|
const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
|
|
@@ -8839,10 +9017,10 @@ ${options.compressionSummary.trim()}`
|
|
|
8839
9017
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
8840
9018
|
const content = parts.join("\n\n");
|
|
8841
9019
|
if (!content) return void 0;
|
|
8842
|
-
const
|
|
9020
|
+
const path11 = join9(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
8843
9021
|
try {
|
|
8844
|
-
writeFileSync4(
|
|
8845
|
-
return
|
|
9022
|
+
writeFileSync4(path11, content, { encoding: "utf8", mode: 384 });
|
|
9023
|
+
return path11;
|
|
8846
9024
|
} catch (err) {
|
|
8847
9025
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
8848
9026
|
return void 0;
|
|
@@ -9688,6 +9866,8 @@ function createTurnState(init) {
|
|
|
9688
9866
|
toolCallMap: /* @__PURE__ */ new Map(),
|
|
9689
9867
|
skipResultForIds: /* @__PURE__ */ new Set(),
|
|
9690
9868
|
toolCallsById: /* @__PURE__ */ new Map(),
|
|
9869
|
+
toolCallEmittedIds: /* @__PURE__ */ new Set(),
|
|
9870
|
+
toolCallAnsweredIds: /* @__PURE__ */ new Set(),
|
|
9691
9871
|
resultMeta: {},
|
|
9692
9872
|
lastCallUsage: void 0,
|
|
9693
9873
|
resultFailure: void 0,
|
|
@@ -9704,6 +9884,23 @@ function createTurnState(init) {
|
|
|
9704
9884
|
};
|
|
9705
9885
|
return state;
|
|
9706
9886
|
}
|
|
9887
|
+
function cliToolCallInputJson(state, id) {
|
|
9888
|
+
for (const entry of state.toolCallMap.values()) {
|
|
9889
|
+
if (entry.id === id) return entry.inputJson || "{}";
|
|
9890
|
+
}
|
|
9891
|
+
return "{}";
|
|
9892
|
+
}
|
|
9893
|
+
function emitCliToolCall(state, id, name, inputJson, executed) {
|
|
9894
|
+
if (state.toolCallEmittedIds.has(id)) return;
|
|
9895
|
+
state.toolCallEmittedIds.add(id);
|
|
9896
|
+
state.controller.enqueue({
|
|
9897
|
+
type: "tool-call",
|
|
9898
|
+
toolCallId: id,
|
|
9899
|
+
toolName: name,
|
|
9900
|
+
input: inputJson,
|
|
9901
|
+
providerExecuted: executed
|
|
9902
|
+
});
|
|
9903
|
+
}
|
|
9707
9904
|
|
|
9708
9905
|
// src/turn-abort.ts
|
|
9709
9906
|
function watchTurnAbort(signal) {
|
|
@@ -10113,9 +10310,46 @@ function deliverPendingCompletions(state, force = false) {
|
|
|
10113
10310
|
armStartWatchdog(state);
|
|
10114
10311
|
return true;
|
|
10115
10312
|
}
|
|
10313
|
+
var CLI_RESULT_DEFERRED_OUTPUT = "Claude Code ran this tool itself and kept its output in its own context. This step ended on a tool call opencode has to run, so the output was not available to show here.";
|
|
10314
|
+
function closeDeferredCliToolResults(state) {
|
|
10315
|
+
const answer = (id, name, stage) => {
|
|
10316
|
+
emitCliToolCall(state, id, name, cliToolCallInputJson(state, id), true);
|
|
10317
|
+
state.controller.enqueue({
|
|
10318
|
+
type: "tool-result",
|
|
10319
|
+
toolCallId: id,
|
|
10320
|
+
toolName: name,
|
|
10321
|
+
result: {
|
|
10322
|
+
output: CLI_RESULT_DEFERRED_OUTPUT,
|
|
10323
|
+
title: name,
|
|
10324
|
+
metadata: {}
|
|
10325
|
+
},
|
|
10326
|
+
providerExecuted: true
|
|
10327
|
+
});
|
|
10328
|
+
state.toolCallsById.delete(id);
|
|
10329
|
+
state.toolCallAnsweredIds.add(id);
|
|
10330
|
+
log.info("closing a CLI-executed tool call whose result this step cannot wait for", {
|
|
10331
|
+
sessionKey: state.sessionKey,
|
|
10332
|
+
toolUseId: id,
|
|
10333
|
+
name,
|
|
10334
|
+
stage
|
|
10335
|
+
});
|
|
10336
|
+
};
|
|
10337
|
+
for (const [id, toolCall] of [...state.toolCallsById]) {
|
|
10338
|
+
if (state.skipResultForIds.has(id)) continue;
|
|
10339
|
+
answer(id, toolCall.name, "registered");
|
|
10340
|
+
}
|
|
10341
|
+
for (const entry of state.toolCallMap.values()) {
|
|
10342
|
+
if (!entry.started || !entry.mappedName) continue;
|
|
10343
|
+
if (state.skipResultForIds.has(entry.id)) continue;
|
|
10344
|
+
if (state.toolCallAnsweredIds.has(entry.id)) continue;
|
|
10345
|
+
if (state.toolCallEmittedIds.has(entry.id)) continue;
|
|
10346
|
+
answer(entry.id, entry.mappedName, "open-block");
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10116
10349
|
function finishWithToolCalls(state, calls) {
|
|
10117
10350
|
if (state.controllerClosed) return;
|
|
10118
10351
|
if (calls.length === 0) return;
|
|
10352
|
+
closeDeferredCliToolResults(state);
|
|
10119
10353
|
const enqueueToolCall = (toolCallId, toolName, input) => {
|
|
10120
10354
|
state.controller.enqueue({
|
|
10121
10355
|
type: "tool-input-start",
|
|
@@ -10321,6 +10555,26 @@ var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
|
10321
10555
|
"signature_delta"
|
|
10322
10556
|
]);
|
|
10323
10557
|
var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
|
|
10558
|
+
function adoptOpenCliToolCall(state, ctx, id) {
|
|
10559
|
+
for (const entry of state.toolCallMap.values()) {
|
|
10560
|
+
if (entry.id !== id || !entry.started || !entry.mappedName) continue;
|
|
10561
|
+
let parsedInput = {};
|
|
10562
|
+
try {
|
|
10563
|
+
parsedInput = JSON.parse(entry.inputJson || "{}");
|
|
10564
|
+
} catch {
|
|
10565
|
+
}
|
|
10566
|
+
const { input: mappedInput, executed } = mapTool(entry.name, parsedInput, {
|
|
10567
|
+
webSearch: ctx.config.webSearch,
|
|
10568
|
+
sessionId: getClaudeSessionId(state.sessionKey),
|
|
10569
|
+
toolUseId: entry.id
|
|
10570
|
+
});
|
|
10571
|
+
const registration = { id: entry.id, name: entry.mappedName, input: parsedInput };
|
|
10572
|
+
state.toolCallsById.set(entry.id, registration);
|
|
10573
|
+
emitCliToolCall(state, entry.id, entry.mappedName, JSON.stringify(mappedInput), executed);
|
|
10574
|
+
return registration;
|
|
10575
|
+
}
|
|
10576
|
+
return void 0;
|
|
10577
|
+
}
|
|
10324
10578
|
function createLineHandler(state, ctx) {
|
|
10325
10579
|
return (line) => {
|
|
10326
10580
|
if (!line.trim()) return;
|
|
@@ -10449,6 +10703,7 @@ function createLineHandler(state, ctx) {
|
|
|
10449
10703
|
);
|
|
10450
10704
|
if (!skip) {
|
|
10451
10705
|
entry.started = true;
|
|
10706
|
+
entry.mappedName = mappedName;
|
|
10452
10707
|
state.controller.enqueue({
|
|
10453
10708
|
type: "tool-input-start",
|
|
10454
10709
|
id: block2.id,
|
|
@@ -10606,20 +10861,14 @@ ${plan}
|
|
|
10606
10861
|
sessionId: getClaudeSessionId(state.sessionKey),
|
|
10607
10862
|
toolUseId: tc.id
|
|
10608
10863
|
});
|
|
10609
|
-
if (!skip) {
|
|
10864
|
+
if (!skip && !state.toolCallAnsweredIds.has(tc.id)) {
|
|
10610
10865
|
state.toolCallsById.set(tc.id, {
|
|
10611
10866
|
id: tc.id,
|
|
10612
10867
|
name: mappedName,
|
|
10613
10868
|
input: parsedInput
|
|
10614
10869
|
});
|
|
10615
10870
|
if (!executed) state.skipResultForIds.add(tc.id);
|
|
10616
|
-
state.
|
|
10617
|
-
type: "tool-call",
|
|
10618
|
-
toolCallId: tc.id,
|
|
10619
|
-
toolName: mappedName,
|
|
10620
|
-
input: JSON.stringify(mappedInput),
|
|
10621
|
-
providerExecuted: executed
|
|
10622
|
-
});
|
|
10871
|
+
emitCliToolCall(state, tc.id, mappedName, JSON.stringify(mappedInput), executed);
|
|
10623
10872
|
}
|
|
10624
10873
|
log.info("tool call complete", {
|
|
10625
10874
|
name: tc.name,
|
|
@@ -10805,13 +11054,7 @@ ${plan}
|
|
|
10805
11054
|
toolName: mappedName,
|
|
10806
11055
|
providerExecuted: executed
|
|
10807
11056
|
});
|
|
10808
|
-
state.
|
|
10809
|
-
type: "tool-call",
|
|
10810
|
-
toolCallId: block2.id,
|
|
10811
|
-
toolName: mappedName,
|
|
10812
|
-
input: JSON.stringify(mappedInput),
|
|
10813
|
-
providerExecuted: executed
|
|
10814
|
-
});
|
|
11057
|
+
emitCliToolCall(state, block2.id, mappedName, JSON.stringify(mappedInput), executed);
|
|
10815
11058
|
}
|
|
10816
11059
|
log.info("tool_use from assistant message", {
|
|
10817
11060
|
name: block2.name,
|
|
@@ -10877,7 +11120,7 @@ ${plan}
|
|
|
10877
11120
|
noteToolActivity(state);
|
|
10878
11121
|
}
|
|
10879
11122
|
}
|
|
10880
|
-
const toolCall = state.toolCallsById.get(block2.tool_use_id);
|
|
11123
|
+
const toolCall = state.toolCallsById.get(block2.tool_use_id) ?? adoptOpenCliToolCall(state, ctx, block2.tool_use_id);
|
|
10881
11124
|
if (toolCall) {
|
|
10882
11125
|
const isError = block2.is_error === true;
|
|
10883
11126
|
state.controller.enqueue({
|
|
@@ -10899,6 +11142,7 @@ ${plan}
|
|
|
10899
11142
|
isError
|
|
10900
11143
|
});
|
|
10901
11144
|
state.toolCallsById.delete(block2.tool_use_id);
|
|
11145
|
+
state.toolCallAnsweredIds.add(block2.tool_use_id);
|
|
10902
11146
|
}
|
|
10903
11147
|
}
|
|
10904
11148
|
}
|
|
@@ -11829,6 +12073,11 @@ var ClaudeCodeLanguageModel = class {
|
|
|
11829
12073
|
// the operator is usually not even looking at.
|
|
11830
12074
|
childSession: !!await fetchSessionParentId(affinity)
|
|
11831
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
|
+
}
|
|
11832
12081
|
log.info("doStream starting", {
|
|
11833
12082
|
cwd,
|
|
11834
12083
|
model: effectiveModelId,
|
|
@@ -12289,6 +12538,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12289
12538
|
if (stoppedBeforeWork()) return;
|
|
12290
12539
|
state.streamStarted = true;
|
|
12291
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
|
+
}
|
|
12292
12549
|
if (failoverNote) {
|
|
12293
12550
|
controller.enqueue({
|
|
12294
12551
|
type: "text-delta",
|
|
@@ -12561,16 +12818,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12561
12818
|
});
|
|
12562
12819
|
let partialText = false;
|
|
12563
12820
|
{
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
`
|
|
12572
|
-
});
|
|
12573
|
-
}
|
|
12821
|
+
const replayId = state.startTextBlock();
|
|
12822
|
+
controller.enqueue({
|
|
12823
|
+
type: "text-delta",
|
|
12824
|
+
id: replayId,
|
|
12825
|
+
delta: formatUnattendedReplayNote(unattended.dropped)
|
|
12826
|
+
});
|
|
12574
12827
|
for (const line of unattended.lines) {
|
|
12575
12828
|
try {
|
|
12576
12829
|
const outer = JSON.parse(line);
|
|
@@ -12740,34 +12993,34 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12740
12993
|
import {
|
|
12741
12994
|
existsSync as existsSync3,
|
|
12742
12995
|
mkdirSync as mkdirSync4,
|
|
12743
|
-
readFileSync as
|
|
12996
|
+
readFileSync as readFileSync7,
|
|
12744
12997
|
realpathSync as realpathSync4,
|
|
12745
12998
|
rmSync as rmSync3,
|
|
12746
12999
|
writeFileSync as writeFileSync5
|
|
12747
13000
|
} from "fs";
|
|
12748
13001
|
import { homedir as homedir8 } from "os";
|
|
12749
|
-
import { dirname as
|
|
12750
|
-
import { fileURLToPath as
|
|
13002
|
+
import { dirname as dirname7, join as join10, resolve as resolve5 } from "path";
|
|
13003
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12751
13004
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
12752
13005
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
12753
13006
|
var alreadyRan = false;
|
|
12754
13007
|
function candidateCacheRoots() {
|
|
12755
13008
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
12756
13009
|
return [
|
|
12757
|
-
xdg ?
|
|
12758
|
-
|
|
12759
|
-
|
|
13010
|
+
xdg ? join10(xdg, "opencode") : null,
|
|
13011
|
+
join10(homedir8(), ".cache", "opencode"),
|
|
13012
|
+
join10(homedir8(), "Library", "Caches", "opencode")
|
|
12760
13013
|
].filter((p) => Boolean(p));
|
|
12761
13014
|
}
|
|
12762
13015
|
function userOpencodeJsonPath() {
|
|
12763
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
12764
|
-
return
|
|
13016
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(homedir8(), ".config");
|
|
13017
|
+
return join10(xdgConfig, "opencode", "opencode.json");
|
|
12765
13018
|
}
|
|
12766
13019
|
function userIntendsToUseUnscoped() {
|
|
12767
13020
|
const cfg = userOpencodeJsonPath();
|
|
12768
13021
|
if (!existsSync3(cfg)) return false;
|
|
12769
13022
|
try {
|
|
12770
|
-
const json = JSON.parse(
|
|
13023
|
+
const json = JSON.parse(readFileSync7(cfg, "utf8"));
|
|
12771
13024
|
const plugins = json.plugin;
|
|
12772
13025
|
if (!Array.isArray(plugins)) return false;
|
|
12773
13026
|
return plugins.some(
|
|
@@ -12779,7 +13032,7 @@ function userIntendsToUseUnscoped() {
|
|
|
12779
13032
|
}
|
|
12780
13033
|
function ourLoadedDir() {
|
|
12781
13034
|
try {
|
|
12782
|
-
const filePath =
|
|
13035
|
+
const filePath = fileURLToPath4(import.meta.url);
|
|
12783
13036
|
return realpathSync4(resolve5(filePath, "..", ".."));
|
|
12784
13037
|
} catch {
|
|
12785
13038
|
return null;
|
|
@@ -12788,19 +13041,19 @@ function ourLoadedDir() {
|
|
|
12788
13041
|
function ourVersion(dir) {
|
|
12789
13042
|
if (!dir) return null;
|
|
12790
13043
|
try {
|
|
12791
|
-
const pkg = JSON.parse(
|
|
13044
|
+
const pkg = JSON.parse(readFileSync7(join10(dir, "package.json"), "utf8"));
|
|
12792
13045
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
12793
13046
|
} catch {
|
|
12794
13047
|
return null;
|
|
12795
13048
|
}
|
|
12796
13049
|
}
|
|
12797
13050
|
function markerPath() {
|
|
12798
|
-
const stateRoot = process.env.XDG_STATE_HOME ??
|
|
12799
|
-
return
|
|
13051
|
+
const stateRoot = process.env.XDG_STATE_HOME ?? join10(homedir8(), ".local", "state");
|
|
13052
|
+
return join10(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
|
|
12800
13053
|
}
|
|
12801
13054
|
function markerVersion() {
|
|
12802
13055
|
try {
|
|
12803
|
-
const marker = JSON.parse(
|
|
13056
|
+
const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
|
|
12804
13057
|
return typeof marker.version === "string" ? marker.version : null;
|
|
12805
13058
|
} catch {
|
|
12806
13059
|
return null;
|
|
@@ -12809,7 +13062,7 @@ function markerVersion() {
|
|
|
12809
13062
|
function writeMarker(version) {
|
|
12810
13063
|
const file = markerPath();
|
|
12811
13064
|
try {
|
|
12812
|
-
mkdirSync4(
|
|
13065
|
+
mkdirSync4(dirname7(file), { recursive: true });
|
|
12813
13066
|
writeFileSync5(
|
|
12814
13067
|
file,
|
|
12815
13068
|
JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
|
|
@@ -12845,7 +13098,7 @@ function cleanupStaleUnscopedInstall() {
|
|
|
12845
13098
|
}
|
|
12846
13099
|
function cleanupOne(cacheRoot, ourDir) {
|
|
12847
13100
|
if (!existsSync3(cacheRoot)) return;
|
|
12848
|
-
const stalePath =
|
|
13101
|
+
const stalePath = join10(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
12849
13102
|
if (!existsSync3(stalePath)) return;
|
|
12850
13103
|
let realStalePath = stalePath;
|
|
12851
13104
|
try {
|
|
@@ -12853,11 +13106,11 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12853
13106
|
} catch {
|
|
12854
13107
|
}
|
|
12855
13108
|
if (ourDir && realStalePath === ourDir) return;
|
|
12856
|
-
const pkgJsonPath =
|
|
13109
|
+
const pkgJsonPath = join10(stalePath, "package.json");
|
|
12857
13110
|
if (!existsSync3(pkgJsonPath)) return;
|
|
12858
13111
|
let pkg = {};
|
|
12859
13112
|
try {
|
|
12860
|
-
pkg = JSON.parse(
|
|
13113
|
+
pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
|
|
12861
13114
|
} catch {
|
|
12862
13115
|
return;
|
|
12863
13116
|
}
|
|
@@ -12873,10 +13126,10 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12873
13126
|
});
|
|
12874
13127
|
return;
|
|
12875
13128
|
}
|
|
12876
|
-
const cachePkgJson =
|
|
13129
|
+
const cachePkgJson = join10(cacheRoot, "package.json");
|
|
12877
13130
|
if (!existsSync3(cachePkgJson)) return;
|
|
12878
13131
|
try {
|
|
12879
|
-
const cfg = JSON.parse(
|
|
13132
|
+
const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
|
|
12880
13133
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
12881
13134
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
12882
13135
|
writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -12890,7 +13143,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12890
13143
|
}
|
|
12891
13144
|
|
|
12892
13145
|
// src/v2-client.ts
|
|
12893
|
-
import
|
|
13146
|
+
import path10 from "path";
|
|
12894
13147
|
function formatAgentTypeList(agents) {
|
|
12895
13148
|
const lines = agents.flatMap((agent) => {
|
|
12896
13149
|
if (agent.hidden === true || agent.mode === "primary") return [];
|
|
@@ -12919,7 +13172,7 @@ function toV1Session(info) {
|
|
|
12919
13172
|
if (!info || typeof info !== "object") return void 0;
|
|
12920
13173
|
const session = info;
|
|
12921
13174
|
const root = session.location?.directory;
|
|
12922
|
-
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ?
|
|
13175
|
+
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path10.resolve(root, session.subpath) : root : void 0;
|
|
12923
13176
|
return { ...info, directory, parentID: session.parentID };
|
|
12924
13177
|
}
|
|
12925
13178
|
function toV1Messages(messages) {
|