@rallycry/conveyor-agent 5.12.0 → 6.0.0
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/{chunk-SQJJL2PU.js → chunk-JFIWJVOH.js} +112 -1307
- package/dist/chunk-JFIWJVOH.js.map +1 -0
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -101
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-SQJJL2PU.js.map +0 -1
|
@@ -427,23 +427,6 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
427
427
|
triggerIdentification() {
|
|
428
428
|
return triggerIdentification(this.socket);
|
|
429
429
|
}
|
|
430
|
-
async refreshAuthToken() {
|
|
431
|
-
const codespaceName = process.env.CODESPACE_NAME;
|
|
432
|
-
const apiUrl = process.env.CONVEYOR_API_URL ?? this.config.conveyorApiUrl;
|
|
433
|
-
if (!codespaceName || !apiUrl) return false;
|
|
434
|
-
try {
|
|
435
|
-
const response = await fetch(`${apiUrl}/api/codespace/bootstrap/${codespaceName}`);
|
|
436
|
-
if (!response.ok) return false;
|
|
437
|
-
const config = await response.json();
|
|
438
|
-
if (config.envVars?.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
439
|
-
process.env.CLAUDE_CODE_OAUTH_TOKEN = config.envVars.CLAUDE_CODE_OAUTH_TOKEN;
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
return false;
|
|
443
|
-
} catch {
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
430
|
emitModeTransition(payload) {
|
|
448
431
|
if (!this.socket) return;
|
|
449
432
|
this.socket.emit("agentRunner:modeTransition", payload);
|
|
@@ -471,16 +454,9 @@ var ProjectConnection = class {
|
|
|
471
454
|
shutdownCallback = null;
|
|
472
455
|
chatMessageCallback = null;
|
|
473
456
|
earlyChatMessages = [];
|
|
474
|
-
auditRequestCallback = null;
|
|
475
|
-
// Branch switching callbacks
|
|
476
|
-
onSwitchBranch = null;
|
|
477
|
-
onSyncEnvironment = null;
|
|
478
|
-
onGetEnvStatus = null;
|
|
479
|
-
onRestartStartCommand = null;
|
|
480
457
|
constructor(config) {
|
|
481
458
|
this.config = config;
|
|
482
459
|
}
|
|
483
|
-
// oxlint-disable-next-line max-lines-per-function -- socket event registration requires co-located handlers
|
|
484
460
|
connect() {
|
|
485
461
|
return new Promise((resolve2, reject) => {
|
|
486
462
|
let settled = false;
|
|
@@ -520,37 +496,6 @@ var ProjectConnection = class {
|
|
|
520
496
|
this.earlyChatMessages.push(msg);
|
|
521
497
|
}
|
|
522
498
|
});
|
|
523
|
-
this.socket.on("projectRunner:auditTags", (data) => {
|
|
524
|
-
if (this.auditRequestCallback) {
|
|
525
|
-
this.auditRequestCallback(data);
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
this.socket.on(
|
|
529
|
-
"projectRunner:switchBranch",
|
|
530
|
-
(data, cb) => {
|
|
531
|
-
if (this.onSwitchBranch) this.onSwitchBranch(data, cb);
|
|
532
|
-
else cb({ ok: false, error: "switchBranch handler not registered" });
|
|
533
|
-
}
|
|
534
|
-
);
|
|
535
|
-
this.socket.on("projectRunner:syncEnvironment", (cb) => {
|
|
536
|
-
if (this.onSyncEnvironment) this.onSyncEnvironment(cb);
|
|
537
|
-
else cb({ ok: false, error: "syncEnvironment handler not registered" });
|
|
538
|
-
});
|
|
539
|
-
this.socket.on("projectRunner:getEnvStatus", (cb) => {
|
|
540
|
-
if (this.onGetEnvStatus) this.onGetEnvStatus(cb);
|
|
541
|
-
else cb({ ok: false, data: void 0 });
|
|
542
|
-
});
|
|
543
|
-
this.socket.on(
|
|
544
|
-
"projectRunner:restartStartCommand",
|
|
545
|
-
(_data, cb) => {
|
|
546
|
-
if (this.onRestartStartCommand) this.onRestartStartCommand(cb);
|
|
547
|
-
else cb({ ok: false, error: "restartStartCommand handler not registered" });
|
|
548
|
-
}
|
|
549
|
-
);
|
|
550
|
-
this.socket.on(
|
|
551
|
-
"projectRunner:runAuthTokenCommand",
|
|
552
|
-
(data, cb) => this.handleRunAuthTokenCommand(data.userEmail, cb)
|
|
553
|
-
);
|
|
554
499
|
this.socket.on("connect", () => {
|
|
555
500
|
if (!settled) {
|
|
556
501
|
settled = true;
|
|
@@ -582,17 +527,6 @@ var ProjectConnection = class {
|
|
|
582
527
|
}
|
|
583
528
|
this.earlyChatMessages = [];
|
|
584
529
|
}
|
|
585
|
-
onAuditRequest(callback) {
|
|
586
|
-
this.auditRequestCallback = callback;
|
|
587
|
-
}
|
|
588
|
-
emitAuditResult(data) {
|
|
589
|
-
if (!this.socket) return;
|
|
590
|
-
this.socket.emit("conveyor:tagAuditResult", data);
|
|
591
|
-
}
|
|
592
|
-
emitAuditProgress(data) {
|
|
593
|
-
if (!this.socket) return;
|
|
594
|
-
this.socket.emit("conveyor:tagAuditProgress", data);
|
|
595
|
-
}
|
|
596
530
|
sendHeartbeat() {
|
|
597
531
|
if (!this.socket) return;
|
|
598
532
|
this.socket.emit("projectRunner:heartbeat", {});
|
|
@@ -640,13 +574,13 @@ var ProjectConnection = class {
|
|
|
640
574
|
);
|
|
641
575
|
});
|
|
642
576
|
}
|
|
643
|
-
fetchChatHistory(limit
|
|
577
|
+
fetchChatHistory(limit) {
|
|
644
578
|
const socket = this.socket;
|
|
645
579
|
if (!socket) return Promise.reject(new Error("Not connected"));
|
|
646
580
|
return new Promise((resolve2, reject) => {
|
|
647
581
|
socket.emit(
|
|
648
582
|
"projectRunner:getChatHistory",
|
|
649
|
-
{ limit
|
|
583
|
+
{ limit },
|
|
650
584
|
(response) => {
|
|
651
585
|
if (response.success && response.data) resolve2(response.data);
|
|
652
586
|
else reject(new Error(response.error ?? "Failed to fetch chat history"));
|
|
@@ -654,78 +588,6 @@ var ProjectConnection = class {
|
|
|
654
588
|
);
|
|
655
589
|
});
|
|
656
590
|
}
|
|
657
|
-
// ── Project MCP tool request methods ──
|
|
658
|
-
requestListTasks(params) {
|
|
659
|
-
return this.requestWithCallback("projectRunner:listTasks", params);
|
|
660
|
-
}
|
|
661
|
-
requestGetTask(taskId) {
|
|
662
|
-
return this.requestWithCallback("projectRunner:getTask", { taskId });
|
|
663
|
-
}
|
|
664
|
-
requestCreateTask(params) {
|
|
665
|
-
return this.requestWithCallback("projectRunner:createTask", params);
|
|
666
|
-
}
|
|
667
|
-
requestUpdateTask(params) {
|
|
668
|
-
return this.requestWithCallback("projectRunner:updateTask", params);
|
|
669
|
-
}
|
|
670
|
-
requestSearchTasks(params) {
|
|
671
|
-
return this.requestWithCallback("projectRunner:searchTasks", params);
|
|
672
|
-
}
|
|
673
|
-
requestListTags() {
|
|
674
|
-
return this.requestWithCallback("projectRunner:listTags", {});
|
|
675
|
-
}
|
|
676
|
-
requestGetProjectSummary() {
|
|
677
|
-
return this.requestWithCallback("projectRunner:getProjectSummary", {});
|
|
678
|
-
}
|
|
679
|
-
requestWithCallback(event, data) {
|
|
680
|
-
const socket = this.socket;
|
|
681
|
-
if (!socket) return Promise.reject(new Error("Not connected"));
|
|
682
|
-
return new Promise((resolve2, reject) => {
|
|
683
|
-
socket.emit(event, data, (response) => {
|
|
684
|
-
if (response.success) resolve2(response.data);
|
|
685
|
-
else reject(new Error(response.error ?? `${event} failed`));
|
|
686
|
-
});
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
emitNewCommitsDetected(data) {
|
|
690
|
-
if (!this.socket) return;
|
|
691
|
-
this.socket.emit("projectRunner:newCommitsDetected", data);
|
|
692
|
-
}
|
|
693
|
-
emitEnvironmentReady(data) {
|
|
694
|
-
if (!this.socket) return;
|
|
695
|
-
this.socket.emit("projectRunner:environmentReady", data);
|
|
696
|
-
}
|
|
697
|
-
emitEnvSwitchProgress(data) {
|
|
698
|
-
if (!this.socket) return;
|
|
699
|
-
this.socket.emit("projectRunner:envSwitchProgress", data);
|
|
700
|
-
}
|
|
701
|
-
handleRunAuthTokenCommand(userEmail, cb) {
|
|
702
|
-
try {
|
|
703
|
-
if (process.env.CODESPACES !== "true") {
|
|
704
|
-
cb({ ok: false, error: "Auth token command only available in codespace environments" });
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
const authCmd = process.env.CONVEYOR_AUTH_TOKEN_COMMAND;
|
|
708
|
-
if (!authCmd) {
|
|
709
|
-
cb({ ok: false, error: "CONVEYOR_AUTH_TOKEN_COMMAND not configured" });
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
const cwd = this.config.projectDir ?? process.cwd();
|
|
713
|
-
const token = runAuthTokenCommand(authCmd, userEmail, cwd);
|
|
714
|
-
if (!token) {
|
|
715
|
-
cb({
|
|
716
|
-
ok: false,
|
|
717
|
-
error: `Auth token command returned empty output. Command: ${authCmd}`
|
|
718
|
-
});
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
cb({ ok: true, token });
|
|
722
|
-
} catch (error) {
|
|
723
|
-
cb({
|
|
724
|
-
ok: false,
|
|
725
|
-
error: error instanceof Error ? error.message : "Auth token command failed"
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
591
|
disconnect() {
|
|
730
592
|
this.socket?.disconnect();
|
|
731
593
|
this.socket = null;
|
|
@@ -845,14 +707,6 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
845
707
|
import { execSync as execSync4 } from "child_process";
|
|
846
708
|
|
|
847
709
|
// src/execution/event-handlers.ts
|
|
848
|
-
function safeVoid(promise, context) {
|
|
849
|
-
if (promise && typeof promise.catch === "function") {
|
|
850
|
-
promise.catch((err) => {
|
|
851
|
-
process.stderr.write(`[safeVoid] ${context}: ${err}
|
|
852
|
-
`);
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
710
|
function epochSecondsToISO(value) {
|
|
857
711
|
if (typeof value === "string") return value;
|
|
858
712
|
if (typeof value !== "number" || value <= 0) return void 0;
|
|
@@ -891,10 +745,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
891
745
|
}
|
|
892
746
|
var API_ERROR_PATTERN = /API Error: [45]\d\d/;
|
|
893
747
|
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
894
|
-
var AUTH_ERROR_PATTERN = /Not logged in|Please run \/login|authentication failed|invalid.*token|unauthorized/i;
|
|
895
|
-
function isAuthError(msg) {
|
|
896
|
-
return AUTH_ERROR_PATTERN.test(msg);
|
|
897
|
-
}
|
|
898
748
|
function isRetriableMessage(msg) {
|
|
899
749
|
if (IMAGE_ERROR_PATTERN.test(msg)) return true;
|
|
900
750
|
if (API_ERROR_PATTERN.test(msg)) return true;
|
|
@@ -975,10 +825,6 @@ function handleErrorResult(event, host) {
|
|
|
975
825
|
if (isStaleSession) {
|
|
976
826
|
return { retriable: false, staleSession: true };
|
|
977
827
|
}
|
|
978
|
-
if (isAuthError(errorMsg)) {
|
|
979
|
-
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
980
|
-
return { retriable: false, authError: true };
|
|
981
|
-
}
|
|
982
828
|
const retriable = isRetriableMessage(errorMsg);
|
|
983
829
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
984
830
|
return { retriable };
|
|
@@ -1018,8 +864,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
1018
864
|
return {
|
|
1019
865
|
retriable: result.retriable,
|
|
1020
866
|
resultSummary: result.resultSummary,
|
|
1021
|
-
staleSession: result.staleSession
|
|
1022
|
-
authError: result.authError
|
|
867
|
+
staleSession: result.staleSession
|
|
1023
868
|
};
|
|
1024
869
|
}
|
|
1025
870
|
function handleRateLimitEvent(event, host) {
|
|
@@ -1038,13 +883,13 @@ function handleRateLimitEvent(event, host) {
|
|
|
1038
883
|
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
1039
884
|
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
1040
885
|
host.connection.sendEvent({ type: "error", message });
|
|
1041
|
-
|
|
886
|
+
void host.callbacks.onEvent({ type: "error", message });
|
|
1042
887
|
return resetsAt;
|
|
1043
888
|
} else if (status === "allowed_warning") {
|
|
1044
889
|
const utilization = rate_limit_info.utilization ? `${Math.round(rate_limit_info.utilization * 100)}%` : "high";
|
|
1045
890
|
const message = `Rate limit warning: ${utilization} utilization (type: ${rate_limit_info.rateLimitType ?? "unknown"})`;
|
|
1046
891
|
host.connection.sendEvent({ type: "thinking", message });
|
|
1047
|
-
|
|
892
|
+
void host.callbacks.onEvent({ type: "thinking", message });
|
|
1048
893
|
}
|
|
1049
894
|
return void 0;
|
|
1050
895
|
}
|
|
@@ -1062,46 +907,34 @@ async function handleSystemEvent(event, host, context, sessionIdStored) {
|
|
|
1062
907
|
}
|
|
1063
908
|
function handleSystemSubevents(systemEvent, host) {
|
|
1064
909
|
if (systemEvent.subtype === "compact_boundary") {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}),
|
|
1071
|
-
"compactBoundary"
|
|
1072
|
-
);
|
|
910
|
+
void host.callbacks.onEvent({
|
|
911
|
+
type: "context_compacted",
|
|
912
|
+
trigger: systemEvent.compact_metadata.trigger,
|
|
913
|
+
preTokens: systemEvent.compact_metadata.pre_tokens
|
|
914
|
+
});
|
|
1073
915
|
} else if (systemEvent.subtype === "task_started") {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
}),
|
|
1080
|
-
"taskStarted"
|
|
1081
|
-
);
|
|
916
|
+
void host.callbacks.onEvent({
|
|
917
|
+
type: "subagent_started",
|
|
918
|
+
sdkTaskId: systemEvent.task_id,
|
|
919
|
+
description: systemEvent.description
|
|
920
|
+
});
|
|
1082
921
|
} else if (systemEvent.subtype === "task_progress") {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}),
|
|
1091
|
-
"taskProgress"
|
|
1092
|
-
);
|
|
922
|
+
void host.callbacks.onEvent({
|
|
923
|
+
type: "subagent_progress",
|
|
924
|
+
sdkTaskId: systemEvent.task_id,
|
|
925
|
+
description: systemEvent.description,
|
|
926
|
+
toolUses: systemEvent.usage?.tool_uses ?? 0,
|
|
927
|
+
durationMs: systemEvent.usage?.duration_ms ?? 0
|
|
928
|
+
});
|
|
1093
929
|
}
|
|
1094
930
|
}
|
|
1095
931
|
function handleToolProgressEvent(event, host) {
|
|
1096
932
|
const msg = event;
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}),
|
|
1103
|
-
"toolProgress"
|
|
1104
|
-
);
|
|
933
|
+
void host.callbacks.onEvent({
|
|
934
|
+
type: "tool_progress",
|
|
935
|
+
toolName: msg.tool_name ?? "",
|
|
936
|
+
elapsedSeconds: msg.elapsed_time_seconds ?? 0
|
|
937
|
+
});
|
|
1105
938
|
}
|
|
1106
939
|
async function handleAssistantCase(event, host, turnToolCalls) {
|
|
1107
940
|
await processAssistantEvent(event, host, turnToolCalls);
|
|
@@ -1119,13 +952,11 @@ async function handleResultCase(event, host, context, startTime, isTyping, lastA
|
|
|
1119
952
|
retriable: resultInfo.retriable,
|
|
1120
953
|
resultSummary: resultInfo.resultSummary,
|
|
1121
954
|
staleSession: resultInfo.staleSession,
|
|
1122
|
-
authError: resultInfo.authError,
|
|
1123
955
|
stoppedTyping
|
|
1124
956
|
};
|
|
1125
957
|
}
|
|
1126
958
|
|
|
1127
959
|
// src/execution/event-processor.ts
|
|
1128
|
-
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
1129
960
|
function stopTypingIfNeeded(host, isTyping) {
|
|
1130
961
|
if (isTyping) host.connection.sendTypingStop();
|
|
1131
962
|
}
|
|
@@ -1147,12 +978,6 @@ async function processAssistantCase(event, host, state) {
|
|
|
1147
978
|
}
|
|
1148
979
|
const usage = await handleAssistantCase(event, host, state.turnToolCalls);
|
|
1149
980
|
if (usage) state.lastAssistantUsage = usage;
|
|
1150
|
-
if (!state.sawApiError) {
|
|
1151
|
-
const fullText = event.message.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
1152
|
-
if (API_ERROR_PATTERN2.test(fullText)) {
|
|
1153
|
-
state.sawApiError = true;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
981
|
}
|
|
1157
982
|
async function processResultCase(event, host, context, startTime, state) {
|
|
1158
983
|
const info = await handleResultCase(
|
|
@@ -1167,7 +992,6 @@ async function processResultCase(event, host, context, startTime, state) {
|
|
|
1167
992
|
state.retriable = info.retriable;
|
|
1168
993
|
state.resultSummary = info.resultSummary;
|
|
1169
994
|
if (info.staleSession) state.staleSession = true;
|
|
1170
|
-
if (info.authError) state.authError = true;
|
|
1171
995
|
}
|
|
1172
996
|
async function processEvents(events, context, host) {
|
|
1173
997
|
const startTime = Date.now();
|
|
@@ -1177,11 +1001,9 @@ async function processEvents(events, context, host) {
|
|
|
1177
1001
|
sessionIdStored: false,
|
|
1178
1002
|
isTyping: false,
|
|
1179
1003
|
retriable: false,
|
|
1180
|
-
sawApiError: false,
|
|
1181
1004
|
resultSummary: void 0,
|
|
1182
1005
|
rateLimitResetsAt: void 0,
|
|
1183
1006
|
staleSession: void 0,
|
|
1184
|
-
authError: void 0,
|
|
1185
1007
|
lastAssistantUsage: void 0,
|
|
1186
1008
|
turnToolCalls: []
|
|
1187
1009
|
};
|
|
@@ -1218,11 +1040,10 @@ async function processEvents(events, context, host) {
|
|
|
1218
1040
|
}
|
|
1219
1041
|
stopTypingIfNeeded(host, state.isTyping);
|
|
1220
1042
|
return {
|
|
1221
|
-
retriable: state.retriable
|
|
1043
|
+
retriable: state.retriable,
|
|
1222
1044
|
resultSummary: state.resultSummary,
|
|
1223
1045
|
rateLimitResetsAt: state.rateLimitResetsAt,
|
|
1224
|
-
...state.staleSession && { staleSession: state.staleSession }
|
|
1225
|
-
...state.authError && { authError: state.authError }
|
|
1046
|
+
...state.staleSession && { staleSession: state.staleSession }
|
|
1226
1047
|
};
|
|
1227
1048
|
}
|
|
1228
1049
|
|
|
@@ -1580,8 +1401,6 @@ function buildDiscoveryPrompt(context) {
|
|
|
1580
1401
|
`You are in Discovery mode \u2014 helping plan and scope this task.`,
|
|
1581
1402
|
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
1582
1403
|
`- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
|
|
1583
|
-
`- Do NOT attempt to edit, write, or modify source code files \u2014 these operations will be denied`,
|
|
1584
|
-
`- If you identify code changes needed, describe them in the plan instead of implementing them`,
|
|
1585
1404
|
`- You can create and manage subtasks`,
|
|
1586
1405
|
`- Goal: collaborate with the user to create a clear plan`,
|
|
1587
1406
|
`- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
|
|
@@ -1716,14 +1535,6 @@ Project Agents:`);
|
|
|
1716
1535
|
parts.push(formatProjectAgentLine(pa));
|
|
1717
1536
|
}
|
|
1718
1537
|
}
|
|
1719
|
-
if (context.projectObjectives && context.projectObjectives.length > 0) {
|
|
1720
|
-
parts.push(`
|
|
1721
|
-
Project Objectives:`);
|
|
1722
|
-
for (const obj of context.projectObjectives) {
|
|
1723
|
-
const dates = `${obj.startDate.split("T")[0]} to ${obj.endDate.split("T")[0]}`;
|
|
1724
|
-
parts.push(`- **${obj.name}** (${dates})${obj.description ? ": " + obj.description : ""}`);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
1538
|
return parts;
|
|
1728
1539
|
}
|
|
1729
1540
|
function buildActivePreamble(context, workspaceDir) {
|
|
@@ -1846,7 +1657,7 @@ function detectRelaunchScenario(context, trustChatHistory = false) {
|
|
|
1846
1657
|
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
1847
1658
|
return hasNewUserMessages ? "feedback_relaunch" : "idle_relaunch";
|
|
1848
1659
|
}
|
|
1849
|
-
function buildRelaunchWithSession(mode, context, agentMode
|
|
1660
|
+
function buildRelaunchWithSession(mode, context, agentMode) {
|
|
1850
1661
|
const scenario = detectRelaunchScenario(context);
|
|
1851
1662
|
if (!context.claudeSessionId || scenario === "fresh") return null;
|
|
1852
1663
|
const parts = [];
|
|
@@ -1894,7 +1705,7 @@ Address the requested changes. Do NOT re-investigate the codebase from scratch o
|
|
|
1894
1705
|
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
1895
1706
|
`Review the current state of the codebase and verify everything is working correctly.`
|
|
1896
1707
|
);
|
|
1897
|
-
if (agentMode === "auto" || agentMode === "building"
|
|
1708
|
+
if (agentMode === "auto" || agentMode === "building") {
|
|
1898
1709
|
parts.push(
|
|
1899
1710
|
`If work is incomplete, continue implementing the plan. When finished, commit, push, and use mcp__conveyor__create_pull_request to open a PR.`,
|
|
1900
1711
|
`Do NOT go idle or wait for instructions \u2014 you are in auto mode.`
|
|
@@ -2100,7 +1911,7 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
2100
1911
|
}
|
|
2101
1912
|
return parts;
|
|
2102
1913
|
}
|
|
2103
|
-
function buildInstructions(mode, context, scenario, agentMode
|
|
1914
|
+
function buildInstructions(mode, context, scenario, agentMode) {
|
|
2104
1915
|
const parts = [`
|
|
2105
1916
|
## Instructions`];
|
|
2106
1917
|
const isPm = mode === "pm";
|
|
@@ -2132,7 +1943,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2132
1943
|
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
2133
1944
|
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`
|
|
2134
1945
|
);
|
|
2135
|
-
if (agentMode === "auto" || agentMode === "building"
|
|
1946
|
+
if (agentMode === "auto" || agentMode === "building") {
|
|
2136
1947
|
parts.push(
|
|
2137
1948
|
`If work is incomplete, continue implementing the plan. When finished, commit, push, and use mcp__conveyor__create_pull_request to open a PR.`,
|
|
2138
1949
|
`Do NOT go idle or wait for instructions \u2014 you are in auto mode.`
|
|
@@ -2155,13 +1966,13 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2155
1966
|
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
2156
1967
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
2157
1968
|
if (!isPackRunner) {
|
|
2158
|
-
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode
|
|
1969
|
+
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode);
|
|
2159
1970
|
if (sessionRelaunch) return sessionRelaunch;
|
|
2160
1971
|
}
|
|
2161
1972
|
const isPm = mode === "pm";
|
|
2162
1973
|
const scenario = detectRelaunchScenario(context, isPm);
|
|
2163
1974
|
const body = await buildTaskBody(context);
|
|
2164
|
-
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode
|
|
1975
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
2165
1976
|
return [...body, ...instructions].join("\n");
|
|
2166
1977
|
}
|
|
2167
1978
|
|
|
@@ -2423,14 +2234,11 @@ function buildCreatePullRequestTool(connection) {
|
|
|
2423
2234
|
"Create a GitHub pull request for this task. Use this instead of gh CLI or git commands to create PRs.",
|
|
2424
2235
|
{
|
|
2425
2236
|
title: z.string().describe("The PR title"),
|
|
2426
|
-
body: z.string().describe("The PR description/body in markdown")
|
|
2427
|
-
branch: z.string().optional().describe(
|
|
2428
|
-
"The head branch name for the PR. If the task doesn't have a branch set, this will be used. Defaults to the task's existing branch."
|
|
2429
|
-
)
|
|
2237
|
+
body: z.string().describe("The PR description/body in markdown")
|
|
2430
2238
|
},
|
|
2431
|
-
async ({ title, body
|
|
2239
|
+
async ({ title, body }) => {
|
|
2432
2240
|
try {
|
|
2433
|
-
const result = await connection.createPR({ title, body
|
|
2241
|
+
const result = await connection.createPR({ title, body });
|
|
2434
2242
|
connection.sendEvent({
|
|
2435
2243
|
type: "pr_created",
|
|
2436
2244
|
url: result.url,
|
|
@@ -2918,9 +2726,7 @@ async function handleAskUserQuestion(host, input) {
|
|
|
2918
2726
|
}
|
|
2919
2727
|
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2920
2728
|
}
|
|
2921
|
-
var DENIAL_WARNING_THRESHOLD = 3;
|
|
2922
2729
|
function buildCanUseTool(host) {
|
|
2923
|
-
let consecutiveDenials = 0;
|
|
2924
2730
|
return async (toolName, input) => {
|
|
2925
2731
|
if (toolName === "ExitPlanMode" && (host.agentMode === "auto" || host.agentMode === "discovery") && !host.hasExitedPlanMode) {
|
|
2926
2732
|
return await handleExitPlanMode(host, input);
|
|
@@ -2928,39 +2734,23 @@ function buildCanUseTool(host) {
|
|
|
2928
2734
|
if (toolName === "AskUserQuestion") {
|
|
2929
2735
|
return await handleAskUserQuestion(host, input);
|
|
2930
2736
|
}
|
|
2931
|
-
let result;
|
|
2932
2737
|
switch (host.agentMode) {
|
|
2933
2738
|
case "discovery":
|
|
2934
|
-
|
|
2935
|
-
break;
|
|
2739
|
+
return handleDiscoveryToolAccess(toolName, input);
|
|
2936
2740
|
case "building":
|
|
2937
|
-
|
|
2938
|
-
break;
|
|
2741
|
+
return handleBuildingToolAccess(toolName, input);
|
|
2939
2742
|
case "review":
|
|
2940
|
-
|
|
2941
|
-
break;
|
|
2743
|
+
return handleReviewToolAccess(toolName, input, host.isParentTask);
|
|
2942
2744
|
case "auto":
|
|
2943
|
-
|
|
2944
|
-
break;
|
|
2745
|
+
return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
2945
2746
|
default:
|
|
2946
|
-
|
|
2747
|
+
return { behavior: "allow", updatedInput: input };
|
|
2947
2748
|
}
|
|
2948
|
-
if (result.behavior === "deny") {
|
|
2949
|
-
consecutiveDenials++;
|
|
2950
|
-
if (consecutiveDenials === DENIAL_WARNING_THRESHOLD) {
|
|
2951
|
-
host.connection.postChatMessage(
|
|
2952
|
-
`\u26A0\uFE0F Multiple tool denials detected. You are in ${host.agentMode} mode \u2014 file writes outside .claude/plans/ are not permitted. Focus on creating a plan instead of implementing code changes.`
|
|
2953
|
-
);
|
|
2954
|
-
}
|
|
2955
|
-
} else {
|
|
2956
|
-
consecutiveDenials = 0;
|
|
2957
|
-
}
|
|
2958
|
-
return result;
|
|
2959
2749
|
};
|
|
2960
2750
|
}
|
|
2961
2751
|
|
|
2962
2752
|
// src/execution/query-executor.ts
|
|
2963
|
-
var
|
|
2753
|
+
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
2964
2754
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
2965
2755
|
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2966
2756
|
function buildHooks(host) {
|
|
@@ -3177,29 +2967,6 @@ async function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
|
3177
2967
|
options: { ...options, resume: void 0 }
|
|
3178
2968
|
});
|
|
3179
2969
|
}
|
|
3180
|
-
async function handleAuthError(context, host, options) {
|
|
3181
|
-
host.connection.postChatMessage("Authentication expired. Re-bootstrapping credentials...");
|
|
3182
|
-
const refreshed = await host.connection.refreshAuthToken();
|
|
3183
|
-
if (!refreshed) {
|
|
3184
|
-
host.connection.postChatMessage("Failed to refresh authentication. Agent will restart.");
|
|
3185
|
-
host.connection.sendEvent({
|
|
3186
|
-
type: "error",
|
|
3187
|
-
message: "Auth re-bootstrap failed, exiting for restart"
|
|
3188
|
-
});
|
|
3189
|
-
process.exit(1);
|
|
3190
|
-
}
|
|
3191
|
-
context.claudeSessionId = null;
|
|
3192
|
-
host.connection.storeSessionId("");
|
|
3193
|
-
const freshPrompt = buildMultimodalPrompt(
|
|
3194
|
-
await buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
3195
|
-
context
|
|
3196
|
-
);
|
|
3197
|
-
const freshQuery = query({
|
|
3198
|
-
prompt: host.createInputStream(freshPrompt),
|
|
3199
|
-
options: { ...options, resume: void 0 }
|
|
3200
|
-
});
|
|
3201
|
-
return runWithRetry(freshQuery, context, host, options);
|
|
3202
|
-
}
|
|
3203
2970
|
async function handleStaleSession(context, host, options) {
|
|
3204
2971
|
context.claudeSessionId = null;
|
|
3205
2972
|
host.connection.storeSessionId("");
|
|
@@ -3231,17 +2998,12 @@ function isStaleOrExitedSession(error, context) {
|
|
|
3231
2998
|
if (error.message.includes("No conversation found with session ID")) return true;
|
|
3232
2999
|
return !!context.claudeSessionId && error.message.includes("process exited");
|
|
3233
3000
|
}
|
|
3234
|
-
function getErrorMessage(error) {
|
|
3235
|
-
if (error instanceof Error) return error.message;
|
|
3236
|
-
if (typeof error === "string") return error;
|
|
3237
|
-
return String(error);
|
|
3238
|
-
}
|
|
3239
3001
|
function isRetriableError(error) {
|
|
3240
|
-
|
|
3241
|
-
return
|
|
3002
|
+
if (!(error instanceof Error)) return false;
|
|
3003
|
+
return API_ERROR_PATTERN2.test(error.message) || IMAGE_ERROR_PATTERN2.test(error.message);
|
|
3242
3004
|
}
|
|
3243
3005
|
function classifyImageError(error) {
|
|
3244
|
-
return IMAGE_ERROR_PATTERN2.test(
|
|
3006
|
+
return error instanceof Error && IMAGE_ERROR_PATTERN2.test(error.message);
|
|
3245
3007
|
}
|
|
3246
3008
|
async function emitRetryStatus(host, attempt, delayMs) {
|
|
3247
3009
|
const delayMin = Math.round(delayMs / 6e4);
|
|
@@ -3268,41 +3030,26 @@ function handleRetryError(error, context, host, options, prevImageError) {
|
|
|
3268
3030
|
if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
|
|
3269
3031
|
return handleStaleSession(context, host, options);
|
|
3270
3032
|
}
|
|
3271
|
-
if (isAuthError(getErrorMessage(error))) {
|
|
3272
|
-
return handleAuthError(context, host, options);
|
|
3273
|
-
}
|
|
3274
3033
|
if (!isRetriableError(error)) throw error;
|
|
3275
3034
|
return { action: "continue", lastErrorWasImage: classifyImageError(error) || prevImageError };
|
|
3276
3035
|
}
|
|
3277
|
-
function handleProcessResult(result, context, host, options) {
|
|
3278
|
-
if (result.modeRestart || host.isStopped()) return { action: "return" };
|
|
3279
|
-
if (result.rateLimitResetsAt) {
|
|
3280
|
-
handleRateLimitPause(host, result.rateLimitResetsAt);
|
|
3281
|
-
return { action: "return" };
|
|
3282
|
-
}
|
|
3283
|
-
if (result.staleSession && context.claudeSessionId) {
|
|
3284
|
-
return { action: "return_promise", promise: handleStaleSession(context, host, options) };
|
|
3285
|
-
}
|
|
3286
|
-
if (result.authError) {
|
|
3287
|
-
return { action: "return_promise", promise: handleAuthError(context, host, options) };
|
|
3288
|
-
}
|
|
3289
|
-
if (!result.retriable) return { action: "return" };
|
|
3290
|
-
return {
|
|
3291
|
-
action: "continue",
|
|
3292
|
-
lastErrorWasImage: IMAGE_ERROR_PATTERN2.test(result.resultSummary ?? "")
|
|
3293
|
-
};
|
|
3294
|
-
}
|
|
3295
3036
|
async function runWithRetry(initialQuery, context, host, options) {
|
|
3296
3037
|
let lastErrorWasImage = false;
|
|
3297
3038
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
3298
3039
|
if (host.isStopped()) return;
|
|
3299
3040
|
const agentQuery = attempt === 0 ? initialQuery : await buildRetryQuery(host, context, options, lastErrorWasImage);
|
|
3300
3041
|
try {
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
if (
|
|
3304
|
-
|
|
3305
|
-
|
|
3042
|
+
const { retriable, resultSummary, modeRestart, rateLimitResetsAt, staleSession } = await processEvents(agentQuery, context, host);
|
|
3043
|
+
if (modeRestart || host.isStopped()) return;
|
|
3044
|
+
if (rateLimitResetsAt) {
|
|
3045
|
+
handleRateLimitPause(host, rateLimitResetsAt);
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
if (staleSession && context.claudeSessionId) {
|
|
3049
|
+
return handleStaleSession(context, host, options);
|
|
3050
|
+
}
|
|
3051
|
+
if (!retriable) return;
|
|
3052
|
+
lastErrorWasImage = IMAGE_ERROR_PATTERN2.test(resultSummary ?? "");
|
|
3306
3053
|
} catch (error) {
|
|
3307
3054
|
const outcome = handleRetryError(error, context, host, options, lastErrorWasImage);
|
|
3308
3055
|
if (outcome instanceof Promise) return outcome;
|
|
@@ -4132,270 +3879,13 @@ var AgentRunner = class {
|
|
|
4132
3879
|
|
|
4133
3880
|
// src/runner/project-runner.ts
|
|
4134
3881
|
import { fork } from "child_process";
|
|
4135
|
-
import { execSync as
|
|
3882
|
+
import { execSync as execSync5 } from "child_process";
|
|
4136
3883
|
import * as path from "path";
|
|
4137
3884
|
import { fileURLToPath } from "url";
|
|
4138
3885
|
|
|
4139
|
-
// src/runner/commit-watcher.ts
|
|
4140
|
-
import { execSync as execSync5 } from "child_process";
|
|
4141
|
-
var logger3 = createServiceLogger("CommitWatcher");
|
|
4142
|
-
var CommitWatcher = class {
|
|
4143
|
-
constructor(config, callbacks) {
|
|
4144
|
-
this.config = config;
|
|
4145
|
-
this.callbacks = callbacks;
|
|
4146
|
-
}
|
|
4147
|
-
interval = null;
|
|
4148
|
-
lastKnownRemoteSha = null;
|
|
4149
|
-
branch = null;
|
|
4150
|
-
debounceTimer = null;
|
|
4151
|
-
isSyncing = false;
|
|
4152
|
-
start(branch) {
|
|
4153
|
-
this.stop();
|
|
4154
|
-
this.branch = branch;
|
|
4155
|
-
this.lastKnownRemoteSha = this.getLocalHeadSha();
|
|
4156
|
-
this.interval = setInterval(() => void this.poll(), this.config.pollIntervalMs);
|
|
4157
|
-
logger3.info("Commit watcher started", {
|
|
4158
|
-
branch,
|
|
4159
|
-
baseSha: this.lastKnownRemoteSha?.slice(0, 8)
|
|
4160
|
-
});
|
|
4161
|
-
}
|
|
4162
|
-
stop() {
|
|
4163
|
-
if (this.interval) clearInterval(this.interval);
|
|
4164
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
4165
|
-
this.interval = null;
|
|
4166
|
-
this.debounceTimer = null;
|
|
4167
|
-
this.branch = null;
|
|
4168
|
-
this.lastKnownRemoteSha = null;
|
|
4169
|
-
this.isSyncing = false;
|
|
4170
|
-
}
|
|
4171
|
-
getLocalHeadSha() {
|
|
4172
|
-
return execSync5("git rev-parse HEAD", {
|
|
4173
|
-
cwd: this.config.projectDir,
|
|
4174
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4175
|
-
}).toString().trim();
|
|
4176
|
-
}
|
|
4177
|
-
poll() {
|
|
4178
|
-
if (!this.branch || this.isSyncing) return;
|
|
4179
|
-
try {
|
|
4180
|
-
execSync5(`git fetch origin ${this.branch} --quiet`, {
|
|
4181
|
-
cwd: this.config.projectDir,
|
|
4182
|
-
stdio: "ignore",
|
|
4183
|
-
timeout: 3e4
|
|
4184
|
-
});
|
|
4185
|
-
const remoteSha = execSync5(`git rev-parse origin/${this.branch}`, {
|
|
4186
|
-
cwd: this.config.projectDir,
|
|
4187
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4188
|
-
}).toString().trim();
|
|
4189
|
-
if (remoteSha !== this.lastKnownRemoteSha) {
|
|
4190
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
4191
|
-
this.debounceTimer = setTimeout(
|
|
4192
|
-
() => void this.handleNewCommits(remoteSha),
|
|
4193
|
-
this.config.debounceMs
|
|
4194
|
-
);
|
|
4195
|
-
}
|
|
4196
|
-
} catch {
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
async handleNewCommits(remoteSha) {
|
|
4200
|
-
if (!this.branch) return;
|
|
4201
|
-
const previousSha = this.lastKnownRemoteSha ?? "HEAD";
|
|
4202
|
-
let commitCount = 1;
|
|
4203
|
-
let latestMessage = "";
|
|
4204
|
-
let latestAuthor = "";
|
|
4205
|
-
try {
|
|
4206
|
-
const countOutput = execSync5(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
|
|
4207
|
-
cwd: this.config.projectDir,
|
|
4208
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4209
|
-
}).toString().trim();
|
|
4210
|
-
commitCount = parseInt(countOutput, 10) || 1;
|
|
4211
|
-
const logOutput = execSync5(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
|
|
4212
|
-
cwd: this.config.projectDir,
|
|
4213
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4214
|
-
}).toString().trim();
|
|
4215
|
-
const parts = logOutput.split("|||");
|
|
4216
|
-
latestMessage = parts[0] ?? "";
|
|
4217
|
-
latestAuthor = parts[1] ?? "";
|
|
4218
|
-
} catch {
|
|
4219
|
-
}
|
|
4220
|
-
this.lastKnownRemoteSha = remoteSha;
|
|
4221
|
-
this.isSyncing = true;
|
|
4222
|
-
logger3.info("New commits detected", {
|
|
4223
|
-
branch: this.branch,
|
|
4224
|
-
commitCount,
|
|
4225
|
-
sha: remoteSha.slice(0, 8)
|
|
4226
|
-
});
|
|
4227
|
-
try {
|
|
4228
|
-
await this.callbacks.onNewCommits({
|
|
4229
|
-
branch: this.branch,
|
|
4230
|
-
previousSha,
|
|
4231
|
-
newCommitSha: remoteSha,
|
|
4232
|
-
commitCount,
|
|
4233
|
-
latestMessage,
|
|
4234
|
-
latestAuthor
|
|
4235
|
-
});
|
|
4236
|
-
} catch (err) {
|
|
4237
|
-
logger3.error("Error handling new commits", errorMeta(err));
|
|
4238
|
-
} finally {
|
|
4239
|
-
this.isSyncing = false;
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
};
|
|
4243
|
-
|
|
4244
3886
|
// src/runner/project-chat-handler.ts
|
|
4245
|
-
import {
|
|
4246
|
-
|
|
4247
|
-
createSdkMcpServer as createSdkMcpServer2
|
|
4248
|
-
} from "@anthropic-ai/claude-agent-sdk";
|
|
4249
|
-
|
|
4250
|
-
// src/tools/project-tools.ts
|
|
4251
|
-
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
4252
|
-
import { z as z4 } from "zod";
|
|
4253
|
-
function buildReadTools(connection) {
|
|
4254
|
-
return [
|
|
4255
|
-
tool4(
|
|
4256
|
-
"list_tasks",
|
|
4257
|
-
"List tasks in the project. Optionally filter by status or assignee.",
|
|
4258
|
-
{
|
|
4259
|
-
status: z4.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
|
|
4260
|
-
assigneeId: z4.string().optional().describe("Filter by assigned user ID"),
|
|
4261
|
-
limit: z4.number().optional().describe("Max number of tasks to return (default 50)")
|
|
4262
|
-
},
|
|
4263
|
-
async (params) => {
|
|
4264
|
-
try {
|
|
4265
|
-
const tasks = await connection.requestListTasks(params);
|
|
4266
|
-
return textResult(JSON.stringify(tasks, null, 2));
|
|
4267
|
-
} catch (error) {
|
|
4268
|
-
return textResult(
|
|
4269
|
-
`Failed to list tasks: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4270
|
-
);
|
|
4271
|
-
}
|
|
4272
|
-
},
|
|
4273
|
-
{ annotations: { readOnlyHint: true } }
|
|
4274
|
-
),
|
|
4275
|
-
tool4(
|
|
4276
|
-
"get_task",
|
|
4277
|
-
"Get detailed information about a task including its chat messages, child tasks, and codespace status.",
|
|
4278
|
-
{ task_id: z4.string().describe("The task ID to look up") },
|
|
4279
|
-
async ({ task_id }) => {
|
|
4280
|
-
try {
|
|
4281
|
-
const task = await connection.requestGetTask(task_id);
|
|
4282
|
-
return textResult(JSON.stringify(task, null, 2));
|
|
4283
|
-
} catch (error) {
|
|
4284
|
-
return textResult(
|
|
4285
|
-
`Failed to get task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4286
|
-
);
|
|
4287
|
-
}
|
|
4288
|
-
},
|
|
4289
|
-
{ annotations: { readOnlyHint: true } }
|
|
4290
|
-
),
|
|
4291
|
-
tool4(
|
|
4292
|
-
"search_tasks",
|
|
4293
|
-
"Search tasks by tags, text query, or status filters.",
|
|
4294
|
-
{
|
|
4295
|
-
tagNames: z4.array(z4.string()).optional().describe("Filter by tag names"),
|
|
4296
|
-
searchQuery: z4.string().optional().describe("Text search in title/description"),
|
|
4297
|
-
statusFilters: z4.array(z4.string()).optional().describe("Filter by statuses"),
|
|
4298
|
-
limit: z4.number().optional().describe("Max results (default 20)")
|
|
4299
|
-
},
|
|
4300
|
-
async (params) => {
|
|
4301
|
-
try {
|
|
4302
|
-
const tasks = await connection.requestSearchTasks(params);
|
|
4303
|
-
return textResult(JSON.stringify(tasks, null, 2));
|
|
4304
|
-
} catch (error) {
|
|
4305
|
-
return textResult(
|
|
4306
|
-
`Failed to search tasks: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4307
|
-
);
|
|
4308
|
-
}
|
|
4309
|
-
},
|
|
4310
|
-
{ annotations: { readOnlyHint: true } }
|
|
4311
|
-
),
|
|
4312
|
-
tool4(
|
|
4313
|
-
"list_tags",
|
|
4314
|
-
"List all tags available in the project.",
|
|
4315
|
-
{},
|
|
4316
|
-
async () => {
|
|
4317
|
-
try {
|
|
4318
|
-
const tags = await connection.requestListTags();
|
|
4319
|
-
return textResult(JSON.stringify(tags, null, 2));
|
|
4320
|
-
} catch (error) {
|
|
4321
|
-
return textResult(
|
|
4322
|
-
`Failed to list tags: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4323
|
-
);
|
|
4324
|
-
}
|
|
4325
|
-
},
|
|
4326
|
-
{ annotations: { readOnlyHint: true } }
|
|
4327
|
-
),
|
|
4328
|
-
tool4(
|
|
4329
|
-
"get_project_summary",
|
|
4330
|
-
"Get a summary of the project including task counts by status and active builds.",
|
|
4331
|
-
{},
|
|
4332
|
-
async () => {
|
|
4333
|
-
try {
|
|
4334
|
-
const summary = await connection.requestGetProjectSummary();
|
|
4335
|
-
return textResult(JSON.stringify(summary, null, 2));
|
|
4336
|
-
} catch (error) {
|
|
4337
|
-
return textResult(
|
|
4338
|
-
`Failed to get project summary: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4339
|
-
);
|
|
4340
|
-
}
|
|
4341
|
-
},
|
|
4342
|
-
{ annotations: { readOnlyHint: true } }
|
|
4343
|
-
)
|
|
4344
|
-
];
|
|
4345
|
-
}
|
|
4346
|
-
function buildMutationTools(connection) {
|
|
4347
|
-
return [
|
|
4348
|
-
tool4(
|
|
4349
|
-
"create_task",
|
|
4350
|
-
"Create a new task in the project.",
|
|
4351
|
-
{
|
|
4352
|
-
title: z4.string().describe("Task title"),
|
|
4353
|
-
description: z4.string().optional().describe("Task description"),
|
|
4354
|
-
plan: z4.string().optional().describe("Implementation plan in markdown"),
|
|
4355
|
-
status: z4.string().optional().describe("Initial status (default: Planning)"),
|
|
4356
|
-
isBug: z4.boolean().optional().describe("Whether this is a bug report")
|
|
4357
|
-
},
|
|
4358
|
-
async (params) => {
|
|
4359
|
-
try {
|
|
4360
|
-
const result = await connection.requestCreateTask(params);
|
|
4361
|
-
return textResult(`Task created: ${result.slug} (ID: ${result.id})`);
|
|
4362
|
-
} catch (error) {
|
|
4363
|
-
return textResult(
|
|
4364
|
-
`Failed to create task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4365
|
-
);
|
|
4366
|
-
}
|
|
4367
|
-
}
|
|
4368
|
-
),
|
|
4369
|
-
tool4(
|
|
4370
|
-
"update_task",
|
|
4371
|
-
"Update an existing task's title, description, plan, status, or assignee.",
|
|
4372
|
-
{
|
|
4373
|
-
task_id: z4.string().describe("The task ID to update"),
|
|
4374
|
-
title: z4.string().optional().describe("New title"),
|
|
4375
|
-
description: z4.string().optional().describe("New description"),
|
|
4376
|
-
plan: z4.string().optional().describe("New plan in markdown"),
|
|
4377
|
-
status: z4.string().optional().describe("New status"),
|
|
4378
|
-
assignedUserId: z4.string().nullable().optional().describe("Assign to user ID, or null to unassign")
|
|
4379
|
-
},
|
|
4380
|
-
async ({ task_id, ...fields }) => {
|
|
4381
|
-
try {
|
|
4382
|
-
await connection.requestUpdateTask({ taskId: task_id, ...fields });
|
|
4383
|
-
return textResult("Task updated successfully.");
|
|
4384
|
-
} catch (error) {
|
|
4385
|
-
return textResult(
|
|
4386
|
-
`Failed to update task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4387
|
-
);
|
|
4388
|
-
}
|
|
4389
|
-
}
|
|
4390
|
-
)
|
|
4391
|
-
];
|
|
4392
|
-
}
|
|
4393
|
-
function buildProjectTools(connection) {
|
|
4394
|
-
return [...buildReadTools(connection), ...buildMutationTools(connection)];
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
|
-
// src/runner/project-chat-handler.ts
|
|
4398
|
-
var logger4 = createServiceLogger("ProjectChat");
|
|
3887
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
3888
|
+
var logger3 = createServiceLogger("ProjectChat");
|
|
4399
3889
|
var FALLBACK_MODEL = "claude-sonnet-4-20250514";
|
|
4400
3890
|
function buildSystemPrompt2(projectDir, agentCtx) {
|
|
4401
3891
|
const parts = [];
|
|
@@ -4448,31 +3938,27 @@ function processContentBlock(block, responseParts, turnToolCalls) {
|
|
|
4448
3938
|
input: inputStr.slice(0, 1e4),
|
|
4449
3939
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4450
3940
|
});
|
|
4451
|
-
|
|
3941
|
+
logger3.debug("Tool use", { tool: block.name });
|
|
4452
3942
|
}
|
|
4453
3943
|
}
|
|
4454
|
-
async function fetchContext(connection
|
|
3944
|
+
async function fetchContext(connection) {
|
|
4455
3945
|
let agentCtx = null;
|
|
4456
3946
|
try {
|
|
4457
3947
|
agentCtx = await connection.fetchAgentContext();
|
|
4458
3948
|
} catch {
|
|
4459
|
-
|
|
3949
|
+
logger3.warn("Could not fetch agent context, using defaults");
|
|
4460
3950
|
}
|
|
4461
3951
|
let chatHistory = [];
|
|
4462
3952
|
try {
|
|
4463
|
-
chatHistory = await connection.fetchChatHistory(30
|
|
3953
|
+
chatHistory = await connection.fetchChatHistory(30);
|
|
4464
3954
|
} catch {
|
|
4465
|
-
|
|
3955
|
+
logger3.warn("Could not fetch chat history, proceeding without it");
|
|
4466
3956
|
}
|
|
4467
3957
|
return { agentCtx, chatHistory };
|
|
4468
3958
|
}
|
|
4469
|
-
function buildChatQueryOptions(agentCtx, projectDir
|
|
3959
|
+
function buildChatQueryOptions(agentCtx, projectDir) {
|
|
4470
3960
|
const model = agentCtx?.model || FALLBACK_MODEL;
|
|
4471
3961
|
const settings = agentCtx?.agentSettings ?? {};
|
|
4472
|
-
const mcpServer = createSdkMcpServer2({
|
|
4473
|
-
name: "conveyor",
|
|
4474
|
-
tools: buildProjectTools(connection)
|
|
4475
|
-
});
|
|
4476
3962
|
return {
|
|
4477
3963
|
model,
|
|
4478
3964
|
systemPrompt: {
|
|
@@ -4484,48 +3970,12 @@ function buildChatQueryOptions(agentCtx, projectDir, connection) {
|
|
|
4484
3970
|
permissionMode: "bypassPermissions",
|
|
4485
3971
|
allowDangerouslySkipPermissions: true,
|
|
4486
3972
|
tools: { type: "preset", preset: "claude_code" },
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 50,
|
|
3973
|
+
maxTurns: settings.maxTurns ?? 15,
|
|
3974
|
+
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
4490
3975
|
effort: settings.effort,
|
|
4491
3976
|
thinking: settings.thinking
|
|
4492
3977
|
};
|
|
4493
3978
|
}
|
|
4494
|
-
function emitResultCostAndContext(event, connection) {
|
|
4495
|
-
const resultEvent = event;
|
|
4496
|
-
if (resultEvent.total_cost_usd !== void 0 && resultEvent.total_cost_usd > 0) {
|
|
4497
|
-
connection.emitEvent({
|
|
4498
|
-
type: "cost_update",
|
|
4499
|
-
costUsd: resultEvent.total_cost_usd
|
|
4500
|
-
});
|
|
4501
|
-
}
|
|
4502
|
-
if (resultEvent.modelUsage && typeof resultEvent.modelUsage === "object") {
|
|
4503
|
-
const modelUsage = resultEvent.modelUsage;
|
|
4504
|
-
let contextWindow = 0;
|
|
4505
|
-
let totalInputTokens = 0;
|
|
4506
|
-
let totalCacheRead = 0;
|
|
4507
|
-
let totalCacheCreation = 0;
|
|
4508
|
-
for (const data of Object.values(modelUsage)) {
|
|
4509
|
-
const d = data;
|
|
4510
|
-
totalInputTokens += d.inputTokens ?? 0;
|
|
4511
|
-
totalCacheRead += d.cacheReadInputTokens ?? 0;
|
|
4512
|
-
totalCacheCreation += d.cacheCreationInputTokens ?? 0;
|
|
4513
|
-
const cw = d.contextWindow ?? 0;
|
|
4514
|
-
if (cw > contextWindow) contextWindow = cw;
|
|
4515
|
-
}
|
|
4516
|
-
if (contextWindow > 0) {
|
|
4517
|
-
const queryInputTokens = totalInputTokens + totalCacheRead + totalCacheCreation;
|
|
4518
|
-
connection.emitEvent({
|
|
4519
|
-
type: "context_update",
|
|
4520
|
-
contextTokens: queryInputTokens,
|
|
4521
|
-
contextWindow,
|
|
4522
|
-
inputTokens: totalInputTokens,
|
|
4523
|
-
cacheReadInputTokens: totalCacheRead,
|
|
4524
|
-
cacheCreationInputTokens: totalCacheCreation
|
|
4525
|
-
});
|
|
4526
|
-
}
|
|
4527
|
-
}
|
|
4528
|
-
}
|
|
4529
3979
|
function processEventStream(event, connection, responseParts, turnToolCalls, isTyping) {
|
|
4530
3980
|
if (event.type === "assistant") {
|
|
4531
3981
|
if (!isTyping.value) {
|
|
@@ -4547,30 +3997,19 @@ function processEventStream(event, connection, responseParts, turnToolCalls, isT
|
|
|
4547
3997
|
connection.emitEvent({ type: "agent_typing_stop" });
|
|
4548
3998
|
isTyping.value = false;
|
|
4549
3999
|
}
|
|
4550
|
-
emitResultCostAndContext(event, connection);
|
|
4551
4000
|
return true;
|
|
4552
4001
|
}
|
|
4553
4002
|
return false;
|
|
4554
4003
|
}
|
|
4555
|
-
async function runChatQuery(message, connection, projectDir
|
|
4556
|
-
const { agentCtx, chatHistory } = await fetchContext(connection
|
|
4557
|
-
const options = buildChatQueryOptions(agentCtx, projectDir
|
|
4004
|
+
async function runChatQuery(message, connection, projectDir) {
|
|
4005
|
+
const { agentCtx, chatHistory } = await fetchContext(connection);
|
|
4006
|
+
const options = buildChatQueryOptions(agentCtx, projectDir);
|
|
4558
4007
|
const prompt = buildPrompt(message, chatHistory);
|
|
4559
|
-
|
|
4560
|
-
const events = query2({
|
|
4561
|
-
prompt,
|
|
4562
|
-
options,
|
|
4563
|
-
...sessionId ? { resume: sessionId } : {}
|
|
4564
|
-
});
|
|
4008
|
+
const events = query2({ prompt, options });
|
|
4565
4009
|
const responseParts = [];
|
|
4566
4010
|
const turnToolCalls = [];
|
|
4567
4011
|
const isTyping = { value: false };
|
|
4568
|
-
let resultSessionId;
|
|
4569
4012
|
for await (const event of events) {
|
|
4570
|
-
if (event.type === "result") {
|
|
4571
|
-
const resultEvent = event;
|
|
4572
|
-
resultSessionId = resultEvent.sessionId;
|
|
4573
|
-
}
|
|
4574
4013
|
const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
|
|
4575
4014
|
if (done) break;
|
|
4576
4015
|
}
|
|
@@ -4581,416 +4020,26 @@ async function runChatQuery(message, connection, projectDir, sessionId) {
|
|
|
4581
4020
|
if (responseText) {
|
|
4582
4021
|
await connection.emitChatMessage(responseText);
|
|
4583
4022
|
}
|
|
4584
|
-
return resultSessionId;
|
|
4585
4023
|
}
|
|
4586
|
-
async function handleProjectChatMessage(message, connection, projectDir
|
|
4587
|
-
connection.emitAgentStatus("
|
|
4024
|
+
async function handleProjectChatMessage(message, connection, projectDir) {
|
|
4025
|
+
connection.emitAgentStatus("busy");
|
|
4588
4026
|
try {
|
|
4589
|
-
|
|
4027
|
+
await runChatQuery(message, connection, projectDir);
|
|
4590
4028
|
} catch (error) {
|
|
4591
|
-
|
|
4592
|
-
connection.emitAgentStatus("error");
|
|
4029
|
+
logger3.error("Failed to handle message", errorMeta(error));
|
|
4593
4030
|
try {
|
|
4594
4031
|
await connection.emitChatMessage(
|
|
4595
4032
|
"I encountered an error processing your message. Please try again."
|
|
4596
4033
|
);
|
|
4597
4034
|
} catch {
|
|
4598
4035
|
}
|
|
4599
|
-
return void 0;
|
|
4600
|
-
} finally {
|
|
4601
|
-
connection.emitAgentStatus("idle");
|
|
4602
|
-
}
|
|
4603
|
-
}
|
|
4604
|
-
|
|
4605
|
-
// src/runner/project-audit-handler.ts
|
|
4606
|
-
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
4607
|
-
|
|
4608
|
-
// src/tools/audit-tools.ts
|
|
4609
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
4610
|
-
import { tool as tool5, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
|
|
4611
|
-
import { z as z5 } from "zod";
|
|
4612
|
-
function mapCreateTag(input) {
|
|
4613
|
-
return {
|
|
4614
|
-
type: "create_tag",
|
|
4615
|
-
tagName: input.name,
|
|
4616
|
-
suggestion: `Create new tag "${input.name}"${input.description ? `: ${input.description}` : ""}`,
|
|
4617
|
-
reasoning: input.reasoning,
|
|
4618
|
-
payload: { name: input.name, color: input.color ?? "#6B7280", description: input.description }
|
|
4619
|
-
};
|
|
4620
|
-
}
|
|
4621
|
-
function mapUpdateDescription(input) {
|
|
4622
|
-
return {
|
|
4623
|
-
type: "update_description",
|
|
4624
|
-
tagId: input.tagId,
|
|
4625
|
-
tagName: input.tagName,
|
|
4626
|
-
suggestion: `Update description for "${input.tagName}"`,
|
|
4627
|
-
reasoning: input.reasoning,
|
|
4628
|
-
payload: { description: input.description }
|
|
4629
|
-
};
|
|
4630
|
-
}
|
|
4631
|
-
function mapContextLink(input) {
|
|
4632
|
-
return {
|
|
4633
|
-
type: "add_context_link",
|
|
4634
|
-
tagId: input.tagId,
|
|
4635
|
-
tagName: input.tagName,
|
|
4636
|
-
suggestion: `Link ${input.linkType}:${input.path} to "${input.tagName}"`,
|
|
4637
|
-
reasoning: input.reasoning,
|
|
4638
|
-
payload: { contextLink: { type: input.linkType, path: input.path, label: input.label } }
|
|
4639
|
-
};
|
|
4640
|
-
}
|
|
4641
|
-
function mapDocGap(input) {
|
|
4642
|
-
return {
|
|
4643
|
-
type: "documentation_gap",
|
|
4644
|
-
tagId: input.tagId,
|
|
4645
|
-
tagName: input.tagName,
|
|
4646
|
-
suggestion: `Documentation gap: ${input.filePath} (${input.readCount} reads)`,
|
|
4647
|
-
reasoning: input.reasoning,
|
|
4648
|
-
payload: {
|
|
4649
|
-
filePath: input.filePath,
|
|
4650
|
-
readCount: input.readCount,
|
|
4651
|
-
suggestedAction: input.suggestedAction
|
|
4652
|
-
}
|
|
4653
|
-
};
|
|
4654
|
-
}
|
|
4655
|
-
function mapMergeTags(input) {
|
|
4656
|
-
return {
|
|
4657
|
-
type: "merge_tags",
|
|
4658
|
-
tagId: input.tagId,
|
|
4659
|
-
tagName: input.tagName,
|
|
4660
|
-
suggestion: `Merge "${input.tagName}" into "${input.mergeIntoTagName}"`,
|
|
4661
|
-
reasoning: input.reasoning,
|
|
4662
|
-
payload: { mergeIntoTagId: input.mergeIntoTagId }
|
|
4663
|
-
};
|
|
4664
|
-
}
|
|
4665
|
-
function mapRenameTag(input) {
|
|
4666
|
-
return {
|
|
4667
|
-
type: "rename_tag",
|
|
4668
|
-
tagId: input.tagId,
|
|
4669
|
-
tagName: input.tagName,
|
|
4670
|
-
suggestion: `Rename "${input.tagName}" to "${input.newName}"`,
|
|
4671
|
-
reasoning: input.reasoning,
|
|
4672
|
-
payload: { newName: input.newName }
|
|
4673
|
-
};
|
|
4674
|
-
}
|
|
4675
|
-
var TOOL_MAPPERS = {
|
|
4676
|
-
recommend_create_tag: mapCreateTag,
|
|
4677
|
-
recommend_update_description: mapUpdateDescription,
|
|
4678
|
-
recommend_context_link: mapContextLink,
|
|
4679
|
-
flag_documentation_gap: mapDocGap,
|
|
4680
|
-
recommend_merge_tags: mapMergeTags,
|
|
4681
|
-
recommend_rename_tag: mapRenameTag
|
|
4682
|
-
};
|
|
4683
|
-
function collectRecommendation(toolName, input, collector, onRecommendation) {
|
|
4684
|
-
const mapper = TOOL_MAPPERS[toolName];
|
|
4685
|
-
if (!mapper) return JSON.stringify({ error: `Unknown tool: ${toolName}` });
|
|
4686
|
-
const rec = { id: randomUUID3(), ...mapper(input) };
|
|
4687
|
-
collector.recommendations.push(rec);
|
|
4688
|
-
onRecommendation?.({ tagName: rec.tagName ?? rec.type, type: rec.type });
|
|
4689
|
-
return JSON.stringify({ success: true, recommendationId: rec.id });
|
|
4690
|
-
}
|
|
4691
|
-
function createAuditMcpServer(collector, onRecommendation) {
|
|
4692
|
-
const auditTools = [
|
|
4693
|
-
tool5(
|
|
4694
|
-
"recommend_create_tag",
|
|
4695
|
-
"Recommend creating a new tag for an uncovered subsystem or area",
|
|
4696
|
-
{
|
|
4697
|
-
name: z5.string().describe("Proposed tag name (lowercase, hyphenated)"),
|
|
4698
|
-
color: z5.string().optional().describe("Hex color code"),
|
|
4699
|
-
description: z5.string().describe("What this tag covers"),
|
|
4700
|
-
reasoning: z5.string().describe("Why this tag should be created")
|
|
4701
|
-
},
|
|
4702
|
-
async (args) => {
|
|
4703
|
-
const result = collectRecommendation(
|
|
4704
|
-
"recommend_create_tag",
|
|
4705
|
-
args,
|
|
4706
|
-
collector,
|
|
4707
|
-
onRecommendation
|
|
4708
|
-
);
|
|
4709
|
-
return { content: [{ type: "text", text: result }] };
|
|
4710
|
-
}
|
|
4711
|
-
),
|
|
4712
|
-
tool5(
|
|
4713
|
-
"recommend_update_description",
|
|
4714
|
-
"Recommend updating a tag's description to better reflect its scope",
|
|
4715
|
-
{
|
|
4716
|
-
tagId: z5.string(),
|
|
4717
|
-
tagName: z5.string(),
|
|
4718
|
-
description: z5.string().describe("Proposed new description"),
|
|
4719
|
-
reasoning: z5.string()
|
|
4720
|
-
},
|
|
4721
|
-
async (args) => {
|
|
4722
|
-
const result = collectRecommendation(
|
|
4723
|
-
"recommend_update_description",
|
|
4724
|
-
args,
|
|
4725
|
-
collector,
|
|
4726
|
-
onRecommendation
|
|
4727
|
-
);
|
|
4728
|
-
return { content: [{ type: "text", text: result }] };
|
|
4729
|
-
}
|
|
4730
|
-
),
|
|
4731
|
-
tool5(
|
|
4732
|
-
"recommend_context_link",
|
|
4733
|
-
"Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
|
|
4734
|
-
{
|
|
4735
|
-
tagId: z5.string(),
|
|
4736
|
-
tagName: z5.string(),
|
|
4737
|
-
linkType: z5.enum(["rule", "doc", "file", "folder"]),
|
|
4738
|
-
path: z5.string(),
|
|
4739
|
-
label: z5.string().optional(),
|
|
4740
|
-
reasoning: z5.string()
|
|
4741
|
-
},
|
|
4742
|
-
async (args) => {
|
|
4743
|
-
const result = collectRecommendation(
|
|
4744
|
-
"recommend_context_link",
|
|
4745
|
-
args,
|
|
4746
|
-
collector,
|
|
4747
|
-
onRecommendation
|
|
4748
|
-
);
|
|
4749
|
-
return { content: [{ type: "text", text: result }] };
|
|
4750
|
-
}
|
|
4751
|
-
),
|
|
4752
|
-
tool5(
|
|
4753
|
-
"flag_documentation_gap",
|
|
4754
|
-
"Flag a file that agents read heavily but has no tag documentation linked",
|
|
4755
|
-
{
|
|
4756
|
-
tagName: z5.string().describe("Tag whose agents read this file"),
|
|
4757
|
-
tagId: z5.string().optional(),
|
|
4758
|
-
filePath: z5.string(),
|
|
4759
|
-
readCount: z5.number(),
|
|
4760
|
-
suggestedAction: z5.string().describe("What doc or rule should be created"),
|
|
4761
|
-
reasoning: z5.string()
|
|
4762
|
-
},
|
|
4763
|
-
async (args) => {
|
|
4764
|
-
const result = collectRecommendation(
|
|
4765
|
-
"flag_documentation_gap",
|
|
4766
|
-
args,
|
|
4767
|
-
collector,
|
|
4768
|
-
onRecommendation
|
|
4769
|
-
);
|
|
4770
|
-
return { content: [{ type: "text", text: result }] };
|
|
4771
|
-
}
|
|
4772
|
-
),
|
|
4773
|
-
tool5(
|
|
4774
|
-
"recommend_merge_tags",
|
|
4775
|
-
"Recommend merging one tag into another",
|
|
4776
|
-
{
|
|
4777
|
-
tagId: z5.string().describe("Tag ID to be merged (removed after merge)"),
|
|
4778
|
-
tagName: z5.string().describe("Name of the tag to be merged"),
|
|
4779
|
-
mergeIntoTagId: z5.string().describe("Tag ID to merge into (kept)"),
|
|
4780
|
-
mergeIntoTagName: z5.string(),
|
|
4781
|
-
reasoning: z5.string()
|
|
4782
|
-
},
|
|
4783
|
-
async (args) => {
|
|
4784
|
-
const result = collectRecommendation(
|
|
4785
|
-
"recommend_merge_tags",
|
|
4786
|
-
args,
|
|
4787
|
-
collector,
|
|
4788
|
-
onRecommendation
|
|
4789
|
-
);
|
|
4790
|
-
return { content: [{ type: "text", text: result }] };
|
|
4791
|
-
}
|
|
4792
|
-
),
|
|
4793
|
-
tool5(
|
|
4794
|
-
"recommend_rename_tag",
|
|
4795
|
-
"Recommend renaming a tag",
|
|
4796
|
-
{
|
|
4797
|
-
tagId: z5.string(),
|
|
4798
|
-
tagName: z5.string().describe("Current tag name"),
|
|
4799
|
-
newName: z5.string().describe("Proposed new name"),
|
|
4800
|
-
reasoning: z5.string()
|
|
4801
|
-
},
|
|
4802
|
-
async (args) => {
|
|
4803
|
-
const result = collectRecommendation(
|
|
4804
|
-
"recommend_rename_tag",
|
|
4805
|
-
args,
|
|
4806
|
-
collector,
|
|
4807
|
-
onRecommendation
|
|
4808
|
-
);
|
|
4809
|
-
return { content: [{ type: "text", text: result }] };
|
|
4810
|
-
}
|
|
4811
|
-
),
|
|
4812
|
-
tool5(
|
|
4813
|
-
"complete_audit",
|
|
4814
|
-
"Signal that the audit is complete with a summary of all findings",
|
|
4815
|
-
{ summary: z5.string().describe("Brief overview of all findings") },
|
|
4816
|
-
async (args) => {
|
|
4817
|
-
collector.complete = true;
|
|
4818
|
-
collector.summary = args.summary ?? "Audit completed.";
|
|
4819
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true }) }] };
|
|
4820
|
-
}
|
|
4821
|
-
)
|
|
4822
|
-
];
|
|
4823
|
-
return createSdkMcpServer3({
|
|
4824
|
-
name: "tag-audit",
|
|
4825
|
-
tools: auditTools
|
|
4826
|
-
});
|
|
4827
|
-
}
|
|
4828
|
-
|
|
4829
|
-
// src/runner/project-audit-handler.ts
|
|
4830
|
-
var logger5 = createServiceLogger("ProjectAudit");
|
|
4831
|
-
var FALLBACK_MODEL2 = "claude-sonnet-4-20250514";
|
|
4832
|
-
function buildTagSection(tags) {
|
|
4833
|
-
if (tags.length === 0) return "No tags configured yet.";
|
|
4834
|
-
return tags.map((t) => {
|
|
4835
|
-
const paths = t.contextPaths ?? [];
|
|
4836
|
-
const pathStr = paths.length > 0 ? `
|
|
4837
|
-
Context links: ${paths.map((p) => `${p.type}:${p.path}`).join(", ")}` : "";
|
|
4838
|
-
return ` - ${t.name} (id: ${t.id})${t.description ? `: ${t.description}` : " [no description]"}${pathStr}
|
|
4839
|
-
Active tasks: ${t.activeTaskCount}`;
|
|
4840
|
-
}).join("\n");
|
|
4841
|
-
}
|
|
4842
|
-
function buildHeatmapSection(entries) {
|
|
4843
|
-
if (entries.length === 0) return "No file read analytics data available.";
|
|
4844
|
-
return entries.slice(0, 50).map((e) => {
|
|
4845
|
-
const tagBreakdown = Object.entries(e.byTag).sort(([, a], [, b]) => b - a).map(([tag, count]) => `${tag}:${count}`).join(", ");
|
|
4846
|
-
return ` ${e.filePath} \u2014 ${e.totalReads} reads${tagBreakdown ? ` (${tagBreakdown})` : ""}`;
|
|
4847
|
-
}).join("\n");
|
|
4848
|
-
}
|
|
4849
|
-
function buildAuditSystemPrompt(projectName, tags, heatmapData, projectDir) {
|
|
4850
|
-
return [
|
|
4851
|
-
"You are a project organization expert analyzing tag taxonomy for a software project.",
|
|
4852
|
-
"Tags are used to categorize tasks and link relevant documentation/rules/files to subsystems.",
|
|
4853
|
-
"",
|
|
4854
|
-
`PROJECT: ${projectName}`,
|
|
4855
|
-
"",
|
|
4856
|
-
`EXISTING TAGS (${tags.length}):`,
|
|
4857
|
-
buildTagSection(tags),
|
|
4858
|
-
"",
|
|
4859
|
-
"FILE READ ANALYTICS (what agents actually read, by tag):",
|
|
4860
|
-
buildHeatmapSection(heatmapData),
|
|
4861
|
-
"",
|
|
4862
|
-
`You have full access to the codebase at: ${projectDir}`,
|
|
4863
|
-
"Use your file reading and searching tools to understand the codebase structure,",
|
|
4864
|
-
"module boundaries, and architectural patterns before making recommendations.",
|
|
4865
|
-
"",
|
|
4866
|
-
"ANALYSIS TASKS:",
|
|
4867
|
-
"1. Read actual source files to understand code areas and module boundaries",
|
|
4868
|
-
"2. Search for imports, class definitions, and architectural patterns",
|
|
4869
|
-
"3. Coverage: Are all major subsystems/services represented by tags?",
|
|
4870
|
-
"4. Descriptions: Do all tags have clear, useful descriptions?",
|
|
4871
|
-
"5. Context Links: Are relevant rules/docs/folders linked to tags via contextPaths?",
|
|
4872
|
-
"6. Documentation Gaps: Which high-read files lack linked documentation?",
|
|
4873
|
-
"7. Cleanup: Any tags that should be merged, renamed, or removed?",
|
|
4874
|
-
"",
|
|
4875
|
-
"Use the tag-audit MCP tools to submit each recommendation.",
|
|
4876
|
-
"Call complete_audit when you are done with a thorough summary.",
|
|
4877
|
-
"Be comprehensive \u2014 recommend all improvements your analysis supports.",
|
|
4878
|
-
"Analyze actual file contents, not just file names."
|
|
4879
|
-
].join("\n");
|
|
4880
|
-
}
|
|
4881
|
-
function emitToolCallProgress(event, request, connection) {
|
|
4882
|
-
if (event.type !== "assistant") return;
|
|
4883
|
-
const assistantEvent = event;
|
|
4884
|
-
for (const block of assistantEvent.message.content) {
|
|
4885
|
-
if (block.type === "tool_use" && block.name) {
|
|
4886
|
-
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
4887
|
-
connection.emitAuditProgress({
|
|
4888
|
-
requestId: request.requestId,
|
|
4889
|
-
activity: {
|
|
4890
|
-
tool: block.name,
|
|
4891
|
-
input: inputStr.slice(0, 500),
|
|
4892
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4893
|
-
}
|
|
4894
|
-
});
|
|
4895
|
-
}
|
|
4896
|
-
}
|
|
4897
|
-
}
|
|
4898
|
-
async function runAuditQuery(request, connection, projectDir) {
|
|
4899
|
-
connection.emitAgentStatus("fetching_context");
|
|
4900
|
-
let agentCtx = null;
|
|
4901
|
-
try {
|
|
4902
|
-
agentCtx = await connection.fetchAgentContext();
|
|
4903
|
-
} catch {
|
|
4904
|
-
logger5.warn("Could not fetch agent context for audit, using defaults");
|
|
4905
|
-
}
|
|
4906
|
-
connection.emitAgentStatus("running");
|
|
4907
|
-
const model = agentCtx?.model || FALLBACK_MODEL2;
|
|
4908
|
-
const settings = agentCtx?.agentSettings ?? {};
|
|
4909
|
-
const collector = {
|
|
4910
|
-
recommendations: [],
|
|
4911
|
-
summary: "Audit completed.",
|
|
4912
|
-
complete: false
|
|
4913
|
-
};
|
|
4914
|
-
const onRecommendation = (rec) => {
|
|
4915
|
-
connection.emitEvent({
|
|
4916
|
-
type: "audit_recommendation",
|
|
4917
|
-
tagName: rec.tagName,
|
|
4918
|
-
recommendationType: rec.type
|
|
4919
|
-
});
|
|
4920
|
-
};
|
|
4921
|
-
const systemPrompt = buildAuditSystemPrompt(
|
|
4922
|
-
request.projectName,
|
|
4923
|
-
request.tags,
|
|
4924
|
-
request.fileHeatmap,
|
|
4925
|
-
projectDir
|
|
4926
|
-
);
|
|
4927
|
-
const userPrompt = [
|
|
4928
|
-
"Analyze the project's tag taxonomy and submit recommendations using the tag-audit MCP tools.",
|
|
4929
|
-
`There are currently ${request.tags.length} tags configured.`,
|
|
4930
|
-
request.fileHeatmap.length > 0 ? `File analytics show ${request.fileHeatmap.length} files with read activity.` : "No file read analytics available.",
|
|
4931
|
-
"",
|
|
4932
|
-
"Start by exploring the codebase structure, then analyze each tag for accuracy and completeness.",
|
|
4933
|
-
"Call complete_audit when done."
|
|
4934
|
-
].join("\n");
|
|
4935
|
-
const events = query3({
|
|
4936
|
-
prompt: userPrompt,
|
|
4937
|
-
options: {
|
|
4938
|
-
model,
|
|
4939
|
-
systemPrompt: { type: "preset", preset: "claude_code", append: systemPrompt },
|
|
4940
|
-
cwd: projectDir,
|
|
4941
|
-
permissionMode: "bypassPermissions",
|
|
4942
|
-
allowDangerouslySkipPermissions: true,
|
|
4943
|
-
tools: { type: "preset", preset: "claude_code" },
|
|
4944
|
-
mcpServers: { "tag-audit": createAuditMcpServer(collector, onRecommendation) },
|
|
4945
|
-
maxTurns: settings.maxTurns ?? 75,
|
|
4946
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
4947
|
-
effort: settings.effort,
|
|
4948
|
-
thinking: settings.thinking
|
|
4949
|
-
}
|
|
4950
|
-
});
|
|
4951
|
-
const responseParts = [];
|
|
4952
|
-
const turnToolCalls = [];
|
|
4953
|
-
const isTyping = { value: false };
|
|
4954
|
-
for await (const event of events) {
|
|
4955
|
-
emitToolCallProgress(event, request, connection);
|
|
4956
|
-
const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
|
|
4957
|
-
if (done) break;
|
|
4958
|
-
}
|
|
4959
|
-
if (isTyping.value) {
|
|
4960
|
-
connection.emitEvent({ type: "agent_typing_stop" });
|
|
4961
|
-
}
|
|
4962
|
-
return collector;
|
|
4963
|
-
}
|
|
4964
|
-
async function handleProjectAuditRequest(request, connection, projectDir) {
|
|
4965
|
-
connection.emitAgentStatus("running");
|
|
4966
|
-
try {
|
|
4967
|
-
const collector = await runAuditQuery(request, connection, projectDir);
|
|
4968
|
-
const result = {
|
|
4969
|
-
recommendations: collector.recommendations,
|
|
4970
|
-
summary: collector.summary,
|
|
4971
|
-
analyzedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4972
|
-
};
|
|
4973
|
-
logger5.info("Tag audit completed", {
|
|
4974
|
-
requestId: request.requestId,
|
|
4975
|
-
recommendationCount: result.recommendations.length
|
|
4976
|
-
});
|
|
4977
|
-
connection.emitAuditResult({ requestId: request.requestId, result });
|
|
4978
|
-
} catch (error) {
|
|
4979
|
-
logger5.error("Tag audit failed", {
|
|
4980
|
-
requestId: request.requestId,
|
|
4981
|
-
...errorMeta(error)
|
|
4982
|
-
});
|
|
4983
|
-
connection.emitAuditResult({
|
|
4984
|
-
requestId: request.requestId,
|
|
4985
|
-
error: error instanceof Error ? error.message : "Tag audit failed"
|
|
4986
|
-
});
|
|
4987
4036
|
} finally {
|
|
4988
4037
|
connection.emitAgentStatus("idle");
|
|
4989
4038
|
}
|
|
4990
4039
|
}
|
|
4991
4040
|
|
|
4992
4041
|
// src/runner/project-runner.ts
|
|
4993
|
-
var
|
|
4042
|
+
var logger4 = createServiceLogger("ProjectRunner");
|
|
4994
4043
|
var __filename = fileURLToPath(import.meta.url);
|
|
4995
4044
|
var __dirname = path.dirname(__filename);
|
|
4996
4045
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
@@ -5009,12 +4058,12 @@ function setupWorkDir(projectDir, assignment) {
|
|
|
5009
4058
|
}
|
|
5010
4059
|
if (branch && branch !== devBranch) {
|
|
5011
4060
|
try {
|
|
5012
|
-
|
|
4061
|
+
execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
5013
4062
|
} catch {
|
|
5014
4063
|
try {
|
|
5015
|
-
|
|
4064
|
+
execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
5016
4065
|
} catch {
|
|
5017
|
-
|
|
4066
|
+
logger4.warn("Could not checkout branch", { taskId: shortId, branch });
|
|
5018
4067
|
}
|
|
5019
4068
|
}
|
|
5020
4069
|
}
|
|
@@ -5053,13 +4102,13 @@ function spawnChildAgent(assignment, workDir) {
|
|
|
5053
4102
|
child.stdout?.on("data", (data) => {
|
|
5054
4103
|
const lines = data.toString().trimEnd().split("\n");
|
|
5055
4104
|
for (const line of lines) {
|
|
5056
|
-
|
|
4105
|
+
logger4.info(line, { taskId: shortId });
|
|
5057
4106
|
}
|
|
5058
4107
|
});
|
|
5059
4108
|
child.stderr?.on("data", (data) => {
|
|
5060
4109
|
const lines = data.toString().trimEnd().split("\n");
|
|
5061
4110
|
for (const line of lines) {
|
|
5062
|
-
|
|
4111
|
+
logger4.error(line, { taskId: shortId });
|
|
5063
4112
|
}
|
|
5064
4113
|
});
|
|
5065
4114
|
return child;
|
|
@@ -5071,60 +4120,27 @@ var ProjectRunner = class {
|
|
|
5071
4120
|
heartbeatTimer = null;
|
|
5072
4121
|
stopping = false;
|
|
5073
4122
|
resolveLifecycle = null;
|
|
5074
|
-
chatSessionIds = /* @__PURE__ */ new Map();
|
|
5075
4123
|
// Start command process management
|
|
5076
4124
|
startCommandChild = null;
|
|
5077
4125
|
startCommandRunning = false;
|
|
5078
4126
|
setupComplete = false;
|
|
5079
|
-
branchSwitchCommand;
|
|
5080
|
-
commitWatcher;
|
|
5081
4127
|
constructor(config) {
|
|
5082
4128
|
this.projectDir = config.projectDir;
|
|
5083
4129
|
this.connection = new ProjectConnection({
|
|
5084
4130
|
apiUrl: config.conveyorApiUrl,
|
|
5085
4131
|
projectToken: config.projectToken,
|
|
5086
|
-
projectId: config.projectId
|
|
5087
|
-
projectDir: config.projectDir
|
|
4132
|
+
projectId: config.projectId
|
|
5088
4133
|
});
|
|
5089
|
-
this.commitWatcher = new CommitWatcher(
|
|
5090
|
-
{
|
|
5091
|
-
projectDir: this.projectDir,
|
|
5092
|
-
pollIntervalMs: Number(process.env.CONVEYOR_COMMIT_POLL_INTERVAL) || 1e4,
|
|
5093
|
-
debounceMs: 3e3
|
|
5094
|
-
},
|
|
5095
|
-
{
|
|
5096
|
-
onNewCommits: async (data) => {
|
|
5097
|
-
this.connection.emitNewCommitsDetected({
|
|
5098
|
-
branch: data.branch,
|
|
5099
|
-
commitCount: data.commitCount,
|
|
5100
|
-
latestCommit: {
|
|
5101
|
-
sha: data.newCommitSha,
|
|
5102
|
-
message: data.latestMessage,
|
|
5103
|
-
author: data.latestAuthor
|
|
5104
|
-
},
|
|
5105
|
-
autoSyncing: true
|
|
5106
|
-
});
|
|
5107
|
-
const startTime = Date.now();
|
|
5108
|
-
const stepsRun = await this.smartSync(data.previousSha, data.newCommitSha, data.branch);
|
|
5109
|
-
this.connection.emitEnvironmentReady({
|
|
5110
|
-
branch: data.branch,
|
|
5111
|
-
commitsSynced: data.commitCount,
|
|
5112
|
-
syncDurationMs: Date.now() - startTime,
|
|
5113
|
-
stepsRun
|
|
5114
|
-
});
|
|
5115
|
-
}
|
|
5116
|
-
}
|
|
5117
|
-
);
|
|
5118
4134
|
}
|
|
5119
4135
|
checkoutWorkspaceBranch() {
|
|
5120
4136
|
const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
|
|
5121
4137
|
if (!workspaceBranch) return;
|
|
5122
4138
|
try {
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
4139
|
+
execSync5(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
4140
|
+
execSync5(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
4141
|
+
logger4.info("Checked out workspace branch", { workspaceBranch });
|
|
5126
4142
|
} catch (err) {
|
|
5127
|
-
|
|
4143
|
+
logger4.warn("Failed to checkout workspace branch, continuing on current branch", {
|
|
5128
4144
|
workspaceBranch,
|
|
5129
4145
|
...errorMeta(err)
|
|
5130
4146
|
});
|
|
@@ -5133,15 +4149,15 @@ var ProjectRunner = class {
|
|
|
5133
4149
|
async executeSetupCommand() {
|
|
5134
4150
|
const cmd = process.env.CONVEYOR_SETUP_COMMAND;
|
|
5135
4151
|
if (!cmd) return;
|
|
5136
|
-
|
|
4152
|
+
logger4.info("Running setup command", { command: cmd });
|
|
5137
4153
|
try {
|
|
5138
4154
|
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
5139
4155
|
this.connection.emitEvent({ type: "setup_output", stream, data });
|
|
5140
4156
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
5141
4157
|
});
|
|
5142
|
-
|
|
4158
|
+
logger4.info("Setup command completed");
|
|
5143
4159
|
} catch (error) {
|
|
5144
|
-
|
|
4160
|
+
logger4.error("Setup command failed", errorMeta(error));
|
|
5145
4161
|
this.connection.emitEvent({
|
|
5146
4162
|
type: "setup_error",
|
|
5147
4163
|
message: error instanceof Error ? error.message : "Setup command failed"
|
|
@@ -5152,7 +4168,7 @@ var ProjectRunner = class {
|
|
|
5152
4168
|
executeStartCommand() {
|
|
5153
4169
|
const cmd = process.env.CONVEYOR_START_COMMAND;
|
|
5154
4170
|
if (!cmd) return;
|
|
5155
|
-
|
|
4171
|
+
logger4.info("Running start command", { command: cmd });
|
|
5156
4172
|
const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
|
|
5157
4173
|
this.connection.emitEvent({ type: "start_command_output", stream, data });
|
|
5158
4174
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
@@ -5162,7 +4178,7 @@ var ProjectRunner = class {
|
|
|
5162
4178
|
child.on("exit", (code, signal) => {
|
|
5163
4179
|
this.startCommandRunning = false;
|
|
5164
4180
|
this.startCommandChild = null;
|
|
5165
|
-
|
|
4181
|
+
logger4.info("Start command exited", { code, signal });
|
|
5166
4182
|
this.connection.emitEvent({
|
|
5167
4183
|
type: "start_command_exited",
|
|
5168
4184
|
code,
|
|
@@ -5173,13 +4189,13 @@ var ProjectRunner = class {
|
|
|
5173
4189
|
child.on("error", (err) => {
|
|
5174
4190
|
this.startCommandRunning = false;
|
|
5175
4191
|
this.startCommandChild = null;
|
|
5176
|
-
|
|
4192
|
+
logger4.error("Start command error", errorMeta(err));
|
|
5177
4193
|
});
|
|
5178
4194
|
}
|
|
5179
4195
|
async killStartCommand() {
|
|
5180
4196
|
const child = this.startCommandChild;
|
|
5181
4197
|
if (!child || !this.startCommandRunning) return;
|
|
5182
|
-
|
|
4198
|
+
logger4.info("Killing start command");
|
|
5183
4199
|
try {
|
|
5184
4200
|
if (child.pid) process.kill(-child.pid, "SIGTERM");
|
|
5185
4201
|
} catch {
|
|
@@ -5211,7 +4227,7 @@ var ProjectRunner = class {
|
|
|
5211
4227
|
getEnvironmentStatus() {
|
|
5212
4228
|
let currentBranch = "unknown";
|
|
5213
4229
|
try {
|
|
5214
|
-
currentBranch =
|
|
4230
|
+
currentBranch = execSync5("git branch --show-current", {
|
|
5215
4231
|
cwd: this.projectDir,
|
|
5216
4232
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5217
4233
|
}).toString().trim();
|
|
@@ -5224,180 +4240,6 @@ var ProjectRunner = class {
|
|
|
5224
4240
|
previewPort: Number(process.env.CONVEYOR_PREVIEW_PORT) || null
|
|
5225
4241
|
};
|
|
5226
4242
|
}
|
|
5227
|
-
getCurrentBranch() {
|
|
5228
|
-
try {
|
|
5229
|
-
return execSync6("git branch --show-current", {
|
|
5230
|
-
cwd: this.projectDir,
|
|
5231
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5232
|
-
}).toString().trim() || null;
|
|
5233
|
-
} catch {
|
|
5234
|
-
return null;
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
5237
|
-
// oxlint-disable-next-line max-lines-per-function, complexity -- sequential sync steps with per-step error handling
|
|
5238
|
-
async smartSync(previousSha, newSha, branch) {
|
|
5239
|
-
const stepsRun = [];
|
|
5240
|
-
const status = execSync6("git status --porcelain", {
|
|
5241
|
-
cwd: this.projectDir,
|
|
5242
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5243
|
-
}).toString().trim();
|
|
5244
|
-
if (status) {
|
|
5245
|
-
this.connection.emitEvent({
|
|
5246
|
-
type: "commit_watch_warning",
|
|
5247
|
-
message: "Working tree has uncommitted changes. Auto-pull skipped."
|
|
5248
|
-
});
|
|
5249
|
-
return ["skipped:dirty_tree"];
|
|
5250
|
-
}
|
|
5251
|
-
await this.killStartCommand();
|
|
5252
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "running" });
|
|
5253
|
-
try {
|
|
5254
|
-
execSync6(`git pull origin ${branch}`, {
|
|
5255
|
-
cwd: this.projectDir,
|
|
5256
|
-
stdio: "pipe",
|
|
5257
|
-
timeout: 6e4
|
|
5258
|
-
});
|
|
5259
|
-
stepsRun.push("pull");
|
|
5260
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "success" });
|
|
5261
|
-
} catch (err) {
|
|
5262
|
-
const message = err instanceof Error ? err.message : "Pull failed";
|
|
5263
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "error", message });
|
|
5264
|
-
logger6.error("Git pull failed during commit sync", errorMeta(err));
|
|
5265
|
-
this.executeStartCommand();
|
|
5266
|
-
return ["error:pull"];
|
|
5267
|
-
}
|
|
5268
|
-
let changedFiles = [];
|
|
5269
|
-
try {
|
|
5270
|
-
changedFiles = execSync6(`git diff --name-only ${previousSha}..${newSha}`, {
|
|
5271
|
-
cwd: this.projectDir,
|
|
5272
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5273
|
-
}).toString().trim().split("\n").filter(Boolean);
|
|
5274
|
-
} catch {
|
|
5275
|
-
}
|
|
5276
|
-
const needsInstall = changedFiles.some(
|
|
5277
|
-
(f) => f === "package.json" || f === "bun.lockb" || f === "bunfig.toml" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
|
|
5278
|
-
);
|
|
5279
|
-
const needsPrisma = changedFiles.some(
|
|
5280
|
-
(f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
|
|
5281
|
-
);
|
|
5282
|
-
const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5283
|
-
if (cmd && (needsInstall || needsPrisma)) {
|
|
5284
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "running" });
|
|
5285
|
-
try {
|
|
5286
|
-
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
5287
|
-
this.connection.emitEvent({ type: "sync_output", stream, data });
|
|
5288
|
-
});
|
|
5289
|
-
stepsRun.push("branchSwitchCommand");
|
|
5290
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "success" });
|
|
5291
|
-
} catch (err) {
|
|
5292
|
-
const message = err instanceof Error ? err.message : "Sync command failed";
|
|
5293
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "error", message });
|
|
5294
|
-
logger6.error("Branch switch command failed during commit sync", errorMeta(err));
|
|
5295
|
-
}
|
|
5296
|
-
} else if (!cmd) {
|
|
5297
|
-
if (needsInstall) {
|
|
5298
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "running" });
|
|
5299
|
-
try {
|
|
5300
|
-
execSync6("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
|
|
5301
|
-
stepsRun.push("install");
|
|
5302
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "success" });
|
|
5303
|
-
} catch (err) {
|
|
5304
|
-
const message = err instanceof Error ? err.message : "Install failed";
|
|
5305
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "error", message });
|
|
5306
|
-
logger6.error("bun install failed during commit sync", errorMeta(err));
|
|
5307
|
-
}
|
|
5308
|
-
}
|
|
5309
|
-
if (needsPrisma) {
|
|
5310
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "running" });
|
|
5311
|
-
try {
|
|
5312
|
-
execSync6("bunx prisma generate", {
|
|
5313
|
-
cwd: this.projectDir,
|
|
5314
|
-
timeout: 6e4,
|
|
5315
|
-
stdio: "pipe"
|
|
5316
|
-
});
|
|
5317
|
-
execSync6("bunx prisma db push --accept-data-loss", {
|
|
5318
|
-
cwd: this.projectDir,
|
|
5319
|
-
timeout: 6e4,
|
|
5320
|
-
stdio: "pipe"
|
|
5321
|
-
});
|
|
5322
|
-
stepsRun.push("prisma");
|
|
5323
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "success" });
|
|
5324
|
-
} catch (err) {
|
|
5325
|
-
const message = err instanceof Error ? err.message : "Prisma sync failed";
|
|
5326
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "error", message });
|
|
5327
|
-
logger6.error("Prisma sync failed during commit sync", errorMeta(err));
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
}
|
|
5331
|
-
this.executeStartCommand();
|
|
5332
|
-
stepsRun.push("startCommand");
|
|
5333
|
-
return stepsRun;
|
|
5334
|
-
}
|
|
5335
|
-
async handleSwitchBranch(data, callback) {
|
|
5336
|
-
const { branch, syncAfter } = data;
|
|
5337
|
-
try {
|
|
5338
|
-
this.connection.emitEnvSwitchProgress({ step: "fetch", status: "running" });
|
|
5339
|
-
try {
|
|
5340
|
-
execSync6("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
|
|
5341
|
-
} catch {
|
|
5342
|
-
logger6.warn("Git fetch failed during branch switch");
|
|
5343
|
-
}
|
|
5344
|
-
this.connection.emitEnvSwitchProgress({ step: "fetch", status: "success" });
|
|
5345
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "running" });
|
|
5346
|
-
try {
|
|
5347
|
-
execSync6(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
5348
|
-
} catch (err) {
|
|
5349
|
-
const message = err instanceof Error ? err.message : "Checkout failed";
|
|
5350
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "error", message });
|
|
5351
|
-
callback({ ok: false, error: `Failed to checkout branch: ${message}` });
|
|
5352
|
-
return;
|
|
5353
|
-
}
|
|
5354
|
-
try {
|
|
5355
|
-
execSync6(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
5356
|
-
} catch {
|
|
5357
|
-
logger6.warn("Git pull failed during branch switch", { branch });
|
|
5358
|
-
}
|
|
5359
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "success" });
|
|
5360
|
-
if (syncAfter !== false) {
|
|
5361
|
-
await this.handleSyncEnvironment();
|
|
5362
|
-
}
|
|
5363
|
-
this.commitWatcher.start(branch);
|
|
5364
|
-
callback({ ok: true, data: this.getEnvironmentStatus() });
|
|
5365
|
-
} catch (err) {
|
|
5366
|
-
const message = err instanceof Error ? err.message : "Branch switch failed";
|
|
5367
|
-
logger6.error("Branch switch failed", errorMeta(err));
|
|
5368
|
-
callback({ ok: false, error: message });
|
|
5369
|
-
}
|
|
5370
|
-
}
|
|
5371
|
-
async handleSyncEnvironment(callback) {
|
|
5372
|
-
try {
|
|
5373
|
-
await this.killStartCommand();
|
|
5374
|
-
const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5375
|
-
if (cmd) {
|
|
5376
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "running" });
|
|
5377
|
-
try {
|
|
5378
|
-
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
5379
|
-
this.connection.emitEvent({ type: "sync_output", stream, data });
|
|
5380
|
-
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
5381
|
-
});
|
|
5382
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "success" });
|
|
5383
|
-
} catch (err) {
|
|
5384
|
-
const message = err instanceof Error ? err.message : "Sync command failed";
|
|
5385
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "error", message });
|
|
5386
|
-
logger6.error("Branch switch sync command failed", errorMeta(err));
|
|
5387
|
-
}
|
|
5388
|
-
}
|
|
5389
|
-
this.executeStartCommand();
|
|
5390
|
-
this.connection.emitEnvSwitchProgress({ step: "startCommand", status: "success" });
|
|
5391
|
-
callback?.({ ok: true, data: this.getEnvironmentStatus() });
|
|
5392
|
-
} catch (err) {
|
|
5393
|
-
const message = err instanceof Error ? err.message : "Sync failed";
|
|
5394
|
-
logger6.error("Environment sync failed", errorMeta(err));
|
|
5395
|
-
callback?.({ ok: false, error: message });
|
|
5396
|
-
}
|
|
5397
|
-
}
|
|
5398
|
-
handleGetEnvStatus(callback) {
|
|
5399
|
-
callback({ ok: true, data: this.getEnvironmentStatus() });
|
|
5400
|
-
}
|
|
5401
4243
|
async start() {
|
|
5402
4244
|
this.checkoutWorkspaceBranch();
|
|
5403
4245
|
await this.connection.connect();
|
|
@@ -5411,7 +4253,7 @@ var ProjectRunner = class {
|
|
|
5411
4253
|
startCommandRunning: this.startCommandRunning
|
|
5412
4254
|
});
|
|
5413
4255
|
} catch (error) {
|
|
5414
|
-
|
|
4256
|
+
logger4.error("Environment setup failed", errorMeta(error));
|
|
5415
4257
|
this.setupComplete = false;
|
|
5416
4258
|
}
|
|
5417
4259
|
this.connection.onTaskAssignment((assignment) => {
|
|
@@ -5421,53 +4263,17 @@ var ProjectRunner = class {
|
|
|
5421
4263
|
this.handleStopTask(data.taskId);
|
|
5422
4264
|
});
|
|
5423
4265
|
this.connection.onShutdown(() => {
|
|
5424
|
-
|
|
4266
|
+
logger4.info("Received shutdown signal from server");
|
|
5425
4267
|
void this.stop();
|
|
5426
4268
|
});
|
|
5427
4269
|
this.connection.onChatMessage((msg) => {
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
const existingSessionId = this.chatSessionIds.get(chatId);
|
|
5431
|
-
void handleProjectChatMessage(msg, this.connection, this.projectDir, existingSessionId).then(
|
|
5432
|
-
(newSessionId) => {
|
|
5433
|
-
if (newSessionId) {
|
|
5434
|
-
this.chatSessionIds.set(chatId, newSessionId);
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
|
-
);
|
|
5438
|
-
});
|
|
5439
|
-
this.connection.onAuditRequest((request) => {
|
|
5440
|
-
logger6.debug("Received tag audit request", { requestId: request.requestId });
|
|
5441
|
-
void handleProjectAuditRequest(request, this.connection, this.projectDir);
|
|
4270
|
+
logger4.debug("Received project chat message");
|
|
4271
|
+
void handleProjectChatMessage(msg, this.connection, this.projectDir);
|
|
5442
4272
|
});
|
|
5443
|
-
this.connection.onSwitchBranch = (data, cb) => {
|
|
5444
|
-
void this.handleSwitchBranch(data, cb);
|
|
5445
|
-
};
|
|
5446
|
-
this.connection.onSyncEnvironment = (cb) => {
|
|
5447
|
-
void this.handleSyncEnvironment(cb);
|
|
5448
|
-
};
|
|
5449
|
-
this.connection.onGetEnvStatus = (cb) => {
|
|
5450
|
-
this.handleGetEnvStatus(cb);
|
|
5451
|
-
};
|
|
5452
|
-
this.connection.onRestartStartCommand = (cb) => {
|
|
5453
|
-
void this.restartStartCommand().then(() => cb({ ok: true })).catch(
|
|
5454
|
-
(err) => cb({ ok: false, error: err instanceof Error ? err.message : "Restart failed" })
|
|
5455
|
-
);
|
|
5456
|
-
};
|
|
5457
|
-
try {
|
|
5458
|
-
const context = await this.connection.fetchAgentContext();
|
|
5459
|
-
this.branchSwitchCommand = context?.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5460
|
-
} catch {
|
|
5461
|
-
this.branchSwitchCommand = process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5462
|
-
}
|
|
5463
4273
|
this.heartbeatTimer = setInterval(() => {
|
|
5464
4274
|
this.connection.sendHeartbeat();
|
|
5465
4275
|
}, HEARTBEAT_INTERVAL_MS2);
|
|
5466
|
-
|
|
5467
|
-
if (currentBranch) {
|
|
5468
|
-
this.commitWatcher.start(currentBranch);
|
|
5469
|
-
}
|
|
5470
|
-
logger6.info("Connected, waiting for task assignments");
|
|
4276
|
+
logger4.info("Connected, waiting for task assignments");
|
|
5471
4277
|
await new Promise((resolve2) => {
|
|
5472
4278
|
this.resolveLifecycle = resolve2;
|
|
5473
4279
|
process.on("SIGTERM", () => void this.stop());
|
|
@@ -5478,11 +4284,11 @@ var ProjectRunner = class {
|
|
|
5478
4284
|
const { taskId, mode } = assignment;
|
|
5479
4285
|
const shortId = taskId.slice(0, 8);
|
|
5480
4286
|
if (this.activeAgents.has(taskId)) {
|
|
5481
|
-
|
|
4287
|
+
logger4.info("Task already running, skipping", { taskId: shortId });
|
|
5482
4288
|
return;
|
|
5483
4289
|
}
|
|
5484
4290
|
if (this.activeAgents.size >= MAX_CONCURRENT) {
|
|
5485
|
-
|
|
4291
|
+
logger4.warn("Max concurrent agents reached, rejecting task", {
|
|
5486
4292
|
maxConcurrent: MAX_CONCURRENT,
|
|
5487
4293
|
taskId: shortId
|
|
5488
4294
|
});
|
|
@@ -5491,9 +4297,9 @@ var ProjectRunner = class {
|
|
|
5491
4297
|
}
|
|
5492
4298
|
try {
|
|
5493
4299
|
try {
|
|
5494
|
-
|
|
4300
|
+
execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
|
|
5495
4301
|
} catch {
|
|
5496
|
-
|
|
4302
|
+
logger4.warn("Git fetch failed", { taskId: shortId });
|
|
5497
4303
|
}
|
|
5498
4304
|
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
5499
4305
|
const child = spawnChildAgent(assignment, workDir);
|
|
@@ -5504,12 +4310,12 @@ var ProjectRunner = class {
|
|
|
5504
4310
|
usesWorktree
|
|
5505
4311
|
});
|
|
5506
4312
|
this.connection.emitTaskStarted(taskId);
|
|
5507
|
-
|
|
4313
|
+
logger4.info("Started task", { taskId: shortId, mode, workDir });
|
|
5508
4314
|
child.on("exit", (code) => {
|
|
5509
4315
|
this.activeAgents.delete(taskId);
|
|
5510
4316
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
5511
4317
|
this.connection.emitTaskStopped(taskId, reason);
|
|
5512
|
-
|
|
4318
|
+
logger4.info("Task exited", { taskId: shortId, reason });
|
|
5513
4319
|
if (code === 0 && usesWorktree) {
|
|
5514
4320
|
try {
|
|
5515
4321
|
removeWorktree(this.projectDir, taskId);
|
|
@@ -5518,7 +4324,7 @@ var ProjectRunner = class {
|
|
|
5518
4324
|
}
|
|
5519
4325
|
});
|
|
5520
4326
|
} catch (error) {
|
|
5521
|
-
|
|
4327
|
+
logger4.error("Failed to start task", {
|
|
5522
4328
|
taskId: shortId,
|
|
5523
4329
|
...errorMeta(error)
|
|
5524
4330
|
});
|
|
@@ -5532,7 +4338,7 @@ var ProjectRunner = class {
|
|
|
5532
4338
|
const agent = this.activeAgents.get(taskId);
|
|
5533
4339
|
if (!agent) return;
|
|
5534
4340
|
const shortId = taskId.slice(0, 8);
|
|
5535
|
-
|
|
4341
|
+
logger4.info("Stopping task", { taskId: shortId });
|
|
5536
4342
|
agent.process.kill("SIGTERM");
|
|
5537
4343
|
const timer = setTimeout(() => {
|
|
5538
4344
|
if (this.activeAgents.has(taskId)) {
|
|
@@ -5552,8 +4358,7 @@ var ProjectRunner = class {
|
|
|
5552
4358
|
async stop() {
|
|
5553
4359
|
if (this.stopping) return;
|
|
5554
4360
|
this.stopping = true;
|
|
5555
|
-
|
|
5556
|
-
this.commitWatcher.stop();
|
|
4361
|
+
logger4.info("Shutting down");
|
|
5557
4362
|
await this.killStartCommand();
|
|
5558
4363
|
if (this.heartbeatTimer) {
|
|
5559
4364
|
clearInterval(this.heartbeatTimer);
|
|
@@ -5579,7 +4384,7 @@ var ProjectRunner = class {
|
|
|
5579
4384
|
})
|
|
5580
4385
|
]);
|
|
5581
4386
|
this.connection.disconnect();
|
|
5582
|
-
|
|
4387
|
+
logger4.info("Shutdown complete");
|
|
5583
4388
|
if (this.resolveLifecycle) {
|
|
5584
4389
|
this.resolveLifecycle();
|
|
5585
4390
|
this.resolveLifecycle = null;
|
|
@@ -5665,4 +4470,4 @@ export {
|
|
|
5665
4470
|
ProjectRunner,
|
|
5666
4471
|
FileCache
|
|
5667
4472
|
};
|
|
5668
|
-
//# sourceMappingURL=chunk-
|
|
4473
|
+
//# sourceMappingURL=chunk-JFIWJVOH.js.map
|