@rallycry/conveyor-agent 10.13.71 → 11.0.0
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-PKHZUDAC.js → boot-PKQ2I66D.js} +56 -5
- package/dist/chunk-3F4ZZKCA.js +291 -0
- package/dist/{chunk-QU53HND5.js → chunk-GL2DIQEQ.js} +116 -47
- package/dist/chunk-JQVAWRVL.js +697 -0
- package/dist/chunk-N4WSUTGV.js +2564 -0
- package/dist/{chunk-7MMECTTJ.js → chunk-PEEGCZAR.js} +2257 -3701
- package/dist/{chunk-DOB2XE2I.js → chunk-UBDSLM44.js} +4 -2
- package/dist/{chunk-GJXAAPJ6.js → chunk-W4LZ7R6Z.js} +117 -400
- package/dist/chunk-XORJ6SII.js +46 -0
- package/dist/cli.js +304 -804
- package/dist/index.d.ts +170 -3
- package/dist/index.js +9 -5
- package/dist/serve-boot-YPAUKENG.js +225 -0
- package/dist/{server-CC7KUJOK.js → server-7XH7RYUX.js} +3 -2
- package/package.json +5 -4
- package/skills/conveyor-build/SKILL.md +262 -0
- package/skills/conveyor-build/references/pack-path.md +224 -0
- package/skills/conveyor-build/references/task-path.md +67 -0
- package/skills/conveyor-consensus/SKILL.md +99 -0
- package/skills/conveyor-consensus/references/doc-template.html +204 -0
- package/skills/conveyor-local-loop/SKILL.md +258 -0
- package/skills/conveyor-meeting-review/SKILL.md +72 -0
- package/skills/conveyor-plan/SKILL.md +177 -0
- package/skills/conveyor-plan/references/plan-format.md +134 -0
- package/skills/conveyor-review/SKILL.md +161 -0
- package/skills/conveyor-start/SKILL.md +106 -0
- package/skills/conveyor-triage/SKILL.md +174 -0
- package/skills/conveyor-workflows/SKILL.md +195 -0
- package/skills/conveyor-workflows/references/mcp-setup.md +43 -0
|
@@ -2,21 +2,24 @@ import {
|
|
|
2
2
|
TOMBSTONE_MESSAGE,
|
|
3
3
|
isLegacyEntrypointLaunch
|
|
4
4
|
} from "./chunk-R3FDJQL6.js";
|
|
5
|
+
import {
|
|
6
|
+
readAgentVersion
|
|
7
|
+
} from "./chunk-XORJ6SII.js";
|
|
5
8
|
import {
|
|
6
9
|
GitPrepJob,
|
|
7
10
|
defaultGit,
|
|
8
11
|
ensureDir,
|
|
9
|
-
readAgentVersion,
|
|
10
12
|
redactToken,
|
|
11
13
|
reportBootMilestone
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GL2DIQEQ.js";
|
|
13
15
|
import {
|
|
14
16
|
workbenchPort
|
|
15
17
|
} from "./chunk-KMB3BU4S.js";
|
|
16
18
|
import {
|
|
17
19
|
startWorkbenchServer
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-UBDSLM44.js";
|
|
21
|
+
import "./chunk-3F4ZZKCA.js";
|
|
22
|
+
import "./chunk-W4LZ7R6Z.js";
|
|
20
23
|
import {
|
|
21
24
|
DEFAULT_WORKBENCH_PORT
|
|
22
25
|
} from "./chunk-6Q6LQBWO.js";
|
|
@@ -98,6 +101,8 @@ function decodeJwtClaims(jwt) {
|
|
|
98
101
|
function resolveModeEnv(claims) {
|
|
99
102
|
if (claims.mode === "review") return { CONVEYOR_MODE: "code-review" };
|
|
100
103
|
if (claims.mode === "pack") return { CONVEYOR_MODE: "pack" };
|
|
104
|
+
if (claims.mode === "plan") return { CONVEYOR_MODE: "plan" };
|
|
105
|
+
if (claims.mode === "serving") return { CONVEYOR_MODE: "serving" };
|
|
101
106
|
if (claims.mode === "adhoc") return { CONVEYOR_MODE: "adhoc" };
|
|
102
107
|
if (!claims.taskId && claims.projectId) return { CONVEYOR_MODE: "pm" };
|
|
103
108
|
return {};
|
|
@@ -849,6 +854,7 @@ async function startWorkspaceSshd(exec, log, port = process.env.CONVEYOR_WORKSPA
|
|
|
849
854
|
// src/boot/workspace-extras.ts
|
|
850
855
|
import {
|
|
851
856
|
appendFileSync as appendFileSync3,
|
|
857
|
+
cpSync,
|
|
852
858
|
existsSync as existsSync3,
|
|
853
859
|
lstatSync as lstatSync2,
|
|
854
860
|
mkdirSync as mkdirSync3,
|
|
@@ -859,7 +865,8 @@ import {
|
|
|
859
865
|
statSync as statSync3,
|
|
860
866
|
symlinkSync as symlinkSync2
|
|
861
867
|
} from "fs";
|
|
862
|
-
import { join as join5 } from "path";
|
|
868
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
869
|
+
import { fileURLToPath } from "url";
|
|
863
870
|
function errText(err) {
|
|
864
871
|
return redactToken(err instanceof Error ? err.message : String(err));
|
|
865
872
|
}
|
|
@@ -1013,6 +1020,49 @@ function linkGrimoireSkills(workspace, log) {
|
|
|
1013
1020
|
excludeLinkedSkills(workspace, linkedNames, log);
|
|
1014
1021
|
log.info(`[boot] Linked ${linkedNames.length} grimoire skills into ${targetDir}`);
|
|
1015
1022
|
}
|
|
1023
|
+
function bundledSkillsDir(moduleUrl = import.meta.url) {
|
|
1024
|
+
try {
|
|
1025
|
+
const dir = join5(dirname2(fileURLToPath(moduleUrl)), "..", "skills");
|
|
1026
|
+
return isDir2(dir) ? dir : null;
|
|
1027
|
+
} catch {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
function materializeBundledSkills(workspace, log, sourceDirOverride) {
|
|
1032
|
+
const sourceDir = sourceDirOverride === void 0 ? bundledSkillsDir() : sourceDirOverride;
|
|
1033
|
+
if (!sourceDir) {
|
|
1034
|
+
log.warn(
|
|
1035
|
+
"[boot] WARN: no bundled skills payload in this agent build. Cloud modes that invoke a skill will have no workflow to follow. This is a packaging fault (tsup copySkillsPayload did not run), not a workspace problem."
|
|
1036
|
+
);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const targetDir = join5(workspace, ".claude", "skills");
|
|
1040
|
+
try {
|
|
1041
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
1042
|
+
} catch {
|
|
1043
|
+
log.warn(`[boot] WARN: unable to create ${targetDir}; skipping bundled skills`);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const copied = [];
|
|
1047
|
+
let names = [];
|
|
1048
|
+
try {
|
|
1049
|
+
names = readdirSync2(sourceDir);
|
|
1050
|
+
} catch {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
for (const name of names) {
|
|
1054
|
+
if (!isDir2(join5(sourceDir, name))) continue;
|
|
1055
|
+
const target = join5(targetDir, name);
|
|
1056
|
+
if (existsSync3(target) || isSymlink(target)) continue;
|
|
1057
|
+
try {
|
|
1058
|
+
cpSync(join5(sourceDir, name), target, { recursive: true, dereference: true });
|
|
1059
|
+
copied.push(name);
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
excludeLinkedSkills(workspace, copied, log);
|
|
1064
|
+
log.info(`[boot] Materialized ${copied.length} bundled skills into ${targetDir}`);
|
|
1065
|
+
}
|
|
1016
1066
|
async function neutralizeGrimoirePointer(workspace, git, log) {
|
|
1017
1067
|
if (!existsSync3(join5(workspace, ".claude", "grimoire"))) return;
|
|
1018
1068
|
try {
|
|
@@ -1054,6 +1104,7 @@ async function runPreReadyBinds(workspace, bundle, git, log) {
|
|
|
1054
1104
|
bindGraphifyBundle(workspace, graphifyEnv, log);
|
|
1055
1105
|
await ensureGrimoireSubmodule(workspace, bundle.githubToken ?? "", git, log);
|
|
1056
1106
|
linkGrimoireSkills(workspace, log);
|
|
1107
|
+
materializeBundledSkills(workspace, log);
|
|
1057
1108
|
await neutralizeGrimoirePointer(workspace, git, log);
|
|
1058
1109
|
}
|
|
1059
1110
|
async function cloneReferenceRepos(refsJson, git, log, referencesDir = "/workspaces/references") {
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ghHostsManagedByConveyor,
|
|
3
|
+
githubTokenFilePath
|
|
4
|
+
} from "./chunk-W4LZ7R6Z.js";
|
|
5
|
+
|
|
6
|
+
// src/harness/pty/pty-support.ts
|
|
7
|
+
import { stat } from "fs/promises";
|
|
8
|
+
import { tmpdir } from "os";
|
|
9
|
+
|
|
10
|
+
// src/harness/pty/spawn-args.ts
|
|
11
|
+
function resolveClaudeBinary() {
|
|
12
|
+
return process.env.CONVEYOR_CLAUDE_BIN ?? "claude";
|
|
13
|
+
}
|
|
14
|
+
function buildSpawnArgs(input) {
|
|
15
|
+
const args = [];
|
|
16
|
+
if (input.resume) {
|
|
17
|
+
args.push("--resume", input.resume);
|
|
18
|
+
} else if (input.sessionId) {
|
|
19
|
+
args.push("--session-id", input.sessionId);
|
|
20
|
+
}
|
|
21
|
+
args.push("--model", input.model);
|
|
22
|
+
if (input.permissionMode === "bypassPermissions") {
|
|
23
|
+
args.push("--dangerously-skip-permissions");
|
|
24
|
+
} else {
|
|
25
|
+
args.push("--permission-mode", "plan");
|
|
26
|
+
}
|
|
27
|
+
args.push("--settings", input.settingsPath);
|
|
28
|
+
if (input.appendSystemPrompt) {
|
|
29
|
+
args.push("--append-system-prompt", input.appendSystemPrompt);
|
|
30
|
+
}
|
|
31
|
+
if (input.mcpConfigPath) {
|
|
32
|
+
args.push("--mcp-config", input.mcpConfigPath);
|
|
33
|
+
if (input.strictMcpConfig) {
|
|
34
|
+
args.push("--strict-mcp-config");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
function spawnOptionsFingerprint(input) {
|
|
40
|
+
return JSON.stringify([
|
|
41
|
+
input.model,
|
|
42
|
+
input.permissionMode,
|
|
43
|
+
input.appendSystemPrompt ?? "",
|
|
44
|
+
input.cwd
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
var ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
48
|
+
function cleanTerminalOutput(raw, maxChars = 1200) {
|
|
49
|
+
const noAnsi = raw.replace(ANSI_CSI, "");
|
|
50
|
+
let out = "";
|
|
51
|
+
for (const ch of noAnsi) {
|
|
52
|
+
const code = ch.charCodeAt(0);
|
|
53
|
+
if (ch === "\r" || ch === "\n") out += "\n";
|
|
54
|
+
else if (ch === " ") out += ch;
|
|
55
|
+
else if (code < 32 || code === 127) continue;
|
|
56
|
+
else out += ch;
|
|
57
|
+
}
|
|
58
|
+
const lines = out.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
59
|
+
const text = lines.join("\n").trim();
|
|
60
|
+
return text.length > maxChars ? `\u2026${text.slice(-maxChars)}` : text;
|
|
61
|
+
}
|
|
62
|
+
function isMissingBinaryFailure(tail) {
|
|
63
|
+
return /execvp\(\d+\) failed|no such file or directory|command not found/i.test(tail);
|
|
64
|
+
}
|
|
65
|
+
function buildExitErrors(exitCode, rawOutput, binary) {
|
|
66
|
+
const errors = [`claude exited (code ${exitCode}) without a result`];
|
|
67
|
+
const tail = cleanTerminalOutput(rawOutput);
|
|
68
|
+
if (isMissingBinaryFailure(tail)) {
|
|
69
|
+
errors.push(
|
|
70
|
+
`The \`${binary}\` CLI could not be started \u2014 it is not installed or not on PATH. Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) or set CONVEYOR_CLAUDE_BIN to its absolute path.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (tail) {
|
|
74
|
+
errors.push(`Last terminal output before exit:
|
|
75
|
+
${tail}`);
|
|
76
|
+
}
|
|
77
|
+
return errors;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/harness/pty/pty-support.ts
|
|
81
|
+
var MAX_DIAGNOSTIC_OUTPUT = 4e3;
|
|
82
|
+
var MAX_BETWEEN_TURN_BUFFER = 500;
|
|
83
|
+
var SUBMIT_SETTLE_MS = 300;
|
|
84
|
+
var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
|
|
85
|
+
var SUBMIT_NUDGE_MAX_PRESSES = 5;
|
|
86
|
+
var SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5e3;
|
|
87
|
+
var SUBMIT_NUDGE_WINDOW_MS = 9e4;
|
|
88
|
+
var SUBMIT_REDELIVERY_MAX_ATTEMPTS = 2;
|
|
89
|
+
var PLAN_DIALOG_FIRST_PRESS_MS = 700;
|
|
90
|
+
var PLAN_DIALOG_INTERVAL_MS = 1500;
|
|
91
|
+
var PLAN_DIALOG_SLOW_INTERVAL_MS = 5e3;
|
|
92
|
+
var PLAN_DIALOG_FAST_WINDOW_MS = 1e4;
|
|
93
|
+
var PLAN_DIALOG_WINDOW_MS = 9e4;
|
|
94
|
+
var RAW_TUI_PROBE_SENTINEL = "zqx";
|
|
95
|
+
var RAW_TUI_PROBE_ACK_MS = 500;
|
|
96
|
+
var RAW_TUI_PROBE_RETRY_MS = 250;
|
|
97
|
+
var RAW_TUI_PROBE_POLL_MS = 20;
|
|
98
|
+
var RAW_TUI_FIRST_OUTPUT_MAX_MS = 1e4;
|
|
99
|
+
var RAW_TUI_INPUT_LIVE_MAX_MS = 2e4;
|
|
100
|
+
function envMs(name, fallback) {
|
|
101
|
+
const raw = Number(process.env[name]);
|
|
102
|
+
return Number.isFinite(raw) && raw > 0 ? raw : fallback;
|
|
103
|
+
}
|
|
104
|
+
function resolveSubmitSettleMs() {
|
|
105
|
+
return envMs("CONVEYOR_PTY_SUBMIT_SETTLE_MS", SUBMIT_SETTLE_MS);
|
|
106
|
+
}
|
|
107
|
+
function resolveRawTuiProbeTiming() {
|
|
108
|
+
const sentinel = process.env.CONVEYOR_PTY_RAW_PROBE_SENTINEL;
|
|
109
|
+
return {
|
|
110
|
+
// A sentinel must be non-empty (it is what we search for) and must stay
|
|
111
|
+
// free of digits and control chars — see the note on menu dialogs above.
|
|
112
|
+
sentinel: sentinel && /^[a-z]{1,8}$/.test(sentinel) ? sentinel : RAW_TUI_PROBE_SENTINEL,
|
|
113
|
+
ackMs: envMs("CONVEYOR_PTY_RAW_PROBE_ACK_MS", RAW_TUI_PROBE_ACK_MS),
|
|
114
|
+
retryMs: envMs("CONVEYOR_PTY_RAW_PROBE_RETRY_MS", RAW_TUI_PROBE_RETRY_MS),
|
|
115
|
+
pollMs: envMs("CONVEYOR_PTY_RAW_PROBE_POLL_MS", RAW_TUI_PROBE_POLL_MS),
|
|
116
|
+
firstOutputMaxMs: envMs("CONVEYOR_PTY_RAW_FIRST_OUTPUT_MAX_MS", RAW_TUI_FIRST_OUTPUT_MAX_MS),
|
|
117
|
+
maxMs: envMs("CONVEYOR_PTY_RAW_INPUT_LIVE_MAX_MS", RAW_TUI_INPUT_LIVE_MAX_MS)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function sentinelEchoed(rawOutput, sentinel) {
|
|
121
|
+
return cleanTerminalOutput(rawOutput, Number.MAX_SAFE_INTEGER).includes(sentinel);
|
|
122
|
+
}
|
|
123
|
+
var DEC_PRIVATE_MODE = new RegExp(`${String.fromCharCode(27)}\\[\\?[0-9;]+[hl]`);
|
|
124
|
+
function sawTerminalSetup(rawOutput) {
|
|
125
|
+
return DEC_PRIVATE_MODE.test(rawOutput);
|
|
126
|
+
}
|
|
127
|
+
function needsRawReadyGate(caps) {
|
|
128
|
+
return caps.rawPromptGate;
|
|
129
|
+
}
|
|
130
|
+
function resolveSubmitNudgeTiming() {
|
|
131
|
+
return {
|
|
132
|
+
intervalMs: envMs("CONVEYOR_PTY_NUDGE_INTERVAL_MS", SUBMIT_NUDGE_INTERVAL_MS),
|
|
133
|
+
slowIntervalMs: envMs("CONVEYOR_PTY_NUDGE_SLOW_INTERVAL_MS", SUBMIT_NUDGE_SLOW_INTERVAL_MS),
|
|
134
|
+
maxPresses: SUBMIT_NUDGE_MAX_PRESSES,
|
|
135
|
+
windowMs: envMs("CONVEYOR_PTY_NUDGE_WINDOW_MS", SUBMIT_NUDGE_WINDOW_MS)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function resolveSubmitRedeliveryMaxAttempts() {
|
|
139
|
+
const value = process.env.CONVEYOR_PTY_SUBMIT_REDELIVERY_MAX;
|
|
140
|
+
if (!value) return SUBMIT_REDELIVERY_MAX_ATTEMPTS;
|
|
141
|
+
const raw = Number(value);
|
|
142
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : SUBMIT_REDELIVERY_MAX_ATTEMPTS;
|
|
143
|
+
}
|
|
144
|
+
function resolvePlanDialogTiming() {
|
|
145
|
+
return {
|
|
146
|
+
firstPressMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FIRST_PRESS_MS", PLAN_DIALOG_FIRST_PRESS_MS),
|
|
147
|
+
intervalMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_INTERVAL_MS", PLAN_DIALOG_INTERVAL_MS),
|
|
148
|
+
slowIntervalMs: envMs(
|
|
149
|
+
"CONVEYOR_PTY_PLAN_DIALOG_SLOW_INTERVAL_MS",
|
|
150
|
+
PLAN_DIALOG_SLOW_INTERVAL_MS
|
|
151
|
+
),
|
|
152
|
+
fastWindowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_FAST_WINDOW_MS", PLAN_DIALOG_FAST_WINDOW_MS),
|
|
153
|
+
windowMs: envMs("CONVEYOR_PTY_PLAN_DIALOG_WINDOW_MS", PLAN_DIALOG_WINDOW_MS)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function turnOptionsFrom(options) {
|
|
157
|
+
return {
|
|
158
|
+
canUseTool: options.canUseTool,
|
|
159
|
+
promptDelivery: options.promptDelivery,
|
|
160
|
+
planDialogAutoAccept: options.planDialogAutoAccept,
|
|
161
|
+
abortController: options.abortController
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
var KILL_ESCALATION_MS = 5e3;
|
|
165
|
+
function killPtyWithEscalation(pty, hasExited, escalationMs = KILL_ESCALATION_MS) {
|
|
166
|
+
try {
|
|
167
|
+
pty.kill();
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
if (hasExited()) return;
|
|
172
|
+
try {
|
|
173
|
+
pty.kill("SIGKILL");
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}, escalationMs);
|
|
177
|
+
timer.unref?.();
|
|
178
|
+
}
|
|
179
|
+
function isRecord(value) {
|
|
180
|
+
return typeof value === "object" && value !== null;
|
|
181
|
+
}
|
|
182
|
+
function extractSpawn(mod) {
|
|
183
|
+
if (!isRecord(mod)) return null;
|
|
184
|
+
if (typeof mod.spawn === "function") return mod.spawn;
|
|
185
|
+
const def = mod.default;
|
|
186
|
+
if (isRecord(def) && typeof def.spawn === "function") return def.spawn;
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
async function loadPtySpawn() {
|
|
190
|
+
const mod = await import("node-pty");
|
|
191
|
+
const spawn = extractSpawn(mod);
|
|
192
|
+
if (!spawn) throw new Error("node-pty: spawn export not found");
|
|
193
|
+
return spawn;
|
|
194
|
+
}
|
|
195
|
+
async function resolvePtySpawn() {
|
|
196
|
+
const { workbenchEnabled } = await import("./mode-ZJSOSLGU.js");
|
|
197
|
+
if (!workbenchEnabled()) return loadPtySpawn();
|
|
198
|
+
const { getWorkbenchClient } = await import("./client-IG6C5F2G.js");
|
|
199
|
+
return (file, args, options) => getWorkbenchClient().spawnPty(file, args, options);
|
|
200
|
+
}
|
|
201
|
+
function sessionTempBase() {
|
|
202
|
+
return process.env.CONVEYOR_SHARED_DIR ?? tmpdir();
|
|
203
|
+
}
|
|
204
|
+
function inheritedEnv(socketPath) {
|
|
205
|
+
const env = {};
|
|
206
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
207
|
+
if (typeof value === "string") env[key] = value;
|
|
208
|
+
}
|
|
209
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
210
|
+
delete env.ANTHROPIC_API_KEY;
|
|
211
|
+
}
|
|
212
|
+
if (ghHostsManagedByConveyor() && env.CONVEYOR_KEEP_GH_TOKEN_ENV !== "1") {
|
|
213
|
+
delete env.GH_TOKEN;
|
|
214
|
+
delete env.GITHUB_TOKEN;
|
|
215
|
+
delete env.CONVEYOR_GITHUB_TOKEN;
|
|
216
|
+
env.CONVEYOR_GITHUB_TOKEN_FILE = githubTokenFilePath();
|
|
217
|
+
}
|
|
218
|
+
if (socketPath) {
|
|
219
|
+
env.CONVEYOR_HOOK_SOCKET = socketPath;
|
|
220
|
+
}
|
|
221
|
+
env.MCP_TIMEOUT ??= "60000";
|
|
222
|
+
env.MCP_TOOL_TIMEOUT ??= "180000";
|
|
223
|
+
return env;
|
|
224
|
+
}
|
|
225
|
+
function buildPromptBytes(text) {
|
|
226
|
+
return `\x1B[200~${text}\x1B[201~`;
|
|
227
|
+
}
|
|
228
|
+
function renderPromptContentText(content) {
|
|
229
|
+
return content.map((block) => {
|
|
230
|
+
const b = block;
|
|
231
|
+
if (b?.type === "text" && typeof b.text === "string") return b.text;
|
|
232
|
+
if (b?.type === "image") {
|
|
233
|
+
return `[Image attachment \u2014 use list_task_files / get_attachment to view]`;
|
|
234
|
+
}
|
|
235
|
+
return JSON.stringify(block);
|
|
236
|
+
}).join("\n\n");
|
|
237
|
+
}
|
|
238
|
+
async function transcriptSize(path) {
|
|
239
|
+
try {
|
|
240
|
+
return (await stat(path)).size;
|
|
241
|
+
} catch {
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function parseUserQuestions(input) {
|
|
246
|
+
if (!Array.isArray(input.questions)) return [];
|
|
247
|
+
const questions = [];
|
|
248
|
+
for (const entry of input.questions) {
|
|
249
|
+
if (!isRecord(entry)) continue;
|
|
250
|
+
if (typeof entry.question !== "string") continue;
|
|
251
|
+
const options = Array.isArray(entry.options) ? entry.options.filter(isRecord).filter((o) => typeof o.label === "string").map((o) => ({
|
|
252
|
+
label: o.label,
|
|
253
|
+
description: typeof o.description === "string" ? o.description : ""
|
|
254
|
+
})) : [];
|
|
255
|
+
questions.push({
|
|
256
|
+
question: entry.question,
|
|
257
|
+
header: typeof entry.header === "string" ? entry.header : "",
|
|
258
|
+
options,
|
|
259
|
+
...typeof entry.multiSelect === "boolean" ? { multiSelect: entry.multiSelect } : {}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return questions;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export {
|
|
266
|
+
resolveClaudeBinary,
|
|
267
|
+
buildSpawnArgs,
|
|
268
|
+
spawnOptionsFingerprint,
|
|
269
|
+
cleanTerminalOutput,
|
|
270
|
+
buildExitErrors,
|
|
271
|
+
MAX_DIAGNOSTIC_OUTPUT,
|
|
272
|
+
MAX_BETWEEN_TURN_BUFFER,
|
|
273
|
+
resolveSubmitSettleMs,
|
|
274
|
+
resolveRawTuiProbeTiming,
|
|
275
|
+
sentinelEchoed,
|
|
276
|
+
sawTerminalSetup,
|
|
277
|
+
needsRawReadyGate,
|
|
278
|
+
resolveSubmitNudgeTiming,
|
|
279
|
+
resolveSubmitRedeliveryMaxAttempts,
|
|
280
|
+
resolvePlanDialogTiming,
|
|
281
|
+
turnOptionsFrom,
|
|
282
|
+
killPtyWithEscalation,
|
|
283
|
+
loadPtySpawn,
|
|
284
|
+
resolvePtySpawn,
|
|
285
|
+
sessionTempBase,
|
|
286
|
+
inheritedEnv,
|
|
287
|
+
buildPromptBytes,
|
|
288
|
+
renderPromptContentText,
|
|
289
|
+
transcriptSize,
|
|
290
|
+
parseUserQuestions
|
|
291
|
+
};
|
|
@@ -2,61 +2,116 @@ import {
|
|
|
2
2
|
gitCredentialHelper,
|
|
3
3
|
syncGithubTokenFiles,
|
|
4
4
|
writeGitCredential
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-W4LZ7R6Z.js";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
// src/boot/git-prep.ts
|
|
8
|
+
import { execFile } from "child_process";
|
|
9
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
10
|
+
import { join as join2 } from "path";
|
|
11
|
+
import { promisify } from "util";
|
|
12
|
+
|
|
13
|
+
// src/boot/commit-trailers.ts
|
|
14
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
|
+
import { dirname, isAbsolute, join } from "path";
|
|
16
|
+
var HOOK_MARKER = "conveyor-commit-trailers";
|
|
17
|
+
var realFs = {
|
|
18
|
+
exists: (path) => existsSync(path),
|
|
19
|
+
read: (path) => readFileSync(path, "utf8"),
|
|
20
|
+
write: (path, content) => writeFileSync(path, content, "utf8"),
|
|
21
|
+
mkdirp: (path) => mkdirSync(path, { recursive: true }),
|
|
22
|
+
chmodExec: (path) => chmodSync(path, 493)
|
|
23
|
+
};
|
|
24
|
+
function buildHookScript() {
|
|
25
|
+
return `#!/bin/sh
|
|
26
|
+
# ${HOOK_MARKER} \u2014 stamps Conveyor card/driver/session provenance on each commit.
|
|
27
|
+
# Generated by @rallycry/conveyor-agent (boot/commit-trailers.ts); edits are
|
|
28
|
+
# overwritten on the next pod boot.
|
|
29
|
+
#
|
|
30
|
+
# Every path exits 0 on purpose: losing a commit to attribution bookkeeping
|
|
31
|
+
# would be a far worse outcome than losing the attribution.
|
|
32
|
+
MSG="$1"
|
|
33
|
+
[ -n "$MSG" ] || exit 0
|
|
34
|
+
|
|
35
|
+
stamp() {
|
|
36
|
+
[ -n "$2" ] || return 0
|
|
37
|
+
git interpret-trailers --in-place --if-exists doNothing --trailer "$1: $2" "$MSG" 2>/dev/null || true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stamp "Conveyor-Card" "$CONVEYOR_CARD_SLUG"
|
|
41
|
+
stamp "Conveyor-Driver" "$CONVEYOR_DRIVER_ID"
|
|
42
|
+
stamp "Conveyor-Session" "$CONVEYOR_SESSION_ID"
|
|
43
|
+
exit 0
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
function resolveHookTargets(repoDir, hooksPath, hasHusky) {
|
|
47
|
+
const huskyTarget = { hookPath: join(repoDir, ".husky", "commit-msg"), huskyManaged: true };
|
|
48
|
+
if (hooksPath?.replace(/\/+$/, "").endsWith(".husky/_")) return [huskyTarget];
|
|
49
|
+
const dir = hooksPath ? isAbsolute(hooksPath) ? hooksPath : join(repoDir, hooksPath) : join(repoDir, ".git", "hooks");
|
|
50
|
+
const resolved = { hookPath: join(dir, "commit-msg"), huskyManaged: false };
|
|
51
|
+
return hasHusky ? [resolved, huskyTarget] : [resolved];
|
|
52
|
+
}
|
|
53
|
+
function excludeFromRepo(repoDir, relPath, fs) {
|
|
54
|
+
const excludePath = join(repoDir, ".git", "info", "exclude");
|
|
55
|
+
const existing = fs.exists(excludePath) ? fs.read(excludePath) : "";
|
|
56
|
+
if (existing.split("\n").some((line) => line.trim() === relPath)) return;
|
|
57
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
58
|
+
fs.write(excludePath, `${existing}${separator}${relPath}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
function declaresHusky(repoDir, fs) {
|
|
62
|
+
if (fs.exists(join(repoDir, ".husky"))) return true;
|
|
63
|
+
const pkgPath = join(repoDir, "package.json");
|
|
64
|
+
if (!fs.exists(pkgPath)) return false;
|
|
65
|
+
try {
|
|
66
|
+
const pkg = JSON.parse(fs.read(pkgPath));
|
|
67
|
+
if (pkg.devDependencies?.husky ?? pkg.dependencies?.husky) return true;
|
|
68
|
+
return Boolean(pkg.scripts?.prepare?.includes("husky"));
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
33
72
|
}
|
|
34
|
-
function
|
|
73
|
+
async function installCommitTrailerHook(deps) {
|
|
74
|
+
const { git, repoDir, log } = deps;
|
|
75
|
+
const env = deps.env ?? process.env;
|
|
76
|
+
const fs = deps.fs ?? realFs;
|
|
77
|
+
if (!env.CONVEYOR_CARD_SLUG) return { state: "skipped", reason: "no-card" };
|
|
35
78
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
79
|
+
let hooksPath = null;
|
|
80
|
+
try {
|
|
81
|
+
const { stdout } = await git(["config", "--get", "core.hooksPath"], { cwd: repoDir });
|
|
82
|
+
hooksPath = stdout.trim() || null;
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
const targets = resolveHookTargets(repoDir, hooksPath, declaresHusky(repoDir, fs));
|
|
86
|
+
const written = [];
|
|
87
|
+
for (const { hookPath, huskyManaged } of targets) {
|
|
88
|
+
if (fs.exists(hookPath) && !fs.read(hookPath).includes(HOOK_MARKER)) {
|
|
89
|
+
log.warn(`[boot] WARN: commit-msg hook already present at ${hookPath}; leaving it alone`);
|
|
90
|
+
continue;
|
|
42
91
|
}
|
|
92
|
+
fs.mkdirp(dirname(hookPath));
|
|
93
|
+
fs.write(hookPath, buildHookScript());
|
|
94
|
+
fs.chmodExec(hookPath);
|
|
95
|
+
if (huskyManaged) excludeFromRepo(repoDir, ".husky/commit-msg", fs);
|
|
96
|
+
written.push(hookPath);
|
|
43
97
|
}
|
|
44
|
-
|
|
98
|
+
if (written.length === 0) return { state: "skipped", reason: "project-hook" };
|
|
99
|
+
log.info(`[boot] Commit trailers installed (${written.join(", ")})`);
|
|
100
|
+
return { state: "installed", paths: written };
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
103
|
+
log.warn(`[boot] WARN: commit trailer hook not installed: ${reason}`);
|
|
104
|
+
return { state: "failed", reason };
|
|
45
105
|
}
|
|
46
|
-
return null;
|
|
47
106
|
}
|
|
48
107
|
|
|
49
108
|
// src/boot/git-prep.ts
|
|
50
|
-
import { execFile } from "child_process";
|
|
51
|
-
import { existsSync, mkdirSync } from "fs";
|
|
52
|
-
import { join as join2 } from "path";
|
|
53
|
-
import { promisify } from "util";
|
|
54
109
|
var QUICK_GIT_TIMEOUT_MS = 6e4;
|
|
55
110
|
var FETCH_TIMEOUT_MS = 3e5;
|
|
56
111
|
var CLONE_TIMEOUT_MS = 6e5;
|
|
57
112
|
var execFileAsync = promisify(execFile);
|
|
58
113
|
function ensureDir(dir) {
|
|
59
|
-
|
|
114
|
+
mkdirSync2(dir, { recursive: true });
|
|
60
115
|
}
|
|
61
116
|
function defaultGit(args, opts = {}) {
|
|
62
117
|
return execFileAsync("git", args, {
|
|
@@ -185,6 +240,22 @@ async function clonePostAssignment(deps, paths) {
|
|
|
185
240
|
}
|
|
186
241
|
return syncTaskBranchToRepo(deps, paths);
|
|
187
242
|
}
|
|
243
|
+
async function withCommitTrailers(deps, paths, state) {
|
|
244
|
+
if (state.state !== "ready") return state;
|
|
245
|
+
await installCommitTrailerHook({
|
|
246
|
+
git: deps.git,
|
|
247
|
+
repoDir: paths.repoDir,
|
|
248
|
+
log: deps.log,
|
|
249
|
+
// The bundle, NOT process.env: git prep runs inside the workbench sidecar,
|
|
250
|
+
// whose own env holds only CONVEYOR_API_URL and POD_BOOTSTRAP_TOKEN.
|
|
251
|
+
// `bundle.envVars` (where CONVEYOR_CARD_SLUG rides) is merged into
|
|
252
|
+
// `ctx.childEnv` for the separate agent container and is never written back
|
|
253
|
+
// onto this process — so reading process.env here saw nothing and skipped
|
|
254
|
+
// the install on every real pod.
|
|
255
|
+
env: { ...process.env, ...deps.bundle.envVars }
|
|
256
|
+
});
|
|
257
|
+
return state;
|
|
258
|
+
}
|
|
188
259
|
async function prepareWorkspaceGit(deps) {
|
|
189
260
|
const { bundle, log } = deps;
|
|
190
261
|
const workspacesDir = deps.workspacesDir ?? "/workspaces";
|
|
@@ -207,17 +278,17 @@ async function prepareWorkspaceGit(deps) {
|
|
|
207
278
|
if (bundle.gitPlan.provider === "github") {
|
|
208
279
|
syncGithubTokenFiles(bundle.githubToken ?? credential.secret);
|
|
209
280
|
}
|
|
210
|
-
if (
|
|
281
|
+
if (existsSync2(join2(repoDir, ".git"))) {
|
|
211
282
|
log.info(
|
|
212
283
|
deps.podImage ? `[boot] Pod image \u2014 updating repo to latest (branch=${branch})...` : `[boot] Repo present (non-pod-image) \u2014 updating repo to latest (branch=${branch})...`
|
|
213
284
|
);
|
|
214
|
-
return await syncTaskBranchToRepo(deps, paths);
|
|
285
|
+
return await withCommitTrailers(deps, paths, await syncTaskBranchToRepo(deps, paths));
|
|
215
286
|
}
|
|
216
287
|
try {
|
|
217
|
-
|
|
288
|
+
mkdirSync2(workspacesDir, { recursive: true });
|
|
218
289
|
} catch {
|
|
219
290
|
}
|
|
220
|
-
return await clonePostAssignment(deps, paths);
|
|
291
|
+
return await withCommitTrailers(deps, paths, await clonePostAssignment(deps, paths));
|
|
221
292
|
} catch (err) {
|
|
222
293
|
log.error(`[boot] ERROR: git prep failed unexpectedly: ${errText(err)}`);
|
|
223
294
|
return { state: "failed", reason: "git prep failed unexpectedly" };
|
|
@@ -318,8 +389,6 @@ async function reportBootMilestone(opts) {
|
|
|
318
389
|
}
|
|
319
390
|
|
|
320
391
|
export {
|
|
321
|
-
mapChatHistory,
|
|
322
|
-
readAgentVersion,
|
|
323
392
|
FETCH_TIMEOUT_MS,
|
|
324
393
|
CLONE_TIMEOUT_MS,
|
|
325
394
|
ensureDir,
|