@khalilgharbaoui/opencode-claude-code-plugin 0.60.1 → 0.61.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.d.ts +27 -12
- package/dist/index.js +217 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +18 -5
package/dist/index.js
CHANGED
|
@@ -1998,17 +1998,21 @@ function describeReset(resetsAt) {
|
|
|
1998
1998
|
}
|
|
1999
1999
|
function createAccountFailoverQuestionCall(sessionKey2, input, toolCallId = `${ACCOUNT_FAILOVER_TOOL_CALL_PREFIX}${Math.random().toString(36).slice(2, 10)}`) {
|
|
2000
2000
|
const source = normalizeAccountName(input.sourceAccount || DEFAULT_ACCOUNT);
|
|
2001
|
+
const failed = normalizeAccountName(input.failedAccount || source);
|
|
2001
2002
|
const candidates = input.candidates.map((c) => normalizeAccountName(c));
|
|
2002
2003
|
const resets = describeReset(input.resetsAt);
|
|
2003
2004
|
const until = resets ?? "opencode restarts";
|
|
2004
|
-
const question = input.reason ? `The Claude account "${
|
|
2005
|
-
`The Claude account "${
|
|
2005
|
+
const question = input.reason ? `The Claude account "${failed}" ${input.reason}. Continue this task on another configured account? Leaving this unanswered waits, at no cost.` : [
|
|
2006
|
+
`The Claude account "${failed}" is out of usage`,
|
|
2006
2007
|
input.window ? ` in ${input.window}` : "",
|
|
2007
2008
|
resets ? `, which resets at ${resets}` : "",
|
|
2008
2009
|
". Continue this task on another configured account? Leaving this unanswered waits, at no cost."
|
|
2009
2010
|
].join("");
|
|
2010
2011
|
pendingQuestions2.set(pendingKey2(sessionKey2, toolCallId), {
|
|
2011
2012
|
sourceAccount: source,
|
|
2013
|
+
// Only after a chain of overrides; otherwise the two are one account.
|
|
2014
|
+
...failed !== source ? { failedAccount: failed } : {},
|
|
2015
|
+
...input.reason ? { reason: input.reason } : {},
|
|
2012
2016
|
candidates: [...candidates],
|
|
2013
2017
|
resetsAt: input.resetsAt,
|
|
2014
2018
|
question
|
|
@@ -2024,7 +2028,7 @@ function createAccountFailoverQuestionCall(sessionKey2, input, toolCallId = `${A
|
|
|
2024
2028
|
options: [
|
|
2025
2029
|
...candidates.map((candidate) => ({
|
|
2026
2030
|
label: candidate,
|
|
2027
|
-
description: input.carryTranscript === false ? `Run on "${candidate}" until ${until}. The conversation is replayed as a fresh Claude session, and any MCP server configured only in "${
|
|
2031
|
+
description: input.carryTranscript === false ? `Run on "${candidate}" until ${until}. The conversation is replayed as a fresh Claude session, and any MCP server configured only in "${failed}"'s Claude profile will be missing.` : `Run on "${candidate}" until ${until}. The conversation continues where it stopped if its Claude transcript can be carried across, and is replayed as a fresh Claude session if it cannot; either way, any MCP server configured only in "${failed}"'s Claude profile will be missing.`
|
|
2028
2032
|
})),
|
|
2029
2033
|
{
|
|
2030
2034
|
label: STOP_ANSWER,
|
|
@@ -2068,6 +2072,8 @@ function classify(pending2, part) {
|
|
|
2068
2072
|
kind: "switch",
|
|
2069
2073
|
target,
|
|
2070
2074
|
sourceAccount: pending2.sourceAccount,
|
|
2075
|
+
...pending2.failedAccount ? { failedAccount: pending2.failedAccount } : {},
|
|
2076
|
+
...pending2.reason ? { reason: pending2.reason } : {},
|
|
2071
2077
|
resetsAt: pending2.resetsAt
|
|
2072
2078
|
};
|
|
2073
2079
|
}
|
|
@@ -2095,6 +2101,7 @@ function consumeAccountFailoverAnswer(sessionKey2, prompt, fallback) {
|
|
|
2095
2101
|
return classify(
|
|
2096
2102
|
{
|
|
2097
2103
|
sourceAccount: normalizeAccountName(fallback.sourceAccount || DEFAULT_ACCOUNT),
|
|
2104
|
+
...fallback.failedAccount ? { failedAccount: normalizeAccountName(fallback.failedAccount) } : {},
|
|
2098
2105
|
candidates: fallback.candidates.map((c) => normalizeAccountName(c))
|
|
2099
2106
|
},
|
|
2100
2107
|
orphan
|
|
@@ -2105,8 +2112,9 @@ function consumeAccountFailoverAnswer(sessionKey2, prompt, fallback) {
|
|
|
2105
2112
|
}
|
|
2106
2113
|
function formatFailoverNote(input) {
|
|
2107
2114
|
const resets = describeReset(input.resetsAt);
|
|
2115
|
+
const arrival = input.carried ? "The conversation continues where it stopped." : "The conversation is replayed into a fresh Claude session there.";
|
|
2108
2116
|
return `
|
|
2109
|
-
${FAILOVER_MARKER} "${input.sourceAccount}" is out of usage, so this conversation continues on "${input.target}" ${resets ? `until ${resets}` : "until opencode restarts"}.
|
|
2117
|
+
${FAILOVER_MARKER} "${input.sourceAccount}" ${input.reason ?? "is out of usage"}, so this conversation continues on "${input.target}" ${resets ? `until ${resets}` : "until opencode restarts"}. ${arrival}
|
|
2110
2118
|
`;
|
|
2111
2119
|
}
|
|
2112
2120
|
function formatAutoFailoverNote(input) {
|
|
@@ -3800,13 +3808,27 @@ import { createHash } from "crypto";
|
|
|
3800
3808
|
var fingerprints = /* @__PURE__ */ new Map();
|
|
3801
3809
|
var fingerprintSeq = 0;
|
|
3802
3810
|
var MAX_FORK_FINGERPRINTS = 64;
|
|
3803
|
-
|
|
3811
|
+
var DIGEST_VERSION = 2;
|
|
3812
|
+
var DCP_MESSAGE_ID_TAG = /<dcp-message-id(?:\s[^>]*)?>[^<]*<\/dcp-message-id>/g;
|
|
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;
|
|
3816
|
+
for (; ; ) {
|
|
3817
|
+
const trimmed = out.trimEnd();
|
|
3818
|
+
if (!trimmed.endsWith("@") && !trimmed.endsWith("]")) return out;
|
|
3819
|
+
const lineStart = trimmed.lastIndexOf("\n") + 1;
|
|
3820
|
+
if (!DCP_COMPACT_REF_LINE.test(trimmed.slice(lineStart))) return out;
|
|
3821
|
+
out = trimmed.slice(0, lineStart);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
function hashPart(hash, part, version) {
|
|
3804
3825
|
const p = part;
|
|
3805
3826
|
const type = typeof p?.type === "string" ? p.type : "unknown";
|
|
3827
|
+
const content = (text) => version >= 2 ? withoutDcpRefs(text) : text;
|
|
3806
3828
|
switch (type) {
|
|
3807
3829
|
case "text":
|
|
3808
3830
|
hash.update("text\0");
|
|
3809
|
-
hash.update(String(p?.text ?? ""));
|
|
3831
|
+
hash.update(content(String(p?.text ?? "")));
|
|
3810
3832
|
break;
|
|
3811
3833
|
case "tool-call":
|
|
3812
3834
|
hash.update("call\0");
|
|
@@ -3818,7 +3840,7 @@ function hashPart(hash, part) {
|
|
|
3818
3840
|
hash.update("result\0");
|
|
3819
3841
|
hash.update(String(p?.toolName ?? ""));
|
|
3820
3842
|
hash.update("\0");
|
|
3821
|
-
hash.update(stableJson(p?.output));
|
|
3843
|
+
hash.update(stableJson(p?.output, version >= 2 ? withoutDcpRefs : void 0));
|
|
3822
3844
|
break;
|
|
3823
3845
|
case "file":
|
|
3824
3846
|
case "image": {
|
|
@@ -3840,9 +3862,10 @@ function hashPart(hash, part) {
|
|
|
3840
3862
|
}
|
|
3841
3863
|
hash.update("");
|
|
3842
3864
|
}
|
|
3843
|
-
function stableJson(value) {
|
|
3865
|
+
function stableJson(value, text) {
|
|
3844
3866
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
3845
3867
|
const walk = (node) => {
|
|
3868
|
+
if (typeof node === "string") return text ? text(node) : node;
|
|
3846
3869
|
if (node === null || typeof node !== "object") return node;
|
|
3847
3870
|
if (seen.has(node)) return "[circular]";
|
|
3848
3871
|
seen.add(node);
|
|
@@ -3859,16 +3882,16 @@ function stableJson(value) {
|
|
|
3859
3882
|
return "[unserializable]";
|
|
3860
3883
|
}
|
|
3861
3884
|
}
|
|
3862
|
-
function messageDigest(msg) {
|
|
3885
|
+
function messageDigest(msg, version) {
|
|
3863
3886
|
const hash = createHash("sha256");
|
|
3864
3887
|
hash.update(msg.role);
|
|
3865
3888
|
hash.update("\0");
|
|
3866
3889
|
if (typeof msg.content === "string") {
|
|
3867
3890
|
hash.update("text\0");
|
|
3868
|
-
hash.update(msg.content);
|
|
3891
|
+
hash.update(version >= 2 ? withoutDcpRefs(msg.content) : msg.content);
|
|
3869
3892
|
hash.update("");
|
|
3870
3893
|
} else if (Array.isArray(msg.content)) {
|
|
3871
|
-
for (const part of msg.content) hashPart(hash, part);
|
|
3894
|
+
for (const part of msg.content) hashPart(hash, part, version);
|
|
3872
3895
|
}
|
|
3873
3896
|
return hash.digest("hex").slice(0, 32);
|
|
3874
3897
|
}
|
|
@@ -3881,7 +3904,7 @@ function conversationDigests(prompt, opts) {
|
|
|
3881
3904
|
}
|
|
3882
3905
|
out.push({
|
|
3883
3906
|
role: msg.role,
|
|
3884
|
-
digest: opts?.assistantContent === false && msg.role === "assistant" ? ASSISTANT_TURN_DIGEST : messageDigest(msg)
|
|
3907
|
+
digest: opts?.assistantContent === false && msg.role === "assistant" ? ASSISTANT_TURN_DIGEST : messageDigest(msg, opts?.version ?? DIGEST_VERSION)
|
|
3885
3908
|
});
|
|
3886
3909
|
}
|
|
3887
3910
|
return out;
|
|
@@ -4251,6 +4274,13 @@ var SPAWN_MCP_TIMEOUT_MS = 12e4;
|
|
|
4251
4274
|
function spawnMcpTimeoutEnv(inherited = process.env) {
|
|
4252
4275
|
return inherited.MCP_TIMEOUT === void 0 ? { MCP_TIMEOUT: String(SPAWN_MCP_TIMEOUT_MS) } : {};
|
|
4253
4276
|
}
|
|
4277
|
+
var CLI_DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
4278
|
+
function effectiveSpawnMcpTimeoutMs(inherited = process.env) {
|
|
4279
|
+
const value = inherited.MCP_TIMEOUT;
|
|
4280
|
+
if (value === void 0) return SPAWN_MCP_TIMEOUT_MS;
|
|
4281
|
+
const parsed = Number.parseInt(value, 10);
|
|
4282
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 2147483647) : CLI_DEFAULT_MCP_TIMEOUT_MS;
|
|
4283
|
+
}
|
|
4254
4284
|
function detectCliVersion(cliPath) {
|
|
4255
4285
|
const cached2 = cache2.get(cliPath);
|
|
4256
4286
|
if (cached2) return cached2;
|
|
@@ -7218,7 +7248,7 @@ function isDigest(value) {
|
|
|
7218
7248
|
}
|
|
7219
7249
|
function isRecord2(value) {
|
|
7220
7250
|
const record = value;
|
|
7221
|
-
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");
|
|
7251
|
+
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") && (record.version === void 0 || typeof record.version === "number");
|
|
7222
7252
|
}
|
|
7223
7253
|
function load2() {
|
|
7224
7254
|
if (records) return records;
|
|
@@ -7271,7 +7301,7 @@ function recordResumePoint(sessionKey2, claudeSessionId, prompt, cliPath, where)
|
|
|
7271
7301
|
const configDir = where?.configDir;
|
|
7272
7302
|
const account = where?.account;
|
|
7273
7303
|
const current = load2().get(sessionKey2);
|
|
7274
|
-
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)) {
|
|
7304
|
+
if (current && current.claudeSessionId === claudeSessionId && current.cliPath === cliPath && current.shape !== void 0 && current.configDir === configDir && current.account === account && current.version === DIGEST_VERSION && current.chain.length === chain.length && current.chain.every((entry, i) => entry.digest === chain[i].digest)) {
|
|
7275
7305
|
return;
|
|
7276
7306
|
}
|
|
7277
7307
|
save2((merged) => {
|
|
@@ -7283,6 +7313,7 @@ function recordResumePoint(sessionKey2, claudeSessionId, prompt, cliPath, where)
|
|
|
7283
7313
|
shape: shape2,
|
|
7284
7314
|
...configDir === void 0 ? {} : { configDir },
|
|
7285
7315
|
...account === void 0 ? {} : { account },
|
|
7316
|
+
version: DIGEST_VERSION,
|
|
7286
7317
|
updatedAt: Date.now()
|
|
7287
7318
|
});
|
|
7288
7319
|
});
|
|
@@ -7317,7 +7348,7 @@ function findResumePoint(opts) {
|
|
|
7317
7348
|
return void 0;
|
|
7318
7349
|
};
|
|
7319
7350
|
if (record.cliPath !== opts.cliPath) return refuse("another-claude-binary");
|
|
7320
|
-
if (!continuesRecordedConversation(record.chain, opts.prompt)) {
|
|
7351
|
+
if (!continuesRecordedConversation(record.chain, opts.prompt, { version: record.version ?? 1 })) {
|
|
7321
7352
|
return refuse("conversation-changed");
|
|
7322
7353
|
}
|
|
7323
7354
|
if (!existsSync2(opts.transcriptPath(record.claudeSessionId))) {
|
|
@@ -7355,7 +7386,8 @@ function findSiblingResumePoint(opts) {
|
|
|
7355
7386
|
continue;
|
|
7356
7387
|
}
|
|
7357
7388
|
if (!record.shape || !continuesRecordedConversation(record.shape, opts.prompt, {
|
|
7358
|
-
assistantContent: false
|
|
7389
|
+
assistantContent: false,
|
|
7390
|
+
version: record.version ?? 1
|
|
7359
7391
|
})) {
|
|
7360
7392
|
refusal = "sibling-conversation-changed";
|
|
7361
7393
|
continue;
|
|
@@ -7840,6 +7872,7 @@ function claudeSpawnEnv(opts) {
|
|
|
7840
7872
|
if (opts?.configDir) {
|
|
7841
7873
|
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
7842
7874
|
}
|
|
7875
|
+
Object.assign(env, todoToolsEnv(opts?.enableTodoTools === true));
|
|
7843
7876
|
if (opts?.ignoreAnthropicApiKey) {
|
|
7844
7877
|
delete env.ANTHROPIC_API_KEY;
|
|
7845
7878
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
@@ -7849,6 +7882,10 @@ function claudeSpawnEnv(opts) {
|
|
|
7849
7882
|
}
|
|
7850
7883
|
return env;
|
|
7851
7884
|
}
|
|
7885
|
+
function todoToolsEnv(enabled) {
|
|
7886
|
+
if (!enabled || process.env.CLAUDE_CODE_ENABLE_TODO_TOOLS !== void 0) return {};
|
|
7887
|
+
return { CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" };
|
|
7888
|
+
}
|
|
7852
7889
|
function touch(key) {
|
|
7853
7890
|
const existing = activeProcesses.get(key);
|
|
7854
7891
|
if (existing) {
|
|
@@ -7885,6 +7922,18 @@ function isTerminalResultLine(line) {
|
|
|
7885
7922
|
return false;
|
|
7886
7923
|
}
|
|
7887
7924
|
}
|
|
7925
|
+
function isSystemInitLine(line) {
|
|
7926
|
+
if (!line.includes('"init"')) return false;
|
|
7927
|
+
try {
|
|
7928
|
+
const parsed = JSON.parse(line);
|
|
7929
|
+
return parsed.type === "system" && parsed.subtype === "init";
|
|
7930
|
+
} catch {
|
|
7931
|
+
return false;
|
|
7932
|
+
}
|
|
7933
|
+
}
|
|
7934
|
+
function noteInitLine(ap, line) {
|
|
7935
|
+
if (!ap.initSeen && isSystemInitLine(line)) ap.initSeen = true;
|
|
7936
|
+
}
|
|
7888
7937
|
function settleTurn(ap) {
|
|
7889
7938
|
ap.turnInFlight = false;
|
|
7890
7939
|
const waiters = ap.turnIdleWaiters ?? [];
|
|
@@ -8208,7 +8257,7 @@ function invalidateOtherEffortSessions(baseKey, effort) {
|
|
|
8208
8257
|
clearBackgroundTasks(key);
|
|
8209
8258
|
}
|
|
8210
8259
|
}
|
|
8211
|
-
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl, configDir) {
|
|
8260
|
+
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl, configDir, enableTodoTools) {
|
|
8212
8261
|
evictIfNeeded();
|
|
8213
8262
|
log.info("spawning new claude process", {
|
|
8214
8263
|
cliPath,
|
|
@@ -8217,13 +8266,14 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
8217
8266
|
sessionKey: sessionKey2,
|
|
8218
8267
|
effort,
|
|
8219
8268
|
promptCacheTtl,
|
|
8220
|
-
configDir
|
|
8269
|
+
configDir,
|
|
8270
|
+
...enableTodoTools ? { enableTodoTools } : {}
|
|
8221
8271
|
});
|
|
8222
8272
|
const plan = planClaudeSpawn(cliPath, cliArgs);
|
|
8223
8273
|
const proc = spawn(plan.file, plan.args, {
|
|
8224
8274
|
cwd,
|
|
8225
8275
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8226
|
-
env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl, configDir }),
|
|
8276
|
+
env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl, configDir, enableTodoTools }),
|
|
8227
8277
|
windowsVerbatimArguments: plan.windowsVerbatimArguments
|
|
8228
8278
|
});
|
|
8229
8279
|
const lineEmitter = new EventEmitter3();
|
|
@@ -8235,6 +8285,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
8235
8285
|
systemPromptFile,
|
|
8236
8286
|
effort,
|
|
8237
8287
|
promptCacheTtl,
|
|
8288
|
+
enableTodoTools,
|
|
8238
8289
|
startedAt: Date.now(),
|
|
8239
8290
|
cliPath,
|
|
8240
8291
|
cliArgs: [...cliArgs],
|
|
@@ -8246,6 +8297,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
8246
8297
|
rl.on("line", (line) => {
|
|
8247
8298
|
if (dispatchSideQuestionResponse(ap, line)) return;
|
|
8248
8299
|
noteTurnLine(ap, line);
|
|
8300
|
+
noteInitLine(ap, line);
|
|
8249
8301
|
if (lineEmitter.listenerCount("line") === 0) {
|
|
8250
8302
|
bufferUnattendedLine(ap, line);
|
|
8251
8303
|
return;
|
|
@@ -8365,7 +8417,8 @@ function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropi
|
|
|
8365
8417
|
ignoreAnthropicApiKey,
|
|
8366
8418
|
old.effort,
|
|
8367
8419
|
old.promptCacheTtl,
|
|
8368
|
-
old.configDir
|
|
8420
|
+
old.configDir,
|
|
8421
|
+
old.enableTodoTools
|
|
8369
8422
|
);
|
|
8370
8423
|
replacement.mcpServers = old.mcpServers;
|
|
8371
8424
|
replacement.pendingProxyCompletions = old.pendingProxyCompletions;
|
|
@@ -8860,9 +8913,10 @@ function resolveBudgetWarnAt(value) {
|
|
|
8860
8913
|
}
|
|
8861
8914
|
return [...new Set(value)].sort((a, b) => a - b);
|
|
8862
8915
|
}
|
|
8863
|
-
function
|
|
8864
|
-
if (!Number.isFinite(totalCostUsd) || totalCostUsd <= 0) return 0;
|
|
8865
|
-
if (!cumulative
|
|
8916
|
+
function takeResultCost(claudeSessionId, totalCostUsd, cumulative) {
|
|
8917
|
+
if (!Number.isFinite(totalCostUsd) || totalCostUsd <= 0) return { deltaUsd: 0, noBaseline: false };
|
|
8918
|
+
if (!cumulative) return { deltaUsd: totalCostUsd, noBaseline: false };
|
|
8919
|
+
if (!claudeSessionId) return { deltaUsd: totalCostUsd, noBaseline: true };
|
|
8866
8920
|
const previous = lastTotals.get(claudeSessionId);
|
|
8867
8921
|
lastTotals.delete(claudeSessionId);
|
|
8868
8922
|
lastTotals.set(claudeSessionId, totalCostUsd);
|
|
@@ -8871,8 +8925,9 @@ function costDelta(claudeSessionId, totalCostUsd, cumulative) {
|
|
|
8871
8925
|
if (oldest.done) break;
|
|
8872
8926
|
lastTotals.delete(oldest.value);
|
|
8873
8927
|
}
|
|
8874
|
-
if (previous === void 0
|
|
8875
|
-
return totalCostUsd
|
|
8928
|
+
if (previous === void 0) return { deltaUsd: totalCostUsd, noBaseline: true };
|
|
8929
|
+
if (totalCostUsd < previous) return { deltaUsd: totalCostUsd, noBaseline: false };
|
|
8930
|
+
return { deltaUsd: totalCostUsd - previous, noBaseline: false };
|
|
8876
8931
|
}
|
|
8877
8932
|
function recordSessionCost(session, deltaUsd) {
|
|
8878
8933
|
if (!(deltaUsd > 0)) return;
|
|
@@ -8931,8 +8986,10 @@ function formatBudgetStopNote(spentUsd, capUsd) {
|
|
|
8931
8986
|
return `${BUDGET_MARKER} this conversation has spent ${formatUsd(spentUsd)} against its ${formatUsd(capUsd)} \`maxBudgetUsd\`, so this turn was not started and nothing was sent to Claude. Raise \`maxBudgetUsd\` in the provider options and relaunch opencode (plugin options are read once at startup), or start a new session.`;
|
|
8932
8987
|
}
|
|
8933
8988
|
function recordResultCost(budget, claudeSessionId, totalCostUsd) {
|
|
8934
|
-
if (typeof totalCostUsd !== "number") return;
|
|
8935
|
-
|
|
8989
|
+
if (typeof totalCostUsd !== "number") return void 0;
|
|
8990
|
+
const cost = takeResultCost(claudeSessionId, totalCostUsd, budget.cumulative);
|
|
8991
|
+
recordSessionCost(budget.session, cost.deltaUsd);
|
|
8992
|
+
return cost;
|
|
8936
8993
|
}
|
|
8937
8994
|
function takeBudgetNote(budget) {
|
|
8938
8995
|
if (!budget.notes) return null;
|
|
@@ -9148,6 +9205,7 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
9148
9205
|
// booleans: every one of these is a decision the plugin made
|
|
9149
9206
|
applied: "flag",
|
|
9150
9207
|
attached: "flag",
|
|
9208
|
+
beforeInit: "flag",
|
|
9151
9209
|
bridgeSkipNativeSkills: "flag",
|
|
9152
9210
|
channelClosed: "flag",
|
|
9153
9211
|
compaction: "flag",
|
|
@@ -9249,9 +9307,11 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
9249
9307
|
durationMs: "duration",
|
|
9250
9308
|
elapsedMs: "duration",
|
|
9251
9309
|
remainingMs: "duration",
|
|
9310
|
+
startWatchdogMs: "duration",
|
|
9252
9311
|
timeoutMs: "duration",
|
|
9253
9312
|
waitedMs: "duration",
|
|
9254
9313
|
costUsd: "money",
|
|
9314
|
+
turnCostUsd: "money",
|
|
9255
9315
|
// enums: a fixed vocabulary this package or the CLI defines
|
|
9256
9316
|
apiKeySource: "enum",
|
|
9257
9317
|
delivery: "enum",
|
|
@@ -11059,6 +11119,11 @@ function formatDoctorReport(report) {
|
|
|
11059
11119
|
`| subagentDispatch | ${report.subagentDispatch}${report.subagentDispatch === "ask" && report.subagentDispatchCrossGroup ? " (cross-group accounts allowed)" : ""} |`
|
|
11060
11120
|
);
|
|
11061
11121
|
lines.push(`| turnStats | ${report.turnStats} |`);
|
|
11122
|
+
if (report.todoTools) {
|
|
11123
|
+
lines.push(
|
|
11124
|
+
`| enableTodoTools | ${report.todoTools.option}${report.todoTools.envSet ? " (CLAUDE_CODE_ENABLE_TODO_TOOLS is set in opencode's environment and decides)" : ""} |`
|
|
11125
|
+
);
|
|
11126
|
+
}
|
|
11062
11127
|
lines.push(`| ANTHROPIC_API_KEY in env | ${report.anthropicApiKeyInEnv ? "yes" : "no"} |`);
|
|
11063
11128
|
const overriding = report.permissionPresets.filter((row) => row.overrides.length > 0);
|
|
11064
11129
|
if (overriding.length > 0) {
|
|
@@ -11490,6 +11555,10 @@ async function gatherDoctorReport(options) {
|
|
|
11490
11555
|
subagentDispatch: base.subagentDispatch,
|
|
11491
11556
|
subagentDispatchCrossGroup: base.subagentDispatchCrossGroup,
|
|
11492
11557
|
turnStats: options.turnStats,
|
|
11558
|
+
todoTools: {
|
|
11559
|
+
option: options.enableTodoTools === true,
|
|
11560
|
+
envSet: process.env.CLAUDE_CODE_ENABLE_TODO_TOOLS !== void 0
|
|
11561
|
+
},
|
|
11493
11562
|
anthropicApiKeyInEnv: base.anthropicApiKeyInEnv,
|
|
11494
11563
|
processes,
|
|
11495
11564
|
pendingCalls: snapshotPendingProxyCalls(),
|
|
@@ -11696,6 +11765,11 @@ function extractTurnStats(msg) {
|
|
|
11696
11765
|
if (Array.isArray(msg.permission_denials)) stats.permissionDenials = msg.permission_denials;
|
|
11697
11766
|
return stats;
|
|
11698
11767
|
}
|
|
11768
|
+
function footerCostUsd(cost, resumedSession) {
|
|
11769
|
+
if (!cost) return void 0;
|
|
11770
|
+
if (cost.noBaseline && resumedSession) return void 0;
|
|
11771
|
+
return cost.deltaUsd;
|
|
11772
|
+
}
|
|
11699
11773
|
function formatCost(costUsd) {
|
|
11700
11774
|
if (!Number.isFinite(costUsd) || costUsd < 0) return "$0.00";
|
|
11701
11775
|
return costUsd >= 1 ? `$${costUsd.toFixed(2)}` : `$${costUsd.toFixed(4)}`;
|
|
@@ -15559,6 +15633,8 @@ function createTurnState(init) {
|
|
|
15559
15633
|
asideSinkUnregister: null,
|
|
15560
15634
|
resultFallbackTimer: null,
|
|
15561
15635
|
startWatchdog: null,
|
|
15636
|
+
startWatchdogArmedMs: 0,
|
|
15637
|
+
startWatchdogBeforeInit: false,
|
|
15562
15638
|
respawnAttempted: false,
|
|
15563
15639
|
hasReceivedContent: false,
|
|
15564
15640
|
hasReceivedProgress: false,
|
|
@@ -15934,7 +16010,11 @@ function onStartWatchdogFire(state) {
|
|
|
15934
16010
|
if (state.respawnAttempted) {
|
|
15935
16011
|
log.error(
|
|
15936
16012
|
"claude process still silent after respawn; ending turn",
|
|
15937
|
-
{
|
|
16013
|
+
{
|
|
16014
|
+
sessionKey: state.sessionKey,
|
|
16015
|
+
startWatchdogMs: state.startWatchdogArmedMs,
|
|
16016
|
+
beforeInit: state.startWatchdogBeforeInit
|
|
16017
|
+
}
|
|
15938
16018
|
);
|
|
15939
16019
|
deleteActiveProcess(state.sessionKey);
|
|
15940
16020
|
deleteClaudeSessionId(state.sessionKey);
|
|
@@ -15955,7 +16035,11 @@ function onStartWatchdogFire(state) {
|
|
|
15955
16035
|
state.respawnAttempted = true;
|
|
15956
16036
|
log.warn(
|
|
15957
16037
|
"no stdout after envelope write; respawning claude process to resume conversation",
|
|
15958
|
-
{
|
|
16038
|
+
{
|
|
16039
|
+
sessionKey: state.sessionKey,
|
|
16040
|
+
startWatchdogMs: state.startWatchdogArmedMs,
|
|
16041
|
+
beforeInit: state.startWatchdogBeforeInit
|
|
16042
|
+
}
|
|
15959
16043
|
);
|
|
15960
16044
|
state.lineEmitter.off("line", state.lineHandler);
|
|
15961
16045
|
state.lineEmitter.off("close", state.closeHandler);
|
|
@@ -16007,14 +16091,29 @@ function onStartWatchdogFire(state) {
|
|
|
16007
16091
|
}
|
|
16008
16092
|
armStartWatchdog(state);
|
|
16009
16093
|
}
|
|
16094
|
+
var START_WATCHDOG_INIT_MARGIN_MS = 3e4;
|
|
16095
|
+
function startWatchdogDelayMs(configuredMs, beforeInit, mcpTimeoutMs = effectiveSpawnMcpTimeoutMs()) {
|
|
16096
|
+
return beforeInit ? Math.max(configuredMs, mcpTimeoutMs + START_WATCHDOG_INIT_MARGIN_MS) : configuredMs;
|
|
16097
|
+
}
|
|
16010
16098
|
function armStartWatchdog(state) {
|
|
16011
16099
|
clearStartWatchdog(state);
|
|
16012
16100
|
if (state.controllerClosed) return;
|
|
16101
|
+
const ap = state.activeProcess;
|
|
16102
|
+
state.startWatchdogBeforeInit = !ap?.interactiveControl && ap?.initSeen !== true;
|
|
16103
|
+
state.startWatchdogArmedMs = startWatchdogDelayMs(
|
|
16104
|
+
state.startWatchdogMs,
|
|
16105
|
+
state.startWatchdogBeforeInit
|
|
16106
|
+
);
|
|
16013
16107
|
state.startWatchdog = setTimeout(
|
|
16014
16108
|
() => onStartWatchdogFire(state),
|
|
16015
|
-
state.
|
|
16109
|
+
state.startWatchdogArmedMs
|
|
16016
16110
|
);
|
|
16017
16111
|
}
|
|
16112
|
+
function noteStartWatchdogInit(state) {
|
|
16113
|
+
if (!state.startWatchdog || !state.startWatchdogBeforeInit) return;
|
|
16114
|
+
if (state.activeProcess?.initSeen !== true) return;
|
|
16115
|
+
armStartWatchdog(state);
|
|
16116
|
+
}
|
|
16018
16117
|
function deliverPendingCompletions(state, force = false) {
|
|
16019
16118
|
const pending2 = state.activeProcess?.pendingProxyCompletions;
|
|
16020
16119
|
const entries = [...pending2?.values() ?? []].filter(
|
|
@@ -16370,6 +16469,7 @@ function createLineHandler(state, ctx) {
|
|
|
16370
16469
|
subtype: msg.subtype
|
|
16371
16470
|
});
|
|
16372
16471
|
if (msg.type === "system" && msg.subtype === "init") {
|
|
16472
|
+
noteStartWatchdogInit(state);
|
|
16373
16473
|
if (msg.session_id) {
|
|
16374
16474
|
setClaudeSessionId(state.sessionKey, msg.session_id);
|
|
16375
16475
|
log.info("session initialized", {
|
|
@@ -16968,7 +17068,7 @@ ${plan}
|
|
|
16968
17068
|
if (msg.session_id) {
|
|
16969
17069
|
setClaudeSessionId(state.sessionKey, msg.session_id);
|
|
16970
17070
|
}
|
|
16971
|
-
|
|
17071
|
+
const resultCost = ctx.budget ? recordResultCost(ctx.budget, msg.session_id, msg.total_cost_usd) : ctx.config.turnStats && typeof msg.total_cost_usd === "number" ? takeResultCost(msg.session_id, msg.total_cost_usd, !ctx.interactive) : void 0;
|
|
16972
17072
|
if (deliverPendingCompletions(state)) {
|
|
16973
17073
|
return;
|
|
16974
17074
|
}
|
|
@@ -17061,10 +17161,17 @@ ${plan}
|
|
|
17061
17161
|
numTurns: msg.num_turns,
|
|
17062
17162
|
isError: msg.is_error,
|
|
17063
17163
|
subtype: msg.subtype,
|
|
17064
|
-
...turnStatsLogPayload(turnStats)
|
|
17164
|
+
...turnStatsLogPayload(turnStats),
|
|
17165
|
+
turnCostUsd: resultCost?.deltaUsd
|
|
17065
17166
|
});
|
|
17066
17167
|
if (ctx.config.turnStats && !ctx.compactionMode && !msg.is_error && !failure) {
|
|
17067
|
-
const footer = formatTurnStatsBlock(
|
|
17168
|
+
const footer = formatTurnStatsBlock({
|
|
17169
|
+
...turnStats,
|
|
17170
|
+
costUsd: footerCostUsd(
|
|
17171
|
+
resultCost,
|
|
17172
|
+
state.activeProcess?.cliArgs?.includes("--resume") === true
|
|
17173
|
+
)
|
|
17174
|
+
});
|
|
17068
17175
|
if (footer) {
|
|
17069
17176
|
state.controller.enqueue({
|
|
17070
17177
|
type: "text-delta",
|
|
@@ -17139,6 +17246,26 @@ async function isProxyCallStillServed(callId) {
|
|
|
17139
17246
|
setProxyDeadlineGuard(({ callId }) => isProxyCallStillServed(callId));
|
|
17140
17247
|
var FORWARD_SETTLE_MS = 250;
|
|
17141
17248
|
var CHILD_EXIT_STATUS_GRACE_MS = 250;
|
|
17249
|
+
function stopOnCancel(stream, cancelled2) {
|
|
17250
|
+
const reader = stream.getReader();
|
|
17251
|
+
return new ReadableStream({
|
|
17252
|
+
async pull(controller) {
|
|
17253
|
+
try {
|
|
17254
|
+
const { done, value } = await reader.read();
|
|
17255
|
+
if (done) controller.close();
|
|
17256
|
+
else controller.enqueue(value);
|
|
17257
|
+
} catch (error) {
|
|
17258
|
+
try {
|
|
17259
|
+
controller.error(error);
|
|
17260
|
+
} catch {
|
|
17261
|
+
}
|
|
17262
|
+
}
|
|
17263
|
+
},
|
|
17264
|
+
cancel(reason) {
|
|
17265
|
+
cancelled2.abort(reason);
|
|
17266
|
+
}
|
|
17267
|
+
});
|
|
17268
|
+
}
|
|
17142
17269
|
var ClaudeCodeLanguageModel = class {
|
|
17143
17270
|
specificationVersion = "v3";
|
|
17144
17271
|
modelId;
|
|
@@ -17626,32 +17753,35 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17626
17753
|
* switch, and outside `runModelChain` on purpose: an account switch starts
|
|
17627
17754
|
* that account's own model chain from the top.
|
|
17628
17755
|
*
|
|
17629
|
-
* Anything but `"auto"` is `runModelChain`
|
|
17630
|
-
* `"
|
|
17631
|
-
*
|
|
17632
|
-
*
|
|
17633
|
-
*
|
|
17634
|
-
*
|
|
17635
|
-
* attempt did not switch.
|
|
17756
|
+
* Anything but `"auto"` is `runModelChain` behind `stopOnCancel`. With
|
|
17757
|
+
* `"auto"`, the attempt that failed decides the switch itself
|
|
17758
|
+
* (`completeResult` sets `run.switchTo` and closes its stream without a
|
|
17759
|
+
* `finish`), and the next attempt's prologue makes the switch the form's pick
|
|
17760
|
+
* would have made. This only splices the attempts into one caller turn: one
|
|
17761
|
+
* `stream-start`, and the `finish` of whichever attempt did not switch.
|
|
17636
17762
|
*
|
|
17637
17763
|
* The bound is `run.tried`, as it is `tried` for the model chain: every
|
|
17638
17764
|
* account spawned or switched to goes in, and none in it is picked again.
|
|
17639
17765
|
*
|
|
17640
|
-
* Cancelling the returned stream is a stop,
|
|
17641
|
-
* the
|
|
17642
|
-
*
|
|
17643
|
-
*
|
|
17644
|
-
* starts no further account
|
|
17766
|
+
* Cancelling the returned stream is a stop, in every mode: a caller that has
|
|
17767
|
+
* gone away must reach the turn it started, or the CLI runs it to the end
|
|
17768
|
+
* and bills it. Each attempt gets a signal that the caller's own
|
|
17769
|
+
* `abortSignal` and a `cancel()` both abort, so a cancel takes the turn's
|
|
17770
|
+
* ordinary abort path, and with `"auto"` the loop starts no further account
|
|
17771
|
+
* once either has happened. Until (h #g257) only `"auto"` did this.
|
|
17645
17772
|
*/
|
|
17646
17773
|
async runAccountFailover(options, mode) {
|
|
17647
|
-
if (this.config.accountFailover !== "auto") return this.runModelChain(options, mode);
|
|
17648
|
-
const self = this;
|
|
17649
|
-
const run = { tried: /* @__PURE__ */ new Set() };
|
|
17650
17774
|
const cancelled2 = new AbortController();
|
|
17651
17775
|
const attemptOptions = {
|
|
17652
17776
|
...options,
|
|
17653
17777
|
abortSignal: options.abortSignal ? AbortSignal.any([options.abortSignal, cancelled2.signal]) : cancelled2.signal
|
|
17654
17778
|
};
|
|
17779
|
+
if (this.config.accountFailover !== "auto") {
|
|
17780
|
+
const inner2 = await this.runModelChain(attemptOptions, mode);
|
|
17781
|
+
return { ...inner2, stream: stopOnCancel(inner2.stream, cancelled2) };
|
|
17782
|
+
}
|
|
17783
|
+
const self = this;
|
|
17784
|
+
const run = { tried: /* @__PURE__ */ new Set() };
|
|
17655
17785
|
const stopped = () => attemptOptions.abortSignal.aborted;
|
|
17656
17786
|
let inner = await this.runModelChain(attemptOptions, mode, run);
|
|
17657
17787
|
const request = inner.request;
|
|
@@ -17693,9 +17823,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17693
17823
|
}
|
|
17694
17824
|
}
|
|
17695
17825
|
},
|
|
17696
|
-
|
|
17826
|
+
cancel(reason) {
|
|
17697
17827
|
cancelled2.abort(reason);
|
|
17698
|
-
await reader?.cancel(reason).catch(() => void 0);
|
|
17699
17828
|
}
|
|
17700
17829
|
});
|
|
17701
17830
|
return { stream, request };
|
|
@@ -17907,6 +18036,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17907
18036
|
cliPath,
|
|
17908
18037
|
interactive: !!useInteractive,
|
|
17909
18038
|
turnStats: this.config.turnStats === true,
|
|
18039
|
+
enableTodoTools: this.config.enableTodoTools === true,
|
|
17910
18040
|
ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey === true,
|
|
17911
18041
|
// Where the plugin applies the account itself rather than through a
|
|
17912
18042
|
// wrapper (`accountInProcess`, Windows), the `/cost` spawn needs the
|
|
@@ -18102,16 +18232,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18102
18232
|
resetsAt: autoSwitch.resetsAt
|
|
18103
18233
|
} : compactionMode || mode === "generate" ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
|
|
18104
18234
|
sourceAccount,
|
|
18235
|
+
// What the form would offer now: an override still standing is
|
|
18236
|
+
// still the account that failed, and is not on offer (h #g257).
|
|
18237
|
+
failedAccount: failover.target ?? sourceAccount,
|
|
18105
18238
|
candidates: failoverCandidates(
|
|
18106
18239
|
this.config.failoverAccounts,
|
|
18107
18240
|
sourceAccount,
|
|
18108
18241
|
groups
|
|
18109
|
-
)
|
|
18242
|
+
).filter((account) => account !== (failover.target ?? sourceAccount))
|
|
18110
18243
|
});
|
|
18111
18244
|
if (failoverAnswer?.kind === "stop") {
|
|
18112
18245
|
log.warn("account failover declined; ending the turn", {
|
|
18113
18246
|
sessionKey: sk,
|
|
18114
|
-
account: sourceAccount,
|
|
18247
|
+
account: failover.target ?? sourceAccount,
|
|
18115
18248
|
reason: failoverAnswer.reason
|
|
18116
18249
|
});
|
|
18117
18250
|
const note = formatFailoverStopNote(failoverAnswer.reason);
|
|
@@ -18125,7 +18258,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18125
18258
|
controller.enqueue({
|
|
18126
18259
|
type: "error",
|
|
18127
18260
|
error: new Error(
|
|
18128
|
-
|
|
18261
|
+
// The account that failed: after a chain of overrides that is
|
|
18262
|
+
// not the provider's own (h #g257). A stop answer also follows
|
|
18263
|
+
// an expired login, so this does not say "out of usage".
|
|
18264
|
+
`Claude account "${failover.target ?? sourceAccount}" cannot serve this turn and no other account was picked.`
|
|
18129
18265
|
)
|
|
18130
18266
|
});
|
|
18131
18267
|
controller.enqueue({
|
|
@@ -18229,13 +18365,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18229
18365
|
}
|
|
18230
18366
|
cliPath = failover.cliPath;
|
|
18231
18367
|
asideTransportRef.cliPath = cliPath;
|
|
18232
|
-
if (!autoSwitch) {
|
|
18233
|
-
failoverNote = formatFailoverNote({
|
|
18234
|
-
sourceAccount: failoverAnswer.sourceAccount,
|
|
18235
|
-
target: failoverAnswer.target,
|
|
18236
|
-
resetsAt: failoverAnswer.resetsAt
|
|
18237
|
-
});
|
|
18238
|
-
}
|
|
18239
18368
|
}
|
|
18240
18369
|
const spawnConfigDir = this.config.accountInProcess ? this.skillBridgeSpawn(failover).configDir : void 0;
|
|
18241
18370
|
const processForAccount = getActiveProcess(sk);
|
|
@@ -18307,6 +18436,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18307
18436
|
reason: autoSwitch.reason,
|
|
18308
18437
|
carried: conversationOnTarget
|
|
18309
18438
|
});
|
|
18439
|
+
} else if (!autoSwitch && failoverAnswer?.kind === "switch") {
|
|
18440
|
+
failoverNote = formatFailoverNote({
|
|
18441
|
+
sourceAccount: failoverAnswer.failedAccount ?? failoverAnswer.sourceAccount,
|
|
18442
|
+
target: failoverAnswer.target,
|
|
18443
|
+
resetsAt: failoverAnswer.resetsAt,
|
|
18444
|
+
reason: failoverAnswer.reason,
|
|
18445
|
+
carried: conversationOnTarget
|
|
18446
|
+
});
|
|
18310
18447
|
}
|
|
18311
18448
|
let accountGroupNote = null;
|
|
18312
18449
|
if (!compactionMode && groups && accountProviders.size > 0 && includeHistoryContext && failoverAnswer?.kind !== "switch") {
|
|
@@ -18573,11 +18710,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18573
18710
|
"The read-only preset on the interactive transport needs Claude Code 2.1.263 or newer (--restricted and --permission-mode dontAsk). Update claude, or use the headless transport."
|
|
18574
18711
|
);
|
|
18575
18712
|
}
|
|
18713
|
+
const spawnedAccount = failover.target ?? sourceAccount;
|
|
18576
18714
|
const failoverAccounts = failoverCandidates(
|
|
18577
18715
|
this.config.failoverAccounts,
|
|
18578
18716
|
sourceAccount,
|
|
18579
18717
|
groups
|
|
18580
|
-
);
|
|
18718
|
+
).filter((account) => account !== spawnedAccount);
|
|
18581
18719
|
const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode && // A `doGenerate` caller takes the override and reports the plain
|
|
18582
18720
|
// rate-limit error, never the form. See `TurnMode`.
|
|
18583
18721
|
mode !== "generate" && isAccountFailoverQuestionActive({
|
|
@@ -18590,7 +18728,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18590
18728
|
// the operator is usually not even looking at.
|
|
18591
18729
|
childSession: !!await fetchSessionParentId(affinity)
|
|
18592
18730
|
});
|
|
18593
|
-
const spawnedAccount = failover.target ?? sourceAccount;
|
|
18594
18731
|
accountRun?.tried.add(spawnedAccount);
|
|
18595
18732
|
let autoFailoverChoice;
|
|
18596
18733
|
const autoFailoverTarget = () => {
|
|
@@ -18599,7 +18736,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18599
18736
|
}
|
|
18600
18737
|
return autoFailoverChoice ?? void 0;
|
|
18601
18738
|
};
|
|
18602
|
-
const limitAccount =
|
|
18739
|
+
const limitAccount = spawnedAccount;
|
|
18603
18740
|
const usageLimitNoteActive = !compactionMode && mode !== "generate" && !failoverAskActive;
|
|
18604
18741
|
const usageWarnThresholds = resolveUsageWarnThresholds(this.config.usageWarnThresholds);
|
|
18605
18742
|
const usageSwitchAt = this.config.usageSwitchAt === null ? null : resolveUsageSwitchAt(this.config.usageSwitchAt);
|
|
@@ -19100,7 +19237,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19100
19237
|
claudeSpawnEnv({
|
|
19101
19238
|
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
19102
19239
|
effort: reasoningEffort,
|
|
19103
|
-
promptCacheTtl
|
|
19240
|
+
promptCacheTtl,
|
|
19241
|
+
enableTodoTools: self.config.enableTodoTools === true
|
|
19104
19242
|
})
|
|
19105
19243
|
)
|
|
19106
19244
|
});
|
|
@@ -19218,7 +19356,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19218
19356
|
self.config.ignoreAnthropicApiKey,
|
|
19219
19357
|
reasoningEffort,
|
|
19220
19358
|
promptCacheTtl,
|
|
19221
|
-
spawnConfigDir
|
|
19359
|
+
spawnConfigDir,
|
|
19360
|
+
// A compaction spawn runs no tools, so it never asks for them.
|
|
19361
|
+
self.config.enableTodoTools === true && !compactionMode
|
|
19222
19362
|
);
|
|
19223
19363
|
ap.mcpServers = spawnMcpServers;
|
|
19224
19364
|
state.proc = ap.proc;
|
|
@@ -19348,7 +19488,11 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19348
19488
|
state.activeProcess?.pendingProxyCompletions?.clear();
|
|
19349
19489
|
if ((state.accountLimitHit || state.accountBlock) && failoverAskActive && msg.is_error === true) {
|
|
19350
19490
|
const call = createAccountFailoverQuestionCall(sk, {
|
|
19491
|
+
// The override a pick sets is keyed on the provider's own
|
|
19492
|
+
// account, the one every turn of this conversation resolves
|
|
19493
|
+
// from; the form names the account that actually failed.
|
|
19351
19494
|
sourceAccount,
|
|
19495
|
+
failedAccount: limitAccount,
|
|
19352
19496
|
candidates: failoverAccounts,
|
|
19353
19497
|
resetsAt: state.accountLimitHit?.resetsAt,
|
|
19354
19498
|
window: state.accountLimitHit?.window,
|
|
@@ -19356,7 +19500,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19356
19500
|
carryTranscript: self.config.crossAccountResume !== false
|
|
19357
19501
|
});
|
|
19358
19502
|
log.warn(
|
|
19359
|
-
`Claude account "${
|
|
19503
|
+
`Claude account "${limitAccount}" ${state.accountLimitHit || !state.accountBlock ? "is out of usage" : `cannot serve (${state.accountBlock})`}; asking which account to continue on.`,
|
|
19360
19504
|
{
|
|
19361
19505
|
sessionKey: sk,
|
|
19362
19506
|
candidates: failoverAccounts,
|
|
@@ -19447,7 +19591,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19447
19591
|
state.endTextBlock();
|
|
19448
19592
|
state.resultFailure ??= "usage_limit";
|
|
19449
19593
|
}
|
|
19450
|
-
if (
|
|
19594
|
+
if (state.accountLimitHit && msg.is_error === true) {
|
|
19451
19595
|
state.resultFailure ??= "usage_limit";
|
|
19452
19596
|
}
|
|
19453
19597
|
if (runAutoContinue(state, msg)) return;
|
|
@@ -19566,7 +19710,11 @@ var ClaudeCodeLanguageModel = class {
|
|
|
19566
19710
|
interactive: useInteractive,
|
|
19567
19711
|
sourceAccount: limitAccount,
|
|
19568
19712
|
usageAccount: spawnedAccount,
|
|
19569
|
-
|
|
19713
|
+
// The config dir of the account that ran, so an account-block note
|
|
19714
|
+
// names that account's login command. The provider's own when no
|
|
19715
|
+
// override moved the turn; until (h #g257) `"off"` and `"ask"`
|
|
19716
|
+
// named the provider's even after one had.
|
|
19717
|
+
accountConfigDir: self.skillBridgeSpawn(failover).configDir,
|
|
19570
19718
|
failoverAskActive,
|
|
19571
19719
|
autoFailoverTarget,
|
|
19572
19720
|
usageLimitNoteActive,
|
|
@@ -20943,6 +21091,7 @@ function createClaudeCode(settings = {}) {
|
|
|
20943
21091
|
bridgeOpencodeSkills: settings.bridgeOpencodeSkills === true,
|
|
20944
21092
|
bridgeSkipNativeSkills: settings.bridgeSkipNativeSkills !== false,
|
|
20945
21093
|
turnStats: settings.turnStats === true,
|
|
21094
|
+
enableTodoTools: settings.enableTodoTools === true,
|
|
20946
21095
|
forkSessions: settings.forkSessions === true,
|
|
20947
21096
|
resumeAfterRestart: settings.resumeAfterRestart !== false,
|
|
20948
21097
|
resumeAcrossModelChanges: settings.resumeAcrossModelChanges !== false,
|