@rallycry/conveyor-agent 6.0.4 → 6.0.6
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.
|
@@ -960,6 +960,7 @@ var RECONNECT_BASE_MS = 1e3;
|
|
|
960
960
|
var RECONNECT_MAX_MS = 3e4;
|
|
961
961
|
var TunnelClient = class _TunnelClient {
|
|
962
962
|
static STABLE_MS = 3e4;
|
|
963
|
+
static MAX_RECONNECT_ATTEMPTS = 10;
|
|
963
964
|
apiUrl;
|
|
964
965
|
token;
|
|
965
966
|
localPort;
|
|
@@ -968,6 +969,7 @@ var TunnelClient = class _TunnelClient {
|
|
|
968
969
|
reconnectAttempts = 0;
|
|
969
970
|
reconnectTimer = null;
|
|
970
971
|
connectedAt = 0;
|
|
972
|
+
loggedNoWebSocket = false;
|
|
971
973
|
constructor(apiUrl, token, localPort) {
|
|
972
974
|
this.apiUrl = apiUrl;
|
|
973
975
|
this.token = token;
|
|
@@ -975,6 +977,13 @@ var TunnelClient = class _TunnelClient {
|
|
|
975
977
|
}
|
|
976
978
|
connect() {
|
|
977
979
|
if (this.stopped) return;
|
|
980
|
+
if (typeof WebSocket === "undefined") {
|
|
981
|
+
if (!this.loggedNoWebSocket) {
|
|
982
|
+
logger2.warn("WebSocket not available in this environment, tunnel disabled");
|
|
983
|
+
this.loggedNoWebSocket = true;
|
|
984
|
+
}
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
978
987
|
const wsUrl = this.apiUrl.replace(/^http/, "ws").replace(/\/$/, "");
|
|
979
988
|
const url = `${wsUrl}/api/tunnel?token=${encodeURIComponent(this.token)}`;
|
|
980
989
|
try {
|
|
@@ -990,7 +999,8 @@ var TunnelClient = class _TunnelClient {
|
|
|
990
999
|
logger2.info("Tunnel connected", { port: this.localPort });
|
|
991
1000
|
});
|
|
992
1001
|
this.ws.addEventListener("close", (event) => {
|
|
993
|
-
|
|
1002
|
+
const closeEvent = event;
|
|
1003
|
+
logger2.info("Tunnel disconnected", { code: closeEvent.code, reason: closeEvent.reason });
|
|
994
1004
|
this.scheduleReconnect();
|
|
995
1005
|
});
|
|
996
1006
|
this.ws.addEventListener("error", (event) => {
|
|
@@ -998,7 +1008,8 @@ var TunnelClient = class _TunnelClient {
|
|
|
998
1008
|
logger2.warn("Tunnel error", { error: msg });
|
|
999
1009
|
});
|
|
1000
1010
|
this.ws.addEventListener("message", (event) => {
|
|
1001
|
-
|
|
1011
|
+
const messageEvent = event;
|
|
1012
|
+
this.handleMessage(messageEvent.data);
|
|
1002
1013
|
});
|
|
1003
1014
|
}
|
|
1004
1015
|
disconnect() {
|
|
@@ -1014,12 +1025,18 @@ var TunnelClient = class _TunnelClient {
|
|
|
1014
1025
|
}
|
|
1015
1026
|
scheduleReconnect() {
|
|
1016
1027
|
if (this.stopped) return;
|
|
1017
|
-
if (Date.now() - this.connectedAt >= _TunnelClient.STABLE_MS) {
|
|
1028
|
+
if (this.connectedAt > 0 && Date.now() - this.connectedAt >= _TunnelClient.STABLE_MS) {
|
|
1018
1029
|
this.reconnectAttempts = 0;
|
|
1019
1030
|
}
|
|
1031
|
+
if (this.reconnectAttempts >= _TunnelClient.MAX_RECONNECT_ATTEMPTS) {
|
|
1032
|
+
logger2.warn("Tunnel gave up after max reconnect attempts", {
|
|
1033
|
+
attempts: this.reconnectAttempts
|
|
1034
|
+
});
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1020
1037
|
const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_MS);
|
|
1021
1038
|
this.reconnectAttempts++;
|
|
1022
|
-
logger2.
|
|
1039
|
+
logger2.debug("Tunnel reconnecting", { delay, attempt: this.reconnectAttempts });
|
|
1023
1040
|
this.reconnectTimer = setTimeout(() => this.connect(), delay);
|
|
1024
1041
|
}
|
|
1025
1042
|
// ---------------------------------------------------------------------------
|
|
@@ -1141,7 +1158,8 @@ var TunnelClient = class _TunnelClient {
|
|
|
1141
1158
|
this.send(JSON.stringify({ type: "ws-open", id }));
|
|
1142
1159
|
});
|
|
1143
1160
|
localWs.addEventListener("message", (event) => {
|
|
1144
|
-
const
|
|
1161
|
+
const messageEvent = event;
|
|
1162
|
+
const data = typeof messageEvent.data === "string" ? messageEvent.data : String(messageEvent.data);
|
|
1145
1163
|
this.send(JSON.stringify({ type: "ws-data", id, data }));
|
|
1146
1164
|
});
|
|
1147
1165
|
localWs.addEventListener("close", () => {
|
|
@@ -1228,10 +1246,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
1228
1246
|
if (turnTextParts.length > 0) {
|
|
1229
1247
|
host.connection.postChatMessage(turnTextParts.join("\n\n"));
|
|
1230
1248
|
}
|
|
1231
|
-
if (turnToolCalls.length > 0) {
|
|
1232
|
-
host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
|
|
1233
|
-
turnToolCalls.length = 0;
|
|
1234
|
-
}
|
|
1235
1249
|
}
|
|
1236
1250
|
var API_ERROR_PATTERN = /API Error: [45]\d\d/;
|
|
1237
1251
|
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
@@ -1473,6 +1487,17 @@ var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
|
1473
1487
|
function stopTypingIfNeeded(host, isTyping) {
|
|
1474
1488
|
if (isTyping) host.connection.sendTypingStop();
|
|
1475
1489
|
}
|
|
1490
|
+
function flushPendingToolCalls(host, turnToolCalls) {
|
|
1491
|
+
if (turnToolCalls.length === 0) return;
|
|
1492
|
+
for (let i = 0; i < turnToolCalls.length; i++) {
|
|
1493
|
+
if (i < host.pendingToolOutputs.length) {
|
|
1494
|
+
turnToolCalls[i].output = host.pendingToolOutputs[i];
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
|
|
1498
|
+
turnToolCalls.length = 0;
|
|
1499
|
+
host.pendingToolOutputs.length = 0;
|
|
1500
|
+
}
|
|
1476
1501
|
async function processSystemCase(event, host, context, state) {
|
|
1477
1502
|
if (event.subtype === "init") {
|
|
1478
1503
|
const stored = await handleSystemEvent(event, host, context, state.sessionIdStored);
|
|
@@ -1509,6 +1534,7 @@ async function processResultCase(event, host, context, startTime, state) {
|
|
|
1509
1534
|
);
|
|
1510
1535
|
if (info.stoppedTyping) state.isTyping = false;
|
|
1511
1536
|
state.retriable = info.retriable;
|
|
1537
|
+
if (!info.retriable) state.sawApiError = false;
|
|
1512
1538
|
state.resultSummary = info.resultSummary;
|
|
1513
1539
|
if (info.staleSession) state.staleSession = true;
|
|
1514
1540
|
if (info.authError) state.authError = true;
|
|
@@ -1531,6 +1557,7 @@ async function processEvents(events, context, host) {
|
|
|
1531
1557
|
};
|
|
1532
1558
|
for await (const event of events) {
|
|
1533
1559
|
if (host.isStopped()) break;
|
|
1560
|
+
flushPendingToolCalls(host, state.turnToolCalls);
|
|
1534
1561
|
const now = Date.now();
|
|
1535
1562
|
if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS) {
|
|
1536
1563
|
host.connection.emitStatus("running");
|
|
@@ -1560,6 +1587,7 @@ async function processEvents(events, context, host) {
|
|
|
1560
1587
|
break;
|
|
1561
1588
|
}
|
|
1562
1589
|
}
|
|
1590
|
+
flushPendingToolCalls(host, state.turnToolCalls);
|
|
1563
1591
|
stopTypingIfNeeded(host, state.isTyping);
|
|
1564
1592
|
return {
|
|
1565
1593
|
retriable: state.retriable || state.sawApiError,
|
|
@@ -1633,7 +1661,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
1633
1661
|
``,
|
|
1634
1662
|
`4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
|
|
1635
1663
|
``,
|
|
1636
|
-
`5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with
|
|
1664
|
+
`5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
|
|
1637
1665
|
``,
|
|
1638
1666
|
`## Important Rules`,
|
|
1639
1667
|
`- Process children ONE at a time, in ordinal order.`,
|
|
@@ -2610,10 +2638,10 @@ function buildPostToChatTool(connection) {
|
|
|
2610
2638
|
}
|
|
2611
2639
|
);
|
|
2612
2640
|
}
|
|
2613
|
-
function
|
|
2641
|
+
function buildForceUpdateTaskStatusTool(connection) {
|
|
2614
2642
|
return tool(
|
|
2615
|
-
"
|
|
2616
|
-
"
|
|
2643
|
+
"force_update_task_status",
|
|
2644
|
+
"EMERGENCY ONLY: Force-override a task's Kanban status. Status transitions happen automatically (building sets InProgress, PR creation sets ReviewPR, merge sets ReviewDev). Only use this if an automatic transition failed or a task is stuck in the wrong status. Omit task_id to update the current task, or provide a child task ID.",
|
|
2617
2645
|
{
|
|
2618
2646
|
status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
|
|
2619
2647
|
task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
@@ -2822,7 +2850,6 @@ function buildCommonTools(connection, config) {
|
|
|
2822
2850
|
return [
|
|
2823
2851
|
buildReadTaskChatTool(connection),
|
|
2824
2852
|
buildPostToChatTool(connection),
|
|
2825
|
-
buildUpdateTaskStatusTool(connection),
|
|
2826
2853
|
buildGetTaskPlanTool(connection, config),
|
|
2827
2854
|
buildGetTaskTool(connection),
|
|
2828
2855
|
buildGetTaskCliTool(connection),
|
|
@@ -4370,9 +4397,10 @@ function createConveyorMcpServer(connection, config, context, agentMode, debugMa
|
|
|
4370
4397
|
const modeTools = getModeTools(effectiveMode, connection, config, context);
|
|
4371
4398
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
4372
4399
|
const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
|
|
4400
|
+
const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
|
|
4373
4401
|
return createSdkMcpServer({
|
|
4374
4402
|
name: "conveyor",
|
|
4375
|
-
tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools]
|
|
4403
|
+
tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools, ...emergencyTools]
|
|
4376
4404
|
});
|
|
4377
4405
|
}
|
|
4378
4406
|
|
|
@@ -4569,6 +4597,7 @@ function buildHooks(host) {
|
|
|
4569
4597
|
output,
|
|
4570
4598
|
isError: false
|
|
4571
4599
|
});
|
|
4600
|
+
host.pendingToolOutputs.push(output);
|
|
4572
4601
|
}
|
|
4573
4602
|
return await Promise.resolve({ continue: true });
|
|
4574
4603
|
}
|
|
@@ -4578,24 +4607,6 @@ function buildHooks(host) {
|
|
|
4578
4607
|
]
|
|
4579
4608
|
};
|
|
4580
4609
|
}
|
|
4581
|
-
function buildSandboxConfig(host) {
|
|
4582
|
-
const apiHostname = new URL(host.config.conveyorApiUrl).hostname;
|
|
4583
|
-
return {
|
|
4584
|
-
enabled: true,
|
|
4585
|
-
autoAllowBashIfSandboxed: true,
|
|
4586
|
-
allowUnsandboxedCommands: false,
|
|
4587
|
-
filesystem: {
|
|
4588
|
-
allowWrite: [`${host.config.workspaceDir}/**`],
|
|
4589
|
-
denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
|
|
4590
|
-
denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
|
|
4591
|
-
},
|
|
4592
|
-
network: {
|
|
4593
|
-
allowedDomains: [apiHostname, "api.anthropic.com"],
|
|
4594
|
-
allowManagedDomainsOnly: true,
|
|
4595
|
-
allowLocalBinding: true
|
|
4596
|
-
}
|
|
4597
|
-
};
|
|
4598
|
-
}
|
|
4599
4610
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
4600
4611
|
return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
|
|
4601
4612
|
}
|
|
@@ -4608,7 +4619,6 @@ function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
|
|
|
4608
4619
|
function buildQueryOptions(host, context) {
|
|
4609
4620
|
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
4610
4621
|
const mode = host.agentMode;
|
|
4611
|
-
const isCloud = host.config.mode === "pm";
|
|
4612
4622
|
const isReadOnly = isReadOnlyMode(mode, host.hasExitedPlanMode);
|
|
4613
4623
|
const needsCanUseTool = isReadOnly;
|
|
4614
4624
|
const systemPromptText = buildSystemPrompt(
|
|
@@ -4645,9 +4655,6 @@ function buildQueryOptions(host, context) {
|
|
|
4645
4655
|
logger3.warn("Claude Code stderr", { data: data.trimEnd() });
|
|
4646
4656
|
}
|
|
4647
4657
|
};
|
|
4648
|
-
if (isCloud && isReadOnly) {
|
|
4649
|
-
baseOptions.sandbox = buildSandboxConfig(host);
|
|
4650
|
-
}
|
|
4651
4658
|
return baseOptions;
|
|
4652
4659
|
}
|
|
4653
4660
|
function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
@@ -4952,7 +4959,6 @@ var CostTracker = class {
|
|
|
4952
4959
|
|
|
4953
4960
|
// src/runner/plan-sync.ts
|
|
4954
4961
|
import { readdirSync, statSync, readFileSync } from "fs";
|
|
4955
|
-
import { homedir } from "os";
|
|
4956
4962
|
import { join as join3 } from "path";
|
|
4957
4963
|
var PlanSync = class {
|
|
4958
4964
|
planFileSnapshot = /* @__PURE__ */ new Map();
|
|
@@ -4967,7 +4973,7 @@ var PlanSync = class {
|
|
|
4967
4973
|
this.workspaceDir = workspaceDir;
|
|
4968
4974
|
}
|
|
4969
4975
|
getPlanDirs() {
|
|
4970
|
-
return [join3(
|
|
4976
|
+
return [join3(this.workspaceDir, ".claude", "plans")];
|
|
4971
4977
|
}
|
|
4972
4978
|
snapshotPlanFiles() {
|
|
4973
4979
|
this.planFileSnapshot.clear();
|
|
@@ -5322,6 +5328,7 @@ function buildQueryHost(deps) {
|
|
|
5322
5328
|
},
|
|
5323
5329
|
sessionIds: deps.sessionIds,
|
|
5324
5330
|
activeQuery: null,
|
|
5331
|
+
pendingToolOutputs: [],
|
|
5325
5332
|
isStopped: deps.isStopped,
|
|
5326
5333
|
createInputStream: deps.createInputStream,
|
|
5327
5334
|
snapshotPlanFiles: () => deps.planSync.snapshotPlanFiles(),
|
|
@@ -7330,4 +7337,4 @@ export {
|
|
|
7330
7337
|
ProjectRunner,
|
|
7331
7338
|
FileCache
|
|
7332
7339
|
};
|
|
7333
|
-
//# sourceMappingURL=chunk-
|
|
7340
|
+
//# sourceMappingURL=chunk-RHRQJO5E.js.map
|