@khalilgharbaoui/opencode-claude-code-plugin 0.61.0 → 0.61.2

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
@@ -3808,23 +3808,36 @@ import { createHash } from "crypto";
3808
3808
  var fingerprints = /* @__PURE__ */ new Map();
3809
3809
  var fingerprintSeq = 0;
3810
3810
  var MAX_FORK_FINGERPRINTS = 64;
3811
- var DIGEST_VERSION = 2;
3811
+ var DIGEST_VERSION = 3;
3812
3812
  var DCP_MESSAGE_ID_TAG = /<dcp-message-id(?:\s[^>]*)?>[^<]*<\/dcp-message-id>/g;
3813
3813
  var DCP_COMPACT_REF_LINE = /^@(?:\d+|b\d+|blocked)@(?: \[[^\]\n]*\])?$/;
3814
- function withoutDcpRefs(text) {
3815
- let out = text.includes("<dcp-message-id") ? text.replace(DCP_MESSAGE_ID_TAG, "") : text;
3814
+ var DCP_SYSTEM_REMINDER_BLOCK = /<dcp-system-reminder>[\s\S]*?<\/dcp-system-reminder>/g;
3815
+ function withoutDcpRefs(text, version) {
3816
+ let out = text;
3817
+ let removed = false;
3818
+ if (version >= 3 && out.includes("<dcp-system-reminder")) {
3819
+ out = out.replace(DCP_SYSTEM_REMINDER_BLOCK, "");
3820
+ removed = true;
3821
+ }
3822
+ if (out.includes("<dcp-message-id")) {
3823
+ out = out.replace(DCP_MESSAGE_ID_TAG, "");
3824
+ removed = true;
3825
+ }
3816
3826
  for (; ; ) {
3817
3827
  const trimmed = out.trimEnd();
3818
- if (!trimmed.endsWith("@") && !trimmed.endsWith("]")) return out;
3828
+ if (!trimmed.endsWith("@") && !trimmed.endsWith("]")) break;
3819
3829
  const lineStart = trimmed.lastIndexOf("\n") + 1;
3820
- if (!DCP_COMPACT_REF_LINE.test(trimmed.slice(lineStart))) return out;
3830
+ if (!DCP_COMPACT_REF_LINE.test(trimmed.slice(lineStart))) break;
3821
3831
  out = trimmed.slice(0, lineStart);
3832
+ removed = true;
3822
3833
  }
3834
+ if (version < 3 || !removed) return out;
3835
+ return out.replace(/\n{3,}/g, "\n\n").trim();
3823
3836
  }
3824
3837
  function hashPart(hash, part, version) {
3825
3838
  const p = part;
3826
3839
  const type = typeof p?.type === "string" ? p.type : "unknown";
3827
- const content = (text) => version >= 2 ? withoutDcpRefs(text) : text;
3840
+ const content = (text) => version >= 2 ? withoutDcpRefs(text, version) : text;
3828
3841
  switch (type) {
3829
3842
  case "text":
3830
3843
  hash.update("text\0");
@@ -3840,7 +3853,9 @@ function hashPart(hash, part, version) {
3840
3853
  hash.update("result\0");
3841
3854
  hash.update(String(p?.toolName ?? ""));
3842
3855
  hash.update("\0");
3843
- hash.update(stableJson(p?.output, version >= 2 ? withoutDcpRefs : void 0));
3856
+ hash.update(
3857
+ stableJson(p?.output, version >= 2 ? (text) => withoutDcpRefs(text, version) : void 0)
3858
+ );
3844
3859
  break;
3845
3860
  case "file":
3846
3861
  case "image": {
@@ -3888,7 +3903,7 @@ function messageDigest(msg, version) {
3888
3903
  hash.update("\0");
3889
3904
  if (typeof msg.content === "string") {
3890
3905
  hash.update("text\0");
3891
- hash.update(version >= 2 ? withoutDcpRefs(msg.content) : msg.content);
3906
+ hash.update(version >= 2 ? withoutDcpRefs(msg.content, version) : msg.content);
3892
3907
  hash.update("");
3893
3908
  } else if (Array.isArray(msg.content)) {
3894
3909
  for (const part of msg.content) hashPart(hash, part, version);
@@ -7429,6 +7444,7 @@ function findForeignAccountSibling(opts) {
7429
7444
  if (modelSiblingSignature(key, providers) !== signature) return void 0;
7430
7445
  const account = sessionKeyAccount(key, providers);
7431
7446
  if (account === void 0 || account === ownAccount) return void 0;
7447
+ if (opts.only && !opts.only(account)) return void 0;
7432
7448
  return { siblingKey: key, account };
7433
7449
  };
7434
7450
  for (const key of opts.extraKeys ?? []) {
@@ -9130,6 +9146,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
9130
9146
  "no configured account could take this turn; it ends here",
9131
9147
  "no proxyTools entry was recognised; nothing will be proxied this turn",
9132
9148
  "no stdout after envelope write; respawning claude process to resume conversation",
9149
+ "not replaying what this claude session missed: the conversation continued on an account in another group",
9133
9150
  "opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction, and stopped turns keep their proxy calls until the next message",
9134
9151
  "opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
9135
9152
  "pending proxy call arrived after stream close; rejecting",
@@ -9267,6 +9284,7 @@ var BUNDLE_DATA_ALLOWLIST = {
9267
9284
  historyLength: "count",
9268
9285
  matchedMessages: "count",
9269
9286
  messages: "count",
9287
+ userMessages: "count",
9270
9288
  inputTokens: "count",
9271
9289
  lastTextLength: "count",
9272
9290
  lines: "count",
@@ -12453,7 +12471,7 @@ function collectSkills(root, claimed, found) {
12453
12471
  if (!skill) continue;
12454
12472
  const winner = claimed.get(skill.name);
12455
12473
  if (winner) {
12456
- if (!sameSkill(winner, skill)) {
12474
+ if (!sameSkill(winner, skill) && firstSkillWarning(`dup:${skill.name}:${winner.dir}:${skill.dir}`)) {
12457
12475
  log.warn("two different skills share a name; bridging the one the nearest root claimed", {
12458
12476
  name: skill.name,
12459
12477
  bridged: winner.dir,
@@ -12466,6 +12484,12 @@ function collectSkills(root, claimed, found) {
12466
12484
  found.push(skill);
12467
12485
  }
12468
12486
  }
12487
+ var warnedSkillClashes = /* @__PURE__ */ new Set();
12488
+ function firstSkillWarning(key) {
12489
+ if (warnedSkillClashes.has(key)) return false;
12490
+ warnedSkillClashes.add(key);
12491
+ return true;
12492
+ }
12469
12493
  var byName = (a, b) => a.name.localeCompare(b.name);
12470
12494
  function discoverOpencodeSkills(cwd) {
12471
12495
  const found = [];
@@ -12566,7 +12590,7 @@ function dropNativelyLoadedSkills(skills, native) {
12566
12590
  }
12567
12591
  const reason = sameCopy ? "same-copy" : sameContent ? "same-content" : "name-taken";
12568
12592
  skipped.push({ skill, native: nameTaken, reason });
12569
- if (reason === "name-taken") {
12593
+ if (reason === "name-taken" && firstSkillWarning(`taken:${skill.name}:${nameTaken.dir}:${skill.dir}`)) {
12570
12594
  log.warn("claude already registers a different skill under this name; not bridging ours", {
12571
12595
  name: skill.name,
12572
12596
  claudeLoads: nameTaken.dir,
@@ -15204,7 +15228,8 @@ function resolvedProxyMcpTools(config, allEnabledServerNames, modelTools, taken,
15204
15228
  if (route === "withhold") withheld.push(server2);
15205
15229
  else if (route === "bridge") bridged.push(server2);
15206
15230
  }
15207
- if (codeMode && resolution.defs.length === 0) {
15231
+ if (codeMode && resolution.defs.length === 0 && !codeModeProxyWarned) {
15232
+ codeModeProxyWarned = true;
15208
15233
  log.warn(
15209
15234
  "proxyOpencodeMcpTools cannot route V2 Code Mode's MCP tools, which reach the model through execute; for Code Mode, explicitly allowlist execute in proxyOpencodeTools and use bridgeOpencodeMcp: false with strictMcpConfig: true",
15210
15235
  { servers: allEnabledServerNames }
@@ -15383,6 +15408,7 @@ function warnCliCapsOnInteractive(caps) {
15383
15408
  "agent maxTurns / maxBudgetUsd not applied: the interactive transport cannot pass print-only flags"
15384
15409
  );
15385
15410
  }
15411
+ var codeModeProxyWarned = false;
15386
15412
 
15387
15413
  // src/mcp-hot-reload.ts
15388
15414
  var DEFAULT_MCP_HOT_RELOAD_COOLDOWN_MS = 6e4;
@@ -18445,30 +18471,45 @@ var ClaudeCodeLanguageModel = class {
18445
18471
  carried: conversationOnTarget
18446
18472
  });
18447
18473
  }
18448
- let accountGroupNote = null;
18449
- if (!compactionMode && groups && accountProviders.size > 0 && includeHistoryContext && failoverAnswer?.kind !== "switch") {
18450
- const foreign = findForeignAccountSibling({
18474
+ let crossGroupAccountRead = false;
18475
+ let crossGroupAccountValue;
18476
+ const crossGroupAccount = () => {
18477
+ if (crossGroupAccountRead) return crossGroupAccountValue;
18478
+ crossGroupAccountRead = true;
18479
+ if (compactionMode || !groups || accountProviders.size === 0) return void 0;
18480
+ if (failoverAnswer?.kind === "switch") return void 0;
18481
+ crossGroupAccountValue = findForeignAccountSibling({
18451
18482
  sessionKey: sk,
18452
18483
  accountProviders,
18453
18484
  // The live process's own view, so the guard holds with
18454
18485
  // `resumeAfterRestart: false`, where the store is never written.
18455
- extraKeys: listClaudeSessionKeys()
18486
+ extraKeys: listClaudeSessionKeys(),
18487
+ only: (account) => !accountsShareGroup(sourceAccount, account, groups)
18488
+ })?.account;
18489
+ return crossGroupAccountValue;
18490
+ };
18491
+ const accountGroupNoteFor = (from) => formatAccountGroupNote({
18492
+ sourceAccount: from,
18493
+ sourceGroup: accountGroup(from, groups),
18494
+ targetAccount: sourceAccount,
18495
+ targetGroup: accountGroup(sourceAccount, groups)
18496
+ });
18497
+ const logAnotherAccountGroup = (from) => {
18498
+ log.notice("starting fresh on this account: it is in another account group", {
18499
+ sessionKey: sk,
18500
+ reason: "another-account-group",
18501
+ account: sourceAccount,
18502
+ from,
18503
+ group: accountGroup(sourceAccount, groups)
18456
18504
  });
18457
- if (foreign && !accountsShareGroup(sourceAccount, foreign.account, groups)) {
18505
+ };
18506
+ let accountGroupNote = null;
18507
+ if (includeHistoryContext) {
18508
+ const from = crossGroupAccount();
18509
+ if (from) {
18458
18510
  includeHistoryContext = false;
18459
- accountGroupNote = formatAccountGroupNote({
18460
- sourceAccount: foreign.account,
18461
- sourceGroup: accountGroup(foreign.account, groups),
18462
- targetAccount: sourceAccount,
18463
- targetGroup: accountGroup(sourceAccount, groups)
18464
- });
18465
- log.notice("starting fresh on this account: it is in another account group", {
18466
- sessionKey: sk,
18467
- reason: "another-account-group",
18468
- account: sourceAccount,
18469
- from: foreign.account,
18470
- group: accountGroup(sourceAccount, groups)
18471
- });
18511
+ accountGroupNote = accountGroupNoteFor(from);
18512
+ logAnotherAccountGroup(from);
18472
18513
  }
18473
18514
  }
18474
18515
  let forkFromClaudeSessionId;
@@ -18618,28 +18659,52 @@ var ClaudeCodeLanguageModel = class {
18618
18659
  });
18619
18660
  }
18620
18661
  const conversationChange = !compactionMode && !includeHistoryContext && failoverAnswer?.kind !== "switch" && !accountGroupNote && (!!getClaudeSessionId(sk) || !!getActiveProcess(sk)) ? classifyConversationChange(sk, filterSideQuestionHistory(options.prompt)) : null;
18621
- const missed = conversationChange?.kind === "missed" ? conversationChange : null;
18622
- if (missed) {
18623
- log.notice("replaying the messages this claude session missed: the conversation continued under another session key", {
18624
- sessionKey: sk,
18625
- messages: missed.messages.length,
18626
- userMessages: missed.userMessages
18627
- });
18662
+ let missed = null;
18663
+ if (conversationChange?.kind === "missed") {
18664
+ const from = crossGroupAccount();
18665
+ if (from) {
18666
+ log.notice(
18667
+ "not replaying what this claude session missed: the conversation continued on an account in another group",
18668
+ {
18669
+ sessionKey: sk,
18670
+ reason: "another-account-group",
18671
+ account: sourceAccount,
18672
+ from,
18673
+ group: accountGroup(sourceAccount, groups),
18674
+ messages: conversationChange.messages.length,
18675
+ userMessages: conversationChange.userMessages
18676
+ }
18677
+ );
18678
+ } else {
18679
+ missed = conversationChange;
18680
+ log.notice("replaying the messages this claude session missed: the conversation continued under another session key", {
18681
+ sessionKey: sk,
18682
+ messages: missed.messages.length,
18683
+ userMessages: missed.userMessages
18684
+ });
18685
+ }
18628
18686
  }
18629
18687
  let rewoundNote = null;
18630
18688
  if (conversationChange?.kind === "rewound") {
18631
18689
  await deleteActiveProcessAndWait(sk);
18632
18690
  deleteClaudeSessionId(sk);
18633
- includeHistoryContext = true;
18634
- replayReason = "history-rewound";
18635
- log.notice("replaying the conversation as text: the thread was edited or undone", {
18636
- sessionKey: sk,
18637
- reason: "history-rewound",
18638
- sharedMessages: conversationChange.sharedMessages,
18639
- removedMessages: conversationChange.removedMessages,
18640
- removedUserMessages: conversationChange.removedUserMessages
18641
- });
18642
- if (mode !== "generate") rewoundNote = formatConversationRewoundNote();
18691
+ const from = crossGroupAccount();
18692
+ if (from) {
18693
+ includeHistoryContext = false;
18694
+ accountGroupNote = accountGroupNote ?? accountGroupNoteFor(from);
18695
+ logAnotherAccountGroup(from);
18696
+ } else {
18697
+ includeHistoryContext = true;
18698
+ replayReason = "history-rewound";
18699
+ log.notice("replaying the conversation as text: the thread was edited or undone", {
18700
+ sessionKey: sk,
18701
+ reason: "history-rewound",
18702
+ sharedMessages: conversationChange.sharedMessages,
18703
+ removedMessages: conversationChange.removedMessages,
18704
+ removedUserMessages: conversationChange.removedUserMessages
18705
+ });
18706
+ if (mode !== "generate") rewoundNote = formatConversationRewoundNote();
18707
+ }
18643
18708
  }
18644
18709
  if (this.config.forkSessions === true && !compactionMode) {
18645
18710
  recordForkFingerprint(sk, options.prompt, cliPath);