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.
- 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 +76 -4
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +541 -153
- 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 +5 -5
- package/prompts/orchestrator.md +29 -11
- 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
|
@@ -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
|
-
//
|
|
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
|
/**
|
|
@@ -1395,9 +1378,13 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1395
1378
|
}
|
|
1396
1379
|
/**
|
|
1397
1380
|
* Handle Claude session error
|
|
1398
|
-
*
|
|
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
|
|
1575
|
+
// Get the mention comment metadata
|
|
1589
1576
|
const mentionContent = agentSession.comment?.body || "";
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
<
|
|
1589
|
+
<mention_comment>
|
|
1590
|
+
<author>${authorName}</author>
|
|
1591
|
+
<timestamp>${timestamp}</timestamp>
|
|
1592
|
+
<content>
|
|
1601
1593
|
${mentionContent}
|
|
1602
|
-
</
|
|
1594
|
+
</content>
|
|
1595
|
+
</mention_comment>
|
|
1603
1596
|
|
|
1604
|
-
|
|
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
|
|
1821
|
-
console.log(`[EdgeWorker]
|
|
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
|
|
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, "../
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
2554
|
+
console.error(`[EdgeWorker] Failed to process feedback in child session:`, error);
|
|
2520
2555
|
});
|
|
2521
|
-
// Return success immediately after initiating the
|
|
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 -
|
|
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
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
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
|
-
//
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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);
|