@rallycry/conveyor-agent 3.9.0 → 4.0.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-6Z262BNH.js → chunk-Y4TAVPZV.js} +666 -103
- package/dist/chunk-Y4TAVPZV.js.map +1 -0
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +33 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6Z262BNH.js.map +0 -1
|
@@ -12,6 +12,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
12
12
|
chatMessageCallback = null;
|
|
13
13
|
stopCallback = null;
|
|
14
14
|
modeChangeCallback = null;
|
|
15
|
+
runStartCommandCallback = null;
|
|
15
16
|
pendingQuestionResolvers = /* @__PURE__ */ new Map();
|
|
16
17
|
constructor(config) {
|
|
17
18
|
this.config = config;
|
|
@@ -61,6 +62,11 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
61
62
|
this.earlyModeChanges.push(data);
|
|
62
63
|
}
|
|
63
64
|
});
|
|
65
|
+
this.socket.on("agentRunner:runStartCommand", () => {
|
|
66
|
+
if (this.runStartCommandCallback) {
|
|
67
|
+
this.runStartCommandCallback();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
64
70
|
this.socket.on("connect", () => {
|
|
65
71
|
if (!settled) {
|
|
66
72
|
settled = true;
|
|
@@ -226,9 +232,12 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
226
232
|
}
|
|
227
233
|
this.earlyModeChanges = [];
|
|
228
234
|
}
|
|
229
|
-
|
|
235
|
+
onRunStartCommand(callback) {
|
|
236
|
+
this.runStartCommandCallback = callback;
|
|
237
|
+
}
|
|
238
|
+
emitModeChanged(agentMode) {
|
|
230
239
|
if (!this.socket) return;
|
|
231
|
-
this.socket.emit("agentRunner:modeChanged", {
|
|
240
|
+
this.socket.emit("agentRunner:modeChanged", { agentMode });
|
|
232
241
|
}
|
|
233
242
|
trackSpending(params) {
|
|
234
243
|
if (!this.socket) throw new Error("Not connected");
|
|
@@ -347,6 +356,66 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
347
356
|
);
|
|
348
357
|
});
|
|
349
358
|
}
|
|
359
|
+
updateTaskProperties(data) {
|
|
360
|
+
if (!this.socket) throw new Error("Not connected");
|
|
361
|
+
this.socket.emit("agentRunner:updateTaskProperties", data);
|
|
362
|
+
}
|
|
363
|
+
listIcons() {
|
|
364
|
+
const socket = this.socket;
|
|
365
|
+
if (!socket) throw new Error("Not connected");
|
|
366
|
+
return new Promise((resolve2, reject) => {
|
|
367
|
+
socket.emit("agentRunner:listIcons", {}, (response) => {
|
|
368
|
+
if (response.success && response.data) resolve2(response.data);
|
|
369
|
+
else reject(new Error(response.error ?? "Failed to list icons"));
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
generateTaskIcon(prompt, aspectRatio) {
|
|
374
|
+
const socket = this.socket;
|
|
375
|
+
if (!socket) throw new Error("Not connected");
|
|
376
|
+
return new Promise((resolve2, reject) => {
|
|
377
|
+
socket.emit(
|
|
378
|
+
"agentRunner:generateTaskIcon",
|
|
379
|
+
{ prompt, aspectRatio },
|
|
380
|
+
(response) => {
|
|
381
|
+
if (response.success && response.data) resolve2(response.data);
|
|
382
|
+
else reject(new Error(response.error ?? "Failed to generate icon"));
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
getTaskProperties() {
|
|
388
|
+
const socket = this.socket;
|
|
389
|
+
if (!socket) throw new Error("Not connected");
|
|
390
|
+
return new Promise((resolve2, reject) => {
|
|
391
|
+
socket.emit(
|
|
392
|
+
"agentRunner:getTaskProperties",
|
|
393
|
+
{},
|
|
394
|
+
(response) => {
|
|
395
|
+
if (response.success && response.data) resolve2(response.data);
|
|
396
|
+
else reject(new Error(response.error ?? "Failed to get task properties"));
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
triggerIdentification() {
|
|
402
|
+
const socket = this.socket;
|
|
403
|
+
if (!socket) throw new Error("Not connected");
|
|
404
|
+
return new Promise((resolve2, reject) => {
|
|
405
|
+
socket.emit(
|
|
406
|
+
"agentRunner:triggerIdentification",
|
|
407
|
+
{},
|
|
408
|
+
(response) => {
|
|
409
|
+
if (response.success && response.data) resolve2(response.data);
|
|
410
|
+
else reject(new Error(response.error ?? "Identification failed"));
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
emitModeTransition(payload) {
|
|
416
|
+
if (!this.socket) return;
|
|
417
|
+
this.socket.emit("agentRunner:modeTransition", payload);
|
|
418
|
+
}
|
|
350
419
|
disconnect() {
|
|
351
420
|
this.flushEvents();
|
|
352
421
|
this.socket?.disconnect();
|
|
@@ -799,6 +868,13 @@ async function processEvents(events, context, host) {
|
|
|
799
868
|
const turnToolCalls = [];
|
|
800
869
|
for await (const event of events) {
|
|
801
870
|
if (host.isStopped()) break;
|
|
871
|
+
if (host.pendingModeRestart) {
|
|
872
|
+
host.pendingModeRestart = false;
|
|
873
|
+
if (isTyping) {
|
|
874
|
+
host.connection.sendTypingStop();
|
|
875
|
+
}
|
|
876
|
+
return { retriable: false, modeRestart: true };
|
|
877
|
+
}
|
|
802
878
|
switch (event.type) {
|
|
803
879
|
case "system": {
|
|
804
880
|
const systemEvent = event;
|
|
@@ -1007,12 +1083,22 @@ ${context.plan}`);
|
|
|
1007
1083
|
}
|
|
1008
1084
|
return parts;
|
|
1009
1085
|
}
|
|
1010
|
-
function buildInstructions(mode, context, scenario) {
|
|
1086
|
+
function buildInstructions(mode, context, scenario, agentMode) {
|
|
1011
1087
|
const parts = [`
|
|
1012
1088
|
## Instructions`];
|
|
1013
1089
|
const isPm = mode === "pm";
|
|
1090
|
+
const isAutoMode = agentMode === "auto";
|
|
1014
1091
|
if (scenario === "fresh") {
|
|
1015
|
-
if (
|
|
1092
|
+
if (isAutoMode && isPm) {
|
|
1093
|
+
parts.push(
|
|
1094
|
+
`You are operating autonomously. Begin planning immediately.`,
|
|
1095
|
+
`1. Explore the codebase to understand the architecture and relevant files`,
|
|
1096
|
+
`2. Draft a clear implementation plan and save it with update_task`,
|
|
1097
|
+
`3. Set story points (set_story_points), tags (set_task_tags), and title (set_task_title)`,
|
|
1098
|
+
`4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
|
|
1099
|
+
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
1100
|
+
);
|
|
1101
|
+
} else if (isPm && context.isParentTask) {
|
|
1016
1102
|
parts.push(
|
|
1017
1103
|
`You are the project manager for this task and its subtasks.`,
|
|
1018
1104
|
`Use list_subtasks to review the current state of child tasks.`,
|
|
@@ -1091,6 +1177,112 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
1091
1177
|
}
|
|
1092
1178
|
return parts;
|
|
1093
1179
|
}
|
|
1180
|
+
function buildPropertyInstructions(context) {
|
|
1181
|
+
const parts = [];
|
|
1182
|
+
parts.push(
|
|
1183
|
+
``,
|
|
1184
|
+
`### Proactive Property Management`,
|
|
1185
|
+
`As you plan this task, proactively fill in task properties when you have enough context:`,
|
|
1186
|
+
`- Once you understand the scope, use set_story_points to assign a value`,
|
|
1187
|
+
`- Use set_task_tags to categorize the work`,
|
|
1188
|
+
`- For icons: FIRST call list_icons to check for existing matches. Use set_task_icon if one fits.`,
|
|
1189
|
+
` Only call generate_task_icon if no existing icon is a good fit.`,
|
|
1190
|
+
`- Use set_task_title if the current title doesn't accurately reflect the plan`,
|
|
1191
|
+
``,
|
|
1192
|
+
`Don't wait for the user to ask \u2014 fill these in naturally as the plan takes shape.`,
|
|
1193
|
+
`If the user adjusts the plan significantly, update the properties to match.`
|
|
1194
|
+
);
|
|
1195
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
1196
|
+
parts.push(``, `Available story point tiers:`);
|
|
1197
|
+
for (const sp of context.storyPoints) {
|
|
1198
|
+
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
1199
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (context.projectTags && context.projectTags.length > 0) {
|
|
1203
|
+
parts.push(``, `Available project tags:`);
|
|
1204
|
+
for (const tag of context.projectTags) {
|
|
1205
|
+
parts.push(`- ID: "${tag.id}", Name: "${tag.name}"`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return parts;
|
|
1209
|
+
}
|
|
1210
|
+
function buildModePrompt(agentMode, context) {
|
|
1211
|
+
switch (agentMode) {
|
|
1212
|
+
case "discovery": {
|
|
1213
|
+
const parts = [
|
|
1214
|
+
`
|
|
1215
|
+
## Mode: Discovery`,
|
|
1216
|
+
`You are in Discovery mode \u2014 helping plan and scope this task.`,
|
|
1217
|
+
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
1218
|
+
`- You have MCP tools: update_task (title, description, plan, tags, SP, icon)`,
|
|
1219
|
+
`- You can create and manage subtasks`,
|
|
1220
|
+
`- You cannot write code or edit files (except .claude/plans/)`,
|
|
1221
|
+
`- Goal: collaborate with the user to create a clear plan`,
|
|
1222
|
+
`- Proactively fill task properties (SP, tags, icon) as the plan takes shape`
|
|
1223
|
+
];
|
|
1224
|
+
if (context) {
|
|
1225
|
+
parts.push(...buildPropertyInstructions(context));
|
|
1226
|
+
}
|
|
1227
|
+
return parts.join("\n");
|
|
1228
|
+
}
|
|
1229
|
+
case "building":
|
|
1230
|
+
return [
|
|
1231
|
+
`
|
|
1232
|
+
## Mode: Building`,
|
|
1233
|
+
`You are in Building mode \u2014 executing the plan.`,
|
|
1234
|
+
`- You have full coding access (read, write, edit, bash, git)`,
|
|
1235
|
+
`- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
|
|
1236
|
+
`- If this is a leaf task (no children): execute the plan directly`,
|
|
1237
|
+
`- Goal: implement the plan, run tests, open a PR when done`
|
|
1238
|
+
].join("\n");
|
|
1239
|
+
case "review":
|
|
1240
|
+
return [
|
|
1241
|
+
`
|
|
1242
|
+
## Mode: Review`,
|
|
1243
|
+
`You are in Review mode \u2014 reviewing and coordinating.`,
|
|
1244
|
+
`- You have read-only access plus light edit capability (can suggest fixes, run tests, check linting)`,
|
|
1245
|
+
`- For parent tasks: you can manage children, review child PRs, fire next child builds`,
|
|
1246
|
+
`- You have Pack Runner coordination tools (list_subtasks, fire builds, approve PRs)`,
|
|
1247
|
+
`- Goal: ensure quality, provide feedback, coordinate progression`
|
|
1248
|
+
].join("\n");
|
|
1249
|
+
case "auto": {
|
|
1250
|
+
const parts = [
|
|
1251
|
+
`
|
|
1252
|
+
## Mode: Auto`,
|
|
1253
|
+
`You are in Auto mode \u2014 operating autonomously through planning \u2192 building \u2192 PR.`,
|
|
1254
|
+
``,
|
|
1255
|
+
`### Phase 1: Discovery & Planning (current)`,
|
|
1256
|
+
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
1257
|
+
`- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
|
|
1258
|
+
`- You have MCP tools for task properties: update_task, set_story_points, set_task_tags, set_task_icon, set_task_title`,
|
|
1259
|
+
``,
|
|
1260
|
+
`### Required before transitioning:`,
|
|
1261
|
+
`Before calling ExitPlanMode, you MUST fill in ALL of these:`,
|
|
1262
|
+
`1. **Plan** \u2014 Save a clear implementation plan using update_task`,
|
|
1263
|
+
`2. **Story Points** \u2014 Assign via set_story_points`,
|
|
1264
|
+
`3. **Title** \u2014 Set an accurate title via set_task_title (if the current one is vague or "Untitled")`,
|
|
1265
|
+
``,
|
|
1266
|
+
`### Transitioning to Building:`,
|
|
1267
|
+
`When your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
|
|
1268
|
+
`- If any required properties are missing, ExitPlanMode will be denied with details on what's missing`,
|
|
1269
|
+
`- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
|
|
1270
|
+
`- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
|
|
1271
|
+
``,
|
|
1272
|
+
`### Autonomous Guidelines:`,
|
|
1273
|
+
`- Make decisions independently \u2014 do not ask the team for approval at each step`,
|
|
1274
|
+
`- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
|
|
1275
|
+
`- Be thorough in discovery: read relevant files, understand the codebase architecture, then plan`
|
|
1276
|
+
];
|
|
1277
|
+
if (context) {
|
|
1278
|
+
parts.push(...buildPropertyInstructions(context));
|
|
1279
|
+
}
|
|
1280
|
+
return parts.join("\n");
|
|
1281
|
+
}
|
|
1282
|
+
default:
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1094
1286
|
function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
1095
1287
|
const parts = [
|
|
1096
1288
|
`You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
|
|
@@ -1201,7 +1393,7 @@ After addressing the feedback, resume your autonomous loop: call list_subtasks a
|
|
|
1201
1393
|
}
|
|
1202
1394
|
return parts;
|
|
1203
1395
|
}
|
|
1204
|
-
function buildInitialPrompt(mode, context, isAuto) {
|
|
1396
|
+
function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
1205
1397
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
1206
1398
|
if (!isPackRunner) {
|
|
1207
1399
|
const sessionRelaunch = buildRelaunchWithSession(mode, context);
|
|
@@ -1209,12 +1401,12 @@ function buildInitialPrompt(mode, context, isAuto) {
|
|
|
1209
1401
|
}
|
|
1210
1402
|
const scenario = detectRelaunchScenario(context);
|
|
1211
1403
|
const body = buildTaskBody(context);
|
|
1212
|
-
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario);
|
|
1404
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
1213
1405
|
return [...body, ...instructions].join("\n");
|
|
1214
1406
|
}
|
|
1215
|
-
function buildSystemPrompt(mode, context, config, setupLog,
|
|
1407
|
+
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
1216
1408
|
const isPm = mode === "pm";
|
|
1217
|
-
const isPmActive = isPm &&
|
|
1409
|
+
const isPmActive = isPm && agentMode === "building";
|
|
1218
1410
|
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
1219
1411
|
if (isPackRunner) {
|
|
1220
1412
|
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
@@ -1340,6 +1532,10 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
|
|
|
1340
1532
|
`Use the create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
|
|
1341
1533
|
);
|
|
1342
1534
|
}
|
|
1535
|
+
const modePrompt = buildModePrompt(agentMode, context);
|
|
1536
|
+
if (modePrompt) {
|
|
1537
|
+
parts.push(modePrompt);
|
|
1538
|
+
}
|
|
1343
1539
|
return parts.join("\n");
|
|
1344
1540
|
}
|
|
1345
1541
|
|
|
@@ -1707,6 +1903,117 @@ function buildTaskTools(connection) {
|
|
|
1707
1903
|
];
|
|
1708
1904
|
}
|
|
1709
1905
|
|
|
1906
|
+
// src/tools/discovery-tools.ts
|
|
1907
|
+
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
1908
|
+
import { z as z4 } from "zod";
|
|
1909
|
+
function buildDiscoveryTools(connection, context) {
|
|
1910
|
+
const spDescription = buildStoryPointDescription(context?.storyPoints);
|
|
1911
|
+
return [
|
|
1912
|
+
tool4(
|
|
1913
|
+
"set_story_points",
|
|
1914
|
+
"Set the story point estimate for this task. Use after understanding the scope of the work.",
|
|
1915
|
+
{ value: z4.number().describe(spDescription) },
|
|
1916
|
+
async ({ value }) => {
|
|
1917
|
+
try {
|
|
1918
|
+
connection.updateTaskProperties({ storyPointValue: value });
|
|
1919
|
+
return textResult(`Story points set to ${value}`);
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
return textResult(
|
|
1922
|
+
`Failed to set story points: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
),
|
|
1927
|
+
tool4(
|
|
1928
|
+
"set_task_tags",
|
|
1929
|
+
"Assign tags to this task from the project's available tags. Use the tag IDs from the project tags list.",
|
|
1930
|
+
{
|
|
1931
|
+
tagIds: z4.array(z4.string()).describe("Array of tag IDs to assign")
|
|
1932
|
+
},
|
|
1933
|
+
async ({ tagIds }) => {
|
|
1934
|
+
try {
|
|
1935
|
+
connection.updateTaskProperties({ tagIds });
|
|
1936
|
+
return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
return textResult(
|
|
1939
|
+
`Failed to set tags: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
),
|
|
1944
|
+
tool4(
|
|
1945
|
+
"set_task_title",
|
|
1946
|
+
"Update the task title to better reflect the planned work.",
|
|
1947
|
+
{
|
|
1948
|
+
title: z4.string().describe("The new task title")
|
|
1949
|
+
},
|
|
1950
|
+
async ({ title }) => {
|
|
1951
|
+
try {
|
|
1952
|
+
connection.updateTaskProperties({ title });
|
|
1953
|
+
return textResult(`Task title updated to: ${title}`);
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
return textResult(
|
|
1956
|
+
`Failed to update title: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
),
|
|
1961
|
+
tool4(
|
|
1962
|
+
"list_icons",
|
|
1963
|
+
"List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before set_task_icon to check for existing matches.",
|
|
1964
|
+
{},
|
|
1965
|
+
async () => {
|
|
1966
|
+
try {
|
|
1967
|
+
const icons = await connection.listIcons();
|
|
1968
|
+
return textResult(JSON.stringify(icons, null, 2));
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
return textResult(
|
|
1971
|
+
`Failed to list icons: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
},
|
|
1975
|
+
{ annotations: { readOnlyHint: true } }
|
|
1976
|
+
),
|
|
1977
|
+
tool4(
|
|
1978
|
+
"set_task_icon",
|
|
1979
|
+
"Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
|
|
1980
|
+
{
|
|
1981
|
+
iconId: z4.string().describe("The icon ID to assign")
|
|
1982
|
+
},
|
|
1983
|
+
async ({ iconId }) => {
|
|
1984
|
+
try {
|
|
1985
|
+
connection.updateTaskProperties({ iconId });
|
|
1986
|
+
return textResult("Icon assigned to task.");
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
return textResult(
|
|
1989
|
+
`Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
),
|
|
1994
|
+
tool4(
|
|
1995
|
+
"generate_task_icon",
|
|
1996
|
+
"Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
|
|
1997
|
+
{
|
|
1998
|
+
prompt: z4.string().describe(
|
|
1999
|
+
"Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
|
|
2000
|
+
),
|
|
2001
|
+
aspectRatio: z4.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
|
|
2002
|
+
},
|
|
2003
|
+
async ({ prompt, aspectRatio }) => {
|
|
2004
|
+
try {
|
|
2005
|
+
const result = await connection.generateTaskIcon(prompt, aspectRatio ?? "square");
|
|
2006
|
+
return textResult(`Icon generated and assigned: ${result.iconId}`);
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
return textResult(
|
|
2009
|
+
`Failed to generate icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
)
|
|
2014
|
+
];
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1710
2017
|
// src/tools/index.ts
|
|
1711
2018
|
function textResult(text) {
|
|
1712
2019
|
return { content: [{ type: "text", text }] };
|
|
@@ -1716,11 +2023,30 @@ function imageBlock(data, mimeType) {
|
|
|
1716
2023
|
}
|
|
1717
2024
|
function createConveyorMcpServer(connection, config, context) {
|
|
1718
2025
|
const commonTools = buildCommonTools(connection, config);
|
|
1719
|
-
const
|
|
1720
|
-
|
|
2026
|
+
const agentMode = context?.agentMode;
|
|
2027
|
+
let modeTools;
|
|
2028
|
+
switch (agentMode) {
|
|
2029
|
+
case "building":
|
|
2030
|
+
modeTools = buildTaskTools(connection);
|
|
2031
|
+
break;
|
|
2032
|
+
case "review":
|
|
2033
|
+
modeTools = buildPmTools(connection, context?.storyPoints, {
|
|
2034
|
+
includePackTools: !!context?.isParentTask
|
|
2035
|
+
});
|
|
2036
|
+
break;
|
|
2037
|
+
case "auto":
|
|
2038
|
+
case "discovery":
|
|
2039
|
+
case "help":
|
|
2040
|
+
modeTools = buildPmTools(connection, context?.storyPoints, { includePackTools: false });
|
|
2041
|
+
break;
|
|
2042
|
+
default:
|
|
2043
|
+
modeTools = config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : buildTaskTools(connection);
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
2046
|
+
const discoveryTools = agentMode === "discovery" || agentMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
1721
2047
|
return createSdkMcpServer({
|
|
1722
2048
|
name: "conveyor",
|
|
1723
|
-
tools: [...commonTools, ...modeTools]
|
|
2049
|
+
tools: [...commonTools, ...modeTools, ...discoveryTools]
|
|
1724
2050
|
});
|
|
1725
2051
|
}
|
|
1726
2052
|
|
|
@@ -1730,84 +2056,159 @@ var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
|
1730
2056
|
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
1731
2057
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
1732
2058
|
var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
|
|
2059
|
+
function handleDiscoveryToolAccess(toolName, input) {
|
|
2060
|
+
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2061
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
2062
|
+
if (filePath.includes(".claude/plans/")) {
|
|
2063
|
+
return { behavior: "allow", updatedInput: input };
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
behavior: "deny",
|
|
2067
|
+
message: "Discovery mode is read-only. File writes are restricted to plan files."
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
return { behavior: "allow", updatedInput: input };
|
|
2071
|
+
}
|
|
2072
|
+
function handleBuildingToolAccess(toolName, input) {
|
|
2073
|
+
if (toolName === "Bash") {
|
|
2074
|
+
const cmd = String(input.command ?? "");
|
|
2075
|
+
if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
|
|
2076
|
+
return {
|
|
2077
|
+
behavior: "deny",
|
|
2078
|
+
message: "Destructive operation blocked. Use safer alternatives."
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return { behavior: "allow", updatedInput: input };
|
|
2083
|
+
}
|
|
2084
|
+
function handleReviewToolAccess(toolName, input, isParentTask) {
|
|
2085
|
+
if (isParentTask) {
|
|
2086
|
+
return handleBuildingToolAccess(toolName, input);
|
|
2087
|
+
}
|
|
2088
|
+
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2089
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
2090
|
+
if (filePath.includes(".claude/plans/")) {
|
|
2091
|
+
return { behavior: "allow", updatedInput: input };
|
|
2092
|
+
}
|
|
2093
|
+
return {
|
|
2094
|
+
behavior: "deny",
|
|
2095
|
+
message: "Review mode restricts file writes. Use bash to run tests and linting instead."
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
if (toolName === "Bash") {
|
|
2099
|
+
const cmd = String(input.command ?? "");
|
|
2100
|
+
if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
|
|
2101
|
+
return { behavior: "deny", message: "Destructive operation blocked in review mode." };
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return { behavior: "allow", updatedInput: input };
|
|
2105
|
+
}
|
|
2106
|
+
function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
|
|
2107
|
+
if (hasExitedPlanMode) {
|
|
2108
|
+
return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
|
|
2109
|
+
}
|
|
2110
|
+
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2111
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
2112
|
+
if (filePath.includes(".claude/plans/")) {
|
|
2113
|
+
return { behavior: "allow", updatedInput: input };
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
behavior: "deny",
|
|
2117
|
+
message: "You are in auto plan mode. File writes are restricted to plan files. Call ExitPlanMode when your plan is ready to start building."
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
return { behavior: "allow", updatedInput: input };
|
|
2121
|
+
}
|
|
1733
2122
|
function buildCanUseTool(host) {
|
|
1734
2123
|
const QUESTION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1735
2124
|
return async (toolName, input) => {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
if (
|
|
2125
|
+
if (toolName === "ExitPlanMode" && host.agentMode === "auto" && !host.hasExitedPlanMode) {
|
|
2126
|
+
try {
|
|
2127
|
+
const taskProps = await host.connection.getTaskProperties();
|
|
2128
|
+
const missingProps = [];
|
|
2129
|
+
if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
|
|
2130
|
+
if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
|
|
2131
|
+
if (!taskProps.title || taskProps.title === "Untitled")
|
|
2132
|
+
missingProps.push("title (use set_task_title)");
|
|
2133
|
+
if (missingProps.length > 0) {
|
|
1743
2134
|
return {
|
|
1744
2135
|
behavior: "deny",
|
|
1745
|
-
message:
|
|
2136
|
+
message: [
|
|
2137
|
+
"Cannot exit plan mode yet. Required task properties are missing:",
|
|
2138
|
+
...missingProps.map((p) => `- ${p}`),
|
|
2139
|
+
"",
|
|
2140
|
+
"Fill these in using MCP tools, then try ExitPlanMode again."
|
|
2141
|
+
].join("\n")
|
|
1746
2142
|
};
|
|
1747
2143
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2144
|
+
await host.connection.triggerIdentification();
|
|
2145
|
+
host.hasExitedPlanMode = true;
|
|
2146
|
+
const newMode = host.isParentTask ? "review" : "building";
|
|
2147
|
+
host.pendingModeRestart = true;
|
|
2148
|
+
if (host.onModeTransition) {
|
|
2149
|
+
host.onModeTransition(newMode);
|
|
2150
|
+
}
|
|
1754
2151
|
return { behavior: "allow", updatedInput: input };
|
|
2152
|
+
} catch (err) {
|
|
2153
|
+
return {
|
|
2154
|
+
behavior: "deny",
|
|
2155
|
+
message: `Identification failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
2156
|
+
};
|
|
1755
2157
|
}
|
|
1756
|
-
return {
|
|
1757
|
-
behavior: "deny",
|
|
1758
|
-
message: "File write tools are only available for plan files in PM mode."
|
|
1759
|
-
};
|
|
1760
2158
|
}
|
|
1761
|
-
if (
|
|
1762
|
-
const
|
|
1763
|
-
|
|
2159
|
+
if (toolName === "AskUserQuestion") {
|
|
2160
|
+
const questions = input.questions;
|
|
2161
|
+
const requestId = randomUUID();
|
|
2162
|
+
host.connection.emitStatus("waiting_for_input");
|
|
2163
|
+
host.connection.sendEvent({
|
|
2164
|
+
type: "tool_use",
|
|
2165
|
+
tool: "AskUserQuestion",
|
|
2166
|
+
input: JSON.stringify(input)
|
|
2167
|
+
});
|
|
2168
|
+
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
2169
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
2170
|
+
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
2171
|
+
});
|
|
2172
|
+
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
2173
|
+
host.connection.emitStatus("running");
|
|
2174
|
+
if (!answers) {
|
|
1764
2175
|
return {
|
|
1765
2176
|
behavior: "deny",
|
|
1766
|
-
message: "
|
|
2177
|
+
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
1767
2178
|
};
|
|
1768
2179
|
}
|
|
2180
|
+
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2181
|
+
}
|
|
2182
|
+
switch (host.agentMode) {
|
|
2183
|
+
case "discovery":
|
|
2184
|
+
return handleDiscoveryToolAccess(toolName, input);
|
|
2185
|
+
case "building":
|
|
2186
|
+
return handleBuildingToolAccess(toolName, input);
|
|
2187
|
+
case "review":
|
|
2188
|
+
return handleReviewToolAccess(toolName, input, host.isParentTask);
|
|
2189
|
+
case "auto":
|
|
2190
|
+
return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
2191
|
+
default:
|
|
2192
|
+
return { behavior: "allow", updatedInput: input };
|
|
1769
2193
|
}
|
|
1770
|
-
if (toolName !== "AskUserQuestion") {
|
|
1771
|
-
return { behavior: "allow", updatedInput: input };
|
|
1772
|
-
}
|
|
1773
|
-
const questions = input.questions;
|
|
1774
|
-
const requestId = randomUUID();
|
|
1775
|
-
host.connection.emitStatus("waiting_for_input");
|
|
1776
|
-
host.connection.sendEvent({
|
|
1777
|
-
type: "tool_use",
|
|
1778
|
-
tool: "AskUserQuestion",
|
|
1779
|
-
input: JSON.stringify(input)
|
|
1780
|
-
});
|
|
1781
|
-
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
1782
|
-
const timeoutPromise = new Promise((resolve2) => {
|
|
1783
|
-
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
1784
|
-
});
|
|
1785
|
-
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
1786
|
-
host.connection.emitStatus("running");
|
|
1787
|
-
if (!answers) {
|
|
1788
|
-
return {
|
|
1789
|
-
behavior: "deny",
|
|
1790
|
-
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
1794
2194
|
};
|
|
1795
2195
|
}
|
|
1796
2196
|
function buildQueryOptions(host, context) {
|
|
1797
2197
|
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
2198
|
+
const mode = host.agentMode;
|
|
2199
|
+
const isActiveMode = mode === "building" || mode === "review" || mode === "auto" && host.hasExitedPlanMode;
|
|
2200
|
+
const shouldSandbox = host.config.mode === "pm" && isActiveMode && context.useSandbox !== false;
|
|
1800
2201
|
const systemPromptText = buildSystemPrompt(
|
|
1801
2202
|
host.config.mode,
|
|
1802
2203
|
context,
|
|
1803
2204
|
host.config,
|
|
1804
2205
|
host.setupLog,
|
|
1805
|
-
|
|
2206
|
+
mode
|
|
1806
2207
|
);
|
|
1807
2208
|
const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
|
|
1808
|
-
const
|
|
1809
|
-
const
|
|
1810
|
-
const disallowedTools = [...settings.disallowedTools ?? [], ...
|
|
2209
|
+
const isReadOnlyMode = mode === "discovery" || mode === "help" || mode === "auto" && !host.hasExitedPlanMode;
|
|
2210
|
+
const modeDisallowedTools = isReadOnlyMode ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
2211
|
+
const disallowedTools = [...settings.disallowedTools ?? [], ...modeDisallowedTools];
|
|
1811
2212
|
const settingSources = settings.settingSources ?? ["user", "project"];
|
|
1812
2213
|
const hooks = {
|
|
1813
2214
|
PostToolUse: [
|
|
@@ -1920,10 +2321,10 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
1920
2321
|
}
|
|
1921
2322
|
async function runSdkQuery(host, context, followUpContent) {
|
|
1922
2323
|
if (host.isStopped()) return;
|
|
1923
|
-
const
|
|
1924
|
-
const
|
|
1925
|
-
const
|
|
1926
|
-
if (
|
|
2324
|
+
const mode = host.agentMode;
|
|
2325
|
+
const isPmMode = host.config.mode === "pm";
|
|
2326
|
+
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
2327
|
+
if (isDiscoveryLike) {
|
|
1927
2328
|
host.snapshotPlanFiles();
|
|
1928
2329
|
}
|
|
1929
2330
|
const options = buildQueryOptions(host, context);
|
|
@@ -1933,14 +2334,14 @@ async function runSdkQuery(host, context, followUpContent) {
|
|
|
1933
2334
|
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
1934
2335
|
(b) => b.type === "image"
|
|
1935
2336
|
);
|
|
1936
|
-
const textPrompt =
|
|
2337
|
+
const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode)}
|
|
1937
2338
|
|
|
1938
2339
|
---
|
|
1939
2340
|
|
|
1940
2341
|
The team says:
|
|
1941
2342
|
${followUpText}` : followUpText;
|
|
1942
2343
|
let prompt;
|
|
1943
|
-
if (
|
|
2344
|
+
if (isPmMode) {
|
|
1944
2345
|
prompt = buildMultimodalPrompt(textPrompt, context);
|
|
1945
2346
|
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
1946
2347
|
prompt.push(...followUpImages);
|
|
@@ -1955,10 +2356,10 @@ ${followUpText}` : followUpText;
|
|
|
1955
2356
|
options: { ...options, resume }
|
|
1956
2357
|
});
|
|
1957
2358
|
await runWithRetry(agentQuery, context, host, options);
|
|
1958
|
-
} else if (
|
|
2359
|
+
} else if (isDiscoveryLike) {
|
|
1959
2360
|
return;
|
|
1960
2361
|
} else {
|
|
1961
|
-
const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto);
|
|
2362
|
+
const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode);
|
|
1962
2363
|
const prompt = buildMultimodalPrompt(initialPrompt, context);
|
|
1963
2364
|
const agentQuery = query({
|
|
1964
2365
|
prompt: host.createInputStream(prompt),
|
|
@@ -1966,7 +2367,7 @@ ${followUpText}` : followUpText;
|
|
|
1966
2367
|
});
|
|
1967
2368
|
await runWithRetry(agentQuery, context, host, options);
|
|
1968
2369
|
}
|
|
1969
|
-
if (
|
|
2370
|
+
if (isDiscoveryLike) {
|
|
1970
2371
|
host.syncPlanFile();
|
|
1971
2372
|
}
|
|
1972
2373
|
}
|
|
@@ -1981,7 +2382,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1981
2382
|
);
|
|
1982
2383
|
}
|
|
1983
2384
|
const retryPrompt = buildMultimodalPrompt(
|
|
1984
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto),
|
|
2385
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
1985
2386
|
context,
|
|
1986
2387
|
lastErrorWasImage
|
|
1987
2388
|
);
|
|
@@ -1991,7 +2392,12 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1991
2392
|
});
|
|
1992
2393
|
})();
|
|
1993
2394
|
try {
|
|
1994
|
-
const { retriable, resultSummary } = await processEvents(
|
|
2395
|
+
const { retriable, resultSummary, modeRestart } = await processEvents(
|
|
2396
|
+
agentQuery,
|
|
2397
|
+
context,
|
|
2398
|
+
host
|
|
2399
|
+
);
|
|
2400
|
+
if (modeRestart) return;
|
|
1995
2401
|
if (!retriable || host.isStopped()) return;
|
|
1996
2402
|
lastErrorWasImage = IMAGE_ERROR_PATTERN.test(resultSummary ?? "");
|
|
1997
2403
|
} catch (error) {
|
|
@@ -2001,7 +2407,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
2001
2407
|
context.claudeSessionId = null;
|
|
2002
2408
|
host.connection.storeSessionId("");
|
|
2003
2409
|
const freshPrompt = buildMultimodalPrompt(
|
|
2004
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto),
|
|
2410
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2005
2411
|
context
|
|
2006
2412
|
);
|
|
2007
2413
|
const freshQuery = query({
|
|
@@ -2187,7 +2593,13 @@ var AgentRunner = class _AgentRunner {
|
|
|
2187
2593
|
planSync;
|
|
2188
2594
|
costTracker = new CostTracker();
|
|
2189
2595
|
worktreeActive = false;
|
|
2190
|
-
|
|
2596
|
+
agentMode = null;
|
|
2597
|
+
hasExitedPlanMode = false;
|
|
2598
|
+
pendingModeRestart = false;
|
|
2599
|
+
sessionIds = /* @__PURE__ */ new Map();
|
|
2600
|
+
lastQueryModeRestart = false;
|
|
2601
|
+
deferredStartConfig = null;
|
|
2602
|
+
startCommandStarted = false;
|
|
2191
2603
|
static MAX_SETUP_LOG_LINES = 50;
|
|
2192
2604
|
constructor(config, callbacks) {
|
|
2193
2605
|
this.config = config;
|
|
@@ -2198,6 +2610,18 @@ var AgentRunner = class _AgentRunner {
|
|
|
2198
2610
|
get state() {
|
|
2199
2611
|
return this._state;
|
|
2200
2612
|
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Resolve the effective AgentMode from explicit agentMode or legacy config flags.
|
|
2615
|
+
* This is the single axis of behavior for all execution path decisions.
|
|
2616
|
+
*/
|
|
2617
|
+
get effectiveAgentMode() {
|
|
2618
|
+
if (this.agentMode) return this.agentMode;
|
|
2619
|
+
if (this.config.mode === "pm") {
|
|
2620
|
+
if (this.config.isAuto) return "auto";
|
|
2621
|
+
return "discovery";
|
|
2622
|
+
}
|
|
2623
|
+
return "building";
|
|
2624
|
+
}
|
|
2201
2625
|
async setState(status) {
|
|
2202
2626
|
this._state = status;
|
|
2203
2627
|
this.connection.emitStatus(status);
|
|
@@ -2223,7 +2647,8 @@ var AgentRunner = class _AgentRunner {
|
|
|
2223
2647
|
this.connection.onChatMessage(
|
|
2224
2648
|
(message) => this.injectHumanMessage(message.content, message.files)
|
|
2225
2649
|
);
|
|
2226
|
-
this.connection.onModeChange((data) => this.handleModeChange(data.
|
|
2650
|
+
this.connection.onModeChange((data) => this.handleModeChange(data.agentMode));
|
|
2651
|
+
this.connection.onRunStartCommand(() => this.runDeferredStartCommand());
|
|
2227
2652
|
await this.setState("connected");
|
|
2228
2653
|
this.connection.sendEvent({ type: "connected", taskId: this.config.taskId });
|
|
2229
2654
|
this.startHeartbeat();
|
|
@@ -2262,6 +2687,9 @@ var AgentRunner = class _AgentRunner {
|
|
|
2262
2687
|
return;
|
|
2263
2688
|
}
|
|
2264
2689
|
this.taskContext._runnerSessionId = randomUUID2();
|
|
2690
|
+
if (this.taskContext.agentMode) {
|
|
2691
|
+
this.agentMode = this.taskContext.agentMode;
|
|
2692
|
+
}
|
|
2265
2693
|
this.logEffectiveSettings();
|
|
2266
2694
|
if (process.env.CODESPACES === "true") {
|
|
2267
2695
|
unshallowRepo(this.config.workspaceDir);
|
|
@@ -2298,18 +2726,30 @@ var AgentRunner = class _AgentRunner {
|
|
|
2298
2726
|
} catch {
|
|
2299
2727
|
}
|
|
2300
2728
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2729
|
+
switch (this.effectiveAgentMode) {
|
|
2730
|
+
case "discovery":
|
|
2731
|
+
case "help":
|
|
2732
|
+
await this.setState("idle");
|
|
2733
|
+
break;
|
|
2734
|
+
case "building":
|
|
2735
|
+
await this.setState("running");
|
|
2736
|
+
await this.runQuerySafe(this.taskContext);
|
|
2737
|
+
if (!this.stopped) await this.setState("idle");
|
|
2738
|
+
break;
|
|
2739
|
+
case "review":
|
|
2740
|
+
if (this.taskContext.isParentTask) {
|
|
2741
|
+
await this.setState("running");
|
|
2742
|
+
await this.runQuerySafe(this.taskContext);
|
|
2743
|
+
if (!this.stopped) await this.setState("idle");
|
|
2744
|
+
} else {
|
|
2745
|
+
await this.setState("idle");
|
|
2746
|
+
}
|
|
2747
|
+
break;
|
|
2748
|
+
case "auto":
|
|
2749
|
+
await this.setState("running");
|
|
2750
|
+
await this.runQuerySafe(this.taskContext);
|
|
2751
|
+
if (!this.stopped) await this.setState("idle");
|
|
2752
|
+
break;
|
|
2313
2753
|
}
|
|
2314
2754
|
await this.runCoreLoop();
|
|
2315
2755
|
this.stopHeartbeat();
|
|
@@ -2317,8 +2757,13 @@ var AgentRunner = class _AgentRunner {
|
|
|
2317
2757
|
this.connection.disconnect();
|
|
2318
2758
|
}
|
|
2319
2759
|
async runQuerySafe(context, followUp) {
|
|
2760
|
+
this.lastQueryModeRestart = false;
|
|
2320
2761
|
try {
|
|
2321
2762
|
await runSdkQuery(this.asQueryHost(), context, followUp);
|
|
2763
|
+
if (this.pendingModeRestart) {
|
|
2764
|
+
this.lastQueryModeRestart = true;
|
|
2765
|
+
this.pendingModeRestart = false;
|
|
2766
|
+
}
|
|
2322
2767
|
} catch (error) {
|
|
2323
2768
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2324
2769
|
this.connection.sendEvent({ type: "error", message });
|
|
@@ -2329,6 +2774,11 @@ var AgentRunner = class _AgentRunner {
|
|
|
2329
2774
|
async runCoreLoop() {
|
|
2330
2775
|
if (!this.taskContext) return;
|
|
2331
2776
|
while (!this.stopped) {
|
|
2777
|
+
if (this.lastQueryModeRestart) {
|
|
2778
|
+
this.lastQueryModeRestart = false;
|
|
2779
|
+
await this.handleAutoModeRestart();
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2332
2782
|
if (this._state === "idle") {
|
|
2333
2783
|
const msg = await this.waitForUserContent();
|
|
2334
2784
|
if (!msg) break;
|
|
@@ -2342,6 +2792,65 @@ var AgentRunner = class _AgentRunner {
|
|
|
2342
2792
|
}
|
|
2343
2793
|
}
|
|
2344
2794
|
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Handle auto mode transition after ExitPlanMode.
|
|
2797
|
+
* Saves the current session, switches model/mode, and restarts with a fresh query.
|
|
2798
|
+
*/
|
|
2799
|
+
async handleAutoModeRestart() {
|
|
2800
|
+
if (!this.taskContext) return;
|
|
2801
|
+
const currentModel = this.taskContext.model;
|
|
2802
|
+
const currentSessionId = this.taskContext.claudeSessionId;
|
|
2803
|
+
if (currentSessionId && currentModel) {
|
|
2804
|
+
this.sessionIds.set(currentModel, currentSessionId);
|
|
2805
|
+
}
|
|
2806
|
+
const newMode = this.agentMode;
|
|
2807
|
+
const isParentTask = !!this.taskContext.isParentTask;
|
|
2808
|
+
const newModel = this.getModelForMode(newMode, isParentTask);
|
|
2809
|
+
const resumeSessionId = newModel ? this.sessionIds.get(newModel) : null;
|
|
2810
|
+
if (resumeSessionId) {
|
|
2811
|
+
this.taskContext.claudeSessionId = resumeSessionId;
|
|
2812
|
+
} else {
|
|
2813
|
+
this.taskContext.claudeSessionId = null;
|
|
2814
|
+
this.connection.storeSessionId("");
|
|
2815
|
+
}
|
|
2816
|
+
if (newModel) {
|
|
2817
|
+
this.taskContext.model = newModel;
|
|
2818
|
+
}
|
|
2819
|
+
this.taskContext.agentMode = newMode;
|
|
2820
|
+
this.connection.emitModeTransition({
|
|
2821
|
+
fromMode: "auto",
|
|
2822
|
+
toMode: newMode ?? "building"
|
|
2823
|
+
});
|
|
2824
|
+
this.connection.emitModeChanged(newMode);
|
|
2825
|
+
this.connection.postChatMessage(
|
|
2826
|
+
`Transitioning to **${newMode}** mode${newModel ? ` with ${newModel}` : ""}. Restarting session...`
|
|
2827
|
+
);
|
|
2828
|
+
try {
|
|
2829
|
+
const freshContext = await this.connection.fetchTaskContext();
|
|
2830
|
+
freshContext._runnerSessionId = this.taskContext._runnerSessionId;
|
|
2831
|
+
freshContext.claudeSessionId = this.taskContext.claudeSessionId;
|
|
2832
|
+
freshContext.agentMode = newMode;
|
|
2833
|
+
if (newModel) freshContext.model = newModel;
|
|
2834
|
+
this.taskContext = freshContext;
|
|
2835
|
+
} catch {
|
|
2836
|
+
}
|
|
2837
|
+
await this.setState("running");
|
|
2838
|
+
await this.runQuerySafe(this.taskContext);
|
|
2839
|
+
if (!this.stopped && this._state !== "error") {
|
|
2840
|
+
await this.setState("idle");
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Get the appropriate model for a given mode.
|
|
2845
|
+
* Building uses the builder model (Sonnet), Discovery/Review use PM model (Opus).
|
|
2846
|
+
*/
|
|
2847
|
+
getModelForMode(mode, isParentTask) {
|
|
2848
|
+
if (!this.taskContext) return null;
|
|
2849
|
+
if (mode === "building" && !isParentTask) {
|
|
2850
|
+
return this.taskContext.builderModel ?? this.taskContext.model;
|
|
2851
|
+
}
|
|
2852
|
+
return this.taskContext.pmModel ?? this.taskContext.model;
|
|
2853
|
+
}
|
|
2345
2854
|
async runSetupSafe() {
|
|
2346
2855
|
await this.setState("setup");
|
|
2347
2856
|
const ports = await loadForwardPorts(this.config.workspaceDir);
|
|
@@ -2363,7 +2872,8 @@ var AgentRunner = class _AgentRunner {
|
|
|
2363
2872
|
await this.executeSetupConfig(config);
|
|
2364
2873
|
const setupEvent = {
|
|
2365
2874
|
type: "setup_complete",
|
|
2366
|
-
previewPort: config.previewPort ?? void 0
|
|
2875
|
+
previewPort: config.previewPort ?? void 0,
|
|
2876
|
+
startCommandDeferred: this.deferredStartConfig !== null ? true : void 0
|
|
2367
2877
|
};
|
|
2368
2878
|
this.connection.sendEvent(setupEvent);
|
|
2369
2879
|
await this.callbacks.onEvent(setupEvent);
|
|
@@ -2415,12 +2925,33 @@ The agent cannot start until this is resolved.`
|
|
|
2415
2925
|
this.pushSetupLog("(exit 0)");
|
|
2416
2926
|
}
|
|
2417
2927
|
if (config.startCommand) {
|
|
2418
|
-
this.
|
|
2419
|
-
|
|
2420
|
-
this.
|
|
2421
|
-
}
|
|
2928
|
+
if (this.effectiveAgentMode === "auto") {
|
|
2929
|
+
this.deferredStartConfig = config;
|
|
2930
|
+
this.pushSetupLog(`[conveyor] startCommand deferred (auto mode)`);
|
|
2931
|
+
} else {
|
|
2932
|
+
this.pushSetupLog(`$ ${config.startCommand} & (background)`);
|
|
2933
|
+
runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
|
|
2934
|
+
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2422
2937
|
}
|
|
2423
2938
|
}
|
|
2939
|
+
runDeferredStartCommand() {
|
|
2940
|
+
if (!this.deferredStartConfig?.startCommand || this.startCommandStarted) return;
|
|
2941
|
+
this.startCommandStarted = true;
|
|
2942
|
+
const config = this.deferredStartConfig;
|
|
2943
|
+
this.deferredStartConfig = null;
|
|
2944
|
+
this.pushSetupLog(`$ ${config.startCommand} & (background, deferred)`);
|
|
2945
|
+
this.connection.sendEvent({ type: "start_command_started" });
|
|
2946
|
+
runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
|
|
2947
|
+
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2948
|
+
});
|
|
2949
|
+
const setupEvent = {
|
|
2950
|
+
type: "setup_complete",
|
|
2951
|
+
previewPort: config.previewPort ?? void 0
|
|
2952
|
+
};
|
|
2953
|
+
this.connection.sendEvent(setupEvent);
|
|
2954
|
+
}
|
|
2424
2955
|
injectHumanMessage(content, files) {
|
|
2425
2956
|
let messageContent;
|
|
2426
2957
|
if (files?.length) {
|
|
@@ -2533,28 +3064,59 @@ ${f.content}
|
|
|
2533
3064
|
}
|
|
2534
3065
|
}
|
|
2535
3066
|
asQueryHost() {
|
|
2536
|
-
const
|
|
3067
|
+
const getEffectiveAgentMode = () => this.effectiveAgentMode;
|
|
3068
|
+
const getHasExitedPlanMode = () => this.hasExitedPlanMode;
|
|
3069
|
+
const setHasExitedPlanMode = (val) => {
|
|
3070
|
+
this.hasExitedPlanMode = val;
|
|
3071
|
+
};
|
|
3072
|
+
const getPendingModeRestart = () => this.pendingModeRestart;
|
|
3073
|
+
const setPendingModeRestart = (val) => {
|
|
3074
|
+
this.pendingModeRestart = val;
|
|
3075
|
+
};
|
|
3076
|
+
const getIsParentTask = () => !!this.taskContext?.isParentTask;
|
|
2537
3077
|
return {
|
|
2538
3078
|
config: this.config,
|
|
2539
3079
|
connection: this.connection,
|
|
2540
3080
|
callbacks: this.callbacks,
|
|
2541
3081
|
setupLog: this.setupLog,
|
|
2542
3082
|
costTracker: this.costTracker,
|
|
2543
|
-
get
|
|
2544
|
-
return
|
|
3083
|
+
get agentMode() {
|
|
3084
|
+
return getEffectiveAgentMode();
|
|
2545
3085
|
},
|
|
3086
|
+
get isParentTask() {
|
|
3087
|
+
return getIsParentTask();
|
|
3088
|
+
},
|
|
3089
|
+
get hasExitedPlanMode() {
|
|
3090
|
+
return getHasExitedPlanMode();
|
|
3091
|
+
},
|
|
3092
|
+
set hasExitedPlanMode(val) {
|
|
3093
|
+
setHasExitedPlanMode(val);
|
|
3094
|
+
},
|
|
3095
|
+
get pendingModeRestart() {
|
|
3096
|
+
return getPendingModeRestart();
|
|
3097
|
+
},
|
|
3098
|
+
set pendingModeRestart(val) {
|
|
3099
|
+
setPendingModeRestart(val);
|
|
3100
|
+
},
|
|
3101
|
+
sessionIds: this.sessionIds,
|
|
2546
3102
|
isStopped: () => this.stopped,
|
|
2547
3103
|
createInputStream: (prompt) => this.createInputStream(prompt),
|
|
2548
3104
|
snapshotPlanFiles: () => this.planSync.snapshotPlanFiles(),
|
|
2549
|
-
syncPlanFile: () => this.planSync.syncPlanFile()
|
|
3105
|
+
syncPlanFile: () => this.planSync.syncPlanFile(),
|
|
3106
|
+
onModeTransition: (newMode) => {
|
|
3107
|
+
this.agentMode = newMode;
|
|
3108
|
+
}
|
|
2550
3109
|
};
|
|
2551
3110
|
}
|
|
2552
|
-
handleModeChange(
|
|
3111
|
+
handleModeChange(newAgentMode) {
|
|
2553
3112
|
if (this.config.mode !== "pm") return;
|
|
2554
|
-
|
|
2555
|
-
|
|
3113
|
+
if (newAgentMode) {
|
|
3114
|
+
this.agentMode = newAgentMode;
|
|
3115
|
+
}
|
|
3116
|
+
const effectiveMode = this.effectiveAgentMode;
|
|
3117
|
+
this.connection.emitModeChanged(effectiveMode);
|
|
2556
3118
|
this.connection.postChatMessage(
|
|
2557
|
-
`Mode switched to **${
|
|
3119
|
+
`Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
|
|
2558
3120
|
);
|
|
2559
3121
|
}
|
|
2560
3122
|
stop() {
|
|
@@ -2811,6 +3373,7 @@ var ProjectRunner = class {
|
|
|
2811
3373
|
CONVEYOR_MODE: mode,
|
|
2812
3374
|
CONVEYOR_WORKSPACE: workDir,
|
|
2813
3375
|
CONVEYOR_USE_WORKTREE: "false",
|
|
3376
|
+
CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
|
|
2814
3377
|
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
2815
3378
|
CONVEYOR_USE_SANDBOX: useSandbox === false ? "false" : "true"
|
|
2816
3379
|
},
|
|
@@ -2990,4 +3553,4 @@ export {
|
|
|
2990
3553
|
ProjectRunner,
|
|
2991
3554
|
FileCache
|
|
2992
3555
|
};
|
|
2993
|
-
//# sourceMappingURL=chunk-
|
|
3556
|
+
//# sourceMappingURL=chunk-Y4TAVPZV.js.map
|