@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 +111 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +1 -1
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 =
|
|
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
|
-
|
|
3815
|
-
|
|
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("]"))
|
|
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)))
|
|
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(
|
|
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
|
|
18449
|
-
|
|
18450
|
-
|
|
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
|
-
|
|
18505
|
+
};
|
|
18506
|
+
let accountGroupNote = null;
|
|
18507
|
+
if (includeHistoryContext) {
|
|
18508
|
+
const from = crossGroupAccount();
|
|
18509
|
+
if (from) {
|
|
18458
18510
|
includeHistoryContext = false;
|
|
18459
|
-
accountGroupNote =
|
|
18460
|
-
|
|
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
|
-
|
|
18622
|
-
if (missed) {
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
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
|
-
|
|
18634
|
-
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
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);
|