@rallycry/conveyor-agent 7.3.2 → 7.3.4

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.
@@ -28,6 +28,7 @@ var AgentConnection = class _AgentConnection {
28
28
  recentMessages = [];
29
29
  static DEDUP_WINDOW_MS = 3e4;
30
30
  static DEDUP_SIMILARITY_THRESHOLD = 0.7;
31
+ static DEDUP_PREVIEW_LIMIT = 120;
31
32
  // Early-buffering: events that arrive before callbacks are registered
32
33
  earlyMessages = [];
33
34
  earlyStop = false;
@@ -276,18 +277,43 @@ var AgentConnection = class _AgentConnection {
276
277
  }
277
278
  postChatMessage(content) {
278
279
  if (!this.socket) return;
279
- if (this.isDuplicateMessage(content)) {
280
- process.stderr.write(`[dedup] Suppressed near-duplicate message
281
- `);
282
- return;
283
- }
280
+ if (this.suppressIfDuplicate(content)) return;
284
281
  void this.call("postAgentMessage", {
285
282
  sessionId: this.config.sessionId,
286
283
  content
287
284
  }).catch(() => {
288
285
  });
289
286
  }
290
- isDuplicateMessage(content) {
287
+ // Awaitable variant of postChatMessage for callers that need to guarantee
288
+ // the message is acknowledged by the server before proceeding (e.g. before
289
+ // aborting the session). Dedup still applies; a suppressed message resolves
290
+ // immediately without hitting the wire.
291
+ async postChatMessageAwait(content) {
292
+ if (!this.socket) return;
293
+ if (this.suppressIfDuplicate(content)) return;
294
+ try {
295
+ await this.call("postAgentMessage", {
296
+ sessionId: this.config.sessionId,
297
+ content
298
+ });
299
+ } catch (err) {
300
+ process.stderr.write(
301
+ `[conveyor-agent] postChatMessageAwait failed: ${err instanceof Error ? err.message : String(err)}
302
+ `
303
+ );
304
+ }
305
+ }
306
+ suppressIfDuplicate(content) {
307
+ const d = this.checkAndTrackDuplicate(content);
308
+ if (!d.duplicate) return false;
309
+ process.stderr.write(
310
+ `[dedup] Suppressed near-duplicate (matched: "${d.matchedMessagePreview}")
311
+ `
312
+ );
313
+ return true;
314
+ }
315
+ // Exposed so `post_to_chat` can surface suppression back to the agent.
316
+ checkAndTrackDuplicate(content) {
291
317
  const now = Date.now();
292
318
  this.recentMessages = this.recentMessages.filter(
293
319
  (m) => now - m.timestamp < _AgentConnection.DEDUP_WINDOW_MS
@@ -295,22 +321,20 @@ var AgentConnection = class _AgentConnection {
295
321
  const words = new Set(
296
322
  content.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length >= 3)
297
323
  );
298
- if (words.size === 0) return false;
324
+ if (words.size === 0) return { duplicate: false };
299
325
  for (const recent of this.recentMessages) {
300
326
  let intersection = 0;
301
- for (const w of words) {
302
- if (recent.words.has(w)) intersection++;
303
- }
327
+ for (const w of words) if (recent.words.has(w)) intersection++;
304
328
  const union = (/* @__PURE__ */ new Set([...words, ...recent.words])).size;
305
329
  if (union > 0 && intersection / union > _AgentConnection.DEDUP_SIMILARITY_THRESHOLD) {
306
- return true;
330
+ return { duplicate: true, matchedMessagePreview: recent.preview };
307
331
  }
308
332
  }
309
- this.recentMessages.push({ words, timestamp: now });
310
- if (this.recentMessages.length > 3) {
311
- this.recentMessages.shift();
312
- }
313
- return false;
333
+ const max = _AgentConnection.DEDUP_PREVIEW_LIMIT;
334
+ const preview = content.length > max ? content.slice(0, max) + "\u2026" : content;
335
+ this.recentMessages.push({ words, timestamp: now, preview });
336
+ if (this.recentMessages.length > 3) this.recentMessages.shift();
337
+ return { duplicate: false };
314
338
  }
315
339
  sendHeartbeat() {
316
340
  if (!this.socket) return;
@@ -332,21 +356,20 @@ var AgentConnection = class _AgentConnection {
332
356
  emitModeChanged(agentMode) {
333
357
  this.sendEvent({ type: "mode_changed", agentMode });
334
358
  }
335
- updateTaskFields(fields) {
336
- if (!this.socket) return;
337
- void this.call("updateTaskFields", {
338
- sessionId: this.config.sessionId,
339
- ...fields
340
- }).catch(() => {
341
- });
359
+ async updateTaskFields(fields) {
360
+ if (!this.socket) return { ok: false, error: "socket not connected" };
361
+ try {
362
+ await this.call("updateTaskFields", { sessionId: this.config.sessionId, ...fields });
363
+ return { ok: true };
364
+ } catch (err) {
365
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
366
+ }
342
367
  }
343
368
  storeSessionId(sdkSessionId) {
344
- if (!this.socket) return;
345
- void this.call("storeSessionId", {
346
- sessionId: this.config.sessionId,
347
- sdkSessionId
348
- }).catch(() => {
349
- });
369
+ void this.call("storeSessionId", { sessionId: this.config.sessionId, sdkSessionId }).catch(
370
+ () => {
371
+ }
372
+ );
350
373
  }
351
374
  // ── Typing indicators ───────────────────────────────────────────────
352
375
  sendTypingStart() {
@@ -411,6 +434,9 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
411
434
  triggerIdentification() {
412
435
  return this.call("triggerIdentification", { sessionId: this.config.sessionId });
413
436
  }
437
+ getCumulativeSpending() {
438
+ return this.call("getCumulativeSpending", { sessionId: this.config.sessionId });
439
+ }
414
440
  async refreshAuthToken() {
415
441
  const codespaceName = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
416
442
  const apiUrl = this.config.apiUrl;
@@ -924,8 +950,8 @@ var PlanSync = class {
924
950
  for (const file of readdirSync(plansDir).filter((f) => f.endsWith(".md"))) {
925
951
  try {
926
952
  const fullPath = join(plansDir, file);
927
- const stat = statSync(fullPath);
928
- this.planFileSnapshot.set(fullPath, stat.mtimeMs);
953
+ const stat2 = statSync(fullPath);
954
+ this.planFileSnapshot.set(fullPath, stat2.mtimeMs);
929
955
  } catch {
930
956
  continue;
931
957
  }
@@ -946,11 +972,11 @@ var PlanSync = class {
946
972
  for (const file of files) {
947
973
  const fullPath = join(plansDir, file);
948
974
  try {
949
- const stat = statSync(fullPath);
975
+ const stat2 = statSync(fullPath);
950
976
  const prevMtime = this.planFileSnapshot.get(fullPath);
951
- const isNew = prevMtime === void 0 || stat.mtimeMs > prevMtime;
952
- if (isNew && (!newest || stat.mtimeMs > newest.mtime)) {
953
- newest = { path: fullPath, mtime: stat.mtimeMs };
977
+ const isNew = prevMtime === void 0 || stat2.mtimeMs > prevMtime;
978
+ if (isNew && (!newest || stat2.mtimeMs > newest.mtime)) {
979
+ newest = { path: fullPath, mtime: stat2.mtimeMs };
954
980
  }
955
981
  } catch {
956
982
  continue;
@@ -959,31 +985,35 @@ var PlanSync = class {
959
985
  }
960
986
  return newest;
961
987
  }
962
- syncPlanFile() {
963
- if (this.lockedPlanFile) {
964
- try {
965
- const content = readFileSync(this.lockedPlanFile, "utf-8").trim();
966
- if (content) {
967
- this.connection.updateTaskFields({ plan: content });
968
- const fileName = this.lockedPlanFile.split("/").pop() ?? "plan";
969
- this.connection.postChatMessage(`Synced local plan file (${fileName}) to the task plan.`);
970
- }
971
- } catch {
972
- }
988
+ async syncPlanFile() {
989
+ const isInitialDetection = this.lockedPlanFile === null;
990
+ const target = this.lockedPlanFile ?? this.findNewestPlanFile()?.path ?? null;
991
+ if (!target) return;
992
+ if (isInitialDetection) {
993
+ this.lockedPlanFile = target;
994
+ }
995
+ const fileName = target.split("/").pop() ?? "plan";
996
+ let content;
997
+ try {
998
+ content = readFileSync(target, "utf-8").trim();
999
+ } catch (err) {
1000
+ const reason = err instanceof Error ? err.message : String(err);
1001
+ this.connection.postChatMessage(
1002
+ `Plan sync warning: could not read local plan file (${fileName}): ${reason}. The task plan was not updated.`
1003
+ );
973
1004
  return;
974
1005
  }
975
- const newest = this.findNewestPlanFile();
976
- if (newest) {
977
- this.lockedPlanFile = newest.path;
978
- const content = readFileSync(newest.path, "utf-8").trim();
979
- if (content) {
980
- this.connection.updateTaskFields({ plan: content });
981
- const fileName = newest.path.split("/").pop() ?? "plan";
982
- this.connection.postChatMessage(
983
- `Detected local plan file (${fileName}) and synced it to the task plan.`
984
- );
985
- }
1006
+ if (!content) return;
1007
+ const result = await this.connection.updateTaskFields({ plan: content });
1008
+ if (!result.ok) {
1009
+ this.connection.postChatMessage(
1010
+ `Plan sync failed: the local plan file (${fileName}) was not persisted to the task (${result.error ?? "unknown error"}). Retry update_task_plan or re-run ExitPlanMode.`
1011
+ );
1012
+ return;
986
1013
  }
1014
+ const verb = isInitialDetection ? "Detected local plan file" : "Synced local plan file";
1015
+ const suffix = isInitialDetection ? " and synced it to the task plan." : " to the task plan.";
1016
+ this.connection.postChatMessage(`${verb} (${fileName})${suffix}`);
987
1017
  }
988
1018
  };
989
1019
 
@@ -993,6 +1023,558 @@ import { existsSync } from "fs";
993
1023
  import { homedir } from "os";
994
1024
  import { join as join2 } from "path";
995
1025
 
1026
+ // ../shared/dist/index.js
1027
+ import { z } from "zod";
1028
+ import { z as z2 } from "zod";
1029
+ import { z as z3 } from "zod";
1030
+ var MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
1031
+ var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
1032
+ var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
1033
+ var AgentHeartbeatSchema = z.object({
1034
+ sessionId: z.string().optional(),
1035
+ timestamp: z.string(),
1036
+ status: z.enum(["active", "idle", "building"]),
1037
+ currentAction: z.string().optional()
1038
+ });
1039
+ var CreatePRInputSchema = z.object({
1040
+ title: z.string().min(1),
1041
+ body: z.string(),
1042
+ head: z.string().optional(),
1043
+ base: z.string().optional()
1044
+ });
1045
+ var PostToChatInputSchema = z.object({
1046
+ message: z.string().min(1),
1047
+ type: z.enum(["message", "question", "update"]).optional().default("message")
1048
+ });
1049
+ var GetTaskContextRequestSchema = z.object({
1050
+ sessionId: z.string(),
1051
+ includeHistory: z.boolean().optional().default(false)
1052
+ });
1053
+ var GetChatMessagesRequestSchema = z.object({
1054
+ sessionId: z.string(),
1055
+ limit: z.number().int().positive().optional().default(50),
1056
+ offset: z.number().int().nonnegative().optional().default(0)
1057
+ });
1058
+ var GetTaskFilesRequestSchema = z.object({
1059
+ sessionId: z.string()
1060
+ });
1061
+ var GetTaskFileRequestSchema = z.object({
1062
+ sessionId: z.string(),
1063
+ fileId: z.string()
1064
+ });
1065
+ var GetTaskRequestSchema = z.object({
1066
+ sessionId: z.string(),
1067
+ taskSlugOrId: z.string()
1068
+ });
1069
+ var GetCliHistoryRequestSchema = z.object({
1070
+ sessionId: z.string(),
1071
+ limit: z.number().int().positive().optional().default(100),
1072
+ source: z.enum(["agent", "application"]).optional()
1073
+ });
1074
+ var ListSubtasksRequestSchema = z.object({
1075
+ sessionId: z.string()
1076
+ });
1077
+ var GetDependenciesRequestSchema = z.object({
1078
+ sessionId: z.string()
1079
+ });
1080
+ var GetSuggestionsRequestSchema = z.object({
1081
+ sessionId: z.string(),
1082
+ status: z.string().optional(),
1083
+ limit: z.number().int().min(1).max(100).optional()
1084
+ });
1085
+ var CreatePullRequestRequestSchema = CreatePRInputSchema.extend({ sessionId: z.string() });
1086
+ var UpdateTaskStatusRequestSchema = z.object({
1087
+ sessionId: z.string(),
1088
+ status: z.string(),
1089
+ force: z.boolean().optional().default(false)
1090
+ });
1091
+ var StoreSessionIdRequestSchema = z.object({
1092
+ sessionId: z.string(),
1093
+ sdkSessionId: z.string()
1094
+ });
1095
+ var TrackSpendingRequestSchema = z.object({
1096
+ sessionId: z.string(),
1097
+ inputTokens: z.number().int().nonnegative(),
1098
+ outputTokens: z.number().int().nonnegative(),
1099
+ costUsd: z.number().nonnegative(),
1100
+ model: z.string()
1101
+ });
1102
+ var SessionStartRequestSchema = z.object({
1103
+ sessionId: z.string(),
1104
+ agentVersion: z.string(),
1105
+ capabilities: z.array(z.string())
1106
+ });
1107
+ var SessionStopRequestSchema = z.object({
1108
+ sessionId: z.string(),
1109
+ reason: z.string().optional()
1110
+ });
1111
+ var ConnectAgentRequestSchema = z.object({
1112
+ sessionId: z.string()
1113
+ });
1114
+ var ReportAgentStatusRequestSchema = z.object({
1115
+ sessionId: z.string(),
1116
+ status: z.string()
1117
+ });
1118
+ var NotifyAgentVersionRequestSchema = z.object({
1119
+ sessionId: z.string(),
1120
+ agentVersion: z.string()
1121
+ });
1122
+ var CreateSubtaskRequestSchema = z.object({
1123
+ sessionId: z.string(),
1124
+ title: z.string().min(1),
1125
+ description: z.string().optional(),
1126
+ plan: z.string().optional(),
1127
+ storyPointValue: z.number().int().positive().optional(),
1128
+ ordinal: z.number().int().nonnegative().optional()
1129
+ });
1130
+ var UpdateSubtaskRequestSchema = z.object({
1131
+ sessionId: z.string(),
1132
+ subtaskId: z.string(),
1133
+ title: z.string().min(1).optional(),
1134
+ description: z.string().optional(),
1135
+ plan: z.string().optional(),
1136
+ status: z.string().optional(),
1137
+ storyPointValue: z.number().int().positive().optional()
1138
+ });
1139
+ var DeleteSubtaskRequestSchema = z.object({
1140
+ sessionId: z.string(),
1141
+ subtaskId: z.string()
1142
+ });
1143
+ var GetTaskPropertiesRequestSchema = z.object({
1144
+ sessionId: z.string()
1145
+ });
1146
+ var GetCumulativeSpendingRequestSchema = z.object({
1147
+ sessionId: z.string()
1148
+ });
1149
+ var ModelUsageEntrySchema = z.object({
1150
+ model: z.string(),
1151
+ inputTokens: z.number().nonnegative(),
1152
+ outputTokens: z.number().nonnegative(),
1153
+ cacheReadInputTokens: z.number().nonnegative(),
1154
+ cacheCreationInputTokens: z.number().nonnegative(),
1155
+ costUSD: z.number().nonnegative()
1156
+ });
1157
+ var GetCumulativeSpendingResponseSchema = z.object({
1158
+ totalCostUsd: z.number().nonnegative(),
1159
+ modelUsage: z.array(ModelUsageEntrySchema)
1160
+ });
1161
+ var UpdateTaskFieldsRequestSchema = z.object({
1162
+ sessionId: z.string(),
1163
+ plan: z.string().optional(),
1164
+ description: z.string().optional()
1165
+ });
1166
+ var UpdateTaskPropertiesRequestSchema = z.object({
1167
+ sessionId: z.string(),
1168
+ title: z.string().optional(),
1169
+ storyPointValue: z.number().int().positive().optional(),
1170
+ tagIds: z.array(z.string()).optional(),
1171
+ tagNames: z.array(z.string()).optional(),
1172
+ githubPRUrl: z.string().url().optional(),
1173
+ githubBranch: z.string().optional()
1174
+ });
1175
+ var ListIconsRequestSchema = z.object({
1176
+ sessionId: z.string()
1177
+ });
1178
+ var GenerateTaskIconRequestSchema = z.object({
1179
+ sessionId: z.string(),
1180
+ prompt: z.string().min(1),
1181
+ aspectRatio: z.string().optional()
1182
+ });
1183
+ var SearchFaIconsRequestSchema = z.object({
1184
+ sessionId: z.string(),
1185
+ query: z.string().min(1),
1186
+ first: z.number().int().positive().optional()
1187
+ });
1188
+ var PickFaIconRequestSchema = z.object({
1189
+ sessionId: z.string(),
1190
+ fontAwesomeId: z.string().min(1),
1191
+ fontAwesomeStyle: z.string().optional()
1192
+ });
1193
+ var CreateFollowUpTaskRequestSchema = z.object({
1194
+ sessionId: z.string(),
1195
+ title: z.string().min(1),
1196
+ description: z.string().optional(),
1197
+ plan: z.string().optional(),
1198
+ storyPointValue: z.number().int().positive().optional()
1199
+ });
1200
+ var AddDependencyRequestSchema = z.object({
1201
+ sessionId: z.string(),
1202
+ dependsOnSlugOrId: z.string()
1203
+ });
1204
+ var RemoveDependencyRequestSchema = z.object({
1205
+ sessionId: z.string(),
1206
+ dependsOnSlugOrId: z.string()
1207
+ });
1208
+ var CreateSuggestionRequestSchema = z.object({
1209
+ sessionId: z.string(),
1210
+ title: z.string().min(1),
1211
+ description: z.string().optional(),
1212
+ tagNames: z.array(z.string()).optional()
1213
+ });
1214
+ var VoteSuggestionRequestSchema = z.object({
1215
+ sessionId: z.string(),
1216
+ suggestionId: z.string(),
1217
+ value: z.union([z.literal(1), z.literal(-1)])
1218
+ });
1219
+ var TriggerIdentificationRequestSchema = z.object({
1220
+ sessionId: z.string()
1221
+ });
1222
+ var SubmitCodeReviewResultRequestSchema = z.object({
1223
+ sessionId: z.string(),
1224
+ approved: z.boolean(),
1225
+ content: z.string()
1226
+ });
1227
+ var StartChildCloudBuildRequestSchema = z.object({
1228
+ sessionId: z.string(),
1229
+ childTaskId: z.string()
1230
+ });
1231
+ var StopChildBuildRequestSchema = z.object({
1232
+ sessionId: z.string(),
1233
+ childTaskId: z.string()
1234
+ });
1235
+ var ApproveAndMergePRRequestSchema = z.object({
1236
+ sessionId: z.string(),
1237
+ childTaskId: z.string()
1238
+ });
1239
+ var PostChildChatMessageRequestSchema = z.object({
1240
+ sessionId: z.string(),
1241
+ childTaskId: z.string(),
1242
+ message: z.string().min(1)
1243
+ });
1244
+ var UpdateChildStatusRequestSchema = z.object({
1245
+ sessionId: z.string(),
1246
+ childTaskId: z.string(),
1247
+ status: z.string()
1248
+ });
1249
+ var GetAgentStatusRequestSchema = z.object({
1250
+ taskId: z.string()
1251
+ });
1252
+ var GetUiCliHistoryRequestSchema = z.object({
1253
+ taskId: z.string()
1254
+ });
1255
+ var SendSoftStopRequestSchema = z.object({
1256
+ taskId: z.string().optional(),
1257
+ projectId: z.string().optional()
1258
+ });
1259
+ var FlushTaskQueueRequestSchema = z.object({
1260
+ taskId: z.string(),
1261
+ softStop: z.boolean().optional()
1262
+ });
1263
+ var FlushProjectQueueRequestSchema = z.object({
1264
+ projectId: z.string()
1265
+ });
1266
+ var CancelTaskQueuedMessageRequestSchema = z.object({
1267
+ taskId: z.string(),
1268
+ messageId: z.string()
1269
+ });
1270
+ var FlushSingleQueuedMessageRequestSchema = z.object({
1271
+ taskId: z.string(),
1272
+ messageId: z.string(),
1273
+ softStop: z.boolean().optional()
1274
+ });
1275
+ var FlushSingleProjectQueuedMessageRequestSchema = z.object({
1276
+ projectId: z.string(),
1277
+ index: z.number().int().nonnegative()
1278
+ });
1279
+ var AnswerAgentQuestionRequestSchema = z.object({
1280
+ taskId: z.string(),
1281
+ requestId: z.string(),
1282
+ answers: z.record(z.string(), z.string())
1283
+ });
1284
+ var ClearAgentTodosRequestSchema = z.object({
1285
+ taskId: z.string()
1286
+ });
1287
+ var AgentQuestionOptionSchema = z.object({
1288
+ label: z.string(),
1289
+ description: z.string(),
1290
+ preview: z.string().optional()
1291
+ });
1292
+ var AgentQuestionSchema = z.object({
1293
+ question: z.string(),
1294
+ header: z.string(),
1295
+ options: z.array(AgentQuestionOptionSchema),
1296
+ multiSelect: z.boolean().optional()
1297
+ });
1298
+ var AskUserQuestionRequestSchema = z.object({
1299
+ sessionId: z.string(),
1300
+ question: z.string().min(1),
1301
+ requestId: z.string().min(1),
1302
+ questions: z.array(AgentQuestionSchema).min(1)
1303
+ });
1304
+ var AgentEventSchema = z.object({
1305
+ type: z.string().min(1)
1306
+ }).catchall(z.unknown());
1307
+ var EmitAgentEventRequestSchema = z.object({
1308
+ sessionId: z.string(),
1309
+ events: z.array(AgentEventSchema).max(500)
1310
+ });
1311
+ var RefreshGithubTokenRequestSchema = z.object({
1312
+ sessionId: z.string()
1313
+ });
1314
+ var RefreshGithubTokenResponseSchema = z.object({
1315
+ token: z.string()
1316
+ });
1317
+ var CreatePRResponseSchema = z.object({
1318
+ prNumber: z.number().int().positive(),
1319
+ prUrl: z.string().url()
1320
+ });
1321
+ var PostToChatResponseSchema = z.object({
1322
+ messageId: z.string()
1323
+ });
1324
+ var UpdateTaskStatusResponseSchema = z.object({
1325
+ taskId: z.string(),
1326
+ status: z.string()
1327
+ });
1328
+ var StoreSessionIdResponseSchema = z.object({
1329
+ success: z.boolean()
1330
+ });
1331
+ var HeartbeatResponseSchema = z.object({
1332
+ acknowledged: z.boolean()
1333
+ });
1334
+ var SessionStartResponseSchema = z.object({
1335
+ sessionId: z.string(),
1336
+ startedAt: z.string()
1337
+ });
1338
+ var SessionStopResponseSchema = z.object({
1339
+ sessionId: z.string(),
1340
+ stoppedAt: z.string()
1341
+ });
1342
+ var DeleteSubtaskResponseSchema = z.object({
1343
+ deleted: z.boolean()
1344
+ });
1345
+ var RegisterProjectAgentResponseSchema = z.object({
1346
+ registered: z.boolean(),
1347
+ agentName: z.string(),
1348
+ agentInstructions: z.string(),
1349
+ model: z.string(),
1350
+ agentSettings: z.record(z.string(), z.unknown()).nullable(),
1351
+ branchSwitchCommand: z.string().nullable()
1352
+ });
1353
+ var RegisterProjectAgentRequestSchema = z2.object({
1354
+ projectId: z2.string(),
1355
+ capabilities: z2.array(z2.string())
1356
+ });
1357
+ var ProjectRunnerHeartbeatRequestSchema = z2.object({
1358
+ projectId: z2.string()
1359
+ });
1360
+ var ReportProjectAgentStatusRequestSchema = z2.object({
1361
+ projectId: z2.string(),
1362
+ status: z2.enum(["busy", "idle"]),
1363
+ activeChatId: z2.string().nullish(),
1364
+ activeTaskId: z2.string().nullish(),
1365
+ activeBranch: z2.string().nullish()
1366
+ });
1367
+ var DisconnectProjectRunnerRequestSchema = z2.object({
1368
+ projectId: z2.string()
1369
+ });
1370
+ var GetProjectAgentContextRequestSchema = z2.object({
1371
+ projectId: z2.string()
1372
+ });
1373
+ var GetProjectAgentContextByRoleRequestSchema = z2.object({
1374
+ projectId: z2.string(),
1375
+ role: z2.string()
1376
+ });
1377
+ var GetProjectFunctionConfigRequestSchema = z2.object({
1378
+ projectId: z2.string(),
1379
+ functionId: z2.string()
1380
+ });
1381
+ var GetProjectChatHistoryRequestSchema = z2.object({
1382
+ projectId: z2.string(),
1383
+ limit: z2.number().int().positive().optional().default(50),
1384
+ chatId: z2.string().optional()
1385
+ });
1386
+ var PostProjectAgentMessageRequestSchema = z2.object({
1387
+ projectId: z2.string(),
1388
+ content: z2.string().min(1),
1389
+ chatId: z2.string().optional()
1390
+ });
1391
+ var ListProjectTasksRequestSchema = z2.object({
1392
+ projectId: z2.string(),
1393
+ status: z2.string().optional(),
1394
+ assigneeId: z2.string().optional(),
1395
+ limit: z2.number().int().positive().optional().default(50)
1396
+ });
1397
+ var GetProjectTaskRequestSchema = z2.object({
1398
+ projectId: z2.string(),
1399
+ taskId: z2.string()
1400
+ });
1401
+ var SearchProjectTasksRequestSchema = z2.object({
1402
+ projectId: z2.string(),
1403
+ tagNames: z2.array(z2.string()).optional(),
1404
+ searchQuery: z2.string().optional(),
1405
+ statusFilters: z2.array(z2.string()).optional(),
1406
+ limit: z2.number().int().positive().optional().default(20)
1407
+ });
1408
+ var ListProjectTagsRequestSchema = z2.object({
1409
+ projectId: z2.string()
1410
+ });
1411
+ var GetProjectSummaryRequestSchema = z2.object({
1412
+ projectId: z2.string()
1413
+ });
1414
+ var CreateProjectTaskRequestSchema = z2.object({
1415
+ projectId: z2.string(),
1416
+ title: z2.string().min(1),
1417
+ description: z2.string().optional(),
1418
+ plan: z2.string().optional(),
1419
+ status: z2.string().optional()
1420
+ });
1421
+ var UpdateProjectTaskRequestSchema = z2.object({
1422
+ projectId: z2.string(),
1423
+ taskId: z2.string(),
1424
+ title: z2.string().optional(),
1425
+ description: z2.string().optional(),
1426
+ plan: z2.string().optional(),
1427
+ status: z2.string().optional(),
1428
+ assignedUserId: z2.string().nullish()
1429
+ });
1430
+ var ReportProjectAgentEventRequestSchema = z2.object({
1431
+ projectId: z2.string(),
1432
+ event: z2.record(z2.string(), z2.unknown())
1433
+ });
1434
+ var ReportTagAuditProgressRequestSchema = z2.object({
1435
+ projectId: z2.string(),
1436
+ requestId: z2.string(),
1437
+ activity: z2.object({
1438
+ tool: z2.string(),
1439
+ input: z2.string().optional(),
1440
+ timestamp: z2.string()
1441
+ })
1442
+ });
1443
+ var ReportTagAuditResultRequestSchema = z2.object({
1444
+ projectId: z2.string(),
1445
+ requestId: z2.string(),
1446
+ recommendations: z2.array(z2.record(z2.string(), z2.unknown())),
1447
+ summary: z2.string(),
1448
+ complete: z2.boolean()
1449
+ });
1450
+ var ReportNewCommitsDetectedRequestSchema = z2.object({
1451
+ projectId: z2.string(),
1452
+ commits: z2.array(
1453
+ z2.object({
1454
+ sha: z2.string(),
1455
+ message: z2.string(),
1456
+ author: z2.string()
1457
+ })
1458
+ ),
1459
+ branch: z2.string()
1460
+ });
1461
+ var ReportEnvironmentReadyRequestSchema = z2.object({
1462
+ projectId: z2.string(),
1463
+ branch: z2.string(),
1464
+ setupComplete: z2.boolean(),
1465
+ startCommandRunning: z2.boolean()
1466
+ });
1467
+ var ReportEnvSwitchProgressRequestSchema = z2.object({
1468
+ projectId: z2.string(),
1469
+ step: z2.string(),
1470
+ progress: z2.number(),
1471
+ message: z2.string().optional()
1472
+ });
1473
+ var ForwardProjectChatMessageRequestSchema = z2.object({
1474
+ projectId: z2.string(),
1475
+ chatId: z2.string(),
1476
+ content: z2.string().min(1),
1477
+ targetUserId: z2.string().optional()
1478
+ });
1479
+ var CancelQueuedProjectMessageRequestSchema = z2.object({
1480
+ projectId: z2.string(),
1481
+ index: z2.number().int().nonnegative()
1482
+ });
1483
+ var GetProjectCliHistoryRequestSchema = z2.object({
1484
+ projectId: z2.string(),
1485
+ limit: z2.number().int().positive().optional().default(50),
1486
+ source: z2.string().optional()
1487
+ });
1488
+ var PostToProjectTaskChatRequestSchema = z2.object({
1489
+ projectId: z2.string(),
1490
+ taskId: z2.string(),
1491
+ content: z2.string()
1492
+ });
1493
+ var GetProjectTaskCliRequestSchema = z2.object({
1494
+ projectId: z2.string(),
1495
+ taskId: z2.string(),
1496
+ limit: z2.number().int().positive().optional().default(50),
1497
+ source: z2.string().optional()
1498
+ });
1499
+ var StartProjectBuildRequestSchema = z2.object({
1500
+ projectId: z2.string(),
1501
+ taskId: z2.string()
1502
+ });
1503
+ var StopProjectBuildRequestSchema = z2.object({
1504
+ projectId: z2.string(),
1505
+ taskId: z2.string()
1506
+ });
1507
+ var ApproveProjectMergePRRequestSchema = z2.object({
1508
+ projectId: z2.string(),
1509
+ childTaskId: z2.string()
1510
+ });
1511
+ var StartTaskAuditRequestSchema = z3.object({
1512
+ projectId: z3.string(),
1513
+ taskIds: z3.array(z3.string()).min(1)
1514
+ });
1515
+ var ReportTaskAuditProgressRequestSchema = z3.object({
1516
+ projectId: z3.string(),
1517
+ requestId: z3.string(),
1518
+ taskId: z3.string(),
1519
+ activity: z3.object({
1520
+ tool: z3.string(),
1521
+ input: z3.string().optional(),
1522
+ timestamp: z3.string()
1523
+ })
1524
+ });
1525
+ var CreateProjectSuggestionRequestSchema = z3.object({
1526
+ projectId: z3.string(),
1527
+ title: z3.string().min(1),
1528
+ description: z3.string().optional(),
1529
+ tagNames: z3.array(z3.string()).optional()
1530
+ });
1531
+ var ReportTaskAuditBatchCompleteRequestSchema = z3.object({
1532
+ projectId: z3.string(),
1533
+ requestId: z3.string()
1534
+ });
1535
+ var ReportTaskAuditResultRequestSchema = z3.object({
1536
+ projectId: z3.string(),
1537
+ requestId: z3.string(),
1538
+ taskId: z3.string(),
1539
+ summary: z3.string(),
1540
+ turnGrades: z3.array(
1541
+ z3.object({
1542
+ turnIndex: z3.number(),
1543
+ phase: z3.enum(["planning", "building", "human"]),
1544
+ grade: z3.enum(["correct", "neutral", "blunder"]),
1545
+ reasoning: z3.string(),
1546
+ eventType: z3.string(),
1547
+ eventSummary: z3.string()
1548
+ })
1549
+ ),
1550
+ planningAccuracy: z3.number().nullable(),
1551
+ buildingAccuracy: z3.number().nullable(),
1552
+ humanAccuracy: z3.number().nullable(),
1553
+ planningCorrect: z3.number(),
1554
+ planningNeutral: z3.number(),
1555
+ planningBlunder: z3.number(),
1556
+ buildingCorrect: z3.number(),
1557
+ buildingNeutral: z3.number(),
1558
+ buildingBlunder: z3.number(),
1559
+ humanCorrect: z3.number(),
1560
+ humanNeutral: z3.number(),
1561
+ humanBlunder: z3.number(),
1562
+ humanEvaluations: z3.array(
1563
+ z3.object({
1564
+ messageIndex: z3.number(),
1565
+ rating: z3.number().int().min(-1).max(1),
1566
+ reasoning: z3.string()
1567
+ })
1568
+ ).optional().default([]),
1569
+ suggestionIds: z3.array(z3.string()),
1570
+ auditCostUsd: z3.number().nullable(),
1571
+ model: z3.string().nullable(),
1572
+ error: z3.string().optional()
1573
+ });
1574
+ var TASK_CHAT_HISTORY_LIMIT = 20;
1575
+ var PM_CHAT_HISTORY_LIMIT = 40;
1576
+ var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
1577
+
996
1578
  // src/execution/pack-runner-prompt.ts
997
1579
  function findLastAgentMessageIndex(history) {
998
1580
  for (let i = history.length - 1; i >= 0; i--) {
@@ -1040,7 +1622,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1040
1622
  `2. Evaluate children by status and dependency readiness:`,
1041
1623
  ` - "ReviewPR": Review and merge its PR with approve_and_merge_pr. (Highest priority)`,
1042
1624
  ` - If merge fails due to pending CI: post a status update to chat, state you are going idle.`,
1043
- ` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check. Escalate to team.`,
1625
+ ` - If merge fails due to failed CI: use get_execution_logs(childTaskId) to check. Escalate to team.`,
1044
1626
  ` - "InProgress": A Task Runner is actively working. Do nothing \u2014 wait.`,
1045
1627
  ` - "Open" + allDependenciesMet=true: Ready to fire. Use start_child_cloud_build.`,
1046
1628
  ` - "Open" + allDependenciesMet=false: Blocked \u2014 skip for now. Will be unblocked when deps complete.`,
@@ -1060,7 +1642,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1060
1642
  `- When NO dependencies are set on any children, fall back to ordinal order (one at a time). This preserves backward compatibility.`,
1061
1643
  `- After firing builds OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
1062
1644
  `- Do NOT attempt to write code yourself. Your role is coordination only.`,
1063
- `- 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.`,
1645
+ `- If a child is stuck in "InProgress" for an unusually long time, use get_execution_logs(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
1064
1646
  `- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
1065
1647
  `- list_subtasks returns PR info (githubPRNumber, githubPRUrl), agent assignment (agentId), and dependency info for each child \u2014 use this to verify readiness before firing builds.`,
1066
1648
  `- You can use read_task_chat to check for team messages.`
@@ -1124,7 +1706,6 @@ After addressing the feedback, resume your autonomous loop: call list_subtasks a
1124
1706
  }
1125
1707
 
1126
1708
  // src/execution/prompt-formatters.ts
1127
- var PM_CHAT_HISTORY_LIMIT = 40;
1128
1709
  function formatFileSize(bytes) {
1129
1710
  if (bytes === void 0) return "";
1130
1711
  if (bytes < 1024) return `${bytes}B`;
@@ -1146,7 +1727,7 @@ function formatChatFile(file) {
1146
1727
  }
1147
1728
  if (file.content && file.contentEncoding === "base64") {
1148
1729
  return [
1149
- `[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_task_file("${file.fileId}") to view]`
1730
+ `[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_attachment("${file.fileId}") to view]`
1150
1731
  ];
1151
1732
  }
1152
1733
  return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`];
@@ -1159,7 +1740,7 @@ function formatTaskFile(file) {
1159
1740
  if (file.content && file.contentEncoding === "base64") {
1160
1741
  const size = formatFileSize(file.fileSize);
1161
1742
  return [
1162
- `- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_task_file("${file.fileId}") to view]`
1743
+ `- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_attachment("${file.fileId}") to view]`
1163
1744
  ];
1164
1745
  }
1165
1746
  if (!file.content) {
@@ -1236,7 +1817,7 @@ function formatIncidents(incidents) {
1236
1817
  }
1237
1818
 
1238
1819
  // src/execution/tag-context-resolver.ts
1239
- import { readFile, readdir } from "fs/promises";
1820
+ import { readFile, readdir, stat } from "fs/promises";
1240
1821
  var TYPE_PRIORITY = { rule: 0, file: 1, folder: 2, doc: 3 };
1241
1822
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1242
1823
  ".png",
@@ -1271,6 +1852,10 @@ function isBinaryPath(filePath) {
1271
1852
  const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1272
1853
  return BINARY_EXTENSIONS.has(ext);
1273
1854
  }
1855
+ var fileContentCache = /* @__PURE__ */ new Map();
1856
+ var folderListingCache = /* @__PURE__ */ new Map();
1857
+ var fileReadCount = 0;
1858
+ var folderReadCount = 0;
1274
1859
  function getContextBudgetChars(_model, betas, runnerMode) {
1275
1860
  const contextWindow = betas?.some((b) => b.includes("context-1m")) ? 1e6 : 2e5;
1276
1861
  const budgetPct = runnerMode === "task" ? 0.15 : 0.25;
@@ -1279,21 +1864,50 @@ function getContextBudgetChars(_model, betas, runnerMode) {
1279
1864
  async function readFileContent(filePath, maxChars) {
1280
1865
  try {
1281
1866
  if (isBinaryPath(filePath)) return null;
1282
- const content = await readFile(filePath, "utf-8");
1283
- if (content.length > maxChars) {
1284
- const omitted = content.length - maxChars;
1285
- return content.slice(0, maxChars) + `
1867
+ let rawContent;
1868
+ let mtimeMs;
1869
+ try {
1870
+ const st = await stat(filePath);
1871
+ mtimeMs = st.mtimeMs;
1872
+ } catch {
1873
+ return null;
1874
+ }
1875
+ const cached = fileContentCache.get(filePath);
1876
+ if (cached && cached.mtimeMs === mtimeMs) {
1877
+ rawContent = cached.content;
1878
+ } else {
1879
+ rawContent = await readFile(filePath, "utf-8");
1880
+ fileReadCount++;
1881
+ fileContentCache.set(filePath, { mtimeMs, content: rawContent });
1882
+ }
1883
+ if (rawContent.length > maxChars) {
1884
+ const omitted = rawContent.length - maxChars;
1885
+ return rawContent.slice(0, maxChars) + `
1286
1886
  [... truncated, ${omitted} chars omitted]`;
1287
1887
  }
1288
- return content;
1888
+ return rawContent;
1289
1889
  } catch {
1290
1890
  return null;
1291
1891
  }
1292
1892
  }
1293
1893
  async function readFolderListing(folderPath) {
1294
1894
  try {
1895
+ let mtimeMs;
1896
+ try {
1897
+ const st = await stat(folderPath);
1898
+ mtimeMs = st.mtimeMs;
1899
+ } catch {
1900
+ return null;
1901
+ }
1902
+ const cached = folderListingCache.get(folderPath);
1903
+ if (cached && cached.mtimeMs === mtimeMs) {
1904
+ return cached.listing;
1905
+ }
1295
1906
  const entries = await readdir(folderPath);
1296
- return `Files: ${entries.join(", ")}`;
1907
+ folderReadCount++;
1908
+ const listing = `Files: ${entries.join(", ")}`;
1909
+ folderListingCache.set(folderPath, { mtimeMs, listing });
1910
+ return listing;
1297
1911
  } catch {
1298
1912
  return null;
1299
1913
  }
@@ -1414,6 +2028,18 @@ async function resolveTagContext(projectTags, taskTagIds, model, betas, runnerMo
1414
2028
  };
1415
2029
  }
1416
2030
 
2031
+ // src/execution/prompt-truncation.ts
2032
+ var PLAN_SOFT_CAP = 4e3;
2033
+ var PLAN_HEAD_CHARS = 3e3;
2034
+ var PLAN_TAIL_CHARS = 500;
2035
+ var PLAN_TRUNCATION_MARKER = "\n\n... plan truncated \u2014 call get_current_plan for full text ...\n\n";
2036
+ function truncatePlanForPrompt(plan) {
2037
+ if (plan.length <= PLAN_SOFT_CAP) return plan;
2038
+ const head = plan.slice(0, PLAN_HEAD_CHARS);
2039
+ const tail = plan.slice(plan.length - PLAN_TAIL_CHARS);
2040
+ return `${head}${PLAN_TRUNCATION_MARKER}${tail}`;
2041
+ }
2042
+
1417
2043
  // src/execution/mode-prompt.ts
1418
2044
  var SP_DESC_MAX_CHARS = 80;
1419
2045
  function truncateDescription(desc, maxChars) {
@@ -1473,7 +2099,19 @@ function buildExplorationMethodology() {
1473
2099
  `- Search first, read second: use grep/glob to locate relevant code, then read only the files that matter`,
1474
2100
  `- Never re-read a file already in your context \u2014 you have a large context window, scroll up instead`,
1475
2101
  `- Start with 3-5 critical files, form a hypothesis about the approach, then validate with targeted reads`,
1476
- `- Stop exploring when you can write specific file paths and function names in your plan \u2014 that's enough`
2102
+ `- Stop exploring when you can cite specific \`file.ts:line\` locations and function names for every step in your plan \u2014 that's enough`
2103
+ ];
2104
+ }
2105
+ function buildPlanCitationFormat() {
2106
+ return [
2107
+ ``,
2108
+ `### Plan Citation Format`,
2109
+ `Plans must ground each change in the code. For every step that touches code:`,
2110
+ `- Cite the exact location as \`path/from/repo/root.ts:lineNumber\` (e.g. \`packages/conveyor-agent/src/execution/mode-prompt.ts:129\`).`,
2111
+ `- Name the specific function, class, constant, or JSX element being touched.`,
2112
+ `- When behavior hinges on a short piece of code, quote 1\u20133 lines inline instead of paraphrasing.`,
2113
+ `- Ranges are fine for larger edits (\`foo.ts:120-145\`). Do not cite whole files without a line.`,
2114
+ `- If a file doesn't exist yet, write \`NEW: path/to/new-file.ts\` and describe the surrounding module it fits into.`
1477
2115
  ];
1478
2116
  }
1479
2117
  function buildDiscoveryPrompt(context, runnerMode) {
@@ -1493,15 +2131,16 @@ function buildDiscoveryPrompt(context, runnerMode) {
1493
2131
  `Your PRIMARY goal is to create a thorough plan. Complete these steps in order:`,
1494
2132
  `1. Read the task description and chat history \u2014 respond to what's been discussed`,
1495
2133
  `2. Investigate the codebase using the methodology below \u2014 search first, read targeted files`,
1496
- `3. Save a detailed plan via \`update_task\``,
2134
+ `3. Save a detailed plan via \`update_task_plan\``,
1497
2135
  `4. Set story points, tags, and title via \`update_task_properties\` (icon is set automatically)`,
1498
2136
  `5. Discuss the plan with the team if they're engaged, incorporate feedback`,
1499
2137
  `6. THEN call ExitPlanMode \u2014 it is the LAST step, not the first`,
1500
2138
  ...buildExplorationMethodology(),
2139
+ ...buildPlanCitationFormat(),
1501
2140
  ``,
1502
2141
  `### Self-Identification Tools`,
1503
2142
  `Use these MCP tools to set your own task properties:`,
1504
- `- \`update_task\` \u2014 save your plan and description`,
2143
+ `- \`update_task_plan\` \u2014 save your plan and description`,
1505
2144
  `- \`update_task_properties\` \u2014 set title, story points, and tags (any combination)`,
1506
2145
  `Note: Icons are assigned automatically during identification after planning is complete.`,
1507
2146
  ``,
@@ -1524,21 +2163,20 @@ function buildDiscoveryPrompt(context, runnerMode) {
1524
2163
  `### Subtask Plan Requirements`,
1525
2164
  `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1526
2165
  `- Plans should be multi-step implementation guides, not vague descriptions`,
1527
- `- Include specific file paths, function names, and code patterns to modify`,
2166
+ `- Include concrete \`file.ts:line\` citations, function/symbol names, and short code snippets where relevant`,
1528
2167
  `- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
1529
2168
  `- Include testing requirements and acceptance criteria`,
1530
2169
  `- Set \`storyPointValue\` based on estimated complexity`,
1531
2170
  ``,
1532
2171
  `### Plan Verification Requirements`,
1533
- `Every plan MUST include a **Testing / Verification** section enumerating:`,
1534
- `- The quality gates: \`bun run lint\`, \`bun run typecheck\`, \`bun run test\` (or scoped equivalents)`,
2172
+ `Every plan MUST include a **Testing / Verification** section so the build agent has a clear definition of "done". Enumerate:`,
2173
+ `- The quality gates the build agent should run: \`bun run lint\`, \`bun run typecheck\`, \`bun run test\` (or scoped equivalents)`,
1535
2174
  `- Any task-specific end-to-end checks (manual UI walk-through, API smoke test, migration dry-run, etc.)`,
1536
- `- For refactors: line-count / file-size expectations and a public-API-surface diff check`,
1537
- `This ensures executing agents and reviewers have a clear definition of "done" and prevents silent regressions.`,
2175
+ `You are NOT expected to run these gates yourself \u2014 discovery is read-only. Just describe them in the plan.`,
1538
2176
  ``,
1539
2177
  `### Completing Planning`,
1540
2178
  `Once ALL checklist items above are done, call the **ExitPlanMode** tool.`,
1541
- `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
2179
+ `- Required before ExitPlanMode will succeed: **plan** (via update_task_plan), **story points** (via update_task_properties), **title** (via update_task_properties)`,
1542
2180
  `- ExitPlanMode validates these properties and marks planning as complete`,
1543
2181
  `- It does NOT start building \u2014 the team controls when to switch to Build mode`,
1544
2182
  `- Do NOT call ExitPlanMode until you have thoroughly explored the codebase and saved a detailed plan`
@@ -1554,11 +2192,11 @@ function buildAutoPrompt(context, runnerMode) {
1554
2192
  ``,
1555
2193
  `### Phase 1: Discovery & Planning (current)`,
1556
2194
  `- You are in the SDK's plan mode \u2014 read-only access is enforced automatically`,
1557
- `- You have MCP tools for task properties: update_task, update_task_properties`,
2195
+ `- You have MCP tools for task properties: update_task_plan, update_task_properties`,
1558
2196
  ``,
1559
2197
  `### Required before transitioning:`,
1560
2198
  `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1561
- `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
2199
+ `1. **Plan** \u2014 Save a clear implementation plan using update_task_plan`,
1562
2200
  `2. **Story Points** \u2014 Assign via update_task_properties`,
1563
2201
  `3. **Title** \u2014 Set an accurate title via update_task_properties (if the current one is vague or "Untitled")`,
1564
2202
  ``,
@@ -1571,11 +2209,13 @@ function buildAutoPrompt(context, runnerMode) {
1571
2209
  `### Subtask Plan Requirements`,
1572
2210
  `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1573
2211
  `- Plans should be multi-step implementation guides, not vague descriptions`,
1574
- `- Include specific file paths, function names, and code patterns to modify`,
2212
+ `- Include concrete \`file.ts:line\` citations, function/symbol names, and short code snippets where relevant`,
1575
2213
  `- Reference existing implementations when relevant`,
1576
2214
  `- Include testing requirements and acceptance criteria`,
1577
2215
  `- Set \`storyPointValue\` based on estimated complexity`,
1578
2216
  ``,
2217
+ ...buildPlanCitationFormat(),
2218
+ ``,
1579
2219
  ...context?.isParentTask ? [
1580
2220
  ``,
1581
2221
  `### Parent Task Guidance`,
@@ -1645,7 +2285,7 @@ function buildReviewPrompt(context) {
1645
2285
  `You are reviewing and coordinating child tasks.`,
1646
2286
  `- Use \`list_subtasks\` to see current child task state and progress.`,
1647
2287
  `- For children in ReviewPR status: review their code quality and merge with \`approve_and_merge_pr\`.`,
1648
- `- For children with failing CI: check with \`get_task_cli(childTaskId)\` and escalate if stuck.`,
2288
+ `- For children with failing CI: check with \`get_execution_logs(childTaskId)\` and escalate if stuck.`,
1649
2289
  `- Fire next child builds with \`start_child_cloud_build\` when ready.`,
1650
2290
  `- Create follow-up tasks for issues discovered during review.`,
1651
2291
  ``,
@@ -1722,7 +2362,7 @@ Environment (ready, no setup required):`,
1722
2362
  `
1723
2363
  Workflow:`,
1724
2364
  `- You can draft and iterate on plans in .claude/plans/*.md \u2014 these files are automatically synced to the task.`,
1725
- `- You can also use update_task directly to save the plan to the task.`,
2365
+ `- You can also use update_task_plan directly to save the plan to the task.`,
1726
2366
  `- After saving the plan, end your turn with a summary reply (the team sees your responses in chat automatically). Do NOT attempt to execute the plan yourself.`,
1727
2367
  `- A separate task agent will handle execution after the team reviews and approves your plan.`
1728
2368
  ];
@@ -1749,7 +2389,7 @@ function buildActivePreamble(context, workspaceDir) {
1749
2389
  `You are an AI project manager in ACTIVE mode for the "${context.title}" project.`,
1750
2390
  `You have direct coding access to the repository at ${workspaceDir}.`,
1751
2391
  `You can edit files, run tests, and make commits.`,
1752
- `You still have access to all PM tools (subtasks, update_task, chat).`,
2392
+ `You still have access to all PM tools (subtasks, update_task_plan, chat).`,
1753
2393
  `
1754
2394
  Environment (ready, no setup required):`,
1755
2395
  `- Repository is cloned at your current working directory.`,
@@ -1876,17 +2516,26 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
1876
2516
  }
1877
2517
 
1878
2518
  // src/execution/prompt-builder.ts
1879
- var TASK_CHAT_HISTORY_LIMIT = 20;
1880
2519
  function findLastAgentMessageIndex2(history) {
1881
2520
  for (let i = history.length - 1; i >= 0; i--) {
1882
2521
  if (history[i].role === "assistant") return i;
1883
2522
  }
1884
2523
  return -1;
1885
2524
  }
2525
+ function messagesAfterCursor(history, lastSeenMessageId) {
2526
+ if (!lastSeenMessageId) return history;
2527
+ const idx = history.findIndex((m) => m.id === lastSeenMessageId);
2528
+ return idx === -1 ? history : history.slice(idx + 1);
2529
+ }
1886
2530
  function detectRelaunchScenario(context, trustChatHistory = false) {
2531
+ if (context.lastSeenMessageId) {
2532
+ const newMessages = messagesAfterCursor(context.chatHistory, context.lastSeenMessageId);
2533
+ const hasNewUserMessages2 = newMessages.some((m) => m.role === "user");
2534
+ return hasNewUserMessages2 ? "feedback_relaunch" : "idle_relaunch";
2535
+ }
1887
2536
  const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
1888
2537
  if (lastAgentIdx === -1) return "fresh";
1889
- const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId || trustChatHistory;
2538
+ const hasPriorWork = !!context.githubPRUrl || trustChatHistory;
1890
2539
  if (!hasPriorWork) return "fresh";
1891
2540
  const messagesAfterAgent = context.chatHistory.slice(lastAgentIdx + 1);
1892
2541
  const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
@@ -1925,7 +2574,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1925
2574
  parts.push(
1926
2575
  `
1927
2576
  You are in auto mode. A plan already exists for this task.`,
1928
- `Verify that story points, title, and tags are set via get_task_plan, then call ExitPlanMode immediately to transition to building.`,
2577
+ `Verify that story points, title, and tags are set via get_current_plan, then call ExitPlanMode immediately to transition to building.`,
1929
2578
  `Do NOT wait for team input \u2014 proceed autonomously.`
1930
2579
  );
1931
2580
  } else {
@@ -1947,13 +2596,14 @@ You are the project manager for this task.`,
1947
2596
  }
1948
2597
  function buildRelaunchWithSession(mode, context, agentMode, isAuto) {
1949
2598
  const scenario = detectRelaunchScenario(context);
1950
- if (!context.claudeSessionId || scenario === "fresh") return null;
2599
+ const hasPriorTurn = !!context.lastSeenMessageId || !!context.claudeSessionId;
2600
+ if (!hasPriorTurn || scenario === "fresh") return null;
1951
2601
  const parts = [];
1952
2602
  const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
1953
2603
  if (mode === "pm") {
1954
2604
  parts.push(...buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode));
1955
2605
  } else if (scenario === "feedback_relaunch") {
1956
- const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
2606
+ const newMessages = (context.lastSeenMessageId ? messagesAfterCursor(context.chatHistory, context.lastSeenMessageId) : context.chatHistory.slice(lastAgentIdx + 1)).filter((m) => m.role === "user");
1957
2607
  parts.push(
1958
2608
  `You have been relaunched with new feedback.`,
1959
2609
  `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
@@ -2024,7 +2674,7 @@ ${context.description}`);
2024
2674
  if (context.plan) {
2025
2675
  parts.push(`
2026
2676
  ## Plan
2027
- ${context.plan}`);
2677
+ ${truncatePlanForPrompt(context.plan)}`);
2028
2678
  }
2029
2679
  if (context.files && context.files.length > 0) {
2030
2680
  parts.push(`
@@ -2081,7 +2731,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2081
2731
  return [
2082
2732
  `You are operating autonomously. Begin planning immediately.`,
2083
2733
  `1. Search the codebase (grep/glob) to locate relevant files, then read the critical ones`,
2084
- `2. Draft a clear implementation plan and save it with update_task`,
2734
+ `2. Draft a clear implementation plan and save it with update_task_plan`,
2085
2735
  `3. Set story points, tags, and title (update_task_properties)`,
2086
2736
  `4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
2087
2737
  `Do NOT wait for team input \u2014 proceed autonomously.`
@@ -2093,7 +2743,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2093
2743
  `Review existing subtasks via \`list_subtasks\` and the chat history before taking action.`,
2094
2744
  `Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
2095
2745
  `Start planning now \u2014 explore the codebase, ask clarifying questions if needed, and propose a subtask breakdown. Do not wait for additional team input before engaging.`,
2096
- `When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
2746
+ `When you finish planning, save the plan with update_task_plan and end your turn. Your reply will be visible to the team in chat.`
2097
2747
  ];
2098
2748
  }
2099
2749
  if (isPm) {
@@ -2101,7 +2751,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2101
2751
  `You are the project manager for this task.`,
2102
2752
  `Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
2103
2753
  `Start planning now \u2014 explore the codebase, ask clarifying questions if needed, and draft a plan. Do not wait for additional team input before engaging; the initial message IS the team engaging.`,
2104
- `When you finish planning, save the plan with update_task and end your turn. Your reply summarizing the plan will be visible in chat. A separate task agent will execute the plan after review.`
2754
+ `When you finish planning, save the plan with update_task_plan and end your turn. Your reply summarizing the plan will be visible in chat. A separate task agent will execute the plan after review.`
2105
2755
  ];
2106
2756
  }
2107
2757
  const parts = [
@@ -2145,7 +2795,7 @@ Your plan has been approved. Address the feedback above, then begin implementing
2145
2795
  } else if (isAuto) {
2146
2796
  parts2.push(
2147
2797
  `
2148
- You are in auto mode. Address the feedback above \u2014 update the plan accordingly using update_task.`,
2798
+ You are in auto mode. Address the feedback above \u2014 update the plan accordingly using update_task_plan.`,
2149
2799
  `When the plan and all required properties are set, call ExitPlanMode to transition to building.`,
2150
2800
  `Do NOT wait for additional team input \u2014 the messages above ARE the team's input. Proceed autonomously.`
2151
2801
  );
@@ -2256,14 +2906,14 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
2256
2906
  }
2257
2907
 
2258
2908
  // src/tools/task-context-tools.ts
2259
- import { z } from "zod";
2909
+ import { z as z4 } from "zod";
2260
2910
  function buildReadTaskChatTool(connection) {
2261
2911
  return defineTool(
2262
2912
  "read_task_chat",
2263
- "Read recent human/user chat messages for a task. When to use: you need to see user instructions, questions, or feedback posted to the task chat. Omit task_id for the current task, or pass a child task ID to read a child's chat. When NOT to use: for agent reasoning, tool calls, or setup/dev-server output \u2014 use get_task_cli instead. Returns: JSON array of recent chat messages (default 20).",
2913
+ "Read recent human/user chat messages for a task. Omit task_id for the current task; pass a child ID for a child's chat. For agent logs use get_execution_logs.",
2264
2914
  {
2265
- limit: z.number().optional().describe("Number of recent messages to fetch (default 20)"),
2266
- task_id: z.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
2915
+ limit: z4.number().optional().describe("Number of recent messages to fetch (default 20)"),
2916
+ task_id: z4.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
2267
2917
  },
2268
2918
  async ({ limit, task_id }) => {
2269
2919
  try {
@@ -2284,10 +2934,10 @@ function buildReadTaskChatTool(connection) {
2284
2934
  { annotations: { readOnlyHint: true } }
2285
2935
  );
2286
2936
  }
2287
- function buildGetTaskPlanTool(connection) {
2937
+ function buildGetCurrentPlanTool(connection) {
2288
2938
  return defineTool(
2289
- "get_task_plan",
2290
- "Re-read the current task's plan. When to use: the user just said they updated the plan mid-session or explicitly asked you to re-read it. When NOT to use: to fetch general task info (title, status, description) \u2014 use get_task. The plan was already included in your initial context, so do not call this speculatively. Returns: the plan markdown string.",
2939
+ "get_current_plan",
2940
+ "Re-read the current task's plan. Use when the user updated the plan or asked you to re-read it \u2014 otherwise the plan is already in initial context. For task metadata use get_task.",
2291
2941
  {},
2292
2942
  async () => {
2293
2943
  try {
@@ -2305,9 +2955,9 @@ function buildGetTaskPlanTool(connection) {
2305
2955
  function buildGetTaskTool(connection) {
2306
2956
  return defineTool(
2307
2957
  "get_task",
2308
- "Look up any task by slug or ID. When to use: you need the current title, description, plan, status, branch, PR info, or story points for this or a related task. When NOT to use: to list child tasks (use list_subtasks), or to re-read only the current plan (use get_task_plan). Returns: JSON task object including id, slug, title, description, plan, status, branch, githubPRNumber, githubPRUrl, storyPointValue.",
2958
+ "Look up any task by slug or ID. Returns JSON with id, slug, title, description, plan, status, branch, githubPRNumber, githubPRUrl, storyPoints. For children use list_subtasks.",
2309
2959
  {
2310
- slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
2960
+ slug_or_id: z4.string().describe("The task slug (e.g. 'my-task') or CUID")
2311
2961
  },
2312
2962
  async ({ slug_or_id }) => {
2313
2963
  try {
@@ -2325,14 +2975,14 @@ function buildGetTaskTool(connection) {
2325
2975
  { annotations: { readOnlyHint: true } }
2326
2976
  );
2327
2977
  }
2328
- function buildGetTaskCliTool(connection) {
2978
+ function buildGetExecutionLogsTool(connection) {
2329
2979
  return defineTool(
2330
- "get_task_cli",
2331
- "Read CLI execution logs from a task \u2014 agent reasoning, tool calls, and setup/dev-server output. When to use: inspecting what another agent did, debugging build/setup output, or reviewing prior tool calls. Filter with source='agent' for reasoning and tool calls only, source='application' for setup/dev-server output only. When NOT to use: for human/user chat \u2014 use read_task_chat. Returns: newline-joined event log lines each prefixed with `[timestamp] [type]` and formatted content.",
2980
+ "get_execution_logs",
2981
+ "Read CLI execution logs \u2014 agent reasoning, tool calls, and setup/dev-server output. Filter via source='agent' or 'application'. For human chat use read_task_chat.",
2332
2982
  {
2333
- task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
2334
- source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
2335
- limit: z.number().optional().describe("Max number of log entries to return (default 50, max 500).")
2983
+ task_id: z4.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
2984
+ source: z4.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
2985
+ limit: z4.number().optional().describe("Max number of log entries to return (default 50, max 500).")
2336
2986
  },
2337
2987
  async ({ task_id, source, limit }) => {
2338
2988
  try {
@@ -2361,7 +3011,7 @@ function buildGetTaskCliTool(connection) {
2361
3011
  function buildListTaskFilesTool(connection) {
2362
3012
  return defineTool(
2363
3013
  "list_task_files",
2364
- "List all files attached to this task with metadata. When to use: before fetching a specific file, to see what is available and how large each is. When NOT to use: to read a file's contents \u2014 use get_task_file. Returns: JSON array of file metadata (id, name, mimeType, size, downloadUrl), plus inline image blocks for any attached images.",
3014
+ "List all files attached to this task with metadata. Use before fetching a specific file to see what is available and how large each is. For file contents use get_attachment.",
2365
3015
  {},
2366
3016
  async () => {
2367
3017
  try {
@@ -2388,11 +3038,11 @@ function buildListTaskFilesTool(connection) {
2388
3038
  { annotations: { readOnlyHint: true } }
2389
3039
  );
2390
3040
  }
2391
- function buildGetTaskFileTool(connection) {
3041
+ function buildGetAttachmentTool(connection) {
2392
3042
  return defineTool(
2393
- "get_task_file",
2394
- "Fetch one task file's content plus metadata by file ID. When to use: you have a file ID from list_task_files and need its content or download URL. Call list_task_files first to discover IDs and check sizes \u2014 large binaries may be truncated by the service's size limit. When NOT to use: to enumerate attachments \u2014 use list_task_files. Returns: JSON metadata; for images an inline image block is also returned, and text content is embedded inline when available.",
2395
- { fileId: z.string().describe("The file ID to retrieve") },
3043
+ "get_attachment",
3044
+ "Fetch one task file's content plus metadata by file ID. Call list_task_files first to discover IDs and check sizes \u2014 large binaries may be truncated by the service's size limit.",
3045
+ { fileId: z4.string().describe("The file ID to retrieve") },
2396
3046
  async ({ fileId }) => {
2397
3047
  try {
2398
3048
  const file = await connection.call("getTaskFile", {
@@ -2421,20 +3071,20 @@ function buildGetTaskFileTool(connection) {
2421
3071
  function buildTaskContextTools(connection) {
2422
3072
  return [
2423
3073
  buildReadTaskChatTool(connection),
2424
- buildGetTaskPlanTool(connection),
3074
+ buildGetCurrentPlanTool(connection),
2425
3075
  buildGetTaskTool(connection),
2426
- buildGetTaskCliTool(connection),
3076
+ buildGetExecutionLogsTool(connection),
2427
3077
  buildListTaskFilesTool(connection),
2428
- buildGetTaskFileTool(connection)
3078
+ buildGetAttachmentTool(connection)
2429
3079
  ];
2430
3080
  }
2431
3081
 
2432
3082
  // src/tools/dependency-suggestion-tools.ts
2433
- import { z as z2 } from "zod";
3083
+ import { z as z5 } from "zod";
2434
3084
  function buildGetDependenciesTool(connection) {
2435
3085
  return defineTool(
2436
3086
  "get_dependencies",
2437
- "Get this task's dependencies and their current met/unmet status. When to use: confirming that blockers have been merged before starting work, or investigating why a task cannot start. When NOT to use: to look up a specific task's state \u2014 use get_task. Returns: JSON array of dependency objects (slug, title, status, and whether the dependency is met \u2014 met means merged to dev).",
3087
+ "Get this task's dependencies and their met/unmet status (met = merged to dev). Use to confirm blockers merged, or see why a task cannot start. For task state use get_task.",
2438
3088
  {},
2439
3089
  async () => {
2440
3090
  try {
@@ -2454,12 +3104,12 @@ function buildGetDependenciesTool(connection) {
2454
3104
  function buildGetSuggestionsTool(connection) {
2455
3105
  return defineTool(
2456
3106
  "get_suggestions",
2457
- "List project suggestions sorted by vote score. When to use: seeing what the team thinks is important, finding a suggestion to vote on, or checking for duplicates before create_suggestion. Filter by status (Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled) or cap results with limit (default 20). When NOT to use: for task lists \u2014 suggestions are project-level idea records, not tasks. Returns: JSON array of suggestions with id, title, description, status, score.",
3107
+ "List project suggestions sorted by vote score. Filter by status or cap with limit (default 20). Suggestions are project-level ideas, not tasks \u2014 use get_task for tasks.",
2458
3108
  {
2459
- status: z2.string().optional().describe(
3109
+ status: z5.string().optional().describe(
2460
3110
  "Filter by status: Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled"
2461
3111
  ),
2462
- limit: z2.number().int().min(1).max(100).optional().describe("Max results (default 20)")
3112
+ limit: z5.number().int().min(1).max(100).optional().describe("Max results (default 20)")
2463
3113
  },
2464
3114
  async ({ status, limit }) => {
2465
3115
  try {
@@ -2483,14 +3133,14 @@ function buildGetSuggestionsTool(connection) {
2483
3133
  }
2484
3134
 
2485
3135
  // src/tools/mutation-tools.ts
2486
- import { z as z3 } from "zod";
3136
+ import { z as z6 } from "zod";
2487
3137
  function buildPostToChatTool(connection) {
2488
3138
  return defineTool(
2489
3139
  "post_to_chat",
2490
- "Post an out-of-band message to a task chat. When to use: the user explicitly asked for a status update that must appear in chat, or you need to message a child task's chat (pass its ID as task_id). When NOT to use: for normal responses \u2014 your regular replies already appear in the current task's chat automatically. Returns: confirmation string.",
3140
+ "Post an out-of-band chat message to a task. Use only when explicitly asked, or to message a child's chat (pass its ID). Normal replies already appear in chat automatically.",
2491
3141
  {
2492
- message: z3.string().describe("The message to post to the team"),
2493
- task_id: z3.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
3142
+ message: z6.string().describe("The message to post to the team"),
3143
+ task_id: z6.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
2494
3144
  },
2495
3145
  async ({ message, task_id }) => {
2496
3146
  try {
@@ -2500,10 +3150,21 @@ function buildPostToChatTool(connection) {
2500
3150
  childTaskId: task_id,
2501
3151
  message
2502
3152
  });
2503
- return textResult(`Message posted to child task ${task_id} chat.`);
3153
+ return textResult(JSON.stringify({ posted: true, target: `child:${task_id}` }));
3154
+ }
3155
+ const dedup = connection.checkAndTrackDuplicate(message);
3156
+ if (dedup.duplicate) {
3157
+ return textResult(
3158
+ JSON.stringify({
3159
+ posted: false,
3160
+ reason: "duplicate",
3161
+ matchedMessagePreview: dedup.matchedMessagePreview,
3162
+ hint: "A near-identical message (>70% word overlap) was posted within the last 30s. Rephrase with new information or skip this post."
3163
+ })
3164
+ );
2504
3165
  }
2505
3166
  await connection.call("postToChat", { message });
2506
- return textResult("Message posted to task chat.");
3167
+ return textResult(JSON.stringify({ posted: true }));
2507
3168
  } catch (error) {
2508
3169
  return textResult(
2509
3170
  `Failed to post message: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -2515,10 +3176,10 @@ function buildPostToChatTool(connection) {
2515
3176
  function buildForceUpdateTaskStatusTool(connection) {
2516
3177
  return defineTool(
2517
3178
  "force_update_task_status",
2518
- "EMERGENCY ONLY: force-override a task's Kanban status. When to use: an automatic transition failed and the task is wedged \u2014 e.g. a PR was merged but status did not advance to ReviewDev, a build completed but status is still InProgress with no session running, or a child is stuck in ReviewPR after its PR was closed. When NOT to use: in normal flow \u2014 building, PR creation, and merge each transition status automatically. Omit task_id for the current task; pass a child task ID to update a child. Returns: confirmation string.",
3179
+ "EMERGENCY ONLY: force-override a task's Kanban status. Use when an automatic transition failed and the task is wedged. Normal flow transitions status automatically.",
2519
3180
  {
2520
- status: z3.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2521
- task_id: z3.string().optional().describe("Child task ID to update. Omit to update the current task.")
3181
+ status: z6.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
3182
+ task_id: z6.string().optional().describe("Child task ID to update. Omit to update the current task.")
2522
3183
  },
2523
3184
  async ({ status, task_id }) => {
2524
3185
  try {
@@ -2547,17 +3208,17 @@ function buildForceUpdateTaskStatusTool(connection) {
2547
3208
  function buildCreatePullRequestTool(connection, config) {
2548
3209
  return defineTool(
2549
3210
  "create_pull_request",
2550
- "Create a GitHub pull request for this task. Runs in order: 1) stage any uncommitted changes, 2) commit them (using commitMessage, or a default derived from title), 3) push to origin, 4) create the PR via GitHub API. When to use: opening the PR for this task \u2014 always use this instead of the gh CLI or raw git. Common failures: missing remote branch, a PR already open for this branch, or an expired git token (retry after the refresh). Returns: confirmation string with the new PR number and URL.",
3211
+ "Create a GitHub PR for this task. Auto-stages, commits (commitMessage or title default), pushes to origin, then opens the PR. Always use this instead of gh CLI or raw git.",
2551
3212
  {
2552
- title: z3.string().describe("The PR title"),
2553
- body: z3.string().describe("The PR description/body in markdown"),
2554
- branch: z3.string().optional().describe(
3213
+ title: z6.string().describe("The PR title"),
3214
+ body: z6.string().describe("The PR description/body in markdown"),
3215
+ branch: z6.string().optional().describe(
2555
3216
  "The head branch name for the PR. If the task doesn't have a branch set, this will be used. Defaults to the task's existing branch."
2556
3217
  ),
2557
- baseBranch: z3.string().optional().describe(
3218
+ baseBranch: z6.string().optional().describe(
2558
3219
  "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
2559
3220
  ),
2560
- commitMessage: z3.string().optional().describe(
3221
+ commitMessage: z6.string().optional().describe(
2561
3222
  "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
2562
3223
  )
2563
3224
  },
@@ -2633,9 +3294,9 @@ Troubleshooting:
2633
3294
  function buildAddDependencyTool(connection) {
2634
3295
  return defineTool(
2635
3296
  "add_dependency",
2636
- "Add a blocking dependency \u2014 this task cannot start until the named task is merged to dev. When to use: you discovered work that must land before this task can proceed. When NOT to use: to track work that should happen after this task \u2014 use create_follow_up_task instead. Returns: confirmation string.",
3297
+ "Add a blocking dependency \u2014 this task cannot start until the named task is merged to dev. For post-task follow-ups use create_follow_up_task instead.",
2637
3298
  {
2638
- depends_on_slug_or_id: z3.string().describe("Slug or ID of the task this task depends on")
3299
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task this task depends on")
2639
3300
  },
2640
3301
  async ({ depends_on_slug_or_id }) => {
2641
3302
  try {
@@ -2657,7 +3318,7 @@ function buildRemoveDependencyTool(connection) {
2657
3318
  "remove_dependency",
2658
3319
  "Remove a previously added dependency from this task. When to use: the dependency was added in error or is no longer relevant. Returns: confirmation string.",
2659
3320
  {
2660
- depends_on_slug_or_id: z3.string().describe("Slug or ID of the task to remove as dependency")
3321
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task to remove as dependency")
2661
3322
  },
2662
3323
  async ({ depends_on_slug_or_id }) => {
2663
3324
  try {
@@ -2677,12 +3338,12 @@ function buildRemoveDependencyTool(connection) {
2677
3338
  function buildCreateFollowUpTaskTool(connection) {
2678
3339
  return defineTool(
2679
3340
  "create_follow_up_task",
2680
- "Create a follow-up task that depends on the current task. When to use: out-of-scope work, v1.1 features, or cleanup that should happen after this task merges. The new task will be unblocked once the current task is merged. When NOT to use: to add a blocker to this task \u2014 use add_dependency instead. Returns: confirmation string with the new task's slug.",
3341
+ "Create a follow-up task that depends on the current task. Use for out-of-scope work or cleanup that should land after this task merges. For blockers use add_dependency.",
2681
3342
  {
2682
- title: z3.string().describe("Follow-up task title"),
2683
- description: z3.string().optional().describe("Brief description of the follow-up work"),
2684
- plan: z3.string().optional().describe("Implementation plan if known"),
2685
- story_point_value: z3.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
3343
+ title: z6.string().describe("Follow-up task title"),
3344
+ description: z6.string().optional().describe("Brief description of the follow-up work"),
3345
+ plan: z6.string().optional().describe("Implementation plan if known"),
3346
+ story_point_value: z6.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
2686
3347
  },
2687
3348
  async ({ title, description, plan, story_point_value }) => {
2688
3349
  try {
@@ -2707,13 +3368,13 @@ function buildCreateFollowUpTaskTool(connection) {
2707
3368
  function buildCreateSuggestionTool(connection) {
2708
3369
  return defineTool(
2709
3370
  "create_suggestion",
2710
- "Suggest a feature, improvement, rule, or idea for the project. When to use: recommending anything project-scoped \u2014 a document, a rule, a feature, a task, an optimization. If a similar suggestion already exists the service will dedupe and record your upvote instead. When NOT to use: for actionable work scoped to the current task \u2014 open a follow-up task. Returns: confirmation string with the suggestion id (and mergedIntoId if deduped).",
3371
+ "Suggest a feature, improvement, rule, or idea for the project. Duplicates are deduped and your upvote is recorded. For actionable work on this task open a follow-up task.",
2711
3372
  {
2712
- title: z3.string().describe("Short title for the suggestion"),
2713
- description: z3.string().optional().describe(
3373
+ title: z6.string().describe("Short title for the suggestion"),
3374
+ description: z6.string().optional().describe(
2714
3375
  "1-2 sentence description of what should change and why. Keep concise and project-focused."
2715
3376
  ),
2716
- tag_names: z3.array(z3.string()).optional().describe("Tag names to categorize the suggestion")
3377
+ tag_names: z6.array(z6.string()).optional().describe("Tag names to categorize the suggestion")
2717
3378
  },
2718
3379
  async ({ title, description, tag_names }) => {
2719
3380
  try {
@@ -2740,10 +3401,10 @@ function buildCreateSuggestionTool(connection) {
2740
3401
  function buildVoteSuggestionTool(connection) {
2741
3402
  return defineTool(
2742
3403
  "vote_suggestion",
2743
- "Vote +1 or -1 on a project suggestion. When to use: expressing support or disagreement with a specific suggestion returned from get_suggestions. Returns: confirmation string with the suggestion's updated score.",
3404
+ "Vote +1 or -1 on a project suggestion. Use to express support or disagreement with a specific suggestion returned by get_suggestions.",
2744
3405
  {
2745
- suggestion_id: z3.string().describe("The suggestion ID to vote on"),
2746
- value: z3.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
3406
+ suggestion_id: z6.string().describe("The suggestion ID to vote on"),
3407
+ value: z6.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
2747
3408
  },
2748
3409
  async ({ suggestion_id, value }) => {
2749
3410
  try {
@@ -2784,15 +3445,15 @@ function buildCommonTools(connection, config) {
2784
3445
  }
2785
3446
 
2786
3447
  // src/tools/pm-tools.ts
2787
- import { z as z4 } from "zod";
3448
+ import { z as z7 } from "zod";
2788
3449
  var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2789
3450
  function buildUpdateTaskTool(connection) {
2790
3451
  return defineTool(
2791
- "update_task",
2792
- "Save the finalized plan and/or description to the current task. When to use: in Plan mode, after reaching alignment with the user on the approach. When NOT to use: for child/subtasks \u2014 use update_subtask. This tool does not change status or other properties. Returns: confirmation string.",
3452
+ "update_task_plan",
3453
+ "Save the finalized plan and/or description to the current task. Use in Plan mode after alignment. For children use update_subtask; for title/tags/PR use update_task_properties.",
2793
3454
  {
2794
- plan: z4.string().optional().describe("The task plan in markdown"),
2795
- description: z4.string().optional().describe("Updated task description")
3455
+ plan: z7.string().optional().describe("The task plan in markdown"),
3456
+ description: z7.string().optional().describe("Updated task description")
2796
3457
  },
2797
3458
  async ({ plan, description }) => {
2798
3459
  try {
@@ -2811,13 +3472,13 @@ function buildUpdateTaskTool(connection) {
2811
3472
  function buildCreateSubtaskTool(connection) {
2812
3473
  return defineTool(
2813
3474
  "create_subtask",
2814
- "Create a subtask under the current parent task. When to use: breaking a complex parent task into smaller pieces during planning. When NOT to use: for follow-ups that depend on this task but are not children \u2014 use create_follow_up_task. Returns: confirmation string with the new subtask id.",
3475
+ "Create a subtask under the current parent task. Use when breaking a complex parent into smaller pieces during planning. For post-task follow-ups use create_follow_up_task.",
2815
3476
  {
2816
- title: z4.string().describe("Subtask title"),
2817
- description: z4.string().optional().describe("Brief description"),
2818
- plan: z4.string().optional().describe("Implementation plan in markdown"),
2819
- ordinal: z4.number().optional().describe("Step/order number (0-based)"),
2820
- storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
3477
+ title: z7.string().describe("Subtask title"),
3478
+ description: z7.string().optional().describe("Brief description"),
3479
+ plan: z7.string().optional().describe("Implementation plan in markdown"),
3480
+ ordinal: z7.number().optional().describe("Step/order number (0-based)"),
3481
+ storyPointValue: z7.number().optional().describe(SP_DESCRIPTION)
2821
3482
  },
2822
3483
  async ({ title, description, plan, ordinal, storyPointValue }) => {
2823
3484
  try {
@@ -2841,14 +3502,14 @@ function buildCreateSubtaskTool(connection) {
2841
3502
  function buildUpdateSubtaskTool(connection) {
2842
3503
  return defineTool(
2843
3504
  "update_subtask",
2844
- "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue). When to use: refining a child's plan or reordering the subtask queue. When NOT to use: to change status (pack tools do that automatically) or to update the current task itself (use update_task). Returns: confirmation string.",
3505
+ "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue). Use when refining a child's plan or reordering. For the current task use update_task_plan.",
2845
3506
  {
2846
- subtaskId: z4.string().describe("The subtask ID to update"),
2847
- title: z4.string().optional(),
2848
- description: z4.string().optional(),
2849
- plan: z4.string().optional(),
2850
- ordinal: z4.number().optional(),
2851
- storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
3507
+ subtaskId: z7.string().describe("The subtask ID to update"),
3508
+ title: z7.string().optional(),
3509
+ description: z7.string().optional(),
3510
+ plan: z7.string().optional(),
3511
+ ordinal: z7.number().optional(),
3512
+ storyPointValue: z7.number().optional().describe(SP_DESCRIPTION)
2852
3513
  },
2853
3514
  async ({ subtaskId, title, description, plan, storyPointValue }) => {
2854
3515
  try {
@@ -2871,7 +3532,7 @@ function buildDeleteSubtaskTool(connection) {
2871
3532
  return defineTool(
2872
3533
  "delete_subtask",
2873
3534
  "Delete a subtask by id. When to use: a subtask was created in error or is no longer needed. Returns: confirmation string.",
2874
- { subtaskId: z4.string().describe("The subtask ID to delete") },
3535
+ { subtaskId: z7.string().describe("The subtask ID to delete") },
2875
3536
  async ({ subtaskId }) => {
2876
3537
  try {
2877
3538
  await connection.call("deleteSubtask", {
@@ -2888,7 +3549,7 @@ function buildDeleteSubtaskTool(connection) {
2888
3549
  function buildListSubtasksTool(connection) {
2889
3550
  return defineTool(
2890
3551
  "list_subtasks",
2891
- "List all subtasks under the current parent task. When to use: coordinating child work \u2014 check who is running, which children are ready for review, or which are blocked. When NOT to use: for non-child related tasks \u2014 use get_task. Returns: JSON array with id, slug, title, status, storyPointValue, githubPRNumber, githubPRUrl, agentId, and plan for each child.",
3552
+ "List all subtasks under the current parent task. Use to coordinate child work \u2014 check who is running, ready for review, or blocked. For non-child tasks use get_task.",
2892
3553
  {},
2893
3554
  async () => {
2894
3555
  try {
@@ -2907,9 +3568,9 @@ function buildPackTools(connection) {
2907
3568
  return [
2908
3569
  defineTool(
2909
3570
  "start_child_cloud_build",
2910
- "Start a cloud build (codespace) for a child task. Preconditions: the child must be in Open status, have a story point value, and have an agent assigned. When NOT to use: if the child is already InProgress or past Open \u2014 it is already building or done. Returns: confirmation string including the child task id that the build was started for.",
3571
+ "Start a cloud build (codespace) for a child task. Preconditions: child is in Open status, has a story point value, and has an agent assigned.",
2911
3572
  {
2912
- childTaskId: z4.string().describe("The child task ID to start a cloud build for")
3573
+ childTaskId: z7.string().describe("The child task ID to start a cloud build for")
2913
3574
  },
2914
3575
  async ({ childTaskId }) => {
2915
3576
  try {
@@ -2927,9 +3588,9 @@ function buildPackTools(connection) {
2927
3588
  ),
2928
3589
  defineTool(
2929
3590
  "stop_child_build",
2930
- "Send a graceful stop signal to a running child build's agent. When to use: you decided the child should halt (plan changed, deadlock, scope pivot) and want the agent to exit cleanly. This is not a force-kill \u2014 the agent may take a moment to wind down. Returns: confirmation string.",
3591
+ "Send a graceful stop signal to a running child build's agent. Not a force-kill \u2014 the agent may take a moment to wind down.",
2931
3592
  {
2932
- childTaskId: z4.string().describe("The child task ID whose build should be stopped")
3593
+ childTaskId: z7.string().describe("The child task ID whose build should be stopped")
2933
3594
  },
2934
3595
  async ({ childTaskId }) => {
2935
3596
  try {
@@ -2947,9 +3608,9 @@ function buildPackTools(connection) {
2947
3608
  ),
2948
3609
  defineTool(
2949
3610
  "approve_and_merge_pr",
2950
- "Approve and merge a child task's PR. Preconditions: child is in ReviewPR and you have reviewed the diff. When NOT to use: if review surfaced issues \u2014 post to the child's chat or escalate instead. Returns: { childTaskId, prNumber, merged }. merged=true means the merge happened and status is now ReviewDev. merged=false means the merge is queued for GitHub automerge (CI still running) \u2014 do NOT proceed as if merged; wait for the child's status to transition to ReviewDev. If checks have failed, this call errors \u2014 investigate the failure.",
3611
+ "Approve and merge a child task's PR. Preconditions: child in ReviewPR. Returns { merged }: true = merged (status\u2192ReviewDev); false = automerge queued, wait for ReviewDev.",
2951
3612
  {
2952
- childTaskId: z4.string().describe("The child task ID whose PR should be approved and merged")
3613
+ childTaskId: z7.string().describe("The child task ID whose PR should be approved and merged")
2953
3614
  },
2954
3615
  async ({ childTaskId }) => {
2955
3616
  try {
@@ -2987,7 +3648,7 @@ function buildPmTools(connection, options) {
2987
3648
  }
2988
3649
 
2989
3650
  // src/tools/discovery-tools.ts
2990
- import { z as z5 } from "zod";
3651
+ import { z as z8 } from "zod";
2991
3652
  var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2992
3653
  function buildDiscoveryTools(connection) {
2993
3654
  return [
@@ -2995,11 +3656,11 @@ function buildDiscoveryTools(connection) {
2995
3656
  "update_task_properties",
2996
3657
  "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
2997
3658
  {
2998
- title: z5.string().optional().describe("The new task title"),
2999
- storyPointValue: z5.number().optional().describe(SP_DESCRIPTION2),
3000
- tagNames: z5.array(z5.string()).optional().describe("Array of tag names to assign"),
3001
- githubPRUrl: z5.string().url().optional().describe("GitHub pull request URL to link to this task"),
3002
- githubBranch: z5.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3659
+ title: z8.string().optional().describe("The new task title"),
3660
+ storyPointValue: z8.number().optional().describe(SP_DESCRIPTION2),
3661
+ tagNames: z8.array(z8.string()).optional().describe("Array of tag names to assign"),
3662
+ githubPRUrl: z8.string().url().optional().describe("GitHub pull request URL to link to this task"),
3663
+ githubBranch: z8.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3003
3664
  },
3004
3665
  async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
3005
3666
  try {
@@ -3030,14 +3691,14 @@ function buildDiscoveryTools(connection) {
3030
3691
  }
3031
3692
 
3032
3693
  // src/tools/code-review-tools.ts
3033
- import { z as z6 } from "zod";
3694
+ import { z as z9 } from "zod";
3034
3695
  function buildCodeReviewTools(connection) {
3035
3696
  return [
3036
3697
  defineTool(
3037
3698
  "approve_code_review",
3038
- "Approve the code review and exit. When to use: the diff passes all review criteria and is ready to merge. When NOT to use: if any substantive issue remains \u2014 call request_code_changes with a structured issues[] list. This tool takes only a summary; it does not accept an issues array (by design \u2014 an approval should have no blocking issues). Returns: confirmation string.",
3699
+ "Approve the code review and exit. Use when the diff passes all review criteria. Takes only a summary \u2014 for changes, use request_code_changes with a structured issues[] list.",
3039
3700
  {
3040
- summary: z6.string().describe("Brief summary of what was reviewed and why it looks good")
3701
+ summary: z9.string().describe("Brief summary of what was reviewed and why it looks good")
3041
3702
  },
3042
3703
  async ({ summary }) => {
3043
3704
  const content = `**Code Review: Approved** :white_check_mark:
@@ -3058,17 +3719,17 @@ ${summary}`;
3058
3719
  ),
3059
3720
  defineTool(
3060
3721
  "request_code_changes",
3061
- "Request changes during code review and exit. When to use: substantive issues were found that must be fixed before merge. Each entry in issues[] is { file: string, line?: number, severity: 'critical' | 'major' | 'minor', description: string }. When NOT to use: for an approval \u2014 use approve_code_review. Returns: confirmation string.",
3722
+ "Request changes during code review and exit. Use when substantive issues must be fixed before merge. Each issue: { file, line?, severity: critical|major|minor, description }.",
3062
3723
  {
3063
- issues: z6.array(
3064
- z6.object({
3065
- file: z6.string().describe("File path where the issue was found"),
3066
- line: z6.number().optional().describe("Line number (if applicable)"),
3067
- severity: z6.enum(["critical", "major", "minor"]).describe("Issue severity"),
3068
- description: z6.string().describe("What is wrong and how to fix it")
3724
+ issues: z9.array(
3725
+ z9.object({
3726
+ file: z9.string().describe("File path where the issue was found"),
3727
+ line: z9.number().optional().describe("Line number (if applicable)"),
3728
+ severity: z9.enum(["critical", "major", "minor"]).describe("Issue severity"),
3729
+ description: z9.string().describe("What is wrong and how to fix it")
3069
3730
  })
3070
3731
  ).describe("List of issues found during review"),
3071
- summary: z6.string().describe("Brief overall summary of the review findings")
3732
+ summary: z9.string().describe("Brief overall summary of the review findings")
3072
3733
  },
3073
3734
  async ({ issues, summary }) => {
3074
3735
  const issueLines = issues.map((issue) => {
@@ -3098,10 +3759,10 @@ ${issueLines}`;
3098
3759
  }
3099
3760
 
3100
3761
  // src/tools/debug-tools.ts
3101
- import { z as z9 } from "zod";
3762
+ import { z as z12 } from "zod";
3102
3763
 
3103
3764
  // src/tools/telemetry-tools.ts
3104
- import { z as z7 } from "zod";
3765
+ import { z as z10 } from "zod";
3105
3766
 
3106
3767
  // src/debug/telemetry-injector.ts
3107
3768
  var BUFFER_SIZE = 200;
@@ -3494,14 +4155,14 @@ function formatError(error) {
3494
4155
  function buildGetTelemetryTool(manager) {
3495
4156
  return defineTool(
3496
4157
  "debug_get_telemetry",
3497
- "Query structured telemetry events (HTTP requests, database queries, Socket.IO events, errors) captured from the running dev server. Returns filtered, structured data instead of raw logs.",
4158
+ "Query structured telemetry events (HTTP, DB, Socket.IO, errors) captured from the dev server. Returns filtered structured data instead of raw logs.",
3498
4159
  {
3499
- type: z7.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3500
- urlPattern: z7.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3501
- minDuration: z7.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3502
- errorOnly: z7.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3503
- since: z7.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3504
- limit: z7.number().optional().describe("Max events to return (default: 20, from most recent)")
4160
+ type: z10.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
4161
+ urlPattern: z10.string().optional().describe("Regex pattern to filter HTTP events by URL"),
4162
+ minDuration: z10.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
4163
+ errorOnly: z10.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
4164
+ since: z10.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
4165
+ limit: z10.number().optional().describe("Max events to return (default: 20, from most recent)")
3505
4166
  },
3506
4167
  async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3507
4168
  const clientOrErr = requireDebugClient(manager);
@@ -3571,7 +4232,7 @@ function buildTelemetryTools(manager) {
3571
4232
  }
3572
4233
 
3573
4234
  // src/tools/client-debug-tools.ts
3574
- import { z as z8 } from "zod";
4235
+ import { z as z11 } from "zod";
3575
4236
  function requirePlaywrightClient(manager) {
3576
4237
  if (!manager.isClientDebugMode()) {
3577
4238
  return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
@@ -3589,13 +4250,13 @@ function buildClientBreakpointTools(manager) {
3589
4250
  return [
3590
4251
  defineTool(
3591
4252
  "debug_set_client_breakpoint",
3592
- "Set a breakpoint in client-side code running in the headless Chromium browser. V8 resolves source maps automatically, so original .tsx/.ts file paths work. Use this for React components, client utilities, and browser-side code.",
4253
+ "Set a breakpoint in client-side code running in headless Chromium. V8 resolves source maps automatically \u2014 use original .tsx/.ts file paths.",
3593
4254
  {
3594
- file: z8.string().describe(
4255
+ file: z11.string().describe(
3595
4256
  "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3596
4257
  ),
3597
- line: z8.number().describe("Line number (1-based) in the original source file"),
3598
- condition: z8.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4258
+ line: z11.number().describe("Line number (1-based) in the original source file"),
4259
+ condition: z11.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3599
4260
  },
3600
4261
  async ({ file, line, condition }) => {
3601
4262
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3617,7 +4278,7 @@ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3617
4278
  "debug_remove_client_breakpoint",
3618
4279
  "Remove a previously set client-side breakpoint by its ID.",
3619
4280
  {
3620
- breakpointId: z8.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
4281
+ breakpointId: z11.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3621
4282
  },
3622
4283
  async ({ breakpointId }) => {
3623
4284
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3652,7 +4313,7 @@ function buildClientInspectionTools(manager) {
3652
4313
  return [
3653
4314
  defineTool(
3654
4315
  "debug_inspect_client_paused",
3655
- "When the client-side (browser) debugger is paused at a breakpoint, returns the call stack and local variables from the paused frame. When to use: after a client breakpoint hits, to see what is in scope in the browser. When NOT to use: for server pauses \u2014 use debug_inspect_paused. Returns: JSON with side='client', reason, hitBreakpoints, callStack, and localVariables. If the debugger paused between turns and has since resumed, queued breakpoint hits are returned instead.",
4316
+ "When the client (browser) debugger is paused, returns call stack and local variables. For server pauses use debug_inspect_paused. Queued hits returned if already resumed.",
3656
4317
  {},
3657
4318
  async () => {
3658
4319
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3695,10 +4356,10 @@ ${JSON.stringify(queuedHits, null, 2)}`
3695
4356
  ),
3696
4357
  defineTool(
3697
4358
  "debug_evaluate_client",
3698
- "Evaluate a JavaScript expression in the browser context. When paused at a client breakpoint, runs in the paused scope (use frameIndex to pick a frame); otherwise runs in the page's global scope with access to DOM, window, and React internals. Side effects are real \u2014 navigations, DOM mutations, and function calls execute. Prefer read-only expressions unless you specifically want to mutate page state. Returns: `(type) value`.",
4359
+ "Evaluate a JavaScript expression in the browser context. When paused, runs in the paused frame's scope; otherwise the page's global scope. Side effects execute \u2014 prefer read-only.",
3699
4360
  {
3700
- expression: z8.string().describe("JavaScript expression to evaluate in the browser context"),
3701
- frameIndex: z8.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4361
+ expression: z11.string().describe("JavaScript expression to evaluate in the browser context"),
4362
+ frameIndex: z11.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3702
4363
  },
3703
4364
  async ({ expression, frameIndex }) => {
3704
4365
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3769,9 +4430,9 @@ function buildClientInteractionTools(manager) {
3769
4430
  ),
3770
4431
  defineTool(
3771
4432
  "debug_navigate_client",
3772
- "Navigate the headless browser to a URL. When to use: reproducing a specific flow or visiting a different page. Waits for `domcontentloaded` before returning (Playwright's default ~30s navigation timeout applies). Returns: confirmation string with the resolved current URL.",
4433
+ "Navigate the headless browser to a URL. Waits for domcontentloaded (Playwright's default ~30s timeout applies).",
3773
4434
  {
3774
- url: z8.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
4435
+ url: z11.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3775
4436
  },
3776
4437
  async ({ url }) => {
3777
4438
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3786,9 +4447,9 @@ function buildClientInteractionTools(manager) {
3786
4447
  ),
3787
4448
  defineTool(
3788
4449
  "debug_click_client",
3789
- "Click an element in the headless browser by CSS selector. When to use: reproducing bugs by interacting with the UI programmatically. Playwright auto-waits for the element to be visible, stable, and enabled before clicking, up to a 10s timeout \u2014 a miss throws and is reported in the failure message. Returns: confirmation string.",
4450
+ "Click an element in the headless browser by CSS selector. Playwright auto-waits for visibility/stability/enabled up to 10s \u2014 a miss throws with a failure message.",
3790
4451
  {
3791
- selector: z8.string().describe(
4452
+ selector: z11.string().describe(
3792
4453
  "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3793
4454
  )
3794
4455
  },
@@ -3810,8 +4471,8 @@ function buildClientConsoleTool(manager) {
3810
4471
  "debug_get_client_console",
3811
4472
  "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3812
4473
  {
3813
- level: z8.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3814
- limit: z8.number().optional().describe("Maximum number of recent messages to return (default: all)")
4474
+ level: z11.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
4475
+ limit: z11.number().optional().describe("Maximum number of recent messages to return (default: all)")
3815
4476
  },
3816
4477
  // oxlint-disable-next-line require-await
3817
4478
  async ({ level, limit }) => {
@@ -3838,8 +4499,8 @@ function buildClientNetworkTool(manager) {
3838
4499
  "debug_get_client_network",
3839
4500
  "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3840
4501
  {
3841
- filter: z8.string().optional().describe("Regex pattern to filter requests by URL"),
3842
- limit: z8.number().optional().describe("Maximum number of recent requests to return (default: all)")
4502
+ filter: z11.string().optional().describe("Regex pattern to filter requests by URL"),
4503
+ limit: z11.number().optional().describe("Maximum number of recent requests to return (default: all)")
3843
4504
  },
3844
4505
  // oxlint-disable-next-line require-await
3845
4506
  async ({ filter, limit }) => {
@@ -3867,7 +4528,7 @@ function buildClientErrorsTool(manager) {
3867
4528
  "debug_get_client_errors",
3868
4529
  "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3869
4530
  {
3870
- limit: z8.number().optional().describe("Maximum number of recent errors to return (default: all)")
4531
+ limit: z11.number().optional().describe("Maximum number of recent errors to return (default: all)")
3871
4532
  },
3872
4533
  // oxlint-disable-next-line require-await
3873
4534
  async ({ limit }) => {
@@ -3959,14 +4620,14 @@ function buildDebugLifecycleTools(manager) {
3959
4620
  return [
3960
4621
  defineTool(
3961
4622
  "debug_enter_mode",
3962
- "Activate debug mode. When to use: at the start of a debug session before setting breakpoints or probes. Restarts the dev server with Node.js --inspect for server-side CDP, and/or launches a headless Chromium via Playwright for client-side. Default: if neither serverSide nor clientSide is passed, server-side only is activated. Pass clientSide=true for frontend debugging (previewUrl required), or set both for full-stack. Returns: activation status string listing active modes, an echoed hypothesis if given, and a warning if source maps are missing.",
4623
+ "Activate debug mode. Default: server-only (Node --inspect via CDP). Set clientSide=true (previewUrl required) or both flags for full-stack (adds headless Chromium via Playwright).",
3963
4624
  {
3964
- hypothesis: z9.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3965
- serverSide: z9.boolean().optional().describe(
4625
+ hypothesis: z12.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
4626
+ serverSide: z12.boolean().optional().describe(
3966
4627
  "Enable server-side Node.js debugging (default: true if clientSide is not set)"
3967
4628
  ),
3968
- clientSide: z9.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
3969
- previewUrl: z9.string().optional().describe(
4629
+ clientSide: z12.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
4630
+ previewUrl: z12.string().optional().describe(
3970
4631
  "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
3971
4632
  )
3972
4633
  },
@@ -4002,9 +4663,9 @@ function buildBreakpointTools(manager) {
4002
4663
  "debug_set_breakpoint",
4003
4664
  "Set a breakpoint at the specified file and line number. Optionally provide a condition expression that must evaluate to true for the breakpoint to pause execution.",
4004
4665
  {
4005
- file: z9.string().describe("Absolute or relative file path to set the breakpoint in"),
4006
- line: z9.number().describe("Line number (1-based) to set the breakpoint on"),
4007
- condition: z9.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4666
+ file: z12.string().describe("Absolute or relative file path to set the breakpoint in"),
4667
+ line: z12.number().describe("Line number (1-based) to set the breakpoint on"),
4668
+ condition: z12.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4008
4669
  },
4009
4670
  async ({ file, line, condition }) => {
4010
4671
  const clientOrErr = requireDebugClient2(manager);
@@ -4026,7 +4687,7 @@ Breakpoint ID: ${breakpointId}`
4026
4687
  "debug_remove_breakpoint",
4027
4688
  "Remove a previously set breakpoint by its ID.",
4028
4689
  {
4029
- breakpointId: z9.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4690
+ breakpointId: z12.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4030
4691
  },
4031
4692
  async ({ breakpointId }) => {
4032
4693
  const clientOrErr = requireDebugClient2(manager);
@@ -4105,10 +4766,10 @@ ${JSON.stringify(queuedHits, null, 2)}`
4105
4766
  ),
4106
4767
  defineTool(
4107
4768
  "debug_evaluate",
4108
- "Evaluate a JavaScript expression server-side in the Node process. When paused at a breakpoint the expression runs in that frame's scope (use frameIndex to pick a different frame); otherwise it runs in the global scope. Side effects are real \u2014 assignments, function calls, and I/O all execute. Prefer read-only expressions unless you specifically want to mutate state. Returns: `(type) value`.",
4769
+ "Evaluate a JavaScript expression server-side in the Node process. When paused, runs in the frame's scope (frameIndex selects frame). Side effects execute \u2014 prefer read-only.",
4109
4770
  {
4110
- expression: z9.string().describe("The JavaScript expression to evaluate"),
4111
- frameIndex: z9.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4771
+ expression: z12.string().describe("The JavaScript expression to evaluate"),
4772
+ frameIndex: z12.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4112
4773
  },
4113
4774
  async ({ expression, frameIndex }) => {
4114
4775
  const clientOrErr = requireDebugClient2(manager);
@@ -4134,14 +4795,14 @@ function buildProbeManagementTools(manager) {
4134
4795
  return [
4135
4796
  defineTool(
4136
4797
  "debug_add_probe",
4137
- "Add a debug probe at a specific code location. Captures expression values each time the line executes \u2014 without pausing or modifying source files. Like console.log but better: structured, no diff pollution, auto-cleaned on debug exit.",
4798
+ "Add a debug probe at a code location. Captures expression values each time the line executes, without pausing or modifying source. Auto-cleaned on debug exit.",
4138
4799
  {
4139
- file: z9.string().describe("File path to probe"),
4140
- line: z9.number().describe("Line number (1-based) to probe"),
4141
- expressions: z9.array(z9.string()).describe(
4800
+ file: z12.string().describe("File path to probe"),
4801
+ line: z12.number().describe("Line number (1-based) to probe"),
4802
+ expressions: z12.array(z12.string()).describe(
4142
4803
  'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4143
4804
  ),
4144
- label: z9.string().optional().describe("Optional label for this probe (defaults to file:line)")
4805
+ label: z12.string().optional().describe("Optional label for this probe (defaults to file:line)")
4145
4806
  },
4146
4807
  async ({ file, line, expressions, label }) => {
4147
4808
  const clientOrErr = requireDebugClient2(manager);
@@ -4166,7 +4827,7 @@ Trigger the code path, then use debug_get_probe_results to see captured values.`
4166
4827
  "debug_remove_probe",
4167
4828
  "Remove a previously set debug probe by its ID.",
4168
4829
  {
4169
- probeId: z9.string().describe("The probe ID returned by debug_add_probe")
4830
+ probeId: z12.string().describe("The probe ID returned by debug_add_probe")
4170
4831
  },
4171
4832
  async ({ probeId }) => {
4172
4833
  const clientOrErr = requireDebugClient2(manager);
@@ -4204,11 +4865,11 @@ function buildProbeResultTools(manager) {
4204
4865
  return [
4205
4866
  defineTool(
4206
4867
  "debug_get_probe_results",
4207
- "Fetch captured probe hit data. When to use: after triggering the code path for a probe set by debug_add_probe. Filtering: pass label to filter directly, or probeId to look up and filter by that probe's label; if both are passed, label wins and probeId is ignored. Returns: grouped text with per-probe hit count, timestamps, and captured expression values.",
4868
+ "Fetch captured probe hit data. Filter by label (wins) or probeId. Returns grouped text with per-probe hit count, timestamps, and captured expression values.",
4208
4869
  {
4209
- probeId: z9.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4210
- label: z9.string().optional().describe("Filter results by probe label"),
4211
- limit: z9.number().optional().describe("Maximum number of recent hits to return (default: all)")
4870
+ probeId: z12.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4871
+ label: z12.string().optional().describe("Filter results by probe label"),
4872
+ limit: z12.number().optional().describe("Maximum number of recent hits to return (default: all)")
4212
4873
  },
4213
4874
  async ({ probeId, label, limit }) => {
4214
4875
  const clientOrErr = requireDebugClient2(manager);
@@ -4802,7 +5463,7 @@ async function processEvents(events, context, host) {
4802
5463
  // src/execution/task-property-utils.ts
4803
5464
  function collectMissingProps(taskProps) {
4804
5465
  const missing = [];
4805
- if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
5466
+ if (!taskProps.plan?.trim()) missing.push("plan (save via update_task_plan)");
4806
5467
  if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
4807
5468
  if (!taskProps.title || taskProps.title === "Untitled")
4808
5469
  missing.push("title (use update_task_properties)");
@@ -4811,7 +5472,31 @@ function collectMissingProps(taskProps) {
4811
5472
 
4812
5473
  // src/execution/tool-access.ts
4813
5474
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
4814
- var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
5475
+ var DESTRUCTIVE_PATTERNS = [
5476
+ {
5477
+ name: "git push --force (without --force-with-lease)",
5478
+ re: /git\s+push\s+(?:-f\b|--force(?!-with-lease))/
5479
+ },
5480
+ { name: "git push --delete", re: /git\s+push\s+(?:-d\b|--delete\b)/ },
5481
+ { name: "git reset --hard", re: /git\s+reset\s+--hard\b/ },
5482
+ {
5483
+ name: "rm -rf /",
5484
+ re: /rm\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r|--recursive\s+--force)\s+\/(?!\S)/
5485
+ },
5486
+ { name: "sudo rm", re: /\bsudo\s+rm\b/ },
5487
+ { name: "chmod world-writable", re: /\bchmod\s+(?:-R\s+)?[0-7]*7{2,3}\b/ },
5488
+ { name: "dd to device", re: /\bdd\s+.*\bof=\/dev\// },
5489
+ { name: "redirect to block device", re: />\s*\/dev\/(?:sd[a-z]|nvme\d|xvd[a-z])/ },
5490
+ { name: "mkfs filesystem creation", re: /\bmkfs(?:\.|\s)/ },
5491
+ { name: "shutdown/poweroff/halt/reboot", re: /\b(?:shutdown|poweroff|halt|reboot)\b/ },
5492
+ { name: "fork bomb", re: /:\(\)\s*\{\s*:\|\s*:&\s*\}\s*;\s*:/ }
5493
+ ];
5494
+ function matchesDestructive(cmd) {
5495
+ for (const { name, re } of DESTRUCTIVE_PATTERNS) {
5496
+ if (re.test(cmd)) return name;
5497
+ }
5498
+ return null;
5499
+ }
4815
5500
  function isPlanFile(input) {
4816
5501
  const filePath = String(input.file_path ?? input.path ?? "");
4817
5502
  return filePath.includes(".claude/plans/");
@@ -4831,10 +5516,11 @@ function handleDiscoveryToolAccess(toolName, input) {
4831
5516
  function handleBuildingToolAccess(toolName, input) {
4832
5517
  if (toolName === "Bash") {
4833
5518
  const cmd = String(input.command ?? "");
4834
- if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
5519
+ const matched = matchesDestructive(cmd);
5520
+ if (matched) {
4835
5521
  return {
4836
5522
  behavior: "deny",
4837
- message: "Destructive operation blocked. Use safer alternatives."
5523
+ message: `Destructive operation blocked (${matched}). Use safer alternatives.`
4838
5524
  };
4839
5525
  }
4840
5526
  }
@@ -4849,13 +5535,32 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
4849
5535
  }
4850
5536
  return { behavior: "allow", updatedInput: input };
4851
5537
  }
5538
+ function enforceMissingProps(host, input, missingProps) {
5539
+ if (missingProps.length === 0) return null;
5540
+ if (input.bypassValidation !== true) {
5541
+ return {
5542
+ behavior: "deny",
5543
+ message: [
5544
+ "Cannot exit plan mode. Required task properties are missing:",
5545
+ ...missingProps.map((p) => `- ${p}`),
5546
+ "",
5547
+ "Fill these in using MCP tools (e.g. update_task_plan, update_task_properties), then call ExitPlanMode again.",
5548
+ "",
5549
+ "If you have a deliberate reason to proceed without them, you must explicitly bypass validation by calling ExitPlanMode with `bypassValidation: true` as a tool argument. Do not bypass unless the team has asked you to \u2014 it will be surfaced in chat."
5550
+ ].join("\n")
5551
+ };
5552
+ }
5553
+ host.connection.postChatMessage(
5554
+ `\u26A0\uFE0F [BYPASS] ExitPlanMode forced through with \`bypassValidation: true\` despite missing required properties: ${missingProps.join(", ")}. Please backfill these.`
5555
+ );
5556
+ return null;
5557
+ }
4852
5558
  async function handleExitPlanMode(host, input) {
4853
5559
  if (host.hasExitedPlanMode) {
4854
5560
  return { behavior: "allow", updatedInput: input };
4855
5561
  }
4856
- host.exitPlanAttempts++;
4857
5562
  try {
4858
- host.syncPlanFile();
5563
+ await host.syncPlanFile();
4859
5564
  const taskProps = await host.connection.getTaskProperties();
4860
5565
  const missingProps = collectMissingProps(taskProps);
4861
5566
  if (host.isParentTask) {
@@ -4875,30 +5580,22 @@ async function handleExitPlanMode(host, input) {
4875
5580
  } catch {
4876
5581
  }
4877
5582
  }
4878
- if (missingProps.length > 0) {
4879
- if (host.exitPlanAttempts <= 1) {
4880
- return {
4881
- behavior: "deny",
4882
- message: [
4883
- "Cannot exit plan mode yet. Required task properties are missing:",
4884
- ...missingProps.map((p) => `- ${p}`),
4885
- "",
4886
- "Fill these in using MCP tools, then try ExitPlanMode again."
4887
- ].join("\n")
4888
- };
4889
- }
4890
- host.connection.postChatMessage(
4891
- `\u26A0\uFE0F ExitPlanMode allowed with missing properties: ${missingProps.join(", ")}. Consider backfilling these later.`
4892
- );
4893
- }
5583
+ const gate = enforceMissingProps(host, input, missingProps);
5584
+ if (gate) return gate;
4894
5585
  if (host.agentMode === "discovery") {
4895
5586
  host.hasExitedPlanMode = true;
4896
5587
  host.discoveryCompleted = true;
4897
- host.requestStop();
4898
- void host.connection.triggerIdentification();
4899
- host.connection.postChatMessage(
4900
- "Plan complete. Running identification \u2014 icon and agent assignment will be set automatically."
5588
+ try {
5589
+ await host.connection.triggerIdentification();
5590
+ } catch (triggerErr) {
5591
+ host.connection.postChatMessage(
5592
+ `Identification trigger encountered an issue (${triggerErr instanceof Error ? triggerErr.message : "unknown error"}). Icon and agent assignment may use fallbacks.`
5593
+ );
5594
+ }
5595
+ await host.connection.postChatMessageAwait(
5596
+ "Planning complete \u2014 awaiting team approval. Icon and agent assignment will be set automatically."
4901
5597
  );
5598
+ host.requestStop();
4902
5599
  return { behavior: "allow", updatedInput: input };
4903
5600
  }
4904
5601
  try {
@@ -5017,6 +5714,65 @@ function buildCanUseTool(host) {
5017
5714
  };
5018
5715
  }
5019
5716
 
5717
+ // src/execution/redactor.ts
5718
+ var REDACTED = "<redacted>";
5719
+ var BEARER_RE = /\b(Bearer\s+)[A-Za-z0-9_\-.]{20,}/g;
5720
+ var VENDOR_KEY_RE = /\b(?:sk-[a-zA-Z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|xoxb-[A-Za-z0-9-]+|xai-[A-Za-z0-9-]{20,})\b/g;
5721
+ var AWS_ACCESS_KEY_RE = /\bAKIA[0-9A-Z]{16}\b/g;
5722
+ var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
5723
+ var ENV_SECRET_RE = /^(\s*(?:export\s+)?([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|CREDENTIAL|CREDENTIALS)[A-Z0-9_]*)\s*=\s*)['"]?([^\s'"]+)['"]?/gm;
5724
+ var COOKIE_HEADER_RE = /(Cookie:\s*)[^\r\n]+/gi;
5725
+ var BASIC_AUTH_URL_RE = /(https?:\/\/)([^:@\s/]+):([^@\s]+)@/g;
5726
+ var AWS_SECRET_LINE_RE = /^.*(?:secret|aws_secret).*$/gim;
5727
+ var AWS_SECRET_VALUE_RE = /\b([A-Za-z0-9/+=]{40})\b/g;
5728
+ var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
5729
+ function redact(input) {
5730
+ if (!input) return { output: input, redacted: 0 };
5731
+ let count = 0;
5732
+ let output = input;
5733
+ output = output.replace(SYSTEM_REMINDER_RE, () => {
5734
+ count++;
5735
+ return "<!-- stripped injection -->";
5736
+ });
5737
+ output = output.replace(BEARER_RE, (_match, prefix) => {
5738
+ count++;
5739
+ return `${prefix}${REDACTED}`;
5740
+ });
5741
+ output = output.replace(VENDOR_KEY_RE, () => {
5742
+ count++;
5743
+ return REDACTED;
5744
+ });
5745
+ output = output.replace(AWS_ACCESS_KEY_RE, () => {
5746
+ count++;
5747
+ return REDACTED;
5748
+ });
5749
+ output = output.replace(JWT_RE, () => {
5750
+ count++;
5751
+ return REDACTED;
5752
+ });
5753
+ output = output.replace(
5754
+ AWS_SECRET_LINE_RE,
5755
+ (line) => line.replace(AWS_SECRET_VALUE_RE, (match) => {
5756
+ if (/^[a-f0-9]+$/i.test(match)) return match;
5757
+ count++;
5758
+ return REDACTED;
5759
+ })
5760
+ );
5761
+ output = output.replace(ENV_SECRET_RE, (_match, prefix, _key, _value) => {
5762
+ count++;
5763
+ return `${prefix}${REDACTED}`;
5764
+ });
5765
+ output = output.replace(COOKIE_HEADER_RE, (_match, prefix) => {
5766
+ count++;
5767
+ return `${prefix}${REDACTED}`;
5768
+ });
5769
+ output = output.replace(BASIC_AUTH_URL_RE, (_match, scheme, user) => {
5770
+ count++;
5771
+ return `${scheme}${user}:${REDACTED}@`;
5772
+ });
5773
+ return { output, redacted: count };
5774
+ }
5775
+
5020
5776
  // src/execution/query-executor.ts
5021
5777
  var logger2 = createServiceLogger("QueryExecutor");
5022
5778
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
@@ -5029,12 +5785,15 @@ function buildHooks(host) {
5029
5785
  async (input) => {
5030
5786
  if (host.isStopped()) return await Promise.resolve({ continue: false });
5031
5787
  if (input.hook_event_name === "PostToolUse") {
5032
- const output = typeof input.tool_response === "string" ? input.tool_response.slice(0, 500) : JSON.stringify(input.tool_response).slice(0, 500);
5788
+ const raw = typeof input.tool_response === "string" ? input.tool_response : JSON.stringify(input.tool_response);
5789
+ const { output: redacted, redacted: redactedCount } = redact(raw);
5790
+ const output = redacted.slice(0, 500);
5033
5791
  host.connection.sendEvent({
5034
5792
  type: "tool_result",
5035
5793
  tool: input.tool_name,
5036
5794
  output,
5037
- isError: false
5795
+ isError: false,
5796
+ ...redactedCount > 0 ? { redactedCount } : {}
5038
5797
  });
5039
5798
  host.pendingToolOutputs.push({ tool: input.tool_name, output });
5040
5799
  if (input.tool_name === "mcp__conveyor__create_pull_request") {
@@ -5229,7 +5988,7 @@ async function runSdkQuery(host, context, followUpContent) {
5229
5988
  }
5230
5989
  }
5231
5990
  if (needsPlanSync) {
5232
- host.syncPlanFile();
5991
+ await host.syncPlanFile();
5233
5992
  }
5234
5993
  }
5235
5994
  async function buildRetryQuery(host, context, options, lastErrorWasImage) {
@@ -5401,6 +6160,22 @@ async function runWithRetry(initialQuery, context, host, options) {
5401
6160
  var CostTracker = class {
5402
6161
  cumulativeCostUsd = 0;
5403
6162
  modelUsage = /* @__PURE__ */ new Map();
6163
+ seeded = false;
6164
+ /**
6165
+ * Rehydrate cumulative spend from the server so `maxBudgetUsd` enforcement
6166
+ * accounts for costs incurred by prior agent runs on the same task. Must be
6167
+ * called before any `addQueryCost` / `addModelUsage` — re-seeding after
6168
+ * costs have accumulated would clobber in-process totals.
6169
+ */
6170
+ seed(totalCostUsd, modelUsage) {
6171
+ if (this.seeded) return;
6172
+ if (this.cumulativeCostUsd > 0 || this.modelUsage.size > 0) return;
6173
+ this.cumulativeCostUsd = totalCostUsd;
6174
+ for (const entry of modelUsage) {
6175
+ this.modelUsage.set(entry.model, { ...entry });
6176
+ }
6177
+ this.seeded = true;
6178
+ }
5404
6179
  /** Add cost from a completed query and return the running total */
5405
6180
  addQueryCost(queryCostUsd) {
5406
6181
  this.cumulativeCostUsd += queryCostUsd;
@@ -5591,6 +6366,10 @@ var QueryBridge = class {
5591
6366
  resume() {
5592
6367
  this._stopped = false;
5593
6368
  }
6369
+ /** Rehydrate CostTracker from server-side cumulative spend on agent boot. */
6370
+ seedCostTracker(totalCostUsd, modelUsage) {
6371
+ this.costTracker.seed(totalCostUsd, modelUsage);
6372
+ }
5594
6373
  /**
5595
6374
  * Execute a Claude SDK query.
5596
6375
  * Without followUpContent: runs initial mode execution (build/plan).
@@ -5646,7 +6425,6 @@ var QueryBridge = class {
5646
6425
  set hasExitedPlanMode(val) {
5647
6426
  bridge.mode.hasExitedPlanMode = val;
5648
6427
  },
5649
- exitPlanAttempts: 0,
5650
6428
  get pendingModeRestart() {
5651
6429
  return bridge.mode.pendingModeRestart;
5652
6430
  },
@@ -5703,6 +6481,7 @@ import { fileURLToPath } from "url";
5703
6481
  function mapChatHistory(messages) {
5704
6482
  if (!messages) return [];
5705
6483
  return messages.map((m) => ({
6484
+ id: m.id,
5706
6485
  role: m.role ?? "user",
5707
6486
  content: m.content ?? "",
5708
6487
  userId: m.userId,
@@ -5856,6 +6635,7 @@ var SessionRunner = class _SessionRunner {
5856
6635
  });
5857
6636
  }
5858
6637
  this.queryBridge = this.createQueryBridge();
6638
+ await this.seedCostTrackerFromServer();
5859
6639
  this.logInitialization();
5860
6640
  const staleMessageCount = this.pendingMessages.length;
5861
6641
  const didExecuteInitialQuery = await this.executeInitialMode();
@@ -6130,6 +6910,7 @@ var SessionRunner = class _SessionRunner {
6130
6910
  projectDescription: ctx.projectDescription ?? null,
6131
6911
  githubPRUrl: ctx.githubPRUrl,
6132
6912
  claudeSessionId: ctx.claudeSessionId ?? null,
6913
+ lastSeenMessageId: ctx.lastSeenMessageId ?? null,
6133
6914
  isParentTask: !!ctx.parentTaskId,
6134
6915
  storyPoints: ctx.storyPoints ?? void 0,
6135
6916
  projectAgents: ctx.projectAgents ?? void 0,
@@ -6217,6 +6998,36 @@ var SessionRunner = class _SessionRunner {
6217
6998
  }
6218
6999
  });
6219
7000
  }
7001
+ /**
7002
+ * Rehydrate CostTracker from server. Caps at 3s so a slow API call never
7003
+ * blocks agent startup — on timeout/error we fall through with a $0 tracker
7004
+ * (the existing behavior before this rehydration path was added).
7005
+ */
7006
+ async seedCostTrackerFromServer() {
7007
+ if (!this.queryBridge) return;
7008
+ const TIMEOUT_MS = 3e3;
7009
+ try {
7010
+ const timeout = new Promise((resolve2) => {
7011
+ setTimeout(() => resolve2(null), TIMEOUT_MS);
7012
+ });
7013
+ const fetched = await Promise.race([this.connection.getCumulativeSpending(), timeout]);
7014
+ if (fetched) {
7015
+ this.queryBridge.seedCostTracker(fetched.totalCostUsd, fetched.modelUsage);
7016
+ process.stderr.write(
7017
+ `[conveyor-agent] CostTracker seeded: $${fetched.totalCostUsd.toFixed(4)} across ${fetched.modelUsage.length} model(s)
7018
+ `
7019
+ );
7020
+ } else {
7021
+ process.stderr.write(
7022
+ "[conveyor-agent] CostTracker seed timed out after 3s \u2014 starting at $0\n"
7023
+ );
7024
+ }
7025
+ } catch (err) {
7026
+ const msg = err instanceof Error ? err.message : String(err);
7027
+ process.stderr.write(`[conveyor-agent] CostTracker seed failed: ${msg} \u2014 starting at $0
7028
+ `);
7029
+ }
7030
+ }
6220
7031
  /** Proactively refresh the GitHub token before the 1-hour expiry. */
6221
7032
  async refreshGithubToken() {
6222
7033
  try {
@@ -6695,7 +7506,7 @@ function runStartCommand(cmd, cwd, onOutput) {
6695
7506
  }
6696
7507
 
6697
7508
  // src/tools/project-tools.ts
6698
- import { z as z10 } from "zod";
7509
+ import { z as z13 } from "zod";
6699
7510
  function buildTaskListTools(connection) {
6700
7511
  const projectId = connection.projectId;
6701
7512
  return [
@@ -6703,9 +7514,9 @@ function buildTaskListTools(connection) {
6703
7514
  "list_tasks",
6704
7515
  "List tasks in the project. Optionally filter by status or assignee.",
6705
7516
  {
6706
- status: z10.string().optional().describe("Filter by task status"),
6707
- assigneeId: z10.string().optional().describe("Filter by assigned user ID"),
6708
- limit: z10.number().optional().describe("Max number of tasks to return (default 50)")
7517
+ status: z13.string().optional().describe("Filter by task status"),
7518
+ assigneeId: z13.string().optional().describe("Filter by assigned user ID"),
7519
+ limit: z13.number().optional().describe("Max number of tasks to return (default 50)")
6709
7520
  },
6710
7521
  async (params) => {
6711
7522
  try {
@@ -6722,7 +7533,7 @@ function buildTaskListTools(connection) {
6722
7533
  defineTool(
6723
7534
  "get_project_task",
6724
7535
  "Get detailed information about a task in this project (chat messages, child tasks, session). Project-runner scope.",
6725
- { task_id: z10.string().describe("The task ID to look up") },
7536
+ { task_id: z13.string().describe("The task ID to look up") },
6726
7537
  async ({ task_id }) => {
6727
7538
  try {
6728
7539
  const task = await connection.call("getProjectTask", { projectId, taskId: task_id });
@@ -6739,10 +7550,10 @@ function buildTaskListTools(connection) {
6739
7550
  "search_tasks",
6740
7551
  "Search tasks by tags, text query, or status filters.",
6741
7552
  {
6742
- tagNames: z10.array(z10.string()).optional().describe("Filter by tag names"),
6743
- searchQuery: z10.string().optional().describe("Text search in title/description"),
6744
- statusFilters: z10.array(z10.string()).optional().describe("Filter by statuses"),
6745
- limit: z10.number().optional().describe("Max results (default 20)")
7553
+ tagNames: z13.array(z13.string()).optional().describe("Filter by tag names"),
7554
+ searchQuery: z13.string().optional().describe("Text search in title/description"),
7555
+ statusFilters: z13.array(z13.string()).optional().describe("Filter by statuses"),
7556
+ limit: z13.number().optional().describe("Max results (default 20)")
6746
7557
  },
6747
7558
  async (params) => {
6748
7559
  try {
@@ -6802,10 +7613,10 @@ function buildMutationTools2(connection) {
6802
7613
  "create_task",
6803
7614
  "Create a new task in the project.",
6804
7615
  {
6805
- title: z10.string().describe("Task title"),
6806
- description: z10.string().optional().describe("Task description"),
6807
- plan: z10.string().optional().describe("Implementation plan in markdown"),
6808
- status: z10.string().optional().describe("Initial status (default: Planning)")
7616
+ title: z13.string().describe("Task title"),
7617
+ description: z13.string().optional().describe("Task description"),
7618
+ plan: z13.string().optional().describe("Implementation plan in markdown"),
7619
+ status: z13.string().optional().describe("Initial status (default: Planning)")
6809
7620
  },
6810
7621
  async (params) => {
6811
7622
  try {
@@ -6822,12 +7633,12 @@ function buildMutationTools2(connection) {
6822
7633
  "update_project_task",
6823
7634
  "Update an existing task's title, description, plan, status, or assignee. Project-runner scope.",
6824
7635
  {
6825
- task_id: z10.string().describe("The task ID to update"),
6826
- title: z10.string().optional().describe("New title"),
6827
- description: z10.string().optional().describe("New description"),
6828
- plan: z10.string().optional().describe("New plan in markdown"),
6829
- status: z10.string().optional().describe("New status"),
6830
- assignedUserId: z10.string().nullable().optional().describe("Assign to user ID, or null to unassign")
7636
+ task_id: z13.string().describe("The task ID to update"),
7637
+ title: z13.string().optional().describe("New title"),
7638
+ description: z13.string().optional().describe("New description"),
7639
+ plan: z13.string().optional().describe("New plan in markdown"),
7640
+ status: z13.string().optional().describe("New status"),
7641
+ assignedUserId: z13.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6831
7642
  },
6832
7643
  async ({ task_id, ...fields }) => {
6833
7644
  try {
@@ -7724,7 +8535,7 @@ var ProjectRunner = class {
7724
8535
  async handleAuditTasks(request) {
7725
8536
  this.connection.emitStatus("busy");
7726
8537
  try {
7727
- const { handleTaskAudit } = await import("./task-audit-handler-TJOM5OJS.js");
8538
+ const { handleTaskAudit } = await import("./task-audit-handler-4675WBIX.js");
7728
8539
  await handleTaskAudit(request, this.connection, this.projectDir);
7729
8540
  } catch (error) {
7730
8541
  const msg = parseErrorMessage(error);
@@ -7825,4 +8636,4 @@ export {
7825
8636
  loadForwardPorts,
7826
8637
  loadConveyorConfig
7827
8638
  };
7828
- //# sourceMappingURL=chunk-WDGSLU5S.js.map
8639
+ //# sourceMappingURL=chunk-TP5WEBQE.js.map