@rallycry/conveyor-agent 11.0.5 → 11.0.7
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-7XH4FFLO.js → boot-SQKXI5NY.js} +23 -333
- package/dist/{chunk-URYBO32C.js → chunk-43XITDZP.js} +2 -2
- package/dist/{chunk-SLUBOIYE.js → chunk-APP6Y2D7.js} +11 -1
- package/dist/{chunk-D57S4UR5.js → chunk-IBAIJY7V.js} +17 -1
- package/dist/{chunk-W6VILPQE.js → chunk-JG3VMWRQ.js} +1 -1
- package/dist/{chunk-J2NPWBVZ.js → chunk-NFNB2C35.js} +153 -8
- package/dist/chunk-SR66HQKB.js +464 -0
- package/dist/{chunk-EXQ6AHOY.js → chunk-TOTCEWJD.js} +18 -0
- package/dist/cli.js +9 -9
- package/dist/{client-IG6C5F2G.js → client-6AUPKKMV.js} +1 -1
- package/dist/index.d.ts +76 -0
- package/dist/index.js +5 -5
- package/dist/{serve-boot-B4BIZMNB.js → serve-boot-ESZKFN5M.js} +3 -3
- package/dist/{server-2Z5V3IPA.js → server-2IZBKD3Q.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-XORJ6SII.js +0 -46
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import {
|
|
2
|
+
redactToken
|
|
3
|
+
} from "./chunk-GL2DIQEQ.js";
|
|
4
|
+
|
|
5
|
+
// src/boot/graphify.ts
|
|
6
|
+
var USER_HOME_MOUNT = "/mnt/conveyor-users";
|
|
7
|
+
function computeGraphifyEnv(env, repoName, dirExists) {
|
|
8
|
+
if (env.CONVEYOR_GRAPHIFY_DISABLE === "1") {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
const slug = env.CONVEYOR_GRAPHIFY_SLUG || repoName;
|
|
12
|
+
if (!slug) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
const primaryRoot = `${USER_HOME_MOUNT}/users/_shared/graphify`;
|
|
16
|
+
const legacyRoot = `${USER_HOME_MOUNT}/_shared/graphify`;
|
|
17
|
+
let sharedRoot = env.CONVEYOR_GRAPHIFY_SHARED_ROOT;
|
|
18
|
+
if (!sharedRoot) {
|
|
19
|
+
sharedRoot = dirExists(primaryRoot) || !dirExists(legacyRoot) ? primaryRoot : legacyRoot;
|
|
20
|
+
}
|
|
21
|
+
const dir = env.CONVEYOR_GRAPHIFY_DIR || `${sharedRoot}/${slug}/latest`;
|
|
22
|
+
const graph = env.CONVEYOR_GRAPHIFY_GRAPH || `${dir}/graph.json`;
|
|
23
|
+
return {
|
|
24
|
+
CONVEYOR_GRAPHIFY_SLUG: slug,
|
|
25
|
+
CONVEYOR_GRAPHIFY_SHARED_ROOT: sharedRoot,
|
|
26
|
+
CONVEYOR_GRAPHIFY_DIR: dir,
|
|
27
|
+
CONVEYOR_GRAPHIFY_GRAPH: graph
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/runner/session-runner-helpers.ts
|
|
32
|
+
import { readFileSync } from "fs";
|
|
33
|
+
import { dirname, join } from "path";
|
|
34
|
+
import { fileURLToPath } from "url";
|
|
35
|
+
function mapChatHistory(messages) {
|
|
36
|
+
if (!messages) return [];
|
|
37
|
+
return messages.map((m) => ({
|
|
38
|
+
id: m.id,
|
|
39
|
+
role: m.role ?? "user",
|
|
40
|
+
content: m.content ?? "",
|
|
41
|
+
userId: m.userId,
|
|
42
|
+
userName: m.user?.name ?? void 0,
|
|
43
|
+
createdAt: m.createdAt,
|
|
44
|
+
...m.source ? { source: m.source } : {},
|
|
45
|
+
...m.files && m.files.length > 0 ? {
|
|
46
|
+
files: m.files.map((f) => ({
|
|
47
|
+
fileId: f.id,
|
|
48
|
+
fileName: f.fileName,
|
|
49
|
+
mimeType: f.mimeType,
|
|
50
|
+
fileSize: f.fileSize,
|
|
51
|
+
downloadUrl: f.downloadUrl ?? "",
|
|
52
|
+
content: f.content,
|
|
53
|
+
contentEncoding: f.contentEncoding
|
|
54
|
+
}))
|
|
55
|
+
} : {}
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
function readAgentVersion() {
|
|
59
|
+
try {
|
|
60
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
61
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
62
|
+
try {
|
|
63
|
+
const pkg = JSON.parse(readFileSync(join(here, rel), "utf-8"));
|
|
64
|
+
if (pkg.version) return pkg.version;
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/boot/workspace-extras.ts
|
|
74
|
+
import {
|
|
75
|
+
appendFileSync,
|
|
76
|
+
cpSync,
|
|
77
|
+
existsSync,
|
|
78
|
+
lstatSync,
|
|
79
|
+
mkdirSync,
|
|
80
|
+
readdirSync,
|
|
81
|
+
readFileSync as readFileSync2,
|
|
82
|
+
readlinkSync,
|
|
83
|
+
renameSync,
|
|
84
|
+
rmSync,
|
|
85
|
+
statSync,
|
|
86
|
+
symlinkSync
|
|
87
|
+
} from "fs";
|
|
88
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
89
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
90
|
+
function errText(err) {
|
|
91
|
+
return redactToken(err instanceof Error ? err.message : String(err));
|
|
92
|
+
}
|
|
93
|
+
function isDir(path) {
|
|
94
|
+
try {
|
|
95
|
+
return statSync(path).isDirectory();
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function isSymlink(path) {
|
|
101
|
+
try {
|
|
102
|
+
return lstatSync(path).isSymbolicLink();
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function isDanglingSymlink(path) {
|
|
108
|
+
return !existsSync(path) && isSymlink(path);
|
|
109
|
+
}
|
|
110
|
+
var GRAPHIFY_BUNDLE_FILES = [
|
|
111
|
+
"graph.json",
|
|
112
|
+
"GRAPH_REPORT.md",
|
|
113
|
+
"manifest.json",
|
|
114
|
+
".graphify_analysis.json",
|
|
115
|
+
".graphify_labels.json",
|
|
116
|
+
"publish-manifest.json",
|
|
117
|
+
"cost.json"
|
|
118
|
+
];
|
|
119
|
+
function bindGraphifyBundle(workspace, graphifyEnv, log) {
|
|
120
|
+
const slug = graphifyEnv.CONVEYOR_GRAPHIFY_SLUG;
|
|
121
|
+
const sourceDir = graphifyEnv.CONVEYOR_GRAPHIFY_DIR;
|
|
122
|
+
const graphFile = graphifyEnv.CONVEYOR_GRAPHIFY_GRAPH;
|
|
123
|
+
if (!slug || !sourceDir || !graphFile) return;
|
|
124
|
+
if (!existsSync(graphFile)) {
|
|
125
|
+
log.info(`[boot] Graphify bundle not found for '${slug}' at ${sourceDir}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const targetDir = join2(workspace, "graphify-out");
|
|
129
|
+
try {
|
|
130
|
+
mkdirSync(targetDir, { recursive: true });
|
|
131
|
+
} catch {
|
|
132
|
+
log.warn(`[boot] WARN: unable to create graphify-out at ${targetDir}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const rel of GRAPHIFY_BUNDLE_FILES) {
|
|
136
|
+
const source = join2(sourceDir, rel);
|
|
137
|
+
const target = join2(targetDir, rel);
|
|
138
|
+
if (!existsSync(source)) continue;
|
|
139
|
+
if (existsSync(target) || isSymlink(target)) continue;
|
|
140
|
+
try {
|
|
141
|
+
symlinkSync(source, target);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
log.info(`[boot] Bound graphify bundle '${slug}' into ${targetDir}`);
|
|
146
|
+
}
|
|
147
|
+
async function ensureGrimoireSubmodule(workspace, githubToken, git, log) {
|
|
148
|
+
try {
|
|
149
|
+
if (!existsSync(join2(workspace, ".gitmodules"))) return;
|
|
150
|
+
let paths = "";
|
|
151
|
+
try {
|
|
152
|
+
({ stdout: paths } = await git(
|
|
153
|
+
["config", "--file", ".gitmodules", "--get-regexp", String.raw`submodule\..*\.path`],
|
|
154
|
+
{ cwd: workspace, timeoutMs: 1e4 }
|
|
155
|
+
));
|
|
156
|
+
} catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!paths.split("\n").some((line) => line.trim().endsWith(".claude/grimoire"))) return;
|
|
160
|
+
if (isDir(join2(workspace, ".claude", "grimoire", "skills"))) return;
|
|
161
|
+
if (!githubToken) {
|
|
162
|
+
log.warn("[boot] WARN: grimoire submodule absent and no token to fetch it");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await git(
|
|
167
|
+
[
|
|
168
|
+
"-c",
|
|
169
|
+
`url.https://x-access-token:${githubToken}@github.com/.insteadOf=https://github.com/`,
|
|
170
|
+
"submodule",
|
|
171
|
+
"update",
|
|
172
|
+
"--init",
|
|
173
|
+
".claude/grimoire"
|
|
174
|
+
],
|
|
175
|
+
{ cwd: workspace, timeoutMs: 3e5 }
|
|
176
|
+
);
|
|
177
|
+
log.info("[boot] Initialized grimoire submodule");
|
|
178
|
+
} catch (err) {
|
|
179
|
+
log.warn(
|
|
180
|
+
`[boot] WARN: grimoire submodule init failed (baked pods may lack rc-* skills): ${errText(err)}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
log.warn(`[boot] WARN: grimoire submodule check failed: ${errText(err)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function excludeLinkedSkills(workspace, names, log) {
|
|
188
|
+
if (names.length === 0) return;
|
|
189
|
+
const infoDir = join2(workspace, ".git", "info");
|
|
190
|
+
if (!isDir(join2(workspace, ".git"))) return;
|
|
191
|
+
try {
|
|
192
|
+
mkdirSync(infoDir, { recursive: true });
|
|
193
|
+
const excludePath = join2(infoDir, "exclude");
|
|
194
|
+
const existing = existsSync(excludePath) ? readFileSync2(excludePath, "utf8") : "";
|
|
195
|
+
const missing = names.map((name) => `.claude/skills/${name}`).filter((line) => !existing.split("\n").includes(line));
|
|
196
|
+
if (missing.length === 0) return;
|
|
197
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
198
|
+
appendFileSync(excludePath, `${prefix}${missing.join("\n")}
|
|
199
|
+
`);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
log.warn(`[boot] WARN: unable to exclude grimoire skill links: ${errText(err)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function symlinkTargetsGrimoire(path) {
|
|
205
|
+
try {
|
|
206
|
+
return readlinkSync(path).includes(join2("grimoire", "skills"));
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function linkGrimoireSkills(workspace, log) {
|
|
212
|
+
const sourceDir = join2(workspace, ".claude", "grimoire", "skills");
|
|
213
|
+
const targetDir = join2(workspace, ".claude", "skills");
|
|
214
|
+
if (!isDir(sourceDir)) return;
|
|
215
|
+
try {
|
|
216
|
+
mkdirSync(targetDir, { recursive: true });
|
|
217
|
+
} catch {
|
|
218
|
+
log.warn(`[boot] WARN: unable to create ${targetDir}; skipping grimoire skill links`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const linkedNames = [];
|
|
222
|
+
let names = [];
|
|
223
|
+
try {
|
|
224
|
+
names = readdirSync(sourceDir);
|
|
225
|
+
} catch {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
for (const name of names) {
|
|
229
|
+
if (!isDir(join2(sourceDir, name))) continue;
|
|
230
|
+
const target = join2(targetDir, name);
|
|
231
|
+
if (existsSync(target) && !isSymlink(target)) {
|
|
232
|
+
log.warn(`[boot] WARN: ${target} exists and is not a symlink; skipping`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (isSymlink(target) && !symlinkTargetsGrimoire(target)) continue;
|
|
236
|
+
try {
|
|
237
|
+
rmSync(target, { force: true });
|
|
238
|
+
symlinkSync(join2("..", "grimoire", "skills", name), target);
|
|
239
|
+
linkedNames.push(name);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
excludeLinkedSkills(workspace, linkedNames, log);
|
|
244
|
+
log.info(`[boot] Linked ${linkedNames.length} grimoire skills into ${targetDir}`);
|
|
245
|
+
}
|
|
246
|
+
function bundledSkillsDir(moduleUrl = import.meta.url) {
|
|
247
|
+
try {
|
|
248
|
+
const dir = join2(dirname2(fileURLToPath2(moduleUrl)), "..", "skills");
|
|
249
|
+
return isDir(dir) ? dir : null;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
var EMPTY_MATERIALIZE = { filled: [], shadowed: [], stale: [] };
|
|
255
|
+
async function hideShadowedPins(workspace, names, git, log) {
|
|
256
|
+
if (names.length === 0) return;
|
|
257
|
+
const paths = names.map((name) => `.claude/skills/${name}`);
|
|
258
|
+
if (!git) {
|
|
259
|
+
log.warn(
|
|
260
|
+
`[boot] WARN: replaced skill pin(s) ${names.join(", ")} stay visible to git (no git runner supplied)`
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
await git(["update-index", "--assume-unchanged", ...paths], {
|
|
266
|
+
cwd: workspace,
|
|
267
|
+
timeoutMs: 1e4
|
|
268
|
+
});
|
|
269
|
+
} catch (err) {
|
|
270
|
+
log.warn(`[boot] WARN: unable to hide replaced skill pins from git: ${errText(err)}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function readlinkOr(path) {
|
|
274
|
+
try {
|
|
275
|
+
return readlinkSync(path);
|
|
276
|
+
} catch {
|
|
277
|
+
return "unknown target";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function fillOneSkill(sourceDir, targetDir, name, log) {
|
|
281
|
+
if (!isDir(join2(sourceDir, name))) return null;
|
|
282
|
+
const target = join2(targetDir, name);
|
|
283
|
+
const dangling = isDanglingSymlink(target);
|
|
284
|
+
if (!dangling && (existsSync(target) || isSymlink(target))) return null;
|
|
285
|
+
if (!dangling) {
|
|
286
|
+
try {
|
|
287
|
+
cpSync(join2(sourceDir, name), target, { recursive: true, dereference: true });
|
|
288
|
+
return "filled";
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
log.warn(
|
|
294
|
+
`[boot] WARN: repo skill '${name}' pointed at a missing target (${readlinkOr(target)}); shadowing it with this agent build's bundled copy`
|
|
295
|
+
);
|
|
296
|
+
const staging = join2(targetDir, `.${name}.conveyor-staging`);
|
|
297
|
+
try {
|
|
298
|
+
rmSync(staging, { recursive: true, force: true });
|
|
299
|
+
cpSync(join2(sourceDir, name), staging, { recursive: true, dereference: true });
|
|
300
|
+
rmSync(target, { force: true });
|
|
301
|
+
renameSync(staging, target);
|
|
302
|
+
return "shadowed";
|
|
303
|
+
} catch (err) {
|
|
304
|
+
try {
|
|
305
|
+
rmSync(staging, { recursive: true, force: true });
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
log.warn(
|
|
309
|
+
`[boot] WARN: could not replace dangling skill pin '${name}' (${errText(err)}) \u2014 it stays unusable in this session's skill registry`
|
|
310
|
+
);
|
|
311
|
+
return "stale";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function reportStalePins(targetDir, bundled, log) {
|
|
315
|
+
let present = [];
|
|
316
|
+
try {
|
|
317
|
+
present = readdirSync(targetDir);
|
|
318
|
+
} catch {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const stale = present.filter(
|
|
322
|
+
(name) => !bundled.has(name) && isDanglingSymlink(join2(targetDir, name))
|
|
323
|
+
);
|
|
324
|
+
for (const name of stale) {
|
|
325
|
+
log.warn(
|
|
326
|
+
`[boot] WARN: repo skill '${name}' is a dangling pin and this agent build has no bundled copy \u2014 it will not appear in the session's skill registry`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
return stale;
|
|
330
|
+
}
|
|
331
|
+
async function materializeBundledSkills(workspace, log, sourceDirOverride, git) {
|
|
332
|
+
const sourceDir = sourceDirOverride === void 0 ? bundledSkillsDir() : sourceDirOverride;
|
|
333
|
+
if (!sourceDir) {
|
|
334
|
+
log.warn(
|
|
335
|
+
"[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."
|
|
336
|
+
);
|
|
337
|
+
return EMPTY_MATERIALIZE;
|
|
338
|
+
}
|
|
339
|
+
const targetDir = join2(workspace, ".claude", "skills");
|
|
340
|
+
try {
|
|
341
|
+
mkdirSync(targetDir, { recursive: true });
|
|
342
|
+
} catch {
|
|
343
|
+
log.warn(`[boot] WARN: unable to create ${targetDir}; skipping bundled skills`);
|
|
344
|
+
return EMPTY_MATERIALIZE;
|
|
345
|
+
}
|
|
346
|
+
const filled = [];
|
|
347
|
+
const shadowed = [];
|
|
348
|
+
const unreplaced = [];
|
|
349
|
+
let names = [];
|
|
350
|
+
try {
|
|
351
|
+
names = readdirSync(sourceDir);
|
|
352
|
+
} catch {
|
|
353
|
+
return EMPTY_MATERIALIZE;
|
|
354
|
+
}
|
|
355
|
+
for (const name of names) {
|
|
356
|
+
const outcome = fillOneSkill(sourceDir, targetDir, name, log);
|
|
357
|
+
if (outcome === "filled") filled.push(name);
|
|
358
|
+
else if (outcome === "shadowed") shadowed.push(name);
|
|
359
|
+
else if (outcome === "stale") unreplaced.push(name);
|
|
360
|
+
}
|
|
361
|
+
const stale = [...unreplaced, ...reportStalePins(targetDir, new Set(names), log)];
|
|
362
|
+
excludeLinkedSkills(workspace, [...filled, ...shadowed], log);
|
|
363
|
+
await hideShadowedPins(workspace, shadowed, git, log);
|
|
364
|
+
log.info(
|
|
365
|
+
`[boot] Materialized ${filled.length} bundled skills into ${targetDir}` + (shadowed.length > 0 ? `; replaced ${shadowed.length} dangling pin(s)` : "") + (stale.length > 0 ? `; ${stale.length} dangling pin(s) left unusable` : "")
|
|
366
|
+
);
|
|
367
|
+
return { filled, shadowed, stale };
|
|
368
|
+
}
|
|
369
|
+
function refreshSkillsAfterCheckout(workspace, git, log, sourceDirOverride) {
|
|
370
|
+
linkGrimoireSkills(workspace, log);
|
|
371
|
+
return materializeBundledSkills(workspace, log, sourceDirOverride, git);
|
|
372
|
+
}
|
|
373
|
+
async function neutralizeGrimoirePointer(workspace, git, log) {
|
|
374
|
+
if (!existsSync(join2(workspace, ".claude", "grimoire"))) return;
|
|
375
|
+
try {
|
|
376
|
+
await git(["update-index", "--assume-unchanged", ".claude/grimoire"], {
|
|
377
|
+
cwd: workspace,
|
|
378
|
+
timeoutMs: 1e4
|
|
379
|
+
});
|
|
380
|
+
log.info("[boot] Marked .claude/grimoire assume-unchanged (submodule pointer drift is noise)");
|
|
381
|
+
} catch (err) {
|
|
382
|
+
log.warn(`[boot] WARN: grimoire assume-unchanged failed: ${errText(err)}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function parseReferenceRepos(refsJson) {
|
|
386
|
+
let parsed;
|
|
387
|
+
try {
|
|
388
|
+
parsed = JSON.parse(refsJson);
|
|
389
|
+
} catch {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
if (!Array.isArray(parsed)) return [];
|
|
393
|
+
const refs = [];
|
|
394
|
+
for (const entry of parsed) {
|
|
395
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
396
|
+
const raw = entry;
|
|
397
|
+
const str = (key) => typeof raw[key] === "string" ? raw[key] : "";
|
|
398
|
+
const ref = {
|
|
399
|
+
slug: str("slug"),
|
|
400
|
+
owner: str("owner"),
|
|
401
|
+
name: str("name"),
|
|
402
|
+
branch: str("branch") || "main",
|
|
403
|
+
token: str("token")
|
|
404
|
+
};
|
|
405
|
+
if (ref.token && ref.slug && ref.owner && ref.name) refs.push(ref);
|
|
406
|
+
}
|
|
407
|
+
return refs;
|
|
408
|
+
}
|
|
409
|
+
async function runPreReadyBinds(workspace, bundle, git, log) {
|
|
410
|
+
const graphifyEnv = computeGraphifyEnv(process.env, bundle.gitPlan.repoName, isDir);
|
|
411
|
+
bindGraphifyBundle(workspace, graphifyEnv, log);
|
|
412
|
+
await ensureGrimoireSubmodule(workspace, bundle.githubToken ?? "", git, log);
|
|
413
|
+
linkGrimoireSkills(workspace, log);
|
|
414
|
+
await materializeBundledSkills(workspace, log, void 0, git);
|
|
415
|
+
await neutralizeGrimoirePointer(workspace, git, log);
|
|
416
|
+
}
|
|
417
|
+
async function cloneReferenceRepos(refsJson, git, log, referencesDir = "/workspaces/references") {
|
|
418
|
+
if (!refsJson) return;
|
|
419
|
+
const refs = parseReferenceRepos(refsJson);
|
|
420
|
+
if (refs.length === 0) return;
|
|
421
|
+
try {
|
|
422
|
+
mkdirSync(referencesDir, { recursive: true });
|
|
423
|
+
} catch {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
for (const ref of refs) {
|
|
427
|
+
const dest = join2(referencesDir, ref.slug);
|
|
428
|
+
if (existsSync(join2(dest, ".git"))) continue;
|
|
429
|
+
try {
|
|
430
|
+
await git(
|
|
431
|
+
[
|
|
432
|
+
"clone",
|
|
433
|
+
"--depth",
|
|
434
|
+
"1",
|
|
435
|
+
"--single-branch",
|
|
436
|
+
"--branch",
|
|
437
|
+
ref.branch,
|
|
438
|
+
`https://x-access-token:${ref.token}@github.com/${ref.owner}/${ref.name}.git`,
|
|
439
|
+
dest
|
|
440
|
+
],
|
|
441
|
+
{ timeoutMs: 6e5 }
|
|
442
|
+
);
|
|
443
|
+
try {
|
|
444
|
+
await git(
|
|
445
|
+
["remote", "set-url", "origin", `https://github.com/${ref.owner}/${ref.name}.git`],
|
|
446
|
+
{ cwd: dest, timeoutMs: 1e4 }
|
|
447
|
+
);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
log.info(`[boot] cloned reference ${ref.slug} (${ref.owner}/${ref.name}@${ref.branch})`);
|
|
451
|
+
} catch {
|
|
452
|
+
log.warn(`[boot] WARN: reference clone failed: ${ref.slug}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export {
|
|
458
|
+
computeGraphifyEnv,
|
|
459
|
+
refreshSkillsAfterCheckout,
|
|
460
|
+
runPreReadyBinds,
|
|
461
|
+
cloneReferenceRepos,
|
|
462
|
+
mapChatHistory,
|
|
463
|
+
readAgentVersion
|
|
464
|
+
};
|
|
@@ -138,6 +138,24 @@ var WorkbenchClient = class {
|
|
|
138
138
|
);
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
+
/** Ask the workbench — which owns the repo filesystem in split mode — to
|
|
142
|
+
* re-run the skill floor after the agent's authoritative task-branch
|
|
143
|
+
* checkout. A daemon without a provider answers an empty result. */
|
|
144
|
+
refreshSkills() {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
let result = null;
|
|
147
|
+
this.open(
|
|
148
|
+
{ op: "refreshSkills" },
|
|
149
|
+
(frame) => {
|
|
150
|
+
if (frame.t === "refreshSkills") result = frame;
|
|
151
|
+
},
|
|
152
|
+
(error) => {
|
|
153
|
+
if (result) resolve(result);
|
|
154
|
+
else reject(error ?? new WorkbenchError("connection closed before refreshSkills"));
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
141
159
|
/** Mirrors setup/commands.ts runSetupCommand (shell form, streamed output,
|
|
142
160
|
* abort → graceful term-group kill, non-zero exit rejects). */
|
|
143
161
|
runSetupCommand(cmd, cwd, onOutput, signal) {
|
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-43XITDZP.js";
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_SONNET_MODEL,
|
|
13
13
|
PtyHarness,
|
|
@@ -27,13 +27,13 @@ import {
|
|
|
27
27
|
resolveTuiKindFromEnv,
|
|
28
28
|
runUsageProbe,
|
|
29
29
|
sampleKeyUsage
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-NFNB2C35.js";
|
|
31
|
+
import "./chunk-SR66HQKB.js";
|
|
32
32
|
import {
|
|
33
33
|
inheritedEnv,
|
|
34
34
|
resolvePtySpawn,
|
|
35
35
|
sessionTempBase
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-JG3VMWRQ.js";
|
|
37
37
|
import {
|
|
38
38
|
AgentConnection,
|
|
39
39
|
CodespacePortVisibility,
|
|
@@ -44,12 +44,12 @@ import {
|
|
|
44
44
|
createServiceLogger,
|
|
45
45
|
fetchBootstrap,
|
|
46
46
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-APP6Y2D7.js";
|
|
48
48
|
import "./chunk-GL2DIQEQ.js";
|
|
49
49
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
50
|
import "./chunk-7P6QZHXZ.js";
|
|
51
51
|
import "./chunk-IA45XHOA.js";
|
|
52
|
-
import "./chunk-
|
|
52
|
+
import "./chunk-TOTCEWJD.js";
|
|
53
53
|
import "./chunk-KMB3BU4S.js";
|
|
54
54
|
import "./chunk-6Q6LQBWO.js";
|
|
55
55
|
|
|
@@ -1201,7 +1201,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
|
1201
1201
|
|
|
1202
1202
|
// src/cli.ts
|
|
1203
1203
|
if (process.argv[2] === "boot") {
|
|
1204
|
-
const { runBoot } = await import("./boot-
|
|
1204
|
+
const { runBoot } = await import("./boot-SQKXI5NY.js");
|
|
1205
1205
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1206
1206
|
}
|
|
1207
1207
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1303,7 +1303,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
1303
1303
|
process.exit(1);
|
|
1304
1304
|
});
|
|
1305
1305
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1306
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1306
|
+
const { startWorkbenchServer } = await import("./server-2IZBKD3Q.js");
|
|
1307
1307
|
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
|
|
1308
1308
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
|
|
1309
1309
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
@@ -1454,7 +1454,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1454
1454
|
process.exit(1);
|
|
1455
1455
|
}
|
|
1456
1456
|
if (CONVEYOR_MODE === "serving") {
|
|
1457
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1457
|
+
const { runServingSession } = await import("./serve-boot-ESZKFN5M.js");
|
|
1458
1458
|
exitContext.runnerMode = "serving";
|
|
1459
1459
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1460
1460
|
const outcome = await runServingSession({
|
package/dist/index.d.ts
CHANGED
|
@@ -131,6 +131,10 @@ declare class AgentConnection {
|
|
|
131
131
|
private earlyProbeUsage;
|
|
132
132
|
private ptyInputCallback;
|
|
133
133
|
private ptyResizeCallback;
|
|
134
|
+
/** Epoch ms of the last relayed keystroke, or null before any input. The
|
|
135
|
+
* question-grace expiry reads it to defer un-parking while a human is
|
|
136
|
+
* actively typing into the Connected-TUI (they may be mid-answer). */
|
|
137
|
+
private _lastPtyInputAt;
|
|
134
138
|
constructor(config: AgentConnectionConfig);
|
|
135
139
|
get sessionId(): string;
|
|
136
140
|
get connected(): boolean;
|
|
@@ -282,6 +286,8 @@ declare class AgentConnection {
|
|
|
282
286
|
* just stays on the relay transport.
|
|
283
287
|
*/
|
|
284
288
|
reportPtyStream(port: number | null): void;
|
|
289
|
+
/** Epoch ms of the last relayed TUI keystroke, or null before any. */
|
|
290
|
+
get lastPtyInputAt(): number | null;
|
|
285
291
|
/** Subscribe to relayed keystrokes. Returns an unsubscribe fn. */
|
|
286
292
|
onPtyInput(handler: (data: string) => void): () => void;
|
|
287
293
|
/** Subscribe to relayed (reconciled) terminal resizes. Returns an unsubscribe fn. */
|
|
@@ -729,6 +735,18 @@ declare class SessionRunner {
|
|
|
729
735
|
/** Guards the one-shot `agent_live` boot-milestone report — fired on the
|
|
730
736
|
* first harness event (createQueryBridge's onEvent hook). */
|
|
731
737
|
private agentLiveReported;
|
|
738
|
+
/** Epoch ms since a TUI questionnaire has been parked on a human with no
|
|
739
|
+
* answer; null when none is pending. While set (and within the grace +
|
|
740
|
+
* slack bound) `resolveLoopStatus` reports `waiting`, which reaches the API
|
|
741
|
+
* as `active` and keeps the workspace activity clock fresh — the pod must
|
|
742
|
+
* not be slept out from under an unanswered question. Deliberately KEPT
|
|
743
|
+
* when the turn ends with the questionnaire still parked (the Shape-B
|
|
744
|
+
* silent death this hold exists for). */
|
|
745
|
+
private questionPendingSince;
|
|
746
|
+
/** Single-shot unref'd timer driving `handleQuestionGraceExpiry`. The
|
|
747
|
+
* liveness hold above is bounded by timestamp arithmetic, so a lost timer
|
|
748
|
+
* still expires the hold — it just skips the un-park injection. */
|
|
749
|
+
private questionGraceTimer;
|
|
732
750
|
constructor(config: SessionRunnerConfig, callbacks: SessionRunnerCallbacks, deps?: SessionRunnerDependencies);
|
|
733
751
|
get state(): AgentRunnerStatus;
|
|
734
752
|
/**
|
|
@@ -833,6 +851,19 @@ declare class SessionRunner {
|
|
|
833
851
|
run(): Promise<void>;
|
|
834
852
|
/** Convenience wrapper: connect() then run(). */
|
|
835
853
|
start(): Promise<void>;
|
|
854
|
+
/**
|
|
855
|
+
* Re-run the skill floor against the tree `ensureOnTaskBranch` just produced.
|
|
856
|
+
*
|
|
857
|
+
* Split-mode pods keep the repo in the WORKBENCH container, so this routes
|
|
858
|
+
* over the launcher: running it in-process here would materialize skills into
|
|
859
|
+
* the AGENT container's own copy of /workspaces/repo — still on the base
|
|
860
|
+
* branch, already complete — and look like it worked while Claude spawned on
|
|
861
|
+
* the untouched real tree. Codespaces/local (no workbench) run it directly.
|
|
862
|
+
*
|
|
863
|
+
* Best-effort by design: a failure warns and boot continues, exactly like the
|
|
864
|
+
* pre-ready binds it repeats.
|
|
865
|
+
*/
|
|
866
|
+
private refreshSkillsForCheckout;
|
|
836
867
|
private coreLoop;
|
|
837
868
|
/**
|
|
838
869
|
* Handle dormant-after-completed idle. Returns true if a critical message
|
|
@@ -1016,6 +1047,29 @@ declare class SessionRunner {
|
|
|
1016
1047
|
* for why an unknown status still fails open to `active`.
|
|
1017
1048
|
*/
|
|
1018
1049
|
private resolveLoopStatus;
|
|
1050
|
+
/**
|
|
1051
|
+
* Question-pending transitions from the event loop. `armed` starts the
|
|
1052
|
+
* bounded liveness hold + the grace timer; `answered` clears both.
|
|
1053
|
+
* `turn_ended` — the turn's result arrived with the questionnaire still
|
|
1054
|
+
* parked — deliberately KEEPS them: that is the Shape-B silent death
|
|
1055
|
+
* (idle heartbeats stop bumping the activity clock and the reconciler
|
|
1056
|
+
* sleeps the pod minutes later) this hold exists to prevent.
|
|
1057
|
+
*/
|
|
1058
|
+
private handleUserQuestionSignal;
|
|
1059
|
+
private armQuestionGraceTimer;
|
|
1060
|
+
private clearQuestionHold;
|
|
1061
|
+
/**
|
|
1062
|
+
* The grace expired with the questionnaire still unanswered. Un-park the
|
|
1063
|
+
* agent by injecting the timeout prompt as a superseding system message —
|
|
1064
|
+
* the proven supersede path (mid-turn: queue + queryBridge.stop(), teardown
|
|
1065
|
+
* kills the parked dialog and the respawn `--resume`s cleanly; idle: the
|
|
1066
|
+
* inputResolver picks it up). Never synthetic keypresses: Enter would
|
|
1067
|
+
* fabricate an answer and Esc handling is CLI-version-dependent.
|
|
1068
|
+
*
|
|
1069
|
+
* A human actively typing in the Connected-TUI defers the expiry — they may
|
|
1070
|
+
* be mid-answer, and superseding would yank the dialog out from under them.
|
|
1071
|
+
*/
|
|
1072
|
+
private handleQuestionGraceExpiry;
|
|
1019
1073
|
/** Mirror the resolved status into the loop-lag buffer so the starvation-proof
|
|
1020
1074
|
* heartbeat worker reports it too. Returns what it wrote. */
|
|
1021
1075
|
private refreshLoopStatus;
|
|
@@ -1055,9 +1109,31 @@ interface AgentRunnerConfig {
|
|
|
1055
1109
|
*/
|
|
1056
1110
|
packExecution?: _project_shared.PackExecution;
|
|
1057
1111
|
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Question-pending transitions surfaced to the runner by the event loop
|
|
1114
|
+
* (`applyQuestionTransitions` in execution/event-processor.ts):
|
|
1115
|
+
* - `armed` — the PTY hook observed an AskUserQuestion; a questionnaire is
|
|
1116
|
+
* parked on a human.
|
|
1117
|
+
* - `answered` — the questionnaire resolved (PostToolUse envelope, or a new
|
|
1118
|
+
* assistant record without the question block).
|
|
1119
|
+
* - `turn_ended` — the turn's `result` arrived while the questionnaire was
|
|
1120
|
+
* still parked. The dialog is still on screen with nobody answering — the
|
|
1121
|
+
* silent-death shape the question grace hold exists for — so the runner
|
|
1122
|
+
* must KEEP its pending hold rather than clear it.
|
|
1123
|
+
*/
|
|
1124
|
+
type UserQuestionSignal = {
|
|
1125
|
+
kind: "armed";
|
|
1126
|
+
questionText?: string;
|
|
1127
|
+
} | {
|
|
1128
|
+
kind: "answered";
|
|
1129
|
+
} | {
|
|
1130
|
+
kind: "turn_ended";
|
|
1131
|
+
};
|
|
1058
1132
|
interface AgentRunnerCallbacks {
|
|
1059
1133
|
onEvent: (event: Record<string, unknown>) => void | Promise<void>;
|
|
1060
1134
|
onStatusChange: (status: string) => void | Promise<void>;
|
|
1135
|
+
/** Optional — fired on question-pending transitions (see UserQuestionSignal). */
|
|
1136
|
+
onUserQuestion?: (signal: UserQuestionSignal) => void;
|
|
1061
1137
|
}
|
|
1062
1138
|
|
|
1063
1139
|
interface GithubTokenFileSync {
|