@khalilgharbaoui/opencode-claude-code-plugin 0.62.1 → 0.63.1
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 +372 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +10 -3
package/dist/index.js
CHANGED
|
@@ -2045,6 +2045,10 @@ function clearAccountFailoverQuestions(sessionKey2) {
|
|
|
2045
2045
|
if (key.startsWith(prefix)) pendingQuestions2.delete(key);
|
|
2046
2046
|
}
|
|
2047
2047
|
}
|
|
2048
|
+
function hasAccountFailoverQuestions(sessionKey2) {
|
|
2049
|
+
const prefix = `${sessionKey2}${KEY_SEPARATOR2}`;
|
|
2050
|
+
return [...pendingQuestions2.keys()].some((key) => key.startsWith(prefix));
|
|
2051
|
+
}
|
|
2048
2052
|
function describeReset(resetsAt) {
|
|
2049
2053
|
return formatResetsAt(resetsAt);
|
|
2050
2054
|
}
|
|
@@ -3164,6 +3168,10 @@ function clearSubagentDispatchQuestions(sessionKey2) {
|
|
|
3164
3168
|
if (key.startsWith(prefix)) pending.delete(key);
|
|
3165
3169
|
}
|
|
3166
3170
|
}
|
|
3171
|
+
function hasPendingSubagentDispatch(sessionKey2) {
|
|
3172
|
+
const prefix = `${sessionKey2}${KEY_SEPARATOR3}`;
|
|
3173
|
+
return [...pending.keys()].some((key) => key.startsWith(prefix));
|
|
3174
|
+
}
|
|
3167
3175
|
function isSubagentDispatchActive(input) {
|
|
3168
3176
|
if (input.compactionMode) return false;
|
|
3169
3177
|
if (input.childSession) return false;
|
|
@@ -5833,6 +5841,9 @@ function isPendingProxyCallChannelClosed(call) {
|
|
|
5833
5841
|
function findPendingProxyCall(toolCallId) {
|
|
5834
5842
|
return pendingByCallId.get(toolCallId);
|
|
5835
5843
|
}
|
|
5844
|
+
function hasAnyPendingProxyCall() {
|
|
5845
|
+
return pendingByCallId.size > 0;
|
|
5846
|
+
}
|
|
5836
5847
|
function getPendingProxyCalls(sessionKey2) {
|
|
5837
5848
|
const s = callIdsBySession.get(sessionKey2);
|
|
5838
5849
|
if (!s || s.size === 0) return [];
|
|
@@ -6074,6 +6085,10 @@ function noteExplicitTaskModels(parentSessionId, tasks, now = Date.now()) {
|
|
|
6074
6085
|
}
|
|
6075
6086
|
return noted;
|
|
6076
6087
|
}
|
|
6088
|
+
function hasUnclaimedExplicitTaskModels(now = Date.now()) {
|
|
6089
|
+
pruneClaims2(now);
|
|
6090
|
+
return claims2.length > 0;
|
|
6091
|
+
}
|
|
6077
6092
|
function explicitTaskModelFor(sessionId) {
|
|
6078
6093
|
return sessionModels.get(sessionId);
|
|
6079
6094
|
}
|
|
@@ -7895,6 +7910,12 @@ function noteRelayMemberServing() {
|
|
|
7895
7910
|
ownServed = true;
|
|
7896
7911
|
join10();
|
|
7897
7912
|
}
|
|
7913
|
+
function relayMemberHasServed() {
|
|
7914
|
+
return ownServed;
|
|
7915
|
+
}
|
|
7916
|
+
function leaveSessionEventRelay() {
|
|
7917
|
+
leave();
|
|
7918
|
+
}
|
|
7898
7919
|
function openRelayFollower() {
|
|
7899
7920
|
const state = relayState();
|
|
7900
7921
|
join10();
|
|
@@ -8327,6 +8348,10 @@ function setClaudeSessionId(key, sessionId) {
|
|
|
8327
8348
|
capClaudeSessions();
|
|
8328
8349
|
}
|
|
8329
8350
|
function deleteClaudeSessionId(key) {
|
|
8351
|
+
forgetClaudeSessionInMemory(key);
|
|
8352
|
+
forgetResumePoint(key);
|
|
8353
|
+
}
|
|
8354
|
+
function forgetClaudeSessionInMemory(key) {
|
|
8330
8355
|
clearExitPlanModeQuestions(key);
|
|
8331
8356
|
clearAccountFailoverQuestions(key);
|
|
8332
8357
|
clearSubagentDispatchQuestions(key);
|
|
@@ -8335,7 +8360,52 @@ function deleteClaudeSessionId(key) {
|
|
|
8335
8360
|
claudeSessions.delete(key);
|
|
8336
8361
|
forgetForkFingerprint(key);
|
|
8337
8362
|
forgetServedConversation(key);
|
|
8338
|
-
|
|
8363
|
+
}
|
|
8364
|
+
function keyBelongsToSession(key, sessionID, ap) {
|
|
8365
|
+
return ap?.opencodeSessionID === sessionID || describeSessionKey(key).session === sessionID;
|
|
8366
|
+
}
|
|
8367
|
+
function keyHeldHere(key) {
|
|
8368
|
+
return claudeSessionIsWriting(key) || hasAccountFailoverQuestions(key) || hasPendingSubagentDispatch(key);
|
|
8369
|
+
}
|
|
8370
|
+
function sessionHeldHere(sessionID) {
|
|
8371
|
+
if (!sessionID || sessionID === "default") return false;
|
|
8372
|
+
for (const [key, ap] of activeProcesses) {
|
|
8373
|
+
if (keyBelongsToSession(key, sessionID, ap) && keyHeldHere(key)) return true;
|
|
8374
|
+
}
|
|
8375
|
+
for (const key of claudeSessions.keys()) {
|
|
8376
|
+
if (keyBelongsToSession(key, sessionID) && keyHeldHere(key)) return true;
|
|
8377
|
+
}
|
|
8378
|
+
return false;
|
|
8379
|
+
}
|
|
8380
|
+
function noWorkInFlightHere() {
|
|
8381
|
+
for (const ap of activeProcesses.values()) {
|
|
8382
|
+
if (isTurnInFlight(ap)) return false;
|
|
8383
|
+
}
|
|
8384
|
+
return !hasAnyPendingProxyCall() && !hasUnclaimedExplicitTaskModels();
|
|
8385
|
+
}
|
|
8386
|
+
function holdsNoClaudeState() {
|
|
8387
|
+
return activeProcesses.size === 0 && claudeSessions.size === 0 && !hasAnyPendingProxyCall();
|
|
8388
|
+
}
|
|
8389
|
+
async function releaseIdleForHandover(sessionID) {
|
|
8390
|
+
if (sessionID === "default") return [];
|
|
8391
|
+
const belongs = (key, ap) => sessionID === void 0 || keyBelongsToSession(key, sessionID, ap);
|
|
8392
|
+
const released = [];
|
|
8393
|
+
const exits = [];
|
|
8394
|
+
for (const [key, ap] of [...activeProcesses]) {
|
|
8395
|
+
if (!belongs(key, ap) || keyHeldHere(key)) continue;
|
|
8396
|
+
exits.push(deleteActiveProcessAndWait(key));
|
|
8397
|
+
released.push(key);
|
|
8398
|
+
}
|
|
8399
|
+
await Promise.all(exits);
|
|
8400
|
+
for (const key of [...claudeSessions.keys()]) {
|
|
8401
|
+
if (!belongs(key) || keyHeldHere(key)) continue;
|
|
8402
|
+
forgetClaudeSessionInMemory(key);
|
|
8403
|
+
if (!released.includes(key)) released.push(key);
|
|
8404
|
+
}
|
|
8405
|
+
if (released.length > 0) {
|
|
8406
|
+
log.info("handed claude sessions to another copy of the plugin", { sessionID, released });
|
|
8407
|
+
}
|
|
8408
|
+
return released;
|
|
8339
8409
|
}
|
|
8340
8410
|
function transferClaudeSession(fromKey, toKey) {
|
|
8341
8411
|
if (fromKey === toKey) return void 0;
|
|
@@ -8359,7 +8429,20 @@ function transferClaudeSession(fromKey, toKey) {
|
|
|
8359
8429
|
function effortSessionKey(baseKey, effort) {
|
|
8360
8430
|
return effort ? `${baseKey}::effort=${effort}` : baseKey;
|
|
8361
8431
|
}
|
|
8362
|
-
function
|
|
8432
|
+
function spawnIdentity(key) {
|
|
8433
|
+
const parts = key.split("::");
|
|
8434
|
+
if (parts.length > 1 && parts[parts.length - 1].startsWith("effort=")) parts.pop();
|
|
8435
|
+
const last = parts.length - 1;
|
|
8436
|
+
if (parts[last].startsWith("context=")) {
|
|
8437
|
+
try {
|
|
8438
|
+
const context = JSON.parse(parts[last].slice("context=".length));
|
|
8439
|
+
if (Array.isArray(context)) parts[last] = `context=${JSON.stringify(context.slice(0, 2))}`;
|
|
8440
|
+
} catch {
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
return parts.join("::");
|
|
8444
|
+
}
|
|
8445
|
+
function invalidateOtherSpawnVariants(baseKey, effort) {
|
|
8363
8446
|
const levels = [
|
|
8364
8447
|
void 0,
|
|
8365
8448
|
"minimal",
|
|
@@ -8369,16 +8452,23 @@ function invalidateOtherEffortSessions(baseKey, effort) {
|
|
|
8369
8452
|
"xhigh",
|
|
8370
8453
|
"max"
|
|
8371
8454
|
];
|
|
8372
|
-
const
|
|
8455
|
+
const currentKey = effortSessionKey(baseKey, effort);
|
|
8456
|
+
const identity = spawnIdentity(currentKey);
|
|
8457
|
+
const staleKeys = [
|
|
8458
|
+
.../* @__PURE__ */ new Set([
|
|
8459
|
+
...[...claudeSessions.keys()].reverse(),
|
|
8460
|
+
...levels.map((level) => effortSessionKey(baseKey, level)),
|
|
8461
|
+
...activeProcesses.keys()
|
|
8462
|
+
])
|
|
8463
|
+
].filter((key) => key !== currentKey && spawnIdentity(key) === identity);
|
|
8373
8464
|
for (const key of staleKeys) {
|
|
8374
8465
|
const active = activeProcesses.get(key);
|
|
8375
8466
|
if (getPendingProxyCalls(key).length || hasExitPlanModeQuestions(key) || active?.pendingProxyCompletions?.size || active && (active.lineEmitter.listenerCount("line") > 0 || isSideQuestionPending(active))) {
|
|
8376
8467
|
throw new Error(
|
|
8377
|
-
"Cannot change reasoning effort while
|
|
8468
|
+
"Cannot change reasoning effort, prompt cache TTL, maxTurns or maxBudgetUsd while this conversation's previous claude process has pending work. Finish that work under its original settings first."
|
|
8378
8469
|
);
|
|
8379
8470
|
}
|
|
8380
8471
|
}
|
|
8381
|
-
const currentKey = effortSessionKey(baseKey, effort);
|
|
8382
8472
|
for (const key of staleKeys) {
|
|
8383
8473
|
deleteActiveProcess(key);
|
|
8384
8474
|
if (transferClaudeSession(key, currentKey)) {
|
|
@@ -9166,6 +9256,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
9166
9256
|
"agent maxTurns refused: not a positive whole number",
|
|
9167
9257
|
"agent model override refused: unknown model",
|
|
9168
9258
|
"agent prompt cache ttl refused: unknown value",
|
|
9259
|
+
"an older plugin build failed to release its claude sessions",
|
|
9169
9260
|
"an opencode read deny rule could not be mapped onto Claude's own Read; that path stays readable by the CLI's own file tools",
|
|
9170
9261
|
"auto-continuation stopped",
|
|
9171
9262
|
"auto-continuing incomplete claude result",
|
|
@@ -9312,6 +9403,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
9312
9403
|
"task_batch resolving with child results missing",
|
|
9313
9404
|
"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",
|
|
9314
9405
|
"the interactive turn did not take a forwarded user message",
|
|
9406
|
+
"the plugin build chosen for this turn could not take it; running it on this one",
|
|
9315
9407
|
"this opencode process is running an older plugin build than the one on disk",
|
|
9316
9408
|
"timed out pending proxy call",
|
|
9317
9409
|
"turn abort listener threw",
|
|
@@ -10017,7 +10109,7 @@ function emptyMapping(enabled) {
|
|
|
10017
10109
|
}
|
|
10018
10110
|
function mapReadRules(rules, options = {}) {
|
|
10019
10111
|
const deny = [];
|
|
10020
|
-
const
|
|
10112
|
+
const ask2 = [];
|
|
10021
10113
|
const unmapped = [];
|
|
10022
10114
|
const shadowedAllows = [];
|
|
10023
10115
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10025,7 +10117,7 @@ function mapReadRules(rules, options = {}) {
|
|
|
10025
10117
|
rules.forEach((rule, index) => {
|
|
10026
10118
|
if (rule.effect === "ask") {
|
|
10027
10119
|
if (supersededBy(index, rule.pattern, () => true)) return;
|
|
10028
|
-
if (!
|
|
10120
|
+
if (!ask2.includes(rule.pattern)) ask2.push(rule.pattern);
|
|
10029
10121
|
return;
|
|
10030
10122
|
}
|
|
10031
10123
|
if (rule.effect !== "deny") return;
|
|
@@ -10046,7 +10138,7 @@ function mapReadRules(rules, options = {}) {
|
|
|
10046
10138
|
if (!shadowedAllows.includes(later.pattern)) shadowedAllows.push(later.pattern);
|
|
10047
10139
|
}
|
|
10048
10140
|
});
|
|
10049
|
-
return { deny, ask, unmapped, shadowedAllows };
|
|
10141
|
+
return { deny, ask: ask2, unmapped, shadowedAllows };
|
|
10050
10142
|
}
|
|
10051
10143
|
function resolveReadRuleMapping(input) {
|
|
10052
10144
|
if (input.enabled === false) return emptyMapping(false);
|
|
@@ -10801,6 +10893,159 @@ import * as fs7 from "fs";
|
|
|
10801
10893
|
import * as path10 from "path";
|
|
10802
10894
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10803
10895
|
|
|
10896
|
+
// src/plugin-copies.ts
|
|
10897
|
+
var ROUTED_TURN_LIMIT_MS = 2 * 60 * 6e4;
|
|
10898
|
+
var COPIES_KEY = /* @__PURE__ */ Symbol.for("@khalilgharbaoui/opencode-claude-code-plugin/copies/v1");
|
|
10899
|
+
function copiesState() {
|
|
10900
|
+
const holder = globalThis;
|
|
10901
|
+
let state = holder[COPIES_KEY];
|
|
10902
|
+
if (!state) {
|
|
10903
|
+
state = { copies: [], active: void 0, switching: void 0, turns: /* @__PURE__ */ new Map() };
|
|
10904
|
+
holder[COPIES_KEY] = state;
|
|
10905
|
+
}
|
|
10906
|
+
return state;
|
|
10907
|
+
}
|
|
10908
|
+
function routedTurns(state, now) {
|
|
10909
|
+
for (const [token, turn] of state.turns) {
|
|
10910
|
+
if (now - turn.startedAt > ROUTED_TURN_LIMIT_MS) state.turns.delete(token);
|
|
10911
|
+
}
|
|
10912
|
+
return [...state.turns.values()];
|
|
10913
|
+
}
|
|
10914
|
+
function ask(question, fallback) {
|
|
10915
|
+
try {
|
|
10916
|
+
return question();
|
|
10917
|
+
} catch {
|
|
10918
|
+
return fallback;
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
async function releaseQuietly(copy, sessionID) {
|
|
10922
|
+
try {
|
|
10923
|
+
await copy.release(sessionID);
|
|
10924
|
+
} catch (err) {
|
|
10925
|
+
log.warn("an older plugin build failed to release its claude sessions", {
|
|
10926
|
+
version: copy.version,
|
|
10927
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10928
|
+
});
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
function registerPluginCopy(copy) {
|
|
10932
|
+
const state = copiesState();
|
|
10933
|
+
if (state.copies.includes(copy)) return;
|
|
10934
|
+
state.copies.push(copy);
|
|
10935
|
+
state.active ??= copy;
|
|
10936
|
+
}
|
|
10937
|
+
function unregisterPluginCopy(copy) {
|
|
10938
|
+
const state = copiesState();
|
|
10939
|
+
const index = state.copies.indexOf(copy);
|
|
10940
|
+
if (index !== -1) state.copies.splice(index, 1);
|
|
10941
|
+
if (state.active === copy) state.active = void 0;
|
|
10942
|
+
}
|
|
10943
|
+
function isActivePluginCopy(copy) {
|
|
10944
|
+
return copiesState().active === copy;
|
|
10945
|
+
}
|
|
10946
|
+
function processLoadedBuild(build) {
|
|
10947
|
+
return copiesState().copies.some(
|
|
10948
|
+
(copy) => !copy.disposed && copy.version === build.version && copy.build.entryPath === build.entryPath && copy.build.mtimeMs === build.mtimeMs && copy.build.size === build.size
|
|
10949
|
+
);
|
|
10950
|
+
}
|
|
10951
|
+
function switchWhenQuiet(state, now) {
|
|
10952
|
+
if (state.switching) return;
|
|
10953
|
+
const newest = [...state.copies].reverse().find((copy) => !copy.disposed);
|
|
10954
|
+
if (!newest || newest === state.active) return;
|
|
10955
|
+
const others = state.copies.filter((copy) => copy !== newest);
|
|
10956
|
+
const busy = new Set(routedTurns(state, now).map((turn) => turn.copy));
|
|
10957
|
+
if (!others.every((copy) => !busy.has(copy) && ask(() => copy.quiet(), false))) return;
|
|
10958
|
+
const previous = state.active;
|
|
10959
|
+
state.active = newest;
|
|
10960
|
+
log.info("switched to the newest plugin build", {
|
|
10961
|
+
from: previous?.version,
|
|
10962
|
+
to: newest.version
|
|
10963
|
+
});
|
|
10964
|
+
state.switching = Promise.all(others.map((copy) => releaseQuietly(copy))).then(() => {
|
|
10965
|
+
state.switching = void 0;
|
|
10966
|
+
});
|
|
10967
|
+
}
|
|
10968
|
+
function servingCopy(state, sessionID, own) {
|
|
10969
|
+
if (sessionID) {
|
|
10970
|
+
const holder = state.copies.find((copy) => ask(() => copy.holds(sessionID), false));
|
|
10971
|
+
if (holder) return holder;
|
|
10972
|
+
}
|
|
10973
|
+
return state.active ?? own;
|
|
10974
|
+
}
|
|
10975
|
+
function followStream(source, end) {
|
|
10976
|
+
const reader = source.getReader();
|
|
10977
|
+
return new ReadableStream({
|
|
10978
|
+
async pull(controller) {
|
|
10979
|
+
try {
|
|
10980
|
+
const { done, value } = await reader.read();
|
|
10981
|
+
if (done) {
|
|
10982
|
+
end();
|
|
10983
|
+
controller.close();
|
|
10984
|
+
return;
|
|
10985
|
+
}
|
|
10986
|
+
controller.enqueue(value);
|
|
10987
|
+
} catch (err) {
|
|
10988
|
+
end();
|
|
10989
|
+
controller.error(err);
|
|
10990
|
+
}
|
|
10991
|
+
},
|
|
10992
|
+
async cancel(reason) {
|
|
10993
|
+
end();
|
|
10994
|
+
await reader.cancel(reason);
|
|
10995
|
+
}
|
|
10996
|
+
});
|
|
10997
|
+
}
|
|
10998
|
+
function untilTurnEnds(result, end) {
|
|
10999
|
+
const stream = result?.stream;
|
|
11000
|
+
if (!(stream instanceof ReadableStream)) {
|
|
11001
|
+
end();
|
|
11002
|
+
return result;
|
|
11003
|
+
}
|
|
11004
|
+
return { ...result, stream: followStream(stream, end) };
|
|
11005
|
+
}
|
|
11006
|
+
async function routeTurn(input) {
|
|
11007
|
+
const state = copiesState();
|
|
11008
|
+
if (state.copies.length === 0) return input.runHere();
|
|
11009
|
+
const now = input.now ?? Date.now;
|
|
11010
|
+
if (state.switching) await state.switching;
|
|
11011
|
+
switchWhenQuiet(state, now());
|
|
11012
|
+
if (state.switching) await state.switching;
|
|
11013
|
+
const sessionID = input.sessionID && input.sessionID !== "default" ? input.sessionID : void 0;
|
|
11014
|
+
const target = servingCopy(state, sessionID, input.own);
|
|
11015
|
+
const token = {};
|
|
11016
|
+
state.turns.set(token, { copy: target, sessionID, startedAt: now() });
|
|
11017
|
+
let ended = false;
|
|
11018
|
+
const end = () => {
|
|
11019
|
+
if (ended) return;
|
|
11020
|
+
ended = true;
|
|
11021
|
+
state.turns.delete(token);
|
|
11022
|
+
};
|
|
11023
|
+
try {
|
|
11024
|
+
if (sessionID) {
|
|
11025
|
+
await Promise.all(
|
|
11026
|
+
state.copies.filter((copy) => copy !== target).map((copy) => releaseQuietly(copy, sessionID))
|
|
11027
|
+
);
|
|
11028
|
+
}
|
|
11029
|
+
if (target === input.own) return untilTurnEnds(await input.runHere(), end);
|
|
11030
|
+
let model;
|
|
11031
|
+
try {
|
|
11032
|
+
model = await target.modelFor(input.turn);
|
|
11033
|
+
} catch (err) {
|
|
11034
|
+
log.warn("the plugin build chosen for this turn could not take it; running it on this one", {
|
|
11035
|
+
version: target.version,
|
|
11036
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11037
|
+
});
|
|
11038
|
+
const turn = state.turns.get(token);
|
|
11039
|
+
if (turn) turn.copy = input.own;
|
|
11040
|
+
return untilTurnEnds(await input.runHere(), end);
|
|
11041
|
+
}
|
|
11042
|
+
return untilTurnEnds(await input.runOn(model), end);
|
|
11043
|
+
} catch (err) {
|
|
11044
|
+
end();
|
|
11045
|
+
throw err;
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
|
|
10804
11049
|
// src/startup-diagnostics.ts
|
|
10805
11050
|
import { execFile as execFile3 } from "child_process";
|
|
10806
11051
|
import * as fs6 from "fs";
|
|
@@ -11065,6 +11310,8 @@ function createStaleBuildWatch(io) {
|
|
|
11065
11310
|
const warned = /* @__PURE__ */ new Set();
|
|
11066
11311
|
let checkedAt;
|
|
11067
11312
|
let cached2 = null;
|
|
11313
|
+
let cachedOnDisk;
|
|
11314
|
+
const loadedHere = (onDisk) => onDisk !== void 0 && io.loadedElsewhere?.(onDisk) === true;
|
|
11068
11315
|
const read = () => {
|
|
11069
11316
|
let onDisk;
|
|
11070
11317
|
try {
|
|
@@ -11082,18 +11329,21 @@ function createStaleBuildWatch(io) {
|
|
|
11082
11329
|
},
|
|
11083
11330
|
check() {
|
|
11084
11331
|
const now = io.now();
|
|
11085
|
-
if (checkedAt !== void 0 && now - checkedAt < throttleMs)
|
|
11332
|
+
if (checkedAt !== void 0 && now - checkedAt < throttleMs) {
|
|
11333
|
+
return cached2 && !loadedHere(cachedOnDisk) ? cached2 : null;
|
|
11334
|
+
}
|
|
11086
11335
|
checkedAt = now;
|
|
11087
11336
|
const status = read();
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11337
|
+
cachedOnDisk = status.onDisk;
|
|
11338
|
+
cached2 = status.stale && loadedHere(status.onDisk) ? null : status.stale;
|
|
11339
|
+
if (cached2 && status.onDisk) {
|
|
11340
|
+
const identity = `${cached2.kind}:${status.onDisk.version}:${status.onDisk.mtimeMs}:${status.onDisk.size}`;
|
|
11091
11341
|
if (!warned.has(identity)) {
|
|
11092
11342
|
warned.add(identity);
|
|
11093
11343
|
warn({
|
|
11094
|
-
kind:
|
|
11095
|
-
loadedVersion:
|
|
11096
|
-
onDiskVersion:
|
|
11344
|
+
kind: cached2.kind,
|
|
11345
|
+
loadedVersion: cached2.loadedVersion,
|
|
11346
|
+
onDiskVersion: cached2.onDiskVersion,
|
|
11097
11347
|
path: io.loaded.entryPath
|
|
11098
11348
|
});
|
|
11099
11349
|
}
|
|
@@ -11152,7 +11402,8 @@ function staleBuildWatch() {
|
|
|
11152
11402
|
watch = createStaleBuildWatch({
|
|
11153
11403
|
loaded,
|
|
11154
11404
|
readOnDisk: () => readOnDiskBuild(loaded.entryPath),
|
|
11155
|
-
now: () => Date.now()
|
|
11405
|
+
now: () => Date.now(),
|
|
11406
|
+
loadedElsewhere: (onDisk) => processLoadedBuild({ entryPath: loaded.entryPath, ...onDisk })
|
|
11156
11407
|
});
|
|
11157
11408
|
}
|
|
11158
11409
|
return watch;
|
|
@@ -18351,7 +18602,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18351
18602
|
});
|
|
18352
18603
|
}
|
|
18353
18604
|
}
|
|
18354
|
-
if (!compactionMode)
|
|
18605
|
+
if (!compactionMode) invalidateOtherSpawnVariants(baseKey, reasoningEffort);
|
|
18355
18606
|
if (!compactionMode && affinity !== "default") {
|
|
18356
18607
|
recordSessionSpawn(affinity, {
|
|
18357
18608
|
model: stripAccountSuffix(failover.modelId),
|
|
@@ -20900,9 +21151,97 @@ function handleSessionEventHere(event) {
|
|
|
20900
21151
|
const stopped = stoppedSessionIdV2(event);
|
|
20901
21152
|
if (stopped) releaseParkedProxyCallsForSession(stopped);
|
|
20902
21153
|
}
|
|
21154
|
+
var ownCopy;
|
|
21155
|
+
var liveSetups = 0;
|
|
21156
|
+
var plannedByDirectory = /* @__PURE__ */ new Map();
|
|
21157
|
+
var forwardedProviders = /* @__PURE__ */ new Map();
|
|
21158
|
+
var MAX_FORWARDED_PROVIDERS = 32;
|
|
21159
|
+
function plannedSettings(directory, providerID) {
|
|
21160
|
+
const here = plannedByDirectory.get(directory)?.get(providerID);
|
|
21161
|
+
if (here) return here;
|
|
21162
|
+
for (const planned of plannedByDirectory.values()) {
|
|
21163
|
+
const found = planned.get(providerID);
|
|
21164
|
+
if (found) return found;
|
|
21165
|
+
}
|
|
21166
|
+
return void 0;
|
|
21167
|
+
}
|
|
21168
|
+
function pluginCopyFor(deps) {
|
|
21169
|
+
if (ownCopy) return ownCopy;
|
|
21170
|
+
const loaded = staleBuildWatch().loaded;
|
|
21171
|
+
ownCopy = {
|
|
21172
|
+
id: randomUUID8(),
|
|
21173
|
+
version: loaded.version,
|
|
21174
|
+
build: { entryPath: loaded.entryPath, mtimeMs: loaded.mtimeMs, size: loaded.size },
|
|
21175
|
+
disposed: false,
|
|
21176
|
+
holds: (sessionID) => sessionHeldHere(sessionID),
|
|
21177
|
+
quiet: () => noWorkInFlightHere(),
|
|
21178
|
+
release: (sessionID) => releaseIdleForHandover(sessionID),
|
|
21179
|
+
async modelFor(turn) {
|
|
21180
|
+
const planned = plannedSettings(turn.directory, turn.providerID);
|
|
21181
|
+
if (!planned) throw new Error(`no provider ${turn.providerID} is planned in this build`);
|
|
21182
|
+
const cacheKey = JSON.stringify([turn.directory ?? null, turn.providerID, turn.sdkOptions]);
|
|
21183
|
+
let provider = forwardedProviders.get(cacheKey);
|
|
21184
|
+
if (!provider) {
|
|
21185
|
+
const settings = await resolveSdkSettings(turn.providerID, planned, turn.sdkOptions);
|
|
21186
|
+
provider = deps.createProvider(settings);
|
|
21187
|
+
forwardedProviders.set(cacheKey, provider);
|
|
21188
|
+
while (forwardedProviders.size > MAX_FORWARDED_PROVIDERS) {
|
|
21189
|
+
const oldest = forwardedProviders.keys().next().value;
|
|
21190
|
+
if (oldest === void 0) break;
|
|
21191
|
+
forwardedProviders.delete(oldest);
|
|
21192
|
+
}
|
|
21193
|
+
}
|
|
21194
|
+
return provider.languageModel(turn.modelId);
|
|
21195
|
+
}
|
|
21196
|
+
};
|
|
21197
|
+
return ownCopy;
|
|
21198
|
+
}
|
|
21199
|
+
function retireWhenSpent() {
|
|
21200
|
+
const copy = ownCopy;
|
|
21201
|
+
if (!copy?.disposed || isActivePluginCopy(copy) || !holdsNoClaudeState()) return;
|
|
21202
|
+
unregisterPluginCopy(copy);
|
|
21203
|
+
leaveSessionEventRelay();
|
|
21204
|
+
log.info("a disposed plugin build left the process", { version: copy.version });
|
|
21205
|
+
}
|
|
21206
|
+
function routedModel(model, copy, turn) {
|
|
21207
|
+
const sessionOf = (options) => resolveSessionAffinity(
|
|
21208
|
+
options.headers,
|
|
21209
|
+
options.providerOptions,
|
|
21210
|
+
turn.providerID
|
|
21211
|
+
);
|
|
21212
|
+
return {
|
|
21213
|
+
specificationVersion: model.specificationVersion,
|
|
21214
|
+
provider: model.provider,
|
|
21215
|
+
modelId: model.modelId,
|
|
21216
|
+
supportedUrls: model.supportedUrls,
|
|
21217
|
+
doStream: (options) => routeTurn({
|
|
21218
|
+
own: copy,
|
|
21219
|
+
turn,
|
|
21220
|
+
sessionID: sessionOf(options),
|
|
21221
|
+
runHere: () => model.doStream(options),
|
|
21222
|
+
runOn: (other) => other.doStream(options)
|
|
21223
|
+
}),
|
|
21224
|
+
doGenerate: (options) => routeTurn({
|
|
21225
|
+
own: copy,
|
|
21226
|
+
turn,
|
|
21227
|
+
sessionID: sessionOf(options),
|
|
21228
|
+
runHere: () => model.doGenerate(options),
|
|
21229
|
+
runOn: (other) => other.doGenerate(options)
|
|
21230
|
+
})
|
|
21231
|
+
};
|
|
21232
|
+
}
|
|
21233
|
+
function routedProvider(provider, copy, base) {
|
|
21234
|
+
const languageModel = (modelId) => routedModel(provider.languageModel(modelId), copy, { ...base, modelId });
|
|
21235
|
+
return Object.assign((modelId) => languageModel(modelId), provider, {
|
|
21236
|
+
languageModel
|
|
21237
|
+
});
|
|
21238
|
+
}
|
|
20903
21239
|
var closeOwnFeed;
|
|
20904
21240
|
defineOwnRelayMember({
|
|
20905
|
-
handle: (event) =>
|
|
21241
|
+
handle: (event) => {
|
|
21242
|
+
handleSessionEventHere(event);
|
|
21243
|
+
retireWhenSpent();
|
|
21244
|
+
},
|
|
20906
21245
|
feedOpened: () => {
|
|
20907
21246
|
closeOwnFeed ??= openSessionRunEventFeed();
|
|
20908
21247
|
},
|
|
@@ -21015,6 +21354,11 @@ function createV2Setup(deps) {
|
|
|
21015
21354
|
}
|
|
21016
21355
|
}
|
|
21017
21356
|
const planned = /* @__PURE__ */ new Map();
|
|
21357
|
+
const copy = pluginCopyFor(deps);
|
|
21358
|
+
copy.disposed = false;
|
|
21359
|
+
registerPluginCopy(copy);
|
|
21360
|
+
liveSetups += 1;
|
|
21361
|
+
plannedByDirectory.set(directory, planned);
|
|
21018
21362
|
registrations.push(
|
|
21019
21363
|
await ctx.provider.transform((editor) => {
|
|
21020
21364
|
const seed = editor.get(BASE_PROVIDER_ID);
|
|
@@ -21058,7 +21402,11 @@ function createV2Setup(deps) {
|
|
|
21058
21402
|
const providerID = event.model.providerID;
|
|
21059
21403
|
if (!isClaudeCodeProviderId(providerID)) return;
|
|
21060
21404
|
const settings = await resolveSdkSettings(providerID, planned.get(providerID), event.options);
|
|
21061
|
-
event.sdk = deps.createProvider(settings)
|
|
21405
|
+
event.sdk = routedProvider(deps.createProvider(settings), copy, {
|
|
21406
|
+
directory,
|
|
21407
|
+
providerID,
|
|
21408
|
+
sdkOptions: event.options
|
|
21409
|
+
});
|
|
21062
21410
|
log.debug("v2 sdk created", { providerID, cliPath: settings.cliPath });
|
|
21063
21411
|
})
|
|
21064
21412
|
);
|
|
@@ -21125,6 +21473,11 @@ function createV2Setup(deps) {
|
|
|
21125
21473
|
});
|
|
21126
21474
|
return async () => {
|
|
21127
21475
|
events.abort();
|
|
21476
|
+
liveSetups = Math.max(0, liveSetups - 1);
|
|
21477
|
+
if (liveSetups === 0) {
|
|
21478
|
+
copy.disposed = true;
|
|
21479
|
+
if (!relayMemberHasServed()) unregisterPluginCopy(copy);
|
|
21480
|
+
}
|
|
21128
21481
|
for (const registration of registrations.splice(0)) {
|
|
21129
21482
|
await registration.dispose().catch(() => void 0);
|
|
21130
21483
|
}
|