@rallycry/conveyor-agent 10.13.13 → 10.13.15
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-YRC57EYG.js → chunk-LZM5OKAC.js} +2 -68
- package/dist/chunk-LZM5OKAC.js.map +1 -0
- package/dist/{chunk-2K6RRTQC.js → chunk-RSIW2UCR.js} +4 -180
- package/dist/chunk-RSIW2UCR.js.map +1 -0
- package/dist/{chunk-TB5SQIGX.js → chunk-SAHOFQBQ.js} +216 -457
- package/dist/chunk-SAHOFQBQ.js.map +1 -0
- package/dist/{chunk-IE7EOKMD.js → chunk-VKZ5W2VO.js} +1 -1
- package/dist/chunk-VKZ5W2VO.js.map +1 -0
- package/dist/cli.js +35 -75
- package/dist/cli.js.map +1 -1
- package/dist/{client-ICIWKSK2.js → client-VQIBGPE6.js} +3 -3
- package/dist/index.d.ts +27 -77
- package/dist/index.js +5 -17
- package/dist/index.js.map +1 -1
- package/dist/{protocol-QJRHTTMQ.js → protocol-IHRTO5C4.js} +2 -2
- package/dist/{server-UGE67KIZ.js → server-TVIXWCAI.js} +7 -72
- package/dist/server-TVIXWCAI.js.map +1 -0
- package/package.json +1 -1
- package/runtime/entrypoint.sh +34 -98
- package/dist/chunk-2K6RRTQC.js.map +0 -1
- package/dist/chunk-IE7EOKMD.js.map +0 -1
- package/dist/chunk-TB5SQIGX.js.map +0 -1
- package/dist/chunk-YRC57EYG.js.map +0 -1
- package/dist/server-UGE67KIZ.js.map +0 -1
- /package/dist/{client-ICIWKSK2.js.map → client-VQIBGPE6.js.map} +0 -0
- /package/dist/{protocol-QJRHTTMQ.js.map → protocol-IHRTO5C4.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-7TQO4ZF4.js";
|
|
4
4
|
import {
|
|
5
5
|
getWorkbenchClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LZM5OKAC.js";
|
|
7
7
|
import {
|
|
8
8
|
workbenchEnabled
|
|
9
9
|
} from "./chunk-4VUQ2NPF.js";
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
MAX_BETWEEN_TURN_BUFFER,
|
|
12
12
|
MAX_DIAGNOSTIC_OUTPUT,
|
|
13
13
|
buildPromptBytes,
|
|
14
|
-
buildSnapshotTar,
|
|
15
|
-
extractSnapshotTar,
|
|
16
14
|
inheritedEnv,
|
|
17
15
|
loadPtySpawn,
|
|
18
16
|
parseUserQuestions,
|
|
@@ -25,7 +23,7 @@ import {
|
|
|
25
23
|
sleep,
|
|
26
24
|
transcriptSize,
|
|
27
25
|
turnOptionsFrom
|
|
28
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-RSIW2UCR.js";
|
|
29
27
|
|
|
30
28
|
// src/setup/bootstrap.ts
|
|
31
29
|
var BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
@@ -207,7 +205,6 @@ var AgentConnection = class _AgentConnection {
|
|
|
207
205
|
modeChangeCallback = null;
|
|
208
206
|
apiKeyUpdateCallback = null;
|
|
209
207
|
pullBranchCallback = null;
|
|
210
|
-
finalizeSnapshotCallback = null;
|
|
211
208
|
runStartCommandCallback = null;
|
|
212
209
|
earlyPullBranches = [];
|
|
213
210
|
spawnReviewCallback = null;
|
|
@@ -431,9 +428,6 @@ var AgentConnection = class _AgentConnection {
|
|
|
431
428
|
if (this.probeUsageCallback) this.probeUsageCallback();
|
|
432
429
|
else this.earlyProbeUsage = true;
|
|
433
430
|
});
|
|
434
|
-
this.socket.on("session:finalizeSnapshot", () => {
|
|
435
|
-
this.finalizeSnapshotCallback?.();
|
|
436
|
-
});
|
|
437
431
|
this.socket.on("session:runStartCommand", () => {
|
|
438
432
|
this.runStartCommandCallback?.();
|
|
439
433
|
});
|
|
@@ -714,9 +708,6 @@ var AgentConnection = class _AgentConnection {
|
|
|
714
708
|
}).catch(() => {
|
|
715
709
|
});
|
|
716
710
|
}
|
|
717
|
-
onFinalizeSnapshot(callback) {
|
|
718
|
-
this.finalizeSnapshotCallback = callback;
|
|
719
|
-
}
|
|
720
711
|
onRunStartCommand(callback) {
|
|
721
712
|
this.runStartCommandCallback = callback;
|
|
722
713
|
}
|
|
@@ -974,15 +965,6 @@ var AgentConnection = class _AgentConnection {
|
|
|
974
965
|
updateStatus(status) {
|
|
975
966
|
this.emitStatus(status);
|
|
976
967
|
}
|
|
977
|
-
emitCodeReviewResult(content, approved) {
|
|
978
|
-
if (!this.socket) return;
|
|
979
|
-
void this.call("submitCodeReviewResult", {
|
|
980
|
-
sessionId: this.config.sessionId,
|
|
981
|
-
content,
|
|
982
|
-
approved
|
|
983
|
-
}).catch(() => {
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
968
|
/**
|
|
987
969
|
* The session's key hit a hard usage cap — ask the server to stamp it
|
|
988
970
|
* limited and hand back the best remaining key's credential env (or a
|
|
@@ -1226,55 +1208,51 @@ async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
1226
1208
|
});
|
|
1227
1209
|
return stdout.toString().trim();
|
|
1228
1210
|
}
|
|
1229
|
-
async function
|
|
1230
|
-
if (!
|
|
1211
|
+
async function ensureOnTaskBranch(cwd, taskBranch, baseBranch) {
|
|
1212
|
+
if (!taskBranch) return true;
|
|
1231
1213
|
try {
|
|
1232
|
-
await
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1214
|
+
if (await getCurrentBranch(cwd) === taskBranch) return true;
|
|
1215
|
+
let existsOnOrigin = true;
|
|
1216
|
+
try {
|
|
1217
|
+
await git(cwd, [
|
|
1218
|
+
"fetch",
|
|
1219
|
+
"origin",
|
|
1220
|
+
`+refs/heads/${taskBranch}:refs/remotes/origin/${taskBranch}`
|
|
1221
|
+
]);
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
if (String(err).includes("couldn't find remote ref")) existsOnOrigin = false;
|
|
1224
|
+
else throw err;
|
|
1225
|
+
}
|
|
1226
|
+
if (existsOnOrigin) {
|
|
1227
|
+
await git(cwd, ["checkout", "-B", taskBranch, `origin/${taskBranch}`], 3e4);
|
|
1228
|
+
process.stderr.write(`[conveyor-agent] Checked out task branch ${taskBranch}
|
|
1229
|
+
`);
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
if (!baseBranch) {
|
|
1233
|
+
process.stderr.write(
|
|
1234
|
+
`[conveyor-agent] Warning: task branch ${taskBranch} missing on origin and no base branch given
|
|
1236
1235
|
`
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1236
|
+
);
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
await git(cwd, [
|
|
1240
|
+
"fetch",
|
|
1241
|
+
"origin",
|
|
1242
|
+
`+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`
|
|
1243
|
+
]);
|
|
1244
|
+
await git(cwd, ["checkout", "-B", taskBranch, `origin/${baseBranch}`], 3e4);
|
|
1245
|
+
await git(cwd, ["push", "-u", "origin", taskBranch], 3e4);
|
|
1243
1246
|
process.stderr.write(
|
|
1244
|
-
`[conveyor-agent]
|
|
1247
|
+
`[conveyor-agent] Created task branch ${taskBranch} from origin/${baseBranch} and pushed
|
|
1245
1248
|
`
|
|
1246
1249
|
);
|
|
1247
|
-
|
|
1248
|
-
await git(cwd, ["merge", "--abort"], 3e4);
|
|
1249
|
-
} catch {
|
|
1250
|
-
}
|
|
1251
|
-
return false;
|
|
1252
|
-
}
|
|
1253
|
-
process.stderr.write(`[conveyor-agent] Synced with latest origin/${baseBranch}
|
|
1254
|
-
`);
|
|
1255
|
-
return true;
|
|
1256
|
-
}
|
|
1257
|
-
async function ensureOnTaskBranch(cwd, taskBranch) {
|
|
1258
|
-
if (!taskBranch) return true;
|
|
1259
|
-
const current = await getCurrentBranch(cwd);
|
|
1260
|
-
if (current === taskBranch) return true;
|
|
1261
|
-
try {
|
|
1262
|
-
await git(cwd, ["fetch", "origin", taskBranch]);
|
|
1263
|
-
} catch {
|
|
1264
|
-
process.stderr.write(`[conveyor-agent] Warning: git fetch origin ${taskBranch} failed
|
|
1265
|
-
`);
|
|
1266
|
-
return false;
|
|
1267
|
-
}
|
|
1268
|
-
try {
|
|
1269
|
-
await git(cwd, ["checkout", "-B", taskBranch, `origin/${taskBranch}`], 3e4);
|
|
1250
|
+
return true;
|
|
1270
1251
|
} catch {
|
|
1271
|
-
process.stderr.write(`[conveyor-agent] Warning:
|
|
1252
|
+
process.stderr.write(`[conveyor-agent] Warning: ensureOnTaskBranch(${taskBranch}) failed
|
|
1272
1253
|
`);
|
|
1273
1254
|
return false;
|
|
1274
1255
|
}
|
|
1275
|
-
process.stderr.write(`[conveyor-agent] Checked out task branch ${taskBranch}
|
|
1276
|
-
`);
|
|
1277
|
-
return true;
|
|
1278
1256
|
}
|
|
1279
1257
|
async function hasUncommittedChanges(cwd) {
|
|
1280
1258
|
const status = await git(cwd, ["status", "--porcelain"], GIT_SLOW_TIMEOUT_MS);
|
|
@@ -1599,206 +1577,6 @@ async function backupOtherBranches(cwd, currentBranch) {
|
|
|
1599
1577
|
return count;
|
|
1600
1578
|
}
|
|
1601
1579
|
|
|
1602
|
-
// src/runner/work-preservation/index.ts
|
|
1603
|
-
import { mkdtemp, rm as rm2 } from "fs/promises";
|
|
1604
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
1605
|
-
import { join } from "path";
|
|
1606
|
-
|
|
1607
|
-
// src/runner/work-preservation/restore-precedence.ts
|
|
1608
|
-
function selectRestoreSource(probe) {
|
|
1609
|
-
if (probe.gcsAvailable) return "gcs";
|
|
1610
|
-
if (probe.wipRestoreResult === "applied") return "wip";
|
|
1611
|
-
return "clean";
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// src/runner/work-preservation/snapshot-transfer.ts
|
|
1615
|
-
import { randomUUID } from "crypto";
|
|
1616
|
-
import { createReadStream, createWriteStream } from "fs";
|
|
1617
|
-
import { rm } from "fs/promises";
|
|
1618
|
-
import { tmpdir } from "os";
|
|
1619
|
-
import path from "path";
|
|
1620
|
-
import { Readable } from "stream";
|
|
1621
|
-
import { pipeline } from "stream/promises";
|
|
1622
|
-
var SNAPSHOT_HTTP_TIMEOUT_MS = 6e4;
|
|
1623
|
-
var SNAPSHOT_CAPTURED_AT_HEADER = "x-snapshot-captured-at";
|
|
1624
|
-
async function putSnapshotToGcs(url, tarResult) {
|
|
1625
|
-
try {
|
|
1626
|
-
const body = Readable.toWeb(createReadStream(tarResult.tarPath));
|
|
1627
|
-
const res = await fetch(url, {
|
|
1628
|
-
method: "PUT",
|
|
1629
|
-
headers: {
|
|
1630
|
-
"Content-Type": "application/gzip",
|
|
1631
|
-
"Content-Length": String(tarResult.sizeBytes),
|
|
1632
|
-
[SNAPSHOT_CAPTURED_AT_HEADER]: String(tarResult.capturedAt)
|
|
1633
|
-
},
|
|
1634
|
-
body,
|
|
1635
|
-
duplex: "half",
|
|
1636
|
-
signal: AbortSignal.timeout(SNAPSHOT_HTTP_TIMEOUT_MS)
|
|
1637
|
-
});
|
|
1638
|
-
return res.ok;
|
|
1639
|
-
} catch {
|
|
1640
|
-
return false;
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
async function downloadSnapshotToTemp(url) {
|
|
1644
|
-
const tmpTar = path.join(tmpdir(), `conveyor-snapshot-restore-${randomUUID()}.tar.gz`);
|
|
1645
|
-
try {
|
|
1646
|
-
const res = await fetch(url, {
|
|
1647
|
-
method: "GET",
|
|
1648
|
-
signal: AbortSignal.timeout(SNAPSHOT_HTTP_TIMEOUT_MS)
|
|
1649
|
-
});
|
|
1650
|
-
if (!res.ok || !res.body) return null;
|
|
1651
|
-
await pipeline(
|
|
1652
|
-
Readable.fromWeb(res.body),
|
|
1653
|
-
createWriteStream(tmpTar)
|
|
1654
|
-
);
|
|
1655
|
-
return tmpTar;
|
|
1656
|
-
} catch {
|
|
1657
|
-
await rm(tmpTar, { force: true }).catch(() => {
|
|
1658
|
-
});
|
|
1659
|
-
return null;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
// src/runner/work-preservation/index.ts
|
|
1664
|
-
async function captureSnapshotTar(cwd) {
|
|
1665
|
-
if (!workbenchEnabled()) return buildSnapshotTar(cwd);
|
|
1666
|
-
const staging = await mkdtemp(join(tmpdir2(), "conveyor-snapshot-remote-"));
|
|
1667
|
-
const cleanup = async () => {
|
|
1668
|
-
await rm2(staging, { recursive: true, force: true }).catch(() => {
|
|
1669
|
-
});
|
|
1670
|
-
};
|
|
1671
|
-
try {
|
|
1672
|
-
const meta = await getWorkbenchClient().buildSnapshot(cwd, join(staging, "snapshot.tar.gz"));
|
|
1673
|
-
return {
|
|
1674
|
-
tarPath: meta.tarPath,
|
|
1675
|
-
sizeBytes: meta.sizeBytes,
|
|
1676
|
-
fileCount: meta.fileCount,
|
|
1677
|
-
deletionCount: meta.deletionCount,
|
|
1678
|
-
capturedAt: meta.capturedAt,
|
|
1679
|
-
cleanup
|
|
1680
|
-
};
|
|
1681
|
-
} catch (err) {
|
|
1682
|
-
await cleanup();
|
|
1683
|
-
throw err;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
function applySnapshotTar(cwd, tarPath) {
|
|
1687
|
-
return workbenchEnabled() ? getWorkbenchClient().extractSnapshot(cwd, tarPath) : extractSnapshotTar(cwd, tarPath);
|
|
1688
|
-
}
|
|
1689
|
-
var periodicTimer = null;
|
|
1690
|
-
var inFlightCapture = null;
|
|
1691
|
-
var inFlightGcsUpload = null;
|
|
1692
|
-
var EMPTY_GCS_RESULT = {
|
|
1693
|
-
uploaded: false,
|
|
1694
|
-
fileCount: 0,
|
|
1695
|
-
deletionCount: 0,
|
|
1696
|
-
sizeBytes: 0
|
|
1697
|
-
};
|
|
1698
|
-
async function doUploadSnapshotToGcs(cwd, uploadUrl, opts) {
|
|
1699
|
-
let tarResult;
|
|
1700
|
-
try {
|
|
1701
|
-
tarResult = await captureSnapshotTar(cwd);
|
|
1702
|
-
} catch {
|
|
1703
|
-
return EMPTY_GCS_RESULT;
|
|
1704
|
-
}
|
|
1705
|
-
try {
|
|
1706
|
-
const uploaded = (opts?.forceUpload || tarResult.sizeBytes > 0) && uploadUrl ? await putSnapshotToGcs(uploadUrl, tarResult) : false;
|
|
1707
|
-
return {
|
|
1708
|
-
uploaded,
|
|
1709
|
-
fileCount: tarResult.fileCount,
|
|
1710
|
-
deletionCount: tarResult.deletionCount,
|
|
1711
|
-
sizeBytes: tarResult.sizeBytes
|
|
1712
|
-
};
|
|
1713
|
-
} finally {
|
|
1714
|
-
await tarResult.cleanup();
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
async function uploadSnapshotToGcs(cwd, uploadUrl, opts) {
|
|
1718
|
-
const previous = inFlightGcsUpload;
|
|
1719
|
-
const run = (async () => {
|
|
1720
|
-
if (previous) await previous.catch(() => {
|
|
1721
|
-
});
|
|
1722
|
-
return doUploadSnapshotToGcs(cwd, uploadUrl, opts);
|
|
1723
|
-
})();
|
|
1724
|
-
inFlightGcsUpload = run;
|
|
1725
|
-
try {
|
|
1726
|
-
return await run;
|
|
1727
|
-
} finally {
|
|
1728
|
-
if (inFlightGcsUpload === run) inFlightGcsUpload = null;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
async function captureSnapshot(ctx) {
|
|
1732
|
-
const gcs = await uploadSnapshotToGcs(ctx.cwd, ctx.snapshotUploadUrl, {
|
|
1733
|
-
forceUpload: ctx.forceUpload
|
|
1734
|
-
});
|
|
1735
|
-
const wipResult = await flushPendingChanges(ctx.cwd, {
|
|
1736
|
-
wipMessage: ctx.wipMessage ?? "WIP: WorkPreservation periodic snapshot",
|
|
1737
|
-
refreshToken: ctx.refreshToken
|
|
1738
|
-
});
|
|
1739
|
-
return {
|
|
1740
|
-
fileCount: gcs.fileCount,
|
|
1741
|
-
deletionCount: gcs.deletionCount,
|
|
1742
|
-
sizeBytes: gcs.sizeBytes,
|
|
1743
|
-
gcsUploaded: gcs.uploaded,
|
|
1744
|
-
wipPushed: wipResult.committed || wipResult.pushed
|
|
1745
|
-
};
|
|
1746
|
-
}
|
|
1747
|
-
function startPeriodic(intervalMs, ctx) {
|
|
1748
|
-
stop();
|
|
1749
|
-
periodicTimer = setInterval(() => {
|
|
1750
|
-
if (inFlightCapture) return;
|
|
1751
|
-
const run = captureSnapshot(ctx);
|
|
1752
|
-
inFlightCapture = run;
|
|
1753
|
-
void run.catch(() => {
|
|
1754
|
-
}).finally(() => {
|
|
1755
|
-
if (inFlightCapture === run) inFlightCapture = null;
|
|
1756
|
-
});
|
|
1757
|
-
}, intervalMs);
|
|
1758
|
-
}
|
|
1759
|
-
function stop() {
|
|
1760
|
-
if (periodicTimer) {
|
|
1761
|
-
clearInterval(periodicTimer);
|
|
1762
|
-
periodicTimer = null;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
async function finalizeForSleep(ctx) {
|
|
1766
|
-
stop();
|
|
1767
|
-
if (inFlightCapture) await inFlightCapture.catch(() => {
|
|
1768
|
-
});
|
|
1769
|
-
return captureSnapshot({
|
|
1770
|
-
...ctx,
|
|
1771
|
-
wipMessage: ctx.wipMessage ?? "WIP: WorkPreservation sleep finalize snapshot",
|
|
1772
|
-
// Always stamp snapshotAt on sleep — even an empty workspace must confirm
|
|
1773
|
-
// the finalize so teardown doesn't wait out the reconciler's cap.
|
|
1774
|
-
forceUpload: true
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
async function restoreOnBoot(bundle, cwd) {
|
|
1778
|
-
let gcsAvailable = false;
|
|
1779
|
-
let gcsFileCount;
|
|
1780
|
-
if (bundle.snapshotUrl) {
|
|
1781
|
-
const tmpTar = await downloadSnapshotToTemp(bundle.snapshotUrl);
|
|
1782
|
-
if (tmpTar) {
|
|
1783
|
-
try {
|
|
1784
|
-
const result = await applySnapshotTar(cwd, tmpTar);
|
|
1785
|
-
if (result.status === "extracted") {
|
|
1786
|
-
gcsAvailable = true;
|
|
1787
|
-
gcsFileCount = result.filesExtracted;
|
|
1788
|
-
}
|
|
1789
|
-
} catch {
|
|
1790
|
-
gcsAvailable = false;
|
|
1791
|
-
} finally {
|
|
1792
|
-
await rm2(tmpTar, { force: true }).catch(() => {
|
|
1793
|
-
});
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
const wipRestoreResult = gcsAvailable ? "none" : await restoreWipSnapshot(cwd, bundle.gitPlan.branch);
|
|
1798
|
-
const source = selectRestoreSource({ gcsAvailable, wipRestoreResult });
|
|
1799
|
-
return source === "gcs" ? { source, fileCount: gcsFileCount } : { source };
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
1580
|
// ../shared/dist/index.js
|
|
1803
1581
|
import { z } from "zod";
|
|
1804
1582
|
import { z as z2 } from "zod";
|
|
@@ -2262,9 +2040,10 @@ var SubmitCodeReviewResultRequestSchema = z3.object({
|
|
|
2262
2040
|
sessionId: z3.string(),
|
|
2263
2041
|
approved: z3.boolean(),
|
|
2264
2042
|
content: z3.string(),
|
|
2265
|
-
// Canonical risk
|
|
2266
|
-
//
|
|
2267
|
-
|
|
2043
|
+
// Canonical risk level the reviewer assigned to this change. Required on every
|
|
2044
|
+
// verdict — the reviewer must judge it. Applied authoritatively server-side
|
|
2045
|
+
// (may raise OR lower an already-set value; the reviewer has that authority).
|
|
2046
|
+
risk: riskLevelSchema,
|
|
2268
2047
|
// The commit SHA the reviewer actually reviewed. When present, the verdict is
|
|
2269
2048
|
// rejected unless the task is still at this SHA (guards against a late
|
|
2270
2049
|
// old-SHA verdict overwriting a newer review cycle).
|
|
@@ -2645,6 +2424,7 @@ var ListMyLiveSessionsRequestSchema = z4.object({
|
|
|
2645
2424
|
var ListProjectSessionGroupsRequestSchema = z4.object({
|
|
2646
2425
|
projectId: z4.string()
|
|
2647
2426
|
});
|
|
2427
|
+
var ListMyLiveSessionsAcrossProjectsRequestSchema = z4.object({});
|
|
2648
2428
|
var GetProjectAvailableTuisRequestSchema = z4.object({
|
|
2649
2429
|
projectId: z4.string()
|
|
2650
2430
|
});
|
|
@@ -3306,9 +3086,9 @@ var ClaudeCodeHarness = class {
|
|
|
3306
3086
|
};
|
|
3307
3087
|
|
|
3308
3088
|
// src/harness/pty/session.ts
|
|
3309
|
-
import { randomUUID
|
|
3310
|
-
import { mkdtemp
|
|
3311
|
-
import { join as
|
|
3089
|
+
import { randomUUID } from "crypto";
|
|
3090
|
+
import { mkdtemp, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
3091
|
+
import { join as join4, dirname } from "path";
|
|
3312
3092
|
|
|
3313
3093
|
// src/harness/pty/event-queue.ts
|
|
3314
3094
|
var AsyncEventQueue = class {
|
|
@@ -3589,8 +3369,8 @@ function mapTranscriptRecord(raw) {
|
|
|
3589
3369
|
// src/harness/pty/jsonl-tailer.ts
|
|
3590
3370
|
var POLL_INTERVAL_MS = 25;
|
|
3591
3371
|
var JsonlTailer = class {
|
|
3592
|
-
constructor(
|
|
3593
|
-
this.path =
|
|
3372
|
+
constructor(path2, onEvent, onRawRecord) {
|
|
3373
|
+
this.path = path2;
|
|
3594
3374
|
this.onEvent = onEvent;
|
|
3595
3375
|
this.onRawRecord = onRawRecord;
|
|
3596
3376
|
}
|
|
@@ -3852,15 +3632,15 @@ function mapChatRecords(raw) {
|
|
|
3852
3632
|
// src/harness/pty/settings.ts
|
|
3853
3633
|
import { mkdir, writeFile, chmod } from "fs/promises";
|
|
3854
3634
|
import { homedir } from "os";
|
|
3855
|
-
import { join
|
|
3635
|
+
import { join } from "path";
|
|
3856
3636
|
function claudeConfigHome() {
|
|
3857
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
3637
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude");
|
|
3858
3638
|
}
|
|
3859
3639
|
function projectSlug(cwd) {
|
|
3860
3640
|
return cwd.replace(/\//g, "-");
|
|
3861
3641
|
}
|
|
3862
3642
|
function sessionTranscriptPath(cwd, sessionId) {
|
|
3863
|
-
return
|
|
3643
|
+
return join(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
|
|
3864
3644
|
}
|
|
3865
3645
|
var ALLOW_RULES = [
|
|
3866
3646
|
"Bash",
|
|
@@ -4028,8 +3808,8 @@ function preToolUse(payload) {
|
|
|
4028
3808
|
}
|
|
4029
3809
|
`;
|
|
4030
3810
|
async function writeHookSettings(dir) {
|
|
4031
|
-
const helperPath =
|
|
4032
|
-
const settingsPath =
|
|
3811
|
+
const helperPath = join(dir, "hook-helper.cjs");
|
|
3812
|
+
const settingsPath = join(dir, "settings.json");
|
|
4033
3813
|
await mkdir(dir, { recursive: true });
|
|
4034
3814
|
await writeFile(helperPath, HOOK_HELPER_SOURCE, "utf8");
|
|
4035
3815
|
await chmod(helperPath, 493);
|
|
@@ -4147,7 +3927,7 @@ var PtyOutputCoalescer = class {
|
|
|
4147
3927
|
import { createServer as createServer2 } from "http";
|
|
4148
3928
|
import { z as z6 } from "zod";
|
|
4149
3929
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
4150
|
-
import { join as
|
|
3930
|
+
import { join as join2 } from "path";
|
|
4151
3931
|
import { randomBytes } from "crypto";
|
|
4152
3932
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4153
3933
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -4338,7 +4118,7 @@ async function startToolServers(mcpServers, tempDir) {
|
|
|
4338
4118
|
config[name] = { type: "http", url, headers: { Authorization: `Bearer ${token}` } };
|
|
4339
4119
|
}
|
|
4340
4120
|
if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null };
|
|
4341
|
-
const mcpConfigPath =
|
|
4121
|
+
const mcpConfigPath = join2(tempDir, "mcp-config.json");
|
|
4342
4122
|
await writeFile2(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
|
|
4343
4123
|
return { servers, mcpConfigPath };
|
|
4344
4124
|
}
|
|
@@ -4414,13 +4194,13 @@ ${tail}`);
|
|
|
4414
4194
|
}
|
|
4415
4195
|
|
|
4416
4196
|
// src/harness/pty/credentials.ts
|
|
4417
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile, rm
|
|
4197
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as writeFile3 } from "fs/promises";
|
|
4418
4198
|
import { homedir as homedir2 } from "os";
|
|
4419
|
-
import { join as
|
|
4199
|
+
import { join as join3 } from "path";
|
|
4420
4200
|
var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
4421
4201
|
var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4422
4202
|
function claudeCredentialsPath() {
|
|
4423
|
-
return
|
|
4203
|
+
return join3(claudeConfigHome(), ".credentials.json");
|
|
4424
4204
|
}
|
|
4425
4205
|
function isConveyorCloudEnv(env = process.env) {
|
|
4426
4206
|
return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
|
|
@@ -4465,9 +4245,9 @@ function planCredentialsWrite(input) {
|
|
|
4465
4245
|
if (fresh) return { action: "skip", reason: "current" };
|
|
4466
4246
|
return { action: "write", contents };
|
|
4467
4247
|
}
|
|
4468
|
-
async function readRaw(
|
|
4248
|
+
async function readRaw(path2) {
|
|
4469
4249
|
try {
|
|
4470
|
-
return await readFile(
|
|
4250
|
+
return await readFile(path2, "utf8");
|
|
4471
4251
|
} catch {
|
|
4472
4252
|
return null;
|
|
4473
4253
|
}
|
|
@@ -4509,10 +4289,10 @@ async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS
|
|
|
4509
4289
|
await sleep2(delaysMs[attempt]);
|
|
4510
4290
|
}
|
|
4511
4291
|
}
|
|
4512
|
-
function fsWriteIo(
|
|
4292
|
+
function fsWriteIo(path2, mode) {
|
|
4513
4293
|
return {
|
|
4514
|
-
write: (contents) => writeFile3(
|
|
4515
|
-
read: () => readRaw(
|
|
4294
|
+
write: (contents) => writeFile3(path2, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
|
|
4295
|
+
read: () => readRaw(path2)
|
|
4516
4296
|
};
|
|
4517
4297
|
}
|
|
4518
4298
|
async function ensureClaudeCredentials(env = process.env) {
|
|
@@ -4522,23 +4302,23 @@ async function ensureClaudeCredentials(env = process.env) {
|
|
|
4522
4302
|
await sanitizeApprovedApiKeys(token);
|
|
4523
4303
|
}
|
|
4524
4304
|
try {
|
|
4525
|
-
const
|
|
4305
|
+
const path2 = claudeCredentialsPath();
|
|
4526
4306
|
const plan = planCredentialsWrite({
|
|
4527
4307
|
isCloud,
|
|
4528
4308
|
token,
|
|
4529
|
-
existingRaw: await readRaw(
|
|
4309
|
+
existingRaw: await readRaw(path2),
|
|
4530
4310
|
now: Date.now()
|
|
4531
4311
|
});
|
|
4532
4312
|
if (plan.action === "skip") return;
|
|
4533
4313
|
await mkdir2(claudeConfigHome(), { recursive: true });
|
|
4534
|
-
const verified = await writeWithReadBackRetry(fsWriteIo(
|
|
4314
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path2, 384), plan.contents);
|
|
4535
4315
|
if (!verified) {
|
|
4536
4316
|
process.stderr.write(
|
|
4537
|
-
`[conveyor-agent] claude credentials read-back still stale after retries at ${
|
|
4317
|
+
`[conveyor-agent] claude credentials read-back still stale after retries at ${path2} \u2014 TUI may land on the login picker
|
|
4538
4318
|
`
|
|
4539
4319
|
);
|
|
4540
4320
|
}
|
|
4541
|
-
await chmod2(
|
|
4321
|
+
await chmod2(path2, 384).catch(() => {
|
|
4542
4322
|
});
|
|
4543
4323
|
} catch (err) {
|
|
4544
4324
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4567,12 +4347,12 @@ function planApprovedApiKeyCleanup(existingRaw, oauthToken) {
|
|
|
4567
4347
|
}
|
|
4568
4348
|
async function sanitizeApprovedApiKeys(oauthToken) {
|
|
4569
4349
|
try {
|
|
4570
|
-
const
|
|
4571
|
-
const cleaned = planApprovedApiKeyCleanup(await readRaw(
|
|
4350
|
+
const path2 = claudeJsonPath();
|
|
4351
|
+
const cleaned = planApprovedApiKeyCleanup(await readRaw(path2), oauthToken);
|
|
4572
4352
|
if (cleaned === null) return;
|
|
4573
|
-
const verified = await writeWithReadBackRetry(fsWriteIo(
|
|
4353
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path2), cleaned);
|
|
4574
4354
|
process.stderr.write(
|
|
4575
|
-
verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${
|
|
4355
|
+
verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${path2} \u2014 CLI may still see the poisoned entry
|
|
4576
4356
|
`
|
|
4577
4357
|
);
|
|
4578
4358
|
} catch (err) {
|
|
@@ -4583,7 +4363,7 @@ async function sanitizeApprovedApiKeys(oauthToken) {
|
|
|
4583
4363
|
}
|
|
4584
4364
|
function claudeJsonPath() {
|
|
4585
4365
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4586
|
-
return configDir ?
|
|
4366
|
+
return configDir ? join3(configDir, ".claude.json") : join3(homedir2(), ".claude.json");
|
|
4587
4367
|
}
|
|
4588
4368
|
function asRecord(value) {
|
|
4589
4369
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
@@ -4676,7 +4456,7 @@ function planClaudeJsonSeed(existingRaw, trustCwd, oauthIdentity) {
|
|
|
4676
4456
|
return changed ? JSON.stringify(config) : null;
|
|
4677
4457
|
}
|
|
4678
4458
|
function conveyorOauthMarkerPath() {
|
|
4679
|
-
return
|
|
4459
|
+
return join3(claudeConfigHome(), "conveyor-oauth-account.json");
|
|
4680
4460
|
}
|
|
4681
4461
|
function parseOauthIdentity(raw) {
|
|
4682
4462
|
if (!raw || raw.trim() === "") return null;
|
|
@@ -4709,8 +4489,8 @@ async function persistOauthIdentityMarker(configIdentity, markerIdentity) {
|
|
|
4709
4489
|
async function ensureClaudeOnboarding(env = process.env, trustCwd) {
|
|
4710
4490
|
try {
|
|
4711
4491
|
if (!isConveyorCloudEnv(env)) return;
|
|
4712
|
-
const
|
|
4713
|
-
const existingRaw = await readRaw(
|
|
4492
|
+
const path2 = claudeJsonPath();
|
|
4493
|
+
const existingRaw = await readRaw(path2);
|
|
4714
4494
|
const markerIdentity = parseOauthIdentity(await readRaw(conveyorOauthMarkerPath()));
|
|
4715
4495
|
await persistOauthIdentityMarker(
|
|
4716
4496
|
extractOauthIdentity(parseClaudeJson(existingRaw)),
|
|
@@ -4718,16 +4498,16 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
|
|
|
4718
4498
|
);
|
|
4719
4499
|
const contents = planClaudeJsonSeed(existingRaw, trustCwd, markerIdentity);
|
|
4720
4500
|
if (contents === null) return;
|
|
4721
|
-
const verified = await writeWithReadBackRetry(fsWriteIo(
|
|
4501
|
+
const verified = await writeWithReadBackRetry(fsWriteIo(path2), contents);
|
|
4722
4502
|
if (verified) {
|
|
4723
4503
|
process.stderr.write(
|
|
4724
4504
|
`[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
|
|
4725
4505
|
`
|
|
4726
4506
|
);
|
|
4727
4507
|
} else {
|
|
4728
|
-
const verify = await readRaw(
|
|
4508
|
+
const verify = await readRaw(path2);
|
|
4729
4509
|
process.stderr.write(
|
|
4730
|
-
`[conveyor-agent] claude onboarding seed read-back MISMATCH at ${
|
|
4510
|
+
`[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path2} after retries: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
|
|
4731
4511
|
`
|
|
4732
4512
|
);
|
|
4733
4513
|
}
|
|
@@ -4740,12 +4520,12 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
|
|
|
4740
4520
|
async function removeConveyorCredentials(env = process.env) {
|
|
4741
4521
|
try {
|
|
4742
4522
|
if (!isConveyorCloudEnv(env)) return;
|
|
4743
|
-
const
|
|
4744
|
-
const existing = parseClaudeAiOauth(await readRaw(
|
|
4523
|
+
const path2 = claudeCredentialsPath();
|
|
4524
|
+
const existing = parseClaudeAiOauth(await readRaw(path2));
|
|
4745
4525
|
if (existing && typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
|
|
4746
4526
|
return;
|
|
4747
4527
|
}
|
|
4748
|
-
await
|
|
4528
|
+
await rm(path2, { force: true });
|
|
4749
4529
|
} catch (err) {
|
|
4750
4530
|
const message = err instanceof Error ? err.message : String(err);
|
|
4751
4531
|
process.stderr.write(`[conveyor-agent] claude credentials removal failed: ${message}
|
|
@@ -5053,7 +4833,7 @@ var PtySession = class {
|
|
|
5053
4833
|
const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
|
|
5054
4834
|
await this.spawn(settingsPath, socketPath);
|
|
5055
4835
|
} else {
|
|
5056
|
-
this.tempDir = await
|
|
4836
|
+
this.tempDir = await mkdtemp(join4(sessionTempBase(), "conveyor-pty-"));
|
|
5057
4837
|
await this.spawn();
|
|
5058
4838
|
this.pushEvent({
|
|
5059
4839
|
type: "system",
|
|
@@ -5090,8 +4870,8 @@ var PtySession = class {
|
|
|
5090
4870
|
* paths spawn() must wire into the child's argv/env.
|
|
5091
4871
|
*/
|
|
5092
4872
|
async startStructuredEventSources(sessionId) {
|
|
5093
|
-
this.tempDir = await
|
|
5094
|
-
const socketPath =
|
|
4873
|
+
this.tempDir = await mkdtemp(join4(sessionTempBase(), "conveyor-pty-"));
|
|
4874
|
+
const socketPath = join4(this.tempDir, "hook.sock");
|
|
5095
4875
|
this.socket = new HookSocketServer(
|
|
5096
4876
|
socketPath,
|
|
5097
4877
|
(progress) => this.handleProgress(progress),
|
|
@@ -5145,7 +4925,7 @@ var PtySession = class {
|
|
|
5145
4925
|
* questionnaire resolves; see the field comment). */
|
|
5146
4926
|
emitSyntheticQuestionCard(questions) {
|
|
5147
4927
|
if (questions.length === 0 || typeof this.bridge?.sendChatEvent !== "function") return;
|
|
5148
|
-
const id = `aq-${
|
|
4928
|
+
const id = `aq-${randomUUID()}`;
|
|
5149
4929
|
this.pendingSyntheticQuestionIds.push(id);
|
|
5150
4930
|
this.sendChatEvent({
|
|
5151
4931
|
kind: "tool_use",
|
|
@@ -5268,7 +5048,7 @@ var PtySession = class {
|
|
|
5268
5048
|
this.activeQueue?.close();
|
|
5269
5049
|
this.activeQueue = null;
|
|
5270
5050
|
if (this.tempDir) {
|
|
5271
|
-
await
|
|
5051
|
+
await rm2(this.tempDir, { recursive: true, force: true });
|
|
5272
5052
|
this.tempDir = "";
|
|
5273
5053
|
}
|
|
5274
5054
|
}
|
|
@@ -5544,7 +5324,7 @@ var PtySession = class {
|
|
|
5544
5324
|
// src/harness/pty/config-home-health.ts
|
|
5545
5325
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
5546
5326
|
import { homedir as homedir3 } from "os";
|
|
5547
|
-
import { join as
|
|
5327
|
+
import { join as join5 } from "path";
|
|
5548
5328
|
var MOUNT_DISCONNECT_CODES = /* @__PURE__ */ new Set(["ENOTCONN", "EIO", "ESTALE", "ENXIO"]);
|
|
5549
5329
|
var MOUNT_DISCONNECT_MESSAGES = [
|
|
5550
5330
|
"socket is not connected",
|
|
@@ -5560,12 +5340,12 @@ function isMountDisconnectError(err) {
|
|
|
5560
5340
|
return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
|
|
5561
5341
|
}
|
|
5562
5342
|
function podLocalConfigHome() {
|
|
5563
|
-
return
|
|
5343
|
+
return join5(homedir3(), ".claude-local");
|
|
5564
5344
|
}
|
|
5565
5345
|
async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
5566
5346
|
const configHome = claudeConfigHome();
|
|
5567
5347
|
try {
|
|
5568
|
-
await mkdir4(
|
|
5348
|
+
await mkdir4(join5(configHome, "projects", projectSlug(cwd)), { recursive: true });
|
|
5569
5349
|
return { configHome, fellBack: false };
|
|
5570
5350
|
} catch (err) {
|
|
5571
5351
|
if (!isMountDisconnectError(err)) throw err;
|
|
@@ -5579,7 +5359,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
|
5579
5359
|
}
|
|
5580
5360
|
);
|
|
5581
5361
|
process.env.CLAUDE_CONFIG_DIR = fallback;
|
|
5582
|
-
await mkdir4(
|
|
5362
|
+
await mkdir4(join5(fallback, "projects", projectSlug(cwd)), { recursive: true });
|
|
5583
5363
|
return { configHome: fallback, fellBack: true };
|
|
5584
5364
|
}
|
|
5585
5365
|
}
|
|
@@ -6066,20 +5846,20 @@ function formatIncidents(incidents) {
|
|
|
6066
5846
|
|
|
6067
5847
|
// src/workbench/fs.ts
|
|
6068
5848
|
import { readFile as localReadFile, readdir as localReaddir, stat as localStat } from "fs/promises";
|
|
6069
|
-
async function readWorkspaceFile(
|
|
5849
|
+
async function readWorkspaceFile(path2) {
|
|
6070
5850
|
if (workbenchEnabled()) {
|
|
6071
|
-
return (await getWorkbenchClient().readFile(
|
|
5851
|
+
return (await getWorkbenchClient().readFile(path2)).toString("utf8");
|
|
6072
5852
|
}
|
|
6073
|
-
return localReadFile(
|
|
5853
|
+
return localReadFile(path2, "utf-8");
|
|
6074
5854
|
}
|
|
6075
|
-
function readWorkspaceDir(
|
|
6076
|
-
if (workbenchEnabled()) return getWorkbenchClient().readdir(
|
|
6077
|
-
return localReaddir(
|
|
5855
|
+
function readWorkspaceDir(path2) {
|
|
5856
|
+
if (workbenchEnabled()) return getWorkbenchClient().readdir(path2);
|
|
5857
|
+
return localReaddir(path2);
|
|
6078
5858
|
}
|
|
6079
|
-
async function statWorkspacePath(
|
|
6080
|
-
if (workbenchEnabled()) return getWorkbenchClient().stat(
|
|
5859
|
+
async function statWorkspacePath(path2) {
|
|
5860
|
+
if (workbenchEnabled()) return getWorkbenchClient().stat(path2);
|
|
6081
5861
|
try {
|
|
6082
|
-
const s = await localStat(
|
|
5862
|
+
const s = await localStat(path2);
|
|
6083
5863
|
return {
|
|
6084
5864
|
exists: true,
|
|
6085
5865
|
isFile: s.isFile(),
|
|
@@ -6091,8 +5871,8 @@ async function statWorkspacePath(path3) {
|
|
|
6091
5871
|
return { exists: false, isFile: false, isDirectory: false, size: 0, mtimeMs: 0 };
|
|
6092
5872
|
}
|
|
6093
5873
|
}
|
|
6094
|
-
async function workspacePathExists(
|
|
6095
|
-
return (await statWorkspacePath(
|
|
5874
|
+
async function workspacePathExists(path2) {
|
|
5875
|
+
return (await statWorkspacePath(path2)).exists;
|
|
6096
5876
|
}
|
|
6097
5877
|
|
|
6098
5878
|
// src/execution/tag-context-resolver.ts
|
|
@@ -6463,13 +6243,15 @@ function buildPlanDocumentationSection(context) {
|
|
|
6463
6243
|
const hasPlan = !!context?.plan?.trim();
|
|
6464
6244
|
return [
|
|
6465
6245
|
``,
|
|
6466
|
-
`### Plan
|
|
6246
|
+
`### Plan first, then BUILD \u2014 the plan is a step, NOT the deliverable`,
|
|
6467
6247
|
`- The card is already In Progress and advances automatically (In Progress \u2192 Review PR when you open the PR). There is no plan-approval step.`,
|
|
6468
6248
|
...hasPlan ? [
|
|
6469
|
-
`- A plan is already saved on the card. Keep it current with update_task_plan if your approach diverges materially
|
|
6249
|
+
`- A plan is already saved on the card. Keep it current with update_task_plan if your approach diverges materially \u2014 then IMPLEMENT it in code. The saved plan is not the deliverable; the working implementation is.`
|
|
6470
6250
|
] : [
|
|
6471
|
-
`- No plan is saved yet: BEFORE writing any code, investigate briefly (search first, read only critical files) and save a concise implementation plan with update_task_plan (file:line citations).
|
|
6251
|
+
`- No plan is saved yet: BEFORE writing any code, investigate briefly (search first, read only critical files) and save a concise implementation plan with update_task_plan (file:line citations). Saving the plan is a planning step, not the goal \u2014 immediately move on to WRITING THE CODE that implements it; never pause for approval.`
|
|
6472
6252
|
],
|
|
6253
|
+
`- Your goal is to BUILD the change, not to produce a plan. After the plan is posted, actually implement it: edit source files, make the change work, then verify. Do NOT stop, go idle, or open a PR the moment the plan exists.`,
|
|
6254
|
+
`- Your pull request MUST contain the actual code implementation. Never open a plan-only or empty-diff PR \u2014 a PR that just records the plan is never the goal of auto mode unless the task explicitly asks ONLY for a plan. (If the task genuinely needs no code changes, don't open a PR at all \u2014 finish per the section below.)`,
|
|
6473
6255
|
`- Identification auto-fills title, story points, and icon with quick AI guesses. After exploring, refine the title and story points with update_task_properties if they look like placeholders. Icons are automatic \u2014 never set them.`
|
|
6474
6256
|
];
|
|
6475
6257
|
}
|
|
@@ -6740,6 +6522,14 @@ function buildReviewPrompt(context) {
|
|
|
6740
6522
|
`- Explain what's wrong and suggest fixes`,
|
|
6741
6523
|
`- Focus on substantive issues, not style nitpicks (linting handles that)`,
|
|
6742
6524
|
``,
|
|
6525
|
+
`#### Risk level (required on BOTH tools):`,
|
|
6526
|
+
`Every verdict MUST include a \`risk\` level \u2014 judge it by the surface area the change touches:`,
|
|
6527
|
+
`- \`critical\`: touches critical/foundational surface (auth, billing, data integrity, migrations)`,
|
|
6528
|
+
`- \`high\`: touches important surface with broad blast radius`,
|
|
6529
|
+
`- \`medium\`: moderate, contained surface area`,
|
|
6530
|
+
`- \`low\`: small or isolated change`,
|
|
6531
|
+
`The task may already have a risk level set. If your review makes you disagree with it, set the level you believe is correct \u2014 you have the authority to override it in either direction.`,
|
|
6532
|
+
``,
|
|
6743
6533
|
`### Previous Review Feedback`,
|
|
6744
6534
|
`If previous review feedback is present in the chat history, verify those specific issues were addressed before raising new concerns.`,
|
|
6745
6535
|
``,
|
|
@@ -7115,7 +6905,7 @@ function buildFreshLeafInstructions(context, isAutoMode) {
|
|
|
7115
6905
|
`Your FIRST action should be reading the relevant source files mentioned in the plan, then writing code. Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`
|
|
7116
6906
|
] : [
|
|
7117
6907
|
`No plan is saved on this card yet. Investigate the task briefly (search first, read only critical files), then save a concise implementation plan with update_task_plan (file:line citations) BEFORE you start writing code.`,
|
|
7118
|
-
`
|
|
6908
|
+
`The plan is a step, not the goal: the moment it's posted, IMPLEMENT it in code \u2014 do NOT pause, wait for approval, or treat the posted plan as the deliverable. The card is already In Progress and advances automatically.`,
|
|
7119
6909
|
`Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`
|
|
7120
6910
|
];
|
|
7121
6911
|
parts.push(
|
|
@@ -7127,9 +6917,10 @@ function buildFreshLeafInstructions(context, isAutoMode) {
|
|
|
7127
6917
|
if (isAutoMode) {
|
|
7128
6918
|
parts.push(
|
|
7129
6919
|
`
|
|
7130
|
-
CRITICAL: You are in Auto mode.
|
|
7131
|
-
`Do NOT summarize the plan or say "ready to implement" \u2014 start implementing immediately.`,
|
|
7132
|
-
`
|
|
6920
|
+
CRITICAL: You are in Auto mode. Your job is to BUILD the change, not to produce a plan \u2014 making a plan and opening a PR of the plan is NOT the goal. The plan is only an intermediate step; you must then write the code that implements it.`,
|
|
6921
|
+
`Do NOT report status, ask for confirmation, or go idle without making code changes. Do NOT summarize the plan or say "ready to implement" \u2014 start implementing immediately.`,
|
|
6922
|
+
`Your pull request MUST contain the actual code implementation. Never open a plan-only or empty-diff PR. (If the task genuinely needs no code changes, do not open a PR \u2014 deliver the result in chat and mark the card Complete.)`,
|
|
6923
|
+
`When the implementation is complete and verified, you MUST use mcp__conveyor__create_pull_request to open a PR before finishing.`,
|
|
7133
6924
|
`If you are genuinely blocked, explain the specific blocker \u2014 do not go idle silently.`
|
|
7134
6925
|
);
|
|
7135
6926
|
}
|
|
@@ -7868,7 +7659,7 @@ function buildMutationTools(connection, config) {
|
|
|
7868
7659
|
|
|
7869
7660
|
// src/tools/attachment-tools.ts
|
|
7870
7661
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
7871
|
-
import { basename, extname, isAbsolute, join as
|
|
7662
|
+
import { basename, extname, isAbsolute, join as join6 } from "path";
|
|
7872
7663
|
import { z as z10 } from "zod";
|
|
7873
7664
|
var IMAGE_MIME_BY_EXT = {
|
|
7874
7665
|
".png": "image/png",
|
|
@@ -7885,9 +7676,9 @@ function buildUploadAttachmentTool(connection, config) {
|
|
|
7885
7676
|
path: z10.string().describe("Path to the image file \u2014 absolute, or relative to the workspace root"),
|
|
7886
7677
|
title: z10.string().optional().describe("Short caption posted with the image (defaults to the file name)")
|
|
7887
7678
|
},
|
|
7888
|
-
async ({ path:
|
|
7679
|
+
async ({ path: path2, title }) => {
|
|
7889
7680
|
try {
|
|
7890
|
-
const filePath = isAbsolute(
|
|
7681
|
+
const filePath = isAbsolute(path2) ? path2 : join6(config.workspaceDir, path2);
|
|
7891
7682
|
const mimeType = IMAGE_MIME_BY_EXT[extname(filePath).toLowerCase()];
|
|
7892
7683
|
if (!mimeType) {
|
|
7893
7684
|
return textResult(
|
|
@@ -8489,20 +8280,18 @@ async function endReviewSession(connection, reason) {
|
|
|
8489
8280
|
reason
|
|
8490
8281
|
});
|
|
8491
8282
|
}
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
if (issues.some((i) => i.severity === "major")) return "high";
|
|
8495
|
-
return "medium";
|
|
8496
|
-
}
|
|
8283
|
+
var RISK_LEVELS2 = ["critical", "high", "medium", "low"];
|
|
8284
|
+
var riskDescription = "REQUIRED. The risk level this change carries, judged by the surface area it touches: critical = touches critical/foundational surface, high = important surface, medium = moderate, low = small/isolated. Set this on every verdict. You have authority to override a risk level already set on the task if you disagree with it.";
|
|
8497
8285
|
function buildCodeReviewTools(connection) {
|
|
8498
8286
|
return [
|
|
8499
8287
|
defineTool(
|
|
8500
8288
|
"approve_code_review",
|
|
8501
|
-
"Approve the code review and exit. Use when the diff passes all review criteria.
|
|
8289
|
+
"Approve the code review and exit. Use when the diff passes all review criteria. Requires a summary and a risk level \u2014 for changes, use request_code_changes with a structured issues[] list.",
|
|
8502
8290
|
{
|
|
8503
|
-
summary: z14.string().describe("Brief summary of what was reviewed and why it looks good")
|
|
8291
|
+
summary: z14.string().describe("Brief summary of what was reviewed and why it looks good"),
|
|
8292
|
+
risk: z14.enum(RISK_LEVELS2).describe(riskDescription)
|
|
8504
8293
|
},
|
|
8505
|
-
async ({ summary }) => {
|
|
8294
|
+
async ({ summary, risk }) => {
|
|
8506
8295
|
const content = `**Code Review: Approved** :white_check_mark:
|
|
8507
8296
|
|
|
8508
8297
|
${summary}`;
|
|
@@ -8510,7 +8299,7 @@ ${summary}`;
|
|
|
8510
8299
|
sessionId: connection.sessionId,
|
|
8511
8300
|
approved: true,
|
|
8512
8301
|
content,
|
|
8513
|
-
risk
|
|
8302
|
+
risk
|
|
8514
8303
|
});
|
|
8515
8304
|
connection.sendEvent({
|
|
8516
8305
|
type: "code_review_complete",
|
|
@@ -8533,9 +8322,10 @@ ${summary}`;
|
|
|
8533
8322
|
description: z14.string().describe("What is wrong and how to fix it")
|
|
8534
8323
|
})
|
|
8535
8324
|
).describe("List of issues found during review"),
|
|
8536
|
-
summary: z14.string().describe("Brief overall summary of the review findings")
|
|
8325
|
+
summary: z14.string().describe("Brief overall summary of the review findings"),
|
|
8326
|
+
risk: z14.enum(RISK_LEVELS2).describe(riskDescription)
|
|
8537
8327
|
},
|
|
8538
|
-
async ({ issues, summary }) => {
|
|
8328
|
+
async ({ issues, summary, risk }) => {
|
|
8539
8329
|
const issueLines = issues.map((issue) => {
|
|
8540
8330
|
const loc = issue.line ? `:${issue.line}` : "";
|
|
8541
8331
|
return `- **[${issue.severity}]** \`${issue.file}${loc}\`: ${issue.description}`;
|
|
@@ -8549,7 +8339,7 @@ ${issueLines}`;
|
|
|
8549
8339
|
sessionId: connection.sessionId,
|
|
8550
8340
|
approved: false,
|
|
8551
8341
|
content,
|
|
8552
|
-
risk
|
|
8342
|
+
risk
|
|
8553
8343
|
});
|
|
8554
8344
|
connection.sendEvent({
|
|
8555
8345
|
type: "code_review_complete",
|
|
@@ -8649,7 +8439,7 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
|
|
|
8649
8439
|
|
|
8650
8440
|
// src/harness/pty/adapters/types.ts
|
|
8651
8441
|
import { accessSync, constants, statSync } from "fs";
|
|
8652
|
-
import { join as
|
|
8442
|
+
import { join as join7 } from "path";
|
|
8653
8443
|
var TuiUnavailableError = class extends Error {
|
|
8654
8444
|
constructor(tui, message) {
|
|
8655
8445
|
super(message);
|
|
@@ -8658,10 +8448,10 @@ var TuiUnavailableError = class extends Error {
|
|
|
8658
8448
|
}
|
|
8659
8449
|
tui;
|
|
8660
8450
|
};
|
|
8661
|
-
function isExecutable(
|
|
8451
|
+
function isExecutable(path2) {
|
|
8662
8452
|
try {
|
|
8663
|
-
if (!statSync(
|
|
8664
|
-
accessSync(
|
|
8453
|
+
if (!statSync(path2).isFile()) return false;
|
|
8454
|
+
accessSync(path2, constants.X_OK);
|
|
8665
8455
|
return true;
|
|
8666
8456
|
} catch {
|
|
8667
8457
|
return false;
|
|
@@ -8673,7 +8463,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
8673
8463
|
}
|
|
8674
8464
|
for (const dir of (env.PATH ?? "").split(":")) {
|
|
8675
8465
|
if (!dir) continue;
|
|
8676
|
-
const candidate =
|
|
8466
|
+
const candidate = join7(dir, binary);
|
|
8677
8467
|
if (isExecutable(candidate)) return candidate;
|
|
8678
8468
|
}
|
|
8679
8469
|
return null;
|
|
@@ -9537,10 +9327,10 @@ function resolveSessionStart(lineageKey, cwd) {
|
|
|
9537
9327
|
}
|
|
9538
9328
|
return { sessionId: sessionUuid };
|
|
9539
9329
|
}
|
|
9540
|
-
function repairTornSessionFile(
|
|
9330
|
+
function repairTornSessionFile(path2) {
|
|
9541
9331
|
try {
|
|
9542
|
-
if (!existsSync2(
|
|
9543
|
-
const content = readFileSync(
|
|
9332
|
+
if (!existsSync2(path2)) return false;
|
|
9333
|
+
const content = readFileSync(path2, "utf8");
|
|
9544
9334
|
if (content.length === 0) return false;
|
|
9545
9335
|
let keepEnd = content.length;
|
|
9546
9336
|
if (!content.endsWith("\n")) {
|
|
@@ -9559,9 +9349,9 @@ function repairTornSessionFile(path3) {
|
|
|
9559
9349
|
keepEnd = prevNewline + 1;
|
|
9560
9350
|
}
|
|
9561
9351
|
if (keepEnd === content.length) return false;
|
|
9562
|
-
truncateSync(
|
|
9352
|
+
truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
|
|
9563
9353
|
logger2.warn("Repaired torn transcript before resume", {
|
|
9564
|
-
path:
|
|
9354
|
+
path: path2,
|
|
9565
9355
|
trimmedBytes: content.length - keepEnd
|
|
9566
9356
|
});
|
|
9567
9357
|
return true;
|
|
@@ -10370,7 +10160,7 @@ var QueryBridge = class {
|
|
|
10370
10160
|
|
|
10371
10161
|
// src/runner/session-runner-helpers.ts
|
|
10372
10162
|
import { readFileSync as readFileSync2 } from "fs";
|
|
10373
|
-
import { dirname as dirname2, join as
|
|
10163
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
10374
10164
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10375
10165
|
function mapChatHistory(messages) {
|
|
10376
10166
|
if (!messages) return [];
|
|
@@ -10399,7 +10189,7 @@ function readAgentVersion() {
|
|
|
10399
10189
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
10400
10190
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
10401
10191
|
try {
|
|
10402
|
-
const pkg = JSON.parse(readFileSync2(
|
|
10192
|
+
const pkg = JSON.parse(readFileSync2(join8(here, rel), "utf-8"));
|
|
10403
10193
|
if (pkg.version) return pkg.version;
|
|
10404
10194
|
} catch {
|
|
10405
10195
|
}
|
|
@@ -10702,10 +10492,10 @@ import { access, stat as stat2 } from "fs/promises";
|
|
|
10702
10492
|
var DEFAULT_FAILED_PATH = "/workspaces/.conveyor-git-failed";
|
|
10703
10493
|
var DEFAULT_TIMEOUT_MS = 6e5;
|
|
10704
10494
|
var DEFAULT_POLL_MS = 200;
|
|
10705
|
-
async function fileExists(
|
|
10495
|
+
async function fileExists(path2) {
|
|
10706
10496
|
try {
|
|
10707
|
-
await access(
|
|
10708
|
-
const s = await stat2(
|
|
10497
|
+
await access(path2);
|
|
10498
|
+
const s = await stat2(path2);
|
|
10709
10499
|
return s.isFile();
|
|
10710
10500
|
} catch {
|
|
10711
10501
|
return false;
|
|
@@ -10820,9 +10610,9 @@ var DEFAULT_PROC_PATHS = ["/proc/net/tcp", "/proc/net/tcp6"];
|
|
|
10820
10610
|
async function readProcListeningPorts(procPaths = DEFAULT_PROC_PATHS) {
|
|
10821
10611
|
const sockets = [];
|
|
10822
10612
|
let readable = false;
|
|
10823
|
-
for (const
|
|
10613
|
+
for (const path2 of procPaths) {
|
|
10824
10614
|
try {
|
|
10825
|
-
const content = await readFile3(
|
|
10615
|
+
const content = await readFile3(path2, "utf8");
|
|
10826
10616
|
readable = true;
|
|
10827
10617
|
sockets.push(...parseProcNetTcpListeners(content));
|
|
10828
10618
|
} catch {
|
|
@@ -11049,7 +10839,7 @@ async function handlePullBranch(workDir, branch) {
|
|
|
11049
10839
|
|
|
11050
10840
|
// src/runner/heavy-gate.ts
|
|
11051
10841
|
import { readFileSync as readFileSync3 } from "fs";
|
|
11052
|
-
import
|
|
10842
|
+
import path from "path";
|
|
11053
10843
|
var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
|
|
11054
10844
|
function runDir() {
|
|
11055
10845
|
return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
|
|
@@ -11065,7 +10855,7 @@ function pidAlive(pid) {
|
|
|
11065
10855
|
function isHeavyGateActive() {
|
|
11066
10856
|
for (const key of GATE_KEYS) {
|
|
11067
10857
|
try {
|
|
11068
|
-
const raw = readFileSync3(
|
|
10858
|
+
const raw = readFileSync3(path.join(runDir(), `${key}.pid`), "utf8").trim();
|
|
11069
10859
|
const pid = Number.parseInt(raw, 10);
|
|
11070
10860
|
if (Number.isInteger(pid) && pid > 0 && pidAlive(pid)) return true;
|
|
11071
10861
|
} catch {
|
|
@@ -11110,6 +10900,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
11110
10900
|
portDiscovery;
|
|
11111
10901
|
/** Main event-loop lag measurement, shared with the heartbeat worker. */
|
|
11112
10902
|
loopLag = new LoopLagMonitor();
|
|
10903
|
+
/** Boot supervisor handle, set post-construction once it's known (it is
|
|
10904
|
+
* started alongside connect(), before the runner itself exists in some
|
|
10905
|
+
* call sites) — notified when the core loop goes live so it can release
|
|
10906
|
+
* the app's start command. Optional: never set in tests/paths that don't
|
|
10907
|
+
* wire a supervisor. */
|
|
10908
|
+
workspaceCommands = null;
|
|
11113
10909
|
constructor(config, callbacks, deps = {}) {
|
|
11114
10910
|
this.config = config;
|
|
11115
10911
|
this.callbacks = callbacks;
|
|
@@ -11156,6 +10952,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
11156
10952
|
get isStopped() {
|
|
11157
10953
|
return this.stopped;
|
|
11158
10954
|
}
|
|
10955
|
+
/** Wire the boot supervisor handle post-construction — cli.ts constructs it
|
|
10956
|
+
* via startWorkspaceCommandsAfterConnect() (which needs connect() to already
|
|
10957
|
+
* be callable), so it can't be a constructor dependency. */
|
|
10958
|
+
setWorkspaceCommands(supervisor) {
|
|
10959
|
+
this.workspaceCommands = supervisor;
|
|
10960
|
+
}
|
|
11159
10961
|
// ── Main lifecycle ─────────────────────────────────────────────────
|
|
11160
10962
|
/**
|
|
11161
10963
|
* Establish the API connection, wire callbacks, and join the session room.
|
|
@@ -11233,39 +11035,27 @@ var SessionRunner = class _SessionRunner {
|
|
|
11233
11035
|
await this.shutdown("error");
|
|
11234
11036
|
return;
|
|
11235
11037
|
}
|
|
11236
|
-
if (
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11038
|
+
if (this.fullContext?.githubBranch) {
|
|
11039
|
+
const ok = await ensureOnTaskBranch(
|
|
11040
|
+
this.config.workspaceDir,
|
|
11041
|
+
this.fullContext.githubBranch,
|
|
11042
|
+
this.fullContext.baseBranch ?? void 0
|
|
11043
|
+
);
|
|
11044
|
+
if (!ok) {
|
|
11045
|
+
process.stderr.write("[conveyor-agent] WARNING: task-branch checkout failed\n");
|
|
11242
11046
|
}
|
|
11243
11047
|
}
|
|
11244
11048
|
if (!this.stopped) {
|
|
11245
11049
|
this.lifecycle.startGitFlush();
|
|
11246
11050
|
}
|
|
11247
11051
|
if (this.fullContext?.githubBranch) {
|
|
11248
|
-
const
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
if (restore.source !== "clean") {
|
|
11255
|
-
process.stderr.write(
|
|
11256
|
-
`[conveyor-agent] WorkPreservation restore: source=${restore.source}${restore.fileCount === void 0 ? "" : ` files=${restore.fileCount}`}
|
|
11257
|
-
`
|
|
11258
|
-
);
|
|
11259
|
-
}
|
|
11260
|
-
} else {
|
|
11261
|
-
const restored = await restoreWipSnapshot(
|
|
11262
|
-
this.config.workspaceDir,
|
|
11263
|
-
this.fullContext.githubBranch
|
|
11264
|
-
);
|
|
11265
|
-
if (restored !== "none") {
|
|
11266
|
-
process.stderr.write(`[conveyor-agent] WIP snapshot restore: ${restored}
|
|
11052
|
+
const restored = await restoreWipSnapshot(
|
|
11053
|
+
this.config.workspaceDir,
|
|
11054
|
+
this.fullContext.githubBranch
|
|
11055
|
+
);
|
|
11056
|
+
if (restored !== "none") {
|
|
11057
|
+
process.stderr.write(`[conveyor-agent] WIP snapshot restore: ${restored}
|
|
11267
11058
|
`);
|
|
11268
|
-
}
|
|
11269
11059
|
}
|
|
11270
11060
|
}
|
|
11271
11061
|
this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
|
|
@@ -11300,6 +11090,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
11300
11090
|
`
|
|
11301
11091
|
);
|
|
11302
11092
|
}
|
|
11093
|
+
this.workspaceCommands?.notifyLoopReady();
|
|
11303
11094
|
while (!this.stopped) {
|
|
11304
11095
|
if (this._state !== "idle") await this.setState("idle");
|
|
11305
11096
|
await this.coreLoop();
|
|
@@ -11369,6 +11160,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
11369
11160
|
this.interrupted = false;
|
|
11370
11161
|
continue;
|
|
11371
11162
|
}
|
|
11163
|
+
await this.flushWipNow("WIP: end of turn");
|
|
11372
11164
|
if (!this.stopped) await this.setState("idle");
|
|
11373
11165
|
} else if (this._state === "error") {
|
|
11374
11166
|
await this.setState("idle");
|
|
@@ -11395,6 +11187,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
11395
11187
|
"[conveyor-agent] Completed \u2014 entering dormant idle (staying connected)\n"
|
|
11396
11188
|
);
|
|
11397
11189
|
}
|
|
11190
|
+
await this.flushWipNow("WIP: turn complete");
|
|
11398
11191
|
this.pendingMessages.length = 0;
|
|
11399
11192
|
if (this._state !== "idle") await this.setState("idle");
|
|
11400
11193
|
const remainingMs = Math.max(0, this.dormantDeadline - Date.now());
|
|
@@ -11614,6 +11407,36 @@ var SessionRunner = class _SessionRunner {
|
|
|
11614
11407
|
}
|
|
11615
11408
|
}
|
|
11616
11409
|
// ── Stop / soft-stop ───────────────────────────────────────────────
|
|
11410
|
+
/** Shared token-refresh closure for the git-flush call sites (periodic
|
|
11411
|
+
* backstop + per-turn flush). Never throws — returns undefined so the
|
|
11412
|
+
* caller falls back to its existing remote credentials. */
|
|
11413
|
+
async refreshGithubTokenForFlush() {
|
|
11414
|
+
try {
|
|
11415
|
+
const res = await this.connection.call("refreshGithubToken", {
|
|
11416
|
+
sessionId: this.connection.sessionId
|
|
11417
|
+
});
|
|
11418
|
+
return res.token;
|
|
11419
|
+
} catch {
|
|
11420
|
+
return void 0;
|
|
11421
|
+
}
|
|
11422
|
+
}
|
|
11423
|
+
/** Push uncommitted work to the conveyor-wip ref after a turn. Git-only (no
|
|
11424
|
+
* GCS). Primary-branch only — cheap enough to run every turn. Shares the
|
|
11425
|
+
* in-flight guard with the periodic backstop so a turn-end flush and a
|
|
11426
|
+
* timer tick never overlap. Best-effort, never throws. */
|
|
11427
|
+
async flushWipNow(wipMessage) {
|
|
11428
|
+
if (this.periodicFlushInFlight || this.stopped) return;
|
|
11429
|
+
this.periodicFlushInFlight = true;
|
|
11430
|
+
try {
|
|
11431
|
+
await flushPendingChanges(this.config.workspaceDir, {
|
|
11432
|
+
wipMessage,
|
|
11433
|
+
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
11434
|
+
});
|
|
11435
|
+
} catch {
|
|
11436
|
+
} finally {
|
|
11437
|
+
this.periodicFlushInFlight = false;
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11617
11440
|
/** Periodic best-effort WIP commit + push during normal agent execution.
|
|
11618
11441
|
* Covers ungraceful pod termination (OOMKilled, node crash/eviction) where
|
|
11619
11442
|
* the preStop hook + SIGTERM flush don't get a chance to run. No-ops on a
|
|
@@ -11632,16 +11455,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
11632
11455
|
try {
|
|
11633
11456
|
const result = await flushAllPendingWork(this.config.workspaceDir, {
|
|
11634
11457
|
wipMessage: "WIP: periodic auto-commit",
|
|
11635
|
-
refreshToken:
|
|
11636
|
-
try {
|
|
11637
|
-
const res = await this.connection.call("refreshGithubToken", {
|
|
11638
|
-
sessionId: this.connection.sessionId
|
|
11639
|
-
});
|
|
11640
|
-
return res.token;
|
|
11641
|
-
} catch {
|
|
11642
|
-
return void 0;
|
|
11643
|
-
}
|
|
11644
|
-
}
|
|
11458
|
+
refreshToken: () => this.refreshGithubTokenForFlush()
|
|
11645
11459
|
});
|
|
11646
11460
|
if (result.hadWork) {
|
|
11647
11461
|
process.stderr.write(
|
|
@@ -11649,7 +11463,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
11649
11463
|
`
|
|
11650
11464
|
);
|
|
11651
11465
|
}
|
|
11652
|
-
await this.uploadGcsSnapshotIfConfigured();
|
|
11653
11466
|
} catch {
|
|
11654
11467
|
} finally {
|
|
11655
11468
|
this.periodicFlushInFlight = false;
|
|
@@ -11675,22 +11488,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
11675
11488
|
});
|
|
11676
11489
|
}
|
|
11677
11490
|
}
|
|
11678
|
-
/** PUT the WorkPreservation snapshot tar to the bundle's capability URL
|
|
11679
|
-
* when this pod is v3 (CONVEYOR_SNAPSHOT_UPLOAD_URL set). Never throws. */
|
|
11680
|
-
async uploadGcsSnapshotIfConfigured() {
|
|
11681
|
-
const uploadUrl = process.env.CONVEYOR_SNAPSHOT_UPLOAD_URL;
|
|
11682
|
-
if (!uploadUrl) return;
|
|
11683
|
-
try {
|
|
11684
|
-
const gcs = await uploadSnapshotToGcs(this.config.workspaceDir, uploadUrl);
|
|
11685
|
-
if (gcs.uploaded) {
|
|
11686
|
-
process.stderr.write(
|
|
11687
|
-
`[conveyor-agent] WorkPreservation GCS snapshot: files=${gcs.fileCount} bytes=${gcs.sizeBytes}
|
|
11688
|
-
`
|
|
11689
|
-
);
|
|
11690
|
-
}
|
|
11691
|
-
} catch {
|
|
11692
|
-
}
|
|
11693
|
-
}
|
|
11694
11491
|
/** Best-effort WIP commit + push on shutdown so in-flight work isn't lost
|
|
11695
11492
|
* when a claudespace pod is killed. Must be called BEFORE stop() so the
|
|
11696
11493
|
* connection is still alive for token refresh. Never throws. */
|
|
@@ -11715,7 +11512,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
11715
11512
|
`
|
|
11716
11513
|
);
|
|
11717
11514
|
}
|
|
11718
|
-
await this.uploadGcsSnapshotIfConfigured();
|
|
11719
11515
|
} catch (err) {
|
|
11720
11516
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11721
11517
|
process.stderr.write(`[conveyor-agent] Shutdown git flush failed: ${msg}
|
|
@@ -11863,37 +11659,6 @@ var SessionRunner = class _SessionRunner {
|
|
|
11863
11659
|
this.connection.onPullBranch(({ branch }) => {
|
|
11864
11660
|
void handlePullBranch(this.config.workspaceDir, branch);
|
|
11865
11661
|
});
|
|
11866
|
-
this.connection.onFinalizeSnapshot(() => void this.finalizeSnapshotNow());
|
|
11867
|
-
}
|
|
11868
|
-
/** Eager finalize snapshot triggered by the reconciler's sleep signal
|
|
11869
|
-
* (session:finalizeSnapshot). Runs a full workspace capture and uploads it
|
|
11870
|
-
* NOW so the sleep confirms on this snapshot instead of waiting for the
|
|
11871
|
-
* ~2min periodic flush. Sidecar DB state is intentionally NOT captured — it
|
|
11872
|
-
* is no longer durable across sleep/wake (#2623); only the agent's
|
|
11873
|
-
* /workspace rides the snapshot. Best-effort: on failure the
|
|
11874
|
-
* periodic/shutdown path stays the fallback. No-op on non-v3 pods. */
|
|
11875
|
-
async finalizeSnapshotNow() {
|
|
11876
|
-
const uploadUrl = process.env.CONVEYOR_SNAPSHOT_UPLOAD_URL;
|
|
11877
|
-
if (!uploadUrl || this.stopped) return;
|
|
11878
|
-
try {
|
|
11879
|
-
await finalizeForSleep({
|
|
11880
|
-
cwd: this.config.workspaceDir,
|
|
11881
|
-
branch: this.fullContext?.githubBranch ?? "",
|
|
11882
|
-
snapshotUploadUrl: uploadUrl,
|
|
11883
|
-
refreshToken: async () => {
|
|
11884
|
-
try {
|
|
11885
|
-
const res = await this.connection.call("refreshGithubToken", {
|
|
11886
|
-
sessionId: this.connection.sessionId
|
|
11887
|
-
});
|
|
11888
|
-
return res.token;
|
|
11889
|
-
} catch {
|
|
11890
|
-
return void 0;
|
|
11891
|
-
}
|
|
11892
|
-
}
|
|
11893
|
-
});
|
|
11894
|
-
process.stderr.write("[conveyor-agent] Finalize snapshot uploaded on sleep signal\n");
|
|
11895
|
-
} catch {
|
|
11896
|
-
}
|
|
11897
11662
|
}
|
|
11898
11663
|
/** Proactively refresh the GitHub token before the 1-hour expiry. */
|
|
11899
11664
|
async refreshGithubToken() {
|
|
@@ -11989,12 +11754,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
11989
11754
|
};
|
|
11990
11755
|
|
|
11991
11756
|
// src/setup/config.ts
|
|
11992
|
-
import { join as
|
|
11757
|
+
import { join as join9 } from "path";
|
|
11993
11758
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
11994
11759
|
var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
|
|
11995
11760
|
async function loadForwardPorts(workspaceDir) {
|
|
11996
11761
|
try {
|
|
11997
|
-
const raw = await readWorkspaceFile(
|
|
11762
|
+
const raw = await readWorkspaceFile(join9(workspaceDir, DEVCONTAINER_PATH));
|
|
11998
11763
|
const parsed = JSON.parse(raw);
|
|
11999
11764
|
const ports = (parsed.forwardPorts ?? []).filter(
|
|
12000
11765
|
(p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
|
|
@@ -12082,16 +11847,10 @@ export {
|
|
|
12082
11847
|
sampleKeyUsage,
|
|
12083
11848
|
awaitGitReady,
|
|
12084
11849
|
PortDiscovery,
|
|
12085
|
-
uploadSnapshotToGcs,
|
|
12086
|
-
captureSnapshot,
|
|
12087
|
-
startPeriodic,
|
|
12088
|
-
stop,
|
|
12089
|
-
finalizeForSleep,
|
|
12090
|
-
restoreOnBoot,
|
|
12091
11850
|
SessionRunner,
|
|
12092
11851
|
loadForwardPorts,
|
|
12093
11852
|
buildSessionPreviewPorts,
|
|
12094
11853
|
loadConveyorConfig,
|
|
12095
11854
|
unshallowRepo
|
|
12096
11855
|
};
|
|
12097
|
-
//# sourceMappingURL=chunk-
|
|
11856
|
+
//# sourceMappingURL=chunk-SAHOFQBQ.js.map
|