@khalilgharbaoui/opencode-claude-code-plugin 0.49.0 → 0.51.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 join12 } from "path";
2
+ import { join as join13 } from "path";
3
3
 
4
4
  // src/ids.ts
5
5
  import { randomInt } from "crypto";
@@ -572,6 +572,139 @@ function titleizeAccount(account) {
572
572
  return normalizeAccountName(account).split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
573
573
  }
574
574
 
575
+ // src/account-groups.ts
576
+ var DEFAULT_ACCOUNT_GROUP = "default";
577
+ function normalizeGroupName(value) {
578
+ return value.trim().toLowerCase();
579
+ }
580
+ var warnedUnknownAccounts = /* @__PURE__ */ new Set();
581
+ var warnedShape = false;
582
+ function resolveAccountGroups(value, knownAccounts) {
583
+ if (value === void 0 || value === null) return null;
584
+ if (typeof value !== "object" || Array.isArray(value)) {
585
+ if (!warnedShape) {
586
+ warnedShape = true;
587
+ log.warn(
588
+ "accountGroups must be an object mapping an account name to a group name; ignoring it",
589
+ { type: Array.isArray(value) ? "array" : typeof value }
590
+ );
591
+ }
592
+ return null;
593
+ }
594
+ const known = knownAccounts ? new Set(knownAccounts.map((account) => normalizeAccountName(String(account)))) : void 0;
595
+ const out = {};
596
+ for (const [rawAccount, rawGroup] of Object.entries(value)) {
597
+ const account = normalizeAccountName(rawAccount);
598
+ if (!account) continue;
599
+ if (typeof rawGroup !== "string") {
600
+ if (!warnedShape) {
601
+ warnedShape = true;
602
+ log.warn(
603
+ "accountGroups must be an object mapping an account name to a group name; ignoring it",
604
+ { account, type: typeof rawGroup }
605
+ );
606
+ }
607
+ continue;
608
+ }
609
+ const group = normalizeGroupName(rawGroup);
610
+ if (!group) continue;
611
+ if (known && !known.has(account)) {
612
+ if (!warnedUnknownAccounts.has(account)) {
613
+ warnedUnknownAccounts.add(account);
614
+ log.warn("accountGroups names an account that is not configured; ignoring it", {
615
+ account,
616
+ group
617
+ });
618
+ }
619
+ continue;
620
+ }
621
+ out[account] = group;
622
+ }
623
+ return Object.keys(out).length > 0 ? out : null;
624
+ }
625
+ function accountGroup(account, groups) {
626
+ const name = normalizeAccountName(account || DEFAULT_ACCOUNT);
627
+ return groups?.[name] ?? DEFAULT_ACCOUNT_GROUP;
628
+ }
629
+ function accountsShareGroup(a, b, groups) {
630
+ if (!groups) return true;
631
+ return accountGroup(a, groups) === accountGroup(b, groups);
632
+ }
633
+ function accountsInGroupOf(account, accounts, groups) {
634
+ const out = [];
635
+ for (const raw of accounts ?? []) {
636
+ const name = normalizeAccountName(String(raw));
637
+ if (!name || out.includes(name)) continue;
638
+ if (!accountsShareGroup(account, name, groups)) continue;
639
+ out.push(name);
640
+ }
641
+ return out;
642
+ }
643
+ function describeAccountGroups(groups) {
644
+ if (!groups) return [];
645
+ return Object.entries(groups).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([account, group]) => `${account}=${group}`);
646
+ }
647
+ function accountProviderMap(accounts) {
648
+ const out = /* @__PURE__ */ new Map();
649
+ for (const raw of accounts ?? []) {
650
+ const account = normalizeAccountName(String(raw));
651
+ if (!account) continue;
652
+ out.set(`${BASE_PROVIDER_ID}-${account}`, account);
653
+ }
654
+ return out;
655
+ }
656
+ var CONTEXT_PREFIX = "context=";
657
+ function sessionKeyContextBlob(sessionKey2) {
658
+ const parts = sessionKey2.split("::");
659
+ if (parts.length < 5) return void 0;
660
+ if (parts[2] === "compaction") return void 0;
661
+ const withoutEffort = parts[parts.length - 1].startsWith("effort=") ? parts.slice(0, -1) : parts;
662
+ if (withoutEffort.length < 5) return void 0;
663
+ const tail = withoutEffort.slice(4).join("::");
664
+ return tail.startsWith(CONTEXT_PREFIX) ? tail.slice(CONTEXT_PREFIX.length) : void 0;
665
+ }
666
+ function sessionKeyProvider(sessionKey2) {
667
+ const blob = sessionKeyContextBlob(sessionKey2);
668
+ if (blob === void 0) return void 0;
669
+ try {
670
+ const parsed = JSON.parse(blob);
671
+ const first = Array.isArray(parsed) ? parsed[0] : void 0;
672
+ return typeof first === "string" ? first : void 0;
673
+ } catch {
674
+ return void 0;
675
+ }
676
+ }
677
+ function sessionKeyAccount(sessionKey2, providers) {
678
+ const provider = sessionKeyProvider(sessionKey2);
679
+ return provider === void 0 ? void 0 : providers.get(provider);
680
+ }
681
+ function blankAccountProvider(sessionKey2, providers) {
682
+ if (providers.size === 0) return sessionKey2;
683
+ const blob = sessionKeyContextBlob(sessionKey2);
684
+ if (blob === void 0) return sessionKey2;
685
+ let parsed;
686
+ try {
687
+ parsed = JSON.parse(blob);
688
+ } catch {
689
+ return sessionKey2;
690
+ }
691
+ if (!Array.isArray(parsed)) return sessionKey2;
692
+ const provider = parsed[0];
693
+ if (typeof provider !== "string" || !providers.has(provider)) return sessionKey2;
694
+ const blanked = [...parsed];
695
+ blanked[0] = "*";
696
+ const prefix = sessionKey2.slice(0, sessionKey2.length - blob.length);
697
+ return `${prefix}${JSON.stringify(blanked)}`;
698
+ }
699
+ var ACCOUNT_GROUP_MARKER = "\u258C **account group:**";
700
+ function formatAccountGroupNote(input) {
701
+ const source = normalizeAccountName(input.sourceAccount || DEFAULT_ACCOUNT);
702
+ const target = normalizeAccountName(input.targetAccount || DEFAULT_ACCOUNT);
703
+ return `
704
+ ${ACCOUNT_GROUP_MARKER} this conversation was running on the Claude account "${source}" (group "${input.sourceGroup}") and is now on "${target}" (group "${input.targetGroup}"), so none of it was carried over: accountGroups keeps a conversation inside one group. This account is starting fresh and sees only your latest message. The conversation is still on "${source}"; switch back to it to continue where you stopped.
705
+ `;
706
+ }
707
+
575
708
  // src/types.ts
576
709
  var READ_ONLY_PERMISSION_MODE = "read-only";
577
710
  var DEFAULT_PROXY_TOOL_NAMES = [
@@ -1489,12 +1622,13 @@ function isAccountFailoverQuestionActive(input) {
1489
1622
  if (input.candidates.length === 0) return false;
1490
1623
  return input.opencodeHasQuestion;
1491
1624
  }
1492
- function failoverCandidates(accounts, source) {
1625
+ function failoverCandidates(accounts, source, groups) {
1493
1626
  const from = normalizeAccountName(source || DEFAULT_ACCOUNT);
1494
1627
  const out = [];
1495
1628
  for (const raw of accounts ?? []) {
1496
1629
  const name = normalizeAccountName(String(raw));
1497
1630
  if (!name || name === from || out.includes(name)) continue;
1631
+ if (!accountsShareGroup(from, name, groups)) continue;
1498
1632
  out.push(name);
1499
1633
  }
1500
1634
  return out;
@@ -3514,33 +3648,97 @@ function clearCompression(sessionKey2) {
3514
3648
  }
3515
3649
 
3516
3650
  // src/mcp-bridge.ts
3517
- import * as fs4 from "fs";
3518
- import * as path5 from "path";
3519
- import * as os2 from "os";
3651
+ import * as fs5 from "fs";
3652
+ import * as path6 from "path";
3653
+ import * as os3 from "os";
3520
3654
  import * as crypto2 from "crypto";
3521
3655
  import {
3522
3656
  parse as parseJsonc,
3523
3657
  printParseErrorCode
3524
3658
  } from "jsonc-parser";
3659
+
3660
+ // src/mcp-auth.ts
3661
+ import * as fs4 from "fs";
3662
+ import * as os2 from "os";
3663
+ import * as path5 from "path";
3664
+ var EXPIRY_SKEW_SECONDS = 30;
3665
+ function opencodeStateDir() {
3666
+ const dataHome = process.env.XDG_DATA_HOME || path5.join(os2.homedir(), ".local", "share");
3667
+ return path5.join(dataHome, "opencode");
3668
+ }
3669
+ function readMcpAuthStore(stateDir) {
3670
+ const file = path5.join(stateDir, "mcp-auth.json");
3671
+ try {
3672
+ const stat = fs4.lstatSync(file);
3673
+ if (!stat.isFile()) {
3674
+ log.debug("ignoring opencode MCP auth store that is not a regular file", { path: file });
3675
+ return {};
3676
+ }
3677
+ if (typeof stat.uid === "number" && typeof process.getuid === "function" && stat.uid !== process.getuid()) {
3678
+ log.debug("ignoring opencode MCP auth store owned by another user", { path: file });
3679
+ return {};
3680
+ }
3681
+ const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
3682
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3683
+ log.debug("opencode MCP auth store has an unexpected shape", { path: file });
3684
+ return {};
3685
+ }
3686
+ return parsed;
3687
+ } catch {
3688
+ return {};
3689
+ }
3690
+ }
3691
+ function selectBearerToken(store, serverName, serverUrl, now = Date.now()) {
3692
+ const entry = Object.prototype.hasOwnProperty.call(store, serverName) ? store[serverName] : void 0;
3693
+ if (!entry || typeof entry !== "object") return { token: null, refusal: "no-entry" };
3694
+ if (!entry.serverUrl || entry.serverUrl !== serverUrl) {
3695
+ return { token: null, refusal: "url-mismatch" };
3696
+ }
3697
+ const accessToken = entry.tokens?.accessToken;
3698
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
3699
+ return { token: null, refusal: "no-token" };
3700
+ }
3701
+ const expiresAt = entry.tokens?.expiresAt;
3702
+ if (typeof expiresAt === "number" && expiresAt - now / 1e3 < EXPIRY_SKEW_SECONDS) {
3703
+ return { token: null, refusal: "expired" };
3704
+ }
3705
+ return { token: accessToken, expiresAt: typeof expiresAt === "number" ? expiresAt : void 0 };
3706
+ }
3707
+ function buildFreshnessKey(expiresAt) {
3708
+ return expiresAt === void 0 ? "no-expiry" : String(expiresAt);
3709
+ }
3710
+ function hasAuthorizationHeader(headers) {
3711
+ return Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
3712
+ }
3713
+ function injectBearerHeaders(server2, accessToken) {
3714
+ const existing = server2.headers && typeof server2.headers === "object" && !Array.isArray(server2.headers) ? server2.headers : {};
3715
+ if (hasAuthorizationHeader(existing)) return { ...server2 };
3716
+ return {
3717
+ ...server2,
3718
+ headers: { ...existing, Authorization: `Bearer ${accessToken}` }
3719
+ };
3720
+ }
3721
+
3722
+ // src/mcp-bridge.ts
3525
3723
  var FILE_NAMES = ["opencode.jsonc", "opencode.json", "config.json"];
3526
3724
  var PROJECT_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
3527
3725
  function fileExists(p) {
3528
3726
  try {
3529
- return fs4.statSync(p).isFile();
3727
+ return fs5.statSync(p).isFile();
3530
3728
  } catch {
3531
3729
  return false;
3532
3730
  }
3533
3731
  }
3534
3732
  function dirExists(p) {
3535
3733
  try {
3536
- return fs4.statSync(p).isDirectory();
3734
+ return fs5.statSync(p).isDirectory();
3537
3735
  } catch {
3538
3736
  return false;
3539
3737
  }
3540
3738
  }
3541
3739
  function readAndParse(file) {
3542
3740
  try {
3543
- const raw = fs4.readFileSync(file, "utf8");
3741
+ const raw = fs5.readFileSync(file, "utf8");
3544
3742
  const errors = [];
3545
3743
  const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
3546
3744
  if (errors.length > 0) {
@@ -3576,14 +3774,14 @@ function deepMerge(target, source) {
3576
3774
  }
3577
3775
  function walkUp(opts) {
3578
3776
  const out = [];
3579
- let current = path5.resolve(opts.start);
3777
+ let current = path6.resolve(opts.start);
3580
3778
  while (true) {
3581
3779
  for (const target of opts.targets) {
3582
- const candidate = path5.join(current, target);
3780
+ const candidate = path6.join(current, target);
3583
3781
  if (opts.predicate(candidate)) out.push(candidate);
3584
3782
  }
3585
- if (opts.stop && current === path5.resolve(opts.stop)) break;
3586
- const parent = path5.dirname(current);
3783
+ if (opts.stop && current === path6.resolve(opts.stop)) break;
3784
+ const parent = path6.dirname(current);
3587
3785
  if (parent === current) break;
3588
3786
  current = parent;
3589
3787
  }
@@ -3591,28 +3789,28 @@ function walkUp(opts) {
3591
3789
  }
3592
3790
  function detectWorktree(cwd) {
3593
3791
  const override = process.env.OPENCODE_WORKTREE;
3594
- if (override) return path5.resolve(override);
3595
- let current = path5.resolve(cwd);
3792
+ if (override) return path6.resolve(override);
3793
+ let current = path6.resolve(cwd);
3596
3794
  while (true) {
3597
- const gitPath = path5.join(current, ".git");
3795
+ const gitPath = path6.join(current, ".git");
3598
3796
  try {
3599
- if (fs4.existsSync(gitPath)) return current;
3797
+ if (fs5.existsSync(gitPath)) return current;
3600
3798
  } catch {
3601
3799
  }
3602
- const parent = path5.dirname(current);
3800
+ const parent = path6.dirname(current);
3603
3801
  if (parent === current) return void 0;
3604
3802
  current = parent;
3605
3803
  }
3606
3804
  }
3607
3805
  function globalConfigDir() {
3608
- const xdg = process.env.XDG_CONFIG_HOME ?? path5.join(os2.homedir(), ".config");
3609
- return path5.join(xdg, "opencode");
3806
+ const xdg = process.env.XDG_CONFIG_HOME ?? path6.join(os3.homedir(), ".config");
3807
+ return path6.join(xdg, "opencode");
3610
3808
  }
3611
3809
  function loadGlobalConfig(hostApi = "v1") {
3612
3810
  const dir = globalConfigDir();
3613
3811
  let merged = {};
3614
3812
  for (const name of FILE_NAMES.slice().reverse()) {
3615
- const file = path5.join(dir, name);
3813
+ const file = path6.join(dir, name);
3616
3814
  if (!fileExists(file)) continue;
3617
3815
  const parsed = readAndParse(file);
3618
3816
  if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
@@ -3622,7 +3820,7 @@ function loadGlobalConfig(hostApi = "v1") {
3622
3820
  function loadProjectFilesInDir(dir, hostApi = "v1") {
3623
3821
  let merged = {};
3624
3822
  for (const name of PROJECT_FILE_NAMES) {
3625
- const file = path5.join(dir, name);
3823
+ const file = path6.join(dir, name);
3626
3824
  if (!fileExists(file)) continue;
3627
3825
  const parsed = readAndParse(file);
3628
3826
  if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
@@ -3633,7 +3831,7 @@ function dotOpencodeDirs(cwd, worktree) {
3633
3831
  const dirs = [];
3634
3832
  const seen = /* @__PURE__ */ new Set();
3635
3833
  const push = (p) => {
3636
- const abs = path5.resolve(p);
3834
+ const abs = path6.resolve(p);
3637
3835
  if (!seen.has(abs) && dirExists(abs)) {
3638
3836
  seen.add(abs);
3639
3837
  dirs.push(abs);
@@ -3647,9 +3845,9 @@ function dotOpencodeDirs(cwd, worktree) {
3647
3845
  })) {
3648
3846
  push(dir);
3649
3847
  }
3650
- const home = os2.homedir();
3848
+ const home = os3.homedir();
3651
3849
  if (home) {
3652
- const homeDot = path5.join(home, ".opencode");
3850
+ const homeDot = path6.join(home, ".opencode");
3653
3851
  if (dirExists(homeDot)) push(homeDot);
3654
3852
  }
3655
3853
  const envDir = process.env.OPENCODE_CONFIG_DIR;
@@ -3667,7 +3865,33 @@ function substituteEnvPlaceholders(source) {
3667
3865
  }
3668
3866
  return out;
3669
3867
  }
3670
- function translateServer(name, spec) {
3868
+ var warnedExpiredBearer = /* @__PURE__ */ new Set();
3869
+ function withBearerToken(name, spec, url, auth) {
3870
+ if (!auth) return spec;
3871
+ const lookup = selectBearerToken(auth.store, name, url);
3872
+ if (lookup.token === null) {
3873
+ if (lookup.refusal === "expired" && !warnedExpiredBearer.has(name)) {
3874
+ warnedExpiredBearer.add(name);
3875
+ log.warn(
3876
+ "opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
3877
+ { server: name }
3878
+ );
3879
+ } else {
3880
+ log.debug("no opencode OAuth token for bridged MCP server", {
3881
+ server: name,
3882
+ reason: lookup.refusal
3883
+ });
3884
+ }
3885
+ return spec;
3886
+ }
3887
+ log.info("bridged MCP server carries opencode's stored OAuth token", { server: name });
3888
+ return injectBearerHeaders(spec, lookup.token);
3889
+ }
3890
+ function bearerAuthContext(options) {
3891
+ if (!options?.oauthTokens) return void 0;
3892
+ return { store: readMcpAuthStore(options.stateDir ?? opencodeStateDir()) };
3893
+ }
3894
+ function translateServer(name, spec, auth) {
3671
3895
  if (spec.enabled === false) return null;
3672
3896
  const type = spec.type;
3673
3897
  if (type === "local") {
@@ -3693,7 +3917,7 @@ function translateServer(name, spec) {
3693
3917
  log.warn("skipping remote MCP server with no url", { name });
3694
3918
  return null;
3695
3919
  }
3696
- const out = {
3920
+ let out = {
3697
3921
  type: "http",
3698
3922
  url: spec.url
3699
3923
  };
@@ -3702,7 +3926,7 @@ function translateServer(name, spec) {
3702
3926
  spec.headers
3703
3927
  );
3704
3928
  }
3705
- return out;
3929
+ return withBearerToken(name, out, spec.url, auth);
3706
3930
  }
3707
3931
  log.warn("skipping MCP server with unknown type", {
3708
3932
  name,
@@ -3753,18 +3977,19 @@ function mergeMcp(target, source) {
3753
3977
  return out;
3754
3978
  }
3755
3979
  var MCP_PENDING_STATUS = "pending";
3756
- function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, hostApi = "v1") {
3980
+ function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, hostApi = "v1", auth) {
3757
3981
  const {
3758
3982
  servers: merged,
3759
3983
  enabledServerNames: allEnabledServerNames,
3760
3984
  hash
3761
- } = mergeOpencodeMcp(cwd, runtimeStatus, hostApi);
3985
+ } = mergeOpencodeMcp(cwd, runtimeStatus, hostApi, auth);
3986
+ const authContext = bearerAuthContext(auth);
3762
3987
  const servers = {};
3763
3988
  const bridgedServerNames = [];
3764
3989
  for (const [name, spec] of Object.entries(merged)) {
3765
3990
  if (!spec || typeof spec !== "object") continue;
3766
3991
  if (excludeServers?.has(name)) continue;
3767
- const translated = translateServer(name, spec);
3992
+ const translated = translateServer(name, spec, authContext);
3768
3993
  if (translated) {
3769
3994
  servers[name] = translated;
3770
3995
  bridgedServerNames.push(name);
@@ -3796,7 +4021,7 @@ function opencodeConfigLayers(cwd, hostApi = "v1") {
3796
4021
  const projectDirs = [];
3797
4022
  const seenProjectDirs = /* @__PURE__ */ new Set();
3798
4023
  for (const f of projectFiles) {
3799
- const d = path5.dirname(f);
4024
+ const d = path6.dirname(f);
3800
4025
  if (!seenProjectDirs.has(d)) {
3801
4026
  seenProjectDirs.add(d);
3802
4027
  projectDirs.push(d);
@@ -3822,7 +4047,7 @@ function loadMergedOpencodeConfig(cwd, hostApi = "v1") {
3822
4047
  for (const layer of opencodeConfigLayers(cwd, hostApi)) merged = mergeConfigLayer(merged, layer, hostApi);
3823
4048
  return merged;
3824
4049
  }
3825
- function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1") {
4050
+ function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1", auth) {
3826
4051
  let merged = {};
3827
4052
  for (const layer of opencodeConfigLayers(cwd, hostApi)) {
3828
4053
  const servers = extractMcpBlock(layer);
@@ -3846,7 +4071,19 @@ function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1") {
3846
4071
  enabledServerNames.push(name);
3847
4072
  }
3848
4073
  const mergedBody = JSON.stringify({ mcpServers: merged }, null, 2);
3849
- const hash = crypto2.createHash("sha256").update(mergedBody).digest("hex").slice(0, 12);
4074
+ const hasher = crypto2.createHash("sha256").update(mergedBody);
4075
+ const authContext = bearerAuthContext(auth);
4076
+ if (authContext) {
4077
+ for (const [name, spec] of Object.entries(merged)) {
4078
+ if (!spec || typeof spec !== "object") continue;
4079
+ const server2 = spec;
4080
+ if (server2.type !== "remote" || typeof server2.url !== "string") continue;
4081
+ const lookup = selectBearerToken(authContext.store, name, server2.url);
4082
+ if (lookup.token === null) continue;
4083
+ hasher.update(`\0bearer:${name}:${buildFreshnessKey(lookup.expiresAt)}`);
4084
+ }
4085
+ }
4086
+ const hash = hasher.digest("hex").slice(0, 12);
3850
4087
  return { servers: merged, enabledServerNames, hash };
3851
4088
  }
3852
4089
  function finishBridge(input) {
@@ -3863,13 +4100,13 @@ function finishBridge(input) {
3863
4100
  }
3864
4101
  const body = JSON.stringify({ mcpServers: servers }, null, 2);
3865
4102
  const bodyDigest = crypto2.createHash("sha256").update(body).digest("hex").slice(0, 12);
3866
- const outPath = path5.join(
4103
+ const outPath = path6.join(
3867
4104
  pluginTmpDir(),
3868
4105
  `mcp-${bodyDigest}.json`
3869
4106
  );
3870
4107
  try {
3871
4108
  if (!fileExists(outPath)) {
3872
- fs4.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
4109
+ fs5.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
3873
4110
  }
3874
4111
  } catch (e) {
3875
4112
  log.warn("failed to write bridged MCP config", {
@@ -4441,7 +4678,7 @@ function forkSiblingSignature(sessionKey2) {
4441
4678
  blanked[3] = "*";
4442
4679
  return blanked.join("::");
4443
4680
  }
4444
- function modelSiblingSignature(sessionKey2) {
4681
+ function modelSiblingSignature(sessionKey2, accountProviders) {
4445
4682
  const parts = sessionKey2.split("::");
4446
4683
  if (parts.length < 5) return null;
4447
4684
  if (parts[2] === "compaction") return null;
@@ -4449,7 +4686,8 @@ function modelSiblingSignature(sessionKey2) {
4449
4686
  if (withoutEffort.length < 5) return null;
4450
4687
  const blanked = [...withoutEffort];
4451
4688
  blanked[1] = "*";
4452
- return blanked.join("::");
4689
+ const signature = blanked.join("::");
4690
+ return accountProviders && accountProviders.size > 0 ? blankAccountProvider(signature, accountProviders) : signature;
4453
4691
  }
4454
4692
  function moveForkFingerprint(fromKey, toKey) {
4455
4693
  const record = fingerprints.get(fromKey);
@@ -4536,17 +4774,17 @@ function tailIsReplyOnly(history, from) {
4536
4774
  }
4537
4775
 
4538
4776
  // src/session-resume-store.ts
4539
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
4540
- import { homedir as homedir4 } from "os";
4541
- import { join as join5 } from "path";
4777
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
4778
+ import { homedir as homedir5 } from "os";
4779
+ import { join as join6 } from "path";
4542
4780
  var MAX_RESUME_RECORDS = 256;
4543
4781
  var RESUME_RECORD_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
4544
4782
  var records = null;
4545
4783
  var pathOverride = null;
4546
4784
  function resumeStorePath() {
4547
4785
  if (pathOverride) return pathOverride;
4548
- const stateRoot = process.env.XDG_STATE_HOME || join5(homedir4(), ".local", "state");
4549
- return join5(stateRoot, "opencode-claude-code-plugin", "claude-sessions.json");
4786
+ const stateRoot = process.env.XDG_STATE_HOME || join6(homedir5(), ".local", "state");
4787
+ return join6(stateRoot, "opencode-claude-code-plugin", "claude-sessions.json");
4550
4788
  }
4551
4789
  function isDigest(value) {
4552
4790
  const entry = value;
@@ -4554,13 +4792,13 @@ function isDigest(value) {
4554
4792
  }
4555
4793
  function isRecord2(value) {
4556
4794
  const record = value;
4557
- return !!record && typeof record.claudeSessionId === "string" && typeof record.cliPath === "string" && typeof record.updatedAt === "number" && Array.isArray(record.chain) && record.chain.every(isDigest) && (record.shape === void 0 || Array.isArray(record.shape) && record.shape.every(isDigest));
4795
+ return !!record && typeof record.claudeSessionId === "string" && typeof record.cliPath === "string" && typeof record.updatedAt === "number" && Array.isArray(record.chain) && record.chain.every(isDigest) && (record.shape === void 0 || Array.isArray(record.shape) && record.shape.every(isDigest)) && (record.configDir === void 0 || typeof record.configDir === "string") && (record.account === void 0 || typeof record.account === "string");
4558
4796
  }
4559
4797
  function load() {
4560
4798
  if (records) return records;
4561
4799
  records = /* @__PURE__ */ new Map();
4562
4800
  try {
4563
- const parsed = JSON.parse(readFileSync2(resumeStorePath(), "utf8"));
4801
+ const parsed = JSON.parse(readFileSync3(resumeStorePath(), "utf8"));
4564
4802
  const entries = parsed && typeof parsed === "object" ? parsed.sessions : null;
4565
4803
  if (entries && typeof entries === "object") {
4566
4804
  const cutoff = Date.now() - RESUME_RECORD_MAX_AGE_MS;
@@ -4575,7 +4813,7 @@ function save(update) {
4575
4813
  const file = resumeStorePath();
4576
4814
  const merged = /* @__PURE__ */ new Map();
4577
4815
  try {
4578
- const onDisk = JSON.parse(readFileSync2(file, "utf8"))?.sessions;
4816
+ const onDisk = JSON.parse(readFileSync3(file, "utf8"))?.sessions;
4579
4817
  if (onDisk && typeof onDisk === "object") {
4580
4818
  for (const [key, record] of Object.entries(onDisk)) {
4581
4819
  if (isRecord2(record)) merged.set(key, record);
@@ -4588,7 +4826,7 @@ function save(update) {
4588
4826
  const kept = [...merged.entries()].filter(([, record]) => record.updatedAt >= cutoff).sort((a, b) => a[1].updatedAt - b[1].updatedAt).slice(-MAX_RESUME_RECORDS);
4589
4827
  records = new Map(kept);
4590
4828
  try {
4591
- mkdirSync3(join5(file, ".."), { recursive: true, mode: 448 });
4829
+ mkdirSync3(join6(file, ".."), { recursive: true, mode: 448 });
4592
4830
  const tmp = `${file}.${process.pid}.tmp`;
4593
4831
  writeFileSync3(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(kept) }), {
4594
4832
  mode: 384
@@ -4600,17 +4838,27 @@ function save(update) {
4600
4838
  });
4601
4839
  }
4602
4840
  }
4603
- function recordResumePoint(sessionKey2, claudeSessionId, prompt, cliPath) {
4841
+ function recordResumePoint(sessionKey2, claudeSessionId, prompt, cliPath, where) {
4604
4842
  const chain = conversationDigests(prompt);
4605
4843
  if (chain.length === 0) return;
4606
4844
  const shape2 = conversationDigests(prompt, { assistantContent: false });
4845
+ const configDir = where?.configDir;
4846
+ const account = where?.account;
4607
4847
  const current = load().get(sessionKey2);
4608
- if (current && current.claudeSessionId === claudeSessionId && current.cliPath === cliPath && current.shape !== void 0 && current.chain.length === chain.length && current.chain.every((entry, i) => entry.digest === chain[i].digest)) {
4848
+ if (current && current.claudeSessionId === claudeSessionId && current.cliPath === cliPath && current.shape !== void 0 && current.configDir === configDir && current.account === account && current.chain.length === chain.length && current.chain.every((entry, i) => entry.digest === chain[i].digest)) {
4609
4849
  return;
4610
4850
  }
4611
4851
  save((merged) => {
4612
4852
  merged.delete(sessionKey2);
4613
- merged.set(sessionKey2, { claudeSessionId, cliPath, chain, shape: shape2, updatedAt: Date.now() });
4853
+ merged.set(sessionKey2, {
4854
+ claudeSessionId,
4855
+ cliPath,
4856
+ chain,
4857
+ shape: shape2,
4858
+ ...configDir === void 0 ? {} : { configDir },
4859
+ ...account === void 0 ? {} : { account },
4860
+ updatedAt: Date.now()
4861
+ });
4614
4862
  });
4615
4863
  }
4616
4864
  function forgetResumePoint(sessionKey2) {
@@ -4652,18 +4900,31 @@ function findResumePoint(opts) {
4652
4900
  return { claudeSessionId: record.claudeSessionId, matched: record.chain.length };
4653
4901
  }
4654
4902
  function findSiblingResumePoint(opts) {
4655
- const signature = modelSiblingSignature(opts.sessionKey);
4903
+ const providers = opts.accountProviders;
4904
+ const signature = modelSiblingSignature(opts.sessionKey, providers);
4656
4905
  if (!signature) {
4657
4906
  opts.onRefused?.("no-sibling-possible");
4658
4907
  return void 0;
4659
4908
  }
4909
+ const ownAccount = providers ? sessionKeyAccount(opts.sessionKey, providers) : void 0;
4660
4910
  let best;
4661
4911
  let bestUpdatedAt = -1;
4662
4912
  let refusal;
4663
4913
  for (const [key, record] of load()) {
4664
4914
  if (key === opts.sessionKey) continue;
4665
- if (modelSiblingSignature(key) !== signature) continue;
4666
- if (record.cliPath !== opts.cliPath) {
4915
+ if (modelSiblingSignature(key, providers) !== signature) continue;
4916
+ const siblingAccount = providers ? sessionKeyAccount(key, providers) : void 0;
4917
+ const crossAccount = ownAccount !== void 0 && siblingAccount !== void 0 && siblingAccount !== ownAccount;
4918
+ if (crossAccount) {
4919
+ if (opts.allowAccount && !opts.allowAccount(siblingAccount)) {
4920
+ refusal = "sibling-another-account-group";
4921
+ continue;
4922
+ }
4923
+ if (!record.configDir) {
4924
+ refusal = "sibling-account-dir-unknown";
4925
+ continue;
4926
+ }
4927
+ } else if (record.cliPath !== opts.cliPath) {
4667
4928
  refusal = "sibling-another-claude-binary";
4668
4929
  continue;
4669
4930
  }
@@ -4677,7 +4938,7 @@ function findSiblingResumePoint(opts) {
4677
4938
  refusal = "sibling-still-writing";
4678
4939
  continue;
4679
4940
  }
4680
- if (!existsSync2(opts.transcriptPath(record.claudeSessionId))) {
4941
+ if (!existsSync2(opts.transcriptPath(record.claudeSessionId, record.configDir))) {
4681
4942
  refusal = "sibling-transcript-gone";
4682
4943
  continue;
4683
4944
  }
@@ -4686,7 +4947,8 @@ function findSiblingResumePoint(opts) {
4686
4947
  best = {
4687
4948
  claudeSessionId: record.claudeSessionId,
4688
4949
  siblingKey: key,
4689
- matched: record.chain.length
4950
+ matched: record.chain.length,
4951
+ ...crossAccount ? { siblingAccount, siblingConfigDir: record.configDir } : {}
4690
4952
  };
4691
4953
  }
4692
4954
  }
@@ -4697,6 +4959,35 @@ function findSiblingResumePoint(opts) {
4697
4959
  }
4698
4960
  return best;
4699
4961
  }
4962
+ function findForeignAccountSibling(opts) {
4963
+ const providers = opts.accountProviders;
4964
+ if (providers.size === 0) return void 0;
4965
+ const signature = modelSiblingSignature(opts.sessionKey, providers);
4966
+ if (!signature) return void 0;
4967
+ const ownAccount = sessionKeyAccount(opts.sessionKey, providers);
4968
+ if (ownAccount === void 0) return void 0;
4969
+ const consider = (key) => {
4970
+ if (key === opts.sessionKey) return void 0;
4971
+ if (modelSiblingSignature(key, providers) !== signature) return void 0;
4972
+ const account = sessionKeyAccount(key, providers);
4973
+ if (account === void 0 || account === ownAccount) return void 0;
4974
+ return { siblingKey: key, account };
4975
+ };
4976
+ for (const key of opts.extraKeys ?? []) {
4977
+ const hit = consider(key);
4978
+ if (hit) return hit;
4979
+ }
4980
+ let found;
4981
+ let foundUpdatedAt = -1;
4982
+ for (const [key, record] of load()) {
4983
+ const hit = consider(key);
4984
+ if (hit && record.updatedAt >= foundUpdatedAt) {
4985
+ found = hit;
4986
+ foundUpdatedAt = record.updatedAt;
4987
+ }
4988
+ }
4989
+ return found;
4990
+ }
4700
4991
 
4701
4992
  // src/permission-presets.ts
4702
4993
  var READ_ONLY_DISALLOWED_CLI_TOOLS = [
@@ -5369,6 +5660,9 @@ async function deleteActiveProcessAndWait(key, options = {}) {
5369
5660
  function getClaudeSessionId(key) {
5370
5661
  return claudeSessions.get(key);
5371
5662
  }
5663
+ function listClaudeSessionKeys() {
5664
+ return claudeSessions.keys();
5665
+ }
5372
5666
  var MAX_CLAUDE_SESSION_ENTRIES = 64;
5373
5667
  function claudeSessionIsBusy(key) {
5374
5668
  if (activeProcesses.has(key)) return true;
@@ -6037,7 +6331,7 @@ async function handleBtwCommand(client, input, options = {}) {
6037
6331
  // src/diagnostic-bundle.ts
6038
6332
  import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
6039
6333
  import { openSync, readSync, closeSync, fstatSync, realpathSync } from "fs";
6040
- import { homedir as homedir5 } from "os";
6334
+ import { homedir as homedir6 } from "os";
6041
6335
 
6042
6336
  // src/log-messages.ts
6043
6337
  var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
@@ -6048,6 +6342,8 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6048
6342
  "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.",
6049
6343
  "MCP tool not routed through the proxy: another proxy tool already holds that name, and it keeps it",
6050
6344
  "account failover declined; ending the turn",
6345
+ "accountGroups must be an object mapping an account name to a group name; ignoring it",
6346
+ "accountGroups names an account that is not configured; ignoring it",
6051
6347
  "agent effort override refused: unknown level",
6052
6348
  "agent model override refused: unknown model",
6053
6349
  "agent prompt cache ttl refused: unknown value",
@@ -6133,6 +6429,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6133
6429
  "no proxyTools entry was recognised; nothing will be proxied this turn",
6134
6430
  "no stdout after envelope write; respawning claude process to resume conversation",
6135
6431
  "opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction",
6432
+ "opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
6136
6433
  "pending proxy call arrived after stream close; rejecting",
6137
6434
  'permissionMode "plan" is enforced: claude cannot edit files or run commands, and --dangerously-skip-permissions is deliberately not passed so it stays that way. Headless Claude Code is not offered an ExitPlanMode tool, so nothing releases plan mode mid-session; approving a plan in chat does not unlock writes. Leaving plan mode means changing the config and restarting opencode.',
6138
6435
  `permissionPreset "read-only": this claude CLI has no --permission-prompts (2.1.263+), so permission requests are denied by the plugin's own can_use_tool handler instead of by the CLI. Same outcome, one layer fewer.`,
@@ -6157,6 +6454,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6157
6454
  "replaying stdout the child emitted between turns",
6158
6455
  "replaying the conversation as text: no claude session was available to continue",
6159
6456
  "replaying this conversation as text on the other account",
6457
+ "replaying this conversation rather than carrying it across accounts",
6160
6458
  "result fallback timer fired \u2014 closing stream without result event",
6161
6459
  "resuming the claude session from before the restart instead of replaying it",
6162
6460
  "shared Claude capability already exists; leaving untouched",
@@ -6164,6 +6462,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
6164
6462
  "skipping local MCP server with no command",
6165
6463
  "skipping remote MCP server with no url",
6166
6464
  "stale scratch directory sweep failed",
6465
+ "starting fresh on this account: it is in another account group",
6167
6466
  "task_batch resolving with child results missing",
6168
6467
  "the interactive transport has not been measured on this Claude Code version; it reads the TUI's screen and transcript, which a new release can change. If a turn hangs or a dialog is not handled, run /claude-code-doctor bundle and report it",
6169
6468
  "the interactive turn did not take a forwarded user message",
@@ -6299,7 +6598,13 @@ var BUNDLE_DATA_ALLOWLIST = {
6299
6598
  // doctor table already prints the same class of value (providers, accounts,
6300
6599
  // proxyTools, MCP servers), so a bundle is no more revealing than the report
6301
6600
  // it is attached to.
6601
+ // An account name and a group name are both operator-chosen labels of the
6602
+ // same class as `accounts`, which the doctor table already prints a column
6603
+ // of; `from` is already here as the account a conversation came from.
6604
+ account: "name",
6302
6605
  accounts: "name",
6606
+ accountGroups: "name",
6607
+ group: "name",
6303
6608
  agent: "name",
6304
6609
  bundled: "name",
6305
6610
  excluded: "name",
@@ -6398,7 +6703,7 @@ function createIdHasher(salt = randomBytes2(16).toString("hex")) {
6398
6703
  return (raw) => createHash4("sha256").update(salt).update("\0").update(raw).digest("hex").slice(0, 8);
6399
6704
  }
6400
6705
  function createRedactionContext(options = {}) {
6401
- const home = options.home ?? homedir5();
6706
+ const home = options.home ?? homedir6();
6402
6707
  const homes = /* @__PURE__ */ new Set();
6403
6708
  if (home) homes.add(home);
6404
6709
  if (home) {
@@ -6583,8 +6888,8 @@ function redactLogLine(raw, context) {
6583
6888
  if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
6584
6889
  return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
6585
6890
  }
6586
- function readLogTail(path14, maxBytes = BUNDLE_TAIL_BYTES) {
6587
- const fd = openSync(path14, "r");
6891
+ function readLogTail(path15, maxBytes = BUNDLE_TAIL_BYTES) {
6892
+ const fd = openSync(path15, "r");
6588
6893
  try {
6589
6894
  const size = fstatSync(fd).size;
6590
6895
  const length = Math.min(size, maxBytes);
@@ -6789,14 +7094,14 @@ async function fetchPlanUsage(cliPath, options = {}) {
6789
7094
  }
6790
7095
 
6791
7096
  // src/stale-build.ts
6792
- import * as fs6 from "fs";
6793
- import * as path7 from "path";
7097
+ import * as fs7 from "fs";
7098
+ import * as path8 from "path";
6794
7099
  import { fileURLToPath as fileURLToPath2 } from "url";
6795
7100
 
6796
7101
  // src/startup-diagnostics.ts
6797
7102
  import { execFile as execFile3 } from "child_process";
6798
- import * as fs5 from "fs";
6799
- import * as path6 from "path";
7103
+ import * as fs6 from "fs";
7104
+ import * as path7 from "path";
6800
7105
  import { promisify as promisify2 } from "util";
6801
7106
  import { fileURLToPath } from "url";
6802
7107
 
@@ -6831,8 +7136,8 @@ var cachedPluginVersion;
6831
7136
  function pluginVersion() {
6832
7137
  if (cachedPluginVersion) return cachedPluginVersion;
6833
7138
  try {
6834
- const here = path6.dirname(fileURLToPath(import.meta.url));
6835
- const raw = fs5.readFileSync(path6.join(here, "..", "package.json"), "utf8");
7139
+ const here = path7.dirname(fileURLToPath(import.meta.url));
7140
+ const raw = fs6.readFileSync(path7.join(here, "..", "package.json"), "utf8");
6836
7141
  const version = JSON.parse(raw).version;
6837
7142
  cachedPluginVersion = typeof version === "string" ? version : "unknown";
6838
7143
  } catch {
@@ -6856,7 +7161,7 @@ var opencodeVersionProbe;
6856
7161
  function detectOpencodeVersion(execPath = process.execPath) {
6857
7162
  if (opencodeVersionProbe) return opencodeVersionProbe;
6858
7163
  opencodeVersionProbe = (async () => {
6859
- if (!path6.basename(execPath).toLowerCase().includes("opencode")) {
7164
+ if (!path7.basename(execPath).toLowerCase().includes("opencode")) {
6860
7165
  log.debug("skipping opencode version probe: execPath is not opencode", { execPath });
6861
7166
  return void 0;
6862
7167
  }
@@ -6925,6 +7230,15 @@ function collectStartupDiagnostics(providers, opencodeVersion) {
6925
7230
  cwd,
6926
7231
  providers: Object.keys(providers),
6927
7232
  accounts,
7233
+ // One guard per install, so the first provider that declares one answers
7234
+ // for all of them; it is validated against the accounts the expansion
7235
+ // produced, which is the same list every provider carries.
7236
+ accountGroups: describeAccountGroups(
7237
+ resolveAccountGroups(
7238
+ firstOption(providers, "accountGroups"),
7239
+ stringList(firstOption(providers, "failoverAccounts"))
7240
+ )
7241
+ ),
6928
7242
  proxyTools: stringList(firstOption(providers, "proxyTools")),
6929
7243
  mcpServers,
6930
7244
  permissionPresets: Object.entries(providers).map(
@@ -7085,10 +7399,10 @@ function defaultEntryPath() {
7085
7399
  }
7086
7400
  function readOnDiskBuild(entryPath) {
7087
7401
  try {
7088
- const raw = fs6.readFileSync(path7.join(path7.dirname(entryPath), "..", "package.json"), "utf8");
7402
+ const raw = fs7.readFileSync(path8.join(path8.dirname(entryPath), "..", "package.json"), "utf8");
7089
7403
  const version = JSON.parse(raw).version;
7090
7404
  if (typeof version !== "string" || version.length === 0) return void 0;
7091
- const stat = fs6.statSync(entryPath);
7405
+ const stat = fs7.statSync(entryPath);
7092
7406
  return { version, mtimeMs: stat.mtimeMs, size: stat.size };
7093
7407
  } catch {
7094
7408
  return void 0;
@@ -7099,7 +7413,7 @@ function recordLoadedBuild() {
7099
7413
  let mtimeMs;
7100
7414
  let size;
7101
7415
  try {
7102
- const stat = fs6.statSync(entryPath);
7416
+ const stat = fs7.statSync(entryPath);
7103
7417
  mtimeMs = stat.mtimeMs;
7104
7418
  size = stat.size;
7105
7419
  } catch {
@@ -7197,6 +7511,9 @@ function formatDoctorReport(report) {
7197
7511
  lines.push(`| cwd | \`${report.cwd.resolved}\` (${report.cwd.source}) |`);
7198
7512
  lines.push(`| providers | ${list(report.providers)} |`);
7199
7513
  lines.push(`| accounts | ${list(report.accounts)} |`);
7514
+ lines.push(
7515
+ `| accountGroups | ${report.accountGroups.length > 0 ? list(report.accountGroups) : "none (every account is one group)"} |`
7516
+ );
7200
7517
  lines.push(`| proxyTools | ${list(report.proxyTools)} |`);
7201
7518
  lines.push(`| MCP servers (on disk) | ${list(report.mcpServers)} |`);
7202
7519
  lines.push(`| permissionPreset | ${describePermissionPresets(report.permissionPresets)} |`);
@@ -7450,6 +7767,7 @@ async function gatherDoctorReport(options) {
7450
7767
  cwd: base.cwd,
7451
7768
  providers: base.providers,
7452
7769
  accounts: base.accounts,
7770
+ accountGroups: base.accountGroups,
7453
7771
  proxyTools: base.proxyTools,
7454
7772
  mcpServers: base.mcpServers,
7455
7773
  permissionPresets: base.permissionPresets,
@@ -7537,7 +7855,7 @@ Could not build the report: ${message}`;
7537
7855
 
7538
7856
  // src/agent-models.ts
7539
7857
  import { readFile, readdir } from "fs/promises";
7540
- import path8 from "path";
7858
+ import path9 from "path";
7541
7859
 
7542
7860
  // src/models.ts
7543
7861
  var PROVIDER_ID = "claude-code";
@@ -8103,13 +8421,13 @@ async function readAgentMarkdownRecords(directories) {
8103
8421
  const name = entry.slice(0, -3);
8104
8422
  if (records2[name]) continue;
8105
8423
  try {
8106
- const text = await readFile(path8.join(directory, entry), "utf8");
8424
+ const text = await readFile(path9.join(directory, entry), "utf8");
8107
8425
  records2[name] = parseAgentFrontmatter(text);
8108
8426
  const prompt = agentMarkdownBody(text);
8109
8427
  if (prompt) records2[name].prompt = prompt;
8110
8428
  } catch (err) {
8111
8429
  log.debug("failed to read agent markdown", {
8112
- file: path8.join(directory, entry),
8430
+ file: path9.join(directory, entry),
8113
8431
  error: String(err)
8114
8432
  });
8115
8433
  }
@@ -8121,12 +8439,12 @@ function agentDirectories(home, projectDirectory) {
8121
8439
  const directories = [];
8122
8440
  if (projectDirectory) {
8123
8441
  for (const name of AGENT_DIR_NAMES) {
8124
- directories.push(path8.join(projectDirectory, ".opencode", name));
8442
+ directories.push(path9.join(projectDirectory, ".opencode", name));
8125
8443
  }
8126
8444
  }
8127
8445
  if (home) {
8128
8446
  for (const name of AGENT_DIR_NAMES) {
8129
- directories.push(path8.join(home, ".config", "opencode", name));
8447
+ directories.push(path9.join(home, ".config", "opencode", name));
8130
8448
  }
8131
8449
  }
8132
8450
  return directories;
@@ -8294,6 +8612,7 @@ var PLUGIN_NOTE_MARKERS = [
8294
8612
  SILENT_TURN_MARKER,
8295
8613
  FAILOVER_MARKER,
8296
8614
  ACCOUNT_BLOCK_MARKER,
8615
+ ACCOUNT_GROUP_MARKER,
8297
8616
  USAGE_LIMIT_MARKER,
8298
8617
  MODEL_FALLBACK_MARKER,
8299
8618
  UNATTENDED_REPLAY_MARKER,
@@ -8442,6 +8761,17 @@ function clipWithMarker(text, max) {
8442
8761
  return `${text.slice(0, max)}
8443
8762
  \u2026[truncated ${text.length - max} chars]`;
8444
8763
  }
8764
+ var MAX_REPLAY_MESSAGE_CHARS = 2e3;
8765
+ var MAX_REPLAY_LAST_REPLY_CHARS = 12e3;
8766
+ var MAX_REPLAY_HEAD_CHARS = 1e3;
8767
+ function clipMiddle(text, max) {
8768
+ if (text.length <= max) return text;
8769
+ const head = Math.min(Math.floor(max / 4), MAX_REPLAY_HEAD_CHARS);
8770
+ const tail = max - head;
8771
+ return `${text.slice(0, head)}
8772
+ \u2026[${text.length - max} chars omitted]\u2026
8773
+ ${text.slice(-tail)}`;
8774
+ }
8445
8775
  function renderToolInput(input) {
8446
8776
  let raw;
8447
8777
  try {
@@ -8510,13 +8840,20 @@ function compactConversationHistory(prompt, opts = {}) {
8510
8840
  return null;
8511
8841
  }
8512
8842
  const historyParts = [];
8843
+ let lastAssistant = -1;
8844
+ for (let i = conversationMessages.length - 2; i >= 0; i--) {
8845
+ if (conversationMessages[i].role === "assistant") {
8846
+ lastAssistant = i;
8847
+ break;
8848
+ }
8849
+ }
8513
8850
  for (let i = 0; i < conversationMessages.length - 1; i++) {
8514
8851
  const msg = conversationMessages[i];
8515
8852
  const role = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "Tool";
8516
8853
  const { text } = renderMessageContentForCompaction(msg);
8517
8854
  if (text.trim()) {
8518
- const truncated = text.length > 2e3 ? text.slice(0, 2e3) + "..." : text;
8519
- historyParts.push(`${role}: ${truncated}`);
8855
+ const max = i === lastAssistant ? MAX_REPLAY_LAST_REPLY_CHARS : MAX_REPLAY_MESSAGE_CHARS;
8856
+ historyParts.push(`${role}: ${clipMiddle(text, max)}`);
8520
8857
  }
8521
8858
  }
8522
8859
  if (historyParts.length === 0) {
@@ -8726,14 +9063,14 @@ function getTrailingUserMessages(prompt, opts = {}) {
8726
9063
 
8727
9064
  // src/skill-bridge.ts
8728
9065
  import * as crypto3 from "crypto";
8729
- import * as fs7 from "fs";
8730
- import * as os3 from "os";
8731
- import * as path9 from "path";
9066
+ import * as fs8 from "fs";
9067
+ import * as os4 from "os";
9068
+ import * as path10 from "path";
8732
9069
  import { fileURLToPath as fileURLToPath3 } from "url";
8733
9070
  function bundledSkillsDir() {
8734
9071
  try {
8735
9072
  const here = fileURLToPath3(import.meta.url);
8736
- const dir = path9.resolve(path9.dirname(here), "..", "skills");
9073
+ const dir = path10.resolve(path10.dirname(here), "..", "skills");
8737
9074
  return dirExists2(dir) ? dir : null;
8738
9075
  } catch {
8739
9076
  return null;
@@ -8741,16 +9078,16 @@ function bundledSkillsDir() {
8741
9078
  }
8742
9079
  function dirExists2(p) {
8743
9080
  try {
8744
- return fs7.statSync(p).isDirectory();
9081
+ return fs8.statSync(p).isDirectory();
8745
9082
  } catch {
8746
9083
  return false;
8747
9084
  }
8748
9085
  }
8749
9086
  function realPath(p) {
8750
9087
  try {
8751
- return fs7.realpathSync(p);
9088
+ return fs8.realpathSync(p);
8752
9089
  } catch {
8753
- return path9.resolve(p);
9090
+ return path10.resolve(p);
8754
9091
  }
8755
9092
  }
8756
9093
  function envEnabled(name) {
@@ -8771,10 +9108,10 @@ function declaredSkillName(source) {
8771
9108
  return SAFE_SKILL_NAME.test(name) ? name : null;
8772
9109
  }
8773
9110
  function readSkill(root, entry) {
8774
- const dir = path9.join(root, entry);
9111
+ const dir = path10.join(root, entry);
8775
9112
  let source;
8776
9113
  try {
8777
- source = fs7.readFileSync(path9.join(dir, "SKILL.md"), "utf8");
9114
+ source = fs8.readFileSync(path10.join(dir, "SKILL.md"), "utf8");
8778
9115
  } catch {
8779
9116
  return null;
8780
9117
  }
@@ -8792,47 +9129,47 @@ function skillRoots(cwd) {
8792
9129
  const roots = [];
8793
9130
  const seen = /* @__PURE__ */ new Set();
8794
9131
  const push = (p) => {
8795
- const abs = path9.resolve(p);
9132
+ const abs = path10.resolve(p);
8796
9133
  if (seen.has(abs)) return;
8797
9134
  seen.add(abs);
8798
9135
  if (dirExists2(abs)) roots.push(abs);
8799
9136
  };
8800
- const home = os3.homedir();
8801
- const homeDir = home ? path9.resolve(home) : null;
8802
- let current = path9.resolve(cwd);
9137
+ const home = os4.homedir();
9138
+ const homeDir = home ? path10.resolve(home) : null;
9139
+ let current = path10.resolve(cwd);
8803
9140
  while (true) {
8804
- push(path9.join(current, ".opencode", "skills"));
9141
+ push(path10.join(current, ".opencode", "skills"));
8805
9142
  if (current !== homeDir) {
8806
- push(path9.join(current, ".claude", "skills"));
8807
- push(path9.join(current, ".agents", "skills"));
9143
+ push(path10.join(current, ".claude", "skills"));
9144
+ push(path10.join(current, ".agents", "skills"));
8808
9145
  }
8809
- const parent = path9.dirname(current);
9146
+ const parent = path10.dirname(current);
8810
9147
  if (parent === current) break;
8811
9148
  current = parent;
8812
9149
  }
8813
- if (homeDir) push(path9.join(homeDir, ".opencode", "skills"));
9150
+ if (homeDir) push(path10.join(homeDir, ".opencode", "skills"));
8814
9151
  const envDir = process.env.OPENCODE_CONFIG_DIR;
8815
9152
  if (envDir) {
8816
- push(path9.join(envDir, "skills"));
8817
- push(path9.join(envDir, "skill"));
9153
+ push(path10.join(envDir, "skills"));
9154
+ push(path10.join(envDir, "skill"));
8818
9155
  }
8819
- const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path9.join(homeDir, ".config") : null);
9156
+ const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path10.join(homeDir, ".config") : null);
8820
9157
  if (xdg) {
8821
- push(path9.join(xdg, "opencode", "skills"));
8822
- push(path9.join(xdg, "opencode", "skill"));
9158
+ push(path10.join(xdg, "opencode", "skills"));
9159
+ push(path10.join(xdg, "opencode", "skill"));
8823
9160
  }
8824
9161
  if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
8825
9162
  if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
8826
- push(path9.join(homeDir, ".claude", "skills"));
9163
+ push(path10.join(homeDir, ".claude", "skills"));
8827
9164
  }
8828
- push(path9.join(homeDir, ".agents", "skills"));
9165
+ push(path10.join(homeDir, ".agents", "skills"));
8829
9166
  }
8830
9167
  return roots;
8831
9168
  }
8832
9169
  function collectSkills(root, claimed, found) {
8833
9170
  let entries;
8834
9171
  try {
8835
- entries = fs7.readdirSync(root, { withFileTypes: true });
9172
+ entries = fs8.readdirSync(root, { withFileTypes: true });
8836
9173
  } catch {
8837
9174
  return;
8838
9175
  }
@@ -8872,23 +9209,23 @@ function discoverBundledSkills() {
8872
9209
  }
8873
9210
  function claudeConfigDir(configDir) {
8874
9211
  const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
8875
- if (explicit) return path9.resolve(expandHome(explicit));
8876
- const home = os3.homedir();
8877
- return home ? path9.resolve(home, ".claude") : null;
9212
+ if (explicit) return path10.resolve(expandHome(explicit));
9213
+ const home = os4.homedir();
9214
+ return home ? path10.resolve(home, ".claude") : null;
8878
9215
  }
8879
9216
  function installedPluginSkillRoots(configDir, cwd) {
8880
9217
  if (!configDir) return [];
8881
9218
  let parsed;
8882
9219
  try {
8883
9220
  parsed = JSON.parse(
8884
- fs7.readFileSync(path9.join(configDir, "plugins", "installed_plugins.json"), "utf8")
9221
+ fs8.readFileSync(path10.join(configDir, "plugins", "installed_plugins.json"), "utf8")
8885
9222
  );
8886
9223
  } catch {
8887
9224
  return [];
8888
9225
  }
8889
9226
  const plugins = parsed?.plugins;
8890
9227
  if (!plugins || typeof plugins !== "object") return [];
8891
- const workspace = path9.resolve(cwd);
9228
+ const workspace = path10.resolve(cwd);
8892
9229
  const roots = [];
8893
9230
  for (const installs of Object.values(plugins)) {
8894
9231
  if (!Array.isArray(installs)) continue;
@@ -8896,28 +9233,28 @@ function installedPluginSkillRoots(configDir, cwd) {
8896
9233
  const entry = install;
8897
9234
  if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
8898
9235
  if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
8899
- roots.push(path9.join(entry.installPath, "skills"));
9236
+ roots.push(path10.join(entry.installPath, "skills"));
8900
9237
  }
8901
9238
  }
8902
9239
  return roots;
8903
9240
  }
8904
9241
  function coversWorkspace(projectPath, workspace) {
8905
9242
  if (typeof projectPath !== "string" || !projectPath) return false;
8906
- const root = path9.resolve(projectPath);
9243
+ const root = path10.resolve(projectPath);
8907
9244
  if (root === workspace) return true;
8908
- const rel = path9.relative(root, workspace);
8909
- return rel !== "" && !rel.startsWith("..") && !path9.isAbsolute(rel);
9245
+ const rel = path10.relative(root, workspace);
9246
+ return rel !== "" && !rel.startsWith("..") && !path10.isAbsolute(rel);
8910
9247
  }
8911
9248
  function discoverNativeClaudeSkills(opts) {
8912
9249
  const found = [];
8913
9250
  const scanned = /* @__PURE__ */ new Set();
8914
9251
  const scan = (root, scope) => {
8915
- const abs = path9.resolve(root);
9252
+ const abs = path10.resolve(root);
8916
9253
  if (scanned.has(abs)) return;
8917
9254
  scanned.add(abs);
8918
9255
  let entries;
8919
9256
  try {
8920
- entries = fs7.readdirSync(abs, { withFileTypes: true });
9257
+ entries = fs8.readdirSync(abs, { withFileTypes: true });
8921
9258
  } catch {
8922
9259
  return;
8923
9260
  }
@@ -8929,13 +9266,13 @@ function discoverNativeClaudeSkills(opts) {
8929
9266
  }
8930
9267
  };
8931
9268
  const configDir = claudeConfigDir(opts.configDir);
8932
- if (configDir) scan(path9.join(configDir, "skills"), "user");
8933
- const home = os3.homedir();
8934
- const homeDir = home ? path9.resolve(home) : null;
8935
- let current = path9.resolve(opts.cwd);
9269
+ if (configDir) scan(path10.join(configDir, "skills"), "user");
9270
+ const home = os4.homedir();
9271
+ const homeDir = home ? path10.resolve(home) : null;
9272
+ let current = path10.resolve(opts.cwd);
8936
9273
  while (true) {
8937
- if (current !== homeDir) scan(path9.join(current, ".claude", "skills"), "project");
8938
- const parent = path9.dirname(current);
9274
+ if (current !== homeDir) scan(path10.join(current, ".claude", "skills"), "project");
9275
+ const parent = path10.dirname(current);
8939
9276
  if (parent === current) break;
8940
9277
  current = parent;
8941
9278
  }
@@ -8969,10 +9306,10 @@ function dropNativelyLoadedSkills(skills, native) {
8969
9306
  }
8970
9307
  function readBundledSkillInfos() {
8971
9308
  return discoverBundledSkills().flatMap((skill) => {
8972
- const file = path9.join(skill.dir, "SKILL.md");
9309
+ const file = path10.join(skill.dir, "SKILL.md");
8973
9310
  let text;
8974
9311
  try {
8975
- text = fs7.readFileSync(file, "utf8");
9312
+ text = fs8.readFileSync(file, "utf8");
8976
9313
  } catch {
8977
9314
  return [];
8978
9315
  }
@@ -8996,15 +9333,15 @@ function readBundledSkillInfos() {
8996
9333
  }
8997
9334
  function linkSkill(source, target) {
8998
9335
  try {
8999
- fs7.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
9336
+ fs8.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
9000
9337
  return;
9001
9338
  } catch {
9002
- fs7.cpSync(source, target, { recursive: true, dereference: true });
9339
+ fs8.cpSync(source, target, { recursive: true, dereference: true });
9003
9340
  }
9004
9341
  }
9005
9342
  function fileExists2(p) {
9006
9343
  try {
9007
- return fs7.statSync(p).isFile();
9344
+ return fs8.statSync(p).isFile();
9008
9345
  } catch {
9009
9346
  return false;
9010
9347
  }
@@ -9013,14 +9350,14 @@ function buildSkillPluginDir(skills) {
9013
9350
  if (skills.length === 0) return null;
9014
9351
  const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
9015
9352
  const hash = crypto3.createHash("sha256").update(fingerprint).digest("hex").slice(0, 12);
9016
- const root = path9.join(pluginTmpDir(), `skills-${hash}`);
9017
- const manifest = path9.join(root, ".claude-plugin", "plugin.json");
9353
+ const root = path10.join(pluginTmpDir(), `skills-${hash}`);
9354
+ const manifest = path10.join(root, ".claude-plugin", "plugin.json");
9018
9355
  if (fileExists2(manifest)) return root;
9019
9356
  try {
9020
- fs7.rmSync(root, { recursive: true, force: true });
9021
- fs7.mkdirSync(path9.join(root, ".claude-plugin"), { recursive: true });
9022
- fs7.mkdirSync(path9.join(root, "skills"), { recursive: true });
9023
- fs7.writeFileSync(
9357
+ fs8.rmSync(root, { recursive: true, force: true });
9358
+ fs8.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
9359
+ fs8.mkdirSync(path10.join(root, "skills"), { recursive: true });
9360
+ fs8.writeFileSync(
9024
9361
  manifest,
9025
9362
  JSON.stringify(
9026
9363
  {
@@ -9033,7 +9370,7 @@ function buildSkillPluginDir(skills) {
9033
9370
  { encoding: "utf8", mode: 384 }
9034
9371
  );
9035
9372
  for (const skill of skills) {
9036
- linkSkill(skill.dir, path9.join(root, "skills", skill.name));
9373
+ linkSkill(skill.dir, path10.join(root, "skills", skill.name));
9037
9374
  }
9038
9375
  } catch (err) {
9039
9376
  log.warn("failed to stage opencode skill plugin dir", {
@@ -9049,7 +9386,7 @@ function registerBundledSkillPath(config) {
9049
9386
  if (!dir) return false;
9050
9387
  config.skills ??= {};
9051
9388
  const paths = config.skills.paths ??= [];
9052
- if (paths.some((p) => path9.resolve(p) === dir)) return false;
9389
+ if (paths.some((p) => path10.resolve(p) === dir)) return false;
9053
9390
  paths.push(dir);
9054
9391
  return true;
9055
9392
  }
@@ -9099,36 +9436,36 @@ import { EventEmitter as EventEmitter4 } from "events";
9099
9436
  import { randomUUID as randomUUID5 } from "crypto";
9100
9437
  import { existsSync as existsSync4, writeFileSync as writeFileSync5 } from "fs";
9101
9438
  import { unlink as unlink2 } from "fs/promises";
9102
- import { join as join10 } from "path";
9439
+ import { join as join11 } from "path";
9103
9440
 
9104
9441
  // src/claude-session-bun.ts
9105
- import * as os4 from "os";
9106
- import * as fs8 from "fs";
9107
- import * as path10 from "path";
9442
+ import * as os5 from "os";
9443
+ import * as fs9 from "fs";
9444
+ import * as path11 from "path";
9108
9445
  import { execFileSync } from "child_process";
9109
9446
  import { randomUUID as randomUUID4 } from "crypto";
9110
9447
  import { StringDecoder } from "string_decoder";
9111
9448
  function resolveClaude(cmd = "claude") {
9112
- if (path10.isAbsolute(cmd) && fs8.existsSync(cmd)) return cmd;
9449
+ if (path11.isAbsolute(cmd) && fs9.existsSync(cmd)) return cmd;
9113
9450
  const viaBun = Bun.which(cmd);
9114
9451
  if (viaBun) return viaBun;
9115
- const isWin = os4.platform() === "win32";
9452
+ const isWin = os5.platform() === "win32";
9116
9453
  try {
9117
9454
  const out = execFileSync(isWin ? "where" : "which", [cmd], {
9118
9455
  encoding: "utf8",
9119
9456
  stdio: ["ignore", "pipe", "ignore"]
9120
9457
  });
9121
- const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs8.existsSync(p));
9458
+ const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs9.existsSync(p));
9122
9459
  if (first) return first;
9123
9460
  } catch {
9124
9461
  }
9125
9462
  throw new Error(`Could not resolve command on PATH: ${cmd}`);
9126
9463
  }
9127
9464
  function encodeCwd(cwd) {
9128
- const resolved = path10.resolve(cwd);
9465
+ const resolved = path11.resolve(cwd);
9129
9466
  let real = resolved;
9130
9467
  try {
9131
- real = fs8.realpathSync.native(resolved);
9468
+ real = fs9.realpathSync.native(resolved);
9132
9469
  } catch {
9133
9470
  }
9134
9471
  return real.replace(/[^a-zA-Z0-9]/g, "-");
@@ -9136,7 +9473,7 @@ function encodeCwd(cwd) {
9136
9473
  var bunPtySpawner = (argv, opts) => {
9137
9474
  const [command, ...args] = argv;
9138
9475
  const resolved = resolveClaude(command ?? "claude");
9139
- if (os4.platform() === "win32" && isBatchFile(resolved)) {
9476
+ if (os5.platform() === "win32" && isBatchFile(resolved)) {
9140
9477
  throw new Error(
9141
9478
  `The interactive transport cannot run a Windows batch shim (${resolved}). Point \`cliPath\` at claude.exe, or use the default headless transport.`
9142
9479
  );
@@ -9244,10 +9581,10 @@ function classifyScreen(text) {
9244
9581
  return null;
9245
9582
  }
9246
9583
  function interactiveTranscriptPath(opts) {
9247
- return path10.join(
9584
+ return path11.join(
9248
9585
  resolveConfigDir(opts.configDir),
9249
9586
  "projects",
9250
- encodeCwd(path10.resolve(opts.cwd)),
9587
+ encodeCwd(path11.resolve(opts.cwd)),
9251
9588
  `${opts.sessionId}.jsonl`
9252
9589
  );
9253
9590
  }
@@ -9425,7 +9762,7 @@ var TranscriptCursor = class {
9425
9762
  const filePath = this.resolvePath();
9426
9763
  let stat;
9427
9764
  try {
9428
- stat = fs8.statSync(filePath);
9765
+ stat = fs9.statSync(filePath);
9429
9766
  } catch {
9430
9767
  return;
9431
9768
  }
@@ -9441,13 +9778,13 @@ var TranscriptCursor = class {
9441
9778
  if (stat.size <= this.offset) return;
9442
9779
  let fd = null;
9443
9780
  try {
9444
- fd = fs8.openSync(filePath, "r");
9781
+ fd = fs9.openSync(filePath, "r");
9445
9782
  const buffer = Buffer.allocUnsafe(
9446
9783
  Math.min(TRANSCRIPT_CHUNK_BYTES, stat.size - this.offset)
9447
9784
  );
9448
9785
  while (this.offset < stat.size) {
9449
9786
  const want = Math.min(buffer.length, stat.size - this.offset);
9450
- const read = fs8.readSync(fd, buffer, 0, want, this.offset);
9787
+ const read = fs9.readSync(fd, buffer, 0, want, this.offset);
9451
9788
  this.stats.reads++;
9452
9789
  if (read <= 0) break;
9453
9790
  this.offset += read;
@@ -9458,7 +9795,7 @@ var TranscriptCursor = class {
9458
9795
  } finally {
9459
9796
  if (fd !== null) {
9460
9797
  try {
9461
- fs8.closeSync(fd);
9798
+ fs9.closeSync(fd);
9462
9799
  } catch {
9463
9800
  }
9464
9801
  }
@@ -9488,12 +9825,12 @@ function isOwnPromptRecord(rec, normalized) {
9488
9825
  }
9489
9826
  function resolveConfigDir(configDir) {
9490
9827
  const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
9491
- if (!value) return path10.join(os4.homedir(), ".claude");
9492
- if (value === "~") return os4.homedir();
9828
+ if (!value) return path11.join(os5.homedir(), ".claude");
9829
+ if (value === "~") return os5.homedir();
9493
9830
  if (value.startsWith("~/") || value.startsWith("~\\")) {
9494
- return path10.join(os4.homedir(), value.slice(2));
9831
+ return path11.join(os5.homedir(), value.slice(2));
9495
9832
  }
9496
- return path10.resolve(value);
9833
+ return path11.resolve(value);
9497
9834
  }
9498
9835
  function fatalScreenMessage(kind, configDir, cwd) {
9499
9836
  if (kind === "trust") {
@@ -9557,7 +9894,7 @@ var ClaudeSession = class {
9557
9894
  abandonTurn = false;
9558
9895
  o;
9559
9896
  constructor(opts = {}) {
9560
- this.cwd = path10.resolve(opts.cwd ?? process.cwd());
9897
+ this.cwd = path11.resolve(opts.cwd ?? process.cwd());
9561
9898
  this.configDir = resolveConfigDir(opts.configDir);
9562
9899
  this.explicitConfigDir = opts.configDir ? this.configDir : void 0;
9563
9900
  this.signal = opts.signal;
@@ -9569,7 +9906,7 @@ var ClaudeSession = class {
9569
9906
  this.onExit = opts.onExit;
9570
9907
  this.spawnPty = opts.spawnPty ?? bunPtySpawner;
9571
9908
  this.sessionId = opts.resumeSessionId ?? randomUUID4();
9572
- this.jsonlPath = path10.join(
9909
+ this.jsonlPath = path11.join(
9573
9910
  this.configDir,
9574
9911
  "projects",
9575
9912
  encodeCwd(this.cwd),
@@ -10280,7 +10617,7 @@ ${text2}`
10280
10617
  }
10281
10618
  function stageImage(data, extension) {
10282
10619
  try {
10283
- const file = join10(pluginTmpDir(), `attachment-${randomUUID5()}.${extension}`);
10620
+ const file = join11(pluginTmpDir(), `attachment-${randomUUID5()}.${extension}`);
10284
10621
  writeFileSync5(file, data, { mode: 384, flag: "wx" });
10285
10622
  return file;
10286
10623
  } catch (err) {
@@ -10765,7 +11102,7 @@ function spawnInteractiveProcess(opts) {
10765
11102
  import { randomUUID as randomUUID6 } from "crypto";
10766
11103
  import { constants as fsConstants } from "fs";
10767
11104
  import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
10768
- import path11 from "path";
11105
+ import path12 from "path";
10769
11106
  var transcriptPath = interactiveTranscriptPath;
10770
11107
  function configDirForAccount(account) {
10771
11108
  const normalized = normalizeAccountName(account || DEFAULT_ACCOUNT);
@@ -10793,7 +11130,7 @@ async function carryTranscriptToAccount(input) {
10793
11130
  const carriedId = renamed ? (input.newSessionId ?? randomUUID6)() : sessionId;
10794
11131
  const target = renamed ? transcriptPath({ configDir: targetConfigDir, cwd, sessionId: carriedId }) : targetOwn;
10795
11132
  try {
10796
- await mkdir2(path11.dirname(target), { recursive: true, mode: 448 });
11133
+ await mkdir2(path12.dirname(target), { recursive: true, mode: 448 });
10797
11134
  await copyFile2(live.file, target, fsConstants.COPYFILE_EXCL);
10798
11135
  await chmod2(target, 384);
10799
11136
  } catch (err) {
@@ -10813,7 +11150,7 @@ function dedupe2(dirs) {
10813
11150
  const seen = /* @__PURE__ */ new Set();
10814
11151
  const out = [];
10815
11152
  for (const dir of dirs) {
10816
- const resolved = path11.resolve(dir);
11153
+ const resolved = path12.resolve(dir);
10817
11154
  if (seen.has(resolved)) continue;
10818
11155
  seen.add(resolved);
10819
11156
  out.push(resolved);
@@ -10822,13 +11159,13 @@ function dedupe2(dirs) {
10822
11159
  }
10823
11160
 
10824
11161
  // src/prompts.ts
10825
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
10826
- import { homedir as homedir8 } from "os";
11162
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
11163
+ import { homedir as homedir9 } from "os";
10827
11164
  import { randomUUID as randomUUID7 } from "crypto";
10828
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
10829
- function readPromptFileIfPresent(path14) {
11165
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join12 } from "path";
11166
+ function readPromptFileIfPresent(path15) {
10830
11167
  try {
10831
- const content = readFileSync6(path14, "utf8").trim();
11168
+ const content = readFileSync7(path15, "utf8").trim();
10832
11169
  return content || void 0;
10833
11170
  } catch {
10834
11171
  return void 0;
@@ -10837,7 +11174,7 @@ function readPromptFileIfPresent(path14) {
10837
11174
  function nearestWorkspaceAgentsPrompt(cwd) {
10838
11175
  let dir = cwd;
10839
11176
  while (true) {
10840
- const content = readPromptFileIfPresent(join11(dir, "AGENTS.md"));
11177
+ const content = readPromptFileIfPresent(join12(dir, "AGENTS.md"));
10841
11178
  if (content) return content;
10842
11179
  const parent = dirname6(dir);
10843
11180
  if (parent === dir) return void 0;
@@ -10933,9 +11270,9 @@ function extractSystemMessages(prompt) {
10933
11270
  }
10934
11271
  var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
10935
11272
  function userAuthoredInstructions(forwarded, opts = {}) {
10936
- const readFile2 = opts.readFile ?? ((path14) => {
11273
+ const readFile2 = opts.readFile ?? ((path15) => {
10937
11274
  try {
10938
- return readFileSync6(path14, "utf8");
11275
+ return readFileSync7(path15, "utf8");
10939
11276
  } catch {
10940
11277
  return null;
10941
11278
  }
@@ -10947,16 +11284,16 @@ function userAuthoredInstructions(forwarded, opts = {}) {
10947
11284
  }
10948
11285
  const seen = /* @__PURE__ */ new Set();
10949
11286
  for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
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);
11287
+ const path15 = match[1].trim();
11288
+ if (seen.has(path15) || !isAbsolute3(path15)) continue;
11289
+ if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path15))) continue;
11290
+ seen.add(path15);
11291
+ const file = readFile2(path15);
10955
11292
  if (!file?.trim()) continue;
10956
11293
  const start = match.index + match[0].length + 1;
10957
11294
  const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
10958
11295
  if (content === null) continue;
10959
- out.push({ text: `Instructions from: ${path14}
11296
+ out.push({ text: `Instructions from: ${path15}
10960
11297
  ${content}`, content });
10961
11298
  }
10962
11299
  return out;
@@ -10976,8 +11313,8 @@ ${options.compressionSummary.trim()}`
10976
11313
  for (const s of extraSystemContent) {
10977
11314
  if (s.trim()) parts.push(s.trim());
10978
11315
  }
10979
- const configRoot = process.env.XDG_CONFIG_HOME ?? join11(homedir8(), ".config");
10980
- const globalAgents = readPromptFileIfPresent(join11(configRoot, "opencode", "AGENTS.md"));
11316
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join12(homedir9(), ".config");
11317
+ const globalAgents = readPromptFileIfPresent(join12(configRoot, "opencode", "AGENTS.md"));
10981
11318
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
10982
11319
  const forwarded = extraSystemContent.join("\n\n");
10983
11320
  const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
@@ -10993,10 +11330,10 @@ ${options.compressionSummary.trim()}`
10993
11330
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
10994
11331
  const content = parts.join("\n\n");
10995
11332
  if (!content) return void 0;
10996
- const path14 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
11333
+ const path15 = join12(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
10997
11334
  try {
10998
- writeFileSync6(path14, content, { encoding: "utf8", mode: 384 });
10999
- return path14;
11335
+ writeFileSync6(path15, content, { encoding: "utf8", mode: 384 });
11336
+ return path15;
11000
11337
  } catch (err) {
11001
11338
  log.warn("failed to write system prompt file", { error: String(err) });
11002
11339
  return void 0;
@@ -11423,7 +11760,9 @@ function effectiveMcpConfig(config, cwd, proxyConfigPath, runtimeStatus, exclude
11423
11760
  let bridgedHash = null;
11424
11761
  let allEnabledServerNames = [];
11425
11762
  if (config.bridgeOpencodeMcp !== false) {
11426
- const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, config.hostApi);
11763
+ const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, config.hostApi, {
11764
+ oauthTokens: config.bridgeMcpOauthTokens === true
11765
+ });
11427
11766
  if (bridged) {
11428
11767
  if (bridged.path) paths.push(bridged.path);
11429
11768
  bridgedHash = bridged.hash;
@@ -13806,6 +14145,8 @@ var ClaudeCodeLanguageModel = class {
13806
14145
  const sourceAccount = normalizeAccountName(
13807
14146
  this.config.account ?? DEFAULT_ACCOUNT
13808
14147
  );
14148
+ const accountProviders = accountProviderMap(this.config.failoverAccounts);
14149
+ const groups = this.config.accountGroups ?? null;
13809
14150
  const baseCliPath = this.config.baseCliPath ?? this.config.cliPath;
13810
14151
  let failover = compactionMode ? { cliPath: this.config.cliPath, modelId: effectiveModelId, failedOver: false } : await resolveFailoverSpawn({
13811
14152
  account: sourceAccount,
@@ -13967,7 +14308,11 @@ var ClaudeCodeLanguageModel = class {
13967
14308
  }
13968
14309
  const failoverAnswer = compactionMode || mode === "generate" ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
13969
14310
  sourceAccount,
13970
- candidates: failoverCandidates(this.config.failoverAccounts, sourceAccount)
14311
+ candidates: failoverCandidates(
14312
+ this.config.failoverAccounts,
14313
+ sourceAccount,
14314
+ groups
14315
+ )
13971
14316
  });
13972
14317
  if (failoverAnswer?.kind === "stop") {
13973
14318
  log.warn("account failover declined; ending the turn", {
@@ -14049,8 +14394,13 @@ var ClaudeCodeLanguageModel = class {
14049
14394
  // Every account's own directory, plus this model's configured one:
14050
14395
  // `configDir` is an option, so an account's directory is not always
14051
14396
  // the `~/.claude-<name>` the name alone would build.
14397
+ // Same-group accounts only (h #g226): a transcript sitting in an
14398
+ // account from another group is not a source this may read from, and
14399
+ // with no groups configured this is the full list exactly as before.
14052
14400
  accountConfigDirs: [
14053
- ...(this.config.failoverAccounts ?? []).map(configDirForAccount),
14401
+ ...accountsInGroupOf(sourceAccount, this.config.failoverAccounts, groups).map(
14402
+ configDirForAccount
14403
+ ),
14054
14404
  resolveConfigDir(this.config.configDir)
14055
14405
  ]
14056
14406
  });
@@ -14084,6 +14434,32 @@ var ClaudeCodeLanguageModel = class {
14084
14434
  if (failoverAnswer?.kind === "switch" && hasPriorConversation && !conversationOnTarget) {
14085
14435
  includeHistoryContext = true;
14086
14436
  }
14437
+ let accountGroupNote = null;
14438
+ if (!compactionMode && groups && accountProviders.size > 0 && includeHistoryContext && failoverAnswer?.kind !== "switch") {
14439
+ const foreign = findForeignAccountSibling({
14440
+ sessionKey: sk,
14441
+ accountProviders,
14442
+ // The live process's own view, so the guard holds with
14443
+ // `resumeAfterRestart: false`, where the store is never written.
14444
+ extraKeys: listClaudeSessionKeys()
14445
+ });
14446
+ if (foreign && !accountsShareGroup(sourceAccount, foreign.account, groups)) {
14447
+ includeHistoryContext = false;
14448
+ accountGroupNote = formatAccountGroupNote({
14449
+ sourceAccount: foreign.account,
14450
+ sourceGroup: accountGroup(foreign.account, groups),
14451
+ targetAccount: sourceAccount,
14452
+ targetGroup: accountGroup(sourceAccount, groups)
14453
+ });
14454
+ log.notice("starting fresh on this account: it is in another account group", {
14455
+ sessionKey: sk,
14456
+ reason: "another-account-group",
14457
+ account: sourceAccount,
14458
+ from: foreign.account,
14459
+ group: accountGroup(sourceAccount, groups)
14460
+ });
14461
+ }
14462
+ }
14087
14463
  let forkFromClaudeSessionId;
14088
14464
  if (this.config.forkSessions === true && includeHistoryContext && !compactionMode && failoverAnswer?.kind !== "switch") {
14089
14465
  const parent = findForkParent({
@@ -14127,7 +14503,7 @@ var ClaudeCodeLanguageModel = class {
14127
14503
  let replayReason = "no-session-for-key";
14128
14504
  if (this.config.resumeAfterRestart !== false && includeHistoryContext && !compactionMode && !forkFromClaudeSessionId && failoverAnswer?.kind !== "switch") {
14129
14505
  const configDir = resolveConfigDir(this.config.configDir);
14130
- const transcriptPath2 = (id) => join12(configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
14506
+ const transcriptPath2 = (id, dir) => join13(dir ?? configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
14131
14507
  const resumePoint = findResumePoint({
14132
14508
  sessionKey: sk,
14133
14509
  prompt: options.prompt,
@@ -14155,25 +14531,70 @@ var ClaudeCodeLanguageModel = class {
14155
14531
  // timers on everything it touches (the same reason the fork lookup
14156
14532
  // above uses it).
14157
14533
  isBusy: claudeSessionIsWriting,
14534
+ // The ACCOUNT is the third thing that may differ (h #g226), and the
14535
+ // guard is where a cross-group sibling is refused by name.
14536
+ accountProviders,
14537
+ allowAccount: (account) => this.config.crossAccountResume !== false && accountsShareGroup(sourceAccount, account, groups),
14158
14538
  onRefused: (reason) => {
14159
14539
  replayReason = reason;
14160
14540
  }
14161
14541
  });
14162
14542
  if (sibling) {
14163
- deleteActiveProcess(sibling.siblingKey);
14164
- const carried = transferClaudeSession(sibling.siblingKey, sk);
14165
- if (carried) {
14166
- includeHistoryContext = false;
14167
- log.notice(
14168
- "continuing this conversation's claude session under the new model or effort instead of replaying it",
14169
- {
14170
- sessionKey: sk,
14171
- siblingKey: sibling.siblingKey,
14172
- matchedMessages: sibling.matched
14173
- }
14174
- );
14543
+ let carriedSessionId;
14544
+ let carryRefused;
14545
+ if (sibling.siblingAccount && sibling.siblingConfigDir) {
14546
+ const carry = await carryTranscriptToAccount({
14547
+ sessionId: sibling.claudeSessionId,
14548
+ cwd,
14549
+ targetConfigDir: resolveConfigDir(this.skillBridgeSpawn(failover).configDir),
14550
+ // The sibling's recorded directory first, then every same-group
14551
+ // account's. The recorded one can be stale by a turn (it is
14552
+ // rewritten only when a turn succeeds), so the conventional
14553
+ // directories are what make a stale record recoverable rather
14554
+ // than a refusal.
14555
+ accountConfigDirs: [
14556
+ sibling.siblingConfigDir,
14557
+ ...accountsInGroupOf(sourceAccount, this.config.failoverAccounts, groups).map(
14558
+ configDirForAccount
14559
+ ),
14560
+ configDir
14561
+ ]
14562
+ });
14563
+ if (carry.kind === "carried" || carry.kind === "already-there") {
14564
+ carriedSessionId = carry.sessionId;
14565
+ } else {
14566
+ carryRefused = carry.reason;
14567
+ }
14568
+ }
14569
+ if (carryRefused) {
14570
+ replayReason = `sibling-${carryRefused}`;
14571
+ log.notice("replaying this conversation rather than carrying it across accounts", {
14572
+ sessionKey: sk,
14573
+ siblingKey: sibling.siblingKey,
14574
+ reason: carryRefused,
14575
+ account: sourceAccount,
14576
+ from: sibling.siblingAccount
14577
+ });
14175
14578
  } else {
14176
- replayReason = "sibling-carry-over-failed";
14579
+ deleteActiveProcess(sibling.siblingKey);
14580
+ const carried = transferClaudeSession(sibling.siblingKey, sk);
14581
+ if (carried) {
14582
+ if (carriedSessionId && carriedSessionId !== carried) {
14583
+ setClaudeSessionId(sk, carriedSessionId);
14584
+ }
14585
+ includeHistoryContext = false;
14586
+ log.notice(
14587
+ "continuing this conversation's claude session under the new model or effort instead of replaying it",
14588
+ {
14589
+ sessionKey: sk,
14590
+ siblingKey: sibling.siblingKey,
14591
+ matchedMessages: sibling.matched,
14592
+ ...sibling.siblingAccount ? { from: sibling.siblingAccount, account: sourceAccount } : {}
14593
+ }
14594
+ );
14595
+ } else {
14596
+ replayReason = "sibling-carry-over-failed";
14597
+ }
14177
14598
  }
14178
14599
  }
14179
14600
  }
@@ -14255,7 +14676,8 @@ var ClaudeCodeLanguageModel = class {
14255
14676
  }
14256
14677
  const failoverAccounts = failoverCandidates(
14257
14678
  this.config.failoverAccounts,
14258
- sourceAccount
14679
+ sourceAccount,
14680
+ groups
14259
14681
  );
14260
14682
  const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode && // A `doGenerate` caller takes the override and reports the plain
14261
14683
  // rate-limit error, never the form. See `TurnMode`.
@@ -14847,6 +15269,14 @@ var ClaudeCodeLanguageModel = class {
14847
15269
  });
14848
15270
  state.endTextBlock();
14849
15271
  }
15272
+ if (accountGroupNote) {
15273
+ controller.enqueue({
15274
+ type: "text-delta",
15275
+ id: state.startTextBlock(),
15276
+ delta: accountGroupNote
15277
+ });
15278
+ state.endTextBlock();
15279
+ }
14850
15280
  if (failoverNote) {
14851
15281
  controller.enqueue({
14852
15282
  type: "text-delta",
@@ -14859,7 +15289,10 @@ var ClaudeCodeLanguageModel = class {
14859
15289
  const completeResult = (msg) => {
14860
15290
  if (state.controllerClosed) return;
14861
15291
  if (self.config.resumeAfterRestart !== false && !compactionMode && msg.is_error !== true && typeof msg.session_id === "string") {
14862
- recordResumePoint(sk, msg.session_id, options.prompt, cliPath);
15292
+ recordResumePoint(sk, msg.session_id, options.prompt, cliPath, {
15293
+ configDir: resolveConfigDir(self.skillBridgeSpawn(failover).configDir),
15294
+ account: failover.target ?? sourceAccount
15295
+ });
14863
15296
  }
14864
15297
  if (deliverPendingCompletions(state)) {
14865
15298
  if (state.drainBuffer.length > 0) drainNow(state);
@@ -15383,8 +15816,8 @@ var ClaudeCodeLanguageModel = class {
15383
15816
  };
15384
15817
 
15385
15818
  // src/account-tier.ts
15386
- import { readFileSync as readFileSync7 } from "fs";
15387
- import path12 from "path";
15819
+ import { readFileSync as readFileSync8 } from "fs";
15820
+ import path13 from "path";
15388
15821
  var ORGANIZATION_TYPE_LABELS = {
15389
15822
  claude_max: "Max",
15390
15823
  claude_pro: "Pro",
@@ -15413,7 +15846,7 @@ function readAccountTier(account) {
15413
15846
  return label;
15414
15847
  }
15415
15848
  function readTierFromConfigDir(configDir, allowLegacyFallback) {
15416
- const candidates = [path12.join(configDir, ".claude.json")];
15849
+ const candidates = [path13.join(configDir, ".claude.json")];
15417
15850
  if (allowLegacyFallback) candidates.push(expandHome("~/.claude.json"));
15418
15851
  for (const candidate of candidates) {
15419
15852
  const oauth = readOauthAccount(candidate);
@@ -15426,7 +15859,7 @@ function readTierFromConfigDir(configDir, allowLegacyFallback) {
15426
15859
  function readOauthAccount(file) {
15427
15860
  let raw;
15428
15861
  try {
15429
- raw = readFileSync7(file, "utf8");
15862
+ raw = readFileSync8(file, "utf8");
15430
15863
  } catch {
15431
15864
  return void 0;
15432
15865
  }
@@ -15455,13 +15888,13 @@ function tierLabel(oauthAccount) {
15455
15888
  import {
15456
15889
  existsSync as existsSync5,
15457
15890
  mkdirSync as mkdirSync5,
15458
- readFileSync as readFileSync8,
15891
+ readFileSync as readFileSync9,
15459
15892
  realpathSync as realpathSync4,
15460
15893
  rmSync as rmSync3,
15461
15894
  writeFileSync as writeFileSync7
15462
15895
  } from "fs";
15463
- import { homedir as homedir9 } from "os";
15464
- import { dirname as dirname7, join as join13, resolve as resolve5 } from "path";
15896
+ import { homedir as homedir10 } from "os";
15897
+ import { dirname as dirname7, join as join14, resolve as resolve5 } from "path";
15465
15898
  import { fileURLToPath as fileURLToPath4 } from "url";
15466
15899
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
15467
15900
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
@@ -15469,20 +15902,20 @@ var alreadyRan = false;
15469
15902
  function candidateCacheRoots() {
15470
15903
  const xdg = process.env.XDG_CACHE_HOME;
15471
15904
  return [
15472
- xdg ? join13(xdg, "opencode") : null,
15473
- join13(homedir9(), ".cache", "opencode"),
15474
- join13(homedir9(), "Library", "Caches", "opencode")
15905
+ xdg ? join14(xdg, "opencode") : null,
15906
+ join14(homedir10(), ".cache", "opencode"),
15907
+ join14(homedir10(), "Library", "Caches", "opencode")
15475
15908
  ].filter((p) => Boolean(p));
15476
15909
  }
15477
15910
  function userOpencodeJsonPath() {
15478
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join13(homedir9(), ".config");
15479
- return join13(xdgConfig, "opencode", "opencode.json");
15911
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join14(homedir10(), ".config");
15912
+ return join14(xdgConfig, "opencode", "opencode.json");
15480
15913
  }
15481
15914
  function userIntendsToUseUnscoped() {
15482
15915
  const cfg = userOpencodeJsonPath();
15483
15916
  if (!existsSync5(cfg)) return false;
15484
15917
  try {
15485
- const json = JSON.parse(readFileSync8(cfg, "utf8"));
15918
+ const json = JSON.parse(readFileSync9(cfg, "utf8"));
15486
15919
  const plugins = json.plugin;
15487
15920
  if (!Array.isArray(plugins)) return false;
15488
15921
  return plugins.some(
@@ -15503,19 +15936,19 @@ function ourLoadedDir() {
15503
15936
  function ourVersion(dir) {
15504
15937
  if (!dir) return null;
15505
15938
  try {
15506
- const pkg = JSON.parse(readFileSync8(join13(dir, "package.json"), "utf8"));
15939
+ const pkg = JSON.parse(readFileSync9(join14(dir, "package.json"), "utf8"));
15507
15940
  return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
15508
15941
  } catch {
15509
15942
  return null;
15510
15943
  }
15511
15944
  }
15512
15945
  function markerPath() {
15513
- const stateRoot = process.env.XDG_STATE_HOME ?? join13(homedir9(), ".local", "state");
15514
- return join13(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
15946
+ const stateRoot = process.env.XDG_STATE_HOME ?? join14(homedir10(), ".local", "state");
15947
+ return join14(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
15515
15948
  }
15516
15949
  function markerVersion() {
15517
15950
  try {
15518
- const marker = JSON.parse(readFileSync8(markerPath(), "utf8"));
15951
+ const marker = JSON.parse(readFileSync9(markerPath(), "utf8"));
15519
15952
  return typeof marker.version === "string" ? marker.version : null;
15520
15953
  } catch {
15521
15954
  return null;
@@ -15560,7 +15993,7 @@ function cleanupStaleUnscopedInstall() {
15560
15993
  }
15561
15994
  function cleanupOne(cacheRoot, ourDir) {
15562
15995
  if (!existsSync5(cacheRoot)) return;
15563
- const stalePath = join13(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
15996
+ const stalePath = join14(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
15564
15997
  if (!existsSync5(stalePath)) return;
15565
15998
  let realStalePath = stalePath;
15566
15999
  try {
@@ -15568,11 +16001,11 @@ function cleanupOne(cacheRoot, ourDir) {
15568
16001
  } catch {
15569
16002
  }
15570
16003
  if (ourDir && realStalePath === ourDir) return;
15571
- const pkgJsonPath = join13(stalePath, "package.json");
16004
+ const pkgJsonPath = join14(stalePath, "package.json");
15572
16005
  if (!existsSync5(pkgJsonPath)) return;
15573
16006
  let pkg = {};
15574
16007
  try {
15575
- pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf8"));
16008
+ pkg = JSON.parse(readFileSync9(pkgJsonPath, "utf8"));
15576
16009
  } catch {
15577
16010
  return;
15578
16011
  }
@@ -15588,10 +16021,10 @@ function cleanupOne(cacheRoot, ourDir) {
15588
16021
  });
15589
16022
  return;
15590
16023
  }
15591
- const cachePkgJson = join13(cacheRoot, "package.json");
16024
+ const cachePkgJson = join14(cacheRoot, "package.json");
15592
16025
  if (!existsSync5(cachePkgJson)) return;
15593
16026
  try {
15594
- const cfg = JSON.parse(readFileSync8(cachePkgJson, "utf8"));
16027
+ const cfg = JSON.parse(readFileSync9(cachePkgJson, "utf8"));
15595
16028
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
15596
16029
  delete cfg.dependencies[STALE_PACKAGE_NAME];
15597
16030
  writeFileSync7(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -15671,7 +16104,7 @@ function registerOpencodeLogSink(client) {
15671
16104
  }
15672
16105
 
15673
16106
  // src/v2-client.ts
15674
- import path13 from "path";
16107
+ import path14 from "path";
15675
16108
  function formatAgentTypeList(agents) {
15676
16109
  const lines = agents.flatMap((agent) => {
15677
16110
  if (agent.hidden === true || agent.mode === "primary") return [];
@@ -15700,7 +16133,7 @@ function toV1Session(info) {
15700
16133
  if (!info || typeof info !== "object") return void 0;
15701
16134
  const session = info;
15702
16135
  const root = session.location?.directory;
15703
- const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path13.resolve(root, session.subpath) : root : void 0;
16136
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path14.resolve(root, session.subpath) : root : void 0;
15704
16137
  return { ...info, directory, parentID: session.parentID };
15705
16138
  }
15706
16139
  function toV1Messages(messages) {
@@ -16198,6 +16631,12 @@ function applyPermissionPreset(settings, defaultProxyTools) {
16198
16631
  }
16199
16632
  return resolved;
16200
16633
  }
16634
+ function accountGroups(settings) {
16635
+ return resolveAccountGroups(
16636
+ settings.accountGroups,
16637
+ settings.failoverAccounts ?? settings.accounts
16638
+ );
16639
+ }
16201
16640
  function createClaudeCode(settings = {}) {
16202
16641
  if (settings.logging) {
16203
16642
  configureLogger({
@@ -16228,6 +16667,11 @@ function createClaudeCode(settings = {}) {
16228
16667
  // unset option must stay unset rather than being written to one mode or
16229
16668
  // the other in only one of the two entry points (h #g194).
16230
16669
  accountFailover: settings.accountFailover,
16670
+ // Resolved once here rather than per turn, so the WARNs an invalid entry
16671
+ // raises are tied to provider creation and a turn reads a value that is
16672
+ // already validated. `failoverAccounts` is what the expansion configured,
16673
+ // which is exactly the set a group may name (h #g226).
16674
+ accountGroups: accountGroups(settings),
16231
16675
  providerID: settings.providerID,
16232
16676
  skipPermissions: preset?.skipPermissions ?? settings.skipPermissions ?? true,
16233
16677
  permissionMode: preset?.permissionMode ?? settings.permissionMode,
@@ -16235,6 +16679,9 @@ function createClaudeCode(settings = {}) {
16235
16679
  mcpConfig: settings.mcpConfig,
16236
16680
  strictMcpConfig: settings.strictMcpConfig,
16237
16681
  bridgeOpencodeMcp: settings.bridgeOpencodeMcp ?? true,
16682
+ // No `?? true` twin: reading opencode's credential store is opt-in, so
16683
+ // the absent value has to stay falsy.
16684
+ bridgeMcpOauthTokens: settings.bridgeMcpOauthTokens,
16238
16685
  controlRequestBehavior: preset?.controlRequestBehavior ?? settings.controlRequestBehavior ?? "allow",
16239
16686
  controlRequestToolBehaviors: preset ? void 0 : settings.controlRequestToolBehaviors,
16240
16687
  controlRequestDenyMessage: settings.controlRequestDenyMessage,