@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 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
- forgetResumePoint(key);
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 invalidateOtherEffortSessions(baseKey, effort) {
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 staleKeys = levels.filter((level) => level !== effort).map((level) => effortSessionKey(baseKey, level));
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 the previous effort session has pending work. Finish that work at its original effort first."
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 ask = [];
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 (!ask.includes(rule.pattern)) ask.push(rule.pattern);
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) return cached2;
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
- cached2 = status.stale;
11089
- if (status.stale && status.onDisk) {
11090
- const identity = `${status.stale.kind}:${status.onDisk.version}:${status.onDisk.mtimeMs}:${status.onDisk.size}`;
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: status.stale.kind,
11095
- loadedVersion: status.stale.loadedVersion,
11096
- onDiskVersion: status.stale.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) invalidateOtherEffortSessions(baseKey, reasoningEffort);
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) => handleSessionEventHere(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
  }