@khalilgharbaoui/opencode-claude-code-plugin 0.47.0 → 0.49.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
@@ -154,7 +154,16 @@ function translateStreamForHost(stream, dialect = "v1") {
154
154
  import { homedir as homedir2 } from "os";
155
155
 
156
156
  // src/accounts.ts
157
- import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
157
+ import {
158
+ chmod,
159
+ copyFile,
160
+ link,
161
+ lstat,
162
+ mkdir,
163
+ readlink,
164
+ symlink,
165
+ writeFile
166
+ } from "fs/promises";
158
167
  import path from "path";
159
168
 
160
169
  // src/logger.ts
@@ -401,8 +410,9 @@ function resolveAccounts(value) {
401
410
  function accountProviderId(account) {
402
411
  return `${BASE_PROVIDER_ID}-${normalizeAccountName(account)}`;
403
412
  }
404
- function accountDisplayName(account) {
405
- return `Claude Code (${titleizeAccount(account)})`;
413
+ function accountDisplayName(account, tier) {
414
+ const name = titleizeAccount(account);
415
+ return `Claude Code (${tier ? `${name}, ${tier}` : name})`;
406
416
  }
407
417
  function accountModelSuffix(account) {
408
418
  const normalized = normalizeAccountName(account);
@@ -425,13 +435,14 @@ function expandHome(value) {
425
435
  }
426
436
  return value;
427
437
  }
428
- async function ensureAccountRuntime(account, baseCliPath) {
438
+ async function ensureAccountRuntime(account, baseCliPath, deps = {}) {
439
+ const platform2 = deps.platform ?? process.platform;
429
440
  const configDir = accountConfigDir(account);
430
441
  if (!configDir) return { cliPath: baseCliPath };
431
442
  const expandedConfigDir = expandHome(configDir);
432
443
  await mkdir(expandedConfigDir, { recursive: true });
433
444
  try {
434
- await ensureSharedCapabilities(expandedConfigDir);
445
+ await ensureSharedCapabilities(expandedConfigDir, platform2);
435
446
  } catch (err) {
436
447
  log.warn("failed to symlink shared capabilities; continuing anyway", {
437
448
  account,
@@ -439,6 +450,13 @@ async function ensureAccountRuntime(account, baseCliPath) {
439
450
  error: String(err)
440
451
  });
441
452
  }
453
+ if (platform2 === "win32") {
454
+ return {
455
+ cliPath: baseCliPath,
456
+ configDir: expandedConfigDir,
457
+ accountInProcess: true
458
+ };
459
+ }
442
460
  const cliPath = await writeAccountWrapper(
443
461
  normalizeAccountName(account),
444
462
  baseCliPath,
@@ -446,13 +464,21 @@ async function ensureAccountRuntime(account, baseCliPath) {
446
464
  );
447
465
  return { cliPath, configDir: expandedConfigDir };
448
466
  }
449
- async function ensureSharedCapabilities(targetRoot) {
467
+ async function ensureSharedCapabilities(targetRoot, platform2) {
450
468
  const sourceRoot = expandHome("~/.claude");
451
469
  for (const item of SHARED_CAPABILITY_ITEMS) {
452
- await ensureSharedCapabilityItem(sourceRoot, targetRoot, item);
470
+ try {
471
+ await ensureSharedCapabilityItem(sourceRoot, targetRoot, item, platform2);
472
+ } catch (err) {
473
+ log.warn("could not share a Claude capability with the account", {
474
+ item,
475
+ target: path.join(targetRoot, item),
476
+ error: String(err)
477
+ });
478
+ }
453
479
  }
454
480
  }
455
- async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
481
+ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item, platform2) {
456
482
  const source = path.join(sourceRoot, item);
457
483
  const target = path.join(targetRoot, item);
458
484
  let sourceStat;
@@ -477,8 +503,29 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
477
503
  return;
478
504
  } catch {
479
505
  }
480
- const type = sourceStat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
481
- await symlink(source, target, type);
506
+ if (sourceStat.isDirectory()) {
507
+ await symlink(source, target, platform2 === "win32" ? "junction" : "dir");
508
+ return;
509
+ }
510
+ if (platform2 !== "win32") {
511
+ await symlink(source, target, "file");
512
+ return;
513
+ }
514
+ try {
515
+ await symlink(source, target, "file");
516
+ return;
517
+ } catch {
518
+ }
519
+ try {
520
+ await link(source, target);
521
+ return;
522
+ } catch {
523
+ }
524
+ await copyFile(source, target);
525
+ log.warn(
526
+ "copied a shared Claude capability into the account config dir instead of linking it; later edits to the original will not be picked up",
527
+ { item, target }
528
+ );
482
529
  }
483
530
  async function writeAccountWrapper(account, baseCliPath, configDir) {
484
531
  const cacheRoot = path.join(
@@ -944,7 +991,10 @@ function formatUnattendedReplayNote(dropped) {
944
991
  var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
945
992
  var MODEL_CLI_FLOORS = {
946
993
  // "Added Claude Sonnet 5.5 (`claude-sonnet-5-5`)" (CHANGELOG, 2.1.284).
947
- "claude-sonnet-5-5": "2.1.284"
994
+ "claude-sonnet-5-5": "2.1.284",
995
+ // "Added Claude Haiku 5.5 (`claude-haiku-5-5`), now the default Haiku model
996
+ // on the Anthropic API" (CHANGELOG, 2.1.293). Absent from 2.1.288's catalog.
997
+ "claude-haiku-5-5": "2.1.293"
948
998
  };
949
999
  function parseUnrecognizedModel(stderr) {
950
1000
  const at = stderr.indexOf(UNRECOGNIZED_MODEL_STDERR_MARKER);
@@ -1838,8 +1888,8 @@ function cliSupportsInteractiveBypass(v) {
1838
1888
  if (!v) return false;
1839
1889
  return gte(v, { major: 2, minor: 1, patch: 263 });
1840
1890
  }
1841
- var INTERACTIVE_MEASURED_CLI = "2.1.288";
1842
- var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch: 288 };
1891
+ var INTERACTIVE_MEASURED_CLI = "2.1.293";
1892
+ var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch: 293 };
1843
1893
  function isUnmeasuredInteractiveCli(v) {
1844
1894
  if (!v) return false;
1845
1895
  return gte(v, INTERACTIVE_MEASURED) && !(v.major === INTERACTIVE_MEASURED.major && v.minor === INTERACTIVE_MEASURED.minor && v.patch === INTERACTIVE_MEASURED.patch);
@@ -5067,6 +5117,9 @@ function claudeSpawnEnv(opts) {
5067
5117
  if (opts?.promptCacheTtl) {
5068
5118
  env.CLAUDE_CODE_PROMPT_CACHE_TTL = opts.promptCacheTtl;
5069
5119
  }
5120
+ if (opts?.configDir) {
5121
+ env.CLAUDE_CONFIG_DIR = opts.configDir;
5122
+ }
5070
5123
  if (opts?.ignoreAnthropicApiKey) {
5071
5124
  delete env.ANTHROPIC_API_KEY;
5072
5125
  delete env.ANTHROPIC_AUTH_TOKEN;
@@ -5184,6 +5237,11 @@ function getActiveProcess(key) {
5184
5237
  }
5185
5238
  return ap;
5186
5239
  }
5240
+ function processBelongsToAnotherAccount(active, cliPath, configDir) {
5241
+ if (!active) return false;
5242
+ if (active.cliPath && active.cliPath !== cliPath) return true;
5243
+ return !!active.configDir && active.configDir !== configDir;
5244
+ }
5187
5245
  function setActiveProcess(key, ap) {
5188
5246
  cancelIdleProcessEviction(key);
5189
5247
  activeProcesses.set(key, ap);
@@ -5399,7 +5457,7 @@ function invalidateOtherEffortSessions(baseKey, effort) {
5399
5457
  clearBackgroundTasks(key);
5400
5458
  }
5401
5459
  }
5402
- function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl) {
5460
+ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl, configDir) {
5403
5461
  evictIfNeeded();
5404
5462
  log.info("spawning new claude process", {
5405
5463
  cliPath,
@@ -5407,13 +5465,14 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
5407
5465
  cwd,
5408
5466
  sessionKey: sessionKey2,
5409
5467
  effort,
5410
- promptCacheTtl
5468
+ promptCacheTtl,
5469
+ configDir
5411
5470
  });
5412
5471
  const plan = planClaudeSpawn(cliPath, cliArgs);
5413
5472
  const proc = spawn(plan.file, plan.args, {
5414
5473
  cwd,
5415
5474
  stdio: ["pipe", "pipe", "pipe"],
5416
- env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl }),
5475
+ env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl, configDir }),
5417
5476
  windowsVerbatimArguments: plan.windowsVerbatimArguments
5418
5477
  });
5419
5478
  const lineEmitter = new EventEmitter3();
@@ -5428,6 +5487,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
5428
5487
  startedAt: Date.now(),
5429
5488
  cliPath,
5430
5489
  cliArgs: [...cliArgs],
5490
+ configDir,
5431
5491
  unattendedLines: [],
5432
5492
  unattendedDropped: 0
5433
5493
  };
@@ -5553,7 +5613,8 @@ function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropi
5553
5613
  old.systemPromptFile,
5554
5614
  ignoreAnthropicApiKey,
5555
5615
  old.effort,
5556
- old.promptCacheTtl
5616
+ old.promptCacheTtl,
5617
+ old.configDir
5557
5618
  );
5558
5619
  replacement.mcpServers = old.mcpServers;
5559
5620
  replacement.pendingProxyCompletions = old.pendingProxyCompletions;
@@ -5984,6 +6045,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
5984
6045
  "ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; stripping it from claude spawns (ignoreAnthropicApiKey) so requests use your subscription auth, not pay-as-you-go API billing.",
5985
6046
  "Code Mode execute not forwarded: no model-visible input schema",
5986
6047
  "Code Mode execute not forwarded: read-only permission preset",
6048
+ "MCP server asked for operator input and was declined: a headless Claude Code session cannot prompt, so its elicitation can only be refused. Run that server's flow in Claude Code directly, or configure it not to elicit.",
5987
6049
  "MCP tool not routed through the proxy: another proxy tool already holds that name, and it keeps it",
5988
6050
  "account failover declined; ending the turn",
5989
6051
  "agent effort override refused: unknown level",
@@ -6020,6 +6082,8 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6020
6082
  "cleanup-stale: error processing cache root",
6021
6083
  "cleanup-stale: rmSync failed",
6022
6084
  "continuing this conversation's claude session under the new model or effort instead of replaying it",
6085
+ "copied a shared Claude capability into the account config dir instead of linking it; later edits to the original will not be picked up",
6086
+ "could not share a Claude capability with the account",
6023
6087
  "delivering proxy results after interrupted continuation",
6024
6088
  "draining proxy calls queued between turns",
6025
6089
  "empty user content; sending sentinel to satisfy CLI",
@@ -6208,6 +6272,8 @@ var BUNDLE_DATA_ALLOWLIST = {
6208
6272
  finishReason: "enum",
6209
6273
  hash: "enum",
6210
6274
  hostApi: "enum",
6275
+ // One of `SHARED_CAPABILITY_ITEMS`: a fixed list this package defines.
6276
+ item: "enum",
6211
6277
  kind: "enum",
6212
6278
  level: "enum",
6213
6279
  mode: "enum",
@@ -6517,8 +6583,8 @@ function redactLogLine(raw, context) {
6517
6583
  if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
6518
6584
  return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
6519
6585
  }
6520
- function readLogTail(path13, maxBytes = BUNDLE_TAIL_BYTES) {
6521
- const fd = openSync(path13, "r");
6586
+ function readLogTail(path14, maxBytes = BUNDLE_TAIL_BYTES) {
6587
+ const fd = openSync(path14, "r");
6522
6588
  try {
6523
6589
  const size = fstatSync(fd).size;
6524
6590
  const length = Math.min(size, maxBytes);
@@ -6698,7 +6764,10 @@ async function fetchPlanUsage(cliPath, options = {}) {
6698
6764
  if (support && await support(cliPath) === "unsupported") {
6699
6765
  return { status: "failed", error: "Plan usage is unavailable: this Claude CLI does not support headless output. Check usage in the Claude CLI directly." };
6700
6766
  }
6701
- const env = claudeSpawnEnv({ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey });
6767
+ const env = claudeSpawnEnv({
6768
+ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
6769
+ configDir: options.configDir
6770
+ });
6702
6771
  const stdout = await run(
6703
6772
  cliPath,
6704
6773
  ["-p", "/cost", "--output-format", "json"],
@@ -7369,7 +7438,8 @@ async function gatherDoctorReport(options) {
7369
7438
  });
7370
7439
  }
7371
7440
  const planUsage = wantsPlanUsage(options.argument ?? "") ? await (options.planUsageImpl ?? fetchPlanUsage)(cliPath, {
7372
- ignoreAnthropicApiKey: options.ignoreAnthropicApiKey
7441
+ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
7442
+ configDir: options.configDir
7373
7443
  }) : { status: "not-requested" };
7374
7444
  return {
7375
7445
  plugin: base.plugin,
@@ -7526,6 +7596,8 @@ function passthroughModel(id) {
7526
7596
  };
7527
7597
  }
7528
7598
  var haikuCost = { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 };
7599
+ var haiku55Cost = { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 };
7600
+ var haiku55LongPromptCost = { input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 };
7529
7601
  var sonnetCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
7530
7602
  var sonnet5Cost = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
7531
7603
  var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
@@ -7566,6 +7638,9 @@ function toConfigModel(model) {
7566
7638
  variants: model.variants
7567
7639
  };
7568
7640
  }
7641
+ var LONG_PROMPT_COSTS = {
7642
+ "claude-haiku-5-5": { aboveTokens: 1e5, cost: haiku55LongPromptCost }
7643
+ };
7569
7644
  var defaultModels = {
7570
7645
  "claude-haiku-4-5": defineModel({
7571
7646
  id: "claude-haiku-4-5",
@@ -7578,6 +7653,32 @@ var defaultModels = {
7578
7653
  multiplier: 1,
7579
7654
  releaseDate: "2025-10-01"
7580
7655
  }),
7656
+ // Haiku 5.5 needs Claude Code 2.1.293+, the release that added it and made
7657
+ // it the default Haiku; an older CLI runs it on fallback limits and says so
7658
+ // on stderr, which `reportUnrecognizedModel` turns into a warning. Unlike
7659
+ // Haiku 4.5 it reasons: the CLI's catalog gives it `effort`, `max_effort`,
7660
+ // `xhigh_effort` and `adaptive_thinking`, and Anthropic's migration note says
7661
+ // manual `budget_tokens` is a 400 and adaptive thinking is on by default. The
7662
+ // plugin never sends `budget_tokens`, so nothing here special-cases it, the
7663
+ // same as Opus 5.5's `rejects_disabled_thinking`. No `-fast` entry: the
7664
+ // catalog gives it no `fast_mode` capability.
7665
+ //
7666
+ // The multiplier is 0.1 because it is exactly a tenth of Haiku 4.5 on input,
7667
+ // output, cache read and cache write alike, and Haiku 4.5's $1/$5 is the 1x
7668
+ // anchor every other entry here is already measured against. Re-anchoring the
7669
+ // whole table on Haiku 5.5 would change every model's displayed number for no
7670
+ // gain in accuracy.
7671
+ "claude-haiku-5-5": defineModel({
7672
+ id: "claude-haiku-5-5",
7673
+ name: "Claude Haiku 5.5",
7674
+ family: "haiku",
7675
+ reasoning: true,
7676
+ context: 1e6,
7677
+ output: 128e3,
7678
+ cost: haiku55Cost,
7679
+ multiplier: 0.1,
7680
+ releaseDate: "2026-10-07"
7681
+ }),
7581
7682
  "claude-sonnet-4-5": defineModel({
7582
7683
  id: "claude-sonnet-4-5",
7583
7684
  name: "Claude Sonnet 4.5",
@@ -7806,14 +7907,25 @@ function apiCallCostUsd(model, usage) {
7806
7907
  const base = model.replace(/@.*$/, "").replace(/\[1m\]$/i, "").replace(/-\d{8}$/, "");
7807
7908
  const entry = (usage.speed === "fast" ? defaultModels[`${base}${FAST_SUFFIX}`] : void 0) ?? defaultModels[base];
7808
7909
  if (!entry) return null;
7809
- const cost = entry.cost;
7810
7910
  const count = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
7911
+ const cost = longPromptCost(base, usage, count) ?? entry.cost;
7811
7912
  const split = usage.cache_creation;
7812
7913
  const write1h = split ? count(split.ephemeral_1h_input_tokens) : 0;
7813
7914
  const write5m = split ? count(split.ephemeral_5m_input_tokens) : count(usage.cache_creation_input_tokens);
7814
7915
  const perMillion = count(usage.input_tokens) * cost.input + count(usage.output_tokens) * cost.output + count(usage.cache_read_input_tokens) * cost.cache.read + write5m * cost.cache.write + write1h * cost.input * 2;
7815
7916
  return perMillion / 1e6 + count(usage.server_tool_use?.web_search_requests) * WEB_SEARCH_USD;
7816
7917
  }
7918
+ function longPromptCost(base, usage, count) {
7919
+ const tier = LONG_PROMPT_COSTS[base];
7920
+ if (!tier) return null;
7921
+ const promptTokens = count(usage.input_tokens) + count(usage.cache_read_input_tokens) + count(usage.cache_creation_input_tokens);
7922
+ if (promptTokens <= tier.aboveTokens) return null;
7923
+ return {
7924
+ input: tier.cost.input,
7925
+ output: tier.cost.output,
7926
+ cache: { read: tier.cost.cacheRead, write: tier.cost.cacheWrite }
7927
+ };
7928
+ }
7817
7929
 
7818
7930
  // src/agent-models.ts
7819
7931
  var AGENT_DIR_NAMES = ["agents", "agent"];
@@ -10652,7 +10764,7 @@ function spawnInteractiveProcess(opts) {
10652
10764
  // src/account-transcript.ts
10653
10765
  import { randomUUID as randomUUID6 } from "crypto";
10654
10766
  import { constants as fsConstants } from "fs";
10655
- import { chmod as chmod2, copyFile, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
10767
+ import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
10656
10768
  import path11 from "path";
10657
10769
  var transcriptPath = interactiveTranscriptPath;
10658
10770
  function configDirForAccount(account) {
@@ -10682,7 +10794,7 @@ async function carryTranscriptToAccount(input) {
10682
10794
  const target = renamed ? transcriptPath({ configDir: targetConfigDir, cwd, sessionId: carriedId }) : targetOwn;
10683
10795
  try {
10684
10796
  await mkdir2(path11.dirname(target), { recursive: true, mode: 448 });
10685
- await copyFile(live.file, target, fsConstants.COPYFILE_EXCL);
10797
+ await copyFile2(live.file, target, fsConstants.COPYFILE_EXCL);
10686
10798
  await chmod2(target, 384);
10687
10799
  } catch (err) {
10688
10800
  return { kind: "refused", reason: "copy-failed", detail: String(err) };
@@ -10714,9 +10826,9 @@ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "
10714
10826
  import { homedir as homedir8 } from "os";
10715
10827
  import { randomUUID as randomUUID7 } from "crypto";
10716
10828
  import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
10717
- function readPromptFileIfPresent(path13) {
10829
+ function readPromptFileIfPresent(path14) {
10718
10830
  try {
10719
- const content = readFileSync6(path13, "utf8").trim();
10831
+ const content = readFileSync6(path14, "utf8").trim();
10720
10832
  return content || void 0;
10721
10833
  } catch {
10722
10834
  return void 0;
@@ -10821,9 +10933,9 @@ function extractSystemMessages(prompt) {
10821
10933
  }
10822
10934
  var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
10823
10935
  function userAuthoredInstructions(forwarded, opts = {}) {
10824
- const readFile2 = opts.readFile ?? ((path13) => {
10936
+ const readFile2 = opts.readFile ?? ((path14) => {
10825
10937
  try {
10826
- return readFileSync6(path13, "utf8");
10938
+ return readFileSync6(path14, "utf8");
10827
10939
  } catch {
10828
10940
  return null;
10829
10941
  }
@@ -10835,16 +10947,16 @@ function userAuthoredInstructions(forwarded, opts = {}) {
10835
10947
  }
10836
10948
  const seen = /* @__PURE__ */ new Set();
10837
10949
  for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
10838
- const path13 = match[1].trim();
10839
- if (seen.has(path13) || !isAbsolute3(path13)) continue;
10840
- if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path13))) continue;
10841
- seen.add(path13);
10842
- const file = readFile2(path13);
10950
+ const path14 = match[1].trim();
10951
+ if (seen.has(path14) || !isAbsolute3(path14)) continue;
10952
+ if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path14))) continue;
10953
+ seen.add(path14);
10954
+ const file = readFile2(path14);
10843
10955
  if (!file?.trim()) continue;
10844
10956
  const start = match.index + match[0].length + 1;
10845
10957
  const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
10846
10958
  if (content === null) continue;
10847
- out.push({ text: `Instructions from: ${path13}
10959
+ out.push({ text: `Instructions from: ${path14}
10848
10960
  ${content}`, content });
10849
10961
  }
10850
10962
  return out;
@@ -10881,10 +10993,10 @@ ${options.compressionSummary.trim()}`
10881
10993
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
10882
10994
  const content = parts.join("\n\n");
10883
10995
  if (!content) return void 0;
10884
- const path13 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
10996
+ const path14 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
10885
10997
  try {
10886
- writeFileSync6(path13, content, { encoding: "utf8", mode: 384 });
10887
- return path13;
10998
+ writeFileSync6(path14, content, { encoding: "utf8", mode: 384 });
10999
+ return path14;
10888
11000
  } catch (err) {
10889
11001
  log.warn("failed to write system prompt file", { error: String(err) });
10890
11002
  return void 0;
@@ -11215,6 +11327,8 @@ _Reply with your answer to continue._
11215
11327
  }
11216
11328
 
11217
11329
  // src/control-request.ts
11330
+ var ELICITATION_DECLINE = { action: "decline" };
11331
+ var warnedElicitationServers = /* @__PURE__ */ new Set();
11218
11332
  function controlRequestBehaviorForTool(config, toolName) {
11219
11333
  const configured = config.controlRequestToolBehaviors;
11220
11334
  if (configured && toolName) {
@@ -11283,6 +11397,18 @@ function handleControlRequest(config, msg, proc) {
11283
11397
  }
11284
11398
  return true;
11285
11399
  }
11400
+ if (request.subtype === "elicitation") {
11401
+ const server2 = request.mcp_server_name || "unknown";
11402
+ writeControlResponse(proc, requestId, { ...ELICITATION_DECLINE });
11403
+ if (!warnedElicitationServers.has(server2)) {
11404
+ warnedElicitationServers.add(server2);
11405
+ log.warn(
11406
+ "MCP server asked for operator input and was declined: a headless Claude Code session cannot prompt, so its elicitation can only be refused. Run that server's flow in Claude Code directly, or configure it not to elicit.",
11407
+ { server: server2, mode: request.mode ?? "form" }
11408
+ );
11409
+ }
11410
+ return true;
11411
+ }
11286
11412
  writeControlResponse(proc, requestId, {});
11287
11413
  log.debug("control request acknowledged", {
11288
11414
  requestId,
@@ -12001,14 +12127,18 @@ var FAST_MODE_REASONS = {
12001
12127
  pending: "the CLI is still checking availability"
12002
12128
  };
12003
12129
  var warnedFastModeReasons = /* @__PURE__ */ new Set();
12004
- function reportFastModeState(msg, requested) {
12130
+ function reportFastModeState(msg, requested, phase = "result") {
12005
12131
  const state = msg.fast_mode_state;
12006
12132
  if (!state) return;
12007
12133
  if (!requested) {
12008
- log.debug("fast mode state", { state });
12134
+ log.debug("fast mode state", { state, phase });
12009
12135
  return;
12010
12136
  }
12011
12137
  if (state === "on") {
12138
+ if (phase === "init") {
12139
+ log.debug("fast mode reported on at session start", { state, phase });
12140
+ return;
12141
+ }
12012
12142
  log.info("fast mode active", { state });
12013
12143
  return;
12014
12144
  }
@@ -12503,7 +12633,7 @@ function createLineHandler(state, ctx) {
12503
12633
  claudeSessionId: msg.session_id
12504
12634
  });
12505
12635
  }
12506
- reportFastModeState(msg, ctx.fastMode);
12636
+ reportFastModeState(msg, ctx.fastMode, "init");
12507
12637
  reportSystemInit(msg, {
12508
12638
  ignoreAnthropicApiKey: ctx.config.ignoreAnthropicApiKey
12509
12639
  });
@@ -13069,6 +13199,7 @@ ${plan}
13069
13199
  }
13070
13200
  if (msg.type === "result") {
13071
13201
  clearFallbackTimer(state);
13202
+ reportFastModeState(msg, ctx.fastMode, "result");
13072
13203
  if (msg.session_id) {
13073
13204
  setClaudeSessionId(state.sessionKey, msg.session_id);
13074
13205
  }
@@ -13691,6 +13822,11 @@ var ClaudeCodeLanguageModel = class {
13691
13822
  interactive: !!useInteractive,
13692
13823
  turnStats: this.config.turnStats === true,
13693
13824
  ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey === true,
13825
+ // Where the plugin applies the account itself rather than through a
13826
+ // wrapper (`accountInProcess`, Windows), the `/cost` spawn needs the
13827
+ // same config dir a turn gets, or it reports the default account's
13828
+ // plan usage instead of this provider's.
13829
+ configDir: this.config.accountInProcess ? this.config.configDir : void 0,
13694
13830
  // `/claude-code-doctor usage` opts into the CLI's own plan-usage
13695
13831
  // report; anything else here is ignored, as it always has been.
13696
13832
  argument: doctor.rest
@@ -13891,11 +14027,12 @@ var ClaudeCodeLanguageModel = class {
13891
14027
  resetsAt: failoverAnswer.resetsAt
13892
14028
  });
13893
14029
  }
14030
+ const spawnConfigDir = this.config.accountInProcess ? this.skillBridgeSpawn(failover).configDir : void 0;
13894
14031
  const processForAccount = getActiveProcess(sk);
13895
- if (!compactionMode && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
14032
+ if (!compactionMode && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
13896
14033
  log.notice("claude process belongs to another account; starting fresh", {
13897
14034
  sessionKey: sk,
13898
- was: processForAccount.cliPath,
14035
+ was: processForAccount?.cliPath,
13899
14036
  now: cliPath,
13900
14037
  failedOver: failover.failedOver
13901
14038
  });
@@ -13938,7 +14075,7 @@ var ClaudeCodeLanguageModel = class {
13938
14075
  });
13939
14076
  deleteClaudeSessionId(sk);
13940
14077
  }
13941
- } else if (!compactionMode && sessionBeforeAccountCheck && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
14078
+ } else if (!compactionMode && sessionBeforeAccountCheck && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
13942
14079
  deleteClaudeSessionId(sk);
13943
14080
  }
13944
14081
  const hasExistingSession = !!getClaudeSessionId(sk);
@@ -14051,7 +14188,9 @@ var ClaudeCodeLanguageModel = class {
14051
14188
  if (this.config.forkSessions === true && !compactionMode) {
14052
14189
  recordForkFingerprint(sk, options.prompt, cliPath);
14053
14190
  }
14054
- const { model: spawnModelId, fast: fastMode } = parseModelId(failover.modelId);
14191
+ const { model: spawnModelId, fast: fastMode } = parseModelId(
14192
+ this.config.accountInProcess ? stripAccountSuffix(failover.modelId) : failover.modelId
14193
+ );
14055
14194
  const exitPlanModeQuestionResult = compactionMode ? null : consumeExitPlanModeQuestionResult(sk, options.prompt);
14056
14195
  if (exitPlanModeQuestionResult) {
14057
14196
  log.info("sending plan approval decision to claude", { sk });
@@ -14681,7 +14820,8 @@ var ClaudeCodeLanguageModel = class {
14681
14820
  spawnSystemPromptFile,
14682
14821
  self.config.ignoreAnthropicApiKey,
14683
14822
  reasoningEffort,
14684
- promptCacheTtl
14823
+ promptCacheTtl,
14824
+ spawnConfigDir
14685
14825
  );
14686
14826
  ap.mcpServers = spawnMcpServers;
14687
14827
  state.proc = ap.proc;
@@ -15242,11 +15382,80 @@ var ClaudeCodeLanguageModel = class {
15242
15382
  }
15243
15383
  };
15244
15384
 
15385
+ // src/account-tier.ts
15386
+ import { readFileSync as readFileSync7 } from "fs";
15387
+ import path12 from "path";
15388
+ var ORGANIZATION_TYPE_LABELS = {
15389
+ claude_max: "Max",
15390
+ claude_pro: "Pro",
15391
+ claude_team: "Team",
15392
+ claude_enterprise: "Enterprise",
15393
+ claude_free: "Free"
15394
+ };
15395
+ var RATE_LIMIT_TIER_LABELS = {
15396
+ default_claude_max_5x: "5x",
15397
+ default_claude_max_20x: "20x"
15398
+ };
15399
+ var tierCache = /* @__PURE__ */ new Map();
15400
+ function accountTierConfigDir(account) {
15401
+ const configured = accountConfigDirPath(account);
15402
+ if (configured) return configured;
15403
+ const fromEnv = process.env.CLAUDE_CONFIG_DIR;
15404
+ return fromEnv && fromEnv.trim() ? expandHome(fromEnv.trim()) : expandHome("~/.claude");
15405
+ }
15406
+ function readAccountTier(account) {
15407
+ const normalized = normalizeAccountName(account) || DEFAULT_ACCOUNT;
15408
+ const configDir = accountTierConfigDir(normalized);
15409
+ const cached = tierCache.get(configDir);
15410
+ if (cached !== void 0) return cached ?? void 0;
15411
+ const label = readTierFromConfigDir(configDir, normalized === DEFAULT_ACCOUNT);
15412
+ tierCache.set(configDir, label ?? null);
15413
+ return label;
15414
+ }
15415
+ function readTierFromConfigDir(configDir, allowLegacyFallback) {
15416
+ const candidates = [path12.join(configDir, ".claude.json")];
15417
+ if (allowLegacyFallback) candidates.push(expandHome("~/.claude.json"));
15418
+ for (const candidate of candidates) {
15419
+ const oauth = readOauthAccount(candidate);
15420
+ if (!oauth) continue;
15421
+ const label = tierLabel(oauth);
15422
+ if (label) return label;
15423
+ }
15424
+ return void 0;
15425
+ }
15426
+ function readOauthAccount(file) {
15427
+ let raw;
15428
+ try {
15429
+ raw = readFileSync7(file, "utf8");
15430
+ } catch {
15431
+ return void 0;
15432
+ }
15433
+ try {
15434
+ const parsed = JSON.parse(raw);
15435
+ if (!parsed || typeof parsed !== "object") return void 0;
15436
+ const oauth = parsed.oauthAccount;
15437
+ return oauth && typeof oauth === "object" ? oauth : void 0;
15438
+ } catch {
15439
+ log.debug("could not parse a Claude config file for its plan tier", { file });
15440
+ return void 0;
15441
+ }
15442
+ }
15443
+ function tierLabel(oauthAccount) {
15444
+ const organizationType = oauthAccount.organizationType;
15445
+ if (typeof organizationType !== "string") return void 0;
15446
+ const base = ORGANIZATION_TYPE_LABELS[organizationType];
15447
+ if (!base) return void 0;
15448
+ if (base !== "Max") return base;
15449
+ const rateLimitTier = oauthAccount.organizationRateLimitTier;
15450
+ const multiple = typeof rateLimitTier === "string" ? RATE_LIMIT_TIER_LABELS[rateLimitTier] : void 0;
15451
+ return multiple ? `${base} ${multiple}` : base;
15452
+ }
15453
+
15245
15454
  // src/cleanup-stale.ts
15246
15455
  import {
15247
15456
  existsSync as existsSync5,
15248
15457
  mkdirSync as mkdirSync5,
15249
- readFileSync as readFileSync7,
15458
+ readFileSync as readFileSync8,
15250
15459
  realpathSync as realpathSync4,
15251
15460
  rmSync as rmSync3,
15252
15461
  writeFileSync as writeFileSync7
@@ -15273,7 +15482,7 @@ function userIntendsToUseUnscoped() {
15273
15482
  const cfg = userOpencodeJsonPath();
15274
15483
  if (!existsSync5(cfg)) return false;
15275
15484
  try {
15276
- const json = JSON.parse(readFileSync7(cfg, "utf8"));
15485
+ const json = JSON.parse(readFileSync8(cfg, "utf8"));
15277
15486
  const plugins = json.plugin;
15278
15487
  if (!Array.isArray(plugins)) return false;
15279
15488
  return plugins.some(
@@ -15294,7 +15503,7 @@ function ourLoadedDir() {
15294
15503
  function ourVersion(dir) {
15295
15504
  if (!dir) return null;
15296
15505
  try {
15297
- const pkg = JSON.parse(readFileSync7(join13(dir, "package.json"), "utf8"));
15506
+ const pkg = JSON.parse(readFileSync8(join13(dir, "package.json"), "utf8"));
15298
15507
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
15299
15508
  } catch {
15300
15509
  return null;
@@ -15306,7 +15515,7 @@ function markerPath() {
15306
15515
  }
15307
15516
  function markerVersion() {
15308
15517
  try {
15309
- const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
15518
+ const marker = JSON.parse(readFileSync8(markerPath(), "utf8"));
15310
15519
  return typeof marker.version === "string" ? marker.version : null;
15311
15520
  } catch {
15312
15521
  return null;
@@ -15363,7 +15572,7 @@ function cleanupOne(cacheRoot, ourDir) {
15363
15572
  if (!existsSync5(pkgJsonPath)) return;
15364
15573
  let pkg = {};
15365
15574
  try {
15366
- pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
15575
+ pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf8"));
15367
15576
  } catch {
15368
15577
  return;
15369
15578
  }
@@ -15382,7 +15591,7 @@ function cleanupOne(cacheRoot, ourDir) {
15382
15591
  const cachePkgJson = join13(cacheRoot, "package.json");
15383
15592
  if (!existsSync5(cachePkgJson)) return;
15384
15593
  try {
15385
- const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
15594
+ const cfg = JSON.parse(readFileSync8(cachePkgJson, "utf8"));
15386
15595
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
15387
15596
  delete cfg.dependencies[STALE_PACKAGE_NAME];
15388
15597
  writeFileSync7(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -15462,7 +15671,7 @@ function registerOpencodeLogSink(client) {
15462
15671
  }
15463
15672
 
15464
15673
  // src/v2-client.ts
15465
- import path12 from "path";
15674
+ import path13 from "path";
15466
15675
  function formatAgentTypeList(agents) {
15467
15676
  const lines = agents.flatMap((agent) => {
15468
15677
  if (agent.hidden === true || agent.mode === "primary") return [];
@@ -15491,7 +15700,7 @@ function toV1Session(info) {
15491
15700
  if (!info || typeof info !== "object") return void 0;
15492
15701
  const session = info;
15493
15702
  const root = session.location?.directory;
15494
- const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path12.resolve(root, session.subpath) : root : void 0;
15703
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path13.resolve(root, session.subpath) : root : void 0;
15495
15704
  return { ...info, directory, parentID: session.parentID };
15496
15705
  }
15497
15706
  function toV1Messages(messages) {
@@ -15683,7 +15892,7 @@ function planV2Providers(seedSettings, defaultProxyTools) {
15683
15892
  return {
15684
15893
  info: {
15685
15894
  id: providerID,
15686
- name: accountDisplayName(account),
15895
+ name: accountDisplayName(account, readAccountTier(account)),
15687
15896
  activation: "enabled",
15688
15897
  package: V2_PLUGIN_PACKAGE,
15689
15898
  settings: {
@@ -16221,7 +16430,7 @@ async function expandAccountProviders(config) {
16221
16430
  // stripped by cleanProviderOptions.
16222
16431
  failoverAccounts: accounts
16223
16432
  },
16224
- accountDisplayName(account)
16433
+ accountDisplayName(account, readAccountTier(account))
16225
16434
  ),
16226
16435
  models: configModelsForProvider(
16227
16436
  existing?.models ?? seed?.models ?? {},