@whittlelabs/sifter 0.30.23 → 0.32.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 +2315 -783
- 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,1300 @@ 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;
|
|
10057
|
+
}
|
|
10058
|
+
});
|
|
10059
|
+
|
|
10060
|
+
// ../../packages/shuttle/dist/executors/agent-process.js
|
|
10061
|
+
var require_agent_process = __commonJS({
|
|
10062
|
+
"../../packages/shuttle/dist/executors/agent-process.js"(exports2) {
|
|
10063
|
+
"use strict";
|
|
10064
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10065
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10066
|
+
};
|
|
10067
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
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/models.js
|
|
10267
|
+
var require_models = __commonJS({
|
|
10268
|
+
"../../packages/shuttle/dist/executors/models.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.LIST_MODELS_TIMEOUT_MS = exports2.CLAUDE_CODE_EFFORTS = exports2.REASONING_EFFORTS = void 0;
|
|
10275
|
+
exports2.parseReasoningEffort = parseReasoningEffort;
|
|
10276
|
+
exports2.hintedEffort = hintedEffort;
|
|
10277
|
+
exports2.runListing = runListing;
|
|
10278
|
+
var os_1 = __importDefault(require("os"));
|
|
10279
|
+
var agent_process_1 = require_agent_process();
|
|
10280
|
+
exports2.REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
10281
|
+
exports2.CLAUDE_CODE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
10282
|
+
function parseReasoningEffort(value, levels) {
|
|
10283
|
+
if (value === void 0)
|
|
10284
|
+
return void 0;
|
|
10285
|
+
if (!levels.includes(value)) {
|
|
10286
|
+
throw new Error(`"reasoningEffort" must be one of ${levels.join(", ")}`);
|
|
10287
|
+
}
|
|
10288
|
+
return value;
|
|
10289
|
+
}
|
|
10290
|
+
function hintedEffort(hints) {
|
|
10291
|
+
const effort = hints.effort;
|
|
10292
|
+
return typeof effort === "string" && /^[a-z]+$/.test(effort) ? effort : void 0;
|
|
10293
|
+
}
|
|
10294
|
+
exports2.LIST_MODELS_TIMEOUT_MS = 15e3;
|
|
10295
|
+
async function runListing(bin, args, signal, env) {
|
|
10296
|
+
try {
|
|
10297
|
+
const run = await (0, agent_process_1.runAgentProcess)({
|
|
10298
|
+
bin,
|
|
10299
|
+
args,
|
|
10300
|
+
cwd: os_1.default.tmpdir(),
|
|
10301
|
+
...env ? { env } : {},
|
|
10302
|
+
idleTimeoutMs: exports2.LIST_MODELS_TIMEOUT_MS,
|
|
10303
|
+
toolName: bin,
|
|
10304
|
+
signal
|
|
10305
|
+
});
|
|
10306
|
+
return run.spawnError || run.code !== 0 ? null : run.stdout;
|
|
10307
|
+
} catch {
|
|
10308
|
+
return null;
|
|
10309
|
+
}
|
|
10310
|
+
}
|
|
10311
|
+
}
|
|
10312
|
+
});
|
|
10313
|
+
|
|
10314
|
+
// ../../packages/shuttle/dist/executors/workspace-telemetry.js
|
|
10315
|
+
var require_workspace_telemetry = __commonJS({
|
|
10316
|
+
"../../packages/shuttle/dist/executors/workspace-telemetry.js"(exports2) {
|
|
10317
|
+
"use strict";
|
|
10318
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10319
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10320
|
+
};
|
|
10321
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10322
|
+
exports2.workspaceOutputs = workspaceOutputs;
|
|
10323
|
+
exports2.relativeWorkspaceReads = relativeWorkspaceReads;
|
|
10324
|
+
exports2.globToRegExp = globToRegExp;
|
|
10325
|
+
exports2.creditOverlayReads = creditOverlayReads;
|
|
10326
|
+
exports2.shellReadPaths = shellReadPaths;
|
|
10327
|
+
var fs_1 = require("fs");
|
|
10328
|
+
var path_1 = __importDefault(require("path"));
|
|
10329
|
+
function workspaceOutputs(workspace, runCwd, readPaths, toolInputs) {
|
|
10330
|
+
const reads = [...readPaths];
|
|
10331
|
+
for (const rel of creditOverlayReads(toolInputs, workspace.overlay?.writtenPaths ?? [], runCwd)) {
|
|
10332
|
+
reads.push(path_1.default.join(runCwd, rel));
|
|
10333
|
+
}
|
|
10334
|
+
return [
|
|
10335
|
+
{ label: "workspace_reads", content: { paths: relativeWorkspaceReads(reads, runCwd) } },
|
|
10336
|
+
{
|
|
10337
|
+
// Structured audit trail: reviewers and operators can see whether a
|
|
10338
|
+
// given review actually explored the checkout or fell back to the
|
|
10339
|
+
// diff (and why, including a failed private-repo auth), plus which
|
|
10340
|
+
// overlay files landed.
|
|
10341
|
+
label: "workspace",
|
|
10342
|
+
content: {
|
|
10343
|
+
materialized: workspace.materialized,
|
|
10344
|
+
...workspace.warning ? { warning: workspace.warning } : {},
|
|
10345
|
+
...workspace.context.length > 0 ? {
|
|
10346
|
+
context: workspace.context.map((c) => ({
|
|
10347
|
+
name: c.requestedName,
|
|
10348
|
+
materialized: c.materialized,
|
|
10349
|
+
...c.warning ? { warning: c.warning } : {}
|
|
10350
|
+
}))
|
|
10351
|
+
} : {},
|
|
10352
|
+
...workspace.overlay ? {
|
|
10353
|
+
overlay: {
|
|
10354
|
+
written: workspace.overlay.written,
|
|
10355
|
+
...workspace.overlay.skipped.length > 0 ? { skipped: workspace.overlay.skipped } : {}
|
|
10356
|
+
}
|
|
10357
|
+
} : {}
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
];
|
|
10361
|
+
}
|
|
10362
|
+
var MAX_REPORTED_READS = 2e3;
|
|
10363
|
+
function workspaceRoots(cwd) {
|
|
10364
|
+
const root = path_1.default.resolve(cwd);
|
|
10365
|
+
const roots = [root];
|
|
10366
|
+
try {
|
|
10367
|
+
const real = (0, fs_1.realpathSync)(root);
|
|
10368
|
+
if (real !== root)
|
|
10369
|
+
roots.push(real);
|
|
10370
|
+
} catch {
|
|
10371
|
+
}
|
|
10372
|
+
return roots;
|
|
10373
|
+
}
|
|
10374
|
+
function relativeWorkspaceReads(readPaths, cwd) {
|
|
10375
|
+
const roots = workspaceRoots(cwd);
|
|
10376
|
+
const root = roots[0];
|
|
10377
|
+
const out = /* @__PURE__ */ new Set();
|
|
10378
|
+
for (const p of readPaths) {
|
|
10379
|
+
const resolved = path_1.default.resolve(root, p);
|
|
10380
|
+
for (const r of roots) {
|
|
10381
|
+
if (resolved === r)
|
|
10382
|
+
break;
|
|
10383
|
+
if (!resolved.startsWith(r + path_1.default.sep))
|
|
10384
|
+
continue;
|
|
10385
|
+
out.add(resolved.slice(r.length + 1));
|
|
10386
|
+
break;
|
|
10387
|
+
}
|
|
10388
|
+
if (out.size >= MAX_REPORTED_READS)
|
|
10389
|
+
break;
|
|
10390
|
+
}
|
|
10391
|
+
return [...out].sort();
|
|
10392
|
+
}
|
|
10393
|
+
var GLOB_CHARS = /[*?[{]/;
|
|
10394
|
+
function escapeRegExp(s) {
|
|
10395
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10396
|
+
}
|
|
10397
|
+
function globToRegExp(glob) {
|
|
10398
|
+
let re = "^";
|
|
10399
|
+
for (let i = 0; i < glob.length; i++) {
|
|
10400
|
+
const ch = glob[i];
|
|
10401
|
+
if (ch === "*") {
|
|
10402
|
+
if (glob[i + 1] === "*") {
|
|
10403
|
+
if (glob[i + 2] === "/") {
|
|
10404
|
+
re += "(?:.*/)?";
|
|
10405
|
+
i += 2;
|
|
10406
|
+
} else {
|
|
10407
|
+
re += ".*";
|
|
10408
|
+
i += 1;
|
|
10409
|
+
}
|
|
10410
|
+
} else {
|
|
10411
|
+
re += "[^/]*";
|
|
10412
|
+
}
|
|
10413
|
+
} else if (ch === "?") {
|
|
10414
|
+
re += "[^/]";
|
|
10415
|
+
} else if (ch === "[") {
|
|
10416
|
+
const end = glob.indexOf("]", i + 1);
|
|
10417
|
+
if (end === -1)
|
|
10418
|
+
return null;
|
|
10419
|
+
let cls = glob.slice(i + 1, end);
|
|
10420
|
+
if (cls.startsWith("!"))
|
|
10421
|
+
cls = `^${cls.slice(1)}`;
|
|
10422
|
+
re += `[${cls.replace(/\\/g, "\\\\")}]`;
|
|
10423
|
+
i = end;
|
|
10424
|
+
} else if (ch === "{") {
|
|
10425
|
+
const end = glob.indexOf("}", i + 1);
|
|
10426
|
+
if (end === -1)
|
|
10427
|
+
return null;
|
|
10428
|
+
re += `(?:${glob.slice(i + 1, end).split(",").map(escapeRegExp).join("|")})`;
|
|
10429
|
+
i = end;
|
|
10430
|
+
} else {
|
|
10431
|
+
re += escapeRegExp(ch);
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
return new RegExp(`${re}$`);
|
|
10435
|
+
}
|
|
10436
|
+
function creditOverlayReads(toolInputs, writtenPaths, runCwd) {
|
|
10437
|
+
if (writtenPaths.length === 0 || toolInputs.length === 0)
|
|
10438
|
+
return [];
|
|
10439
|
+
const roots = runCwd ? workspaceRoots(runCwd) : [];
|
|
10440
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10441
|
+
for (const rel of writtenPaths) {
|
|
10442
|
+
const parts = rel.split("/");
|
|
10443
|
+
for (let i = 1; i < parts.length; i++)
|
|
10444
|
+
dirs.add(`${parts.slice(0, i).join("/")}/`);
|
|
10445
|
+
}
|
|
10446
|
+
const dirAlt = [...dirs].map(escapeRegExp).join("|");
|
|
10447
|
+
const wordRe = dirs.size > 0 ? new RegExp(`(?:${dirAlt})[^\\s"'\`;|&<>()\\\\]*`, "g") : null;
|
|
10448
|
+
const findRe = dirs.size > 0 ? new RegExp(`\\bfind\\s+(?:\\./)?(${[...dirs].map((d) => escapeRegExp(d.slice(0, -1))).join("|")})/?(?=\\s|$)`, "g") : null;
|
|
10449
|
+
const readerRe = new RegExp(`(?:^|[\\s"])(?:-exec|-execdir|xargs)\\b[^;|&]*\\b${READERS}\\b`);
|
|
10450
|
+
const credited = /* @__PURE__ */ new Set();
|
|
10451
|
+
for (const raw of toolInputs) {
|
|
10452
|
+
const input = roots.reduce((s, r) => s.split(`${r}/`).join(""), raw);
|
|
10453
|
+
for (const rel of writtenPaths) {
|
|
10454
|
+
if (input.includes(rel))
|
|
10455
|
+
credited.add(rel);
|
|
10456
|
+
}
|
|
10457
|
+
if (wordRe) {
|
|
10458
|
+
for (const match of input.matchAll(wordRe)) {
|
|
10459
|
+
const word = match[0];
|
|
10460
|
+
if (!GLOB_CHARS.test(word))
|
|
10461
|
+
continue;
|
|
10462
|
+
const re = globToRegExp(word);
|
|
10463
|
+
if (!re)
|
|
10464
|
+
continue;
|
|
10465
|
+
for (const rel of writtenPaths) {
|
|
10466
|
+
if (re.test(rel))
|
|
10467
|
+
credited.add(rel);
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
if (findRe && readerRe.test(input)) {
|
|
10472
|
+
for (const match of input.matchAll(findRe)) {
|
|
10473
|
+
const dir = `${match[1]}/`;
|
|
10474
|
+
for (const rel of writtenPaths) {
|
|
10475
|
+
if (rel.startsWith(dir))
|
|
10476
|
+
credited.add(rel);
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
if (!PLAIN_READER_RE.test(input))
|
|
10481
|
+
continue;
|
|
10482
|
+
for (const match of input.matchAll(CD_RE)) {
|
|
10483
|
+
const target = match[1].replace(/^\.\//, "").replace(/\/+$/, "");
|
|
10484
|
+
const base = target.length > 0 ? `${target}/` : "";
|
|
10485
|
+
for (const rel of writtenPaths) {
|
|
10486
|
+
if (!rel.startsWith(base))
|
|
10487
|
+
continue;
|
|
10488
|
+
if (namesBareWord(input, rel.slice(base.length)))
|
|
10489
|
+
credited.add(rel);
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
return [...credited].sort();
|
|
10494
|
+
}
|
|
10495
|
+
var READERS = "(?:cat|head|tail|sed|awk|less|more|bat)";
|
|
10496
|
+
var PLAIN_READER_RE = new RegExp(`(?:^|[\\s"'\`;|&(])${READERS}\\b`);
|
|
10497
|
+
var CD_RE = /(?:^|[\s"'`;|&(])cd\s+(?:--\s+)?["']?([^\s"'`;|&<>()\\]+)/g;
|
|
10498
|
+
function namesBareWord(input, word) {
|
|
10499
|
+
const edge = String.raw`[\s"'\`;|&<>(),\\]`;
|
|
10500
|
+
return new RegExp(`(?:^|${edge})${escapeRegExp(word)}(?=$|${edge})`).test(input);
|
|
10501
|
+
}
|
|
10502
|
+
function shellReadPaths(commands, runCwd) {
|
|
10503
|
+
const roots = workspaceRoots(runCwd);
|
|
10504
|
+
const out = /* @__PURE__ */ new Set();
|
|
10505
|
+
for (const command of commands) {
|
|
10506
|
+
if (!PLAIN_READER_RE.test(command))
|
|
10507
|
+
continue;
|
|
10508
|
+
for (const raw of command.split(/[\s"'`;|&<>()]+/)) {
|
|
10509
|
+
const word = raw.replace(/^\.\//, "");
|
|
10510
|
+
if (word.length === 0 || word.startsWith("-") || GLOB_CHARS.test(word))
|
|
10511
|
+
continue;
|
|
10512
|
+
const abs = path_1.default.resolve(roots[0], roots.reduce((w, r) => w.startsWith(`${r}/`) ? w.slice(r.length + 1) : w, word));
|
|
10513
|
+
if (!roots.some((r) => abs.startsWith(r + path_1.default.sep)))
|
|
10514
|
+
continue;
|
|
10515
|
+
try {
|
|
10516
|
+
if ((0, fs_1.statSync)(abs).isFile())
|
|
10517
|
+
out.add(abs);
|
|
10518
|
+
} catch {
|
|
10519
|
+
}
|
|
10520
|
+
if (out.size >= MAX_REPORTED_READS)
|
|
10521
|
+
return [...out];
|
|
10522
|
+
}
|
|
10523
|
+
}
|
|
10524
|
+
return [...out];
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
});
|
|
10528
|
+
|
|
10529
|
+
// ../../packages/shuttle/dist/executors/claude-code.js
|
|
10530
|
+
var require_claude_code = __commonJS({
|
|
10531
|
+
"../../packages/shuttle/dist/executors/claude-code.js"(exports2) {
|
|
10532
|
+
"use strict";
|
|
10533
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10534
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10535
|
+
};
|
|
10536
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10537
|
+
exports2.classifyEnvironmentFault = classifyEnvironmentFault;
|
|
10538
|
+
exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
|
|
10539
|
+
exports2.checkoutScopedTools = checkoutScopedTools;
|
|
10540
|
+
exports2.parseStreamJson = parseStreamJson;
|
|
10541
|
+
var fs_1 = require("fs");
|
|
10542
|
+
var os_1 = __importDefault(require("os"));
|
|
10543
|
+
var path_1 = __importDefault(require("path"));
|
|
10544
|
+
var loom_1 = require_dist();
|
|
10545
|
+
var types_1 = require_types2();
|
|
10546
|
+
var validate_output_1 = require_validate_output();
|
|
10547
|
+
var progress_1 = require_progress();
|
|
10548
|
+
var agent_process_1 = require_agent_process();
|
|
10549
|
+
var json_answer_1 = require_json_answer();
|
|
10550
|
+
var models_1 = require_models();
|
|
10551
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
10552
|
+
var apply_1 = require_apply();
|
|
10553
|
+
var DEFAULT_TIMEOUT = 6e5;
|
|
10554
|
+
var REVIEW_TOOLS = ["Read", "Grep", "Glob"];
|
|
10555
|
+
function reviewRunArgs(settingSources) {
|
|
10556
|
+
return [
|
|
10557
|
+
"--setting-sources",
|
|
10558
|
+
settingSources,
|
|
10559
|
+
"--strict-mcp-config",
|
|
10560
|
+
"--tools",
|
|
10561
|
+
REVIEW_TOOLS.join(","),
|
|
10562
|
+
"--permission-mode",
|
|
10563
|
+
"default",
|
|
10564
|
+
"--settings",
|
|
10565
|
+
JSON.stringify({ disableAllHooks: true })
|
|
10566
|
+
];
|
|
10567
|
+
}
|
|
10568
|
+
var RETIRED_READ_TOOLS = ["LS"];
|
|
10569
|
+
var HOSTED_ENV_PASSTHROUGH = [
|
|
10570
|
+
"PATH",
|
|
10571
|
+
"HOME",
|
|
10572
|
+
"TMPDIR",
|
|
10573
|
+
"LANG",
|
|
10574
|
+
"LC_ALL",
|
|
10575
|
+
"LC_CTYPE",
|
|
10576
|
+
"TZ",
|
|
10577
|
+
"NODE_EXTRA_CA_CERTS",
|
|
10578
|
+
"SSL_CERT_FILE",
|
|
10579
|
+
"SSL_CERT_DIR",
|
|
10580
|
+
"HTTPS_PROXY",
|
|
10581
|
+
"HTTP_PROXY",
|
|
10582
|
+
"NO_PROXY",
|
|
10583
|
+
"https_proxy",
|
|
10584
|
+
"http_proxy",
|
|
10585
|
+
"no_proxy"
|
|
10586
|
+
];
|
|
10587
|
+
function describeClaudeFailure(meta, fallback) {
|
|
10588
|
+
if (!meta)
|
|
10589
|
+
return fallback;
|
|
10590
|
+
const status = meta.apiErrorStatus !== null ? ` (provider HTTP ${meta.apiErrorStatus})` : "";
|
|
10591
|
+
if (meta.detail)
|
|
10592
|
+
return `${meta.detail}${status}`;
|
|
10593
|
+
const subtype = meta.subtype && meta.subtype !== "success" ? `: ${meta.subtype}` : "";
|
|
10594
|
+
return `${fallback}${subtype}${status}`;
|
|
10595
|
+
}
|
|
10596
|
+
var CLAUDE_CODE_MODELS = [
|
|
10597
|
+
{ id: "opus", label: "Claude Opus (latest)", efforts: [...models_1.CLAUDE_CODE_EFFORTS] },
|
|
10598
|
+
{ id: "fable", label: "Claude Fable (latest)", efforts: [...models_1.CLAUDE_CODE_EFFORTS] },
|
|
10599
|
+
{ id: "sonnet", label: "Claude Sonnet (latest)", efforts: [...models_1.CLAUDE_CODE_EFFORTS] },
|
|
10600
|
+
{ id: "haiku", label: "Claude Haiku (latest)", efforts: [...models_1.CLAUDE_CODE_EFFORTS] }
|
|
10601
|
+
];
|
|
10602
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
10603
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
10604
|
+
var INSTALL_REMEDY = "install Claude Code (npm install -g @anthropic-ai/claude-code) where this process can find it on PATH";
|
|
10605
|
+
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";
|
|
10606
|
+
var LIMIT_REMEDY = "wait for the plan's usage limit to reset, or raise it";
|
|
10607
|
+
var SIGNED_OUT = /invalid api key|please run \/login|not (?:logged|signed) in|oauth token (?:has )?expired|authentication_error|invalid x-api-key/i;
|
|
10608
|
+
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;
|
|
10609
|
+
var CLAUDE_FAULTS = {
|
|
10610
|
+
installRemedy: INSTALL_REMEDY,
|
|
10611
|
+
loginRemedy: LOGIN_REMEDY,
|
|
10612
|
+
limitRemedy: LIMIT_REMEDY,
|
|
10613
|
+
signedOut: SIGNED_OUT,
|
|
10614
|
+
outOfUsage: OUT_OF_USAGE
|
|
10615
|
+
};
|
|
10616
|
+
function classifyEnvironmentFault(failure) {
|
|
10617
|
+
return (0, agent_process_1.classifyAgentFault)(failure, CLAUDE_FAULTS);
|
|
10618
|
+
}
|
|
10619
|
+
function runFailure(local, message, facts) {
|
|
10620
|
+
return (0, agent_process_1.agentFailure)(local, message, facts, CLAUDE_FAULTS);
|
|
10621
|
+
}
|
|
10622
|
+
var ClaudeCodeExecutor = class {
|
|
10623
|
+
capability;
|
|
10624
|
+
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
10625
|
+
config;
|
|
10626
|
+
constructor(config) {
|
|
10627
|
+
this.config = config;
|
|
10628
|
+
this.capability = { id: config.capabilityId };
|
|
10629
|
+
}
|
|
10630
|
+
estimate(dispatch) {
|
|
10631
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
10632
|
+
return {
|
|
10633
|
+
inputTokens: spec.costCapHint?.estimatedInputTokens,
|
|
10634
|
+
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
10635
|
+
};
|
|
10636
|
+
}
|
|
10637
|
+
/**
|
|
10638
|
+
* Declare the run environment to the runner (ADR 0026): the opaque workspace
|
|
10639
|
+
* bag plus the materialization config (tmp dir, overlay allowlist). No
|
|
10640
|
+
* credentials here (ADR 0027) — the runner's broker reveals them on demand:
|
|
10641
|
+
* this executor pulls its model key from `ctx.credentials` in `execute()`,
|
|
10642
|
+
* and the git request references its own fetch credential. The executor
|
|
10643
|
+
* touches neither git nor Keep.
|
|
10644
|
+
*/
|
|
10645
|
+
async prepareEnvironment(dispatch) {
|
|
10646
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
10647
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : void 0;
|
|
10648
|
+
return {
|
|
10649
|
+
...spec.workspace ? { workspaceRequest: spec.workspace } : {},
|
|
10650
|
+
materializeOptions: {
|
|
10651
|
+
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
10652
|
+
overlayAllowedHosts: this.config.overlayAllowedHosts ?? []
|
|
10653
|
+
}
|
|
10654
|
+
};
|
|
10655
|
+
}
|
|
10656
|
+
async execute(dispatch, signal, ctx) {
|
|
10657
|
+
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
10658
|
+
const workspace = ctx?.workspace ?? null;
|
|
10659
|
+
let modelKey = null;
|
|
10660
|
+
if (ctx?.credentials) {
|
|
10661
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "anthropic-api");
|
|
10662
|
+
const customerId = typeof hints.customerId === "string" ? hints.customerId : void 0;
|
|
10663
|
+
if (!customerId) {
|
|
10664
|
+
throw new Error("claude-code dynamic-key mode requires providerHints.anthropicApi.customerId on every job");
|
|
10665
|
+
}
|
|
10666
|
+
modelKey = await ctx.credentials.reveal({ name: "managed-model-key", principal: customerId });
|
|
10667
|
+
}
|
|
10668
|
+
let jobScratch = null;
|
|
10669
|
+
let configDir;
|
|
10670
|
+
if (modelKey) {
|
|
10671
|
+
jobScratch = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-cc-"));
|
|
10672
|
+
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
10673
|
+
await fs_1.promises.mkdir(configDir);
|
|
10674
|
+
}
|
|
10675
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
10676
|
+
let promptOnlyScratch = null;
|
|
10677
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
10678
|
+
try {
|
|
10679
|
+
const childEnv = buildChildEnv(modelKey, configDir);
|
|
10680
|
+
const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
|
|
10681
|
+
const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
|
|
10682
|
+
const effort = (0, models_1.hintedEffort)(claudeHints);
|
|
10683
|
+
if (!workspace) {
|
|
10684
|
+
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
10685
|
+
}
|
|
10686
|
+
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
10687
|
+
const { response, readPaths, toolInputs } = await this.runClaude(
|
|
10688
|
+
prompt,
|
|
10689
|
+
spec,
|
|
10690
|
+
runCwd,
|
|
10691
|
+
childEnv,
|
|
10692
|
+
signal,
|
|
10693
|
+
hintedModel,
|
|
10694
|
+
reporter,
|
|
10695
|
+
void 0,
|
|
10696
|
+
dispatch.job.id,
|
|
10697
|
+
// Local mode is the operator's own login: a failure of THAT is the
|
|
10698
|
+
// machine's, and the runner pauses on it. With a revealed key it is one
|
|
10699
|
+
// customer's, and only their job fails.
|
|
10700
|
+
modelKey === null,
|
|
10701
|
+
effort
|
|
10702
|
+
);
|
|
10703
|
+
if (workspace) {
|
|
10704
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, readPaths, toolInputs));
|
|
10705
|
+
}
|
|
10706
|
+
return response;
|
|
10707
|
+
} finally {
|
|
10708
|
+
reporter?.close();
|
|
10709
|
+
if (promptOnlyScratch)
|
|
10710
|
+
await fs_1.promises.rm(promptOnlyScratch, { recursive: true, force: true }).catch(() => {
|
|
10711
|
+
});
|
|
10712
|
+
if (jobScratch)
|
|
10713
|
+
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
10714
|
+
});
|
|
10715
|
+
}
|
|
10716
|
+
}
|
|
10717
|
+
/**
|
|
10718
|
+
* In-session gate repair (ADR 0017): resume the session `execute()` returned a
|
|
10719
|
+
* handle for and correct its output. The repair turn works from the resumed
|
|
10720
|
+
* session's own context (no checkout needed), under the same `--json-schema`
|
|
10721
|
+
* constraint. A throwaway cwd hosts the run; the same handle rides back out so
|
|
10722
|
+
* a second repair round can resume again.
|
|
10723
|
+
*/
|
|
10724
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
10725
|
+
const handle = continuation;
|
|
10726
|
+
if (!handle || typeof handle.sessionId !== "string") {
|
|
10727
|
+
throw new Error("claude-code continue: missing session to resume");
|
|
10728
|
+
}
|
|
10729
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
10730
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-resume-"));
|
|
10731
|
+
try {
|
|
10732
|
+
const spec = {
|
|
10733
|
+
rendering: "producer",
|
|
10734
|
+
outputLabel: handle.outputLabel,
|
|
10735
|
+
outputSchema: handle.outputSchema
|
|
10736
|
+
};
|
|
10737
|
+
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId, `${handle.sessionId}-repair`, handle.childEnv === void 0, handle.effort);
|
|
10738
|
+
return response;
|
|
10739
|
+
} finally {
|
|
10740
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
10741
|
+
});
|
|
10742
|
+
}
|
|
10743
|
+
}
|
|
10744
|
+
/**
|
|
10745
|
+
* Can this machine run a job now? One real one-turn call through the CLI,
|
|
10746
|
+
* the same probe `doctor --deep` makes. The runner asks it only while
|
|
10747
|
+
* claiming is paused on an `ExecutorEnvironmentError`, and only in local
|
|
10748
|
+
* mode. A signed-out or missing CLI fails before any model call, so the
|
|
10749
|
+
* re-check spends nothing until the login works again, and then one turn.
|
|
10750
|
+
*/
|
|
10751
|
+
async checkReadiness(signal) {
|
|
10752
|
+
let run;
|
|
10753
|
+
try {
|
|
10754
|
+
run = await (0, agent_process_1.runAgentProcess)({
|
|
10755
|
+
bin: "claude",
|
|
10756
|
+
args: ["--print", "--output-format", "stream-json", "--verbose", "--max-turns", "1", READINESS_PROMPT],
|
|
10757
|
+
cwd: process.cwd(),
|
|
10758
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
10759
|
+
toolName: "the claude CLI",
|
|
10760
|
+
signal
|
|
10761
|
+
});
|
|
10762
|
+
} catch (err) {
|
|
10763
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10764
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
10765
|
+
}
|
|
10766
|
+
if (run.spawnError) {
|
|
10767
|
+
return {
|
|
10768
|
+
ready: false,
|
|
10769
|
+
reason: run.spawnError.code === "ENOENT" ? `the claude CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
10770
|
+
};
|
|
10771
|
+
}
|
|
10772
|
+
let meta = null;
|
|
10773
|
+
try {
|
|
10774
|
+
meta = parseStreamJson(run.stdout).resultMeta;
|
|
10775
|
+
} catch {
|
|
10776
|
+
meta = null;
|
|
10777
|
+
}
|
|
10778
|
+
if (run.code === 0 && !meta?.isError)
|
|
10779
|
+
return { ready: true };
|
|
10780
|
+
const said = meta?.detail ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
10781
|
+
return { ready: false, reason: said ?? `the claude CLI exited with code ${run.code}` };
|
|
10782
|
+
}
|
|
10783
|
+
/** The CLI's aliases (see CLAUDE_CODE_MODELS); nothing to ask, so nothing can fail. */
|
|
10784
|
+
async listModels() {
|
|
10785
|
+
return CLAUDE_CODE_MODELS;
|
|
10786
|
+
}
|
|
10787
|
+
async runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName, local = false, effortOverride) {
|
|
10788
|
+
const outputLabel = spec.outputLabel;
|
|
10789
|
+
const model = modelOverride ?? this.config.model;
|
|
10790
|
+
const effort = effortOverride ?? this.config.reasoningEffort;
|
|
10791
|
+
const allowedTools = local ? this.config.allowedTools : checkoutScopedTools(this.config.allowedTools);
|
|
10792
|
+
if (!local && this.config.claudeSettings === "user") {
|
|
10793
|
+
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");
|
|
10794
|
+
}
|
|
10795
|
+
const settingSources = local && this.config.claudeSettings === "user" ? "user" : "";
|
|
10796
|
+
const args = ["--print", "--output-format", "stream-json", "--verbose", ...reviewRunArgs(settingSources)];
|
|
10797
|
+
if (resumeSessionId)
|
|
10798
|
+
args.push("--resume", resumeSessionId);
|
|
10799
|
+
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
10800
|
+
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
10801
|
+
}
|
|
10802
|
+
if (model)
|
|
10803
|
+
args.push("--model", model);
|
|
10804
|
+
if (effort)
|
|
10805
|
+
args.push("--effort", effort);
|
|
10806
|
+
if (this.config.maxTurns !== void 0)
|
|
10807
|
+
args.push("--max-turns", String(this.config.maxTurns));
|
|
10808
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
10809
|
+
args.push("--allowedTools", allowedTools.join(","));
|
|
10810
|
+
}
|
|
10811
|
+
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
10812
|
+
onReasoning: (t) => reporter.reasoning(t),
|
|
10813
|
+
onTool: (name, target) => reporter.tool(name, target),
|
|
10814
|
+
onResult: () => reporter.flush()
|
|
10815
|
+
}) : null;
|
|
10816
|
+
const run = await (0, agent_process_1.runAgentProcess)({
|
|
10817
|
+
bin: "claude",
|
|
10818
|
+
args,
|
|
10819
|
+
cwd,
|
|
10820
|
+
// Undefined in local mode → inherit the ambient env (and its
|
|
10821
|
+
// `~/.claude` login) unchanged.
|
|
10822
|
+
env: childEnv,
|
|
10823
|
+
stdin: prompt,
|
|
10824
|
+
idleTimeoutMs: this.config.timeout ?? DEFAULT_TIMEOUT,
|
|
10825
|
+
toolName: "Claude Code",
|
|
10826
|
+
signal,
|
|
10827
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
10828
|
+
});
|
|
10829
|
+
tap?.end();
|
|
10830
|
+
if (run.spawnError) {
|
|
10831
|
+
if (local && run.spawnError.code === "ENOENT") {
|
|
10832
|
+
throw new types_1.ExecutorEnvironmentError(`the claude CLI is not on this machine's PATH (${run.spawnError.message})`, INSTALL_REMEDY);
|
|
10833
|
+
}
|
|
10834
|
+
throw new Error(run.spawnError.message);
|
|
10835
|
+
}
|
|
10836
|
+
const { stdout, stderr: stderrText, code } = run;
|
|
10837
|
+
if (this.config.keepTranscripts && transcriptName) {
|
|
10838
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), transcriptName, stdout);
|
|
10839
|
+
}
|
|
10840
|
+
if (code !== 0) {
|
|
10841
|
+
let meta = null;
|
|
10842
|
+
try {
|
|
10843
|
+
meta = parseStreamJson(stdout).resultMeta;
|
|
10844
|
+
} catch {
|
|
10845
|
+
}
|
|
10846
|
+
const said = meta?.detail ?? (0, agent_process_1.firstLine)(stderrText) ?? (0, agent_process_1.firstLine)(stdout);
|
|
10847
|
+
const fallback = `Claude Code exited with code ${code}`;
|
|
10848
|
+
const failure = runFailure(local, describeClaudeFailure(meta, fallback), {
|
|
10849
|
+
apiErrorStatus: meta?.apiErrorStatus ?? null,
|
|
10850
|
+
text: said
|
|
10851
|
+
});
|
|
10852
|
+
if (failure instanceof types_1.ExecutorEnvironmentError && !meta && said) {
|
|
10853
|
+
throw new types_1.ExecutorEnvironmentError(`${fallback}: ${said}`, failure.remedy);
|
|
10854
|
+
}
|
|
10855
|
+
throw failure;
|
|
10856
|
+
}
|
|
10857
|
+
const stream = parseStreamJson(stdout);
|
|
10858
|
+
if (stream.resultMeta?.isError) {
|
|
10859
|
+
throw runFailure(local, describeClaudeFailure(stream.resultMeta, "Claude Code reported a failure"), {
|
|
10860
|
+
apiErrorStatus: stream.resultMeta.apiErrorStatus,
|
|
10861
|
+
text: stream.resultMeta.detail ?? (0, agent_process_1.firstLine)(stderrText)
|
|
10862
|
+
});
|
|
10863
|
+
}
|
|
10864
|
+
const answerText = stream.finalText ?? stdout;
|
|
10865
|
+
const parsed = stream.structuredOutput ?? (0, json_answer_1.extractJsonObject)(answerText);
|
|
10866
|
+
if (parsed === void 0 && stream.resultMeta) {
|
|
10867
|
+
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))})`);
|
|
10868
|
+
}
|
|
10869
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
10870
|
+
return {
|
|
10871
|
+
response: {
|
|
10872
|
+
outputs: [{ label: outputLabel, content: parsed ?? { raw: answerText } }],
|
|
10873
|
+
rawText: answerText,
|
|
10874
|
+
parsed,
|
|
10875
|
+
usage: {
|
|
10876
|
+
aiProvider: "claude-code",
|
|
10877
|
+
// The model that ran (an alias resolved), over the one asked for.
|
|
10878
|
+
...stream.model ?? model ? { aiModel: stream.model ?? model } : {},
|
|
10879
|
+
...effort ? { aiEffort: effort } : {},
|
|
10880
|
+
...stream.usage ?? {}
|
|
10881
|
+
},
|
|
10882
|
+
// In-session repair handle (ADR 0017): present only when the run
|
|
10883
|
+
// reported a session id, so the runner can `--resume` it.
|
|
10884
|
+
...stream.sessionId ? {
|
|
10885
|
+
continuation: {
|
|
10886
|
+
sessionId: stream.sessionId,
|
|
10887
|
+
model,
|
|
10888
|
+
effort,
|
|
10889
|
+
childEnv,
|
|
10890
|
+
outputLabel,
|
|
10891
|
+
outputSchema: spec.outputSchema
|
|
10892
|
+
}
|
|
10893
|
+
} : {}
|
|
10894
|
+
},
|
|
10895
|
+
readPaths: stream.readPaths,
|
|
10896
|
+
toolInputs: stream.toolInputs
|
|
10897
|
+
};
|
|
10898
|
+
}
|
|
10899
|
+
};
|
|
10900
|
+
function createClaudeCodeExecutor(config, capabilityId = "claude-code") {
|
|
10901
|
+
const validated = { capabilityId };
|
|
10902
|
+
if (config.tmpDir !== void 0) {
|
|
10903
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
10904
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
10905
|
+
}
|
|
10906
|
+
validated.tmpDir = config.tmpDir;
|
|
10907
|
+
}
|
|
10908
|
+
if (config.model !== void 0) {
|
|
10909
|
+
if (typeof config.model !== "string")
|
|
10910
|
+
throw new Error('"model" must be a string');
|
|
10911
|
+
validated.model = config.model;
|
|
10912
|
+
}
|
|
10913
|
+
const effort = (0, models_1.parseReasoningEffort)(config.reasoningEffort, models_1.CLAUDE_CODE_EFFORTS);
|
|
10914
|
+
if (effort)
|
|
10915
|
+
validated.reasoningEffort = effort;
|
|
10916
|
+
if (config.maxTurns !== void 0) {
|
|
10917
|
+
if (typeof config.maxTurns !== "number" || !Number.isInteger(config.maxTurns) || config.maxTurns < 1) {
|
|
10918
|
+
throw new Error('"maxTurns" must be a positive integer');
|
|
10919
|
+
}
|
|
10920
|
+
validated.maxTurns = config.maxTurns;
|
|
10921
|
+
}
|
|
10922
|
+
if (config.allowedTools !== void 0) {
|
|
10923
|
+
if (!Array.isArray(config.allowedTools) || !config.allowedTools.every((t) => typeof t === "string")) {
|
|
10924
|
+
throw new Error('"allowedTools" must be an array of strings');
|
|
10925
|
+
}
|
|
10926
|
+
validated.allowedTools = config.allowedTools;
|
|
10927
|
+
}
|
|
10928
|
+
if (config.timeout !== void 0) {
|
|
10929
|
+
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
10930
|
+
throw new Error('"timeout" must be a positive number');
|
|
10931
|
+
}
|
|
10932
|
+
validated.timeout = config.timeout;
|
|
10933
|
+
}
|
|
10934
|
+
if (config.keepTranscripts !== void 0) {
|
|
10935
|
+
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
10936
|
+
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
10937
|
+
}
|
|
10938
|
+
validated.keepTranscripts = config.keepTranscripts;
|
|
10939
|
+
}
|
|
10940
|
+
if (config.claudeSettings !== void 0) {
|
|
10941
|
+
if (config.claudeSettings !== "user") {
|
|
10942
|
+
throw new Error('"claudeSettings" may only be "user" (leave it unset to load no settings file)');
|
|
10943
|
+
}
|
|
10944
|
+
validated.claudeSettings = "user";
|
|
10945
|
+
}
|
|
10946
|
+
if (config.overlayAllowedHosts !== void 0) {
|
|
10947
|
+
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
10948
|
+
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
10949
|
+
}
|
|
10950
|
+
validated.overlayAllowedHosts = config.overlayAllowedHosts;
|
|
10951
|
+
}
|
|
10952
|
+
return new ClaudeCodeExecutor(validated);
|
|
10953
|
+
}
|
|
10954
|
+
function checkoutScopedTools(tools) {
|
|
10955
|
+
if (!tools)
|
|
10956
|
+
return void 0;
|
|
10957
|
+
const scoped = [];
|
|
10958
|
+
for (const tool of tools) {
|
|
10959
|
+
if (RETIRED_READ_TOOLS.some((t) => tool === t || tool === `${t}(./**)`))
|
|
10960
|
+
continue;
|
|
10961
|
+
const name = REVIEW_TOOLS.find((t) => tool === t || tool === `${t}(./**)`);
|
|
10962
|
+
if (!name) {
|
|
10963
|
+
throw new Error(`claude-code: a hosted run may use only ${REVIEW_TOOLS.join(", ")} inside the checkout; "${tool}" is not allowed`);
|
|
10964
|
+
}
|
|
10965
|
+
scoped.push(`${name}(./**)`);
|
|
10966
|
+
}
|
|
10967
|
+
return scoped;
|
|
10968
|
+
}
|
|
10969
|
+
function buildChildEnv(modelKey, configDir) {
|
|
10970
|
+
if (!modelKey && !configDir)
|
|
10971
|
+
return void 0;
|
|
10972
|
+
const env = {};
|
|
10973
|
+
for (const name of HOSTED_ENV_PASSTHROUGH) {
|
|
10974
|
+
if (process.env[name] !== void 0)
|
|
10975
|
+
env[name] = process.env[name];
|
|
10976
|
+
}
|
|
10977
|
+
return {
|
|
10978
|
+
...env,
|
|
10979
|
+
...modelKey ? { ANTHROPIC_API_KEY: modelKey } : {},
|
|
10980
|
+
...configDir ? { CLAUDE_CONFIG_DIR: configDir } : {}
|
|
10981
|
+
};
|
|
10982
|
+
}
|
|
10983
|
+
function parseStreamJson(stdout) {
|
|
10984
|
+
let finalText = null;
|
|
10985
|
+
const readPaths = [];
|
|
10986
|
+
let usage = null;
|
|
10987
|
+
let resultMeta = null;
|
|
10988
|
+
let structuredOutput = null;
|
|
10989
|
+
let sessionId = null;
|
|
10990
|
+
let model = null;
|
|
10991
|
+
const toolInputs = [];
|
|
10992
|
+
for (const line of stdout.split("\n")) {
|
|
10993
|
+
const trimmed = line.trim();
|
|
10994
|
+
if (!trimmed.startsWith("{"))
|
|
10995
|
+
continue;
|
|
10996
|
+
let event;
|
|
10997
|
+
try {
|
|
10998
|
+
event = JSON.parse(trimmed);
|
|
10999
|
+
} catch {
|
|
11000
|
+
continue;
|
|
11001
|
+
}
|
|
11002
|
+
if (typeof event.session_id === "string")
|
|
11003
|
+
sessionId = event.session_id;
|
|
11004
|
+
if (event.type === "system" && event.subtype === "init" && typeof event.model === "string" && event.model) {
|
|
11005
|
+
model = event.model;
|
|
11006
|
+
} else if (event.type === "assistant") {
|
|
11007
|
+
const content = event.message?.content;
|
|
11008
|
+
if (!Array.isArray(content))
|
|
11009
|
+
continue;
|
|
11010
|
+
for (const block of content) {
|
|
11011
|
+
const b = block;
|
|
11012
|
+
if (b?.type !== "tool_use")
|
|
11013
|
+
continue;
|
|
11014
|
+
const fp = b.input?.file_path;
|
|
11015
|
+
if (b.name === "Read" && typeof fp === "string") {
|
|
11016
|
+
readPaths.push(fp);
|
|
11017
|
+
}
|
|
11018
|
+
if (b.input && typeof b.input === "object")
|
|
11019
|
+
toolInputs.push(JSON.stringify(b.input));
|
|
11020
|
+
}
|
|
11021
|
+
} else if (event.type === "result") {
|
|
11022
|
+
if (typeof event.result === "string" && event.result.trim().length > 0) {
|
|
11023
|
+
finalText = event.result;
|
|
11024
|
+
}
|
|
11025
|
+
resultMeta = {
|
|
11026
|
+
subtype: typeof event.subtype === "string" ? event.subtype : null,
|
|
11027
|
+
isError: event.is_error === true,
|
|
11028
|
+
// On a failure the CLI puts a human-readable reason in `result` — the
|
|
11029
|
+
// model was unavailable, spend limit hit, and so on. It is the only
|
|
11030
|
+
// place that reason exists, so keep it for the failure message.
|
|
11031
|
+
detail: typeof event.result === "string" && event.result.trim().length > 0 ? event.result.trim() : null,
|
|
11032
|
+
apiErrorStatus: typeof event.api_error_status === "number" ? event.api_error_status : null
|
|
11033
|
+
};
|
|
11034
|
+
const so = event.structured_output;
|
|
11035
|
+
if (so && typeof so === "object" && !Array.isArray(so)) {
|
|
11036
|
+
structuredOutput = so;
|
|
11037
|
+
}
|
|
11038
|
+
const u = event.usage;
|
|
11039
|
+
const cost = event.total_cost_usd;
|
|
11040
|
+
usage = {
|
|
11041
|
+
...typeof u?.input_tokens === "number" ? { inputTokens: u.input_tokens } : {},
|
|
11042
|
+
...typeof u?.output_tokens === "number" ? { outputTokens: u.output_tokens } : {},
|
|
11043
|
+
...typeof cost === "number" ? { costUsd: cost } : {}
|
|
11044
|
+
};
|
|
11045
|
+
}
|
|
11046
|
+
}
|
|
11047
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, model, toolInputs };
|
|
11048
|
+
}
|
|
10020
11049
|
}
|
|
10021
11050
|
});
|
|
10022
11051
|
|
|
10023
|
-
// ../../packages/shuttle/dist/executors/
|
|
10024
|
-
var
|
|
10025
|
-
"../../packages/shuttle/dist/executors/
|
|
11052
|
+
// ../../packages/shuttle/dist/executors/agent-common.js
|
|
11053
|
+
var require_agent_common = __commonJS({
|
|
11054
|
+
"../../packages/shuttle/dist/executors/agent-common.js"(exports2) {
|
|
11055
|
+
"use strict";
|
|
11056
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11057
|
+
exports2.DEFAULT_AGENT_TIMEOUT = void 0;
|
|
11058
|
+
exports2.parseAgentCommonConfig = parseAgentCommonConfig;
|
|
11059
|
+
exports2.agentEnvironment = agentEnvironment;
|
|
11060
|
+
exports2.refuseHosted = refuseHosted;
|
|
11061
|
+
var loom_1 = require_dist();
|
|
11062
|
+
var apply_1 = require_apply();
|
|
11063
|
+
exports2.DEFAULT_AGENT_TIMEOUT = 6e5;
|
|
11064
|
+
function parseAgentCommonConfig(config, capabilityId) {
|
|
11065
|
+
const out = { capabilityId };
|
|
11066
|
+
if (config.tmpDir !== void 0) {
|
|
11067
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
11068
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
11069
|
+
}
|
|
11070
|
+
out.tmpDir = config.tmpDir;
|
|
11071
|
+
}
|
|
11072
|
+
if (config.model !== void 0) {
|
|
11073
|
+
if (typeof config.model !== "string" || config.model.length === 0) {
|
|
11074
|
+
throw new Error('"model" must be a non-empty string');
|
|
11075
|
+
}
|
|
11076
|
+
out.model = config.model;
|
|
11077
|
+
}
|
|
11078
|
+
if (config.timeout !== void 0) {
|
|
11079
|
+
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
11080
|
+
throw new Error('"timeout" must be a positive number');
|
|
11081
|
+
}
|
|
11082
|
+
out.timeout = config.timeout;
|
|
11083
|
+
}
|
|
11084
|
+
if (config.keepTranscripts !== void 0) {
|
|
11085
|
+
if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
|
|
11086
|
+
throw new Error('"keepTranscripts" must be a non-empty directory path');
|
|
11087
|
+
}
|
|
11088
|
+
out.keepTranscripts = config.keepTranscripts;
|
|
11089
|
+
}
|
|
11090
|
+
if (config.overlayAllowedHosts !== void 0) {
|
|
11091
|
+
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
11092
|
+
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
11093
|
+
}
|
|
11094
|
+
out.overlayAllowedHosts = config.overlayAllowedHosts;
|
|
11095
|
+
}
|
|
11096
|
+
return out;
|
|
11097
|
+
}
|
|
11098
|
+
function agentEnvironment(dispatch, config) {
|
|
11099
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
11100
|
+
const tmpBase = config.tmpDir ? (0, apply_1.expandHome)(config.tmpDir) : void 0;
|
|
11101
|
+
return {
|
|
11102
|
+
...spec.workspace ? { workspaceRequest: spec.workspace } : {},
|
|
11103
|
+
materializeOptions: {
|
|
11104
|
+
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
11105
|
+
overlayAllowedHosts: config.overlayAllowedHosts ?? []
|
|
11106
|
+
}
|
|
11107
|
+
};
|
|
11108
|
+
}
|
|
11109
|
+
function refuseHosted(executor, hosted) {
|
|
11110
|
+
if (hosted) {
|
|
11111
|
+
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`);
|
|
11112
|
+
}
|
|
11113
|
+
}
|
|
11114
|
+
}
|
|
11115
|
+
});
|
|
11116
|
+
|
|
11117
|
+
// ../../packages/shuttle/dist/executors/strict-schema.js
|
|
11118
|
+
var require_strict_schema = __commonJS({
|
|
11119
|
+
"../../packages/shuttle/dist/executors/strict-schema.js"(exports2) {
|
|
11120
|
+
"use strict";
|
|
11121
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11122
|
+
exports2.toStrict = toStrict;
|
|
11123
|
+
exports2.dropNulls = dropNulls;
|
|
11124
|
+
var UNSUPPORTED = ["format", "default", "$schema"];
|
|
11125
|
+
function toStrict(schema) {
|
|
11126
|
+
if (Array.isArray(schema))
|
|
11127
|
+
return schema.map(toStrict);
|
|
11128
|
+
if (!isSchema(schema))
|
|
11129
|
+
return schema;
|
|
11130
|
+
const out = {};
|
|
11131
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
11132
|
+
if (UNSUPPORTED.includes(key))
|
|
11133
|
+
continue;
|
|
11134
|
+
if (key === "properties" && isSchema(value)) {
|
|
11135
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
11136
|
+
const props = {};
|
|
11137
|
+
for (const [name, prop] of Object.entries(value)) {
|
|
11138
|
+
const strict = toStrict(prop);
|
|
11139
|
+
props[name] = required.has(name) ? strict : nullable(strict);
|
|
11140
|
+
}
|
|
11141
|
+
out.properties = props;
|
|
11142
|
+
out.required = Object.keys(value);
|
|
11143
|
+
continue;
|
|
11144
|
+
}
|
|
11145
|
+
if (key === "required")
|
|
11146
|
+
continue;
|
|
11147
|
+
out[key] = SUBSCHEMA_KEYS.has(key) ? toStrict(value) : value;
|
|
11148
|
+
}
|
|
11149
|
+
if (out.type === "object" || out.properties !== void 0)
|
|
11150
|
+
out.additionalProperties = false;
|
|
11151
|
+
return out;
|
|
11152
|
+
}
|
|
11153
|
+
function dropNulls(value, schema) {
|
|
11154
|
+
if (!isSchema(schema))
|
|
11155
|
+
return value;
|
|
11156
|
+
if (Array.isArray(value)) {
|
|
11157
|
+
return isSchema(schema.items) ? value.map((v) => dropNulls(v, schema.items)) : value;
|
|
11158
|
+
}
|
|
11159
|
+
if (!isSchema(value)) {
|
|
11160
|
+
return value;
|
|
11161
|
+
}
|
|
11162
|
+
const branches = [schema.anyOf, schema.oneOf].find(Array.isArray);
|
|
11163
|
+
if (branches) {
|
|
11164
|
+
const branch = branches.find((b) => isSchema(b) && (b.type === "object" || isSchema(b.properties)));
|
|
11165
|
+
return branch ? dropNulls(value, branch) : value;
|
|
11166
|
+
}
|
|
11167
|
+
if (!isSchema(schema.properties))
|
|
11168
|
+
return value;
|
|
11169
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
11170
|
+
const out = {};
|
|
11171
|
+
for (const [name, v] of Object.entries(value)) {
|
|
11172
|
+
const prop = schema.properties[name];
|
|
11173
|
+
if (v === null && !required.has(name) && !allowsNull(prop))
|
|
11174
|
+
continue;
|
|
11175
|
+
out[name] = dropNulls(v, prop);
|
|
11176
|
+
}
|
|
11177
|
+
return out;
|
|
11178
|
+
}
|
|
11179
|
+
var SUBSCHEMA_KEYS = /* @__PURE__ */ new Set(["items", "anyOf", "oneOf", "allOf", "not", "additionalProperties", "definitions", "$defs"]);
|
|
11180
|
+
function nullable(schema) {
|
|
11181
|
+
if (!isSchema(schema) || allowsNull(schema))
|
|
11182
|
+
return schema;
|
|
11183
|
+
if (typeof schema.type === "string" && schema.anyOf === void 0 && schema.oneOf === void 0) {
|
|
11184
|
+
const out = { ...schema, type: [schema.type, "null"] };
|
|
11185
|
+
if (Array.isArray(schema.enum))
|
|
11186
|
+
out.enum = [...schema.enum, null];
|
|
11187
|
+
return out;
|
|
11188
|
+
}
|
|
11189
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
11190
|
+
}
|
|
11191
|
+
function allowsNull(schema) {
|
|
11192
|
+
if (!isSchema(schema))
|
|
11193
|
+
return false;
|
|
11194
|
+
if (schema.type === "null")
|
|
11195
|
+
return true;
|
|
11196
|
+
if (Array.isArray(schema.type) && schema.type.includes("null"))
|
|
11197
|
+
return true;
|
|
11198
|
+
const branches = [schema.anyOf, schema.oneOf].find(Array.isArray);
|
|
11199
|
+
return branches?.some(allowsNull) ?? false;
|
|
11200
|
+
}
|
|
11201
|
+
function isSchema(v) {
|
|
11202
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11203
|
+
}
|
|
11204
|
+
}
|
|
11205
|
+
});
|
|
11206
|
+
|
|
11207
|
+
// ../../packages/shuttle/dist/executors/codex.js
|
|
11208
|
+
var require_codex = __commonJS({
|
|
11209
|
+
"../../packages/shuttle/dist/executors/codex.js"(exports2) {
|
|
10026
11210
|
"use strict";
|
|
10027
11211
|
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
10028
11212
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
10029
11213
|
};
|
|
10030
11214
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10031
|
-
exports2.
|
|
10032
|
-
exports2.
|
|
10033
|
-
exports2.
|
|
10034
|
-
exports2.
|
|
10035
|
-
exports2.
|
|
10036
|
-
exports2.globToRegExp = globToRegExp;
|
|
10037
|
-
exports2.creditOverlayReads = creditOverlayReads;
|
|
10038
|
-
exports2.extractJsonObject = extractJsonObject;
|
|
10039
|
-
var child_process_1 = require("child_process");
|
|
11215
|
+
exports2.resolveCodex = resolveCodex;
|
|
11216
|
+
exports2.codexReviewArgs = codexReviewArgs;
|
|
11217
|
+
exports2.parseCodexEvents = parseCodexEvents;
|
|
11218
|
+
exports2.parseCodexModels = parseCodexModels;
|
|
11219
|
+
exports2.createCodexExecutor = createCodexExecutor;
|
|
10040
11220
|
var fs_1 = require("fs");
|
|
10041
11221
|
var os_1 = __importDefault(require("os"));
|
|
10042
11222
|
var path_1 = __importDefault(require("path"));
|
|
10043
|
-
var string_decoder_1 = require("string_decoder");
|
|
10044
11223
|
var loom_1 = require_dist();
|
|
10045
11224
|
var types_1 = require_types2();
|
|
10046
11225
|
var validate_output_1 = require_validate_output();
|
|
10047
11226
|
var progress_1 = require_progress();
|
|
11227
|
+
var agent_process_1 = require_agent_process();
|
|
11228
|
+
var agent_common_1 = require_agent_common();
|
|
11229
|
+
var json_answer_1 = require_json_answer();
|
|
11230
|
+
var models_1 = require_models();
|
|
11231
|
+
var strict_schema_1 = require_strict_schema();
|
|
11232
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
10048
11233
|
var apply_1 = require_apply();
|
|
10049
|
-
var
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
"
|
|
10054
|
-
|
|
10055
|
-
"
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
"--permission-mode",
|
|
10059
|
-
"default",
|
|
10060
|
-
"--settings",
|
|
10061
|
-
JSON.stringify({ disableAllHooks: true })
|
|
10062
|
-
];
|
|
11234
|
+
var CODEX_PROFILE = "sift-review";
|
|
11235
|
+
function reviewProfile(installDir, platform = process.platform) {
|
|
11236
|
+
const rules = ['":root"="deny"', '":minimal"="read"', '":workspace_roots"="read"', `${JSON.stringify(installDir)}="read"`];
|
|
11237
|
+
if (platform === "linux") {
|
|
11238
|
+
rules.push(`${JSON.stringify(path_1.default.join(codexHome(), "tmp", "arg0"))}="read"`);
|
|
11239
|
+
} else {
|
|
11240
|
+
rules.push('":tmpdir"="deny"', '":slash_tmp"="deny"');
|
|
11241
|
+
}
|
|
11242
|
+
return `permissions.${CODEX_PROFILE}={extends=":read-only", filesystem={${rules.join(", ")}}}`;
|
|
10063
11243
|
}
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
"PATH",
|
|
10067
|
-
"HOME",
|
|
10068
|
-
"TMPDIR",
|
|
10069
|
-
"LANG",
|
|
10070
|
-
"LC_ALL",
|
|
10071
|
-
"LC_CTYPE",
|
|
10072
|
-
"TZ",
|
|
10073
|
-
"NODE_EXTRA_CA_CERTS",
|
|
10074
|
-
"SSL_CERT_FILE",
|
|
10075
|
-
"SSL_CERT_DIR",
|
|
10076
|
-
"HTTPS_PROXY",
|
|
10077
|
-
"HTTP_PROXY",
|
|
10078
|
-
"NO_PROXY",
|
|
10079
|
-
"https_proxy",
|
|
10080
|
-
"http_proxy",
|
|
10081
|
-
"no_proxy"
|
|
10082
|
-
];
|
|
10083
|
-
function describeClaudeFailure(meta, fallback) {
|
|
10084
|
-
if (!meta)
|
|
10085
|
-
return fallback;
|
|
10086
|
-
const status = meta.apiErrorStatus !== null ? ` (provider HTTP ${meta.apiErrorStatus})` : "";
|
|
10087
|
-
if (meta.detail)
|
|
10088
|
-
return `${meta.detail}${status}`;
|
|
10089
|
-
const subtype = meta.subtype && meta.subtype !== "success" ? `: ${meta.subtype}` : "";
|
|
10090
|
-
return `${fallback}${subtype}${status}`;
|
|
11244
|
+
function codexHome() {
|
|
11245
|
+
return process.env.CODEX_HOME ?? path_1.default.join(os_1.default.homedir(), ".codex");
|
|
10091
11246
|
}
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
return LIMIT_REMEDY;
|
|
11247
|
+
function resolveCodex(pathEnv = process.env.PATH ?? "") {
|
|
11248
|
+
for (const dir of pathEnv.split(path_1.default.delimiter)) {
|
|
11249
|
+
if (!dir)
|
|
11250
|
+
continue;
|
|
11251
|
+
const candidate = path_1.default.join(dir, "codex");
|
|
11252
|
+
let real;
|
|
11253
|
+
try {
|
|
11254
|
+
real = (0, fs_1.realpathSync)(candidate);
|
|
11255
|
+
if (!(0, fs_1.statSync)(real).isFile())
|
|
11256
|
+
continue;
|
|
11257
|
+
} catch {
|
|
11258
|
+
continue;
|
|
11259
|
+
}
|
|
11260
|
+
const installDir = real.endsWith(".js") ? path_1.default.dirname(path_1.default.dirname(real)) : path_1.default.dirname(real);
|
|
11261
|
+
return { bin: real.endsWith(".js") ? candidate : real, installDir };
|
|
10108
11262
|
}
|
|
10109
11263
|
return null;
|
|
10110
11264
|
}
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
11265
|
+
var DISABLED_FEATURES = [
|
|
11266
|
+
"hooks",
|
|
11267
|
+
"apps",
|
|
11268
|
+
"plugins",
|
|
11269
|
+
"remote_plugin",
|
|
11270
|
+
"plugin_sharing",
|
|
11271
|
+
"skill_mcp_dependency_install",
|
|
11272
|
+
"browser_use",
|
|
11273
|
+
"browser_use_external",
|
|
11274
|
+
"browser_use_full_cdp_access",
|
|
11275
|
+
"browser_annotation_api",
|
|
11276
|
+
"computer_use",
|
|
11277
|
+
"image_generation",
|
|
11278
|
+
"workspace_dependencies"
|
|
11279
|
+
];
|
|
11280
|
+
function requireCodex() {
|
|
11281
|
+
const codex = resolveCodex();
|
|
11282
|
+
if (!codex) {
|
|
11283
|
+
throw new types_1.ExecutorEnvironmentError("the codex CLI is not on this machine's PATH", CODEX_FAULTS.installRemedy);
|
|
10116
11284
|
}
|
|
10117
|
-
return
|
|
11285
|
+
return codex;
|
|
10118
11286
|
}
|
|
10119
|
-
function
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
11287
|
+
function codexReviewArgs(installDir, platform = process.platform) {
|
|
11288
|
+
return [
|
|
11289
|
+
"--json",
|
|
11290
|
+
"--skip-git-repo-check",
|
|
11291
|
+
"--ignore-user-config",
|
|
11292
|
+
"--ignore-rules",
|
|
11293
|
+
"-c",
|
|
11294
|
+
reviewProfile(installDir, platform),
|
|
11295
|
+
"-c",
|
|
11296
|
+
`default_permissions="${CODEX_PROFILE}"`,
|
|
11297
|
+
"-c",
|
|
11298
|
+
'approval_policy="never"',
|
|
11299
|
+
"-c",
|
|
11300
|
+
'web_search="disabled"',
|
|
11301
|
+
...DISABLED_FEATURES.flatMap((f) => ["--disable", f])
|
|
11302
|
+
];
|
|
10124
11303
|
}
|
|
10125
|
-
var
|
|
11304
|
+
var CODEX_FAULTS = {
|
|
11305
|
+
installRemedy: "install Codex (brew install codex, or npm install -g @openai/codex) where this process can find it on PATH",
|
|
11306
|
+
loginRemedy: "run `codex login` and sign in (or `codex login --with-api-key`)",
|
|
11307
|
+
limitRemedy: "wait for the plan's usage limit to reset, or raise it",
|
|
11308
|
+
signedOut: /not logged in|run `?codex login|401 unauthorized|invalid api key|refresh token|token (?:has )?expired|sign in again/i,
|
|
11309
|
+
outOfUsage: /usage limit|hit your (?:\w+ )?limit|insufficient_quota|exceeded your current quota|429 too many requests/i
|
|
11310
|
+
};
|
|
11311
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
11312
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
11313
|
+
var CodexExecutor = class {
|
|
10126
11314
|
capability;
|
|
10127
11315
|
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
10128
11316
|
config;
|
|
@@ -10137,132 +11325,81 @@ var require_claude_code = __commonJS({
|
|
|
10137
11325
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
10138
11326
|
};
|
|
10139
11327
|
}
|
|
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
11328
|
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
|
-
};
|
|
11329
|
+
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
10158
11330
|
}
|
|
10159
11331
|
async execute(dispatch, signal, ctx) {
|
|
11332
|
+
(0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
|
|
10160
11333
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
10161
11334
|
const workspace = ctx?.workspace ?? null;
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
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;
|
|
11335
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "codex");
|
|
11336
|
+
const model = typeof hints.model === "string" && hints.model.length > 0 ? hints.model : this.config.model;
|
|
11337
|
+
const effort = (0, models_1.hintedEffort)(hints) ?? this.config.reasoningEffort;
|
|
10180
11338
|
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
11339
|
+
const codex = requireCodex();
|
|
11340
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-"));
|
|
10181
11341
|
try {
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
}
|
|
10188
|
-
|
|
10189
|
-
|
|
11342
|
+
let runCwd = workspace?.cwd;
|
|
11343
|
+
if (!runCwd) {
|
|
11344
|
+
runCwd = path_1.default.join(scratch, "run");
|
|
11345
|
+
await fs_1.promises.mkdir(runCwd);
|
|
11346
|
+
}
|
|
11347
|
+
const { response, commands } = await this.runCodex({
|
|
11348
|
+
codex,
|
|
11349
|
+
args: ["exec", ...codexReviewArgs(codex.installDir), "-C", runCwd],
|
|
10190
11350
|
prompt,
|
|
10191
11351
|
spec,
|
|
10192
|
-
runCwd,
|
|
10193
|
-
|
|
11352
|
+
cwd: runCwd,
|
|
11353
|
+
scratch,
|
|
11354
|
+
model,
|
|
11355
|
+
effort,
|
|
10194
11356
|
signal,
|
|
10195
|
-
hintedModel,
|
|
10196
11357
|
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
|
-
);
|
|
11358
|
+
transcriptName: dispatch.job.id
|
|
11359
|
+
});
|
|
10204
11360
|
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
|
-
});
|
|
11361
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, (0, workspace_telemetry_1.shellReadPaths)(commands, runCwd), commands.map((command) => JSON.stringify({ command }))));
|
|
10233
11362
|
}
|
|
10234
11363
|
return response;
|
|
10235
11364
|
} finally {
|
|
10236
11365
|
reporter?.close();
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
});
|
|
10240
|
-
if (jobScratch)
|
|
10241
|
-
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
10242
|
-
});
|
|
11366
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11367
|
+
});
|
|
10243
11368
|
}
|
|
10244
11369
|
}
|
|
10245
11370
|
/**
|
|
10246
|
-
* In-session gate repair (ADR 0017): resume the
|
|
10247
|
-
*
|
|
10248
|
-
*
|
|
10249
|
-
*
|
|
10250
|
-
* a second repair round can resume again.
|
|
11371
|
+
* In-session gate repair (ADR 0017): resume the thread and send the
|
|
11372
|
+
* follow-up under the same schema. The checkout is gone by now (the runner
|
|
11373
|
+
* cleans up before gates run), so the repair works from the thread's own
|
|
11374
|
+
* context in a scratch dir, as claude-code's does.
|
|
10251
11375
|
*/
|
|
10252
|
-
async continue(continuation, followUp, signal,
|
|
11376
|
+
async continue(continuation, followUp, signal, ctx) {
|
|
11377
|
+
(0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
|
|
10253
11378
|
const handle = continuation;
|
|
10254
|
-
if (!handle || typeof handle.
|
|
10255
|
-
throw new Error("
|
|
11379
|
+
if (!handle || typeof handle.threadId !== "string") {
|
|
11380
|
+
throw new Error("codex continue: missing thread to resume");
|
|
10256
11381
|
}
|
|
10257
|
-
const
|
|
10258
|
-
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-
|
|
11382
|
+
const codex = requireCodex();
|
|
11383
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-resume-"));
|
|
10259
11384
|
try {
|
|
10260
11385
|
const spec = {
|
|
10261
11386
|
rendering: "producer",
|
|
10262
11387
|
outputLabel: handle.outputLabel,
|
|
10263
11388
|
outputSchema: handle.outputSchema
|
|
10264
11389
|
};
|
|
10265
|
-
const { response } = await this.
|
|
11390
|
+
const { response } = await this.runCodex({
|
|
11391
|
+
codex,
|
|
11392
|
+
args: ["exec", "resume", handle.threadId, ...codexReviewArgs(codex.installDir)],
|
|
11393
|
+
prompt: followUp,
|
|
11394
|
+
spec,
|
|
11395
|
+
cwd: scratch,
|
|
11396
|
+
scratch,
|
|
11397
|
+
model: handle.model,
|
|
11398
|
+
effort: handle.effort,
|
|
11399
|
+
signal,
|
|
11400
|
+
reporter: null,
|
|
11401
|
+
transcriptName: `${handle.threadId}-repair`
|
|
11402
|
+
});
|
|
10266
11403
|
return response;
|
|
10267
11404
|
} finally {
|
|
10268
11405
|
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
@@ -10270,335 +11407,168 @@ var require_claude_code = __commonJS({
|
|
|
10270
11407
|
}
|
|
10271
11408
|
}
|
|
10272
11409
|
/**
|
|
10273
|
-
*
|
|
10274
|
-
*
|
|
10275
|
-
*
|
|
10276
|
-
* mode. A signed-out or missing CLI fails before any model call, so the
|
|
10277
|
-
* re-check spends nothing until the login works again, and then one turn.
|
|
11410
|
+
* One real turn under the review flags, in a throwaway dir. A signed-out
|
|
11411
|
+
* CLI fails before any model call, so the re-check spends nothing until the
|
|
11412
|
+
* login works again; a spent plan is only visible to a real call.
|
|
10278
11413
|
*/
|
|
10279
11414
|
async checkReadiness(signal) {
|
|
10280
|
-
|
|
10281
|
-
|
|
11415
|
+
const codex = resolveCodex();
|
|
11416
|
+
if (!codex)
|
|
11417
|
+
return { ready: false, reason: "the codex CLI is not on this machine's PATH" };
|
|
11418
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-codex-ready-"));
|
|
11419
|
+
try {
|
|
11420
|
+
let run;
|
|
10282
11421
|
try {
|
|
10283
|
-
|
|
11422
|
+
run = await (0, agent_process_1.runAgentProcess)({
|
|
11423
|
+
bin: codex.bin,
|
|
11424
|
+
args: ["exec", ...codexReviewArgs(codex.installDir), "--ephemeral", "-C", scratch, "-"],
|
|
11425
|
+
cwd: scratch,
|
|
11426
|
+
stdin: READINESS_PROMPT,
|
|
11427
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
11428
|
+
toolName: "the codex CLI",
|
|
11429
|
+
signal
|
|
11430
|
+
});
|
|
10284
11431
|
} catch (err) {
|
|
10285
|
-
|
|
10286
|
-
return;
|
|
10287
|
-
}
|
|
10288
|
-
let stdout = "";
|
|
10289
|
-
let stderr = "";
|
|
10290
|
-
let settled = false;
|
|
10291
|
-
const settle = (value) => {
|
|
10292
|
-
if (settled)
|
|
10293
|
-
return;
|
|
10294
|
-
settled = true;
|
|
10295
|
-
clearTimeout(timer);
|
|
10296
|
-
signal.removeEventListener("abort", onAbort);
|
|
10297
|
-
resolve(value);
|
|
10298
|
-
};
|
|
10299
|
-
const timer = setTimeout(() => {
|
|
10300
|
-
child.kill("SIGKILL");
|
|
10301
|
-
settle({ ready: false, reason: `the claude CLI did not answer within ${READINESS_TIMEOUT_MS / 1e3}s` });
|
|
10302
|
-
}, READINESS_TIMEOUT_MS);
|
|
10303
|
-
function onAbort() {
|
|
10304
|
-
child.kill("SIGTERM");
|
|
10305
|
-
settle({ ready: false, reason: "readiness check aborted" });
|
|
10306
|
-
}
|
|
10307
|
-
if (signal.aborted) {
|
|
10308
|
-
onAbort();
|
|
10309
|
-
return;
|
|
11432
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11433
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
10310
11434
|
}
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
stdout += chunk.toString("utf-8");
|
|
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({
|
|
11435
|
+
if (run.spawnError) {
|
|
11436
|
+
return {
|
|
10322
11437
|
ready: false,
|
|
10323
|
-
reason: code === "ENOENT" ? `the
|
|
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;
|
|
11438
|
+
reason: run.spawnError.code === "ENOENT" ? `the codex CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
11439
|
+
};
|
|
10376
11440
|
}
|
|
10377
|
-
const
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
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);
|
|
11441
|
+
const events = parseCodexEvents(run.stdout);
|
|
11442
|
+
if (run.code === 0 && !events.failure)
|
|
11443
|
+
return { ready: true };
|
|
11444
|
+
return {
|
|
11445
|
+
ready: false,
|
|
11446
|
+
reason: events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? `the codex CLI exited with code ${run.code}`
|
|
10405
11447
|
};
|
|
10406
|
-
|
|
10407
|
-
|
|
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
|
-
}
|
|
11448
|
+
} finally {
|
|
11449
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
10510
11450
|
});
|
|
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');
|
|
10536
|
-
}
|
|
10537
|
-
validated.allowedTools = config.allowedTools;
|
|
10538
|
-
}
|
|
10539
|
-
if (config.timeout !== void 0) {
|
|
10540
|
-
if (typeof config.timeout !== "number" || config.timeout <= 0) {
|
|
10541
|
-
throw new Error('"timeout" must be a positive number');
|
|
10542
|
-
}
|
|
10543
|
-
validated.timeout = config.timeout;
|
|
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');
|
|
10548
|
-
}
|
|
10549
|
-
validated.keepTranscripts = config.keepTranscripts;
|
|
10550
|
-
}
|
|
10551
|
-
if (config.claudeSettings !== void 0) {
|
|
10552
|
-
if (config.claudeSettings !== "user") {
|
|
10553
|
-
throw new Error('"claudeSettings" may only be "user" (leave it unset to load no settings file)');
|
|
10554
11451
|
}
|
|
10555
|
-
validated.claudeSettings = "user";
|
|
10556
11452
|
}
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
11453
|
+
/**
|
|
11454
|
+
* The catalog `codex debug models` prints: the models codex itself offers
|
|
11455
|
+
* in its picker, each with the reasoning levels it supports. A `debug`
|
|
11456
|
+
* subcommand, so not a stable contract; any failure or unknown shape
|
|
11457
|
+
* reports nothing.
|
|
11458
|
+
*/
|
|
11459
|
+
async listModels(signal) {
|
|
11460
|
+
const codex = resolveCodex();
|
|
11461
|
+
if (!codex)
|
|
11462
|
+
return [];
|
|
11463
|
+
const stdout = await (0, models_1.runListing)(codex.bin, ["debug", "models"], signal);
|
|
11464
|
+
return stdout === null ? [] : parseCodexModels(stdout);
|
|
11465
|
+
}
|
|
11466
|
+
tmpBase() {
|
|
11467
|
+
return this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
11468
|
+
}
|
|
11469
|
+
async runCodex(opts) {
|
|
11470
|
+
const { spec, model, effort } = opts;
|
|
11471
|
+
const args = [...opts.args];
|
|
11472
|
+
const hasSchema = spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0;
|
|
11473
|
+
if (hasSchema) {
|
|
11474
|
+
const schemaFile = path_1.default.join(opts.scratch, "output-schema.json");
|
|
11475
|
+
await fs_1.promises.writeFile(schemaFile, JSON.stringify((0, strict_schema_1.toStrict)(spec.outputSchema)));
|
|
11476
|
+
args.push("--output-schema", schemaFile);
|
|
11477
|
+
}
|
|
11478
|
+
if (model)
|
|
11479
|
+
args.push("-m", model);
|
|
11480
|
+
if (effort)
|
|
11481
|
+
args.push("-c", `model_reasoning_effort="${effort}"`);
|
|
11482
|
+
args.push("-");
|
|
11483
|
+
const tap = opts.reporter ? codexProgressTap(opts.reporter) : null;
|
|
11484
|
+
const run = await (0, agent_process_1.runAgentProcess)({
|
|
11485
|
+
bin: opts.codex.bin,
|
|
11486
|
+
args,
|
|
11487
|
+
cwd: opts.cwd,
|
|
11488
|
+
stdin: opts.prompt,
|
|
11489
|
+
idleTimeoutMs: this.config.timeout ?? agent_common_1.DEFAULT_AGENT_TIMEOUT,
|
|
11490
|
+
toolName: "Codex",
|
|
11491
|
+
signal: opts.signal,
|
|
11492
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
11493
|
+
});
|
|
11494
|
+
tap?.end();
|
|
11495
|
+
if (run.spawnError) {
|
|
11496
|
+
if (run.spawnError.code === "ENOENT") {
|
|
11497
|
+
throw new types_1.ExecutorEnvironmentError(`the codex CLI is not on this machine's PATH (${run.spawnError.message})`, CODEX_FAULTS.installRemedy);
|
|
11498
|
+
}
|
|
11499
|
+
throw new Error(run.spawnError.message);
|
|
10560
11500
|
}
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
return new ClaudeCodeExecutor(validated);
|
|
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`);
|
|
11501
|
+
if (this.config.keepTranscripts) {
|
|
11502
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), opts.transcriptName, run.stdout);
|
|
10575
11503
|
}
|
|
10576
|
-
|
|
11504
|
+
const events = parseCodexEvents(run.stdout);
|
|
11505
|
+
if (run.code !== 0 || events.failure) {
|
|
11506
|
+
const said = events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
11507
|
+
const message = said ? `Codex ${run.code === 0 ? "reported a failure" : `exited with code ${run.code}`}: ${said}` : `Codex exited with code ${run.code}`;
|
|
11508
|
+
throw (0, agent_process_1.agentFailure)(true, message, { apiErrorStatus: null, text: said }, CODEX_FAULTS);
|
|
11509
|
+
}
|
|
11510
|
+
if (events.finalText === null) {
|
|
11511
|
+
throw new Error("Codex finished without a final message");
|
|
11512
|
+
}
|
|
11513
|
+
const answerText = events.finalText;
|
|
11514
|
+
const raw = (0, json_answer_1.extractJsonObject)(answerText);
|
|
11515
|
+
if (raw === void 0 && hasSchema) {
|
|
11516
|
+
throw new Error(`codex's final message contained no JSON object (message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
11517
|
+
}
|
|
11518
|
+
const parsed = raw === void 0 ? void 0 : (0, strict_schema_1.dropNulls)(raw, spec.outputSchema);
|
|
11519
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
11520
|
+
return {
|
|
11521
|
+
response: {
|
|
11522
|
+
outputs: [{ label: spec.outputLabel, content: parsed ?? { raw: answerText } }],
|
|
11523
|
+
rawText: answerText,
|
|
11524
|
+
parsed,
|
|
11525
|
+
usage: {
|
|
11526
|
+
aiProvider: "codex",
|
|
11527
|
+
...model ? { aiModel: model } : {},
|
|
11528
|
+
...effort ? { aiEffort: effort } : {},
|
|
11529
|
+
...events.usage ?? {}
|
|
11530
|
+
},
|
|
11531
|
+
...events.threadId ? {
|
|
11532
|
+
continuation: {
|
|
11533
|
+
threadId: events.threadId,
|
|
11534
|
+
model,
|
|
11535
|
+
effort,
|
|
11536
|
+
outputLabel: spec.outputLabel,
|
|
11537
|
+
outputSchema: spec.outputSchema
|
|
11538
|
+
}
|
|
11539
|
+
} : {}
|
|
11540
|
+
},
|
|
11541
|
+
commands: events.commands
|
|
11542
|
+
};
|
|
10577
11543
|
}
|
|
10578
|
-
|
|
11544
|
+
};
|
|
11545
|
+
function codexProgressTap(reporter) {
|
|
11546
|
+
return new progress_1.JsonLineTap((event) => {
|
|
11547
|
+
const item = event.item;
|
|
11548
|
+
const kind = item ? itemKind(item) : null;
|
|
11549
|
+
if (event.type === "item.started" && kind === "command_execution" && typeof item?.command === "string") {
|
|
11550
|
+
reporter.tool("shell", item.command);
|
|
11551
|
+
} else if (event.type === "item.completed" && kind === "reasoning" && typeof item?.text === "string") {
|
|
11552
|
+
reporter.reasoning(item.text);
|
|
11553
|
+
} else if (event.type === "turn.completed") {
|
|
11554
|
+
reporter.flush();
|
|
11555
|
+
}
|
|
11556
|
+
});
|
|
10579
11557
|
}
|
|
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
|
-
};
|
|
11558
|
+
function itemKind(item) {
|
|
11559
|
+
const kind = typeof item.type === "string" ? item.type : typeof item.item_type === "string" ? item.item_type : null;
|
|
11560
|
+
return kind === "assistant_message" ? "agent_message" : kind;
|
|
10593
11561
|
}
|
|
10594
|
-
function
|
|
11562
|
+
function parseCodexEvents(stdout) {
|
|
11563
|
+
let threadId = null;
|
|
10595
11564
|
let finalText = null;
|
|
10596
|
-
const
|
|
10597
|
-
|
|
10598
|
-
let
|
|
10599
|
-
let
|
|
10600
|
-
let
|
|
10601
|
-
|
|
11565
|
+
const commands = [];
|
|
11566
|
+
const seenCommands = /* @__PURE__ */ new Set();
|
|
11567
|
+
let input = 0;
|
|
11568
|
+
let output = 0;
|
|
11569
|
+
let sawUsage = false;
|
|
11570
|
+
let turnFailure = null;
|
|
11571
|
+
let lastError = null;
|
|
10602
11572
|
for (const line of stdout.split("\n")) {
|
|
10603
11573
|
const trimmed = line.trim();
|
|
10604
11574
|
if (!trimmed.startsWith("{"))
|
|
@@ -10609,257 +11579,580 @@ var require_claude_code = __commonJS({
|
|
|
10609
11579
|
} catch {
|
|
10610
11580
|
continue;
|
|
10611
11581
|
}
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
11582
|
+
switch (event.type) {
|
|
11583
|
+
case "thread.started":
|
|
11584
|
+
if (typeof event.thread_id === "string")
|
|
11585
|
+
threadId = event.thread_id;
|
|
11586
|
+
break;
|
|
11587
|
+
case "item.started":
|
|
11588
|
+
case "item.updated":
|
|
11589
|
+
case "item.completed": {
|
|
11590
|
+
const item = event.item;
|
|
11591
|
+
if (!item)
|
|
11592
|
+
break;
|
|
11593
|
+
const kind = itemKind(item);
|
|
11594
|
+
if (kind === "agent_message" && event.type === "item.completed" && typeof item.text === "string") {
|
|
11595
|
+
finalText = item.text;
|
|
11596
|
+
lastError = null;
|
|
11597
|
+
} else if (kind === "command_execution" && typeof item.command === "string") {
|
|
11598
|
+
const key = typeof item.id === "string" ? item.id : item.command;
|
|
11599
|
+
if (!seenCommands.has(key)) {
|
|
11600
|
+
seenCommands.add(key);
|
|
11601
|
+
commands.push(item.command);
|
|
11602
|
+
}
|
|
10625
11603
|
}
|
|
10626
|
-
|
|
10627
|
-
toolInputs.push(JSON.stringify(b.input));
|
|
11604
|
+
break;
|
|
10628
11605
|
}
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
11606
|
+
case "turn.completed": {
|
|
11607
|
+
const u = event.usage;
|
|
11608
|
+
if (typeof u?.input_tokens === "number") {
|
|
11609
|
+
input += u.input_tokens;
|
|
11610
|
+
sawUsage = true;
|
|
11611
|
+
}
|
|
11612
|
+
if (typeof u?.output_tokens === "number") {
|
|
11613
|
+
output += u.output_tokens;
|
|
11614
|
+
sawUsage = true;
|
|
11615
|
+
}
|
|
11616
|
+
break;
|
|
10632
11617
|
}
|
|
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;
|
|
11618
|
+
case "turn.failed": {
|
|
11619
|
+
const err = event.error;
|
|
11620
|
+
turnFailure = typeof err?.message === "string" ? err.message : "the turn failed";
|
|
11621
|
+
break;
|
|
10645
11622
|
}
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
};
|
|
11623
|
+
case "error":
|
|
11624
|
+
if (typeof event.message === "string")
|
|
11625
|
+
lastError = event.message;
|
|
11626
|
+
break;
|
|
11627
|
+
default:
|
|
11628
|
+
break;
|
|
10653
11629
|
}
|
|
10654
11630
|
}
|
|
10655
|
-
return {
|
|
11631
|
+
return {
|
|
11632
|
+
threadId,
|
|
11633
|
+
finalText,
|
|
11634
|
+
commands,
|
|
11635
|
+
usage: sawUsage ? { inputTokens: input, outputTokens: output } : null,
|
|
11636
|
+
failure: turnFailure ?? (finalText === null ? lastError : null)
|
|
11637
|
+
};
|
|
10656
11638
|
}
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
const root = path_1.default.resolve(cwd);
|
|
10660
|
-
const roots = [root];
|
|
11639
|
+
function parseCodexModels(stdout) {
|
|
11640
|
+
let body;
|
|
10661
11641
|
try {
|
|
10662
|
-
|
|
10663
|
-
if (real !== root)
|
|
10664
|
-
roots.push(real);
|
|
11642
|
+
body = JSON.parse(stdout);
|
|
10665
11643
|
} catch {
|
|
11644
|
+
return [];
|
|
10666
11645
|
}
|
|
10667
|
-
|
|
11646
|
+
const entries = body?.models;
|
|
11647
|
+
if (!Array.isArray(entries))
|
|
11648
|
+
return [];
|
|
11649
|
+
const listed = entries.flatMap((entry) => {
|
|
11650
|
+
if (!entry || typeof entry !== "object")
|
|
11651
|
+
return [];
|
|
11652
|
+
const e = entry;
|
|
11653
|
+
if (typeof e.slug !== "string" || e.slug.length === 0 || e.visibility !== "list")
|
|
11654
|
+
return [];
|
|
11655
|
+
const levels = Array.isArray(e.supported_reasoning_levels) ? e.supported_reasoning_levels.flatMap((level) => {
|
|
11656
|
+
const effort = level?.effort;
|
|
11657
|
+
return typeof effort === "string" && /^[a-z]+$/.test(effort) ? [effort] : [];
|
|
11658
|
+
}) : [];
|
|
11659
|
+
const model = {
|
|
11660
|
+
id: e.slug,
|
|
11661
|
+
label: typeof e.display_name === "string" && e.display_name ? e.display_name : e.slug,
|
|
11662
|
+
...levels.length > 0 ? { efforts: levels } : {}
|
|
11663
|
+
};
|
|
11664
|
+
return [{ model, priority: typeof e.priority === "number" ? e.priority : Number.MAX_SAFE_INTEGER }];
|
|
11665
|
+
});
|
|
11666
|
+
return listed.sort((a, b) => a.priority - b.priority).map((entry) => entry.model);
|
|
10668
11667
|
}
|
|
10669
|
-
function
|
|
10670
|
-
const
|
|
10671
|
-
const
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
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;
|
|
10682
|
-
}
|
|
10683
|
-
if (out.size >= MAX_REPORTED_READS)
|
|
10684
|
-
break;
|
|
10685
|
-
}
|
|
10686
|
-
return [...out].sort();
|
|
11668
|
+
function createCodexExecutor(config, capabilityId = "codex") {
|
|
11669
|
+
const validated = (0, agent_common_1.parseAgentCommonConfig)(config, capabilityId);
|
|
11670
|
+
const effort = (0, models_1.parseReasoningEffort)(config.reasoningEffort, models_1.REASONING_EFFORTS);
|
|
11671
|
+
if (effort)
|
|
11672
|
+
validated.reasoningEffort = effort;
|
|
11673
|
+
return new CodexExecutor(validated);
|
|
10687
11674
|
}
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
11675
|
+
}
|
|
11676
|
+
});
|
|
11677
|
+
|
|
11678
|
+
// ../../packages/shuttle/dist/executors/copilot.js
|
|
11679
|
+
var require_copilot = __commonJS({
|
|
11680
|
+
"../../packages/shuttle/dist/executors/copilot.js"(exports2) {
|
|
11681
|
+
"use strict";
|
|
11682
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
11683
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
11684
|
+
};
|
|
11685
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11686
|
+
exports2.copilotReviewArgs = copilotReviewArgs;
|
|
11687
|
+
exports2.withSchema = withSchema;
|
|
11688
|
+
exports2.usageOf = usageOf;
|
|
11689
|
+
exports2.parseCopilotEvents = parseCopilotEvents;
|
|
11690
|
+
exports2.parseCopilotModels = parseCopilotModels;
|
|
11691
|
+
exports2.createCopilotExecutor = createCopilotExecutor;
|
|
11692
|
+
var crypto_1 = require("crypto");
|
|
11693
|
+
var fs_1 = require("fs");
|
|
11694
|
+
var os_1 = __importDefault(require("os"));
|
|
11695
|
+
var path_1 = __importDefault(require("path"));
|
|
11696
|
+
var loom_1 = require_dist();
|
|
11697
|
+
var types_1 = require_types2();
|
|
11698
|
+
var validate_output_1 = require_validate_output();
|
|
11699
|
+
var progress_1 = require_progress();
|
|
11700
|
+
var agent_process_1 = require_agent_process();
|
|
11701
|
+
var agent_common_1 = require_agent_common();
|
|
11702
|
+
var json_answer_1 = require_json_answer();
|
|
11703
|
+
var models_1 = require_models();
|
|
11704
|
+
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
11705
|
+
var apply_1 = require_apply();
|
|
11706
|
+
var REVIEW_TOOLS = ["view", "grep", "glob"];
|
|
11707
|
+
var DENIED_TOOLS = ["shell", "write"];
|
|
11708
|
+
function copilotReviewArgs() {
|
|
11709
|
+
return [
|
|
11710
|
+
"--output-format",
|
|
11711
|
+
"json",
|
|
11712
|
+
"--no-color",
|
|
11713
|
+
"--allow-all-tools",
|
|
11714
|
+
`--available-tools=${REVIEW_TOOLS.join(",")}`,
|
|
11715
|
+
...DENIED_TOOLS.map((t) => `--deny-tool=${t}`),
|
|
11716
|
+
"--disable-builtin-mcps",
|
|
11717
|
+
"--disallow-temp-dir",
|
|
11718
|
+
"--no-ask-user",
|
|
11719
|
+
"--no-auto-update",
|
|
11720
|
+
"--no-remote"
|
|
11721
|
+
];
|
|
10691
11722
|
}
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
re += ".*";
|
|
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
|
-
}
|
|
11723
|
+
var RUN_SETTINGS = { disableAllHooks: true, autoUpdate: false };
|
|
11724
|
+
var COPILOT_FAULTS = {
|
|
11725
|
+
installRemedy: "install GitHub Copilot CLI (brew install --cask copilot-cli, or npm install -g @github/copilot) where this process can find it on PATH",
|
|
11726
|
+
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)",
|
|
11727
|
+
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",
|
|
11728
|
+
signedOut: /not (?:logged|signed) in|run `?copilot login|no authentication|authentication (?:failed|required)|bad credentials/i,
|
|
11729
|
+
outOfUsage: /exceeded your monthly quota|premium request|usage limit|hit your (?:\w+ )?limit|exceeded .*(?:allowance|limit)/i,
|
|
11730
|
+
notEntitled: {
|
|
11731
|
+
pattern: /access denied by policy|not authorized to use this copilot feature/i,
|
|
11732
|
+
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
11733
|
}
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
dirs.add(`${parts.slice(0, i).join("/")}/`);
|
|
11734
|
+
};
|
|
11735
|
+
var READINESS_PROMPT = "Reply with the single word: ok";
|
|
11736
|
+
var READINESS_TIMEOUT_MS = 12e4;
|
|
11737
|
+
var CopilotExecutor = class {
|
|
11738
|
+
capability;
|
|
11739
|
+
strategies = [loom_1.PROMPT_EXECUTION_STRATEGY];
|
|
11740
|
+
config;
|
|
11741
|
+
constructor(config) {
|
|
11742
|
+
this.config = config;
|
|
11743
|
+
this.capability = { id: config.capabilityId };
|
|
10740
11744
|
}
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
11745
|
+
estimate(dispatch) {
|
|
11746
|
+
const spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
11747
|
+
return {
|
|
11748
|
+
inputTokens: spec.costCapHint?.estimatedInputTokens,
|
|
11749
|
+
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
11750
|
+
};
|
|
11751
|
+
}
|
|
11752
|
+
async prepareEnvironment(dispatch) {
|
|
11753
|
+
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
11754
|
+
}
|
|
11755
|
+
async execute(dispatch, signal, ctx) {
|
|
11756
|
+
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
11757
|
+
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
11758
|
+
const workspace = ctx?.workspace ?? null;
|
|
11759
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "copilot");
|
|
11760
|
+
const model = typeof hints.model === "string" && hints.model.length > 0 ? hints.model : this.config.model;
|
|
11761
|
+
const effort = (0, models_1.hintedEffort)(hints) ?? this.config.reasoningEffort;
|
|
11762
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
11763
|
+
const runs = path_1.default.join(this.tmpBase(), "shuttle-copilot-runs");
|
|
11764
|
+
await fs_1.promises.mkdir(runs, { recursive: true, mode: 448 });
|
|
11765
|
+
await pruneRuns(runs);
|
|
11766
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(runs, "run-"));
|
|
11767
|
+
let keep = false;
|
|
11768
|
+
try {
|
|
11769
|
+
const home = await makeRunHome(scratch);
|
|
11770
|
+
let runCwd = workspace?.cwd;
|
|
11771
|
+
if (!runCwd) {
|
|
11772
|
+
runCwd = path_1.default.join(scratch, "run");
|
|
11773
|
+
await fs_1.promises.mkdir(runCwd);
|
|
11774
|
+
}
|
|
11775
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
11776
|
+
const { response, reads, toolInputs } = await this.runCopilot({
|
|
11777
|
+
args: [...copilotReviewArgs(), `--session-id=${sessionId}`, "-C", runCwd],
|
|
11778
|
+
prompt: withSchema(prompt, spec),
|
|
11779
|
+
spec,
|
|
11780
|
+
cwd: runCwd,
|
|
11781
|
+
home,
|
|
11782
|
+
scratch,
|
|
11783
|
+
model,
|
|
11784
|
+
effort,
|
|
11785
|
+
signal,
|
|
11786
|
+
reporter,
|
|
11787
|
+
transcriptName: dispatch.job.id
|
|
11788
|
+
});
|
|
11789
|
+
response.continuation = {
|
|
11790
|
+
sessionId,
|
|
11791
|
+
home,
|
|
11792
|
+
model,
|
|
11793
|
+
effort,
|
|
11794
|
+
outputLabel: spec.outputLabel,
|
|
11795
|
+
outputSchema: spec.outputSchema
|
|
11796
|
+
};
|
|
11797
|
+
keep = true;
|
|
11798
|
+
if (workspace)
|
|
11799
|
+
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, reads, toolInputs));
|
|
11800
|
+
return response;
|
|
11801
|
+
} finally {
|
|
11802
|
+
reporter?.close();
|
|
11803
|
+
if (!keep)
|
|
11804
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11805
|
+
});
|
|
10752
11806
|
}
|
|
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
|
-
}
|
|
11807
|
+
}
|
|
11808
|
+
/** In-session gate repair (ADR 0017): resume the session this executor named, from the run's own COPILOT_HOME. */
|
|
11809
|
+
async continue(continuation, followUp, signal, ctx) {
|
|
11810
|
+
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
11811
|
+
const handle = continuation;
|
|
11812
|
+
if (!handle || typeof handle.sessionId !== "string" || typeof handle.home !== "string") {
|
|
11813
|
+
throw new Error("copilot continue: missing session to resume");
|
|
10766
11814
|
}
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
11815
|
+
const scratch = path_1.default.dirname(handle.home);
|
|
11816
|
+
const cwd = path_1.default.join(scratch, "resume");
|
|
11817
|
+
await fs_1.promises.mkdir(cwd, { recursive: true });
|
|
11818
|
+
const spec = {
|
|
11819
|
+
rendering: "producer",
|
|
11820
|
+
outputLabel: handle.outputLabel,
|
|
11821
|
+
outputSchema: handle.outputSchema
|
|
11822
|
+
};
|
|
11823
|
+
const { response } = await this.runCopilot({
|
|
11824
|
+
args: [...copilotReviewArgs(), `--resume=${handle.sessionId}`, "-C", cwd],
|
|
11825
|
+
prompt: followUp,
|
|
11826
|
+
spec,
|
|
11827
|
+
cwd,
|
|
11828
|
+
home: handle.home,
|
|
11829
|
+
scratch,
|
|
11830
|
+
model: handle.model,
|
|
11831
|
+
effort: handle.effort,
|
|
11832
|
+
signal,
|
|
11833
|
+
reporter: null,
|
|
11834
|
+
transcriptName: `${handle.sessionId}-repair`
|
|
11835
|
+
});
|
|
11836
|
+
response.continuation = handle;
|
|
11837
|
+
return response;
|
|
11838
|
+
}
|
|
11839
|
+
/** One real turn under the review flags. A signed-out CLI fails before any model call. */
|
|
11840
|
+
async checkReadiness(signal) {
|
|
11841
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-copilot-ready-"));
|
|
11842
|
+
try {
|
|
11843
|
+
const home = await makeRunHome(scratch);
|
|
11844
|
+
let run;
|
|
11845
|
+
try {
|
|
11846
|
+
run = await withOperatorLogin(home, () => (0, agent_process_1.runAgentProcess)({
|
|
11847
|
+
bin: "copilot",
|
|
11848
|
+
args: [...copilotReviewArgs(), "-C", scratch, "-p", READINESS_PROMPT],
|
|
11849
|
+
cwd: scratch,
|
|
11850
|
+
env: runEnv(home),
|
|
11851
|
+
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
11852
|
+
toolName: "the copilot CLI",
|
|
11853
|
+
signal
|
|
11854
|
+
}));
|
|
11855
|
+
} catch (err) {
|
|
11856
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11857
|
+
return { ready: false, reason: message === "Dispatch aborted" ? "readiness check aborted" : message };
|
|
10774
11858
|
}
|
|
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);
|
|
11859
|
+
if (run.spawnError) {
|
|
11860
|
+
return {
|
|
11861
|
+
ready: false,
|
|
11862
|
+
reason: run.spawnError.code === "ENOENT" ? `the copilot CLI is not on this machine's PATH (${run.spawnError.message})` : run.spawnError.message
|
|
11863
|
+
};
|
|
10786
11864
|
}
|
|
11865
|
+
const events = parseCopilotEvents(run.stdout);
|
|
11866
|
+
if (run.code === 0 && !events.failure)
|
|
11867
|
+
return { ready: true };
|
|
11868
|
+
return {
|
|
11869
|
+
ready: false,
|
|
11870
|
+
reason: events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? `the copilot CLI exited with code ${run.code}`
|
|
11871
|
+
};
|
|
11872
|
+
} finally {
|
|
11873
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
11874
|
+
});
|
|
10787
11875
|
}
|
|
10788
11876
|
}
|
|
10789
|
-
|
|
11877
|
+
/**
|
|
11878
|
+
* The `model` setting's values as `copilot help config` lists them: the
|
|
11879
|
+
* CLI's built-in set. It does not reflect an organization's model policy,
|
|
11880
|
+
* so a model listed here can still be refused for one account, and that
|
|
11881
|
+
* review fails in copilot's own words. Each takes the CLI-wide effort list.
|
|
11882
|
+
*/
|
|
11883
|
+
async listModels(signal) {
|
|
11884
|
+
const stdout = await (0, models_1.runListing)("copilot", ["help", "config"], signal);
|
|
11885
|
+
return stdout === null ? [] : parseCopilotModels(stdout);
|
|
11886
|
+
}
|
|
11887
|
+
tmpBase() {
|
|
11888
|
+
return this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
11889
|
+
}
|
|
11890
|
+
async runCopilot(opts) {
|
|
11891
|
+
const { spec, model, effort } = opts;
|
|
11892
|
+
const usageFile = path_1.default.join(opts.scratch, `usage-${(0, crypto_1.randomUUID)()}.json`);
|
|
11893
|
+
const args = [...opts.args, `--usage-output-file=${usageFile}`, `--log-dir=${path_1.default.join(opts.home, "logs")}`];
|
|
11894
|
+
if (model)
|
|
11895
|
+
args.push(`--model=${model}`);
|
|
11896
|
+
if (effort)
|
|
11897
|
+
args.push(`--reasoning-effort=${effort}`);
|
|
11898
|
+
const tap = opts.reporter ? copilotProgressTap(opts.reporter) : null;
|
|
11899
|
+
const run = await withOperatorLogin(opts.home, () => (0, agent_process_1.runAgentProcess)({
|
|
11900
|
+
bin: "copilot",
|
|
11901
|
+
args,
|
|
11902
|
+
cwd: opts.cwd,
|
|
11903
|
+
env: runEnv(opts.home),
|
|
11904
|
+
// The prompt comes from stdin: a review prompt carries the diff, far
|
|
11905
|
+
// past what argv should hold, and argv is visible to every process.
|
|
11906
|
+
stdin: opts.prompt,
|
|
11907
|
+
idleTimeoutMs: this.config.timeout ?? agent_common_1.DEFAULT_AGENT_TIMEOUT,
|
|
11908
|
+
toolName: "Copilot",
|
|
11909
|
+
signal: opts.signal,
|
|
11910
|
+
...tap ? { onStdout: (text) => tap.push(text) } : {}
|
|
11911
|
+
}));
|
|
11912
|
+
tap?.end();
|
|
11913
|
+
if (run.spawnError) {
|
|
11914
|
+
if (run.spawnError.code === "ENOENT") {
|
|
11915
|
+
throw new types_1.ExecutorEnvironmentError(`the copilot CLI is not on this machine's PATH (${run.spawnError.message})`, COPILOT_FAULTS.installRemedy);
|
|
11916
|
+
}
|
|
11917
|
+
throw new Error(run.spawnError.message);
|
|
11918
|
+
}
|
|
11919
|
+
if (this.config.keepTranscripts) {
|
|
11920
|
+
void (0, agent_process_1.keepTranscript)((0, apply_1.expandHome)(this.config.keepTranscripts), opts.transcriptName, run.stdout);
|
|
11921
|
+
}
|
|
11922
|
+
const events = parseCopilotEvents(run.stdout);
|
|
11923
|
+
if (run.code !== 0 || events.failure) {
|
|
11924
|
+
const said = events.failure ?? (0, agent_process_1.firstLine)(run.stderr) ?? (0, agent_process_1.firstLine)(run.stdout);
|
|
11925
|
+
const message = said ? `Copilot ${run.code === 0 ? "reported a failure" : `exited with code ${run.code}`}: ${said}` : `Copilot exited with code ${run.code}`;
|
|
11926
|
+
throw (0, agent_process_1.agentFailure)(true, message, { apiErrorStatus: null, text: said }, COPILOT_FAULTS);
|
|
11927
|
+
}
|
|
11928
|
+
if (events.finalText === null)
|
|
11929
|
+
throw new Error("Copilot finished without a final message");
|
|
11930
|
+
const answerText = events.finalText;
|
|
11931
|
+
const parsed = (0, json_answer_1.extractJsonObject)(answerText);
|
|
11932
|
+
if (parsed === void 0 && hasSchema(spec)) {
|
|
11933
|
+
throw new Error(`copilot's final message contained no JSON object (message head: ${JSON.stringify(answerText.slice(0, 160))})`);
|
|
11934
|
+
}
|
|
11935
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
11936
|
+
const usage = await readUsage(usageFile);
|
|
11937
|
+
return {
|
|
11938
|
+
response: {
|
|
11939
|
+
outputs: [{ label: spec.outputLabel, content: parsed ?? { raw: answerText } }],
|
|
11940
|
+
rawText: answerText,
|
|
11941
|
+
parsed,
|
|
11942
|
+
// The model the session actually ran, from its usage file, over the one asked for.
|
|
11943
|
+
usage: {
|
|
11944
|
+
aiProvider: "copilot",
|
|
11945
|
+
...model ? { aiModel: model } : {},
|
|
11946
|
+
...effort ? { aiEffort: effort } : {},
|
|
11947
|
+
...usage ?? {}
|
|
11948
|
+
}
|
|
11949
|
+
},
|
|
11950
|
+
reads: events.readPaths,
|
|
11951
|
+
toolInputs: events.toolInputs
|
|
11952
|
+
};
|
|
11953
|
+
}
|
|
11954
|
+
};
|
|
11955
|
+
function hasSchema(spec) {
|
|
11956
|
+
return Boolean(spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0);
|
|
10790
11957
|
}
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
11958
|
+
function withSchema(prompt, spec) {
|
|
11959
|
+
if (!hasSchema(spec))
|
|
11960
|
+
return prompt;
|
|
11961
|
+
return `${prompt}
|
|
11962
|
+
|
|
11963
|
+
---
|
|
11964
|
+
|
|
11965
|
+
Your final message must be one JSON object, and nothing else, that validates against this JSON Schema:
|
|
11966
|
+
|
|
11967
|
+
${JSON.stringify(spec.outputSchema)}
|
|
11968
|
+
`;
|
|
10796
11969
|
}
|
|
10797
|
-
|
|
11970
|
+
var RUN_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
11971
|
+
async function pruneRuns(runs) {
|
|
11972
|
+
let entries;
|
|
10798
11973
|
try {
|
|
10799
|
-
await fs_1.promises.
|
|
10800
|
-
|
|
10801
|
-
|
|
11974
|
+
entries = await fs_1.promises.readdir(runs);
|
|
11975
|
+
} catch {
|
|
11976
|
+
return;
|
|
11977
|
+
}
|
|
11978
|
+
const cutoff = Date.now() - RUN_RETENTION_MS;
|
|
11979
|
+
await Promise.all(entries.map(async (name) => {
|
|
11980
|
+
const dir = path_1.default.join(runs, name);
|
|
11981
|
+
try {
|
|
11982
|
+
if ((await fs_1.promises.stat(dir)).mtimeMs < cutoff)
|
|
11983
|
+
await fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
11984
|
+
} catch {
|
|
11985
|
+
}
|
|
11986
|
+
}));
|
|
11987
|
+
}
|
|
11988
|
+
async function makeRunHome(scratch) {
|
|
11989
|
+
const home = path_1.default.join(scratch, "copilot-home");
|
|
11990
|
+
await fs_1.promises.mkdir(home, { mode: 448 });
|
|
11991
|
+
await fs_1.promises.writeFile(path_1.default.join(home, "settings.json"), JSON.stringify(RUN_SETTINGS));
|
|
11992
|
+
const bin = path_1.default.join(scratch, "bin");
|
|
11993
|
+
await fs_1.promises.mkdir(bin, { recursive: true });
|
|
11994
|
+
await fs_1.promises.writeFile(path_1.default.join(bin, "gh"), REFUSING_GH, { mode: 493 });
|
|
11995
|
+
return home;
|
|
11996
|
+
}
|
|
11997
|
+
var REFUSING_GH = '#!/bin/sh\necho "gh is not available to a Sifter review" >&2\nexit 1\n';
|
|
11998
|
+
var LOGIN_POINTER_KEYS = ["lastLoggedInUser", "loggedInUsers"];
|
|
11999
|
+
var LOGIN_TOKEN_KEYS = ["authTokens"];
|
|
12000
|
+
async function withOperatorLogin(home, fn) {
|
|
12001
|
+
const file = path_1.default.join(home, "config.json");
|
|
12002
|
+
const login = await operatorLogin();
|
|
12003
|
+
if (login)
|
|
12004
|
+
await fs_1.promises.writeFile(file, JSON.stringify(login), { mode: 384 });
|
|
12005
|
+
else
|
|
12006
|
+
await fs_1.promises.rm(file, { force: true });
|
|
12007
|
+
try {
|
|
12008
|
+
return await fn();
|
|
12009
|
+
} finally {
|
|
12010
|
+
await forgetToken(file);
|
|
12011
|
+
}
|
|
12012
|
+
}
|
|
12013
|
+
async function forgetToken(file) {
|
|
12014
|
+
let config;
|
|
12015
|
+
try {
|
|
12016
|
+
config = await readJsonc(file);
|
|
10802
12017
|
} catch (err) {
|
|
10803
|
-
|
|
12018
|
+
if (err.code !== "ENOENT")
|
|
12019
|
+
await fs_1.promises.rm(file, { force: true }).catch(() => {
|
|
12020
|
+
});
|
|
12021
|
+
return;
|
|
10804
12022
|
}
|
|
12023
|
+
if (!LOGIN_TOKEN_KEYS.some((k) => k in config))
|
|
12024
|
+
return;
|
|
12025
|
+
for (const k of LOGIN_TOKEN_KEYS)
|
|
12026
|
+
delete config[k];
|
|
12027
|
+
await fs_1.promises.writeFile(file, JSON.stringify(config), { mode: 384 }).catch(() => fs_1.promises.rm(file, { force: true }));
|
|
10805
12028
|
}
|
|
10806
|
-
function
|
|
10807
|
-
const
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
12029
|
+
async function operatorLogin() {
|
|
12030
|
+
const home = process.env.COPILOT_HOME ?? path_1.default.join(os_1.default.homedir(), ".copilot");
|
|
12031
|
+
try {
|
|
12032
|
+
const parsed = await readJsonc(path_1.default.join(home, "config.json"));
|
|
12033
|
+
const keys = [...LOGIN_POINTER_KEYS, ...LOGIN_TOKEN_KEYS].filter((k) => k in parsed);
|
|
12034
|
+
return keys.length > 0 ? Object.fromEntries(keys.map((k) => [k, parsed[k]])) : null;
|
|
12035
|
+
} catch {
|
|
12036
|
+
return null;
|
|
12037
|
+
}
|
|
12038
|
+
}
|
|
12039
|
+
async function readJsonc(file) {
|
|
12040
|
+
const raw = await fs_1.promises.readFile(file, "utf8");
|
|
12041
|
+
return JSON.parse(raw.split("\n").filter((line) => !line.trim().startsWith("//")).join("\n"));
|
|
12042
|
+
}
|
|
12043
|
+
function runEnv(home) {
|
|
12044
|
+
const bin = path_1.default.join(path_1.default.dirname(home), "bin");
|
|
12045
|
+
return {
|
|
12046
|
+
...process.env,
|
|
12047
|
+
PATH: `${bin}${path_1.default.delimiter}${process.env.PATH ?? ""}`,
|
|
12048
|
+
COPILOT_HOME: home,
|
|
12049
|
+
COPILOT_AUTO_UPDATE: "false"
|
|
12050
|
+
};
|
|
12051
|
+
}
|
|
12052
|
+
async function readUsage(file) {
|
|
12053
|
+
try {
|
|
12054
|
+
const raw = JSON.parse(await fs_1.promises.readFile(file, "utf8"));
|
|
12055
|
+
return usageOf(raw);
|
|
12056
|
+
} catch {
|
|
12057
|
+
return null;
|
|
12058
|
+
}
|
|
12059
|
+
}
|
|
12060
|
+
function usageOf(raw) {
|
|
12061
|
+
const models = Object.entries(raw.modelMetrics ?? {});
|
|
12062
|
+
if (models.length === 0)
|
|
12063
|
+
return null;
|
|
12064
|
+
const num = (v) => typeof v === "number" ? v : 0;
|
|
12065
|
+
let input = 0;
|
|
12066
|
+
let output = 0;
|
|
12067
|
+
for (const [, m] of models) {
|
|
12068
|
+
input += num(m.usage?.inputTokens) + num(m.usage?.cacheReadTokens);
|
|
12069
|
+
output += num(m.usage?.outputTokens);
|
|
12070
|
+
}
|
|
12071
|
+
return { inputTokens: input, outputTokens: output, ...models.length === 1 ? { aiModel: models[0][0] } : {} };
|
|
12072
|
+
}
|
|
12073
|
+
function copilotProgressTap(reporter) {
|
|
12074
|
+
return new progress_1.JsonLineTap((event) => {
|
|
12075
|
+
const data = event.data ?? {};
|
|
12076
|
+
if (event.type === "tool.execution_start" && typeof data.toolName === "string") {
|
|
12077
|
+
const args = data.arguments ?? {};
|
|
12078
|
+
const target = [args.path, args.pattern].find((v) => typeof v === "string");
|
|
12079
|
+
reporter.tool(data.toolName, target);
|
|
12080
|
+
} else if (event.type === "assistant.reasoning" && typeof data.content === "string") {
|
|
12081
|
+
reporter.reasoning(data.content);
|
|
12082
|
+
} else if (event.type === "assistant.turn_end") {
|
|
12083
|
+
reporter.flush();
|
|
12084
|
+
}
|
|
12085
|
+
});
|
|
12086
|
+
}
|
|
12087
|
+
function parseCopilotEvents(stdout) {
|
|
12088
|
+
let finalText = null;
|
|
12089
|
+
const readPaths = [];
|
|
12090
|
+
const toolInputs = [];
|
|
12091
|
+
const started = /* @__PURE__ */ new Map();
|
|
12092
|
+
let lastError = null;
|
|
12093
|
+
for (const line of stdout.split("\n")) {
|
|
12094
|
+
const trimmed = line.trim();
|
|
12095
|
+
if (!trimmed.startsWith("{"))
|
|
12096
|
+
continue;
|
|
12097
|
+
let event;
|
|
12098
|
+
try {
|
|
12099
|
+
event = JSON.parse(trimmed);
|
|
12100
|
+
} catch {
|
|
10815
12101
|
continue;
|
|
10816
12102
|
}
|
|
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;
|
|
12103
|
+
const data = event.data ?? {};
|
|
12104
|
+
switch (event.type) {
|
|
12105
|
+
case "tool.execution_start": {
|
|
12106
|
+
if (typeof data.toolCallId !== "string" || typeof data.toolName !== "string")
|
|
10843
12107
|
break;
|
|
12108
|
+
const args = data.arguments ?? {};
|
|
12109
|
+
started.set(data.toolCallId, { name: data.toolName, args });
|
|
12110
|
+
toolInputs.push(JSON.stringify(args));
|
|
12111
|
+
break;
|
|
12112
|
+
}
|
|
12113
|
+
case "tool.execution_complete": {
|
|
12114
|
+
const call = typeof data.toolCallId === "string" ? started.get(data.toolCallId) : void 0;
|
|
12115
|
+
if (call && data.success === true && call.name === "view" && typeof call.args.path === "string") {
|
|
12116
|
+
readPaths.push(call.args.path);
|
|
10844
12117
|
}
|
|
12118
|
+
break;
|
|
10845
12119
|
}
|
|
12120
|
+
case "assistant.message":
|
|
12121
|
+
if (typeof data.content === "string" && data.content.trim().length > 0) {
|
|
12122
|
+
finalText = data.content;
|
|
12123
|
+
lastError = null;
|
|
12124
|
+
}
|
|
12125
|
+
break;
|
|
12126
|
+
default:
|
|
12127
|
+
if (typeof event.type === "string" && /(^|\.)error$/.test(event.type)) {
|
|
12128
|
+
const message = [data.message, data.error?.message].find((v) => typeof v === "string");
|
|
12129
|
+
if (message)
|
|
12130
|
+
lastError = message;
|
|
12131
|
+
}
|
|
10846
12132
|
}
|
|
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
12133
|
}
|
|
10854
|
-
return
|
|
12134
|
+
return { finalText, readPaths, toolInputs, failure: finalText === null ? lastError : null };
|
|
10855
12135
|
}
|
|
10856
|
-
function
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
12136
|
+
function parseCopilotModels(helpText) {
|
|
12137
|
+
const lines = helpText.split("\n");
|
|
12138
|
+
const start = lines.findIndex((line) => /^\s*`model`:/.test(line));
|
|
12139
|
+
if (start === -1)
|
|
12140
|
+
return [];
|
|
12141
|
+
const models = [];
|
|
12142
|
+
for (const line of lines.slice(start + 1)) {
|
|
12143
|
+
const value = /^\s+-\s+"([^"]+)"\s*$/.exec(line);
|
|
12144
|
+
if (!value)
|
|
12145
|
+
break;
|
|
12146
|
+
models.push({ id: value[1], label: value[1], efforts: [...models_1.REASONING_EFFORTS] });
|
|
10862
12147
|
}
|
|
12148
|
+
return models;
|
|
12149
|
+
}
|
|
12150
|
+
function createCopilotExecutor(config, capabilityId = "copilot") {
|
|
12151
|
+
const validated = (0, agent_common_1.parseAgentCommonConfig)(config, capabilityId);
|
|
12152
|
+
const effort = (0, models_1.parseReasoningEffort)(config.reasoningEffort, models_1.REASONING_EFFORTS);
|
|
12153
|
+
if (effort)
|
|
12154
|
+
validated.reasoningEffort = effort;
|
|
12155
|
+
return new CopilotExecutor(validated);
|
|
10863
12156
|
}
|
|
10864
12157
|
}
|
|
10865
12158
|
});
|
|
@@ -10889,6 +12182,14 @@ var require_http_api = __commonJS({
|
|
|
10889
12182
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
10890
12183
|
};
|
|
10891
12184
|
}
|
|
12185
|
+
/**
|
|
12186
|
+
* The one model it runs. This executor takes no model hint (every call
|
|
12187
|
+
* names the configured model), so reporting the endpoint's catalog would
|
|
12188
|
+
* offer picks it would silently ignore.
|
|
12189
|
+
*/
|
|
12190
|
+
async listModels() {
|
|
12191
|
+
return [{ id: this.config.model, label: this.config.model }];
|
|
12192
|
+
}
|
|
10892
12193
|
async execute(dispatch, signal) {
|
|
10893
12194
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
10894
12195
|
const outputLabel = spec.outputLabel;
|
|
@@ -11829,10 +13130,12 @@ var require_anthropic_api = __commonJS({
|
|
|
11829
13130
|
"use strict";
|
|
11830
13131
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11831
13132
|
exports2.createAnthropicApiExecutor = createAnthropicApiExecutor;
|
|
13133
|
+
exports2.parseAnthropicModels = parseAnthropicModels;
|
|
11832
13134
|
var loom_1 = require_dist();
|
|
11833
13135
|
var keep_1 = require_dist2();
|
|
11834
13136
|
var env_ref_1 = require_env_ref();
|
|
11835
13137
|
var validate_output_1 = require_validate_output();
|
|
13138
|
+
var models_1 = require_models();
|
|
11836
13139
|
var DEFAULT_BASE_URL = "https://api.anthropic.com/v1/messages";
|
|
11837
13140
|
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
11838
13141
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -11888,6 +13191,37 @@ var require_anthropic_api = __commonJS({
|
|
|
11888
13191
|
};
|
|
11889
13192
|
return this.callApi(handle.apiKey, body, spec, handle.outputLabel, signal);
|
|
11890
13193
|
}
|
|
13194
|
+
/**
|
|
13195
|
+
* The provider's own catalog, `/v1/models` beside the configured Messages
|
|
13196
|
+
* URL, asked with the operator's key. Dynamic mode reports nothing: its key
|
|
13197
|
+
* is a customer's, revealed per job, and there is none to ask with at start.
|
|
13198
|
+
*/
|
|
13199
|
+
async listModels(signal) {
|
|
13200
|
+
const apiKey = this.instance.staticKey;
|
|
13201
|
+
if (!apiKey)
|
|
13202
|
+
return [];
|
|
13203
|
+
const url = modelsUrl(this.instance.baseUrl);
|
|
13204
|
+
if (!url)
|
|
13205
|
+
return [];
|
|
13206
|
+
const controller = new AbortController();
|
|
13207
|
+
const abort = () => controller.abort();
|
|
13208
|
+
const timer = setTimeout(abort, models_1.LIST_MODELS_TIMEOUT_MS);
|
|
13209
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
13210
|
+
try {
|
|
13211
|
+
const response = await fetch(url, {
|
|
13212
|
+
headers: { "x-api-key": apiKey, "anthropic-version": ANTHROPIC_API_VERSION },
|
|
13213
|
+
signal: controller.signal
|
|
13214
|
+
});
|
|
13215
|
+
if (!response.ok)
|
|
13216
|
+
return [];
|
|
13217
|
+
return parseAnthropicModels(await response.json());
|
|
13218
|
+
} catch {
|
|
13219
|
+
return [];
|
|
13220
|
+
} finally {
|
|
13221
|
+
clearTimeout(timer);
|
|
13222
|
+
signal.removeEventListener("abort", abort);
|
|
13223
|
+
}
|
|
13224
|
+
}
|
|
11891
13225
|
/** Shared Messages-API call + parse + validate, used by execute and continue. */
|
|
11892
13226
|
async callApi(apiKey, body, spec, outputLabel, signal) {
|
|
11893
13227
|
const timeoutController = new AbortController();
|
|
@@ -11929,7 +13263,8 @@ var require_anthropic_api = __commonJS({
|
|
|
11929
13263
|
inputTokens: u.input_tokens,
|
|
11930
13264
|
outputTokens: u.output_tokens,
|
|
11931
13265
|
aiProvider: "anthropic",
|
|
11932
|
-
|
|
13266
|
+
// The model this call ran, which a hint may have chosen.
|
|
13267
|
+
aiModel: body.model
|
|
11933
13268
|
} : void 0;
|
|
11934
13269
|
const parsed = safeJsonParse(rawText);
|
|
11935
13270
|
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
@@ -11961,7 +13296,8 @@ var require_anthropic_api = __commonJS({
|
|
|
11961
13296
|
if (rawApiKeyFrom !== void 0 && rawApiKey !== void 0) {
|
|
11962
13297
|
throw new Error('AnthropicApiExecutor accepts either "apiKey" (static) or "apiKeyFrom" (dynamic), not both');
|
|
11963
13298
|
}
|
|
11964
|
-
const
|
|
13299
|
+
const staticKey = rawApiKeyFrom === void 0 ? resolveStaticKey(rawApiKey) : null;
|
|
13300
|
+
const resolveKey = staticKey === null ? buildDynamicResolver(config, deps) : async () => staticKey;
|
|
11965
13301
|
let baseUrl = DEFAULT_BASE_URL;
|
|
11966
13302
|
if (config.baseUrl !== void 0) {
|
|
11967
13303
|
if (typeof config.baseUrl !== "string" || config.baseUrl.length === 0) {
|
|
@@ -11992,6 +13328,7 @@ var require_anthropic_api = __commonJS({
|
|
|
11992
13328
|
}
|
|
11993
13329
|
return new AnthropicApiExecutor({
|
|
11994
13330
|
resolveKey,
|
|
13331
|
+
staticKey,
|
|
11995
13332
|
baseUrl,
|
|
11996
13333
|
model,
|
|
11997
13334
|
maxTokens,
|
|
@@ -11999,12 +13336,25 @@ var require_anthropic_api = __commonJS({
|
|
|
11999
13336
|
capabilityId
|
|
12000
13337
|
});
|
|
12001
13338
|
}
|
|
12002
|
-
function
|
|
13339
|
+
function resolveStaticKey(rawApiKey) {
|
|
12003
13340
|
if (typeof rawApiKey !== "string" || rawApiKey.length === 0) {
|
|
12004
13341
|
throw new Error('AnthropicApiExecutor requires a non-empty "apiKey" string in config (or "apiKeyFrom: \\"job-metadata\\"" for dynamic key mode)');
|
|
12005
13342
|
}
|
|
12006
|
-
|
|
12007
|
-
|
|
13343
|
+
return (0, env_ref_1.resolveEnvRef)(rawApiKey, "apiKey");
|
|
13344
|
+
}
|
|
13345
|
+
function modelsUrl(messagesUrl) {
|
|
13346
|
+
return /\/messages\/?$/.test(messagesUrl) ? messagesUrl.replace(/\/messages\/?$/, "/models") : null;
|
|
13347
|
+
}
|
|
13348
|
+
function parseAnthropicModels(body) {
|
|
13349
|
+
const data = body?.data;
|
|
13350
|
+
if (!Array.isArray(data))
|
|
13351
|
+
return [];
|
|
13352
|
+
return data.flatMap((entry) => {
|
|
13353
|
+
const { id, display_name: displayName } = entry ?? {};
|
|
13354
|
+
if (typeof id !== "string" || id.length === 0)
|
|
13355
|
+
return [];
|
|
13356
|
+
return [{ id, label: typeof displayName === "string" && displayName ? displayName : id }];
|
|
13357
|
+
});
|
|
12008
13358
|
}
|
|
12009
13359
|
function buildDynamicResolver(config, deps) {
|
|
12010
13360
|
if (config.apiKeyFrom !== "job-metadata") {
|
|
@@ -12502,6 +13852,8 @@ var require_checks = __commonJS({
|
|
|
12502
13852
|
var path_1 = require("path");
|
|
12503
13853
|
var registry_1 = require_registry();
|
|
12504
13854
|
var claude_code_1 = require_claude_code();
|
|
13855
|
+
var codex_1 = require_codex();
|
|
13856
|
+
var copilot_1 = require_copilot();
|
|
12505
13857
|
var http_api_1 = require_http_api();
|
|
12506
13858
|
var anthropic_api_1 = require_anthropic_api();
|
|
12507
13859
|
var webhook_1 = require_webhook();
|
|
@@ -12513,9 +13865,21 @@ var require_checks = __commonJS({
|
|
|
12513
13865
|
var scaffold_1 = require_scaffold();
|
|
12514
13866
|
var NODE_FLOOR = "20.0.0";
|
|
12515
13867
|
var DISK_FLOOR_BYTES = 1024 * 1024 * 1024;
|
|
12516
|
-
var
|
|
13868
|
+
var AGENTS = [
|
|
13869
|
+
{ executor: "claude-code", bin: "claude", install: "install Claude Code (npm install -g @anthropic-ai/claude-code)" },
|
|
13870
|
+
{ executor: "codex", bin: "codex", install: "install Codex (brew install codex, or npm install -g @openai/codex)" },
|
|
13871
|
+
{
|
|
13872
|
+
executor: "copilot",
|
|
13873
|
+
bin: "copilot",
|
|
13874
|
+
install: "install GitHub Copilot CLI (brew install --cask copilot-cli, or npm install -g @github/copilot)"
|
|
13875
|
+
}
|
|
13876
|
+
];
|
|
13877
|
+
var AGENT_EXECUTORS2 = AGENTS.map((a) => a.executor);
|
|
13878
|
+
var DEFAULT_AGENT = "claude-code";
|
|
12517
13879
|
var BUILT_IN_EXECUTORS = [
|
|
12518
13880
|
["claude-code", claude_code_1.createClaudeCodeExecutor],
|
|
13881
|
+
["codex", codex_1.createCodexExecutor],
|
|
13882
|
+
["copilot", copilot_1.createCopilotExecutor],
|
|
12519
13883
|
["http-api", http_api_1.createHttpApiExecutor],
|
|
12520
13884
|
["anthropic-api", anthropic_api_1.createAnthropicApiExecutor],
|
|
12521
13885
|
["webhook", webhook_1.createWebhookExecutor],
|
|
@@ -12544,22 +13908,27 @@ var require_checks = __commonJS({
|
|
|
12544
13908
|
function configuredExecutors(config) {
|
|
12545
13909
|
return Object.keys(config.executors ?? {});
|
|
12546
13910
|
}
|
|
12547
|
-
function
|
|
12548
|
-
const
|
|
12549
|
-
|
|
13911
|
+
function activeAgent(config) {
|
|
13912
|
+
const configured = configuredExecutors(config);
|
|
13913
|
+
const pooled = config.pools.map((p) => p.executor).find((e) => AGENT_EXECUTORS2.includes(e) && configured.includes(e));
|
|
13914
|
+
return pooled ?? configured.find((e) => AGENT_EXECUTORS2.includes(e)) ?? null;
|
|
13915
|
+
}
|
|
13916
|
+
function agentSettings(config, subject) {
|
|
13917
|
+
const executor = activeAgent(config);
|
|
13918
|
+
if (executor === null)
|
|
12550
13919
|
return null;
|
|
12551
|
-
const
|
|
12552
|
-
|
|
13920
|
+
const user = config.executors[executor] ?? {};
|
|
13921
|
+
const defaults = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[executor] ?? {};
|
|
13922
|
+
return { executor, settings: { ...defaults, ...user } };
|
|
12553
13923
|
}
|
|
12554
13924
|
function claudeSettingsOf(config) {
|
|
12555
|
-
return config.executors[
|
|
13925
|
+
return config.executors["claude-code"]?.claudeSettings;
|
|
12556
13926
|
}
|
|
12557
13927
|
function describeLogin(status) {
|
|
12558
13928
|
return [status.authMethod ?? "unknown method", status.subscriptionType].filter((part) => part !== null).join(", ");
|
|
12559
13929
|
}
|
|
12560
13930
|
function runRoot(config, subject, osTmpDir) {
|
|
12561
|
-
const
|
|
12562
|
-
const tmpDir = settings?.tmpDir;
|
|
13931
|
+
const tmpDir = agentSettings(config, subject)?.settings.tmpDir;
|
|
12563
13932
|
return typeof tmpDir === "string" && tmpDir.length > 0 ? (0, apply_1.expandHome)(tmpDir) : osTmpDir;
|
|
12564
13933
|
}
|
|
12565
13934
|
async function withConfig(name, probes, body) {
|
|
@@ -12585,13 +13954,13 @@ var require_checks = __commonJS({
|
|
|
12585
13954
|
};
|
|
12586
13955
|
}
|
|
12587
13956
|
function initRunRoot(plan, subject, osTmpDir) {
|
|
12588
|
-
if (plan.executor
|
|
13957
|
+
if (plan.executor === void 0 || !AGENT_EXECUTORS2.includes(plan.executor))
|
|
12589
13958
|
return osTmpDir;
|
|
12590
13959
|
if (subject.requestedTmpDir !== void 0 && subject.requestedTmpDir.length > 0) {
|
|
12591
13960
|
return (0, apply_1.expandHome)(subject.requestedTmpDir);
|
|
12592
13961
|
}
|
|
12593
13962
|
const settings = {
|
|
12594
|
-
...(0, environment_1.executorDefaultsFor)(subject.brand, subject.brand.keepRegistration.jobsApiUrl)?.[
|
|
13963
|
+
...(0, environment_1.executorDefaultsFor)(subject.brand, subject.brand.keepRegistration.jobsApiUrl)?.[plan.executor] ?? {},
|
|
12595
13964
|
...plan.keptBlock ?? {}
|
|
12596
13965
|
};
|
|
12597
13966
|
const tmpDir = settings.tmpDir;
|
|
@@ -12787,7 +14156,7 @@ var require_checks = __commonJS({
|
|
|
12787
14156
|
}
|
|
12788
14157
|
const covering = built.filter((e) => e.strategies.includes(loom_1.PROMPT_EXECUTION_STRATEGY));
|
|
12789
14158
|
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. ${
|
|
14159
|
+
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
14160
|
}
|
|
12792
14161
|
return pass("executor-coverage", `${covering.map((e) => e.capability.id).join(", ")} handle${covering.length === 1 ? "s" : ""} ${loom_1.PROMPT_EXECUTION_STRATEGY}`);
|
|
12793
14162
|
});
|
|
@@ -12798,13 +14167,13 @@ var require_checks = __commonJS({
|
|
|
12798
14167
|
phases: ["init", "run", "doctor"],
|
|
12799
14168
|
async run(probes, subject) {
|
|
12800
14169
|
if (subject.phase === "init") {
|
|
12801
|
-
return atInit("git-cli", probes, subject, () => toolVerdict("git-cli", "git", probes, subject, {
|
|
14170
|
+
return atInit("git-cli", AGENT_EXECUTORS2, probes, subject, () => toolVerdict("git-cli", "git", probes, subject, {
|
|
12802
14171
|
missingRemedy: "install git, or configure an executor that needs no checkout"
|
|
12803
14172
|
}));
|
|
12804
14173
|
}
|
|
12805
14174
|
return withConfig("git-cli", probes, async (config) => {
|
|
12806
|
-
if (
|
|
12807
|
-
return skip("git-cli",
|
|
14175
|
+
if (activeAgent(config) === null) {
|
|
14176
|
+
return skip("git-cli", "no agent executor configured; nothing checks out a workspace");
|
|
12808
14177
|
}
|
|
12809
14178
|
return toolVerdict("git-cli", "git", probes, subject, {
|
|
12810
14179
|
missingRemedy: "install git, or configure an executor that needs no checkout"
|
|
@@ -12812,28 +14181,32 @@ var require_checks = __commonJS({
|
|
|
12812
14181
|
});
|
|
12813
14182
|
}
|
|
12814
14183
|
};
|
|
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`);
|
|
14184
|
+
function agentCli(agent) {
|
|
14185
|
+
const name = `${agent.bin}-cli`;
|
|
14186
|
+
return {
|
|
14187
|
+
name,
|
|
14188
|
+
phases: ["init", "run", "doctor"],
|
|
14189
|
+
async run(probes, subject) {
|
|
14190
|
+
if (subject.phase === "init") {
|
|
14191
|
+
return atInit(name, [agent.executor], probes, subject, () => toolVerdict(name, agent.bin, probes, subject, { missingRemedy: agent.install }));
|
|
12825
14192
|
}
|
|
12826
|
-
return
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
14193
|
+
return withConfig(name, probes, async (config) => {
|
|
14194
|
+
if (!configuredExecutors(config).includes(agent.executor)) {
|
|
14195
|
+
return skip(name, `no ${agent.executor} executor configured`);
|
|
14196
|
+
}
|
|
14197
|
+
return toolVerdict(name, agent.bin, probes, subject, { missingRemedy: agent.install });
|
|
14198
|
+
});
|
|
14199
|
+
}
|
|
14200
|
+
};
|
|
14201
|
+
}
|
|
14202
|
+
var [CLAUDE, CODEX, COPILOT] = AGENTS;
|
|
12830
14203
|
var claudeAuth = {
|
|
12831
14204
|
name: "claude-auth",
|
|
12832
14205
|
phases: ["run", "doctor"],
|
|
12833
14206
|
async run(probes, subject) {
|
|
12834
14207
|
return withConfig("claude-auth", probes, async (config) => {
|
|
12835
|
-
if (!configuredExecutors(config).includes(
|
|
12836
|
-
return skip("claude-auth", `no ${
|
|
14208
|
+
if (!configuredExecutors(config).includes(CLAUDE.executor)) {
|
|
14209
|
+
return skip("claude-auth", `no ${CLAUDE.executor} executor configured`);
|
|
12837
14210
|
}
|
|
12838
14211
|
if (config.credentials !== void 0) {
|
|
12839
14212
|
return skip("claude-auth", "hosted mode: the model key is revealed per job, not read from a login");
|
|
@@ -12867,6 +14240,64 @@ var require_checks = __commonJS({
|
|
|
12867
14240
|
});
|
|
12868
14241
|
}
|
|
12869
14242
|
};
|
|
14243
|
+
var codexAuth = {
|
|
14244
|
+
name: "codex-auth",
|
|
14245
|
+
phases: ["run", "doctor"],
|
|
14246
|
+
async run(probes, subject) {
|
|
14247
|
+
return withConfig("codex-auth", probes, async (config) => {
|
|
14248
|
+
const gate = await agentAuthGate("codex-auth", CODEX, config, probes);
|
|
14249
|
+
if (gate)
|
|
14250
|
+
return gate;
|
|
14251
|
+
if (subject.deep)
|
|
14252
|
+
return roundTripVerdict("codex-auth", CODEX, probes, "run `codex login`");
|
|
14253
|
+
const status = await probes.codexLoginStatus();
|
|
14254
|
+
if (status === null) {
|
|
14255
|
+
return warn("codex-auth", "`codex login status` gave no answer", "run `codex login status` to see why, or doctor --deep");
|
|
14256
|
+
}
|
|
14257
|
+
if (!status.loggedIn) {
|
|
14258
|
+
return fail("codex-auth", "the codex CLI is not signed in", "run `codex login`");
|
|
14259
|
+
}
|
|
14260
|
+
return pass("codex-auth", `${status.detail} (not verified)`);
|
|
14261
|
+
});
|
|
14262
|
+
}
|
|
14263
|
+
};
|
|
14264
|
+
var copilotAuth = {
|
|
14265
|
+
name: "copilot-auth",
|
|
14266
|
+
phases: ["run", "doctor"],
|
|
14267
|
+
async run(probes, subject) {
|
|
14268
|
+
return withConfig("copilot-auth", probes, async (config) => {
|
|
14269
|
+
const gate = await agentAuthGate("copilot-auth", COPILOT, config, probes);
|
|
14270
|
+
if (gate)
|
|
14271
|
+
return gate;
|
|
14272
|
+
if (subject.deep)
|
|
14273
|
+
return roundTripVerdict("copilot-auth", COPILOT, probes, "run `copilot login`");
|
|
14274
|
+
const token = probes.ambientCopilotToken();
|
|
14275
|
+
if (token !== null) {
|
|
14276
|
+
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`);
|
|
14277
|
+
}
|
|
14278
|
+
return pass("copilot-auth", "no token in the environment; the copilot login is used (not verified; doctor --deep spends one turn to prove it)");
|
|
14279
|
+
});
|
|
14280
|
+
}
|
|
14281
|
+
};
|
|
14282
|
+
async function agentAuthGate(name, agent, config, probes) {
|
|
14283
|
+
if (!configuredExecutors(config).includes(agent.executor)) {
|
|
14284
|
+
return skip(name, `no ${agent.executor} executor configured`);
|
|
14285
|
+
}
|
|
14286
|
+
if (config.credentials !== void 0) {
|
|
14287
|
+
return skip(name, `hosted mode: the ${agent.executor} executor refuses hosted runs`);
|
|
14288
|
+
}
|
|
14289
|
+
if (await probes.which(agent.bin) === null || !(await probes.versionOf(agent.bin)).ran) {
|
|
14290
|
+
return skip(name, `no working ${agent.bin} CLI to be signed in to; see ${agent.bin}-cli`);
|
|
14291
|
+
}
|
|
14292
|
+
return null;
|
|
14293
|
+
}
|
|
14294
|
+
async function roundTripVerdict(name, agent, probes, signIn) {
|
|
14295
|
+
const round = await probes.agentRoundTrip(agent.executor);
|
|
14296
|
+
if (!round.ok) {
|
|
14297
|
+
return fail(name, `the ${agent.bin} CLI refused a one-turn call: ${oneLine(round.error)}`, `${signIn}, then re-run doctor --deep`);
|
|
14298
|
+
}
|
|
14299
|
+
return pass(name, `one-turn call through the ${agent.bin} CLI succeeded`);
|
|
14300
|
+
}
|
|
12870
14301
|
var runRootWritable = {
|
|
12871
14302
|
name: "run-root-writable",
|
|
12872
14303
|
phases: ["init", "run", "doctor"],
|
|
@@ -12930,15 +14361,15 @@ var require_checks = __commonJS({
|
|
|
12930
14361
|
phases: ["run", "doctor"],
|
|
12931
14362
|
async run(probes, subject) {
|
|
12932
14363
|
return withConfig("overlay-hosts", probes, (config) => {
|
|
12933
|
-
const
|
|
12934
|
-
if (
|
|
12935
|
-
return skip("overlay-hosts",
|
|
14364
|
+
const agent = agentSettings(config, subject);
|
|
14365
|
+
if (agent === null) {
|
|
14366
|
+
return skip("overlay-hosts", "no agent executor configured");
|
|
12936
14367
|
}
|
|
12937
|
-
const hosts = settings.overlayAllowedHosts;
|
|
14368
|
+
const hosts = agent.settings.overlayAllowedHosts;
|
|
12938
14369
|
const origins = Array.isArray(hosts) ? hosts.filter((h) => typeof h === "string") : [];
|
|
12939
14370
|
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 ${
|
|
14371
|
+
const fallback = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[agent.executor]?.overlayAllowedHosts;
|
|
14372
|
+
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
14373
|
}
|
|
12943
14374
|
return pass("overlay-hosts", origins.join(", "));
|
|
12944
14375
|
});
|
|
@@ -12969,8 +14400,12 @@ var require_checks = __commonJS({
|
|
|
12969
14400
|
needsFetch(keepReachable),
|
|
12970
14401
|
executorCoverage,
|
|
12971
14402
|
gitCli,
|
|
12972
|
-
|
|
14403
|
+
agentCli(CLAUDE),
|
|
12973
14404
|
claudeAuth,
|
|
14405
|
+
agentCli(CODEX),
|
|
14406
|
+
codexAuth,
|
|
14407
|
+
agentCli(COPILOT),
|
|
14408
|
+
copilotAuth,
|
|
12974
14409
|
runRootWritable,
|
|
12975
14410
|
diskSpace,
|
|
12976
14411
|
overlayHosts,
|
|
@@ -13002,13 +14437,12 @@ var require_checks = __commonJS({
|
|
|
13002
14437
|
}
|
|
13003
14438
|
return `run \`${bin} --version\` to see why ${found} fails, then reinstall it (${missingRemedy})`;
|
|
13004
14439
|
}
|
|
13005
|
-
|
|
13006
|
-
async function atInit(name, probes, subject, body) {
|
|
14440
|
+
async function atInit(name, needs, probes, subject, body) {
|
|
13007
14441
|
const plan = initPlan(probes, subject);
|
|
13008
14442
|
if (!plan.ok)
|
|
13009
14443
|
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
|
|
14444
|
+
if (plan.value.executor === void 0 || !needs.includes(plan.value.executor)) {
|
|
14445
|
+
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
14446
|
}
|
|
13013
14447
|
return body();
|
|
13014
14448
|
}
|
|
@@ -20914,12 +22348,14 @@ var require_subscribe = __commonJS({
|
|
|
20914
22348
|
if (id === STOPPED)
|
|
20915
22349
|
return;
|
|
20916
22350
|
resolvedPoolIds.push(id);
|
|
22351
|
+
const perPool = opts.subscriber.poolMetadata?.(ref);
|
|
22352
|
+
const metadata = opts.subscriber.metadata !== void 0 || perPool !== void 0 ? { ...opts.subscriber.metadata, ...perPool } : void 0;
|
|
20917
22353
|
const registered = await untilAvailable(() => opts.jobsClient.registerMember(id, {
|
|
20918
22354
|
identityType: opts.subscriber.identityType,
|
|
20919
22355
|
identityId: opts.subscriber.identityId,
|
|
20920
22356
|
intelligence: opts.subscriber.intelligence,
|
|
20921
22357
|
...opts.subscriber.displayName !== void 0 ? { displayName: opts.subscriber.displayName } : {},
|
|
20922
|
-
...
|
|
22358
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
20923
22359
|
}), mySession);
|
|
20924
22360
|
if (registered === STOPPED)
|
|
20925
22361
|
return;
|
|
@@ -21233,7 +22669,7 @@ var require_schema4 = __commonJS({
|
|
|
21233
22669
|
message: 'Each pool must specify at least one of "id" or "name"'
|
|
21234
22670
|
});
|
|
21235
22671
|
var executorSchema = zod_1.z.object({
|
|
21236
|
-
type: zod_1.z.enum(["claude-code", "http-api", "anthropic-api", "webhook", "custom-script"])
|
|
22672
|
+
type: zod_1.z.enum(["claude-code", "codex", "copilot", "http-api", "anthropic-api", "webhook", "custom-script"])
|
|
21237
22673
|
}).passthrough();
|
|
21238
22674
|
var shuttleConfigSchema = zod_1.z.object({
|
|
21239
22675
|
auth: zod_1.z.object({
|
|
@@ -21483,11 +22919,14 @@ var require_probes = __commonJS({
|
|
|
21483
22919
|
var jobs_1 = require_dist4();
|
|
21484
22920
|
var scaffold_1 = require_scaffold();
|
|
21485
22921
|
var loader_1 = require_loader();
|
|
22922
|
+
var codex_1 = require_codex();
|
|
22923
|
+
var copilot_1 = require_copilot();
|
|
21486
22924
|
var store_1 = require_store();
|
|
21487
22925
|
var version_check_1 = require_version_check();
|
|
21488
22926
|
var PROBE_TIMEOUT_MS = 1e4;
|
|
21489
22927
|
var VERSION_TIMEOUT_MS = 1e4;
|
|
21490
22928
|
var DEEP_PROBE_PROMPT = "Reply with the single word: ok";
|
|
22929
|
+
var COPILOT_TOKEN_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
|
21491
22930
|
function createProbes(options) {
|
|
21492
22931
|
let observedLatest = null;
|
|
21493
22932
|
const observe = (response) => {
|
|
@@ -21708,6 +23147,24 @@ var require_probes = __commonJS({
|
|
|
21708
23147
|
}
|
|
21709
23148
|
return { ok: true, value: void 0 };
|
|
21710
23149
|
},
|
|
23150
|
+
async codexLoginStatus() {
|
|
23151
|
+
const outcome = await runCapturing("codex", ["login", "status"], VERSION_TIMEOUT_MS);
|
|
23152
|
+
if (outcome.ok) {
|
|
23153
|
+
const line = `${outcome.stdout}
|
|
23154
|
+
${outcome.stderr}`.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
23155
|
+
return { loggedIn: true, detail: line ?? "signed in" };
|
|
23156
|
+
}
|
|
23157
|
+
if (outcome.exited && /not logged in/i.test(outcome.reason)) {
|
|
23158
|
+
return { loggedIn: false, detail: "not logged in" };
|
|
23159
|
+
}
|
|
23160
|
+
return null;
|
|
23161
|
+
},
|
|
23162
|
+
ambientCopilotToken: () => COPILOT_TOKEN_VARS.find((name) => (process.env[name] ?? "").length > 0) ?? null,
|
|
23163
|
+
async agentRoundTrip(agent) {
|
|
23164
|
+
const executor = agent === "codex" ? (0, codex_1.createCodexExecutor)({}) : (0, copilot_1.createCopilotExecutor)({});
|
|
23165
|
+
const readiness = await executor.checkReadiness(AbortSignal.timeout(18e4));
|
|
23166
|
+
return readiness.ready ? { ok: true, value: void 0 } : { ok: false, error: readiness.reason };
|
|
23167
|
+
},
|
|
21711
23168
|
latestVersion: () => observedLatest
|
|
21712
23169
|
};
|
|
21713
23170
|
}
|
|
@@ -21774,7 +23231,7 @@ var require_probes = __commonJS({
|
|
|
21774
23231
|
child.on("close", (code, signal) => {
|
|
21775
23232
|
clearTimeout(timer);
|
|
21776
23233
|
if (code === 0) {
|
|
21777
|
-
settle({ ok: true, stdout: stdout.trim() });
|
|
23234
|
+
settle({ ok: true, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
21778
23235
|
return;
|
|
21779
23236
|
}
|
|
21780
23237
|
const status = code === null ? `was killed by ${signal ?? "a signal"}` : `exited ${code}`;
|
|
@@ -25380,6 +26837,37 @@ var require_logger = __commonJS({
|
|
|
25380
26837
|
}
|
|
25381
26838
|
});
|
|
25382
26839
|
|
|
26840
|
+
// ../../packages/shuttle/dist/core/demux.js
|
|
26841
|
+
var require_demux = __commonJS({
|
|
26842
|
+
"../../packages/shuttle/dist/core/demux.js"(exports2) {
|
|
26843
|
+
"use strict";
|
|
26844
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
26845
|
+
exports2.createDemux = createDemux;
|
|
26846
|
+
function createDemux(executors, pools) {
|
|
26847
|
+
const byStrategy = /* @__PURE__ */ new Map();
|
|
26848
|
+
for (const executor of executors) {
|
|
26849
|
+
for (const strategy of executor.strategies)
|
|
26850
|
+
byStrategy.set(strategy, executor);
|
|
26851
|
+
}
|
|
26852
|
+
const byName = new Map(executors.map((e) => [e.capability.id, e]));
|
|
26853
|
+
const byPool = /* @__PURE__ */ new Map();
|
|
26854
|
+
for (const pool of pools) {
|
|
26855
|
+
const named = byName.get(pool.executor);
|
|
26856
|
+
if (!named)
|
|
26857
|
+
continue;
|
|
26858
|
+
if (pool.id)
|
|
26859
|
+
byPool.set(`id:${pool.id}`, named);
|
|
26860
|
+
if (pool.name)
|
|
26861
|
+
byPool.set(`name:${pool.name}`, named);
|
|
26862
|
+
}
|
|
26863
|
+
return (pool, strategy) => {
|
|
26864
|
+
const named = byPool.get(`id:${pool.id}`) ?? byPool.get(`name:${pool.name}`);
|
|
26865
|
+
return named && named.strategies.includes(strategy) ? named : byStrategy.get(strategy);
|
|
26866
|
+
};
|
|
26867
|
+
}
|
|
26868
|
+
}
|
|
26869
|
+
});
|
|
26870
|
+
|
|
25383
26871
|
// ../../packages/shuttle/dist/observers/logger.js
|
|
25384
26872
|
var require_logger2 = __commonJS({
|
|
25385
26873
|
"../../packages/shuttle/dist/observers/logger.js"(exports2) {
|
|
@@ -25901,6 +27389,9 @@ var require_shuttle = __commonJS({
|
|
|
25901
27389
|
var logger_1 = require_logger();
|
|
25902
27390
|
var registry_1 = require_registry();
|
|
25903
27391
|
var claude_code_1 = require_claude_code();
|
|
27392
|
+
var demux_1 = require_demux();
|
|
27393
|
+
var codex_1 = require_codex();
|
|
27394
|
+
var copilot_1 = require_copilot();
|
|
25904
27395
|
var http_api_1 = require_http_api();
|
|
25905
27396
|
var anthropic_api_1 = require_anthropic_api();
|
|
25906
27397
|
var webhook_1 = require_webhook();
|
|
@@ -25951,6 +27442,13 @@ var require_shuttle = __commonJS({
|
|
|
25951
27442
|
});
|
|
25952
27443
|
return { promise, resolve, reject };
|
|
25953
27444
|
}
|
|
27445
|
+
async function listExecutorModels(executors, signal) {
|
|
27446
|
+
const listed = await Promise.all(executors.map(async (executor) => {
|
|
27447
|
+
const models = executor.listModels ? await executor.listModels(signal).catch(() => []) : [];
|
|
27448
|
+
return [executor.capability.id, models];
|
|
27449
|
+
}));
|
|
27450
|
+
return new Map(listed);
|
|
27451
|
+
}
|
|
25954
27452
|
function isKeepUnavailable(err) {
|
|
25955
27453
|
return err instanceof Error && err.name === "KeepUnavailableError";
|
|
25956
27454
|
}
|
|
@@ -26098,11 +27596,11 @@ var require_shuttle = __commonJS({
|
|
|
26098
27596
|
const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
|
|
26099
27597
|
registerBuiltInExecutors(registry, this.brand.executorAllowlist);
|
|
26100
27598
|
const executors = registry.build(mergeExecutorDefaults((0, environment_1.executorDefaultsFor)(this.brand, this.config.auth.jobsApiUrl), this.config.executors));
|
|
26101
|
-
const
|
|
26102
|
-
|
|
26103
|
-
|
|
26104
|
-
|
|
26105
|
-
|
|
27599
|
+
const executorFor = (0, demux_1.createDemux)(executors, this.config.pools);
|
|
27600
|
+
const modelsByExecutor = await listExecutorModels(executors, this.stopping.signal);
|
|
27601
|
+
if (this.stopping.signal.aborted)
|
|
27602
|
+
return;
|
|
27603
|
+
logger.info("executor models listed", Object.fromEntries([...modelsByExecutor].map(([id, m]) => [id, m.length])));
|
|
26106
27604
|
this.spendTracker = (0, spend_tracker_1.createSpendTrackerObserver)(this.brand, paths.spendStateFile);
|
|
26107
27605
|
const observers = [
|
|
26108
27606
|
(0, logger_2.createLoggerObserver)(),
|
|
@@ -26123,7 +27621,7 @@ var require_shuttle = __commonJS({
|
|
|
26123
27621
|
const attendanceId = dispatch.attendanceId;
|
|
26124
27622
|
const startedAt = Date.now();
|
|
26125
27623
|
const strategy = (0, loom_1.readShuttleStrategy)(dispatch.inputs) ?? "";
|
|
26126
|
-
const executor =
|
|
27624
|
+
const executor = executorFor(dispatch.pool, strategy);
|
|
26127
27625
|
if (!executor) {
|
|
26128
27626
|
const reason = `no executor for shuttle strategy "${strategy}"`;
|
|
26129
27627
|
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
@@ -26218,6 +27716,14 @@ var require_shuttle = __commonJS({
|
|
|
26218
27716
|
return { status: "completed", outputs };
|
|
26219
27717
|
};
|
|
26220
27718
|
const pools = this.config.pools.map((p) => p.id ? { id: p.id } : { name: p.name });
|
|
27719
|
+
const poolExecutors = new Map(pools.map((ref, i) => [ref, this.config.pools[i].executor]));
|
|
27720
|
+
const poolMetadata = (ref) => {
|
|
27721
|
+
const executor = poolExecutors.get(ref);
|
|
27722
|
+
const models = executor ? modelsByExecutor.get(executor) : void 0;
|
|
27723
|
+
if (!executor || !models)
|
|
27724
|
+
return void 0;
|
|
27725
|
+
return { executor, ...models.length > 0 ? { models } : {} };
|
|
27726
|
+
};
|
|
26221
27727
|
const attemptedUpgrades = /* @__PURE__ */ new Set();
|
|
26222
27728
|
this.subscription = (0, jobs_1.subscribe)({
|
|
26223
27729
|
jobsClient,
|
|
@@ -26234,7 +27740,8 @@ var require_shuttle = __commonJS({
|
|
|
26234
27740
|
...this.config.agent.metadata,
|
|
26235
27741
|
client_version: this.brand.product.version,
|
|
26236
27742
|
hostname: (0, os_1.hostname)()
|
|
26237
|
-
}
|
|
27743
|
+
},
|
|
27744
|
+
poolMetadata
|
|
26238
27745
|
},
|
|
26239
27746
|
onJob,
|
|
26240
27747
|
onDirectives: async (directives) => {
|
|
@@ -26378,6 +27885,8 @@ var require_shuttle = __commonJS({
|
|
|
26378
27885
|
function registerBuiltInExecutors(registry, allowlist) {
|
|
26379
27886
|
const all = [
|
|
26380
27887
|
["claude-code", claude_code_1.createClaudeCodeExecutor],
|
|
27888
|
+
["codex", codex_1.createCodexExecutor],
|
|
27889
|
+
["copilot", copilot_1.createCopilotExecutor],
|
|
26381
27890
|
["http-api", http_api_1.createHttpApiExecutor],
|
|
26382
27891
|
["anthropic-api", anthropic_api_1.createAnthropicApiExecutor],
|
|
26383
27892
|
["webhook", webhook_1.createWebhookExecutor],
|
|
@@ -26880,8 +28389,8 @@ var require_dist5 = __commonJS({
|
|
|
26880
28389
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
26881
28390
|
"use strict";
|
|
26882
28391
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
26883
|
-
exports2.
|
|
26884
|
-
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = void 0;
|
|
28392
|
+
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;
|
|
28393
|
+
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = void 0;
|
|
26885
28394
|
var cli_1 = require_cli();
|
|
26886
28395
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
26887
28396
|
return cli_1.createCli;
|
|
@@ -26960,6 +28469,14 @@ var require_dist5 = __commonJS({
|
|
|
26960
28469
|
Object.defineProperty(exports2, "createClaudeCodeExecutor", { enumerable: true, get: function() {
|
|
26961
28470
|
return claude_code_1.createClaudeCodeExecutor;
|
|
26962
28471
|
} });
|
|
28472
|
+
var codex_1 = require_codex();
|
|
28473
|
+
Object.defineProperty(exports2, "createCodexExecutor", { enumerable: true, get: function() {
|
|
28474
|
+
return codex_1.createCodexExecutor;
|
|
28475
|
+
} });
|
|
28476
|
+
var copilot_1 = require_copilot();
|
|
28477
|
+
Object.defineProperty(exports2, "createCopilotExecutor", { enumerable: true, get: function() {
|
|
28478
|
+
return copilot_1.createCopilotExecutor;
|
|
28479
|
+
} });
|
|
26963
28480
|
var http_api_1 = require_http_api();
|
|
26964
28481
|
Object.defineProperty(exports2, "createHttpApiExecutor", { enumerable: true, get: function() {
|
|
26965
28482
|
return http_api_1.createHttpApiExecutor;
|
|
@@ -27086,7 +28603,8 @@ var import_path = require("path");
|
|
|
27086
28603
|
var import_promises = require("fs/promises");
|
|
27087
28604
|
var import_yaml = __toESM(require_dist3());
|
|
27088
28605
|
var import_shuttle = __toESM(require_dist5());
|
|
27089
|
-
var buildVersion = true ? "0.
|
|
28606
|
+
var buildVersion = true ? "0.32.0" : pkg.version;
|
|
28607
|
+
var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
|
|
27090
28608
|
var sifterBrand = {
|
|
27091
28609
|
product: {
|
|
27092
28610
|
id: "sifter",
|
|
@@ -27109,7 +28627,7 @@ var sifterBrand = {
|
|
|
27109
28627
|
// shared config across multiple machines.
|
|
27110
28628
|
configSearchPaths: ["~/.sifter/sifter.yaml"]
|
|
27111
28629
|
},
|
|
27112
|
-
executorAllowlist: ["claude-code", "anthropic-api", "http-api", "webhook", "custom-script"],
|
|
28630
|
+
executorAllowlist: ["claude-code", "codex", "copilot", "anthropic-api", "http-api", "webhook", "custom-script"],
|
|
27113
28631
|
// What `init` configures when neither --executor nor an existing
|
|
27114
28632
|
// sifter.yaml names one. The init pre-flight reads it too, to check git and
|
|
27115
28633
|
// claude before the pairing code is spent.
|
|
@@ -27123,23 +28641,35 @@ var sifterBrand = {
|
|
|
27123
28641
|
// newest of the flags every review passes (`--tools` arrived in 2.0.31;
|
|
27124
28642
|
// the executor's REVIEW_RUN_ARGS names them all), and the hosted image pins
|
|
27125
28643
|
// 2.1.x. Below it a review fails on an unknown option.
|
|
28644
|
+
//
|
|
28645
|
+
// `codex` and `copilot` are floored at the releases their containment was
|
|
28646
|
+
// verified against, and the floor is a containment rule rather than a
|
|
28647
|
+
// compatibility one: an older codex that does not know permission profiles
|
|
28648
|
+
// ignores the review's profile and falls back to a sandbox that reads the
|
|
28649
|
+
// whole disk, and an older copilot may not know a flag that keeps a tool or
|
|
28650
|
+
// a hook out of the run. Both executors are local only.
|
|
27126
28651
|
minimumToolVersions: {
|
|
27127
28652
|
git: "2.20.0",
|
|
27128
|
-
claude: "2.0.45"
|
|
28653
|
+
claude: "2.0.45",
|
|
28654
|
+
codex: "0.162.0",
|
|
28655
|
+
copilot: "1.0.97"
|
|
27129
28656
|
},
|
|
27130
28657
|
// The overlay-fetch allowlist is NOT here: each tier signs its standards
|
|
27131
28658
|
// URLs with its own Sift origin (SIFT_PUBLIC_API_URL), so the origin is a
|
|
27132
28659
|
// property of the tier and lives on its `environments` entry below. A
|
|
27133
28660
|
// Sifter paired to production allows production's origin and nothing else.
|
|
27134
|
-
executorDefaults:
|
|
27135
|
-
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
27142
|
-
|
|
28661
|
+
executorDefaults: Object.fromEntries(
|
|
28662
|
+
AGENT_EXECUTORS.map((executor) => [
|
|
28663
|
+
executor,
|
|
28664
|
+
{
|
|
28665
|
+
// Transcript retention is a diagnostic switch a person flips for one
|
|
28666
|
+
// session, so it rides the environment rather than a file they would
|
|
28667
|
+
// have to remember to unset. The shared package reads no env of its
|
|
28668
|
+
// own (BE-005); this brand is where the variable becomes config.
|
|
28669
|
+
...process.env.SHUTTLE_KEEP_TRANSCRIPTS ? { keepTranscripts: process.env.SHUTTLE_KEEP_TRANSCRIPTS } : {}
|
|
28670
|
+
}
|
|
28671
|
+
])
|
|
28672
|
+
),
|
|
27143
28673
|
// The environments Whittle Labs operates, keyed by the profile name that
|
|
27144
28674
|
// selects one. `init --profile stage` resolves both URLs from here, so a
|
|
27145
28675
|
// non-prod pairing is one flag instead of three and there is no host to
|
|
@@ -27244,7 +28774,7 @@ var sifterBrand = {
|
|
|
27244
28774
|
}
|
|
27245
28775
|
const executorBlock = plan.keptBlock !== void 0 ? {
|
|
27246
28776
|
...plan.keptBlock,
|
|
27247
|
-
...executor
|
|
28777
|
+
...AGENT_EXECUTORS.includes(executor) && ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
27248
28778
|
} : await buildExecutorBlock(executor, ctx);
|
|
27249
28779
|
const doc = {
|
|
27250
28780
|
auth: {
|
|
@@ -27278,13 +28808,15 @@ var sifterBrand = {
|
|
|
27278
28808
|
]
|
|
27279
28809
|
};
|
|
27280
28810
|
function overlayOrigin(origin) {
|
|
27281
|
-
return
|
|
28811
|
+
return Object.fromEntries(AGENT_EXECUTORS.map((e) => [e, { overlayAllowedHosts: [origin] }]));
|
|
27282
28812
|
}
|
|
27283
28813
|
async function buildExecutorBlock(executor, ctx) {
|
|
27284
28814
|
switch (executor) {
|
|
27285
|
-
case "claude-code":
|
|
28815
|
+
case "claude-code":
|
|
28816
|
+
case "codex":
|
|
28817
|
+
case "copilot": {
|
|
27286
28818
|
return {
|
|
27287
|
-
type:
|
|
28819
|
+
type: executor,
|
|
27288
28820
|
...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
27289
28821
|
};
|
|
27290
28822
|
}
|