@whittlelabs/sifter 0.30.22 → 0.31.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/bin.js +2090 -786
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -9876,7 +9876,7 @@ var require_progress = __commonJS({
|
|
|
9876
9876
|
"../../packages/shuttle/dist/executors/progress.js"(exports2) {
|
|
9877
9877
|
"use strict";
|
|
9878
9878
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
9879
|
-
exports2.StreamJsonTap = exports2.ProgressReporter = void 0;
|
|
9879
|
+
exports2.JsonLineTap = exports2.StreamJsonTap = exports2.ProgressReporter = void 0;
|
|
9880
9880
|
var ProgressReporter = class {
|
|
9881
9881
|
sink;
|
|
9882
9882
|
seq = 0;
|
|
@@ -10017,112 +10017,1226 @@ var require_progress = __commonJS({
|
|
|
10017
10017
|
}
|
|
10018
10018
|
return void 0;
|
|
10019
10019
|
}
|
|
10020
|
+
var JsonLineTap = class {
|
|
10021
|
+
onEvent;
|
|
10022
|
+
buf = "";
|
|
10023
|
+
constructor(onEvent) {
|
|
10024
|
+
this.onEvent = onEvent;
|
|
10025
|
+
}
|
|
10026
|
+
push(chunk) {
|
|
10027
|
+
this.buf += chunk;
|
|
10028
|
+
let nl;
|
|
10029
|
+
while ((nl = this.buf.indexOf("\n")) !== -1) {
|
|
10030
|
+
const line = this.buf.slice(0, nl);
|
|
10031
|
+
this.buf = this.buf.slice(nl + 1);
|
|
10032
|
+
this.consume(line);
|
|
10033
|
+
}
|
|
10034
|
+
}
|
|
10035
|
+
end() {
|
|
10036
|
+
if (this.buf.trim().length > 0)
|
|
10037
|
+
this.consume(this.buf);
|
|
10038
|
+
this.buf = "";
|
|
10039
|
+
}
|
|
10040
|
+
consume(line) {
|
|
10041
|
+
const trimmed = line.trim();
|
|
10042
|
+
if (!trimmed.startsWith("{"))
|
|
10043
|
+
return;
|
|
10044
|
+
let event;
|
|
10045
|
+
try {
|
|
10046
|
+
event = JSON.parse(trimmed);
|
|
10047
|
+
} catch {
|
|
10048
|
+
return;
|
|
10049
|
+
}
|
|
10050
|
+
try {
|
|
10051
|
+
this.onEvent(event);
|
|
10052
|
+
} catch {
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
};
|
|
10056
|
+
exports2.JsonLineTap = JsonLineTap;
|
|
10020
10057
|
}
|
|
10021
10058
|
});
|
|
10022
10059
|
|
|
10023
|
-
// ../../packages/shuttle/dist/executors/
|
|
10024
|
-
var
|
|
10025
|
-
"../../packages/shuttle/dist/executors/
|
|
10060
|
+
// ../../packages/shuttle/dist/executors/agent-process.js
|
|
10061
|
+
var require_agent_process = __commonJS({
|
|
10062
|
+
"../../packages/shuttle/dist/executors/agent-process.js"(exports2) {
|
|
10026
10063
|
"use strict";
|
|
10027
10064
|
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10028
10065
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10029
10066
|
};
|
|
10030
10067
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10031
|
-
exports2.
|
|
10032
|
-
exports2.
|
|
10033
|
-
exports2.
|
|
10034
|
-
exports2.
|
|
10068
|
+
exports2.STDERR_LIMIT = void 0;
|
|
10069
|
+
exports2.runAgentProcess = runAgentProcess;
|
|
10070
|
+
exports2.firstLine = firstLine;
|
|
10071
|
+
exports2.keepTranscript = keepTranscript;
|
|
10072
|
+
exports2.classifyAgentFault = classifyAgentFault;
|
|
10073
|
+
exports2.agentFailure = agentFailure;
|
|
10074
|
+
var types_1 = require_types2();
|
|
10075
|
+
var child_process_1 = require("child_process");
|
|
10076
|
+
var fs_1 = require("fs");
|
|
10077
|
+
var path_1 = __importDefault(require("path"));
|
|
10078
|
+
var string_decoder_1 = require("string_decoder");
|
|
10079
|
+
exports2.STDERR_LIMIT = 4e3;
|
|
10080
|
+
function runAgentProcess(opts) {
|
|
10081
|
+
return new Promise((resolve, reject) => {
|
|
10082
|
+
let child;
|
|
10083
|
+
try {
|
|
10084
|
+
child = (0, child_process_1.spawn)(opts.bin, opts.args, {
|
|
10085
|
+
cwd: opts.cwd,
|
|
10086
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10087
|
+
...opts.env ? { env: opts.env } : {}
|
|
10088
|
+
});
|
|
10089
|
+
} catch (err) {
|
|
10090
|
+
const e = err;
|
|
10091
|
+
resolve({ code: null, stdout: "", stderr: "", spawnError: { code: e.code ?? null, message: e.message } });
|
|
10092
|
+
return;
|
|
10093
|
+
}
|
|
10094
|
+
const stdoutChunks = [];
|
|
10095
|
+
let stderr = "";
|
|
10096
|
+
let settled = false;
|
|
10097
|
+
let timer;
|
|
10098
|
+
const decoder = opts.onStdout ? new string_decoder_1.StringDecoder("utf8") : null;
|
|
10099
|
+
const finish = (fn) => {
|
|
10100
|
+
if (settled)
|
|
10101
|
+
return;
|
|
10102
|
+
settled = true;
|
|
10103
|
+
if (timer)
|
|
10104
|
+
clearTimeout(timer);
|
|
10105
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
10106
|
+
fn();
|
|
10107
|
+
};
|
|
10108
|
+
function onAbort() {
|
|
10109
|
+
child.kill("SIGTERM");
|
|
10110
|
+
finish(() => reject(new Error("Dispatch aborted")));
|
|
10111
|
+
}
|
|
10112
|
+
if (opts.signal.aborted) {
|
|
10113
|
+
child.kill("SIGTERM");
|
|
10114
|
+
finish(() => reject(new Error("Dispatch aborted")));
|
|
10115
|
+
return;
|
|
10116
|
+
}
|
|
10117
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
10118
|
+
const armIdleTimer = () => {
|
|
10119
|
+
if (settled)
|
|
10120
|
+
return;
|
|
10121
|
+
if (timer)
|
|
10122
|
+
clearTimeout(timer);
|
|
10123
|
+
timer = setTimeout(() => {
|
|
10124
|
+
child.kill("SIGTERM");
|
|
10125
|
+
finish(() => reject(new Error(`${opts.toolName} produced no output for ${opts.idleTimeoutMs}ms; killed as hung`)));
|
|
10126
|
+
}, opts.idleTimeoutMs);
|
|
10127
|
+
};
|
|
10128
|
+
armIdleTimer();
|
|
10129
|
+
child.stdout?.on("data", (chunk) => {
|
|
10130
|
+
stdoutChunks.push(chunk);
|
|
10131
|
+
armIdleTimer();
|
|
10132
|
+
if (decoder && opts.onStdout)
|
|
10133
|
+
opts.onStdout(decoder.write(chunk));
|
|
10134
|
+
});
|
|
10135
|
+
child.stderr?.on("data", (chunk) => {
|
|
10136
|
+
if (stderr.length < exports2.STDERR_LIMIT)
|
|
10137
|
+
stderr += chunk.toString("utf-8");
|
|
10138
|
+
});
|
|
10139
|
+
child.stdin?.on("error", () => {
|
|
10140
|
+
});
|
|
10141
|
+
if (opts.stdin !== void 0)
|
|
10142
|
+
child.stdin?.write(opts.stdin);
|
|
10143
|
+
child.stdin?.end();
|
|
10144
|
+
child.on("error", (err) => {
|
|
10145
|
+
const code = err.code ?? null;
|
|
10146
|
+
finish(() => resolve({
|
|
10147
|
+
code: null,
|
|
10148
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
10149
|
+
stderr,
|
|
10150
|
+
spawnError: { code, message: err.message }
|
|
10151
|
+
}));
|
|
10152
|
+
});
|
|
10153
|
+
child.on("close", (code) => {
|
|
10154
|
+
if (decoder && opts.onStdout)
|
|
10155
|
+
opts.onStdout(decoder.end());
|
|
10156
|
+
finish(() => resolve({ code, stdout: Buffer.concat(stdoutChunks).toString("utf-8"), stderr }));
|
|
10157
|
+
});
|
|
10158
|
+
});
|
|
10159
|
+
}
|
|
10160
|
+
function firstLine(text) {
|
|
10161
|
+
for (const line of text.split("\n")) {
|
|
10162
|
+
const trimmed = line.trim();
|
|
10163
|
+
if (trimmed.length > 0)
|
|
10164
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 300)}...` : trimmed;
|
|
10165
|
+
}
|
|
10166
|
+
return null;
|
|
10167
|
+
}
|
|
10168
|
+
async function keepTranscript(dir, name, stdout) {
|
|
10169
|
+
try {
|
|
10170
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
10171
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10172
|
+
await fs_1.promises.writeFile(path_1.default.join(dir, `${safe}.jsonl`), stdout);
|
|
10173
|
+
} catch (err) {
|
|
10174
|
+
console.warn(`[shuttle] could not keep the transcript for ${name} under ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10175
|
+
}
|
|
10176
|
+
}
|
|
10177
|
+
function classifyAgentFault(failure, vocab) {
|
|
10178
|
+
if (failure.spawnErrorCode === "ENOENT")
|
|
10179
|
+
return vocab.installRemedy;
|
|
10180
|
+
const text = failure.text ?? "";
|
|
10181
|
+
if (failure.apiErrorStatus === 401 || vocab.signedOut.test(text))
|
|
10182
|
+
return vocab.loginRemedy;
|
|
10183
|
+
if (vocab.notEntitled?.pattern.test(text))
|
|
10184
|
+
return vocab.notEntitled.remedy;
|
|
10185
|
+
if (failure.apiErrorStatus === 402 || failure.apiErrorStatus === 429 || vocab.outOfUsage.test(text)) {
|
|
10186
|
+
return vocab.limitRemedy;
|
|
10187
|
+
}
|
|
10188
|
+
return null;
|
|
10189
|
+
}
|
|
10190
|
+
function agentFailure(local, message, facts, vocab) {
|
|
10191
|
+
if (!local)
|
|
10192
|
+
return new Error(message);
|
|
10193
|
+
const remedy = classifyAgentFault(facts, vocab);
|
|
10194
|
+
return remedy ? new types_1.ExecutorEnvironmentError(message, remedy) : new Error(message);
|
|
10195
|
+
}
|
|
10196
|
+
}
|
|
10197
|
+
});
|
|
10198
|
+
|
|
10199
|
+
// ../../packages/shuttle/dist/executors/json-answer.js
|
|
10200
|
+
var require_json_answer = __commonJS({
|
|
10201
|
+
"../../packages/shuttle/dist/executors/json-answer.js"(exports2) {
|
|
10202
|
+
"use strict";
|
|
10203
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10204
|
+
exports2.extractJsonObject = extractJsonObject;
|
|
10205
|
+
function extractJsonObject(s) {
|
|
10206
|
+
const exact = tryParseObject(s);
|
|
10207
|
+
if (exact)
|
|
10208
|
+
return exact;
|
|
10209
|
+
let last;
|
|
10210
|
+
let i = 0;
|
|
10211
|
+
while (i < s.length) {
|
|
10212
|
+
if (s[i] !== "{") {
|
|
10213
|
+
i++;
|
|
10214
|
+
continue;
|
|
10215
|
+
}
|
|
10216
|
+
let depth = 0;
|
|
10217
|
+
let inString = false;
|
|
10218
|
+
let escaped = false;
|
|
10219
|
+
let end = -1;
|
|
10220
|
+
for (let j = i; j < s.length; j++) {
|
|
10221
|
+
const ch = s[j];
|
|
10222
|
+
if (escaped) {
|
|
10223
|
+
escaped = false;
|
|
10224
|
+
continue;
|
|
10225
|
+
}
|
|
10226
|
+
if (ch === "\\") {
|
|
10227
|
+
escaped = inString;
|
|
10228
|
+
continue;
|
|
10229
|
+
}
|
|
10230
|
+
if (ch === '"') {
|
|
10231
|
+
inString = !inString;
|
|
10232
|
+
continue;
|
|
10233
|
+
}
|
|
10234
|
+
if (inString)
|
|
10235
|
+
continue;
|
|
10236
|
+
if (ch === "{")
|
|
10237
|
+
depth++;
|
|
10238
|
+
else if (ch === "}") {
|
|
10239
|
+
depth--;
|
|
10240
|
+
if (depth === 0) {
|
|
10241
|
+
end = j;
|
|
10242
|
+
break;
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
}
|
|
10246
|
+
if (end === -1)
|
|
10247
|
+
break;
|
|
10248
|
+
const candidate = tryParseObject(s.slice(i, end + 1));
|
|
10249
|
+
if (candidate)
|
|
10250
|
+
last = candidate;
|
|
10251
|
+
i = end + 1;
|
|
10252
|
+
}
|
|
10253
|
+
return last;
|
|
10254
|
+
}
|
|
10255
|
+
function tryParseObject(s) {
|
|
10256
|
+
try {
|
|
10257
|
+
const parsed = JSON.parse(s);
|
|
10258
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
10259
|
+
} catch {
|
|
10260
|
+
return void 0;
|
|
10261
|
+
}
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
});
|
|
10265
|
+
|
|
10266
|
+
// ../../packages/shuttle/dist/executors/workspace-telemetry.js
|
|
10267
|
+
var require_workspace_telemetry = __commonJS({
|
|
10268
|
+
"../../packages/shuttle/dist/executors/workspace-telemetry.js"(exports2) {
|
|
10269
|
+
"use strict";
|
|
10270
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10271
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10272
|
+
};
|
|
10273
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10274
|
+
exports2.workspaceOutputs = workspaceOutputs;
|
|
10035
10275
|
exports2.relativeWorkspaceReads = relativeWorkspaceReads;
|
|
10036
10276
|
exports2.globToRegExp = globToRegExp;
|
|
10037
10277
|
exports2.creditOverlayReads = creditOverlayReads;
|
|
10038
|
-
exports2.
|
|
10039
|
-
var child_process_1 = require("child_process");
|
|
10278
|
+
exports2.shellReadPaths = shellReadPaths;
|
|
10040
10279
|
var fs_1 = require("fs");
|
|
10041
|
-
var os_1 = __importDefault(require("os"));
|
|
10042
10280
|
var path_1 = __importDefault(require("path"));
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
var apply_1 = require_apply();
|
|
10049
|
-
var DEFAULT_TIMEOUT = 6e5;
|
|
10050
|
-
var REVIEW_TOOLS = ["Read", "Grep", "Glob"];
|
|
10051
|
-
function reviewRunArgs(settingSources) {
|
|
10281
|
+
function workspaceOutputs(workspace, runCwd, readPaths, toolInputs) {
|
|
10282
|
+
const reads = [...readPaths];
|
|
10283
|
+
for (const rel of creditOverlayReads(toolInputs, workspace.overlay?.writtenPaths ?? [], runCwd)) {
|
|
10284
|
+
reads.push(path_1.default.join(runCwd, rel));
|
|
10285
|
+
}
|
|
10052
10286
|
return [
|
|
10053
|
-
"
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10287
|
+
{ label: "workspace_reads", content: { paths: relativeWorkspaceReads(reads, runCwd) } },
|
|
10288
|
+
{
|
|
10289
|
+
// Structured audit trail: reviewers and operators can see whether a
|
|
10290
|
+
// given review actually explored the checkout or fell back to the
|
|
10291
|
+
// diff (and why, including a failed private-repo auth), plus which
|
|
10292
|
+
// overlay files landed.
|
|
10293
|
+
label: "workspace",
|
|
10294
|
+
content: {
|
|
10295
|
+
materialized: workspace.materialized,
|
|
10296
|
+
...workspace.warning ? { warning: workspace.warning } : {},
|
|
10297
|
+
...workspace.context.length > 0 ? {
|
|
10298
|
+
context: workspace.context.map((c) => ({
|
|
10299
|
+
name: c.requestedName,
|
|
10300
|
+
materialized: c.materialized,
|
|
10301
|
+
...c.warning ? { warning: c.warning } : {}
|
|
10302
|
+
}))
|
|
10303
|
+
} : {},
|
|
10304
|
+
...workspace.overlay ? {
|
|
10305
|
+
overlay: {
|
|
10306
|
+
written: workspace.overlay.written,
|
|
10307
|
+
...workspace.overlay.skipped.length > 0 ? { skipped: workspace.overlay.skipped } : {}
|
|
10308
|
+
}
|
|
10309
|
+
} : {}
|
|
10310
|
+
}
|
|
10311
|
+
}
|
|
10062
10312
|
];
|
|
10063
10313
|
}
|
|
10064
|
-
var
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10314
|
+
var MAX_REPORTED_READS = 2e3;
|
|
10315
|
+
function workspaceRoots(cwd) {
|
|
10316
|
+
const root = path_1.default.resolve(cwd);
|
|
10317
|
+
const roots = [root];
|
|
10318
|
+
try {
|
|
10319
|
+
const real = (0, fs_1.realpathSync)(root);
|
|
10320
|
+
if (real !== root)
|
|
10321
|
+
roots.push(real);
|
|
10322
|
+
} catch {
|
|
10323
|
+
}
|
|
10324
|
+
return roots;
|
|
10325
|
+
}
|
|
10326
|
+
function relativeWorkspaceReads(readPaths, cwd) {
|
|
10327
|
+
const roots = workspaceRoots(cwd);
|
|
10328
|
+
const root = roots[0];
|
|
10329
|
+
const out = /* @__PURE__ */ new Set();
|
|
10330
|
+
for (const p of readPaths) {
|
|
10331
|
+
const resolved = path_1.default.resolve(root, p);
|
|
10332
|
+
for (const r of roots) {
|
|
10333
|
+
if (resolved === r)
|
|
10334
|
+
break;
|
|
10335
|
+
if (!resolved.startsWith(r + path_1.default.sep))
|
|
10336
|
+
continue;
|
|
10337
|
+
out.add(resolved.slice(r.length + 1));
|
|
10338
|
+
break;
|
|
10339
|
+
}
|
|
10340
|
+
if (out.size >= MAX_REPORTED_READS)
|
|
10341
|
+
break;
|
|
10342
|
+
}
|
|
10343
|
+
return [...out].sort();
|
|
10344
|
+
}
|
|
10345
|
+
var GLOB_CHARS = /[*?[{]/;
|
|
10346
|
+
function escapeRegExp(s) {
|
|
10347
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10348
|
+
}
|
|
10349
|
+
function globToRegExp(glob) {
|
|
10350
|
+
let re = "^";
|
|
10351
|
+
for (let i = 0; i < glob.length; i++) {
|
|
10352
|
+
const ch = glob[i];
|
|
10353
|
+
if (ch === "*") {
|
|
10354
|
+
if (glob[i + 1] === "*") {
|
|
10355
|
+
if (glob[i + 2] === "/") {
|
|
10356
|
+
re += "(?:.*/)?";
|
|
10357
|
+
i += 2;
|
|
10358
|
+
} else {
|
|
10359
|
+
re += ".*";
|
|
10360
|
+
i += 1;
|
|
10361
|
+
}
|
|
10362
|
+
} else {
|
|
10363
|
+
re += "[^/]*";
|
|
10364
|
+
}
|
|
10365
|
+
} else if (ch === "?") {
|
|
10366
|
+
re += "[^/]";
|
|
10367
|
+
} else if (ch === "[") {
|
|
10368
|
+
const end = glob.indexOf("]", i + 1);
|
|
10369
|
+
if (end === -1)
|
|
10370
|
+
return null;
|
|
10371
|
+
let cls = glob.slice(i + 1, end);
|
|
10372
|
+
if (cls.startsWith("!"))
|
|
10373
|
+
cls = `^${cls.slice(1)}`;
|
|
10374
|
+
re += `[${cls.replace(/\\/g, "\\\\")}]`;
|
|
10375
|
+
i = end;
|
|
10376
|
+
} else if (ch === "{") {
|
|
10377
|
+
const end = glob.indexOf("}", i + 1);
|
|
10378
|
+
if (end === -1)
|
|
10379
|
+
return null;
|
|
10380
|
+
re += `(?:${glob.slice(i + 1, end).split(",").map(escapeRegExp).join("|")})`;
|
|
10381
|
+
i = end;
|
|
10382
|
+
} else {
|
|
10383
|
+
re += escapeRegExp(ch);
|
|
10384
|
+
}
|
|
10385
|
+
}
|
|
10386
|
+
return new RegExp(`${re}$`);
|
|
10387
|
+
}
|
|
10388
|
+
function creditOverlayReads(toolInputs, writtenPaths, runCwd) {
|
|
10389
|
+
if (writtenPaths.length === 0 || toolInputs.length === 0)
|
|
10390
|
+
return [];
|
|
10391
|
+
const roots = runCwd ? workspaceRoots(runCwd) : [];
|
|
10392
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10393
|
+
for (const rel of writtenPaths) {
|
|
10394
|
+
const parts = rel.split("/");
|
|
10395
|
+
for (let i = 1; i < parts.length; i++)
|
|
10396
|
+
dirs.add(`${parts.slice(0, i).join("/")}/`);
|
|
10397
|
+
}
|
|
10398
|
+
const dirAlt = [...dirs].map(escapeRegExp).join("|");
|
|
10399
|
+
const wordRe = dirs.size > 0 ? new RegExp(`(?:${dirAlt})[^\\s"'\`;|&<>()\\\\]*`, "g") : null;
|
|
10400
|
+
const findRe = dirs.size > 0 ? new RegExp(`\\bfind\\s+(?:\\./)?(${[...dirs].map((d) => escapeRegExp(d.slice(0, -1))).join("|")})/?(?=\\s|$)`, "g") : null;
|
|
10401
|
+
const readerRe = new RegExp(`(?:^|[\\s"])(?:-exec|-execdir|xargs)\\b[^;|&]*\\b${READERS}\\b`);
|
|
10402
|
+
const credited = /* @__PURE__ */ new Set();
|
|
10403
|
+
for (const raw of toolInputs) {
|
|
10404
|
+
const input = roots.reduce((s, r) => s.split(`${r}/`).join(""), raw);
|
|
10405
|
+
for (const rel of writtenPaths) {
|
|
10406
|
+
if (input.includes(rel))
|
|
10407
|
+
credited.add(rel);
|
|
10408
|
+
}
|
|
10409
|
+
if (wordRe) {
|
|
10410
|
+
for (const match of input.matchAll(wordRe)) {
|
|
10411
|
+
const word = match[0];
|
|
10412
|
+
if (!GLOB_CHARS.test(word))
|
|
10413
|
+
continue;
|
|
10414
|
+
const re = globToRegExp(word);
|
|
10415
|
+
if (!re)
|
|
10416
|
+
continue;
|
|
10417
|
+
for (const rel of writtenPaths) {
|
|
10418
|
+
if (re.test(rel))
|
|
10419
|
+
credited.add(rel);
|
|
10420
|
+
}
|
|
10421
|
+
}
|
|
10422
|
+
}
|
|
10423
|
+
if (findRe && readerRe.test(input)) {
|
|
10424
|
+
for (const match of input.matchAll(findRe)) {
|
|
10425
|
+
const dir = `${match[1]}/`;
|
|
10426
|
+
for (const rel of writtenPaths) {
|
|
10427
|
+
if (rel.startsWith(dir))
|
|
10428
|
+
credited.add(rel);
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
}
|
|
10432
|
+
if (!PLAIN_READER_RE.test(input))
|
|
10433
|
+
continue;
|
|
10434
|
+
for (const match of input.matchAll(CD_RE)) {
|
|
10435
|
+
const target = match[1].replace(/^\.\//, "").replace(/\/+$/, "");
|
|
10436
|
+
const base = target.length > 0 ? `${target}/` : "";
|
|
10437
|
+
for (const rel of writtenPaths) {
|
|
10438
|
+
if (!rel.startsWith(base))
|
|
10439
|
+
continue;
|
|
10440
|
+
if (namesBareWord(input, rel.slice(base.length)))
|
|
10441
|
+
credited.add(rel);
|
|
10442
|
+
}
|
|
10443
|
+
}
|
|
10444
|
+
}
|
|
10445
|
+
return [...credited].sort();
|
|
10446
|
+
}
|
|
10447
|
+
var READERS = "(?:cat|head|tail|sed|awk|less|more|bat)";
|
|
10448
|
+
var PLAIN_READER_RE = new RegExp(`(?:^|[\\s"'\`;|&(])${READERS}\\b`);
|
|
10449
|
+
var CD_RE = /(?:^|[\s"'`;|&(])cd\s+(?:--\s+)?["']?([^\s"'`;|&<>()\\]+)/g;
|
|
10450
|
+
function namesBareWord(input, word) {
|
|
10451
|
+
const edge = String.raw`[\s"'\`;|&<>(),\\]`;
|
|
10452
|
+
return new RegExp(`(?:^|${edge})${escapeRegExp(word)}(?=$|${edge})`).test(input);
|
|
10453
|
+
}
|
|
10454
|
+
function shellReadPaths(commands, runCwd) {
|
|
10455
|
+
const roots = workspaceRoots(runCwd);
|
|
10456
|
+
const out = /* @__PURE__ */ new Set();
|
|
10457
|
+
for (const command of commands) {
|
|
10458
|
+
if (!PLAIN_READER_RE.test(command))
|
|
10459
|
+
continue;
|
|
10460
|
+
for (const raw of command.split(/[\s"'`;|&<>()]+/)) {
|
|
10461
|
+
const word = raw.replace(/^\.\//, "");
|
|
10462
|
+
if (word.length === 0 || word.startsWith("-") || GLOB_CHARS.test(word))
|
|
10463
|
+
continue;
|
|
10464
|
+
const abs = path_1.default.resolve(roots[0], roots.reduce((w, r) => w.startsWith(`${r}/`) ? w.slice(r.length + 1) : w, word));
|
|
10465
|
+
if (!roots.some((r) => abs.startsWith(r + path_1.default.sep)))
|
|
10466
|
+
continue;
|
|
10467
|
+
try {
|
|
10468
|
+
if ((0, fs_1.statSync)(abs).isFile())
|
|
10469
|
+
out.add(abs);
|
|
10470
|
+
} catch {
|
|
10471
|
+
}
|
|
10472
|
+
if (out.size >= MAX_REPORTED_READS)
|
|
10473
|
+
return [...out];
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10476
|
+
return [...out];
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
});
|
|
10480
|
+
|
|
10481
|
+
// ../../packages/shuttle/dist/executors/claude-code.js
|
|
10482
|
+
var require_claude_code = __commonJS({
|
|
10483
|
+
"../../packages/shuttle/dist/executors/claude-code.js"(exports2) {
|
|
10484
|
+
"use strict";
|
|
10485
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10486
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10487
|
+
};
|
|
10488
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10489
|
+
exports2.classifyEnvironmentFault = classifyEnvironmentFault;
|
|
10490
|
+
exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
|
|
10491
|
+
exports2.checkoutScopedTools = checkoutScopedTools;
|
|
10492
|
+
exports2.parseStreamJson = parseStreamJson;
|
|
10493
|
+
var fs_1 = require("fs");
|
|
10494
|
+
var os_1 = __importDefault(require("os"));
|
|
10495
|
+
var path_1 = __importDefault(require("path"));
|
|
10496
|
+
var loom_1 = require_dist();
|
|
10497
|
+
var types_1 = require_types2();
|
|
10498
|
+
var validate_output_1 = require_validate_output();
|
|
10499
|
+
var progress_1 = require_progress();
|
|
10500
|
+
var agent_process_1 = require_agent_process();
|
|
10501
|
+
var json_answer_1 = require_json_answer();
|
|
10502
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
10503
|
+
var apply_1 = require_apply();
|
|
10504
|
+
var DEFAULT_TIMEOUT = 6e5;
|
|
10505
|
+
var REVIEW_TOOLS = ["Read", "Grep", "Glob"];
|
|
10506
|
+
function reviewRunArgs(settingSources) {
|
|
10507
|
+
return [
|
|
10508
|
+
"--setting-sources",
|
|
10509
|
+
settingSources,
|
|
10510
|
+
"--strict-mcp-config",
|
|
10511
|
+
"--tools",
|
|
10512
|
+
REVIEW_TOOLS.join(","),
|
|
10513
|
+
"--permission-mode",
|
|
10514
|
+
"default",
|
|
10515
|
+
"--settings",
|
|
10516
|
+
JSON.stringify({ disableAllHooks: true })
|
|
10517
|
+
];
|
|
10518
|
+
}
|
|
10519
|
+
var RETIRED_READ_TOOLS = ["LS"];
|
|
10520
|
+
var HOSTED_ENV_PASSTHROUGH = [
|
|
10521
|
+
"PATH",
|
|
10522
|
+
"HOME",
|
|
10523
|
+
"TMPDIR",
|
|
10524
|
+
"LANG",
|
|
10525
|
+
"LC_ALL",
|
|
10526
|
+
"LC_CTYPE",
|
|
10527
|
+
"TZ",
|
|
10528
|
+
"NODE_EXTRA_CA_CERTS",
|
|
10529
|
+
"SSL_CERT_FILE",
|
|
10530
|
+
"SSL_CERT_DIR",
|
|
10531
|
+
"HTTPS_PROXY",
|
|
10532
|
+
"HTTP_PROXY",
|
|
10533
|
+
"NO_PROXY",
|
|
10534
|
+
"https_proxy",
|
|
10535
|
+
"http_proxy",
|
|
10536
|
+
"no_proxy"
|
|
10537
|
+
];
|
|
10538
|
+
function describeClaudeFailure(meta, fallback) {
|
|
10539
|
+
if (!meta)
|
|
10540
|
+
return fallback;
|
|
10541
|
+
const status = meta.apiErrorStatus !== null ? ` (provider HTTP ${meta.apiErrorStatus})` : "";
|
|
10542
|
+
if (meta.detail)
|
|
10543
|
+
return `${meta.detail}${status}`;
|
|
10544
|
+
const subtype = meta.subtype && meta.subtype !== "success" ? `: ${meta.subtype}` : "";
|
|
10545
|
+
return `${fallback}${subtype}${status}`;
|
|
10546
|
+
}
|
|
10547
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
10548
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
10549
|
+
var INSTALL_REMEDY = "install Claude Code (npm install -g @anthropic-ai/claude-code) where this process can find it on PATH";
|
|
10550
|
+
var LOGIN_REMEDY = "run `claude` once and sign in (or set a valid ANTHROPIC_API_KEY); if your login or provider is configured in Claude Code settings (apiKeyHelper, or an env block), set `claudeSettings: user` on the claude-code executor, since a review loads no settings file without it";
|
|
10551
|
+
var LIMIT_REMEDY = "wait for the plan's usage limit to reset, or raise it";
|
|
10552
|
+
var SIGNED_OUT = /invalid api key|please run \/login|not (?:logged|signed) in|oauth token (?:has )?expired|authentication_error|invalid x-api-key/i;
|
|
10553
|
+
var OUT_OF_USAGE = /usage limit|credit balance is too low|out of (?:extra )?usage|hit your (?:\w+ )?(?:usage |spend )?limit|5-hour limit/i;
|
|
10554
|
+
var CLAUDE_FAULTS = {
|
|
10555
|
+
installRemedy: INSTALL_REMEDY,
|
|
10556
|
+
loginRemedy: LOGIN_REMEDY,
|
|
10557
|
+
limitRemedy: LIMIT_REMEDY,
|
|
10558
|
+
signedOut: SIGNED_OUT,
|
|
10559
|
+
outOfUsage: OUT_OF_USAGE
|
|
10560
|
+
};
|
|
10561
|
+
function classifyEnvironmentFault(failure) {
|
|
10562
|
+
return (0, agent_process_1.classifyAgentFault)(failure, CLAUDE_FAULTS);
|
|
10563
|
+
}
|
|
10564
|
+
function runFailure(local, message, facts) {
|
|
10565
|
+
return (0, agent_process_1.agentFailure)(local, message, facts, CLAUDE_FAULTS);
|
|
10566
|
+
}
|
|
10567
|
+
var ClaudeCodeExecutor = class {
|
|
10568
|
+
capability;
|
|
10569
|
+
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
10570
|
+
config;
|
|
10571
|
+
constructor(config) {
|
|
10572
|
+
this.config = config;
|
|
10573
|
+
this.capability = { id: config.capabilityId };
|
|
10574
|
+
}
|
|
10575
|
+
estimate(dispatch) {
|
|
10576
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
10577
|
+
return {
|
|
10578
|
+
inputTokens: spec.costCapHint?.estimatedInputTokens,
|
|
10579
|
+
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
10580
|
+
};
|
|
10581
|
+
}
|
|
10582
|
+
/**
|
|
10583
|
+
* Declare the run environment to the runner (ADR 0026): the opaque workspace
|
|
10584
|
+
* bag plus the materialization config (tmp dir, overlay allowlist). No
|
|
10585
|
+
* credentials here (ADR 0027) — the runner's broker reveals them on demand:
|
|
10586
|
+
* this executor pulls its model key from `ctx.credentials` in `execute()`,
|
|
10587
|
+
* and the git request references its own fetch credential. The executor
|
|
10588
|
+
* touches neither git nor Keep.
|
|
10589
|
+
*/
|
|
10590
|
+
async prepareEnvironment(dispatch) {
|
|
10591
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
10592
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : void 0;
|
|
10593
|
+
return {
|
|
10594
|
+
...spec.workspace ? { workspaceRequest: spec.workspace } : {},
|
|
10595
|
+
materializeOptions: {
|
|
10596
|
+
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
10597
|
+
overlayAllowedHosts: this.config.overlayAllowedHosts ?? []
|
|
10598
|
+
}
|
|
10599
|
+
};
|
|
10600
|
+
}
|
|
10601
|
+
async execute(dispatch, signal, ctx) {
|
|
10602
|
+
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
10603
|
+
const workspace = ctx?.workspace ?? null;
|
|
10604
|
+
let modelKey = null;
|
|
10605
|
+
if (ctx?.credentials) {
|
|
10606
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "anthropic-api");
|
|
10607
|
+
const customerId = typeof hints.customerId === "string" ? hints.customerId : void 0;
|
|
10608
|
+
if (!customerId) {
|
|
10609
|
+
throw new Error("claude-code dynamic-key mode requires providerHints.anthropicApi.customerId on every job");
|
|
10610
|
+
}
|
|
10611
|
+
modelKey = await ctx.credentials.reveal({ name: "managed-model-key", principal: customerId });
|
|
10612
|
+
}
|
|
10613
|
+
let jobScratch = null;
|
|
10614
|
+
let configDir;
|
|
10615
|
+
if (modelKey) {
|
|
10616
|
+
jobScratch = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-cc-"));
|
|
10617
|
+
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
10618
|
+
await fs_1.promises.mkdir(configDir);
|
|
10619
|
+
}
|
|
10620
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
10621
|
+
let promptOnlyScratch = null;
|
|
10622
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
10623
|
+
try {
|
|
10624
|
+
const childEnv = buildChildEnv(modelKey, configDir);
|
|
10625
|
+
const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
|
|
10626
|
+
const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
|
|
10627
|
+
if (!workspace) {
|
|
10628
|
+
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
10629
|
+
}
|
|
10630
|
+
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
10631
|
+
const { response, readPaths, toolInputs } = await this.runClaude(
|
|
10632
|
+
prompt,
|
|
10633
|
+
spec,
|
|
10634
|
+
runCwd,
|
|
10635
|
+
childEnv,
|
|
10636
|
+
signal,
|
|
10637
|
+
hintedModel,
|
|
10638
|
+
reporter,
|
|
10639
|
+
void 0,
|
|
10640
|
+
dispatch.job.id,
|
|
10641
|
+
// Local mode is the operator's own login: a failure of THAT is the
|
|
10642
|
+
// machine's, and the runner pauses on it. With a revealed key it is one
|
|
10643
|
+
// customer's, and only their job fails.
|
|
10644
|
+
modelKey === null
|
|
10645
|
+
);
|
|
10646
|
+
if (workspace) {
|
|
10647
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, readPaths, toolInputs));
|
|
10648
|
+
}
|
|
10649
|
+
return response;
|
|
10650
|
+
} finally {
|
|
10651
|
+
reporter?.close();
|
|
10652
|
+
if (promptOnlyScratch)
|
|
10653
|
+
await fs_1.promises.rm(promptOnlyScratch, { recursive: true, force: true }).catch(() => {
|
|
10654
|
+
});
|
|
10655
|
+
if (jobScratch)
|
|
10656
|
+
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
10657
|
+
});
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
/**
|
|
10661
|
+
* In-session gate repair (ADR 0017): resume the session `execute()` returned a
|
|
10662
|
+
* handle for and correct its output. The repair turn works from the resumed
|
|
10663
|
+
* session's own context (no checkout needed), under the same `--json-schema`
|
|
10664
|
+
* constraint. A throwaway cwd hosts the run; the same handle rides back out so
|
|
10665
|
+
* a second repair round can resume again.
|
|
10666
|
+
*/
|
|
10667
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
10668
|
+
const handle = continuation;
|
|
10669
|
+
if (!handle || typeof handle.sessionId !== "string") {
|
|
10670
|
+
throw new Error("claude-code continue: missing session to resume");
|
|
10671
|
+
}
|
|
10672
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
10673
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-resume-"));
|
|
10674
|
+
try {
|
|
10675
|
+
const spec = {
|
|
10676
|
+
rendering: "producer",
|
|
10677
|
+
outputLabel: handle.outputLabel,
|
|
10678
|
+
outputSchema: handle.outputSchema
|
|
10679
|
+
};
|
|
10680
|
+
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId, `${handle.sessionId}-repair`, handle.childEnv === void 0);
|
|
10681
|
+
return response;
|
|
10682
|
+
} finally {
|
|
10683
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
10684
|
+
});
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
/**
|
|
10688
|
+
* Can this machine run a job now? One real one-turn call through the CLI,
|
|
10689
|
+
* the same probe `doctor --deep` makes. The runner asks it only while
|
|
10690
|
+
* claiming is paused on an `ExecutorEnvironmentError`, and only in local
|
|
10691
|
+
* mode. A signed-out or missing CLI fails before any model call, so the
|
|
10692
|
+
* re-check spends nothing until the login works again, and then one turn.
|
|
10693
|
+
*/
|
|
10694
|
+
async checkReadiness(signal) {
|
|
10695
|
+
let run;
|
|
10696
|
+
try {
|
|
10697
|
+
run = await (0, agent_process_1.runAgentProcess)({
|
|
10698
|
+
bin: "claude",
|
|
10699
|
+
args: ["--print", "--output-format", "stream-json", "--verbose", "--max-turns", "1", READINESS_PROMPT],
|
|
10700
|
+
cwd: process.cwd(),
|
|
10701
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
10702
|
+
toolName: "the claude CLI",
|
|
10703
|
+
signal
|
|
10704
|
+
});
|
|
10705
|
+
} catch (err) {
|
|
10706
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10707
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
10708
|
+
}
|
|
10709
|
+
if (run.spawnError) {
|
|
10710
|
+
return {
|
|
10711
|
+
ready: false,
|
|
10712
|
+
reason: run.spawnError.code === "ENOENT" ? `the claude CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
10713
|
+
};
|
|
10714
|
+
}
|
|
10715
|
+
let meta = null;
|
|
10716
|
+
try {
|
|
10717
|
+
meta = parseStreamJson(run.stdout).resultMeta;
|
|
10718
|
+
} catch {
|
|
10719
|
+
meta = null;
|
|
10720
|
+
}
|
|
10721
|
+
if (run.code === 0 && !meta?.isError)
|
|
10722
|
+
return { ready: true };
|
|
10723
|
+
const said = meta?.detail ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
10724
|
+
return { ready: false, reason: said ?? `the claude CLI exited with code ${run.code}` };
|
|
10725
|
+
}
|
|
10726
|
+
async runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName, local = false) {
|
|
10727
|
+
const outputLabel = spec.outputLabel;
|
|
10728
|
+
const model = modelOverride ?? this.config.model;
|
|
10729
|
+
const allowedTools = local ? this.config.allowedTools : checkoutScopedTools(this.config.allowedTools);
|
|
10730
|
+
if (!local && this.config.claudeSettings === "user") {
|
|
10731
|
+
throw new Error("claude-code: `claudeSettings: user` is for an installed Sifter whose login lives in Claude Code settings; a hosted run authenticates with the key it is handed and loads no settings");
|
|
10732
|
+
}
|
|
10733
|
+
const settingSources = local && this.config.claudeSettings === "user" ? "user" : "";
|
|
10734
|
+
const args = ["--print", "--output-format", "stream-json", "--verbose", ...reviewRunArgs(settingSources)];
|
|
10735
|
+
if (resumeSessionId)
|
|
10736
|
+
args.push("--resume", resumeSessionId);
|
|
10737
|
+
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
10738
|
+
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
10739
|
+
}
|
|
10740
|
+
if (model)
|
|
10741
|
+
args.push("--model", model);
|
|
10742
|
+
if (this.config.maxTurns !== void 0)
|
|
10743
|
+
args.push("--max-turns", String(this.config.maxTurns));
|
|
10744
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
10745
|
+
args.push("--allowedTools", allowedTools.join(","));
|
|
10746
|
+
}
|
|
10747
|
+
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
10748
|
+
onReasoning: (t) => reporter.reasoning(t),
|
|
10749
|
+
onTool: (name, target) => reporter.tool(name, target),
|
|
10750
|
+
onResult: () => reporter.flush()
|
|
10751
|
+
}) : null;
|
|
10752
|
+
const run = await (0, agent_process_1.runAgentProcess)({
|
|
10753
|
+
bin: "claude",
|
|
10754
|
+
args,
|
|
10755
|
+
cwd,
|
|
10756
|
+
// Undefined in local mode → inherit the ambient env (and its
|
|
10757
|
+
// `~/.claude` login) unchanged.
|
|
10758
|
+
env: childEnv,
|
|
10759
|
+
stdin: prompt,
|
|
10760
|
+
idleTimeoutMs: this.config.timeout ?? DEFAULT_TIMEOUT,
|
|
10761
|
+
toolName: "Claude Code",
|
|
10762
|
+
signal,
|
|
10763
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
10764
|
+
});
|
|
10765
|
+
tap?.end();
|
|
10766
|
+
if (run.spawnError) {
|
|
10767
|
+
if (local && run.spawnError.code === "ENOENT") {
|
|
10768
|
+
throw new types_1.ExecutorEnvironmentError(`the claude CLI is not on this machine's PATH (${run.spawnError.message})`, INSTALL_REMEDY);
|
|
10769
|
+
}
|
|
10770
|
+
throw new Error(run.spawnError.message);
|
|
10771
|
+
}
|
|
10772
|
+
const { stdout, stderr: stderrText, code } = run;
|
|
10773
|
+
if (this.config.keepTranscripts && transcriptName) {
|
|
10774
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), transcriptName, stdout);
|
|
10775
|
+
}
|
|
10776
|
+
if (code !== 0) {
|
|
10777
|
+
let meta = null;
|
|
10778
|
+
try {
|
|
10779
|
+
meta = parseStreamJson(stdout).resultMeta;
|
|
10780
|
+
} catch {
|
|
10781
|
+
}
|
|
10782
|
+
const said = meta?.detail ?? (0, agent_process_1.firstLine)(stderrText) ?? (0, agent_process_1.firstLine)(stdout);
|
|
10783
|
+
const fallback = `Claude Code exited with code ${code}`;
|
|
10784
|
+
const failure = runFailure(local, describeClaudeFailure(meta, fallback), {
|
|
10785
|
+
apiErrorStatus: meta?.apiErrorStatus ?? null,
|
|
10786
|
+
text: said
|
|
10787
|
+
});
|
|
10788
|
+
if (failure instanceof types_1.ExecutorEnvironmentError && !meta && said) {
|
|
10789
|
+
throw new types_1.ExecutorEnvironmentError(`${fallback}: ${said}`, failure.remedy);
|
|
10790
|
+
}
|
|
10791
|
+
throw failure;
|
|
10792
|
+
}
|
|
10793
|
+
const stream = parseStreamJson(stdout);
|
|
10794
|
+
if (stream.resultMeta?.isError) {
|
|
10795
|
+
throw runFailure(local, describeClaudeFailure(stream.resultMeta, "Claude Code reported a failure"), {
|
|
10796
|
+
apiErrorStatus: stream.resultMeta.apiErrorStatus,
|
|
10797
|
+
text: stream.resultMeta.detail ?? (0, agent_process_1.firstLine)(stderrText)
|
|
10798
|
+
});
|
|
10799
|
+
}
|
|
10800
|
+
const answerText = stream.finalText ?? stdout;
|
|
10801
|
+
const parsed = stream.structuredOutput ?? (0, json_answer_1.extractJsonObject)(answerText);
|
|
10802
|
+
if (parsed === void 0 && stream.resultMeta) {
|
|
10803
|
+
throw new Error(`claude's final message contained no JSON object (result subtype=${stream.resultMeta.subtype ?? "unknown"}; message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
10804
|
+
}
|
|
10805
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
10806
|
+
return {
|
|
10807
|
+
response: {
|
|
10808
|
+
outputs: [{ label: outputLabel, content: parsed ?? { raw: answerText } }],
|
|
10809
|
+
rawText: answerText,
|
|
10810
|
+
parsed,
|
|
10811
|
+
usage: {
|
|
10812
|
+
aiProvider: "claude-code",
|
|
10813
|
+
...model ? { aiModel: model } : {},
|
|
10814
|
+
...stream.usage ?? {}
|
|
10815
|
+
},
|
|
10816
|
+
// In-session repair handle (ADR 0017): present only when the run
|
|
10817
|
+
// reported a session id, so the runner can `--resume` it.
|
|
10818
|
+
...stream.sessionId ? {
|
|
10819
|
+
continuation: {
|
|
10820
|
+
sessionId: stream.sessionId,
|
|
10821
|
+
model,
|
|
10822
|
+
childEnv,
|
|
10823
|
+
outputLabel,
|
|
10824
|
+
outputSchema: spec.outputSchema
|
|
10825
|
+
}
|
|
10826
|
+
} : {}
|
|
10827
|
+
},
|
|
10828
|
+
readPaths: stream.readPaths,
|
|
10829
|
+
toolInputs: stream.toolInputs
|
|
10830
|
+
};
|
|
10831
|
+
}
|
|
10832
|
+
};
|
|
10833
|
+
function createClaudeCodeExecutor(config, capabilityId = "claude-code") {
|
|
10834
|
+
const validated = { capabilityId };
|
|
10835
|
+
if (config.tmpDir !== void 0) {
|
|
10836
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
10837
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
10838
|
+
}
|
|
10839
|
+
validated.tmpDir = config.tmpDir;
|
|
10840
|
+
}
|
|
10841
|
+
if (config.model !== void 0) {
|
|
10842
|
+
if (typeof config.model !== "string")
|
|
10843
|
+
throw new Error('"model" must be a string');
|
|
10844
|
+
validated.model = config.model;
|
|
10845
|
+
}
|
|
10846
|
+
if (config.maxTurns !== void 0) {
|
|
10847
|
+
if (typeof config.maxTurns !== "number" || !Number.isInteger(config.maxTurns) || config.maxTurns < 1) {
|
|
10848
|
+
throw new Error('"maxTurns" must be a positive integer');
|
|
10849
|
+
}
|
|
10850
|
+
validated.maxTurns = config.maxTurns;
|
|
10851
|
+
}
|
|
10852
|
+
if (config.allowedTools !== void 0) {
|
|
10853
|
+
if (!Array.isArray(config.allowedTools) || !config.allowedTools.every((t) => typeof t === "string")) {
|
|
10854
|
+
throw new Error('"allowedTools" must be an array of strings');
|
|
10855
|
+
}
|
|
10856
|
+
validated.allowedTools = config.allowedTools;
|
|
10857
|
+
}
|
|
10858
|
+
if (config.timeout !== void 0) {
|
|
10859
|
+
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
10860
|
+
throw new Error('"timeout" must be a positive number');
|
|
10861
|
+
}
|
|
10862
|
+
validated.timeout = config.timeout;
|
|
10863
|
+
}
|
|
10864
|
+
if (config.keepTranscripts !== void 0) {
|
|
10865
|
+
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
10866
|
+
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
10867
|
+
}
|
|
10868
|
+
validated.keepTranscripts = config.keepTranscripts;
|
|
10869
|
+
}
|
|
10870
|
+
if (config.claudeSettings !== void 0) {
|
|
10871
|
+
if (config.claudeSettings !== "user") {
|
|
10872
|
+
throw new Error('"claudeSettings" may only be "user" (leave it unset to load no settings file)');
|
|
10873
|
+
}
|
|
10874
|
+
validated.claudeSettings = "user";
|
|
10875
|
+
}
|
|
10876
|
+
if (config.overlayAllowedHosts !== void 0) {
|
|
10877
|
+
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
10878
|
+
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
10879
|
+
}
|
|
10880
|
+
validated.overlayAllowedHosts = config.overlayAllowedHosts;
|
|
10881
|
+
}
|
|
10882
|
+
return new ClaudeCodeExecutor(validated);
|
|
10883
|
+
}
|
|
10884
|
+
function checkoutScopedTools(tools) {
|
|
10885
|
+
if (!tools)
|
|
10886
|
+
return void 0;
|
|
10887
|
+
const scoped = [];
|
|
10888
|
+
for (const tool of tools) {
|
|
10889
|
+
if (RETIRED_READ_TOOLS.some((t) => tool === t || tool === `${t}(./**)`))
|
|
10890
|
+
continue;
|
|
10891
|
+
const name = REVIEW_TOOLS.find((t) => tool === t || tool === `${t}(./**)`);
|
|
10892
|
+
if (!name) {
|
|
10893
|
+
throw new Error(`claude-code: a hosted run may use only ${REVIEW_TOOLS.join(", ")} inside the checkout; "${tool}" is not allowed`);
|
|
10894
|
+
}
|
|
10895
|
+
scoped.push(`${name}(./**)`);
|
|
10896
|
+
}
|
|
10897
|
+
return scoped;
|
|
10898
|
+
}
|
|
10899
|
+
function buildChildEnv(modelKey, configDir) {
|
|
10900
|
+
if (!modelKey && !configDir)
|
|
10901
|
+
return void 0;
|
|
10902
|
+
const env = {};
|
|
10903
|
+
for (const name of HOSTED_ENV_PASSTHROUGH) {
|
|
10904
|
+
if (process.env[name] !== void 0)
|
|
10905
|
+
env[name] = process.env[name];
|
|
10906
|
+
}
|
|
10907
|
+
return {
|
|
10908
|
+
...env,
|
|
10909
|
+
...modelKey ? { ANTHROPIC_API_KEY: modelKey } : {},
|
|
10910
|
+
...configDir ? { CLAUDE_CONFIG_DIR: configDir } : {}
|
|
10911
|
+
};
|
|
10912
|
+
}
|
|
10913
|
+
function parseStreamJson(stdout) {
|
|
10914
|
+
let finalText = null;
|
|
10915
|
+
const readPaths = [];
|
|
10916
|
+
let usage = null;
|
|
10917
|
+
let resultMeta = null;
|
|
10918
|
+
let structuredOutput = null;
|
|
10919
|
+
let sessionId = null;
|
|
10920
|
+
const toolInputs = [];
|
|
10921
|
+
for (const line of stdout.split("\n")) {
|
|
10922
|
+
const trimmed = line.trim();
|
|
10923
|
+
if (!trimmed.startsWith("{"))
|
|
10924
|
+
continue;
|
|
10925
|
+
let event;
|
|
10926
|
+
try {
|
|
10927
|
+
event = JSON.parse(trimmed);
|
|
10928
|
+
} catch {
|
|
10929
|
+
continue;
|
|
10930
|
+
}
|
|
10931
|
+
if (typeof event.session_id === "string")
|
|
10932
|
+
sessionId = event.session_id;
|
|
10933
|
+
if (event.type === "assistant") {
|
|
10934
|
+
const content = event.message?.content;
|
|
10935
|
+
if (!Array.isArray(content))
|
|
10936
|
+
continue;
|
|
10937
|
+
for (const block of content) {
|
|
10938
|
+
const b = block;
|
|
10939
|
+
if (b?.type !== "tool_use")
|
|
10940
|
+
continue;
|
|
10941
|
+
const fp = b.input?.file_path;
|
|
10942
|
+
if (b.name === "Read" && typeof fp === "string") {
|
|
10943
|
+
readPaths.push(fp);
|
|
10944
|
+
}
|
|
10945
|
+
if (b.input && typeof b.input === "object")
|
|
10946
|
+
toolInputs.push(JSON.stringify(b.input));
|
|
10947
|
+
}
|
|
10948
|
+
} else if (event.type === "result") {
|
|
10949
|
+
if (typeof event.result === "string" && event.result.trim().length > 0) {
|
|
10950
|
+
finalText = event.result;
|
|
10951
|
+
}
|
|
10952
|
+
resultMeta = {
|
|
10953
|
+
subtype: typeof event.subtype === "string" ? event.subtype : null,
|
|
10954
|
+
isError: event.is_error === true,
|
|
10955
|
+
// On a failure the CLI puts a human-readable reason in `result` — the
|
|
10956
|
+
// model was unavailable, spend limit hit, and so on. It is the only
|
|
10957
|
+
// place that reason exists, so keep it for the failure message.
|
|
10958
|
+
detail: typeof event.result === "string" && event.result.trim().length > 0 ? event.result.trim() : null,
|
|
10959
|
+
apiErrorStatus: typeof event.api_error_status === "number" ? event.api_error_status : null
|
|
10960
|
+
};
|
|
10961
|
+
const so = event.structured_output;
|
|
10962
|
+
if (so && typeof so === "object" && !Array.isArray(so)) {
|
|
10963
|
+
structuredOutput = so;
|
|
10964
|
+
}
|
|
10965
|
+
const u = event.usage;
|
|
10966
|
+
const cost = event.total_cost_usd;
|
|
10967
|
+
usage = {
|
|
10968
|
+
...typeof u?.input_tokens === "number" ? { inputTokens: u.input_tokens } : {},
|
|
10969
|
+
...typeof u?.output_tokens === "number" ? { outputTokens: u.output_tokens } : {},
|
|
10970
|
+
...typeof cost === "number" ? { costUsd: cost } : {}
|
|
10971
|
+
};
|
|
10972
|
+
}
|
|
10973
|
+
}
|
|
10974
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolInputs };
|
|
10975
|
+
}
|
|
10976
|
+
}
|
|
10977
|
+
});
|
|
10978
|
+
|
|
10979
|
+
// ../../packages/shuttle/dist/executors/agent-common.js
|
|
10980
|
+
var require_agent_common = __commonJS({
|
|
10981
|
+
"../../packages/shuttle/dist/executors/agent-common.js"(exports2) {
|
|
10982
|
+
"use strict";
|
|
10983
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10984
|
+
exports2.DEFAULT_AGENT_TIMEOUT = void 0;
|
|
10985
|
+
exports2.parseAgentCommonConfig = parseAgentCommonConfig;
|
|
10986
|
+
exports2.agentEnvironment = agentEnvironment;
|
|
10987
|
+
exports2.refuseHosted = refuseHosted;
|
|
10988
|
+
var loom_1 = require_dist();
|
|
10989
|
+
var apply_1 = require_apply();
|
|
10990
|
+
exports2.DEFAULT_AGENT_TIMEOUT = 6e5;
|
|
10991
|
+
function parseAgentCommonConfig(config, capabilityId) {
|
|
10992
|
+
const out = { capabilityId };
|
|
10993
|
+
if (config.tmpDir !== void 0) {
|
|
10994
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
10995
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
10996
|
+
}
|
|
10997
|
+
out.tmpDir = config.tmpDir;
|
|
10998
|
+
}
|
|
10999
|
+
if (config.model !== void 0) {
|
|
11000
|
+
if (typeof config.model !== "string" || config.model.length === 0) {
|
|
11001
|
+
throw new Error('"model" must be a non-empty string');
|
|
11002
|
+
}
|
|
11003
|
+
out.model = config.model;
|
|
11004
|
+
}
|
|
11005
|
+
if (config.timeout !== void 0) {
|
|
11006
|
+
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
11007
|
+
throw new Error('"timeout" must be a positive number');
|
|
11008
|
+
}
|
|
11009
|
+
out.timeout = config.timeout;
|
|
11010
|
+
}
|
|
11011
|
+
if (config.keepTranscripts !== void 0) {
|
|
11012
|
+
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
11013
|
+
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
11014
|
+
}
|
|
11015
|
+
out.keepTranscripts = config.keepTranscripts;
|
|
11016
|
+
}
|
|
11017
|
+
if (config.overlayAllowedHosts !== void 0) {
|
|
11018
|
+
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
11019
|
+
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
11020
|
+
}
|
|
11021
|
+
out.overlayAllowedHosts = config.overlayAllowedHosts;
|
|
11022
|
+
}
|
|
11023
|
+
return out;
|
|
11024
|
+
}
|
|
11025
|
+
function agentEnvironment(dispatch, config) {
|
|
11026
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
11027
|
+
const tmpBase = config.tmpDir ? (0, apply_1.expandHome)(config.tmpDir) : void 0;
|
|
11028
|
+
return {
|
|
11029
|
+
...spec.workspace ? { workspaceRequest: spec.workspace } : {},
|
|
11030
|
+
materializeOptions: {
|
|
11031
|
+
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
11032
|
+
overlayAllowedHosts: config.overlayAllowedHosts ?? []
|
|
11033
|
+
}
|
|
11034
|
+
};
|
|
11035
|
+
}
|
|
11036
|
+
function refuseHosted(executor, hosted) {
|
|
11037
|
+
if (hosted) {
|
|
11038
|
+
throw new Error(`${executor}: this executor runs on an installed Sifter only, on the operator's own login; a hosted Sifter has no managed credential for it`);
|
|
11039
|
+
}
|
|
11040
|
+
}
|
|
11041
|
+
}
|
|
11042
|
+
});
|
|
11043
|
+
|
|
11044
|
+
// ../../packages/shuttle/dist/executors/strict-schema.js
|
|
11045
|
+
var require_strict_schema = __commonJS({
|
|
11046
|
+
"../../packages/shuttle/dist/executors/strict-schema.js"(exports2) {
|
|
11047
|
+
"use strict";
|
|
11048
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11049
|
+
exports2.toStrict = toStrict;
|
|
11050
|
+
exports2.dropNulls = dropNulls;
|
|
11051
|
+
var UNSUPPORTED = ["format", "default", "$schema"];
|
|
11052
|
+
function toStrict(schema) {
|
|
11053
|
+
if (Array.isArray(schema))
|
|
11054
|
+
return schema.map(toStrict);
|
|
11055
|
+
if (!isSchema(schema))
|
|
11056
|
+
return schema;
|
|
11057
|
+
const out = {};
|
|
11058
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
11059
|
+
if (UNSUPPORTED.includes(key))
|
|
11060
|
+
continue;
|
|
11061
|
+
if (key === "properties" && isSchema(value)) {
|
|
11062
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
11063
|
+
const props = {};
|
|
11064
|
+
for (const [name, prop] of Object.entries(value)) {
|
|
11065
|
+
const strict = toStrict(prop);
|
|
11066
|
+
props[name] = required.has(name) ? strict : nullable(strict);
|
|
11067
|
+
}
|
|
11068
|
+
out.properties = props;
|
|
11069
|
+
out.required = Object.keys(value);
|
|
11070
|
+
continue;
|
|
11071
|
+
}
|
|
11072
|
+
if (key === "required")
|
|
11073
|
+
continue;
|
|
11074
|
+
out[key] = SUBSCHEMA_KEYS.has(key) ? toStrict(value) : value;
|
|
11075
|
+
}
|
|
11076
|
+
if (out.type === "object" || out.properties !== void 0)
|
|
11077
|
+
out.additionalProperties = false;
|
|
11078
|
+
return out;
|
|
11079
|
+
}
|
|
11080
|
+
function dropNulls(value, schema) {
|
|
11081
|
+
if (!isSchema(schema))
|
|
11082
|
+
return value;
|
|
11083
|
+
if (Array.isArray(value)) {
|
|
11084
|
+
return isSchema(schema.items) ? value.map((v) => dropNulls(v, schema.items)) : value;
|
|
11085
|
+
}
|
|
11086
|
+
if (!isSchema(value)) {
|
|
11087
|
+
return value;
|
|
11088
|
+
}
|
|
11089
|
+
const branches = [schema.anyOf, schema.oneOf].find(Array.isArray);
|
|
11090
|
+
if (branches) {
|
|
11091
|
+
const branch = branches.find((b) => isSchema(b) && (b.type === "object" || isSchema(b.properties)));
|
|
11092
|
+
return branch ? dropNulls(value, branch) : value;
|
|
11093
|
+
}
|
|
11094
|
+
if (!isSchema(schema.properties))
|
|
11095
|
+
return value;
|
|
11096
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
11097
|
+
const out = {};
|
|
11098
|
+
for (const [name, v] of Object.entries(value)) {
|
|
11099
|
+
const prop = schema.properties[name];
|
|
11100
|
+
if (v === null && !required.has(name) && !allowsNull(prop))
|
|
11101
|
+
continue;
|
|
11102
|
+
out[name] = dropNulls(v, prop);
|
|
11103
|
+
}
|
|
11104
|
+
return out;
|
|
11105
|
+
}
|
|
11106
|
+
var SUBSCHEMA_KEYS = /* @__PURE__ */ new Set(["items", "anyOf", "oneOf", "allOf", "not", "additionalProperties", "definitions", "$defs"]);
|
|
11107
|
+
function nullable(schema) {
|
|
11108
|
+
if (!isSchema(schema) || allowsNull(schema))
|
|
11109
|
+
return schema;
|
|
11110
|
+
if (typeof schema.type === "string" && schema.anyOf === void 0 && schema.oneOf === void 0) {
|
|
11111
|
+
const out = { ...schema, type: [schema.type, "null"] };
|
|
11112
|
+
if (Array.isArray(schema.enum))
|
|
11113
|
+
out.enum = [...schema.enum, null];
|
|
11114
|
+
return out;
|
|
11115
|
+
}
|
|
11116
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
11117
|
+
}
|
|
11118
|
+
function allowsNull(schema) {
|
|
11119
|
+
if (!isSchema(schema))
|
|
11120
|
+
return false;
|
|
11121
|
+
if (schema.type === "null")
|
|
11122
|
+
return true;
|
|
11123
|
+
if (Array.isArray(schema.type) && schema.type.includes("null"))
|
|
11124
|
+
return true;
|
|
11125
|
+
const branches = [schema.anyOf, schema.oneOf].find(Array.isArray);
|
|
11126
|
+
return branches?.some(allowsNull) ?? false;
|
|
11127
|
+
}
|
|
11128
|
+
function isSchema(v) {
|
|
11129
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11130
|
+
}
|
|
11131
|
+
}
|
|
11132
|
+
});
|
|
11133
|
+
|
|
11134
|
+
// ../../packages/shuttle/dist/executors/codex.js
|
|
11135
|
+
var require_codex = __commonJS({
|
|
11136
|
+
"../../packages/shuttle/dist/executors/codex.js"(exports2) {
|
|
11137
|
+
"use strict";
|
|
11138
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
11139
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
11140
|
+
};
|
|
11141
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11142
|
+
exports2.resolveCodex = resolveCodex;
|
|
11143
|
+
exports2.codexReviewArgs = codexReviewArgs;
|
|
11144
|
+
exports2.parseCodexEvents = parseCodexEvents;
|
|
11145
|
+
exports2.createCodexExecutor = createCodexExecutor;
|
|
11146
|
+
var fs_1 = require("fs");
|
|
11147
|
+
var os_1 = __importDefault(require("os"));
|
|
11148
|
+
var path_1 = __importDefault(require("path"));
|
|
11149
|
+
var loom_1 = require_dist();
|
|
11150
|
+
var types_1 = require_types2();
|
|
11151
|
+
var validate_output_1 = require_validate_output();
|
|
11152
|
+
var progress_1 = require_progress();
|
|
11153
|
+
var agent_process_1 = require_agent_process();
|
|
11154
|
+
var agent_common_1 = require_agent_common();
|
|
11155
|
+
var json_answer_1 = require_json_answer();
|
|
11156
|
+
var strict_schema_1 = require_strict_schema();
|
|
11157
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
11158
|
+
var apply_1 = require_apply();
|
|
11159
|
+
var CODEX_PROFILE = "sift-review";
|
|
11160
|
+
function reviewProfile(installDir, platform = process.platform) {
|
|
11161
|
+
const rules = ['":root"="deny"', '":minimal"="read"', '":workspace_roots"="read"', `${JSON.stringify(installDir)}="read"`];
|
|
11162
|
+
if (platform === "linux") {
|
|
11163
|
+
rules.push(`${JSON.stringify(path_1.default.join(codexHome(), "tmp", "arg0"))}="read"`);
|
|
11164
|
+
} else {
|
|
11165
|
+
rules.push('":tmpdir"="deny"', '":slash_tmp"="deny"');
|
|
11166
|
+
}
|
|
11167
|
+
return `permissions.${CODEX_PROFILE}={extends=":read-only", filesystem={${rules.join(", ")}}}`;
|
|
10091
11168
|
}
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
11169
|
+
function codexHome() {
|
|
11170
|
+
return process.env.CODEX_HOME ?? path_1.default.join(os_1.default.homedir(), ".codex");
|
|
11171
|
+
}
|
|
11172
|
+
function resolveCodex(pathEnv = process.env.PATH ?? "") {
|
|
11173
|
+
for (const dir of pathEnv.split(path_1.default.delimiter)) {
|
|
11174
|
+
if (!dir)
|
|
11175
|
+
continue;
|
|
11176
|
+
const candidate = path_1.default.join(dir, "codex");
|
|
11177
|
+
let real;
|
|
11178
|
+
try {
|
|
11179
|
+
real = (0, fs_1.realpathSync)(candidate);
|
|
11180
|
+
if (!(0, fs_1.statSync)(real).isFile())
|
|
11181
|
+
continue;
|
|
11182
|
+
} catch {
|
|
11183
|
+
continue;
|
|
11184
|
+
}
|
|
11185
|
+
const installDir = real.endsWith(".js") ? path_1.default.dirname(path_1.default.dirname(real)) : path_1.default.dirname(real);
|
|
11186
|
+
return { bin: real.endsWith(".js") ? candidate : real, installDir };
|
|
10108
11187
|
}
|
|
10109
11188
|
return null;
|
|
10110
11189
|
}
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
11190
|
+
var DISABLED_FEATURES = [
|
|
11191
|
+
"hooks",
|
|
11192
|
+
"apps",
|
|
11193
|
+
"plugins",
|
|
11194
|
+
"remote_plugin",
|
|
11195
|
+
"plugin_sharing",
|
|
11196
|
+
"skill_mcp_dependency_install",
|
|
11197
|
+
"browser_use",
|
|
11198
|
+
"browser_use_external",
|
|
11199
|
+
"browser_use_full_cdp_access",
|
|
11200
|
+
"browser_annotation_api",
|
|
11201
|
+
"computer_use",
|
|
11202
|
+
"image_generation",
|
|
11203
|
+
"workspace_dependencies"
|
|
11204
|
+
];
|
|
11205
|
+
function requireCodex() {
|
|
11206
|
+
const codex = resolveCodex();
|
|
11207
|
+
if (!codex) {
|
|
11208
|
+
throw new types_1.ExecutorEnvironmentError("the codex CLI is not on this machine's PATH", CODEX_FAULTS.installRemedy);
|
|
10116
11209
|
}
|
|
10117
|
-
return
|
|
11210
|
+
return codex;
|
|
10118
11211
|
}
|
|
10119
|
-
function
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
11212
|
+
function codexReviewArgs(installDir, platform = process.platform) {
|
|
11213
|
+
return [
|
|
11214
|
+
"--json",
|
|
11215
|
+
"--skip-git-repo-check",
|
|
11216
|
+
"--ignore-user-config",
|
|
11217
|
+
"--ignore-rules",
|
|
11218
|
+
"-c",
|
|
11219
|
+
reviewProfile(installDir, platform),
|
|
11220
|
+
"-c",
|
|
11221
|
+
`default_permissions="${CODEX_PROFILE}"`,
|
|
11222
|
+
"-c",
|
|
11223
|
+
'approval_policy="never"',
|
|
11224
|
+
"-c",
|
|
11225
|
+
'web_search="disabled"',
|
|
11226
|
+
...DISABLED_FEATURES.flatMap((f) => ["--disable", f])
|
|
11227
|
+
];
|
|
10124
11228
|
}
|
|
10125
|
-
var
|
|
11229
|
+
var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
11230
|
+
var CODEX_FAULTS = {
|
|
11231
|
+
installRemedy: "install Codex (brew install codex, or npm install -g @openai/codex) where this process can find it on PATH",
|
|
11232
|
+
loginRemedy: "run `codex login` and sign in (or `codex login --with-api-key`)",
|
|
11233
|
+
limitRemedy: "wait for the plan's usage limit to reset, or raise it",
|
|
11234
|
+
signedOut: /not logged in|run `?codex login|401 unauthorized|invalid api key|refresh token|token (?:has )?expired|sign in again/i,
|
|
11235
|
+
outOfUsage: /usage limit|hit your (?:\w+ )?limit|insufficient_quota|exceeded your current quota|429 too many requests/i
|
|
11236
|
+
};
|
|
11237
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
11238
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
11239
|
+
var CodexExecutor = class {
|
|
10126
11240
|
capability;
|
|
10127
11241
|
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
10128
11242
|
config;
|
|
@@ -10137,468 +11251,232 @@ var require_claude_code = __commonJS({
|
|
|
10137
11251
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
10138
11252
|
};
|
|
10139
11253
|
}
|
|
10140
|
-
/**
|
|
10141
|
-
* Declare the run environment to the runner (ADR 0026): the opaque workspace
|
|
10142
|
-
* bag plus the materialization config (tmp dir, overlay allowlist). No
|
|
10143
|
-
* credentials here (ADR 0027) — the runner's broker reveals them on demand:
|
|
10144
|
-
* this executor pulls its model key from `ctx.credentials` in `execute()`,
|
|
10145
|
-
* and the git request references its own fetch credential. The executor
|
|
10146
|
-
* touches neither git nor Keep.
|
|
10147
|
-
*/
|
|
10148
11254
|
async prepareEnvironment(dispatch) {
|
|
10149
|
-
|
|
10150
|
-
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : void 0;
|
|
10151
|
-
return {
|
|
10152
|
-
...spec.workspace ? { workspaceRequest: spec.workspace } : {},
|
|
10153
|
-
materializeOptions: {
|
|
10154
|
-
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
10155
|
-
overlayAllowedHosts: this.config.overlayAllowedHosts ?? []
|
|
10156
|
-
}
|
|
10157
|
-
};
|
|
11255
|
+
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
10158
11256
|
}
|
|
10159
11257
|
async execute(dispatch, signal, ctx) {
|
|
11258
|
+
(0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
|
|
10160
11259
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
10161
11260
|
const workspace = ctx?.workspace ?? null;
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
const hints = (0, loom_1.pickProviderHints)(spec, "anthropic-api");
|
|
10165
|
-
const customerId = typeof hints.customerId === "string" ? hints.customerId : void 0;
|
|
10166
|
-
if (!customerId) {
|
|
10167
|
-
throw new Error("claude-code dynamic-key mode requires providerHints.anthropicApi.customerId on every job");
|
|
10168
|
-
}
|
|
10169
|
-
modelKey = await ctx.credentials.reveal({ name: "managed-model-key", principal: customerId });
|
|
10170
|
-
}
|
|
10171
|
-
let jobScratch = null;
|
|
10172
|
-
let configDir;
|
|
10173
|
-
if (modelKey) {
|
|
10174
|
-
jobScratch = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-cc-"));
|
|
10175
|
-
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
10176
|
-
await fs_1.promises.mkdir(configDir);
|
|
10177
|
-
}
|
|
10178
|
-
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
10179
|
-
let promptOnlyScratch = null;
|
|
11261
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "codex");
|
|
11262
|
+
const model = typeof hints.model === "string" && hints.model.length > 0 ? hints.model : this.config.model;
|
|
10180
11263
|
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
11264
|
+
const codex = requireCodex();
|
|
11265
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-"));
|
|
10181
11266
|
try {
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
}
|
|
10188
|
-
|
|
10189
|
-
|
|
11267
|
+
let runCwd = workspace?.cwd;
|
|
11268
|
+
if (!runCwd) {
|
|
11269
|
+
runCwd = path_1.default.join(scratch, "run");
|
|
11270
|
+
await fs_1.promises.mkdir(runCwd);
|
|
11271
|
+
}
|
|
11272
|
+
const { response, commands } = await this.runCodex({
|
|
11273
|
+
codex,
|
|
11274
|
+
args: ["exec", ...codexReviewArgs(codex.installDir), "-C", runCwd],
|
|
10190
11275
|
prompt,
|
|
10191
11276
|
spec,
|
|
10192
|
-
runCwd,
|
|
10193
|
-
|
|
11277
|
+
cwd: runCwd,
|
|
11278
|
+
scratch,
|
|
11279
|
+
model,
|
|
10194
11280
|
signal,
|
|
10195
|
-
hintedModel,
|
|
10196
11281
|
reporter,
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
// Local mode is the operator's own login: a failure of THAT is the
|
|
10200
|
-
// machine's, and the runner pauses on it. With a revealed key it is one
|
|
10201
|
-
// customer's, and only their job fails.
|
|
10202
|
-
modelKey === null
|
|
10203
|
-
);
|
|
11282
|
+
transcriptName: dispatch.job.id
|
|
11283
|
+
});
|
|
10204
11284
|
if (workspace) {
|
|
10205
|
-
|
|
10206
|
-
for (const rel of creditOverlayReads(toolInputs, workspace.overlay?.writtenPaths ?? [], runCwd)) {
|
|
10207
|
-
reads.push(path_1.default.join(runCwd, rel));
|
|
10208
|
-
}
|
|
10209
|
-
response.outputs.push({
|
|
10210
|
-
label: "workspace_reads",
|
|
10211
|
-
content: { paths: relativeWorkspaceReads(reads, runCwd) }
|
|
10212
|
-
});
|
|
10213
|
-
response.outputs.push({
|
|
10214
|
-
label: "workspace",
|
|
10215
|
-
content: {
|
|
10216
|
-
materialized: workspace.materialized,
|
|
10217
|
-
...workspace.warning ? { warning: workspace.warning } : {},
|
|
10218
|
-
...workspace.context.length > 0 ? {
|
|
10219
|
-
context: workspace.context.map((c) => ({
|
|
10220
|
-
name: c.requestedName,
|
|
10221
|
-
materialized: c.materialized,
|
|
10222
|
-
...c.warning ? { warning: c.warning } : {}
|
|
10223
|
-
}))
|
|
10224
|
-
} : {},
|
|
10225
|
-
...workspace.overlay ? {
|
|
10226
|
-
overlay: {
|
|
10227
|
-
written: workspace.overlay.written,
|
|
10228
|
-
...workspace.overlay.skipped.length > 0 ? { skipped: workspace.overlay.skipped } : {}
|
|
10229
|
-
}
|
|
10230
|
-
} : {}
|
|
10231
|
-
}
|
|
10232
|
-
});
|
|
11285
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, (0, workspace_telemetry_1.shellReadPaths)(commands, runCwd), commands.map((command) => JSON.stringify({ command }))));
|
|
10233
11286
|
}
|
|
10234
11287
|
return response;
|
|
10235
11288
|
} finally {
|
|
10236
11289
|
reporter?.close();
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
});
|
|
10240
|
-
if (jobScratch)
|
|
10241
|
-
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
10242
|
-
});
|
|
11290
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11291
|
+
});
|
|
10243
11292
|
}
|
|
10244
11293
|
}
|
|
10245
11294
|
/**
|
|
10246
|
-
* In-session gate repair (ADR 0017): resume the
|
|
10247
|
-
*
|
|
10248
|
-
*
|
|
10249
|
-
*
|
|
10250
|
-
* a second repair round can resume again.
|
|
11295
|
+
* In-session gate repair (ADR 0017): resume the thread and send the
|
|
11296
|
+
* follow-up under the same schema. The checkout is gone by now (the runner
|
|
11297
|
+
* cleans up before gates run), so the repair works from the thread's own
|
|
11298
|
+
* context in a scratch dir, as claude-code's does.
|
|
10251
11299
|
*/
|
|
10252
|
-
async continue(continuation, followUp, signal,
|
|
11300
|
+
async continue(continuation, followUp, signal, ctx) {
|
|
11301
|
+
(0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
|
|
10253
11302
|
const handle = continuation;
|
|
10254
|
-
if (!handle || typeof handle.
|
|
10255
|
-
throw new Error("
|
|
11303
|
+
if (!handle || typeof handle.threadId !== "string") {
|
|
11304
|
+
throw new Error("codex continue: missing thread to resume");
|
|
10256
11305
|
}
|
|
10257
|
-
const
|
|
10258
|
-
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-
|
|
11306
|
+
const codex = requireCodex();
|
|
11307
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-resume-"));
|
|
10259
11308
|
try {
|
|
10260
11309
|
const spec = {
|
|
10261
11310
|
rendering: "producer",
|
|
10262
11311
|
outputLabel: handle.outputLabel,
|
|
10263
11312
|
outputSchema: handle.outputSchema
|
|
10264
11313
|
};
|
|
10265
|
-
const { response } = await this.
|
|
11314
|
+
const { response } = await this.runCodex({
|
|
11315
|
+
codex,
|
|
11316
|
+
args: ["exec", "resume", handle.threadId, ...codexReviewArgs(codex.installDir)],
|
|
11317
|
+
prompt: followUp,
|
|
11318
|
+
spec,
|
|
11319
|
+
cwd: scratch,
|
|
11320
|
+
scratch,
|
|
11321
|
+
model: handle.model,
|
|
11322
|
+
signal,
|
|
11323
|
+
reporter: null,
|
|
11324
|
+
transcriptName: `${handle.threadId}-repair`
|
|
11325
|
+
});
|
|
10266
11326
|
return response;
|
|
10267
11327
|
} finally {
|
|
10268
11328
|
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
10269
11329
|
});
|
|
10270
11330
|
}
|
|
10271
11331
|
}
|
|
10272
|
-
/**
|
|
10273
|
-
*
|
|
10274
|
-
*
|
|
10275
|
-
*
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
}
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
});
|
|
10315
|
-
child.stderr?.on("data", (chunk) => {
|
|
10316
|
-
if (stderr.length < STDERR_LIMIT)
|
|
10317
|
-
stderr += chunk.toString("utf-8");
|
|
10318
|
-
});
|
|
10319
|
-
child.on("error", (err) => {
|
|
10320
|
-
const code = err.code;
|
|
10321
|
-
settle({
|
|
10322
|
-
ready: false,
|
|
10323
|
-
reason: code === "ENOENT" ? `the claude CLI is not on this machine's PATH (${err.message})` : err.message
|
|
10324
|
-
});
|
|
10325
|
-
});
|
|
10326
|
-
child.on("close", (code) => {
|
|
10327
|
-
let meta = null;
|
|
10328
|
-
try {
|
|
10329
|
-
meta = parseStreamJson(stdout).resultMeta;
|
|
10330
|
-
} catch {
|
|
10331
|
-
meta = null;
|
|
10332
|
-
}
|
|
10333
|
-
if (code === 0 && !meta?.isError) {
|
|
10334
|
-
settle({ ready: true });
|
|
10335
|
-
return;
|
|
10336
|
-
}
|
|
10337
|
-
const said = meta?.detail ?? firstLine(stderr) ?? firstLine(stdout);
|
|
10338
|
-
settle({ ready: false, reason: said ?? `the claude CLI exited with code ${code}` });
|
|
10339
|
-
});
|
|
10340
|
-
});
|
|
10341
|
-
}
|
|
10342
|
-
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName, local = false) {
|
|
10343
|
-
const outputLabel = spec.outputLabel;
|
|
10344
|
-
const model = modelOverride ?? this.config.model;
|
|
10345
|
-
const allowedTools = local ? this.config.allowedTools : checkoutScopedTools(this.config.allowedTools);
|
|
10346
|
-
if (!local && this.config.claudeSettings === "user") {
|
|
10347
|
-
throw new Error("claude-code: `claudeSettings: user` is for an installed Sifter whose login lives in Claude Code settings; a hosted run authenticates with the key it is handed and loads no settings");
|
|
10348
|
-
}
|
|
10349
|
-
const settingSources = local && this.config.claudeSettings === "user" ? "user" : "";
|
|
10350
|
-
return new Promise((resolve, reject) => {
|
|
10351
|
-
const args = ["--print", "--output-format", "stream-json", "--verbose", ...reviewRunArgs(settingSources)];
|
|
10352
|
-
if (resumeSessionId)
|
|
10353
|
-
args.push("--resume", resumeSessionId);
|
|
10354
|
-
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
10355
|
-
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
10356
|
-
}
|
|
10357
|
-
if (model)
|
|
10358
|
-
args.push("--model", model);
|
|
10359
|
-
if (this.config.maxTurns !== void 0)
|
|
10360
|
-
args.push("--max-turns", String(this.config.maxTurns));
|
|
10361
|
-
if (allowedTools && allowedTools.length > 0) {
|
|
10362
|
-
args.push("--allowedTools", allowedTools.join(","));
|
|
10363
|
-
}
|
|
10364
|
-
let child;
|
|
10365
|
-
try {
|
|
10366
|
-
child = (0, child_process_1.spawn)("claude", args, {
|
|
10367
|
-
cwd,
|
|
10368
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
10369
|
-
// Undefined in local mode → inherit the ambient env (and its
|
|
10370
|
-
// `~/.claude` login) unchanged.
|
|
10371
|
-
...childEnv ? { env: childEnv } : {}
|
|
10372
|
-
});
|
|
10373
|
-
} catch (err) {
|
|
10374
|
-
reject(err);
|
|
10375
|
-
return;
|
|
10376
|
-
}
|
|
10377
|
-
const stdoutChunks = [];
|
|
10378
|
-
let stderrText = "";
|
|
10379
|
-
let settled = false;
|
|
10380
|
-
const settle = (fn) => {
|
|
10381
|
-
if (settled)
|
|
10382
|
-
return;
|
|
10383
|
-
settled = true;
|
|
10384
|
-
fn();
|
|
10385
|
-
};
|
|
10386
|
-
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
10387
|
-
onReasoning: (t) => reporter.reasoning(t),
|
|
10388
|
-
onTool: (name, target) => reporter.tool(name, target),
|
|
10389
|
-
onResult: () => reporter.flush()
|
|
10390
|
-
}) : null;
|
|
10391
|
-
const decoder = tap ? new string_decoder_1.StringDecoder("utf8") : null;
|
|
10392
|
-
child.stdin.write(prompt);
|
|
10393
|
-
child.stdin.end();
|
|
10394
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
10395
|
-
let timer;
|
|
10396
|
-
const armIdleTimer = () => {
|
|
10397
|
-
if (settled)
|
|
10398
|
-
return;
|
|
10399
|
-
if (timer)
|
|
10400
|
-
clearTimeout(timer);
|
|
10401
|
-
timer = setTimeout(() => {
|
|
10402
|
-
child.kill("SIGTERM");
|
|
10403
|
-
settle(() => reject(new Error(`Claude Code produced no output for ${timeout}ms; killed as hung`)));
|
|
10404
|
-
}, timeout);
|
|
10405
|
-
};
|
|
10406
|
-
armIdleTimer();
|
|
10407
|
-
child.stdout.on("data", (chunk) => {
|
|
10408
|
-
stdoutChunks.push(chunk);
|
|
10409
|
-
armIdleTimer();
|
|
10410
|
-
if (tap && decoder)
|
|
10411
|
-
tap.push(decoder.write(chunk));
|
|
10412
|
-
});
|
|
10413
|
-
child.stderr?.on("data", (chunk) => {
|
|
10414
|
-
if (stderrText.length < STDERR_LIMIT)
|
|
10415
|
-
stderrText += chunk.toString("utf-8");
|
|
10416
|
-
});
|
|
10417
|
-
function onAbort() {
|
|
10418
|
-
child.kill("SIGTERM");
|
|
10419
|
-
settle(() => reject(new Error("Dispatch aborted")));
|
|
10420
|
-
}
|
|
10421
|
-
if (signal.aborted) {
|
|
10422
|
-
clearTimeout(timer);
|
|
10423
|
-
child.kill("SIGTERM");
|
|
10424
|
-
settle(() => reject(new Error("Dispatch aborted")));
|
|
10425
|
-
return;
|
|
10426
|
-
}
|
|
10427
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
10428
|
-
child.on("error", (err) => {
|
|
10429
|
-
clearTimeout(timer);
|
|
10430
|
-
signal.removeEventListener("abort", onAbort);
|
|
10431
|
-
const code = err.code;
|
|
10432
|
-
if (local && code === "ENOENT") {
|
|
10433
|
-
settle(() => reject(new types_1.ExecutorEnvironmentError(`the claude CLI is not on this machine's PATH (${err.message})`, INSTALL_REMEDY)));
|
|
10434
|
-
return;
|
|
10435
|
-
}
|
|
10436
|
-
settle(() => reject(err));
|
|
10437
|
-
});
|
|
10438
|
-
child.on("close", (code) => {
|
|
10439
|
-
clearTimeout(timer);
|
|
10440
|
-
signal.removeEventListener("abort", onAbort);
|
|
10441
|
-
if (tap && decoder) {
|
|
10442
|
-
tap.push(decoder.end());
|
|
10443
|
-
tap.end();
|
|
10444
|
-
}
|
|
10445
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
10446
|
-
if (this.config.keepTranscripts && transcriptName) {
|
|
10447
|
-
void keepTranscript((0, apply_1.expandHome)(this.config.keepTranscripts), transcriptName, stdout);
|
|
10448
|
-
}
|
|
10449
|
-
if (code === 0) {
|
|
10450
|
-
try {
|
|
10451
|
-
const stream = parseStreamJson(stdout);
|
|
10452
|
-
if (stream.resultMeta?.isError) {
|
|
10453
|
-
throw runFailure(local, describeClaudeFailure(stream.resultMeta, "Claude Code reported a failure"), {
|
|
10454
|
-
apiErrorStatus: stream.resultMeta.apiErrorStatus,
|
|
10455
|
-
text: stream.resultMeta.detail ?? firstLine(stderrText)
|
|
10456
|
-
});
|
|
10457
|
-
}
|
|
10458
|
-
const answerText = stream.finalText ?? stdout;
|
|
10459
|
-
const parsed = stream.structuredOutput ?? extractJsonObject(answerText);
|
|
10460
|
-
if (parsed === void 0 && stream.resultMeta) {
|
|
10461
|
-
throw new Error(`claude's final message contained no JSON object (result subtype=${stream.resultMeta.subtype ?? "unknown"}; message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
10462
|
-
}
|
|
10463
|
-
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
10464
|
-
settle(() => resolve({
|
|
10465
|
-
response: {
|
|
10466
|
-
outputs: [{ label: outputLabel, content: parsed ?? { raw: answerText } }],
|
|
10467
|
-
rawText: answerText,
|
|
10468
|
-
parsed,
|
|
10469
|
-
usage: {
|
|
10470
|
-
aiProvider: "claude-code",
|
|
10471
|
-
...model ? { aiModel: model } : {},
|
|
10472
|
-
...stream.usage ?? {}
|
|
10473
|
-
},
|
|
10474
|
-
// In-session repair handle (ADR 0017): present only when the run
|
|
10475
|
-
// reported a session id, so the runner can `--resume` it.
|
|
10476
|
-
...stream.sessionId ? {
|
|
10477
|
-
continuation: {
|
|
10478
|
-
sessionId: stream.sessionId,
|
|
10479
|
-
model,
|
|
10480
|
-
childEnv,
|
|
10481
|
-
outputLabel,
|
|
10482
|
-
outputSchema: spec.outputSchema
|
|
10483
|
-
}
|
|
10484
|
-
} : {}
|
|
10485
|
-
},
|
|
10486
|
-
readPaths: stream.readPaths,
|
|
10487
|
-
toolInputs: stream.toolInputs
|
|
10488
|
-
}));
|
|
10489
|
-
} catch (err) {
|
|
10490
|
-
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
10491
|
-
}
|
|
10492
|
-
} else {
|
|
10493
|
-
let meta = null;
|
|
10494
|
-
try {
|
|
10495
|
-
meta = parseStreamJson(stdout).resultMeta;
|
|
10496
|
-
} catch {
|
|
10497
|
-
}
|
|
10498
|
-
const said = meta?.detail ?? firstLine(stderrText) ?? firstLine(stdout);
|
|
10499
|
-
const fallback = `Claude Code exited with code ${code}`;
|
|
10500
|
-
const failure = runFailure(local, describeClaudeFailure(meta, fallback), {
|
|
10501
|
-
apiErrorStatus: meta?.apiErrorStatus ?? null,
|
|
10502
|
-
text: said
|
|
10503
|
-
});
|
|
10504
|
-
if (failure instanceof types_1.ExecutorEnvironmentError && !meta && said) {
|
|
10505
|
-
settle(() => reject(new types_1.ExecutorEnvironmentError(`${fallback}: ${said}`, failure.remedy)));
|
|
10506
|
-
return;
|
|
10507
|
-
}
|
|
10508
|
-
settle(() => reject(failure));
|
|
10509
|
-
}
|
|
10510
|
-
});
|
|
10511
|
-
});
|
|
10512
|
-
}
|
|
10513
|
-
};
|
|
10514
|
-
function createClaudeCodeExecutor(config, capabilityId = "claude-code") {
|
|
10515
|
-
const validated = { capabilityId };
|
|
10516
|
-
if (config.tmpDir !== void 0) {
|
|
10517
|
-
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
10518
|
-
throw new Error('"tmpDir" must be a non-empty string');
|
|
10519
|
-
}
|
|
10520
|
-
validated.tmpDir = config.tmpDir;
|
|
10521
|
-
}
|
|
10522
|
-
if (config.model !== void 0) {
|
|
10523
|
-
if (typeof config.model !== "string")
|
|
10524
|
-
throw new Error('"model" must be a string');
|
|
10525
|
-
validated.model = config.model;
|
|
10526
|
-
}
|
|
10527
|
-
if (config.maxTurns !== void 0) {
|
|
10528
|
-
if (typeof config.maxTurns !== "number" || !Number.isInteger(config.maxTurns) || config.maxTurns < 1) {
|
|
10529
|
-
throw new Error('"maxTurns" must be a positive integer');
|
|
10530
|
-
}
|
|
10531
|
-
validated.maxTurns = config.maxTurns;
|
|
10532
|
-
}
|
|
10533
|
-
if (config.allowedTools !== void 0) {
|
|
10534
|
-
if (!Array.isArray(config.allowedTools) || !config.allowedTools.every((t) => typeof t === "string")) {
|
|
10535
|
-
throw new Error('"allowedTools" must be an array of strings');
|
|
11332
|
+
/**
|
|
11333
|
+
* One real turn under the review flags, in a throwaway dir. A signed-out
|
|
11334
|
+
* CLI fails before any model call, so the re-check spends nothing until the
|
|
11335
|
+
* login works again; a spent plan is only visible to a real call.
|
|
11336
|
+
*/
|
|
11337
|
+
async checkReadiness(signal) {
|
|
11338
|
+
const codex = resolveCodex();
|
|
11339
|
+
if (!codex)
|
|
11340
|
+
return { ready: false, reason: "the codex CLI is not on this machine's PATH" };
|
|
11341
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-ready-"));
|
|
11342
|
+
try {
|
|
11343
|
+
let run;
|
|
11344
|
+
try {
|
|
11345
|
+
run = await (0, agent_process_1.runAgentProcess)({
|
|
11346
|
+
bin: codex.bin,
|
|
11347
|
+
args: ["exec", ...codexReviewArgs(codex.installDir), "--ephemeral", "-C", scratch, "-"],
|
|
11348
|
+
cwd: scratch,
|
|
11349
|
+
stdin: READINESS_PROMPT,
|
|
11350
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
11351
|
+
toolName: "the codex CLI",
|
|
11352
|
+
signal
|
|
11353
|
+
});
|
|
11354
|
+
} catch (err) {
|
|
11355
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11356
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
11357
|
+
}
|
|
11358
|
+
if (run.spawnError) {
|
|
11359
|
+
return {
|
|
11360
|
+
ready: false,
|
|
11361
|
+
reason: run.spawnError.code === "ENOENT" ? `the codex CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
11362
|
+
};
|
|
11363
|
+
}
|
|
11364
|
+
const events = parseCodexEvents(run.stdout);
|
|
11365
|
+
if (run.code === 0 && !events.failure)
|
|
11366
|
+
return { ready: true };
|
|
11367
|
+
return {
|
|
11368
|
+
ready: false,
|
|
11369
|
+
reason: events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? `the codex CLI exited with code ${run.code}`
|
|
11370
|
+
};
|
|
11371
|
+
} finally {
|
|
11372
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11373
|
+
});
|
|
10536
11374
|
}
|
|
10537
|
-
validated.allowedTools = config.allowedTools;
|
|
10538
11375
|
}
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
11376
|
+
tmpBase() {
|
|
11377
|
+
return this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
11378
|
+
}
|
|
11379
|
+
async runCodex(opts) {
|
|
11380
|
+
const { spec, model } = opts;
|
|
11381
|
+
const args = [...opts.args];
|
|
11382
|
+
const hasSchema = spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0;
|
|
11383
|
+
if (hasSchema) {
|
|
11384
|
+
const schemaFile = path_1.default.join(opts.scratch, "output-schema.json");
|
|
11385
|
+
await fs_1.promises.writeFile(schemaFile, JSON.stringify((0, strict_schema_1.toStrict)(spec.outputSchema)));
|
|
11386
|
+
args.push("--output-schema", schemaFile);
|
|
11387
|
+
}
|
|
11388
|
+
if (model)
|
|
11389
|
+
args.push("-m", model);
|
|
11390
|
+
if (this.config.reasoningEffort)
|
|
11391
|
+
args.push("-c", `model_reasoning_effort="${this.config.reasoningEffort}"`);
|
|
11392
|
+
args.push("-");
|
|
11393
|
+
const tap = opts.reporter ? codexProgressTap(opts.reporter) : null;
|
|
11394
|
+
const run = await (0, agent_process_1.runAgentProcess)({
|
|
11395
|
+
bin: opts.codex.bin,
|
|
11396
|
+
args,
|
|
11397
|
+
cwd: opts.cwd,
|
|
11398
|
+
stdin: opts.prompt,
|
|
11399
|
+
idleTimeoutMs: this.config.timeout ?? agent_common_1.DEFAULT_AGENT_TIMEOUT,
|
|
11400
|
+
toolName: "Codex",
|
|
11401
|
+
signal: opts.signal,
|
|
11402
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
11403
|
+
});
|
|
11404
|
+
tap?.end();
|
|
11405
|
+
if (run.spawnError) {
|
|
11406
|
+
if (run.spawnError.code === "ENOENT") {
|
|
11407
|
+
throw new types_1.ExecutorEnvironmentError(`the codex CLI is not on this machine's PATH (${run.spawnError.message})`, CODEX_FAULTS.installRemedy);
|
|
11408
|
+
}
|
|
11409
|
+
throw new Error(run.spawnError.message);
|
|
10542
11410
|
}
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
if (config.keepTranscripts !== void 0) {
|
|
10546
|
-
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
10547
|
-
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
11411
|
+
if (this.config.keepTranscripts) {
|
|
11412
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), opts.transcriptName, run.stdout);
|
|
10548
11413
|
}
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
throw
|
|
11414
|
+
const events = parseCodexEvents(run.stdout);
|
|
11415
|
+
if (run.code !== 0 || events.failure) {
|
|
11416
|
+
const said = events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
11417
|
+
const message = said ? `Codex ${run.code === 0 ? "reported a failure" : `exited with code ${run.code}`}: ${said}` : `Codex exited with code ${run.code}`;
|
|
11418
|
+
throw (0, agent_process_1.agentFailure)(true, message, { apiErrorStatus: null, text: said }, CODEX_FAULTS);
|
|
10554
11419
|
}
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
if (config.overlayAllowedHosts !== void 0) {
|
|
10558
|
-
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
10559
|
-
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
11420
|
+
if (events.finalText === null) {
|
|
11421
|
+
throw new Error("Codex finished without a final message");
|
|
10560
11422
|
}
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
function checkoutScopedTools(tools) {
|
|
10566
|
-
if (!tools)
|
|
10567
|
-
return void 0;
|
|
10568
|
-
const scoped = [];
|
|
10569
|
-
for (const tool of tools) {
|
|
10570
|
-
if (RETIRED_READ_TOOLS.some((t) => tool === t || tool === `${t}(./**)`))
|
|
10571
|
-
continue;
|
|
10572
|
-
const name = REVIEW_TOOLS.find((t) => tool === t || tool === `${t}(./**)`);
|
|
10573
|
-
if (!name) {
|
|
10574
|
-
throw new Error(`claude-code: a hosted run may use only ${REVIEW_TOOLS.join(", ")} inside the checkout; "${tool}" is not allowed`);
|
|
11423
|
+
const answerText = events.finalText;
|
|
11424
|
+
const raw = (0, json_answer_1.extractJsonObject)(answerText);
|
|
11425
|
+
if (raw === void 0 && hasSchema) {
|
|
11426
|
+
throw new Error(`codex's final message contained no JSON object (message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
10575
11427
|
}
|
|
10576
|
-
|
|
11428
|
+
const parsed = raw === void 0 ? void 0 : (0, strict_schema_1.dropNulls)(raw, spec.outputSchema);
|
|
11429
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
11430
|
+
return {
|
|
11431
|
+
response: {
|
|
11432
|
+
outputs: [{ label: spec.outputLabel, content: parsed ?? { raw: answerText } }],
|
|
11433
|
+
rawText: answerText,
|
|
11434
|
+
parsed,
|
|
11435
|
+
usage: {
|
|
11436
|
+
aiProvider: "codex",
|
|
11437
|
+
...model ? { aiModel: model } : {},
|
|
11438
|
+
...events.usage ?? {}
|
|
11439
|
+
},
|
|
11440
|
+
...events.threadId ? {
|
|
11441
|
+
continuation: {
|
|
11442
|
+
threadId: events.threadId,
|
|
11443
|
+
model,
|
|
11444
|
+
outputLabel: spec.outputLabel,
|
|
11445
|
+
outputSchema: spec.outputSchema
|
|
11446
|
+
}
|
|
11447
|
+
} : {}
|
|
11448
|
+
},
|
|
11449
|
+
commands: events.commands
|
|
11450
|
+
};
|
|
10577
11451
|
}
|
|
10578
|
-
|
|
11452
|
+
};
|
|
11453
|
+
function codexProgressTap(reporter) {
|
|
11454
|
+
return new progress_1.JsonLineTap((event) => {
|
|
11455
|
+
const item = event.item;
|
|
11456
|
+
const kind = item ? itemKind(item) : null;
|
|
11457
|
+
if (event.type === "item.started" && kind === "command_execution" && typeof item?.command === "string") {
|
|
11458
|
+
reporter.tool("shell", item.command);
|
|
11459
|
+
} else if (event.type === "item.completed" && kind === "reasoning" && typeof item?.text === "string") {
|
|
11460
|
+
reporter.reasoning(item.text);
|
|
11461
|
+
} else if (event.type === "turn.completed") {
|
|
11462
|
+
reporter.flush();
|
|
11463
|
+
}
|
|
11464
|
+
});
|
|
10579
11465
|
}
|
|
10580
|
-
function
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
const env = {};
|
|
10584
|
-
for (const name of HOSTED_ENV_PASSTHROUGH) {
|
|
10585
|
-
if (process.env[name] !== void 0)
|
|
10586
|
-
env[name] = process.env[name];
|
|
10587
|
-
}
|
|
10588
|
-
return {
|
|
10589
|
-
...env,
|
|
10590
|
-
...modelKey ? { ANTHROPIC_API_KEY: modelKey } : {},
|
|
10591
|
-
...configDir ? { CLAUDE_CONFIG_DIR: configDir } : {}
|
|
10592
|
-
};
|
|
11466
|
+
function itemKind(item) {
|
|
11467
|
+
const kind = typeof item.type === "string" ? item.type : typeof item.item_type === "string" ? item.item_type : null;
|
|
11468
|
+
return kind === "assistant_message" ? "agent_message" : kind;
|
|
10593
11469
|
}
|
|
10594
|
-
function
|
|
11470
|
+
function parseCodexEvents(stdout) {
|
|
11471
|
+
let threadId = null;
|
|
10595
11472
|
let finalText = null;
|
|
10596
|
-
const
|
|
10597
|
-
|
|
10598
|
-
let
|
|
10599
|
-
let
|
|
10600
|
-
let
|
|
10601
|
-
|
|
11473
|
+
const commands = [];
|
|
11474
|
+
const seenCommands = /* @__PURE__ */ new Set();
|
|
11475
|
+
let input = 0;
|
|
11476
|
+
let output = 0;
|
|
11477
|
+
let sawUsage = false;
|
|
11478
|
+
let turnFailure = null;
|
|
11479
|
+
let lastError = null;
|
|
10602
11480
|
for (const line of stdout.split("\n")) {
|
|
10603
11481
|
const trimmed = line.trim();
|
|
10604
11482
|
if (!trimmed.startsWith("{"))
|
|
@@ -10609,257 +11487,523 @@ var require_claude_code = __commonJS({
|
|
|
10609
11487
|
} catch {
|
|
10610
11488
|
continue;
|
|
10611
11489
|
}
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
11490
|
+
switch (event.type) {
|
|
11491
|
+
case "thread.started":
|
|
11492
|
+
if (typeof event.thread_id === "string")
|
|
11493
|
+
threadId = event.thread_id;
|
|
11494
|
+
break;
|
|
11495
|
+
case "item.started":
|
|
11496
|
+
case "item.updated":
|
|
11497
|
+
case "item.completed": {
|
|
11498
|
+
const item = event.item;
|
|
11499
|
+
if (!item)
|
|
11500
|
+
break;
|
|
11501
|
+
const kind = itemKind(item);
|
|
11502
|
+
if (kind === "agent_message" && event.type === "item.completed" && typeof item.text === "string") {
|
|
11503
|
+
finalText = item.text;
|
|
11504
|
+
lastError = null;
|
|
11505
|
+
} else if (kind === "command_execution" && typeof item.command === "string") {
|
|
11506
|
+
const key = typeof item.id === "string" ? item.id : item.command;
|
|
11507
|
+
if (!seenCommands.has(key)) {
|
|
11508
|
+
seenCommands.add(key);
|
|
11509
|
+
commands.push(item.command);
|
|
11510
|
+
}
|
|
10625
11511
|
}
|
|
10626
|
-
|
|
10627
|
-
toolInputs.push(JSON.stringify(b.input));
|
|
11512
|
+
break;
|
|
10628
11513
|
}
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
11514
|
+
case "turn.completed": {
|
|
11515
|
+
const u = event.usage;
|
|
11516
|
+
if (typeof u?.input_tokens === "number") {
|
|
11517
|
+
input += u.input_tokens;
|
|
11518
|
+
sawUsage = true;
|
|
11519
|
+
}
|
|
11520
|
+
if (typeof u?.output_tokens === "number") {
|
|
11521
|
+
output += u.output_tokens;
|
|
11522
|
+
sawUsage = true;
|
|
11523
|
+
}
|
|
11524
|
+
break;
|
|
10632
11525
|
}
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
// model was unavailable, spend limit hit, and so on. It is the only
|
|
10638
|
-
// place that reason exists, so keep it for the failure message.
|
|
10639
|
-
detail: typeof event.result === "string" && event.result.trim().length > 0 ? event.result.trim() : null,
|
|
10640
|
-
apiErrorStatus: typeof event.api_error_status === "number" ? event.api_error_status : null
|
|
10641
|
-
};
|
|
10642
|
-
const so = event.structured_output;
|
|
10643
|
-
if (so && typeof so === "object" && !Array.isArray(so)) {
|
|
10644
|
-
structuredOutput = so;
|
|
11526
|
+
case "turn.failed": {
|
|
11527
|
+
const err = event.error;
|
|
11528
|
+
turnFailure = typeof err?.message === "string" ? err.message : "the turn failed";
|
|
11529
|
+
break;
|
|
10645
11530
|
}
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
};
|
|
11531
|
+
case "error":
|
|
11532
|
+
if (typeof event.message === "string")
|
|
11533
|
+
lastError = event.message;
|
|
11534
|
+
break;
|
|
11535
|
+
default:
|
|
11536
|
+
break;
|
|
10653
11537
|
}
|
|
10654
11538
|
}
|
|
10655
|
-
return {
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
const real = (0, fs_1.realpathSync)(root);
|
|
10663
|
-
if (real !== root)
|
|
10664
|
-
roots.push(real);
|
|
10665
|
-
} catch {
|
|
10666
|
-
}
|
|
10667
|
-
return roots;
|
|
11539
|
+
return {
|
|
11540
|
+
threadId,
|
|
11541
|
+
finalText,
|
|
11542
|
+
commands,
|
|
11543
|
+
usage: sawUsage ? { inputTokens: input, outputTokens: output } : null,
|
|
11544
|
+
failure: turnFailure ?? (finalText === null ? lastError : null)
|
|
11545
|
+
};
|
|
10668
11546
|
}
|
|
10669
|
-
function
|
|
10670
|
-
const
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
const resolved = path_1.default.resolve(root, p);
|
|
10675
|
-
for (const r of roots) {
|
|
10676
|
-
if (resolved === r)
|
|
10677
|
-
break;
|
|
10678
|
-
if (!resolved.startsWith(r + path_1.default.sep))
|
|
10679
|
-
continue;
|
|
10680
|
-
out.add(resolved.slice(r.length + 1));
|
|
10681
|
-
break;
|
|
11547
|
+
function createCodexExecutor(config, capabilityId = "codex") {
|
|
11548
|
+
const validated = (0, agent_common_1.parseAgentCommonConfig)(config, capabilityId);
|
|
11549
|
+
if (config.reasoningEffort !== void 0) {
|
|
11550
|
+
if (!REASONING_EFFORTS.includes(config.reasoningEffort)) {
|
|
11551
|
+
throw new Error(`"reasoningEffort" must be one of ${REASONING_EFFORTS.join(", ")}`);
|
|
10682
11552
|
}
|
|
10683
|
-
|
|
10684
|
-
break;
|
|
11553
|
+
validated.reasoningEffort = config.reasoningEffort;
|
|
10685
11554
|
}
|
|
10686
|
-
return
|
|
11555
|
+
return new CodexExecutor(validated);
|
|
10687
11556
|
}
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
11557
|
+
}
|
|
11558
|
+
});
|
|
11559
|
+
|
|
11560
|
+
// ../../packages/shuttle/dist/executors/copilot.js
|
|
11561
|
+
var require_copilot = __commonJS({
|
|
11562
|
+
"../../packages/shuttle/dist/executors/copilot.js"(exports2) {
|
|
11563
|
+
"use strict";
|
|
11564
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
11565
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
11566
|
+
};
|
|
11567
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11568
|
+
exports2.copilotReviewArgs = copilotReviewArgs;
|
|
11569
|
+
exports2.withSchema = withSchema;
|
|
11570
|
+
exports2.usageOf = usageOf;
|
|
11571
|
+
exports2.parseCopilotEvents = parseCopilotEvents;
|
|
11572
|
+
exports2.createCopilotExecutor = createCopilotExecutor;
|
|
11573
|
+
var crypto_1 = require("crypto");
|
|
11574
|
+
var fs_1 = require("fs");
|
|
11575
|
+
var os_1 = __importDefault(require("os"));
|
|
11576
|
+
var path_1 = __importDefault(require("path"));
|
|
11577
|
+
var loom_1 = require_dist();
|
|
11578
|
+
var types_1 = require_types2();
|
|
11579
|
+
var validate_output_1 = require_validate_output();
|
|
11580
|
+
var progress_1 = require_progress();
|
|
11581
|
+
var agent_process_1 = require_agent_process();
|
|
11582
|
+
var agent_common_1 = require_agent_common();
|
|
11583
|
+
var json_answer_1 = require_json_answer();
|
|
11584
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
11585
|
+
var apply_1 = require_apply();
|
|
11586
|
+
var REVIEW_TOOLS = ["view", "grep", "glob"];
|
|
11587
|
+
var DENIED_TOOLS = ["shell", "write"];
|
|
11588
|
+
function copilotReviewArgs() {
|
|
11589
|
+
return [
|
|
11590
|
+
"--output-format",
|
|
11591
|
+
"json",
|
|
11592
|
+
"--no-color",
|
|
11593
|
+
"--allow-all-tools",
|
|
11594
|
+
`--available-tools=${REVIEW_TOOLS.join(",")}`,
|
|
11595
|
+
...DENIED_TOOLS.map((t) => `--deny-tool=${t}`),
|
|
11596
|
+
"--disable-builtin-mcps",
|
|
11597
|
+
"--disallow-temp-dir",
|
|
11598
|
+
"--no-ask-user",
|
|
11599
|
+
"--no-auto-update",
|
|
11600
|
+
"--no-remote"
|
|
11601
|
+
];
|
|
10691
11602
|
}
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
i += 1;
|
|
10704
|
-
}
|
|
10705
|
-
} else {
|
|
10706
|
-
re += "[^/]*";
|
|
10707
|
-
}
|
|
10708
|
-
} else if (ch === "?") {
|
|
10709
|
-
re += "[^/]";
|
|
10710
|
-
} else if (ch === "[") {
|
|
10711
|
-
const end = glob.indexOf("]", i + 1);
|
|
10712
|
-
if (end === -1)
|
|
10713
|
-
return null;
|
|
10714
|
-
let cls = glob.slice(i + 1, end);
|
|
10715
|
-
if (cls.startsWith("!"))
|
|
10716
|
-
cls = `^${cls.slice(1)}`;
|
|
10717
|
-
re += `[${cls.replace(/\\/g, "\\\\")}]`;
|
|
10718
|
-
i = end;
|
|
10719
|
-
} else if (ch === "{") {
|
|
10720
|
-
const end = glob.indexOf("}", i + 1);
|
|
10721
|
-
if (end === -1)
|
|
10722
|
-
return null;
|
|
10723
|
-
re += `(?:${glob.slice(i + 1, end).split(",").map(escapeRegExp).join("|")})`;
|
|
10724
|
-
i = end;
|
|
10725
|
-
} else {
|
|
10726
|
-
re += escapeRegExp(ch);
|
|
10727
|
-
}
|
|
11603
|
+
var RUN_SETTINGS = { disableAllHooks: true, autoUpdate: false };
|
|
11604
|
+
var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
11605
|
+
var COPILOT_FAULTS = {
|
|
11606
|
+
installRemedy: "install GitHub Copilot CLI (brew install --cask copilot-cli, or npm install -g @github/copilot) where this process can find it on PATH",
|
|
11607
|
+
loginRemedy: "run `copilot login` and sign in with a GitHub account that has a Copilot seat (a COPILOT_GITHUB_TOKEN, GH_TOKEN or GITHUB_TOKEN in the environment wins over that login)",
|
|
11608
|
+
limitRemedy: "the account has no Copilot allowance left: on an organization seat, an owner must allow paid usage in the organization's Copilot policies and fund a budget for it; otherwise wait for the monthly reset",
|
|
11609
|
+
signedOut: /not (?:logged|signed) in|run `?copilot login|no authentication|authentication (?:failed|required)|bad credentials/i,
|
|
11610
|
+
outOfUsage: /exceeded your monthly quota|premium request|usage limit|hit your (?:\w+ )?limit|exceeded .*(?:allowance|limit)/i,
|
|
11611
|
+
notEntitled: {
|
|
11612
|
+
pattern: /access denied by policy|not authorized to use this copilot feature/i,
|
|
11613
|
+
remedy: "this account's Copilot does not allow the Copilot CLI: on an organization seat, an owner must assign the seat and enable the Copilot CLI policy, then run `copilot login` again so the new seat is picked up"
|
|
10728
11614
|
}
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
dirs.add(`${parts.slice(0, i).join("/")}/`);
|
|
11615
|
+
};
|
|
11616
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
11617
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
11618
|
+
var CopilotExecutor = class {
|
|
11619
|
+
capability;
|
|
11620
|
+
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
11621
|
+
config;
|
|
11622
|
+
constructor(config) {
|
|
11623
|
+
this.config = config;
|
|
11624
|
+
this.capability = { id: config.capabilityId };
|
|
10740
11625
|
}
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
11626
|
+
estimate(dispatch) {
|
|
11627
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
11628
|
+
return {
|
|
11629
|
+
inputTokens: spec.costCapHint?.estimatedInputTokens,
|
|
11630
|
+
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
11631
|
+
};
|
|
11632
|
+
}
|
|
11633
|
+
async prepareEnvironment(dispatch) {
|
|
11634
|
+
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
11635
|
+
}
|
|
11636
|
+
async execute(dispatch, signal, ctx) {
|
|
11637
|
+
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
11638
|
+
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
11639
|
+
const workspace = ctx?.workspace ?? null;
|
|
11640
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "copilot");
|
|
11641
|
+
const model = typeof hints.model === "string" && hints.model.length > 0 ? hints.model : this.config.model;
|
|
11642
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
11643
|
+
const runs = path_1.default.join(this.tmpBase(), "shuttle-copilot-runs");
|
|
11644
|
+
await fs_1.promises.mkdir(runs, { recursive: true, mode: 448 });
|
|
11645
|
+
await pruneRuns(runs);
|
|
11646
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(runs, "run-"));
|
|
11647
|
+
let keep = false;
|
|
11648
|
+
try {
|
|
11649
|
+
const home = await makeRunHome(scratch);
|
|
11650
|
+
let runCwd = workspace?.cwd;
|
|
11651
|
+
if (!runCwd) {
|
|
11652
|
+
runCwd = path_1.default.join(scratch, "run");
|
|
11653
|
+
await fs_1.promises.mkdir(runCwd);
|
|
11654
|
+
}
|
|
11655
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
11656
|
+
const { response, reads, toolInputs } = await this.runCopilot({
|
|
11657
|
+
args: [...copilotReviewArgs(), `--session-id=${sessionId}`, "-C", runCwd],
|
|
11658
|
+
prompt: withSchema(prompt, spec),
|
|
11659
|
+
spec,
|
|
11660
|
+
cwd: runCwd,
|
|
11661
|
+
home,
|
|
11662
|
+
scratch,
|
|
11663
|
+
model,
|
|
11664
|
+
signal,
|
|
11665
|
+
reporter,
|
|
11666
|
+
transcriptName: dispatch.job.id
|
|
11667
|
+
});
|
|
11668
|
+
response.continuation = {
|
|
11669
|
+
sessionId,
|
|
11670
|
+
home,
|
|
11671
|
+
model,
|
|
11672
|
+
outputLabel: spec.outputLabel,
|
|
11673
|
+
outputSchema: spec.outputSchema
|
|
11674
|
+
};
|
|
11675
|
+
keep = true;
|
|
11676
|
+
if (workspace)
|
|
11677
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, reads, toolInputs));
|
|
11678
|
+
return response;
|
|
11679
|
+
} finally {
|
|
11680
|
+
reporter?.close();
|
|
11681
|
+
if (!keep)
|
|
11682
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11683
|
+
});
|
|
10752
11684
|
}
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
continue;
|
|
10761
|
-
for (const rel of writtenPaths) {
|
|
10762
|
-
if (re.test(rel))
|
|
10763
|
-
credited.add(rel);
|
|
10764
|
-
}
|
|
10765
|
-
}
|
|
11685
|
+
}
|
|
11686
|
+
/** In-session gate repair (ADR 0017): resume the session this executor named, from the run's own COPILOT_HOME. */
|
|
11687
|
+
async continue(continuation, followUp, signal, ctx) {
|
|
11688
|
+
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
11689
|
+
const handle = continuation;
|
|
11690
|
+
if (!handle || typeof handle.sessionId !== "string" || typeof handle.home !== "string") {
|
|
11691
|
+
throw new Error("copilot continue: missing session to resume");
|
|
10766
11692
|
}
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
11693
|
+
const scratch = path_1.default.dirname(handle.home);
|
|
11694
|
+
const cwd = path_1.default.join(scratch, "resume");
|
|
11695
|
+
await fs_1.promises.mkdir(cwd, { recursive: true });
|
|
11696
|
+
const spec = {
|
|
11697
|
+
rendering: "producer",
|
|
11698
|
+
outputLabel: handle.outputLabel,
|
|
11699
|
+
outputSchema: handle.outputSchema
|
|
11700
|
+
};
|
|
11701
|
+
const { response } = await this.runCopilot({
|
|
11702
|
+
args: [...copilotReviewArgs(), `--resume=${handle.sessionId}`, "-C", cwd],
|
|
11703
|
+
prompt: followUp,
|
|
11704
|
+
spec,
|
|
11705
|
+
cwd,
|
|
11706
|
+
home: handle.home,
|
|
11707
|
+
scratch,
|
|
11708
|
+
model: handle.model,
|
|
11709
|
+
signal,
|
|
11710
|
+
reporter: null,
|
|
11711
|
+
transcriptName: `${handle.sessionId}-repair`
|
|
11712
|
+
});
|
|
11713
|
+
response.continuation = handle;
|
|
11714
|
+
return response;
|
|
11715
|
+
}
|
|
11716
|
+
/** One real turn under the review flags. A signed-out CLI fails before any model call. */
|
|
11717
|
+
async checkReadiness(signal) {
|
|
11718
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-copilot-ready-"));
|
|
11719
|
+
try {
|
|
11720
|
+
const home = await makeRunHome(scratch);
|
|
11721
|
+
let run;
|
|
11722
|
+
try {
|
|
11723
|
+
run = await withOperatorLogin(home, () => (0, agent_process_1.runAgentProcess)({
|
|
11724
|
+
bin: "copilot",
|
|
11725
|
+
args: [...copilotReviewArgs(), "-C", scratch, "-p", READINESS_PROMPT],
|
|
11726
|
+
cwd: scratch,
|
|
11727
|
+
env: runEnv(home),
|
|
11728
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
11729
|
+
toolName: "the copilot CLI",
|
|
11730
|
+
signal
|
|
11731
|
+
}));
|
|
11732
|
+
} catch (err) {
|
|
11733
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11734
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
10774
11735
|
}
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
const base = target.length > 0 ? `${target}/` : "";
|
|
10781
|
-
for (const rel of writtenPaths) {
|
|
10782
|
-
if (!rel.startsWith(base))
|
|
10783
|
-
continue;
|
|
10784
|
-
if (namesBareWord(input, rel.slice(base.length)))
|
|
10785
|
-
credited.add(rel);
|
|
11736
|
+
if (run.spawnError) {
|
|
11737
|
+
return {
|
|
11738
|
+
ready: false,
|
|
11739
|
+
reason: run.spawnError.code === "ENOENT" ? `the copilot CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
11740
|
+
};
|
|
10786
11741
|
}
|
|
11742
|
+
const events = parseCopilotEvents(run.stdout);
|
|
11743
|
+
if (run.code === 0 && !events.failure)
|
|
11744
|
+
return { ready: true };
|
|
11745
|
+
return {
|
|
11746
|
+
ready: false,
|
|
11747
|
+
reason: events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? `the copilot CLI exited with code ${run.code}`
|
|
11748
|
+
};
|
|
11749
|
+
} finally {
|
|
11750
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11751
|
+
});
|
|
10787
11752
|
}
|
|
10788
11753
|
}
|
|
10789
|
-
|
|
11754
|
+
tmpBase() {
|
|
11755
|
+
return this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
11756
|
+
}
|
|
11757
|
+
async runCopilot(opts) {
|
|
11758
|
+
const { spec, model } = opts;
|
|
11759
|
+
const usageFile = path_1.default.join(opts.scratch, `usage-${(0, crypto_1.randomUUID)()}.json`);
|
|
11760
|
+
const args = [...opts.args, `--usage-output-file=${usageFile}`, `--log-dir=${path_1.default.join(opts.home, "logs")}`];
|
|
11761
|
+
if (model)
|
|
11762
|
+
args.push(`--model=${model}`);
|
|
11763
|
+
if (this.config.reasoningEffort)
|
|
11764
|
+
args.push(`--reasoning-effort=${this.config.reasoningEffort}`);
|
|
11765
|
+
const tap = opts.reporter ? copilotProgressTap(opts.reporter) : null;
|
|
11766
|
+
const run = await withOperatorLogin(opts.home, () => (0, agent_process_1.runAgentProcess)({
|
|
11767
|
+
bin: "copilot",
|
|
11768
|
+
args,
|
|
11769
|
+
cwd: opts.cwd,
|
|
11770
|
+
env: runEnv(opts.home),
|
|
11771
|
+
// The prompt comes from stdin: a review prompt carries the diff, far
|
|
11772
|
+
// past what argv should hold, and argv is visible to every process.
|
|
11773
|
+
stdin: opts.prompt,
|
|
11774
|
+
idleTimeoutMs: this.config.timeout ?? agent_common_1.DEFAULT_AGENT_TIMEOUT,
|
|
11775
|
+
toolName: "Copilot",
|
|
11776
|
+
signal: opts.signal,
|
|
11777
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
11778
|
+
}));
|
|
11779
|
+
tap?.end();
|
|
11780
|
+
if (run.spawnError) {
|
|
11781
|
+
if (run.spawnError.code === "ENOENT") {
|
|
11782
|
+
throw new types_1.ExecutorEnvironmentError(`the copilot CLI is not on this machine's PATH (${run.spawnError.message})`, COPILOT_FAULTS.installRemedy);
|
|
11783
|
+
}
|
|
11784
|
+
throw new Error(run.spawnError.message);
|
|
11785
|
+
}
|
|
11786
|
+
if (this.config.keepTranscripts) {
|
|
11787
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), opts.transcriptName, run.stdout);
|
|
11788
|
+
}
|
|
11789
|
+
const events = parseCopilotEvents(run.stdout);
|
|
11790
|
+
if (run.code !== 0 || events.failure) {
|
|
11791
|
+
const said = events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
11792
|
+
const message = said ? `Copilot ${run.code === 0 ? "reported a failure" : `exited with code ${run.code}`}: ${said}` : `Copilot exited with code ${run.code}`;
|
|
11793
|
+
throw (0, agent_process_1.agentFailure)(true, message, { apiErrorStatus: null, text: said }, COPILOT_FAULTS);
|
|
11794
|
+
}
|
|
11795
|
+
if (events.finalText === null)
|
|
11796
|
+
throw new Error("Copilot finished without a final message");
|
|
11797
|
+
const answerText = events.finalText;
|
|
11798
|
+
const parsed = (0, json_answer_1.extractJsonObject)(answerText);
|
|
11799
|
+
if (parsed === void 0 && hasSchema(spec)) {
|
|
11800
|
+
throw new Error(`copilot's final message contained no JSON object (message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
11801
|
+
}
|
|
11802
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
11803
|
+
const usage = await readUsage(usageFile);
|
|
11804
|
+
return {
|
|
11805
|
+
response: {
|
|
11806
|
+
outputs: [{ label: spec.outputLabel, content: parsed ?? { raw: answerText } }],
|
|
11807
|
+
rawText: answerText,
|
|
11808
|
+
parsed,
|
|
11809
|
+
// The model the session actually ran, from its usage file, over the one asked for.
|
|
11810
|
+
usage: { aiProvider: "copilot", ...model ? { aiModel: model } : {}, ...usage ?? {} }
|
|
11811
|
+
},
|
|
11812
|
+
reads: events.readPaths,
|
|
11813
|
+
toolInputs: events.toolInputs
|
|
11814
|
+
};
|
|
11815
|
+
}
|
|
11816
|
+
};
|
|
11817
|
+
function hasSchema(spec) {
|
|
11818
|
+
return Boolean(spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0);
|
|
10790
11819
|
}
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
11820
|
+
function withSchema(prompt, spec) {
|
|
11821
|
+
if (!hasSchema(spec))
|
|
11822
|
+
return prompt;
|
|
11823
|
+
return `${prompt}
|
|
11824
|
+
|
|
11825
|
+
---
|
|
11826
|
+
|
|
11827
|
+
Your final message must be one JSON object, and nothing else, that validates against this JSON Schema:
|
|
11828
|
+
|
|
11829
|
+
${JSON.stringify(spec.outputSchema)}
|
|
11830
|
+
`;
|
|
10796
11831
|
}
|
|
10797
|
-
|
|
11832
|
+
var RUN_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
11833
|
+
async function pruneRuns(runs) {
|
|
11834
|
+
let entries;
|
|
10798
11835
|
try {
|
|
10799
|
-
await fs_1.promises.
|
|
10800
|
-
|
|
10801
|
-
|
|
11836
|
+
entries = await fs_1.promises.readdir(runs);
|
|
11837
|
+
} catch {
|
|
11838
|
+
return;
|
|
11839
|
+
}
|
|
11840
|
+
const cutoff = Date.now() - RUN_RETENTION_MS;
|
|
11841
|
+
await Promise.all(entries.map(async (name) => {
|
|
11842
|
+
const dir = path_1.default.join(runs, name);
|
|
11843
|
+
try {
|
|
11844
|
+
if ((await fs_1.promises.stat(dir)).mtimeMs < cutoff)
|
|
11845
|
+
await fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
11846
|
+
} catch {
|
|
11847
|
+
}
|
|
11848
|
+
}));
|
|
11849
|
+
}
|
|
11850
|
+
async function makeRunHome(scratch) {
|
|
11851
|
+
const home = path_1.default.join(scratch, "copilot-home");
|
|
11852
|
+
await fs_1.promises.mkdir(home, { mode: 448 });
|
|
11853
|
+
await fs_1.promises.writeFile(path_1.default.join(home, "settings.json"), JSON.stringify(RUN_SETTINGS));
|
|
11854
|
+
const bin = path_1.default.join(scratch, "bin");
|
|
11855
|
+
await fs_1.promises.mkdir(bin, { recursive: true });
|
|
11856
|
+
await fs_1.promises.writeFile(path_1.default.join(bin, "gh"), REFUSING_GH, { mode: 493 });
|
|
11857
|
+
return home;
|
|
11858
|
+
}
|
|
11859
|
+
var REFUSING_GH = '#!/bin/sh\necho "gh is not available to a Sifter review" >&2\nexit 1\n';
|
|
11860
|
+
var LOGIN_POINTER_KEYS = ["lastLoggedInUser", "loggedInUsers"];
|
|
11861
|
+
var LOGIN_TOKEN_KEYS = ["authTokens"];
|
|
11862
|
+
async function withOperatorLogin(home, fn) {
|
|
11863
|
+
const file = path_1.default.join(home, "config.json");
|
|
11864
|
+
const login = await operatorLogin();
|
|
11865
|
+
if (login)
|
|
11866
|
+
await fs_1.promises.writeFile(file, JSON.stringify(login), { mode: 384 });
|
|
11867
|
+
else
|
|
11868
|
+
await fs_1.promises.rm(file, { force: true });
|
|
11869
|
+
try {
|
|
11870
|
+
return await fn();
|
|
11871
|
+
} finally {
|
|
11872
|
+
await forgetToken(file);
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
11875
|
+
async function forgetToken(file) {
|
|
11876
|
+
let config;
|
|
11877
|
+
try {
|
|
11878
|
+
config = await readJsonc(file);
|
|
10802
11879
|
} catch (err) {
|
|
10803
|
-
|
|
11880
|
+
if (err.code !== "ENOENT")
|
|
11881
|
+
await fs_1.promises.rm(file, { force: true }).catch(() => {
|
|
11882
|
+
});
|
|
11883
|
+
return;
|
|
10804
11884
|
}
|
|
11885
|
+
if (!LOGIN_TOKEN_KEYS.some((k) => k in config))
|
|
11886
|
+
return;
|
|
11887
|
+
for (const k of LOGIN_TOKEN_KEYS)
|
|
11888
|
+
delete config[k];
|
|
11889
|
+
await fs_1.promises.writeFile(file, JSON.stringify(config), { mode: 384 }).catch(() => fs_1.promises.rm(file, { force: true }));
|
|
10805
11890
|
}
|
|
10806
|
-
function
|
|
10807
|
-
const
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
11891
|
+
async function operatorLogin() {
|
|
11892
|
+
const home = process.env.COPILOT_HOME ?? path_1.default.join(os_1.default.homedir(), ".copilot");
|
|
11893
|
+
try {
|
|
11894
|
+
const parsed = await readJsonc(path_1.default.join(home, "config.json"));
|
|
11895
|
+
const keys = [...LOGIN_POINTER_KEYS, ...LOGIN_TOKEN_KEYS].filter((k) => k in parsed);
|
|
11896
|
+
return keys.length > 0 ? Object.fromEntries(keys.map((k) => [k, parsed[k]])) : null;
|
|
11897
|
+
} catch {
|
|
11898
|
+
return null;
|
|
11899
|
+
}
|
|
11900
|
+
}
|
|
11901
|
+
async function readJsonc(file) {
|
|
11902
|
+
const raw = await fs_1.promises.readFile(file, "utf8");
|
|
11903
|
+
return JSON.parse(raw.split("\n").filter((line) => !line.trim().startsWith("//")).join("\n"));
|
|
11904
|
+
}
|
|
11905
|
+
function runEnv(home) {
|
|
11906
|
+
const bin = path_1.default.join(path_1.default.dirname(home), "bin");
|
|
11907
|
+
return {
|
|
11908
|
+
...process.env,
|
|
11909
|
+
PATH: `${bin}${path_1.default.delimiter}${process.env.PATH ?? ""}`,
|
|
11910
|
+
COPILOT_HOME: home,
|
|
11911
|
+
COPILOT_AUTO_UPDATE: "false"
|
|
11912
|
+
};
|
|
11913
|
+
}
|
|
11914
|
+
async function readUsage(file) {
|
|
11915
|
+
try {
|
|
11916
|
+
const raw = JSON.parse(await fs_1.promises.readFile(file, "utf8"));
|
|
11917
|
+
return usageOf(raw);
|
|
11918
|
+
} catch {
|
|
11919
|
+
return null;
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11922
|
+
function usageOf(raw) {
|
|
11923
|
+
const models = Object.entries(raw.modelMetrics ?? {});
|
|
11924
|
+
if (models.length === 0)
|
|
11925
|
+
return null;
|
|
11926
|
+
const num = (v) => typeof v === "number" ? v : 0;
|
|
11927
|
+
let input = 0;
|
|
11928
|
+
let output = 0;
|
|
11929
|
+
for (const [, m] of models) {
|
|
11930
|
+
input += num(m.usage?.inputTokens) + num(m.usage?.cacheReadTokens);
|
|
11931
|
+
output += num(m.usage?.outputTokens);
|
|
11932
|
+
}
|
|
11933
|
+
return { inputTokens: input, outputTokens: output, ...models.length === 1 ? { aiModel: models[0][0] } : {} };
|
|
11934
|
+
}
|
|
11935
|
+
function copilotProgressTap(reporter) {
|
|
11936
|
+
return new progress_1.JsonLineTap((event) => {
|
|
11937
|
+
const data = event.data ?? {};
|
|
11938
|
+
if (event.type === "tool.execution_start" && typeof data.toolName === "string") {
|
|
11939
|
+
const args = data.arguments ?? {};
|
|
11940
|
+
const target = [args.path, args.pattern].find((v) => typeof v === "string");
|
|
11941
|
+
reporter.tool(data.toolName, target);
|
|
11942
|
+
} else if (event.type === "assistant.reasoning" && typeof data.content === "string") {
|
|
11943
|
+
reporter.reasoning(data.content);
|
|
11944
|
+
} else if (event.type === "assistant.turn_end") {
|
|
11945
|
+
reporter.flush();
|
|
11946
|
+
}
|
|
11947
|
+
});
|
|
11948
|
+
}
|
|
11949
|
+
function parseCopilotEvents(stdout) {
|
|
11950
|
+
let finalText = null;
|
|
11951
|
+
const readPaths = [];
|
|
11952
|
+
const toolInputs = [];
|
|
11953
|
+
const started = /* @__PURE__ */ new Map();
|
|
11954
|
+
let lastError = null;
|
|
11955
|
+
for (const line of stdout.split("\n")) {
|
|
11956
|
+
const trimmed = line.trim();
|
|
11957
|
+
if (!trimmed.startsWith("{"))
|
|
11958
|
+
continue;
|
|
11959
|
+
let event;
|
|
11960
|
+
try {
|
|
11961
|
+
event = JSON.parse(trimmed);
|
|
11962
|
+
} catch {
|
|
10815
11963
|
continue;
|
|
10816
11964
|
}
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
for (let j = i; j < s.length; j++) {
|
|
10822
|
-
const ch = s[j];
|
|
10823
|
-
if (escaped) {
|
|
10824
|
-
escaped = false;
|
|
10825
|
-
continue;
|
|
10826
|
-
}
|
|
10827
|
-
if (ch === "\\") {
|
|
10828
|
-
escaped = inString;
|
|
10829
|
-
continue;
|
|
10830
|
-
}
|
|
10831
|
-
if (ch === '"') {
|
|
10832
|
-
inString = !inString;
|
|
10833
|
-
continue;
|
|
10834
|
-
}
|
|
10835
|
-
if (inString)
|
|
10836
|
-
continue;
|
|
10837
|
-
if (ch === "{")
|
|
10838
|
-
depth++;
|
|
10839
|
-
else if (ch === "}") {
|
|
10840
|
-
depth--;
|
|
10841
|
-
if (depth === 0) {
|
|
10842
|
-
end = j;
|
|
11965
|
+
const data = event.data ?? {};
|
|
11966
|
+
switch (event.type) {
|
|
11967
|
+
case "tool.execution_start": {
|
|
11968
|
+
if (typeof data.toolCallId !== "string" || typeof data.toolName !== "string")
|
|
10843
11969
|
break;
|
|
11970
|
+
const args = data.arguments ?? {};
|
|
11971
|
+
started.set(data.toolCallId, { name: data.toolName, args });
|
|
11972
|
+
toolInputs.push(JSON.stringify(args));
|
|
11973
|
+
break;
|
|
11974
|
+
}
|
|
11975
|
+
case "tool.execution_complete": {
|
|
11976
|
+
const call = typeof data.toolCallId === "string" ? started.get(data.toolCallId) : void 0;
|
|
11977
|
+
if (call && data.success === true && call.name === "view" && typeof call.args.path === "string") {
|
|
11978
|
+
readPaths.push(call.args.path);
|
|
10844
11979
|
}
|
|
11980
|
+
break;
|
|
10845
11981
|
}
|
|
11982
|
+
case "assistant.message":
|
|
11983
|
+
if (typeof data.content === "string" && data.content.trim().length > 0) {
|
|
11984
|
+
finalText = data.content;
|
|
11985
|
+
lastError = null;
|
|
11986
|
+
}
|
|
11987
|
+
break;
|
|
11988
|
+
default:
|
|
11989
|
+
if (typeof event.type === "string" && /(^|\.)error$/.test(event.type)) {
|
|
11990
|
+
const message = [data.message, data.error?.message].find((v) => typeof v === "string");
|
|
11991
|
+
if (message)
|
|
11992
|
+
lastError = message;
|
|
11993
|
+
}
|
|
10846
11994
|
}
|
|
10847
|
-
if (end === -1)
|
|
10848
|
-
break;
|
|
10849
|
-
const candidate = tryParseObject(s.slice(i, end + 1));
|
|
10850
|
-
if (candidate)
|
|
10851
|
-
last = candidate;
|
|
10852
|
-
i = end + 1;
|
|
10853
11995
|
}
|
|
10854
|
-
return
|
|
11996
|
+
return { finalText, readPaths, toolInputs, failure: finalText === null ? lastError : null };
|
|
10855
11997
|
}
|
|
10856
|
-
function
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
11998
|
+
function createCopilotExecutor(config, capabilityId = "copilot") {
|
|
11999
|
+
const validated = (0, agent_common_1.parseAgentCommonConfig)(config, capabilityId);
|
|
12000
|
+
if (config.reasoningEffort !== void 0) {
|
|
12001
|
+
if (!REASONING_EFFORTS.includes(config.reasoningEffort)) {
|
|
12002
|
+
throw new Error(`"reasoningEffort" must be one of ${REASONING_EFFORTS.join(", ")}`);
|
|
12003
|
+
}
|
|
12004
|
+
validated.reasoningEffort = config.reasoningEffort;
|
|
10862
12005
|
}
|
|
12006
|
+
return new CopilotExecutor(validated);
|
|
10863
12007
|
}
|
|
10864
12008
|
}
|
|
10865
12009
|
});
|
|
@@ -12502,6 +13646,8 @@ var require_checks = __commonJS({
|
|
|
12502
13646
|
var path_1 = require("path");
|
|
12503
13647
|
var registry_1 = require_registry();
|
|
12504
13648
|
var claude_code_1 = require_claude_code();
|
|
13649
|
+
var codex_1 = require_codex();
|
|
13650
|
+
var copilot_1 = require_copilot();
|
|
12505
13651
|
var http_api_1 = require_http_api();
|
|
12506
13652
|
var anthropic_api_1 = require_anthropic_api();
|
|
12507
13653
|
var webhook_1 = require_webhook();
|
|
@@ -12513,9 +13659,21 @@ var require_checks = __commonJS({
|
|
|
12513
13659
|
var scaffold_1 = require_scaffold();
|
|
12514
13660
|
var NODE_FLOOR = "20.0.0";
|
|
12515
13661
|
var DISK_FLOOR_BYTES = 1024 * 1024 * 1024;
|
|
12516
|
-
var
|
|
13662
|
+
var AGENTS = [
|
|
13663
|
+
{ executor: "claude-code", bin: "claude", install: "install Claude Code (npm install -g @anthropic-ai/claude-code)" },
|
|
13664
|
+
{ executor: "codex", bin: "codex", install: "install Codex (brew install codex, or npm install -g @openai/codex)" },
|
|
13665
|
+
{
|
|
13666
|
+
executor: "copilot",
|
|
13667
|
+
bin: "copilot",
|
|
13668
|
+
install: "install GitHub Copilot CLI (brew install --cask copilot-cli, or npm install -g @github/copilot)"
|
|
13669
|
+
}
|
|
13670
|
+
];
|
|
13671
|
+
var AGENT_EXECUTORS2 = AGENTS.map((a) => a.executor);
|
|
13672
|
+
var DEFAULT_AGENT = "claude-code";
|
|
12517
13673
|
var BUILT_IN_EXECUTORS = [
|
|
12518
13674
|
["claude-code", claude_code_1.createClaudeCodeExecutor],
|
|
13675
|
+
["codex", codex_1.createCodexExecutor],
|
|
13676
|
+
["copilot", copilot_1.createCopilotExecutor],
|
|
12519
13677
|
["http-api", http_api_1.createHttpApiExecutor],
|
|
12520
13678
|
["anthropic-api", anthropic_api_1.createAnthropicApiExecutor],
|
|
12521
13679
|
["webhook", webhook_1.createWebhookExecutor],
|
|
@@ -12544,22 +13702,27 @@ var require_checks = __commonJS({
|
|
|
12544
13702
|
function configuredExecutors(config) {
|
|
12545
13703
|
return Object.keys(config.executors ?? {});
|
|
12546
13704
|
}
|
|
12547
|
-
function
|
|
12548
|
-
const
|
|
12549
|
-
|
|
13705
|
+
function activeAgent(config) {
|
|
13706
|
+
const configured = configuredExecutors(config);
|
|
13707
|
+
const pooled = config.pools.map((p) => p.executor).find((e) => AGENT_EXECUTORS2.includes(e) && configured.includes(e));
|
|
13708
|
+
return pooled ?? configured.find((e) => AGENT_EXECUTORS2.includes(e)) ?? null;
|
|
13709
|
+
}
|
|
13710
|
+
function agentSettings(config, subject) {
|
|
13711
|
+
const executor = activeAgent(config);
|
|
13712
|
+
if (executor === null)
|
|
12550
13713
|
return null;
|
|
12551
|
-
const
|
|
12552
|
-
|
|
13714
|
+
const user = config.executors[executor] ?? {};
|
|
13715
|
+
const defaults = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[executor] ?? {};
|
|
13716
|
+
return { executor, settings: { ...defaults, ...user } };
|
|
12553
13717
|
}
|
|
12554
13718
|
function claudeSettingsOf(config) {
|
|
12555
|
-
return config.executors[
|
|
13719
|
+
return config.executors["claude-code"]?.claudeSettings;
|
|
12556
13720
|
}
|
|
12557
13721
|
function describeLogin(status) {
|
|
12558
13722
|
return [status.authMethod ?? "unknown method", status.subscriptionType].filter((part) => part !== null).join(", ");
|
|
12559
13723
|
}
|
|
12560
13724
|
function runRoot(config, subject, osTmpDir) {
|
|
12561
|
-
const
|
|
12562
|
-
const tmpDir = settings?.tmpDir;
|
|
13725
|
+
const tmpDir = agentSettings(config, subject)?.settings.tmpDir;
|
|
12563
13726
|
return typeof tmpDir === "string" && tmpDir.length > 0 ? (0, apply_1.expandHome)(tmpDir) : osTmpDir;
|
|
12564
13727
|
}
|
|
12565
13728
|
async function withConfig(name, probes, body) {
|
|
@@ -12585,13 +13748,13 @@ var require_checks = __commonJS({
|
|
|
12585
13748
|
};
|
|
12586
13749
|
}
|
|
12587
13750
|
function initRunRoot(plan, subject, osTmpDir) {
|
|
12588
|
-
if (plan.executor
|
|
13751
|
+
if (plan.executor === void 0 || !AGENT_EXECUTORS2.includes(plan.executor))
|
|
12589
13752
|
return osTmpDir;
|
|
12590
13753
|
if (subject.requestedTmpDir !== void 0 && subject.requestedTmpDir.length > 0) {
|
|
12591
13754
|
return (0, apply_1.expandHome)(subject.requestedTmpDir);
|
|
12592
13755
|
}
|
|
12593
13756
|
const settings = {
|
|
12594
|
-
...(0, environment_1.executorDefaultsFor)(subject.brand, subject.brand.keepRegistration.jobsApiUrl)?.[
|
|
13757
|
+
...(0, environment_1.executorDefaultsFor)(subject.brand, subject.brand.keepRegistration.jobsApiUrl)?.[plan.executor] ?? {},
|
|
12595
13758
|
...plan.keptBlock ?? {}
|
|
12596
13759
|
};
|
|
12597
13760
|
const tmpDir = settings.tmpDir;
|
|
@@ -12787,7 +13950,7 @@ var require_checks = __commonJS({
|
|
|
12787
13950
|
}
|
|
12788
13951
|
const covering = built.filter((e) => e.strategies.includes(loom_1.PROMPT_EXECUTION_STRATEGY));
|
|
12789
13952
|
if (covering.length === 0) {
|
|
12790
|
-
return fail("executor-coverage", `no configured executor handles the "${loom_1.PROMPT_EXECUTION_STRATEGY}" strategy every review job carries`, `configure an executor that runs prompts, e.g. ${
|
|
13953
|
+
return fail("executor-coverage", `no configured executor handles the "${loom_1.PROMPT_EXECUTION_STRATEGY}" strategy every review job carries`, `configure an executor that runs prompts, e.g. ${DEFAULT_AGENT}`);
|
|
12791
13954
|
}
|
|
12792
13955
|
return pass("executor-coverage", `${covering.map((e) => e.capability.id).join(", ")} handle${covering.length === 1 ? "s" : ""} ${loom_1.PROMPT_EXECUTION_STRATEGY}`);
|
|
12793
13956
|
});
|
|
@@ -12798,13 +13961,13 @@ var require_checks = __commonJS({
|
|
|
12798
13961
|
phases: ["init", "run", "doctor"],
|
|
12799
13962
|
async run(probes, subject) {
|
|
12800
13963
|
if (subject.phase === "init") {
|
|
12801
|
-
return atInit("git-cli", probes, subject, () => toolVerdict("git-cli", "git", probes, subject, {
|
|
13964
|
+
return atInit("git-cli", AGENT_EXECUTORS2, probes, subject, () => toolVerdict("git-cli", "git", probes, subject, {
|
|
12802
13965
|
missingRemedy: "install git, or configure an executor that needs no checkout"
|
|
12803
13966
|
}));
|
|
12804
13967
|
}
|
|
12805
13968
|
return withConfig("git-cli", probes, async (config) => {
|
|
12806
|
-
if (
|
|
12807
|
-
return skip("git-cli",
|
|
13969
|
+
if (activeAgent(config) === null) {
|
|
13970
|
+
return skip("git-cli", "no agent executor configured; nothing checks out a workspace");
|
|
12808
13971
|
}
|
|
12809
13972
|
return toolVerdict("git-cli", "git", probes, subject, {
|
|
12810
13973
|
missingRemedy: "install git, or configure an executor that needs no checkout"
|
|
@@ -12812,28 +13975,32 @@ var require_checks = __commonJS({
|
|
|
12812
13975
|
});
|
|
12813
13976
|
}
|
|
12814
13977
|
};
|
|
12815
|
-
|
|
12816
|
-
name
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
if (!configuredExecutors(config).includes(WORKSPACE_EXECUTOR)) {
|
|
12824
|
-
return skip("claude-cli", `no ${WORKSPACE_EXECUTOR} executor configured`);
|
|
13978
|
+
function agentCli(agent) {
|
|
13979
|
+
const name = `${agent.bin}-cli`;
|
|
13980
|
+
return {
|
|
13981
|
+
name,
|
|
13982
|
+
phases: ["init", "run", "doctor"],
|
|
13983
|
+
async run(probes, subject) {
|
|
13984
|
+
if (subject.phase === "init") {
|
|
13985
|
+
return atInit(name, [agent.executor], probes, subject, () => toolVerdict(name, agent.bin, probes, subject, { missingRemedy: agent.install }));
|
|
12825
13986
|
}
|
|
12826
|
-
return
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
13987
|
+
return withConfig(name, probes, async (config) => {
|
|
13988
|
+
if (!configuredExecutors(config).includes(agent.executor)) {
|
|
13989
|
+
return skip(name, `no ${agent.executor} executor configured`);
|
|
13990
|
+
}
|
|
13991
|
+
return toolVerdict(name, agent.bin, probes, subject, { missingRemedy: agent.install });
|
|
13992
|
+
});
|
|
13993
|
+
}
|
|
13994
|
+
};
|
|
13995
|
+
}
|
|
13996
|
+
var [CLAUDE, CODEX, COPILOT] = AGENTS;
|
|
12830
13997
|
var claudeAuth = {
|
|
12831
13998
|
name: "claude-auth",
|
|
12832
13999
|
phases: ["run", "doctor"],
|
|
12833
14000
|
async run(probes, subject) {
|
|
12834
14001
|
return withConfig("claude-auth", probes, async (config) => {
|
|
12835
|
-
if (!configuredExecutors(config).includes(
|
|
12836
|
-
return skip("claude-auth", `no ${
|
|
14002
|
+
if (!configuredExecutors(config).includes(CLAUDE.executor)) {
|
|
14003
|
+
return skip("claude-auth", `no ${CLAUDE.executor} executor configured`);
|
|
12837
14004
|
}
|
|
12838
14005
|
if (config.credentials !== void 0) {
|
|
12839
14006
|
return skip("claude-auth", "hosted mode: the model key is revealed per job, not read from a login");
|
|
@@ -12867,6 +14034,64 @@ var require_checks = __commonJS({
|
|
|
12867
14034
|
});
|
|
12868
14035
|
}
|
|
12869
14036
|
};
|
|
14037
|
+
var codexAuth = {
|
|
14038
|
+
name: "codex-auth",
|
|
14039
|
+
phases: ["run", "doctor"],
|
|
14040
|
+
async run(probes, subject) {
|
|
14041
|
+
return withConfig("codex-auth", probes, async (config) => {
|
|
14042
|
+
const gate = await agentAuthGate("codex-auth", CODEX, config, probes);
|
|
14043
|
+
if (gate)
|
|
14044
|
+
return gate;
|
|
14045
|
+
if (subject.deep)
|
|
14046
|
+
return roundTripVerdict("codex-auth", CODEX, probes, "run `codex login`");
|
|
14047
|
+
const status = await probes.codexLoginStatus();
|
|
14048
|
+
if (status === null) {
|
|
14049
|
+
return warn("codex-auth", "`codex login status` gave no answer", "run `codex login status` to see why, or doctor --deep");
|
|
14050
|
+
}
|
|
14051
|
+
if (!status.loggedIn) {
|
|
14052
|
+
return fail("codex-auth", "the codex CLI is not signed in", "run `codex login`");
|
|
14053
|
+
}
|
|
14054
|
+
return pass("codex-auth", `${status.detail} (not verified)`);
|
|
14055
|
+
});
|
|
14056
|
+
}
|
|
14057
|
+
};
|
|
14058
|
+
var copilotAuth = {
|
|
14059
|
+
name: "copilot-auth",
|
|
14060
|
+
phases: ["run", "doctor"],
|
|
14061
|
+
async run(probes, subject) {
|
|
14062
|
+
return withConfig("copilot-auth", probes, async (config) => {
|
|
14063
|
+
const gate = await agentAuthGate("copilot-auth", COPILOT, config, probes);
|
|
14064
|
+
if (gate)
|
|
14065
|
+
return gate;
|
|
14066
|
+
if (subject.deep)
|
|
14067
|
+
return roundTripVerdict("copilot-auth", COPILOT, probes, "run `copilot login`");
|
|
14068
|
+
const token = probes.ambientCopilotToken();
|
|
14069
|
+
if (token !== null) {
|
|
14070
|
+
return warn("copilot-auth", `${token} is set and Copilot uses it ahead of any \`copilot login\``, `unset ${token} for this process if it is not a token with Copilot access, or run doctor --deep to prove it`);
|
|
14071
|
+
}
|
|
14072
|
+
return pass("copilot-auth", "no token in the environment; the copilot login is used (not verified; doctor --deep spends one turn to prove it)");
|
|
14073
|
+
});
|
|
14074
|
+
}
|
|
14075
|
+
};
|
|
14076
|
+
async function agentAuthGate(name, agent, config, probes) {
|
|
14077
|
+
if (!configuredExecutors(config).includes(agent.executor)) {
|
|
14078
|
+
return skip(name, `no ${agent.executor} executor configured`);
|
|
14079
|
+
}
|
|
14080
|
+
if (config.credentials !== void 0) {
|
|
14081
|
+
return skip(name, `hosted mode: the ${agent.executor} executor refuses hosted runs`);
|
|
14082
|
+
}
|
|
14083
|
+
if (await probes.which(agent.bin) === null || !(await probes.versionOf(agent.bin)).ran) {
|
|
14084
|
+
return skip(name, `no working ${agent.bin} CLI to be signed in to; see ${agent.bin}-cli`);
|
|
14085
|
+
}
|
|
14086
|
+
return null;
|
|
14087
|
+
}
|
|
14088
|
+
async function roundTripVerdict(name, agent, probes, signIn) {
|
|
14089
|
+
const round = await probes.agentRoundTrip(agent.executor);
|
|
14090
|
+
if (!round.ok) {
|
|
14091
|
+
return fail(name, `the ${agent.bin} CLI refused a one-turn call: ${oneLine(round.error)}`, `${signIn}, then re-run doctor --deep`);
|
|
14092
|
+
}
|
|
14093
|
+
return pass(name, `one-turn call through the ${agent.bin} CLI succeeded`);
|
|
14094
|
+
}
|
|
12870
14095
|
var runRootWritable = {
|
|
12871
14096
|
name: "run-root-writable",
|
|
12872
14097
|
phases: ["init", "run", "doctor"],
|
|
@@ -12930,15 +14155,15 @@ var require_checks = __commonJS({
|
|
|
12930
14155
|
phases: ["run", "doctor"],
|
|
12931
14156
|
async run(probes, subject) {
|
|
12932
14157
|
return withConfig("overlay-hosts", probes, (config) => {
|
|
12933
|
-
const
|
|
12934
|
-
if (
|
|
12935
|
-
return skip("overlay-hosts",
|
|
14158
|
+
const agent = agentSettings(config, subject);
|
|
14159
|
+
if (agent === null) {
|
|
14160
|
+
return skip("overlay-hosts", "no agent executor configured");
|
|
12936
14161
|
}
|
|
12937
|
-
const hosts = settings.overlayAllowedHosts;
|
|
14162
|
+
const hosts = agent.settings.overlayAllowedHosts;
|
|
12938
14163
|
const origins = Array.isArray(hosts) ? hosts.filter((h) => typeof h === "string") : [];
|
|
12939
14164
|
if (origins.length === 0) {
|
|
12940
|
-
const fallback = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[
|
|
12941
|
-
return warn("overlay-hosts", "the overlay allowlist is empty, so every review will run standards-blind", Array.isArray(fallback) && fallback.length > 0 ? `remove overlayAllowedHosts from the ${
|
|
14165
|
+
const fallback = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[agent.executor]?.overlayAllowedHosts;
|
|
14166
|
+
return warn("overlay-hosts", "the overlay allowlist is empty, so every review will run standards-blind", Array.isArray(fallback) && fallback.length > 0 ? `remove overlayAllowedHosts from the ${agent.executor} executor to use this tier's default` : `add overlayAllowedHosts to the ${agent.executor} executor, naming the origin your backend signs standards URLs with (this build names none for Jobs at ${(0, environment_1.hostOf)(config.auth.jobsApiUrl)})`);
|
|
12942
14167
|
}
|
|
12943
14168
|
return pass("overlay-hosts", origins.join(", "));
|
|
12944
14169
|
});
|
|
@@ -12969,8 +14194,12 @@ var require_checks = __commonJS({
|
|
|
12969
14194
|
needsFetch(keepReachable),
|
|
12970
14195
|
executorCoverage,
|
|
12971
14196
|
gitCli,
|
|
12972
|
-
|
|
14197
|
+
agentCli(CLAUDE),
|
|
12973
14198
|
claudeAuth,
|
|
14199
|
+
agentCli(CODEX),
|
|
14200
|
+
codexAuth,
|
|
14201
|
+
agentCli(COPILOT),
|
|
14202
|
+
copilotAuth,
|
|
12974
14203
|
runRootWritable,
|
|
12975
14204
|
diskSpace,
|
|
12976
14205
|
overlayHosts,
|
|
@@ -13002,13 +14231,12 @@ var require_checks = __commonJS({
|
|
|
13002
14231
|
}
|
|
13003
14232
|
return `run \`${bin} --version\` to see why ${found} fails, then reinstall it (${missingRemedy})`;
|
|
13004
14233
|
}
|
|
13005
|
-
|
|
13006
|
-
async function atInit(name, probes, subject, body) {
|
|
14234
|
+
async function atInit(name, needs, probes, subject, body) {
|
|
13007
14235
|
const plan = initPlan(probes, subject);
|
|
13008
14236
|
if (!plan.ok)
|
|
13009
14237
|
return skip(name, "the existing config could not be read; see executor-coverage");
|
|
13010
|
-
if (plan.value.executor
|
|
13011
|
-
return skip(name, plan.value.executor === void 0 ? "init configures no executor for this build" : `init configures ${plan.value.executor}, which
|
|
14238
|
+
if (plan.value.executor === void 0 || !needs.includes(plan.value.executor)) {
|
|
14239
|
+
return skip(name, plan.value.executor === void 0 ? "init configures no executor for this build" : `init configures ${plan.value.executor}, which does not need this tool`);
|
|
13012
14240
|
}
|
|
13013
14241
|
return body();
|
|
13014
14242
|
}
|
|
@@ -21233,7 +22461,7 @@ var require_schema4 = __commonJS({
|
|
|
21233
22461
|
message: 'Each pool must specify at least one of "id" or "name"'
|
|
21234
22462
|
});
|
|
21235
22463
|
var executorSchema = zod_1.z.object({
|
|
21236
|
-
type: zod_1.z.enum(["claude-code", "http-api", "anthropic-api", "webhook", "custom-script"])
|
|
22464
|
+
type: zod_1.z.enum(["claude-code", "codex", "copilot", "http-api", "anthropic-api", "webhook", "custom-script"])
|
|
21237
22465
|
}).passthrough();
|
|
21238
22466
|
var shuttleConfigSchema = zod_1.z.object({
|
|
21239
22467
|
auth: zod_1.z.object({
|
|
@@ -21483,11 +22711,14 @@ var require_probes = __commonJS({
|
|
|
21483
22711
|
var jobs_1 = require_dist4();
|
|
21484
22712
|
var scaffold_1 = require_scaffold();
|
|
21485
22713
|
var loader_1 = require_loader();
|
|
22714
|
+
var codex_1 = require_codex();
|
|
22715
|
+
var copilot_1 = require_copilot();
|
|
21486
22716
|
var store_1 = require_store();
|
|
21487
22717
|
var version_check_1 = require_version_check();
|
|
21488
22718
|
var PROBE_TIMEOUT_MS = 1e4;
|
|
21489
22719
|
var VERSION_TIMEOUT_MS = 1e4;
|
|
21490
22720
|
var DEEP_PROBE_PROMPT = "Reply with the single word: ok";
|
|
22721
|
+
var COPILOT_TOKEN_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
|
21491
22722
|
function createProbes(options) {
|
|
21492
22723
|
let observedLatest = null;
|
|
21493
22724
|
const observe = (response) => {
|
|
@@ -21708,6 +22939,24 @@ var require_probes = __commonJS({
|
|
|
21708
22939
|
}
|
|
21709
22940
|
return { ok: true, value: void 0 };
|
|
21710
22941
|
},
|
|
22942
|
+
async codexLoginStatus() {
|
|
22943
|
+
const outcome = await runCapturing("codex", ["login", "status"], VERSION_TIMEOUT_MS);
|
|
22944
|
+
if (outcome.ok) {
|
|
22945
|
+
const line = `${outcome.stdout}
|
|
22946
|
+
${outcome.stderr}`.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
22947
|
+
return { loggedIn: true, detail: line ?? "signed in" };
|
|
22948
|
+
}
|
|
22949
|
+
if (outcome.exited && /not logged in/i.test(outcome.reason)) {
|
|
22950
|
+
return { loggedIn: false, detail: "not logged in" };
|
|
22951
|
+
}
|
|
22952
|
+
return null;
|
|
22953
|
+
},
|
|
22954
|
+
ambientCopilotToken: () => COPILOT_TOKEN_VARS.find((name) => (process.env[name] ?? "").length > 0) ?? null,
|
|
22955
|
+
async agentRoundTrip(agent) {
|
|
22956
|
+
const executor = agent === "codex" ? (0, codex_1.createCodexExecutor)({}) : (0, copilot_1.createCopilotExecutor)({});
|
|
22957
|
+
const readiness = await executor.checkReadiness(AbortSignal.timeout(18e4));
|
|
22958
|
+
return readiness.ready ? { ok: true, value: void 0 } : { ok: false, error: readiness.reason };
|
|
22959
|
+
},
|
|
21711
22960
|
latestVersion: () => observedLatest
|
|
21712
22961
|
};
|
|
21713
22962
|
}
|
|
@@ -21774,7 +23023,7 @@ var require_probes = __commonJS({
|
|
|
21774
23023
|
child.on("close", (code, signal) => {
|
|
21775
23024
|
clearTimeout(timer);
|
|
21776
23025
|
if (code === 0) {
|
|
21777
|
-
settle({ ok: true, stdout: stdout.trim() });
|
|
23026
|
+
settle({ ok: true, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
21778
23027
|
return;
|
|
21779
23028
|
}
|
|
21780
23029
|
const status = code === null ? `was killed by ${signal ?? "a signal"}` : `exited ${code}`;
|
|
@@ -25380,6 +26629,37 @@ var require_logger = __commonJS({
|
|
|
25380
26629
|
}
|
|
25381
26630
|
});
|
|
25382
26631
|
|
|
26632
|
+
// ../../packages/shuttle/dist/core/demux.js
|
|
26633
|
+
var require_demux = __commonJS({
|
|
26634
|
+
"../../packages/shuttle/dist/core/demux.js"(exports2) {
|
|
26635
|
+
"use strict";
|
|
26636
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
26637
|
+
exports2.createDemux = createDemux;
|
|
26638
|
+
function createDemux(executors, pools) {
|
|
26639
|
+
const byStrategy = /* @__PURE__ */ new Map();
|
|
26640
|
+
for (const executor of executors) {
|
|
26641
|
+
for (const strategy of executor.strategies)
|
|
26642
|
+
byStrategy.set(strategy, executor);
|
|
26643
|
+
}
|
|
26644
|
+
const byName = new Map(executors.map((e) => [e.capability.id, e]));
|
|
26645
|
+
const byPool = /* @__PURE__ */ new Map();
|
|
26646
|
+
for (const pool of pools) {
|
|
26647
|
+
const named = byName.get(pool.executor);
|
|
26648
|
+
if (!named)
|
|
26649
|
+
continue;
|
|
26650
|
+
if (pool.id)
|
|
26651
|
+
byPool.set(`id:${pool.id}`, named);
|
|
26652
|
+
if (pool.name)
|
|
26653
|
+
byPool.set(`name:${pool.name}`, named);
|
|
26654
|
+
}
|
|
26655
|
+
return (pool, strategy) => {
|
|
26656
|
+
const named = byPool.get(`id:${pool.id}`) ?? byPool.get(`name:${pool.name}`);
|
|
26657
|
+
return named && named.strategies.includes(strategy) ? named : byStrategy.get(strategy);
|
|
26658
|
+
};
|
|
26659
|
+
}
|
|
26660
|
+
}
|
|
26661
|
+
});
|
|
26662
|
+
|
|
25383
26663
|
// ../../packages/shuttle/dist/observers/logger.js
|
|
25384
26664
|
var require_logger2 = __commonJS({
|
|
25385
26665
|
"../../packages/shuttle/dist/observers/logger.js"(exports2) {
|
|
@@ -25901,6 +27181,9 @@ var require_shuttle = __commonJS({
|
|
|
25901
27181
|
var logger_1 = require_logger();
|
|
25902
27182
|
var registry_1 = require_registry();
|
|
25903
27183
|
var claude_code_1 = require_claude_code();
|
|
27184
|
+
var demux_1 = require_demux();
|
|
27185
|
+
var codex_1 = require_codex();
|
|
27186
|
+
var copilot_1 = require_copilot();
|
|
25904
27187
|
var http_api_1 = require_http_api();
|
|
25905
27188
|
var anthropic_api_1 = require_anthropic_api();
|
|
25906
27189
|
var webhook_1 = require_webhook();
|
|
@@ -26098,11 +27381,7 @@ var require_shuttle = __commonJS({
|
|
|
26098
27381
|
const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
|
|
26099
27382
|
registerBuiltInExecutors(registry, this.brand.executorAllowlist);
|
|
26100
27383
|
const executors = registry.build(mergeExecutorDefaults((0, environment_1.executorDefaultsFor)(this.brand, this.config.auth.jobsApiUrl), this.config.executors));
|
|
26101
|
-
const
|
|
26102
|
-
for (const executor of executors) {
|
|
26103
|
-
for (const strategy of executor.strategies)
|
|
26104
|
-
byStrategy.set(strategy, executor);
|
|
26105
|
-
}
|
|
27384
|
+
const executorFor = (0, demux_1.createDemux)(executors, this.config.pools);
|
|
26106
27385
|
this.spendTracker = (0, spend_tracker_1.createSpendTrackerObserver)(this.brand, paths.spendStateFile);
|
|
26107
27386
|
const observers = [
|
|
26108
27387
|
(0, logger_2.createLoggerObserver)(),
|
|
@@ -26123,7 +27402,7 @@ var require_shuttle = __commonJS({
|
|
|
26123
27402
|
const attendanceId = dispatch.attendanceId;
|
|
26124
27403
|
const startedAt = Date.now();
|
|
26125
27404
|
const strategy = (0, loom_1.readShuttleStrategy)(dispatch.inputs) ?? "";
|
|
26126
|
-
const executor =
|
|
27405
|
+
const executor = executorFor(dispatch.pool, strategy);
|
|
26127
27406
|
if (!executor) {
|
|
26128
27407
|
const reason = `no executor for shuttle strategy "${strategy}"`;
|
|
26129
27408
|
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
@@ -26378,6 +27657,8 @@ var require_shuttle = __commonJS({
|
|
|
26378
27657
|
function registerBuiltInExecutors(registry, allowlist) {
|
|
26379
27658
|
const all = [
|
|
26380
27659
|
["claude-code", claude_code_1.createClaudeCodeExecutor],
|
|
27660
|
+
["codex", codex_1.createCodexExecutor],
|
|
27661
|
+
["copilot", copilot_1.createCopilotExecutor],
|
|
26381
27662
|
["http-api", http_api_1.createHttpApiExecutor],
|
|
26382
27663
|
["anthropic-api", anthropic_api_1.createAnthropicApiExecutor],
|
|
26383
27664
|
["webhook", webhook_1.createWebhookExecutor],
|
|
@@ -26880,8 +28161,8 @@ var require_dist5 = __commonJS({
|
|
|
26880
28161
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
26881
28162
|
"use strict";
|
|
26882
28163
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
26883
|
-
exports2.
|
|
26884
|
-
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = void 0;
|
|
28164
|
+
exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.executorDefaultsFor = exports2.environmentReportJson = exports2.environmentPolledBy = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createCopilotExecutor = exports2.createCodexExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorEnvironmentError = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
|
|
28165
|
+
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = void 0;
|
|
26885
28166
|
var cli_1 = require_cli();
|
|
26886
28167
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
26887
28168
|
return cli_1.createCli;
|
|
@@ -26960,6 +28241,14 @@ var require_dist5 = __commonJS({
|
|
|
26960
28241
|
Object.defineProperty(exports2, "createClaudeCodeExecutor", { enumerable: true, get: function() {
|
|
26961
28242
|
return claude_code_1.createClaudeCodeExecutor;
|
|
26962
28243
|
} });
|
|
28244
|
+
var codex_1 = require_codex();
|
|
28245
|
+
Object.defineProperty(exports2, "createCodexExecutor", { enumerable: true, get: function() {
|
|
28246
|
+
return codex_1.createCodexExecutor;
|
|
28247
|
+
} });
|
|
28248
|
+
var copilot_1 = require_copilot();
|
|
28249
|
+
Object.defineProperty(exports2, "createCopilotExecutor", { enumerable: true, get: function() {
|
|
28250
|
+
return copilot_1.createCopilotExecutor;
|
|
28251
|
+
} });
|
|
26963
28252
|
var http_api_1 = require_http_api();
|
|
26964
28253
|
Object.defineProperty(exports2, "createHttpApiExecutor", { enumerable: true, get: function() {
|
|
26965
28254
|
return http_api_1.createHttpApiExecutor;
|
|
@@ -27086,7 +28375,8 @@ var import_path = require("path");
|
|
|
27086
28375
|
var import_promises = require("fs/promises");
|
|
27087
28376
|
var import_yaml = __toESM(require_dist3());
|
|
27088
28377
|
var import_shuttle = __toESM(require_dist5());
|
|
27089
|
-
var buildVersion = true ? "0.
|
|
28378
|
+
var buildVersion = true ? "0.31.0" : pkg.version;
|
|
28379
|
+
var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
|
|
27090
28380
|
var sifterBrand = {
|
|
27091
28381
|
product: {
|
|
27092
28382
|
id: "sifter",
|
|
@@ -27109,7 +28399,7 @@ var sifterBrand = {
|
|
|
27109
28399
|
// shared config across multiple machines.
|
|
27110
28400
|
configSearchPaths: ["~/.sifter/sifter.yaml"]
|
|
27111
28401
|
},
|
|
27112
|
-
executorAllowlist: ["claude-code", "anthropic-api", "http-api", "webhook", "custom-script"],
|
|
28402
|
+
executorAllowlist: ["claude-code", "codex", "copilot", "anthropic-api", "http-api", "webhook", "custom-script"],
|
|
27113
28403
|
// What `init` configures when neither --executor nor an existing
|
|
27114
28404
|
// sifter.yaml names one. The init pre-flight reads it too, to check git and
|
|
27115
28405
|
// claude before the pairing code is spent.
|
|
@@ -27123,23 +28413,35 @@ var sifterBrand = {
|
|
|
27123
28413
|
// newest of the flags every review passes (`--tools` arrived in 2.0.31;
|
|
27124
28414
|
// the executor's REVIEW_RUN_ARGS names them all), and the hosted image pins
|
|
27125
28415
|
// 2.1.x. Below it a review fails on an unknown option.
|
|
28416
|
+
//
|
|
28417
|
+
// `codex` and `copilot` are floored at the releases their containment was
|
|
28418
|
+
// verified against, and the floor is a containment rule rather than a
|
|
28419
|
+
// compatibility one: an older codex that does not know permission profiles
|
|
28420
|
+
// ignores the review's profile and falls back to a sandbox that reads the
|
|
28421
|
+
// whole disk, and an older copilot may not know a flag that keeps a tool or
|
|
28422
|
+
// a hook out of the run. Both executors are local only.
|
|
27126
28423
|
minimumToolVersions: {
|
|
27127
28424
|
git: "2.20.0",
|
|
27128
|
-
claude: "2.0.45"
|
|
28425
|
+
claude: "2.0.45",
|
|
28426
|
+
codex: "0.162.0",
|
|
28427
|
+
copilot: "1.0.97"
|
|
27129
28428
|
},
|
|
27130
28429
|
// The overlay-fetch allowlist is NOT here: each tier signs its standards
|
|
27131
28430
|
// URLs with its own Sift origin (SIFT_PUBLIC_API_URL), so the origin is a
|
|
27132
28431
|
// property of the tier and lives on its `environments` entry below. A
|
|
27133
28432
|
// Sifter paired to production allows production's origin and nothing else.
|
|
27134
|
-
executorDefaults:
|
|
27135
|
-
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
27142
|
-
|
|
28433
|
+
executorDefaults: Object.fromEntries(
|
|
28434
|
+
AGENT_EXECUTORS.map((executor) => [
|
|
28435
|
+
executor,
|
|
28436
|
+
{
|
|
28437
|
+
// Transcript retention is a diagnostic switch a person flips for one
|
|
28438
|
+
// session, so it rides the environment rather than a file they would
|
|
28439
|
+
// have to remember to unset. The shared package reads no env of its
|
|
28440
|
+
// own (BE-005); this brand is where the variable becomes config.
|
|
28441
|
+
...process.env.SHUTTLE_KEEP_TRANSCRIPTS ? { keepTranscripts: process.env.SHUTTLE_KEEP_TRANSCRIPTS } : {}
|
|
28442
|
+
}
|
|
28443
|
+
])
|
|
28444
|
+
),
|
|
27143
28445
|
// The environments Whittle Labs operates, keyed by the profile name that
|
|
27144
28446
|
// selects one. `init --profile stage` resolves both URLs from here, so a
|
|
27145
28447
|
// non-prod pairing is one flag instead of three and there is no host to
|
|
@@ -27244,7 +28546,7 @@ var sifterBrand = {
|
|
|
27244
28546
|
}
|
|
27245
28547
|
const executorBlock = plan.keptBlock !== void 0 ? {
|
|
27246
28548
|
...plan.keptBlock,
|
|
27247
|
-
...executor
|
|
28549
|
+
...AGENT_EXECUTORS.includes(executor) && ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
27248
28550
|
} : await buildExecutorBlock(executor, ctx);
|
|
27249
28551
|
const doc = {
|
|
27250
28552
|
auth: {
|
|
@@ -27278,13 +28580,15 @@ var sifterBrand = {
|
|
|
27278
28580
|
]
|
|
27279
28581
|
};
|
|
27280
28582
|
function overlayOrigin(origin) {
|
|
27281
|
-
return
|
|
28583
|
+
return Object.fromEntries(AGENT_EXECUTORS.map((e) => [e, { overlayAllowedHosts: [origin] }]));
|
|
27282
28584
|
}
|
|
27283
28585
|
async function buildExecutorBlock(executor, ctx) {
|
|
27284
28586
|
switch (executor) {
|
|
27285
|
-
case "claude-code":
|
|
28587
|
+
case "claude-code":
|
|
28588
|
+
case "codex":
|
|
28589
|
+
case "copilot": {
|
|
27286
28590
|
return {
|
|
27287
|
-
type:
|
|
28591
|
+
type: executor,
|
|
27288
28592
|
...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
27289
28593
|
};
|
|
27290
28594
|
}
|