@rallycry/conveyor-agent 3.8.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-GVE6FQ75.js → chunk-Y4TAVPZV.js} +704 -118
- 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-GVE6FQ75.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,27 +1177,155 @@ 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.`,
|
|
1097
1289
|
`You are running locally with full access to the repository and task management tools.`,
|
|
1098
1290
|
`Your job is to sequentially execute child tasks by firing cloud builds, reviewing their PRs, and merging them.`,
|
|
1099
1291
|
``,
|
|
1292
|
+
`## Child Task Status Lifecycle`,
|
|
1293
|
+
`- "Planning" \u2014 Not ready for execution. Skip it (or escalate if blocking).`,
|
|
1294
|
+
`- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
|
|
1295
|
+
`- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
|
|
1296
|
+
`- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
|
|
1297
|
+
`- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
|
|
1298
|
+
`- "Complete" \u2014 Fully done. Move on.`,
|
|
1299
|
+
``,
|
|
1100
1300
|
`## Autonomous Loop`,
|
|
1101
|
-
`Follow this loop
|
|
1301
|
+
`Follow this loop each time you are launched or relaunched:`,
|
|
1302
|
+
``,
|
|
1102
1303
|
`1. Call list_subtasks to see the current state of all child tasks.`,
|
|
1103
|
-
`
|
|
1104
|
-
|
|
1105
|
-
`
|
|
1106
|
-
`
|
|
1304
|
+
` The response includes PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId).`,
|
|
1305
|
+
``,
|
|
1306
|
+
`2. Evaluate each child by status (in ordinal order):`,
|
|
1307
|
+
` - "ReviewPR": Review and merge its PR with approve_and_merge_pr.`,
|
|
1308
|
+
` - If merge fails due to pending CI: post a status update to chat, state you are going idle, and the system will relaunch you to try again.`,
|
|
1309
|
+
` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check what went wrong. Escalate to the team in chat.`,
|
|
1310
|
+
` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
|
|
1311
|
+
` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
|
|
1312
|
+
` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
|
|
1313
|
+
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
1314
|
+
` - "Planning": Not ready. If this is blocking progress, notify the team.`,
|
|
1315
|
+
``,
|
|
1316
|
+
`3. After merging a PR: run \`git pull origin ${context.baseBranch}\` to get the merged changes before firing the next child.`,
|
|
1317
|
+
``,
|
|
1318
|
+
`4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
|
|
1319
|
+
``,
|
|
1320
|
+
`5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with update_task_status("Complete").`,
|
|
1107
1321
|
``,
|
|
1108
1322
|
`## Important Rules`,
|
|
1109
1323
|
`- Process children ONE at a time, in ordinal order.`,
|
|
1110
|
-
`- After firing a child build, explicitly state you are going idle. The system will disconnect you
|
|
1324
|
+
`- After firing a child build OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
|
|
1111
1325
|
`- Do NOT attempt to write code yourself. Your role is coordination only.`,
|
|
1112
|
-
`- If a child
|
|
1113
|
-
`-
|
|
1114
|
-
`-
|
|
1326
|
+
`- If a child is stuck in "InProgress" for an unusually long time, use get_task_cli(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
|
|
1327
|
+
`- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
|
|
1328
|
+
`- list_subtasks returns PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId) for each child \u2014 use this to verify readiness before firing builds.`,
|
|
1115
1329
|
`- You can use read_task_chat to check for team messages.`
|
|
1116
1330
|
];
|
|
1117
1331
|
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
@@ -1159,10 +1373,11 @@ function buildPackRunnerInstructions(context, scenario) {
|
|
|
1159
1373
|
);
|
|
1160
1374
|
} else if (scenario === "idle_relaunch") {
|
|
1161
1375
|
parts.push(
|
|
1162
|
-
`You have been relaunched \u2014 a child task
|
|
1376
|
+
`You have been relaunched \u2014 a child task likely changed status (completed work, opened a PR, or was merged).`,
|
|
1163
1377
|
`Call list_subtasks to check the current state of all children.`,
|
|
1164
|
-
`
|
|
1165
|
-
`
|
|
1378
|
+
`Look for children in "ReviewPR" status first \u2014 review and merge their PRs.`,
|
|
1379
|
+
`If a child you previously fired is now in "ReviewDev", it was merged. Pull latest with \`git pull origin ${context.baseBranch}\` and continue to the next "Open" child.`,
|
|
1380
|
+
`If no children need action (all InProgress or waiting), state you are going idle.`
|
|
1166
1381
|
);
|
|
1167
1382
|
} else {
|
|
1168
1383
|
const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
|
|
@@ -1173,12 +1388,12 @@ function buildPackRunnerInstructions(context, scenario) {
|
|
|
1173
1388
|
New messages since your last run:`,
|
|
1174
1389
|
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1175
1390
|
`
|
|
1176
|
-
|
|
1391
|
+
After addressing the feedback, resume your autonomous loop: call list_subtasks and proceed accordingly.`
|
|
1177
1392
|
);
|
|
1178
1393
|
}
|
|
1179
1394
|
return parts;
|
|
1180
1395
|
}
|
|
1181
|
-
function buildInitialPrompt(mode, context, isAuto) {
|
|
1396
|
+
function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
1182
1397
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
1183
1398
|
if (!isPackRunner) {
|
|
1184
1399
|
const sessionRelaunch = buildRelaunchWithSession(mode, context);
|
|
@@ -1186,12 +1401,12 @@ function buildInitialPrompt(mode, context, isAuto) {
|
|
|
1186
1401
|
}
|
|
1187
1402
|
const scenario = detectRelaunchScenario(context);
|
|
1188
1403
|
const body = buildTaskBody(context);
|
|
1189
|
-
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario);
|
|
1404
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
1190
1405
|
return [...body, ...instructions].join("\n");
|
|
1191
1406
|
}
|
|
1192
|
-
function buildSystemPrompt(mode, context, config, setupLog,
|
|
1407
|
+
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
1193
1408
|
const isPm = mode === "pm";
|
|
1194
|
-
const isPmActive = isPm &&
|
|
1409
|
+
const isPmActive = isPm && agentMode === "building";
|
|
1195
1410
|
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
1196
1411
|
if (isPackRunner) {
|
|
1197
1412
|
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
@@ -1317,6 +1532,10 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
|
|
|
1317
1532
|
`Use the create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
|
|
1318
1533
|
);
|
|
1319
1534
|
}
|
|
1535
|
+
const modePrompt = buildModePrompt(agentMode, context);
|
|
1536
|
+
if (modePrompt) {
|
|
1537
|
+
parts.push(modePrompt);
|
|
1538
|
+
}
|
|
1320
1539
|
return parts.join("\n");
|
|
1321
1540
|
}
|
|
1322
1541
|
|
|
@@ -1599,7 +1818,7 @@ function buildPmTools(connection, storyPoints, options) {
|
|
|
1599
1818
|
),
|
|
1600
1819
|
tool2(
|
|
1601
1820
|
"list_subtasks",
|
|
1602
|
-
"List all subtasks under the current parent task",
|
|
1821
|
+
"List all subtasks under the current parent task. Returns status, PR info (githubPRNumber, githubPRUrl), agent assignment (agentId), and plan for each child.",
|
|
1603
1822
|
{},
|
|
1604
1823
|
async () => {
|
|
1605
1824
|
try {
|
|
@@ -1634,7 +1853,7 @@ function buildPmTools(connection, storyPoints, options) {
|
|
|
1634
1853
|
),
|
|
1635
1854
|
tool2(
|
|
1636
1855
|
"approve_and_merge_pr",
|
|
1637
|
-
"Approve and merge a child task's pull request. Only succeeds if all CI/CD checks are passing. The child task must be in ReviewPR status.",
|
|
1856
|
+
"Approve and merge a child task's pull request. Only succeeds if all CI/CD checks are passing. Returns an error if checks are pending (retry after waiting) or failed (investigate). The child task must be in ReviewPR status.",
|
|
1638
1857
|
{
|
|
1639
1858
|
childTaskId: z2.string().describe("The child task ID whose PR should be approved and merged")
|
|
1640
1859
|
},
|
|
@@ -1684,6 +1903,117 @@ function buildTaskTools(connection) {
|
|
|
1684
1903
|
];
|
|
1685
1904
|
}
|
|
1686
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
|
+
|
|
1687
2017
|
// src/tools/index.ts
|
|
1688
2018
|
function textResult(text) {
|
|
1689
2019
|
return { content: [{ type: "text", text }] };
|
|
@@ -1693,11 +2023,30 @@ function imageBlock(data, mimeType) {
|
|
|
1693
2023
|
}
|
|
1694
2024
|
function createConveyorMcpServer(connection, config, context) {
|
|
1695
2025
|
const commonTools = buildCommonTools(connection, config);
|
|
1696
|
-
const
|
|
1697
|
-
|
|
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) : [];
|
|
1698
2047
|
return createSdkMcpServer({
|
|
1699
2048
|
name: "conveyor",
|
|
1700
|
-
tools: [...commonTools, ...modeTools]
|
|
2049
|
+
tools: [...commonTools, ...modeTools, ...discoveryTools]
|
|
1701
2050
|
});
|
|
1702
2051
|
}
|
|
1703
2052
|
|
|
@@ -1707,84 +2056,159 @@ var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
|
1707
2056
|
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
1708
2057
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
1709
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
|
+
}
|
|
1710
2122
|
function buildCanUseTool(host) {
|
|
1711
2123
|
const QUESTION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1712
2124
|
return async (toolName, input) => {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
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) {
|
|
1720
2134
|
return {
|
|
1721
2135
|
behavior: "deny",
|
|
1722
|
-
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")
|
|
1723
2142
|
};
|
|
1724
2143
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
+
}
|
|
1731
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
|
+
};
|
|
1732
2157
|
}
|
|
1733
|
-
return {
|
|
1734
|
-
behavior: "deny",
|
|
1735
|
-
message: "File write tools are only available for plan files in PM mode."
|
|
1736
|
-
};
|
|
1737
2158
|
}
|
|
1738
|
-
if (
|
|
1739
|
-
const
|
|
1740
|
-
|
|
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) {
|
|
1741
2175
|
return {
|
|
1742
2176
|
behavior: "deny",
|
|
1743
|
-
message: "
|
|
2177
|
+
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
1744
2178
|
};
|
|
1745
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 };
|
|
1746
2193
|
}
|
|
1747
|
-
if (toolName !== "AskUserQuestion") {
|
|
1748
|
-
return { behavior: "allow", updatedInput: input };
|
|
1749
|
-
}
|
|
1750
|
-
const questions = input.questions;
|
|
1751
|
-
const requestId = randomUUID();
|
|
1752
|
-
host.connection.emitStatus("waiting_for_input");
|
|
1753
|
-
host.connection.sendEvent({
|
|
1754
|
-
type: "tool_use",
|
|
1755
|
-
tool: "AskUserQuestion",
|
|
1756
|
-
input: JSON.stringify(input)
|
|
1757
|
-
});
|
|
1758
|
-
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
1759
|
-
const timeoutPromise = new Promise((resolve2) => {
|
|
1760
|
-
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
1761
|
-
});
|
|
1762
|
-
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
1763
|
-
host.connection.emitStatus("running");
|
|
1764
|
-
if (!answers) {
|
|
1765
|
-
return {
|
|
1766
|
-
behavior: "deny",
|
|
1767
|
-
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
1771
2194
|
};
|
|
1772
2195
|
}
|
|
1773
2196
|
function buildQueryOptions(host, context) {
|
|
1774
2197
|
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
1775
|
-
const
|
|
1776
|
-
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;
|
|
1777
2201
|
const systemPromptText = buildSystemPrompt(
|
|
1778
2202
|
host.config.mode,
|
|
1779
2203
|
context,
|
|
1780
2204
|
host.config,
|
|
1781
2205
|
host.setupLog,
|
|
1782
|
-
|
|
2206
|
+
mode
|
|
1783
2207
|
);
|
|
1784
2208
|
const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
|
|
1785
|
-
const
|
|
1786
|
-
const
|
|
1787
|
-
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];
|
|
1788
2212
|
const settingSources = settings.settingSources ?? ["user", "project"];
|
|
1789
2213
|
const hooks = {
|
|
1790
2214
|
PostToolUse: [
|
|
@@ -1897,10 +2321,10 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
1897
2321
|
}
|
|
1898
2322
|
async function runSdkQuery(host, context, followUpContent) {
|
|
1899
2323
|
if (host.isStopped()) return;
|
|
1900
|
-
const
|
|
1901
|
-
const
|
|
1902
|
-
const
|
|
1903
|
-
if (
|
|
2324
|
+
const mode = host.agentMode;
|
|
2325
|
+
const isPmMode = host.config.mode === "pm";
|
|
2326
|
+
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
2327
|
+
if (isDiscoveryLike) {
|
|
1904
2328
|
host.snapshotPlanFiles();
|
|
1905
2329
|
}
|
|
1906
2330
|
const options = buildQueryOptions(host, context);
|
|
@@ -1910,14 +2334,14 @@ async function runSdkQuery(host, context, followUpContent) {
|
|
|
1910
2334
|
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
1911
2335
|
(b) => b.type === "image"
|
|
1912
2336
|
);
|
|
1913
|
-
const textPrompt =
|
|
2337
|
+
const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode)}
|
|
1914
2338
|
|
|
1915
2339
|
---
|
|
1916
2340
|
|
|
1917
2341
|
The team says:
|
|
1918
2342
|
${followUpText}` : followUpText;
|
|
1919
2343
|
let prompt;
|
|
1920
|
-
if (
|
|
2344
|
+
if (isPmMode) {
|
|
1921
2345
|
prompt = buildMultimodalPrompt(textPrompt, context);
|
|
1922
2346
|
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
1923
2347
|
prompt.push(...followUpImages);
|
|
@@ -1932,10 +2356,10 @@ ${followUpText}` : followUpText;
|
|
|
1932
2356
|
options: { ...options, resume }
|
|
1933
2357
|
});
|
|
1934
2358
|
await runWithRetry(agentQuery, context, host, options);
|
|
1935
|
-
} else if (
|
|
2359
|
+
} else if (isDiscoveryLike) {
|
|
1936
2360
|
return;
|
|
1937
2361
|
} else {
|
|
1938
|
-
const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto);
|
|
2362
|
+
const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode);
|
|
1939
2363
|
const prompt = buildMultimodalPrompt(initialPrompt, context);
|
|
1940
2364
|
const agentQuery = query({
|
|
1941
2365
|
prompt: host.createInputStream(prompt),
|
|
@@ -1943,7 +2367,7 @@ ${followUpText}` : followUpText;
|
|
|
1943
2367
|
});
|
|
1944
2368
|
await runWithRetry(agentQuery, context, host, options);
|
|
1945
2369
|
}
|
|
1946
|
-
if (
|
|
2370
|
+
if (isDiscoveryLike) {
|
|
1947
2371
|
host.syncPlanFile();
|
|
1948
2372
|
}
|
|
1949
2373
|
}
|
|
@@ -1958,7 +2382,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1958
2382
|
);
|
|
1959
2383
|
}
|
|
1960
2384
|
const retryPrompt = buildMultimodalPrompt(
|
|
1961
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto),
|
|
2385
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
1962
2386
|
context,
|
|
1963
2387
|
lastErrorWasImage
|
|
1964
2388
|
);
|
|
@@ -1968,7 +2392,12 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1968
2392
|
});
|
|
1969
2393
|
})();
|
|
1970
2394
|
try {
|
|
1971
|
-
const { retriable, resultSummary } = await processEvents(
|
|
2395
|
+
const { retriable, resultSummary, modeRestart } = await processEvents(
|
|
2396
|
+
agentQuery,
|
|
2397
|
+
context,
|
|
2398
|
+
host
|
|
2399
|
+
);
|
|
2400
|
+
if (modeRestart) return;
|
|
1972
2401
|
if (!retriable || host.isStopped()) return;
|
|
1973
2402
|
lastErrorWasImage = IMAGE_ERROR_PATTERN.test(resultSummary ?? "");
|
|
1974
2403
|
} catch (error) {
|
|
@@ -1978,7 +2407,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1978
2407
|
context.claudeSessionId = null;
|
|
1979
2408
|
host.connection.storeSessionId("");
|
|
1980
2409
|
const freshPrompt = buildMultimodalPrompt(
|
|
1981
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto),
|
|
2410
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
1982
2411
|
context
|
|
1983
2412
|
);
|
|
1984
2413
|
const freshQuery = query({
|
|
@@ -2164,7 +2593,13 @@ var AgentRunner = class _AgentRunner {
|
|
|
2164
2593
|
planSync;
|
|
2165
2594
|
costTracker = new CostTracker();
|
|
2166
2595
|
worktreeActive = false;
|
|
2167
|
-
|
|
2596
|
+
agentMode = null;
|
|
2597
|
+
hasExitedPlanMode = false;
|
|
2598
|
+
pendingModeRestart = false;
|
|
2599
|
+
sessionIds = /* @__PURE__ */ new Map();
|
|
2600
|
+
lastQueryModeRestart = false;
|
|
2601
|
+
deferredStartConfig = null;
|
|
2602
|
+
startCommandStarted = false;
|
|
2168
2603
|
static MAX_SETUP_LOG_LINES = 50;
|
|
2169
2604
|
constructor(config, callbacks) {
|
|
2170
2605
|
this.config = config;
|
|
@@ -2175,6 +2610,18 @@ var AgentRunner = class _AgentRunner {
|
|
|
2175
2610
|
get state() {
|
|
2176
2611
|
return this._state;
|
|
2177
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
|
+
}
|
|
2178
2625
|
async setState(status) {
|
|
2179
2626
|
this._state = status;
|
|
2180
2627
|
this.connection.emitStatus(status);
|
|
@@ -2200,7 +2647,8 @@ var AgentRunner = class _AgentRunner {
|
|
|
2200
2647
|
this.connection.onChatMessage(
|
|
2201
2648
|
(message) => this.injectHumanMessage(message.content, message.files)
|
|
2202
2649
|
);
|
|
2203
|
-
this.connection.onModeChange((data) => this.handleModeChange(data.
|
|
2650
|
+
this.connection.onModeChange((data) => this.handleModeChange(data.agentMode));
|
|
2651
|
+
this.connection.onRunStartCommand(() => this.runDeferredStartCommand());
|
|
2204
2652
|
await this.setState("connected");
|
|
2205
2653
|
this.connection.sendEvent({ type: "connected", taskId: this.config.taskId });
|
|
2206
2654
|
this.startHeartbeat();
|
|
@@ -2239,6 +2687,9 @@ var AgentRunner = class _AgentRunner {
|
|
|
2239
2687
|
return;
|
|
2240
2688
|
}
|
|
2241
2689
|
this.taskContext._runnerSessionId = randomUUID2();
|
|
2690
|
+
if (this.taskContext.agentMode) {
|
|
2691
|
+
this.agentMode = this.taskContext.agentMode;
|
|
2692
|
+
}
|
|
2242
2693
|
this.logEffectiveSettings();
|
|
2243
2694
|
if (process.env.CODESPACES === "true") {
|
|
2244
2695
|
unshallowRepo(this.config.workspaceDir);
|
|
@@ -2275,18 +2726,30 @@ var AgentRunner = class _AgentRunner {
|
|
|
2275
2726
|
} catch {
|
|
2276
2727
|
}
|
|
2277
2728
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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;
|
|
2290
2753
|
}
|
|
2291
2754
|
await this.runCoreLoop();
|
|
2292
2755
|
this.stopHeartbeat();
|
|
@@ -2294,8 +2757,13 @@ var AgentRunner = class _AgentRunner {
|
|
|
2294
2757
|
this.connection.disconnect();
|
|
2295
2758
|
}
|
|
2296
2759
|
async runQuerySafe(context, followUp) {
|
|
2760
|
+
this.lastQueryModeRestart = false;
|
|
2297
2761
|
try {
|
|
2298
2762
|
await runSdkQuery(this.asQueryHost(), context, followUp);
|
|
2763
|
+
if (this.pendingModeRestart) {
|
|
2764
|
+
this.lastQueryModeRestart = true;
|
|
2765
|
+
this.pendingModeRestart = false;
|
|
2766
|
+
}
|
|
2299
2767
|
} catch (error) {
|
|
2300
2768
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2301
2769
|
this.connection.sendEvent({ type: "error", message });
|
|
@@ -2306,6 +2774,11 @@ var AgentRunner = class _AgentRunner {
|
|
|
2306
2774
|
async runCoreLoop() {
|
|
2307
2775
|
if (!this.taskContext) return;
|
|
2308
2776
|
while (!this.stopped) {
|
|
2777
|
+
if (this.lastQueryModeRestart) {
|
|
2778
|
+
this.lastQueryModeRestart = false;
|
|
2779
|
+
await this.handleAutoModeRestart();
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2309
2782
|
if (this._state === "idle") {
|
|
2310
2783
|
const msg = await this.waitForUserContent();
|
|
2311
2784
|
if (!msg) break;
|
|
@@ -2319,6 +2792,65 @@ var AgentRunner = class _AgentRunner {
|
|
|
2319
2792
|
}
|
|
2320
2793
|
}
|
|
2321
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
|
+
}
|
|
2322
2854
|
async runSetupSafe() {
|
|
2323
2855
|
await this.setState("setup");
|
|
2324
2856
|
const ports = await loadForwardPorts(this.config.workspaceDir);
|
|
@@ -2340,7 +2872,8 @@ var AgentRunner = class _AgentRunner {
|
|
|
2340
2872
|
await this.executeSetupConfig(config);
|
|
2341
2873
|
const setupEvent = {
|
|
2342
2874
|
type: "setup_complete",
|
|
2343
|
-
previewPort: config.previewPort ?? void 0
|
|
2875
|
+
previewPort: config.previewPort ?? void 0,
|
|
2876
|
+
startCommandDeferred: this.deferredStartConfig !== null ? true : void 0
|
|
2344
2877
|
};
|
|
2345
2878
|
this.connection.sendEvent(setupEvent);
|
|
2346
2879
|
await this.callbacks.onEvent(setupEvent);
|
|
@@ -2392,12 +2925,33 @@ The agent cannot start until this is resolved.`
|
|
|
2392
2925
|
this.pushSetupLog("(exit 0)");
|
|
2393
2926
|
}
|
|
2394
2927
|
if (config.startCommand) {
|
|
2395
|
-
this.
|
|
2396
|
-
|
|
2397
|
-
this.
|
|
2398
|
-
}
|
|
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
|
+
}
|
|
2399
2937
|
}
|
|
2400
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
|
+
}
|
|
2401
2955
|
injectHumanMessage(content, files) {
|
|
2402
2956
|
let messageContent;
|
|
2403
2957
|
if (files?.length) {
|
|
@@ -2510,28 +3064,59 @@ ${f.content}
|
|
|
2510
3064
|
}
|
|
2511
3065
|
}
|
|
2512
3066
|
asQueryHost() {
|
|
2513
|
-
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;
|
|
2514
3077
|
return {
|
|
2515
3078
|
config: this.config,
|
|
2516
3079
|
connection: this.connection,
|
|
2517
3080
|
callbacks: this.callbacks,
|
|
2518
3081
|
setupLog: this.setupLog,
|
|
2519
3082
|
costTracker: this.costTracker,
|
|
2520
|
-
get
|
|
2521
|
-
return
|
|
3083
|
+
get agentMode() {
|
|
3084
|
+
return getEffectiveAgentMode();
|
|
2522
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,
|
|
2523
3102
|
isStopped: () => this.stopped,
|
|
2524
3103
|
createInputStream: (prompt) => this.createInputStream(prompt),
|
|
2525
3104
|
snapshotPlanFiles: () => this.planSync.snapshotPlanFiles(),
|
|
2526
|
-
syncPlanFile: () => this.planSync.syncPlanFile()
|
|
3105
|
+
syncPlanFile: () => this.planSync.syncPlanFile(),
|
|
3106
|
+
onModeTransition: (newMode) => {
|
|
3107
|
+
this.agentMode = newMode;
|
|
3108
|
+
}
|
|
2527
3109
|
};
|
|
2528
3110
|
}
|
|
2529
|
-
handleModeChange(
|
|
3111
|
+
handleModeChange(newAgentMode) {
|
|
2530
3112
|
if (this.config.mode !== "pm") return;
|
|
2531
|
-
|
|
2532
|
-
|
|
3113
|
+
if (newAgentMode) {
|
|
3114
|
+
this.agentMode = newAgentMode;
|
|
3115
|
+
}
|
|
3116
|
+
const effectiveMode = this.effectiveAgentMode;
|
|
3117
|
+
this.connection.emitModeChanged(effectiveMode);
|
|
2533
3118
|
this.connection.postChatMessage(
|
|
2534
|
-
`Mode switched to **${
|
|
3119
|
+
`Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
|
|
2535
3120
|
);
|
|
2536
3121
|
}
|
|
2537
3122
|
stop() {
|
|
@@ -2788,6 +3373,7 @@ var ProjectRunner = class {
|
|
|
2788
3373
|
CONVEYOR_MODE: mode,
|
|
2789
3374
|
CONVEYOR_WORKSPACE: workDir,
|
|
2790
3375
|
CONVEYOR_USE_WORKTREE: "false",
|
|
3376
|
+
CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
|
|
2791
3377
|
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
2792
3378
|
CONVEYOR_USE_SANDBOX: useSandbox === false ? "false" : "true"
|
|
2793
3379
|
},
|
|
@@ -2967,4 +3553,4 @@ export {
|
|
|
2967
3553
|
ProjectRunner,
|
|
2968
3554
|
FileCache
|
|
2969
3555
|
};
|
|
2970
|
-
//# sourceMappingURL=chunk-
|
|
3556
|
+
//# sourceMappingURL=chunk-Y4TAVPZV.js.map
|