@rallycry/conveyor-agent 5.11.1 → 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-U3YWTVH3.js → chunk-JFIWJVOH.js} +97 -1083
- 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 +1 -65
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-U3YWTVH3.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", {});
|
|
@@ -654,46 +588,6 @@ var ProjectConnection = class {
|
|
|
654
588
|
);
|
|
655
589
|
});
|
|
656
590
|
}
|
|
657
|
-
emitNewCommitsDetected(data) {
|
|
658
|
-
if (!this.socket) return;
|
|
659
|
-
this.socket.emit("projectRunner:newCommitsDetected", data);
|
|
660
|
-
}
|
|
661
|
-
emitEnvironmentReady(data) {
|
|
662
|
-
if (!this.socket) return;
|
|
663
|
-
this.socket.emit("projectRunner:environmentReady", data);
|
|
664
|
-
}
|
|
665
|
-
emitEnvSwitchProgress(data) {
|
|
666
|
-
if (!this.socket) return;
|
|
667
|
-
this.socket.emit("projectRunner:envSwitchProgress", data);
|
|
668
|
-
}
|
|
669
|
-
handleRunAuthTokenCommand(userEmail, cb) {
|
|
670
|
-
try {
|
|
671
|
-
if (process.env.CODESPACES !== "true") {
|
|
672
|
-
cb({ ok: false, error: "Auth token command only available in codespace environments" });
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
const authCmd = process.env.CONVEYOR_AUTH_TOKEN_COMMAND;
|
|
676
|
-
if (!authCmd) {
|
|
677
|
-
cb({ ok: false, error: "CONVEYOR_AUTH_TOKEN_COMMAND not configured" });
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const cwd = this.config.projectDir ?? process.cwd();
|
|
681
|
-
const token = runAuthTokenCommand(authCmd, userEmail, cwd);
|
|
682
|
-
if (!token) {
|
|
683
|
-
cb({
|
|
684
|
-
ok: false,
|
|
685
|
-
error: `Auth token command returned empty output. Command: ${authCmd}`
|
|
686
|
-
});
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
cb({ ok: true, token });
|
|
690
|
-
} catch (error) {
|
|
691
|
-
cb({
|
|
692
|
-
ok: false,
|
|
693
|
-
error: error instanceof Error ? error.message : "Auth token command failed"
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
591
|
disconnect() {
|
|
698
592
|
this.socket?.disconnect();
|
|
699
593
|
this.socket = null;
|
|
@@ -813,14 +707,6 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
813
707
|
import { execSync as execSync4 } from "child_process";
|
|
814
708
|
|
|
815
709
|
// src/execution/event-handlers.ts
|
|
816
|
-
function safeVoid(promise, context) {
|
|
817
|
-
if (promise && typeof promise.catch === "function") {
|
|
818
|
-
promise.catch((err) => {
|
|
819
|
-
process.stderr.write(`[safeVoid] ${context}: ${err}
|
|
820
|
-
`);
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
710
|
function epochSecondsToISO(value) {
|
|
825
711
|
if (typeof value === "string") return value;
|
|
826
712
|
if (typeof value !== "number" || value <= 0) return void 0;
|
|
@@ -859,10 +745,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
859
745
|
}
|
|
860
746
|
var API_ERROR_PATTERN = /API Error: [45]\d\d/;
|
|
861
747
|
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
862
|
-
var AUTH_ERROR_PATTERN = /Not logged in|Please run \/login|authentication failed|invalid.*token|unauthorized/i;
|
|
863
|
-
function isAuthError(msg) {
|
|
864
|
-
return AUTH_ERROR_PATTERN.test(msg);
|
|
865
|
-
}
|
|
866
748
|
function isRetriableMessage(msg) {
|
|
867
749
|
if (IMAGE_ERROR_PATTERN.test(msg)) return true;
|
|
868
750
|
if (API_ERROR_PATTERN.test(msg)) return true;
|
|
@@ -943,10 +825,6 @@ function handleErrorResult(event, host) {
|
|
|
943
825
|
if (isStaleSession) {
|
|
944
826
|
return { retriable: false, staleSession: true };
|
|
945
827
|
}
|
|
946
|
-
if (isAuthError(errorMsg)) {
|
|
947
|
-
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
948
|
-
return { retriable: false, authError: true };
|
|
949
|
-
}
|
|
950
828
|
const retriable = isRetriableMessage(errorMsg);
|
|
951
829
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
952
830
|
return { retriable };
|
|
@@ -986,8 +864,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
986
864
|
return {
|
|
987
865
|
retriable: result.retriable,
|
|
988
866
|
resultSummary: result.resultSummary,
|
|
989
|
-
staleSession: result.staleSession
|
|
990
|
-
authError: result.authError
|
|
867
|
+
staleSession: result.staleSession
|
|
991
868
|
};
|
|
992
869
|
}
|
|
993
870
|
function handleRateLimitEvent(event, host) {
|
|
@@ -1006,13 +883,13 @@ function handleRateLimitEvent(event, host) {
|
|
|
1006
883
|
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
1007
884
|
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
1008
885
|
host.connection.sendEvent({ type: "error", message });
|
|
1009
|
-
|
|
886
|
+
void host.callbacks.onEvent({ type: "error", message });
|
|
1010
887
|
return resetsAt;
|
|
1011
888
|
} else if (status === "allowed_warning") {
|
|
1012
889
|
const utilization = rate_limit_info.utilization ? `${Math.round(rate_limit_info.utilization * 100)}%` : "high";
|
|
1013
890
|
const message = `Rate limit warning: ${utilization} utilization (type: ${rate_limit_info.rateLimitType ?? "unknown"})`;
|
|
1014
891
|
host.connection.sendEvent({ type: "thinking", message });
|
|
1015
|
-
|
|
892
|
+
void host.callbacks.onEvent({ type: "thinking", message });
|
|
1016
893
|
}
|
|
1017
894
|
return void 0;
|
|
1018
895
|
}
|
|
@@ -1030,46 +907,34 @@ async function handleSystemEvent(event, host, context, sessionIdStored) {
|
|
|
1030
907
|
}
|
|
1031
908
|
function handleSystemSubevents(systemEvent, host) {
|
|
1032
909
|
if (systemEvent.subtype === "compact_boundary") {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}),
|
|
1039
|
-
"compactBoundary"
|
|
1040
|
-
);
|
|
910
|
+
void host.callbacks.onEvent({
|
|
911
|
+
type: "context_compacted",
|
|
912
|
+
trigger: systemEvent.compact_metadata.trigger,
|
|
913
|
+
preTokens: systemEvent.compact_metadata.pre_tokens
|
|
914
|
+
});
|
|
1041
915
|
} else if (systemEvent.subtype === "task_started") {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}),
|
|
1048
|
-
"taskStarted"
|
|
1049
|
-
);
|
|
916
|
+
void host.callbacks.onEvent({
|
|
917
|
+
type: "subagent_started",
|
|
918
|
+
sdkTaskId: systemEvent.task_id,
|
|
919
|
+
description: systemEvent.description
|
|
920
|
+
});
|
|
1050
921
|
} else if (systemEvent.subtype === "task_progress") {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}),
|
|
1059
|
-
"taskProgress"
|
|
1060
|
-
);
|
|
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
|
+
});
|
|
1061
929
|
}
|
|
1062
930
|
}
|
|
1063
931
|
function handleToolProgressEvent(event, host) {
|
|
1064
932
|
const msg = event;
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}),
|
|
1071
|
-
"toolProgress"
|
|
1072
|
-
);
|
|
933
|
+
void host.callbacks.onEvent({
|
|
934
|
+
type: "tool_progress",
|
|
935
|
+
toolName: msg.tool_name ?? "",
|
|
936
|
+
elapsedSeconds: msg.elapsed_time_seconds ?? 0
|
|
937
|
+
});
|
|
1073
938
|
}
|
|
1074
939
|
async function handleAssistantCase(event, host, turnToolCalls) {
|
|
1075
940
|
await processAssistantEvent(event, host, turnToolCalls);
|
|
@@ -1087,13 +952,11 @@ async function handleResultCase(event, host, context, startTime, isTyping, lastA
|
|
|
1087
952
|
retriable: resultInfo.retriable,
|
|
1088
953
|
resultSummary: resultInfo.resultSummary,
|
|
1089
954
|
staleSession: resultInfo.staleSession,
|
|
1090
|
-
authError: resultInfo.authError,
|
|
1091
955
|
stoppedTyping
|
|
1092
956
|
};
|
|
1093
957
|
}
|
|
1094
958
|
|
|
1095
959
|
// src/execution/event-processor.ts
|
|
1096
|
-
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
1097
960
|
function stopTypingIfNeeded(host, isTyping) {
|
|
1098
961
|
if (isTyping) host.connection.sendTypingStop();
|
|
1099
962
|
}
|
|
@@ -1115,12 +978,6 @@ async function processAssistantCase(event, host, state) {
|
|
|
1115
978
|
}
|
|
1116
979
|
const usage = await handleAssistantCase(event, host, state.turnToolCalls);
|
|
1117
980
|
if (usage) state.lastAssistantUsage = usage;
|
|
1118
|
-
if (!state.sawApiError) {
|
|
1119
|
-
const fullText = event.message.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
1120
|
-
if (API_ERROR_PATTERN2.test(fullText)) {
|
|
1121
|
-
state.sawApiError = true;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
981
|
}
|
|
1125
982
|
async function processResultCase(event, host, context, startTime, state) {
|
|
1126
983
|
const info = await handleResultCase(
|
|
@@ -1135,7 +992,6 @@ async function processResultCase(event, host, context, startTime, state) {
|
|
|
1135
992
|
state.retriable = info.retriable;
|
|
1136
993
|
state.resultSummary = info.resultSummary;
|
|
1137
994
|
if (info.staleSession) state.staleSession = true;
|
|
1138
|
-
if (info.authError) state.authError = true;
|
|
1139
995
|
}
|
|
1140
996
|
async function processEvents(events, context, host) {
|
|
1141
997
|
const startTime = Date.now();
|
|
@@ -1145,11 +1001,9 @@ async function processEvents(events, context, host) {
|
|
|
1145
1001
|
sessionIdStored: false,
|
|
1146
1002
|
isTyping: false,
|
|
1147
1003
|
retriable: false,
|
|
1148
|
-
sawApiError: false,
|
|
1149
1004
|
resultSummary: void 0,
|
|
1150
1005
|
rateLimitResetsAt: void 0,
|
|
1151
1006
|
staleSession: void 0,
|
|
1152
|
-
authError: void 0,
|
|
1153
1007
|
lastAssistantUsage: void 0,
|
|
1154
1008
|
turnToolCalls: []
|
|
1155
1009
|
};
|
|
@@ -1186,11 +1040,10 @@ async function processEvents(events, context, host) {
|
|
|
1186
1040
|
}
|
|
1187
1041
|
stopTypingIfNeeded(host, state.isTyping);
|
|
1188
1042
|
return {
|
|
1189
|
-
retriable: state.retriable
|
|
1043
|
+
retriable: state.retriable,
|
|
1190
1044
|
resultSummary: state.resultSummary,
|
|
1191
1045
|
rateLimitResetsAt: state.rateLimitResetsAt,
|
|
1192
|
-
...state.staleSession && { staleSession: state.staleSession }
|
|
1193
|
-
...state.authError && { authError: state.authError }
|
|
1046
|
+
...state.staleSession && { staleSession: state.staleSession }
|
|
1194
1047
|
};
|
|
1195
1048
|
}
|
|
1196
1049
|
|
|
@@ -1548,8 +1401,6 @@ function buildDiscoveryPrompt(context) {
|
|
|
1548
1401
|
`You are in Discovery mode \u2014 helping plan and scope this task.`,
|
|
1549
1402
|
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
1550
1403
|
`- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
|
|
1551
|
-
`- Do NOT attempt to edit, write, or modify source code files \u2014 these operations will be denied`,
|
|
1552
|
-
`- If you identify code changes needed, describe them in the plan instead of implementing them`,
|
|
1553
1404
|
`- You can create and manage subtasks`,
|
|
1554
1405
|
`- Goal: collaborate with the user to create a clear plan`,
|
|
1555
1406
|
`- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
|
|
@@ -1684,14 +1535,6 @@ Project Agents:`);
|
|
|
1684
1535
|
parts.push(formatProjectAgentLine(pa));
|
|
1685
1536
|
}
|
|
1686
1537
|
}
|
|
1687
|
-
if (context.projectObjectives && context.projectObjectives.length > 0) {
|
|
1688
|
-
parts.push(`
|
|
1689
|
-
Project Objectives:`);
|
|
1690
|
-
for (const obj of context.projectObjectives) {
|
|
1691
|
-
const dates = `${obj.startDate.split("T")[0]} to ${obj.endDate.split("T")[0]}`;
|
|
1692
|
-
parts.push(`- **${obj.name}** (${dates})${obj.description ? ": " + obj.description : ""}`);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
1538
|
return parts;
|
|
1696
1539
|
}
|
|
1697
1540
|
function buildActivePreamble(context, workspaceDir) {
|
|
@@ -1814,7 +1657,7 @@ function detectRelaunchScenario(context, trustChatHistory = false) {
|
|
|
1814
1657
|
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
1815
1658
|
return hasNewUserMessages ? "feedback_relaunch" : "idle_relaunch";
|
|
1816
1659
|
}
|
|
1817
|
-
function buildRelaunchWithSession(mode, context, agentMode
|
|
1660
|
+
function buildRelaunchWithSession(mode, context, agentMode) {
|
|
1818
1661
|
const scenario = detectRelaunchScenario(context);
|
|
1819
1662
|
if (!context.claudeSessionId || scenario === "fresh") return null;
|
|
1820
1663
|
const parts = [];
|
|
@@ -1862,7 +1705,7 @@ Address the requested changes. Do NOT re-investigate the codebase from scratch o
|
|
|
1862
1705
|
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
1863
1706
|
`Review the current state of the codebase and verify everything is working correctly.`
|
|
1864
1707
|
);
|
|
1865
|
-
if (agentMode === "auto" || agentMode === "building"
|
|
1708
|
+
if (agentMode === "auto" || agentMode === "building") {
|
|
1866
1709
|
parts.push(
|
|
1867
1710
|
`If work is incomplete, continue implementing the plan. When finished, commit, push, and use mcp__conveyor__create_pull_request to open a PR.`,
|
|
1868
1711
|
`Do NOT go idle or wait for instructions \u2014 you are in auto mode.`
|
|
@@ -2068,7 +1911,7 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
2068
1911
|
}
|
|
2069
1912
|
return parts;
|
|
2070
1913
|
}
|
|
2071
|
-
function buildInstructions(mode, context, scenario, agentMode
|
|
1914
|
+
function buildInstructions(mode, context, scenario, agentMode) {
|
|
2072
1915
|
const parts = [`
|
|
2073
1916
|
## Instructions`];
|
|
2074
1917
|
const isPm = mode === "pm";
|
|
@@ -2100,7 +1943,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2100
1943
|
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
2101
1944
|
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`
|
|
2102
1945
|
);
|
|
2103
|
-
if (agentMode === "auto" || agentMode === "building"
|
|
1946
|
+
if (agentMode === "auto" || agentMode === "building") {
|
|
2104
1947
|
parts.push(
|
|
2105
1948
|
`If work is incomplete, continue implementing the plan. When finished, commit, push, and use mcp__conveyor__create_pull_request to open a PR.`,
|
|
2106
1949
|
`Do NOT go idle or wait for instructions \u2014 you are in auto mode.`
|
|
@@ -2123,13 +1966,13 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2123
1966
|
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
2124
1967
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
2125
1968
|
if (!isPackRunner) {
|
|
2126
|
-
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode
|
|
1969
|
+
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode);
|
|
2127
1970
|
if (sessionRelaunch) return sessionRelaunch;
|
|
2128
1971
|
}
|
|
2129
1972
|
const isPm = mode === "pm";
|
|
2130
1973
|
const scenario = detectRelaunchScenario(context, isPm);
|
|
2131
1974
|
const body = await buildTaskBody(context);
|
|
2132
|
-
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode
|
|
1975
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
2133
1976
|
return [...body, ...instructions].join("\n");
|
|
2134
1977
|
}
|
|
2135
1978
|
|
|
@@ -2391,14 +2234,11 @@ function buildCreatePullRequestTool(connection) {
|
|
|
2391
2234
|
"Create a GitHub pull request for this task. Use this instead of gh CLI or git commands to create PRs.",
|
|
2392
2235
|
{
|
|
2393
2236
|
title: z.string().describe("The PR title"),
|
|
2394
|
-
body: z.string().describe("The PR description/body in markdown")
|
|
2395
|
-
branch: z.string().optional().describe(
|
|
2396
|
-
"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."
|
|
2397
|
-
)
|
|
2237
|
+
body: z.string().describe("The PR description/body in markdown")
|
|
2398
2238
|
},
|
|
2399
|
-
async ({ title, body
|
|
2239
|
+
async ({ title, body }) => {
|
|
2400
2240
|
try {
|
|
2401
|
-
const result = await connection.createPR({ title, body
|
|
2241
|
+
const result = await connection.createPR({ title, body });
|
|
2402
2242
|
connection.sendEvent({
|
|
2403
2243
|
type: "pr_created",
|
|
2404
2244
|
url: result.url,
|
|
@@ -2886,9 +2726,7 @@ async function handleAskUserQuestion(host, input) {
|
|
|
2886
2726
|
}
|
|
2887
2727
|
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2888
2728
|
}
|
|
2889
|
-
var DENIAL_WARNING_THRESHOLD = 3;
|
|
2890
2729
|
function buildCanUseTool(host) {
|
|
2891
|
-
let consecutiveDenials = 0;
|
|
2892
2730
|
return async (toolName, input) => {
|
|
2893
2731
|
if (toolName === "ExitPlanMode" && (host.agentMode === "auto" || host.agentMode === "discovery") && !host.hasExitedPlanMode) {
|
|
2894
2732
|
return await handleExitPlanMode(host, input);
|
|
@@ -2896,39 +2734,23 @@ function buildCanUseTool(host) {
|
|
|
2896
2734
|
if (toolName === "AskUserQuestion") {
|
|
2897
2735
|
return await handleAskUserQuestion(host, input);
|
|
2898
2736
|
}
|
|
2899
|
-
let result;
|
|
2900
2737
|
switch (host.agentMode) {
|
|
2901
2738
|
case "discovery":
|
|
2902
|
-
|
|
2903
|
-
break;
|
|
2739
|
+
return handleDiscoveryToolAccess(toolName, input);
|
|
2904
2740
|
case "building":
|
|
2905
|
-
|
|
2906
|
-
break;
|
|
2741
|
+
return handleBuildingToolAccess(toolName, input);
|
|
2907
2742
|
case "review":
|
|
2908
|
-
|
|
2909
|
-
break;
|
|
2743
|
+
return handleReviewToolAccess(toolName, input, host.isParentTask);
|
|
2910
2744
|
case "auto":
|
|
2911
|
-
|
|
2912
|
-
break;
|
|
2745
|
+
return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
2913
2746
|
default:
|
|
2914
|
-
|
|
2915
|
-
}
|
|
2916
|
-
if (result.behavior === "deny") {
|
|
2917
|
-
consecutiveDenials++;
|
|
2918
|
-
if (consecutiveDenials === DENIAL_WARNING_THRESHOLD) {
|
|
2919
|
-
host.connection.postChatMessage(
|
|
2920
|
-
`\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.`
|
|
2921
|
-
);
|
|
2922
|
-
}
|
|
2923
|
-
} else {
|
|
2924
|
-
consecutiveDenials = 0;
|
|
2747
|
+
return { behavior: "allow", updatedInput: input };
|
|
2925
2748
|
}
|
|
2926
|
-
return result;
|
|
2927
2749
|
};
|
|
2928
2750
|
}
|
|
2929
2751
|
|
|
2930
2752
|
// src/execution/query-executor.ts
|
|
2931
|
-
var
|
|
2753
|
+
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
2932
2754
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
2933
2755
|
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2934
2756
|
function buildHooks(host) {
|
|
@@ -3145,29 +2967,6 @@ async function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
|
3145
2967
|
options: { ...options, resume: void 0 }
|
|
3146
2968
|
});
|
|
3147
2969
|
}
|
|
3148
|
-
async function handleAuthError(context, host, options) {
|
|
3149
|
-
host.connection.postChatMessage("Authentication expired. Re-bootstrapping credentials...");
|
|
3150
|
-
const refreshed = await host.connection.refreshAuthToken();
|
|
3151
|
-
if (!refreshed) {
|
|
3152
|
-
host.connection.postChatMessage("Failed to refresh authentication. Agent will restart.");
|
|
3153
|
-
host.connection.sendEvent({
|
|
3154
|
-
type: "error",
|
|
3155
|
-
message: "Auth re-bootstrap failed, exiting for restart"
|
|
3156
|
-
});
|
|
3157
|
-
process.exit(1);
|
|
3158
|
-
}
|
|
3159
|
-
context.claudeSessionId = null;
|
|
3160
|
-
host.connection.storeSessionId("");
|
|
3161
|
-
const freshPrompt = buildMultimodalPrompt(
|
|
3162
|
-
await buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
3163
|
-
context
|
|
3164
|
-
);
|
|
3165
|
-
const freshQuery = query({
|
|
3166
|
-
prompt: host.createInputStream(freshPrompt),
|
|
3167
|
-
options: { ...options, resume: void 0 }
|
|
3168
|
-
});
|
|
3169
|
-
return runWithRetry(freshQuery, context, host, options);
|
|
3170
|
-
}
|
|
3171
2970
|
async function handleStaleSession(context, host, options) {
|
|
3172
2971
|
context.claudeSessionId = null;
|
|
3173
2972
|
host.connection.storeSessionId("");
|
|
@@ -3199,17 +2998,12 @@ function isStaleOrExitedSession(error, context) {
|
|
|
3199
2998
|
if (error.message.includes("No conversation found with session ID")) return true;
|
|
3200
2999
|
return !!context.claudeSessionId && error.message.includes("process exited");
|
|
3201
3000
|
}
|
|
3202
|
-
function getErrorMessage(error) {
|
|
3203
|
-
if (error instanceof Error) return error.message;
|
|
3204
|
-
if (typeof error === "string") return error;
|
|
3205
|
-
return String(error);
|
|
3206
|
-
}
|
|
3207
3001
|
function isRetriableError(error) {
|
|
3208
|
-
|
|
3209
|
-
return
|
|
3002
|
+
if (!(error instanceof Error)) return false;
|
|
3003
|
+
return API_ERROR_PATTERN2.test(error.message) || IMAGE_ERROR_PATTERN2.test(error.message);
|
|
3210
3004
|
}
|
|
3211
3005
|
function classifyImageError(error) {
|
|
3212
|
-
return IMAGE_ERROR_PATTERN2.test(
|
|
3006
|
+
return error instanceof Error && IMAGE_ERROR_PATTERN2.test(error.message);
|
|
3213
3007
|
}
|
|
3214
3008
|
async function emitRetryStatus(host, attempt, delayMs) {
|
|
3215
3009
|
const delayMin = Math.round(delayMs / 6e4);
|
|
@@ -3236,41 +3030,26 @@ function handleRetryError(error, context, host, options, prevImageError) {
|
|
|
3236
3030
|
if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
|
|
3237
3031
|
return handleStaleSession(context, host, options);
|
|
3238
3032
|
}
|
|
3239
|
-
if (isAuthError(getErrorMessage(error))) {
|
|
3240
|
-
return handleAuthError(context, host, options);
|
|
3241
|
-
}
|
|
3242
3033
|
if (!isRetriableError(error)) throw error;
|
|
3243
3034
|
return { action: "continue", lastErrorWasImage: classifyImageError(error) || prevImageError };
|
|
3244
3035
|
}
|
|
3245
|
-
function handleProcessResult(result, context, host, options) {
|
|
3246
|
-
if (result.modeRestart || host.isStopped()) return { action: "return" };
|
|
3247
|
-
if (result.rateLimitResetsAt) {
|
|
3248
|
-
handleRateLimitPause(host, result.rateLimitResetsAt);
|
|
3249
|
-
return { action: "return" };
|
|
3250
|
-
}
|
|
3251
|
-
if (result.staleSession && context.claudeSessionId) {
|
|
3252
|
-
return { action: "return_promise", promise: handleStaleSession(context, host, options) };
|
|
3253
|
-
}
|
|
3254
|
-
if (result.authError) {
|
|
3255
|
-
return { action: "return_promise", promise: handleAuthError(context, host, options) };
|
|
3256
|
-
}
|
|
3257
|
-
if (!result.retriable) return { action: "return" };
|
|
3258
|
-
return {
|
|
3259
|
-
action: "continue",
|
|
3260
|
-
lastErrorWasImage: IMAGE_ERROR_PATTERN2.test(result.resultSummary ?? "")
|
|
3261
|
-
};
|
|
3262
|
-
}
|
|
3263
3036
|
async function runWithRetry(initialQuery, context, host, options) {
|
|
3264
3037
|
let lastErrorWasImage = false;
|
|
3265
3038
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
3266
3039
|
if (host.isStopped()) return;
|
|
3267
3040
|
const agentQuery = attempt === 0 ? initialQuery : await buildRetryQuery(host, context, options, lastErrorWasImage);
|
|
3268
3041
|
try {
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
if (
|
|
3272
|
-
|
|
3273
|
-
|
|
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 ?? "");
|
|
3274
3053
|
} catch (error) {
|
|
3275
3054
|
const outcome = handleRetryError(error, context, host, options, lastErrorWasImage);
|
|
3276
3055
|
if (outcome instanceof Promise) return outcome;
|
|
@@ -4100,118 +3879,13 @@ var AgentRunner = class {
|
|
|
4100
3879
|
|
|
4101
3880
|
// src/runner/project-runner.ts
|
|
4102
3881
|
import { fork } from "child_process";
|
|
4103
|
-
import { execSync as
|
|
3882
|
+
import { execSync as execSync5 } from "child_process";
|
|
4104
3883
|
import * as path from "path";
|
|
4105
3884
|
import { fileURLToPath } from "url";
|
|
4106
3885
|
|
|
4107
|
-
// src/runner/commit-watcher.ts
|
|
4108
|
-
import { execSync as execSync5 } from "child_process";
|
|
4109
|
-
var logger3 = createServiceLogger("CommitWatcher");
|
|
4110
|
-
var CommitWatcher = class {
|
|
4111
|
-
constructor(config, callbacks) {
|
|
4112
|
-
this.config = config;
|
|
4113
|
-
this.callbacks = callbacks;
|
|
4114
|
-
}
|
|
4115
|
-
interval = null;
|
|
4116
|
-
lastKnownRemoteSha = null;
|
|
4117
|
-
branch = null;
|
|
4118
|
-
debounceTimer = null;
|
|
4119
|
-
isSyncing = false;
|
|
4120
|
-
start(branch) {
|
|
4121
|
-
this.stop();
|
|
4122
|
-
this.branch = branch;
|
|
4123
|
-
this.lastKnownRemoteSha = this.getLocalHeadSha();
|
|
4124
|
-
this.interval = setInterval(() => void this.poll(), this.config.pollIntervalMs);
|
|
4125
|
-
logger3.info("Commit watcher started", {
|
|
4126
|
-
branch,
|
|
4127
|
-
baseSha: this.lastKnownRemoteSha?.slice(0, 8)
|
|
4128
|
-
});
|
|
4129
|
-
}
|
|
4130
|
-
stop() {
|
|
4131
|
-
if (this.interval) clearInterval(this.interval);
|
|
4132
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
4133
|
-
this.interval = null;
|
|
4134
|
-
this.debounceTimer = null;
|
|
4135
|
-
this.branch = null;
|
|
4136
|
-
this.lastKnownRemoteSha = null;
|
|
4137
|
-
this.isSyncing = false;
|
|
4138
|
-
}
|
|
4139
|
-
getLocalHeadSha() {
|
|
4140
|
-
return execSync5("git rev-parse HEAD", {
|
|
4141
|
-
cwd: this.config.projectDir,
|
|
4142
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4143
|
-
}).toString().trim();
|
|
4144
|
-
}
|
|
4145
|
-
poll() {
|
|
4146
|
-
if (!this.branch || this.isSyncing) return;
|
|
4147
|
-
try {
|
|
4148
|
-
execSync5(`git fetch origin ${this.branch} --quiet`, {
|
|
4149
|
-
cwd: this.config.projectDir,
|
|
4150
|
-
stdio: "ignore",
|
|
4151
|
-
timeout: 3e4
|
|
4152
|
-
});
|
|
4153
|
-
const remoteSha = execSync5(`git rev-parse origin/${this.branch}`, {
|
|
4154
|
-
cwd: this.config.projectDir,
|
|
4155
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4156
|
-
}).toString().trim();
|
|
4157
|
-
if (remoteSha !== this.lastKnownRemoteSha) {
|
|
4158
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
4159
|
-
this.debounceTimer = setTimeout(
|
|
4160
|
-
() => void this.handleNewCommits(remoteSha),
|
|
4161
|
-
this.config.debounceMs
|
|
4162
|
-
);
|
|
4163
|
-
}
|
|
4164
|
-
} catch {
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
async handleNewCommits(remoteSha) {
|
|
4168
|
-
if (!this.branch) return;
|
|
4169
|
-
const previousSha = this.lastKnownRemoteSha ?? "HEAD";
|
|
4170
|
-
let commitCount = 1;
|
|
4171
|
-
let latestMessage = "";
|
|
4172
|
-
let latestAuthor = "";
|
|
4173
|
-
try {
|
|
4174
|
-
const countOutput = execSync5(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
|
|
4175
|
-
cwd: this.config.projectDir,
|
|
4176
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4177
|
-
}).toString().trim();
|
|
4178
|
-
commitCount = parseInt(countOutput, 10) || 1;
|
|
4179
|
-
const logOutput = execSync5(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
|
|
4180
|
-
cwd: this.config.projectDir,
|
|
4181
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4182
|
-
}).toString().trim();
|
|
4183
|
-
const parts = logOutput.split("|||");
|
|
4184
|
-
latestMessage = parts[0] ?? "";
|
|
4185
|
-
latestAuthor = parts[1] ?? "";
|
|
4186
|
-
} catch {
|
|
4187
|
-
}
|
|
4188
|
-
this.lastKnownRemoteSha = remoteSha;
|
|
4189
|
-
this.isSyncing = true;
|
|
4190
|
-
logger3.info("New commits detected", {
|
|
4191
|
-
branch: this.branch,
|
|
4192
|
-
commitCount,
|
|
4193
|
-
sha: remoteSha.slice(0, 8)
|
|
4194
|
-
});
|
|
4195
|
-
try {
|
|
4196
|
-
await this.callbacks.onNewCommits({
|
|
4197
|
-
branch: this.branch,
|
|
4198
|
-
previousSha,
|
|
4199
|
-
newCommitSha: remoteSha,
|
|
4200
|
-
commitCount,
|
|
4201
|
-
latestMessage,
|
|
4202
|
-
latestAuthor
|
|
4203
|
-
});
|
|
4204
|
-
} catch (err) {
|
|
4205
|
-
logger3.error("Error handling new commits", errorMeta(err));
|
|
4206
|
-
} finally {
|
|
4207
|
-
this.isSyncing = false;
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
};
|
|
4211
|
-
|
|
4212
3886
|
// src/runner/project-chat-handler.ts
|
|
4213
3887
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
4214
|
-
var
|
|
3888
|
+
var logger3 = createServiceLogger("ProjectChat");
|
|
4215
3889
|
var FALLBACK_MODEL = "claude-sonnet-4-20250514";
|
|
4216
3890
|
function buildSystemPrompt2(projectDir, agentCtx) {
|
|
4217
3891
|
const parts = [];
|
|
@@ -4264,7 +3938,7 @@ function processContentBlock(block, responseParts, turnToolCalls) {
|
|
|
4264
3938
|
input: inputStr.slice(0, 1e4),
|
|
4265
3939
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4266
3940
|
});
|
|
4267
|
-
|
|
3941
|
+
logger3.debug("Tool use", { tool: block.name });
|
|
4268
3942
|
}
|
|
4269
3943
|
}
|
|
4270
3944
|
async function fetchContext(connection) {
|
|
@@ -4272,13 +3946,13 @@ async function fetchContext(connection) {
|
|
|
4272
3946
|
try {
|
|
4273
3947
|
agentCtx = await connection.fetchAgentContext();
|
|
4274
3948
|
} catch {
|
|
4275
|
-
|
|
3949
|
+
logger3.warn("Could not fetch agent context, using defaults");
|
|
4276
3950
|
}
|
|
4277
3951
|
let chatHistory = [];
|
|
4278
3952
|
try {
|
|
4279
3953
|
chatHistory = await connection.fetchChatHistory(30);
|
|
4280
3954
|
} catch {
|
|
4281
|
-
|
|
3955
|
+
logger3.warn("Could not fetch chat history, proceeding without it");
|
|
4282
3956
|
}
|
|
4283
3957
|
return { agentCtx, chatHistory };
|
|
4284
3958
|
}
|
|
@@ -4302,41 +3976,6 @@ function buildChatQueryOptions(agentCtx, projectDir) {
|
|
|
4302
3976
|
thinking: settings.thinking
|
|
4303
3977
|
};
|
|
4304
3978
|
}
|
|
4305
|
-
function emitResultCostAndContext(event, connection) {
|
|
4306
|
-
const resultEvent = event;
|
|
4307
|
-
if (resultEvent.total_cost_usd !== void 0 && resultEvent.total_cost_usd > 0) {
|
|
4308
|
-
connection.emitEvent({
|
|
4309
|
-
type: "cost_update",
|
|
4310
|
-
costUsd: resultEvent.total_cost_usd
|
|
4311
|
-
});
|
|
4312
|
-
}
|
|
4313
|
-
if (resultEvent.modelUsage && typeof resultEvent.modelUsage === "object") {
|
|
4314
|
-
const modelUsage = resultEvent.modelUsage;
|
|
4315
|
-
let contextWindow = 0;
|
|
4316
|
-
let totalInputTokens = 0;
|
|
4317
|
-
let totalCacheRead = 0;
|
|
4318
|
-
let totalCacheCreation = 0;
|
|
4319
|
-
for (const data of Object.values(modelUsage)) {
|
|
4320
|
-
const d = data;
|
|
4321
|
-
totalInputTokens += d.inputTokens ?? 0;
|
|
4322
|
-
totalCacheRead += d.cacheReadInputTokens ?? 0;
|
|
4323
|
-
totalCacheCreation += d.cacheCreationInputTokens ?? 0;
|
|
4324
|
-
const cw = d.contextWindow ?? 0;
|
|
4325
|
-
if (cw > contextWindow) contextWindow = cw;
|
|
4326
|
-
}
|
|
4327
|
-
if (contextWindow > 0) {
|
|
4328
|
-
const queryInputTokens = totalInputTokens + totalCacheRead + totalCacheCreation;
|
|
4329
|
-
connection.emitEvent({
|
|
4330
|
-
type: "context_update",
|
|
4331
|
-
contextTokens: queryInputTokens,
|
|
4332
|
-
contextWindow,
|
|
4333
|
-
inputTokens: totalInputTokens,
|
|
4334
|
-
cacheReadInputTokens: totalCacheRead,
|
|
4335
|
-
cacheCreationInputTokens: totalCacheCreation
|
|
4336
|
-
});
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
3979
|
function processEventStream(event, connection, responseParts, turnToolCalls, isTyping) {
|
|
4341
3980
|
if (event.type === "assistant") {
|
|
4342
3981
|
if (!isTyping.value) {
|
|
@@ -4358,7 +3997,6 @@ function processEventStream(event, connection, responseParts, turnToolCalls, isT
|
|
|
4358
3997
|
connection.emitEvent({ type: "agent_typing_stop" });
|
|
4359
3998
|
isTyping.value = false;
|
|
4360
3999
|
}
|
|
4361
|
-
emitResultCostAndContext(event, connection);
|
|
4362
4000
|
return true;
|
|
4363
4001
|
}
|
|
4364
4002
|
return false;
|
|
@@ -4367,7 +4005,6 @@ async function runChatQuery(message, connection, projectDir) {
|
|
|
4367
4005
|
const { agentCtx, chatHistory } = await fetchContext(connection);
|
|
4368
4006
|
const options = buildChatQueryOptions(agentCtx, projectDir);
|
|
4369
4007
|
const prompt = buildPrompt(message, chatHistory);
|
|
4370
|
-
connection.emitAgentStatus("running");
|
|
4371
4008
|
const events = query2({ prompt, options });
|
|
4372
4009
|
const responseParts = [];
|
|
4373
4010
|
const turnToolCalls = [];
|
|
@@ -4385,12 +4022,11 @@ async function runChatQuery(message, connection, projectDir) {
|
|
|
4385
4022
|
}
|
|
4386
4023
|
}
|
|
4387
4024
|
async function handleProjectChatMessage(message, connection, projectDir) {
|
|
4388
|
-
connection.emitAgentStatus("
|
|
4025
|
+
connection.emitAgentStatus("busy");
|
|
4389
4026
|
try {
|
|
4390
4027
|
await runChatQuery(message, connection, projectDir);
|
|
4391
4028
|
} catch (error) {
|
|
4392
|
-
|
|
4393
|
-
connection.emitAgentStatus("error");
|
|
4029
|
+
logger3.error("Failed to handle message", errorMeta(error));
|
|
4394
4030
|
try {
|
|
4395
4031
|
await connection.emitChatMessage(
|
|
4396
4032
|
"I encountered an error processing your message. Please try again."
|
|
@@ -4402,395 +4038,8 @@ async function handleProjectChatMessage(message, connection, projectDir) {
|
|
|
4402
4038
|
}
|
|
4403
4039
|
}
|
|
4404
4040
|
|
|
4405
|
-
// src/runner/project-audit-handler.ts
|
|
4406
|
-
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
4407
|
-
|
|
4408
|
-
// src/tools/audit-tools.ts
|
|
4409
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
4410
|
-
import { tool as tool4, createSdkMcpServer as createSdkMcpServer2 } from "@anthropic-ai/claude-agent-sdk";
|
|
4411
|
-
import { z as z4 } from "zod";
|
|
4412
|
-
function mapCreateTag(input) {
|
|
4413
|
-
return {
|
|
4414
|
-
type: "create_tag",
|
|
4415
|
-
tagName: input.name,
|
|
4416
|
-
suggestion: `Create new tag "${input.name}"${input.description ? `: ${input.description}` : ""}`,
|
|
4417
|
-
reasoning: input.reasoning,
|
|
4418
|
-
payload: { name: input.name, color: input.color ?? "#6B7280", description: input.description }
|
|
4419
|
-
};
|
|
4420
|
-
}
|
|
4421
|
-
function mapUpdateDescription(input) {
|
|
4422
|
-
return {
|
|
4423
|
-
type: "update_description",
|
|
4424
|
-
tagId: input.tagId,
|
|
4425
|
-
tagName: input.tagName,
|
|
4426
|
-
suggestion: `Update description for "${input.tagName}"`,
|
|
4427
|
-
reasoning: input.reasoning,
|
|
4428
|
-
payload: { description: input.description }
|
|
4429
|
-
};
|
|
4430
|
-
}
|
|
4431
|
-
function mapContextLink(input) {
|
|
4432
|
-
return {
|
|
4433
|
-
type: "add_context_link",
|
|
4434
|
-
tagId: input.tagId,
|
|
4435
|
-
tagName: input.tagName,
|
|
4436
|
-
suggestion: `Link ${input.linkType}:${input.path} to "${input.tagName}"`,
|
|
4437
|
-
reasoning: input.reasoning,
|
|
4438
|
-
payload: { contextLink: { type: input.linkType, path: input.path, label: input.label } }
|
|
4439
|
-
};
|
|
4440
|
-
}
|
|
4441
|
-
function mapDocGap(input) {
|
|
4442
|
-
return {
|
|
4443
|
-
type: "documentation_gap",
|
|
4444
|
-
tagId: input.tagId,
|
|
4445
|
-
tagName: input.tagName,
|
|
4446
|
-
suggestion: `Documentation gap: ${input.filePath} (${input.readCount} reads)`,
|
|
4447
|
-
reasoning: input.reasoning,
|
|
4448
|
-
payload: {
|
|
4449
|
-
filePath: input.filePath,
|
|
4450
|
-
readCount: input.readCount,
|
|
4451
|
-
suggestedAction: input.suggestedAction
|
|
4452
|
-
}
|
|
4453
|
-
};
|
|
4454
|
-
}
|
|
4455
|
-
function mapMergeTags(input) {
|
|
4456
|
-
return {
|
|
4457
|
-
type: "merge_tags",
|
|
4458
|
-
tagId: input.tagId,
|
|
4459
|
-
tagName: input.tagName,
|
|
4460
|
-
suggestion: `Merge "${input.tagName}" into "${input.mergeIntoTagName}"`,
|
|
4461
|
-
reasoning: input.reasoning,
|
|
4462
|
-
payload: { mergeIntoTagId: input.mergeIntoTagId }
|
|
4463
|
-
};
|
|
4464
|
-
}
|
|
4465
|
-
function mapRenameTag(input) {
|
|
4466
|
-
return {
|
|
4467
|
-
type: "rename_tag",
|
|
4468
|
-
tagId: input.tagId,
|
|
4469
|
-
tagName: input.tagName,
|
|
4470
|
-
suggestion: `Rename "${input.tagName}" to "${input.newName}"`,
|
|
4471
|
-
reasoning: input.reasoning,
|
|
4472
|
-
payload: { newName: input.newName }
|
|
4473
|
-
};
|
|
4474
|
-
}
|
|
4475
|
-
var TOOL_MAPPERS = {
|
|
4476
|
-
recommend_create_tag: mapCreateTag,
|
|
4477
|
-
recommend_update_description: mapUpdateDescription,
|
|
4478
|
-
recommend_context_link: mapContextLink,
|
|
4479
|
-
flag_documentation_gap: mapDocGap,
|
|
4480
|
-
recommend_merge_tags: mapMergeTags,
|
|
4481
|
-
recommend_rename_tag: mapRenameTag
|
|
4482
|
-
};
|
|
4483
|
-
function collectRecommendation(toolName, input, collector, onRecommendation) {
|
|
4484
|
-
const mapper = TOOL_MAPPERS[toolName];
|
|
4485
|
-
if (!mapper) return JSON.stringify({ error: `Unknown tool: ${toolName}` });
|
|
4486
|
-
const rec = { id: randomUUID3(), ...mapper(input) };
|
|
4487
|
-
collector.recommendations.push(rec);
|
|
4488
|
-
onRecommendation?.({ tagName: rec.tagName ?? rec.type, type: rec.type });
|
|
4489
|
-
return JSON.stringify({ success: true, recommendationId: rec.id });
|
|
4490
|
-
}
|
|
4491
|
-
function createAuditMcpServer(collector, onRecommendation) {
|
|
4492
|
-
const auditTools = [
|
|
4493
|
-
tool4(
|
|
4494
|
-
"recommend_create_tag",
|
|
4495
|
-
"Recommend creating a new tag for an uncovered subsystem or area",
|
|
4496
|
-
{
|
|
4497
|
-
name: z4.string().describe("Proposed tag name (lowercase, hyphenated)"),
|
|
4498
|
-
color: z4.string().optional().describe("Hex color code"),
|
|
4499
|
-
description: z4.string().describe("What this tag covers"),
|
|
4500
|
-
reasoning: z4.string().describe("Why this tag should be created")
|
|
4501
|
-
},
|
|
4502
|
-
async (args) => {
|
|
4503
|
-
const result = collectRecommendation(
|
|
4504
|
-
"recommend_create_tag",
|
|
4505
|
-
args,
|
|
4506
|
-
collector,
|
|
4507
|
-
onRecommendation
|
|
4508
|
-
);
|
|
4509
|
-
return { content: [{ type: "text", text: result }] };
|
|
4510
|
-
}
|
|
4511
|
-
),
|
|
4512
|
-
tool4(
|
|
4513
|
-
"recommend_update_description",
|
|
4514
|
-
"Recommend updating a tag's description to better reflect its scope",
|
|
4515
|
-
{
|
|
4516
|
-
tagId: z4.string(),
|
|
4517
|
-
tagName: z4.string(),
|
|
4518
|
-
description: z4.string().describe("Proposed new description"),
|
|
4519
|
-
reasoning: z4.string()
|
|
4520
|
-
},
|
|
4521
|
-
async (args) => {
|
|
4522
|
-
const result = collectRecommendation(
|
|
4523
|
-
"recommend_update_description",
|
|
4524
|
-
args,
|
|
4525
|
-
collector,
|
|
4526
|
-
onRecommendation
|
|
4527
|
-
);
|
|
4528
|
-
return { content: [{ type: "text", text: result }] };
|
|
4529
|
-
}
|
|
4530
|
-
),
|
|
4531
|
-
tool4(
|
|
4532
|
-
"recommend_context_link",
|
|
4533
|
-
"Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
|
|
4534
|
-
{
|
|
4535
|
-
tagId: z4.string(),
|
|
4536
|
-
tagName: z4.string(),
|
|
4537
|
-
linkType: z4.enum(["rule", "doc", "file", "folder"]),
|
|
4538
|
-
path: z4.string(),
|
|
4539
|
-
label: z4.string().optional(),
|
|
4540
|
-
reasoning: z4.string()
|
|
4541
|
-
},
|
|
4542
|
-
async (args) => {
|
|
4543
|
-
const result = collectRecommendation(
|
|
4544
|
-
"recommend_context_link",
|
|
4545
|
-
args,
|
|
4546
|
-
collector,
|
|
4547
|
-
onRecommendation
|
|
4548
|
-
);
|
|
4549
|
-
return { content: [{ type: "text", text: result }] };
|
|
4550
|
-
}
|
|
4551
|
-
),
|
|
4552
|
-
tool4(
|
|
4553
|
-
"flag_documentation_gap",
|
|
4554
|
-
"Flag a file that agents read heavily but has no tag documentation linked",
|
|
4555
|
-
{
|
|
4556
|
-
tagName: z4.string().describe("Tag whose agents read this file"),
|
|
4557
|
-
tagId: z4.string().optional(),
|
|
4558
|
-
filePath: z4.string(),
|
|
4559
|
-
readCount: z4.number(),
|
|
4560
|
-
suggestedAction: z4.string().describe("What doc or rule should be created"),
|
|
4561
|
-
reasoning: z4.string()
|
|
4562
|
-
},
|
|
4563
|
-
async (args) => {
|
|
4564
|
-
const result = collectRecommendation(
|
|
4565
|
-
"flag_documentation_gap",
|
|
4566
|
-
args,
|
|
4567
|
-
collector,
|
|
4568
|
-
onRecommendation
|
|
4569
|
-
);
|
|
4570
|
-
return { content: [{ type: "text", text: result }] };
|
|
4571
|
-
}
|
|
4572
|
-
),
|
|
4573
|
-
tool4(
|
|
4574
|
-
"recommend_merge_tags",
|
|
4575
|
-
"Recommend merging one tag into another",
|
|
4576
|
-
{
|
|
4577
|
-
tagId: z4.string().describe("Tag ID to be merged (removed after merge)"),
|
|
4578
|
-
tagName: z4.string().describe("Name of the tag to be merged"),
|
|
4579
|
-
mergeIntoTagId: z4.string().describe("Tag ID to merge into (kept)"),
|
|
4580
|
-
mergeIntoTagName: z4.string(),
|
|
4581
|
-
reasoning: z4.string()
|
|
4582
|
-
},
|
|
4583
|
-
async (args) => {
|
|
4584
|
-
const result = collectRecommendation(
|
|
4585
|
-
"recommend_merge_tags",
|
|
4586
|
-
args,
|
|
4587
|
-
collector,
|
|
4588
|
-
onRecommendation
|
|
4589
|
-
);
|
|
4590
|
-
return { content: [{ type: "text", text: result }] };
|
|
4591
|
-
}
|
|
4592
|
-
),
|
|
4593
|
-
tool4(
|
|
4594
|
-
"recommend_rename_tag",
|
|
4595
|
-
"Recommend renaming a tag",
|
|
4596
|
-
{
|
|
4597
|
-
tagId: z4.string(),
|
|
4598
|
-
tagName: z4.string().describe("Current tag name"),
|
|
4599
|
-
newName: z4.string().describe("Proposed new name"),
|
|
4600
|
-
reasoning: z4.string()
|
|
4601
|
-
},
|
|
4602
|
-
async (args) => {
|
|
4603
|
-
const result = collectRecommendation(
|
|
4604
|
-
"recommend_rename_tag",
|
|
4605
|
-
args,
|
|
4606
|
-
collector,
|
|
4607
|
-
onRecommendation
|
|
4608
|
-
);
|
|
4609
|
-
return { content: [{ type: "text", text: result }] };
|
|
4610
|
-
}
|
|
4611
|
-
),
|
|
4612
|
-
tool4(
|
|
4613
|
-
"complete_audit",
|
|
4614
|
-
"Signal that the audit is complete with a summary of all findings",
|
|
4615
|
-
{ summary: z4.string().describe("Brief overview of all findings") },
|
|
4616
|
-
async (args) => {
|
|
4617
|
-
collector.complete = true;
|
|
4618
|
-
collector.summary = args.summary ?? "Audit completed.";
|
|
4619
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true }) }] };
|
|
4620
|
-
}
|
|
4621
|
-
)
|
|
4622
|
-
];
|
|
4623
|
-
return createSdkMcpServer2({
|
|
4624
|
-
name: "tag-audit",
|
|
4625
|
-
tools: auditTools
|
|
4626
|
-
});
|
|
4627
|
-
}
|
|
4628
|
-
|
|
4629
|
-
// src/runner/project-audit-handler.ts
|
|
4630
|
-
var logger5 = createServiceLogger("ProjectAudit");
|
|
4631
|
-
var FALLBACK_MODEL2 = "claude-sonnet-4-20250514";
|
|
4632
|
-
function buildTagSection(tags) {
|
|
4633
|
-
if (tags.length === 0) return "No tags configured yet.";
|
|
4634
|
-
return tags.map((t) => {
|
|
4635
|
-
const paths = t.contextPaths ?? [];
|
|
4636
|
-
const pathStr = paths.length > 0 ? `
|
|
4637
|
-
Context links: ${paths.map((p) => `${p.type}:${p.path}`).join(", ")}` : "";
|
|
4638
|
-
return ` - ${t.name} (id: ${t.id})${t.description ? `: ${t.description}` : " [no description]"}${pathStr}
|
|
4639
|
-
Active tasks: ${t.activeTaskCount}`;
|
|
4640
|
-
}).join("\n");
|
|
4641
|
-
}
|
|
4642
|
-
function buildHeatmapSection(entries) {
|
|
4643
|
-
if (entries.length === 0) return "No file read analytics data available.";
|
|
4644
|
-
return entries.slice(0, 50).map((e) => {
|
|
4645
|
-
const tagBreakdown = Object.entries(e.byTag).sort(([, a], [, b]) => b - a).map(([tag, count]) => `${tag}:${count}`).join(", ");
|
|
4646
|
-
return ` ${e.filePath} \u2014 ${e.totalReads} reads${tagBreakdown ? ` (${tagBreakdown})` : ""}`;
|
|
4647
|
-
}).join("\n");
|
|
4648
|
-
}
|
|
4649
|
-
function buildAuditSystemPrompt(projectName, tags, heatmapData, projectDir) {
|
|
4650
|
-
return [
|
|
4651
|
-
"You are a project organization expert analyzing tag taxonomy for a software project.",
|
|
4652
|
-
"Tags are used to categorize tasks and link relevant documentation/rules/files to subsystems.",
|
|
4653
|
-
"",
|
|
4654
|
-
`PROJECT: ${projectName}`,
|
|
4655
|
-
"",
|
|
4656
|
-
`EXISTING TAGS (${tags.length}):`,
|
|
4657
|
-
buildTagSection(tags),
|
|
4658
|
-
"",
|
|
4659
|
-
"FILE READ ANALYTICS (what agents actually read, by tag):",
|
|
4660
|
-
buildHeatmapSection(heatmapData),
|
|
4661
|
-
"",
|
|
4662
|
-
`You have full access to the codebase at: ${projectDir}`,
|
|
4663
|
-
"Use your file reading and searching tools to understand the codebase structure,",
|
|
4664
|
-
"module boundaries, and architectural patterns before making recommendations.",
|
|
4665
|
-
"",
|
|
4666
|
-
"ANALYSIS TASKS:",
|
|
4667
|
-
"1. Read actual source files to understand code areas and module boundaries",
|
|
4668
|
-
"2. Search for imports, class definitions, and architectural patterns",
|
|
4669
|
-
"3. Coverage: Are all major subsystems/services represented by tags?",
|
|
4670
|
-
"4. Descriptions: Do all tags have clear, useful descriptions?",
|
|
4671
|
-
"5. Context Links: Are relevant rules/docs/folders linked to tags via contextPaths?",
|
|
4672
|
-
"6. Documentation Gaps: Which high-read files lack linked documentation?",
|
|
4673
|
-
"7. Cleanup: Any tags that should be merged, renamed, or removed?",
|
|
4674
|
-
"",
|
|
4675
|
-
"Use the tag-audit MCP tools to submit each recommendation.",
|
|
4676
|
-
"Call complete_audit when you are done with a thorough summary.",
|
|
4677
|
-
"Be comprehensive \u2014 recommend all improvements your analysis supports.",
|
|
4678
|
-
"Analyze actual file contents, not just file names."
|
|
4679
|
-
].join("\n");
|
|
4680
|
-
}
|
|
4681
|
-
function emitToolCallProgress(event, request, connection) {
|
|
4682
|
-
if (event.type !== "assistant") return;
|
|
4683
|
-
const assistantEvent = event;
|
|
4684
|
-
for (const block of assistantEvent.message.content) {
|
|
4685
|
-
if (block.type === "tool_use" && block.name) {
|
|
4686
|
-
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
4687
|
-
connection.emitAuditProgress({
|
|
4688
|
-
requestId: request.requestId,
|
|
4689
|
-
activity: {
|
|
4690
|
-
tool: block.name,
|
|
4691
|
-
input: inputStr.slice(0, 500),
|
|
4692
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4693
|
-
}
|
|
4694
|
-
});
|
|
4695
|
-
}
|
|
4696
|
-
}
|
|
4697
|
-
}
|
|
4698
|
-
async function runAuditQuery(request, connection, projectDir) {
|
|
4699
|
-
connection.emitAgentStatus("fetching_context");
|
|
4700
|
-
let agentCtx = null;
|
|
4701
|
-
try {
|
|
4702
|
-
agentCtx = await connection.fetchAgentContext();
|
|
4703
|
-
} catch {
|
|
4704
|
-
logger5.warn("Could not fetch agent context for audit, using defaults");
|
|
4705
|
-
}
|
|
4706
|
-
connection.emitAgentStatus("running");
|
|
4707
|
-
const model = agentCtx?.model || FALLBACK_MODEL2;
|
|
4708
|
-
const settings = agentCtx?.agentSettings ?? {};
|
|
4709
|
-
const collector = {
|
|
4710
|
-
recommendations: [],
|
|
4711
|
-
summary: "Audit completed.",
|
|
4712
|
-
complete: false
|
|
4713
|
-
};
|
|
4714
|
-
const onRecommendation = (rec) => {
|
|
4715
|
-
connection.emitEvent({
|
|
4716
|
-
type: "audit_recommendation",
|
|
4717
|
-
tagName: rec.tagName,
|
|
4718
|
-
recommendationType: rec.type
|
|
4719
|
-
});
|
|
4720
|
-
};
|
|
4721
|
-
const systemPrompt = buildAuditSystemPrompt(
|
|
4722
|
-
request.projectName,
|
|
4723
|
-
request.tags,
|
|
4724
|
-
request.fileHeatmap,
|
|
4725
|
-
projectDir
|
|
4726
|
-
);
|
|
4727
|
-
const userPrompt = [
|
|
4728
|
-
"Analyze the project's tag taxonomy and submit recommendations using the tag-audit MCP tools.",
|
|
4729
|
-
`There are currently ${request.tags.length} tags configured.`,
|
|
4730
|
-
request.fileHeatmap.length > 0 ? `File analytics show ${request.fileHeatmap.length} files with read activity.` : "No file read analytics available.",
|
|
4731
|
-
"",
|
|
4732
|
-
"Start by exploring the codebase structure, then analyze each tag for accuracy and completeness.",
|
|
4733
|
-
"Call complete_audit when done."
|
|
4734
|
-
].join("\n");
|
|
4735
|
-
const events = query3({
|
|
4736
|
-
prompt: userPrompt,
|
|
4737
|
-
options: {
|
|
4738
|
-
model,
|
|
4739
|
-
systemPrompt: { type: "preset", preset: "claude_code", append: systemPrompt },
|
|
4740
|
-
cwd: projectDir,
|
|
4741
|
-
permissionMode: "bypassPermissions",
|
|
4742
|
-
allowDangerouslySkipPermissions: true,
|
|
4743
|
-
tools: { type: "preset", preset: "claude_code" },
|
|
4744
|
-
mcpServers: { "tag-audit": createAuditMcpServer(collector, onRecommendation) },
|
|
4745
|
-
maxTurns: settings.maxTurns ?? 75,
|
|
4746
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
4747
|
-
effort: settings.effort,
|
|
4748
|
-
thinking: settings.thinking
|
|
4749
|
-
}
|
|
4750
|
-
});
|
|
4751
|
-
const responseParts = [];
|
|
4752
|
-
const turnToolCalls = [];
|
|
4753
|
-
const isTyping = { value: false };
|
|
4754
|
-
for await (const event of events) {
|
|
4755
|
-
emitToolCallProgress(event, request, connection);
|
|
4756
|
-
const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
|
|
4757
|
-
if (done) break;
|
|
4758
|
-
}
|
|
4759
|
-
if (isTyping.value) {
|
|
4760
|
-
connection.emitEvent({ type: "agent_typing_stop" });
|
|
4761
|
-
}
|
|
4762
|
-
return collector;
|
|
4763
|
-
}
|
|
4764
|
-
async function handleProjectAuditRequest(request, connection, projectDir) {
|
|
4765
|
-
connection.emitAgentStatus("running");
|
|
4766
|
-
try {
|
|
4767
|
-
const collector = await runAuditQuery(request, connection, projectDir);
|
|
4768
|
-
const result = {
|
|
4769
|
-
recommendations: collector.recommendations,
|
|
4770
|
-
summary: collector.summary,
|
|
4771
|
-
analyzedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4772
|
-
};
|
|
4773
|
-
logger5.info("Tag audit completed", {
|
|
4774
|
-
requestId: request.requestId,
|
|
4775
|
-
recommendationCount: result.recommendations.length
|
|
4776
|
-
});
|
|
4777
|
-
connection.emitAuditResult({ requestId: request.requestId, result });
|
|
4778
|
-
} catch (error) {
|
|
4779
|
-
logger5.error("Tag audit failed", {
|
|
4780
|
-
requestId: request.requestId,
|
|
4781
|
-
...errorMeta(error)
|
|
4782
|
-
});
|
|
4783
|
-
connection.emitAuditResult({
|
|
4784
|
-
requestId: request.requestId,
|
|
4785
|
-
error: error instanceof Error ? error.message : "Tag audit failed"
|
|
4786
|
-
});
|
|
4787
|
-
} finally {
|
|
4788
|
-
connection.emitAgentStatus("idle");
|
|
4789
|
-
}
|
|
4790
|
-
}
|
|
4791
|
-
|
|
4792
4041
|
// src/runner/project-runner.ts
|
|
4793
|
-
var
|
|
4042
|
+
var logger4 = createServiceLogger("ProjectRunner");
|
|
4794
4043
|
var __filename = fileURLToPath(import.meta.url);
|
|
4795
4044
|
var __dirname = path.dirname(__filename);
|
|
4796
4045
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
@@ -4809,12 +4058,12 @@ function setupWorkDir(projectDir, assignment) {
|
|
|
4809
4058
|
}
|
|
4810
4059
|
if (branch && branch !== devBranch) {
|
|
4811
4060
|
try {
|
|
4812
|
-
|
|
4061
|
+
execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
4813
4062
|
} catch {
|
|
4814
4063
|
try {
|
|
4815
|
-
|
|
4064
|
+
execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
4816
4065
|
} catch {
|
|
4817
|
-
|
|
4066
|
+
logger4.warn("Could not checkout branch", { taskId: shortId, branch });
|
|
4818
4067
|
}
|
|
4819
4068
|
}
|
|
4820
4069
|
}
|
|
@@ -4853,13 +4102,13 @@ function spawnChildAgent(assignment, workDir) {
|
|
|
4853
4102
|
child.stdout?.on("data", (data) => {
|
|
4854
4103
|
const lines = data.toString().trimEnd().split("\n");
|
|
4855
4104
|
for (const line of lines) {
|
|
4856
|
-
|
|
4105
|
+
logger4.info(line, { taskId: shortId });
|
|
4857
4106
|
}
|
|
4858
4107
|
});
|
|
4859
4108
|
child.stderr?.on("data", (data) => {
|
|
4860
4109
|
const lines = data.toString().trimEnd().split("\n");
|
|
4861
4110
|
for (const line of lines) {
|
|
4862
|
-
|
|
4111
|
+
logger4.error(line, { taskId: shortId });
|
|
4863
4112
|
}
|
|
4864
4113
|
});
|
|
4865
4114
|
return child;
|
|
@@ -4875,55 +4124,23 @@ var ProjectRunner = class {
|
|
|
4875
4124
|
startCommandChild = null;
|
|
4876
4125
|
startCommandRunning = false;
|
|
4877
4126
|
setupComplete = false;
|
|
4878
|
-
branchSwitchCommand;
|
|
4879
|
-
commitWatcher;
|
|
4880
4127
|
constructor(config) {
|
|
4881
4128
|
this.projectDir = config.projectDir;
|
|
4882
4129
|
this.connection = new ProjectConnection({
|
|
4883
4130
|
apiUrl: config.conveyorApiUrl,
|
|
4884
4131
|
projectToken: config.projectToken,
|
|
4885
|
-
projectId: config.projectId
|
|
4886
|
-
projectDir: config.projectDir
|
|
4132
|
+
projectId: config.projectId
|
|
4887
4133
|
});
|
|
4888
|
-
this.commitWatcher = new CommitWatcher(
|
|
4889
|
-
{
|
|
4890
|
-
projectDir: this.projectDir,
|
|
4891
|
-
pollIntervalMs: Number(process.env.CONVEYOR_COMMIT_POLL_INTERVAL) || 1e4,
|
|
4892
|
-
debounceMs: 3e3
|
|
4893
|
-
},
|
|
4894
|
-
{
|
|
4895
|
-
onNewCommits: async (data) => {
|
|
4896
|
-
this.connection.emitNewCommitsDetected({
|
|
4897
|
-
branch: data.branch,
|
|
4898
|
-
commitCount: data.commitCount,
|
|
4899
|
-
latestCommit: {
|
|
4900
|
-
sha: data.newCommitSha,
|
|
4901
|
-
message: data.latestMessage,
|
|
4902
|
-
author: data.latestAuthor
|
|
4903
|
-
},
|
|
4904
|
-
autoSyncing: true
|
|
4905
|
-
});
|
|
4906
|
-
const startTime = Date.now();
|
|
4907
|
-
const stepsRun = await this.smartSync(data.previousSha, data.newCommitSha, data.branch);
|
|
4908
|
-
this.connection.emitEnvironmentReady({
|
|
4909
|
-
branch: data.branch,
|
|
4910
|
-
commitsSynced: data.commitCount,
|
|
4911
|
-
syncDurationMs: Date.now() - startTime,
|
|
4912
|
-
stepsRun
|
|
4913
|
-
});
|
|
4914
|
-
}
|
|
4915
|
-
}
|
|
4916
|
-
);
|
|
4917
4134
|
}
|
|
4918
4135
|
checkoutWorkspaceBranch() {
|
|
4919
4136
|
const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
|
|
4920
4137
|
if (!workspaceBranch) return;
|
|
4921
4138
|
try {
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
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 });
|
|
4925
4142
|
} catch (err) {
|
|
4926
|
-
|
|
4143
|
+
logger4.warn("Failed to checkout workspace branch, continuing on current branch", {
|
|
4927
4144
|
workspaceBranch,
|
|
4928
4145
|
...errorMeta(err)
|
|
4929
4146
|
});
|
|
@@ -4932,15 +4149,15 @@ var ProjectRunner = class {
|
|
|
4932
4149
|
async executeSetupCommand() {
|
|
4933
4150
|
const cmd = process.env.CONVEYOR_SETUP_COMMAND;
|
|
4934
4151
|
if (!cmd) return;
|
|
4935
|
-
|
|
4152
|
+
logger4.info("Running setup command", { command: cmd });
|
|
4936
4153
|
try {
|
|
4937
4154
|
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
4938
4155
|
this.connection.emitEvent({ type: "setup_output", stream, data });
|
|
4939
4156
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
4940
4157
|
});
|
|
4941
|
-
|
|
4158
|
+
logger4.info("Setup command completed");
|
|
4942
4159
|
} catch (error) {
|
|
4943
|
-
|
|
4160
|
+
logger4.error("Setup command failed", errorMeta(error));
|
|
4944
4161
|
this.connection.emitEvent({
|
|
4945
4162
|
type: "setup_error",
|
|
4946
4163
|
message: error instanceof Error ? error.message : "Setup command failed"
|
|
@@ -4951,7 +4168,7 @@ var ProjectRunner = class {
|
|
|
4951
4168
|
executeStartCommand() {
|
|
4952
4169
|
const cmd = process.env.CONVEYOR_START_COMMAND;
|
|
4953
4170
|
if (!cmd) return;
|
|
4954
|
-
|
|
4171
|
+
logger4.info("Running start command", { command: cmd });
|
|
4955
4172
|
const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
|
|
4956
4173
|
this.connection.emitEvent({ type: "start_command_output", stream, data });
|
|
4957
4174
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
@@ -4961,7 +4178,7 @@ var ProjectRunner = class {
|
|
|
4961
4178
|
child.on("exit", (code, signal) => {
|
|
4962
4179
|
this.startCommandRunning = false;
|
|
4963
4180
|
this.startCommandChild = null;
|
|
4964
|
-
|
|
4181
|
+
logger4.info("Start command exited", { code, signal });
|
|
4965
4182
|
this.connection.emitEvent({
|
|
4966
4183
|
type: "start_command_exited",
|
|
4967
4184
|
code,
|
|
@@ -4972,13 +4189,13 @@ var ProjectRunner = class {
|
|
|
4972
4189
|
child.on("error", (err) => {
|
|
4973
4190
|
this.startCommandRunning = false;
|
|
4974
4191
|
this.startCommandChild = null;
|
|
4975
|
-
|
|
4192
|
+
logger4.error("Start command error", errorMeta(err));
|
|
4976
4193
|
});
|
|
4977
4194
|
}
|
|
4978
4195
|
async killStartCommand() {
|
|
4979
4196
|
const child = this.startCommandChild;
|
|
4980
4197
|
if (!child || !this.startCommandRunning) return;
|
|
4981
|
-
|
|
4198
|
+
logger4.info("Killing start command");
|
|
4982
4199
|
try {
|
|
4983
4200
|
if (child.pid) process.kill(-child.pid, "SIGTERM");
|
|
4984
4201
|
} catch {
|
|
@@ -5010,7 +4227,7 @@ var ProjectRunner = class {
|
|
|
5010
4227
|
getEnvironmentStatus() {
|
|
5011
4228
|
let currentBranch = "unknown";
|
|
5012
4229
|
try {
|
|
5013
|
-
currentBranch =
|
|
4230
|
+
currentBranch = execSync5("git branch --show-current", {
|
|
5014
4231
|
cwd: this.projectDir,
|
|
5015
4232
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5016
4233
|
}).toString().trim();
|
|
@@ -5023,180 +4240,6 @@ var ProjectRunner = class {
|
|
|
5023
4240
|
previewPort: Number(process.env.CONVEYOR_PREVIEW_PORT) || null
|
|
5024
4241
|
};
|
|
5025
4242
|
}
|
|
5026
|
-
getCurrentBranch() {
|
|
5027
|
-
try {
|
|
5028
|
-
return execSync6("git branch --show-current", {
|
|
5029
|
-
cwd: this.projectDir,
|
|
5030
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5031
|
-
}).toString().trim() || null;
|
|
5032
|
-
} catch {
|
|
5033
|
-
return null;
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
// oxlint-disable-next-line max-lines-per-function, complexity -- sequential sync steps with per-step error handling
|
|
5037
|
-
async smartSync(previousSha, newSha, branch) {
|
|
5038
|
-
const stepsRun = [];
|
|
5039
|
-
const status = execSync6("git status --porcelain", {
|
|
5040
|
-
cwd: this.projectDir,
|
|
5041
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5042
|
-
}).toString().trim();
|
|
5043
|
-
if (status) {
|
|
5044
|
-
this.connection.emitEvent({
|
|
5045
|
-
type: "commit_watch_warning",
|
|
5046
|
-
message: "Working tree has uncommitted changes. Auto-pull skipped."
|
|
5047
|
-
});
|
|
5048
|
-
return ["skipped:dirty_tree"];
|
|
5049
|
-
}
|
|
5050
|
-
await this.killStartCommand();
|
|
5051
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "running" });
|
|
5052
|
-
try {
|
|
5053
|
-
execSync6(`git pull origin ${branch}`, {
|
|
5054
|
-
cwd: this.projectDir,
|
|
5055
|
-
stdio: "pipe",
|
|
5056
|
-
timeout: 6e4
|
|
5057
|
-
});
|
|
5058
|
-
stepsRun.push("pull");
|
|
5059
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "success" });
|
|
5060
|
-
} catch (err) {
|
|
5061
|
-
const message = err instanceof Error ? err.message : "Pull failed";
|
|
5062
|
-
this.connection.emitEnvSwitchProgress({ step: "pull", status: "error", message });
|
|
5063
|
-
logger6.error("Git pull failed during commit sync", errorMeta(err));
|
|
5064
|
-
this.executeStartCommand();
|
|
5065
|
-
return ["error:pull"];
|
|
5066
|
-
}
|
|
5067
|
-
let changedFiles = [];
|
|
5068
|
-
try {
|
|
5069
|
-
changedFiles = execSync6(`git diff --name-only ${previousSha}..${newSha}`, {
|
|
5070
|
-
cwd: this.projectDir,
|
|
5071
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5072
|
-
}).toString().trim().split("\n").filter(Boolean);
|
|
5073
|
-
} catch {
|
|
5074
|
-
}
|
|
5075
|
-
const needsInstall = changedFiles.some(
|
|
5076
|
-
(f) => f === "package.json" || f === "bun.lockb" || f === "bunfig.toml" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
|
|
5077
|
-
);
|
|
5078
|
-
const needsPrisma = changedFiles.some(
|
|
5079
|
-
(f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
|
|
5080
|
-
);
|
|
5081
|
-
const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5082
|
-
if (cmd && (needsInstall || needsPrisma)) {
|
|
5083
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "running" });
|
|
5084
|
-
try {
|
|
5085
|
-
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
5086
|
-
this.connection.emitEvent({ type: "sync_output", stream, data });
|
|
5087
|
-
});
|
|
5088
|
-
stepsRun.push("branchSwitchCommand");
|
|
5089
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "success" });
|
|
5090
|
-
} catch (err) {
|
|
5091
|
-
const message = err instanceof Error ? err.message : "Sync command failed";
|
|
5092
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "error", message });
|
|
5093
|
-
logger6.error("Branch switch command failed during commit sync", errorMeta(err));
|
|
5094
|
-
}
|
|
5095
|
-
} else if (!cmd) {
|
|
5096
|
-
if (needsInstall) {
|
|
5097
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "running" });
|
|
5098
|
-
try {
|
|
5099
|
-
execSync6("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
|
|
5100
|
-
stepsRun.push("install");
|
|
5101
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "success" });
|
|
5102
|
-
} catch (err) {
|
|
5103
|
-
const message = err instanceof Error ? err.message : "Install failed";
|
|
5104
|
-
this.connection.emitEnvSwitchProgress({ step: "install", status: "error", message });
|
|
5105
|
-
logger6.error("bun install failed during commit sync", errorMeta(err));
|
|
5106
|
-
}
|
|
5107
|
-
}
|
|
5108
|
-
if (needsPrisma) {
|
|
5109
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "running" });
|
|
5110
|
-
try {
|
|
5111
|
-
execSync6("bunx prisma generate", {
|
|
5112
|
-
cwd: this.projectDir,
|
|
5113
|
-
timeout: 6e4,
|
|
5114
|
-
stdio: "pipe"
|
|
5115
|
-
});
|
|
5116
|
-
execSync6("bunx prisma db push --accept-data-loss", {
|
|
5117
|
-
cwd: this.projectDir,
|
|
5118
|
-
timeout: 6e4,
|
|
5119
|
-
stdio: "pipe"
|
|
5120
|
-
});
|
|
5121
|
-
stepsRun.push("prisma");
|
|
5122
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "success" });
|
|
5123
|
-
} catch (err) {
|
|
5124
|
-
const message = err instanceof Error ? err.message : "Prisma sync failed";
|
|
5125
|
-
this.connection.emitEnvSwitchProgress({ step: "prisma", status: "error", message });
|
|
5126
|
-
logger6.error("Prisma sync failed during commit sync", errorMeta(err));
|
|
5127
|
-
}
|
|
5128
|
-
}
|
|
5129
|
-
}
|
|
5130
|
-
this.executeStartCommand();
|
|
5131
|
-
stepsRun.push("startCommand");
|
|
5132
|
-
return stepsRun;
|
|
5133
|
-
}
|
|
5134
|
-
async handleSwitchBranch(data, callback) {
|
|
5135
|
-
const { branch, syncAfter } = data;
|
|
5136
|
-
try {
|
|
5137
|
-
this.connection.emitEnvSwitchProgress({ step: "fetch", status: "running" });
|
|
5138
|
-
try {
|
|
5139
|
-
execSync6("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
|
|
5140
|
-
} catch {
|
|
5141
|
-
logger6.warn("Git fetch failed during branch switch");
|
|
5142
|
-
}
|
|
5143
|
-
this.connection.emitEnvSwitchProgress({ step: "fetch", status: "success" });
|
|
5144
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "running" });
|
|
5145
|
-
try {
|
|
5146
|
-
execSync6(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
5147
|
-
} catch (err) {
|
|
5148
|
-
const message = err instanceof Error ? err.message : "Checkout failed";
|
|
5149
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "error", message });
|
|
5150
|
-
callback({ ok: false, error: `Failed to checkout branch: ${message}` });
|
|
5151
|
-
return;
|
|
5152
|
-
}
|
|
5153
|
-
try {
|
|
5154
|
-
execSync6(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
|
|
5155
|
-
} catch {
|
|
5156
|
-
logger6.warn("Git pull failed during branch switch", { branch });
|
|
5157
|
-
}
|
|
5158
|
-
this.connection.emitEnvSwitchProgress({ step: "checkout", status: "success" });
|
|
5159
|
-
if (syncAfter !== false) {
|
|
5160
|
-
await this.handleSyncEnvironment();
|
|
5161
|
-
}
|
|
5162
|
-
this.commitWatcher.start(branch);
|
|
5163
|
-
callback({ ok: true, data: this.getEnvironmentStatus() });
|
|
5164
|
-
} catch (err) {
|
|
5165
|
-
const message = err instanceof Error ? err.message : "Branch switch failed";
|
|
5166
|
-
logger6.error("Branch switch failed", errorMeta(err));
|
|
5167
|
-
callback({ ok: false, error: message });
|
|
5168
|
-
}
|
|
5169
|
-
}
|
|
5170
|
-
async handleSyncEnvironment(callback) {
|
|
5171
|
-
try {
|
|
5172
|
-
await this.killStartCommand();
|
|
5173
|
-
const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5174
|
-
if (cmd) {
|
|
5175
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "running" });
|
|
5176
|
-
try {
|
|
5177
|
-
await runSetupCommand(cmd, this.projectDir, (stream, data) => {
|
|
5178
|
-
this.connection.emitEvent({ type: "sync_output", stream, data });
|
|
5179
|
-
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
5180
|
-
});
|
|
5181
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "success" });
|
|
5182
|
-
} catch (err) {
|
|
5183
|
-
const message = err instanceof Error ? err.message : "Sync command failed";
|
|
5184
|
-
this.connection.emitEnvSwitchProgress({ step: "sync", status: "error", message });
|
|
5185
|
-
logger6.error("Branch switch sync command failed", errorMeta(err));
|
|
5186
|
-
}
|
|
5187
|
-
}
|
|
5188
|
-
this.executeStartCommand();
|
|
5189
|
-
this.connection.emitEnvSwitchProgress({ step: "startCommand", status: "success" });
|
|
5190
|
-
callback?.({ ok: true, data: this.getEnvironmentStatus() });
|
|
5191
|
-
} catch (err) {
|
|
5192
|
-
const message = err instanceof Error ? err.message : "Sync failed";
|
|
5193
|
-
logger6.error("Environment sync failed", errorMeta(err));
|
|
5194
|
-
callback?.({ ok: false, error: message });
|
|
5195
|
-
}
|
|
5196
|
-
}
|
|
5197
|
-
handleGetEnvStatus(callback) {
|
|
5198
|
-
callback({ ok: true, data: this.getEnvironmentStatus() });
|
|
5199
|
-
}
|
|
5200
4243
|
async start() {
|
|
5201
4244
|
this.checkoutWorkspaceBranch();
|
|
5202
4245
|
await this.connection.connect();
|
|
@@ -5210,7 +4253,7 @@ var ProjectRunner = class {
|
|
|
5210
4253
|
startCommandRunning: this.startCommandRunning
|
|
5211
4254
|
});
|
|
5212
4255
|
} catch (error) {
|
|
5213
|
-
|
|
4256
|
+
logger4.error("Environment setup failed", errorMeta(error));
|
|
5214
4257
|
this.setupComplete = false;
|
|
5215
4258
|
}
|
|
5216
4259
|
this.connection.onTaskAssignment((assignment) => {
|
|
@@ -5220,45 +4263,17 @@ var ProjectRunner = class {
|
|
|
5220
4263
|
this.handleStopTask(data.taskId);
|
|
5221
4264
|
});
|
|
5222
4265
|
this.connection.onShutdown(() => {
|
|
5223
|
-
|
|
4266
|
+
logger4.info("Received shutdown signal from server");
|
|
5224
4267
|
void this.stop();
|
|
5225
4268
|
});
|
|
5226
4269
|
this.connection.onChatMessage((msg) => {
|
|
5227
|
-
|
|
4270
|
+
logger4.debug("Received project chat message");
|
|
5228
4271
|
void handleProjectChatMessage(msg, this.connection, this.projectDir);
|
|
5229
4272
|
});
|
|
5230
|
-
this.connection.onAuditRequest((request) => {
|
|
5231
|
-
logger6.debug("Received tag audit request", { requestId: request.requestId });
|
|
5232
|
-
void handleProjectAuditRequest(request, this.connection, this.projectDir);
|
|
5233
|
-
});
|
|
5234
|
-
this.connection.onSwitchBranch = (data, cb) => {
|
|
5235
|
-
void this.handleSwitchBranch(data, cb);
|
|
5236
|
-
};
|
|
5237
|
-
this.connection.onSyncEnvironment = (cb) => {
|
|
5238
|
-
void this.handleSyncEnvironment(cb);
|
|
5239
|
-
};
|
|
5240
|
-
this.connection.onGetEnvStatus = (cb) => {
|
|
5241
|
-
this.handleGetEnvStatus(cb);
|
|
5242
|
-
};
|
|
5243
|
-
this.connection.onRestartStartCommand = (cb) => {
|
|
5244
|
-
void this.restartStartCommand().then(() => cb({ ok: true })).catch(
|
|
5245
|
-
(err) => cb({ ok: false, error: err instanceof Error ? err.message : "Restart failed" })
|
|
5246
|
-
);
|
|
5247
|
-
};
|
|
5248
|
-
try {
|
|
5249
|
-
const context = await this.connection.fetchAgentContext();
|
|
5250
|
-
this.branchSwitchCommand = context?.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5251
|
-
} catch {
|
|
5252
|
-
this.branchSwitchCommand = process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
|
|
5253
|
-
}
|
|
5254
4273
|
this.heartbeatTimer = setInterval(() => {
|
|
5255
4274
|
this.connection.sendHeartbeat();
|
|
5256
4275
|
}, HEARTBEAT_INTERVAL_MS2);
|
|
5257
|
-
|
|
5258
|
-
if (currentBranch) {
|
|
5259
|
-
this.commitWatcher.start(currentBranch);
|
|
5260
|
-
}
|
|
5261
|
-
logger6.info("Connected, waiting for task assignments");
|
|
4276
|
+
logger4.info("Connected, waiting for task assignments");
|
|
5262
4277
|
await new Promise((resolve2) => {
|
|
5263
4278
|
this.resolveLifecycle = resolve2;
|
|
5264
4279
|
process.on("SIGTERM", () => void this.stop());
|
|
@@ -5269,11 +4284,11 @@ var ProjectRunner = class {
|
|
|
5269
4284
|
const { taskId, mode } = assignment;
|
|
5270
4285
|
const shortId = taskId.slice(0, 8);
|
|
5271
4286
|
if (this.activeAgents.has(taskId)) {
|
|
5272
|
-
|
|
4287
|
+
logger4.info("Task already running, skipping", { taskId: shortId });
|
|
5273
4288
|
return;
|
|
5274
4289
|
}
|
|
5275
4290
|
if (this.activeAgents.size >= MAX_CONCURRENT) {
|
|
5276
|
-
|
|
4291
|
+
logger4.warn("Max concurrent agents reached, rejecting task", {
|
|
5277
4292
|
maxConcurrent: MAX_CONCURRENT,
|
|
5278
4293
|
taskId: shortId
|
|
5279
4294
|
});
|
|
@@ -5282,9 +4297,9 @@ var ProjectRunner = class {
|
|
|
5282
4297
|
}
|
|
5283
4298
|
try {
|
|
5284
4299
|
try {
|
|
5285
|
-
|
|
4300
|
+
execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
|
|
5286
4301
|
} catch {
|
|
5287
|
-
|
|
4302
|
+
logger4.warn("Git fetch failed", { taskId: shortId });
|
|
5288
4303
|
}
|
|
5289
4304
|
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
5290
4305
|
const child = spawnChildAgent(assignment, workDir);
|
|
@@ -5295,12 +4310,12 @@ var ProjectRunner = class {
|
|
|
5295
4310
|
usesWorktree
|
|
5296
4311
|
});
|
|
5297
4312
|
this.connection.emitTaskStarted(taskId);
|
|
5298
|
-
|
|
4313
|
+
logger4.info("Started task", { taskId: shortId, mode, workDir });
|
|
5299
4314
|
child.on("exit", (code) => {
|
|
5300
4315
|
this.activeAgents.delete(taskId);
|
|
5301
4316
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
5302
4317
|
this.connection.emitTaskStopped(taskId, reason);
|
|
5303
|
-
|
|
4318
|
+
logger4.info("Task exited", { taskId: shortId, reason });
|
|
5304
4319
|
if (code === 0 && usesWorktree) {
|
|
5305
4320
|
try {
|
|
5306
4321
|
removeWorktree(this.projectDir, taskId);
|
|
@@ -5309,7 +4324,7 @@ var ProjectRunner = class {
|
|
|
5309
4324
|
}
|
|
5310
4325
|
});
|
|
5311
4326
|
} catch (error) {
|
|
5312
|
-
|
|
4327
|
+
logger4.error("Failed to start task", {
|
|
5313
4328
|
taskId: shortId,
|
|
5314
4329
|
...errorMeta(error)
|
|
5315
4330
|
});
|
|
@@ -5323,7 +4338,7 @@ var ProjectRunner = class {
|
|
|
5323
4338
|
const agent = this.activeAgents.get(taskId);
|
|
5324
4339
|
if (!agent) return;
|
|
5325
4340
|
const shortId = taskId.slice(0, 8);
|
|
5326
|
-
|
|
4341
|
+
logger4.info("Stopping task", { taskId: shortId });
|
|
5327
4342
|
agent.process.kill("SIGTERM");
|
|
5328
4343
|
const timer = setTimeout(() => {
|
|
5329
4344
|
if (this.activeAgents.has(taskId)) {
|
|
@@ -5343,8 +4358,7 @@ var ProjectRunner = class {
|
|
|
5343
4358
|
async stop() {
|
|
5344
4359
|
if (this.stopping) return;
|
|
5345
4360
|
this.stopping = true;
|
|
5346
|
-
|
|
5347
|
-
this.commitWatcher.stop();
|
|
4361
|
+
logger4.info("Shutting down");
|
|
5348
4362
|
await this.killStartCommand();
|
|
5349
4363
|
if (this.heartbeatTimer) {
|
|
5350
4364
|
clearInterval(this.heartbeatTimer);
|
|
@@ -5370,7 +4384,7 @@ var ProjectRunner = class {
|
|
|
5370
4384
|
})
|
|
5371
4385
|
]);
|
|
5372
4386
|
this.connection.disconnect();
|
|
5373
|
-
|
|
4387
|
+
logger4.info("Shutdown complete");
|
|
5374
4388
|
if (this.resolveLifecycle) {
|
|
5375
4389
|
this.resolveLifecycle();
|
|
5376
4390
|
this.resolveLifecycle = null;
|
|
@@ -5456,4 +4470,4 @@ export {
|
|
|
5456
4470
|
ProjectRunner,
|
|
5457
4471
|
FileCache
|
|
5458
4472
|
};
|
|
5459
|
-
//# sourceMappingURL=chunk-
|
|
4473
|
+
//# sourceMappingURL=chunk-JFIWJVOH.js.map
|