@khalilgharbaoui/opencode-claude-code-plugin 0.53.4 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/claude-code-language-model.ts
2
- import { join as join15 } from "path";
2
+ import { join as join16 } from "path";
3
3
 
4
4
  // src/ids.ts
5
5
  import { randomInt } from "crypto";
@@ -5927,9 +5927,20 @@ function backgroundTaskIdsIn(text) {
5927
5927
  }
5928
5928
  return [...ids];
5929
5929
  }
5930
+ var BATCH_SECTION = /^## task \d+ of \d+: /m;
5931
+ function acceptedBackgroundTaskIds(result) {
5932
+ if (result.kind !== "text") return [];
5933
+ if (result.isError !== true) return backgroundTaskIdsIn(result.text);
5934
+ const ids = /* @__PURE__ */ new Set();
5935
+ for (const section of result.text.split(BATCH_SECTION).slice(1)) {
5936
+ const body = section.slice(section.indexOf("\n") + 1);
5937
+ if (body.startsWith("[error] ") || body.startsWith("[missing] ")) continue;
5938
+ for (const id of backgroundTaskIdsIn(body)) ids.add(id);
5939
+ }
5940
+ return [...ids];
5941
+ }
5930
5942
  function noteBackgroundDispatchResult(sessionKey2, result, now = Date.now()) {
5931
- if (result.kind !== "text" || result.isError === true) return;
5932
- for (const taskId of backgroundTaskIdsIn(result.text)) {
5943
+ for (const taskId of acceptedBackgroundTaskIds(result)) {
5933
5944
  started.delete(taskId);
5934
5945
  started.set(taskId, { sessionKey: sessionKey2, at: now });
5935
5946
  while (started.size > MAX_STARTED) {
@@ -6012,8 +6023,8 @@ async function countOtherRunningWithin(exclude, budgetMs) {
6012
6023
  }
6013
6024
  }
6014
6025
  async function withBackgroundRunningCount(result, budgetMs = BACKGROUND_COUNT_BUDGET_MS) {
6015
- if (result.kind !== "text" || result.isError === true) return result;
6016
- const dispatched = backgroundTaskIdsIn(result.text);
6026
+ if (result.kind !== "text") return result;
6027
+ const dispatched = acceptedBackgroundTaskIds(result);
6017
6028
  if (dispatched.length === 0) return result;
6018
6029
  const others = await countOtherRunningWithin(new Set(dispatched), budgetMs);
6019
6030
  if (others === void 0) {
@@ -12155,6 +12166,80 @@ function spawnInteractiveProcess(opts) {
12155
12166
  return ap;
12156
12167
  }
12157
12168
 
12169
+ // src/spawn-record-store.ts
12170
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync as renameSync4, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
12171
+ import { homedir as homedir10 } from "os";
12172
+ import { join as join14 } from "path";
12173
+ var MAX_SPAWN_RECORDS = 256;
12174
+ var SPAWN_RECORD_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
12175
+ var pathOverride3 = null;
12176
+ var lastWritten = /* @__PURE__ */ new Map();
12177
+ function spawnRecordStorePath() {
12178
+ if (pathOverride3) return pathOverride3;
12179
+ const stateRoot = process.env.XDG_STATE_HOME || join14(homedir10(), ".local", "state");
12180
+ return join14(stateRoot, "opencode-claude-code-plugin", "session-spawns.json");
12181
+ }
12182
+ function isRecord5(value) {
12183
+ const record = value;
12184
+ if (!record || typeof record !== "object" || Array.isArray(record)) return false;
12185
+ if (typeof record.at !== "number" || !Number.isFinite(record.at)) return false;
12186
+ const ok = (field) => field === void 0 || typeof field === "string";
12187
+ if (!ok(record.model) || !ok(record.effort) || !ok(record.account)) return false;
12188
+ return !!record.model;
12189
+ }
12190
+ function readEntries(file) {
12191
+ const out = /* @__PURE__ */ new Map();
12192
+ try {
12193
+ const parsed = JSON.parse(readFileSync8(file, "utf8"));
12194
+ const entries = parsed && typeof parsed === "object" ? parsed.sessions : null;
12195
+ if (!entries || typeof entries !== "object") return out;
12196
+ const cutoff = Date.now() - SPAWN_RECORD_MAX_AGE_MS;
12197
+ const valid = Object.entries(entries).filter((entry) => isRecord5(entry[1])).filter(([, record]) => record.at >= cutoff).sort((a, b) => a[1].at - b[1].at);
12198
+ for (const [key, record] of valid) {
12199
+ out.set(key, {
12200
+ model: record.model,
12201
+ ...record.effort ? { effort: record.effort } : {},
12202
+ ...record.account ? { account: record.account } : {},
12203
+ at: record.at
12204
+ });
12205
+ }
12206
+ } catch {
12207
+ }
12208
+ return out;
12209
+ }
12210
+ function recordSessionSpawn(sessionID, spawn2, now = Date.now()) {
12211
+ if (!sessionID || sessionID === "default" || !spawn2.model) return false;
12212
+ const record = {
12213
+ model: spawn2.model,
12214
+ ...spawn2.effort ? { effort: spawn2.effort } : {},
12215
+ ...spawn2.account ? { account: spawn2.account } : {},
12216
+ at: now
12217
+ };
12218
+ const fingerprint = `${record.model}
12219
+ ${record.effort ?? ""}
12220
+ ${record.account ?? ""}`;
12221
+ if (lastWritten.get(sessionID) === fingerprint) return false;
12222
+ const file = spawnRecordStorePath();
12223
+ const merged = readEntries(file);
12224
+ merged.delete(sessionID);
12225
+ merged.set(sessionID, record);
12226
+ const cutoff = now - SPAWN_RECORD_MAX_AGE_MS;
12227
+ const kept = [...merged.entries()].filter(([, entry]) => entry.at >= cutoff).sort((a, b) => a[1].at - b[1].at).slice(-MAX_SPAWN_RECORDS);
12228
+ try {
12229
+ mkdirSync6(join14(file, ".."), { recursive: true, mode: 448 });
12230
+ const tmp = `${file}.${process.pid}.tmp`;
12231
+ writeFileSync7(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(kept) }), {
12232
+ mode: 384
12233
+ });
12234
+ renameSync4(tmp, file);
12235
+ } catch {
12236
+ return false;
12237
+ }
12238
+ if (lastWritten.size >= MAX_SPAWN_RECORDS) lastWritten.clear();
12239
+ lastWritten.set(sessionID, fingerprint);
12240
+ return true;
12241
+ }
12242
+
12158
12243
  // src/account-transcript.ts
12159
12244
  import { randomUUID as randomUUID6 } from "crypto";
12160
12245
  import { constants as fsConstants } from "fs";
@@ -12216,13 +12301,13 @@ function dedupe2(dirs) {
12216
12301
  }
12217
12302
 
12218
12303
  // src/prompts.ts
12219
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
12220
- import { homedir as homedir10 } from "os";
12304
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
12305
+ import { homedir as homedir11 } from "os";
12221
12306
  import { randomUUID as randomUUID7 } from "crypto";
12222
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join14 } from "path";
12307
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join15 } from "path";
12223
12308
  function readPromptFileIfPresent(path15) {
12224
12309
  try {
12225
- const content = readFileSync8(path15, "utf8").trim();
12310
+ const content = readFileSync9(path15, "utf8").trim();
12226
12311
  return content || void 0;
12227
12312
  } catch {
12228
12313
  return void 0;
@@ -12231,7 +12316,7 @@ function readPromptFileIfPresent(path15) {
12231
12316
  function nearestWorkspaceAgentsPrompt(cwd) {
12232
12317
  let dir = cwd;
12233
12318
  while (true) {
12234
- const content = readPromptFileIfPresent(join14(dir, "AGENTS.md"));
12319
+ const content = readPromptFileIfPresent(join15(dir, "AGENTS.md"));
12235
12320
  if (content) return content;
12236
12321
  const parent = dirname6(dir);
12237
12322
  if (parent === dir) return void 0;
@@ -12342,7 +12427,7 @@ var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLA
12342
12427
  function userAuthoredInstructions(forwarded, opts = {}) {
12343
12428
  const readFile3 = opts.readFile ?? ((path15) => {
12344
12429
  try {
12345
- return readFileSync8(path15, "utf8");
12430
+ return readFileSync9(path15, "utf8");
12346
12431
  } catch {
12347
12432
  return null;
12348
12433
  }
@@ -12383,8 +12468,8 @@ ${options.compressionSummary.trim()}`
12383
12468
  for (const s of extraSystemContent) {
12384
12469
  if (s.trim()) parts.push(s.trim());
12385
12470
  }
12386
- const configRoot = process.env.XDG_CONFIG_HOME ?? join14(homedir10(), ".config");
12387
- const globalAgents = readPromptFileIfPresent(join14(configRoot, "opencode", "AGENTS.md"));
12471
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join15(homedir11(), ".config");
12472
+ const globalAgents = readPromptFileIfPresent(join15(configRoot, "opencode", "AGENTS.md"));
12388
12473
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
12389
12474
  const forwarded = extraSystemContent.join("\n\n");
12390
12475
  const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
@@ -12400,9 +12485,9 @@ ${options.compressionSummary.trim()}`
12400
12485
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
12401
12486
  const content = parts.join("\n\n");
12402
12487
  if (!content) return void 0;
12403
- const path15 = join14(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
12488
+ const path15 = join15(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
12404
12489
  try {
12405
- writeFileSync7(path15, content, { encoding: "utf8", mode: 384 });
12490
+ writeFileSync8(path15, content, { encoding: "utf8", mode: 384 });
12406
12491
  return path15;
12407
12492
  } catch (err) {
12408
12493
  log.warn("failed to write system prompt file", { error: String(err) });
@@ -15567,6 +15652,13 @@ var ClaudeCodeLanguageModel = class {
15567
15652
  return this.subagentDispatchReleaseStream(dispatchStep, sk, warnings);
15568
15653
  }
15569
15654
  if (!compactionMode) invalidateOtherEffortSessions(baseKey, reasoningEffort);
15655
+ if (!compactionMode && affinity !== "default") {
15656
+ recordSessionSpawn(affinity, {
15657
+ model: stripAccountSuffix(failover.modelId),
15658
+ effort: reasoningEffort,
15659
+ account: (this.config.failoverAccounts?.length ?? 0) > 1 ? failover.target ?? sourceAccount : void 0
15660
+ });
15661
+ }
15570
15662
  const hasPriorConversation = options.prompt.filter((m) => m.role === "user" || m.role === "assistant").length > 1;
15571
15663
  if (!hasPriorConversation) {
15572
15664
  deleteClaudeSessionId(sk);
@@ -15770,7 +15862,7 @@ var ClaudeCodeLanguageModel = class {
15770
15862
  let replayReason = "no-session-for-key";
15771
15863
  if (this.config.resumeAfterRestart !== false && includeHistoryContext && !compactionMode && !forkFromClaudeSessionId && failoverAnswer?.kind !== "switch") {
15772
15864
  const configDir = resolveConfigDir(this.config.configDir);
15773
- const transcriptPath2 = (id, dir) => join15(dir ?? configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
15865
+ const transcriptPath2 = (id, dir) => join16(dir ?? configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
15774
15866
  const resumePoint = findResumePoint({
15775
15867
  sessionKey: sk,
15776
15868
  prompt: options.prompt,
@@ -17126,7 +17218,7 @@ var ClaudeCodeLanguageModel = class {
17126
17218
  };
17127
17219
 
17128
17220
  // src/account-tier.ts
17129
- import { readFileSync as readFileSync9 } from "fs";
17221
+ import { readFileSync as readFileSync10 } from "fs";
17130
17222
  import path13 from "path";
17131
17223
  var ORGANIZATION_TYPE_LABELS = {
17132
17224
  claude_max: "Max",
@@ -17169,7 +17261,7 @@ function readTierFromConfigDir(configDir, allowLegacyFallback) {
17169
17261
  function readOauthAccount(file) {
17170
17262
  let raw;
17171
17263
  try {
17172
- raw = readFileSync9(file, "utf8");
17264
+ raw = readFileSync10(file, "utf8");
17173
17265
  } catch {
17174
17266
  return void 0;
17175
17267
  }
@@ -17197,14 +17289,14 @@ function tierLabel(oauthAccount) {
17197
17289
  // src/cleanup-stale.ts
17198
17290
  import {
17199
17291
  existsSync as existsSync5,
17200
- mkdirSync as mkdirSync6,
17201
- readFileSync as readFileSync10,
17292
+ mkdirSync as mkdirSync7,
17293
+ readFileSync as readFileSync11,
17202
17294
  realpathSync as realpathSync4,
17203
17295
  rmSync as rmSync3,
17204
- writeFileSync as writeFileSync8
17296
+ writeFileSync as writeFileSync9
17205
17297
  } from "fs";
17206
- import { homedir as homedir11 } from "os";
17207
- import { dirname as dirname7, join as join16, resolve as resolve5 } from "path";
17298
+ import { homedir as homedir12 } from "os";
17299
+ import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
17208
17300
  import { fileURLToPath as fileURLToPath4 } from "url";
17209
17301
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
17210
17302
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
@@ -17212,20 +17304,20 @@ var alreadyRan = false;
17212
17304
  function candidateCacheRoots() {
17213
17305
  const xdg = process.env.XDG_CACHE_HOME;
17214
17306
  return [
17215
- xdg ? join16(xdg, "opencode") : null,
17216
- join16(homedir11(), ".cache", "opencode"),
17217
- join16(homedir11(), "Library", "Caches", "opencode")
17307
+ xdg ? join17(xdg, "opencode") : null,
17308
+ join17(homedir12(), ".cache", "opencode"),
17309
+ join17(homedir12(), "Library", "Caches", "opencode")
17218
17310
  ].filter((p) => Boolean(p));
17219
17311
  }
17220
17312
  function userOpencodeJsonPath() {
17221
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join16(homedir11(), ".config");
17222
- return join16(xdgConfig, "opencode", "opencode.json");
17313
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join17(homedir12(), ".config");
17314
+ return join17(xdgConfig, "opencode", "opencode.json");
17223
17315
  }
17224
17316
  function userIntendsToUseUnscoped() {
17225
17317
  const cfg = userOpencodeJsonPath();
17226
17318
  if (!existsSync5(cfg)) return false;
17227
17319
  try {
17228
- const json = JSON.parse(readFileSync10(cfg, "utf8"));
17320
+ const json = JSON.parse(readFileSync11(cfg, "utf8"));
17229
17321
  const plugins = json.plugin;
17230
17322
  if (!Array.isArray(plugins)) return false;
17231
17323
  return plugins.some(
@@ -17246,19 +17338,19 @@ function ourLoadedDir() {
17246
17338
  function ourVersion(dir) {
17247
17339
  if (!dir) return null;
17248
17340
  try {
17249
- const pkg = JSON.parse(readFileSync10(join16(dir, "package.json"), "utf8"));
17341
+ const pkg = JSON.parse(readFileSync11(join17(dir, "package.json"), "utf8"));
17250
17342
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
17251
17343
  } catch {
17252
17344
  return null;
17253
17345
  }
17254
17346
  }
17255
17347
  function markerPath() {
17256
- const stateRoot = process.env.XDG_STATE_HOME ?? join16(homedir11(), ".local", "state");
17257
- return join16(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
17348
+ const stateRoot = process.env.XDG_STATE_HOME ?? join17(homedir12(), ".local", "state");
17349
+ return join17(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
17258
17350
  }
17259
17351
  function markerVersion() {
17260
17352
  try {
17261
- const marker = JSON.parse(readFileSync10(markerPath(), "utf8"));
17353
+ const marker = JSON.parse(readFileSync11(markerPath(), "utf8"));
17262
17354
  return typeof marker.version === "string" ? marker.version : null;
17263
17355
  } catch {
17264
17356
  return null;
@@ -17267,8 +17359,8 @@ function markerVersion() {
17267
17359
  function writeMarker(version) {
17268
17360
  const file = markerPath();
17269
17361
  try {
17270
- mkdirSync6(dirname7(file), { recursive: true });
17271
- writeFileSync8(
17362
+ mkdirSync7(dirname7(file), { recursive: true });
17363
+ writeFileSync9(
17272
17364
  file,
17273
17365
  JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
17274
17366
  "utf8"
@@ -17303,7 +17395,7 @@ function cleanupStaleUnscopedInstall() {
17303
17395
  }
17304
17396
  function cleanupOne(cacheRoot, ourDir) {
17305
17397
  if (!existsSync5(cacheRoot)) return;
17306
- const stalePath = join16(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
17398
+ const stalePath = join17(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
17307
17399
  if (!existsSync5(stalePath)) return;
17308
17400
  let realStalePath = stalePath;
17309
17401
  try {
@@ -17311,11 +17403,11 @@ function cleanupOne(cacheRoot, ourDir) {
17311
17403
  } catch {
17312
17404
  }
17313
17405
  if (ourDir && realStalePath === ourDir) return;
17314
- const pkgJsonPath = join16(stalePath, "package.json");
17406
+ const pkgJsonPath = join17(stalePath, "package.json");
17315
17407
  if (!existsSync5(pkgJsonPath)) return;
17316
17408
  let pkg = {};
17317
17409
  try {
17318
- pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf8"));
17410
+ pkg = JSON.parse(readFileSync11(pkgJsonPath, "utf8"));
17319
17411
  } catch {
17320
17412
  return;
17321
17413
  }
@@ -17331,13 +17423,13 @@ function cleanupOne(cacheRoot, ourDir) {
17331
17423
  });
17332
17424
  return;
17333
17425
  }
17334
- const cachePkgJson = join16(cacheRoot, "package.json");
17426
+ const cachePkgJson = join17(cacheRoot, "package.json");
17335
17427
  if (!existsSync5(cachePkgJson)) return;
17336
17428
  try {
17337
- const cfg = JSON.parse(readFileSync10(cachePkgJson, "utf8"));
17429
+ const cfg = JSON.parse(readFileSync11(cachePkgJson, "utf8"));
17338
17430
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
17339
17431
  delete cfg.dependencies[STALE_PACKAGE_NAME];
17340
- writeFileSync8(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
17432
+ writeFileSync9(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
17341
17433
  log.info("cleanup-stale: pruned dep from cache package.json");
17342
17434
  }
17343
17435
  } catch (err) {
@@ -18109,12 +18201,12 @@ function configModelsForProvider(providerModels, providerID, modelSuffix) {
18109
18201
  }
18110
18202
  return models;
18111
18203
  }
18112
- function isRecord5(value) {
18204
+ function isRecord6(value) {
18113
18205
  return typeof value === "object" && value !== null && !Array.isArray(value);
18114
18206
  }
18115
18207
  function completeUserModel(id, entry, providerID) {
18116
- const record = isRecord5(entry) ? entry : {};
18117
- const runtimeShaped = isRecord5(record.capabilities) && isRecord5(record.capabilities.input) && isRecord5(record.capabilities.output) && isRecord5(record.cost) && isRecord5(record.cost.cache) && isRecord5(record.api);
18208
+ const record = isRecord6(entry) ? entry : {};
18209
+ const runtimeShaped = isRecord6(record.capabilities) && isRecord6(record.capabilities.input) && isRecord6(record.capabilities.output) && isRecord6(record.cost) && isRecord6(record.cost.cache) && isRecord6(record.api);
18118
18210
  if (runtimeShaped) {
18119
18211
  return toConfigModel({ ...record, providerID });
18120
18212
  }
@@ -18127,8 +18219,8 @@ function completeUserModel(id, entry, providerID) {
18127
18219
  const merged = { ...base, ...record, id };
18128
18220
  for (const key of ["cost", "limit", "variants"]) {
18129
18221
  merged[key] = {
18130
- ...isRecord5(base[key]) ? base[key] : {},
18131
- ...isRecord5(record[key]) ? record[key] : {}
18222
+ ...isRecord6(base[key]) ? base[key] : {},
18223
+ ...isRecord6(record[key]) ? record[key] : {}
18132
18224
  };
18133
18225
  }
18134
18226
  return merged;