@rallycry/conveyor-agent 10.6.0 → 10.7.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-5FRDOFI4.js → chunk-TIEFSYIY.js} +139 -287
- package/dist/chunk-TIEFSYIY.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-5FRDOFI4.js.map +0 -1
|
@@ -2308,6 +2308,9 @@ var SpawnTaskSessionRequestSchema = z.object({
|
|
|
2308
2308
|
taskId: z.string(),
|
|
2309
2309
|
kind: z.enum(["tui", "shell"])
|
|
2310
2310
|
});
|
|
2311
|
+
var SpawnTaskReviewRequestSchema = z.object({
|
|
2312
|
+
taskId: z.string()
|
|
2313
|
+
});
|
|
2311
2314
|
var ReportSessionSpawnFailureRequestSchema = z.object({
|
|
2312
2315
|
sessionId: z.string(),
|
|
2313
2316
|
spawnedSessionId: z.string(),
|
|
@@ -2499,6 +2502,9 @@ var ListMyLiveSessionsRequestSchema = z2.object({
|
|
|
2499
2502
|
/** Admin-only: list another member's sessions instead of the caller's. */
|
|
2500
2503
|
targetUserId: z2.string().optional()
|
|
2501
2504
|
});
|
|
2505
|
+
var ListActiveSessionUsersRequestSchema = z2.object({
|
|
2506
|
+
projectId: z2.string()
|
|
2507
|
+
});
|
|
2502
2508
|
var GetProjectAvailableTuisRequestSchema = z2.object({
|
|
2503
2509
|
projectId: z2.string()
|
|
2504
2510
|
});
|
|
@@ -3024,13 +3030,12 @@ var ClaudeCodeHarness = class {
|
|
|
3024
3030
|
}
|
|
3025
3031
|
createMcpServer(config) {
|
|
3026
3032
|
const sdkTools = config.tools.map(
|
|
3027
|
-
(t) => tool(
|
|
3028
|
-
t.
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
t.
|
|
3032
|
-
|
|
3033
|
-
)
|
|
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
|
+
})
|
|
3034
3039
|
);
|
|
3035
3040
|
return createSdkMcpServer({ name: config.name, tools: sdkTools });
|
|
3036
3041
|
}
|
|
@@ -3858,7 +3863,16 @@ var PtyToolServer = class {
|
|
|
3858
3863
|
const inputSchema = tool2.strict ? z3.strictObject(tool2.schema) : tool2.schema;
|
|
3859
3864
|
register(
|
|
3860
3865
|
tool2.name,
|
|
3861
|
-
{
|
|
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
|
+
},
|
|
3862
3876
|
(args) => tool2.handler(args)
|
|
3863
3877
|
);
|
|
3864
3878
|
}
|
|
@@ -4116,18 +4130,48 @@ async function readRaw(path4) {
|
|
|
4116
4130
|
return null;
|
|
4117
4131
|
}
|
|
4118
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
|
+
}
|
|
4119
4152
|
async function ensureClaudeCredentials(env = process.env) {
|
|
4153
|
+
const isCloud = isConveyorCloudEnv(env);
|
|
4154
|
+
const token = env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
4155
|
+
if (isCloud && token) {
|
|
4156
|
+
await sanitizeApprovedApiKeys(token);
|
|
4157
|
+
}
|
|
4120
4158
|
try {
|
|
4121
4159
|
const path4 = claudeCredentialsPath();
|
|
4122
4160
|
const plan = planCredentialsWrite({
|
|
4123
|
-
isCloud
|
|
4124
|
-
token
|
|
4161
|
+
isCloud,
|
|
4162
|
+
token,
|
|
4125
4163
|
existingRaw: await readRaw(path4),
|
|
4126
4164
|
now: Date.now()
|
|
4127
4165
|
});
|
|
4128
4166
|
if (plan.action === "skip") return;
|
|
4129
4167
|
await mkdir2(claudeConfigHome(), { recursive: true });
|
|
4130
|
-
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
|
+
}
|
|
4131
4175
|
await chmod2(path4, 384).catch(() => {
|
|
4132
4176
|
});
|
|
4133
4177
|
} catch (err) {
|
|
@@ -4136,6 +4180,41 @@ async function ensureClaudeCredentials(env = process.env) {
|
|
|
4136
4180
|
`);
|
|
4137
4181
|
}
|
|
4138
4182
|
}
|
|
4183
|
+
function apiKeyApprovalSuffix(token) {
|
|
4184
|
+
return token.slice(-20);
|
|
4185
|
+
}
|
|
4186
|
+
function planApprovedApiKeyCleanup(existingRaw, oauthToken) {
|
|
4187
|
+
if (!oauthToken) return null;
|
|
4188
|
+
const config = parseClaudeJson(existingRaw);
|
|
4189
|
+
const responses = config.customApiKeyResponses;
|
|
4190
|
+
if (typeof responses !== "object" || responses === null || Array.isArray(responses)) {
|
|
4191
|
+
return null;
|
|
4192
|
+
}
|
|
4193
|
+
const record = responses;
|
|
4194
|
+
const approved = record.approved;
|
|
4195
|
+
if (!Array.isArray(approved)) return null;
|
|
4196
|
+
const suffix = apiKeyApprovalSuffix(oauthToken);
|
|
4197
|
+
const filtered = approved.filter((entry) => entry !== suffix);
|
|
4198
|
+
if (filtered.length === approved.length) return null;
|
|
4199
|
+
record.approved = filtered;
|
|
4200
|
+
return JSON.stringify(config);
|
|
4201
|
+
}
|
|
4202
|
+
async function sanitizeApprovedApiKeys(oauthToken) {
|
|
4203
|
+
try {
|
|
4204
|
+
const path4 = claudeJsonPath();
|
|
4205
|
+
const cleaned = planApprovedApiKeyCleanup(await readRaw(path4), oauthToken);
|
|
4206
|
+
if (cleaned === null) return;
|
|
4207
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path4), cleaned);
|
|
4208
|
+
process.stderr.write(
|
|
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
|
+
`
|
|
4211
|
+
);
|
|
4212
|
+
} catch (err) {
|
|
4213
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4214
|
+
process.stderr.write(`[conveyor-agent] claude approved-key sanitize failed: ${message}
|
|
4215
|
+
`);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4139
4218
|
function claudeJsonPath() {
|
|
4140
4219
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4141
4220
|
return configDir ? join3(configDir, ".claude.json") : join3(homedir2(), ".claude.json");
|
|
@@ -4187,16 +4266,16 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
|
|
|
4187
4266
|
const path4 = claudeJsonPath();
|
|
4188
4267
|
const contents = planClaudeJsonSeed(await readRaw(path4), trustCwd);
|
|
4189
4268
|
if (contents === null) return;
|
|
4190
|
-
await
|
|
4191
|
-
|
|
4192
|
-
if (verify === contents) {
|
|
4269
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path4), contents);
|
|
4270
|
+
if (verified) {
|
|
4193
4271
|
process.stderr.write(
|
|
4194
4272
|
`[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
|
|
4195
4273
|
`
|
|
4196
4274
|
);
|
|
4197
4275
|
} else {
|
|
4276
|
+
const verify = await readRaw(path4);
|
|
4198
4277
|
process.stderr.write(
|
|
4199
|
-
`[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
|
|
4200
4279
|
`
|
|
4201
4280
|
);
|
|
4202
4281
|
}
|
|
@@ -4255,7 +4334,10 @@ function resolvePlanDialogTiming() {
|
|
|
4255
4334
|
return {
|
|
4256
4335
|
firstPressMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS", PLAN_DIALOG_FIRST_PRESS_MS),
|
|
4257
4336
|
intervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS", PLAN_DIALOG_INTERVAL_MS),
|
|
4258
|
-
slowIntervalMs: envMs(
|
|
4337
|
+
slowIntervalMs: envMs(
|
|
4338
|
+
"CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS",
|
|
4339
|
+
PLAN_DIALOG_SLOW_INTERVAL_MS
|
|
4340
|
+
),
|
|
4259
4341
|
fastWindowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS", PLAN_DIALOG_FAST_WINDOW_MS),
|
|
4260
4342
|
windowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS", PLAN_DIALOG_WINDOW_MS)
|
|
4261
4343
|
};
|
|
@@ -4289,6 +4371,9 @@ function inheritedEnv(socketPath) {
|
|
|
4289
4371
|
for (const [key, value] of Object.entries(process.env)) {
|
|
4290
4372
|
if (typeof value === "string") env[key] = value;
|
|
4291
4373
|
}
|
|
4374
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
4375
|
+
delete env.ANTHROPIC_API_KEY;
|
|
4376
|
+
}
|
|
4292
4377
|
if (socketPath) {
|
|
4293
4378
|
env.CONVEYOR_HOOK_SOCKET = socketPath;
|
|
4294
4379
|
}
|
|
@@ -6136,7 +6221,7 @@ Environment (fully ready \u2014 do NOT verify or set up):`,
|
|
|
6136
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\`.`,
|
|
6137
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.`,
|
|
6138
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.`,
|
|
6139
|
-
`-
|
|
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.`,
|
|
6140
6225
|
`- The \`gh\` CLI may not be installed \u2014 use the mcp__conveyor__* tools for PR and task state.`,
|
|
6141
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.`,
|
|
6142
6227
|
`
|
|
@@ -6389,9 +6474,10 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
|
|
|
6389
6474
|
}
|
|
6390
6475
|
if (isPm) {
|
|
6391
6476
|
return [
|
|
6392
|
-
`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.`,
|
|
6393
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.`,
|
|
6394
|
-
`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.`,
|
|
6395
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.`
|
|
6396
6482
|
];
|
|
6397
6483
|
}
|
|
@@ -7804,6 +7890,28 @@ function getModeTools(agentMode, connection, config, context) {
|
|
|
7804
7890
|
return config.mode === "pm" ? buildPmTools(connection, { includePackTools: false }) : [];
|
|
7805
7891
|
}
|
|
7806
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
|
+
}
|
|
7807
7915
|
function buildConveyorTools(connection, config, context, agentMode) {
|
|
7808
7916
|
const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
|
|
7809
7917
|
const commonTools = buildCommonTools(connection, config);
|
|
@@ -7811,7 +7919,13 @@ function buildConveyorTools(connection, config, context, agentMode) {
|
|
|
7811
7919
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" ? buildDiscoveryTools(connection) : [];
|
|
7812
7920
|
const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
|
|
7813
7921
|
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
7814
|
-
return [
|
|
7922
|
+
return withAlwaysLoad([
|
|
7923
|
+
...commonTools,
|
|
7924
|
+
...modeTools,
|
|
7925
|
+
...discoveryTools,
|
|
7926
|
+
...codeReviewTools,
|
|
7927
|
+
...emergencyTools
|
|
7928
|
+
]);
|
|
7815
7929
|
}
|
|
7816
7930
|
function createConveyorMcpServer(harness, connection, config, context, agentMode) {
|
|
7817
7931
|
return harness.createMcpServer({
|
|
@@ -8417,7 +8531,6 @@ async function handleExitPlanMode(host, input) {
|
|
|
8417
8531
|
if (gate) return gate;
|
|
8418
8532
|
if (host.agentMode === "discovery") {
|
|
8419
8533
|
host.hasExitedPlanMode = true;
|
|
8420
|
-
host.discoveryCompleted = true;
|
|
8421
8534
|
try {
|
|
8422
8535
|
await host.connection.triggerIdentification();
|
|
8423
8536
|
} catch (triggerErr) {
|
|
@@ -8426,13 +8539,8 @@ async function handleExitPlanMode(host, input) {
|
|
|
8426
8539
|
);
|
|
8427
8540
|
}
|
|
8428
8541
|
await host.connection.postChatMessageAwait(
|
|
8429
|
-
"
|
|
8542
|
+
"Plan posted \u2014 switching to auto mode. I'll start building now; send a message any time to steer or add follow-ups."
|
|
8430
8543
|
);
|
|
8431
|
-
if (host.harnessKind === "pty") {
|
|
8432
|
-
setTimeout(() => host.requestStop(), 250);
|
|
8433
|
-
} else {
|
|
8434
|
-
host.requestStop();
|
|
8435
|
-
}
|
|
8436
8544
|
return { behavior: "allow", updatedInput: input };
|
|
8437
8545
|
}
|
|
8438
8546
|
try {
|
|
@@ -8477,19 +8585,6 @@ async function handleAskUserQuestion(host, input) {
|
|
|
8477
8585
|
}
|
|
8478
8586
|
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
8479
8587
|
}
|
|
8480
|
-
var EXPLORATION_TOOLS = /* @__PURE__ */ new Set(["Read", "Grep", "Glob"]);
|
|
8481
|
-
function trackExploration(tracker, toolName, input) {
|
|
8482
|
-
if (!EXPLORATION_TOOLS.has(toolName)) return null;
|
|
8483
|
-
if (toolName === "Read") {
|
|
8484
|
-
const filePath = String(input.file_path ?? "");
|
|
8485
|
-
if (filePath) return tracker.recordFileRead(filePath);
|
|
8486
|
-
}
|
|
8487
|
-
if (toolName === "Grep" || toolName === "Glob") {
|
|
8488
|
-
const pattern = String(input.pattern ?? "");
|
|
8489
|
-
if (pattern) return tracker.recordSearch(pattern);
|
|
8490
|
-
}
|
|
8491
|
-
return null;
|
|
8492
|
-
}
|
|
8493
8588
|
var DENIAL_WARNING_THRESHOLD = 3;
|
|
8494
8589
|
var DENIAL_FORCE_STOP_THRESHOLD = 8;
|
|
8495
8590
|
function handleDenialEscalation(host, consecutiveDenials) {
|
|
@@ -8535,12 +8630,6 @@ function buildCanUseTool(host) {
|
|
|
8535
8630
|
return await handleAskUserQuestion(host, input);
|
|
8536
8631
|
}
|
|
8537
8632
|
const result = resolveToolAccess(host, toolName, input);
|
|
8538
|
-
if (result.behavior === "allow" && host.explorationTracker && !host.hasExitedPlanMode) {
|
|
8539
|
-
const signal = trackExploration(host.explorationTracker, toolName, input);
|
|
8540
|
-
if (signal) {
|
|
8541
|
-
host.connection.postChatMessage(signal.message);
|
|
8542
|
-
}
|
|
8543
|
-
}
|
|
8544
8633
|
if (result.behavior === "deny") {
|
|
8545
8634
|
consecutiveDenials++;
|
|
8546
8635
|
handleDenialEscalation(host, consecutiveDenials);
|
|
@@ -8717,7 +8806,7 @@ function resolvePromptDelivery(inputs) {
|
|
|
8717
8806
|
if (inputs.runnerMode === "code-review") return "submit";
|
|
8718
8807
|
if (inputs.isFollowUp || inputs.hasExistingSession) return "submit";
|
|
8719
8808
|
if (inputs.isAuto || inputs.agentMode === "auto") return "submit";
|
|
8720
|
-
if (inputs.agentMode !== "
|
|
8809
|
+
if (inputs.agentMode !== "help") return "submit";
|
|
8721
8810
|
return "prefill";
|
|
8722
8811
|
}
|
|
8723
8812
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
@@ -9195,108 +9284,6 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
9195
9284
|
}
|
|
9196
9285
|
}
|
|
9197
9286
|
|
|
9198
|
-
// src/execution/exploration-tracker.ts
|
|
9199
|
-
var FILE_REREAD_NUDGE_L1 = 3;
|
|
9200
|
-
var FILE_REREAD_NUDGE_L2 = 5;
|
|
9201
|
-
var SEARCH_REPEAT_NUDGE_L1 = 3;
|
|
9202
|
-
var SEARCH_REPEAT_NUDGE_L2 = 5;
|
|
9203
|
-
var RATIO_MIN_READS_L1 = 10;
|
|
9204
|
-
var RATIO_THRESHOLD_L1 = 0.4;
|
|
9205
|
-
var RATIO_MIN_READS_L2 = 15;
|
|
9206
|
-
var RATIO_THRESHOLD_L2 = 0.3;
|
|
9207
|
-
function normalizePath(filePath) {
|
|
9208
|
-
let normalized = filePath.replace(/^\.\//, "");
|
|
9209
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
9210
|
-
return normalized;
|
|
9211
|
-
}
|
|
9212
|
-
var ExplorationTracker = class {
|
|
9213
|
-
fileReadCounts = /* @__PURE__ */ new Map();
|
|
9214
|
-
searchCounts = /* @__PURE__ */ new Map();
|
|
9215
|
-
totalReads = 0;
|
|
9216
|
-
highestNudgeEmitted = 0;
|
|
9217
|
-
complexityMultiplier;
|
|
9218
|
-
constructor(isParentTask) {
|
|
9219
|
-
this.complexityMultiplier = isParentTask ? 1.5 : 1;
|
|
9220
|
-
}
|
|
9221
|
-
/** Record a file read and return a staleness signal if thresholds are crossed. */
|
|
9222
|
-
recordFileRead(filePath) {
|
|
9223
|
-
const normalized = normalizePath(filePath);
|
|
9224
|
-
const count = (this.fileReadCounts.get(normalized) ?? 0) + 1;
|
|
9225
|
-
this.fileReadCounts.set(normalized, count);
|
|
9226
|
-
this.totalReads++;
|
|
9227
|
-
return this.checkFileRereadSignal(normalized, count) ?? this.checkExplorationRatio();
|
|
9228
|
-
}
|
|
9229
|
-
/** Record a search pattern and return a staleness signal if thresholds are crossed. */
|
|
9230
|
-
recordSearch(pattern) {
|
|
9231
|
-
const count = (this.searchCounts.get(pattern) ?? 0) + 1;
|
|
9232
|
-
this.searchCounts.set(pattern, count);
|
|
9233
|
-
return this.checkSearchRepeatSignal(pattern, count);
|
|
9234
|
-
}
|
|
9235
|
-
scaledThreshold(base) {
|
|
9236
|
-
return Math.ceil(base * this.complexityMultiplier);
|
|
9237
|
-
}
|
|
9238
|
-
checkFileRereadSignal(filePath, count) {
|
|
9239
|
-
const l2 = this.scaledThreshold(FILE_REREAD_NUDGE_L2);
|
|
9240
|
-
const l1 = this.scaledThreshold(FILE_REREAD_NUDGE_L1);
|
|
9241
|
-
if (count >= l2 && this.highestNudgeEmitted < 2) {
|
|
9242
|
-
this.highestNudgeEmitted = 2;
|
|
9243
|
-
return {
|
|
9244
|
-
level: 2,
|
|
9245
|
-
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.`
|
|
9246
|
-
};
|
|
9247
|
-
}
|
|
9248
|
-
if (count >= l1 && this.highestNudgeEmitted < 1) {
|
|
9249
|
-
this.highestNudgeEmitted = 1;
|
|
9250
|
-
return {
|
|
9251
|
-
level: 1,
|
|
9252
|
-
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.`
|
|
9253
|
-
};
|
|
9254
|
-
}
|
|
9255
|
-
return null;
|
|
9256
|
-
}
|
|
9257
|
-
checkSearchRepeatSignal(pattern, count) {
|
|
9258
|
-
const l2 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L2);
|
|
9259
|
-
const l1 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L1);
|
|
9260
|
-
const displayPattern = pattern.length > 60 ? pattern.slice(0, 57) + "..." : pattern;
|
|
9261
|
-
if (count >= l2 && this.highestNudgeEmitted < 2) {
|
|
9262
|
-
this.highestNudgeEmitted = 2;
|
|
9263
|
-
return {
|
|
9264
|
-
level: 2,
|
|
9265
|
-
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.`
|
|
9266
|
-
};
|
|
9267
|
-
}
|
|
9268
|
-
if (count >= l1 && this.highestNudgeEmitted < 1) {
|
|
9269
|
-
this.highestNudgeEmitted = 1;
|
|
9270
|
-
return {
|
|
9271
|
-
level: 1,
|
|
9272
|
-
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.`
|
|
9273
|
-
};
|
|
9274
|
-
}
|
|
9275
|
-
return null;
|
|
9276
|
-
}
|
|
9277
|
-
checkExplorationRatio() {
|
|
9278
|
-
const uniqueReads = this.fileReadCounts.size;
|
|
9279
|
-
const ratio = this.totalReads > 0 ? uniqueReads / this.totalReads : 1;
|
|
9280
|
-
const minReadsL2 = this.scaledThreshold(RATIO_MIN_READS_L2);
|
|
9281
|
-
if (this.totalReads >= minReadsL2 && ratio < RATIO_THRESHOLD_L2 && this.highestNudgeEmitted < 2) {
|
|
9282
|
-
this.highestNudgeEmitted = 2;
|
|
9283
|
-
return {
|
|
9284
|
-
level: 2,
|
|
9285
|
-
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.`
|
|
9286
|
-
};
|
|
9287
|
-
}
|
|
9288
|
-
const minReadsL1 = this.scaledThreshold(RATIO_MIN_READS_L1);
|
|
9289
|
-
if (this.totalReads >= minReadsL1 && ratio < RATIO_THRESHOLD_L1 && this.highestNudgeEmitted < 1) {
|
|
9290
|
-
this.highestNudgeEmitted = 1;
|
|
9291
|
-
return {
|
|
9292
|
-
level: 1,
|
|
9293
|
-
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.`
|
|
9294
|
-
};
|
|
9295
|
-
}
|
|
9296
|
-
return null;
|
|
9297
|
-
}
|
|
9298
|
-
};
|
|
9299
|
-
|
|
9300
9287
|
// src/runner/query-bridge.ts
|
|
9301
9288
|
var logger3 = createServiceLogger("QueryBridge");
|
|
9302
9289
|
function resolveHarnessKind() {
|
|
@@ -9480,7 +9467,6 @@ var QueryBridge = class {
|
|
|
9480
9467
|
harness: this.harness,
|
|
9481
9468
|
harnessKind: this.harnessKind,
|
|
9482
9469
|
setupLog: [],
|
|
9483
|
-
explorationTracker: bridge.mode.effectiveMode === "discovery" || bridge.mode.effectiveMode === "auto" && !bridge.mode.hasExitedPlanMode ? new ExplorationTracker(bridge._isParentTask) : null,
|
|
9484
9470
|
sessionIds: this.sessionIds,
|
|
9485
9471
|
pendingToolOutputs: this.pendingToolOutputs,
|
|
9486
9472
|
// Live getters/setters delegating to ModeController + bridge state
|
|
@@ -9969,9 +9955,7 @@ function isHeavyGateActive() {
|
|
|
9969
9955
|
}
|
|
9970
9956
|
|
|
9971
9957
|
// src/runner/session-runner.ts
|
|
9972
|
-
|
|
9973
|
-
return typeof name === "string" && (name === "post_to_chat" || name.endsWith("__post_to_chat"));
|
|
9974
|
-
}
|
|
9958
|
+
var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery"]);
|
|
9975
9959
|
var SessionRunner = class _SessionRunner {
|
|
9976
9960
|
connection;
|
|
9977
9961
|
mode;
|
|
@@ -9996,13 +9980,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
9996
9980
|
queryBridge = null;
|
|
9997
9981
|
inputResolver = null;
|
|
9998
9982
|
pendingMessages = [];
|
|
9999
|
-
prNudgeCount = 0;
|
|
10000
|
-
/** Set when the agent posts a substantive message to the task chat (the
|
|
10001
|
-
* `post_to_chat` tool) during the current turn; reset at the start of every
|
|
10002
|
-
* query. Read by the stuck-detection: an auto agent that finished without a
|
|
10003
|
-
* PR but DID explain itself / ask for help in chat is waiting on a human,
|
|
10004
|
-
* not silently stuck, so it is left attached rather than nudged. */
|
|
10005
|
-
agentSpokeThisTurn = false;
|
|
10006
9983
|
/** Guards overlapping runs of the periodic git flush. */
|
|
10007
9984
|
periodicFlushInFlight = false;
|
|
10008
9985
|
/** Consecutive flush ticks skipped because a heavy gate was running. */
|
|
@@ -10192,9 +10169,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10192
10169
|
);
|
|
10193
10170
|
this.queryBridge.isDiscoveryCompleted = false;
|
|
10194
10171
|
}
|
|
10195
|
-
if (!this.stopped && this.pendingMessages.length === 0) {
|
|
10196
|
-
await this.maybeHandleStuckAuto();
|
|
10197
|
-
}
|
|
10198
10172
|
if (!this.stopped) {
|
|
10199
10173
|
process.stderr.write(
|
|
10200
10174
|
`[conveyor-agent] Listening for messages (mode: ${this.mode.effectiveMode})
|
|
@@ -10270,9 +10244,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10270
10244
|
this.interrupted = false;
|
|
10271
10245
|
continue;
|
|
10272
10246
|
}
|
|
10273
|
-
if (!this.stopped && this.pendingMessages.length === 0) {
|
|
10274
|
-
await this.maybeHandleStuckAuto();
|
|
10275
|
-
}
|
|
10276
10247
|
if (!this.stopped) await this.setState("idle");
|
|
10277
10248
|
} else if (this._state === "error") {
|
|
10278
10249
|
await this.setState("idle");
|
|
@@ -10328,7 +10299,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
10328
10299
|
return true;
|
|
10329
10300
|
}
|
|
10330
10301
|
const delivery = this.pendingMessages.length > 0 ? "submit" : intrinsicDelivery;
|
|
10331
|
-
const shouldRun = effectiveMode
|
|
10302
|
+
const shouldRun = AUTO_RUN_MODES.has(effectiveMode) || delivery === "prefill";
|
|
10332
10303
|
if (!shouldRun) {
|
|
10333
10304
|
await this.setState("idle");
|
|
10334
10305
|
return false;
|
|
@@ -10463,7 +10434,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10463
10434
|
/** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
|
|
10464
10435
|
async executeQuery(followUpContent, promptDelivery) {
|
|
10465
10436
|
if (!this.fullContext || !this.queryBridge) return;
|
|
10466
|
-
this.agentSpokeThisTurn = false;
|
|
10467
10437
|
try {
|
|
10468
10438
|
await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
|
|
10469
10439
|
} catch (err) {
|
|
@@ -10485,7 +10455,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10485
10455
|
/** Run queryBridge.executePassive, swallowing abort errors from stop/softStop. */
|
|
10486
10456
|
async executePassive() {
|
|
10487
10457
|
if (!this.fullContext || !this.queryBridge) return;
|
|
10488
|
-
this.agentSpokeThisTurn = false;
|
|
10489
10458
|
try {
|
|
10490
10459
|
await this.queryBridge.executePassive(this.fullContext);
|
|
10491
10460
|
} catch (err) {
|
|
@@ -10627,119 +10596,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10627
10596
|
resolver(null);
|
|
10628
10597
|
}
|
|
10629
10598
|
}
|
|
10630
|
-
// ── Auto-mode stuck detection & nudge ───────────────────────────────
|
|
10631
|
-
static MAX_STUCK_NUDGES = 3;
|
|
10632
|
-
/** The build-phase nudge (In Progress, no PR). Offers BOTH escape routes so
|
|
10633
|
-
* the agent never just goes idle. */
|
|
10634
|
-
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.";
|
|
10635
|
-
/** The pre-build nudge (still Planning/Open, identification/plan unfinished).
|
|
10636
|
-
* Same two-escape-route shape, aimed at landing the plan + continuing to build. */
|
|
10637
|
-
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.";
|
|
10638
|
-
/**
|
|
10639
|
-
* Classify how an auto task is "stuck" after its turn ended (call sites run
|
|
10640
|
-
* post-turn, so the TUI is no longer working), or null if it isn't. Shared
|
|
10641
|
-
* guards: only auto, not rate-limited, under the nudge cap, and the agent
|
|
10642
|
-
* stayed silent this turn — if it posted to chat (explained itself / asked for
|
|
10643
|
-
* help) it's waiting on a human, not silently stuck.
|
|
10644
|
-
*
|
|
10645
|
-
* - "pr": In Progress with no open PR (build finished without shipping).
|
|
10646
|
-
* - "planning": still in a pre-build status (Planning/Open) without
|
|
10647
|
-
* (identified + saved plan) — e.g. the server-side InProgress bump failed
|
|
10648
|
-
* and the agent idled before documenting its plan. An agent that idles
|
|
10649
|
-
* here would otherwise clean-exit → its session Ends → the workspace is
|
|
10650
|
-
* reaped "agent_gone" before a plan ever landed. Nudging (and, on
|
|
10651
|
-
* exhaustion, going dormant) keeps the session Active so the pod is never
|
|
10652
|
-
* prematurely slept.
|
|
10653
|
-
*/
|
|
10654
|
-
autoStuckKind() {
|
|
10655
|
-
if (!this.mode.isAuto || this.stopped) return null;
|
|
10656
|
-
if (this.queryBridge?.wasRateLimited) return null;
|
|
10657
|
-
if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) return null;
|
|
10658
|
-
if (this.agentSpokeThisTurn) return null;
|
|
10659
|
-
if (!this.taskContext) return null;
|
|
10660
|
-
const { status, storyPointId, plan, githubPRUrl } = this.taskContext;
|
|
10661
|
-
if (status === "InProgress" && !githubPRUrl) return "pr";
|
|
10662
|
-
if (PRE_BUILD_TASK_STATUSES.has(status) && !(storyPointId !== null && !!plan?.trim())) {
|
|
10663
|
-
return "planning";
|
|
10664
|
-
}
|
|
10665
|
-
return null;
|
|
10666
|
-
}
|
|
10667
|
-
isAutoStuck() {
|
|
10668
|
-
return this.autoStuckKind() !== null;
|
|
10669
|
-
}
|
|
10670
|
-
/**
|
|
10671
|
-
* Stop driving the agent and route the core loop into dormant idle —
|
|
10672
|
-
* connected and attachable, but NOT running (so no tokens are spent while it
|
|
10673
|
-
* waits). A human reply (chat or TUI keystroke after the next wake) resumes
|
|
10674
|
-
* it. This replaces the old hard `stop()`: the auto agent must never kill its
|
|
10675
|
-
* own session, so a person can always take over.
|
|
10676
|
-
*/
|
|
10677
|
-
enterDormantForHumanTakeover(message) {
|
|
10678
|
-
this.connection.postChatMessage(message);
|
|
10679
|
-
this.completedThisTurn = true;
|
|
10680
|
-
void this.connection.sendHeartbeat();
|
|
10681
|
-
}
|
|
10682
|
-
async refreshTaskContext() {
|
|
10683
|
-
try {
|
|
10684
|
-
const ctx = await this.connection.call("getTaskContext", {
|
|
10685
|
-
sessionId: this.sessionId,
|
|
10686
|
-
includeHistory: false
|
|
10687
|
-
});
|
|
10688
|
-
this.taskContext = {
|
|
10689
|
-
status: ctx.status,
|
|
10690
|
-
plan: ctx.plan,
|
|
10691
|
-
storyPointId: ctx.storyPoints === null || ctx.storyPoints === void 0 ? null : String(ctx.storyPoints),
|
|
10692
|
-
model: ctx.model,
|
|
10693
|
-
githubPRUrl: ctx.githubPRUrl
|
|
10694
|
-
};
|
|
10695
|
-
if (this.fullContext) {
|
|
10696
|
-
this.fullContext.status = ctx.status;
|
|
10697
|
-
this.fullContext.plan = ctx.plan;
|
|
10698
|
-
this.fullContext.githubPRUrl = ctx.githubPRUrl;
|
|
10699
|
-
this.fullContext.model = ctx.model;
|
|
10700
|
-
}
|
|
10701
|
-
} catch {
|
|
10702
|
-
}
|
|
10703
|
-
}
|
|
10704
|
-
/** Prompt + chat-message label for a stuck-nudge of the given kind. */
|
|
10705
|
-
stuckNudgeContent(kind) {
|
|
10706
|
-
if (kind === "planning") {
|
|
10707
|
-
return {
|
|
10708
|
-
prompt: _SessionRunner.STUCK_PROMPT_PLANNING,
|
|
10709
|
-
situation: "still Planning with no identified plan"
|
|
10710
|
-
};
|
|
10711
|
-
}
|
|
10712
|
-
return { prompt: _SessionRunner.STUCK_PROMPT, situation: "still no PR" };
|
|
10713
|
-
}
|
|
10714
|
-
async maybeHandleStuckAuto() {
|
|
10715
|
-
await this.refreshTaskContext();
|
|
10716
|
-
if (!this.isAutoStuck()) return;
|
|
10717
|
-
while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
|
|
10718
|
-
this.prNudgeCount++;
|
|
10719
|
-
if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) {
|
|
10720
|
-
this.enterDormantForHumanTakeover(
|
|
10721
|
-
`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.`
|
|
10722
|
-
);
|
|
10723
|
-
return;
|
|
10724
|
-
}
|
|
10725
|
-
const { prompt, situation } = this.stuckNudgeContent(this.autoStuckKind());
|
|
10726
|
-
const attempt = this.prNudgeCount;
|
|
10727
|
-
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...`;
|
|
10728
|
-
this.connection.postChatMessage(chatMsg);
|
|
10729
|
-
await this.setState("running");
|
|
10730
|
-
await this.callbacks.onEvent({ type: "pr_nudge", prompt });
|
|
10731
|
-
if (this.interrupted || this.stopped) return;
|
|
10732
|
-
await this.executeQuery(prompt);
|
|
10733
|
-
if (this.interrupted || this.stopped) return;
|
|
10734
|
-
await this.refreshTaskContext();
|
|
10735
|
-
if (this.agentSpokeThisTurn && !this.taskContext?.githubPRUrl) {
|
|
10736
|
-
this.enterDormantForHumanTakeover(
|
|
10737
|
-
"Auto-mode: the agent posted an update and is waiting for input. Staying connected \u2014 reply here or in the terminal to continue."
|
|
10738
|
-
);
|
|
10739
|
-
return;
|
|
10740
|
-
}
|
|
10741
|
-
}
|
|
10742
|
-
}
|
|
10743
10599
|
// ── Context & bridge construction ────────────────────────────────
|
|
10744
10600
|
// oxlint-disable-next-line complexity
|
|
10745
10601
|
buildFullContext(ctx) {
|
|
@@ -10796,10 +10652,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
10796
10652
|
return this.callbacks.onStatusChange(status);
|
|
10797
10653
|
},
|
|
10798
10654
|
onEvent: (event) => {
|
|
10799
|
-
const evt = event;
|
|
10800
|
-
if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
|
|
10801
|
-
this.agentSpokeThisTurn = true;
|
|
10802
|
-
}
|
|
10803
10655
|
if (event.type === "completed") {
|
|
10804
10656
|
this.completedThisTurn = true;
|
|
10805
10657
|
void this.connection.sendHeartbeat();
|
|
@@ -11149,4 +11001,4 @@ export {
|
|
|
11149
11001
|
runStartCommand,
|
|
11150
11002
|
unshallowRepo
|
|
11151
11003
|
};
|
|
11152
|
-
//# sourceMappingURL=chunk-
|
|
11004
|
+
//# sourceMappingURL=chunk-TIEFSYIY.js.map
|