cyrus-edge-worker 0.0.39 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AgentSessionManager.d.ts.map +1 -1
- package/dist/AgentSessionManager.js +2 -4
- package/dist/AgentSessionManager.js.map +1 -1
- package/dist/EdgeWorker.d.ts +75 -3
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +496 -151
- package/dist/EdgeWorker.js.map +1 -1
- package/dist/procedures/ProcedureRouter.d.ts.map +1 -1
- package/dist/procedures/ProcedureRouter.js +11 -2
- package/dist/procedures/ProcedureRouter.js.map +1 -1
- package/dist/procedures/registry.d.ts +29 -0
- package/dist/procedures/registry.d.ts.map +1 -1
- package/dist/procedures/registry.js +45 -8
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/types.d.ts +1 -1
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/prompt-assembly/types.d.ts +81 -0
- package/dist/prompt-assembly/types.d.ts.map +1 -0
- package/dist/prompt-assembly/types.js +8 -0
- package/dist/prompt-assembly/types.js.map +1 -0
- package/dist/prompts/subroutines/coding-activity.md +10 -0
- package/dist/prompts/subroutines/concise-summary.md +16 -2
- package/dist/prompts/subroutines/debugger-fix.md +8 -25
- package/dist/prompts/subroutines/debugger-reproduction.md +11 -44
- package/dist/prompts/subroutines/git-gh.md +9 -6
- package/dist/prompts/subroutines/plan-summary.md +21 -0
- package/dist/prompts/subroutines/preparation.md +16 -0
- package/dist/prompts/subroutines/question-answer.md +8 -0
- package/dist/prompts/subroutines/question-investigation.md +8 -0
- package/dist/prompts/subroutines/verifications.md +9 -6
- package/package.json +3 -3
- package/prompts/orchestrator.md +9 -1
- package/prompts/standard-issue-assigned-user-prompt.md +33 -0
- package/prompts/todolist-system-prompt-extension.md +15 -0
- package/prompt-template-v2.md +0 -89
package/dist/EdgeWorker.js
CHANGED
|
@@ -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
|
-
//
|
|
343
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
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(
|
|
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
|
-
//
|
|
1239
|
-
|
|
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(
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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
|
/**
|
|
@@ -1585,10 +1568,12 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1585
1568
|
async buildMentionPrompt(issue, agentSession, attachmentManifest = "", guidance) {
|
|
1586
1569
|
try {
|
|
1587
1570
|
console.log(`[EdgeWorker] Building mention prompt for issue ${issue.identifier}`);
|
|
1588
|
-
// Get the mention comment
|
|
1571
|
+
// Get the mention comment metadata
|
|
1589
1572
|
const mentionContent = agentSession.comment?.body || "";
|
|
1590
|
-
|
|
1591
|
-
|
|
1573
|
+
const authorName = agentSession.creator?.name || agentSession.creator?.id || "Unknown";
|
|
1574
|
+
const timestamp = agentSession.createdAt || new Date().toISOString();
|
|
1575
|
+
// Build a focused prompt with comment metadata
|
|
1576
|
+
let prompt = `You were mentioned in a Linear comment on this issue:
|
|
1592
1577
|
|
|
1593
1578
|
<linear_issue>
|
|
1594
1579
|
<id>${issue.id}</id>
|
|
@@ -1597,11 +1582,15 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1597
1582
|
<url>${issue.url}</url>
|
|
1598
1583
|
</linear_issue>
|
|
1599
1584
|
|
|
1600
|
-
<
|
|
1585
|
+
<mention_comment>
|
|
1586
|
+
<author>${authorName}</author>
|
|
1587
|
+
<timestamp>${timestamp}</timestamp>
|
|
1588
|
+
<content>
|
|
1601
1589
|
${mentionContent}
|
|
1602
|
-
</
|
|
1590
|
+
</content>
|
|
1591
|
+
</mention_comment>
|
|
1603
1592
|
|
|
1604
|
-
|
|
1593
|
+
Focus on addressing the specific request in the mention. You can use the Linear MCP tools to fetch additional context if needed.`;
|
|
1605
1594
|
// Append agent guidance if present
|
|
1606
1595
|
prompt += this.formatAgentGuidance(guidance);
|
|
1607
1596
|
// Append attachment manifest if any
|
|
@@ -1817,17 +1806,17 @@ ${reply.body}
|
|
|
1817
1806
|
* @param guidance Optional agent guidance rules from Linear
|
|
1818
1807
|
* @returns Formatted prompt string
|
|
1819
1808
|
*/
|
|
1820
|
-
async
|
|
1821
|
-
console.log(`[EdgeWorker]
|
|
1809
|
+
async buildIssueContextPrompt(issue, repository, newComment, attachmentManifest = "", guidance) {
|
|
1810
|
+
console.log(`[EdgeWorker] buildIssueContextPrompt called for issue ${issue.identifier}${newComment ? " with new comment" : ""}`);
|
|
1822
1811
|
try {
|
|
1823
1812
|
// Use custom template if provided (repository-specific takes precedence)
|
|
1824
1813
|
let templatePath = repository.promptTemplatePath ||
|
|
1825
1814
|
this.config.features?.promptTemplatePath;
|
|
1826
|
-
// If no custom template, use the
|
|
1815
|
+
// If no custom template, use the standard issue assigned user prompt template
|
|
1827
1816
|
if (!templatePath) {
|
|
1828
1817
|
const __filename = fileURLToPath(import.meta.url);
|
|
1829
1818
|
const __dirname = dirname(__filename);
|
|
1830
|
-
templatePath = resolve(__dirname, "../
|
|
1819
|
+
templatePath = resolve(__dirname, "../prompts/standard-issue-assigned-user-prompt.md");
|
|
1831
1820
|
}
|
|
1832
1821
|
// Load the template
|
|
1833
1822
|
console.log(`[EdgeWorker] Loading prompt template from: ${templatePath}`);
|
|
@@ -1913,8 +1902,8 @@ IMPORTANT: Focus specifically on addressing the new comment above. This is a new
|
|
|
1913
1902
|
.replace(/{{new_comment_content}}/g, newComment.body || "");
|
|
1914
1903
|
}
|
|
1915
1904
|
else {
|
|
1916
|
-
// Remove the new comment section entirely
|
|
1917
|
-
prompt = prompt.replace(
|
|
1905
|
+
// Remove the new comment section entirely (including preceding newlines)
|
|
1906
|
+
prompt = prompt.replace(/\n*{{#if new_comment}}[\s\S]*?{{\/if}}/g, "");
|
|
1918
1907
|
}
|
|
1919
1908
|
// Append agent guidance if present
|
|
1920
1909
|
prompt += this.formatAgentGuidance(guidance);
|
|
@@ -2481,6 +2470,8 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2481
2470
|
},
|
|
2482
2471
|
onFeedbackDelivery: async (childSessionId, message) => {
|
|
2483
2472
|
console.log(`[EdgeWorker] Processing feedback delivery to child session ${childSessionId}`);
|
|
2473
|
+
// Find the parent session ID for context
|
|
2474
|
+
const parentSessionId = this.childToParentAgentSession.get(childSessionId);
|
|
2484
2475
|
// Find the repository containing the child session
|
|
2485
2476
|
// We need to search all repositories for this child session
|
|
2486
2477
|
let childRepo;
|
|
@@ -2503,22 +2494,62 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2503
2494
|
return false;
|
|
2504
2495
|
}
|
|
2505
2496
|
console.log(`[EdgeWorker] Found child session - Issue: ${childSession.issueId}`);
|
|
2497
|
+
// Get parent session info for better context in the thought
|
|
2498
|
+
let parentIssueId;
|
|
2499
|
+
if (parentSessionId) {
|
|
2500
|
+
// Find parent session across all repositories
|
|
2501
|
+
for (const manager of this.agentSessionManagers.values()) {
|
|
2502
|
+
const parentSession = manager.getSession(parentSessionId);
|
|
2503
|
+
if (parentSession) {
|
|
2504
|
+
parentIssueId =
|
|
2505
|
+
parentSession.issue?.identifier || parentSession.issueId;
|
|
2506
|
+
break;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
// Post thought to Linear showing feedback receipt
|
|
2511
|
+
const linearClient = this.linearClients.get(childRepo.id);
|
|
2512
|
+
if (linearClient) {
|
|
2513
|
+
const feedbackThought = parentIssueId
|
|
2514
|
+
? `Received feedback from orchestrator (${parentIssueId}):\n\n---\n\n${message}\n\n---`
|
|
2515
|
+
: `Received feedback from orchestrator:\n\n---\n\n${message}\n\n---`;
|
|
2516
|
+
try {
|
|
2517
|
+
const result = await linearClient.createAgentActivity({
|
|
2518
|
+
agentSessionId: childSessionId,
|
|
2519
|
+
content: {
|
|
2520
|
+
type: "thought",
|
|
2521
|
+
body: feedbackThought,
|
|
2522
|
+
},
|
|
2523
|
+
});
|
|
2524
|
+
if (result.success) {
|
|
2525
|
+
console.log(`[EdgeWorker] Posted feedback receipt thought for child session ${childSessionId}`);
|
|
2526
|
+
}
|
|
2527
|
+
else {
|
|
2528
|
+
console.error(`[EdgeWorker] Failed to post feedback receipt thought:`, result);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
catch (error) {
|
|
2532
|
+
console.error(`[EdgeWorker] Error posting feedback receipt thought:`, error);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2506
2535
|
// Format the feedback as a prompt for the child session with enhanced markdown formatting
|
|
2507
2536
|
const feedbackPrompt = `## Received feedback from orchestrator\n\n---\n\n${message}\n\n---`;
|
|
2508
|
-
//
|
|
2537
|
+
// Use centralized streaming check and routing logic
|
|
2509
2538
|
// Important: We don't await the full session completion to avoid timeouts.
|
|
2510
2539
|
// The feedback is delivered immediately when the session starts, so we can
|
|
2511
2540
|
// return success right away while the session continues in the background.
|
|
2512
|
-
|
|
2541
|
+
console.log(`[EdgeWorker] Handling feedback delivery to child session ${childSessionId}`);
|
|
2542
|
+
this.handlePromptWithStreamingCheck(childSession, childRepo, childSessionId, childAgentSessionManager, feedbackPrompt, "", // No attachment manifest for feedback
|
|
2513
2543
|
false, // Not a new session
|
|
2514
|
-
[]
|
|
2544
|
+
[], // No additional allowed directories for feedback
|
|
2545
|
+
"give feedback to child")
|
|
2515
2546
|
.then(() => {
|
|
2516
2547
|
console.log(`[EdgeWorker] Child session ${childSessionId} completed processing feedback`);
|
|
2517
2548
|
})
|
|
2518
2549
|
.catch((error) => {
|
|
2519
|
-
console.error(`[EdgeWorker] Failed to
|
|
2550
|
+
console.error(`[EdgeWorker] Failed to process feedback in child session:`, error);
|
|
2520
2551
|
});
|
|
2521
|
-
// Return success immediately after initiating the
|
|
2552
|
+
// Return success immediately after initiating the handling
|
|
2522
2553
|
console.log(`[EdgeWorker] Feedback delivered successfully to child session ${childSessionId}`);
|
|
2523
2554
|
return true;
|
|
2524
2555
|
},
|
|
@@ -2562,22 +2593,264 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2562
2593
|
}
|
|
2563
2594
|
}
|
|
2564
2595
|
/**
|
|
2565
|
-
* Build prompt for a session -
|
|
2596
|
+
* Build the complete prompt for a session - shows full prompt assembly in one place
|
|
2597
|
+
*
|
|
2598
|
+
* New session prompt structure:
|
|
2599
|
+
* 1. Issue context (from buildIssueContextPrompt)
|
|
2600
|
+
* 2. Initial subroutine prompt (if procedure initialized)
|
|
2601
|
+
* 3. User comment
|
|
2602
|
+
*
|
|
2603
|
+
* Existing session prompt structure:
|
|
2604
|
+
* 1. User comment
|
|
2605
|
+
* 2. Attachment manifest (if present)
|
|
2566
2606
|
*/
|
|
2567
|
-
async buildSessionPrompt(isNewSession, fullIssue, repository, promptBody, attachmentManifest) {
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2607
|
+
async buildSessionPrompt(isNewSession, session, fullIssue, repository, promptBody, attachmentManifest, commentAuthor, commentTimestamp) {
|
|
2608
|
+
// Fetch labels for system prompt determination
|
|
2609
|
+
const labels = await this.fetchIssueLabels(fullIssue);
|
|
2610
|
+
// Create input for unified prompt assembly
|
|
2611
|
+
const input = {
|
|
2612
|
+
session,
|
|
2613
|
+
fullIssue,
|
|
2614
|
+
repository,
|
|
2615
|
+
userComment: promptBody,
|
|
2616
|
+
commentAuthor,
|
|
2617
|
+
commentTimestamp,
|
|
2618
|
+
attachmentManifest,
|
|
2619
|
+
isNewSession,
|
|
2620
|
+
isStreaming: false, // This path is only for non-streaming prompts
|
|
2621
|
+
labels,
|
|
2622
|
+
};
|
|
2623
|
+
// Use unified prompt assembly
|
|
2624
|
+
const assembly = await this.assemblePrompt(input);
|
|
2625
|
+
// Log metadata for debugging
|
|
2626
|
+
console.log(`[EdgeWorker] Built prompt - components: ${assembly.metadata.components.join(", ")}, type: ${assembly.metadata.promptType}`);
|
|
2627
|
+
return assembly.userPrompt;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Assemble a complete prompt - unified entry point for all prompt building
|
|
2631
|
+
* This method contains all prompt assembly logic in one place
|
|
2632
|
+
*/
|
|
2633
|
+
async assemblePrompt(input) {
|
|
2634
|
+
// If actively streaming, just pass through the comment
|
|
2635
|
+
if (input.isStreaming) {
|
|
2636
|
+
return this.buildStreamingPrompt(input);
|
|
2637
|
+
}
|
|
2638
|
+
// If new session, build full prompt with all components
|
|
2639
|
+
if (input.isNewSession) {
|
|
2640
|
+
return this.buildNewSessionPrompt(input);
|
|
2641
|
+
}
|
|
2642
|
+
// Existing session continuation - just user comment + attachments
|
|
2643
|
+
return this.buildContinuationPrompt(input);
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Build prompt for actively streaming session - pass through user comment as-is
|
|
2647
|
+
*/
|
|
2648
|
+
buildStreamingPrompt(input) {
|
|
2649
|
+
const components = ["user-comment"];
|
|
2650
|
+
if (input.attachmentManifest) {
|
|
2651
|
+
components.push("attachment-manifest");
|
|
2652
|
+
}
|
|
2653
|
+
const parts = [input.userComment];
|
|
2654
|
+
if (input.attachmentManifest) {
|
|
2655
|
+
parts.push(input.attachmentManifest);
|
|
2656
|
+
}
|
|
2657
|
+
return {
|
|
2658
|
+
systemPrompt: undefined,
|
|
2659
|
+
userPrompt: parts.join("\n\n"),
|
|
2660
|
+
metadata: {
|
|
2661
|
+
components,
|
|
2662
|
+
promptType: "continuation",
|
|
2663
|
+
isNewSession: false,
|
|
2664
|
+
isStreaming: true,
|
|
2665
|
+
},
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Build prompt for new session - includes issue context, subroutine prompt, and user comment
|
|
2670
|
+
*/
|
|
2671
|
+
async buildNewSessionPrompt(input) {
|
|
2672
|
+
const components = [];
|
|
2673
|
+
const parts = [];
|
|
2674
|
+
// 1. Determine system prompt from labels
|
|
2675
|
+
// Only for delegation (not mentions) or when /label-based-prompt is requested
|
|
2676
|
+
let labelBasedSystemPrompt;
|
|
2677
|
+
if (!input.isMentionTriggered || input.isLabelBasedPromptRequested) {
|
|
2678
|
+
labelBasedSystemPrompt = await this.determineSystemPromptForAssembly(input.labels || [], input.repository);
|
|
2679
|
+
}
|
|
2680
|
+
// 2. Determine system prompt based on prompt type
|
|
2681
|
+
// Label-based: Use only the label-based system prompt
|
|
2682
|
+
// Fallback: Use scenarios system prompt (shared instructions)
|
|
2683
|
+
let systemPrompt;
|
|
2684
|
+
if (labelBasedSystemPrompt) {
|
|
2685
|
+
// Use label-based system prompt as-is (no shared instructions)
|
|
2686
|
+
systemPrompt = labelBasedSystemPrompt;
|
|
2573
2687
|
}
|
|
2574
2688
|
else {
|
|
2575
|
-
//
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2689
|
+
// Use scenarios system prompt for fallback cases
|
|
2690
|
+
const sharedInstructions = await this.loadSharedInstructions();
|
|
2691
|
+
systemPrompt = sharedInstructions;
|
|
2692
|
+
}
|
|
2693
|
+
// 3. Build issue context using appropriate builder
|
|
2694
|
+
// Use label-based prompt ONLY if we have a label-based system prompt
|
|
2695
|
+
const promptType = this.determinePromptType(input, !!labelBasedSystemPrompt);
|
|
2696
|
+
const issueContext = await this.buildIssueContextForPromptAssembly(input.fullIssue, input.repository, promptType, input.attachmentManifest, input.guidance, input.agentSession);
|
|
2697
|
+
parts.push(issueContext.prompt);
|
|
2698
|
+
components.push("issue-context");
|
|
2699
|
+
// 4. Load and append initial subroutine prompt
|
|
2700
|
+
const currentSubroutine = this.procedureRouter.getCurrentSubroutine(input.session);
|
|
2701
|
+
let subroutineName;
|
|
2702
|
+
if (currentSubroutine) {
|
|
2703
|
+
const subroutinePrompt = await this.loadSubroutinePrompt(currentSubroutine);
|
|
2704
|
+
if (subroutinePrompt) {
|
|
2705
|
+
parts.push(subroutinePrompt);
|
|
2706
|
+
components.push("subroutine-prompt");
|
|
2707
|
+
subroutineName = currentSubroutine.name;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
// 5. Add user comment (if present)
|
|
2711
|
+
// Skip for mention-triggered prompts since the comment is already in the mention block
|
|
2712
|
+
if (input.userComment.trim() && !input.isMentionTriggered) {
|
|
2713
|
+
// If we have author/timestamp metadata, include it for multi-player context
|
|
2714
|
+
if (input.commentAuthor || input.commentTimestamp) {
|
|
2715
|
+
const author = input.commentAuthor || "Unknown";
|
|
2716
|
+
const timestamp = input.commentTimestamp || new Date().toISOString();
|
|
2717
|
+
parts.push(`<user_comment>
|
|
2718
|
+
<author>${author}</author>
|
|
2719
|
+
<timestamp>${timestamp}</timestamp>
|
|
2720
|
+
<content>
|
|
2721
|
+
${input.userComment}
|
|
2722
|
+
</content>
|
|
2723
|
+
</user_comment>`);
|
|
2724
|
+
}
|
|
2725
|
+
else {
|
|
2726
|
+
// Legacy format without metadata
|
|
2727
|
+
parts.push(`<user_comment>\n${input.userComment}\n</user_comment>`);
|
|
2728
|
+
}
|
|
2729
|
+
components.push("user-comment");
|
|
2730
|
+
}
|
|
2731
|
+
// 6. Add guidance rules (if present)
|
|
2732
|
+
if (input.guidance && input.guidance.length > 0) {
|
|
2733
|
+
components.push("guidance-rules");
|
|
2734
|
+
}
|
|
2735
|
+
return {
|
|
2736
|
+
systemPrompt,
|
|
2737
|
+
userPrompt: parts.join("\n\n"),
|
|
2738
|
+
metadata: {
|
|
2739
|
+
components,
|
|
2740
|
+
subroutineName,
|
|
2741
|
+
promptType,
|
|
2742
|
+
isNewSession: true,
|
|
2743
|
+
isStreaming: false,
|
|
2744
|
+
},
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Build prompt for existing session continuation - user comment and attachments only
|
|
2749
|
+
*/
|
|
2750
|
+
buildContinuationPrompt(input) {
|
|
2751
|
+
const components = ["user-comment"];
|
|
2752
|
+
if (input.attachmentManifest) {
|
|
2753
|
+
components.push("attachment-manifest");
|
|
2754
|
+
}
|
|
2755
|
+
// Wrap comment in XML with author and timestamp for multi-player context
|
|
2756
|
+
const author = input.commentAuthor || "Unknown";
|
|
2757
|
+
const timestamp = input.commentTimestamp || new Date().toISOString();
|
|
2758
|
+
const commentXml = `<new_comment>
|
|
2759
|
+
<author>${author}</author>
|
|
2760
|
+
<timestamp>${timestamp}</timestamp>
|
|
2761
|
+
<content>
|
|
2762
|
+
${input.userComment}
|
|
2763
|
+
</content>
|
|
2764
|
+
</new_comment>`;
|
|
2765
|
+
const parts = [commentXml];
|
|
2766
|
+
if (input.attachmentManifest) {
|
|
2767
|
+
parts.push(input.attachmentManifest);
|
|
2580
2768
|
}
|
|
2769
|
+
return {
|
|
2770
|
+
systemPrompt: undefined,
|
|
2771
|
+
userPrompt: parts.join("\n\n"),
|
|
2772
|
+
metadata: {
|
|
2773
|
+
components,
|
|
2774
|
+
promptType: "continuation",
|
|
2775
|
+
isNewSession: false,
|
|
2776
|
+
isStreaming: false,
|
|
2777
|
+
},
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Determine the prompt type based on input flags and system prompt availability
|
|
2782
|
+
*/
|
|
2783
|
+
determinePromptType(input, hasSystemPrompt) {
|
|
2784
|
+
if (input.isMentionTriggered && input.isLabelBasedPromptRequested) {
|
|
2785
|
+
return "label-based-prompt-command";
|
|
2786
|
+
}
|
|
2787
|
+
if (input.isMentionTriggered) {
|
|
2788
|
+
return "mention";
|
|
2789
|
+
}
|
|
2790
|
+
if (hasSystemPrompt) {
|
|
2791
|
+
return "label-based";
|
|
2792
|
+
}
|
|
2793
|
+
return "fallback";
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Load a subroutine prompt file
|
|
2797
|
+
* Extracted helper to make prompt assembly more readable
|
|
2798
|
+
*/
|
|
2799
|
+
async loadSubroutinePrompt(subroutine) {
|
|
2800
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
2801
|
+
const __dirname = dirname(__filename);
|
|
2802
|
+
const subroutinePromptPath = join(__dirname, "prompts", subroutine.promptPath);
|
|
2803
|
+
try {
|
|
2804
|
+
const prompt = await readFile(subroutinePromptPath, "utf-8");
|
|
2805
|
+
console.log(`[EdgeWorker] Loaded ${subroutine.name} subroutine prompt (${prompt.length} characters)`);
|
|
2806
|
+
return prompt;
|
|
2807
|
+
}
|
|
2808
|
+
catch (error) {
|
|
2809
|
+
console.warn(`[EdgeWorker] Failed to load subroutine prompt from ${subroutinePromptPath}:`, error);
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Load shared instructions that get appended to all system prompts
|
|
2815
|
+
*/
|
|
2816
|
+
async loadSharedInstructions() {
|
|
2817
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
2818
|
+
const __dirname = dirname(__filename);
|
|
2819
|
+
const instructionsPath = join(__dirname, "..", "prompts", "todolist-system-prompt-extension.md");
|
|
2820
|
+
try {
|
|
2821
|
+
const instructions = await readFile(instructionsPath, "utf-8");
|
|
2822
|
+
return instructions;
|
|
2823
|
+
}
|
|
2824
|
+
catch (error) {
|
|
2825
|
+
console.error(`[EdgeWorker] Failed to load shared instructions from ${instructionsPath}:`, error);
|
|
2826
|
+
return ""; // Return empty string if file can't be loaded
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Adapter method for prompt assembly - extracts just the prompt string
|
|
2831
|
+
*/
|
|
2832
|
+
async determineSystemPromptForAssembly(labels, repository) {
|
|
2833
|
+
const result = await this.determineSystemPromptFromLabels(labels, repository);
|
|
2834
|
+
return result?.prompt;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Adapter method for prompt assembly - routes to appropriate issue context builder
|
|
2838
|
+
*/
|
|
2839
|
+
async buildIssueContextForPromptAssembly(issue, repository, promptType, attachmentManifest, guidance, agentSession) {
|
|
2840
|
+
// Delegate to appropriate builder based on promptType
|
|
2841
|
+
if (promptType === "mention") {
|
|
2842
|
+
if (!agentSession) {
|
|
2843
|
+
throw new Error("agentSession is required for mention-triggered prompts");
|
|
2844
|
+
}
|
|
2845
|
+
return this.buildMentionPrompt(issue, agentSession, attachmentManifest, guidance);
|
|
2846
|
+
}
|
|
2847
|
+
if (promptType === "label-based" ||
|
|
2848
|
+
promptType === "label-based-prompt-command") {
|
|
2849
|
+
return this.buildLabelBasedPrompt(issue, repository, attachmentManifest, guidance);
|
|
2850
|
+
}
|
|
2851
|
+
// Fallback to standard issue context
|
|
2852
|
+
return this.buildIssueContextPrompt(issue, repository, undefined, // No new comment for initial prompt assembly
|
|
2853
|
+
attachmentManifest, guidance);
|
|
2581
2854
|
}
|
|
2582
2855
|
/**
|
|
2583
2856
|
* Build Claude runner configuration with common settings
|
|
@@ -2879,6 +3152,78 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2879
3152
|
console.error(`[EdgeWorker] Error posting parent resumption acknowledgment:`, error);
|
|
2880
3153
|
}
|
|
2881
3154
|
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Re-route procedure for a session (used when resuming from child or give feedback)
|
|
3157
|
+
* This ensures the currentSubroutine is reset to avoid suppression issues
|
|
3158
|
+
*/
|
|
3159
|
+
async rerouteProcedureForSession(session, linearAgentActivitySessionId, agentSessionManager, promptBody) {
|
|
3160
|
+
// Initialize procedure metadata using intelligent routing
|
|
3161
|
+
if (!session.metadata) {
|
|
3162
|
+
session.metadata = {};
|
|
3163
|
+
}
|
|
3164
|
+
// Post ephemeral "Routing..." thought
|
|
3165
|
+
await agentSessionManager.postRoutingThought(linearAgentActivitySessionId);
|
|
3166
|
+
// Route based on the prompt content
|
|
3167
|
+
const routingDecision = await this.procedureRouter.determineRoutine(promptBody.trim());
|
|
3168
|
+
const selectedProcedure = routingDecision.procedure;
|
|
3169
|
+
// Initialize procedure metadata in session (resets currentSubroutine)
|
|
3170
|
+
this.procedureRouter.initializeProcedureMetadata(session, selectedProcedure);
|
|
3171
|
+
// Post procedure selection result (replaces ephemeral routing thought)
|
|
3172
|
+
await agentSessionManager.postProcedureSelectionThought(linearAgentActivitySessionId, selectedProcedure.name, routingDecision.classification);
|
|
3173
|
+
// Log routing decision
|
|
3174
|
+
console.log(`[EdgeWorker] Routing decision for ${linearAgentActivitySessionId}:`);
|
|
3175
|
+
console.log(` Classification: ${routingDecision.classification}`);
|
|
3176
|
+
console.log(` Procedure: ${selectedProcedure.name}`);
|
|
3177
|
+
console.log(` Reasoning: ${routingDecision.reasoning}`);
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Handle prompt with streaming check - centralized logic for all input types
|
|
3181
|
+
*
|
|
3182
|
+
* This method implements the unified pattern for handling prompts:
|
|
3183
|
+
* 1. Check if runner is actively streaming
|
|
3184
|
+
* 2. Route procedure if NOT streaming (resets currentSubroutine)
|
|
3185
|
+
* 3. Add to stream if streaming, OR resume session if not
|
|
3186
|
+
*
|
|
3187
|
+
* @param session The Cyrus agent session
|
|
3188
|
+
* @param repository Repository configuration
|
|
3189
|
+
* @param linearAgentActivitySessionId Linear agent activity session ID
|
|
3190
|
+
* @param agentSessionManager Agent session manager instance
|
|
3191
|
+
* @param promptBody The prompt text to send
|
|
3192
|
+
* @param attachmentManifest Optional attachment manifest to append
|
|
3193
|
+
* @param isNewSession Whether this is a new session
|
|
3194
|
+
* @param additionalAllowedDirs Additional directories to allow access to
|
|
3195
|
+
* @param logContext Context string for logging (e.g., "prompted webhook", "parent resume")
|
|
3196
|
+
* @returns true if message was added to stream, false if session was resumed
|
|
3197
|
+
*/
|
|
3198
|
+
async handlePromptWithStreamingCheck(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, additionalAllowedDirs, logContext, commentAuthor, commentTimestamp) {
|
|
3199
|
+
// Check if runner is actively streaming before routing
|
|
3200
|
+
const existingRunner = session.claudeRunner;
|
|
3201
|
+
const isStreaming = existingRunner?.isStreaming() || false;
|
|
3202
|
+
// Always route procedure for new input, UNLESS actively streaming
|
|
3203
|
+
if (!isStreaming) {
|
|
3204
|
+
await this.rerouteProcedureForSession(session, linearAgentActivitySessionId, agentSessionManager, promptBody);
|
|
3205
|
+
console.log(`[EdgeWorker] Routed procedure for ${logContext}`);
|
|
3206
|
+
}
|
|
3207
|
+
else {
|
|
3208
|
+
console.log(`[EdgeWorker] Skipping routing for ${linearAgentActivitySessionId} (${logContext}) - runner is actively streaming`);
|
|
3209
|
+
}
|
|
3210
|
+
// Handle streaming case - add message to existing stream
|
|
3211
|
+
if (existingRunner?.isStreaming()) {
|
|
3212
|
+
console.log(`[EdgeWorker] Adding prompt to existing stream for ${linearAgentActivitySessionId} (${logContext})`);
|
|
3213
|
+
// Append attachment manifest to the prompt if we have one
|
|
3214
|
+
let fullPrompt = promptBody;
|
|
3215
|
+
if (attachmentManifest) {
|
|
3216
|
+
fullPrompt = `${promptBody}\n\n${attachmentManifest}`;
|
|
3217
|
+
}
|
|
3218
|
+
existingRunner.addStreamMessage(fullPrompt);
|
|
3219
|
+
return true; // Message added to stream
|
|
3220
|
+
}
|
|
3221
|
+
// Not streaming - resume/start session
|
|
3222
|
+
console.log(`[EdgeWorker] Resuming Claude session for ${linearAgentActivitySessionId} (${logContext})`);
|
|
3223
|
+
await this.resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest, isNewSession, additionalAllowedDirs, undefined, // maxTurns
|
|
3224
|
+
commentAuthor, commentTimestamp);
|
|
3225
|
+
return false; // Session was resumed
|
|
3226
|
+
}
|
|
2882
3227
|
/**
|
|
2883
3228
|
* Post thought about system prompt selection based on labels
|
|
2884
3229
|
*/
|
|
@@ -2975,7 +3320,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2975
3320
|
* @param attachmentManifest Optional attachment manifest
|
|
2976
3321
|
* @param isNewSession Whether this is a new session
|
|
2977
3322
|
*/
|
|
2978
|
-
async resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest = "", isNewSession = false, additionalAllowedDirectories = [], maxTurns) {
|
|
3323
|
+
async resumeClaudeSession(session, repository, linearAgentActivitySessionId, agentSessionManager, promptBody, attachmentManifest = "", isNewSession = false, additionalAllowedDirectories = [], maxTurns, commentAuthor, commentTimestamp) {
|
|
2979
3324
|
// Check for existing runner
|
|
2980
3325
|
const existingRunner = session.claudeRunner;
|
|
2981
3326
|
// If there's an existing streaming runner, add to it
|
|
@@ -3027,7 +3372,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
3027
3372
|
// Save state
|
|
3028
3373
|
await this.savePersistedState();
|
|
3029
3374
|
// Prepare the full prompt
|
|
3030
|
-
const fullPrompt = await this.buildSessionPrompt(isNewSession, fullIssue, repository, promptBody, attachmentManifest);
|
|
3375
|
+
const fullPrompt = await this.buildSessionPrompt(isNewSession, session, fullIssue, repository, promptBody, attachmentManifest, commentAuthor, commentTimestamp);
|
|
3031
3376
|
// Start streaming session
|
|
3032
3377
|
try {
|
|
3033
3378
|
await runner.startStreaming(fullPrompt);
|