@khalilgharbaoui/opencode-claude-code-plugin 0.47.0 → 0.48.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.d.ts CHANGED
@@ -382,6 +382,13 @@ interface ClaudeCodeConfig {
382
382
  * `providerConfig`, not by the user.
383
383
  */
384
384
  baseCliPath?: string;
385
+ /**
386
+ * This spawn applies the account itself rather than through a wrapper
387
+ * script: it exports `CLAUDE_CONFIG_DIR` and takes the `@<account>` marker
388
+ * off `--model`. Set by `ensureAccountRuntime` on Windows only, where the
389
+ * wrapper is a shape worth avoiding (h #g221). Not a user option.
390
+ */
391
+ accountInProcess?: boolean;
385
392
  accountFailover?: AccountFailoverMode;
386
393
  providerID?: string;
387
394
  skipPermissions?: boolean;
@@ -510,6 +517,11 @@ interface ClaudeCodeProviderSettings {
510
517
  failoverAccounts?: string[];
511
518
  /** The CLI path before the per-account wrapper substitution. */
512
519
  baseCliPath?: string;
520
+ /**
521
+ * Written by the config hook on Windows, where there is no wrapper script
522
+ * and the spawn applies the account itself. Not a user option.
523
+ */
524
+ accountInProcess?: boolean;
513
525
  /**
514
526
  * When this account is out of usage, show the operator a form listing the
515
527
  * other configured accounts and continue the task on the pick, inside the
@@ -1051,6 +1063,10 @@ interface ClaudeStreamMessage {
1051
1063
  display_name?: string;
1052
1064
  agent_id?: string;
1053
1065
  description?: string;
1066
+ /** `elicitation` only: the MCP server that asked for operator input. */
1067
+ mcp_server_name?: string;
1068
+ /** `elicitation` only: `form` or `url`. */
1069
+ mode?: string;
1054
1070
  };
1055
1071
  /**
1056
1072
  * On an `assistant` message the CLI synthesised to report a failure: the
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(
@@ -5067,6 +5114,9 @@ function claudeSpawnEnv(opts) {
5067
5114
  if (opts?.promptCacheTtl) {
5068
5115
  env.CLAUDE_CODE_PROMPT_CACHE_TTL = opts.promptCacheTtl;
5069
5116
  }
5117
+ if (opts?.configDir) {
5118
+ env.CLAUDE_CONFIG_DIR = opts.configDir;
5119
+ }
5070
5120
  if (opts?.ignoreAnthropicApiKey) {
5071
5121
  delete env.ANTHROPIC_API_KEY;
5072
5122
  delete env.ANTHROPIC_AUTH_TOKEN;
@@ -5184,6 +5234,11 @@ function getActiveProcess(key) {
5184
5234
  }
5185
5235
  return ap;
5186
5236
  }
5237
+ function processBelongsToAnotherAccount(active, cliPath, configDir) {
5238
+ if (!active) return false;
5239
+ if (active.cliPath && active.cliPath !== cliPath) return true;
5240
+ return !!active.configDir && active.configDir !== configDir;
5241
+ }
5187
5242
  function setActiveProcess(key, ap) {
5188
5243
  cancelIdleProcessEviction(key);
5189
5244
  activeProcesses.set(key, ap);
@@ -5399,7 +5454,7 @@ function invalidateOtherEffortSessions(baseKey, effort) {
5399
5454
  clearBackgroundTasks(key);
5400
5455
  }
5401
5456
  }
5402
- function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl) {
5457
+ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl, configDir) {
5403
5458
  evictIfNeeded();
5404
5459
  log.info("spawning new claude process", {
5405
5460
  cliPath,
@@ -5407,13 +5462,14 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
5407
5462
  cwd,
5408
5463
  sessionKey: sessionKey2,
5409
5464
  effort,
5410
- promptCacheTtl
5465
+ promptCacheTtl,
5466
+ configDir
5411
5467
  });
5412
5468
  const plan = planClaudeSpawn(cliPath, cliArgs);
5413
5469
  const proc = spawn(plan.file, plan.args, {
5414
5470
  cwd,
5415
5471
  stdio: ["pipe", "pipe", "pipe"],
5416
- env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl }),
5472
+ env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl, configDir }),
5417
5473
  windowsVerbatimArguments: plan.windowsVerbatimArguments
5418
5474
  });
5419
5475
  const lineEmitter = new EventEmitter3();
@@ -5428,6 +5484,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
5428
5484
  startedAt: Date.now(),
5429
5485
  cliPath,
5430
5486
  cliArgs: [...cliArgs],
5487
+ configDir,
5431
5488
  unattendedLines: [],
5432
5489
  unattendedDropped: 0
5433
5490
  };
@@ -5553,7 +5610,8 @@ function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropi
5553
5610
  old.systemPromptFile,
5554
5611
  ignoreAnthropicApiKey,
5555
5612
  old.effort,
5556
- old.promptCacheTtl
5613
+ old.promptCacheTtl,
5614
+ old.configDir
5557
5615
  );
5558
5616
  replacement.mcpServers = old.mcpServers;
5559
5617
  replacement.pendingProxyCompletions = old.pendingProxyCompletions;
@@ -5984,6 +6042,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
5984
6042
  "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
6043
  "Code Mode execute not forwarded: no model-visible input schema",
5986
6044
  "Code Mode execute not forwarded: read-only permission preset",
6045
+ "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
6046
  "MCP tool not routed through the proxy: another proxy tool already holds that name, and it keeps it",
5988
6047
  "account failover declined; ending the turn",
5989
6048
  "agent effort override refused: unknown level",
@@ -6020,6 +6079,8 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6020
6079
  "cleanup-stale: error processing cache root",
6021
6080
  "cleanup-stale: rmSync failed",
6022
6081
  "continuing this conversation's claude session under the new model or effort instead of replaying it",
6082
+ "copied a shared Claude capability into the account config dir instead of linking it; later edits to the original will not be picked up",
6083
+ "could not share a Claude capability with the account",
6023
6084
  "delivering proxy results after interrupted continuation",
6024
6085
  "draining proxy calls queued between turns",
6025
6086
  "empty user content; sending sentinel to satisfy CLI",
@@ -6208,6 +6269,8 @@ var BUNDLE_DATA_ALLOWLIST = {
6208
6269
  finishReason: "enum",
6209
6270
  hash: "enum",
6210
6271
  hostApi: "enum",
6272
+ // One of `SHARED_CAPABILITY_ITEMS`: a fixed list this package defines.
6273
+ item: "enum",
6211
6274
  kind: "enum",
6212
6275
  level: "enum",
6213
6276
  mode: "enum",
@@ -6517,8 +6580,8 @@ function redactLogLine(raw, context) {
6517
6580
  if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
6518
6581
  return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
6519
6582
  }
6520
- function readLogTail(path13, maxBytes = BUNDLE_TAIL_BYTES) {
6521
- const fd = openSync(path13, "r");
6583
+ function readLogTail(path14, maxBytes = BUNDLE_TAIL_BYTES) {
6584
+ const fd = openSync(path14, "r");
6522
6585
  try {
6523
6586
  const size = fstatSync(fd).size;
6524
6587
  const length = Math.min(size, maxBytes);
@@ -6698,7 +6761,10 @@ async function fetchPlanUsage(cliPath, options = {}) {
6698
6761
  if (support && await support(cliPath) === "unsupported") {
6699
6762
  return { status: "failed", error: "Plan usage is unavailable: this Claude CLI does not support headless output. Check usage in the Claude CLI directly." };
6700
6763
  }
6701
- const env = claudeSpawnEnv({ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey });
6764
+ const env = claudeSpawnEnv({
6765
+ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
6766
+ configDir: options.configDir
6767
+ });
6702
6768
  const stdout = await run(
6703
6769
  cliPath,
6704
6770
  ["-p", "/cost", "--output-format", "json"],
@@ -7369,7 +7435,8 @@ async function gatherDoctorReport(options) {
7369
7435
  });
7370
7436
  }
7371
7437
  const planUsage = wantsPlanUsage(options.argument ?? "") ? await (options.planUsageImpl ?? fetchPlanUsage)(cliPath, {
7372
- ignoreAnthropicApiKey: options.ignoreAnthropicApiKey
7438
+ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
7439
+ configDir: options.configDir
7373
7440
  }) : { status: "not-requested" };
7374
7441
  return {
7375
7442
  plugin: base.plugin,
@@ -10652,7 +10719,7 @@ function spawnInteractiveProcess(opts) {
10652
10719
  // src/account-transcript.ts
10653
10720
  import { randomUUID as randomUUID6 } from "crypto";
10654
10721
  import { constants as fsConstants } from "fs";
10655
- import { chmod as chmod2, copyFile, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
10722
+ import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
10656
10723
  import path11 from "path";
10657
10724
  var transcriptPath = interactiveTranscriptPath;
10658
10725
  function configDirForAccount(account) {
@@ -10682,7 +10749,7 @@ async function carryTranscriptToAccount(input) {
10682
10749
  const target = renamed ? transcriptPath({ configDir: targetConfigDir, cwd, sessionId: carriedId }) : targetOwn;
10683
10750
  try {
10684
10751
  await mkdir2(path11.dirname(target), { recursive: true, mode: 448 });
10685
- await copyFile(live.file, target, fsConstants.COPYFILE_EXCL);
10752
+ await copyFile2(live.file, target, fsConstants.COPYFILE_EXCL);
10686
10753
  await chmod2(target, 384);
10687
10754
  } catch (err) {
10688
10755
  return { kind: "refused", reason: "copy-failed", detail: String(err) };
@@ -10714,9 +10781,9 @@ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "
10714
10781
  import { homedir as homedir8 } from "os";
10715
10782
  import { randomUUID as randomUUID7 } from "crypto";
10716
10783
  import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
10717
- function readPromptFileIfPresent(path13) {
10784
+ function readPromptFileIfPresent(path14) {
10718
10785
  try {
10719
- const content = readFileSync6(path13, "utf8").trim();
10786
+ const content = readFileSync6(path14, "utf8").trim();
10720
10787
  return content || void 0;
10721
10788
  } catch {
10722
10789
  return void 0;
@@ -10821,9 +10888,9 @@ function extractSystemMessages(prompt) {
10821
10888
  }
10822
10889
  var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
10823
10890
  function userAuthoredInstructions(forwarded, opts = {}) {
10824
- const readFile2 = opts.readFile ?? ((path13) => {
10891
+ const readFile2 = opts.readFile ?? ((path14) => {
10825
10892
  try {
10826
- return readFileSync6(path13, "utf8");
10893
+ return readFileSync6(path14, "utf8");
10827
10894
  } catch {
10828
10895
  return null;
10829
10896
  }
@@ -10835,16 +10902,16 @@ function userAuthoredInstructions(forwarded, opts = {}) {
10835
10902
  }
10836
10903
  const seen = /* @__PURE__ */ new Set();
10837
10904
  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);
10905
+ const path14 = match[1].trim();
10906
+ if (seen.has(path14) || !isAbsolute3(path14)) continue;
10907
+ if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path14))) continue;
10908
+ seen.add(path14);
10909
+ const file = readFile2(path14);
10843
10910
  if (!file?.trim()) continue;
10844
10911
  const start = match.index + match[0].length + 1;
10845
10912
  const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
10846
10913
  if (content === null) continue;
10847
- out.push({ text: `Instructions from: ${path13}
10914
+ out.push({ text: `Instructions from: ${path14}
10848
10915
  ${content}`, content });
10849
10916
  }
10850
10917
  return out;
@@ -10881,10 +10948,10 @@ ${options.compressionSummary.trim()}`
10881
10948
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
10882
10949
  const content = parts.join("\n\n");
10883
10950
  if (!content) return void 0;
10884
- const path13 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
10951
+ const path14 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
10885
10952
  try {
10886
- writeFileSync6(path13, content, { encoding: "utf8", mode: 384 });
10887
- return path13;
10953
+ writeFileSync6(path14, content, { encoding: "utf8", mode: 384 });
10954
+ return path14;
10888
10955
  } catch (err) {
10889
10956
  log.warn("failed to write system prompt file", { error: String(err) });
10890
10957
  return void 0;
@@ -11215,6 +11282,8 @@ _Reply with your answer to continue._
11215
11282
  }
11216
11283
 
11217
11284
  // src/control-request.ts
11285
+ var ELICITATION_DECLINE = { action: "decline" };
11286
+ var warnedElicitationServers = /* @__PURE__ */ new Set();
11218
11287
  function controlRequestBehaviorForTool(config, toolName) {
11219
11288
  const configured = config.controlRequestToolBehaviors;
11220
11289
  if (configured && toolName) {
@@ -11283,6 +11352,18 @@ function handleControlRequest(config, msg, proc) {
11283
11352
  }
11284
11353
  return true;
11285
11354
  }
11355
+ if (request.subtype === "elicitation") {
11356
+ const server2 = request.mcp_server_name || "unknown";
11357
+ writeControlResponse(proc, requestId, { ...ELICITATION_DECLINE });
11358
+ if (!warnedElicitationServers.has(server2)) {
11359
+ warnedElicitationServers.add(server2);
11360
+ log.warn(
11361
+ "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.",
11362
+ { server: server2, mode: request.mode ?? "form" }
11363
+ );
11364
+ }
11365
+ return true;
11366
+ }
11286
11367
  writeControlResponse(proc, requestId, {});
11287
11368
  log.debug("control request acknowledged", {
11288
11369
  requestId,
@@ -12001,14 +12082,18 @@ var FAST_MODE_REASONS = {
12001
12082
  pending: "the CLI is still checking availability"
12002
12083
  };
12003
12084
  var warnedFastModeReasons = /* @__PURE__ */ new Set();
12004
- function reportFastModeState(msg, requested) {
12085
+ function reportFastModeState(msg, requested, phase = "result") {
12005
12086
  const state = msg.fast_mode_state;
12006
12087
  if (!state) return;
12007
12088
  if (!requested) {
12008
- log.debug("fast mode state", { state });
12089
+ log.debug("fast mode state", { state, phase });
12009
12090
  return;
12010
12091
  }
12011
12092
  if (state === "on") {
12093
+ if (phase === "init") {
12094
+ log.debug("fast mode reported on at session start", { state, phase });
12095
+ return;
12096
+ }
12012
12097
  log.info("fast mode active", { state });
12013
12098
  return;
12014
12099
  }
@@ -12503,7 +12588,7 @@ function createLineHandler(state, ctx) {
12503
12588
  claudeSessionId: msg.session_id
12504
12589
  });
12505
12590
  }
12506
- reportFastModeState(msg, ctx.fastMode);
12591
+ reportFastModeState(msg, ctx.fastMode, "init");
12507
12592
  reportSystemInit(msg, {
12508
12593
  ignoreAnthropicApiKey: ctx.config.ignoreAnthropicApiKey
12509
12594
  });
@@ -13069,6 +13154,7 @@ ${plan}
13069
13154
  }
13070
13155
  if (msg.type === "result") {
13071
13156
  clearFallbackTimer(state);
13157
+ reportFastModeState(msg, ctx.fastMode, "result");
13072
13158
  if (msg.session_id) {
13073
13159
  setClaudeSessionId(state.sessionKey, msg.session_id);
13074
13160
  }
@@ -13691,6 +13777,11 @@ var ClaudeCodeLanguageModel = class {
13691
13777
  interactive: !!useInteractive,
13692
13778
  turnStats: this.config.turnStats === true,
13693
13779
  ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey === true,
13780
+ // Where the plugin applies the account itself rather than through a
13781
+ // wrapper (`accountInProcess`, Windows), the `/cost` spawn needs the
13782
+ // same config dir a turn gets, or it reports the default account's
13783
+ // plan usage instead of this provider's.
13784
+ configDir: this.config.accountInProcess ? this.config.configDir : void 0,
13694
13785
  // `/claude-code-doctor usage` opts into the CLI's own plan-usage
13695
13786
  // report; anything else here is ignored, as it always has been.
13696
13787
  argument: doctor.rest
@@ -13891,11 +13982,12 @@ var ClaudeCodeLanguageModel = class {
13891
13982
  resetsAt: failoverAnswer.resetsAt
13892
13983
  });
13893
13984
  }
13985
+ const spawnConfigDir = this.config.accountInProcess ? this.skillBridgeSpawn(failover).configDir : void 0;
13894
13986
  const processForAccount = getActiveProcess(sk);
13895
- if (!compactionMode && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
13987
+ if (!compactionMode && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
13896
13988
  log.notice("claude process belongs to another account; starting fresh", {
13897
13989
  sessionKey: sk,
13898
- was: processForAccount.cliPath,
13990
+ was: processForAccount?.cliPath,
13899
13991
  now: cliPath,
13900
13992
  failedOver: failover.failedOver
13901
13993
  });
@@ -13938,7 +14030,7 @@ var ClaudeCodeLanguageModel = class {
13938
14030
  });
13939
14031
  deleteClaudeSessionId(sk);
13940
14032
  }
13941
- } else if (!compactionMode && sessionBeforeAccountCheck && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
14033
+ } else if (!compactionMode && sessionBeforeAccountCheck && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
13942
14034
  deleteClaudeSessionId(sk);
13943
14035
  }
13944
14036
  const hasExistingSession = !!getClaudeSessionId(sk);
@@ -14051,7 +14143,9 @@ var ClaudeCodeLanguageModel = class {
14051
14143
  if (this.config.forkSessions === true && !compactionMode) {
14052
14144
  recordForkFingerprint(sk, options.prompt, cliPath);
14053
14145
  }
14054
- const { model: spawnModelId, fast: fastMode } = parseModelId(failover.modelId);
14146
+ const { model: spawnModelId, fast: fastMode } = parseModelId(
14147
+ this.config.accountInProcess ? stripAccountSuffix(failover.modelId) : failover.modelId
14148
+ );
14055
14149
  const exitPlanModeQuestionResult = compactionMode ? null : consumeExitPlanModeQuestionResult(sk, options.prompt);
14056
14150
  if (exitPlanModeQuestionResult) {
14057
14151
  log.info("sending plan approval decision to claude", { sk });
@@ -14681,7 +14775,8 @@ var ClaudeCodeLanguageModel = class {
14681
14775
  spawnSystemPromptFile,
14682
14776
  self.config.ignoreAnthropicApiKey,
14683
14777
  reasoningEffort,
14684
- promptCacheTtl
14778
+ promptCacheTtl,
14779
+ spawnConfigDir
14685
14780
  );
14686
14781
  ap.mcpServers = spawnMcpServers;
14687
14782
  state.proc = ap.proc;
@@ -15242,11 +15337,80 @@ var ClaudeCodeLanguageModel = class {
15242
15337
  }
15243
15338
  };
15244
15339
 
15340
+ // src/account-tier.ts
15341
+ import { readFileSync as readFileSync7 } from "fs";
15342
+ import path12 from "path";
15343
+ var ORGANIZATION_TYPE_LABELS = {
15344
+ claude_max: "Max",
15345
+ claude_pro: "Pro",
15346
+ claude_team: "Team",
15347
+ claude_enterprise: "Enterprise",
15348
+ claude_free: "Free"
15349
+ };
15350
+ var RATE_LIMIT_TIER_LABELS = {
15351
+ default_claude_max_5x: "5x",
15352
+ default_claude_max_20x: "20x"
15353
+ };
15354
+ var tierCache = /* @__PURE__ */ new Map();
15355
+ function accountTierConfigDir(account) {
15356
+ const configured = accountConfigDirPath(account);
15357
+ if (configured) return configured;
15358
+ const fromEnv = process.env.CLAUDE_CONFIG_DIR;
15359
+ return fromEnv && fromEnv.trim() ? expandHome(fromEnv.trim()) : expandHome("~/.claude");
15360
+ }
15361
+ function readAccountTier(account) {
15362
+ const normalized = normalizeAccountName(account) || DEFAULT_ACCOUNT;
15363
+ const configDir = accountTierConfigDir(normalized);
15364
+ const cached = tierCache.get(configDir);
15365
+ if (cached !== void 0) return cached ?? void 0;
15366
+ const label = readTierFromConfigDir(configDir, normalized === DEFAULT_ACCOUNT);
15367
+ tierCache.set(configDir, label ?? null);
15368
+ return label;
15369
+ }
15370
+ function readTierFromConfigDir(configDir, allowLegacyFallback) {
15371
+ const candidates = [path12.join(configDir, ".claude.json")];
15372
+ if (allowLegacyFallback) candidates.push(expandHome("~/.claude.json"));
15373
+ for (const candidate of candidates) {
15374
+ const oauth = readOauthAccount(candidate);
15375
+ if (!oauth) continue;
15376
+ const label = tierLabel(oauth);
15377
+ if (label) return label;
15378
+ }
15379
+ return void 0;
15380
+ }
15381
+ function readOauthAccount(file) {
15382
+ let raw;
15383
+ try {
15384
+ raw = readFileSync7(file, "utf8");
15385
+ } catch {
15386
+ return void 0;
15387
+ }
15388
+ try {
15389
+ const parsed = JSON.parse(raw);
15390
+ if (!parsed || typeof parsed !== "object") return void 0;
15391
+ const oauth = parsed.oauthAccount;
15392
+ return oauth && typeof oauth === "object" ? oauth : void 0;
15393
+ } catch {
15394
+ log.debug("could not parse a Claude config file for its plan tier", { file });
15395
+ return void 0;
15396
+ }
15397
+ }
15398
+ function tierLabel(oauthAccount) {
15399
+ const organizationType = oauthAccount.organizationType;
15400
+ if (typeof organizationType !== "string") return void 0;
15401
+ const base = ORGANIZATION_TYPE_LABELS[organizationType];
15402
+ if (!base) return void 0;
15403
+ if (base !== "Max") return base;
15404
+ const rateLimitTier = oauthAccount.organizationRateLimitTier;
15405
+ const multiple = typeof rateLimitTier === "string" ? RATE_LIMIT_TIER_LABELS[rateLimitTier] : void 0;
15406
+ return multiple ? `${base} ${multiple}` : base;
15407
+ }
15408
+
15245
15409
  // src/cleanup-stale.ts
15246
15410
  import {
15247
15411
  existsSync as existsSync5,
15248
15412
  mkdirSync as mkdirSync5,
15249
- readFileSync as readFileSync7,
15413
+ readFileSync as readFileSync8,
15250
15414
  realpathSync as realpathSync4,
15251
15415
  rmSync as rmSync3,
15252
15416
  writeFileSync as writeFileSync7
@@ -15273,7 +15437,7 @@ function userIntendsToUseUnscoped() {
15273
15437
  const cfg = userOpencodeJsonPath();
15274
15438
  if (!existsSync5(cfg)) return false;
15275
15439
  try {
15276
- const json = JSON.parse(readFileSync7(cfg, "utf8"));
15440
+ const json = JSON.parse(readFileSync8(cfg, "utf8"));
15277
15441
  const plugins = json.plugin;
15278
15442
  if (!Array.isArray(plugins)) return false;
15279
15443
  return plugins.some(
@@ -15294,7 +15458,7 @@ function ourLoadedDir() {
15294
15458
  function ourVersion(dir) {
15295
15459
  if (!dir) return null;
15296
15460
  try {
15297
- const pkg = JSON.parse(readFileSync7(join13(dir, "package.json"), "utf8"));
15461
+ const pkg = JSON.parse(readFileSync8(join13(dir, "package.json"), "utf8"));
15298
15462
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
15299
15463
  } catch {
15300
15464
  return null;
@@ -15306,7 +15470,7 @@ function markerPath() {
15306
15470
  }
15307
15471
  function markerVersion() {
15308
15472
  try {
15309
- const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
15473
+ const marker = JSON.parse(readFileSync8(markerPath(), "utf8"));
15310
15474
  return typeof marker.version === "string" ? marker.version : null;
15311
15475
  } catch {
15312
15476
  return null;
@@ -15363,7 +15527,7 @@ function cleanupOne(cacheRoot, ourDir) {
15363
15527
  if (!existsSync5(pkgJsonPath)) return;
15364
15528
  let pkg = {};
15365
15529
  try {
15366
- pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
15530
+ pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf8"));
15367
15531
  } catch {
15368
15532
  return;
15369
15533
  }
@@ -15382,7 +15546,7 @@ function cleanupOne(cacheRoot, ourDir) {
15382
15546
  const cachePkgJson = join13(cacheRoot, "package.json");
15383
15547
  if (!existsSync5(cachePkgJson)) return;
15384
15548
  try {
15385
- const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
15549
+ const cfg = JSON.parse(readFileSync8(cachePkgJson, "utf8"));
15386
15550
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
15387
15551
  delete cfg.dependencies[STALE_PACKAGE_NAME];
15388
15552
  writeFileSync7(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -15462,7 +15626,7 @@ function registerOpencodeLogSink(client) {
15462
15626
  }
15463
15627
 
15464
15628
  // src/v2-client.ts
15465
- import path12 from "path";
15629
+ import path13 from "path";
15466
15630
  function formatAgentTypeList(agents) {
15467
15631
  const lines = agents.flatMap((agent) => {
15468
15632
  if (agent.hidden === true || agent.mode === "primary") return [];
@@ -15491,7 +15655,7 @@ function toV1Session(info) {
15491
15655
  if (!info || typeof info !== "object") return void 0;
15492
15656
  const session = info;
15493
15657
  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;
15658
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path13.resolve(root, session.subpath) : root : void 0;
15495
15659
  return { ...info, directory, parentID: session.parentID };
15496
15660
  }
15497
15661
  function toV1Messages(messages) {
@@ -15683,7 +15847,7 @@ function planV2Providers(seedSettings, defaultProxyTools) {
15683
15847
  return {
15684
15848
  info: {
15685
15849
  id: providerID,
15686
- name: accountDisplayName(account),
15850
+ name: accountDisplayName(account, readAccountTier(account)),
15687
15851
  activation: "enabled",
15688
15852
  package: V2_PLUGIN_PACKAGE,
15689
15853
  settings: {
@@ -16221,7 +16385,7 @@ async function expandAccountProviders(config) {
16221
16385
  // stripped by cleanProviderOptions.
16222
16386
  failoverAccounts: accounts
16223
16387
  },
16224
- accountDisplayName(account)
16388
+ accountDisplayName(account, readAccountTier(account))
16225
16389
  ),
16226
16390
  models: configModelsForProvider(
16227
16391
  existing?.models ?? seed?.models ?? {},