@khalilgharbaoui/opencode-claude-code-plugin 0.42.3 → 0.43.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.js +184 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +13 -8
package/dist/index.js
CHANGED
|
@@ -1327,7 +1327,7 @@ function formatUsageLimitNote(input) {
|
|
|
1327
1327
|
const what = [
|
|
1328
1328
|
`the Claude account "${account}" is out of usage`,
|
|
1329
1329
|
window ? ` in ${window}` : "",
|
|
1330
|
-
resets ? `, which resets at ${resets}` : "",
|
|
1330
|
+
resets ? `, which resets at ${resets}` : input.resetsText ? `, which resets ${input.resetsText}` : "",
|
|
1331
1331
|
"."
|
|
1332
1332
|
].join("");
|
|
1333
1333
|
const advice = others ? `Pick a model from ${others} and resend your message, or wait for the window to reset.` : "Wait for the window to reset, or enable extra usage on the account.";
|
|
@@ -1341,6 +1341,13 @@ function isAccountLimitError(input) {
|
|
|
1341
1341
|
if (typeof text !== "string" || text.length === 0) return false;
|
|
1342
1342
|
return ACCOUNT_LIMIT_PATTERNS.some((pattern) => pattern.test(text));
|
|
1343
1343
|
}
|
|
1344
|
+
function limitFactsFromText(text) {
|
|
1345
|
+
const facts = {};
|
|
1346
|
+
if (/\bsession limit\b/i.test(text)) facts.window = "five_hour";
|
|
1347
|
+
const reset = /\bresets\s+([^·\n]{1,60}?)\s*$/im.exec(text);
|
|
1348
|
+
if (reset) facts.resetsText = reset[1].trim();
|
|
1349
|
+
return facts;
|
|
1350
|
+
}
|
|
1344
1351
|
var lastAccountLimits = /* @__PURE__ */ new Map();
|
|
1345
1352
|
function rememberAccountLimit(account, facts) {
|
|
1346
1353
|
const name = normalizeAccountName(account || DEFAULT_ACCOUNT);
|
|
@@ -1423,7 +1430,6 @@ async function resolveFailoverSpawn(input) {
|
|
|
1423
1430
|
}
|
|
1424
1431
|
function isAccountFailoverQuestionActive(input) {
|
|
1425
1432
|
if (input.compactionMode) return false;
|
|
1426
|
-
if (input.interactive) return false;
|
|
1427
1433
|
if (input.childSession) return false;
|
|
1428
1434
|
if (input.configured !== "ask") return false;
|
|
1429
1435
|
if (input.candidates.length === 0) return false;
|
|
@@ -1723,6 +1729,10 @@ function cliSupportsDontAsk(v) {
|
|
|
1723
1729
|
if (!v) return false;
|
|
1724
1730
|
return gte(v, { major: 2, minor: 1, patch: 263 });
|
|
1725
1731
|
}
|
|
1732
|
+
function cliSupportsInteractiveBypass(v) {
|
|
1733
|
+
if (!v) return false;
|
|
1734
|
+
return gte(v, { major: 2, minor: 1, patch: 263 });
|
|
1735
|
+
}
|
|
1726
1736
|
var INTERACTIVE_MEASURED_CLI = "2.1.288";
|
|
1727
1737
|
var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch: 288 };
|
|
1728
1738
|
function isUnmeasuredInteractiveCli(v) {
|
|
@@ -4559,18 +4569,36 @@ function summarizePermissionPreset(provider, options, defaultProxyTools = DEFAUL
|
|
|
4559
4569
|
};
|
|
4560
4570
|
}
|
|
4561
4571
|
function interactivePermissionPosture(input) {
|
|
4562
|
-
if (
|
|
4563
|
-
|
|
4572
|
+
if (isReadOnlyPermissionMode(input.permissionMode)) {
|
|
4573
|
+
if (!input.supportsReadOnly) return null;
|
|
4574
|
+
const refused = new Set(READ_ONLY_DISALLOWED_CLI_TOOLS);
|
|
4575
|
+
return {
|
|
4576
|
+
permissionMode: "dontAsk",
|
|
4577
|
+
restricted: true,
|
|
4578
|
+
allow: input.allow.filter(
|
|
4579
|
+
(rule) => !rule.startsWith("mcp__") && !refused.has(rule.split("(")[0].trim())
|
|
4580
|
+
),
|
|
4581
|
+
bypass: false,
|
|
4582
|
+
disallowed: []
|
|
4583
|
+
};
|
|
4564
4584
|
}
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
(rule) => !rule.startsWith("mcp__") && !refused.has(rule.split("(")[0].trim())
|
|
4572
|
-
)
|
|
4585
|
+
const unchanged = {
|
|
4586
|
+
permissionMode: input.permissionMode === "bypassPermissions" ? void 0 : input.permissionMode,
|
|
4587
|
+
restricted: false,
|
|
4588
|
+
allow: input.allow,
|
|
4589
|
+
bypass: false,
|
|
4590
|
+
disallowed: []
|
|
4573
4591
|
};
|
|
4592
|
+
if (input.permissionMode === "plan" || !input.supportsBypass) return unchanged;
|
|
4593
|
+
const perTool = Object.entries(input.controlRequestToolBehaviors ?? {});
|
|
4594
|
+
const named = (behavior) => perTool.filter(([, value]) => value === behavior).map(([tool]) => tool);
|
|
4595
|
+
if (input.skipPermissions !== false || input.permissionMode === "bypassPermissions") {
|
|
4596
|
+
return { ...unchanged, bypass: true };
|
|
4597
|
+
}
|
|
4598
|
+
if ((input.controlRequestBehavior ?? "allow") === "allow") {
|
|
4599
|
+
return { ...unchanged, bypass: true, disallowed: named("deny") };
|
|
4600
|
+
}
|
|
4601
|
+
return { ...unchanged, permissionMode: "dontAsk", allow: named("allow") };
|
|
4574
4602
|
}
|
|
4575
4603
|
function isReadOnlyPermissionMode(mode) {
|
|
4576
4604
|
return mode === READ_ONLY_PERMISSION_MODE;
|
|
@@ -5825,7 +5853,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5825
5853
|
"interactive transport denied a permission prompt",
|
|
5826
5854
|
"interactive transport dropped non-text content blocks",
|
|
5827
5855
|
"interactive turn failed",
|
|
5828
|
-
"interactiveBypass ignored:
|
|
5856
|
+
"interactiveBypass is ignored: skipPermissions governs the interactive transport too",
|
|
5829
5857
|
"malformed data URI, skipping file part",
|
|
5830
5858
|
"no active process to respawn (start watchdog); ending turn",
|
|
5831
5859
|
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
@@ -7542,6 +7570,20 @@ function parseModelId(modelId) {
|
|
|
7542
7570
|
if (!Object.hasOwn(defaultModels, base)) return { model: modelId, fast: false };
|
|
7543
7571
|
return { model: base.slice(0, -FAST_SUFFIX.length) + account, fast: true };
|
|
7544
7572
|
}
|
|
7573
|
+
var WEB_SEARCH_USD = 0.01;
|
|
7574
|
+
function apiCallCostUsd(model, usage) {
|
|
7575
|
+
if (typeof model !== "string" || !usage || typeof usage !== "object") return null;
|
|
7576
|
+
const base = model.replace(/@.*$/, "").replace(/\[1m\]$/i, "").replace(/-\d{8}$/, "");
|
|
7577
|
+
const entry = (usage.speed === "fast" ? defaultModels[`${base}${FAST_SUFFIX}`] : void 0) ?? defaultModels[base];
|
|
7578
|
+
if (!entry) return null;
|
|
7579
|
+
const cost = entry.cost;
|
|
7580
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
7581
|
+
const split = usage.cache_creation;
|
|
7582
|
+
const write1h = split ? count(split.ephemeral_1h_input_tokens) : 0;
|
|
7583
|
+
const write5m = split ? count(split.ephemeral_5m_input_tokens) : count(usage.cache_creation_input_tokens);
|
|
7584
|
+
const perMillion = count(usage.input_tokens) * cost.input + count(usage.output_tokens) * cost.output + count(usage.cache_read_input_tokens) * cost.cache.read + write5m * cost.cache.write + write1h * cost.input * 2;
|
|
7585
|
+
return perMillion / 1e6 + count(usage.server_tool_use?.web_search_requests) * WEB_SEARCH_USD;
|
|
7586
|
+
}
|
|
7545
7587
|
|
|
7546
7588
|
// src/agent-models.ts
|
|
7547
7589
|
var AGENT_DIR_NAMES = ["agents", "agent"];
|
|
@@ -8861,6 +8903,7 @@ function isApiCallRecord(rec) {
|
|
|
8861
8903
|
}
|
|
8862
8904
|
var TurnUsageAccumulator = class {
|
|
8863
8905
|
calls = /* @__PURE__ */ new Map();
|
|
8906
|
+
models = /* @__PURE__ */ new Map();
|
|
8864
8907
|
lastKey = null;
|
|
8865
8908
|
/** Feed every transcript record of the turn, in file order. */
|
|
8866
8909
|
add(rec) {
|
|
@@ -8868,8 +8911,24 @@ var TurnUsageAccumulator = class {
|
|
|
8868
8911
|
const id = rec.message.id;
|
|
8869
8912
|
const key = typeof id === "string" && id.length > 0 ? id : `uuid:${rec.uuid ?? this.calls.size}`;
|
|
8870
8913
|
this.calls.set(key, rec.message.usage);
|
|
8914
|
+
this.models.set(key, rec.message.model);
|
|
8871
8915
|
this.lastKey = key;
|
|
8872
8916
|
}
|
|
8917
|
+
/**
|
|
8918
|
+
* The turn's list-price cost over its distinct calls, the headless
|
|
8919
|
+
* `result`'s `total_cost_usd` (h #g208). Null when no call was made or any
|
|
8920
|
+
* call's model is one the catalog cannot price, never a partial sum.
|
|
8921
|
+
*/
|
|
8922
|
+
get costUsd() {
|
|
8923
|
+
if (this.calls.size === 0) return null;
|
|
8924
|
+
let total = 0;
|
|
8925
|
+
for (const [key, usage] of this.calls) {
|
|
8926
|
+
const cost = apiCallCostUsd(this.models.get(key), usage);
|
|
8927
|
+
if (cost === null) return null;
|
|
8928
|
+
total += cost;
|
|
8929
|
+
}
|
|
8930
|
+
return total;
|
|
8931
|
+
}
|
|
8873
8932
|
/** Distinct API calls seen, for diagnostics. */
|
|
8874
8933
|
get callCount() {
|
|
8875
8934
|
return this.calls.size;
|
|
@@ -9620,11 +9679,29 @@ var ClaudeSession = class {
|
|
|
9620
9679
|
async tailTurn(prompt, onLine, perTurnTimeoutMs, onHeartbeat) {
|
|
9621
9680
|
const timeout = perTurnTimeoutMs ?? this.o.turnTimeoutMs;
|
|
9622
9681
|
const usage = new TurnUsageAccumulator();
|
|
9682
|
+
const startedAt = Date.now();
|
|
9683
|
+
let reportedDurationMs = null;
|
|
9684
|
+
let apiError = null;
|
|
9623
9685
|
const { stopReason, end, denied } = await this.runTurnLoop(
|
|
9624
9686
|
prompt,
|
|
9625
9687
|
(raw, rec) => {
|
|
9626
9688
|
onLine(raw);
|
|
9627
9689
|
if (rec) usage.add(rec);
|
|
9690
|
+
if (rec?.type === "assistant") {
|
|
9691
|
+
if (rec.isApiErrorMessage === true) {
|
|
9692
|
+
const text = (Array.isArray(rec.message?.content) ? rec.message.content : []).filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
9693
|
+
apiError = {
|
|
9694
|
+
text,
|
|
9695
|
+
...typeof rec.apiErrorStatus === "number" ? { status: rec.apiErrorStatus } : {},
|
|
9696
|
+
...typeof rec.error === "string" ? { kind: rec.error } : {}
|
|
9697
|
+
};
|
|
9698
|
+
} else if (rec.message?.model !== "<synthetic>") {
|
|
9699
|
+
apiError = null;
|
|
9700
|
+
}
|
|
9701
|
+
}
|
|
9702
|
+
if (isTurnDurationRecord(rec) && typeof rec.durationMs === "number") {
|
|
9703
|
+
reportedDurationMs = rec.durationMs;
|
|
9704
|
+
}
|
|
9628
9705
|
},
|
|
9629
9706
|
timeout,
|
|
9630
9707
|
onHeartbeat
|
|
@@ -9642,6 +9719,9 @@ var ClaudeSession = class {
|
|
|
9642
9719
|
usage: usage.turnTotal,
|
|
9643
9720
|
lastCallUsage: usage.lastCall,
|
|
9644
9721
|
callCount: usage.callCount,
|
|
9722
|
+
costUsd: usage.costUsd,
|
|
9723
|
+
durationMs: reportedDurationMs ?? Date.now() - startedAt,
|
|
9724
|
+
apiError,
|
|
9645
9725
|
denied
|
|
9646
9726
|
};
|
|
9647
9727
|
}
|
|
@@ -9789,13 +9869,19 @@ function interactiveExtraArgs(opts) {
|
|
|
9789
9869
|
if (opts.fastMode) {
|
|
9790
9870
|
flagSettings.fastMode = true;
|
|
9791
9871
|
}
|
|
9872
|
+
if (opts.bypass) {
|
|
9873
|
+
flagSettings.skipDangerousModePermissionPrompt = true;
|
|
9874
|
+
extraArgs.push("--dangerously-skip-permissions");
|
|
9875
|
+
}
|
|
9792
9876
|
if (Object.keys(flagSettings).length > 0) {
|
|
9793
9877
|
extraArgs.push("--settings", JSON.stringify(flagSettings));
|
|
9794
9878
|
}
|
|
9795
9879
|
if (opts.permissionMode === "bypassPermissions") {
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9880
|
+
if (!opts.bypass) {
|
|
9881
|
+
log.warn(
|
|
9882
|
+
"interactive permissionMode bypassPermissions ignored: Claude Code prompts for confirmation in the TUI"
|
|
9883
|
+
);
|
|
9884
|
+
}
|
|
9799
9885
|
} else if (opts.permissionMode) {
|
|
9800
9886
|
extraArgs.push("--permission-mode", opts.permissionMode);
|
|
9801
9887
|
}
|
|
@@ -9847,6 +9933,7 @@ function heartbeatFrame(sessionId, openCall, now = Date.now()) {
|
|
|
9847
9933
|
}
|
|
9848
9934
|
function interactiveResultFrame(opts) {
|
|
9849
9935
|
const completed = opts.end === "stop" && !!opts.stopReason;
|
|
9936
|
+
const apiError = completed && opts.apiError ? opts.apiError : null;
|
|
9850
9937
|
let result;
|
|
9851
9938
|
let terminalReason = "completed";
|
|
9852
9939
|
if (!completed) {
|
|
@@ -9864,20 +9951,35 @@ function interactiveResultFrame(opts) {
|
|
|
9864
9951
|
return JSON.stringify({
|
|
9865
9952
|
type: "result",
|
|
9866
9953
|
subtype: completed ? "success" : "error_during_execution",
|
|
9867
|
-
is_error: !completed,
|
|
9954
|
+
is_error: !completed || apiError !== null,
|
|
9868
9955
|
stop_reason: completed ? opts.stopReason : null,
|
|
9869
9956
|
terminal_reason: terminalReason,
|
|
9870
|
-
result,
|
|
9957
|
+
result: apiError ? apiError.text : result,
|
|
9958
|
+
...apiError?.status !== void 0 ? { api_error_status: apiError.status } : {},
|
|
9871
9959
|
session_id: opts.sessionId,
|
|
9872
9960
|
usage: opts.usage ?? {},
|
|
9873
9961
|
// The TUI has no `can_use_tool` channel, so a permission dialog is the
|
|
9874
9962
|
// only way it asks; each one the session denied is reported in the
|
|
9875
9963
|
// headless field's place, names and ids only (h #g109).
|
|
9876
9964
|
permission_denials: opts.denials ?? [],
|
|
9877
|
-
total_cost_usd: null,
|
|
9878
|
-
duration_ms: 0
|
|
9965
|
+
total_cost_usd: opts.costUsd ?? null,
|
|
9966
|
+
duration_ms: opts.durationMs ?? 0
|
|
9879
9967
|
});
|
|
9880
9968
|
}
|
|
9969
|
+
function interactiveSpawnEnv2(env) {
|
|
9970
|
+
if (env.CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS !== void 0) return env;
|
|
9971
|
+
return { ...env, CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS: "0" };
|
|
9972
|
+
}
|
|
9973
|
+
function streamFrameFromRecord(raw) {
|
|
9974
|
+
if (!raw.includes('"isApiErrorMessage":true')) return raw;
|
|
9975
|
+
try {
|
|
9976
|
+
const rec = JSON.parse(raw);
|
|
9977
|
+
if (rec?.isApiErrorMessage !== true || rec.is_api_error_message !== void 0) return raw;
|
|
9978
|
+
return JSON.stringify({ ...rec, is_api_error_message: true });
|
|
9979
|
+
} catch {
|
|
9980
|
+
return raw;
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9881
9983
|
function carriesReplyText(line) {
|
|
9882
9984
|
try {
|
|
9883
9985
|
const rec = JSON.parse(line);
|
|
@@ -9915,6 +10017,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9915
10017
|
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
9916
10018
|
effort: opts.effort ? cliEffortLevel(opts.effort) : void 0,
|
|
9917
10019
|
resumeSessionId: opts.resumeSessionId,
|
|
10020
|
+
...opts.forkOf ? { forkOf: opts.forkOf } : {},
|
|
9918
10021
|
env: opts.env,
|
|
9919
10022
|
spawnPty: opts.spawnPty,
|
|
9920
10023
|
onScreen: (event) => {
|
|
@@ -9946,6 +10049,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9946
10049
|
effort: opts.effort,
|
|
9947
10050
|
sessionId: session.sessionId,
|
|
9948
10051
|
resumed: !!opts.resumeSessionId,
|
|
10052
|
+
forkedFrom: opts.forkOf ?? null,
|
|
9949
10053
|
jsonlPath: session.jsonlPath
|
|
9950
10054
|
});
|
|
9951
10055
|
let startPromise = null;
|
|
@@ -9975,14 +10079,14 @@ function spawnInteractiveProcess(opts) {
|
|
|
9975
10079
|
}
|
|
9976
10080
|
openCalls.clear();
|
|
9977
10081
|
denials = [];
|
|
9978
|
-
const { stopReason, end, usage, lastCallUsage, callCount, denied } = await session.tailTurn(
|
|
10082
|
+
const { stopReason, end, usage, lastCallUsage, callCount, costUsd, durationMs, apiError, denied } = await session.tailTurn(
|
|
9979
10083
|
userMsg,
|
|
9980
10084
|
(raw) => {
|
|
9981
10085
|
try {
|
|
9982
10086
|
openCalls.add(JSON.parse(raw));
|
|
9983
10087
|
} catch {
|
|
9984
10088
|
}
|
|
9985
|
-
emit2(raw);
|
|
10089
|
+
emit2(streamFrameFromRecord(raw));
|
|
9986
10090
|
},
|
|
9987
10091
|
void 0,
|
|
9988
10092
|
// Only to a listening turn: it feeds that turn's watchdog, and the
|
|
@@ -10013,7 +10117,10 @@ function spawnInteractiveProcess(opts) {
|
|
|
10013
10117
|
end,
|
|
10014
10118
|
stopReason,
|
|
10015
10119
|
usage,
|
|
10016
|
-
denials
|
|
10120
|
+
denials,
|
|
10121
|
+
costUsd,
|
|
10122
|
+
durationMs,
|
|
10123
|
+
apiError
|
|
10017
10124
|
})
|
|
10018
10125
|
);
|
|
10019
10126
|
} catch (err) {
|
|
@@ -10057,7 +10164,9 @@ function spawnInteractiveProcess(opts) {
|
|
|
10057
10164
|
configDir: opts.configDir,
|
|
10058
10165
|
model: opts.model,
|
|
10059
10166
|
settingSources: opts.settingSources === void 0 ? null : opts.settingSources,
|
|
10060
|
-
|
|
10167
|
+
// Tool-free whatever the main spawn's posture: `dontAsk` with nothing
|
|
10168
|
+
// allowed, and never the skip flag, which would win over it (h #g210).
|
|
10169
|
+
extraArgs: interactiveExtraArgs({ ...opts, permissionsAllow: [], permissionMode: "dontAsk", bypass: false }),
|
|
10061
10170
|
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
10062
10171
|
effort: opts.effort ? cliEffortLevel(opts.effort) : void 0,
|
|
10063
10172
|
env: opts.env,
|
|
@@ -11162,6 +11271,7 @@ function createTurnState(init) {
|
|
|
11162
11271
|
lastCallUsage: void 0,
|
|
11163
11272
|
resultFailure: void 0,
|
|
11164
11273
|
accountLimitHit: null,
|
|
11274
|
+
apiErrorTextShown: null,
|
|
11165
11275
|
usageLimitNote: false,
|
|
11166
11276
|
accountBlock: null,
|
|
11167
11277
|
modelRefusal: null,
|
|
@@ -12253,11 +12363,21 @@ ${plan}
|
|
|
12253
12363
|
state.hasReceivedContent = true;
|
|
12254
12364
|
}
|
|
12255
12365
|
const apiErrorReply = msg.is_api_error_message === true;
|
|
12256
|
-
const
|
|
12366
|
+
const limitFromReply = apiErrorReply && !state.accountBlock && (msg.error === "rate_limit" || isAccountLimitError({
|
|
12257
12367
|
resultText: msg.message.content.filter((b) => b.type === "text" && b.text).map((b) => String(b.text)).join("\n")
|
|
12258
12368
|
}));
|
|
12259
|
-
|
|
12369
|
+
const limitErrorReply = limitFromReply && ctx.usageLimitNoteActive;
|
|
12370
|
+
if (limitFromReply) {
|
|
12260
12371
|
state.accountLimitHit ??= recallAccountLimit(ctx.sourceAccount);
|
|
12372
|
+
const said = limitFactsFromText(
|
|
12373
|
+
msg.message.content.filter((b) => b.type === "text" && b.text).map((b) => String(b.text)).join("\n")
|
|
12374
|
+
);
|
|
12375
|
+
const known = state.accountLimitHit;
|
|
12376
|
+
state.accountLimitHit = {
|
|
12377
|
+
...known,
|
|
12378
|
+
window: known.window ?? said.window,
|
|
12379
|
+
...known.resetsAt === void 0 && said.resetsText ? { resetsText: said.resetsText } : {}
|
|
12380
|
+
};
|
|
12261
12381
|
}
|
|
12262
12382
|
if (hasText && !hasToolUse) {
|
|
12263
12383
|
startResultFallback(state);
|
|
@@ -12268,6 +12388,7 @@ ${plan}
|
|
|
12268
12388
|
for (const block2 of msg.message.content) {
|
|
12269
12389
|
if (block2.type === "text" && block2.text) {
|
|
12270
12390
|
if (limitErrorReply) continue;
|
|
12391
|
+
if (apiErrorReply) state.apiErrorTextShown = block2.text;
|
|
12271
12392
|
resetLastVisibleTextBlock(state);
|
|
12272
12393
|
const blockId = state.startTextBlock();
|
|
12273
12394
|
state.controller.enqueue({
|
|
@@ -12492,7 +12613,8 @@ ${plan}
|
|
|
12492
12613
|
state.usageLimitNote = ctx.usageLimitNoteActive && !!state.accountLimitHit && // An expired login or a billing hold writes its own note just below
|
|
12493
12614
|
// and says what to run; two notes about one failure is one too many.
|
|
12494
12615
|
!state.accountBlock && msg.is_error === true;
|
|
12495
|
-
if (!state.currentTextId && msg.is_error &&
|
|
12616
|
+
if (!state.currentTextId && msg.is_error && // Already on screen from the reply frame, word for word.
|
|
12617
|
+
!(typeof msg.result === "string" && state.apiErrorTextShown !== null && msg.result.trim() === state.apiErrorTextShown.trim()) && !state.usageLimitNote && typeof msg.result === "string" && msg.result.trim().length > 0) {
|
|
12496
12618
|
const errId = state.startTextBlock();
|
|
12497
12619
|
state.controller.enqueue({
|
|
12498
12620
|
type: "text-delta",
|
|
@@ -13067,12 +13189,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13067
13189
|
if (turnAbort.aborted) return abortedBeforeWork("selecting the transport");
|
|
13068
13190
|
const useInteractive = selectedTransport === "interactive";
|
|
13069
13191
|
const interactiveBypassRequested = this.config.interactiveBypass ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_BYPASS);
|
|
13070
|
-
const modelFallbackArmed = attempt?.armed === true && !compactionMode
|
|
13192
|
+
const modelFallbackArmed = attempt?.armed === true && !compactionMode;
|
|
13071
13193
|
const sourceAccount = normalizeAccountName(
|
|
13072
13194
|
this.config.account ?? DEFAULT_ACCOUNT
|
|
13073
13195
|
);
|
|
13074
13196
|
const baseCliPath = this.config.baseCliPath ?? this.config.cliPath;
|
|
13075
|
-
let failover =
|
|
13197
|
+
let failover = compactionMode ? { cliPath: this.config.cliPath, modelId: effectiveModelId, failedOver: false } : await resolveFailoverSpawn({
|
|
13076
13198
|
account: sourceAccount,
|
|
13077
13199
|
baseCliPath,
|
|
13078
13200
|
cliPath: this.config.cliPath,
|
|
@@ -13225,7 +13347,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13225
13347
|
deleteActiveProcess(sk);
|
|
13226
13348
|
clearCompression(sk);
|
|
13227
13349
|
}
|
|
13228
|
-
const failoverAnswer = compactionMode ||
|
|
13350
|
+
const failoverAnswer = compactionMode || mode === "generate" ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
|
|
13229
13351
|
sourceAccount,
|
|
13230
13352
|
candidates: failoverCandidates(this.config.failoverAccounts, sourceAccount)
|
|
13231
13353
|
});
|
|
@@ -13288,7 +13410,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13288
13410
|
});
|
|
13289
13411
|
}
|
|
13290
13412
|
const processForAccount = getActiveProcess(sk);
|
|
13291
|
-
if (!compactionMode &&
|
|
13413
|
+
if (!compactionMode && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
|
|
13292
13414
|
log.notice("claude process belongs to another account; starting fresh", {
|
|
13293
13415
|
sessionKey: sk,
|
|
13294
13416
|
was: processForAccount.cliPath,
|
|
@@ -13305,7 +13427,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13305
13427
|
includeHistoryContext = true;
|
|
13306
13428
|
}
|
|
13307
13429
|
let forkFromClaudeSessionId;
|
|
13308
|
-
if (this.config.forkSessions === true && includeHistoryContext && !compactionMode &&
|
|
13430
|
+
if (this.config.forkSessions === true && includeHistoryContext && !compactionMode && failoverAnswer?.kind !== "switch") {
|
|
13309
13431
|
const parent = findForkParent({
|
|
13310
13432
|
sessionKey: sk,
|
|
13311
13433
|
prompt: options.prompt,
|
|
@@ -13361,7 +13483,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13361
13483
|
});
|
|
13362
13484
|
}
|
|
13363
13485
|
}
|
|
13364
|
-
if (this.config.forkSessions === true && !compactionMode
|
|
13486
|
+
if (this.config.forkSessions === true && !compactionMode) {
|
|
13365
13487
|
recordForkFingerprint(sk, options.prompt, cliPath);
|
|
13366
13488
|
}
|
|
13367
13489
|
const { model: spawnModelId, fast: fastMode } = parseModelId(failover.modelId);
|
|
@@ -13417,6 +13539,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13417
13539
|
if (useInteractive) reportUnmeasuredInteractiveCli(cliVersion);
|
|
13418
13540
|
const ptyReadOnly = useInteractive && !compactionMode && !doctor && isReadOnlyPermissionMode(this.config.permissionMode);
|
|
13419
13541
|
const ptyReadOnlySupported = cliSupportsRestricted(cliVersion) && cliSupportsDontAsk(cliVersion);
|
|
13542
|
+
const ptyBypassSupported = cliSupportsInteractiveBypass(cliVersion);
|
|
13420
13543
|
if (ptyReadOnly && !ptyReadOnlySupported) {
|
|
13421
13544
|
throw new Error(
|
|
13422
13545
|
"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."
|
|
@@ -13426,14 +13549,13 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13426
13549
|
this.config.failoverAccounts,
|
|
13427
13550
|
sourceAccount
|
|
13428
13551
|
);
|
|
13429
|
-
const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode &&
|
|
13552
|
+
const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode && // A `doGenerate` caller takes the override and reports the plain
|
|
13430
13553
|
// rate-limit error, never the form. See `TurnMode`.
|
|
13431
13554
|
mode !== "generate" && isAccountFailoverQuestionActive({
|
|
13432
13555
|
configured: this.config.accountFailover,
|
|
13433
13556
|
candidates: failoverAccounts,
|
|
13434
13557
|
opencodeHasQuestion: (await loadLiveToolInfo()).hasQuestion,
|
|
13435
13558
|
compactionMode,
|
|
13436
|
-
interactive: !!useInteractive,
|
|
13437
13559
|
// A subagent follows its parent's account for free, because the
|
|
13438
13560
|
// override is account-scoped. Asking it would put a form in a session
|
|
13439
13561
|
// the operator is usually not even looking at.
|
|
@@ -13815,7 +13937,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13815
13937
|
}
|
|
13816
13938
|
if (interactiveBypassRequested) {
|
|
13817
13939
|
log.warn(
|
|
13818
|
-
"interactiveBypass ignored:
|
|
13940
|
+
"interactiveBypass is ignored: skipPermissions governs the interactive transport too"
|
|
13819
13941
|
);
|
|
13820
13942
|
}
|
|
13821
13943
|
const skillPluginDirs = await resolveSkillPluginDirs({
|
|
@@ -13829,13 +13951,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13829
13951
|
const posture = interactivePermissionPosture({
|
|
13830
13952
|
permissionMode: self.config.permissionMode,
|
|
13831
13953
|
allow,
|
|
13832
|
-
supportsReadOnly: ptyReadOnlySupported
|
|
13954
|
+
supportsReadOnly: ptyReadOnlySupported,
|
|
13955
|
+
skipPermissions: self.config.skipPermissions,
|
|
13956
|
+
controlRequestBehavior: self.config.controlRequestBehavior,
|
|
13957
|
+
controlRequestToolBehaviors: self.config.controlRequestToolBehaviors,
|
|
13958
|
+
supportsBypass: ptyBypassSupported
|
|
13833
13959
|
});
|
|
13834
13960
|
if (!posture) throw new Error("the interactive transport cannot hold this permission posture");
|
|
13835
13961
|
const ap = spawnInteractiveProcess({
|
|
13836
13962
|
cwd,
|
|
13837
13963
|
cliPath,
|
|
13838
|
-
|
|
13964
|
+
// On an account switch, the other account's directory: the
|
|
13965
|
+
// TUI writes its transcript there (h #g209).
|
|
13966
|
+
configDir: self.skillBridgeSpawn(failover).configDir,
|
|
13839
13967
|
model: spawnModelId,
|
|
13840
13968
|
fastMode,
|
|
13841
13969
|
mcpConfigPaths: mcp.paths,
|
|
@@ -13843,19 +13971,26 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13843
13971
|
permissionsAllow: posture.allow,
|
|
13844
13972
|
permissionMode: posture.permissionMode,
|
|
13845
13973
|
restricted: posture.restricted,
|
|
13846
|
-
|
|
13974
|
+
bypass: posture.bypass,
|
|
13975
|
+
disallowedTools: [...wiring.allDisallowed, ...posture.disallowed],
|
|
13847
13976
|
proxyServer: state.proxyServer,
|
|
13848
13977
|
systemPromptFile,
|
|
13849
13978
|
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
13850
13979
|
effort: reasoningEffort,
|
|
13851
13980
|
resumeSessionId,
|
|
13981
|
+
// A fork of a conversation already served (`forkSessions`):
|
|
13982
|
+
// the TUI branches it instead of the history being replayed.
|
|
13983
|
+
...forkFromClaudeSessionId && !resumeSessionId ? { forkOf: forkFromClaudeSessionId } : {},
|
|
13852
13984
|
// The headless spawn's env, so hygiene, effort and the
|
|
13853
|
-
// agent's prompt cache TTL reach both transports alike
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13985
|
+
// agent's prompt cache TTL reach both transports alike, plus
|
|
13986
|
+
// what keeps a proxied call in the foreground (h #g210).
|
|
13987
|
+
env: interactiveSpawnEnv2(
|
|
13988
|
+
claudeSpawnEnv({
|
|
13989
|
+
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
13990
|
+
effort: reasoningEffort,
|
|
13991
|
+
promptCacheTtl
|
|
13992
|
+
})
|
|
13993
|
+
)
|
|
13859
13994
|
});
|
|
13860
13995
|
ap.proc.once(
|
|
13861
13996
|
"exit",
|
|
@@ -13863,6 +13998,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13863
13998
|
);
|
|
13864
13999
|
ap.mcpHash = mcp.bridgedHash;
|
|
13865
14000
|
ap.mcpServers = mcp.allEnabledServerNames;
|
|
14001
|
+
ap.cliPath = cliPath;
|
|
13866
14002
|
setActiveProcess(sk, ap);
|
|
13867
14003
|
state.proc = ap.proc;
|
|
13868
14004
|
state.lineEmitter = ap.lineEmitter;
|
|
@@ -13870,6 +14006,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13870
14006
|
log.info("spawned interactive claude session", {
|
|
13871
14007
|
sk,
|
|
13872
14008
|
resumed: !!resumeSessionId,
|
|
14009
|
+
forked: !!forkFromClaudeSessionId && !resumeSessionId,
|
|
13873
14010
|
cliPath,
|
|
13874
14011
|
configDir: self.config.configDir,
|
|
13875
14012
|
model: effectiveModelId
|
|
@@ -14084,7 +14221,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14084
14221
|
sourceAccount,
|
|
14085
14222
|
candidates: failoverAccounts,
|
|
14086
14223
|
resetsAt: state.accountLimitHit?.resetsAt,
|
|
14087
|
-
window: state.accountLimitHit?.window
|
|
14224
|
+
window: state.accountLimitHit?.window,
|
|
14225
|
+
resetsText: state.accountLimitHit?.resetsText
|
|
14088
14226
|
})
|
|
14089
14227
|
});
|
|
14090
14228
|
state.endTextBlock();
|