@rallycry/conveyor-agent 10.8.1 → 10.9.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/{chunk-LBMFXTIV.js → chunk-H3OGNJS4.js} +281 -21
- package/dist/chunk-H3OGNJS4.js.map +1 -0
- package/dist/cli.js +5 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +49 -3
- package/dist/chunk-LBMFXTIV.js.map +0 -1
|
@@ -951,6 +951,19 @@ var AgentConnection = class _AgentConnection {
|
|
|
951
951
|
}).catch(() => {
|
|
952
952
|
});
|
|
953
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* The session's key hit a hard usage cap — ask the server to stamp it
|
|
956
|
+
* limited and hand back the best remaining key's credential env (or a
|
|
957
|
+
* requeue confirmation when none is left). Awaited: the caller swaps
|
|
958
|
+
* credentials and resumes on success, so it needs the real response.
|
|
959
|
+
*/
|
|
960
|
+
async cycleCodingAgentKey(rateLimitType, resetsAt) {
|
|
961
|
+
return await this.call("cycleCodingAgentKey", {
|
|
962
|
+
sessionId: this.config.sessionId,
|
|
963
|
+
rateLimitType,
|
|
964
|
+
...resetsAt ? { resetsAt } : {}
|
|
965
|
+
});
|
|
966
|
+
}
|
|
954
967
|
// ── Question handling ──────────────────────────────────────────────
|
|
955
968
|
async askUserQuestion(questions) {
|
|
956
969
|
const questionText = questions.map(
|
|
@@ -2175,6 +2188,11 @@ var SubmitCodeReviewResultRequestSchema = z.object({
|
|
|
2175
2188
|
approved: z.boolean(),
|
|
2176
2189
|
content: z.string()
|
|
2177
2190
|
});
|
|
2191
|
+
var CycleCodingAgentKeyRequestSchema = z.object({
|
|
2192
|
+
sessionId: z.string(),
|
|
2193
|
+
rateLimitType: z.string(),
|
|
2194
|
+
resetsAt: z.string().optional()
|
|
2195
|
+
});
|
|
2178
2196
|
var StartChildCloudBuildRequestSchema = z.object({
|
|
2179
2197
|
sessionId: z.string(),
|
|
2180
2198
|
childTaskId: z.string()
|
|
@@ -2726,7 +2744,7 @@ var ModeController = class {
|
|
|
2726
2744
|
}
|
|
2727
2745
|
get isBuildCapable() {
|
|
2728
2746
|
const m = this.effectiveMode;
|
|
2729
|
-
return m === "building" || m === "review" || m === "auto" && this._hasExitedPlanMode;
|
|
2747
|
+
return m === "building" || m === "review" || m === "chat" || m === "auto" && this._hasExitedPlanMode;
|
|
2730
2748
|
}
|
|
2731
2749
|
/**
|
|
2732
2750
|
* Apply authoritative mode from the server's task context.
|
|
@@ -3375,6 +3393,36 @@ var JsonlTailer = class {
|
|
|
3375
3393
|
}
|
|
3376
3394
|
};
|
|
3377
3395
|
|
|
3396
|
+
// src/harness/pty/limit-banner.ts
|
|
3397
|
+
var BANNER_RE = /you'?(?:ve| have) (?:hit|reached) your ([\w-]+ )?(?:usage )?limit/i;
|
|
3398
|
+
var RESET_RE = /resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(UTC\)/i;
|
|
3399
|
+
function nextUtcOccurrenceSeconds(hour12, minute, meridiem, nowMs) {
|
|
3400
|
+
let hour = hour12 % 12;
|
|
3401
|
+
if (meridiem.toLowerCase() === "pm") hour += 12;
|
|
3402
|
+
const candidate = new Date(nowMs);
|
|
3403
|
+
candidate.setUTCHours(hour, minute, 0, 0);
|
|
3404
|
+
if (candidate.getTime() <= nowMs) candidate.setUTCDate(candidate.getUTCDate() + 1);
|
|
3405
|
+
return Math.floor(candidate.getTime() / 1e3);
|
|
3406
|
+
}
|
|
3407
|
+
function matchUsageLimitBanner(text, now = Date.now()) {
|
|
3408
|
+
const banner = BANNER_RE.exec(text);
|
|
3409
|
+
if (!banner) return null;
|
|
3410
|
+
const qualifier = banner[1]?.trim().toLowerCase();
|
|
3411
|
+
const rateLimitType = qualifier === "weekly" ? "seven_day" : "five_hour";
|
|
3412
|
+
const reset = RESET_RE.exec(text);
|
|
3413
|
+
return {
|
|
3414
|
+
rateLimitType,
|
|
3415
|
+
...reset ? {
|
|
3416
|
+
resetsAtEpochSeconds: nextUtcOccurrenceSeconds(
|
|
3417
|
+
Number(reset[1]),
|
|
3418
|
+
reset[2] ? Number(reset[2]) : 0,
|
|
3419
|
+
reset[3],
|
|
3420
|
+
now
|
|
3421
|
+
)
|
|
3422
|
+
} : {}
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3378
3426
|
// src/harness/pty/chat-record-mapper.ts
|
|
3379
3427
|
var TEXT_MAX = 16e3;
|
|
3380
3428
|
var TOOL_INPUT_MAX = 1900;
|
|
@@ -4095,6 +4143,14 @@ async function readRaw(path4) {
|
|
|
4095
4143
|
return null;
|
|
4096
4144
|
}
|
|
4097
4145
|
}
|
|
4146
|
+
async function readCredentialsIdentity() {
|
|
4147
|
+
const parsed = parseClaudeAiOauth(await readRaw(claudeCredentialsPath()));
|
|
4148
|
+
if (!parsed) return null;
|
|
4149
|
+
return {
|
|
4150
|
+
accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : null,
|
|
4151
|
+
hasRefreshToken: typeof parsed.refreshToken === "string" && parsed.refreshToken.length > 0
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4098
4154
|
var READ_BACK_DELAYS_MS = [250, 500, 1e3, 2e3];
|
|
4099
4155
|
var defaultSleep = (ms) => new Promise((resolve) => {
|
|
4100
4156
|
setTimeout(resolve, ms);
|
|
@@ -4563,6 +4619,7 @@ var PtySession = class {
|
|
|
4563
4619
|
pty = null;
|
|
4564
4620
|
tempDir = "";
|
|
4565
4621
|
sawResult = false;
|
|
4622
|
+
limitBannerReported = false;
|
|
4566
4623
|
// Rolling tail of raw PTY output, retained only to enrich the error when the
|
|
4567
4624
|
// CLI exits before emitting a result (the bytes are otherwise relayed to S5
|
|
4568
4625
|
// and never become events). Trimmed to MAX_DIAGNOSTIC_OUTPUT on every write.
|
|
@@ -5093,6 +5150,7 @@ var PtySession = class {
|
|
|
5093
5150
|
this.disarmPlanDialogAutoAccept();
|
|
5094
5151
|
}
|
|
5095
5152
|
if (this.pendingSubmitNudge) this.disarmSubmitNudge();
|
|
5153
|
+
this.synthesizeRateLimitFromBanner(event);
|
|
5096
5154
|
this.pushEvent(event);
|
|
5097
5155
|
if (event.type === "result") {
|
|
5098
5156
|
this.sawResult = true;
|
|
@@ -5100,6 +5158,35 @@ var PtySession = class {
|
|
|
5100
5158
|
this.endTurn(true);
|
|
5101
5159
|
}
|
|
5102
5160
|
}
|
|
5161
|
+
/**
|
|
5162
|
+
* The interactive CLI reports a hard usage cap only as a conversation banner
|
|
5163
|
+
* ("You've hit your weekly limit · resets 7am (UTC)") — it never writes a
|
|
5164
|
+
* structured rate_limit_event to the transcript. Recognize the banner in
|
|
5165
|
+
* assistant/result text and push the same harness-level event the SDK
|
|
5166
|
+
* harness emits, so cap handling (key cycle → pause) is one code path.
|
|
5167
|
+
* Once per session: the CLI repeats the banner on every rejected turn.
|
|
5168
|
+
*/
|
|
5169
|
+
synthesizeRateLimitFromBanner(event) {
|
|
5170
|
+
if (this.limitBannerReported) return;
|
|
5171
|
+
let text;
|
|
5172
|
+
if (event.type === "assistant") {
|
|
5173
|
+
text = event.message.content.map((block) => block.text ?? "").filter(Boolean).join("\n");
|
|
5174
|
+
} else if (event.type === "result" && event.subtype === "success") {
|
|
5175
|
+
text = event.result;
|
|
5176
|
+
}
|
|
5177
|
+
if (!text) return;
|
|
5178
|
+
const match = matchUsageLimitBanner(text);
|
|
5179
|
+
if (!match) return;
|
|
5180
|
+
this.limitBannerReported = true;
|
|
5181
|
+
this.pushEvent({
|
|
5182
|
+
type: "rate_limit_event",
|
|
5183
|
+
rate_limit_info: {
|
|
5184
|
+
status: "rejected",
|
|
5185
|
+
rateLimitType: match.rateLimitType,
|
|
5186
|
+
...match.resetsAtEpochSeconds === void 0 ? {} : { resetsAt: match.resetsAtEpochSeconds }
|
|
5187
|
+
}
|
|
5188
|
+
});
|
|
5189
|
+
}
|
|
5103
5190
|
async finalizeOnExit(exitCode) {
|
|
5104
5191
|
this.coalescer?.flush();
|
|
5105
5192
|
this.exited = true;
|
|
@@ -6104,10 +6191,30 @@ function buildModePrompt(agentMode, context, runnerMode) {
|
|
|
6104
6191
|
return buildReviewPrompt(context);
|
|
6105
6192
|
case "auto":
|
|
6106
6193
|
return buildAutoPrompt(context, runnerMode);
|
|
6194
|
+
case "chat":
|
|
6195
|
+
return buildChatPrompt();
|
|
6107
6196
|
default:
|
|
6108
6197
|
return null;
|
|
6109
6198
|
}
|
|
6110
6199
|
}
|
|
6200
|
+
function buildChatPrompt() {
|
|
6201
|
+
return [
|
|
6202
|
+
`
|
|
6203
|
+
## Mode: Chat`,
|
|
6204
|
+
`You are in Chat mode \u2014 a conversational assistant working directly with the user on this card.`,
|
|
6205
|
+
`- Respond conversationally to the user in chat. Ask clarifying questions when useful; this is a back-and-forth, not an autonomous build.`,
|
|
6206
|
+
`- You have full read/write access to the workspace and can run non-destructive shell commands, so you CAN create files (notes, scripts, docs, data, diagrams, etc.) when they help the user.`,
|
|
6207
|
+
``,
|
|
6208
|
+
`### Deliverables \u2014 attach, do NOT open a PR`,
|
|
6209
|
+
`- This card has NO pull-request workflow. Do NOT run \`git push\`, do NOT open a PR, and do NOT rely on branch commits to deliver work \u2014 those operations are blocked.`,
|
|
6210
|
+
`- When you create a file the user should keep, attach it to the card with the \`upload_attachment\` tool so it shows up on the card. Mention in chat what you attached.`,
|
|
6211
|
+
``,
|
|
6212
|
+
`### Finishing the conversation`,
|
|
6213
|
+
`- When the user indicates they are done (or explicitly asks to wrap up / close the card), call \`force_update_task_status\` with status \`"Complete"\` to move the card InProgress \u2192 Done. There is no review or PR step.`,
|
|
6214
|
+
`- If the user is still engaged, keep the card InProgress and keep helping \u2014 only complete it once the interaction has concluded.`,
|
|
6215
|
+
`- Do not complete the card while you still owe the user a response or an attachment.`
|
|
6216
|
+
].join("\n");
|
|
6217
|
+
}
|
|
6111
6218
|
function buildReviewPrompt(context) {
|
|
6112
6219
|
const parts = [
|
|
6113
6220
|
`
|
|
@@ -7980,16 +8087,20 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
7980
8087
|
const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
|
|
7981
8088
|
const commonTools = buildCommonTools(connection, config);
|
|
7982
8089
|
const modeTools = getModeTools(effectiveMode, connection, config, context);
|
|
7983
|
-
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" ? buildDiscoveryTools(connection) : [];
|
|
8090
|
+
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" || effectiveMode === "chat" ? buildDiscoveryTools(connection) : [];
|
|
7984
8091
|
const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
|
|
7985
8092
|
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
7986
|
-
|
|
8093
|
+
const tools = withAlwaysLoad([
|
|
7987
8094
|
...commonTools,
|
|
7988
8095
|
...modeTools,
|
|
7989
8096
|
...discoveryTools,
|
|
7990
8097
|
...codeReviewTools,
|
|
7991
8098
|
...emergencyTools
|
|
7992
8099
|
]);
|
|
8100
|
+
if (effectiveMode === "chat") {
|
|
8101
|
+
return tools.filter((tool2) => tool2.name !== "create_pull_request");
|
|
8102
|
+
}
|
|
8103
|
+
return tools;
|
|
7993
8104
|
}
|
|
7994
8105
|
function createConveyorMcpServer(harness, connection, config, context, agentMode) {
|
|
7995
8106
|
return harness.createMcpServer({
|
|
@@ -8411,6 +8522,13 @@ async function processResultCase(event, host, context, startTime, state) {
|
|
|
8411
8522
|
if (info.staleSession) state.staleSession = true;
|
|
8412
8523
|
if (info.authError) state.authError = true;
|
|
8413
8524
|
}
|
|
8525
|
+
function processRateLimitCase(event, host, state) {
|
|
8526
|
+
const resetsAt = handleRateLimitEvent(event, host);
|
|
8527
|
+
if (resetsAt) state.rateLimitResetsAt = resetsAt;
|
|
8528
|
+
if (event.rate_limit_info.status === "rejected") {
|
|
8529
|
+
state.rateLimitRejectedType = event.rate_limit_info.rateLimitType ?? "unknown";
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8414
8532
|
async function processEvents(events, context, host) {
|
|
8415
8533
|
const startTime = Date.now();
|
|
8416
8534
|
let lastStatusEmit = Date.now();
|
|
@@ -8422,6 +8540,7 @@ async function processEvents(events, context, host) {
|
|
|
8422
8540
|
sawApiError: false,
|
|
8423
8541
|
resultSummary: void 0,
|
|
8424
8542
|
rateLimitResetsAt: void 0,
|
|
8543
|
+
rateLimitRejectedType: void 0,
|
|
8425
8544
|
staleSession: void 0,
|
|
8426
8545
|
authError: void 0,
|
|
8427
8546
|
lastAssistantUsage: void 0,
|
|
@@ -8451,11 +8570,9 @@ async function processEvents(events, context, host) {
|
|
|
8451
8570
|
case "result":
|
|
8452
8571
|
await processResultCase(event, host, context, startTime, state);
|
|
8453
8572
|
break;
|
|
8454
|
-
case "rate_limit_event":
|
|
8455
|
-
|
|
8456
|
-
if (resetsAt) state.rateLimitResetsAt = resetsAt;
|
|
8573
|
+
case "rate_limit_event":
|
|
8574
|
+
processRateLimitCase(event, host, state);
|
|
8457
8575
|
break;
|
|
8458
|
-
}
|
|
8459
8576
|
case "tool_progress":
|
|
8460
8577
|
handleToolProgressEvent(event, host);
|
|
8461
8578
|
break;
|
|
@@ -8467,11 +8584,38 @@ async function processEvents(events, context, host) {
|
|
|
8467
8584
|
retriable: state.retriable || state.sawApiError,
|
|
8468
8585
|
resultSummary: state.resultSummary,
|
|
8469
8586
|
rateLimitResetsAt: state.rateLimitResetsAt,
|
|
8587
|
+
...state.rateLimitRejectedType && { rateLimitRejectedType: state.rateLimitRejectedType },
|
|
8470
8588
|
...state.staleSession && { staleSession: state.staleSession },
|
|
8471
8589
|
...state.authError && { authError: state.authError }
|
|
8472
8590
|
};
|
|
8473
8591
|
}
|
|
8474
8592
|
|
|
8593
|
+
// src/execution/key-cycle.ts
|
|
8594
|
+
var FIVE_HOURS_MS = 5 * 60 * 60 * 1e3;
|
|
8595
|
+
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
8596
|
+
function applyCycledKeyEnv(envVars, env = process.env) {
|
|
8597
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
8598
|
+
env[key] = value;
|
|
8599
|
+
}
|
|
8600
|
+
if (envVars.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
8601
|
+
delete env.ANTHROPIC_API_KEY;
|
|
8602
|
+
if (!envVars.CONVEYOR_AGENT_KEY) delete env.CONVEYOR_AGENT_KEY;
|
|
8603
|
+
} else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH) {
|
|
8604
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
8605
|
+
}
|
|
8606
|
+
}
|
|
8607
|
+
async function syncCredentialsAfterCycle(env = process.env) {
|
|
8608
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
8609
|
+
await ensureClaudeCredentials(env);
|
|
8610
|
+
} else {
|
|
8611
|
+
await removeConveyorCredentials();
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
function fallbackResetIso(rateLimitType, now = Date.now()) {
|
|
8615
|
+
const weekly = /weekly|seven_day/i.test(rateLimitType);
|
|
8616
|
+
return new Date(now + (weekly ? TWENTY_FOUR_HOURS_MS : FIVE_HOURS_MS)).toISOString();
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8475
8619
|
// src/execution/task-property-utils.ts
|
|
8476
8620
|
function collectMissingProps(taskProps) {
|
|
8477
8621
|
const missing = [];
|
|
@@ -8541,6 +8685,19 @@ function handleBuildingToolAccess(toolName, input) {
|
|
|
8541
8685
|
function handleReviewToolAccess(toolName, input) {
|
|
8542
8686
|
return handleBuildingToolAccess(toolName, input);
|
|
8543
8687
|
}
|
|
8688
|
+
var CHAT_BLOCKED_BASH = /\bgit\s+push\b|\bgh\s+pr\b|\bhub\s+pull-request\b/;
|
|
8689
|
+
function handleChatToolAccess(toolName, input) {
|
|
8690
|
+
if (toolName === "Bash") {
|
|
8691
|
+
const cmd = String(input.command ?? "");
|
|
8692
|
+
if (CHAT_BLOCKED_BASH.test(cmd)) {
|
|
8693
|
+
return {
|
|
8694
|
+
behavior: "deny",
|
|
8695
|
+
message: "Chat mode does not open pull requests. Create files locally and attach them to the card with upload_attachment instead of pushing a branch or opening a PR."
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
}
|
|
8699
|
+
return handleBuildingToolAccess(toolName, input);
|
|
8700
|
+
}
|
|
8544
8701
|
function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
|
|
8545
8702
|
if (hasExitedPlanMode) {
|
|
8546
8703
|
return isParentTask ? handleReviewToolAccess(toolName, input) : handleBuildingToolAccess(toolName, input);
|
|
@@ -8675,6 +8832,8 @@ function resolveToolAccess(host, toolName, input) {
|
|
|
8675
8832
|
return handleReviewToolAccess(toolName, input);
|
|
8676
8833
|
case "auto":
|
|
8677
8834
|
return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
8835
|
+
case "chat":
|
|
8836
|
+
return handleChatToolAccess(toolName, input);
|
|
8678
8837
|
default:
|
|
8679
8838
|
return { behavior: "allow", updatedInput: input };
|
|
8680
8839
|
}
|
|
@@ -8694,6 +8853,12 @@ function buildCanUseTool(host) {
|
|
|
8694
8853
|
if (toolName === "AskUserQuestion") {
|
|
8695
8854
|
return await handleAskUserQuestion(host, input);
|
|
8696
8855
|
}
|
|
8856
|
+
if (host.agentMode === "chat" && /(^|__)create_pull_request$/.test(toolName)) {
|
|
8857
|
+
return {
|
|
8858
|
+
behavior: "deny",
|
|
8859
|
+
message: 'Chat mode does not open pull requests. When the conversation is complete, mark the card done with force_update_task_status("Complete").'
|
|
8860
|
+
};
|
|
8861
|
+
}
|
|
8697
8862
|
const result = resolveToolAccess(host, toolName, input);
|
|
8698
8863
|
if (result.behavior === "deny") {
|
|
8699
8864
|
consecutiveDenials++;
|
|
@@ -9294,6 +9459,48 @@ function handleRateLimitPause(host, rateLimitResetsAt) {
|
|
|
9294
9459
|
`Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
|
|
9295
9460
|
);
|
|
9296
9461
|
}
|
|
9462
|
+
var MAX_KEY_CYCLES = 5;
|
|
9463
|
+
async function handleUsageCapRejection(context, host, options, rateLimitType, resetsAt) {
|
|
9464
|
+
const pauseAt = resetsAt ?? fallbackResetIso(rateLimitType);
|
|
9465
|
+
if (host.keyCycleCount >= MAX_KEY_CYCLES) {
|
|
9466
|
+
handleRateLimitPause(host, pauseAt);
|
|
9467
|
+
return;
|
|
9468
|
+
}
|
|
9469
|
+
host.keyCycleCount += 1;
|
|
9470
|
+
let response;
|
|
9471
|
+
try {
|
|
9472
|
+
response = await host.connection.cycleCodingAgentKey(rateLimitType, resetsAt);
|
|
9473
|
+
} catch (error) {
|
|
9474
|
+
host.connection.postChatMessage(
|
|
9475
|
+
`Usage cap hit and key cycling failed (${getErrorMessage(error)}) \u2014 pausing until ${new Date(pauseAt).toLocaleString()}.`
|
|
9476
|
+
);
|
|
9477
|
+
handleRateLimitPause(host, pauseAt);
|
|
9478
|
+
return;
|
|
9479
|
+
}
|
|
9480
|
+
if (!response.cycled) {
|
|
9481
|
+
handleRateLimitPause(host, response.resetsAt);
|
|
9482
|
+
return;
|
|
9483
|
+
}
|
|
9484
|
+
applyCycledKeyEnv(response.envVars);
|
|
9485
|
+
await syncCredentialsAfterCycle();
|
|
9486
|
+
await host.harness.dispose?.();
|
|
9487
|
+
host.connection.postChatMessage(
|
|
9488
|
+
`Usage cap hit \u2014 switched to key **${response.label}** and resuming.`
|
|
9489
|
+
);
|
|
9490
|
+
context.claudeSessionId = null;
|
|
9491
|
+
host.connection.storeSessionId("");
|
|
9492
|
+
const freshPrompt = buildMultimodalPrompt(
|
|
9493
|
+
await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
|
|
9494
|
+
context,
|
|
9495
|
+
host.harnessKind === "pty"
|
|
9496
|
+
);
|
|
9497
|
+
const freshQuery = host.harness.executeQuery({
|
|
9498
|
+
prompt: host.createInputStream(freshPrompt),
|
|
9499
|
+
options: { ...options, sessionId: void 0 },
|
|
9500
|
+
resume: void 0
|
|
9501
|
+
});
|
|
9502
|
+
return runWithRetry(freshQuery, context, host, options);
|
|
9503
|
+
}
|
|
9297
9504
|
function handleRetryError(error, context, host, options, prevImageError) {
|
|
9298
9505
|
if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
|
|
9299
9506
|
return handleStaleSession(context, host, options);
|
|
@@ -9306,9 +9513,17 @@ function handleRetryError(error, context, host, options, prevImageError) {
|
|
|
9306
9513
|
}
|
|
9307
9514
|
function handleProcessResult(result, context, host, options) {
|
|
9308
9515
|
if (result.modeRestart || host.isStopped()) return { action: "return" };
|
|
9309
|
-
if (result.rateLimitResetsAt) {
|
|
9310
|
-
|
|
9311
|
-
|
|
9516
|
+
if (result.rateLimitRejectedType || result.rateLimitResetsAt) {
|
|
9517
|
+
return {
|
|
9518
|
+
action: "return_promise",
|
|
9519
|
+
promise: handleUsageCapRejection(
|
|
9520
|
+
context,
|
|
9521
|
+
host,
|
|
9522
|
+
options,
|
|
9523
|
+
result.rateLimitRejectedType ?? "unknown",
|
|
9524
|
+
result.rateLimitResetsAt
|
|
9525
|
+
)
|
|
9526
|
+
};
|
|
9312
9527
|
}
|
|
9313
9528
|
if (result.staleSession && context.claudeSessionId) {
|
|
9314
9529
|
return { action: "return_promise", promise: handleStaleSession(context, host, options) };
|
|
@@ -9391,6 +9606,7 @@ var QueryBridge = class {
|
|
|
9391
9606
|
_discoveryCompleted = false;
|
|
9392
9607
|
_isParentTask = false;
|
|
9393
9608
|
_wasRateLimited = false;
|
|
9609
|
+
_keyCycleCount = 0;
|
|
9394
9610
|
_abortController = null;
|
|
9395
9611
|
/** Called by SessionRunner when ExitPlanMode triggers a mode transition. */
|
|
9396
9612
|
onModeTransition;
|
|
@@ -9568,6 +9784,12 @@ var QueryBridge = class {
|
|
|
9568
9784
|
set wasRateLimited(val) {
|
|
9569
9785
|
bridge._wasRateLimited = val;
|
|
9570
9786
|
},
|
|
9787
|
+
get keyCycleCount() {
|
|
9788
|
+
return bridge._keyCycleCount;
|
|
9789
|
+
},
|
|
9790
|
+
set keyCycleCount(val) {
|
|
9791
|
+
bridge._keyCycleCount = val;
|
|
9792
|
+
},
|
|
9571
9793
|
get activeQuery() {
|
|
9572
9794
|
return bridge.activeQuery;
|
|
9573
9795
|
},
|
|
@@ -9651,11 +9873,21 @@ function normalizeUsageText(stdout) {
|
|
|
9651
9873
|
}
|
|
9652
9874
|
function parseUsageGauges(stdout) {
|
|
9653
9875
|
const text = normalizeUsageText(stdout);
|
|
9654
|
-
const
|
|
9655
|
-
|
|
9876
|
+
const rows = [
|
|
9877
|
+
...text.matchAll(
|
|
9878
|
+
/Current (session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/gi
|
|
9879
|
+
)
|
|
9880
|
+
];
|
|
9881
|
+
const gauges = rows.map((m) => ({
|
|
9882
|
+
label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
|
|
9883
|
+
utilization: Number(m[3]) / 100
|
|
9884
|
+
}));
|
|
9885
|
+
const session = gauges.find((g) => g.label.startsWith("Current session"));
|
|
9886
|
+
const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
|
|
9656
9887
|
return {
|
|
9657
|
-
sessionUsage: session ?
|
|
9658
|
-
weeklyUsage: weekly.length ? Math.max(...weekly.map((
|
|
9888
|
+
sessionUsage: session ? session.utilization : null,
|
|
9889
|
+
weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
|
|
9890
|
+
gauges
|
|
9659
9891
|
};
|
|
9660
9892
|
}
|
|
9661
9893
|
|
|
@@ -9768,17 +10000,44 @@ async function runUsageProbe(deps = {}) {
|
|
|
9768
10000
|
|
|
9769
10001
|
// src/execution/usage-sampler.ts
|
|
9770
10002
|
var logger4 = createServiceLogger("usage-sampler");
|
|
9771
|
-
|
|
10003
|
+
function isAttributable(identity, sessionToken) {
|
|
10004
|
+
if (!identity) return { ok: true };
|
|
10005
|
+
if (identity.hasRefreshToken) {
|
|
10006
|
+
return { ok: false, reason: "manual-login-credentials" };
|
|
10007
|
+
}
|
|
10008
|
+
if (sessionToken && identity.accessToken && identity.accessToken !== sessionToken) {
|
|
10009
|
+
return { ok: false, reason: "credentials-token-mismatch" };
|
|
10010
|
+
}
|
|
10011
|
+
return { ok: true };
|
|
10012
|
+
}
|
|
10013
|
+
async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
|
|
9772
10014
|
if (!token && !hasSubscriptionCredentials()) return [];
|
|
9773
10015
|
try {
|
|
10016
|
+
const attributable = isAttributable(await readIdentity(), token);
|
|
10017
|
+
if (!attributable.ok) {
|
|
10018
|
+
logger4.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
|
|
10019
|
+
reason: attributable.reason
|
|
10020
|
+
});
|
|
10021
|
+
return [];
|
|
10022
|
+
}
|
|
9774
10023
|
const stdout = await probe();
|
|
9775
|
-
const { sessionUsage, weeklyUsage } = parseUsageGauges(stdout);
|
|
10024
|
+
const { sessionUsage, weeklyUsage, gauges } = parseUsageGauges(stdout);
|
|
9776
10025
|
const samples = [];
|
|
9777
10026
|
if (sessionUsage !== null) {
|
|
9778
|
-
samples.push({
|
|
10027
|
+
samples.push({
|
|
10028
|
+
rateLimitType: "five_hour",
|
|
10029
|
+
utilization: sessionUsage,
|
|
10030
|
+
status: "allowed",
|
|
10031
|
+
gauges
|
|
10032
|
+
});
|
|
9779
10033
|
}
|
|
9780
10034
|
if (weeklyUsage !== null) {
|
|
9781
|
-
samples.push({
|
|
10035
|
+
samples.push({
|
|
10036
|
+
rateLimitType: "seven_day",
|
|
10037
|
+
utilization: weeklyUsage,
|
|
10038
|
+
status: "allowed",
|
|
10039
|
+
gauges
|
|
10040
|
+
});
|
|
9782
10041
|
}
|
|
9783
10042
|
if (samples.length === 0) {
|
|
9784
10043
|
logger4.info("usage sample produced no gauges", {
|
|
@@ -10173,7 +10432,7 @@ function isHeavyGateActive() {
|
|
|
10173
10432
|
}
|
|
10174
10433
|
|
|
10175
10434
|
// src/runner/session-runner.ts
|
|
10176
|
-
var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery"]);
|
|
10435
|
+
var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery", "chat"]);
|
|
10177
10436
|
var SessionRunner = class _SessionRunner {
|
|
10178
10437
|
connection;
|
|
10179
10438
|
mode;
|
|
@@ -10744,7 +11003,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
10744
11003
|
type: "rate_limit_update",
|
|
10745
11004
|
rateLimitType: sample.rateLimitType,
|
|
10746
11005
|
utilization: sample.utilization,
|
|
10747
|
-
status: sample.status
|
|
11006
|
+
status: sample.status,
|
|
11007
|
+
gauges: sample.gauges
|
|
10748
11008
|
});
|
|
10749
11009
|
}
|
|
10750
11010
|
}
|
|
@@ -11281,4 +11541,4 @@ export {
|
|
|
11281
11541
|
runStartCommand,
|
|
11282
11542
|
unshallowRepo
|
|
11283
11543
|
};
|
|
11284
|
-
//# sourceMappingURL=chunk-
|
|
11544
|
+
//# sourceMappingURL=chunk-H3OGNJS4.js.map
|