cyrus-edge-worker 0.0.39 → 0.0.41

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.
Files changed (35) hide show
  1. package/dist/AgentSessionManager.d.ts.map +1 -1
  2. package/dist/AgentSessionManager.js +2 -4
  3. package/dist/AgentSessionManager.js.map +1 -1
  4. package/dist/EdgeWorker.d.ts +76 -4
  5. package/dist/EdgeWorker.d.ts.map +1 -1
  6. package/dist/EdgeWorker.js +541 -153
  7. package/dist/EdgeWorker.js.map +1 -1
  8. package/dist/procedures/ProcedureRouter.d.ts.map +1 -1
  9. package/dist/procedures/ProcedureRouter.js +11 -2
  10. package/dist/procedures/ProcedureRouter.js.map +1 -1
  11. package/dist/procedures/registry.d.ts +29 -0
  12. package/dist/procedures/registry.d.ts.map +1 -1
  13. package/dist/procedures/registry.js +45 -8
  14. package/dist/procedures/registry.js.map +1 -1
  15. package/dist/procedures/types.d.ts +1 -1
  16. package/dist/procedures/types.d.ts.map +1 -1
  17. package/dist/prompt-assembly/types.d.ts +81 -0
  18. package/dist/prompt-assembly/types.d.ts.map +1 -0
  19. package/dist/prompt-assembly/types.js +8 -0
  20. package/dist/prompt-assembly/types.js.map +1 -0
  21. package/dist/prompts/subroutines/coding-activity.md +10 -0
  22. package/dist/prompts/subroutines/concise-summary.md +16 -2
  23. package/dist/prompts/subroutines/debugger-fix.md +8 -25
  24. package/dist/prompts/subroutines/debugger-reproduction.md +11 -44
  25. package/dist/prompts/subroutines/git-gh.md +9 -6
  26. package/dist/prompts/subroutines/plan-summary.md +21 -0
  27. package/dist/prompts/subroutines/preparation.md +16 -0
  28. package/dist/prompts/subroutines/question-answer.md +8 -0
  29. package/dist/prompts/subroutines/question-investigation.md +8 -0
  30. package/dist/prompts/subroutines/verifications.md +9 -6
  31. package/package.json +5 -5
  32. package/prompts/orchestrator.md +29 -11
  33. package/prompts/standard-issue-assigned-user-prompt.md +33 -0
  34. package/prompts/todolist-system-prompt-extension.md +15 -0
  35. package/prompt-template-v2.md +0 -89
@@ -4,7 +4,7 @@ import { basename, dirname, extname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { LinearClient, } from "@linear/sdk";
6
6
  import { watch as chokidarWatch } from "chokidar";
7
- import { ClaudeRunner, createCyrusToolsServer, createImageToolsServer, createSoraToolsServer, getAllTools, getCoordinatorTools, getReadOnlyTools, getSafeTools, } from "cyrus-claude-runner";
7
+ import { AbortError, ClaudeRunner, createCyrusToolsServer, createImageToolsServer, createSoraToolsServer, getAllTools, getCoordinatorTools, getReadOnlyTools, getSafeTools, } from "cyrus-claude-runner";
8
8
  import { isAgentSessionCreatedWebhook, isAgentSessionPromptedWebhook, isIssueAssignedWebhook, isIssueCommentMentionWebhook, isIssueNewCommentWebhook, isIssueUnassignedWebhook, PersistenceManager, } from "cyrus-core";
9
9
  import { LinearWebhookClient } from "cyrus-linear-webhook-client";
10
10
  import { NdjsonClient } from "cyrus-ndjson-client";
@@ -339,13 +339,38 @@ export class EdgeWorker extends EventEmitter {
339
339
  console.warn(`[Parent Session Resume] Could not find child session ${childSessionId} to add workspace to parent allowed directories`);
340
340
  }
341
341
  await this.postParentResumeAcknowledgment(parentSessionId, repo.id);
342
- // Resume the parent session with the child's result
343
- console.log(`[Parent Session Resume] Resuming parent Claude session with child results`);
342
+ // Post thought to Linear showing child result receipt
343
+ const linearClient = this.linearClients.get(repo.id);
344
+ if (linearClient && childSession) {
345
+ const childIssueIdentifier = childSession.issue?.identifier || childSession.issueId;
346
+ const resultThought = `Received result from sub-issue ${childIssueIdentifier}:\n\n---\n\n${prompt}\n\n---`;
347
+ try {
348
+ const result = await linearClient.createAgentActivity({
349
+ agentSessionId: parentSessionId,
350
+ content: {
351
+ type: "thought",
352
+ body: resultThought,
353
+ },
354
+ });
355
+ if (result.success) {
356
+ console.log(`[Parent Session Resume] Posted child result receipt thought for parent session ${parentSessionId}`);
357
+ }
358
+ else {
359
+ console.error(`[Parent Session Resume] Failed to post child result receipt thought:`, result);
360
+ }
361
+ }
362
+ catch (error) {
363
+ console.error(`[Parent Session Resume] Error posting child result receipt thought:`, error);
364
+ }
365
+ }
366
+ // Use centralized streaming check and routing logic
367
+ console.log(`[Parent Session Resume] Handling child result for parent session ${parentSessionId}`);
344
368
  try {
345
- await this.resumeClaudeSession(parentSession, repo, parentSessionId, agentSessionManager, prompt, "", // No attachment manifest for child results
369
+ await this.handlePromptWithStreamingCheck(parentSession, repo, parentSessionId, agentSessionManager, prompt, "", // No attachment manifest for child results
346
370
  false, // Not a new session
347
- childWorkspaceDirs);
348
- console.log(`[Parent Session Resume] Successfully resumed parent session ${parentSessionId} with child results`);
371
+ childWorkspaceDirs, // Add child workspace directories to parent's allowed directories
372
+ "parent resume from child");
373
+ console.log(`[Parent Session Resume] Successfully handled child result for parent session ${parentSessionId}`);
349
374
  }
350
375
  catch (error) {
351
376
  console.error(`[Parent Session Resume] Failed to resume parent session ${parentSessionId}:`, error);
@@ -1106,71 +1131,66 @@ export class EdgeWorker extends EventEmitter {
1106
1131
  this.procedureRouter.initializeProcedureMetadata(session, finalProcedure);
1107
1132
  // Post single procedure selection result (replaces ephemeral routing thought)
1108
1133
  await agentSessionManager.postProcedureSelectionThought(linearAgentActivitySessionId, finalProcedure.name, finalClassification);
1109
- // Only determine system prompt for delegation (not mentions) or when /label-based-prompt is requested
1110
- let systemPrompt;
1111
- let systemPromptVersion;
1112
- let promptType;
1113
- if (!isMentionTriggered || isLabelBasedPromptRequested) {
1114
- // Determine system prompt based on labels (delegation case or /label-based-prompt command)
1115
- const systemPromptResult = await this.determineSystemPromptFromLabels(labels, repository);
1116
- systemPrompt = systemPromptResult?.prompt;
1117
- systemPromptVersion = systemPromptResult?.version;
1118
- promptType = systemPromptResult?.type;
1119
- // Post thought about system prompt selection
1120
- if (systemPrompt) {
1121
- await this.postSystemPromptSelectionThought(linearAgentActivitySessionId, labels, repository.id);
1122
- }
1123
- }
1124
- else {
1125
- console.log(`[EdgeWorker] Skipping system prompt for mention-triggered session ${linearAgentActivitySessionId}`);
1126
- }
1127
- // Build allowed tools list with Linear MCP tools (now with prompt type context)
1128
- const allowedTools = this.buildAllowedTools(repository, promptType);
1129
- const disallowedTools = this.buildDisallowedTools(repository, promptType);
1130
- console.log(`[EdgeWorker] Configured allowed tools for ${fullIssue.identifier}:`, allowedTools);
1131
- if (disallowedTools.length > 0) {
1132
- console.log(`[EdgeWorker] Configured disallowed tools for ${fullIssue.identifier}:`, disallowedTools);
1133
- }
1134
- // Create Claude runner with attachment directory access and optional system prompt
1135
- const runnerConfig = this.buildClaudeRunnerConfig(session, repository, linearAgentActivitySessionId, systemPrompt, allowedTools, allowedDirectories, disallowedTools, undefined, // resumeSessionId
1136
- labels);
1137
- const runner = new ClaudeRunner(runnerConfig);
1138
- // Store runner by comment ID
1139
- agentSessionManager.addClaudeRunner(linearAgentActivitySessionId, runner);
1140
- // Save state after mapping changes
1141
- await this.savePersistedState();
1142
- // Emit events using full Linear issue
1143
- this.emit("session:started", fullIssue.id, fullIssue, repository.id);
1144
- this.config.handlers?.onSessionStart?.(fullIssue.id, fullIssue, repository.id);
1145
1134
  // Build and start Claude with initial prompt using full issue (streaming mode)
1146
1135
  console.log(`[EdgeWorker] Building initial prompt for issue ${fullIssue.identifier}`);
1147
1136
  try {
1148
- // Choose the appropriate prompt builder based on trigger type and system prompt
1149
- const promptResult = isMentionTriggered && isLabelBasedPromptRequested
1150
- ? await this.buildLabelBasedPrompt(fullIssue, repository, attachmentResult.manifest, guidance)
1151
- : isMentionTriggered
1152
- ? await this.buildMentionPrompt(fullIssue, agentSession, attachmentResult.manifest, guidance)
1153
- : systemPrompt
1154
- ? await this.buildLabelBasedPrompt(fullIssue, repository, attachmentResult.manifest, guidance)
1155
- : await this.buildPromptV2(fullIssue, repository, undefined, attachmentResult.manifest, guidance);
1156
- const { prompt, version: userPromptVersion } = promptResult;
1157
- // Update runner with version information
1158
- if (userPromptVersion || systemPromptVersion) {
1137
+ // Create input for unified prompt assembly
1138
+ const input = {
1139
+ session,
1140
+ fullIssue,
1141
+ repository,
1142
+ userComment: commentBody || "", // Empty for delegation, present for mentions
1143
+ attachmentManifest: attachmentResult.manifest,
1144
+ guidance,
1145
+ agentSession,
1146
+ labels,
1147
+ isNewSession: true,
1148
+ isStreaming: false, // Not yet streaming
1149
+ isMentionTriggered: isMentionTriggered || false,
1150
+ isLabelBasedPromptRequested: isLabelBasedPromptRequested || false,
1151
+ };
1152
+ // Use unified prompt assembly
1153
+ const assembly = await this.assemblePrompt(input);
1154
+ // Get systemPromptVersion for tracking (TODO: add to PromptAssembly metadata)
1155
+ let systemPromptVersion;
1156
+ let promptType;
1157
+ if (!isMentionTriggered || isLabelBasedPromptRequested) {
1158
+ const systemPromptResult = await this.determineSystemPromptFromLabels(labels, repository);
1159
+ systemPromptVersion = systemPromptResult?.version;
1160
+ promptType = systemPromptResult?.type;
1161
+ // Post thought about system prompt selection
1162
+ if (assembly.systemPrompt) {
1163
+ await this.postSystemPromptSelectionThought(linearAgentActivitySessionId, labels, repository.id);
1164
+ }
1165
+ }
1166
+ // Build allowed tools list with Linear MCP tools (now with prompt type context)
1167
+ const allowedTools = this.buildAllowedTools(repository, promptType);
1168
+ const disallowedTools = this.buildDisallowedTools(repository, promptType);
1169
+ console.log(`[EdgeWorker] Configured allowed tools for ${fullIssue.identifier}:`, allowedTools);
1170
+ if (disallowedTools.length > 0) {
1171
+ console.log(`[EdgeWorker] Configured disallowed tools for ${fullIssue.identifier}:`, disallowedTools);
1172
+ }
1173
+ // Create Claude runner with system prompt from assembly
1174
+ const runnerConfig = this.buildClaudeRunnerConfig(session, repository, linearAgentActivitySessionId, assembly.systemPrompt, allowedTools, allowedDirectories, disallowedTools, undefined, // resumeSessionId
1175
+ labels);
1176
+ const runner = new ClaudeRunner(runnerConfig);
1177
+ // Store runner by comment ID
1178
+ agentSessionManager.addClaudeRunner(linearAgentActivitySessionId, runner);
1179
+ // Save state after mapping changes
1180
+ await this.savePersistedState();
1181
+ // Emit events using full Linear issue
1182
+ this.emit("session:started", fullIssue.id, fullIssue, repository.id);
1183
+ this.config.handlers?.onSessionStart?.(fullIssue.id, fullIssue, repository.id);
1184
+ // Update runner with version information (if available)
1185
+ if (systemPromptVersion) {
1159
1186
  runner.updatePromptVersions({
1160
- userPromptVersion,
1161
1187
  systemPromptVersion,
1162
1188
  });
1163
1189
  }
1164
- const promptType = isMentionTriggered && isLabelBasedPromptRequested
1165
- ? "label-based-prompt-command"
1166
- : isMentionTriggered
1167
- ? "mention"
1168
- : systemPrompt
1169
- ? "label-based"
1170
- : "fallback";
1171
- console.log(`[EdgeWorker] Initial prompt built successfully using ${promptType} workflow, length: ${prompt.length} characters`);
1190
+ // Log metadata for debugging
1191
+ console.log(`[EdgeWorker] Initial prompt built successfully - components: ${assembly.metadata.components.join(", ")}, type: ${assembly.metadata.promptType}, length: ${assembly.userPrompt.length} characters`);
1172
1192
  console.log(`[EdgeWorker] Starting Claude streaming session`);
1173
- const sessionInfo = await runner.startStreaming(prompt);
1193
+ const sessionInfo = await runner.startStreaming(assembly.userPrompt);
1174
1194
  console.log(`[EdgeWorker] Claude streaming session started: ${sessionInfo.sessionId}`);
1175
1195
  // Note: AgentSessionManager will be initialized automatically when the first system message
1176
1196
  // is received via handleClaudeMessage() callback
@@ -1235,38 +1255,8 @@ export class EdgeWorker extends EventEmitter {
1235
1255
  }
1236
1256
  }
1237
1257
  }
1238
- // Check if runner is actively streaming before routing
1239
- const existingRunner = session?.claudeRunner;
1240
- const isStreaming = existingRunner?.isStreaming() || false;
1241
- // Always route procedure for new comments, UNLESS actively streaming
1242
- if (!isStreaming) {
1243
- // Initialize procedure metadata using intelligent routing
1244
- if (!session.metadata) {
1245
- session.metadata = {};
1246
- }
1247
- // Post ephemeral "Routing..." thought
1248
- await agentSessionManager.postRoutingThought(linearAgentActivitySessionId);
1249
- // For prompted events, use the actual prompt content from the user
1250
- // Combine with issue context for better routing
1251
- if (!fullIssue) {
1252
- console.warn(`[EdgeWorker] Routing without full issue details for ${linearAgentActivitySessionId}`);
1253
- }
1254
- const promptBody = webhook.agentActivity.content.body;
1255
- const routingDecision = await this.procedureRouter.determineRoutine(promptBody.trim());
1256
- const selectedProcedure = routingDecision.procedure;
1257
- // Initialize procedure metadata in session (resets for each new comment)
1258
- this.procedureRouter.initializeProcedureMetadata(session, selectedProcedure);
1259
- // Post procedure selection result (replaces ephemeral routing thought)
1260
- await agentSessionManager.postProcedureSelectionThought(linearAgentActivitySessionId, selectedProcedure.name, routingDecision.classification);
1261
- // Log routing decision
1262
- console.log(`[EdgeWorker] Routing decision for ${linearAgentActivitySessionId} (prompted webhook, ${isNewSession ? "new" : "existing"} session):`);
1263
- console.log(` Classification: ${routingDecision.classification}`);
1264
- console.log(` Procedure: ${selectedProcedure.name}`);
1265
- console.log(` Reasoning: ${routingDecision.reasoning}`);
1266
- }
1267
- else {
1268
- console.log(`[EdgeWorker] Skipping routing for ${linearAgentActivitySessionId} - runner is actively streaming`);
1269
- }
1258
+ // Note: Routing and streaming check happens later in handlePromptWithStreamingCheck
1259
+ // after attachments are processed
1270
1260
  // Ensure session is not null after creation/retrieval
1271
1261
  if (!session) {
1272
1262
  throw new Error(`Failed to get or create session for agent activity session ${linearAgentActivitySessionId}`);
@@ -1285,6 +1275,8 @@ export class EdgeWorker extends EventEmitter {
1285
1275
  // Ensure directory exists
1286
1276
  await mkdir(attachmentsDir, { recursive: true });
1287
1277
  let attachmentManifest = "";
1278
+ let commentAuthor;
1279
+ let commentTimestamp;
1288
1280
  try {
1289
1281
  const result = await linearClient.client.rawRequest(`
1290
1282
  query GetComment($id: String!) {
@@ -1295,16 +1287,27 @@ export class EdgeWorker extends EventEmitter {
1295
1287
  updatedAt
1296
1288
  user {
1297
1289
  name
1290
+ displayName
1291
+ email
1298
1292
  id
1299
1293
  }
1300
1294
  }
1301
1295
  }
1302
1296
  `, { id: commentId });
1297
+ // Extract comment data
1298
+ const comment = result.data.comment;
1299
+ // Extract comment metadata for multi-player context
1300
+ if (comment) {
1301
+ const user = comment.user;
1302
+ commentAuthor =
1303
+ user?.displayName || user?.name || user?.email || "Unknown";
1304
+ commentTimestamp = comment.createdAt || new Date().toISOString();
1305
+ }
1303
1306
  // Count existing attachments
1304
1307
  const existingFiles = await readdir(attachmentsDir).catch(() => []);
1305
1308
  const existingAttachmentCount = existingFiles.filter((file) => file.startsWith("attachment_") || file.startsWith("image_")).length;
1306
1309
  // Download new attachments from the comment
1307
- const downloadResult = await this.downloadCommentAttachments(result.data.comment.body, attachmentsDir, repository.linearToken, existingAttachmentCount);
1310
+ const downloadResult = await this.downloadCommentAttachments(comment.body, attachmentsDir, repository.linearToken, existingAttachmentCount);
1308
1311
  if (downloadResult.totalNewAttachments > 0) {
1309
1312
  attachmentManifest = this.generateNewAttachmentManifest(downloadResult);
1310
1313
  }
@@ -1318,6 +1321,7 @@ export class EdgeWorker extends EventEmitter {
1318
1321
  if (stopSignal) {
1319
1322
  console.log(`[EdgeWorker] Received stop signal for agent activity session ${linearAgentActivitySessionId}`);
1320
1323
  // Stop the existing runner if it's active
1324
+ const existingRunner = session.claudeRunner;
1321
1325
  if (existingRunner) {
1322
1326
  existingRunner.stop();
1323
1327
  console.log(`[EdgeWorker] Stopped Claude session for agent activity session ${linearAgentActivitySessionId}`);
@@ -1327,34 +1331,13 @@ export class EdgeWorker extends EventEmitter {
1327
1331
  await agentSessionManager.createResponseActivity(linearAgentActivitySessionId, stopConfirmation);
1328
1332
  return; // Exit early - stop signal handled
1329
1333
  }
1330
- // Check if there's an existing runner for this comment thread
1331
- if (existingRunner?.isStreaming()) {
1332
- // Add comment with attachment manifest to existing stream
1333
- console.log(`[EdgeWorker] Adding comment to existing stream for agent activity session ${linearAgentActivitySessionId}`);
1334
- // Append attachment manifest to the prompt if we have one
1335
- let fullPrompt = promptBody;
1336
- if (attachmentManifest) {
1337
- fullPrompt = `${promptBody}\n\n${attachmentManifest}`;
1338
- }
1339
- existingRunner.addStreamMessage(fullPrompt);
1340
- return; // Exit early - comment has been added to stream
1341
- }
1342
- // Use the new resumeClaudeSession function
1334
+ // Use centralized streaming check and routing logic
1343
1335
  try {
1344
- await this.resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, []);
1336
+ await this.handlePromptWithStreamingCheck(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, [], // No additional allowed directories for regular continuation
1337
+ `prompted webhook (${isNewSession ? "new" : "existing"} session)`, commentAuthor, commentTimestamp);
1345
1338
  }
1346
1339
  catch (error) {
1347
- console.error("Failed to continue conversation:", error);
1348
- // Remove any partially created session
1349
- // this.sessionManager.removeSession(threadRootCommentId)
1350
- // this.commentToRepo.delete(threadRootCommentId)
1351
- // this.commentToIssue.delete(threadRootCommentId)
1352
- // // Start fresh for root comments, or fall back to issue assignment
1353
- // if (isRootComment) {
1354
- // await this.handleNewRootComment(issue, comment, repository)
1355
- // } else {
1356
- // await this.handleIssueAssigned(issue, repository)
1357
- // }
1340
+ console.error("Failed to handle prompted webhook:", error);
1358
1341
  }
1359
1342
  }
1360
1343
  /**
@@ -1395,9 +1378,13 @@ export class EdgeWorker extends EventEmitter {
1395
1378
  }
1396
1379
  /**
1397
1380
  * Handle Claude session error
1398
- * TODO: improve this
1381
+ * Silently ignores AbortError (user-initiated stop), logs other errors
1399
1382
  */
1400
1383
  async handleClaudeError(error) {
1384
+ // AbortError is expected when user stops Claude process, don't log it
1385
+ if (error instanceof AbortError) {
1386
+ return;
1387
+ }
1401
1388
  console.error("Unhandled claude error:", error);
1402
1389
  }
1403
1390
  /**
@@ -1585,10 +1572,12 @@ export class EdgeWorker extends EventEmitter {
1585
1572
  async buildMentionPrompt(issue, agentSession, attachmentManifest = "", guidance) {
1586
1573
  try {
1587
1574
  console.log(`[EdgeWorker] Building mention prompt for issue ${issue.identifier}`);
1588
- // Get the mention comment body
1575
+ // Get the mention comment metadata
1589
1576
  const mentionContent = agentSession.comment?.body || "";
1590
- // Build a simple prompt focused on the mention
1591
- let prompt = `You were mentioned in a Linear comment. Please help with the following request.
1577
+ const authorName = agentSession.creator?.name || agentSession.creator?.id || "Unknown";
1578
+ const timestamp = agentSession.createdAt || new Date().toISOString();
1579
+ // Build a focused prompt with comment metadata
1580
+ let prompt = `You were mentioned in a Linear comment on this issue:
1592
1581
 
1593
1582
  <linear_issue>
1594
1583
  <id>${issue.id}</id>
@@ -1597,11 +1586,15 @@ export class EdgeWorker extends EventEmitter {
1597
1586
  <url>${issue.url}</url>
1598
1587
  </linear_issue>
1599
1588
 
1600
- <mention_request>
1589
+ <mention_comment>
1590
+ <author>${authorName}</author>
1591
+ <timestamp>${timestamp}</timestamp>
1592
+ <content>
1601
1593
  ${mentionContent}
1602
- </mention_request>
1594
+ </content>
1595
+ </mention_comment>
1603
1596
 
1604
- IMPORTANT: You were specifically mentioned in the comment above. Focus on addressing the specific question or request in the mention. You can use the Linear MCP tools to fetch additional context about the issue if needed.`;
1597
+ Focus on addressing the specific request in the mention. You can use the Linear MCP tools to fetch additional context if needed.`;
1605
1598
  // Append agent guidance if present
1606
1599
  prompt += this.formatAgentGuidance(guidance);
1607
1600
  // Append attachment manifest if any
@@ -1817,17 +1810,17 @@ ${reply.body}
1817
1810
  * @param guidance Optional agent guidance rules from Linear
1818
1811
  * @returns Formatted prompt string
1819
1812
  */
1820
- async buildPromptV2(issue, repository, newComment, attachmentManifest = "", guidance) {
1821
- console.log(`[EdgeWorker] buildPromptV2 called for issue ${issue.identifier}${newComment ? " with new comment" : ""}`);
1813
+ async buildIssueContextPrompt(issue, repository, newComment, attachmentManifest = "", guidance) {
1814
+ console.log(`[EdgeWorker] buildIssueContextPrompt called for issue ${issue.identifier}${newComment ? " with new comment" : ""}`);
1822
1815
  try {
1823
1816
  // Use custom template if provided (repository-specific takes precedence)
1824
1817
  let templatePath = repository.promptTemplatePath ||
1825
1818
  this.config.features?.promptTemplatePath;
1826
- // If no custom template, use the v2 template
1819
+ // If no custom template, use the standard issue assigned user prompt template
1827
1820
  if (!templatePath) {
1828
1821
  const __filename = fileURLToPath(import.meta.url);
1829
1822
  const __dirname = dirname(__filename);
1830
- templatePath = resolve(__dirname, "../prompt-template-v2.md");
1823
+ templatePath = resolve(__dirname, "../prompts/standard-issue-assigned-user-prompt.md");
1831
1824
  }
1832
1825
  // Load the template
1833
1826
  console.log(`[EdgeWorker] Loading prompt template from: ${templatePath}`);
@@ -1913,8 +1906,8 @@ IMPORTANT: Focus specifically on addressing the new comment above. This is a new
1913
1906
  .replace(/{{new_comment_content}}/g, newComment.body || "");
1914
1907
  }
1915
1908
  else {
1916
- // Remove the new comment section entirely
1917
- prompt = prompt.replace(/{{#if new_comment}}[\s\S]*?{{\/if}}/g, "");
1909
+ // Remove the new comment section entirely (including preceding newlines)
1910
+ prompt = prompt.replace(/\n*{{#if new_comment}}[\s\S]*?{{\/if}}/g, "");
1918
1911
  }
1919
1912
  // Append agent guidance if present
1920
1913
  prompt += this.formatAgentGuidance(guidance);
@@ -2481,6 +2474,8 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
2481
2474
  },
2482
2475
  onFeedbackDelivery: async (childSessionId, message) => {
2483
2476
  console.log(`[EdgeWorker] Processing feedback delivery to child session ${childSessionId}`);
2477
+ // Find the parent session ID for context
2478
+ const parentSessionId = this.childToParentAgentSession.get(childSessionId);
2484
2479
  // Find the repository containing the child session
2485
2480
  // We need to search all repositories for this child session
2486
2481
  let childRepo;
@@ -2503,22 +2498,62 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
2503
2498
  return false;
2504
2499
  }
2505
2500
  console.log(`[EdgeWorker] Found child session - Issue: ${childSession.issueId}`);
2501
+ // Get parent session info for better context in the thought
2502
+ let parentIssueId;
2503
+ if (parentSessionId) {
2504
+ // Find parent session across all repositories
2505
+ for (const manager of this.agentSessionManagers.values()) {
2506
+ const parentSession = manager.getSession(parentSessionId);
2507
+ if (parentSession) {
2508
+ parentIssueId =
2509
+ parentSession.issue?.identifier || parentSession.issueId;
2510
+ break;
2511
+ }
2512
+ }
2513
+ }
2514
+ // Post thought to Linear showing feedback receipt
2515
+ const linearClient = this.linearClients.get(childRepo.id);
2516
+ if (linearClient) {
2517
+ const feedbackThought = parentIssueId
2518
+ ? `Received feedback from orchestrator (${parentIssueId}):\n\n---\n\n${message}\n\n---`
2519
+ : `Received feedback from orchestrator:\n\n---\n\n${message}\n\n---`;
2520
+ try {
2521
+ const result = await linearClient.createAgentActivity({
2522
+ agentSessionId: childSessionId,
2523
+ content: {
2524
+ type: "thought",
2525
+ body: feedbackThought,
2526
+ },
2527
+ });
2528
+ if (result.success) {
2529
+ console.log(`[EdgeWorker] Posted feedback receipt thought for child session ${childSessionId}`);
2530
+ }
2531
+ else {
2532
+ console.error(`[EdgeWorker] Failed to post feedback receipt thought:`, result);
2533
+ }
2534
+ }
2535
+ catch (error) {
2536
+ console.error(`[EdgeWorker] Error posting feedback receipt thought:`, error);
2537
+ }
2538
+ }
2506
2539
  // Format the feedback as a prompt for the child session with enhanced markdown formatting
2507
2540
  const feedbackPrompt = `## Received feedback from orchestrator\n\n---\n\n${message}\n\n---`;
2508
- // Resume the CHILD session with the feedback from the parent
2541
+ // Use centralized streaming check and routing logic
2509
2542
  // Important: We don't await the full session completion to avoid timeouts.
2510
2543
  // The feedback is delivered immediately when the session starts, so we can
2511
2544
  // return success right away while the session continues in the background.
2512
- this.resumeClaudeSession(childSession, childRepo, childSessionId, childAgentSessionManager, feedbackPrompt, "", // No attachment manifest for feedback
2545
+ console.log(`[EdgeWorker] Handling feedback delivery to child session ${childSessionId}`);
2546
+ this.handlePromptWithStreamingCheck(childSession, childRepo, childSessionId, childAgentSessionManager, feedbackPrompt, "", // No attachment manifest for feedback
2513
2547
  false, // Not a new session
2514
- [])
2548
+ [], // No additional allowed directories for feedback
2549
+ "give feedback to child")
2515
2550
  .then(() => {
2516
2551
  console.log(`[EdgeWorker] Child session ${childSessionId} completed processing feedback`);
2517
2552
  })
2518
2553
  .catch((error) => {
2519
- console.error(`[EdgeWorker] Failed to complete child session with feedback:`, error);
2554
+ console.error(`[EdgeWorker] Failed to process feedback in child session:`, error);
2520
2555
  });
2521
- // Return success immediately after initiating the session
2556
+ // Return success immediately after initiating the handling
2522
2557
  console.log(`[EdgeWorker] Feedback delivered successfully to child session ${childSessionId}`);
2523
2558
  return true;
2524
2559
  },
@@ -2562,22 +2597,268 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
2562
2597
  }
2563
2598
  }
2564
2599
  /**
2565
- * Build prompt for a session - handles both new and existing sessions
2600
+ * Build the complete prompt for a session - shows full prompt assembly in one place
2601
+ *
2602
+ * New session prompt structure:
2603
+ * 1. Issue context (from buildIssueContextPrompt)
2604
+ * 2. Initial subroutine prompt (if procedure initialized)
2605
+ * 3. User comment
2606
+ *
2607
+ * Existing session prompt structure:
2608
+ * 1. User comment
2609
+ * 2. Attachment manifest (if present)
2610
+ */
2611
+ async buildSessionPrompt(isNewSession, session, fullIssue, repository, promptBody, attachmentManifest, commentAuthor, commentTimestamp) {
2612
+ // Fetch labels for system prompt determination
2613
+ const labels = await this.fetchIssueLabels(fullIssue);
2614
+ // Create input for unified prompt assembly
2615
+ const input = {
2616
+ session,
2617
+ fullIssue,
2618
+ repository,
2619
+ userComment: promptBody,
2620
+ commentAuthor,
2621
+ commentTimestamp,
2622
+ attachmentManifest,
2623
+ isNewSession,
2624
+ isStreaming: false, // This path is only for non-streaming prompts
2625
+ labels,
2626
+ };
2627
+ // Use unified prompt assembly
2628
+ const assembly = await this.assemblePrompt(input);
2629
+ // Log metadata for debugging
2630
+ console.log(`[EdgeWorker] Built prompt - components: ${assembly.metadata.components.join(", ")}, type: ${assembly.metadata.promptType}`);
2631
+ return assembly.userPrompt;
2632
+ }
2633
+ /**
2634
+ * Assemble a complete prompt - unified entry point for all prompt building
2635
+ * This method contains all prompt assembly logic in one place
2636
+ */
2637
+ async assemblePrompt(input) {
2638
+ // If actively streaming, just pass through the comment
2639
+ if (input.isStreaming) {
2640
+ return this.buildStreamingPrompt(input);
2641
+ }
2642
+ // If new session, build full prompt with all components
2643
+ if (input.isNewSession) {
2644
+ return this.buildNewSessionPrompt(input);
2645
+ }
2646
+ // Existing session continuation - just user comment + attachments
2647
+ return this.buildContinuationPrompt(input);
2648
+ }
2649
+ /**
2650
+ * Build prompt for actively streaming session - pass through user comment as-is
2651
+ */
2652
+ buildStreamingPrompt(input) {
2653
+ const components = ["user-comment"];
2654
+ if (input.attachmentManifest) {
2655
+ components.push("attachment-manifest");
2656
+ }
2657
+ const parts = [input.userComment];
2658
+ if (input.attachmentManifest) {
2659
+ parts.push(input.attachmentManifest);
2660
+ }
2661
+ return {
2662
+ systemPrompt: undefined,
2663
+ userPrompt: parts.join("\n\n"),
2664
+ metadata: {
2665
+ components,
2666
+ promptType: "continuation",
2667
+ isNewSession: false,
2668
+ isStreaming: true,
2669
+ },
2670
+ };
2671
+ }
2672
+ /**
2673
+ * Build prompt for new session - includes issue context, subroutine prompt, and user comment
2566
2674
  */
2567
- async buildSessionPrompt(isNewSession, fullIssue, repository, promptBody, attachmentManifest) {
2568
- if (isNewSession) {
2569
- // For completely new sessions, create a complete initial prompt
2570
- const promptResult = await this.buildPromptV2(fullIssue, repository, undefined, attachmentManifest);
2571
- // Add the user's comment to the initial prompt
2572
- return `${promptResult.prompt}\n\nUser comment: ${promptBody}`;
2675
+ async buildNewSessionPrompt(input) {
2676
+ const components = [];
2677
+ const parts = [];
2678
+ // 1. Determine system prompt from labels
2679
+ // Only for delegation (not mentions) or when /label-based-prompt is requested
2680
+ let labelBasedSystemPrompt;
2681
+ if (!input.isMentionTriggered || input.isLabelBasedPromptRequested) {
2682
+ labelBasedSystemPrompt = await this.determineSystemPromptForAssembly(input.labels || [], input.repository);
2683
+ }
2684
+ // 2. Determine system prompt based on prompt type
2685
+ // Label-based: Use only the label-based system prompt
2686
+ // Fallback: Use scenarios system prompt (shared instructions)
2687
+ let systemPrompt;
2688
+ if (labelBasedSystemPrompt) {
2689
+ // Use label-based system prompt as-is (no shared instructions)
2690
+ systemPrompt = labelBasedSystemPrompt;
2573
2691
  }
2574
2692
  else {
2575
- // For existing sessions, just use the comment with attachment manifest
2576
- const manifestSuffix = attachmentManifest
2577
- ? `\n\n${attachmentManifest}`
2578
- : "";
2579
- return `${promptBody}${manifestSuffix}`;
2693
+ // Use scenarios system prompt for fallback cases
2694
+ const sharedInstructions = await this.loadSharedInstructions();
2695
+ systemPrompt = sharedInstructions;
2696
+ }
2697
+ // 3. Build issue context using appropriate builder
2698
+ // Use label-based prompt ONLY if we have a label-based system prompt
2699
+ const promptType = this.determinePromptType(input, !!labelBasedSystemPrompt);
2700
+ const issueContext = await this.buildIssueContextForPromptAssembly(input.fullIssue, input.repository, promptType, input.attachmentManifest, input.guidance, input.agentSession);
2701
+ parts.push(issueContext.prompt);
2702
+ components.push("issue-context");
2703
+ // 4. Load and append initial subroutine prompt
2704
+ const currentSubroutine = this.procedureRouter.getCurrentSubroutine(input.session);
2705
+ let subroutineName;
2706
+ if (currentSubroutine) {
2707
+ const subroutinePrompt = await this.loadSubroutinePrompt(currentSubroutine);
2708
+ if (subroutinePrompt) {
2709
+ parts.push(subroutinePrompt);
2710
+ components.push("subroutine-prompt");
2711
+ subroutineName = currentSubroutine.name;
2712
+ }
2713
+ }
2714
+ // 5. Add user comment (if present)
2715
+ // Skip for mention-triggered prompts since the comment is already in the mention block
2716
+ if (input.userComment.trim() && !input.isMentionTriggered) {
2717
+ // If we have author/timestamp metadata, include it for multi-player context
2718
+ if (input.commentAuthor || input.commentTimestamp) {
2719
+ const author = input.commentAuthor || "Unknown";
2720
+ const timestamp = input.commentTimestamp || new Date().toISOString();
2721
+ parts.push(`<user_comment>
2722
+ <author>${author}</author>
2723
+ <timestamp>${timestamp}</timestamp>
2724
+ <content>
2725
+ ${input.userComment}
2726
+ </content>
2727
+ </user_comment>`);
2728
+ }
2729
+ else {
2730
+ // Legacy format without metadata
2731
+ parts.push(`<user_comment>\n${input.userComment}\n</user_comment>`);
2732
+ }
2733
+ components.push("user-comment");
2734
+ }
2735
+ // 6. Add guidance rules (if present)
2736
+ if (input.guidance && input.guidance.length > 0) {
2737
+ components.push("guidance-rules");
2580
2738
  }
2739
+ return {
2740
+ systemPrompt,
2741
+ userPrompt: parts.join("\n\n"),
2742
+ metadata: {
2743
+ components,
2744
+ subroutineName,
2745
+ promptType,
2746
+ isNewSession: true,
2747
+ isStreaming: false,
2748
+ },
2749
+ };
2750
+ }
2751
+ /**
2752
+ * Build prompt for existing session continuation - user comment and attachments only
2753
+ */
2754
+ buildContinuationPrompt(input) {
2755
+ const components = ["user-comment"];
2756
+ if (input.attachmentManifest) {
2757
+ components.push("attachment-manifest");
2758
+ }
2759
+ // Wrap comment in XML with author and timestamp for multi-player context
2760
+ const author = input.commentAuthor || "Unknown";
2761
+ const timestamp = input.commentTimestamp || new Date().toISOString();
2762
+ const commentXml = `<new_comment>
2763
+ <author>${author}</author>
2764
+ <timestamp>${timestamp}</timestamp>
2765
+ <content>
2766
+ ${input.userComment}
2767
+ </content>
2768
+ </new_comment>`;
2769
+ const parts = [commentXml];
2770
+ if (input.attachmentManifest) {
2771
+ parts.push(input.attachmentManifest);
2772
+ }
2773
+ return {
2774
+ systemPrompt: undefined,
2775
+ userPrompt: parts.join("\n\n"),
2776
+ metadata: {
2777
+ components,
2778
+ promptType: "continuation",
2779
+ isNewSession: false,
2780
+ isStreaming: false,
2781
+ },
2782
+ };
2783
+ }
2784
+ /**
2785
+ * Determine the prompt type based on input flags and system prompt availability
2786
+ */
2787
+ determinePromptType(input, hasSystemPrompt) {
2788
+ if (input.isMentionTriggered && input.isLabelBasedPromptRequested) {
2789
+ return "label-based-prompt-command";
2790
+ }
2791
+ if (input.isMentionTriggered) {
2792
+ return "mention";
2793
+ }
2794
+ if (hasSystemPrompt) {
2795
+ return "label-based";
2796
+ }
2797
+ return "fallback";
2798
+ }
2799
+ /**
2800
+ * Load a subroutine prompt file
2801
+ * Extracted helper to make prompt assembly more readable
2802
+ */
2803
+ async loadSubroutinePrompt(subroutine) {
2804
+ // Skip loading for "primary" - it's a placeholder that doesn't have a file
2805
+ if (subroutine.promptPath === "primary") {
2806
+ return null;
2807
+ }
2808
+ const __filename = fileURLToPath(import.meta.url);
2809
+ const __dirname = dirname(__filename);
2810
+ const subroutinePromptPath = join(__dirname, "prompts", subroutine.promptPath);
2811
+ try {
2812
+ const prompt = await readFile(subroutinePromptPath, "utf-8");
2813
+ console.log(`[EdgeWorker] Loaded ${subroutine.name} subroutine prompt (${prompt.length} characters)`);
2814
+ return prompt;
2815
+ }
2816
+ catch (error) {
2817
+ console.warn(`[EdgeWorker] Failed to load subroutine prompt from ${subroutinePromptPath}:`, error);
2818
+ return null;
2819
+ }
2820
+ }
2821
+ /**
2822
+ * Load shared instructions that get appended to all system prompts
2823
+ */
2824
+ async loadSharedInstructions() {
2825
+ const __filename = fileURLToPath(import.meta.url);
2826
+ const __dirname = dirname(__filename);
2827
+ const instructionsPath = join(__dirname, "..", "prompts", "todolist-system-prompt-extension.md");
2828
+ try {
2829
+ const instructions = await readFile(instructionsPath, "utf-8");
2830
+ return instructions;
2831
+ }
2832
+ catch (error) {
2833
+ console.error(`[EdgeWorker] Failed to load shared instructions from ${instructionsPath}:`, error);
2834
+ return ""; // Return empty string if file can't be loaded
2835
+ }
2836
+ }
2837
+ /**
2838
+ * Adapter method for prompt assembly - extracts just the prompt string
2839
+ */
2840
+ async determineSystemPromptForAssembly(labels, repository) {
2841
+ const result = await this.determineSystemPromptFromLabels(labels, repository);
2842
+ return result?.prompt;
2843
+ }
2844
+ /**
2845
+ * Adapter method for prompt assembly - routes to appropriate issue context builder
2846
+ */
2847
+ async buildIssueContextForPromptAssembly(issue, repository, promptType, attachmentManifest, guidance, agentSession) {
2848
+ // Delegate to appropriate builder based on promptType
2849
+ if (promptType === "mention") {
2850
+ if (!agentSession) {
2851
+ throw new Error("agentSession is required for mention-triggered prompts");
2852
+ }
2853
+ return this.buildMentionPrompt(issue, agentSession, attachmentManifest, guidance);
2854
+ }
2855
+ if (promptType === "label-based" ||
2856
+ promptType === "label-based-prompt-command") {
2857
+ return this.buildLabelBasedPrompt(issue, repository, attachmentManifest, guidance);
2858
+ }
2859
+ // Fallback to standard issue context
2860
+ return this.buildIssueContextPrompt(issue, repository, undefined, // No new comment for initial prompt assembly
2861
+ attachmentManifest, guidance);
2581
2862
  }
2582
2863
  /**
2583
2864
  * Build Claude runner configuration with common settings
@@ -2879,6 +3160,113 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
2879
3160
  console.error(`[EdgeWorker] Error posting parent resumption acknowledgment:`, error);
2880
3161
  }
2881
3162
  }
3163
+ /**
3164
+ * Re-route procedure for a session (used when resuming from child or give feedback)
3165
+ * This ensures the currentSubroutine is reset to avoid suppression issues
3166
+ */
3167
+ async rerouteProcedureForSession(session, linearAgentActivitySessionId, agentSessionManager, promptBody, repository) {
3168
+ // Initialize procedure metadata using intelligent routing
3169
+ if (!session.metadata) {
3170
+ session.metadata = {};
3171
+ }
3172
+ // Post ephemeral "Routing..." thought
3173
+ await agentSessionManager.postRoutingThought(linearAgentActivitySessionId);
3174
+ // Fetch full issue and labels to check for Orchestrator label override
3175
+ const linearClient = this.linearClients.get(repository.id);
3176
+ let hasOrchestratorLabel = false;
3177
+ if (linearClient) {
3178
+ try {
3179
+ const fullIssue = await linearClient.issue(session.issueId);
3180
+ const labels = await this.fetchIssueLabels(fullIssue);
3181
+ // Check for Orchestrator label (same logic as initial routing)
3182
+ const orchestratorConfig = repository.labelPrompts?.orchestrator;
3183
+ const orchestratorLabels = Array.isArray(orchestratorConfig)
3184
+ ? orchestratorConfig
3185
+ : orchestratorConfig?.labels;
3186
+ hasOrchestratorLabel =
3187
+ orchestratorLabels?.some((label) => labels.includes(label)) || false;
3188
+ }
3189
+ catch (error) {
3190
+ console.error(`[EdgeWorker] Failed to fetch issue labels for routing:`, error);
3191
+ // Continue with AI routing if label fetch fails
3192
+ }
3193
+ }
3194
+ let selectedProcedure;
3195
+ let finalClassification;
3196
+ // If Orchestrator label is present, ALWAYS use orchestrator-full procedure
3197
+ if (hasOrchestratorLabel) {
3198
+ const orchestratorProcedure = this.procedureRouter.getProcedure("orchestrator-full");
3199
+ if (!orchestratorProcedure) {
3200
+ throw new Error("orchestrator-full procedure not found in registry");
3201
+ }
3202
+ selectedProcedure = orchestratorProcedure;
3203
+ finalClassification = "orchestrator";
3204
+ console.log(`[EdgeWorker] Using orchestrator-full procedure due to Orchestrator label (skipping AI routing)`);
3205
+ }
3206
+ else {
3207
+ // No Orchestrator label - use AI routing based on prompt content
3208
+ const routingDecision = await this.procedureRouter.determineRoutine(promptBody.trim());
3209
+ selectedProcedure = routingDecision.procedure;
3210
+ finalClassification = routingDecision.classification;
3211
+ // Log AI routing decision
3212
+ console.log(`[EdgeWorker] AI routing decision for ${linearAgentActivitySessionId}:`);
3213
+ console.log(` Classification: ${routingDecision.classification}`);
3214
+ console.log(` Procedure: ${selectedProcedure.name}`);
3215
+ console.log(` Reasoning: ${routingDecision.reasoning}`);
3216
+ }
3217
+ // Initialize procedure metadata in session (resets currentSubroutine)
3218
+ this.procedureRouter.initializeProcedureMetadata(session, selectedProcedure);
3219
+ // Post procedure selection result (replaces ephemeral routing thought)
3220
+ await agentSessionManager.postProcedureSelectionThought(linearAgentActivitySessionId, selectedProcedure.name, finalClassification);
3221
+ }
3222
+ /**
3223
+ * Handle prompt with streaming check - centralized logic for all input types
3224
+ *
3225
+ * This method implements the unified pattern for handling prompts:
3226
+ * 1. Check if runner is actively streaming
3227
+ * 2. Route procedure if NOT streaming (resets currentSubroutine)
3228
+ * 3. Add to stream if streaming, OR resume session if not
3229
+ *
3230
+ * @param session The Cyrus agent session
3231
+ * @param repository Repository configuration
3232
+ * @param linearAgentActivitySessionId Linear agent activity session ID
3233
+ * @param agentSessionManager Agent session manager instance
3234
+ * @param promptBody The prompt text to send
3235
+ * @param attachmentManifest Optional attachment manifest to append
3236
+ * @param isNewSession Whether this is a new session
3237
+ * @param additionalAllowedDirs Additional directories to allow access to
3238
+ * @param logContext Context string for logging (e.g., "prompted webhook", "parent resume")
3239
+ * @returns true if message was added to stream, false if session was resumed
3240
+ */
3241
+ async handlePromptWithStreamingCheck(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, additionalAllowedDirs, logContext, commentAuthor, commentTimestamp) {
3242
+ // Check if runner is actively streaming before routing
3243
+ const existingRunner = session.claudeRunner;
3244
+ const isStreaming = existingRunner?.isStreaming() || false;
3245
+ // Always route procedure for new input, UNLESS actively streaming
3246
+ if (!isStreaming) {
3247
+ await this.rerouteProcedureForSession(session, linearAgentActivitySessionId, agentSessionManager, promptBody, repository);
3248
+ console.log(`[EdgeWorker] Routed procedure for ${logContext}`);
3249
+ }
3250
+ else {
3251
+ console.log(`[EdgeWorker] Skipping routing for ${linearAgentActivitySessionId} (${logContext}) - runner is actively streaming`);
3252
+ }
3253
+ // Handle streaming case - add message to existing stream
3254
+ if (existingRunner?.isStreaming()) {
3255
+ console.log(`[EdgeWorker] Adding prompt to existing stream for ${linearAgentActivitySessionId} (${logContext})`);
3256
+ // Append attachment manifest to the prompt if we have one
3257
+ let fullPrompt = promptBody;
3258
+ if (attachmentManifest) {
3259
+ fullPrompt = `${promptBody}\n\n${attachmentManifest}`;
3260
+ }
3261
+ existingRunner.addStreamMessage(fullPrompt);
3262
+ return true; // Message added to stream
3263
+ }
3264
+ // Not streaming - resume/start session
3265
+ console.log(`[EdgeWorker] Resuming Claude session for ${linearAgentActivitySessionId} (${logContext})`);
3266
+ await this.resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, additionalAllowedDirs, undefined, // maxTurns
3267
+ commentAuthor, commentTimestamp);
3268
+ return false; // Session was resumed
3269
+ }
2882
3270
  /**
2883
3271
  * Post thought about system prompt selection based on labels
2884
3272
  */
@@ -2975,7 +3363,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
2975
3363
  * @param attachmentManifest Optional attachment manifest
2976
3364
  * @param isNewSession Whether this is a new session
2977
3365
  */
2978
- async resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest = "", isNewSession = false, additionalAllowedDirectories = [], maxTurns) {
3366
+ async resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest = "", isNewSession = false, additionalAllowedDirectories = [], maxTurns, commentAuthor, commentTimestamp) {
2979
3367
  // Check for existing runner
2980
3368
  const existingRunner = session.claudeRunner;
2981
3369
  // If there's an existing streaming runner, add to it
@@ -3027,7 +3415,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
3027
3415
  // Save state
3028
3416
  await this.savePersistedState();
3029
3417
  // Prepare the full prompt
3030
- const fullPrompt = await this.buildSessionPrompt(isNewSession, fullIssue, repository, promptBody, attachmentManifest);
3418
+ const fullPrompt = await this.buildSessionPrompt(isNewSession, session, fullIssue, repository, promptBody, attachmentManifest, commentAuthor, commentTimestamp);
3031
3419
  // Start streaming session
3032
3420
  try {
3033
3421
  await runner.startStreaming(fullPrompt);