@rallycry/conveyor-agent 8.4.1 → 8.5.1
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-VDH55LTT.js → chunk-6B545CHM.js} +352 -40
- package/dist/chunk-6B545CHM.js.map +1 -0
- package/dist/{chunk-B4QHEMMV.js → chunk-WZCO64YR.js} +344 -72
- package/dist/chunk-WZCO64YR.js.map +1 -0
- package/dist/cli.js +23 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +53 -3
- package/dist/index.js +2 -2
- package/dist/{tag-audit-handler-SWVMCAJH.js → tag-audit-handler-3IFB7YDV.js} +2 -2
- package/dist/{task-audit-handler-CZ2WWJFO.js → task-audit-handler-U5Q52YT2.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-B4QHEMMV.js.map +0 -1
- package/dist/chunk-VDH55LTT.js.map +0 -1
- /package/dist/{tag-audit-handler-SWVMCAJH.js.map → tag-audit-handler-3IFB7YDV.js.map} +0 -0
- /package/dist/{task-audit-handler-CZ2WWJFO.js.map → task-audit-handler-U5Q52YT2.js.map} +0 -0
|
@@ -5,13 +5,14 @@ import {
|
|
|
5
5
|
textResult
|
|
6
6
|
} from "./chunk-FDWECEDJ.js";
|
|
7
7
|
import {
|
|
8
|
+
configHomePlansDir,
|
|
8
9
|
createHarness,
|
|
9
10
|
createServiceLogger,
|
|
10
11
|
defineTool,
|
|
11
12
|
ensureClaudeCredentials,
|
|
12
13
|
removeConveyorCredentials,
|
|
13
14
|
sessionTranscriptPath
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6B545CHM.js";
|
|
15
16
|
|
|
16
17
|
// src/setup/bootstrap.ts
|
|
17
18
|
var BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
@@ -133,11 +134,13 @@ function applyBootstrapToEnv(config) {
|
|
|
133
134
|
import { io } from "socket.io-client";
|
|
134
135
|
var EVENT_BATCH_MS = 500;
|
|
135
136
|
var MAX_EVENT_BUFFER = 5e3;
|
|
137
|
+
var TOKEN_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
136
138
|
var AgentConnection = class _AgentConnection {
|
|
137
139
|
socket = null;
|
|
138
140
|
config;
|
|
139
141
|
eventBuffer = [];
|
|
140
142
|
flushTimer = null;
|
|
143
|
+
tokenRefreshTimer = null;
|
|
141
144
|
lastEmittedStatus = null;
|
|
142
145
|
lastReportedStatus = null;
|
|
143
146
|
droppedEventCount = 0;
|
|
@@ -201,6 +204,7 @@ var AgentConnection = class _AgentConnection {
|
|
|
201
204
|
if (!this.config.apiUrl) {
|
|
202
205
|
return Promise.reject(new Error("Cannot connect: apiUrl is empty"));
|
|
203
206
|
}
|
|
207
|
+
this.startProactiveTokenRefresh();
|
|
204
208
|
return new Promise((resolve2, reject) => {
|
|
205
209
|
let settled = false;
|
|
206
210
|
let attempts = 0;
|
|
@@ -227,7 +231,8 @@ var AgentConnection = class _AgentConnection {
|
|
|
227
231
|
content: msg.content,
|
|
228
232
|
userId: msg.userId,
|
|
229
233
|
...msg.source && { source: msg.source },
|
|
230
|
-
...msg.files && { files: msg.files }
|
|
234
|
+
...msg.files && { files: msg.files },
|
|
235
|
+
...msg.delivery === "prefill" && { delivery: msg.delivery }
|
|
231
236
|
};
|
|
232
237
|
if (this.messageCallback) this.messageCallback(incoming);
|
|
233
238
|
else this.earlyMessages.push(incoming);
|
|
@@ -314,6 +319,7 @@ var AgentConnection = class _AgentConnection {
|
|
|
314
319
|
});
|
|
315
320
|
}
|
|
316
321
|
disconnect() {
|
|
322
|
+
this.stopProactiveTokenRefresh();
|
|
317
323
|
void this.flushEvents();
|
|
318
324
|
if (this.socket) {
|
|
319
325
|
this.socket.io.reconnection(false);
|
|
@@ -397,7 +403,32 @@ var AgentConnection = class _AgentConnection {
|
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
405
|
looksLikeAuthError(message) {
|
|
400
|
-
return /unauthor|forbid|auth|token/i.test(
|
|
406
|
+
return /unauthor|forbid|auth|token|session (?:not found|expired|invalid)|invalid session/i.test(
|
|
407
|
+
message
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
// ── Proactive task-token refresh ────────────────────────────────────────
|
|
411
|
+
//
|
|
412
|
+
// Socket.IO only re-presents the taskToken on a (re)connect handshake, and
|
|
413
|
+
// the server only re-validates the JWT then. So a token that expires while
|
|
414
|
+
// the socket stays connected goes unnoticed until the next RPC fails. Re-mint
|
|
415
|
+
// periodically from the bootstrap endpoint — refreshFromBootstrap() updates
|
|
416
|
+
// both this.config.taskToken and socket.auth.taskToken, so any later
|
|
417
|
+
// reconnect carries a fresh token. No-ops for project mode / missing
|
|
418
|
+
// codespace env, and is rate-limited to once/60s inside refreshFromBootstrap.
|
|
419
|
+
startProactiveTokenRefresh() {
|
|
420
|
+
if (this.tokenRefreshTimer) return;
|
|
421
|
+
this.tokenRefreshTimer = setInterval(() => {
|
|
422
|
+
void this.refreshTaskTokenFromBootstrap().catch(() => {
|
|
423
|
+
});
|
|
424
|
+
}, TOKEN_REFRESH_INTERVAL_MS);
|
|
425
|
+
this.tokenRefreshTimer.unref?.();
|
|
426
|
+
}
|
|
427
|
+
stopProactiveTokenRefresh() {
|
|
428
|
+
if (this.tokenRefreshTimer) {
|
|
429
|
+
clearInterval(this.tokenRefreshTimer);
|
|
430
|
+
this.tokenRefreshTimer = null;
|
|
431
|
+
}
|
|
401
432
|
}
|
|
402
433
|
drainPendingMessages(messages) {
|
|
403
434
|
for (const msg of messages) {
|
|
@@ -1316,7 +1347,7 @@ var PlanSync = class {
|
|
|
1316
1347
|
this.workspaceDir = workspaceDir;
|
|
1317
1348
|
}
|
|
1318
1349
|
getPlanDirs() {
|
|
1319
|
-
return [join(this.workspaceDir, ".claude", "plans")];
|
|
1350
|
+
return [join(this.workspaceDir, ".claude", "plans"), configHomePlansDir()];
|
|
1320
1351
|
}
|
|
1321
1352
|
snapshotPlanFiles() {
|
|
1322
1353
|
this.planFileSnapshot.clear();
|
|
@@ -1934,6 +1965,11 @@ var StopProjectBuildRequestSchema = z2.object({
|
|
|
1934
1965
|
taskId: z2.string(),
|
|
1935
1966
|
requestingUserId: z2.string().optional()
|
|
1936
1967
|
});
|
|
1968
|
+
var CreateProjectReleaseRequestSchema = z2.object({
|
|
1969
|
+
projectId: z2.string(),
|
|
1970
|
+
taskIds: z2.array(z2.string()).optional(),
|
|
1971
|
+
requestingUserId: z2.string().optional()
|
|
1972
|
+
});
|
|
1937
1973
|
var ApproveProjectMergePRRequestSchema = z2.object({
|
|
1938
1974
|
projectId: z2.string(),
|
|
1939
1975
|
childTaskId: z2.string(),
|
|
@@ -2009,6 +2045,22 @@ var GetProjectAttachmentRequestSchema = z2.object({
|
|
|
2009
2045
|
/** Max bytes of text content to return from `offset`. Server default applies. */
|
|
2010
2046
|
maxBytes: z2.number().int().positive().optional()
|
|
2011
2047
|
});
|
|
2048
|
+
var RequestProjectFileUploadRequestSchema = z2.object({
|
|
2049
|
+
projectId: z2.string(),
|
|
2050
|
+
taskId: z2.string(),
|
|
2051
|
+
fileName: z2.string().min(1).max(255),
|
|
2052
|
+
mimeType: z2.string().min(1).max(128),
|
|
2053
|
+
fileSize: z2.number().int().positive().max(MAX_FILE_SIZE_BYTES),
|
|
2054
|
+
requestingUserId: z2.string().optional()
|
|
2055
|
+
});
|
|
2056
|
+
var ConfirmProjectFileUploadRequestSchema = z2.object({
|
|
2057
|
+
projectId: z2.string(),
|
|
2058
|
+
taskId: z2.string(),
|
|
2059
|
+
fileId: z2.string(),
|
|
2060
|
+
/** When set, the attachment is also posted to the task chat with this text. */
|
|
2061
|
+
comment: z2.string().max(2e3).optional(),
|
|
2062
|
+
requestingUserId: z2.string().optional()
|
|
2063
|
+
});
|
|
2012
2064
|
var CreateProjectPullRequestRequestSchema = z2.object({
|
|
2013
2065
|
projectId: z2.string(),
|
|
2014
2066
|
taskId: z2.string(),
|
|
@@ -2033,6 +2085,16 @@ var RemoveProjectTaskReviewerRequestSchema = z2.object({
|
|
|
2033
2085
|
userId: z2.string(),
|
|
2034
2086
|
requestingUserId: z2.string().optional()
|
|
2035
2087
|
});
|
|
2088
|
+
var ListProjectManualTestsRequestSchema = z2.object({
|
|
2089
|
+
projectId: z2.string(),
|
|
2090
|
+
taskId: z2.string()
|
|
2091
|
+
});
|
|
2092
|
+
var SetProjectManualTestsRequestSchema = z2.object({
|
|
2093
|
+
projectId: z2.string(),
|
|
2094
|
+
taskId: z2.string(),
|
|
2095
|
+
items: z2.array(z2.object({ title: z2.string().min(1) })).min(1),
|
|
2096
|
+
requestingUserId: z2.string().optional()
|
|
2097
|
+
});
|
|
2036
2098
|
var StartTaskAuditRequestSchema = z3.object({
|
|
2037
2099
|
projectId: z3.string(),
|
|
2038
2100
|
taskIds: z3.array(z3.string()).min(1)
|
|
@@ -6259,7 +6321,11 @@ async function handleExitPlanMode(host, input) {
|
|
|
6259
6321
|
await host.connection.postChatMessageAwait(
|
|
6260
6322
|
"Planning complete \u2014 awaiting team approval. Icon and agent assignment will be set automatically."
|
|
6261
6323
|
);
|
|
6262
|
-
host.
|
|
6324
|
+
if (host.harnessKind === "pty") {
|
|
6325
|
+
setTimeout(() => host.requestStop(), 250);
|
|
6326
|
+
} else {
|
|
6327
|
+
host.requestStop();
|
|
6328
|
+
}
|
|
6263
6329
|
return { behavior: "allow", updatedInput: input };
|
|
6264
6330
|
}
|
|
6265
6331
|
try {
|
|
@@ -6533,9 +6599,10 @@ function repairTornSessionFile(path2) {
|
|
|
6533
6599
|
}
|
|
6534
6600
|
function resolvePromptDelivery(inputs) {
|
|
6535
6601
|
if (inputs.harnessKind !== "pty") return "submit";
|
|
6536
|
-
if (
|
|
6602
|
+
if (inputs.runnerMode === "code-review") return "submit";
|
|
6537
6603
|
if (inputs.isFollowUp || inputs.hasExistingSession) return "submit";
|
|
6538
6604
|
if (inputs.isAuto || inputs.agentMode === "auto") return "submit";
|
|
6605
|
+
if (inputs.agentMode !== "discovery" && inputs.agentMode !== "help") return "submit";
|
|
6539
6606
|
return "prefill";
|
|
6540
6607
|
}
|
|
6541
6608
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
@@ -6572,6 +6639,14 @@ function buildQueryOptions(host, context) {
|
|
|
6572
6639
|
permissionMode: needsCanUseTool ? "plan" : "bypassPermissions",
|
|
6573
6640
|
allowDangerouslySkipPermissions: !needsCanUseTool,
|
|
6574
6641
|
canUseTool: buildCanUseTool(host),
|
|
6642
|
+
// The spawned CLI never sees `systemPrompt` (an SDK-only option) — deliver
|
|
6643
|
+
// the same text via `--append-system-prompt`. PTY-only: the SDK harness
|
|
6644
|
+
// already receives it through systemPrompt.append.
|
|
6645
|
+
...host.harnessKind === "pty" && systemPromptText ? { appendSystemPrompt: systemPromptText } : {},
|
|
6646
|
+
// Auto mode pre-exit: after the ExitPlanMode hook allows the call, the
|
|
6647
|
+
// CLI's plan dialog still renders — press Enter so the autonomous agent
|
|
6648
|
+
// continues building in the same session (Conveyor validated in the hook).
|
|
6649
|
+
planDialogAutoAccept: mode === "auto" && !host.hasExitedPlanMode,
|
|
6575
6650
|
tools: { type: "preset", preset: "claude_code" },
|
|
6576
6651
|
mcpServers: {
|
|
6577
6652
|
conveyor: createConveyorMcpServer(host.harness, host.connection, host.config, context, mode)
|
|
@@ -6712,13 +6787,7 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
6712
6787
|
};
|
|
6713
6788
|
const resume = hasExistingSession ? sessionUuid : void 0;
|
|
6714
6789
|
if (followUpContent) {
|
|
6715
|
-
|
|
6716
|
-
const agentQuery = host.harness.executeQuery({
|
|
6717
|
-
prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
|
|
6718
|
-
options: { ...options },
|
|
6719
|
-
resume
|
|
6720
|
-
});
|
|
6721
|
-
await trackAndRun(host, context, options, agentQuery);
|
|
6790
|
+
await runFollowUpQuery(host, context, options, resume, followUpContent);
|
|
6722
6791
|
} else if (isDiscoveryLike && promptDelivery !== "prefill") {
|
|
6723
6792
|
return;
|
|
6724
6793
|
} else {
|
|
@@ -6728,6 +6797,42 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
6728
6797
|
await host.syncPlanFile();
|
|
6729
6798
|
}
|
|
6730
6799
|
}
|
|
6800
|
+
async function runFollowUpQuery(host, context, options, resume, followUpContent) {
|
|
6801
|
+
if (options.promptDelivery === "prefill") {
|
|
6802
|
+
await runPrefilledFollowUp(host, context, options, resume, followUpContent);
|
|
6803
|
+
return;
|
|
6804
|
+
}
|
|
6805
|
+
const prompt = await buildFollowUpPrompt(host, context, followUpContent);
|
|
6806
|
+
const agentQuery = host.harness.executeQuery({
|
|
6807
|
+
prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
|
|
6808
|
+
options: { ...options },
|
|
6809
|
+
resume
|
|
6810
|
+
});
|
|
6811
|
+
await trackAndRun(host, context, options, agentQuery);
|
|
6812
|
+
}
|
|
6813
|
+
async function runPrefilledFollowUp(host, context, options, resume, followUpContent) {
|
|
6814
|
+
const followUpText = typeof followUpContent === "string" ? followUpContent : followUpContent.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
6815
|
+
const queryOptions = { ...options };
|
|
6816
|
+
if (host.config.mode === "pm" && !resume) {
|
|
6817
|
+
const initialPrompt = await buildInitialPrompt(
|
|
6818
|
+
host.config.mode,
|
|
6819
|
+
context,
|
|
6820
|
+
host.isAuto,
|
|
6821
|
+
host.agentMode
|
|
6822
|
+
);
|
|
6823
|
+
queryOptions.appendSystemPrompt = [queryOptions.appendSystemPrompt, initialPrompt].filter(Boolean).join("\n\n").slice(0, APPEND_SYSTEM_PROMPT_MAX_CHARS);
|
|
6824
|
+
}
|
|
6825
|
+
let agentQuery = host.harness.executeQuery({
|
|
6826
|
+
prompt: followUpText,
|
|
6827
|
+
options: queryOptions,
|
|
6828
|
+
resume
|
|
6829
|
+
});
|
|
6830
|
+
agentQuery = notifyOnFirstEvent(agentQuery, async () => {
|
|
6831
|
+
host.connection.emitStatus("running");
|
|
6832
|
+
await host.callbacks.onStatusChange("running");
|
|
6833
|
+
});
|
|
6834
|
+
await trackAndRun(host, context, queryOptions, agentQuery);
|
|
6835
|
+
}
|
|
6731
6836
|
async function trackAndRun(host, context, options, agentQuery) {
|
|
6732
6837
|
if (host.harnessKind === "pty" && options.promptDelivery !== "prefill") {
|
|
6733
6838
|
agentQuery = watchForParkedTui(agentQuery, host);
|
|
@@ -6739,6 +6844,14 @@ async function trackAndRun(host, context, options, agentQuery) {
|
|
|
6739
6844
|
host.activeQuery = null;
|
|
6740
6845
|
}
|
|
6741
6846
|
}
|
|
6847
|
+
var APPEND_SYSTEM_PROMPT_MAX_CHARS = 96e3;
|
|
6848
|
+
function latestUserMessageText(context) {
|
|
6849
|
+
for (let i = context.chatHistory.length - 1; i >= 0; i--) {
|
|
6850
|
+
const msg = context.chatHistory[i];
|
|
6851
|
+
if (msg.role === "user" && msg.content.trim()) return msg.content.trim();
|
|
6852
|
+
}
|
|
6853
|
+
return null;
|
|
6854
|
+
}
|
|
6742
6855
|
async function runInitialQuery(host, context, options, resume, promptDelivery) {
|
|
6743
6856
|
const initialPrompt = await buildInitialPrompt(
|
|
6744
6857
|
host.config.mode,
|
|
@@ -6746,10 +6859,18 @@ async function runInitialQuery(host, context, options, resume, promptDelivery) {
|
|
|
6746
6859
|
host.isAuto,
|
|
6747
6860
|
host.agentMode
|
|
6748
6861
|
);
|
|
6749
|
-
|
|
6862
|
+
let prompt;
|
|
6863
|
+
const queryOptions = { ...options };
|
|
6864
|
+
const prefillMessage = promptDelivery === "prefill" ? latestUserMessageText(context) : null;
|
|
6865
|
+
if (prefillMessage) {
|
|
6866
|
+
prompt = prefillMessage;
|
|
6867
|
+
queryOptions.appendSystemPrompt = [queryOptions.appendSystemPrompt, initialPrompt].filter(Boolean).join("\n\n").slice(0, APPEND_SYSTEM_PROMPT_MAX_CHARS);
|
|
6868
|
+
} else {
|
|
6869
|
+
prompt = buildMultimodalPrompt(initialPrompt, context);
|
|
6870
|
+
}
|
|
6750
6871
|
let agentQuery = host.harness.executeQuery({
|
|
6751
6872
|
prompt: host.createInputStream(prompt),
|
|
6752
|
-
options:
|
|
6873
|
+
options: queryOptions,
|
|
6753
6874
|
resume
|
|
6754
6875
|
});
|
|
6755
6876
|
if (promptDelivery === "prefill") {
|
|
@@ -6758,7 +6879,7 @@ async function runInitialQuery(host, context, options, resume, promptDelivery) {
|
|
|
6758
6879
|
await host.callbacks.onStatusChange("running");
|
|
6759
6880
|
});
|
|
6760
6881
|
}
|
|
6761
|
-
await trackAndRun(host, context,
|
|
6882
|
+
await trackAndRun(host, context, queryOptions, agentQuery);
|
|
6762
6883
|
}
|
|
6763
6884
|
async function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
6764
6885
|
if (lastErrorWasImage) {
|
|
@@ -7111,6 +7232,7 @@ var QueryBridge = class {
|
|
|
7111
7232
|
runnerConfig;
|
|
7112
7233
|
callbacks;
|
|
7113
7234
|
harness;
|
|
7235
|
+
/** Which harness drives this bridge ("pty" task chat vs "sdk" rollback). */
|
|
7114
7236
|
harnessKind;
|
|
7115
7237
|
costTracker;
|
|
7116
7238
|
planSync;
|
|
@@ -7516,10 +7638,13 @@ var SessionRunner = class _SessionRunner {
|
|
|
7516
7638
|
this.queryBridge = this.createQueryBridge();
|
|
7517
7639
|
await this.seedCostTrackerFromServer();
|
|
7518
7640
|
this.logInitialization();
|
|
7519
|
-
const
|
|
7641
|
+
const staleBatch = [...this.pendingMessages];
|
|
7520
7642
|
const didExecuteInitialQuery = await this.executeInitialMode();
|
|
7521
|
-
if (
|
|
7522
|
-
|
|
7643
|
+
if (staleBatch.length > 0 && didExecuteInitialQuery) {
|
|
7644
|
+
for (const stale of staleBatch) {
|
|
7645
|
+
const idx = this.pendingMessages.indexOf(stale);
|
|
7646
|
+
if (idx !== -1) this.pendingMessages.splice(idx, 1);
|
|
7647
|
+
}
|
|
7523
7648
|
}
|
|
7524
7649
|
if (this.queryBridge?.isDiscoveryCompleted) {
|
|
7525
7650
|
process.stderr.write(
|
|
@@ -7567,14 +7692,18 @@ var SessionRunner = class _SessionRunner {
|
|
|
7567
7692
|
}
|
|
7568
7693
|
break;
|
|
7569
7694
|
}
|
|
7570
|
-
await this.setState("running");
|
|
7571
7695
|
this.interrupted = false;
|
|
7572
7696
|
await this.callbacks.onEvent({
|
|
7573
7697
|
type: "user_message",
|
|
7574
7698
|
content: msg.content,
|
|
7575
7699
|
userId: msg.userId
|
|
7576
7700
|
});
|
|
7577
|
-
|
|
7701
|
+
if (this.prefillEligible(msg)) {
|
|
7702
|
+
await this.runPrefilledMessage(msg, this.mode.effectiveMode);
|
|
7703
|
+
} else {
|
|
7704
|
+
await this.setState("running");
|
|
7705
|
+
await this.executeQuery(msg.content);
|
|
7706
|
+
}
|
|
7578
7707
|
if (this.queryBridge?.isDiscoveryCompleted) {
|
|
7579
7708
|
process.stderr.write(
|
|
7580
7709
|
"[conveyor-agent] Discovery completed \u2014 entering dormant idle (staying connected)\n"
|
|
@@ -7647,7 +7776,13 @@ var SessionRunner = class _SessionRunner {
|
|
|
7647
7776
|
async executeInitialMode() {
|
|
7648
7777
|
if (!this.taskContext || !this.fullContext) return false;
|
|
7649
7778
|
const effectiveMode = this.mode.effectiveMode;
|
|
7650
|
-
const
|
|
7779
|
+
const intrinsicDelivery = this.queryBridge?.initialPromptDelivery(this.fullContext) ?? "submit";
|
|
7780
|
+
const hinted = this.prepareInitialPendingMessages(intrinsicDelivery);
|
|
7781
|
+
if (hinted) {
|
|
7782
|
+
await this.runPrefilledMessage(hinted, effectiveMode);
|
|
7783
|
+
return true;
|
|
7784
|
+
}
|
|
7785
|
+
const delivery = this.pendingMessages.length > 0 ? "submit" : intrinsicDelivery;
|
|
7651
7786
|
const shouldRun = effectiveMode === "building" || effectiveMode === "auto" || effectiveMode === "review" || delivery === "prefill";
|
|
7652
7787
|
if (!shouldRun) {
|
|
7653
7788
|
await this.setState("idle");
|
|
@@ -7674,6 +7809,88 @@ var SessionRunner = class _SessionRunner {
|
|
|
7674
7809
|
if (!this.stopped) await this.setState("idle");
|
|
7675
7810
|
return true;
|
|
7676
7811
|
}
|
|
7812
|
+
// ── Prefill-delivery helpers ───────────────────────────────────────
|
|
7813
|
+
/**
|
|
7814
|
+
* Startup-queue preamble: the card's initial message reaches a fresh pod
|
|
7815
|
+
* twice — inside the task context (chat history) AND as a queued pending
|
|
7816
|
+
* message. When the intrinsic delivery is prefill (manual-mode fresh TUI)
|
|
7817
|
+
* or a hinted message is queued (Refine), drop those context-duplicates so
|
|
7818
|
+
* they don't force submit semantics — a genuinely-new live message still
|
|
7819
|
+
* does. Returns the lone hinted message to park, if any.
|
|
7820
|
+
*/
|
|
7821
|
+
prepareInitialPendingMessages(intrinsicDelivery) {
|
|
7822
|
+
const hasHintedPending = this.pendingMessages.some((m) => m.delivery === "prefill");
|
|
7823
|
+
if (this.pendingMessages.length > 0 && (intrinsicDelivery === "prefill" || hasHintedPending)) {
|
|
7824
|
+
this.foldContextDuplicatePendingMessages();
|
|
7825
|
+
}
|
|
7826
|
+
return this.takePrefillHintedMessage();
|
|
7827
|
+
}
|
|
7828
|
+
/**
|
|
7829
|
+
* Drop pending messages whose content is already present as a user message
|
|
7830
|
+
* in the task-context chat history (the card's initial message is delivered
|
|
7831
|
+
* both ways). Hinted (prefill) messages are kept — they are consumed by the
|
|
7832
|
+
* dedicated prefill path.
|
|
7833
|
+
*/
|
|
7834
|
+
foldContextDuplicatePendingMessages() {
|
|
7835
|
+
const userContents = new Set(
|
|
7836
|
+
(this.fullContext?.chatHistory ?? []).filter((m) => m.role === "user" && m.content.trim()).map((m) => m.content.trim())
|
|
7837
|
+
);
|
|
7838
|
+
const kept = this.pendingMessages.filter(
|
|
7839
|
+
(m) => m.delivery === "prefill" || !m.content.trim() || !userContents.has(m.content.trim())
|
|
7840
|
+
);
|
|
7841
|
+
const dropped = this.pendingMessages.length - kept.length;
|
|
7842
|
+
if (dropped > 0) {
|
|
7843
|
+
this.pendingMessages.length = 0;
|
|
7844
|
+
this.pendingMessages.push(...kept);
|
|
7845
|
+
process.stderr.write(
|
|
7846
|
+
`[conveyor-agent] Folded ${dropped} pending message(s) already present in chat context
|
|
7847
|
+
`
|
|
7848
|
+
);
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* Honor a prefill hint only when it is unambiguous: PTY harness, a manual
|
|
7853
|
+
* (human-facing) agent mode, no other message queued behind it, and not an
|
|
7854
|
+
* automated-critical source. Everything else keeps submit semantics.
|
|
7855
|
+
*/
|
|
7856
|
+
prefillEligible(msg) {
|
|
7857
|
+
if (msg.delivery !== "prefill") return false;
|
|
7858
|
+
if (this.pendingMessages.length > 0) return false;
|
|
7859
|
+
if (this.queryBridge?.harnessKind !== "pty") return false;
|
|
7860
|
+
if (msg.source && msg.source !== "mode_change" && msg.source !== "user") return false;
|
|
7861
|
+
const m = this.mode.effectiveMode;
|
|
7862
|
+
return m === "discovery" || m === "review" || m === "help";
|
|
7863
|
+
}
|
|
7864
|
+
/** Consume the sole pending message when it should park as a prefill. */
|
|
7865
|
+
takePrefillHintedMessage() {
|
|
7866
|
+
if (this.pendingMessages.length !== 1) return null;
|
|
7867
|
+
const [first] = this.pendingMessages;
|
|
7868
|
+
if (!first || first.delivery !== "prefill") return null;
|
|
7869
|
+
this.pendingMessages.length = 0;
|
|
7870
|
+
if (this.prefillEligible(first)) return first;
|
|
7871
|
+
this.pendingMessages.push(first);
|
|
7872
|
+
return null;
|
|
7873
|
+
}
|
|
7874
|
+
/**
|
|
7875
|
+
* Park a message in the Connected-TUI input (prefill) and wait for the human
|
|
7876
|
+
* to submit. Empty content prefills the mode's initial prompt instead (the
|
|
7877
|
+
* latest user chat message — e.g. a Refine wake on a dormant agent). The
|
|
7878
|
+
* caller restores the idle state afterwards.
|
|
7879
|
+
*/
|
|
7880
|
+
async runPrefilledMessage(msg, effectiveMode) {
|
|
7881
|
+
await this.setState("waiting_for_input");
|
|
7882
|
+
await this.callbacks.onEvent({
|
|
7883
|
+
type: "execute_mode",
|
|
7884
|
+
mode: effectiveMode,
|
|
7885
|
+
delivery: "prefill"
|
|
7886
|
+
});
|
|
7887
|
+
this.lifecycle.startIdleTimer();
|
|
7888
|
+
try {
|
|
7889
|
+
await this.executeQuery(msg.content.trim() ? msg.content : void 0, "prefill");
|
|
7890
|
+
} finally {
|
|
7891
|
+
this.lifecycle.cancelIdleTimer();
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7677
7894
|
// ── Message waiting ────────────────────────────────────────────────
|
|
7678
7895
|
waitForMessage() {
|
|
7679
7896
|
if (this.pendingMessages.length > 0) {
|
|
@@ -8043,7 +8260,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
8043
8260
|
`);
|
|
8044
8261
|
this.connection.sendEvent({ type: "shutdown", reason: finalState });
|
|
8045
8262
|
this.lifecycle.destroy();
|
|
8046
|
-
|
|
8263
|
+
try {
|
|
8264
|
+
await this.setState(finalState);
|
|
8265
|
+
} catch {
|
|
8266
|
+
}
|
|
8047
8267
|
this.connection.disconnect();
|
|
8048
8268
|
this._finalState = finalState;
|
|
8049
8269
|
}
|
|
@@ -8102,20 +8322,31 @@ var ProjectConnection = class {
|
|
|
8102
8322
|
return this.socket?.connected ?? false;
|
|
8103
8323
|
}
|
|
8104
8324
|
// ── Typed service method call ──────────────────────────────────────────
|
|
8105
|
-
call(method, payload) {
|
|
8325
|
+
call(method, payload, opts) {
|
|
8106
8326
|
const socket = this.requireSocket();
|
|
8107
8327
|
return new Promise((resolve2, reject) => {
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
resolve2(response.data);
|
|
8114
|
-
} else {
|
|
8115
|
-
reject(new Error(response.error ?? `Service call failed: ${String(method)}`));
|
|
8116
|
-
}
|
|
8328
|
+
const handleResponse = (response) => {
|
|
8329
|
+
if (response.success && response.data !== void 0) {
|
|
8330
|
+
resolve2(response.data);
|
|
8331
|
+
} else {
|
|
8332
|
+
reject(new Error(response.error ?? `Service call failed: ${String(method)}`));
|
|
8117
8333
|
}
|
|
8118
|
-
|
|
8334
|
+
};
|
|
8335
|
+
if (opts?.timeoutMs) {
|
|
8336
|
+
socket.timeout(opts.timeoutMs).emit(
|
|
8337
|
+
`agentSessionService:${String(method)}`,
|
|
8338
|
+
payload,
|
|
8339
|
+
(err, response) => {
|
|
8340
|
+
if (err) {
|
|
8341
|
+
reject(new Error(`Service call timed out: ${String(method)}`));
|
|
8342
|
+
} else {
|
|
8343
|
+
handleResponse(response);
|
|
8344
|
+
}
|
|
8345
|
+
}
|
|
8346
|
+
);
|
|
8347
|
+
return;
|
|
8348
|
+
}
|
|
8349
|
+
socket.emit(`agentSessionService:${String(method)}`, payload, handleResponse);
|
|
8119
8350
|
});
|
|
8120
8351
|
}
|
|
8121
8352
|
// ── Connection lifecycle ───────────────────────────────────────────────
|
|
@@ -8173,8 +8404,13 @@ var ProjectConnection = class {
|
|
|
8173
8404
|
this.flushTimer = null;
|
|
8174
8405
|
}
|
|
8175
8406
|
this.flushEvents();
|
|
8176
|
-
this.socket
|
|
8177
|
-
|
|
8407
|
+
if (this.socket) {
|
|
8408
|
+
this.socket.io.reconnection(false);
|
|
8409
|
+
this.socket.removeAllListeners();
|
|
8410
|
+
this.socket.disconnect();
|
|
8411
|
+
this.socket = null;
|
|
8412
|
+
}
|
|
8413
|
+
this.reconnectCallbacks = [];
|
|
8178
8414
|
}
|
|
8179
8415
|
onReconnect(callback) {
|
|
8180
8416
|
this.reconnectCallbacks.push(callback);
|
|
@@ -9771,6 +10007,10 @@ function spawnChildAgent(assignment, workDir) {
|
|
|
9771
10007
|
CONVEYOR_MODE: mode,
|
|
9772
10008
|
CONVEYOR_WORKSPACE: workDir,
|
|
9773
10009
|
CONVEYOR_USE_WORKTREE: "false",
|
|
10010
|
+
// The project runner flushes git for every child working tree after the
|
|
10011
|
+
// kill (see ProjectRunner.stop), so the child skips its own shutdown
|
|
10012
|
+
// flush — keeps SIGTERM teardown well inside the SIGKILL window.
|
|
10013
|
+
CONVEYOR_SKIP_SHUTDOWN_GIT_FLUSH: "true",
|
|
9774
10014
|
CONVEYOR_AGENT_MODE: effectiveAgentMode,
|
|
9775
10015
|
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
9776
10016
|
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false",
|
|
@@ -9895,6 +10135,8 @@ function handleStopTask(taskId, activeAgents, projectDir) {
|
|
|
9895
10135
|
// src/runner/project-runner.ts
|
|
9896
10136
|
var logger10 = createServiceLogger("ProjectRunner");
|
|
9897
10137
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
10138
|
+
var DISCONNECT_ACK_TIMEOUT_MS = 5e3;
|
|
10139
|
+
var SHUTDOWN_WATCHDOG_MS = 9e4;
|
|
9898
10140
|
var ProjectRunner = class {
|
|
9899
10141
|
connection;
|
|
9900
10142
|
projectDir;
|
|
@@ -9978,11 +10220,17 @@ var ProjectRunner = class {
|
|
|
9978
10220
|
}
|
|
9979
10221
|
/** Best-effort WIP commit + push of every tracked working tree on shutdown.
|
|
9980
10222
|
* Iterates the main project dir plus any active agent worktrees so nothing
|
|
9981
|
-
* pending is lost when the pod is killed. Never throws.
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
10223
|
+
* pending is lost when the pod is killed. Never throws.
|
|
10224
|
+
*
|
|
10225
|
+
* Callers that kill child agents first must snapshot the worktree dirs
|
|
10226
|
+
* BEFORE the kill — the child exit handler removes entries from
|
|
10227
|
+
* `activeAgents`, so reading it after the kill misses the worktrees. */
|
|
10228
|
+
async flushGitOnShutdown(dirs) {
|
|
10229
|
+
if (!dirs) {
|
|
10230
|
+
dirs = /* @__PURE__ */ new Set([this.projectDir]);
|
|
10231
|
+
for (const agent of this.activeAgents.values()) {
|
|
10232
|
+
if (agent.worktreePath) dirs.add(agent.worktreePath);
|
|
10233
|
+
}
|
|
9986
10234
|
}
|
|
9987
10235
|
for (const dir of dirs) {
|
|
9988
10236
|
try {
|
|
@@ -10006,38 +10254,62 @@ var ProjectRunner = class {
|
|
|
10006
10254
|
if (this.stopping) return;
|
|
10007
10255
|
this.stopping = true;
|
|
10008
10256
|
logger10.info("Shutting down");
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10257
|
+
const watchdog = setTimeout(() => {
|
|
10258
|
+
logger10.warn("Shutdown watchdog fired, forcing lifecycle resolution");
|
|
10259
|
+
this.finishShutdown();
|
|
10260
|
+
}, SHUTDOWN_WATCHDOG_MS);
|
|
10261
|
+
watchdog.unref();
|
|
10262
|
+
const flushDirs = /* @__PURE__ */ new Set([this.projectDir]);
|
|
10263
|
+
for (const agent of this.activeAgents.values()) {
|
|
10264
|
+
if (agent.worktreePath) flushDirs.add(agent.worktreePath);
|
|
10014
10265
|
}
|
|
10015
|
-
const stopPromises = [...this.activeAgents.keys()].map(
|
|
10016
|
-
(key) => new Promise((resolve2) => {
|
|
10017
|
-
const agent = this.activeAgents.get(key);
|
|
10018
|
-
if (!agent) {
|
|
10019
|
-
resolve2();
|
|
10020
|
-
return;
|
|
10021
|
-
}
|
|
10022
|
-
agent.process.on("exit", () => resolve2());
|
|
10023
|
-
handleStopTask(key, this.activeAgents, this.projectDir);
|
|
10024
|
-
})
|
|
10025
|
-
);
|
|
10026
|
-
await Promise.race([
|
|
10027
|
-
Promise.all(stopPromises),
|
|
10028
|
-
new Promise((resolve2) => {
|
|
10029
|
-
setTimeout(resolve2, STOP_TIMEOUT_MS + 5e3);
|
|
10030
|
-
})
|
|
10031
|
-
]);
|
|
10032
|
-
await this.flushGitOnShutdown();
|
|
10033
10266
|
try {
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10267
|
+
this.commitWatcher.stop();
|
|
10268
|
+
await killStartCommand(this.startCmd);
|
|
10269
|
+
if (this.heartbeatTimer) {
|
|
10270
|
+
clearInterval(this.heartbeatTimer);
|
|
10271
|
+
this.heartbeatTimer = null;
|
|
10272
|
+
}
|
|
10273
|
+
const stopPromises = [...this.activeAgents.keys()].map(
|
|
10274
|
+
(key) => new Promise((resolve2) => {
|
|
10275
|
+
const agent = this.activeAgents.get(key);
|
|
10276
|
+
if (!agent) {
|
|
10277
|
+
resolve2();
|
|
10278
|
+
return;
|
|
10279
|
+
}
|
|
10280
|
+
agent.process.on("exit", () => resolve2());
|
|
10281
|
+
handleStopTask(key, this.activeAgents, this.projectDir);
|
|
10282
|
+
})
|
|
10283
|
+
);
|
|
10284
|
+
await Promise.race([
|
|
10285
|
+
Promise.all(stopPromises),
|
|
10286
|
+
new Promise((resolve2) => {
|
|
10287
|
+
setTimeout(resolve2, STOP_TIMEOUT_MS + 5e3);
|
|
10288
|
+
})
|
|
10289
|
+
]);
|
|
10290
|
+
await this.flushGitOnShutdown(flushDirs);
|
|
10291
|
+
try {
|
|
10292
|
+
await this.connection.call(
|
|
10293
|
+
"disconnectProjectRunner",
|
|
10294
|
+
{ projectId: this.connection.projectId },
|
|
10295
|
+
{ timeoutMs: DISCONNECT_ACK_TIMEOUT_MS }
|
|
10296
|
+
);
|
|
10297
|
+
} catch {
|
|
10298
|
+
}
|
|
10299
|
+
logger10.info("Shutdown complete");
|
|
10300
|
+
} finally {
|
|
10301
|
+
clearTimeout(watchdog);
|
|
10302
|
+
this.finishShutdown();
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
/** Idempotent final step: tear down the socket and resolve the lifecycle
|
|
10306
|
+
* promise so start() returns. Called from stop()'s finally and from the
|
|
10307
|
+
* shutdown watchdog. */
|
|
10308
|
+
finishShutdown() {
|
|
10309
|
+
try {
|
|
10310
|
+
this.connection.disconnect();
|
|
10037
10311
|
} catch {
|
|
10038
10312
|
}
|
|
10039
|
-
this.connection.disconnect();
|
|
10040
|
-
logger10.info("Shutdown complete");
|
|
10041
10313
|
if (this.resolveLifecycle) {
|
|
10042
10314
|
this.resolveLifecycle();
|
|
10043
10315
|
this.resolveLifecycle = null;
|
|
@@ -10111,7 +10383,7 @@ var ProjectRunner = class {
|
|
|
10111
10383
|
async handleAuditTags(request) {
|
|
10112
10384
|
this.connection.emitStatus("busy");
|
|
10113
10385
|
try {
|
|
10114
|
-
const { handleTagAudit } = await import("./tag-audit-handler-
|
|
10386
|
+
const { handleTagAudit } = await import("./tag-audit-handler-3IFB7YDV.js");
|
|
10115
10387
|
await handleTagAudit(request, this.connection, this.projectDir);
|
|
10116
10388
|
} catch (error) {
|
|
10117
10389
|
const msg = parseErrorMessage(error);
|
|
@@ -10134,7 +10406,7 @@ var ProjectRunner = class {
|
|
|
10134
10406
|
async handleAuditTasks(request) {
|
|
10135
10407
|
this.connection.emitStatus("busy");
|
|
10136
10408
|
try {
|
|
10137
|
-
const { handleTaskAudit } = await import("./task-audit-handler-
|
|
10409
|
+
const { handleTaskAudit } = await import("./task-audit-handler-U5Q52YT2.js");
|
|
10138
10410
|
await handleTaskAudit(request, this.connection, this.projectDir);
|
|
10139
10411
|
} catch (error) {
|
|
10140
10412
|
const msg = parseErrorMessage(error);
|
|
@@ -10275,4 +10547,4 @@ export {
|
|
|
10275
10547
|
loadConveyorConfig,
|
|
10276
10548
|
unshallowRepo
|
|
10277
10549
|
};
|
|
10278
|
-
//# sourceMappingURL=chunk-
|
|
10550
|
+
//# sourceMappingURL=chunk-WZCO64YR.js.map
|