@rallycry/conveyor-agent 11.0.17 → 11.0.19
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-GVDQQZCO.js → chunk-N5LEVAGA.js} +188 -26
- 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/cli.js +13 -12
- package/dist/{client-DVVHMIOO.js → client-VIFYMBT2.js} +1 -1
- package/dist/gate-cli.js +4 -1
- package/dist/index.d.ts +33 -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/skills/conveyor-build/SKILL.md +36 -0
- package/skills/conveyor-build/references/pack-path.md +28 -7
- package/skills/conveyor-plan/SKILL.md +32 -1
- package/skills/conveyor-review/SKILL.md +28 -2
- 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";
|
|
@@ -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";
|
|
@@ -1414,6 +1416,7 @@ var ListProjectSessionGroupsRequestSchema = z5.object({
|
|
|
1414
1416
|
projectId: z5.string()
|
|
1415
1417
|
});
|
|
1416
1418
|
var ListMyLiveSessionsAcrossProjectsRequestSchema = z5.object({});
|
|
1419
|
+
var ListSessionGroupsAcrossProjectsRequestSchema = z5.object({});
|
|
1417
1420
|
var GetProjectAvailableTuisRequestSchema = z5.object({
|
|
1418
1421
|
projectId: z5.string()
|
|
1419
1422
|
});
|
|
@@ -2040,6 +2043,11 @@ var ANTHROPIC_CATALOG = [
|
|
|
2040
2043
|
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
|
|
2041
2044
|
anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
|
|
2042
2045
|
];
|
|
2046
|
+
var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
2047
|
+
var DEFAULT_CODEX_CODING_MODEL = "gpt-5.6-terra";
|
|
2048
|
+
function isCodexReasoningEffort(value) {
|
|
2049
|
+
return typeof value === "string" && CODEX_REASONING_EFFORTS.includes(value);
|
|
2050
|
+
}
|
|
2043
2051
|
var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
|
|
2044
2052
|
When you write prose a person will read \u2014 chat messages, plan updates, PR titles and bodies, PR review guides (the \`publish_review_guide\` overview and section explanations), review comments \u2014 follow these rules (based on ASD-STE100 Simplified Technical English):
|
|
2045
2053
|
- Use active voice. Say who does what ("The API rejects the request", not "the request is rejected").
|
|
@@ -2181,6 +2189,10 @@ var CATALOG = {
|
|
|
2181
2189
|
// rejects (media_type_header_exception), breaking search/audit indexing
|
|
2182
2190
|
// in pods. 512m heap matches the project compose sizing.
|
|
2183
2191
|
image: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
|
|
2192
|
+
mirror: {
|
|
2193
|
+
src: "docker.elastic.co/elasticsearch/elasticsearch:9.4.0",
|
|
2194
|
+
dest: "mirror-elasticsearch:9.4.0"
|
|
2195
|
+
},
|
|
2184
2196
|
ports: [9200],
|
|
2185
2197
|
// Baked service images are `docker commit`s of a recently-running ES, so
|
|
2186
2198
|
// they carry a stale data-dir node.lock; ES 9 hard-fails on it at boot
|
|
@@ -3245,6 +3257,13 @@ var JsonlTailer = class {
|
|
|
3245
3257
|
};
|
|
3246
3258
|
|
|
3247
3259
|
// src/harness/codex/event-source.ts
|
|
3260
|
+
var USAGE_CAP_PATTERN = /usage_limit_(?:reached|exceeded)|rate_limit_reached|UsageLimitReached|hit your usage limit/i;
|
|
3261
|
+
var WEEKLY_WINDOW_MINUTES = 24 * 60;
|
|
3262
|
+
function isUsageCap(error) {
|
|
3263
|
+
return [error.codex_error_info, error.code, error.type, error.message].some(
|
|
3264
|
+
(value) => typeof value === "string" && USAGE_CAP_PATTERN.test(value)
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3248
3267
|
var CodexEventSource = class {
|
|
3249
3268
|
constructor(emit, onSession, chat) {
|
|
3250
3269
|
this.emit = emit;
|
|
@@ -3257,6 +3276,8 @@ var CodexEventSource = class {
|
|
|
3257
3276
|
sessionId = null;
|
|
3258
3277
|
completed = /* @__PURE__ */ new Set();
|
|
3259
3278
|
transcript = null;
|
|
3279
|
+
/** Last reported reading per rate-limit window, keyed by `five_hour` / `seven_day`. */
|
|
3280
|
+
windows = /* @__PURE__ */ new Map();
|
|
3260
3281
|
handleRecord(raw) {
|
|
3261
3282
|
if (!raw || typeof raw !== "object") return;
|
|
3262
3283
|
const e = raw;
|
|
@@ -3311,11 +3332,16 @@ var CodexEventSource = class {
|
|
|
3311
3332
|
if (record.type !== "event_msg" || !record.payload || typeof record.payload !== "object")
|
|
3312
3333
|
return;
|
|
3313
3334
|
const event = record.payload;
|
|
3335
|
+
if (event.type === "token_count") {
|
|
3336
|
+
this.handleTokenCount(event);
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3314
3339
|
if (event.type !== "task_complete" || !event.error || typeof event.error !== "object") return;
|
|
3315
3340
|
const turn = event.turn_id;
|
|
3316
3341
|
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
3317
3342
|
this.rememberTurn(turn);
|
|
3318
3343
|
const error = event.error;
|
|
3344
|
+
if (isUsageCap(error)) this.emitUsageCap();
|
|
3319
3345
|
this.emit({
|
|
3320
3346
|
type: "result",
|
|
3321
3347
|
subtype: "error",
|
|
@@ -3323,6 +3349,59 @@ var CodexEventSource = class {
|
|
|
3323
3349
|
});
|
|
3324
3350
|
this.chat({ kind: "turn_end" });
|
|
3325
3351
|
}
|
|
3352
|
+
/**
|
|
3353
|
+
* `token_count` carries the account's rate-limit gauges on every turn:
|
|
3354
|
+
* `rate_limits.primary` (the 5-hour window) and `.secondary` (weekly), each
|
|
3355
|
+
* with `used_percent`, `window_minutes` and `resets_at` in epoch seconds.
|
|
3356
|
+
* Classified by window length rather than by name, and reported only when a
|
|
3357
|
+
* window's numbers changed — the event fires per turn, and every report
|
|
3358
|
+
* rewrites the key row and fans out `project:codingAgentKeyUpdated`.
|
|
3359
|
+
*/
|
|
3360
|
+
handleTokenCount(event) {
|
|
3361
|
+
const limits = event.rate_limits;
|
|
3362
|
+
if (!limits || typeof limits !== "object") return;
|
|
3363
|
+
for (const name of ["primary", "secondary"]) {
|
|
3364
|
+
const window = limits[name];
|
|
3365
|
+
if (!window || typeof window !== "object") continue;
|
|
3366
|
+
const reading = window;
|
|
3367
|
+
if (typeof reading.used_percent !== "number") continue;
|
|
3368
|
+
const minutes = typeof reading.window_minutes === "number" ? reading.window_minutes : name === "secondary" ? WEEKLY_WINDOW_MINUTES : 0;
|
|
3369
|
+
const rateLimitType = minutes >= WEEKLY_WINDOW_MINUTES ? "seven_day" : "five_hour";
|
|
3370
|
+
const resetsAt = typeof reading.resets_at === "number" ? reading.resets_at : void 0;
|
|
3371
|
+
const last = this.windows.get(rateLimitType);
|
|
3372
|
+
if (last && last.usedPercent === reading.used_percent && last.resetsAt === resetsAt) continue;
|
|
3373
|
+
this.windows.set(rateLimitType, { usedPercent: reading.used_percent, resetsAt });
|
|
3374
|
+
this.emit({
|
|
3375
|
+
type: "rate_limit_event",
|
|
3376
|
+
rate_limit_info: {
|
|
3377
|
+
status: "allowed",
|
|
3378
|
+
rateLimitType,
|
|
3379
|
+
utilization: Math.max(0, Math.min(1, reading.used_percent / 100)),
|
|
3380
|
+
resetsAt
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* The CLI does not say which window capped. The fullest window last reported
|
|
3387
|
+
* is the one that did; with no reading at all, the 5-hour window is the
|
|
3388
|
+
* conservative guess (its fallback pause is the shorter one).
|
|
3389
|
+
*/
|
|
3390
|
+
emitUsageCap() {
|
|
3391
|
+
let rateLimitType = "five_hour";
|
|
3392
|
+
let resetsAt;
|
|
3393
|
+
let fullest = -Infinity;
|
|
3394
|
+
for (const [type, window] of this.windows) {
|
|
3395
|
+
if (window.usedPercent <= fullest) continue;
|
|
3396
|
+
fullest = window.usedPercent;
|
|
3397
|
+
rateLimitType = type;
|
|
3398
|
+
resetsAt = window.resetsAt;
|
|
3399
|
+
}
|
|
3400
|
+
this.emit({
|
|
3401
|
+
type: "rate_limit_event",
|
|
3402
|
+
rate_limit_info: { status: "rejected", rateLimitType, resetsAt }
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3326
3405
|
rememberTurn(turn) {
|
|
3327
3406
|
this.completed.add(turn);
|
|
3328
3407
|
const oldest = this.completed.values().next().value;
|
|
@@ -6465,13 +6544,16 @@ async function seedOpenCodeOauth(env) {
|
|
|
6465
6544
|
}
|
|
6466
6545
|
|
|
6467
6546
|
// src/harness/openai-model.ts
|
|
6468
|
-
var DEFAULT_OPENAI_CODING_MODEL =
|
|
6547
|
+
var DEFAULT_OPENAI_CODING_MODEL = DEFAULT_CODEX_CODING_MODEL;
|
|
6469
6548
|
function resolveOpenAiCodingModel(model) {
|
|
6470
6549
|
if (!model || model.startsWith("claude-") || model.includes("/") && !model.startsWith("openai/")) {
|
|
6471
6550
|
return DEFAULT_OPENAI_CODING_MODEL;
|
|
6472
6551
|
}
|
|
6473
6552
|
return model.replace(/^openai\//, "");
|
|
6474
6553
|
}
|
|
6554
|
+
function resolveCodexReasoningEffort(...candidates) {
|
|
6555
|
+
return candidates.find(isCodexReasoningEffort);
|
|
6556
|
+
}
|
|
6475
6557
|
|
|
6476
6558
|
// src/harness/opencode/credentials.ts
|
|
6477
6559
|
var PROVIDER_KEY_ENV = {
|
|
@@ -6981,11 +7063,16 @@ var CodexTuiAdapter = class {
|
|
|
6981
7063
|
sandbox_mode: "danger-full-access",
|
|
6982
7064
|
developer_instructions: [
|
|
6983
7065
|
input.options.appendSystemPrompt,
|
|
6984
|
-
"Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command."
|
|
7066
|
+
"Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command. When a skill's Goal and finish line section tells you to create a thread goal, do so with your goal tool before the first step; the objective is that section's finish line."
|
|
6985
7067
|
].filter(Boolean).join("\n\n"),
|
|
6986
7068
|
mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
|
|
6987
7069
|
projects: { [trustedCwd]: { trust_level: "trusted" } }
|
|
6988
7070
|
};
|
|
7071
|
+
const effort = resolveCodexReasoningEffort(
|
|
7072
|
+
this.env.CONVEYOR_AGENT_REASONING_EFFORT,
|
|
7073
|
+
input.options.codex?.effort
|
|
7074
|
+
);
|
|
7075
|
+
if (effort) config.model_reasoning_effort = effort;
|
|
6989
7076
|
if (input.pluginPath && input.eventsSinkPath) {
|
|
6990
7077
|
const profile = basename(dirname3(input.pluginPath));
|
|
6991
7078
|
const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
|
|
@@ -7003,7 +7090,9 @@ var CodexTuiAdapter = class {
|
|
|
7003
7090
|
if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
|
|
7004
7091
|
for (const [key, value] of Object.entries(config))
|
|
7005
7092
|
args.push("-c", `${key}=${tomlValue(value)}`);
|
|
7006
|
-
const model = resolveOpenAiCodingModel(
|
|
7093
|
+
const model = resolveOpenAiCodingModel(
|
|
7094
|
+
this.env.CONVEYOR_AGENT_MODEL ?? input.options.codex?.model ?? input.options.model
|
|
7095
|
+
);
|
|
7007
7096
|
args.push("--model", model);
|
|
7008
7097
|
if (input.resume) args.push("resume", input.resume);
|
|
7009
7098
|
if (input.initialPrompt) args.push("--", input.initialPrompt);
|
|
@@ -13237,17 +13326,21 @@ function handleRateLimitEvent(event, host) {
|
|
|
13237
13326
|
const { rate_limit_info } = event;
|
|
13238
13327
|
logger4.info("Rate limit event received", { rate_limit_info });
|
|
13239
13328
|
const status = rate_limit_info.status;
|
|
13329
|
+
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13240
13330
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
13241
13331
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
13242
13332
|
host.connection.sendEvent({
|
|
13243
13333
|
type: "rate_limit_update",
|
|
13244
13334
|
rateLimitType: rate_limit_info.rateLimitType,
|
|
13245
13335
|
utilization,
|
|
13246
|
-
status
|
|
13336
|
+
status,
|
|
13337
|
+
// The server parses this into the key's sessionResetsAt / weeklyResetsAt;
|
|
13338
|
+
// Codex reports a reset instant with every gauge, so allowed events carry
|
|
13339
|
+
// it too, not only rejections.
|
|
13340
|
+
...resetsAt ? { resetsAt } : {}
|
|
13247
13341
|
});
|
|
13248
13342
|
}
|
|
13249
13343
|
if (status === "rejected") {
|
|
13250
|
-
const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
|
|
13251
13344
|
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
13252
13345
|
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
13253
13346
|
host.connection.sendEvent({ type: "error", message });
|
|
@@ -14157,6 +14250,7 @@ function buildQueryOptions(host, context) {
|
|
|
14157
14250
|
effort: settings.effort,
|
|
14158
14251
|
thinking: settings.thinking,
|
|
14159
14252
|
betas: settings.betas,
|
|
14253
|
+
codex: settings.codex,
|
|
14160
14254
|
abortController: host.abortController ?? void 0,
|
|
14161
14255
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
14162
14256
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
@@ -14312,6 +14406,7 @@ async function* watchForParkedTui(inner, host, opts) {
|
|
|
14312
14406
|
const timer = setTimeout(() => {
|
|
14313
14407
|
parked = true;
|
|
14314
14408
|
host.connection.emitStatus("waiting_for_input");
|
|
14409
|
+
host.callbacks.onSubmittedPtyParked?.();
|
|
14315
14410
|
void host.callbacks.onStatusChange("waiting_for_input");
|
|
14316
14411
|
}, PARKED_TUI_GRACE_MS);
|
|
14317
14412
|
const silenceTimeoutMs = resolveTurnSilenceTimeoutMs();
|
|
@@ -15367,6 +15462,9 @@ async function runUsageProbe(deps = {}) {
|
|
|
15367
15462
|
// src/execution/usage-sampler.ts
|
|
15368
15463
|
var logger7 = createServiceLogger("usage-sampler");
|
|
15369
15464
|
var NO_SAMPLES = { samples: [], unmeasurable: null };
|
|
15465
|
+
function usesNativeUsageReporting(env = process.env) {
|
|
15466
|
+
return env.CONVEYOR_TUI === "codex";
|
|
15467
|
+
}
|
|
15370
15468
|
function isAttributable(identity, sessionToken) {
|
|
15371
15469
|
if (!identity) return { ok: true };
|
|
15372
15470
|
if (identity.hasRefreshToken && !identity.isConveyorOwned) {
|
|
@@ -15688,7 +15786,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
15688
15786
|
onHeartbeat: () => {
|
|
15689
15787
|
const loopStatus = this.refreshLoopStatus();
|
|
15690
15788
|
this.connection.sendHeartbeat(this.loopLag.takeMaxLagMs(), loopStatus);
|
|
15691
|
-
this.
|
|
15789
|
+
void this.refreshGateStatusAndMaybeWake();
|
|
15692
15790
|
},
|
|
15693
15791
|
onIdleTimeout: () => {
|
|
15694
15792
|
if (this.deferShutdownForLiveChild("idle")) return;
|
|
@@ -15829,6 +15927,10 @@ var SessionRunner = class _SessionRunner {
|
|
|
15829
15927
|
/** One-shot latch: at most one watchdog wake per idle episode. Reset when a
|
|
15830
15928
|
* turn runs. */
|
|
15831
15929
|
backgroundWakeFiredThisIdle = false;
|
|
15930
|
+
/** Start of the last active episode. A gate can be launched shortly before
|
|
15931
|
+
* the agent declares completion, so idle-start freshness would reject its
|
|
15932
|
+
* terminal receipt as stale. */
|
|
15933
|
+
activeEpisodeStartedAt = Date.now();
|
|
15832
15934
|
/** When the current idle episode began — an exit sentinel older than this
|
|
15833
15935
|
* appeared while a turn could still have consumed it, so it never wakes. */
|
|
15834
15936
|
idleEpisodeStartedAt = 0;
|
|
@@ -15843,22 +15945,30 @@ var SessionRunner = class _SessionRunner {
|
|
|
15843
15945
|
* evidence of orphaned background work — a tracked launch with no live gate
|
|
15844
15946
|
* process continuously past `ORPHAN_WAKE_GRACE_MS`, or an unconsumed
|
|
15845
15947
|
* `gates/<label>.exit` sentinel that appeared this idle episode — never on
|
|
15846
|
-
* "the agent seems stuck". A `completed` agent
|
|
15847
|
-
*
|
|
15948
|
+
* "the agent seems stuck". A `completed` agent remains parked unless a
|
|
15949
|
+
* current-episode generic receipt proves a required gate died without an
|
|
15950
|
+
* exit record, and a live pid always suppresses the wake.
|
|
15848
15951
|
*/
|
|
15849
15952
|
maybeWakeForOrphanedBackgroundWork() {
|
|
15850
15953
|
try {
|
|
15851
|
-
if (this._state !== "idle" || this.stopped
|
|
15954
|
+
if (this._state !== "idle" || this.stopped) {
|
|
15852
15955
|
this.orphanGraceStartedAt = null;
|
|
15853
15956
|
return;
|
|
15854
15957
|
}
|
|
15855
15958
|
if (this.backgroundWakeFiredThisIdle) return;
|
|
15856
15959
|
if (!this.inputResolver) return;
|
|
15960
|
+
const abandoned = listAbandonedGateReceipts().find(
|
|
15961
|
+
(receipt) => receipt.mtimeMs >= this.activeEpisodeStartedAt
|
|
15962
|
+
);
|
|
15963
|
+
if (this.completedThisTurn && !abandoned) {
|
|
15964
|
+
this.orphanGraceStartedAt = null;
|
|
15965
|
+
return;
|
|
15966
|
+
}
|
|
15857
15967
|
if (isHeavyGateActive()) {
|
|
15858
15968
|
this.orphanGraceStartedAt = null;
|
|
15859
15969
|
return;
|
|
15860
15970
|
}
|
|
15861
|
-
const evidence = this.findOrphanEvidence(Date.now());
|
|
15971
|
+
const evidence = abandoned ? this.abandonedReceiptEvidence(abandoned) : this.findOrphanEvidence(Date.now());
|
|
15862
15972
|
if (!evidence) return;
|
|
15863
15973
|
process.stderr.write(
|
|
15864
15974
|
`[conveyor-agent] Background-work watchdog: waking agent \u2014 ${evidence.description}
|
|
@@ -15867,11 +15977,16 @@ var SessionRunner = class _SessionRunner {
|
|
|
15867
15977
|
this.backgroundWakeFiredThisIdle = true;
|
|
15868
15978
|
this.orphanGraceStartedAt = null;
|
|
15869
15979
|
if (evidence.sentinel) rmSync(evidence.sentinel.path, { force: true });
|
|
15980
|
+
if (evidence.abandonedReceipt) rmSync(evidence.abandonedReceipt.path, { force: true });
|
|
15870
15981
|
this.backgroundWork.clear();
|
|
15982
|
+
if (evidence.abandonedReceipt) {
|
|
15983
|
+
this.completedThisTurn = false;
|
|
15984
|
+
this.dormantDeadline = null;
|
|
15985
|
+
}
|
|
15871
15986
|
const resolver = this.inputResolver;
|
|
15872
15987
|
this.inputResolver = null;
|
|
15873
15988
|
resolver({
|
|
15874
|
-
content: `A
|
|
15989
|
+
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
15990
|
userId: "system",
|
|
15876
15991
|
source: "background_work_check"
|
|
15877
15992
|
});
|
|
@@ -15902,6 +16017,25 @@ var SessionRunner = class _SessionRunner {
|
|
|
15902
16017
|
description: `${this.backgroundWork.pendingCount()} tracked background launch(es) with no live gate process for ${Math.round(ORPHAN_WAKE_GRACE_MS / 6e4)}+ minutes`
|
|
15903
16018
|
};
|
|
15904
16019
|
}
|
|
16020
|
+
abandonedReceiptEvidence(receipt) {
|
|
16021
|
+
return {
|
|
16022
|
+
abandonedReceipt: receipt,
|
|
16023
|
+
description: `gate '${receipt.label}' stopped before writing an exit receipt (pid ${receipt.pid})`
|
|
16024
|
+
};
|
|
16025
|
+
}
|
|
16026
|
+
/** Refresh PID liveness in the owning workbench namespace before checking
|
|
16027
|
+
* terminal receipts. The local path is immediate; socket failures preserve
|
|
16028
|
+
* the conservative cached state and never interrupt the heartbeat. */
|
|
16029
|
+
async refreshGateStatusAndMaybeWake() {
|
|
16030
|
+
try {
|
|
16031
|
+
await refreshGenericGateStatus();
|
|
16032
|
+
this.refreshLoopStatus();
|
|
16033
|
+
this.maybeWakeForOrphanedBackgroundWork();
|
|
16034
|
+
} catch (err) {
|
|
16035
|
+
process.stderr.write(`[conveyor-agent] Gate status refresh failed: ${err}
|
|
16036
|
+
`);
|
|
16037
|
+
}
|
|
16038
|
+
}
|
|
15905
16039
|
get sessionId() {
|
|
15906
16040
|
return this.connection.sessionId;
|
|
15907
16041
|
}
|
|
@@ -16564,7 +16698,7 @@ var SessionRunner = class _SessionRunner {
|
|
|
16564
16698
|
* `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
|
|
16565
16699
|
* pod has no OAuth token (e.g. API-key projects). */
|
|
16566
16700
|
async sampleAndReportKeyUsage() {
|
|
16567
|
-
if (this.stopped) return;
|
|
16701
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
16568
16702
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
16569
16703
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
16570
16704
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -16712,13 +16846,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
16712
16846
|
isAuto: this.config.isAuto
|
|
16713
16847
|
};
|
|
16714
16848
|
const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
|
|
16715
|
-
onStatusChange: (status) =>
|
|
16716
|
-
|
|
16717
|
-
this._state = "running";
|
|
16718
|
-
this.lifecycle.cancelIdleTimer();
|
|
16719
|
-
}
|
|
16720
|
-
return this.callbacks.onStatusChange(status);
|
|
16721
|
-
},
|
|
16849
|
+
onStatusChange: (status) => this.handleQueryStatus(status),
|
|
16850
|
+
onSubmittedPtyParked: () => this.applySubmittedPtyParkedStatus(),
|
|
16722
16851
|
onEvent: (event) => {
|
|
16723
16852
|
if (!this.agentLiveReported) {
|
|
16724
16853
|
this.agentLiveReported = true;
|
|
@@ -16963,8 +17092,7 @@ ${outcome.failures.join("\n")}
|
|
|
16963
17092
|
}
|
|
16964
17093
|
async setState(status) {
|
|
16965
17094
|
if (status === "running") {
|
|
16966
|
-
this.
|
|
16967
|
-
this.backgroundWakeFiredThisIdle = false;
|
|
17095
|
+
this.beginRunningEpisode();
|
|
16968
17096
|
}
|
|
16969
17097
|
if (status === "idle" && this._state !== "idle") this.idleEpisodeStartedAt = Date.now();
|
|
16970
17098
|
this._state = status;
|
|
@@ -16972,6 +17100,39 @@ ${outcome.failures.join("\n")}
|
|
|
16972
17100
|
await this.connection.emitStatus(status);
|
|
16973
17101
|
await this.callbacks.onStatusChange(status);
|
|
16974
17102
|
}
|
|
17103
|
+
/**
|
|
17104
|
+
* A submitted PTY query can park on an unexpected terminal dialog without
|
|
17105
|
+
* returning its turn. Keep the runner's local state (which drives heartbeat
|
|
17106
|
+
* liveness) in sync with that specific watchdog signal, but do not emit it
|
|
17107
|
+
* again: QueryBridge already sent it.
|
|
17108
|
+
*
|
|
17109
|
+
* Retry delays and pending questionnaires also report `waiting_for_input`,
|
|
17110
|
+
* but they retain their existing bounded liveness holds and must not change
|
|
17111
|
+
* `_state` here.
|
|
17112
|
+
*/
|
|
17113
|
+
applySubmittedPtyParkedStatus() {
|
|
17114
|
+
this._state = "waiting_for_input";
|
|
17115
|
+
this.refreshLoopStatus();
|
|
17116
|
+
}
|
|
17117
|
+
/** Forward an ordinary query status without changing liveness for transient
|
|
17118
|
+
* waiting states. The submitted-PTY watchdog calls its dedicated handler. */
|
|
17119
|
+
handleQueryStatus(status) {
|
|
17120
|
+
if (status === "running") {
|
|
17121
|
+
this.beginRunningEpisode();
|
|
17122
|
+
if (this._state === "waiting_for_input") {
|
|
17123
|
+
this._state = "running";
|
|
17124
|
+
this.lifecycle.cancelIdleTimer();
|
|
17125
|
+
}
|
|
17126
|
+
}
|
|
17127
|
+
return this.callbacks.onStatusChange(status);
|
|
17128
|
+
}
|
|
17129
|
+
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
17130
|
+
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
17131
|
+
beginRunningEpisode() {
|
|
17132
|
+
this.activeEpisodeStartedAt = Date.now();
|
|
17133
|
+
this.backgroundDeferStartedAt = null;
|
|
17134
|
+
this.backgroundWakeFiredThisIdle = false;
|
|
17135
|
+
}
|
|
16975
17136
|
async shutdown(finalState) {
|
|
16976
17137
|
process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
|
|
16977
17138
|
`);
|
|
@@ -17070,6 +17231,7 @@ export {
|
|
|
17070
17231
|
resolveSessionStart,
|
|
17071
17232
|
parseUsageGauges,
|
|
17072
17233
|
runUsageProbe,
|
|
17234
|
+
usesNativeUsageReporting,
|
|
17073
17235
|
sampleKeyUsage,
|
|
17074
17236
|
buildRateLimitEvents,
|
|
17075
17237
|
buildUnmeasurableEvent,
|
|
@@ -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() {
|
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,
|
|
@@ -26,14 +26,15 @@ import {
|
|
|
26
26
|
resolveTuiAdapter,
|
|
27
27
|
resolveTuiKindFromEnv,
|
|
28
28
|
runUsageProbe,
|
|
29
|
-
sampleKeyUsage
|
|
30
|
-
|
|
29
|
+
sampleKeyUsage,
|
|
30
|
+
usesNativeUsageReporting
|
|
31
|
+
} from "./chunk-N5LEVAGA.js";
|
|
31
32
|
import "./chunk-SR66HQKB.js";
|
|
32
33
|
import {
|
|
33
34
|
inheritedEnv,
|
|
34
35
|
resolvePtySpawn,
|
|
35
36
|
sessionTempBase
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-W5INK3NE.js";
|
|
37
38
|
import {
|
|
38
39
|
AgentConnection,
|
|
39
40
|
CodespacePortVisibility,
|
|
@@ -44,12 +45,12 @@ import {
|
|
|
44
45
|
createServiceLogger,
|
|
45
46
|
fetchBootstrap,
|
|
46
47
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-WS7QRB37.js";
|
|
48
49
|
import "./chunk-GL2DIQEQ.js";
|
|
49
50
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
|
-
import "./chunk-
|
|
51
|
+
import "./chunk-372R6E4C.js";
|
|
51
52
|
import "./chunk-IA45XHOA.js";
|
|
52
|
-
import "./chunk-
|
|
53
|
+
import "./chunk-SQM2BQ7H.js";
|
|
53
54
|
import "./chunk-KMB3BU4S.js";
|
|
54
55
|
import "./chunk-6Q6LQBWO.js";
|
|
55
56
|
|
|
@@ -236,7 +237,7 @@ var ProjectSessionRunner = class {
|
|
|
236
237
|
/** Report the running subscription key's rate-limit utilization (same path as
|
|
237
238
|
* the task runner). Best-effort — never throws, no-op without an OAuth token. */
|
|
238
239
|
async sampleAndReportKeyUsage() {
|
|
239
|
-
if (this.stopped) return;
|
|
240
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
240
241
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
241
242
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
242
243
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -575,7 +576,7 @@ var AdhocSessionRunner = class {
|
|
|
575
576
|
}
|
|
576
577
|
/** Single-key fallback: sample only the key this pod booted under. */
|
|
577
578
|
async sampleAndReportOwnKey() {
|
|
578
|
-
if (this.stopped) return;
|
|
579
|
+
if (this.stopped || usesNativeUsageReporting()) return;
|
|
579
580
|
const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
|
|
580
581
|
const { samples, unmeasurable } = await sampleKeyUsage(
|
|
581
582
|
process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
@@ -1205,7 +1206,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
|
1205
1206
|
|
|
1206
1207
|
// src/cli.ts
|
|
1207
1208
|
if (process.argv[2] === "boot") {
|
|
1208
|
-
const { runBoot } = await import("./boot-
|
|
1209
|
+
const { runBoot } = await import("./boot-4ZNOCKFZ.js");
|
|
1209
1210
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1210
1211
|
}
|
|
1211
1212
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1307,7 +1308,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
1307
1308
|
process.exit(1);
|
|
1308
1309
|
});
|
|
1309
1310
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1310
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1311
|
+
const { startWorkbenchServer } = await import("./server-L5CQ5EJB.js");
|
|
1311
1312
|
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
|
|
1312
1313
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
|
|
1313
1314
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
@@ -1458,7 +1459,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1458
1459
|
process.exit(1);
|
|
1459
1460
|
}
|
|
1460
1461
|
if (CONVEYOR_MODE === "serving") {
|
|
1461
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1462
|
+
const { runServingSession } = await import("./serve-boot-RU5BYIK4.js");
|
|
1462
1463
|
exitContext.runnerMode = "serving";
|
|
1463
1464
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1464
1465
|
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,23 @@ declare class SessionRunner {
|
|
|
1097
1107
|
* the tool starts work that outlives the turn. */
|
|
1098
1108
|
private noteToolUseForBackgroundWork;
|
|
1099
1109
|
private setState;
|
|
1110
|
+
/**
|
|
1111
|
+
* A submitted PTY query can park on an unexpected terminal dialog without
|
|
1112
|
+
* returning its turn. Keep the runner's local state (which drives heartbeat
|
|
1113
|
+
* liveness) in sync with that specific watchdog signal, but do not emit it
|
|
1114
|
+
* again: QueryBridge already sent it.
|
|
1115
|
+
*
|
|
1116
|
+
* Retry delays and pending questionnaires also report `waiting_for_input`,
|
|
1117
|
+
* but they retain their existing bounded liveness holds and must not change
|
|
1118
|
+
* `_state` here.
|
|
1119
|
+
*/
|
|
1120
|
+
private applySubmittedPtyParkedStatus;
|
|
1121
|
+
/** Forward an ordinary query status without changing liveness for transient
|
|
1122
|
+
* waiting states. The submitted-PTY watchdog calls its dedicated handler. */
|
|
1123
|
+
private handleQueryStatus;
|
|
1124
|
+
/** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
|
|
1125
|
+
* prefilled PTY callback already emitted `running` before it reaches here. */
|
|
1126
|
+
private beginRunningEpisode;
|
|
1100
1127
|
private shutdown;
|
|
1101
1128
|
private _finalState;
|
|
1102
1129
|
/** The final status after run() completes. Use to determine exit code. */
|
|
@@ -1152,6 +1179,10 @@ type UserQuestionSignal = {
|
|
|
1152
1179
|
interface AgentRunnerCallbacks {
|
|
1153
1180
|
onEvent: (event: Record<string, unknown>) => void | Promise<void>;
|
|
1154
1181
|
onStatusChange: (status: string) => void | Promise<void>;
|
|
1182
|
+
/** Fired only when a submitted PTY query stays silent past the parked-TUI
|
|
1183
|
+
* grace window. This differs from ordinary `waiting_for_input` reports:
|
|
1184
|
+
* retry delays and questionnaires keep the runner locally live. */
|
|
1185
|
+
onSubmittedPtyParked?: () => void;
|
|
1155
1186
|
/** Optional — fired on question-pending transitions (see UserQuestionSignal). */
|
|
1156
1187
|
onUserQuestion?: (signal: UserQuestionSignal) => void;
|
|
1157
1188
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionRunner,
|
|
3
3
|
unshallowRepo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N5LEVAGA.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.19",
|
|
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",
|
|
@@ -38,6 +38,42 @@ out in an **Environment** note — never assume; check which one you are in.
|
|
|
38
38
|
serially. Parallel fan-out is a deliberate choice the user makes by pressing
|
|
39
39
|
Build on the parent, not something a build session opts into.
|
|
40
40
|
|
|
41
|
+
## Goal and finish line
|
|
42
|
+
|
|
43
|
+
Which goal applies depends on the route below. State it once, then hold it: a
|
|
44
|
+
run ends when its finish line is true, not earlier.
|
|
45
|
+
|
|
46
|
+
**Task path goal:** a pull request for this card.
|
|
47
|
+
**Finish line:** the card in ReviewPR, the PR open, CI started. Then aftercare
|
|
48
|
+
per `## Aftercare` — in a pod you stay wake-able; standalone locally you stop.
|
|
49
|
+
**A task turn may end only when:** a harness-tracked background gate is in
|
|
50
|
+
flight (Claude Code only); you are blocked on a human with the question posted;
|
|
51
|
+
or the finish line is true.
|
|
52
|
+
|
|
53
|
+
**Pack path goal:** the WHOLE pack, not one child. Per child the goal covers
|
|
54
|
+
the full cycle: plan reviewed and promoted → implemented on the pack branch →
|
|
55
|
+
gated → reviewer-of-record pass → merged (locally) or
|
|
56
|
+
`mcp__conveyor__update_task` → ReviewDev (pod) → base synced. Then
|
|
57
|
+
cross-reference, then the finale.
|
|
58
|
+
**Finish line:** every child is ReviewDev, Complete, or Cancelled AND the
|
|
59
|
+
parent's finale PR into the base branch is open with CI started. One child done
|
|
60
|
+
is not the finish line. A turn that ends with a ready child and no wake armed
|
|
61
|
+
is the failure this section exists to prevent.
|
|
62
|
+
**A pack turn may end only when:** a harness-tracked background gate is in
|
|
63
|
+
flight (Claude Code only — Codex holds the gate's command session open
|
|
64
|
+
instead); you are blocked on a human with the parked protocol posted; or the
|
|
65
|
+
finale PR is open and green-and-quiet. Nothing else. Anything else is a stalled
|
|
66
|
+
run, not a paused one.
|
|
67
|
+
|
|
68
|
+
**Runtime:** *Codex CLI* — before the first step (Setup step 1 on the pack
|
|
69
|
+
path), create a thread goal with your goal tool whose objective is the finish
|
|
70
|
+
line sentence for this route, naming the card slug. Call `update_goal` with
|
|
71
|
+
`complete` only when that finish line is true (for a pack: the finale PR is
|
|
72
|
+
open); `blocked` only per the parked protocol; never because the budget is
|
|
73
|
+
low. *Claude Code* — there is no goal tool; the pacing rule in
|
|
74
|
+
[references/pack-path.md](references/pack-path.md) is the loop, and its pack
|
|
75
|
+
heartbeat is the fallback.
|
|
76
|
+
|
|
41
77
|
## Environment
|
|
42
78
|
|
|
43
79
|
Resolve which one you are in before touching git or the filesystem.
|
|
@@ -96,18 +96,39 @@ End a turn with children remaining ONLY when a wake is guaranteed:
|
|
|
96
96
|
|
|
97
97
|
- **A background gate is in flight.** Its completion notification re-invokes
|
|
98
98
|
you. (Launch it with the harness's `run_in_background`; hand-rolled
|
|
99
|
-
backgrounding wakes nothing.)
|
|
99
|
+
backgrounding wakes nothing.) *Codex CLI substitution:* Codex has no
|
|
100
|
+
background gate. Hold the gate's command session open until it returns an
|
|
101
|
+
exit result, then continue in the same turn — ending the turn is not how a
|
|
102
|
+
Codex runner waits.
|
|
100
103
|
- **You are blocked on a human** — the parked protocol below. The user's chat
|
|
101
104
|
reply is the wake.
|
|
102
105
|
- **The finale PR is open and quiet** — the Babysit tier. Green and quiet is
|
|
103
106
|
the end.
|
|
104
107
|
|
|
105
|
-
Any other end-of-turn
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
pack
|
|
108
|
+
Any other end-of-turn is runtime-specific:
|
|
109
|
+
|
|
110
|
+
**Claude Code.** Arm `ScheduleWakeup` first: 60–90s, prompt restating which
|
|
111
|
+
child comes next. That is the pod-sanctioned mechanism (the pod prompt allows
|
|
112
|
+
`ScheduleWakeup` exactly when nothing else will notify you), and it is a rare
|
|
113
|
+
exception, not the cadence. On top of it, run a **pack heartbeat**: at Setup,
|
|
114
|
+
before the first child, arm ONE long fallback wake (`ScheduleWakeup`,
|
|
115
|
+
1200–1500s) whose prompt is the pack continuation — "re-derive with
|
|
116
|
+
`mcp__conveyor__list_subtasks` and take the next ready child; if nothing is
|
|
117
|
+
ready and no gate is in flight, stop" — and re-arm it at every child boundary
|
|
118
|
+
so exactly one is outstanding. A heartbeat that fires mid-work re-derives
|
|
119
|
+
state and is a no-op. This is NOT the "insurance wakeup" the pod prompt
|
|
120
|
+
forbids: that rule is about a background job's own completion notification,
|
|
121
|
+
which does fire. Nothing at all notifies a pack between children, so the
|
|
122
|
+
heartbeat is the only guaranteed wake a Claude pack has. Do not delete it.
|
|
123
|
+
|
|
124
|
+
**Codex CLI.** There is no `ScheduleWakeup` and no `/loop`. The thread goal
|
|
125
|
+
created per the SKILL.md *Goal and finish line* section is the continuation:
|
|
126
|
+
an idle thread is re-prompted to keep working toward it. The only way to leave
|
|
127
|
+
a child ready is therefore to keep working in the same turn — merge, next
|
|
128
|
+
child, merge. Mark the goal complete only when the finale PR is open.
|
|
129
|
+
|
|
130
|
+
Never end a turn "to report progress" — in either runtime, chat is where
|
|
131
|
+
progress goes, and posting there does not end the pack.
|
|
111
132
|
|
|
112
133
|
**Pre-exit invariant.** Before ending ANY turn, re-run
|
|
113
134
|
`mcp__conveyor__list_subtasks`. A child that is `Open` with its dependencies
|
|
@@ -19,6 +19,34 @@ identification decides.** Moving the card to Open fires identification
|
|
|
19
19
|
automatically (story points, icon, agent, and tags if you set none). Never set
|
|
20
20
|
icon or story points yourself, and never `start_task` unless the user asks.
|
|
21
21
|
|
|
22
|
+
## Goal and finish line
|
|
23
|
+
|
|
24
|
+
**Goal:** a plan on this card that a context-free executor can build, or one
|
|
25
|
+
batched round of questions for a human when scope is genuinely ambiguous.
|
|
26
|
+
**Finish line:** the plan is saved with `mcp__conveyor__update_task` AND the
|
|
27
|
+
recommendation is posted with `mcp__conveyor__post_to_chat` and
|
|
28
|
+
`milestone: "plan_ready"`. In a pod, story points and risk are also set
|
|
29
|
+
(`mcp__conveyor__update_task_properties`) and plan mode is exited.
|
|
30
|
+
**A turn may end only when:** (a) the finish line above is true, or (b) an
|
|
31
|
+
`AskUserQuestion` is pending. Nothing else. Anything else is a stalled run, not
|
|
32
|
+
a paused one.
|
|
33
|
+
|
|
34
|
+
**Never ask whether to start building, and never offer to.** Once the plan is
|
|
35
|
+
saved, the handoff is not yours: in the cloud the system spawns a separate
|
|
36
|
+
Builder session for the card, and locally a human runs `/conveyor-build` when
|
|
37
|
+
they are ready. Until one of those happens, every further turn on this card is
|
|
38
|
+
a plan iteration — read the feedback, revise, re-save, re-post `plan_ready`.
|
|
39
|
+
Do not claim the card, do not touch the tree, and do not invoke
|
|
40
|
+
`/conveyor-build` unprompted. (Locally the user may ask for it in the same
|
|
41
|
+
session; that request is the only trigger.)
|
|
42
|
+
|
|
43
|
+
**Runtime:** *Codex CLI* — before Phase 0, create a thread goal with your goal
|
|
44
|
+
tool whose objective is: "Save a plan on card <slug> that a context-free
|
|
45
|
+
builder can execute, or ask the blocking questions; do not start
|
|
46
|
+
implementation." Mark it complete only when the finish line is true, never
|
|
47
|
+
because the budget is low. *Claude Code* — there is no goal tool; the finish
|
|
48
|
+
line and the two turn endings above are the loop.
|
|
49
|
+
|
|
22
50
|
## Phase 0 — Resolve context
|
|
23
51
|
|
|
24
52
|
1. `mcp__conveyor__get_connection_context` (all Conveyor tools fully-qualified;
|
|
@@ -135,7 +163,10 @@ touching Conveyor, unless they asked you to just ship it.
|
|
|
135
163
|
> does not exist in a pod. In a pod, the card exists and you are on it: save
|
|
136
164
|
> the plan with `mcp__conveyor__update_task` (`plan`, and `description` if it
|
|
137
165
|
> needs sharpening), post the same recommendation message to chat with
|
|
138
|
-
> `milestone: "plan_ready"`, and stop
|
|
166
|
+
> `milestone: "plan_ready"`, and stop. "Stop" means end the turn with no
|
|
167
|
+
> question about starting work — per *Goal and finish line*, the Builder is a
|
|
168
|
+
> separate session the system spawns, and every later turn here is a plan
|
|
169
|
+
> iteration. A pod does not promote its own card to
|
|
139
170
|
> Open. A parked plan is never `blocked`: that milestone means you cannot
|
|
140
171
|
> continue until a human acts, and it pages nobody. To page a person, ask them
|
|
141
172
|
> with AskUserQuestion. If the pod also has to leave
|
|
@@ -26,6 +26,30 @@ process, this skill loses: a project's own rules outrank a general one.
|
|
|
26
26
|
PR touches are not this PR's job — note them in chat if they matter, or file
|
|
27
27
|
a suggestion, but do not block on them.
|
|
28
28
|
|
|
29
|
+
## Goal and finish line
|
|
30
|
+
|
|
31
|
+
**Goal:** one verdict on this PR.
|
|
32
|
+
**Finish line:** the environment's verdict tool returned success —
|
|
33
|
+
`mcp__conveyor__approve_code_review` / `mcp__conveyor__request_code_changes`
|
|
34
|
+
in a pod, `mcp__conveyor__approve_task` / `mcp__conveyor__request_changes`
|
|
35
|
+
locally — OR a blocked note is posted with the specific blocker and no verdict
|
|
36
|
+
(the `## Blocked` case).
|
|
37
|
+
**A turn may end only when:** (a) the verdict call succeeded — read the
|
|
38
|
+
response; a "superseded by a newer review cycle" error is not success; (b) the
|
|
39
|
+
blocked note is posted; or (c) you pushed your own fix and are waiting for CI
|
|
40
|
+
before approving. Nothing else. Findings posted with no verdict called is a
|
|
41
|
+
stalled review, not a paused one.
|
|
42
|
+
|
|
43
|
+
**Reviewer of record for a pack child** has no verdict tool: its finish line is
|
|
44
|
+
the merge (locally) or the `mcp__conveyor__update_task` → ReviewDev write
|
|
45
|
+
(pod), and the pack path's goal wording in conveyor-build governs instead.
|
|
46
|
+
|
|
47
|
+
**Runtime:** *Codex CLI* — before reading the change, create a thread goal with
|
|
48
|
+
your goal tool: "Render exactly one review verdict on PR <n> for card <slug>."
|
|
49
|
+
Mark it complete only after the verdict call succeeded, never because the
|
|
50
|
+
budget is low. *Claude Code* — there is no goal tool; the finish line above is
|
|
51
|
+
the loop.
|
|
52
|
+
|
|
29
53
|
## Read the change
|
|
30
54
|
|
|
31
55
|
1. `mcp__conveyor__get_task` for the plan — you cannot judge "does this do what
|
|
@@ -142,8 +166,10 @@ is the reviewer, and the independent review happens later on the pack's PR into
|
|
|
142
166
|
|
|
143
167
|
- Apply the same criteria above. The absence of an automated pass makes this
|
|
144
168
|
review more load-bearing, not less.
|
|
145
|
-
- There is **no verdict tool** — merging the child PR
|
|
146
|
-
|
|
169
|
+
- There is **no verdict tool** — merging the child PR (locally) or the
|
|
170
|
+
`mcp__conveyor__update_task` → ReviewDev write (pod) is the approval, and
|
|
171
|
+
that write or merge is this review's finish line; the pack path's goal
|
|
172
|
+
wording applies. Say what you checked in chat so the record exists.
|
|
147
173
|
- You wrote this code, which makes self-review the weak point. An independent
|
|
148
174
|
reviewer with the diff and no memory of writing it catches what you cannot.
|
|
149
175
|
|
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
|
-
};
|