@khalilgharbaoui/opencode-claude-code-plugin 0.26.1 → 0.26.3
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/README.md +4 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +284 -123
- 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
|
@@ -514,6 +514,9 @@ function translateStreamForHost(stream, dialect = "v1") {
|
|
|
514
514
|
);
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
// src/account-failover.ts
|
|
518
|
+
import { homedir as homedir2 } from "os";
|
|
519
|
+
|
|
517
520
|
// src/accounts.ts
|
|
518
521
|
import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
|
|
519
522
|
import path from "path";
|
|
@@ -907,8 +910,9 @@ var QUESTION_TOOL_NAME = "question";
|
|
|
907
910
|
var APPROVED_EXIT_PLAN_MODE_MESSAGE = "User has approved your plan. You can now start coding. Start with updating your todo list if applicable.";
|
|
908
911
|
var REJECTED_EXIT_PLAN_MODE_PREFIX = "The user doesn't want to proceed with this tool use. The tool use was rejected. To tell you how to proceed, the user said:";
|
|
909
912
|
var PLAN_MODE_APPROVAL_QUESTION = "Do you want to proceed with this plan?";
|
|
910
|
-
var OPENCODE_QUESTION_RESULT_PREFIX =
|
|
911
|
-
var OPENCODE_QUESTION_RESULT_SUFFIX =
|
|
913
|
+
var OPENCODE_QUESTION_RESULT_PREFIX = "User has answered your questions: ";
|
|
914
|
+
var OPENCODE_QUESTION_RESULT_SUFFIX = ". You can now continue with the user's answers in mind.";
|
|
915
|
+
var OPENCODE_UNANSWERED = "Unanswered";
|
|
912
916
|
var KEY_SEPARATOR = "\0";
|
|
913
917
|
function isPlanModeQuestionActive(input) {
|
|
914
918
|
if (input.compactionMode) return false;
|
|
@@ -1007,29 +1011,40 @@ function unwrapToolOutput(part) {
|
|
|
1007
1011
|
return output;
|
|
1008
1012
|
}
|
|
1009
1013
|
}
|
|
1010
|
-
function unwrapOpencodeQuestionResult(value) {
|
|
1011
|
-
if (value.startsWith(OPENCODE_QUESTION_RESULT_PREFIX)
|
|
1012
|
-
return value
|
|
1013
|
-
OPENCODE_QUESTION_RESULT_PREFIX.length,
|
|
1014
|
-
-OPENCODE_QUESTION_RESULT_SUFFIX.length
|
|
1015
|
-
);
|
|
1014
|
+
function unwrapOpencodeQuestionResult(value, question) {
|
|
1015
|
+
if (!value.startsWith(OPENCODE_QUESTION_RESULT_PREFIX) || !value.endsWith(OPENCODE_QUESTION_RESULT_SUFFIX)) {
|
|
1016
|
+
return value;
|
|
1016
1017
|
}
|
|
1017
|
-
|
|
1018
|
+
const body = value.slice(
|
|
1019
|
+
OPENCODE_QUESTION_RESULT_PREFIX.length,
|
|
1020
|
+
value.length - OPENCODE_QUESTION_RESULT_SUFFIX.length
|
|
1021
|
+
);
|
|
1022
|
+
if (!body.startsWith('"') || !body.endsWith('"')) return value;
|
|
1023
|
+
let answer;
|
|
1024
|
+
const head = question === void 0 ? void 0 : `"${question}"="`;
|
|
1025
|
+
if (head && body.startsWith(head) && body.length > head.length) {
|
|
1026
|
+
answer = body.slice(head.length, -1);
|
|
1027
|
+
} else {
|
|
1028
|
+
const split = body.lastIndexOf('"="');
|
|
1029
|
+
if (split > 0) answer = body.slice(split + 3, -1);
|
|
1030
|
+
}
|
|
1031
|
+
if (answer === void 0) return value;
|
|
1032
|
+
return answer === OPENCODE_UNANSWERED ? "" : answer;
|
|
1018
1033
|
}
|
|
1019
|
-
function collectAnswerStrings(value) {
|
|
1020
|
-
if (typeof value === "string") return [unwrapOpencodeQuestionResult(value)];
|
|
1021
|
-
if (Array.isArray(value)) return value.flatMap(collectAnswerStrings);
|
|
1034
|
+
function collectAnswerStrings(value, question) {
|
|
1035
|
+
if (typeof value === "string") return [unwrapOpencodeQuestionResult(value, question)];
|
|
1036
|
+
if (Array.isArray(value)) return value.flatMap((item) => collectAnswerStrings(item, question));
|
|
1022
1037
|
if (!value || typeof value !== "object") return [];
|
|
1023
1038
|
const obj = value;
|
|
1024
1039
|
if (obj.denied === true) return [String(obj.reason ?? "question rejected")];
|
|
1025
1040
|
for (const key of ["answers", "answer", "selected", "selection", "value"]) {
|
|
1026
|
-
if (key in obj) return collectAnswerStrings(obj[key]);
|
|
1041
|
+
if (key in obj) return collectAnswerStrings(obj[key], question);
|
|
1027
1042
|
}
|
|
1028
1043
|
return [];
|
|
1029
1044
|
}
|
|
1030
1045
|
function classifyQuestionResult(part) {
|
|
1031
1046
|
const output = unwrapToolOutput(part);
|
|
1032
|
-
const answers = collectAnswerStrings(output).map((answer) => answer.trim()).filter(Boolean);
|
|
1047
|
+
const answers = collectAnswerStrings(output, PLAN_MODE_APPROVAL_QUESTION).map((answer) => answer.trim()).filter(Boolean);
|
|
1033
1048
|
if (answers.length === 1 && answers[0].toLowerCase() === "yes") {
|
|
1034
1049
|
return { approved: true, feedback: "" };
|
|
1035
1050
|
}
|
|
@@ -1070,6 +1085,35 @@ var ACCOUNT_LIMIT_PATTERNS = [
|
|
|
1070
1085
|
/third-party apps now draw from your extra usage/i,
|
|
1071
1086
|
/you[’'`]?ve hit your individual spend limit/i
|
|
1072
1087
|
];
|
|
1088
|
+
var ACCOUNT_BLOCK_MARKER = "\u258C **claude account:**";
|
|
1089
|
+
var ACCOUNT_BLOCKS = {
|
|
1090
|
+
authentication_failed: { what: "is not logged in (its login expired or was revoked)", login: true },
|
|
1091
|
+
oauth_org_not_allowed: { what: "belongs to an organization that does not allow this login", login: false },
|
|
1092
|
+
account_on_hold: { what: "is on hold", login: false },
|
|
1093
|
+
verification_required: { what: "needs to be verified at claude.ai", login: false },
|
|
1094
|
+
billing_error: { what: "has a billing problem", login: false }
|
|
1095
|
+
};
|
|
1096
|
+
function accountBlockKind(msg) {
|
|
1097
|
+
if (msg.type !== "assistant" || typeof msg.error !== "string") return null;
|
|
1098
|
+
return Object.prototype.hasOwnProperty.call(ACCOUNT_BLOCKS, msg.error) ? msg.error : null;
|
|
1099
|
+
}
|
|
1100
|
+
function describeAccountBlock(kind) {
|
|
1101
|
+
return ACCOUNT_BLOCKS[kind].what;
|
|
1102
|
+
}
|
|
1103
|
+
function loginCommandFor(configDir, home = homedir2()) {
|
|
1104
|
+
if (!configDir) return "claude auth login";
|
|
1105
|
+
const shown = configDir === home || configDir.startsWith(`${home}/`) ? `~${configDir.slice(home.length)}` : configDir;
|
|
1106
|
+
return `CLAUDE_CONFIG_DIR=${shown} claude auth login`;
|
|
1107
|
+
}
|
|
1108
|
+
function formatAccountBlockNote(input) {
|
|
1109
|
+
const block = ACCOUNT_BLOCKS[input.kind];
|
|
1110
|
+
const account = normalizeAccountName(input.account || DEFAULT_ACCOUNT);
|
|
1111
|
+
const fix = block.login ? `Log in again with \`${loginCommandFor(input.configDir)}\`, then resend your message.` : "Check the account at claude.ai, then resend your message.";
|
|
1112
|
+
const then = input.offeringSwitch ? " Or pick another account below." : "";
|
|
1113
|
+
return `
|
|
1114
|
+
${ACCOUNT_BLOCK_MARKER} the Claude account "${account}" ${block.what}. ${fix}${then}
|
|
1115
|
+
`;
|
|
1116
|
+
}
|
|
1073
1117
|
function isAccountLimitError(input) {
|
|
1074
1118
|
if (input.rateLimit && isRateLimitRejected(input.rateLimit)) return true;
|
|
1075
1119
|
const text = input.resultText;
|
|
@@ -1175,17 +1219,18 @@ function createAccountFailoverQuestionCall(sessionKey2, input, toolCallId = `${A
|
|
|
1175
1219
|
const candidates = input.candidates.map((c) => normalizeAccountName(c));
|
|
1176
1220
|
const resets = describeReset(input.resetsAt);
|
|
1177
1221
|
const until = resets ?? "opencode restarts";
|
|
1178
|
-
|
|
1179
|
-
sourceAccount: source,
|
|
1180
|
-
candidates: [...candidates],
|
|
1181
|
-
resetsAt: input.resetsAt
|
|
1182
|
-
});
|
|
1183
|
-
const question = [
|
|
1222
|
+
const question = input.reason ? `The Claude account "${source}" ${input.reason}. Continue this task on another configured account? Leaving this unanswered waits, at no cost.` : [
|
|
1184
1223
|
`The Claude account "${source}" is out of usage`,
|
|
1185
1224
|
input.window ? ` in ${input.window}` : "",
|
|
1186
1225
|
resets ? `, which resets at ${resets}` : "",
|
|
1187
1226
|
". Continue this task on another configured account? Leaving this unanswered waits, at no cost."
|
|
1188
1227
|
].join("");
|
|
1228
|
+
pendingQuestions2.set(pendingKey2(sessionKey2, toolCallId), {
|
|
1229
|
+
sourceAccount: source,
|
|
1230
|
+
candidates: [...candidates],
|
|
1231
|
+
resetsAt: input.resetsAt,
|
|
1232
|
+
question
|
|
1233
|
+
});
|
|
1189
1234
|
return {
|
|
1190
1235
|
toolCallId,
|
|
1191
1236
|
toolName: QUESTION_TOOL_NAME,
|
|
@@ -1223,7 +1268,11 @@ function classify(pending, part) {
|
|
|
1223
1268
|
reason: String(output.reason ?? "question rejected")
|
|
1224
1269
|
};
|
|
1225
1270
|
}
|
|
1226
|
-
const
|
|
1271
|
+
const outputType = part?.output?.type;
|
|
1272
|
+
if (outputType === "error-text" || outputType === "error-json") {
|
|
1273
|
+
return { kind: "stop", reason: String(output ?? "the form was dismissed") };
|
|
1274
|
+
}
|
|
1275
|
+
const answers = collectAnswerStrings(output, pending.question).map((answer) => answer.trim()).filter(Boolean);
|
|
1227
1276
|
if (answers.length === 0) return { kind: "stop", reason: "no answer" };
|
|
1228
1277
|
const picked = normalizeAccountName(answers[0]);
|
|
1229
1278
|
if (picked === STOP_ANSWER) {
|
|
@@ -1240,7 +1289,7 @@ function classify(pending, part) {
|
|
|
1240
1289
|
resetsAt: pending.resetsAt
|
|
1241
1290
|
};
|
|
1242
1291
|
}
|
|
1243
|
-
function consumeAccountFailoverAnswer(sessionKey2, prompt) {
|
|
1292
|
+
function consumeAccountFailoverAnswer(sessionKey2, prompt, fallback) {
|
|
1244
1293
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1245
1294
|
const msg = prompt[i];
|
|
1246
1295
|
if (!Array.isArray(msg.content)) continue;
|
|
@@ -1255,6 +1304,21 @@ function consumeAccountFailoverAnswer(sessionKey2, prompt) {
|
|
|
1255
1304
|
return classify(pending, part);
|
|
1256
1305
|
}
|
|
1257
1306
|
}
|
|
1307
|
+
const last = prompt[prompt.length - 1];
|
|
1308
|
+
if (fallback && fallback.candidates.length > 0 && Array.isArray(last?.content)) {
|
|
1309
|
+
const orphan = last.content.find(
|
|
1310
|
+
(part) => part?.type === "tool-result" && typeof part.toolCallId === "string" && part.toolCallId.startsWith(ACCOUNT_FAILOVER_TOOL_CALL_PREFIX)
|
|
1311
|
+
);
|
|
1312
|
+
if (orphan) {
|
|
1313
|
+
return classify(
|
|
1314
|
+
{
|
|
1315
|
+
sourceAccount: normalizeAccountName(fallback.sourceAccount || DEFAULT_ACCOUNT),
|
|
1316
|
+
candidates: fallback.candidates.map((c) => normalizeAccountName(c))
|
|
1317
|
+
},
|
|
1318
|
+
orphan
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1258
1322
|
return null;
|
|
1259
1323
|
}
|
|
1260
1324
|
function formatFailoverNote(input) {
|
|
@@ -1515,6 +1579,53 @@ function resolveProxyClientCeilingMs(overrides) {
|
|
|
1515
1579
|
}
|
|
1516
1580
|
return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
|
|
1517
1581
|
}
|
|
1582
|
+
var proxyDeadlineGuard = null;
|
|
1583
|
+
function setProxyDeadlineGuard(guard) {
|
|
1584
|
+
proxyDeadlineGuard = guard;
|
|
1585
|
+
}
|
|
1586
|
+
var PROXY_DEADLINE_RECHECK_MS = 6e4;
|
|
1587
|
+
var deadlineRecheckMs = PROXY_DEADLINE_RECHECK_MS;
|
|
1588
|
+
function armProxyDeadline(opts) {
|
|
1589
|
+
let cancelled = false;
|
|
1590
|
+
let extended = false;
|
|
1591
|
+
let timer = null;
|
|
1592
|
+
const check = async () => {
|
|
1593
|
+
timer = null;
|
|
1594
|
+
if (cancelled) return;
|
|
1595
|
+
let keep = false;
|
|
1596
|
+
const guard = proxyDeadlineGuard;
|
|
1597
|
+
if (guard) {
|
|
1598
|
+
try {
|
|
1599
|
+
keep = await guard({ callId: opts.callId, toolName: opts.toolName });
|
|
1600
|
+
} catch {
|
|
1601
|
+
keep = false;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (cancelled) return;
|
|
1605
|
+
if (keep) {
|
|
1606
|
+
if (!extended && opts.logExtension) {
|
|
1607
|
+
log.warn("proxy call past its deadline, but opencode is still serving it; waiting", {
|
|
1608
|
+
callId: opts.callId,
|
|
1609
|
+
toolName: opts.toolName,
|
|
1610
|
+
deadlineMs: opts.deadlineMs,
|
|
1611
|
+
note: "usually a permission prompt in opencode; the call ends when it is answered, you abort, or the claude process goes"
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
extended = true;
|
|
1615
|
+
timer = setTimeout(() => void check(), opts.recheckMs ?? deadlineRecheckMs);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
opts.onExpire();
|
|
1619
|
+
};
|
|
1620
|
+
timer = setTimeout(() => void check(), opts.deadlineMs);
|
|
1621
|
+
return {
|
|
1622
|
+
cancel() {
|
|
1623
|
+
cancelled = true;
|
|
1624
|
+
if (timer) clearTimeout(timer);
|
|
1625
|
+
timer = null;
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1518
1629
|
function buildProxyTimeoutError(toolName, ms) {
|
|
1519
1630
|
const key = toolName.toLowerCase();
|
|
1520
1631
|
const base = `Proxy tool '${toolName}' timed out after ${ms}ms waiting for opencode to resolve the call`;
|
|
@@ -2000,21 +2111,26 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverride
|
|
|
2000
2111
|
timeoutOverrides
|
|
2001
2112
|
);
|
|
2002
2113
|
if (deadlineMs > PROXY_NO_DEADLINE_MS) {
|
|
2003
|
-
timer =
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2114
|
+
timer = armProxyDeadline({
|
|
2115
|
+
callId,
|
|
2116
|
+
toolName,
|
|
2117
|
+
deadlineMs,
|
|
2118
|
+
onExpire: () => {
|
|
2119
|
+
if (!pending.has(callId)) return;
|
|
2120
|
+
pending.delete(callId);
|
|
2121
|
+
log.notice("proxy-mcp tool call timed out", {
|
|
2122
|
+
callId,
|
|
2123
|
+
toolName,
|
|
2124
|
+
deadlineMs
|
|
2125
|
+
});
|
|
2126
|
+
reject2(buildProxyTimeoutError(toolName, deadlineMs));
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2013
2129
|
}
|
|
2014
2130
|
calls.emit("call", entry);
|
|
2015
2131
|
}
|
|
2016
2132
|
).finally(() => {
|
|
2017
|
-
|
|
2133
|
+
timer?.cancel();
|
|
2018
2134
|
pending.delete(callId);
|
|
2019
2135
|
});
|
|
2020
2136
|
if (channel.closed) {
|
|
@@ -2381,7 +2497,7 @@ var PROXY_STALL_WARNING_MS = 5 * 6e4;
|
|
|
2381
2497
|
var PROXY_DEADLINE_WARNING_FRACTION = 0.6;
|
|
2382
2498
|
var PROXY_DEADLINE_WARNING_MIN_MS = 6e4;
|
|
2383
2499
|
function clearPendingTimers(pending) {
|
|
2384
|
-
|
|
2500
|
+
pending.timer?.cancel();
|
|
2385
2501
|
if (pending.stallTimer) clearInterval(pending.stallTimer);
|
|
2386
2502
|
if (pending.deadlineWarnTimer) clearTimeout(pending.deadlineWarnTimer);
|
|
2387
2503
|
}
|
|
@@ -2425,20 +2541,26 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides, stallWarning
|
|
|
2425
2541
|
call.input,
|
|
2426
2542
|
timeoutOverrides
|
|
2427
2543
|
);
|
|
2428
|
-
const timer = deadlineMs > PROXY_NO_DEADLINE_MS ?
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
deadlineMs
|
|
2440
|
-
|
|
2441
|
-
|
|
2544
|
+
const timer = deadlineMs > PROXY_NO_DEADLINE_MS ? armProxyDeadline({
|
|
2545
|
+
callId: call.id,
|
|
2546
|
+
toolName: call.toolName,
|
|
2547
|
+
deadlineMs,
|
|
2548
|
+
logExtension: true,
|
|
2549
|
+
onExpire: () => {
|
|
2550
|
+
const current = pendingByCallId.get(call.id);
|
|
2551
|
+
if (!current) return;
|
|
2552
|
+
pendingByCallId.delete(call.id);
|
|
2553
|
+
indexRemove(current.sessionKey, call.id);
|
|
2554
|
+
clearPendingTimers(current);
|
|
2555
|
+
current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
|
|
2556
|
+
log.notice("timed out pending proxy call", {
|
|
2557
|
+
sessionKey: current.sessionKey,
|
|
2558
|
+
toolCallId: call.id,
|
|
2559
|
+
toolName: call.toolName,
|
|
2560
|
+
deadlineMs
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}) : null;
|
|
2442
2564
|
const stallTimer = deadlineMs === PROXY_NO_DEADLINE_MS && stallWarningMs > 0 ? setInterval(() => {
|
|
2443
2565
|
const current = pendingByCallId.get(call.id);
|
|
2444
2566
|
if (!current) return;
|
|
@@ -2467,7 +2589,7 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides, stallWarning
|
|
|
2467
2589
|
remainingMs: Math.max(0, deadlineMs - waitedMs),
|
|
2468
2590
|
emitted: current.emitted === true,
|
|
2469
2591
|
channelClosed: current.channel?.closed === true,
|
|
2470
|
-
note: "it will be rejected when the deadline passes; raise this tool's proxyToolTimeoutMs if the work is legitimately this long"
|
|
2592
|
+
note: "it will be rejected when the deadline passes unless opencode is still serving it (a permission prompt is waiting); raise this tool's proxyToolTimeoutMs if the work is legitimately this long"
|
|
2471
2593
|
});
|
|
2472
2594
|
}, warnAtMs) : null;
|
|
2473
2595
|
deadlineWarnTimer?.unref?.();
|
|
@@ -2502,6 +2624,9 @@ function markPendingProxyCallEmitted(toolCallId) {
|
|
|
2502
2624
|
function isPendingProxyCallChannelClosed(call) {
|
|
2503
2625
|
return call.channel?.closed === true;
|
|
2504
2626
|
}
|
|
2627
|
+
function findPendingProxyCall(toolCallId) {
|
|
2628
|
+
return pendingByCallId.get(toolCallId);
|
|
2629
|
+
}
|
|
2505
2630
|
function getPendingProxyCalls(sessionKey2) {
|
|
2506
2631
|
const s = callIdsBySession.get(sessionKey2);
|
|
2507
2632
|
if (!s || s.size === 0) return [];
|
|
@@ -4600,7 +4725,8 @@ var PLUGIN_NOTE_MARKERS = [
|
|
|
4600
4725
|
RESULT_ERROR_MARKER,
|
|
4601
4726
|
DOCTOR_MARKER,
|
|
4602
4727
|
STREAM_TIMEOUT_MARKER,
|
|
4603
|
-
FAILOVER_MARKER
|
|
4728
|
+
FAILOVER_MARKER,
|
|
4729
|
+
ACCOUNT_BLOCK_MARKER
|
|
4604
4730
|
];
|
|
4605
4731
|
function isPluginNote(part) {
|
|
4606
4732
|
if (!part || part.type !== "text" || typeof part.text !== "string") return false;
|
|
@@ -4854,6 +4980,7 @@ function getClaudeUserMessage(prompt, includeHistoryContext = false, opts = {})
|
|
|
4854
4980
|
const compactionMode = opts.compactionMode === true;
|
|
4855
4981
|
const cliToolCallIds = opts.cliToolCallIds;
|
|
4856
4982
|
const content = [];
|
|
4983
|
+
prompt = stripAccountFailoverParts(prompt);
|
|
4857
4984
|
if (opts.stripContextReminders) {
|
|
4858
4985
|
const stripped = stripContextReminders(prompt);
|
|
4859
4986
|
if (stripped.removed > 0) {
|
|
@@ -6195,9 +6322,16 @@ function spawnInteractiveProcess(opts) {
|
|
|
6195
6322
|
// src/claude-code-language-model.ts
|
|
6196
6323
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6197
6324
|
import { unlink as unlink3 } from "fs/promises";
|
|
6198
|
-
import { homedir as
|
|
6325
|
+
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
6199
6326
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6200
6327
|
import { dirname as dirname5, join as join8 } from "path";
|
|
6328
|
+
async function isProxyCallStillServed(callId) {
|
|
6329
|
+
const pending = findPendingProxyCall(callId);
|
|
6330
|
+
if (!pending || pending.emitted !== true) return false;
|
|
6331
|
+
const session = describeSessionKey(pending.sessionKey).session;
|
|
6332
|
+
return await fetchSessionRunState(session) === "busy";
|
|
6333
|
+
}
|
|
6334
|
+
setProxyDeadlineGuard(({ callId }) => isProxyCallStillServed(callId));
|
|
6201
6335
|
var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
|
|
6202
6336
|
function resolveCompactionModel(configured) {
|
|
6203
6337
|
const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
|
|
@@ -6552,7 +6686,7 @@ ${options.compressionSummary.trim()}`
|
|
|
6552
6686
|
for (const s of extraSystemContent) {
|
|
6553
6687
|
if (s.trim()) parts.push(s.trim());
|
|
6554
6688
|
}
|
|
6555
|
-
const configRoot = process.env.XDG_CONFIG_HOME ?? join8(
|
|
6689
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir6(), ".config");
|
|
6556
6690
|
const globalAgents = readPromptFileIfPresent(join8(configRoot, "opencode", "AGENTS.md"));
|
|
6557
6691
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
6558
6692
|
const forwarded = extraSystemContent.join("\n\n");
|
|
@@ -7855,7 +7989,10 @@ ${plan}
|
|
|
7855
7989
|
deleteActiveProcess(sk);
|
|
7856
7990
|
clearCompression(sk);
|
|
7857
7991
|
}
|
|
7858
|
-
const failoverAnswer = compactionMode || useInteractive ? null : consumeAccountFailoverAnswer(sk, options.prompt
|
|
7992
|
+
const failoverAnswer = compactionMode || useInteractive ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
|
|
7993
|
+
sourceAccount,
|
|
7994
|
+
candidates: failoverCandidates(this.config.failoverAccounts, sourceAccount)
|
|
7995
|
+
});
|
|
7859
7996
|
if (failoverAnswer?.kind === "stop") {
|
|
7860
7997
|
log.warn("account failover declined; ending the turn", {
|
|
7861
7998
|
sessionKey: sk,
|
|
@@ -8487,6 +8624,7 @@ ${plan}
|
|
|
8487
8624
|
let resultMeta = {};
|
|
8488
8625
|
let resultFailure;
|
|
8489
8626
|
let accountLimitHit = null;
|
|
8627
|
+
let accountBlock = null;
|
|
8490
8628
|
const drainBuffer = [];
|
|
8491
8629
|
let drainTimer = null;
|
|
8492
8630
|
const DRAIN_QUIET_MS = 100;
|
|
@@ -8648,20 +8786,21 @@ ${plan}
|
|
|
8648
8786
|
});
|
|
8649
8787
|
}
|
|
8650
8788
|
activeProcess?.pendingProxyCompletions?.clear();
|
|
8651
|
-
if (accountLimitHit && failoverAskActive && msg.is_error === true) {
|
|
8789
|
+
if ((accountLimitHit || accountBlock) && failoverAskActive && msg.is_error === true) {
|
|
8652
8790
|
const call = createAccountFailoverQuestionCall(sk, {
|
|
8653
8791
|
sourceAccount,
|
|
8654
8792
|
candidates: failoverAccounts,
|
|
8655
|
-
resetsAt: accountLimitHit
|
|
8656
|
-
window: accountLimitHit
|
|
8793
|
+
resetsAt: accountLimitHit?.resetsAt,
|
|
8794
|
+
window: accountLimitHit?.window,
|
|
8795
|
+
reason: accountLimitHit || !accountBlock ? void 0 : describeAccountBlock(accountBlock)
|
|
8657
8796
|
});
|
|
8658
8797
|
log.warn(
|
|
8659
|
-
`Claude account "${sourceAccount}" is out of usage; asking which account to continue on.`,
|
|
8798
|
+
`Claude account "${sourceAccount}" ${accountLimitHit || !accountBlock ? "is out of usage" : `cannot serve (${accountBlock})`}; asking which account to continue on.`,
|
|
8660
8799
|
{
|
|
8661
8800
|
sessionKey: sk,
|
|
8662
8801
|
candidates: failoverAccounts,
|
|
8663
8802
|
toolCallId: call.toolCallId,
|
|
8664
|
-
resetsAt: accountLimitHit
|
|
8803
|
+
resetsAt: accountLimitHit?.resetsAt ?? null
|
|
8665
8804
|
}
|
|
8666
8805
|
);
|
|
8667
8806
|
finishWithQuestionCall(call);
|
|
@@ -8799,6 +8938,8 @@ ${plan}
|
|
|
8799
8938
|
endTextBlock();
|
|
8800
8939
|
}
|
|
8801
8940
|
}
|
|
8941
|
+
const block = accountBlockKind(msg);
|
|
8942
|
+
if (block) accountBlock = block;
|
|
8802
8943
|
if (msg.type === "rate_limit_event") {
|
|
8803
8944
|
const info = parseRateLimitEvent(msg);
|
|
8804
8945
|
if (info && isRateLimitRejected(info)) {
|
|
@@ -8815,58 +8956,58 @@ ${plan}
|
|
|
8815
8956
|
return;
|
|
8816
8957
|
}
|
|
8817
8958
|
if (msg.type === "content_block_start" && msg.content_block && msg.index !== void 0) {
|
|
8818
|
-
const
|
|
8959
|
+
const block2 = msg.content_block;
|
|
8819
8960
|
const idx = msg.index;
|
|
8820
|
-
if (
|
|
8961
|
+
if (block2.type === "thinking") {
|
|
8821
8962
|
noteReasoning();
|
|
8822
8963
|
const reasoningId = generateId();
|
|
8823
8964
|
reasoningIds.set(idx, reasoningId);
|
|
8824
8965
|
}
|
|
8825
|
-
if (
|
|
8966
|
+
if (block2.type === "text") {
|
|
8826
8967
|
textBlockIndices.add(idx);
|
|
8827
8968
|
resetLastVisibleTextBlock();
|
|
8828
|
-
if (
|
|
8969
|
+
if (block2.text) {
|
|
8829
8970
|
if (!currentTextId) startTextBlock();
|
|
8830
8971
|
controller.enqueue({
|
|
8831
8972
|
type: "text-delta",
|
|
8832
8973
|
id: currentTextId,
|
|
8833
|
-
delta:
|
|
8974
|
+
delta: block2.text
|
|
8834
8975
|
});
|
|
8835
|
-
noteVisibleText(
|
|
8976
|
+
noteVisibleText(block2.text);
|
|
8836
8977
|
hasReceivedContent = true;
|
|
8837
8978
|
}
|
|
8838
8979
|
}
|
|
8839
|
-
if (
|
|
8980
|
+
if (block2.type === "tool_use" && block2.id && block2.name) {
|
|
8840
8981
|
noteToolActivity();
|
|
8841
8982
|
const entry = {
|
|
8842
|
-
id:
|
|
8843
|
-
name:
|
|
8983
|
+
id: block2.id,
|
|
8984
|
+
name: block2.name,
|
|
8844
8985
|
inputJson: "",
|
|
8845
8986
|
started: false
|
|
8846
8987
|
};
|
|
8847
8988
|
toolCallMap.set(idx, entry);
|
|
8848
|
-
if (
|
|
8989
|
+
if (block2.name !== "AskUserQuestion" && block2.name !== "ask_user_question" && block2.name !== "ExitPlanMode" && !block2.name.startsWith(PROXY_TOOL_PREFIX)) {
|
|
8849
8990
|
const { name: mappedName, skip, executed } = mapTool(
|
|
8850
|
-
|
|
8991
|
+
block2.name,
|
|
8851
8992
|
void 0,
|
|
8852
8993
|
{
|
|
8853
8994
|
webSearch: self.config.webSearch,
|
|
8854
8995
|
sessionId: getClaudeSessionId(sk),
|
|
8855
|
-
toolUseId:
|
|
8996
|
+
toolUseId: block2.id
|
|
8856
8997
|
}
|
|
8857
8998
|
);
|
|
8858
8999
|
if (!skip) {
|
|
8859
9000
|
entry.started = true;
|
|
8860
9001
|
controller.enqueue({
|
|
8861
9002
|
type: "tool-input-start",
|
|
8862
|
-
id:
|
|
9003
|
+
id: block2.id,
|
|
8863
9004
|
toolName: mappedName,
|
|
8864
9005
|
providerExecuted: executed
|
|
8865
9006
|
});
|
|
8866
9007
|
log.info("tool started", {
|
|
8867
|
-
name:
|
|
9008
|
+
name: block2.name,
|
|
8868
9009
|
mappedName,
|
|
8869
|
-
id:
|
|
9010
|
+
id: block2.id
|
|
8870
9011
|
});
|
|
8871
9012
|
}
|
|
8872
9013
|
}
|
|
@@ -9057,8 +9198,8 @@ ${plan}
|
|
|
9057
9198
|
hadStreamThinking: hadThinkingTextFromStream
|
|
9058
9199
|
});
|
|
9059
9200
|
if (!hadThinkingTextFromStream) {
|
|
9060
|
-
for (const
|
|
9061
|
-
if (
|
|
9201
|
+
for (const block2 of thinkingBlocks) {
|
|
9202
|
+
if (block2.thinking && block2.thinking.length > 0) {
|
|
9062
9203
|
noteReasoning();
|
|
9063
9204
|
hadThinkingTextFromStream = true;
|
|
9064
9205
|
const thinkingId = generateId();
|
|
@@ -9069,7 +9210,7 @@ ${plan}
|
|
|
9069
9210
|
controller.enqueue({
|
|
9070
9211
|
type: "reasoning-delta",
|
|
9071
9212
|
id: thinkingId,
|
|
9072
|
-
delta:
|
|
9213
|
+
delta: block2.thinking
|
|
9073
9214
|
});
|
|
9074
9215
|
controller.enqueue({
|
|
9075
9216
|
type: "reasoning-end",
|
|
@@ -9096,20 +9237,20 @@ ${plan}
|
|
|
9096
9237
|
if (hasToolUse) {
|
|
9097
9238
|
clearFallbackTimer();
|
|
9098
9239
|
}
|
|
9099
|
-
for (const
|
|
9100
|
-
if (
|
|
9240
|
+
for (const block2 of msg.message.content) {
|
|
9241
|
+
if (block2.type === "text" && block2.text) {
|
|
9101
9242
|
resetLastVisibleTextBlock();
|
|
9102
9243
|
const blockId = startTextBlock();
|
|
9103
9244
|
controller.enqueue({
|
|
9104
9245
|
type: "text-delta",
|
|
9105
9246
|
id: blockId,
|
|
9106
|
-
delta:
|
|
9247
|
+
delta: block2.text
|
|
9107
9248
|
});
|
|
9108
9249
|
endTextBlock();
|
|
9109
|
-
noteVisibleText(
|
|
9250
|
+
noteVisibleText(block2.text);
|
|
9110
9251
|
hasReceivedContent = true;
|
|
9111
9252
|
}
|
|
9112
|
-
if (
|
|
9253
|
+
if (block2.type === "thinking" && block2.thinking) {
|
|
9113
9254
|
noteReasoning();
|
|
9114
9255
|
const thinkingId = generateId();
|
|
9115
9256
|
controller.enqueue({
|
|
@@ -9119,17 +9260,17 @@ ${plan}
|
|
|
9119
9260
|
controller.enqueue({
|
|
9120
9261
|
type: "reasoning-delta",
|
|
9121
9262
|
id: thinkingId,
|
|
9122
|
-
delta:
|
|
9263
|
+
delta: block2.thinking
|
|
9123
9264
|
});
|
|
9124
9265
|
controller.enqueue({
|
|
9125
9266
|
type: "reasoning-end",
|
|
9126
9267
|
id: thinkingId
|
|
9127
9268
|
});
|
|
9128
9269
|
}
|
|
9129
|
-
if (
|
|
9270
|
+
if (block2.type === "tool_use" && block2.id && block2.name) {
|
|
9130
9271
|
noteToolActivity();
|
|
9131
|
-
const parsedInput =
|
|
9132
|
-
if (isAskUserQuestionTool(
|
|
9272
|
+
const parsedInput = block2.input ?? {};
|
|
9273
|
+
if (isAskUserQuestionTool(block2.name)) {
|
|
9133
9274
|
const askId = startTextBlock();
|
|
9134
9275
|
controller.enqueue({
|
|
9135
9276
|
type: "text-delta",
|
|
@@ -9137,12 +9278,12 @@ ${plan}
|
|
|
9137
9278
|
delta: formatAskUserQuestion(parsedInput)
|
|
9138
9279
|
});
|
|
9139
9280
|
endTextBlock();
|
|
9140
|
-
} else if (
|
|
9281
|
+
} else if (block2.name === "ExitPlanMode") {
|
|
9141
9282
|
const plan = parsedInput?.plan || "";
|
|
9142
9283
|
if (planModeQuestionActive) {
|
|
9143
9284
|
const questionCall = createExitPlanModeQuestionCall(
|
|
9144
9285
|
sk,
|
|
9145
|
-
|
|
9286
|
+
block2.id,
|
|
9146
9287
|
plan
|
|
9147
9288
|
);
|
|
9148
9289
|
const planId2 = startTextBlock();
|
|
@@ -9167,8 +9308,8 @@ ${plan}
|
|
|
9167
9308
|
`
|
|
9168
9309
|
});
|
|
9169
9310
|
endTextBlock();
|
|
9170
|
-
} else if (isWebSearchTool(
|
|
9171
|
-
toolCallsById.delete(
|
|
9311
|
+
} else if (isWebSearchTool(block2.name) && isWebSearchHandledByCli(self.config.webSearch)) {
|
|
9312
|
+
toolCallsById.delete(block2.id);
|
|
9172
9313
|
const query = typeof parsedInput?.query === "string" ? parsedInput.query : JSON.stringify(parsedInput);
|
|
9173
9314
|
const searchId = startTextBlock();
|
|
9174
9315
|
controller.enqueue({
|
|
@@ -9179,11 +9320,11 @@ ${plan}
|
|
|
9179
9320
|
`
|
|
9180
9321
|
});
|
|
9181
9322
|
endTextBlock();
|
|
9182
|
-
} else if (
|
|
9323
|
+
} else if (block2.name.startsWith(PROXY_TOOL_PREFIX)) {
|
|
9183
9324
|
noteProxyActivity();
|
|
9184
9325
|
log.debug("ignoring proxy tool_use from assistant message", {
|
|
9185
|
-
name:
|
|
9186
|
-
id:
|
|
9326
|
+
name: block2.name,
|
|
9327
|
+
id: block2.id
|
|
9187
9328
|
});
|
|
9188
9329
|
} else {
|
|
9189
9330
|
const {
|
|
@@ -9191,61 +9332,61 @@ ${plan}
|
|
|
9191
9332
|
input: mappedInput,
|
|
9192
9333
|
executed,
|
|
9193
9334
|
skip
|
|
9194
|
-
} = mapTool(
|
|
9335
|
+
} = mapTool(block2.name, parsedInput, {
|
|
9195
9336
|
webSearch: self.config.webSearch,
|
|
9196
9337
|
sessionId: getClaudeSessionId(sk),
|
|
9197
|
-
toolUseId:
|
|
9338
|
+
toolUseId: block2.id
|
|
9198
9339
|
});
|
|
9199
9340
|
if (!skip) {
|
|
9200
|
-
toolCallsById.set(
|
|
9201
|
-
id:
|
|
9202
|
-
name:
|
|
9341
|
+
toolCallsById.set(block2.id, {
|
|
9342
|
+
id: block2.id,
|
|
9343
|
+
name: block2.name,
|
|
9203
9344
|
input: parsedInput
|
|
9204
9345
|
});
|
|
9205
|
-
if (!executed) skipResultForIds.add(
|
|
9346
|
+
if (!executed) skipResultForIds.add(block2.id);
|
|
9206
9347
|
controller.enqueue({
|
|
9207
9348
|
type: "tool-input-start",
|
|
9208
|
-
id:
|
|
9349
|
+
id: block2.id,
|
|
9209
9350
|
toolName: mappedName,
|
|
9210
9351
|
providerExecuted: executed
|
|
9211
9352
|
});
|
|
9212
9353
|
controller.enqueue({
|
|
9213
9354
|
type: "tool-call",
|
|
9214
|
-
toolCallId:
|
|
9355
|
+
toolCallId: block2.id,
|
|
9215
9356
|
toolName: mappedName,
|
|
9216
9357
|
input: JSON.stringify(mappedInput),
|
|
9217
9358
|
providerExecuted: executed
|
|
9218
9359
|
});
|
|
9219
9360
|
}
|
|
9220
9361
|
log.info("tool_use from assistant message", {
|
|
9221
|
-
name:
|
|
9362
|
+
name: block2.name,
|
|
9222
9363
|
mappedName,
|
|
9223
|
-
id:
|
|
9364
|
+
id: block2.id,
|
|
9224
9365
|
executed
|
|
9225
9366
|
});
|
|
9226
9367
|
}
|
|
9227
9368
|
}
|
|
9228
|
-
if (
|
|
9369
|
+
if (block2.type === "tool_result") {
|
|
9229
9370
|
log.debug("tool_result", {
|
|
9230
|
-
toolUseId:
|
|
9371
|
+
toolUseId: block2.tool_use_id
|
|
9231
9372
|
});
|
|
9232
9373
|
}
|
|
9233
9374
|
}
|
|
9234
9375
|
}
|
|
9235
9376
|
if (msg.type === "user" && msg.message?.content) {
|
|
9236
|
-
for (const
|
|
9237
|
-
if (
|
|
9238
|
-
if (skipResultForIds.has(
|
|
9377
|
+
for (const block2 of msg.message.content) {
|
|
9378
|
+
if (block2.type === "tool_result" && block2.tool_use_id) {
|
|
9379
|
+
if (skipResultForIds.has(block2.tool_use_id)) {
|
|
9239
9380
|
log.debug("skipping tool-result (opencode runs it)", {
|
|
9240
|
-
toolUseId:
|
|
9381
|
+
toolUseId: block2.tool_use_id
|
|
9241
9382
|
});
|
|
9242
9383
|
continue;
|
|
9243
9384
|
}
|
|
9244
9385
|
let resultText = "";
|
|
9245
|
-
if (typeof
|
|
9246
|
-
resultText =
|
|
9247
|
-
} else if (Array.isArray(
|
|
9248
|
-
resultText =
|
|
9386
|
+
if (typeof block2.content === "string") {
|
|
9387
|
+
resultText = block2.content;
|
|
9388
|
+
} else if (Array.isArray(block2.content)) {
|
|
9389
|
+
resultText = block2.content.filter(
|
|
9249
9390
|
(c) => c.type === "text" && typeof c.text === "string"
|
|
9250
9391
|
).map((c) => c.text).join("\n");
|
|
9251
9392
|
}
|
|
@@ -9253,11 +9394,11 @@ ${plan}
|
|
|
9253
9394
|
if (claudeSessionId) {
|
|
9254
9395
|
const list2 = applyTaskCreateToolResult(
|
|
9255
9396
|
claudeSessionId,
|
|
9256
|
-
|
|
9397
|
+
block2.tool_use_id,
|
|
9257
9398
|
resultText
|
|
9258
9399
|
);
|
|
9259
9400
|
if (list2) {
|
|
9260
|
-
const synthId = `todowrite_${
|
|
9401
|
+
const synthId = `todowrite_${block2.tool_use_id}`;
|
|
9261
9402
|
controller.enqueue({
|
|
9262
9403
|
type: "tool-input-start",
|
|
9263
9404
|
id: synthId,
|
|
@@ -9281,12 +9422,12 @@ ${plan}
|
|
|
9281
9422
|
noteToolActivity();
|
|
9282
9423
|
}
|
|
9283
9424
|
}
|
|
9284
|
-
const toolCall = toolCallsById.get(
|
|
9425
|
+
const toolCall = toolCallsById.get(block2.tool_use_id);
|
|
9285
9426
|
if (toolCall) {
|
|
9286
|
-
const isError =
|
|
9427
|
+
const isError = block2.is_error === true;
|
|
9287
9428
|
controller.enqueue({
|
|
9288
9429
|
type: "tool-result",
|
|
9289
|
-
toolCallId:
|
|
9430
|
+
toolCallId: block2.tool_use_id,
|
|
9290
9431
|
toolName: toolCall.name,
|
|
9291
9432
|
result: {
|
|
9292
9433
|
output: resultText,
|
|
@@ -9298,11 +9439,11 @@ ${plan}
|
|
|
9298
9439
|
});
|
|
9299
9440
|
noteToolActivity();
|
|
9300
9441
|
log.info("tool result emitted", {
|
|
9301
|
-
toolUseId:
|
|
9442
|
+
toolUseId: block2.tool_use_id,
|
|
9302
9443
|
name: toolCall.name,
|
|
9303
9444
|
isError
|
|
9304
9445
|
});
|
|
9305
|
-
toolCallsById.delete(
|
|
9446
|
+
toolCallsById.delete(block2.tool_use_id);
|
|
9306
9447
|
}
|
|
9307
9448
|
}
|
|
9308
9449
|
}
|
|
@@ -9328,6 +9469,26 @@ ${plan}
|
|
|
9328
9469
|
})) {
|
|
9329
9470
|
accountLimitHit = {};
|
|
9330
9471
|
}
|
|
9472
|
+
if (accountBlock && msg.is_error) {
|
|
9473
|
+
resultFailure ??= accountBlock;
|
|
9474
|
+
const offeringSwitch = failoverAskActive;
|
|
9475
|
+
controller.enqueue({
|
|
9476
|
+
type: "text-delta",
|
|
9477
|
+
id: startTextBlock(),
|
|
9478
|
+
delta: formatAccountBlockNote({
|
|
9479
|
+
kind: accountBlock,
|
|
9480
|
+
account: sourceAccount,
|
|
9481
|
+
configDir: self.config.configDir,
|
|
9482
|
+
offeringSwitch
|
|
9483
|
+
})
|
|
9484
|
+
});
|
|
9485
|
+
endTextBlock();
|
|
9486
|
+
log.warn(`Claude account "${sourceAccount}" cannot serve requests`, {
|
|
9487
|
+
sessionKey: sk,
|
|
9488
|
+
kind: accountBlock,
|
|
9489
|
+
offeringSwitch
|
|
9490
|
+
});
|
|
9491
|
+
}
|
|
9331
9492
|
const failure = describeResultFailure(msg);
|
|
9332
9493
|
if (failure) {
|
|
9333
9494
|
resultFailure = msg.subtype;
|
|
@@ -9773,7 +9934,7 @@ import {
|
|
|
9773
9934
|
rmSync as rmSync3,
|
|
9774
9935
|
writeFileSync as writeFileSync5
|
|
9775
9936
|
} from "fs";
|
|
9776
|
-
import { homedir as
|
|
9937
|
+
import { homedir as homedir7 } from "os";
|
|
9777
9938
|
import { join as join9, resolve as resolve4 } from "path";
|
|
9778
9939
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9779
9940
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
@@ -9783,12 +9944,12 @@ function candidateCacheRoots() {
|
|
|
9783
9944
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
9784
9945
|
return [
|
|
9785
9946
|
xdg ? join9(xdg, "opencode") : null,
|
|
9786
|
-
join9(
|
|
9787
|
-
join9(
|
|
9947
|
+
join9(homedir7(), ".cache", "opencode"),
|
|
9948
|
+
join9(homedir7(), "Library", "Caches", "opencode")
|
|
9788
9949
|
].filter((p) => Boolean(p));
|
|
9789
9950
|
}
|
|
9790
9951
|
function userOpencodeJsonPath() {
|
|
9791
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(
|
|
9952
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
|
|
9792
9953
|
return join9(xdgConfig, "opencode", "opencode.json");
|
|
9793
9954
|
}
|
|
9794
9955
|
function userIntendsToUseUnscoped() {
|