@khalilgharbaoui/opencode-claude-code-plugin 0.42.3 → 0.44.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 +17 -0
- package/dist/index.js +243 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +14 -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"];
|
|
@@ -7691,6 +7733,13 @@ function parseAgentFrontmatter(text) {
|
|
|
7691
7733
|
}
|
|
7692
7734
|
return record;
|
|
7693
7735
|
}
|
|
7736
|
+
function agentMarkdownBody(text) {
|
|
7737
|
+
if (!text.startsWith("---")) return text.trim() || void 0;
|
|
7738
|
+
const lines = text.split(/\r?\n/);
|
|
7739
|
+
const close = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
7740
|
+
if (close === -1) return void 0;
|
|
7741
|
+
return lines.slice(close + 1).join("\n").trim() || void 0;
|
|
7742
|
+
}
|
|
7694
7743
|
function parseFallbackModelList(value) {
|
|
7695
7744
|
if (Array.isArray(value)) {
|
|
7696
7745
|
return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
@@ -7714,6 +7763,8 @@ async function readAgentMarkdownRecords(directories) {
|
|
|
7714
7763
|
try {
|
|
7715
7764
|
const text = await readFile(path7.join(directory, entry), "utf8");
|
|
7716
7765
|
records2[name] = parseAgentFrontmatter(text);
|
|
7766
|
+
const prompt = agentMarkdownBody(text);
|
|
7767
|
+
if (prompt) records2[name].prompt = prompt;
|
|
7717
7768
|
} catch (err) {
|
|
7718
7769
|
log.debug("failed to read agent markdown", {
|
|
7719
7770
|
file: path7.join(directory, entry),
|
|
@@ -8861,6 +8912,7 @@ function isApiCallRecord(rec) {
|
|
|
8861
8912
|
}
|
|
8862
8913
|
var TurnUsageAccumulator = class {
|
|
8863
8914
|
calls = /* @__PURE__ */ new Map();
|
|
8915
|
+
models = /* @__PURE__ */ new Map();
|
|
8864
8916
|
lastKey = null;
|
|
8865
8917
|
/** Feed every transcript record of the turn, in file order. */
|
|
8866
8918
|
add(rec) {
|
|
@@ -8868,8 +8920,24 @@ var TurnUsageAccumulator = class {
|
|
|
8868
8920
|
const id = rec.message.id;
|
|
8869
8921
|
const key = typeof id === "string" && id.length > 0 ? id : `uuid:${rec.uuid ?? this.calls.size}`;
|
|
8870
8922
|
this.calls.set(key, rec.message.usage);
|
|
8923
|
+
this.models.set(key, rec.message.model);
|
|
8871
8924
|
this.lastKey = key;
|
|
8872
8925
|
}
|
|
8926
|
+
/**
|
|
8927
|
+
* The turn's list-price cost over its distinct calls, the headless
|
|
8928
|
+
* `result`'s `total_cost_usd` (h #g208). Null when no call was made or any
|
|
8929
|
+
* call's model is one the catalog cannot price, never a partial sum.
|
|
8930
|
+
*/
|
|
8931
|
+
get costUsd() {
|
|
8932
|
+
if (this.calls.size === 0) return null;
|
|
8933
|
+
let total = 0;
|
|
8934
|
+
for (const [key, usage] of this.calls) {
|
|
8935
|
+
const cost = apiCallCostUsd(this.models.get(key), usage);
|
|
8936
|
+
if (cost === null) return null;
|
|
8937
|
+
total += cost;
|
|
8938
|
+
}
|
|
8939
|
+
return total;
|
|
8940
|
+
}
|
|
8873
8941
|
/** Distinct API calls seen, for diagnostics. */
|
|
8874
8942
|
get callCount() {
|
|
8875
8943
|
return this.calls.size;
|
|
@@ -9620,11 +9688,29 @@ var ClaudeSession = class {
|
|
|
9620
9688
|
async tailTurn(prompt, onLine, perTurnTimeoutMs, onHeartbeat) {
|
|
9621
9689
|
const timeout = perTurnTimeoutMs ?? this.o.turnTimeoutMs;
|
|
9622
9690
|
const usage = new TurnUsageAccumulator();
|
|
9691
|
+
const startedAt = Date.now();
|
|
9692
|
+
let reportedDurationMs = null;
|
|
9693
|
+
let apiError = null;
|
|
9623
9694
|
const { stopReason, end, denied } = await this.runTurnLoop(
|
|
9624
9695
|
prompt,
|
|
9625
9696
|
(raw, rec) => {
|
|
9626
9697
|
onLine(raw);
|
|
9627
9698
|
if (rec) usage.add(rec);
|
|
9699
|
+
if (rec?.type === "assistant") {
|
|
9700
|
+
if (rec.isApiErrorMessage === true) {
|
|
9701
|
+
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");
|
|
9702
|
+
apiError = {
|
|
9703
|
+
text,
|
|
9704
|
+
...typeof rec.apiErrorStatus === "number" ? { status: rec.apiErrorStatus } : {},
|
|
9705
|
+
...typeof rec.error === "string" ? { kind: rec.error } : {}
|
|
9706
|
+
};
|
|
9707
|
+
} else if (rec.message?.model !== "<synthetic>") {
|
|
9708
|
+
apiError = null;
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
if (isTurnDurationRecord(rec) && typeof rec.durationMs === "number") {
|
|
9712
|
+
reportedDurationMs = rec.durationMs;
|
|
9713
|
+
}
|
|
9628
9714
|
},
|
|
9629
9715
|
timeout,
|
|
9630
9716
|
onHeartbeat
|
|
@@ -9642,6 +9728,9 @@ var ClaudeSession = class {
|
|
|
9642
9728
|
usage: usage.turnTotal,
|
|
9643
9729
|
lastCallUsage: usage.lastCall,
|
|
9644
9730
|
callCount: usage.callCount,
|
|
9731
|
+
costUsd: usage.costUsd,
|
|
9732
|
+
durationMs: reportedDurationMs ?? Date.now() - startedAt,
|
|
9733
|
+
apiError,
|
|
9645
9734
|
denied
|
|
9646
9735
|
};
|
|
9647
9736
|
}
|
|
@@ -9789,13 +9878,19 @@ function interactiveExtraArgs(opts) {
|
|
|
9789
9878
|
if (opts.fastMode) {
|
|
9790
9879
|
flagSettings.fastMode = true;
|
|
9791
9880
|
}
|
|
9881
|
+
if (opts.bypass) {
|
|
9882
|
+
flagSettings.skipDangerousModePermissionPrompt = true;
|
|
9883
|
+
extraArgs.push("--dangerously-skip-permissions");
|
|
9884
|
+
}
|
|
9792
9885
|
if (Object.keys(flagSettings).length > 0) {
|
|
9793
9886
|
extraArgs.push("--settings", JSON.stringify(flagSettings));
|
|
9794
9887
|
}
|
|
9795
9888
|
if (opts.permissionMode === "bypassPermissions") {
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9889
|
+
if (!opts.bypass) {
|
|
9890
|
+
log.warn(
|
|
9891
|
+
"interactive permissionMode bypassPermissions ignored: Claude Code prompts for confirmation in the TUI"
|
|
9892
|
+
);
|
|
9893
|
+
}
|
|
9799
9894
|
} else if (opts.permissionMode) {
|
|
9800
9895
|
extraArgs.push("--permission-mode", opts.permissionMode);
|
|
9801
9896
|
}
|
|
@@ -9847,6 +9942,7 @@ function heartbeatFrame(sessionId, openCall, now = Date.now()) {
|
|
|
9847
9942
|
}
|
|
9848
9943
|
function interactiveResultFrame(opts) {
|
|
9849
9944
|
const completed = opts.end === "stop" && !!opts.stopReason;
|
|
9945
|
+
const apiError = completed && opts.apiError ? opts.apiError : null;
|
|
9850
9946
|
let result;
|
|
9851
9947
|
let terminalReason = "completed";
|
|
9852
9948
|
if (!completed) {
|
|
@@ -9864,20 +9960,35 @@ function interactiveResultFrame(opts) {
|
|
|
9864
9960
|
return JSON.stringify({
|
|
9865
9961
|
type: "result",
|
|
9866
9962
|
subtype: completed ? "success" : "error_during_execution",
|
|
9867
|
-
is_error: !completed,
|
|
9963
|
+
is_error: !completed || apiError !== null,
|
|
9868
9964
|
stop_reason: completed ? opts.stopReason : null,
|
|
9869
9965
|
terminal_reason: terminalReason,
|
|
9870
|
-
result,
|
|
9966
|
+
result: apiError ? apiError.text : result,
|
|
9967
|
+
...apiError?.status !== void 0 ? { api_error_status: apiError.status } : {},
|
|
9871
9968
|
session_id: opts.sessionId,
|
|
9872
9969
|
usage: opts.usage ?? {},
|
|
9873
9970
|
// The TUI has no `can_use_tool` channel, so a permission dialog is the
|
|
9874
9971
|
// only way it asks; each one the session denied is reported in the
|
|
9875
9972
|
// headless field's place, names and ids only (h #g109).
|
|
9876
9973
|
permission_denials: opts.denials ?? [],
|
|
9877
|
-
total_cost_usd: null,
|
|
9878
|
-
duration_ms: 0
|
|
9974
|
+
total_cost_usd: opts.costUsd ?? null,
|
|
9975
|
+
duration_ms: opts.durationMs ?? 0
|
|
9879
9976
|
});
|
|
9880
9977
|
}
|
|
9978
|
+
function interactiveSpawnEnv2(env) {
|
|
9979
|
+
if (env.CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS !== void 0) return env;
|
|
9980
|
+
return { ...env, CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS: "0" };
|
|
9981
|
+
}
|
|
9982
|
+
function streamFrameFromRecord(raw) {
|
|
9983
|
+
if (!raw.includes('"isApiErrorMessage":true')) return raw;
|
|
9984
|
+
try {
|
|
9985
|
+
const rec = JSON.parse(raw);
|
|
9986
|
+
if (rec?.isApiErrorMessage !== true || rec.is_api_error_message !== void 0) return raw;
|
|
9987
|
+
return JSON.stringify({ ...rec, is_api_error_message: true });
|
|
9988
|
+
} catch {
|
|
9989
|
+
return raw;
|
|
9990
|
+
}
|
|
9991
|
+
}
|
|
9881
9992
|
function carriesReplyText(line) {
|
|
9882
9993
|
try {
|
|
9883
9994
|
const rec = JSON.parse(line);
|
|
@@ -9915,6 +10026,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9915
10026
|
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
9916
10027
|
effort: opts.effort ? cliEffortLevel(opts.effort) : void 0,
|
|
9917
10028
|
resumeSessionId: opts.resumeSessionId,
|
|
10029
|
+
...opts.forkOf ? { forkOf: opts.forkOf } : {},
|
|
9918
10030
|
env: opts.env,
|
|
9919
10031
|
spawnPty: opts.spawnPty,
|
|
9920
10032
|
onScreen: (event) => {
|
|
@@ -9946,6 +10058,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
9946
10058
|
effort: opts.effort,
|
|
9947
10059
|
sessionId: session.sessionId,
|
|
9948
10060
|
resumed: !!opts.resumeSessionId,
|
|
10061
|
+
forkedFrom: opts.forkOf ?? null,
|
|
9949
10062
|
jsonlPath: session.jsonlPath
|
|
9950
10063
|
});
|
|
9951
10064
|
let startPromise = null;
|
|
@@ -9975,14 +10088,14 @@ function spawnInteractiveProcess(opts) {
|
|
|
9975
10088
|
}
|
|
9976
10089
|
openCalls.clear();
|
|
9977
10090
|
denials = [];
|
|
9978
|
-
const { stopReason, end, usage, lastCallUsage, callCount, denied } = await session.tailTurn(
|
|
10091
|
+
const { stopReason, end, usage, lastCallUsage, callCount, costUsd, durationMs, apiError, denied } = await session.tailTurn(
|
|
9979
10092
|
userMsg,
|
|
9980
10093
|
(raw) => {
|
|
9981
10094
|
try {
|
|
9982
10095
|
openCalls.add(JSON.parse(raw));
|
|
9983
10096
|
} catch {
|
|
9984
10097
|
}
|
|
9985
|
-
emit2(raw);
|
|
10098
|
+
emit2(streamFrameFromRecord(raw));
|
|
9986
10099
|
},
|
|
9987
10100
|
void 0,
|
|
9988
10101
|
// Only to a listening turn: it feeds that turn's watchdog, and the
|
|
@@ -10013,7 +10126,10 @@ function spawnInteractiveProcess(opts) {
|
|
|
10013
10126
|
end,
|
|
10014
10127
|
stopReason,
|
|
10015
10128
|
usage,
|
|
10016
|
-
denials
|
|
10129
|
+
denials,
|
|
10130
|
+
costUsd,
|
|
10131
|
+
durationMs,
|
|
10132
|
+
apiError
|
|
10017
10133
|
})
|
|
10018
10134
|
);
|
|
10019
10135
|
} catch (err) {
|
|
@@ -10057,7 +10173,9 @@ function spawnInteractiveProcess(opts) {
|
|
|
10057
10173
|
configDir: opts.configDir,
|
|
10058
10174
|
model: opts.model,
|
|
10059
10175
|
settingSources: opts.settingSources === void 0 ? null : opts.settingSources,
|
|
10060
|
-
|
|
10176
|
+
// Tool-free whatever the main spawn's posture: `dontAsk` with nothing
|
|
10177
|
+
// allowed, and never the skip flag, which would win over it (h #g210).
|
|
10178
|
+
extraArgs: interactiveExtraArgs({ ...opts, permissionsAllow: [], permissionMode: "dontAsk", bypass: false }),
|
|
10061
10179
|
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
10062
10180
|
effort: opts.effort ? cliEffortLevel(opts.effort) : void 0,
|
|
10063
10181
|
env: opts.env,
|
|
@@ -10175,7 +10293,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
10175
10293
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
10176
10294
|
import { homedir as homedir8 } from "os";
|
|
10177
10295
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
10178
|
-
import { dirname as dirname6, join as join11 } from "path";
|
|
10296
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
10179
10297
|
function readPromptFileIfPresent(path11) {
|
|
10180
10298
|
try {
|
|
10181
10299
|
const content = readFileSync7(path11, "utf8").trim();
|
|
@@ -10281,6 +10399,36 @@ function extractSystemMessages(prompt) {
|
|
|
10281
10399
|
}
|
|
10282
10400
|
return out;
|
|
10283
10401
|
}
|
|
10402
|
+
var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
|
|
10403
|
+
function userAuthoredInstructions(forwarded, opts = {}) {
|
|
10404
|
+
const readFile2 = opts.readFile ?? ((path11) => {
|
|
10405
|
+
try {
|
|
10406
|
+
return readFileSync7(path11, "utf8");
|
|
10407
|
+
} catch {
|
|
10408
|
+
return null;
|
|
10409
|
+
}
|
|
10410
|
+
});
|
|
10411
|
+
const out = [];
|
|
10412
|
+
const agentPrompt = opts.agentPrompt?.trim();
|
|
10413
|
+
if (agentPrompt && forwarded.includes(agentPrompt)) {
|
|
10414
|
+
out.push({ text: agentPrompt, content: agentPrompt });
|
|
10415
|
+
}
|
|
10416
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10417
|
+
for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
|
|
10418
|
+
const path11 = match[1].trim();
|
|
10419
|
+
if (seen.has(path11) || !isAbsolute3(path11)) continue;
|
|
10420
|
+
if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path11))) continue;
|
|
10421
|
+
seen.add(path11);
|
|
10422
|
+
const file = readFile2(path11);
|
|
10423
|
+
if (!file?.trim()) continue;
|
|
10424
|
+
const start = match.index + match[0].length + 1;
|
|
10425
|
+
const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
|
|
10426
|
+
if (content === null) continue;
|
|
10427
|
+
out.push({ text: `Instructions from: ${path11}
|
|
10428
|
+
${content}`, content });
|
|
10429
|
+
}
|
|
10430
|
+
return out;
|
|
10431
|
+
}
|
|
10284
10432
|
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = [], options = {}) {
|
|
10285
10433
|
const parts = [];
|
|
10286
10434
|
if (options.compressionSummary?.trim()) {
|
|
@@ -10305,6 +10453,11 @@ ${options.compressionSummary.trim()}`
|
|
|
10305
10453
|
if (pushGlobal) parts.push(globalAgents);
|
|
10306
10454
|
if (pushWorkspace) parts.push(workspaceAgents);
|
|
10307
10455
|
if (pushGlobal || pushWorkspace) parts.push(AGENTS_MAINTENANCE_HINT);
|
|
10456
|
+
for (const instruction of options.userInstructions ?? []) {
|
|
10457
|
+
const authored = instruction.content.trim();
|
|
10458
|
+
if (authored === globalAgents || authored === workspaceAgents) continue;
|
|
10459
|
+
parts.push(instruction.text);
|
|
10460
|
+
}
|
|
10308
10461
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
10309
10462
|
const content = parts.join("\n\n");
|
|
10310
10463
|
if (!content) return void 0;
|
|
@@ -11162,6 +11315,7 @@ function createTurnState(init) {
|
|
|
11162
11315
|
lastCallUsage: void 0,
|
|
11163
11316
|
resultFailure: void 0,
|
|
11164
11317
|
accountLimitHit: null,
|
|
11318
|
+
apiErrorTextShown: null,
|
|
11165
11319
|
usageLimitNote: false,
|
|
11166
11320
|
accountBlock: null,
|
|
11167
11321
|
modelRefusal: null,
|
|
@@ -12253,11 +12407,21 @@ ${plan}
|
|
|
12253
12407
|
state.hasReceivedContent = true;
|
|
12254
12408
|
}
|
|
12255
12409
|
const apiErrorReply = msg.is_api_error_message === true;
|
|
12256
|
-
const
|
|
12410
|
+
const limitFromReply = apiErrorReply && !state.accountBlock && (msg.error === "rate_limit" || isAccountLimitError({
|
|
12257
12411
|
resultText: msg.message.content.filter((b) => b.type === "text" && b.text).map((b) => String(b.text)).join("\n")
|
|
12258
12412
|
}));
|
|
12259
|
-
|
|
12413
|
+
const limitErrorReply = limitFromReply && ctx.usageLimitNoteActive;
|
|
12414
|
+
if (limitFromReply) {
|
|
12260
12415
|
state.accountLimitHit ??= recallAccountLimit(ctx.sourceAccount);
|
|
12416
|
+
const said = limitFactsFromText(
|
|
12417
|
+
msg.message.content.filter((b) => b.type === "text" && b.text).map((b) => String(b.text)).join("\n")
|
|
12418
|
+
);
|
|
12419
|
+
const known = state.accountLimitHit;
|
|
12420
|
+
state.accountLimitHit = {
|
|
12421
|
+
...known,
|
|
12422
|
+
window: known.window ?? said.window,
|
|
12423
|
+
...known.resetsAt === void 0 && said.resetsText ? { resetsText: said.resetsText } : {}
|
|
12424
|
+
};
|
|
12261
12425
|
}
|
|
12262
12426
|
if (hasText && !hasToolUse) {
|
|
12263
12427
|
startResultFallback(state);
|
|
@@ -12268,6 +12432,7 @@ ${plan}
|
|
|
12268
12432
|
for (const block2 of msg.message.content) {
|
|
12269
12433
|
if (block2.type === "text" && block2.text) {
|
|
12270
12434
|
if (limitErrorReply) continue;
|
|
12435
|
+
if (apiErrorReply) state.apiErrorTextShown = block2.text;
|
|
12271
12436
|
resetLastVisibleTextBlock(state);
|
|
12272
12437
|
const blockId = state.startTextBlock();
|
|
12273
12438
|
state.controller.enqueue({
|
|
@@ -12492,7 +12657,8 @@ ${plan}
|
|
|
12492
12657
|
state.usageLimitNote = ctx.usageLimitNoteActive && !!state.accountLimitHit && // An expired login or a billing hold writes its own note just below
|
|
12493
12658
|
// and says what to run; two notes about one failure is one too many.
|
|
12494
12659
|
!state.accountBlock && msg.is_error === true;
|
|
12495
|
-
if (!state.currentTextId && msg.is_error &&
|
|
12660
|
+
if (!state.currentTextId && msg.is_error && // Already on screen from the reply frame, word for word.
|
|
12661
|
+
!(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
12662
|
const errId = state.startTextBlock();
|
|
12497
12663
|
state.controller.enqueue({
|
|
12498
12664
|
type: "text-delta",
|
|
@@ -13067,12 +13233,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13067
13233
|
if (turnAbort.aborted) return abortedBeforeWork("selecting the transport");
|
|
13068
13234
|
const useInteractive = selectedTransport === "interactive";
|
|
13069
13235
|
const interactiveBypassRequested = this.config.interactiveBypass ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_BYPASS);
|
|
13070
|
-
const modelFallbackArmed = attempt?.armed === true && !compactionMode
|
|
13236
|
+
const modelFallbackArmed = attempt?.armed === true && !compactionMode;
|
|
13071
13237
|
const sourceAccount = normalizeAccountName(
|
|
13072
13238
|
this.config.account ?? DEFAULT_ACCOUNT
|
|
13073
13239
|
);
|
|
13074
13240
|
const baseCliPath = this.config.baseCliPath ?? this.config.cliPath;
|
|
13075
|
-
let failover =
|
|
13241
|
+
let failover = compactionMode ? { cliPath: this.config.cliPath, modelId: effectiveModelId, failedOver: false } : await resolveFailoverSpawn({
|
|
13076
13242
|
account: sourceAccount,
|
|
13077
13243
|
baseCliPath,
|
|
13078
13244
|
cliPath: this.config.cliPath,
|
|
@@ -13225,7 +13391,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13225
13391
|
deleteActiveProcess(sk);
|
|
13226
13392
|
clearCompression(sk);
|
|
13227
13393
|
}
|
|
13228
|
-
const failoverAnswer = compactionMode ||
|
|
13394
|
+
const failoverAnswer = compactionMode || mode === "generate" ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
|
|
13229
13395
|
sourceAccount,
|
|
13230
13396
|
candidates: failoverCandidates(this.config.failoverAccounts, sourceAccount)
|
|
13231
13397
|
});
|
|
@@ -13288,7 +13454,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13288
13454
|
});
|
|
13289
13455
|
}
|
|
13290
13456
|
const processForAccount = getActiveProcess(sk);
|
|
13291
|
-
if (!compactionMode &&
|
|
13457
|
+
if (!compactionMode && processForAccount?.cliPath && processForAccount.cliPath !== cliPath) {
|
|
13292
13458
|
log.notice("claude process belongs to another account; starting fresh", {
|
|
13293
13459
|
sessionKey: sk,
|
|
13294
13460
|
was: processForAccount.cliPath,
|
|
@@ -13305,7 +13471,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13305
13471
|
includeHistoryContext = true;
|
|
13306
13472
|
}
|
|
13307
13473
|
let forkFromClaudeSessionId;
|
|
13308
|
-
if (this.config.forkSessions === true && includeHistoryContext && !compactionMode &&
|
|
13474
|
+
if (this.config.forkSessions === true && includeHistoryContext && !compactionMode && failoverAnswer?.kind !== "switch") {
|
|
13309
13475
|
const parent = findForkParent({
|
|
13310
13476
|
sessionKey: sk,
|
|
13311
13477
|
prompt: options.prompt,
|
|
@@ -13361,7 +13527,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13361
13527
|
});
|
|
13362
13528
|
}
|
|
13363
13529
|
}
|
|
13364
|
-
if (this.config.forkSessions === true && !compactionMode
|
|
13530
|
+
if (this.config.forkSessions === true && !compactionMode) {
|
|
13365
13531
|
recordForkFingerprint(sk, options.prompt, cliPath);
|
|
13366
13532
|
}
|
|
13367
13533
|
const { model: spawnModelId, fast: fastMode } = parseModelId(failover.modelId);
|
|
@@ -13417,6 +13583,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13417
13583
|
if (useInteractive) reportUnmeasuredInteractiveCli(cliVersion);
|
|
13418
13584
|
const ptyReadOnly = useInteractive && !compactionMode && !doctor && isReadOnlyPermissionMode(this.config.permissionMode);
|
|
13419
13585
|
const ptyReadOnlySupported = cliSupportsRestricted(cliVersion) && cliSupportsDontAsk(cliVersion);
|
|
13586
|
+
const ptyBypassSupported = cliSupportsInteractiveBypass(cliVersion);
|
|
13420
13587
|
if (ptyReadOnly && !ptyReadOnlySupported) {
|
|
13421
13588
|
throw new Error(
|
|
13422
13589
|
"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 +13593,13 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13426
13593
|
this.config.failoverAccounts,
|
|
13427
13594
|
sourceAccount
|
|
13428
13595
|
);
|
|
13429
|
-
const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode &&
|
|
13596
|
+
const failoverAskActive = failoverAccounts.length > 0 && this.config.accountFailover === "ask" && !compactionMode && // A `doGenerate` caller takes the override and reports the plain
|
|
13430
13597
|
// rate-limit error, never the form. See `TurnMode`.
|
|
13431
13598
|
mode !== "generate" && isAccountFailoverQuestionActive({
|
|
13432
13599
|
configured: this.config.accountFailover,
|
|
13433
13600
|
candidates: failoverAccounts,
|
|
13434
13601
|
opencodeHasQuestion: (await loadLiveToolInfo()).hasQuestion,
|
|
13435
13602
|
compactionMode,
|
|
13436
|
-
interactive: !!useInteractive,
|
|
13437
13603
|
// A subagent follows its parent's account for free, because the
|
|
13438
13604
|
// override is account-scoped. Asking it would put a form in a session
|
|
13439
13605
|
// the operator is usually not even looking at.
|
|
@@ -13784,6 +13950,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13784
13950
|
"WebFetch"
|
|
13785
13951
|
]
|
|
13786
13952
|
];
|
|
13953
|
+
const userInstructions = self.config.interactiveUserInstructions === true ? userAuthoredInstructions(extractSystemMessages(options.prompt).join("\n\n"), {
|
|
13954
|
+
agentPrompt: getAgentRegistry()[self.getOpencodeAgent(options) ?? ""]?.prompt
|
|
13955
|
+
}) : [];
|
|
13956
|
+
if (userInstructions.length > 0) {
|
|
13957
|
+
log.info("forwarding user-authored instructions to the interactive TUI", {
|
|
13958
|
+
count: userInstructions.length
|
|
13959
|
+
});
|
|
13960
|
+
}
|
|
13787
13961
|
const systemPromptFile = self.config.interactiveSystemPrompt === false ? void 0 : buildAppendedSystemPrompt(
|
|
13788
13962
|
cwd,
|
|
13789
13963
|
self.config.multiStepContinuation !== false,
|
|
@@ -13805,7 +13979,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13805
13979
|
opencodeCompressEnabled: wiring.opencodeToolDefs.some(
|
|
13806
13980
|
(t) => t.name === "compress"
|
|
13807
13981
|
),
|
|
13808
|
-
compressionSummary: getCompressionSummary(sk)
|
|
13982
|
+
compressionSummary: getCompressionSummary(sk),
|
|
13983
|
+
userInstructions
|
|
13809
13984
|
}
|
|
13810
13985
|
);
|
|
13811
13986
|
if (self.config.interactiveSystemPrompt === false) {
|
|
@@ -13815,7 +13990,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13815
13990
|
}
|
|
13816
13991
|
if (interactiveBypassRequested) {
|
|
13817
13992
|
log.warn(
|
|
13818
|
-
"interactiveBypass ignored:
|
|
13993
|
+
"interactiveBypass is ignored: skipPermissions governs the interactive transport too"
|
|
13819
13994
|
);
|
|
13820
13995
|
}
|
|
13821
13996
|
const skillPluginDirs = await resolveSkillPluginDirs({
|
|
@@ -13829,13 +14004,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13829
14004
|
const posture = interactivePermissionPosture({
|
|
13830
14005
|
permissionMode: self.config.permissionMode,
|
|
13831
14006
|
allow,
|
|
13832
|
-
supportsReadOnly: ptyReadOnlySupported
|
|
14007
|
+
supportsReadOnly: ptyReadOnlySupported,
|
|
14008
|
+
skipPermissions: self.config.skipPermissions,
|
|
14009
|
+
controlRequestBehavior: self.config.controlRequestBehavior,
|
|
14010
|
+
controlRequestToolBehaviors: self.config.controlRequestToolBehaviors,
|
|
14011
|
+
supportsBypass: ptyBypassSupported
|
|
13833
14012
|
});
|
|
13834
14013
|
if (!posture) throw new Error("the interactive transport cannot hold this permission posture");
|
|
13835
14014
|
const ap = spawnInteractiveProcess({
|
|
13836
14015
|
cwd,
|
|
13837
14016
|
cliPath,
|
|
13838
|
-
|
|
14017
|
+
// On an account switch, the other account's directory: the
|
|
14018
|
+
// TUI writes its transcript there (h #g209).
|
|
14019
|
+
configDir: self.skillBridgeSpawn(failover).configDir,
|
|
13839
14020
|
model: spawnModelId,
|
|
13840
14021
|
fastMode,
|
|
13841
14022
|
mcpConfigPaths: mcp.paths,
|
|
@@ -13843,19 +14024,26 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13843
14024
|
permissionsAllow: posture.allow,
|
|
13844
14025
|
permissionMode: posture.permissionMode,
|
|
13845
14026
|
restricted: posture.restricted,
|
|
13846
|
-
|
|
14027
|
+
bypass: posture.bypass,
|
|
14028
|
+
disallowedTools: [...wiring.allDisallowed, ...posture.disallowed],
|
|
13847
14029
|
proxyServer: state.proxyServer,
|
|
13848
14030
|
systemPromptFile,
|
|
13849
14031
|
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
13850
14032
|
effort: reasoningEffort,
|
|
13851
14033
|
resumeSessionId,
|
|
14034
|
+
// A fork of a conversation already served (`forkSessions`):
|
|
14035
|
+
// the TUI branches it instead of the history being replayed.
|
|
14036
|
+
...forkFromClaudeSessionId && !resumeSessionId ? { forkOf: forkFromClaudeSessionId } : {},
|
|
13852
14037
|
// 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
|
-
|
|
14038
|
+
// agent's prompt cache TTL reach both transports alike, plus
|
|
14039
|
+
// what keeps a proxied call in the foreground (h #g210).
|
|
14040
|
+
env: interactiveSpawnEnv2(
|
|
14041
|
+
claudeSpawnEnv({
|
|
14042
|
+
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey,
|
|
14043
|
+
effort: reasoningEffort,
|
|
14044
|
+
promptCacheTtl
|
|
14045
|
+
})
|
|
14046
|
+
)
|
|
13859
14047
|
});
|
|
13860
14048
|
ap.proc.once(
|
|
13861
14049
|
"exit",
|
|
@@ -13863,6 +14051,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13863
14051
|
);
|
|
13864
14052
|
ap.mcpHash = mcp.bridgedHash;
|
|
13865
14053
|
ap.mcpServers = mcp.allEnabledServerNames;
|
|
14054
|
+
ap.cliPath = cliPath;
|
|
13866
14055
|
setActiveProcess(sk, ap);
|
|
13867
14056
|
state.proc = ap.proc;
|
|
13868
14057
|
state.lineEmitter = ap.lineEmitter;
|
|
@@ -13870,6 +14059,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13870
14059
|
log.info("spawned interactive claude session", {
|
|
13871
14060
|
sk,
|
|
13872
14061
|
resumed: !!resumeSessionId,
|
|
14062
|
+
forked: !!forkFromClaudeSessionId && !resumeSessionId,
|
|
13873
14063
|
cliPath,
|
|
13874
14064
|
configDir: self.config.configDir,
|
|
13875
14065
|
model: effectiveModelId
|
|
@@ -14084,7 +14274,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14084
14274
|
sourceAccount,
|
|
14085
14275
|
candidates: failoverAccounts,
|
|
14086
14276
|
resetsAt: state.accountLimitHit?.resetsAt,
|
|
14087
|
-
window: state.accountLimitHit?.window
|
|
14277
|
+
window: state.accountLimitHit?.window,
|
|
14278
|
+
resetsText: state.accountLimitHit?.resetsText
|
|
14088
14279
|
})
|
|
14089
14280
|
});
|
|
14090
14281
|
state.endTextBlock();
|
|
@@ -15323,7 +15514,8 @@ function createClaudeCode(settings = {}) {
|
|
|
15323
15514
|
interactive: settings.interactive,
|
|
15324
15515
|
interactiveBypass: settings.interactiveBypass,
|
|
15325
15516
|
interactiveAllowTools: settings.interactiveAllowTools,
|
|
15326
|
-
interactiveSystemPrompt: settings.interactiveSystemPrompt
|
|
15517
|
+
interactiveSystemPrompt: settings.interactiveSystemPrompt,
|
|
15518
|
+
interactiveUserInstructions: settings.interactiveUserInstructions
|
|
15327
15519
|
});
|
|
15328
15520
|
};
|
|
15329
15521
|
const provider = function(modelId) {
|
|
@@ -15542,7 +15734,8 @@ async function buildAgentRegistry(config) {
|
|
|
15542
15734
|
forceModel: pick("forceModel") ?? records2[name]?.forceModel,
|
|
15543
15735
|
reasoningEffort: pick("reasoningEffort") ?? records2[name]?.reasoningEffort,
|
|
15544
15736
|
cacheTtl: pick("cacheTtl") ?? records2[name]?.cacheTtl,
|
|
15545
|
-
fallbackModels: declaredChain.length ? declaredChain : records2[name]?.fallbackModels
|
|
15737
|
+
fallbackModels: declaredChain.length ? declaredChain : records2[name]?.fallbackModels,
|
|
15738
|
+
prompt: pick("prompt") ?? records2[name]?.prompt
|
|
15546
15739
|
};
|
|
15547
15740
|
}
|
|
15548
15741
|
setAgentRegistry(records2);
|