@rallycry/conveyor-agent 11.0.17 → 11.0.18
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/{boot-CCRZMY2P.js → boot-4ZNOCKFZ.js} +4 -2
- package/dist/chunk-372R6E4C.js +147 -0
- package/dist/{chunk-7TSQXIN3.js → chunk-EIHZRSYI.js} +16 -9
- package/dist/{chunk-X2MKHJBZ.js → chunk-GZRZGBIK.js} +2 -2
- package/dist/{chunk-B54XFOXL.js → chunk-SQM2BQ7H.js} +32 -0
- package/dist/{chunk-QLMNQSJL.js → chunk-W5INK3NE.js} +1 -1
- package/dist/{chunk-M4SD2ESQ.js → chunk-WS7QRB37.js} +1 -1
- package/dist/{chunk-GVDQQZCO.js → chunk-ZFHN7GO7.js} +67 -16
- package/dist/cli.js +9 -9
- package/dist/{client-DVVHMIOO.js → client-VIFYMBT2.js} +1 -1
- package/dist/gate-cli.js +4 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +5 -5
- package/dist/{serve-boot-VJR6J5PL.js → serve-boot-RU5BYIK4.js} +3 -3
- package/dist/{server-WM23BC5C.js → server-L5CQ5EJB.js} +5 -2
- package/package.json +1 -1
- package/dist/chunk-7P6QZHXZ.js +0 -76
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startWorkbenchServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EIHZRSYI.js";
|
|
4
4
|
import {
|
|
5
5
|
TOMBSTONE_MESSAGE,
|
|
6
6
|
isLegacyEntrypointLaunch
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
refreshSkillsAfterCheckout,
|
|
13
13
|
runPreReadyBinds
|
|
14
14
|
} from "./chunk-SR66HQKB.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-W5INK3NE.js";
|
|
16
16
|
import {
|
|
17
17
|
GitPrepJob,
|
|
18
18
|
defaultGit,
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
reportBootMilestone
|
|
21
21
|
} from "./chunk-GL2DIQEQ.js";
|
|
22
22
|
import "./chunk-W4LZ7R6Z.js";
|
|
23
|
+
import "./chunk-372R6E4C.js";
|
|
24
|
+
import "./chunk-SQM2BQ7H.js";
|
|
23
25
|
import {
|
|
24
26
|
workbenchPort
|
|
25
27
|
} from "./chunk-KMB3BU4S.js";
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getWorkbenchClient
|
|
3
|
+
} from "./chunk-SQM2BQ7H.js";
|
|
4
|
+
import {
|
|
5
|
+
sharedDir,
|
|
6
|
+
workbenchEnabled
|
|
7
|
+
} from "./chunk-KMB3BU4S.js";
|
|
8
|
+
|
|
9
|
+
// src/runner/heavy-gate.ts
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
|
|
13
|
+
var GATES_DIR_NAME = "gates";
|
|
14
|
+
function runDir() {
|
|
15
|
+
return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
|
|
16
|
+
}
|
|
17
|
+
function gatesDir() {
|
|
18
|
+
return path.join(sharedDir() ?? runDir(), GATES_DIR_NAME);
|
|
19
|
+
}
|
|
20
|
+
function pidAlive(pid) {
|
|
21
|
+
if (process.platform === "linux" && pidIsZombie(pid)) return false;
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function pidIsZombie(pid) {
|
|
30
|
+
try {
|
|
31
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
32
|
+
const closingParen = stat.lastIndexOf(")");
|
|
33
|
+
return closingParen >= 0 && stat.slice(closingParen + 1).trimStart().startsWith("Z");
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parsePid(raw) {
|
|
39
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
40
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
41
|
+
}
|
|
42
|
+
function singletonPidFiles() {
|
|
43
|
+
return GATE_KEYS.map((key) => path.join(runDir(), `${key}.pid`));
|
|
44
|
+
}
|
|
45
|
+
var splitGenericGateStatus = null;
|
|
46
|
+
var splitGenericGateStatusRefresh = null;
|
|
47
|
+
function getLocalGenericGateStatus() {
|
|
48
|
+
const activeLabels = [];
|
|
49
|
+
const abandonedReceipts = [];
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = readdirSync(gatesDir());
|
|
53
|
+
} catch {
|
|
54
|
+
return { activeLabels, abandonedReceipts };
|
|
55
|
+
}
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (!entry.endsWith(".pid")) continue;
|
|
58
|
+
const pidPath = path.join(gatesDir(), entry);
|
|
59
|
+
const label = entry.slice(0, -".pid".length);
|
|
60
|
+
try {
|
|
61
|
+
const pid = parsePid(readFileSync(pidPath, "utf8"));
|
|
62
|
+
if (pid === null) continue;
|
|
63
|
+
if (pidAlive(pid)) {
|
|
64
|
+
activeLabels.push(label);
|
|
65
|
+
} else if (!existsSync(path.join(gatesDir(), `${label}.exit`))) {
|
|
66
|
+
abandonedReceipts.push({ path: pidPath, label, pid, mtimeMs: statSync(pidPath).mtimeMs });
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
abandonedReceipts.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
72
|
+
return { activeLabels, abandonedReceipts };
|
|
73
|
+
}
|
|
74
|
+
function genericGateStatus() {
|
|
75
|
+
if (!workbenchEnabled()) return getLocalGenericGateStatus();
|
|
76
|
+
return splitGenericGateStatus ?? { activeLabels: ["unknown"], abandonedReceipts: [] };
|
|
77
|
+
}
|
|
78
|
+
function refreshGenericGateStatus() {
|
|
79
|
+
if (!workbenchEnabled()) return Promise.resolve(getLocalGenericGateStatus());
|
|
80
|
+
if (splitGenericGateStatusRefresh) return splitGenericGateStatusRefresh;
|
|
81
|
+
splitGenericGateStatusRefresh = getWorkbenchClient().gateStatus().then((status) => {
|
|
82
|
+
splitGenericGateStatus = {
|
|
83
|
+
activeLabels: status.activeLabels,
|
|
84
|
+
abandonedReceipts: status.abandonedReceipts.map((receipt) => ({
|
|
85
|
+
...receipt,
|
|
86
|
+
path: path.join(gatesDir(), `${receipt.label}.pid`)
|
|
87
|
+
}))
|
|
88
|
+
};
|
|
89
|
+
return splitGenericGateStatus;
|
|
90
|
+
}).catch(() => {
|
|
91
|
+
splitGenericGateStatus = null;
|
|
92
|
+
return genericGateStatus();
|
|
93
|
+
}).finally(() => {
|
|
94
|
+
splitGenericGateStatusRefresh = null;
|
|
95
|
+
});
|
|
96
|
+
return splitGenericGateStatusRefresh;
|
|
97
|
+
}
|
|
98
|
+
function listAbandonedGateReceipts() {
|
|
99
|
+
return genericGateStatus().abandonedReceipts;
|
|
100
|
+
}
|
|
101
|
+
function listGateExitSentinels() {
|
|
102
|
+
const sentinels = [];
|
|
103
|
+
let entries;
|
|
104
|
+
try {
|
|
105
|
+
entries = readdirSync(gatesDir());
|
|
106
|
+
} catch {
|
|
107
|
+
return sentinels;
|
|
108
|
+
}
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (!entry.endsWith(".exit")) continue;
|
|
111
|
+
const file = path.join(gatesDir(), entry);
|
|
112
|
+
try {
|
|
113
|
+
const mtimeMs = statSync(file).mtimeMs;
|
|
114
|
+
let code = null;
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
117
|
+
const value = parsed?.code;
|
|
118
|
+
if (typeof value === "number") code = value;
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
sentinels.push({ path: file, label: entry.slice(0, -".exit".length), code, mtimeMs });
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
sentinels.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
126
|
+
return sentinels;
|
|
127
|
+
}
|
|
128
|
+
function isHeavyGateActive() {
|
|
129
|
+
if (genericGateStatus().activeLabels.length > 0) return true;
|
|
130
|
+
for (const file of singletonPidFiles()) {
|
|
131
|
+
try {
|
|
132
|
+
const pid = parsePid(readFileSync(file, "utf8"));
|
|
133
|
+
if (pid !== null && pidAlive(pid)) return true;
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
gatesDir,
|
|
142
|
+
getLocalGenericGateStatus,
|
|
143
|
+
refreshGenericGateStatus,
|
|
144
|
+
listAbandonedGateReceipts,
|
|
145
|
+
listGateExitSentinels,
|
|
146
|
+
isHeavyGateActive
|
|
147
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadPtySpawn
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-W5INK3NE.js";
|
|
4
4
|
import {
|
|
5
5
|
terminateProcessGroup
|
|
6
6
|
} from "./chunk-W4LZ7R6Z.js";
|
|
7
|
+
import {
|
|
8
|
+
getLocalGenericGateStatus
|
|
9
|
+
} from "./chunk-372R6E4C.js";
|
|
7
10
|
import {
|
|
8
11
|
FrameReader,
|
|
9
12
|
timingSafeTokenEqual,
|
|
@@ -112,21 +115,25 @@ async function runRefreshSkills(socket, opts) {
|
|
|
112
115
|
}
|
|
113
116
|
socket.end();
|
|
114
117
|
}
|
|
118
|
+
function immediateResponse(req, opts) {
|
|
119
|
+
if (req.op === "ping") return { t: "pong", version: opts.version };
|
|
120
|
+
if (req.op === "gitStatus") return opts.getGitStatus?.() ?? { t: "gitStatus", state: "ready" };
|
|
121
|
+
if (req.op === "gateStatus") return { t: "gateStatus", ...getLocalGenericGateStatus() };
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
115
124
|
function dispatch(socket, req, sink, opts, liveOps) {
|
|
116
125
|
if (!req || typeof req !== "object" || !timingSafeTokenEqual(req.token ?? "", opts.token)) {
|
|
117
126
|
writeFrame(socket, { t: "error", message: "unauthorized", code: "unauthorized" });
|
|
118
127
|
socket.destroy();
|
|
119
128
|
return;
|
|
120
129
|
}
|
|
130
|
+
const immediate = immediateResponse(req, opts);
|
|
131
|
+
if (immediate) {
|
|
132
|
+
writeFrame(socket, immediate);
|
|
133
|
+
socket.end();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
121
136
|
switch (req.op) {
|
|
122
|
-
case "ping":
|
|
123
|
-
writeFrame(socket, { t: "pong", version: opts.version });
|
|
124
|
-
socket.end();
|
|
125
|
-
return;
|
|
126
|
-
case "gitStatus":
|
|
127
|
-
writeFrame(socket, opts.getGitStatus?.() ?? { t: "gitStatus", state: "ready" });
|
|
128
|
-
socket.end();
|
|
129
|
-
return;
|
|
130
137
|
case "refreshSkills":
|
|
131
138
|
void runRefreshSkills(socket, opts);
|
|
132
139
|
return;
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
readWorkspaceBytes,
|
|
6
6
|
statWorkspacePath,
|
|
7
7
|
workspacePathExists
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WS7QRB37.js";
|
|
9
9
|
import {
|
|
10
10
|
reportBootMilestone
|
|
11
11
|
} from "./chunk-GL2DIQEQ.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./chunk-W4LZ7R6Z.js";
|
|
17
17
|
import {
|
|
18
18
|
getWorkbenchClient
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-SQM2BQ7H.js";
|
|
20
20
|
import {
|
|
21
21
|
workbenchEnabled
|
|
22
22
|
} from "./chunk-KMB3BU4S.js";
|
|
@@ -37,7 +37,31 @@ var RemoteProcessHandle = class extends EventEmitter {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
// src/workbench/gate-status.ts
|
|
41
|
+
function readGateStatus(open, timeoutMs) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
let status = null;
|
|
44
|
+
let timer = null;
|
|
45
|
+
const socket = open(
|
|
46
|
+
(frame) => {
|
|
47
|
+
if (frame.t === "gateStatus") status = frame;
|
|
48
|
+
},
|
|
49
|
+
(error) => {
|
|
50
|
+
if (timer) clearTimeout(timer);
|
|
51
|
+
if (status) resolve(status);
|
|
52
|
+
else reject(error ?? new WorkbenchError("connection closed before gateStatus"));
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
timer = setTimeout(
|
|
56
|
+
() => socket.destroy(new WorkbenchError("gateStatus timed out", "workbench_timeout")),
|
|
57
|
+
timeoutMs
|
|
58
|
+
);
|
|
59
|
+
timer.unref();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
// src/workbench/client.ts
|
|
64
|
+
var DEFAULT_GATE_STATUS_TIMEOUT_MS = 5e3;
|
|
41
65
|
var WORKBENCH_COMMAND_ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
42
66
|
"POD_BOOTSTRAP_TOKEN",
|
|
43
67
|
"CONVEYOR_BOOTSTRAP_TOKEN",
|
|
@@ -107,10 +131,12 @@ var WorkbenchClient = class {
|
|
|
107
131
|
port;
|
|
108
132
|
host;
|
|
109
133
|
token;
|
|
134
|
+
gateStatusTimeoutMs;
|
|
110
135
|
constructor(options = {}) {
|
|
111
136
|
this.port = options.port ?? workbenchPort() ?? DEFAULT_WORKBENCH_PORT;
|
|
112
137
|
this.host = options.host ?? "127.0.0.1";
|
|
113
138
|
this.token = options.token ?? workbenchToken();
|
|
139
|
+
this.gateStatusTimeoutMs = options.gateStatusTimeoutMs ?? DEFAULT_GATE_STATUS_TIMEOUT_MS;
|
|
114
140
|
}
|
|
115
141
|
/** Open a connection, send the request, route response frames. */
|
|
116
142
|
open(request, onFrame, onClose) {
|
|
@@ -168,6 +194,12 @@ var WorkbenchClient = class {
|
|
|
168
194
|
);
|
|
169
195
|
});
|
|
170
196
|
}
|
|
197
|
+
gateStatus() {
|
|
198
|
+
return readGateStatus(
|
|
199
|
+
(onFrame, onClose) => this.open({ op: "gateStatus" }, onFrame, onClose),
|
|
200
|
+
this.gateStatusTimeoutMs
|
|
201
|
+
);
|
|
202
|
+
}
|
|
171
203
|
/** Ask the workbench — which owns the repo filesystem in split mode — to
|
|
172
204
|
* re-run the skill floor after the agent's authoritative task-branch
|
|
173
205
|
* checkout. A daemon without a provider answers an empty result. */
|
|
@@ -200,7 +200,7 @@ async function loadPtySpawn() {
|
|
|
200
200
|
async function resolvePtySpawn() {
|
|
201
201
|
const { workbenchEnabled } = await import("./mode-ZJSOSLGU.js");
|
|
202
202
|
if (!workbenchEnabled()) return loadPtySpawn();
|
|
203
|
-
const { getWorkbenchClient } = await import("./client-
|
|
203
|
+
const { getWorkbenchClient } = await import("./client-VIFYMBT2.js");
|
|
204
204
|
return (file, args, options) => getWorkbenchClient().spawnPty(file, args, options);
|
|
205
205
|
}
|
|
206
206
|
function sessionTempBase() {
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
spawnOptionsFingerprint,
|
|
30
30
|
transcriptSize,
|
|
31
31
|
turnOptionsFrom
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-W5INK3NE.js";
|
|
33
33
|
import {
|
|
34
34
|
AgentConnection,
|
|
35
35
|
CodespacePortVisibility,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
statWorkspacePath,
|
|
60
60
|
updateRemoteToken,
|
|
61
61
|
verifyGitCredential
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-WS7QRB37.js";
|
|
63
63
|
import {
|
|
64
64
|
registerBootMilestoneSocketFallback,
|
|
65
65
|
reportBootMilestone
|
|
@@ -72,15 +72,17 @@ import {
|
|
|
72
72
|
} from "./chunk-W4LZ7R6Z.js";
|
|
73
73
|
import {
|
|
74
74
|
isHeavyGateActive,
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
listAbandonedGateReceipts,
|
|
76
|
+
listGateExitSentinels,
|
|
77
|
+
refreshGenericGateStatus
|
|
78
|
+
} from "./chunk-372R6E4C.js";
|
|
77
79
|
import {
|
|
78
80
|
LoopLagMonitor,
|
|
79
81
|
loopStatusForRunnerStatus
|
|
80
82
|
} from "./chunk-IA45XHOA.js";
|
|
81
83
|
import {
|
|
82
84
|
getWorkbenchClient
|
|
83
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-SQM2BQ7H.js";
|
|
84
86
|
import {
|
|
85
87
|
workbenchEnabled
|
|
86
88
|
} from "./chunk-KMB3BU4S.js";
|
|
@@ -2181,6 +2183,10 @@ var CATALOG = {
|
|
|
2181
2183
|
// rejects (media_type_header_exception), breaking search/audit indexing
|
|
2182
2184
|
// in pods. 512m heap matches the project compose sizing.
|
|
2183
2185
|
image: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
|
|
2186
|
+
mirror: {
|
|
2187
|
+
src: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
|
|
2188
|
+
dest: "mirror-elasticsearch:9.4.0"
|
|
2189
|
+
},
|
|
2184
2190
|
ports: [9200],
|
|
2185
2191
|
// Baked service images are `docker commit`s of a recently-running ES, so
|
|
2186
2192
|
// they carry a stale data-dir node.lock; ES 9 hard-fails on it at boot
|
|
@@ -15688,7 +15694,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
15688
15694
|
onHeartbeat: () => {
|
|
15689
15695
|
const loopStatus = this.refreshLoopStatus();
|
|
15690
15696
|
this.connection.sendHeartbeat(this.loopLag.takeMaxLagMs(), loopStatus);
|
|
15691
|
-
this.
|
|
15697
|
+
void this.refreshGateStatusAndMaybeWake();
|
|
15692
15698
|
},
|
|
15693
15699
|
onIdleTimeout: () => {
|
|
15694
15700
|
if (this.deferShutdownForLiveChild("idle")) return;
|
|
@@ -15829,6 +15835,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
15829
15835
|
/** One-shot latch: at most one watchdog wake per idle episode. Reset when a
|
|
15830
15836
|
* turn runs. */
|
|
15831
15837
|
backgroundWakeFiredThisIdle = false;
|
|
15838
|
+
/** Start of the last active episode. A gate can be launched shortly before
|
|
15839
|
+
* the agent declares completion, so idle-start freshness would reject its
|
|
15840
|
+
* terminal receipt as stale. */
|
|
15841
|
+
activeEpisodeStartedAt = Date.now();
|
|
15832
15842
|
/** When the current idle episode began — an exit sentinel older than this
|
|
15833
15843
|
* appeared while a turn could still have consumed it, so it never wakes. */
|
|
15834
15844
|
idleEpisodeStartedAt = 0;
|
|
@@ -15843,22 +15853,30 @@ var SessionRunner = class _SessionRunner {
|
|
|
15843
15853
|
* evidence of orphaned background work — a tracked launch with no live gate
|
|
15844
15854
|
* process continuously past `ORPHAN_WAKE_GRACE_MS`, or an unconsumed
|
|
15845
15855
|
* `gates/<label>.exit` sentinel that appeared this idle episode — never on
|
|
15846
|
-
* "the agent seems stuck". A `completed` agent
|
|
15847
|
-
*
|
|
15856
|
+
* "the agent seems stuck". A `completed` agent remains parked unless a
|
|
15857
|
+
* current-episode generic receipt proves a required gate died without an
|
|
15858
|
+
* exit record, and a live pid always suppresses the wake.
|
|
15848
15859
|
*/
|
|
15849
15860
|
maybeWakeForOrphanedBackgroundWork() {
|
|
15850
15861
|
try {
|
|
15851
|
-
if (this._state !== "idle" || this.stopped
|
|
15862
|
+
if (this._state !== "idle" || this.stopped) {
|
|
15852
15863
|
this.orphanGraceStartedAt = null;
|
|
15853
15864
|
return;
|
|
15854
15865
|
}
|
|
15855
15866
|
if (this.backgroundWakeFiredThisIdle) return;
|
|
15856
15867
|
if (!this.inputResolver) return;
|
|
15868
|
+
const abandoned = listAbandonedGateReceipts().find(
|
|
15869
|
+
(receipt) => receipt.mtimeMs >= this.activeEpisodeStartedAt
|
|
15870
|
+
);
|
|
15871
|
+
if (this.completedThisTurn && !abandoned) {
|
|
15872
|
+
this.orphanGraceStartedAt = null;
|
|
15873
|
+
return;
|
|
15874
|
+
}
|
|
15857
15875
|
if (isHeavyGateActive()) {
|
|
15858
15876
|
this.orphanGraceStartedAt = null;
|
|
15859
15877
|
return;
|
|
15860
15878
|
}
|
|
15861
|
-
const evidence = this.findOrphanEvidence(Date.now());
|
|
15879
|
+
const evidence = abandoned ? this.abandonedReceiptEvidence(abandoned) : this.findOrphanEvidence(Date.now());
|
|
15862
15880
|
if (!evidence) return;
|
|
15863
15881
|
process.stderr.write(
|
|
15864
15882
|
`[conveyor-agent] Background-work watchdog: waking agent \u2014 ${evidence.description}
|
|
@@ -15867,11 +15885,16 @@ var SessionRunner = class _SessionRunner {
|
|
|
15867
15885
|
this.backgroundWakeFiredThisIdle = true;
|
|
15868
15886
|
this.orphanGraceStartedAt = null;
|
|
15869
15887
|
if (evidence.sentinel) rmSync(evidence.sentinel.path, { force: true });
|
|
15888
|
+
if (evidence.abandonedReceipt) rmSync(evidence.abandonedReceipt.path, { force: true });
|
|
15870
15889
|
this.backgroundWork.clear();
|
|
15890
|
+
if (evidence.abandonedReceipt) {
|
|
15891
|
+
this.completedThisTurn = false;
|
|
15892
|
+
this.dormantDeadline = null;
|
|
15893
|
+
}
|
|
15871
15894
|
const resolver = this.inputResolver;
|
|
15872
15895
|
this.inputResolver = null;
|
|
15873
15896
|
resolver({
|
|
15874
|
-
content: `A
|
|
15897
|
+
content: `A required gate you started appears to have finished or died without delivering its completion notification (${evidence.description}). Read the job's output \u2014 its log file, or the corresponding file under $CONVEYOR_SHARED_DIR/gates on split pods \u2014 to determine the real result, then continue your plan. Do not assume the job succeeded; verify its output first. If it died, rerun it in the foreground without shell '&' and retain the command session until its exit result.`,
|
|
15875
15898
|
userId: "system",
|
|
15876
15899
|
source: "background_work_check"
|
|
15877
15900
|
});
|
|
@@ -15902,6 +15925,25 @@ var SessionRunner = class _SessionRunner {
|
|
|
15902
15925
|
description: `${this.backgroundWork.pendingCount()} tracked background launch(es) with no live gate process for ${Math.round(ORPHAN_WAKE_GRACE_MS / 6e4)}+ minutes`
|
|
15903
15926
|
};
|
|
15904
15927
|
}
|
|
15928
|
+
abandonedReceiptEvidence(receipt) {
|
|
15929
|
+
return {
|
|
15930
|
+
abandonedReceipt: receipt,
|
|
15931
|
+
description: `gate '${receipt.label}' stopped before writing an exit receipt (pid ${receipt.pid})`
|
|
15932
|
+
};
|
|
15933
|
+
}
|
|
15934
|
+
/** Refresh PID liveness in the owning workbench namespace before checking
|
|
15935
|
+
* terminal receipts. The local path is immediate; socket failures preserve
|
|
15936
|
+
* the conservative cached state and never interrupt the heartbeat. */
|
|
15937
|
+
async refreshGateStatusAndMaybeWake() {
|
|
15938
|
+
try {
|
|
15939
|
+
await refreshGenericGateStatus();
|
|
15940
|
+
this.refreshLoopStatus();
|
|
15941
|
+
this.maybeWakeForOrphanedBackgroundWork();
|
|
15942
|
+
} catch (err) {
|
|
15943
|
+
process.stderr.write(`[conveyor-agent] Gate status refresh failed: ${err}
|
|
15944
|
+
`);
|
|
15945
|
+
}
|
|
15946
|
+
}
|
|
15905
15947
|
get sessionId() {
|
|
15906
15948
|
return this.connection.sessionId;
|
|
15907
15949
|
}
|
|
@@ -16713,9 +16755,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
16713
16755
|
};
|
|
16714
16756
|
const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
|
|
16715
16757
|
onStatusChange: (status) => {
|
|
16716
|
-
if (status === "running"
|
|
16717
|
-
this.
|
|
16718
|
-
this.
|
|
16758
|
+
if (status === "running") {
|
|
16759
|
+
this.beginRunningEpisode();
|
|
16760
|
+
if (this._state === "waiting_for_input") {
|
|
16761
|
+
this._state = "running";
|
|
16762
|
+
this.lifecycle.cancelIdleTimer();
|
|
16763
|
+
}
|
|
16719
16764
|
}
|
|
16720
16765
|
return this.callbacks.onStatusChange(status);
|
|
16721
16766
|
},
|
|
@@ -16963,8 +17008,7 @@ ${outcome.failures.join("\n")}
|
|
|
16963
17008
|
}
|
|
16964
17009
|
async setState(status) {
|
|
16965
17010
|
if (status === "running") {
|
|
16966
|
-
this.
|
|
16967
|
-
this.backgroundWakeFiredThisIdle = false;
|
|
17011
|
+
this.beginRunningEpisode();
|
|
16968
17012
|
}
|
|
16969
17013
|
if (status === "idle" && this._state !== "idle") this.idleEpisodeStartedAt = Date.now();
|
|
16970
17014
|
this._state = status;
|
|
@@ -16972,6 +17016,13 @@ ${outcome.failures.join("\n")}
|
|
|
16972
17016
|
await this.connection.emitStatus(status);
|
|
16973
17017
|
await this.callbacks.onStatusChange(status);
|
|
16974
17018
|
}
|
|
17019
|
+
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
17020
|
+
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
17021
|
+
beginRunningEpisode() {
|
|
17022
|
+
this.activeEpisodeStartedAt = Date.now();
|
|
17023
|
+
this.backgroundDeferStartedAt = null;
|
|
17024
|
+
this.backgroundWakeFiredThisIdle = false;
|
|
17025
|
+
}
|
|
16975
17026
|
async shutdown(finalState) {
|
|
16976
17027
|
process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
|
|
16977
17028
|
`);
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WorkspaceCommandSupervisor,
|
|
8
8
|
startWorkspaceCommandsAfterConnect,
|
|
9
9
|
stopWorkspaceCommands
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-GZRZGBIK.js";
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_SONNET_MODEL,
|
|
13
13
|
PtyHarness,
|
|
@@ -27,13 +27,13 @@ import {
|
|
|
27
27
|
resolveTuiKindFromEnv,
|
|
28
28
|
runUsageProbe,
|
|
29
29
|
sampleKeyUsage
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ZFHN7GO7.js";
|
|
31
31
|
import "./chunk-SR66HQKB.js";
|
|
32
32
|
import {
|
|
33
33
|
inheritedEnv,
|
|
34
34
|
resolvePtySpawn,
|
|
35
35
|
sessionTempBase
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-W5INK3NE.js";
|
|
37
37
|
import {
|
|
38
38
|
AgentConnection,
|
|
39
39
|
CodespacePortVisibility,
|
|
@@ -44,12 +44,12 @@ import {
|
|
|
44
44
|
createServiceLogger,
|
|
45
45
|
fetchBootstrap,
|
|
46
46
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-WS7QRB37.js";
|
|
48
48
|
import "./chunk-GL2DIQEQ.js";
|
|
49
49
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-372R6E4C.js";
|
|
51
51
|
import "./chunk-IA45XHOA.js";
|
|
52
|
-
import "./chunk-
|
|
52
|
+
import "./chunk-SQM2BQ7H.js";
|
|
53
53
|
import "./chunk-KMB3BU4S.js";
|
|
54
54
|
import "./chunk-6Q6LQBWO.js";
|
|
55
55
|
|
|
@@ -1205,7 +1205,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
|
1205
1205
|
|
|
1206
1206
|
// src/cli.ts
|
|
1207
1207
|
if (process.argv[2] === "boot") {
|
|
1208
|
-
const { runBoot } = await import("./boot-
|
|
1208
|
+
const { runBoot } = await import("./boot-4ZNOCKFZ.js");
|
|
1209
1209
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1210
1210
|
}
|
|
1211
1211
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1307,7 +1307,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
1307
1307
|
process.exit(1);
|
|
1308
1308
|
});
|
|
1309
1309
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1310
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1310
|
+
const { startWorkbenchServer } = await import("./server-L5CQ5EJB.js");
|
|
1311
1311
|
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
|
|
1312
1312
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
|
|
1313
1313
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
@@ -1458,7 +1458,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1458
1458
|
process.exit(1);
|
|
1459
1459
|
}
|
|
1460
1460
|
if (CONVEYOR_MODE === "serving") {
|
|
1461
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1461
|
+
const { runServingSession } = await import("./serve-boot-RU5BYIK4.js");
|
|
1462
1462
|
exitContext.runnerMode = "serving";
|
|
1463
1463
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1464
1464
|
const outcome = await runServingSession({
|
package/dist/gate-cli.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
gatesDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-372R6E4C.js";
|
|
5
|
+
import "./chunk-SQM2BQ7H.js";
|
|
6
|
+
import "./chunk-KMB3BU4S.js";
|
|
7
|
+
import "./chunk-6Q6LQBWO.js";
|
|
5
8
|
|
|
6
9
|
// src/runner/gate-wrapper.ts
|
|
7
10
|
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
package/dist/index.d.ts
CHANGED
|
@@ -810,6 +810,10 @@ declare class SessionRunner {
|
|
|
810
810
|
/** One-shot latch: at most one watchdog wake per idle episode. Reset when a
|
|
811
811
|
* turn runs. */
|
|
812
812
|
private backgroundWakeFiredThisIdle;
|
|
813
|
+
/** Start of the last active episode. A gate can be launched shortly before
|
|
814
|
+
* the agent declares completion, so idle-start freshness would reject its
|
|
815
|
+
* terminal receipt as stale. */
|
|
816
|
+
private activeEpisodeStartedAt;
|
|
813
817
|
/** When the current idle episode began — an exit sentinel older than this
|
|
814
818
|
* appeared while a turn could still have consumed it, so it never wakes. */
|
|
815
819
|
private idleEpisodeStartedAt;
|
|
@@ -824,13 +828,19 @@ declare class SessionRunner {
|
|
|
824
828
|
* evidence of orphaned background work — a tracked launch with no live gate
|
|
825
829
|
* process continuously past `ORPHAN_WAKE_GRACE_MS`, or an unconsumed
|
|
826
830
|
* `gates/<label>.exit` sentinel that appeared this idle episode — never on
|
|
827
|
-
* "the agent seems stuck". A `completed` agent
|
|
828
|
-
*
|
|
831
|
+
* "the agent seems stuck". A `completed` agent remains parked unless a
|
|
832
|
+
* current-episode generic receipt proves a required gate died without an
|
|
833
|
+
* exit record, and a live pid always suppresses the wake.
|
|
829
834
|
*/
|
|
830
835
|
private maybeWakeForOrphanedBackgroundWork;
|
|
831
836
|
/** Orphan evidence that has held past the grace, or null. Maintains the
|
|
832
837
|
* grace clock for the tracked-work condition as a side effect. */
|
|
833
838
|
private findOrphanEvidence;
|
|
839
|
+
private abandonedReceiptEvidence;
|
|
840
|
+
/** Refresh PID liveness in the owning workbench namespace before checking
|
|
841
|
+
* terminal receipts. The local path is immediate; socket failures preserve
|
|
842
|
+
* the conservative cached state and never interrupt the heartbeat. */
|
|
843
|
+
private refreshGateStatusAndMaybeWake;
|
|
834
844
|
get sessionId(): string;
|
|
835
845
|
get isStopped(): boolean;
|
|
836
846
|
/** Wire the boot supervisor handle post-construction — cli.ts constructs it
|
|
@@ -1097,6 +1107,9 @@ declare class SessionRunner {
|
|
|
1097
1107
|
* the tool starts work that outlives the turn. */
|
|
1098
1108
|
private noteToolUseForBackgroundWork;
|
|
1099
1109
|
private setState;
|
|
1110
|
+
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
1111
|
+
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
1112
|
+
private beginRunningEpisode;
|
|
1100
1113
|
private shutdown;
|
|
1101
1114
|
private _finalState;
|
|
1102
1115
|
/** The final status after run() completes. Use to determine exit code. */
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionRunner,
|
|
3
3
|
unshallowRepo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZFHN7GO7.js";
|
|
5
5
|
import "./chunk-SR66HQKB.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-W5INK3NE.js";
|
|
7
7
|
import {
|
|
8
8
|
AgentConnection,
|
|
9
9
|
GIT_TIMEOUT_MS,
|
|
@@ -17,18 +17,18 @@ import {
|
|
|
17
17
|
stageAndCommit,
|
|
18
18
|
updateRemoteToken,
|
|
19
19
|
workspacePathExists
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-WS7QRB37.js";
|
|
21
21
|
import "./chunk-GL2DIQEQ.js";
|
|
22
22
|
import {
|
|
23
23
|
runAuthTokenCommand,
|
|
24
24
|
runSetupCommand,
|
|
25
25
|
runStartCommand
|
|
26
26
|
} from "./chunk-W4LZ7R6Z.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-372R6E4C.js";
|
|
28
28
|
import "./chunk-IA45XHOA.js";
|
|
29
29
|
import {
|
|
30
30
|
getWorkbenchClient
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-SQM2BQ7H.js";
|
|
32
32
|
import {
|
|
33
33
|
workbenchEnabled
|
|
34
34
|
} from "./chunk-KMB3BU4S.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkspaceCommandSupervisor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GZRZGBIK.js";
|
|
4
4
|
import {
|
|
5
5
|
AgentConnection,
|
|
6
6
|
CodespacePortVisibility,
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
createServiceLogger,
|
|
12
12
|
ensureOnTaskBranch,
|
|
13
13
|
loadConveyorConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WS7QRB37.js";
|
|
15
15
|
import "./chunk-GL2DIQEQ.js";
|
|
16
16
|
import "./chunk-W4LZ7R6Z.js";
|
|
17
17
|
import "./chunk-IA45XHOA.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-SQM2BQ7H.js";
|
|
19
19
|
import "./chunk-KMB3BU4S.js";
|
|
20
20
|
import "./chunk-6Q6LQBWO.js";
|
|
21
21
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startWorkbenchServer
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-EIHZRSYI.js";
|
|
4
|
+
import "./chunk-W5INK3NE.js";
|
|
5
5
|
import "./chunk-W4LZ7R6Z.js";
|
|
6
|
+
import "./chunk-372R6E4C.js";
|
|
7
|
+
import "./chunk-SQM2BQ7H.js";
|
|
8
|
+
import "./chunk-KMB3BU4S.js";
|
|
6
9
|
import "./chunk-6Q6LQBWO.js";
|
|
7
10
|
import "./chunk-6W6UZ4SJ.js";
|
|
8
11
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.18",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
package/dist/chunk-7P6QZHXZ.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// src/runner/heavy-gate.ts
|
|
2
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
|
|
5
|
-
var GATES_DIR_NAME = "gates";
|
|
6
|
-
function runDir() {
|
|
7
|
-
return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
|
|
8
|
-
}
|
|
9
|
-
function gatesDir() {
|
|
10
|
-
return path.join(runDir(), GATES_DIR_NAME);
|
|
11
|
-
}
|
|
12
|
-
function pidAlive(pid) {
|
|
13
|
-
try {
|
|
14
|
-
process.kill(pid, 0);
|
|
15
|
-
return true;
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function parsePid(raw) {
|
|
21
|
-
const pid = Number.parseInt(raw.trim(), 10);
|
|
22
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
23
|
-
}
|
|
24
|
-
function gatePidFiles() {
|
|
25
|
-
const files = GATE_KEYS.map((key) => path.join(runDir(), `${key}.pid`));
|
|
26
|
-
try {
|
|
27
|
-
for (const entry of readdirSync(gatesDir())) {
|
|
28
|
-
if (entry.endsWith(".pid")) files.push(path.join(gatesDir(), entry));
|
|
29
|
-
}
|
|
30
|
-
} catch {
|
|
31
|
-
}
|
|
32
|
-
return files;
|
|
33
|
-
}
|
|
34
|
-
function listGateExitSentinels() {
|
|
35
|
-
const sentinels = [];
|
|
36
|
-
let entries;
|
|
37
|
-
try {
|
|
38
|
-
entries = readdirSync(gatesDir());
|
|
39
|
-
} catch {
|
|
40
|
-
return sentinels;
|
|
41
|
-
}
|
|
42
|
-
for (const entry of entries) {
|
|
43
|
-
if (!entry.endsWith(".exit")) continue;
|
|
44
|
-
const file = path.join(gatesDir(), entry);
|
|
45
|
-
try {
|
|
46
|
-
const mtimeMs = statSync(file).mtimeMs;
|
|
47
|
-
let code = null;
|
|
48
|
-
try {
|
|
49
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
50
|
-
const value = parsed?.code;
|
|
51
|
-
if (typeof value === "number") code = value;
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
sentinels.push({ path: file, label: entry.slice(0, -".exit".length), code, mtimeMs });
|
|
55
|
-
} catch {
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
sentinels.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
59
|
-
return sentinels;
|
|
60
|
-
}
|
|
61
|
-
function isHeavyGateActive() {
|
|
62
|
-
for (const file of gatePidFiles()) {
|
|
63
|
-
try {
|
|
64
|
-
const pid = parsePid(readFileSync(file, "utf8"));
|
|
65
|
-
if (pid !== null && pidAlive(pid)) return true;
|
|
66
|
-
} catch {
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export {
|
|
73
|
-
gatesDir,
|
|
74
|
-
listGateExitSentinels,
|
|
75
|
-
isHeavyGateActive
|
|
76
|
-
};
|