@rallycry/conveyor-agent 7.3.3 → 7.3.5

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() {
@@ -618,8 +641,10 @@ var ModeController = class {
618
641
  // src/runner/lifecycle.ts
619
642
  var DEFAULT_LIFECYCLE_CONFIG = {
620
643
  idleTimeoutMs: 30 * 60 * 1e3,
644
+ dormantTimeoutMs: 60 * 60 * 1e3,
621
645
  heartbeatIntervalMs: 3e4,
622
- tokenRefreshIntervalMs: 45 * 60 * 1e3
646
+ tokenRefreshIntervalMs: 45 * 60 * 1e3,
647
+ gitFlushIntervalMs: 2 * 60 * 1e3
623
648
  };
624
649
  var Lifecycle = class {
625
650
  config;
@@ -628,6 +653,8 @@ var Lifecycle = class {
628
653
  tokenRefreshTimer = null;
629
654
  idleTimer = null;
630
655
  idleCheckInterval = null;
656
+ dormantTimer = null;
657
+ gitFlushTimer = null;
631
658
  constructor(config, callbacks) {
632
659
  this.config = config;
633
660
  this.callbacks = callbacks;
@@ -659,6 +686,19 @@ var Lifecycle = class {
659
686
  this.tokenRefreshTimer = null;
660
687
  }
661
688
  }
689
+ // ── Periodic git flush ────────────────────────────────────────────
690
+ startGitFlush() {
691
+ this.stopGitFlush();
692
+ this.gitFlushTimer = setInterval(() => {
693
+ this.callbacks.onGitFlush();
694
+ }, this.config.gitFlushIntervalMs);
695
+ }
696
+ stopGitFlush() {
697
+ if (this.gitFlushTimer) {
698
+ clearInterval(this.gitFlushTimer);
699
+ this.gitFlushTimer = null;
700
+ }
701
+ }
662
702
  // ── Idle timer ─────────────────────────────────────────────────────
663
703
  startIdleTimer() {
664
704
  this.clearIdleTimers();
@@ -669,11 +709,26 @@ var Lifecycle = class {
669
709
  cancelIdleTimer() {
670
710
  this.clearIdleTimers();
671
711
  }
712
+ // ── Dormant timer ──────────────────────────────────────────────────
713
+ startDormantTimer() {
714
+ this.cancelDormantTimer();
715
+ this.dormantTimer = setTimeout(() => {
716
+ this.callbacks.onDormantTimeout();
717
+ }, this.config.dormantTimeoutMs);
718
+ }
719
+ cancelDormantTimer() {
720
+ if (this.dormantTimer) {
721
+ clearTimeout(this.dormantTimer);
722
+ this.dormantTimer = null;
723
+ }
724
+ }
672
725
  // ── Cleanup ────────────────────────────────────────────────────────
673
726
  destroy() {
674
727
  this.stopHeartbeat();
675
728
  this.stopTokenRefresh();
729
+ this.stopGitFlush();
676
730
  this.clearIdleTimers();
731
+ this.cancelDormantTimer();
677
732
  }
678
733
  // ── Private ────────────────────────────────────────────────────────
679
734
  clearIdleTimers() {
@@ -927,8 +982,8 @@ var PlanSync = class {
927
982
  for (const file of readdirSync(plansDir).filter((f) => f.endsWith(".md"))) {
928
983
  try {
929
984
  const fullPath = join(plansDir, file);
930
- const stat = statSync(fullPath);
931
- this.planFileSnapshot.set(fullPath, stat.mtimeMs);
985
+ const stat2 = statSync(fullPath);
986
+ this.planFileSnapshot.set(fullPath, stat2.mtimeMs);
932
987
  } catch {
933
988
  continue;
934
989
  }
@@ -949,11 +1004,11 @@ var PlanSync = class {
949
1004
  for (const file of files) {
950
1005
  const fullPath = join(plansDir, file);
951
1006
  try {
952
- const stat = statSync(fullPath);
1007
+ const stat2 = statSync(fullPath);
953
1008
  const prevMtime = this.planFileSnapshot.get(fullPath);
954
- const isNew = prevMtime === void 0 || stat.mtimeMs > prevMtime;
955
- if (isNew && (!newest || stat.mtimeMs > newest.mtime)) {
956
- newest = { path: fullPath, mtime: stat.mtimeMs };
1009
+ const isNew = prevMtime === void 0 || stat2.mtimeMs > prevMtime;
1010
+ if (isNew && (!newest || stat2.mtimeMs > newest.mtime)) {
1011
+ newest = { path: fullPath, mtime: stat2.mtimeMs };
957
1012
  }
958
1013
  } catch {
959
1014
  continue;
@@ -962,31 +1017,35 @@ var PlanSync = class {
962
1017
  }
963
1018
  return newest;
964
1019
  }
965
- syncPlanFile() {
966
- if (this.lockedPlanFile) {
967
- try {
968
- const content = readFileSync(this.lockedPlanFile, "utf-8").trim();
969
- if (content) {
970
- this.connection.updateTaskFields({ plan: content });
971
- const fileName = this.lockedPlanFile.split("/").pop() ?? "plan";
972
- this.connection.postChatMessage(`Synced local plan file (${fileName}) to the task plan.`);
973
- }
974
- } catch {
975
- }
1020
+ async syncPlanFile() {
1021
+ const isInitialDetection = this.lockedPlanFile === null;
1022
+ const target = this.lockedPlanFile ?? this.findNewestPlanFile()?.path ?? null;
1023
+ if (!target) return;
1024
+ if (isInitialDetection) {
1025
+ this.lockedPlanFile = target;
1026
+ }
1027
+ const fileName = target.split("/").pop() ?? "plan";
1028
+ let content;
1029
+ try {
1030
+ content = readFileSync(target, "utf-8").trim();
1031
+ } catch (err) {
1032
+ const reason = err instanceof Error ? err.message : String(err);
1033
+ this.connection.postChatMessage(
1034
+ `Plan sync warning: could not read local plan file (${fileName}): ${reason}. The task plan was not updated.`
1035
+ );
976
1036
  return;
977
1037
  }
978
- const newest = this.findNewestPlanFile();
979
- if (newest) {
980
- this.lockedPlanFile = newest.path;
981
- const content = readFileSync(newest.path, "utf-8").trim();
982
- if (content) {
983
- this.connection.updateTaskFields({ plan: content });
984
- const fileName = newest.path.split("/").pop() ?? "plan";
985
- this.connection.postChatMessage(
986
- `Detected local plan file (${fileName}) and synced it to the task plan.`
987
- );
988
- }
1038
+ if (!content) return;
1039
+ const result = await this.connection.updateTaskFields({ plan: content });
1040
+ if (!result.ok) {
1041
+ this.connection.postChatMessage(
1042
+ `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.`
1043
+ );
1044
+ return;
989
1045
  }
1046
+ const verb = isInitialDetection ? "Detected local plan file" : "Synced local plan file";
1047
+ const suffix = isInitialDetection ? " and synced it to the task plan." : " to the task plan.";
1048
+ this.connection.postChatMessage(`${verb} (${fileName})${suffix}`);
990
1049
  }
991
1050
  };
992
1051
 
@@ -996,6 +1055,558 @@ import { existsSync } from "fs";
996
1055
  import { homedir } from "os";
997
1056
  import { join as join2 } from "path";
998
1057
 
1058
+ // ../shared/dist/index.js
1059
+ import { z } from "zod";
1060
+ import { z as z2 } from "zod";
1061
+ import { z as z3 } from "zod";
1062
+ var MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
1063
+ var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
1064
+ var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
1065
+ var AgentHeartbeatSchema = z.object({
1066
+ sessionId: z.string().optional(),
1067
+ timestamp: z.string(),
1068
+ status: z.enum(["active", "idle", "building"]),
1069
+ currentAction: z.string().optional()
1070
+ });
1071
+ var CreatePRInputSchema = z.object({
1072
+ title: z.string().min(1),
1073
+ body: z.string(),
1074
+ head: z.string().optional(),
1075
+ base: z.string().optional()
1076
+ });
1077
+ var PostToChatInputSchema = z.object({
1078
+ message: z.string().min(1),
1079
+ type: z.enum(["message", "question", "update"]).optional().default("message")
1080
+ });
1081
+ var GetTaskContextRequestSchema = z.object({
1082
+ sessionId: z.string(),
1083
+ includeHistory: z.boolean().optional().default(false)
1084
+ });
1085
+ var GetChatMessagesRequestSchema = z.object({
1086
+ sessionId: z.string(),
1087
+ limit: z.number().int().positive().optional().default(50),
1088
+ offset: z.number().int().nonnegative().optional().default(0)
1089
+ });
1090
+ var GetTaskFilesRequestSchema = z.object({
1091
+ sessionId: z.string()
1092
+ });
1093
+ var GetTaskFileRequestSchema = z.object({
1094
+ sessionId: z.string(),
1095
+ fileId: z.string()
1096
+ });
1097
+ var GetTaskRequestSchema = z.object({
1098
+ sessionId: z.string(),
1099
+ taskSlugOrId: z.string()
1100
+ });
1101
+ var GetCliHistoryRequestSchema = z.object({
1102
+ sessionId: z.string(),
1103
+ limit: z.number().int().positive().optional().default(100),
1104
+ source: z.enum(["agent", "application"]).optional()
1105
+ });
1106
+ var ListSubtasksRequestSchema = z.object({
1107
+ sessionId: z.string()
1108
+ });
1109
+ var GetDependenciesRequestSchema = z.object({
1110
+ sessionId: z.string()
1111
+ });
1112
+ var GetSuggestionsRequestSchema = z.object({
1113
+ sessionId: z.string(),
1114
+ status: z.string().optional(),
1115
+ limit: z.number().int().min(1).max(100).optional()
1116
+ });
1117
+ var CreatePullRequestRequestSchema = CreatePRInputSchema.extend({ sessionId: z.string() });
1118
+ var UpdateTaskStatusRequestSchema = z.object({
1119
+ sessionId: z.string(),
1120
+ status: z.string(),
1121
+ force: z.boolean().optional().default(false)
1122
+ });
1123
+ var StoreSessionIdRequestSchema = z.object({
1124
+ sessionId: z.string(),
1125
+ sdkSessionId: z.string()
1126
+ });
1127
+ var TrackSpendingRequestSchema = z.object({
1128
+ sessionId: z.string(),
1129
+ inputTokens: z.number().int().nonnegative(),
1130
+ outputTokens: z.number().int().nonnegative(),
1131
+ costUsd: z.number().nonnegative(),
1132
+ model: z.string()
1133
+ });
1134
+ var SessionStartRequestSchema = z.object({
1135
+ sessionId: z.string(),
1136
+ agentVersion: z.string(),
1137
+ capabilities: z.array(z.string())
1138
+ });
1139
+ var SessionStopRequestSchema = z.object({
1140
+ sessionId: z.string(),
1141
+ reason: z.string().optional()
1142
+ });
1143
+ var ConnectAgentRequestSchema = z.object({
1144
+ sessionId: z.string()
1145
+ });
1146
+ var ReportAgentStatusRequestSchema = z.object({
1147
+ sessionId: z.string(),
1148
+ status: z.string()
1149
+ });
1150
+ var NotifyAgentVersionRequestSchema = z.object({
1151
+ sessionId: z.string(),
1152
+ agentVersion: z.string()
1153
+ });
1154
+ var CreateSubtaskRequestSchema = z.object({
1155
+ sessionId: z.string(),
1156
+ title: z.string().min(1),
1157
+ description: z.string().optional(),
1158
+ plan: z.string().optional(),
1159
+ storyPointValue: z.number().int().positive().optional(),
1160
+ ordinal: z.number().int().nonnegative().optional()
1161
+ });
1162
+ var UpdateSubtaskRequestSchema = z.object({
1163
+ sessionId: z.string(),
1164
+ subtaskId: z.string(),
1165
+ title: z.string().min(1).optional(),
1166
+ description: z.string().optional(),
1167
+ plan: z.string().optional(),
1168
+ status: z.string().optional(),
1169
+ storyPointValue: z.number().int().positive().optional()
1170
+ });
1171
+ var DeleteSubtaskRequestSchema = z.object({
1172
+ sessionId: z.string(),
1173
+ subtaskId: z.string()
1174
+ });
1175
+ var GetTaskPropertiesRequestSchema = z.object({
1176
+ sessionId: z.string()
1177
+ });
1178
+ var GetCumulativeSpendingRequestSchema = z.object({
1179
+ sessionId: z.string()
1180
+ });
1181
+ var ModelUsageEntrySchema = z.object({
1182
+ model: z.string(),
1183
+ inputTokens: z.number().nonnegative(),
1184
+ outputTokens: z.number().nonnegative(),
1185
+ cacheReadInputTokens: z.number().nonnegative(),
1186
+ cacheCreationInputTokens: z.number().nonnegative(),
1187
+ costUSD: z.number().nonnegative()
1188
+ });
1189
+ var GetCumulativeSpendingResponseSchema = z.object({
1190
+ totalCostUsd: z.number().nonnegative(),
1191
+ modelUsage: z.array(ModelUsageEntrySchema)
1192
+ });
1193
+ var UpdateTaskFieldsRequestSchema = z.object({
1194
+ sessionId: z.string(),
1195
+ plan: z.string().optional(),
1196
+ description: z.string().optional()
1197
+ });
1198
+ var UpdateTaskPropertiesRequestSchema = z.object({
1199
+ sessionId: z.string(),
1200
+ title: z.string().optional(),
1201
+ storyPointValue: z.number().int().positive().optional(),
1202
+ tagIds: z.array(z.string()).optional(),
1203
+ tagNames: z.array(z.string()).optional(),
1204
+ githubPRUrl: z.string().url().optional(),
1205
+ githubBranch: z.string().optional()
1206
+ });
1207
+ var ListIconsRequestSchema = z.object({
1208
+ sessionId: z.string()
1209
+ });
1210
+ var GenerateTaskIconRequestSchema = z.object({
1211
+ sessionId: z.string(),
1212
+ prompt: z.string().min(1),
1213
+ aspectRatio: z.string().optional()
1214
+ });
1215
+ var SearchFaIconsRequestSchema = z.object({
1216
+ sessionId: z.string(),
1217
+ query: z.string().min(1),
1218
+ first: z.number().int().positive().optional()
1219
+ });
1220
+ var PickFaIconRequestSchema = z.object({
1221
+ sessionId: z.string(),
1222
+ fontAwesomeId: z.string().min(1),
1223
+ fontAwesomeStyle: z.string().optional()
1224
+ });
1225
+ var CreateFollowUpTaskRequestSchema = z.object({
1226
+ sessionId: z.string(),
1227
+ title: z.string().min(1),
1228
+ description: z.string().optional(),
1229
+ plan: z.string().optional(),
1230
+ storyPointValue: z.number().int().positive().optional()
1231
+ });
1232
+ var AddDependencyRequestSchema = z.object({
1233
+ sessionId: z.string(),
1234
+ dependsOnSlugOrId: z.string()
1235
+ });
1236
+ var RemoveDependencyRequestSchema = z.object({
1237
+ sessionId: z.string(),
1238
+ dependsOnSlugOrId: z.string()
1239
+ });
1240
+ var CreateSuggestionRequestSchema = z.object({
1241
+ sessionId: z.string(),
1242
+ title: z.string().min(1),
1243
+ description: z.string().optional(),
1244
+ tagNames: z.array(z.string()).optional()
1245
+ });
1246
+ var VoteSuggestionRequestSchema = z.object({
1247
+ sessionId: z.string(),
1248
+ suggestionId: z.string(),
1249
+ value: z.union([z.literal(1), z.literal(-1)])
1250
+ });
1251
+ var TriggerIdentificationRequestSchema = z.object({
1252
+ sessionId: z.string()
1253
+ });
1254
+ var SubmitCodeReviewResultRequestSchema = z.object({
1255
+ sessionId: z.string(),
1256
+ approved: z.boolean(),
1257
+ content: z.string()
1258
+ });
1259
+ var StartChildCloudBuildRequestSchema = z.object({
1260
+ sessionId: z.string(),
1261
+ childTaskId: z.string()
1262
+ });
1263
+ var StopChildBuildRequestSchema = z.object({
1264
+ sessionId: z.string(),
1265
+ childTaskId: z.string()
1266
+ });
1267
+ var ApproveAndMergePRRequestSchema = z.object({
1268
+ sessionId: z.string(),
1269
+ childTaskId: z.string()
1270
+ });
1271
+ var PostChildChatMessageRequestSchema = z.object({
1272
+ sessionId: z.string(),
1273
+ childTaskId: z.string(),
1274
+ message: z.string().min(1)
1275
+ });
1276
+ var UpdateChildStatusRequestSchema = z.object({
1277
+ sessionId: z.string(),
1278
+ childTaskId: z.string(),
1279
+ status: z.string()
1280
+ });
1281
+ var GetAgentStatusRequestSchema = z.object({
1282
+ taskId: z.string()
1283
+ });
1284
+ var GetUiCliHistoryRequestSchema = z.object({
1285
+ taskId: z.string()
1286
+ });
1287
+ var SendSoftStopRequestSchema = z.object({
1288
+ taskId: z.string().optional(),
1289
+ projectId: z.string().optional()
1290
+ });
1291
+ var FlushTaskQueueRequestSchema = z.object({
1292
+ taskId: z.string(),
1293
+ softStop: z.boolean().optional()
1294
+ });
1295
+ var FlushProjectQueueRequestSchema = z.object({
1296
+ projectId: z.string()
1297
+ });
1298
+ var CancelTaskQueuedMessageRequestSchema = z.object({
1299
+ taskId: z.string(),
1300
+ messageId: z.string()
1301
+ });
1302
+ var FlushSingleQueuedMessageRequestSchema = z.object({
1303
+ taskId: z.string(),
1304
+ messageId: z.string(),
1305
+ softStop: z.boolean().optional()
1306
+ });
1307
+ var FlushSingleProjectQueuedMessageRequestSchema = z.object({
1308
+ projectId: z.string(),
1309
+ index: z.number().int().nonnegative()
1310
+ });
1311
+ var AnswerAgentQuestionRequestSchema = z.object({
1312
+ taskId: z.string(),
1313
+ requestId: z.string(),
1314
+ answers: z.record(z.string(), z.string())
1315
+ });
1316
+ var ClearAgentTodosRequestSchema = z.object({
1317
+ taskId: z.string()
1318
+ });
1319
+ var AgentQuestionOptionSchema = z.object({
1320
+ label: z.string(),
1321
+ description: z.string(),
1322
+ preview: z.string().optional()
1323
+ });
1324
+ var AgentQuestionSchema = z.object({
1325
+ question: z.string(),
1326
+ header: z.string(),
1327
+ options: z.array(AgentQuestionOptionSchema),
1328
+ multiSelect: z.boolean().optional()
1329
+ });
1330
+ var AskUserQuestionRequestSchema = z.object({
1331
+ sessionId: z.string(),
1332
+ question: z.string().min(1),
1333
+ requestId: z.string().min(1),
1334
+ questions: z.array(AgentQuestionSchema).min(1)
1335
+ });
1336
+ var AgentEventSchema = z.object({
1337
+ type: z.string().min(1)
1338
+ }).catchall(z.unknown());
1339
+ var EmitAgentEventRequestSchema = z.object({
1340
+ sessionId: z.string(),
1341
+ events: z.array(AgentEventSchema).max(500)
1342
+ });
1343
+ var RefreshGithubTokenRequestSchema = z.object({
1344
+ sessionId: z.string()
1345
+ });
1346
+ var RefreshGithubTokenResponseSchema = z.object({
1347
+ token: z.string()
1348
+ });
1349
+ var CreatePRResponseSchema = z.object({
1350
+ prNumber: z.number().int().positive(),
1351
+ prUrl: z.string().url()
1352
+ });
1353
+ var PostToChatResponseSchema = z.object({
1354
+ messageId: z.string()
1355
+ });
1356
+ var UpdateTaskStatusResponseSchema = z.object({
1357
+ taskId: z.string(),
1358
+ status: z.string()
1359
+ });
1360
+ var StoreSessionIdResponseSchema = z.object({
1361
+ success: z.boolean()
1362
+ });
1363
+ var HeartbeatResponseSchema = z.object({
1364
+ acknowledged: z.boolean()
1365
+ });
1366
+ var SessionStartResponseSchema = z.object({
1367
+ sessionId: z.string(),
1368
+ startedAt: z.string()
1369
+ });
1370
+ var SessionStopResponseSchema = z.object({
1371
+ sessionId: z.string(),
1372
+ stoppedAt: z.string()
1373
+ });
1374
+ var DeleteSubtaskResponseSchema = z.object({
1375
+ deleted: z.boolean()
1376
+ });
1377
+ var RegisterProjectAgentResponseSchema = z.object({
1378
+ registered: z.boolean(),
1379
+ agentName: z.string(),
1380
+ agentInstructions: z.string(),
1381
+ model: z.string(),
1382
+ agentSettings: z.record(z.string(), z.unknown()).nullable(),
1383
+ branchSwitchCommand: z.string().nullable()
1384
+ });
1385
+ var RegisterProjectAgentRequestSchema = z2.object({
1386
+ projectId: z2.string(),
1387
+ capabilities: z2.array(z2.string())
1388
+ });
1389
+ var ProjectRunnerHeartbeatRequestSchema = z2.object({
1390
+ projectId: z2.string()
1391
+ });
1392
+ var ReportProjectAgentStatusRequestSchema = z2.object({
1393
+ projectId: z2.string(),
1394
+ status: z2.enum(["busy", "idle"]),
1395
+ activeChatId: z2.string().nullish(),
1396
+ activeTaskId: z2.string().nullish(),
1397
+ activeBranch: z2.string().nullish()
1398
+ });
1399
+ var DisconnectProjectRunnerRequestSchema = z2.object({
1400
+ projectId: z2.string()
1401
+ });
1402
+ var GetProjectAgentContextRequestSchema = z2.object({
1403
+ projectId: z2.string()
1404
+ });
1405
+ var GetProjectAgentContextByRoleRequestSchema = z2.object({
1406
+ projectId: z2.string(),
1407
+ role: z2.string()
1408
+ });
1409
+ var GetProjectFunctionConfigRequestSchema = z2.object({
1410
+ projectId: z2.string(),
1411
+ functionId: z2.string()
1412
+ });
1413
+ var GetProjectChatHistoryRequestSchema = z2.object({
1414
+ projectId: z2.string(),
1415
+ limit: z2.number().int().positive().optional().default(50),
1416
+ chatId: z2.string().optional()
1417
+ });
1418
+ var PostProjectAgentMessageRequestSchema = z2.object({
1419
+ projectId: z2.string(),
1420
+ content: z2.string().min(1),
1421
+ chatId: z2.string().optional()
1422
+ });
1423
+ var ListProjectTasksRequestSchema = z2.object({
1424
+ projectId: z2.string(),
1425
+ status: z2.string().optional(),
1426
+ assigneeId: z2.string().optional(),
1427
+ limit: z2.number().int().positive().optional().default(50)
1428
+ });
1429
+ var GetProjectTaskRequestSchema = z2.object({
1430
+ projectId: z2.string(),
1431
+ taskId: z2.string()
1432
+ });
1433
+ var SearchProjectTasksRequestSchema = z2.object({
1434
+ projectId: z2.string(),
1435
+ tagNames: z2.array(z2.string()).optional(),
1436
+ searchQuery: z2.string().optional(),
1437
+ statusFilters: z2.array(z2.string()).optional(),
1438
+ limit: z2.number().int().positive().optional().default(20)
1439
+ });
1440
+ var ListProjectTagsRequestSchema = z2.object({
1441
+ projectId: z2.string()
1442
+ });
1443
+ var GetProjectSummaryRequestSchema = z2.object({
1444
+ projectId: z2.string()
1445
+ });
1446
+ var CreateProjectTaskRequestSchema = z2.object({
1447
+ projectId: z2.string(),
1448
+ title: z2.string().min(1),
1449
+ description: z2.string().optional(),
1450
+ plan: z2.string().optional(),
1451
+ status: z2.string().optional()
1452
+ });
1453
+ var UpdateProjectTaskRequestSchema = z2.object({
1454
+ projectId: z2.string(),
1455
+ taskId: z2.string(),
1456
+ title: z2.string().optional(),
1457
+ description: z2.string().optional(),
1458
+ plan: z2.string().optional(),
1459
+ status: z2.string().optional(),
1460
+ assignedUserId: z2.string().nullish()
1461
+ });
1462
+ var ReportProjectAgentEventRequestSchema = z2.object({
1463
+ projectId: z2.string(),
1464
+ event: z2.record(z2.string(), z2.unknown())
1465
+ });
1466
+ var ReportTagAuditProgressRequestSchema = z2.object({
1467
+ projectId: z2.string(),
1468
+ requestId: z2.string(),
1469
+ activity: z2.object({
1470
+ tool: z2.string(),
1471
+ input: z2.string().optional(),
1472
+ timestamp: z2.string()
1473
+ })
1474
+ });
1475
+ var ReportTagAuditResultRequestSchema = z2.object({
1476
+ projectId: z2.string(),
1477
+ requestId: z2.string(),
1478
+ recommendations: z2.array(z2.record(z2.string(), z2.unknown())),
1479
+ summary: z2.string(),
1480
+ complete: z2.boolean()
1481
+ });
1482
+ var ReportNewCommitsDetectedRequestSchema = z2.object({
1483
+ projectId: z2.string(),
1484
+ commits: z2.array(
1485
+ z2.object({
1486
+ sha: z2.string(),
1487
+ message: z2.string(),
1488
+ author: z2.string()
1489
+ })
1490
+ ),
1491
+ branch: z2.string()
1492
+ });
1493
+ var ReportEnvironmentReadyRequestSchema = z2.object({
1494
+ projectId: z2.string(),
1495
+ branch: z2.string(),
1496
+ setupComplete: z2.boolean(),
1497
+ startCommandRunning: z2.boolean()
1498
+ });
1499
+ var ReportEnvSwitchProgressRequestSchema = z2.object({
1500
+ projectId: z2.string(),
1501
+ step: z2.string(),
1502
+ progress: z2.number(),
1503
+ message: z2.string().optional()
1504
+ });
1505
+ var ForwardProjectChatMessageRequestSchema = z2.object({
1506
+ projectId: z2.string(),
1507
+ chatId: z2.string(),
1508
+ content: z2.string().min(1),
1509
+ targetUserId: z2.string().optional()
1510
+ });
1511
+ var CancelQueuedProjectMessageRequestSchema = z2.object({
1512
+ projectId: z2.string(),
1513
+ index: z2.number().int().nonnegative()
1514
+ });
1515
+ var GetProjectCliHistoryRequestSchema = z2.object({
1516
+ projectId: z2.string(),
1517
+ limit: z2.number().int().positive().optional().default(50),
1518
+ source: z2.string().optional()
1519
+ });
1520
+ var PostToProjectTaskChatRequestSchema = z2.object({
1521
+ projectId: z2.string(),
1522
+ taskId: z2.string(),
1523
+ content: z2.string()
1524
+ });
1525
+ var GetProjectTaskCliRequestSchema = z2.object({
1526
+ projectId: z2.string(),
1527
+ taskId: z2.string(),
1528
+ limit: z2.number().int().positive().optional().default(50),
1529
+ source: z2.string().optional()
1530
+ });
1531
+ var StartProjectBuildRequestSchema = z2.object({
1532
+ projectId: z2.string(),
1533
+ taskId: z2.string()
1534
+ });
1535
+ var StopProjectBuildRequestSchema = z2.object({
1536
+ projectId: z2.string(),
1537
+ taskId: z2.string()
1538
+ });
1539
+ var ApproveProjectMergePRRequestSchema = z2.object({
1540
+ projectId: z2.string(),
1541
+ childTaskId: z2.string()
1542
+ });
1543
+ var StartTaskAuditRequestSchema = z3.object({
1544
+ projectId: z3.string(),
1545
+ taskIds: z3.array(z3.string()).min(1)
1546
+ });
1547
+ var ReportTaskAuditProgressRequestSchema = z3.object({
1548
+ projectId: z3.string(),
1549
+ requestId: z3.string(),
1550
+ taskId: z3.string(),
1551
+ activity: z3.object({
1552
+ tool: z3.string(),
1553
+ input: z3.string().optional(),
1554
+ timestamp: z3.string()
1555
+ })
1556
+ });
1557
+ var CreateProjectSuggestionRequestSchema = z3.object({
1558
+ projectId: z3.string(),
1559
+ title: z3.string().min(1),
1560
+ description: z3.string().optional(),
1561
+ tagNames: z3.array(z3.string()).optional()
1562
+ });
1563
+ var ReportTaskAuditBatchCompleteRequestSchema = z3.object({
1564
+ projectId: z3.string(),
1565
+ requestId: z3.string()
1566
+ });
1567
+ var ReportTaskAuditResultRequestSchema = z3.object({
1568
+ projectId: z3.string(),
1569
+ requestId: z3.string(),
1570
+ taskId: z3.string(),
1571
+ summary: z3.string(),
1572
+ turnGrades: z3.array(
1573
+ z3.object({
1574
+ turnIndex: z3.number(),
1575
+ phase: z3.enum(["planning", "building", "human"]),
1576
+ grade: z3.enum(["correct", "neutral", "blunder"]),
1577
+ reasoning: z3.string(),
1578
+ eventType: z3.string(),
1579
+ eventSummary: z3.string()
1580
+ })
1581
+ ),
1582
+ planningAccuracy: z3.number().nullable(),
1583
+ buildingAccuracy: z3.number().nullable(),
1584
+ humanAccuracy: z3.number().nullable(),
1585
+ planningCorrect: z3.number(),
1586
+ planningNeutral: z3.number(),
1587
+ planningBlunder: z3.number(),
1588
+ buildingCorrect: z3.number(),
1589
+ buildingNeutral: z3.number(),
1590
+ buildingBlunder: z3.number(),
1591
+ humanCorrect: z3.number(),
1592
+ humanNeutral: z3.number(),
1593
+ humanBlunder: z3.number(),
1594
+ humanEvaluations: z3.array(
1595
+ z3.object({
1596
+ messageIndex: z3.number(),
1597
+ rating: z3.number().int().min(-1).max(1),
1598
+ reasoning: z3.string()
1599
+ })
1600
+ ).optional().default([]),
1601
+ suggestionIds: z3.array(z3.string()),
1602
+ auditCostUsd: z3.number().nullable(),
1603
+ model: z3.string().nullable(),
1604
+ error: z3.string().optional()
1605
+ });
1606
+ var TASK_CHAT_HISTORY_LIMIT = 20;
1607
+ var PM_CHAT_HISTORY_LIMIT = 40;
1608
+ var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
1609
+
999
1610
  // src/execution/pack-runner-prompt.ts
1000
1611
  function findLastAgentMessageIndex(history) {
1001
1612
  for (let i = history.length - 1; i >= 0; i--) {
@@ -1043,7 +1654,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1043
1654
  `2. Evaluate children by status and dependency readiness:`,
1044
1655
  ` - "ReviewPR": Review and merge its PR with approve_and_merge_pr. (Highest priority)`,
1045
1656
  ` - If merge fails due to pending CI: post a status update to chat, state you are going idle.`,
1046
- ` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check. Escalate to team.`,
1657
+ ` - If merge fails due to failed CI: use get_execution_logs(childTaskId) to check. Escalate to team.`,
1047
1658
  ` - "InProgress": A Task Runner is actively working. Do nothing \u2014 wait.`,
1048
1659
  ` - "Open" + allDependenciesMet=true: Ready to fire. Use start_child_cloud_build.`,
1049
1660
  ` - "Open" + allDependenciesMet=false: Blocked \u2014 skip for now. Will be unblocked when deps complete.`,
@@ -1063,7 +1674,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1063
1674
  `- When NO dependencies are set on any children, fall back to ordinal order (one at a time). This preserves backward compatibility.`,
1064
1675
  `- 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.`,
1065
1676
  `- Do NOT attempt to write code yourself. Your role is coordination only.`,
1066
- `- 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.`,
1677
+ `- 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.`,
1067
1678
  `- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
1068
1679
  `- 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.`,
1069
1680
  `- You can use read_task_chat to check for team messages.`
@@ -1127,7 +1738,6 @@ After addressing the feedback, resume your autonomous loop: call list_subtasks a
1127
1738
  }
1128
1739
 
1129
1740
  // src/execution/prompt-formatters.ts
1130
- var PM_CHAT_HISTORY_LIMIT = 40;
1131
1741
  function formatFileSize(bytes) {
1132
1742
  if (bytes === void 0) return "";
1133
1743
  if (bytes < 1024) return `${bytes}B`;
@@ -1149,7 +1759,7 @@ function formatChatFile(file) {
1149
1759
  }
1150
1760
  if (file.content && file.contentEncoding === "base64") {
1151
1761
  return [
1152
- `[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_task_file("${file.fileId}") to view]`
1762
+ `[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_attachment("${file.fileId}") to view]`
1153
1763
  ];
1154
1764
  }
1155
1765
  return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`];
@@ -1162,7 +1772,7 @@ function formatTaskFile(file) {
1162
1772
  if (file.content && file.contentEncoding === "base64") {
1163
1773
  const size = formatFileSize(file.fileSize);
1164
1774
  return [
1165
- `- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_task_file("${file.fileId}") to view]`
1775
+ `- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_attachment("${file.fileId}") to view]`
1166
1776
  ];
1167
1777
  }
1168
1778
  if (!file.content) {
@@ -1195,6 +1805,20 @@ function formatRepoRefs(repoRefs) {
1195
1805
  }
1196
1806
  return parts;
1197
1807
  }
1808
+ function formatReferenceProjects(referenceProjects) {
1809
+ const parts = [];
1810
+ parts.push(`
1811
+ ## Reference Projects`);
1812
+ parts.push(
1813
+ `These sibling Conveyor projects have been shallow-cloned read-only into \`/workspaces/references/<slug>/\` for inspiration. You MAY grep/read them to compare approaches, but you MUST NOT modify them or commit anything from them into this task's repo.
1814
+ `
1815
+ );
1816
+ for (const ref of referenceProjects) {
1817
+ const repo = ref.githubRepoOwner && ref.githubRepoName ? ` (${ref.githubRepoOwner}/${ref.githubRepoName})` : "";
1818
+ parts.push(`- **${ref.name}**${repo} \u2014 \`/workspaces/references/${ref.slug}/\``);
1819
+ }
1820
+ return parts;
1821
+ }
1198
1822
  function formatProjectObjectives(objectives) {
1199
1823
  const parts = [];
1200
1824
  parts.push(`
@@ -1239,7 +1863,7 @@ function formatIncidents(incidents) {
1239
1863
  }
1240
1864
 
1241
1865
  // src/execution/tag-context-resolver.ts
1242
- import { readFile, readdir } from "fs/promises";
1866
+ import { readFile, readdir, stat } from "fs/promises";
1243
1867
  var TYPE_PRIORITY = { rule: 0, file: 1, folder: 2, doc: 3 };
1244
1868
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1245
1869
  ".png",
@@ -1274,6 +1898,10 @@ function isBinaryPath(filePath) {
1274
1898
  const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1275
1899
  return BINARY_EXTENSIONS.has(ext);
1276
1900
  }
1901
+ var fileContentCache = /* @__PURE__ */ new Map();
1902
+ var folderListingCache = /* @__PURE__ */ new Map();
1903
+ var fileReadCount = 0;
1904
+ var folderReadCount = 0;
1277
1905
  function getContextBudgetChars(_model, betas, runnerMode) {
1278
1906
  const contextWindow = betas?.some((b) => b.includes("context-1m")) ? 1e6 : 2e5;
1279
1907
  const budgetPct = runnerMode === "task" ? 0.15 : 0.25;
@@ -1282,21 +1910,50 @@ function getContextBudgetChars(_model, betas, runnerMode) {
1282
1910
  async function readFileContent(filePath, maxChars) {
1283
1911
  try {
1284
1912
  if (isBinaryPath(filePath)) return null;
1285
- const content = await readFile(filePath, "utf-8");
1286
- if (content.length > maxChars) {
1287
- const omitted = content.length - maxChars;
1288
- return content.slice(0, maxChars) + `
1913
+ let rawContent;
1914
+ let mtimeMs;
1915
+ try {
1916
+ const st = await stat(filePath);
1917
+ mtimeMs = st.mtimeMs;
1918
+ } catch {
1919
+ return null;
1920
+ }
1921
+ const cached = fileContentCache.get(filePath);
1922
+ if (cached && cached.mtimeMs === mtimeMs) {
1923
+ rawContent = cached.content;
1924
+ } else {
1925
+ rawContent = await readFile(filePath, "utf-8");
1926
+ fileReadCount++;
1927
+ fileContentCache.set(filePath, { mtimeMs, content: rawContent });
1928
+ }
1929
+ if (rawContent.length > maxChars) {
1930
+ const omitted = rawContent.length - maxChars;
1931
+ return rawContent.slice(0, maxChars) + `
1289
1932
  [... truncated, ${omitted} chars omitted]`;
1290
1933
  }
1291
- return content;
1934
+ return rawContent;
1292
1935
  } catch {
1293
1936
  return null;
1294
1937
  }
1295
1938
  }
1296
1939
  async function readFolderListing(folderPath) {
1297
1940
  try {
1941
+ let mtimeMs;
1942
+ try {
1943
+ const st = await stat(folderPath);
1944
+ mtimeMs = st.mtimeMs;
1945
+ } catch {
1946
+ return null;
1947
+ }
1948
+ const cached = folderListingCache.get(folderPath);
1949
+ if (cached && cached.mtimeMs === mtimeMs) {
1950
+ return cached.listing;
1951
+ }
1298
1952
  const entries = await readdir(folderPath);
1299
- return `Files: ${entries.join(", ")}`;
1953
+ folderReadCount++;
1954
+ const listing = `Files: ${entries.join(", ")}`;
1955
+ folderListingCache.set(folderPath, { mtimeMs, listing });
1956
+ return listing;
1300
1957
  } catch {
1301
1958
  return null;
1302
1959
  }
@@ -1417,6 +2074,18 @@ async function resolveTagContext(projectTags, taskTagIds, model, betas, runnerMo
1417
2074
  };
1418
2075
  }
1419
2076
 
2077
+ // src/execution/prompt-truncation.ts
2078
+ var PLAN_SOFT_CAP = 4e3;
2079
+ var PLAN_HEAD_CHARS = 3e3;
2080
+ var PLAN_TAIL_CHARS = 500;
2081
+ var PLAN_TRUNCATION_MARKER = "\n\n... plan truncated \u2014 call get_current_plan for full text ...\n\n";
2082
+ function truncatePlanForPrompt(plan) {
2083
+ if (plan.length <= PLAN_SOFT_CAP) return plan;
2084
+ const head = plan.slice(0, PLAN_HEAD_CHARS);
2085
+ const tail = plan.slice(plan.length - PLAN_TAIL_CHARS);
2086
+ return `${head}${PLAN_TRUNCATION_MARKER}${tail}`;
2087
+ }
2088
+
1420
2089
  // src/execution/mode-prompt.ts
1421
2090
  var SP_DESC_MAX_CHARS = 80;
1422
2091
  function truncateDescription(desc, maxChars) {
@@ -1476,7 +2145,19 @@ function buildExplorationMethodology() {
1476
2145
  `- Search first, read second: use grep/glob to locate relevant code, then read only the files that matter`,
1477
2146
  `- Never re-read a file already in your context \u2014 you have a large context window, scroll up instead`,
1478
2147
  `- Start with 3-5 critical files, form a hypothesis about the approach, then validate with targeted reads`,
1479
- `- Stop exploring when you can write specific file paths and function names in your plan \u2014 that's enough`
2148
+ `- Stop exploring when you can cite specific \`file.ts:line\` locations and function names for every step in your plan \u2014 that's enough`
2149
+ ];
2150
+ }
2151
+ function buildPlanCitationFormat() {
2152
+ return [
2153
+ ``,
2154
+ `### Plan Citation Format`,
2155
+ `Plans must ground each change in the code. For every step that touches code:`,
2156
+ `- Cite the exact location as \`path/from/repo/root.ts:lineNumber\` (e.g. \`packages/conveyor-agent/src/execution/mode-prompt.ts:129\`).`,
2157
+ `- Name the specific function, class, constant, or JSX element being touched.`,
2158
+ `- When behavior hinges on a short piece of code, quote 1\u20133 lines inline instead of paraphrasing.`,
2159
+ `- Ranges are fine for larger edits (\`foo.ts:120-145\`). Do not cite whole files without a line.`,
2160
+ `- If a file doesn't exist yet, write \`NEW: path/to/new-file.ts\` and describe the surrounding module it fits into.`
1480
2161
  ];
1481
2162
  }
1482
2163
  function buildDiscoveryPrompt(context, runnerMode) {
@@ -1496,15 +2177,16 @@ function buildDiscoveryPrompt(context, runnerMode) {
1496
2177
  `Your PRIMARY goal is to create a thorough plan. Complete these steps in order:`,
1497
2178
  `1. Read the task description and chat history \u2014 respond to what's been discussed`,
1498
2179
  `2. Investigate the codebase using the methodology below \u2014 search first, read targeted files`,
1499
- `3. Save a detailed plan via \`update_task\``,
2180
+ `3. Save a detailed plan via \`update_task_plan\``,
1500
2181
  `4. Set story points, tags, and title via \`update_task_properties\` (icon is set automatically)`,
1501
2182
  `5. Discuss the plan with the team if they're engaged, incorporate feedback`,
1502
2183
  `6. THEN call ExitPlanMode \u2014 it is the LAST step, not the first`,
1503
2184
  ...buildExplorationMethodology(),
2185
+ ...buildPlanCitationFormat(),
1504
2186
  ``,
1505
2187
  `### Self-Identification Tools`,
1506
2188
  `Use these MCP tools to set your own task properties:`,
1507
- `- \`update_task\` \u2014 save your plan and description`,
2189
+ `- \`update_task_plan\` \u2014 save your plan and description`,
1508
2190
  `- \`update_task_properties\` \u2014 set title, story points, and tags (any combination)`,
1509
2191
  `Note: Icons are assigned automatically during identification after planning is complete.`,
1510
2192
  ``,
@@ -1527,21 +2209,20 @@ function buildDiscoveryPrompt(context, runnerMode) {
1527
2209
  `### Subtask Plan Requirements`,
1528
2210
  `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1529
2211
  `- Plans should be multi-step implementation guides, not vague descriptions`,
1530
- `- 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`,
1531
2213
  `- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
1532
2214
  `- Include testing requirements and acceptance criteria`,
1533
2215
  `- Set \`storyPointValue\` based on estimated complexity`,
1534
2216
  ``,
1535
2217
  `### Plan Verification Requirements`,
1536
- `Every plan MUST include a **Testing / Verification** section enumerating:`,
1537
- `- The quality gates: \`bun run lint\`, \`bun run typecheck\`, \`bun run test\` (or scoped equivalents)`,
2218
+ `Every plan MUST include a **Testing / Verification** section so the build agent has a clear definition of "done". Enumerate:`,
2219
+ `- The quality gates the build agent should run: \`bun run lint\`, \`bun run typecheck\`, \`bun run test\` (or scoped equivalents)`,
1538
2220
  `- Any task-specific end-to-end checks (manual UI walk-through, API smoke test, migration dry-run, etc.)`,
1539
- `- For refactors: line-count / file-size expectations and a public-API-surface diff check`,
1540
- `This ensures executing agents and reviewers have a clear definition of "done" and prevents silent regressions.`,
2221
+ `You are NOT expected to run these gates yourself \u2014 discovery is read-only. Just describe them in the plan.`,
1541
2222
  ``,
1542
2223
  `### Completing Planning`,
1543
2224
  `Once ALL checklist items above are done, call the **ExitPlanMode** tool.`,
1544
- `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
2225
+ `- Required before ExitPlanMode will succeed: **plan** (via update_task_plan), **story points** (via update_task_properties), **title** (via update_task_properties)`,
1545
2226
  `- ExitPlanMode validates these properties and marks planning as complete`,
1546
2227
  `- It does NOT start building \u2014 the team controls when to switch to Build mode`,
1547
2228
  `- Do NOT call ExitPlanMode until you have thoroughly explored the codebase and saved a detailed plan`
@@ -1557,11 +2238,11 @@ function buildAutoPrompt(context, runnerMode) {
1557
2238
  ``,
1558
2239
  `### Phase 1: Discovery & Planning (current)`,
1559
2240
  `- You are in the SDK's plan mode \u2014 read-only access is enforced automatically`,
1560
- `- You have MCP tools for task properties: update_task, update_task_properties`,
2241
+ `- You have MCP tools for task properties: update_task_plan, update_task_properties`,
1561
2242
  ``,
1562
2243
  `### Required before transitioning:`,
1563
2244
  `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1564
- `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
2245
+ `1. **Plan** \u2014 Save a clear implementation plan using update_task_plan`,
1565
2246
  `2. **Story Points** \u2014 Assign via update_task_properties`,
1566
2247
  `3. **Title** \u2014 Set an accurate title via update_task_properties (if the current one is vague or "Untitled")`,
1567
2248
  ``,
@@ -1574,11 +2255,13 @@ function buildAutoPrompt(context, runnerMode) {
1574
2255
  `### Subtask Plan Requirements`,
1575
2256
  `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1576
2257
  `- Plans should be multi-step implementation guides, not vague descriptions`,
1577
- `- Include specific file paths, function names, and code patterns to modify`,
2258
+ `- Include concrete \`file.ts:line\` citations, function/symbol names, and short code snippets where relevant`,
1578
2259
  `- Reference existing implementations when relevant`,
1579
2260
  `- Include testing requirements and acceptance criteria`,
1580
2261
  `- Set \`storyPointValue\` based on estimated complexity`,
1581
2262
  ``,
2263
+ ...buildPlanCitationFormat(),
2264
+ ``,
1582
2265
  ...context?.isParentTask ? [
1583
2266
  ``,
1584
2267
  `### Parent Task Guidance`,
@@ -1648,7 +2331,7 @@ function buildReviewPrompt(context) {
1648
2331
  `You are reviewing and coordinating child tasks.`,
1649
2332
  `- Use \`list_subtasks\` to see current child task state and progress.`,
1650
2333
  `- For children in ReviewPR status: review their code quality and merge with \`approve_and_merge_pr\`.`,
1651
- `- For children with failing CI: check with \`get_task_cli(childTaskId)\` and escalate if stuck.`,
2334
+ `- For children with failing CI: check with \`get_execution_logs(childTaskId)\` and escalate if stuck.`,
1652
2335
  `- Fire next child builds with \`start_child_cloud_build\` when ready.`,
1653
2336
  `- Create follow-up tasks for issues discovered during review.`,
1654
2337
  ``,
@@ -1725,7 +2408,7 @@ Environment (ready, no setup required):`,
1725
2408
  `
1726
2409
  Workflow:`,
1727
2410
  `- You can draft and iterate on plans in .claude/plans/*.md \u2014 these files are automatically synced to the task.`,
1728
- `- You can also use update_task directly to save the plan to the task.`,
2411
+ `- You can also use update_task_plan directly to save the plan to the task.`,
1729
2412
  `- 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.`,
1730
2413
  `- A separate task agent will handle execution after the team reviews and approves your plan.`
1731
2414
  ];
@@ -1752,7 +2435,7 @@ function buildActivePreamble(context, workspaceDir) {
1752
2435
  `You are an AI project manager in ACTIVE mode for the "${context.title}" project.`,
1753
2436
  `You have direct coding access to the repository at ${workspaceDir}.`,
1754
2437
  `You can edit files, run tests, and make commits.`,
1755
- `You still have access to all PM tools (subtasks, update_task, chat).`,
2438
+ `You still have access to all PM tools (subtasks, update_task_plan, chat).`,
1756
2439
  `
1757
2440
  Environment (ready, no setup required):`,
1758
2441
  `- Repository is cloned at your current working directory.`,
@@ -1879,17 +2562,26 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
1879
2562
  }
1880
2563
 
1881
2564
  // src/execution/prompt-builder.ts
1882
- var TASK_CHAT_HISTORY_LIMIT = 20;
1883
2565
  function findLastAgentMessageIndex2(history) {
1884
2566
  for (let i = history.length - 1; i >= 0; i--) {
1885
2567
  if (history[i].role === "assistant") return i;
1886
2568
  }
1887
2569
  return -1;
1888
2570
  }
2571
+ function messagesAfterCursor(history, lastSeenMessageId) {
2572
+ if (!lastSeenMessageId) return history;
2573
+ const idx = history.findIndex((m) => m.id === lastSeenMessageId);
2574
+ return idx === -1 ? history : history.slice(idx + 1);
2575
+ }
1889
2576
  function detectRelaunchScenario(context, trustChatHistory = false) {
2577
+ if (context.lastSeenMessageId) {
2578
+ const newMessages = messagesAfterCursor(context.chatHistory, context.lastSeenMessageId);
2579
+ const hasNewUserMessages2 = newMessages.some((m) => m.role === "user");
2580
+ return hasNewUserMessages2 ? "feedback_relaunch" : "idle_relaunch";
2581
+ }
1890
2582
  const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
1891
2583
  if (lastAgentIdx === -1) return "fresh";
1892
- const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId || trustChatHistory;
2584
+ const hasPriorWork = !!context.githubPRUrl || trustChatHistory;
1893
2585
  if (!hasPriorWork) return "fresh";
1894
2586
  const messagesAfterAgent = context.chatHistory.slice(lastAgentIdx + 1);
1895
2587
  const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
@@ -1928,7 +2620,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1928
2620
  parts.push(
1929
2621
  `
1930
2622
  You are in auto mode. A plan already exists for this task.`,
1931
- `Verify that story points, title, and tags are set via get_task_plan, then call ExitPlanMode immediately to transition to building.`,
2623
+ `Verify that story points, title, and tags are set via get_current_plan, then call ExitPlanMode immediately to transition to building.`,
1932
2624
  `Do NOT wait for team input \u2014 proceed autonomously.`
1933
2625
  );
1934
2626
  } else {
@@ -1950,13 +2642,14 @@ You are the project manager for this task.`,
1950
2642
  }
1951
2643
  function buildRelaunchWithSession(mode, context, agentMode, isAuto) {
1952
2644
  const scenario = detectRelaunchScenario(context);
1953
- if (!context.claudeSessionId || scenario === "fresh") return null;
2645
+ const hasPriorTurn = !!context.lastSeenMessageId || !!context.claudeSessionId;
2646
+ if (!hasPriorTurn || scenario === "fresh") return null;
1954
2647
  const parts = [];
1955
2648
  const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
1956
2649
  if (mode === "pm") {
1957
2650
  parts.push(...buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode));
1958
2651
  } else if (scenario === "feedback_relaunch") {
1959
- const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
2652
+ const newMessages = (context.lastSeenMessageId ? messagesAfterCursor(context.chatHistory, context.lastSeenMessageId) : context.chatHistory.slice(lastAgentIdx + 1)).filter((m) => m.role === "user");
1960
2653
  parts.push(
1961
2654
  `You have been relaunched with new feedback.`,
1962
2655
  `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
@@ -2027,7 +2720,7 @@ ${context.description}`);
2027
2720
  if (context.plan) {
2028
2721
  parts.push(`
2029
2722
  ## Plan
2030
- ${context.plan}`);
2723
+ ${truncatePlanForPrompt(context.plan)}`);
2031
2724
  }
2032
2725
  if (context.files && context.files.length > 0) {
2033
2726
  parts.push(`
@@ -2039,6 +2732,9 @@ ${context.plan}`);
2039
2732
  if (context.repoRefs && context.repoRefs.length > 0) {
2040
2733
  parts.push(...formatRepoRefs(context.repoRefs));
2041
2734
  }
2735
+ if (context.referenceProjects && context.referenceProjects.length > 0) {
2736
+ parts.push(...formatReferenceProjects(context.referenceProjects));
2737
+ }
2042
2738
  const tagSection = await resolveTaskTagContext(context, runnerMode);
2043
2739
  if (tagSection) parts.push(tagSection);
2044
2740
  if (runnerMode !== "task") {
@@ -2084,7 +2780,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2084
2780
  return [
2085
2781
  `You are operating autonomously. Begin planning immediately.`,
2086
2782
  `1. Search the codebase (grep/glob) to locate relevant files, then read the critical ones`,
2087
- `2. Draft a clear implementation plan and save it with update_task`,
2783
+ `2. Draft a clear implementation plan and save it with update_task_plan`,
2088
2784
  `3. Set story points, tags, and title (update_task_properties)`,
2089
2785
  `4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
2090
2786
  `Do NOT wait for team input \u2014 proceed autonomously.`
@@ -2096,7 +2792,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2096
2792
  `Review existing subtasks via \`list_subtasks\` and the chat history before taking action.`,
2097
2793
  `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.`,
2098
2794
  `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.`,
2099
- `When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
2795
+ `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.`
2100
2796
  ];
2101
2797
  }
2102
2798
  if (isPm) {
@@ -2104,7 +2800,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2104
2800
  `You are the project manager for this task.`,
2105
2801
  `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.`,
2106
2802
  `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.`,
2107
- `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.`
2803
+ `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.`
2108
2804
  ];
2109
2805
  }
2110
2806
  const parts = [
@@ -2148,7 +2844,7 @@ Your plan has been approved. Address the feedback above, then begin implementing
2148
2844
  } else if (isAuto) {
2149
2845
  parts2.push(
2150
2846
  `
2151
- You are in auto mode. Address the feedback above \u2014 update the plan accordingly using update_task.`,
2847
+ You are in auto mode. Address the feedback above \u2014 update the plan accordingly using update_task_plan.`,
2152
2848
  `When the plan and all required properties are set, call ExitPlanMode to transition to building.`,
2153
2849
  `Do NOT wait for additional team input \u2014 the messages above ARE the team's input. Proceed autonomously.`
2154
2850
  );
@@ -2259,14 +2955,14 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
2259
2955
  }
2260
2956
 
2261
2957
  // src/tools/task-context-tools.ts
2262
- import { z } from "zod";
2958
+ import { z as z4 } from "zod";
2263
2959
  function buildReadTaskChatTool(connection) {
2264
2960
  return defineTool(
2265
2961
  "read_task_chat",
2266
- "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).",
2962
+ "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.",
2267
2963
  {
2268
- limit: z.number().optional().describe("Number of recent messages to fetch (default 20)"),
2269
- task_id: z.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
2964
+ limit: z4.number().optional().describe("Number of recent messages to fetch (default 20)"),
2965
+ task_id: z4.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
2270
2966
  },
2271
2967
  async ({ limit, task_id }) => {
2272
2968
  try {
@@ -2287,10 +2983,10 @@ function buildReadTaskChatTool(connection) {
2287
2983
  { annotations: { readOnlyHint: true } }
2288
2984
  );
2289
2985
  }
2290
- function buildGetTaskPlanTool(connection) {
2986
+ function buildGetCurrentPlanTool(connection) {
2291
2987
  return defineTool(
2292
- "get_task_plan",
2293
- "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.",
2988
+ "get_current_plan",
2989
+ "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.",
2294
2990
  {},
2295
2991
  async () => {
2296
2992
  try {
@@ -2308,9 +3004,9 @@ function buildGetTaskPlanTool(connection) {
2308
3004
  function buildGetTaskTool(connection) {
2309
3005
  return defineTool(
2310
3006
  "get_task",
2311
- "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.",
3007
+ "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.",
2312
3008
  {
2313
- slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
3009
+ slug_or_id: z4.string().describe("The task slug (e.g. 'my-task') or CUID")
2314
3010
  },
2315
3011
  async ({ slug_or_id }) => {
2316
3012
  try {
@@ -2328,14 +3024,14 @@ function buildGetTaskTool(connection) {
2328
3024
  { annotations: { readOnlyHint: true } }
2329
3025
  );
2330
3026
  }
2331
- function buildGetTaskCliTool(connection) {
3027
+ function buildGetExecutionLogsTool(connection) {
2332
3028
  return defineTool(
2333
- "get_task_cli",
2334
- "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.",
3029
+ "get_execution_logs",
3030
+ "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.",
2335
3031
  {
2336
- task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
2337
- source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
2338
- limit: z.number().optional().describe("Max number of log entries to return (default 50, max 500).")
3032
+ task_id: z4.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
3033
+ source: z4.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
3034
+ limit: z4.number().optional().describe("Max number of log entries to return (default 50, max 500).")
2339
3035
  },
2340
3036
  async ({ task_id, source, limit }) => {
2341
3037
  try {
@@ -2364,7 +3060,7 @@ function buildGetTaskCliTool(connection) {
2364
3060
  function buildListTaskFilesTool(connection) {
2365
3061
  return defineTool(
2366
3062
  "list_task_files",
2367
- "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.",
3063
+ "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.",
2368
3064
  {},
2369
3065
  async () => {
2370
3066
  try {
@@ -2391,11 +3087,11 @@ function buildListTaskFilesTool(connection) {
2391
3087
  { annotations: { readOnlyHint: true } }
2392
3088
  );
2393
3089
  }
2394
- function buildGetTaskFileTool(connection) {
3090
+ function buildGetAttachmentTool(connection) {
2395
3091
  return defineTool(
2396
- "get_task_file",
2397
- "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.",
2398
- { fileId: z.string().describe("The file ID to retrieve") },
3092
+ "get_attachment",
3093
+ "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.",
3094
+ { fileId: z4.string().describe("The file ID to retrieve") },
2399
3095
  async ({ fileId }) => {
2400
3096
  try {
2401
3097
  const file = await connection.call("getTaskFile", {
@@ -2424,20 +3120,20 @@ function buildGetTaskFileTool(connection) {
2424
3120
  function buildTaskContextTools(connection) {
2425
3121
  return [
2426
3122
  buildReadTaskChatTool(connection),
2427
- buildGetTaskPlanTool(connection),
3123
+ buildGetCurrentPlanTool(connection),
2428
3124
  buildGetTaskTool(connection),
2429
- buildGetTaskCliTool(connection),
3125
+ buildGetExecutionLogsTool(connection),
2430
3126
  buildListTaskFilesTool(connection),
2431
- buildGetTaskFileTool(connection)
3127
+ buildGetAttachmentTool(connection)
2432
3128
  ];
2433
3129
  }
2434
3130
 
2435
3131
  // src/tools/dependency-suggestion-tools.ts
2436
- import { z as z2 } from "zod";
3132
+ import { z as z5 } from "zod";
2437
3133
  function buildGetDependenciesTool(connection) {
2438
3134
  return defineTool(
2439
3135
  "get_dependencies",
2440
- "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).",
3136
+ "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.",
2441
3137
  {},
2442
3138
  async () => {
2443
3139
  try {
@@ -2457,12 +3153,12 @@ function buildGetDependenciesTool(connection) {
2457
3153
  function buildGetSuggestionsTool(connection) {
2458
3154
  return defineTool(
2459
3155
  "get_suggestions",
2460
- "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.",
3156
+ "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.",
2461
3157
  {
2462
- status: z2.string().optional().describe(
3158
+ status: z5.string().optional().describe(
2463
3159
  "Filter by status: Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled"
2464
3160
  ),
2465
- limit: z2.number().int().min(1).max(100).optional().describe("Max results (default 20)")
3161
+ limit: z5.number().int().min(1).max(100).optional().describe("Max results (default 20)")
2466
3162
  },
2467
3163
  async ({ status, limit }) => {
2468
3164
  try {
@@ -2486,14 +3182,14 @@ function buildGetSuggestionsTool(connection) {
2486
3182
  }
2487
3183
 
2488
3184
  // src/tools/mutation-tools.ts
2489
- import { z as z3 } from "zod";
3185
+ import { z as z6 } from "zod";
2490
3186
  function buildPostToChatTool(connection) {
2491
3187
  return defineTool(
2492
3188
  "post_to_chat",
2493
- "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.",
3189
+ "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.",
2494
3190
  {
2495
- message: z3.string().describe("The message to post to the team"),
2496
- task_id: z3.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
3191
+ message: z6.string().describe("The message to post to the team"),
3192
+ task_id: z6.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
2497
3193
  },
2498
3194
  async ({ message, task_id }) => {
2499
3195
  try {
@@ -2503,10 +3199,21 @@ function buildPostToChatTool(connection) {
2503
3199
  childTaskId: task_id,
2504
3200
  message
2505
3201
  });
2506
- return textResult(`Message posted to child task ${task_id} chat.`);
3202
+ return textResult(JSON.stringify({ posted: true, target: `child:${task_id}` }));
3203
+ }
3204
+ const dedup = connection.checkAndTrackDuplicate(message);
3205
+ if (dedup.duplicate) {
3206
+ return textResult(
3207
+ JSON.stringify({
3208
+ posted: false,
3209
+ reason: "duplicate",
3210
+ matchedMessagePreview: dedup.matchedMessagePreview,
3211
+ hint: "A near-identical message (>70% word overlap) was posted within the last 30s. Rephrase with new information or skip this post."
3212
+ })
3213
+ );
2507
3214
  }
2508
3215
  await connection.call("postToChat", { message });
2509
- return textResult("Message posted to task chat.");
3216
+ return textResult(JSON.stringify({ posted: true }));
2510
3217
  } catch (error) {
2511
3218
  return textResult(
2512
3219
  `Failed to post message: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -2518,10 +3225,10 @@ function buildPostToChatTool(connection) {
2518
3225
  function buildForceUpdateTaskStatusTool(connection) {
2519
3226
  return defineTool(
2520
3227
  "force_update_task_status",
2521
- "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.",
3228
+ "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.",
2522
3229
  {
2523
- status: z3.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2524
- task_id: z3.string().optional().describe("Child task ID to update. Omit to update the current task.")
3230
+ status: z6.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
3231
+ task_id: z6.string().optional().describe("Child task ID to update. Omit to update the current task.")
2525
3232
  },
2526
3233
  async ({ status, task_id }) => {
2527
3234
  try {
@@ -2550,17 +3257,17 @@ function buildForceUpdateTaskStatusTool(connection) {
2550
3257
  function buildCreatePullRequestTool(connection, config) {
2551
3258
  return defineTool(
2552
3259
  "create_pull_request",
2553
- "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.",
3260
+ "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.",
2554
3261
  {
2555
- title: z3.string().describe("The PR title"),
2556
- body: z3.string().describe("The PR description/body in markdown"),
2557
- branch: z3.string().optional().describe(
3262
+ title: z6.string().describe("The PR title"),
3263
+ body: z6.string().describe("The PR description/body in markdown"),
3264
+ branch: z6.string().optional().describe(
2558
3265
  "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."
2559
3266
  ),
2560
- baseBranch: z3.string().optional().describe(
3267
+ baseBranch: z6.string().optional().describe(
2561
3268
  "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
2562
3269
  ),
2563
- commitMessage: z3.string().optional().describe(
3270
+ commitMessage: z6.string().optional().describe(
2564
3271
  "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
2565
3272
  )
2566
3273
  },
@@ -2636,9 +3343,9 @@ Troubleshooting:
2636
3343
  function buildAddDependencyTool(connection) {
2637
3344
  return defineTool(
2638
3345
  "add_dependency",
2639
- "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.",
3346
+ "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.",
2640
3347
  {
2641
- depends_on_slug_or_id: z3.string().describe("Slug or ID of the task this task depends on")
3348
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task this task depends on")
2642
3349
  },
2643
3350
  async ({ depends_on_slug_or_id }) => {
2644
3351
  try {
@@ -2660,7 +3367,7 @@ function buildRemoveDependencyTool(connection) {
2660
3367
  "remove_dependency",
2661
3368
  "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.",
2662
3369
  {
2663
- depends_on_slug_or_id: z3.string().describe("Slug or ID of the task to remove as dependency")
3370
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task to remove as dependency")
2664
3371
  },
2665
3372
  async ({ depends_on_slug_or_id }) => {
2666
3373
  try {
@@ -2680,12 +3387,12 @@ function buildRemoveDependencyTool(connection) {
2680
3387
  function buildCreateFollowUpTaskTool(connection) {
2681
3388
  return defineTool(
2682
3389
  "create_follow_up_task",
2683
- "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.",
3390
+ "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.",
2684
3391
  {
2685
- title: z3.string().describe("Follow-up task title"),
2686
- description: z3.string().optional().describe("Brief description of the follow-up work"),
2687
- plan: z3.string().optional().describe("Implementation plan if known"),
2688
- story_point_value: z3.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
3392
+ title: z6.string().describe("Follow-up task title"),
3393
+ description: z6.string().optional().describe("Brief description of the follow-up work"),
3394
+ plan: z6.string().optional().describe("Implementation plan if known"),
3395
+ story_point_value: z6.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
2689
3396
  },
2690
3397
  async ({ title, description, plan, story_point_value }) => {
2691
3398
  try {
@@ -2710,13 +3417,13 @@ function buildCreateFollowUpTaskTool(connection) {
2710
3417
  function buildCreateSuggestionTool(connection) {
2711
3418
  return defineTool(
2712
3419
  "create_suggestion",
2713
- "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).",
3420
+ "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.",
2714
3421
  {
2715
- title: z3.string().describe("Short title for the suggestion"),
2716
- description: z3.string().optional().describe(
3422
+ title: z6.string().describe("Short title for the suggestion"),
3423
+ description: z6.string().optional().describe(
2717
3424
  "1-2 sentence description of what should change and why. Keep concise and project-focused."
2718
3425
  ),
2719
- tag_names: z3.array(z3.string()).optional().describe("Tag names to categorize the suggestion")
3426
+ tag_names: z6.array(z6.string()).optional().describe("Tag names to categorize the suggestion")
2720
3427
  },
2721
3428
  async ({ title, description, tag_names }) => {
2722
3429
  try {
@@ -2743,10 +3450,10 @@ function buildCreateSuggestionTool(connection) {
2743
3450
  function buildVoteSuggestionTool(connection) {
2744
3451
  return defineTool(
2745
3452
  "vote_suggestion",
2746
- "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.",
3453
+ "Vote +1 or -1 on a project suggestion. Use to express support or disagreement with a specific suggestion returned by get_suggestions.",
2747
3454
  {
2748
- suggestion_id: z3.string().describe("The suggestion ID to vote on"),
2749
- value: z3.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
3455
+ suggestion_id: z6.string().describe("The suggestion ID to vote on"),
3456
+ value: z6.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
2750
3457
  },
2751
3458
  async ({ suggestion_id, value }) => {
2752
3459
  try {
@@ -2787,15 +3494,15 @@ function buildCommonTools(connection, config) {
2787
3494
  }
2788
3495
 
2789
3496
  // src/tools/pm-tools.ts
2790
- import { z as z4 } from "zod";
3497
+ import { z as z7 } from "zod";
2791
3498
  var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2792
3499
  function buildUpdateTaskTool(connection) {
2793
3500
  return defineTool(
2794
- "update_task",
2795
- "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.",
3501
+ "update_task_plan",
3502
+ "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.",
2796
3503
  {
2797
- plan: z4.string().optional().describe("The task plan in markdown"),
2798
- description: z4.string().optional().describe("Updated task description")
3504
+ plan: z7.string().optional().describe("The task plan in markdown"),
3505
+ description: z7.string().optional().describe("Updated task description")
2799
3506
  },
2800
3507
  async ({ plan, description }) => {
2801
3508
  try {
@@ -2814,13 +3521,13 @@ function buildUpdateTaskTool(connection) {
2814
3521
  function buildCreateSubtaskTool(connection) {
2815
3522
  return defineTool(
2816
3523
  "create_subtask",
2817
- "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.",
3524
+ "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.",
2818
3525
  {
2819
- title: z4.string().describe("Subtask title"),
2820
- description: z4.string().optional().describe("Brief description"),
2821
- plan: z4.string().optional().describe("Implementation plan in markdown"),
2822
- ordinal: z4.number().optional().describe("Step/order number (0-based)"),
2823
- storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
3526
+ title: z7.string().describe("Subtask title"),
3527
+ description: z7.string().optional().describe("Brief description"),
3528
+ plan: z7.string().optional().describe("Implementation plan in markdown"),
3529
+ ordinal: z7.number().optional().describe("Step/order number (0-based)"),
3530
+ storyPointValue: z7.number().optional().describe(SP_DESCRIPTION)
2824
3531
  },
2825
3532
  async ({ title, description, plan, ordinal, storyPointValue }) => {
2826
3533
  try {
@@ -2844,14 +3551,14 @@ function buildCreateSubtaskTool(connection) {
2844
3551
  function buildUpdateSubtaskTool(connection) {
2845
3552
  return defineTool(
2846
3553
  "update_subtask",
2847
- "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.",
3554
+ "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.",
2848
3555
  {
2849
- subtaskId: z4.string().describe("The subtask ID to update"),
2850
- title: z4.string().optional(),
2851
- description: z4.string().optional(),
2852
- plan: z4.string().optional(),
2853
- ordinal: z4.number().optional(),
2854
- storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
3556
+ subtaskId: z7.string().describe("The subtask ID to update"),
3557
+ title: z7.string().optional(),
3558
+ description: z7.string().optional(),
3559
+ plan: z7.string().optional(),
3560
+ ordinal: z7.number().optional(),
3561
+ storyPointValue: z7.number().optional().describe(SP_DESCRIPTION)
2855
3562
  },
2856
3563
  async ({ subtaskId, title, description, plan, storyPointValue }) => {
2857
3564
  try {
@@ -2874,7 +3581,7 @@ function buildDeleteSubtaskTool(connection) {
2874
3581
  return defineTool(
2875
3582
  "delete_subtask",
2876
3583
  "Delete a subtask by id. When to use: a subtask was created in error or is no longer needed. Returns: confirmation string.",
2877
- { subtaskId: z4.string().describe("The subtask ID to delete") },
3584
+ { subtaskId: z7.string().describe("The subtask ID to delete") },
2878
3585
  async ({ subtaskId }) => {
2879
3586
  try {
2880
3587
  await connection.call("deleteSubtask", {
@@ -2891,7 +3598,7 @@ function buildDeleteSubtaskTool(connection) {
2891
3598
  function buildListSubtasksTool(connection) {
2892
3599
  return defineTool(
2893
3600
  "list_subtasks",
2894
- "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.",
3601
+ "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.",
2895
3602
  {},
2896
3603
  async () => {
2897
3604
  try {
@@ -2910,9 +3617,9 @@ function buildPackTools(connection) {
2910
3617
  return [
2911
3618
  defineTool(
2912
3619
  "start_child_cloud_build",
2913
- "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.",
3620
+ "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.",
2914
3621
  {
2915
- childTaskId: z4.string().describe("The child task ID to start a cloud build for")
3622
+ childTaskId: z7.string().describe("The child task ID to start a cloud build for")
2916
3623
  },
2917
3624
  async ({ childTaskId }) => {
2918
3625
  try {
@@ -2930,9 +3637,9 @@ function buildPackTools(connection) {
2930
3637
  ),
2931
3638
  defineTool(
2932
3639
  "stop_child_build",
2933
- "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.",
3640
+ "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.",
2934
3641
  {
2935
- childTaskId: z4.string().describe("The child task ID whose build should be stopped")
3642
+ childTaskId: z7.string().describe("The child task ID whose build should be stopped")
2936
3643
  },
2937
3644
  async ({ childTaskId }) => {
2938
3645
  try {
@@ -2950,9 +3657,9 @@ function buildPackTools(connection) {
2950
3657
  ),
2951
3658
  defineTool(
2952
3659
  "approve_and_merge_pr",
2953
- "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.",
3660
+ "Approve and merge a child task's PR. Preconditions: child in ReviewPR. Returns { merged }: true = merged (status\u2192ReviewDev); false = automerge queued, wait for ReviewDev.",
2954
3661
  {
2955
- childTaskId: z4.string().describe("The child task ID whose PR should be approved and merged")
3662
+ childTaskId: z7.string().describe("The child task ID whose PR should be approved and merged")
2956
3663
  },
2957
3664
  async ({ childTaskId }) => {
2958
3665
  try {
@@ -2990,7 +3697,7 @@ function buildPmTools(connection, options) {
2990
3697
  }
2991
3698
 
2992
3699
  // src/tools/discovery-tools.ts
2993
- import { z as z5 } from "zod";
3700
+ import { z as z8 } from "zod";
2994
3701
  var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2995
3702
  function buildDiscoveryTools(connection) {
2996
3703
  return [
@@ -2998,11 +3705,11 @@ function buildDiscoveryTools(connection) {
2998
3705
  "update_task_properties",
2999
3706
  "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
3000
3707
  {
3001
- title: z5.string().optional().describe("The new task title"),
3002
- storyPointValue: z5.number().optional().describe(SP_DESCRIPTION2),
3003
- tagNames: z5.array(z5.string()).optional().describe("Array of tag names to assign"),
3004
- githubPRUrl: z5.string().url().optional().describe("GitHub pull request URL to link to this task"),
3005
- githubBranch: z5.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3708
+ title: z8.string().optional().describe("The new task title"),
3709
+ storyPointValue: z8.number().optional().describe(SP_DESCRIPTION2),
3710
+ tagNames: z8.array(z8.string()).optional().describe("Array of tag names to assign"),
3711
+ githubPRUrl: z8.string().url().optional().describe("GitHub pull request URL to link to this task"),
3712
+ githubBranch: z8.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3006
3713
  },
3007
3714
  async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
3008
3715
  try {
@@ -3033,14 +3740,14 @@ function buildDiscoveryTools(connection) {
3033
3740
  }
3034
3741
 
3035
3742
  // src/tools/code-review-tools.ts
3036
- import { z as z6 } from "zod";
3743
+ import { z as z9 } from "zod";
3037
3744
  function buildCodeReviewTools(connection) {
3038
3745
  return [
3039
3746
  defineTool(
3040
3747
  "approve_code_review",
3041
- "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.",
3748
+ "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.",
3042
3749
  {
3043
- summary: z6.string().describe("Brief summary of what was reviewed and why it looks good")
3750
+ summary: z9.string().describe("Brief summary of what was reviewed and why it looks good")
3044
3751
  },
3045
3752
  async ({ summary }) => {
3046
3753
  const content = `**Code Review: Approved** :white_check_mark:
@@ -3061,17 +3768,17 @@ ${summary}`;
3061
3768
  ),
3062
3769
  defineTool(
3063
3770
  "request_code_changes",
3064
- "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.",
3771
+ "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 }.",
3065
3772
  {
3066
- issues: z6.array(
3067
- z6.object({
3068
- file: z6.string().describe("File path where the issue was found"),
3069
- line: z6.number().optional().describe("Line number (if applicable)"),
3070
- severity: z6.enum(["critical", "major", "minor"]).describe("Issue severity"),
3071
- description: z6.string().describe("What is wrong and how to fix it")
3773
+ issues: z9.array(
3774
+ z9.object({
3775
+ file: z9.string().describe("File path where the issue was found"),
3776
+ line: z9.number().optional().describe("Line number (if applicable)"),
3777
+ severity: z9.enum(["critical", "major", "minor"]).describe("Issue severity"),
3778
+ description: z9.string().describe("What is wrong and how to fix it")
3072
3779
  })
3073
3780
  ).describe("List of issues found during review"),
3074
- summary: z6.string().describe("Brief overall summary of the review findings")
3781
+ summary: z9.string().describe("Brief overall summary of the review findings")
3075
3782
  },
3076
3783
  async ({ issues, summary }) => {
3077
3784
  const issueLines = issues.map((issue) => {
@@ -3101,10 +3808,10 @@ ${issueLines}`;
3101
3808
  }
3102
3809
 
3103
3810
  // src/tools/debug-tools.ts
3104
- import { z as z9 } from "zod";
3811
+ import { z as z12 } from "zod";
3105
3812
 
3106
3813
  // src/tools/telemetry-tools.ts
3107
- import { z as z7 } from "zod";
3814
+ import { z as z10 } from "zod";
3108
3815
 
3109
3816
  // src/debug/telemetry-injector.ts
3110
3817
  var BUFFER_SIZE = 200;
@@ -3497,14 +4204,14 @@ function formatError(error) {
3497
4204
  function buildGetTelemetryTool(manager) {
3498
4205
  return defineTool(
3499
4206
  "debug_get_telemetry",
3500
- "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.",
4207
+ "Query structured telemetry events (HTTP, DB, Socket.IO, errors) captured from the dev server. Returns filtered structured data instead of raw logs.",
3501
4208
  {
3502
- type: z7.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3503
- urlPattern: z7.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3504
- minDuration: z7.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3505
- errorOnly: z7.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3506
- since: z7.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3507
- limit: z7.number().optional().describe("Max events to return (default: 20, from most recent)")
4209
+ type: z10.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
4210
+ urlPattern: z10.string().optional().describe("Regex pattern to filter HTTP events by URL"),
4211
+ minDuration: z10.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
4212
+ errorOnly: z10.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
4213
+ since: z10.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
4214
+ limit: z10.number().optional().describe("Max events to return (default: 20, from most recent)")
3508
4215
  },
3509
4216
  async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3510
4217
  const clientOrErr = requireDebugClient(manager);
@@ -3574,7 +4281,7 @@ function buildTelemetryTools(manager) {
3574
4281
  }
3575
4282
 
3576
4283
  // src/tools/client-debug-tools.ts
3577
- import { z as z8 } from "zod";
4284
+ import { z as z11 } from "zod";
3578
4285
  function requirePlaywrightClient(manager) {
3579
4286
  if (!manager.isClientDebugMode()) {
3580
4287
  return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
@@ -3592,13 +4299,13 @@ function buildClientBreakpointTools(manager) {
3592
4299
  return [
3593
4300
  defineTool(
3594
4301
  "debug_set_client_breakpoint",
3595
- "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.",
4302
+ "Set a breakpoint in client-side code running in headless Chromium. V8 resolves source maps automatically \u2014 use original .tsx/.ts file paths.",
3596
4303
  {
3597
- file: z8.string().describe(
4304
+ file: z11.string().describe(
3598
4305
  "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3599
4306
  ),
3600
- line: z8.number().describe("Line number (1-based) in the original source file"),
3601
- condition: z8.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4307
+ line: z11.number().describe("Line number (1-based) in the original source file"),
4308
+ condition: z11.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3602
4309
  },
3603
4310
  async ({ file, line, condition }) => {
3604
4311
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3620,7 +4327,7 @@ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3620
4327
  "debug_remove_client_breakpoint",
3621
4328
  "Remove a previously set client-side breakpoint by its ID.",
3622
4329
  {
3623
- breakpointId: z8.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
4330
+ breakpointId: z11.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3624
4331
  },
3625
4332
  async ({ breakpointId }) => {
3626
4333
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3655,7 +4362,7 @@ function buildClientInspectionTools(manager) {
3655
4362
  return [
3656
4363
  defineTool(
3657
4364
  "debug_inspect_client_paused",
3658
- "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.",
4365
+ "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.",
3659
4366
  {},
3660
4367
  async () => {
3661
4368
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3698,10 +4405,10 @@ ${JSON.stringify(queuedHits, null, 2)}`
3698
4405
  ),
3699
4406
  defineTool(
3700
4407
  "debug_evaluate_client",
3701
- "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`.",
4408
+ "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.",
3702
4409
  {
3703
- expression: z8.string().describe("JavaScript expression to evaluate in the browser context"),
3704
- frameIndex: z8.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4410
+ expression: z11.string().describe("JavaScript expression to evaluate in the browser context"),
4411
+ frameIndex: z11.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3705
4412
  },
3706
4413
  async ({ expression, frameIndex }) => {
3707
4414
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3772,9 +4479,9 @@ function buildClientInteractionTools(manager) {
3772
4479
  ),
3773
4480
  defineTool(
3774
4481
  "debug_navigate_client",
3775
- "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.",
4482
+ "Navigate the headless browser to a URL. Waits for domcontentloaded (Playwright's default ~30s timeout applies).",
3776
4483
  {
3777
- url: z8.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
4484
+ url: z11.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3778
4485
  },
3779
4486
  async ({ url }) => {
3780
4487
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3789,9 +4496,9 @@ function buildClientInteractionTools(manager) {
3789
4496
  ),
3790
4497
  defineTool(
3791
4498
  "debug_click_client",
3792
- "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.",
4499
+ "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.",
3793
4500
  {
3794
- selector: z8.string().describe(
4501
+ selector: z11.string().describe(
3795
4502
  "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3796
4503
  )
3797
4504
  },
@@ -3813,8 +4520,8 @@ function buildClientConsoleTool(manager) {
3813
4520
  "debug_get_client_console",
3814
4521
  "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3815
4522
  {
3816
- level: z8.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3817
- limit: z8.number().optional().describe("Maximum number of recent messages to return (default: all)")
4523
+ level: z11.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
4524
+ limit: z11.number().optional().describe("Maximum number of recent messages to return (default: all)")
3818
4525
  },
3819
4526
  // oxlint-disable-next-line require-await
3820
4527
  async ({ level, limit }) => {
@@ -3841,8 +4548,8 @@ function buildClientNetworkTool(manager) {
3841
4548
  "debug_get_client_network",
3842
4549
  "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3843
4550
  {
3844
- filter: z8.string().optional().describe("Regex pattern to filter requests by URL"),
3845
- limit: z8.number().optional().describe("Maximum number of recent requests to return (default: all)")
4551
+ filter: z11.string().optional().describe("Regex pattern to filter requests by URL"),
4552
+ limit: z11.number().optional().describe("Maximum number of recent requests to return (default: all)")
3846
4553
  },
3847
4554
  // oxlint-disable-next-line require-await
3848
4555
  async ({ filter, limit }) => {
@@ -3870,7 +4577,7 @@ function buildClientErrorsTool(manager) {
3870
4577
  "debug_get_client_errors",
3871
4578
  "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3872
4579
  {
3873
- limit: z8.number().optional().describe("Maximum number of recent errors to return (default: all)")
4580
+ limit: z11.number().optional().describe("Maximum number of recent errors to return (default: all)")
3874
4581
  },
3875
4582
  // oxlint-disable-next-line require-await
3876
4583
  async ({ limit }) => {
@@ -3962,14 +4669,14 @@ function buildDebugLifecycleTools(manager) {
3962
4669
  return [
3963
4670
  defineTool(
3964
4671
  "debug_enter_mode",
3965
- "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.",
4672
+ "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).",
3966
4673
  {
3967
- hypothesis: z9.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3968
- serverSide: z9.boolean().optional().describe(
4674
+ hypothesis: z12.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
4675
+ serverSide: z12.boolean().optional().describe(
3969
4676
  "Enable server-side Node.js debugging (default: true if clientSide is not set)"
3970
4677
  ),
3971
- clientSide: z9.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
3972
- previewUrl: z9.string().optional().describe(
4678
+ clientSide: z12.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
4679
+ previewUrl: z12.string().optional().describe(
3973
4680
  "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
3974
4681
  )
3975
4682
  },
@@ -4005,9 +4712,9 @@ function buildBreakpointTools(manager) {
4005
4712
  "debug_set_breakpoint",
4006
4713
  "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.",
4007
4714
  {
4008
- file: z9.string().describe("Absolute or relative file path to set the breakpoint in"),
4009
- line: z9.number().describe("Line number (1-based) to set the breakpoint on"),
4010
- condition: z9.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4715
+ file: z12.string().describe("Absolute or relative file path to set the breakpoint in"),
4716
+ line: z12.number().describe("Line number (1-based) to set the breakpoint on"),
4717
+ condition: z12.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4011
4718
  },
4012
4719
  async ({ file, line, condition }) => {
4013
4720
  const clientOrErr = requireDebugClient2(manager);
@@ -4029,7 +4736,7 @@ Breakpoint ID: ${breakpointId}`
4029
4736
  "debug_remove_breakpoint",
4030
4737
  "Remove a previously set breakpoint by its ID.",
4031
4738
  {
4032
- breakpointId: z9.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4739
+ breakpointId: z12.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4033
4740
  },
4034
4741
  async ({ breakpointId }) => {
4035
4742
  const clientOrErr = requireDebugClient2(manager);
@@ -4108,10 +4815,10 @@ ${JSON.stringify(queuedHits, null, 2)}`
4108
4815
  ),
4109
4816
  defineTool(
4110
4817
  "debug_evaluate",
4111
- "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`.",
4818
+ "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.",
4112
4819
  {
4113
- expression: z9.string().describe("The JavaScript expression to evaluate"),
4114
- frameIndex: z9.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4820
+ expression: z12.string().describe("The JavaScript expression to evaluate"),
4821
+ frameIndex: z12.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4115
4822
  },
4116
4823
  async ({ expression, frameIndex }) => {
4117
4824
  const clientOrErr = requireDebugClient2(manager);
@@ -4137,14 +4844,14 @@ function buildProbeManagementTools(manager) {
4137
4844
  return [
4138
4845
  defineTool(
4139
4846
  "debug_add_probe",
4140
- "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.",
4847
+ "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.",
4141
4848
  {
4142
- file: z9.string().describe("File path to probe"),
4143
- line: z9.number().describe("Line number (1-based) to probe"),
4144
- expressions: z9.array(z9.string()).describe(
4849
+ file: z12.string().describe("File path to probe"),
4850
+ line: z12.number().describe("Line number (1-based) to probe"),
4851
+ expressions: z12.array(z12.string()).describe(
4145
4852
  'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4146
4853
  ),
4147
- label: z9.string().optional().describe("Optional label for this probe (defaults to file:line)")
4854
+ label: z12.string().optional().describe("Optional label for this probe (defaults to file:line)")
4148
4855
  },
4149
4856
  async ({ file, line, expressions, label }) => {
4150
4857
  const clientOrErr = requireDebugClient2(manager);
@@ -4169,7 +4876,7 @@ Trigger the code path, then use debug_get_probe_results to see captured values.`
4169
4876
  "debug_remove_probe",
4170
4877
  "Remove a previously set debug probe by its ID.",
4171
4878
  {
4172
- probeId: z9.string().describe("The probe ID returned by debug_add_probe")
4879
+ probeId: z12.string().describe("The probe ID returned by debug_add_probe")
4173
4880
  },
4174
4881
  async ({ probeId }) => {
4175
4882
  const clientOrErr = requireDebugClient2(manager);
@@ -4207,11 +4914,11 @@ function buildProbeResultTools(manager) {
4207
4914
  return [
4208
4915
  defineTool(
4209
4916
  "debug_get_probe_results",
4210
- "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.",
4917
+ "Fetch captured probe hit data. Filter by label (wins) or probeId. Returns grouped text with per-probe hit count, timestamps, and captured expression values.",
4211
4918
  {
4212
- probeId: z9.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4213
- label: z9.string().optional().describe("Filter results by probe label"),
4214
- limit: z9.number().optional().describe("Maximum number of recent hits to return (default: all)")
4919
+ probeId: z12.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4920
+ label: z12.string().optional().describe("Filter results by probe label"),
4921
+ limit: z12.number().optional().describe("Maximum number of recent hits to return (default: all)")
4215
4922
  },
4216
4923
  async ({ probeId, label, limit }) => {
4217
4924
  const clientOrErr = requireDebugClient2(manager);
@@ -4805,7 +5512,7 @@ async function processEvents(events, context, host) {
4805
5512
  // src/execution/task-property-utils.ts
4806
5513
  function collectMissingProps(taskProps) {
4807
5514
  const missing = [];
4808
- if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
5515
+ if (!taskProps.plan?.trim()) missing.push("plan (save via update_task_plan)");
4809
5516
  if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
4810
5517
  if (!taskProps.title || taskProps.title === "Untitled")
4811
5518
  missing.push("title (use update_task_properties)");
@@ -4877,13 +5584,32 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
4877
5584
  }
4878
5585
  return { behavior: "allow", updatedInput: input };
4879
5586
  }
5587
+ function enforceMissingProps(host, input, missingProps) {
5588
+ if (missingProps.length === 0) return null;
5589
+ if (input.bypassValidation !== true) {
5590
+ return {
5591
+ behavior: "deny",
5592
+ message: [
5593
+ "Cannot exit plan mode. Required task properties are missing:",
5594
+ ...missingProps.map((p) => `- ${p}`),
5595
+ "",
5596
+ "Fill these in using MCP tools (e.g. update_task_plan, update_task_properties), then call ExitPlanMode again.",
5597
+ "",
5598
+ "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."
5599
+ ].join("\n")
5600
+ };
5601
+ }
5602
+ host.connection.postChatMessage(
5603
+ `\u26A0\uFE0F [BYPASS] ExitPlanMode forced through with \`bypassValidation: true\` despite missing required properties: ${missingProps.join(", ")}. Please backfill these.`
5604
+ );
5605
+ return null;
5606
+ }
4880
5607
  async function handleExitPlanMode(host, input) {
4881
5608
  if (host.hasExitedPlanMode) {
4882
5609
  return { behavior: "allow", updatedInput: input };
4883
5610
  }
4884
- host.exitPlanAttempts++;
4885
5611
  try {
4886
- host.syncPlanFile();
5612
+ await host.syncPlanFile();
4887
5613
  const taskProps = await host.connection.getTaskProperties();
4888
5614
  const missingProps = collectMissingProps(taskProps);
4889
5615
  if (host.isParentTask) {
@@ -4903,30 +5629,22 @@ async function handleExitPlanMode(host, input) {
4903
5629
  } catch {
4904
5630
  }
4905
5631
  }
4906
- if (missingProps.length > 0) {
4907
- if (host.exitPlanAttempts <= 1) {
4908
- return {
4909
- behavior: "deny",
4910
- message: [
4911
- "Cannot exit plan mode yet. Required task properties are missing:",
4912
- ...missingProps.map((p) => `- ${p}`),
4913
- "",
4914
- "Fill these in using MCP tools, then try ExitPlanMode again."
4915
- ].join("\n")
4916
- };
4917
- }
4918
- host.connection.postChatMessage(
4919
- `\u26A0\uFE0F ExitPlanMode allowed with missing properties: ${missingProps.join(", ")}. Consider backfilling these later.`
4920
- );
4921
- }
5632
+ const gate = enforceMissingProps(host, input, missingProps);
5633
+ if (gate) return gate;
4922
5634
  if (host.agentMode === "discovery") {
4923
5635
  host.hasExitedPlanMode = true;
4924
5636
  host.discoveryCompleted = true;
4925
- host.requestStop();
4926
- void host.connection.triggerIdentification();
4927
- host.connection.postChatMessage(
4928
- "Plan complete. Running identification \u2014 icon and agent assignment will be set automatically."
5637
+ try {
5638
+ await host.connection.triggerIdentification();
5639
+ } catch (triggerErr) {
5640
+ host.connection.postChatMessage(
5641
+ `Identification trigger encountered an issue (${triggerErr instanceof Error ? triggerErr.message : "unknown error"}). Icon and agent assignment may use fallbacks.`
5642
+ );
5643
+ }
5644
+ await host.connection.postChatMessageAwait(
5645
+ "Planning complete \u2014 awaiting team approval. Icon and agent assignment will be set automatically."
4929
5646
  );
5647
+ host.requestStop();
4930
5648
  return { behavior: "allow", updatedInput: input };
4931
5649
  }
4932
5650
  try {
@@ -5319,7 +6037,7 @@ async function runSdkQuery(host, context, followUpContent) {
5319
6037
  }
5320
6038
  }
5321
6039
  if (needsPlanSync) {
5322
- host.syncPlanFile();
6040
+ await host.syncPlanFile();
5323
6041
  }
5324
6042
  }
5325
6043
  async function buildRetryQuery(host, context, options, lastErrorWasImage) {
@@ -5756,7 +6474,6 @@ var QueryBridge = class {
5756
6474
  set hasExitedPlanMode(val) {
5757
6475
  bridge.mode.hasExitedPlanMode = val;
5758
6476
  },
5759
- exitPlanAttempts: 0,
5760
6477
  get pendingModeRestart() {
5761
6478
  return bridge.mode.pendingModeRestart;
5762
6479
  },
@@ -5813,6 +6530,7 @@ import { fileURLToPath } from "url";
5813
6530
  function mapChatHistory(messages) {
5814
6531
  if (!messages) return [];
5815
6532
  return messages.map((m) => ({
6533
+ id: m.id,
5816
6534
  role: m.role ?? "user",
5817
6535
  content: m.content ?? "",
5818
6536
  userId: m.userId,
@@ -5865,6 +6583,8 @@ var SessionRunner = class _SessionRunner {
5865
6583
  inputResolver = null;
5866
6584
  pendingMessages = [];
5867
6585
  prNudgeCount = 0;
6586
+ /** Guards overlapping runs of the periodic git flush. */
6587
+ periodicFlushInFlight = false;
5868
6588
  constructor(config, callbacks) {
5869
6589
  this.config = config;
5870
6590
  this.callbacks = callbacks;
@@ -5883,7 +6603,17 @@ var SessionRunner = class _SessionRunner {
5883
6603
  resolver(null);
5884
6604
  }
5885
6605
  },
5886
- onTokenRefresh: () => void this.refreshGithubToken()
6606
+ onDormantTimeout: () => {
6607
+ process.stderr.write("[conveyor-agent] Dormant idle timeout reached, shutting down\n");
6608
+ this.stopped = true;
6609
+ if (this.inputResolver) {
6610
+ const resolver = this.inputResolver;
6611
+ this.inputResolver = null;
6612
+ resolver(null);
6613
+ }
6614
+ },
6615
+ onTokenRefresh: () => void this.refreshGithubToken(),
6616
+ onGitFlush: () => void this.periodicGitFlush()
5887
6617
  });
5888
6618
  }
5889
6619
  get state() {
@@ -5909,6 +6639,7 @@ var SessionRunner = class _SessionRunner {
5909
6639
  this.wireConnectionCallbacks();
5910
6640
  this.lifecycle.startHeartbeat();
5911
6641
  this.lifecycle.startTokenRefresh();
6642
+ this.lifecycle.startGitFlush();
5912
6643
  const { pendingMessages: serverMessages } = await this.connection.call("connectAgent", {
5913
6644
  sessionId: this.sessionId
5914
6645
  });
@@ -6003,9 +6734,15 @@ var SessionRunner = class _SessionRunner {
6003
6734
  );
6004
6735
  this.pendingMessages.length = 0;
6005
6736
  if (this._state !== "idle") await this.setState("idle");
6737
+ this.lifecycle.startDormantTimer();
6006
6738
  const dormantMsg = await this.waitForMessage();
6739
+ this.lifecycle.cancelDormantTimer();
6007
6740
  if (!dormantMsg) break;
6008
- process.stderr.write("[conveyor-agent] Received message while dormant, resuming\n");
6741
+ const contentPreview = dormantMsg.content.length > 80 ? `${dormantMsg.content.slice(0, 80)}...` : dormantMsg.content;
6742
+ process.stderr.write(
6743
+ `[conveyor-agent] Received message while dormant, resuming: userId=${dormantMsg.userId}, source=${dormantMsg.source || "unknown"}, content="${contentPreview.replace(/\n/g, "\\n")}"
6744
+ `
6745
+ );
6009
6746
  this.completedThisTurn = false;
6010
6747
  this.pendingMessages.unshift(dormantMsg);
6011
6748
  continue;
@@ -6114,6 +6851,38 @@ var SessionRunner = class _SessionRunner {
6114
6851
  }
6115
6852
  }
6116
6853
  // ── Stop / soft-stop ───────────────────────────────────────────────
6854
+ /** Periodic best-effort WIP commit + push during normal agent execution.
6855
+ * Covers ungraceful pod termination (OOMKilled, node crash/eviction) where
6856
+ * the preStop hook + SIGTERM flush don't get a chance to run. No-ops on a
6857
+ * clean tree. Guarded so two ticks can't overlap. Never throws. */
6858
+ async periodicGitFlush() {
6859
+ if (this.periodicFlushInFlight || this.stopped) return;
6860
+ this.periodicFlushInFlight = true;
6861
+ try {
6862
+ const result = await flushPendingChanges(this.config.workspaceDir, {
6863
+ wipMessage: "WIP: periodic auto-commit",
6864
+ refreshToken: async () => {
6865
+ try {
6866
+ const res = await this.connection.call("refreshGithubToken", {
6867
+ sessionId: this.connection.sessionId
6868
+ });
6869
+ return res.token;
6870
+ } catch {
6871
+ return void 0;
6872
+ }
6873
+ }
6874
+ });
6875
+ if (result.hadWork) {
6876
+ process.stderr.write(
6877
+ `[conveyor-agent] Periodic git flush: committed=${result.committed} pushed=${result.pushed}
6878
+ `
6879
+ );
6880
+ }
6881
+ } catch {
6882
+ } finally {
6883
+ this.periodicFlushInFlight = false;
6884
+ }
6885
+ }
6117
6886
  /** Best-effort WIP commit + push on shutdown so in-flight work isn't lost
6118
6887
  * when a claudespace pod is killed. Must be called BEFORE stop() so the
6119
6888
  * connection is still alive for token refresh. Never throws. */
@@ -6241,6 +7010,7 @@ var SessionRunner = class _SessionRunner {
6241
7010
  projectDescription: ctx.projectDescription ?? null,
6242
7011
  githubPRUrl: ctx.githubPRUrl,
6243
7012
  claudeSessionId: ctx.claudeSessionId ?? null,
7013
+ lastSeenMessageId: ctx.lastSeenMessageId ?? null,
6244
7014
  isParentTask: !!ctx.parentTaskId,
6245
7015
  storyPoints: ctx.storyPoints ?? void 0,
6246
7016
  projectAgents: ctx.projectAgents ?? void 0,
@@ -6274,6 +7044,7 @@ var SessionRunner = class _SessionRunner {
6274
7044
  onEvent: (event) => {
6275
7045
  if (event.type === "completed") {
6276
7046
  this.completedThisTurn = true;
7047
+ void this.connection.sendHeartbeat();
6277
7048
  }
6278
7049
  return this.callbacks.onEvent(event);
6279
7050
  }
@@ -6410,23 +7181,30 @@ var SessionRunner = class _SessionRunner {
6410
7181
  get finalState() {
6411
7182
  return this._finalState;
6412
7183
  }
6413
- logInitialization() {
6414
- const context = {
6415
- mode: this.mode.effectiveMode,
6416
- runnerMode: this.config.runnerMode ?? "task",
6417
- sessionId: this.sessionId,
6418
- // Task context
7184
+ buildTaskContextSnapshot() {
7185
+ return {
6419
7186
  isParentTask: this.fullContext?.isParentTask ?? false,
6420
7187
  status: this.taskContext?.status,
6421
7188
  taskTitle: this.fullContext?.title,
6422
7189
  hasExistingPR: !!this.fullContext?.githubPRUrl,
6423
7190
  hasExistingSession: !!this.fullContext?.claudeSessionId,
6424
7191
  chatHistoryLength: this.fullContext?.chatHistory?.length ?? 0,
6425
- tagIds: this.fullContext?.taskTagIds ?? [],
6426
- // Agent config
7192
+ tagIds: this.fullContext?.taskTagIds ?? []
7193
+ };
7194
+ }
7195
+ buildInitializationContext() {
7196
+ return {
7197
+ mode: this.mode.effectiveMode,
7198
+ runnerMode: this.config.runnerMode ?? "task",
7199
+ sessionId: this.sessionId,
7200
+ ...this.buildTaskContextSnapshot(),
6427
7201
  model: this.taskContext?.model,
6428
- isAuto: this.config.isAuto ?? false
7202
+ isAuto: this.config.isAuto ?? false,
7203
+ subscriptionKeyLabel: process.env.CONVEYOR_SUBSCRIPTION_KEY_LABEL ?? null
6429
7204
  };
7205
+ }
7206
+ logInitialization() {
7207
+ const context = this.buildInitializationContext();
6430
7208
  process.stderr.write(`[conveyor-agent] Initialized: ${JSON.stringify(context)}
6431
7209
  `);
6432
7210
  this.connection.sendEvent({ type: "session_manifest", ...context });
@@ -6836,7 +7614,7 @@ function runStartCommand(cmd, cwd, onOutput) {
6836
7614
  }
6837
7615
 
6838
7616
  // src/tools/project-tools.ts
6839
- import { z as z10 } from "zod";
7617
+ import { z as z13 } from "zod";
6840
7618
  function buildTaskListTools(connection) {
6841
7619
  const projectId = connection.projectId;
6842
7620
  return [
@@ -6844,9 +7622,9 @@ function buildTaskListTools(connection) {
6844
7622
  "list_tasks",
6845
7623
  "List tasks in the project. Optionally filter by status or assignee.",
6846
7624
  {
6847
- status: z10.string().optional().describe("Filter by task status"),
6848
- assigneeId: z10.string().optional().describe("Filter by assigned user ID"),
6849
- limit: z10.number().optional().describe("Max number of tasks to return (default 50)")
7625
+ status: z13.string().optional().describe("Filter by task status"),
7626
+ assigneeId: z13.string().optional().describe("Filter by assigned user ID"),
7627
+ limit: z13.number().optional().describe("Max number of tasks to return (default 50)")
6850
7628
  },
6851
7629
  async (params) => {
6852
7630
  try {
@@ -6863,7 +7641,7 @@ function buildTaskListTools(connection) {
6863
7641
  defineTool(
6864
7642
  "get_project_task",
6865
7643
  "Get detailed information about a task in this project (chat messages, child tasks, session). Project-runner scope.",
6866
- { task_id: z10.string().describe("The task ID to look up") },
7644
+ { task_id: z13.string().describe("The task ID to look up") },
6867
7645
  async ({ task_id }) => {
6868
7646
  try {
6869
7647
  const task = await connection.call("getProjectTask", { projectId, taskId: task_id });
@@ -6880,10 +7658,10 @@ function buildTaskListTools(connection) {
6880
7658
  "search_tasks",
6881
7659
  "Search tasks by tags, text query, or status filters.",
6882
7660
  {
6883
- tagNames: z10.array(z10.string()).optional().describe("Filter by tag names"),
6884
- searchQuery: z10.string().optional().describe("Text search in title/description"),
6885
- statusFilters: z10.array(z10.string()).optional().describe("Filter by statuses"),
6886
- limit: z10.number().optional().describe("Max results (default 20)")
7661
+ tagNames: z13.array(z13.string()).optional().describe("Filter by tag names"),
7662
+ searchQuery: z13.string().optional().describe("Text search in title/description"),
7663
+ statusFilters: z13.array(z13.string()).optional().describe("Filter by statuses"),
7664
+ limit: z13.number().optional().describe("Max results (default 20)")
6887
7665
  },
6888
7666
  async (params) => {
6889
7667
  try {
@@ -6943,10 +7721,10 @@ function buildMutationTools2(connection) {
6943
7721
  "create_task",
6944
7722
  "Create a new task in the project.",
6945
7723
  {
6946
- title: z10.string().describe("Task title"),
6947
- description: z10.string().optional().describe("Task description"),
6948
- plan: z10.string().optional().describe("Implementation plan in markdown"),
6949
- status: z10.string().optional().describe("Initial status (default: Planning)")
7724
+ title: z13.string().describe("Task title"),
7725
+ description: z13.string().optional().describe("Task description"),
7726
+ plan: z13.string().optional().describe("Implementation plan in markdown"),
7727
+ status: z13.string().optional().describe("Initial status (default: Planning)")
6950
7728
  },
6951
7729
  async (params) => {
6952
7730
  try {
@@ -6963,12 +7741,12 @@ function buildMutationTools2(connection) {
6963
7741
  "update_project_task",
6964
7742
  "Update an existing task's title, description, plan, status, or assignee. Project-runner scope.",
6965
7743
  {
6966
- task_id: z10.string().describe("The task ID to update"),
6967
- title: z10.string().optional().describe("New title"),
6968
- description: z10.string().optional().describe("New description"),
6969
- plan: z10.string().optional().describe("New plan in markdown"),
6970
- status: z10.string().optional().describe("New status"),
6971
- assignedUserId: z10.string().nullable().optional().describe("Assign to user ID, or null to unassign")
7744
+ task_id: z13.string().describe("The task ID to update"),
7745
+ title: z13.string().optional().describe("New title"),
7746
+ description: z13.string().optional().describe("New description"),
7747
+ plan: z13.string().optional().describe("New plan in markdown"),
7748
+ status: z13.string().optional().describe("New status"),
7749
+ assignedUserId: z13.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6972
7750
  },
6973
7751
  async ({ task_id, ...fields }) => {
6974
7752
  try {
@@ -7865,7 +8643,7 @@ var ProjectRunner = class {
7865
8643
  async handleAuditTasks(request) {
7866
8644
  this.connection.emitStatus("busy");
7867
8645
  try {
7868
- const { handleTaskAudit } = await import("./task-audit-handler-TJOM5OJS.js");
8646
+ const { handleTaskAudit } = await import("./task-audit-handler-4675WBIX.js");
7869
8647
  await handleTaskAudit(request, this.connection, this.projectDir);
7870
8648
  } catch (error) {
7871
8649
  const msg = parseErrorMessage(error);
@@ -7916,15 +8694,38 @@ var ProjectRunner = class {
7916
8694
  import { readFile as readFile2 } from "fs/promises";
7917
8695
  import { join as join5 } from "path";
7918
8696
  var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
8697
+ var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
7919
8698
  async function loadForwardPorts(workspaceDir) {
7920
8699
  try {
7921
8700
  const raw = await readFile2(join5(workspaceDir, DEVCONTAINER_PATH), "utf-8");
7922
8701
  const parsed = JSON.parse(raw);
7923
- return parsed.forwardPorts ?? [];
8702
+ const ports = (parsed.forwardPorts ?? []).filter(
8703
+ (p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
8704
+ );
8705
+ const attributes = {};
8706
+ for (const [key, value] of Object.entries(parsed.portsAttributes ?? {})) {
8707
+ if (!value || typeof value !== "object") continue;
8708
+ const entry = {};
8709
+ if (typeof value.label === "string") entry.label = value.label;
8710
+ if (value.visibility === "public" || value.visibility === "private") {
8711
+ entry.visibility = value.visibility;
8712
+ }
8713
+ attributes[key] = entry;
8714
+ }
8715
+ return { ports, attributes };
7924
8716
  } catch {
7925
- return [];
8717
+ return { ports: [], attributes: {} };
7926
8718
  }
7927
8719
  }
8720
+ function buildSessionPreviewPorts(result) {
8721
+ return result.ports.filter((port) => !DEVCONTAINER_PORT_DENY_LIST.has(port)).map((port) => {
8722
+ const attr = result.attributes[String(port)];
8723
+ const entry = { port };
8724
+ if (attr?.label) entry.label = attr.label;
8725
+ if (attr?.visibility) entry.visibility = attr.visibility;
8726
+ return entry;
8727
+ });
8728
+ }
7928
8729
  function loadConveyorConfig() {
7929
8730
  const envSetup = process.env.CONVEYOR_SETUP_COMMAND;
7930
8731
  const envStart = process.env.CONVEYOR_START_COMMAND;
@@ -7964,6 +8765,7 @@ export {
7964
8765
  runStartCommand,
7965
8766
  ProjectRunner,
7966
8767
  loadForwardPorts,
8768
+ buildSessionPreviewPorts,
7967
8769
  loadConveyorConfig
7968
8770
  };
7969
- //# sourceMappingURL=chunk-COJPX2QI.js.map
8771
+ //# sourceMappingURL=chunk-KO2YQJEV.js.map