@rallycry/conveyor-agent 10.6.1 → 10.7.1
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-5ELLQUP4.js → chunk-EBZRRNO5.js} +99 -288
- package/dist/chunk-EBZRRNO5.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +0 -44
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +23 -4
- package/dist/chunk-5ELLQUP4.js.map +0 -1
|
@@ -2502,6 +2502,9 @@ var ListMyLiveSessionsRequestSchema = z2.object({
|
|
|
2502
2502
|
/** Admin-only: list another member's sessions instead of the caller's. */
|
|
2503
2503
|
targetUserId: z2.string().optional()
|
|
2504
2504
|
});
|
|
2505
|
+
var ListActiveSessionUsersRequestSchema = z2.object({
|
|
2506
|
+
projectId: z2.string()
|
|
2507
|
+
});
|
|
2505
2508
|
var GetProjectAvailableTuisRequestSchema = z2.object({
|
|
2506
2509
|
projectId: z2.string()
|
|
2507
2510
|
});
|
|
@@ -3027,13 +3030,12 @@ var ClaudeCodeHarness = class {
|
|
|
3027
3030
|
}
|
|
3028
3031
|
createMcpServer(config) {
|
|
3029
3032
|
const sdkTools = config.tools.map(
|
|
3030
|
-
(t) => tool(
|
|
3031
|
-
t.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
t.
|
|
3035
|
-
|
|
3036
|
-
)
|
|
3033
|
+
(t) => tool(t.name, t.description, t.schema, t.handler, {
|
|
3034
|
+
...t.annotations ? { annotations: t.annotations } : {},
|
|
3035
|
+
// Preloads the tool into the prompt (skips ToolSearch deferral) via
|
|
3036
|
+
// `_meta['anthropic/alwaysLoad']` on the SDK MCP transport.
|
|
3037
|
+
...t.alwaysLoad ? { alwaysLoad: true } : {}
|
|
3038
|
+
})
|
|
3037
3039
|
);
|
|
3038
3040
|
return createSdkMcpServer({ name: config.name, tools: sdkTools });
|
|
3039
3041
|
}
|
|
@@ -3861,7 +3863,16 @@ var PtyToolServer = class {
|
|
|
3861
3863
|
const inputSchema = tool2.strict ? z3.strictObject(tool2.schema) : tool2.schema;
|
|
3862
3864
|
register(
|
|
3863
3865
|
tool2.name,
|
|
3864
|
-
{
|
|
3866
|
+
{
|
|
3867
|
+
description: tool2.description,
|
|
3868
|
+
inputSchema,
|
|
3869
|
+
// The spawned CLI reads `_meta['anthropic/alwaysLoad']` from the
|
|
3870
|
+
// `tools/list` response and preloads the tool into the prompt
|
|
3871
|
+
// instead of deferring it behind ToolSearch. Per-tool (vs per-server
|
|
3872
|
+
// `alwaysLoad` in the mcp-config) so only the hot protocol tools pay
|
|
3873
|
+
// the prompt cost while the long tail stays deferred.
|
|
3874
|
+
...tool2.alwaysLoad ? { _meta: { "anthropic/alwaysLoad": true } } : {}
|
|
3875
|
+
},
|
|
3865
3876
|
(args) => tool2.handler(args)
|
|
3866
3877
|
);
|
|
3867
3878
|
}
|
|
@@ -4119,6 +4130,25 @@ async function readRaw(path4) {
|
|
|
4119
4130
|
return null;
|
|
4120
4131
|
}
|
|
4121
4132
|
}
|
|
4133
|
+
var READ_BACK_DELAYS_MS = [250, 500, 1e3, 2e3];
|
|
4134
|
+
var defaultSleep = (ms) => new Promise((resolve) => {
|
|
4135
|
+
setTimeout(resolve, ms);
|
|
4136
|
+
});
|
|
4137
|
+
async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS_MS) {
|
|
4138
|
+
const sleep4 = io2.sleep ?? defaultSleep;
|
|
4139
|
+
for (let attempt = 0; ; attempt++) {
|
|
4140
|
+
await io2.write(contents);
|
|
4141
|
+
if (await io2.read() === contents) return true;
|
|
4142
|
+
if (attempt >= delaysMs.length) return false;
|
|
4143
|
+
await sleep4(delaysMs[attempt]);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
function fsWriteIo(path4, mode) {
|
|
4147
|
+
return {
|
|
4148
|
+
write: (contents) => writeFile4(path4, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
|
|
4149
|
+
read: () => readRaw(path4)
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4122
4152
|
async function ensureClaudeCredentials(env = process.env) {
|
|
4123
4153
|
const isCloud = isConveyorCloudEnv(env);
|
|
4124
4154
|
const token = env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
@@ -4135,7 +4165,13 @@ async function ensureClaudeCredentials(env = process.env) {
|
|
|
4135
4165
|
});
|
|
4136
4166
|
if (plan.action === "skip") return;
|
|
4137
4167
|
await mkdir2(claudeConfigHome(), { recursive: true });
|
|
4138
|
-
await
|
|
4168
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path4, 384), plan.contents);
|
|
4169
|
+
if (!verified) {
|
|
4170
|
+
process.stderr.write(
|
|
4171
|
+
`[conveyor-agent] claude credentials read-back still stale after retries at ${path4} \u2014 TUI may land on the login picker
|
|
4172
|
+
`
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4139
4175
|
await chmod2(path4, 384).catch(() => {
|
|
4140
4176
|
});
|
|
4141
4177
|
} catch (err) {
|
|
@@ -4168,9 +4204,10 @@ async function sanitizeApprovedApiKeys(oauthToken) {
|
|
|
4168
4204
|
const path4 = claudeJsonPath();
|
|
4169
4205
|
const cleaned = planApprovedApiKeyCleanup(await readRaw(path4), oauthToken);
|
|
4170
4206
|
if (cleaned === null) return;
|
|
4171
|
-
await
|
|
4207
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path4), cleaned);
|
|
4172
4208
|
process.stderr.write(
|
|
4173
|
-
"[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n"
|
|
4209
|
+
verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${path4} \u2014 CLI may still see the poisoned entry
|
|
4210
|
+
`
|
|
4174
4211
|
);
|
|
4175
4212
|
} catch (err) {
|
|
4176
4213
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4229,16 +4266,16 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
|
|
|
4229
4266
|
const path4 = claudeJsonPath();
|
|
4230
4267
|
const contents = planClaudeJsonSeed(await readRaw(path4), trustCwd);
|
|
4231
4268
|
if (contents === null) return;
|
|
4232
|
-
await
|
|
4233
|
-
|
|
4234
|
-
if (verify === contents) {
|
|
4269
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path4), contents);
|
|
4270
|
+
if (verified) {
|
|
4235
4271
|
process.stderr.write(
|
|
4236
4272
|
`[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
|
|
4237
4273
|
`
|
|
4238
4274
|
);
|
|
4239
4275
|
} else {
|
|
4276
|
+
const verify = await readRaw(path4);
|
|
4240
4277
|
process.stderr.write(
|
|
4241
|
-
`[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4}: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
|
|
4278
|
+
`[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4} after retries: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
|
|
4242
4279
|
`
|
|
4243
4280
|
);
|
|
4244
4281
|
}
|
|
@@ -6184,7 +6221,7 @@ Environment (fully ready \u2014 do NOT verify or set up):`,
|
|
|
6184
6221
|
`- The shell cwd resets between Bash calls \u2014 always use absolute paths (or \`git -C\`, \`bun run --cwd\`); never spend a tool call on a bare \`cd\`.`,
|
|
6185
6222
|
`- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output.`,
|
|
6186
6223
|
`- Waiting on long-running commands: if a gate finishes in under ~2 minutes, run it in the foreground with a timeout. Anything longer: launch it with run_in_background and STOP \u2014 a completion notification arrives when it finishes. Never busy-wait with sleep/pgrep/tail loops (foreground shell commands are killed at ~2 minutes), and never re-run the suite to escape a wait that looks stalled.`,
|
|
6187
|
-
`-
|
|
6224
|
+
`- The core mcp__conveyor__* tools are preloaded \u2014 call them directly; do NOT spend a ToolSearch call on them. For any tool that IS still deferred (schema not loaded), load ALL the schemas you expect to need in ONE ToolSearch call using fully-qualified names (query "select:Monitor,mcp__conveyor__<name>" \u2014 bare MCP tool names without the mcp__<server>__ prefix do not match); never guess a deferred tool's parameters.`,
|
|
6188
6225
|
`- The \`gh\` CLI may not be installed \u2014 use the mcp__conveyor__* tools for PR and task state.`,
|
|
6189
6226
|
`- Read a file before your first Write/Edit to it, and batch multiple changes to the same file into a single call instead of many sequential edits.`,
|
|
6190
6227
|
`
|
|
@@ -6437,9 +6474,10 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
|
|
|
6437
6474
|
}
|
|
6438
6475
|
if (isPm) {
|
|
6439
6476
|
return [
|
|
6440
|
-
`You are the project manager for this task.`,
|
|
6477
|
+
`You are the project manager for this task \u2014 a thoughtful, collaborative planner, not the implementer.`,
|
|
6478
|
+
`Your job right now is to help the team turn this request into a clear, well-scoped plan. Be a helpful planning partner: think out loud, surface trade-offs, and keep the human in the loop.`,
|
|
6441
6479
|
`Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
|
|
6442
|
-
`Start planning now \u2014 explore the codebase, ask clarifying questions if
|
|
6480
|
+
`Start planning now \u2014 explore the codebase, ask clarifying questions if anything is ambiguous, and draft a plan. Do not wait for additional team input before engaging; the initial message IS the team engaging.`,
|
|
6443
6481
|
`When you finish planning, save the plan with update_task_plan, post a summary of the plan for the team with post_to_chat (your turn output is NOT shown in chat), then end your turn. A separate task agent will execute the plan after review.`
|
|
6444
6482
|
];
|
|
6445
6483
|
}
|
|
@@ -7852,6 +7890,28 @@ function getModeTools(agentMode, connection, config, context) {
|
|
|
7852
7890
|
return config.mode === "pm" ? buildPmTools(connection, { includePackTools: false }) : [];
|
|
7853
7891
|
}
|
|
7854
7892
|
}
|
|
7893
|
+
var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
|
|
7894
|
+
// Every mode/session
|
|
7895
|
+
"post_to_chat",
|
|
7896
|
+
"read_task_chat",
|
|
7897
|
+
"get_task",
|
|
7898
|
+
"get_current_plan",
|
|
7899
|
+
"set_manual_tests",
|
|
7900
|
+
"create_pull_request",
|
|
7901
|
+
// Planning/auto/building
|
|
7902
|
+
"update_task_plan",
|
|
7903
|
+
"update_task_properties",
|
|
7904
|
+
// Review mode
|
|
7905
|
+
"approve_code_review",
|
|
7906
|
+
"request_code_changes",
|
|
7907
|
+
// Pack/parent orchestration
|
|
7908
|
+
"list_subtasks"
|
|
7909
|
+
]);
|
|
7910
|
+
function withAlwaysLoad(tools) {
|
|
7911
|
+
return tools.map(
|
|
7912
|
+
(tool2) => ALWAYS_LOADED_TOOLS.has(tool2.name) ? { ...tool2, alwaysLoad: true } : tool2
|
|
7913
|
+
);
|
|
7914
|
+
}
|
|
7855
7915
|
function buildConveyorTools(connection, config, context, agentMode) {
|
|
7856
7916
|
const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
|
|
7857
7917
|
const commonTools = buildCommonTools(connection, config);
|
|
@@ -7859,7 +7919,13 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
7859
7919
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" ? buildDiscoveryTools(connection) : [];
|
|
7860
7920
|
const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
|
|
7861
7921
|
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
7862
|
-
return [
|
|
7922
|
+
return withAlwaysLoad([
|
|
7923
|
+
...commonTools,
|
|
7924
|
+
...modeTools,
|
|
7925
|
+
...discoveryTools,
|
|
7926
|
+
...codeReviewTools,
|
|
7927
|
+
...emergencyTools
|
|
7928
|
+
]);
|
|
7863
7929
|
}
|
|
7864
7930
|
function createConveyorMcpServer(harness, connection, config, context, agentMode) {
|
|
7865
7931
|
return harness.createMcpServer({
|
|
@@ -8465,7 +8531,6 @@ async function handleExitPlanMode(host, input) {
|
|
|
8465
8531
|
if (gate) return gate;
|
|
8466
8532
|
if (host.agentMode === "discovery") {
|
|
8467
8533
|
host.hasExitedPlanMode = true;
|
|
8468
|
-
host.discoveryCompleted = true;
|
|
8469
8534
|
try {
|
|
8470
8535
|
await host.connection.triggerIdentification();
|
|
8471
8536
|
} catch (triggerErr) {
|
|
@@ -8474,13 +8539,8 @@ async function handleExitPlanMode(host, input) {
|
|
|
8474
8539
|
);
|
|
8475
8540
|
}
|
|
8476
8541
|
await host.connection.postChatMessageAwait(
|
|
8477
|
-
"
|
|
8542
|
+
"Plan posted \u2014 switching to auto mode. I'll start building now; send a message any time to steer or add follow-ups."
|
|
8478
8543
|
);
|
|
8479
|
-
if (host.harnessKind === "pty") {
|
|
8480
|
-
setTimeout(() => host.requestStop(), 250);
|
|
8481
|
-
} else {
|
|
8482
|
-
host.requestStop();
|
|
8483
|
-
}
|
|
8484
8544
|
return { behavior: "allow", updatedInput: input };
|
|
8485
8545
|
}
|
|
8486
8546
|
try {
|
|
@@ -8525,19 +8585,6 @@ async function handleAskUserQuestion(host, input) {
|
|
|
8525
8585
|
}
|
|
8526
8586
|
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
8527
8587
|
}
|
|
8528
|
-
var EXPLORATION_TOOLS = /* @__PURE__ */ new Set(["Read", "Grep", "Glob"]);
|
|
8529
|
-
function trackExploration(tracker, toolName, input) {
|
|
8530
|
-
if (!EXPLORATION_TOOLS.has(toolName)) return null;
|
|
8531
|
-
if (toolName === "Read") {
|
|
8532
|
-
const filePath = String(input.file_path ?? "");
|
|
8533
|
-
if (filePath) return tracker.recordFileRead(filePath);
|
|
8534
|
-
}
|
|
8535
|
-
if (toolName === "Grep" || toolName === "Glob") {
|
|
8536
|
-
const pattern = String(input.pattern ?? "");
|
|
8537
|
-
if (pattern) return tracker.recordSearch(pattern);
|
|
8538
|
-
}
|
|
8539
|
-
return null;
|
|
8540
|
-
}
|
|
8541
8588
|
var DENIAL_WARNING_THRESHOLD = 3;
|
|
8542
8589
|
var DENIAL_FORCE_STOP_THRESHOLD = 8;
|
|
8543
8590
|
function handleDenialEscalation(host, consecutiveDenials) {
|
|
@@ -8583,12 +8630,6 @@ function buildCanUseTool(host) {
|
|
|
8583
8630
|
return await handleAskUserQuestion(host, input);
|
|
8584
8631
|
}
|
|
8585
8632
|
const result = resolveToolAccess(host, toolName, input);
|
|
8586
|
-
if (result.behavior === "allow" && host.explorationTracker && !host.hasExitedPlanMode) {
|
|
8587
|
-
const signal = trackExploration(host.explorationTracker, toolName, input);
|
|
8588
|
-
if (signal) {
|
|
8589
|
-
host.connection.postChatMessage(signal.message);
|
|
8590
|
-
}
|
|
8591
|
-
}
|
|
8592
8633
|
if (result.behavior === "deny") {
|
|
8593
8634
|
consecutiveDenials++;
|
|
8594
8635
|
handleDenialEscalation(host, consecutiveDenials);
|
|
@@ -8765,7 +8806,7 @@ function resolvePromptDelivery(inputs) {
|
|
|
8765
8806
|
if (inputs.runnerMode === "code-review") return "submit";
|
|
8766
8807
|
if (inputs.isFollowUp || inputs.hasExistingSession) return "submit";
|
|
8767
8808
|
if (inputs.isAuto || inputs.agentMode === "auto") return "submit";
|
|
8768
|
-
if (inputs.agentMode !== "
|
|
8809
|
+
if (inputs.agentMode !== "help") return "submit";
|
|
8769
8810
|
return "prefill";
|
|
8770
8811
|
}
|
|
8771
8812
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
@@ -9243,108 +9284,6 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
9243
9284
|
}
|
|
9244
9285
|
}
|
|
9245
9286
|
|
|
9246
|
-
// src/execution/exploration-tracker.ts
|
|
9247
|
-
var FILE_REREAD_NUDGE_L1 = 3;
|
|
9248
|
-
var FILE_REREAD_NUDGE_L2 = 5;
|
|
9249
|
-
var SEARCH_REPEAT_NUDGE_L1 = 3;
|
|
9250
|
-
var SEARCH_REPEAT_NUDGE_L2 = 5;
|
|
9251
|
-
var RATIO_MIN_READS_L1 = 10;
|
|
9252
|
-
var RATIO_THRESHOLD_L1 = 0.4;
|
|
9253
|
-
var RATIO_MIN_READS_L2 = 15;
|
|
9254
|
-
var RATIO_THRESHOLD_L2 = 0.3;
|
|
9255
|
-
function normalizePath(filePath) {
|
|
9256
|
-
let normalized = filePath.replace(/^\.\//, "");
|
|
9257
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
9258
|
-
return normalized;
|
|
9259
|
-
}
|
|
9260
|
-
var ExplorationTracker = class {
|
|
9261
|
-
fileReadCounts = /* @__PURE__ */ new Map();
|
|
9262
|
-
searchCounts = /* @__PURE__ */ new Map();
|
|
9263
|
-
totalReads = 0;
|
|
9264
|
-
highestNudgeEmitted = 0;
|
|
9265
|
-
complexityMultiplier;
|
|
9266
|
-
constructor(isParentTask) {
|
|
9267
|
-
this.complexityMultiplier = isParentTask ? 1.5 : 1;
|
|
9268
|
-
}
|
|
9269
|
-
/** Record a file read and return a staleness signal if thresholds are crossed. */
|
|
9270
|
-
recordFileRead(filePath) {
|
|
9271
|
-
const normalized = normalizePath(filePath);
|
|
9272
|
-
const count = (this.fileReadCounts.get(normalized) ?? 0) + 1;
|
|
9273
|
-
this.fileReadCounts.set(normalized, count);
|
|
9274
|
-
this.totalReads++;
|
|
9275
|
-
return this.checkFileRereadSignal(normalized, count) ?? this.checkExplorationRatio();
|
|
9276
|
-
}
|
|
9277
|
-
/** Record a search pattern and return a staleness signal if thresholds are crossed. */
|
|
9278
|
-
recordSearch(pattern) {
|
|
9279
|
-
const count = (this.searchCounts.get(pattern) ?? 0) + 1;
|
|
9280
|
-
this.searchCounts.set(pattern, count);
|
|
9281
|
-
return this.checkSearchRepeatSignal(pattern, count);
|
|
9282
|
-
}
|
|
9283
|
-
scaledThreshold(base) {
|
|
9284
|
-
return Math.ceil(base * this.complexityMultiplier);
|
|
9285
|
-
}
|
|
9286
|
-
checkFileRereadSignal(filePath, count) {
|
|
9287
|
-
const l2 = this.scaledThreshold(FILE_REREAD_NUDGE_L2);
|
|
9288
|
-
const l1 = this.scaledThreshold(FILE_REREAD_NUDGE_L1);
|
|
9289
|
-
if (count >= l2 && this.highestNudgeEmitted < 2) {
|
|
9290
|
-
this.highestNudgeEmitted = 2;
|
|
9291
|
-
return {
|
|
9292
|
-
level: 2,
|
|
9293
|
-
message: `\u26A0\uFE0F Exploration warning: You've read "${filePath}" ${count} times, suggesting analysis paralysis. Write your plan with the context you have and call ExitPlanMode. If something specific is blocking you, describe the blocker in your plan.`
|
|
9294
|
-
};
|
|
9295
|
-
}
|
|
9296
|
-
if (count >= l1 && this.highestNudgeEmitted < 1) {
|
|
9297
|
-
this.highestNudgeEmitted = 1;
|
|
9298
|
-
return {
|
|
9299
|
-
level: 1,
|
|
9300
|
-
message: `Exploration note: You've read "${filePath}" ${count} times. Consider whether you have enough context to start writing your plan. If you're still exploring, try looking at different files.`
|
|
9301
|
-
};
|
|
9302
|
-
}
|
|
9303
|
-
return null;
|
|
9304
|
-
}
|
|
9305
|
-
checkSearchRepeatSignal(pattern, count) {
|
|
9306
|
-
const l2 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L2);
|
|
9307
|
-
const l1 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L1);
|
|
9308
|
-
const displayPattern = pattern.length > 60 ? pattern.slice(0, 57) + "..." : pattern;
|
|
9309
|
-
if (count >= l2 && this.highestNudgeEmitted < 2) {
|
|
9310
|
-
this.highestNudgeEmitted = 2;
|
|
9311
|
-
return {
|
|
9312
|
-
level: 2,
|
|
9313
|
-
message: `\u26A0\uFE0F Exploration warning: You've searched for "${displayPattern}" ${count} times. Repeated searches for the same pattern suggest you may be stuck. Proceed with the information you have.`
|
|
9314
|
-
};
|
|
9315
|
-
}
|
|
9316
|
-
if (count >= l1 && this.highestNudgeEmitted < 1) {
|
|
9317
|
-
this.highestNudgeEmitted = 1;
|
|
9318
|
-
return {
|
|
9319
|
-
level: 1,
|
|
9320
|
-
message: `Exploration note: You've searched for "${displayPattern}" ${count} times. If you're not finding what you need, try a different approach or broader pattern.`
|
|
9321
|
-
};
|
|
9322
|
-
}
|
|
9323
|
-
return null;
|
|
9324
|
-
}
|
|
9325
|
-
checkExplorationRatio() {
|
|
9326
|
-
const uniqueReads = this.fileReadCounts.size;
|
|
9327
|
-
const ratio = this.totalReads > 0 ? uniqueReads / this.totalReads : 1;
|
|
9328
|
-
const minReadsL2 = this.scaledThreshold(RATIO_MIN_READS_L2);
|
|
9329
|
-
if (this.totalReads >= minReadsL2 && ratio < RATIO_THRESHOLD_L2 && this.highestNudgeEmitted < 2) {
|
|
9330
|
-
this.highestNudgeEmitted = 2;
|
|
9331
|
-
return {
|
|
9332
|
-
level: 2,
|
|
9333
|
-
message: `\u26A0\uFE0F Exploration warning: Only ${Math.round(ratio * 100)}% of your ${this.totalReads} file reads are unique. This indicates significant re-reading. Write your plan with the context you have.`
|
|
9334
|
-
};
|
|
9335
|
-
}
|
|
9336
|
-
const minReadsL1 = this.scaledThreshold(RATIO_MIN_READS_L1);
|
|
9337
|
-
if (this.totalReads >= minReadsL1 && ratio < RATIO_THRESHOLD_L1 && this.highestNudgeEmitted < 1) {
|
|
9338
|
-
this.highestNudgeEmitted = 1;
|
|
9339
|
-
return {
|
|
9340
|
-
level: 1,
|
|
9341
|
-
message: `Exploration note: ${Math.round(ratio * 100)}% of your ${this.totalReads} file reads are unique. You're re-reading files more than exploring new ones. Consider starting your plan.`
|
|
9342
|
-
};
|
|
9343
|
-
}
|
|
9344
|
-
return null;
|
|
9345
|
-
}
|
|
9346
|
-
};
|
|
9347
|
-
|
|
9348
9287
|
// src/runner/query-bridge.ts
|
|
9349
9288
|
var logger3 = createServiceLogger("QueryBridge");
|
|
9350
9289
|
function resolveHarnessKind() {
|
|
@@ -9528,7 +9467,6 @@ var QueryBridge = class {
|
|
|
9528
9467
|
harness: this.harness,
|
|
9529
9468
|
harnessKind: this.harnessKind,
|
|
9530
9469
|
setupLog: [],
|
|
9531
|
-
explorationTracker: bridge.mode.effectiveMode === "discovery" || bridge.mode.effectiveMode === "auto" && !bridge.mode.hasExitedPlanMode ? new ExplorationTracker(bridge._isParentTask) : null,
|
|
9532
9470
|
sessionIds: this.sessionIds,
|
|
9533
9471
|
pendingToolOutputs: this.pendingToolOutputs,
|
|
9534
9472
|
// Live getters/setters delegating to ModeController + bridge state
|
|
@@ -9648,6 +9586,10 @@ function parseUsageGauges(stdout) {
|
|
|
9648
9586
|
// src/usage/run-probe.ts
|
|
9649
9587
|
import { spawn } from "child_process";
|
|
9650
9588
|
var PROBE_TIMEOUT_MS = 15e3;
|
|
9589
|
+
function buildProbeEnv(env = process.env) {
|
|
9590
|
+
const { CLAUDE_CODE_OAUTH_TOKEN: _oauth, ANTHROPIC_API_KEY: _apiKey, ...rest } = env;
|
|
9591
|
+
return rest;
|
|
9592
|
+
}
|
|
9651
9593
|
function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"]) {
|
|
9652
9594
|
return new Promise((resolve) => {
|
|
9653
9595
|
let stdout = "";
|
|
@@ -9659,7 +9601,7 @@ function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"])
|
|
|
9659
9601
|
};
|
|
9660
9602
|
let child;
|
|
9661
9603
|
try {
|
|
9662
|
-
child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
9604
|
+
child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"], env: buildProbeEnv() });
|
|
9663
9605
|
} catch {
|
|
9664
9606
|
finish("");
|
|
9665
9607
|
return;
|
|
@@ -9701,7 +9643,10 @@ async function sampleKeyUsage(token, probe = runUsageProbe) {
|
|
|
9701
9643
|
samples.push({ rateLimitType: "seven_day", utilization: weeklyUsage, status: "allowed" });
|
|
9702
9644
|
}
|
|
9703
9645
|
if (samples.length === 0) {
|
|
9704
|
-
logger4.info("usage sample produced no gauges", {
|
|
9646
|
+
logger4.info("usage sample produced no gauges", {
|
|
9647
|
+
stdoutLength: stdout.length,
|
|
9648
|
+
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
9649
|
+
});
|
|
9705
9650
|
}
|
|
9706
9651
|
return samples;
|
|
9707
9652
|
} catch (error) {
|
|
@@ -10017,9 +9962,7 @@ function isHeavyGateActive() {
|
|
|
10017
9962
|
}
|
|
10018
9963
|
|
|
10019
9964
|
// src/runner/session-runner.ts
|
|
10020
|
-
|
|
10021
|
-
return typeof name === "string" && (name === "post_to_chat" || name.endsWith("__post_to_chat"));
|
|
10022
|
-
}
|
|
9965
|
+
var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery"]);
|
|
10023
9966
|
var SessionRunner = class _SessionRunner {
|
|
10024
9967
|
connection;
|
|
10025
9968
|
mode;
|
|
@@ -10044,13 +9987,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10044
9987
|
queryBridge = null;
|
|
10045
9988
|
inputResolver = null;
|
|
10046
9989
|
pendingMessages = [];
|
|
10047
|
-
prNudgeCount = 0;
|
|
10048
|
-
/** Set when the agent posts a substantive message to the task chat (the
|
|
10049
|
-
* `post_to_chat` tool) during the current turn; reset at the start of every
|
|
10050
|
-
* query. Read by the stuck-detection: an auto agent that finished without a
|
|
10051
|
-
* PR but DID explain itself / ask for help in chat is waiting on a human,
|
|
10052
|
-
* not silently stuck, so it is left attached rather than nudged. */
|
|
10053
|
-
agentSpokeThisTurn = false;
|
|
10054
9990
|
/** Guards overlapping runs of the periodic git flush. */
|
|
10055
9991
|
periodicFlushInFlight = false;
|
|
10056
9992
|
/** Consecutive flush ticks skipped because a heavy gate was running. */
|
|
@@ -10240,9 +10176,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10240
10176
|
);
|
|
10241
10177
|
this.queryBridge.isDiscoveryCompleted = false;
|
|
10242
10178
|
}
|
|
10243
|
-
if (!this.stopped && this.pendingMessages.length === 0) {
|
|
10244
|
-
await this.maybeHandleStuckAuto();
|
|
10245
|
-
}
|
|
10246
10179
|
if (!this.stopped) {
|
|
10247
10180
|
process.stderr.write(
|
|
10248
10181
|
`[conveyor-agent] Listening for messages (mode: ${this.mode.effectiveMode})
|
|
@@ -10318,9 +10251,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10318
10251
|
this.interrupted = false;
|
|
10319
10252
|
continue;
|
|
10320
10253
|
}
|
|
10321
|
-
if (!this.stopped && this.pendingMessages.length === 0) {
|
|
10322
|
-
await this.maybeHandleStuckAuto();
|
|
10323
|
-
}
|
|
10324
10254
|
if (!this.stopped) await this.setState("idle");
|
|
10325
10255
|
} else if (this._state === "error") {
|
|
10326
10256
|
await this.setState("idle");
|
|
@@ -10376,7 +10306,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
10376
10306
|
return true;
|
|
10377
10307
|
}
|
|
10378
10308
|
const delivery = this.pendingMessages.length > 0 ? "submit" : intrinsicDelivery;
|
|
10379
|
-
const shouldRun = effectiveMode
|
|
10309
|
+
const shouldRun = AUTO_RUN_MODES.has(effectiveMode) || delivery === "prefill";
|
|
10380
10310
|
if (!shouldRun) {
|
|
10381
10311
|
await this.setState("idle");
|
|
10382
10312
|
return false;
|
|
@@ -10511,7 +10441,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10511
10441
|
/** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
|
|
10512
10442
|
async executeQuery(followUpContent, promptDelivery) {
|
|
10513
10443
|
if (!this.fullContext || !this.queryBridge) return;
|
|
10514
|
-
this.agentSpokeThisTurn = false;
|
|
10515
10444
|
try {
|
|
10516
10445
|
await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
|
|
10517
10446
|
} catch (err) {
|
|
@@ -10533,7 +10462,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10533
10462
|
/** Run queryBridge.executePassive, swallowing abort errors from stop/softStop. */
|
|
10534
10463
|
async executePassive() {
|
|
10535
10464
|
if (!this.fullContext || !this.queryBridge) return;
|
|
10536
|
-
this.agentSpokeThisTurn = false;
|
|
10537
10465
|
try {
|
|
10538
10466
|
await this.queryBridge.executePassive(this.fullContext);
|
|
10539
10467
|
} catch (err) {
|
|
@@ -10675,119 +10603,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10675
10603
|
resolver(null);
|
|
10676
10604
|
}
|
|
10677
10605
|
}
|
|
10678
|
-
// ── Auto-mode stuck detection & nudge ───────────────────────────────
|
|
10679
|
-
static MAX_STUCK_NUDGES = 3;
|
|
10680
|
-
/** The build-phase nudge (In Progress, no PR). Offers BOTH escape routes so
|
|
10681
|
-
* the agent never just goes idle. */
|
|
10682
|
-
static STUCK_PROMPT = "You are in Auto mode, your task is still In Progress, and there is no open pull request. Do ONE of these right now: (1) open a pull request with the create_pull_request tool, OR (2) call post_to_chat to explain exactly where you are stuck and what you need from a human. Do not finish or go idle silently.";
|
|
10683
|
-
/** The pre-build nudge (still Planning/Open, identification/plan unfinished).
|
|
10684
|
-
* Same two-escape-route shape, aimed at landing the plan + continuing to build. */
|
|
10685
|
-
static STUCK_PROMPT_PLANNING = "You are in Auto mode and the card is still in a pre-build status \u2014 identification and/or the plan are not finished. Do ONE of these right now: (1) set the story points, save the plan with the update_task_plan tool, and continue implementing, OR (2) call post_to_chat to explain exactly where you are stuck and what you need from a human. Do not finish or go idle silently.";
|
|
10686
|
-
/**
|
|
10687
|
-
* Classify how an auto task is "stuck" after its turn ended (call sites run
|
|
10688
|
-
* post-turn, so the TUI is no longer working), or null if it isn't. Shared
|
|
10689
|
-
* guards: only auto, not rate-limited, under the nudge cap, and the agent
|
|
10690
|
-
* stayed silent this turn — if it posted to chat (explained itself / asked for
|
|
10691
|
-
* help) it's waiting on a human, not silently stuck.
|
|
10692
|
-
*
|
|
10693
|
-
* - "pr": In Progress with no open PR (build finished without shipping).
|
|
10694
|
-
* - "planning": still in a pre-build status (Planning/Open) without
|
|
10695
|
-
* (identified + saved plan) — e.g. the server-side InProgress bump failed
|
|
10696
|
-
* and the agent idled before documenting its plan. An agent that idles
|
|
10697
|
-
* here would otherwise clean-exit → its session Ends → the workspace is
|
|
10698
|
-
* reaped "agent_gone" before a plan ever landed. Nudging (and, on
|
|
10699
|
-
* exhaustion, going dormant) keeps the session Active so the pod is never
|
|
10700
|
-
* prematurely slept.
|
|
10701
|
-
*/
|
|
10702
|
-
autoStuckKind() {
|
|
10703
|
-
if (!this.mode.isAuto || this.stopped) return null;
|
|
10704
|
-
if (this.queryBridge?.wasRateLimited) return null;
|
|
10705
|
-
if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) return null;
|
|
10706
|
-
if (this.agentSpokeThisTurn) return null;
|
|
10707
|
-
if (!this.taskContext) return null;
|
|
10708
|
-
const { status, storyPointId, plan, githubPRUrl } = this.taskContext;
|
|
10709
|
-
if (status === "InProgress" && !githubPRUrl) return "pr";
|
|
10710
|
-
if (PRE_BUILD_TASK_STATUSES.has(status) && !(storyPointId !== null && !!plan?.trim())) {
|
|
10711
|
-
return "planning";
|
|
10712
|
-
}
|
|
10713
|
-
return null;
|
|
10714
|
-
}
|
|
10715
|
-
isAutoStuck() {
|
|
10716
|
-
return this.autoStuckKind() !== null;
|
|
10717
|
-
}
|
|
10718
|
-
/**
|
|
10719
|
-
* Stop driving the agent and route the core loop into dormant idle —
|
|
10720
|
-
* connected and attachable, but NOT running (so no tokens are spent while it
|
|
10721
|
-
* waits). A human reply (chat or TUI keystroke after the next wake) resumes
|
|
10722
|
-
* it. This replaces the old hard `stop()`: the auto agent must never kill its
|
|
10723
|
-
* own session, so a person can always take over.
|
|
10724
|
-
*/
|
|
10725
|
-
enterDormantForHumanTakeover(message) {
|
|
10726
|
-
this.connection.postChatMessage(message);
|
|
10727
|
-
this.completedThisTurn = true;
|
|
10728
|
-
void this.connection.sendHeartbeat();
|
|
10729
|
-
}
|
|
10730
|
-
async refreshTaskContext() {
|
|
10731
|
-
try {
|
|
10732
|
-
const ctx = await this.connection.call("getTaskContext", {
|
|
10733
|
-
sessionId: this.sessionId,
|
|
10734
|
-
includeHistory: false
|
|
10735
|
-
});
|
|
10736
|
-
this.taskContext = {
|
|
10737
|
-
status: ctx.status,
|
|
10738
|
-
plan: ctx.plan,
|
|
10739
|
-
storyPointId: ctx.storyPoints === null || ctx.storyPoints === void 0 ? null : String(ctx.storyPoints),
|
|
10740
|
-
model: ctx.model,
|
|
10741
|
-
githubPRUrl: ctx.githubPRUrl
|
|
10742
|
-
};
|
|
10743
|
-
if (this.fullContext) {
|
|
10744
|
-
this.fullContext.status = ctx.status;
|
|
10745
|
-
this.fullContext.plan = ctx.plan;
|
|
10746
|
-
this.fullContext.githubPRUrl = ctx.githubPRUrl;
|
|
10747
|
-
this.fullContext.model = ctx.model;
|
|
10748
|
-
}
|
|
10749
|
-
} catch {
|
|
10750
|
-
}
|
|
10751
|
-
}
|
|
10752
|
-
/** Prompt + chat-message label for a stuck-nudge of the given kind. */
|
|
10753
|
-
stuckNudgeContent(kind) {
|
|
10754
|
-
if (kind === "planning") {
|
|
10755
|
-
return {
|
|
10756
|
-
prompt: _SessionRunner.STUCK_PROMPT_PLANNING,
|
|
10757
|
-
situation: "still Planning with no identified plan"
|
|
10758
|
-
};
|
|
10759
|
-
}
|
|
10760
|
-
return { prompt: _SessionRunner.STUCK_PROMPT, situation: "still no PR" };
|
|
10761
|
-
}
|
|
10762
|
-
async maybeHandleStuckAuto() {
|
|
10763
|
-
await this.refreshTaskContext();
|
|
10764
|
-
if (!this.isAutoStuck()) return;
|
|
10765
|
-
while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
|
|
10766
|
-
this.prNudgeCount++;
|
|
10767
|
-
if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) {
|
|
10768
|
-
this.enterDormantForHumanTakeover(
|
|
10769
|
-
`Auto-mode: I couldn't finish or get unstuck after ${_SessionRunner.MAX_STUCK_NUDGES} attempts. Staying connected \u2014 reply here or in the terminal and I'll keep working.`
|
|
10770
|
-
);
|
|
10771
|
-
return;
|
|
10772
|
-
}
|
|
10773
|
-
const { prompt, situation } = this.stuckNudgeContent(this.autoStuckKind());
|
|
10774
|
-
const attempt = this.prNudgeCount;
|
|
10775
|
-
const chatMsg = attempt === 1 ? `Auto-nudge: ${situation} \u2014 prompting the agent to finish or explain where it's stuck...` : `Auto-nudge (attempt ${attempt}/${_SessionRunner.MAX_STUCK_NUDGES}): ${situation} \u2014 prompting the agent again...`;
|
|
10776
|
-
this.connection.postChatMessage(chatMsg);
|
|
10777
|
-
await this.setState("running");
|
|
10778
|
-
await this.callbacks.onEvent({ type: "pr_nudge", prompt });
|
|
10779
|
-
if (this.interrupted || this.stopped) return;
|
|
10780
|
-
await this.executeQuery(prompt);
|
|
10781
|
-
if (this.interrupted || this.stopped) return;
|
|
10782
|
-
await this.refreshTaskContext();
|
|
10783
|
-
if (this.agentSpokeThisTurn && !this.taskContext?.githubPRUrl) {
|
|
10784
|
-
this.enterDormantForHumanTakeover(
|
|
10785
|
-
"Auto-mode: the agent posted an update and is waiting for input. Staying connected \u2014 reply here or in the terminal to continue."
|
|
10786
|
-
);
|
|
10787
|
-
return;
|
|
10788
|
-
}
|
|
10789
|
-
}
|
|
10790
|
-
}
|
|
10791
10606
|
// ── Context & bridge construction ────────────────────────────────
|
|
10792
10607
|
// oxlint-disable-next-line complexity
|
|
10793
10608
|
buildFullContext(ctx) {
|
|
@@ -10844,10 +10659,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10844
10659
|
return this.callbacks.onStatusChange(status);
|
|
10845
10660
|
},
|
|
10846
10661
|
onEvent: (event) => {
|
|
10847
|
-
const evt = event;
|
|
10848
|
-
if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
|
|
10849
|
-
this.agentSpokeThisTurn = true;
|
|
10850
|
-
}
|
|
10851
10662
|
if (event.type === "completed") {
|
|
10852
10663
|
this.completedThisTurn = true;
|
|
10853
10664
|
void this.connection.sendHeartbeat();
|
|
@@ -11197,4 +11008,4 @@ export {
|
|
|
11197
11008
|
runStartCommand,
|
|
11198
11009
|
unshallowRepo
|
|
11199
11010
|
};
|
|
11200
|
-
//# sourceMappingURL=chunk-
|
|
11011
|
+
//# sourceMappingURL=chunk-EBZRRNO5.js.map
|