@tryarcanist/cli 0.1.256 → 0.1.257

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.
Files changed (3) hide show
  1. package/README.md +1 -75
  2. package/dist/index.js +819 -1166
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8207,856 +8207,201 @@ async function whoamiCommand(options, command) {
8207
8207
  });
8208
8208
  }
8209
8209
 
8210
- // src/utils/pagination.ts
8211
- var MAX_ALL_PAGES = 1e3;
8212
- async function fetchPages(label, all, initialCursor, fetchPage) {
8213
- const items = [];
8214
- let cursor = initialCursor;
8215
- let nextCursor = null;
8216
- let pageCount = 0;
8217
- do {
8218
- pageCount += 1;
8219
- if (all && pageCount > MAX_ALL_PAGES) {
8220
- throw new CliError("user", `${label} exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
8210
+ // src/commands/codex.ts
8211
+ import { readFile } from "fs/promises";
8212
+ import { join as join4 } from "path";
8213
+
8214
+ // src/vendor-login.ts
8215
+ import { spawn } from "child_process";
8216
+ import { rmSync } from "fs";
8217
+ import { mkdtemp, rm } from "fs/promises";
8218
+ import { tmpdir } from "os";
8219
+ import { join as join3 } from "path";
8220
+ function runVendorLoginProcess(spec) {
8221
+ return new Promise((resolve3, reject) => {
8222
+ const child = spawn(spec.binPath, spec.args, {
8223
+ stdio: "inherit",
8224
+ env: { ...process.env, ...spec.env }
8225
+ });
8226
+ child.on("error", (err) => {
8227
+ if (err.code === "ENOENT") {
8228
+ reject(
8229
+ new CliError("user", `Could not find the \`${spec.binPath}\` executable.`, {
8230
+ hint: spec.missingBinaryHint
8231
+ })
8232
+ );
8233
+ return;
8234
+ }
8235
+ reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
8236
+ });
8237
+ child.on("close", (code) => {
8238
+ if (code === 0) {
8239
+ resolve3();
8240
+ return;
8241
+ }
8242
+ reject(
8243
+ new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
8244
+ hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
8245
+ })
8246
+ );
8247
+ });
8248
+ });
8249
+ }
8250
+ async function withIsolatedLoginDir(prefix, fn) {
8251
+ let tempDir;
8252
+ const handleSigint = () => {
8253
+ if (tempDir) {
8254
+ try {
8255
+ rmSync(tempDir, { recursive: true, force: true });
8256
+ } catch {
8257
+ }
8221
8258
  }
8222
- const page = await fetchPage(cursor);
8223
- items.push(...page.items);
8224
- nextCursor = page.nextCursor;
8225
- cursor = nextCursor ?? void 0;
8226
- } while (all && nextCursor);
8227
- return { items, nextCursor: all ? null : nextCursor };
8259
+ process.exit(EXIT_CODE_INTERRUPTED);
8260
+ };
8261
+ process.on("SIGINT", handleSigint);
8262
+ try {
8263
+ tempDir = await mkdtemp(join3(tmpdir(), prefix));
8264
+ return await fn(tempDir);
8265
+ } finally {
8266
+ try {
8267
+ if (tempDir) {
8268
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
8269
+ });
8270
+ }
8271
+ } finally {
8272
+ process.off("SIGINT", handleSigint);
8273
+ }
8274
+ }
8228
8275
  }
8229
8276
 
8230
- // ../../shared/github/repo-url.ts
8231
- function parseGithubRepoFullName(value) {
8232
- const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
8233
- if (shorthand) return { owner: shorthand[1], repo: shorthand[2] };
8234
- const ssh = value.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
8235
- if (ssh) return { owner: ssh[1], repo: ssh[2] };
8236
- const https = value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
8237
- if (https) return { owner: https[1], repo: https[2] };
8238
- return null;
8277
+ // src/commands/codex.ts
8278
+ var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
8279
+ var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
8280
+ var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
8281
+ function describeCredentialStatus(credential) {
8282
+ if (!credential.isSet) return null;
8283
+ if (credential.lastValidationStatus === "invalid") {
8284
+ return "Expired. Upload a fresh auth.json in Settings, or run `arcanist codex login` if the Codex CLI is installed. `arcanist codex use off` falls back to your OpenAI key.";
8285
+ }
8286
+ if (credential.lastValidationStatus === "validated") return "Verified with OpenAI.";
8287
+ return "Saved, but not verified with OpenAI yet.";
8239
8288
  }
8240
-
8241
- // src/git.ts
8242
- import { execFileSync } from "child_process";
8243
- function git(args) {
8289
+ function resolveCodexPath(optionPath) {
8290
+ return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
8291
+ }
8292
+ function runCodexDeviceLogin(codexPath, codexHome) {
8293
+ return runVendorLoginProcess({
8294
+ displayName: "Codex",
8295
+ binPath: codexPath,
8296
+ args: ["login", "--device-auth"],
8297
+ env: { CODEX_HOME: codexHome },
8298
+ missingBinaryHint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN.",
8299
+ retryCommand: "arcanist codex login"
8300
+ });
8301
+ }
8302
+ async function setCodexSubscriptionEnabled(config, enabled) {
8303
+ return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
8304
+ method: "PUT",
8305
+ body: JSON.stringify({ enabled })
8306
+ });
8307
+ }
8308
+ async function codexLoginCommand(options, command) {
8309
+ const { config } = resolveBusinessContext(command, options);
8310
+ const codexPath = resolveCodexPath(options.codexPath);
8311
+ const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
8312
+ await runCodexDeviceLogin(codexPath, codexHome);
8313
+ let raw;
8314
+ try {
8315
+ raw = await readFile(join4(codexHome, "auth.json"), "utf8");
8316
+ } catch {
8317
+ throw new CliError("user", "Codex login completed but no auth.json was written.", {
8318
+ hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
8319
+ });
8320
+ }
8321
+ if (!raw.trim()) {
8322
+ throw new CliError("user", "Codex login produced an empty auth.json.");
8323
+ }
8324
+ return raw;
8325
+ });
8326
+ const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
8327
+ method: "PUT",
8328
+ body: JSON.stringify({ authJson })
8329
+ });
8330
+ let activated = false;
8331
+ let activationError;
8244
8332
  try {
8245
- return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
8333
+ await setCodexSubscriptionEnabled(config, true);
8334
+ activated = true;
8246
8335
  } catch (err) {
8247
- throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
8336
+ activationError = err instanceof Error ? err.message : String(err);
8248
8337
  }
8338
+ emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
8339
+ console.log(
8340
+ activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
8341
+ );
8342
+ const status = describeCredentialStatus(payload);
8343
+ if (status) console.log(`Status: ${status}`);
8344
+ if (!activated) {
8345
+ console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
8346
+ console.log("Run `arcanist codex use on` to start using it.");
8347
+ }
8348
+ });
8249
8349
  }
8250
- function currentRepo() {
8251
- const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
8252
- if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
8253
- return { owner: parsed.owner, repo: parsed.repo };
8350
+ async function codexUseCommand(state, options, command) {
8351
+ const normalized = state.trim().toLowerCase();
8352
+ if (normalized !== "on" && normalized !== "off") {
8353
+ throw new CliError("user", "Usage: arcanist codex use <on|off>");
8354
+ }
8355
+ const enabled = normalized === "on";
8356
+ const { config } = resolveBusinessContext(command, options);
8357
+ const result = await setCodexSubscriptionEnabled(config, enabled);
8358
+ emit(
8359
+ command,
8360
+ options,
8361
+ result,
8362
+ () => console.log(
8363
+ result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
8364
+ )
8365
+ );
8254
8366
  }
8255
-
8256
- // src/utils/repo-arg.ts
8257
- function parseRepoArg(value, argName = "repo") {
8258
- const parsed = parseGithubRepoFullName(value);
8259
- if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
8260
- return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
8367
+ async function codexStatusCommand(options, command) {
8368
+ const { config } = resolveBusinessContext(command, options);
8369
+ const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
8370
+ emit(command, options, payload, (state) => {
8371
+ console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
8372
+ console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
8373
+ const status = describeCredentialStatus(state.credential);
8374
+ if (status) console.log(`Status: ${status}`);
8375
+ });
8261
8376
  }
8262
- function parseRepoArgOrCurrent(value, argName = "repo") {
8263
- if (value === void 0) return currentRepo();
8264
- return parseRepoArg(value, argName);
8377
+ async function codexLogoutCommand(options, command) {
8378
+ const { config } = resolveBusinessContext(command, options);
8379
+ await setCodexSubscriptionEnabled(config, false).catch(() => {
8380
+ });
8381
+ const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
8382
+ method: "DELETE"
8383
+ });
8384
+ emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
8265
8385
  }
8266
8386
 
8267
- // ../../shared/agent/agent-runtime-backend.ts
8268
- var CODEX_AGENT_RUNTIME_BACKEND = "codex";
8269
- var AGENT_RUNTIME_BACKENDS = [CODEX_AGENT_RUNTIME_BACKEND];
8387
+ // src/uploads.ts
8388
+ import { readFile as readFile2 } from "fs/promises";
8389
+ import { basename as basename2, extname } from "path";
8270
8390
 
8271
- // ../../shared/utils/type-guards.ts
8272
- function isRecord(value) {
8273
- return !!value && typeof value === "object" && !Array.isArray(value);
8391
+ // ../../shared/constants/uploads.ts
8392
+ var MAX_UPLOADED_FILES = 5;
8393
+ var MAX_UPLOADED_FILE_SIZE_BYTES = 102400;
8394
+ var UPLOADED_FILE_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml", ".xml", ".html"];
8395
+ var MAX_UPLOADED_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
8396
+ var ALLOWED_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
8397
+
8398
+ // ../../shared/utils/uploads.ts
8399
+ var ALLOWED_IMAGE_MEDIA_TYPE_SET = new Set(ALLOWED_IMAGE_MEDIA_TYPES);
8400
+ function ok(value) {
8401
+ return { ok: true, value };
8274
8402
  }
8275
- function asNonEmptyString(value) {
8276
- if (typeof value !== "string") return void 0;
8277
- const trimmed = value.trim();
8278
- return trimmed.length > 0 ? trimmed : void 0;
8279
- }
8280
-
8281
- // ../../shared/constants/models.ts
8282
- var OpenAIModel = {
8283
- GPT56: "gpt-5.6",
8284
- GPT56Sol: "gpt-5.6-sol",
8285
- GPT56Terra: "gpt-5.6-terra",
8286
- GPT56Luna: "gpt-5.6-luna",
8287
- GPT55: "gpt-5.5",
8288
- GPT54: "gpt-5.4",
8289
- GPT54Pro: "gpt-5.4-pro",
8290
- GPT54Mini: "gpt-5.4-mini",
8291
- GPT54Nano: "gpt-5.4-nano",
8292
- GPT53CodexSpark: "gpt-5.3-codex-spark",
8293
- GPT53Codex: "gpt-5.3-codex",
8294
- GPT52: "gpt-5.2",
8295
- GPT52ChatLatest: "gpt-5.2-chat-latest",
8296
- GPT52Codex: "gpt-5.2-codex"
8297
- };
8298
- var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
8299
- var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
8300
- [CODEX_AGENT_RUNTIME_BACKEND]: {
8301
- backend: CODEX_AGENT_RUNTIME_BACKEND,
8302
- fixture: "known_image_fixture",
8303
- deliveryPath: "synthetic_image_context",
8304
- verifiedAt: "2026-07-07"
8305
- }
8306
- };
8307
- var MODEL_REGISTRY = [
8308
- {
8309
- id: OpenAIModel.GPT54,
8310
- name: "GPT-5.4",
8311
- provider: "openai",
8312
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8313
- capabilities: { codexToolSearch: true },
8314
- contextWindow: 1e6,
8315
- // Default codex model after the gpt-5.5 downgrade; carries gpt-5.5's prior
8316
- // "medium" default so default sessions keep the same reasoning effort.
8317
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
8318
- // No cacheWritePerMillion: the pricing page shows a dash in the cache-writes
8319
- // column for gpt-5.4 and gpt-5.5, so the absence is deliberate, not an
8320
- // oversight (checked 2026-07-30). The 5.6 family does publish that rate.
8321
- pricing: {
8322
- inputPerMillion: 2.5,
8323
- outputPerMillion: 15,
8324
- cacheReadPerMillion: 0.25,
8325
- longContext: {
8326
- thresholdTokens: 272e3,
8327
- inputPerMillion: 5,
8328
- outputPerMillion: 22.5,
8329
- cacheReadPerMillion: 0.5
8330
- }
8331
- },
8332
- sessionStart: { eligible: true, isDefault: true }
8333
- },
8334
- {
8335
- id: OpenAIModel.GPT56,
8336
- name: "GPT-5.6",
8337
- provider: "openai",
8338
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8339
- capabilities: { codexToolSearch: true },
8340
- contextWindow: 105e4,
8341
- // Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
8342
- // catalog: the alias routes to gpt-5.6-sol and supports max effort. Pricing
8343
- // mirrors gpt-5.6-sol since the alias resolves to it. Cache-write rates
8344
- // verified 2026-07-30 against the Standard tier at
8345
- // developers.openai.com/api/docs/pricing.
8346
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
8347
- pricing: {
8348
- inputPerMillion: 5,
8349
- outputPerMillion: 30,
8350
- cacheReadPerMillion: 0.5,
8351
- cacheWritePerMillion: 6.25,
8352
- longContext: {
8353
- thresholdTokens: 272e3,
8354
- inputPerMillion: 10,
8355
- outputPerMillion: 45,
8356
- cacheReadPerMillion: 1,
8357
- cacheWritePerMillion: 12.5
8358
- }
8359
- },
8360
- sessionStart: { eligible: true }
8361
- },
8362
- {
8363
- id: OpenAIModel.GPT56Sol,
8364
- name: "GPT-5.6 Sol",
8365
- provider: "openai",
8366
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8367
- capabilities: { codexToolSearch: true },
8368
- contextWindow: 105e4,
8369
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
8370
- // Cache-write rates verified 2026-07-30 against the Standard tier at
8371
- // developers.openai.com/api/docs/pricing.
8372
- pricing: {
8373
- inputPerMillion: 5,
8374
- outputPerMillion: 30,
8375
- cacheReadPerMillion: 0.5,
8376
- cacheWritePerMillion: 6.25,
8377
- longContext: {
8378
- thresholdTokens: 272e3,
8379
- inputPerMillion: 10,
8380
- outputPerMillion: 45,
8381
- cacheReadPerMillion: 1,
8382
- cacheWritePerMillion: 12.5
8383
- }
8384
- },
8385
- sessionStart: { eligible: true }
8386
- },
8387
- {
8388
- id: OpenAIModel.GPT56Terra,
8389
- name: "GPT-5.6 Terra",
8390
- provider: "openai",
8391
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8392
- capabilities: { codexToolSearch: true },
8393
- contextWindow: 105e4,
8394
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
8395
- // Verified 2026-07-30 against the Standard tier at
8396
- // developers.openai.com/api/docs/pricing: short and long context input,
8397
- // cached input, cache writes, and output.
8398
- pricing: {
8399
- inputPerMillion: 2,
8400
- outputPerMillion: 12,
8401
- cacheReadPerMillion: 0.2,
8402
- cacheWritePerMillion: 2.5,
8403
- longContext: {
8404
- thresholdTokens: 272e3,
8405
- inputPerMillion: 4,
8406
- outputPerMillion: 18,
8407
- cacheReadPerMillion: 0.4,
8408
- cacheWritePerMillion: 5
8409
- }
8410
- },
8411
- sessionStart: { eligible: true }
8412
- },
8413
- {
8414
- id: OpenAIModel.GPT56Luna,
8415
- name: "GPT-5.6 Luna",
8416
- provider: "openai",
8417
- overloadFallback: OpenAIModel.GPT56Terra,
8418
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8419
- capabilities: { codexToolSearch: true },
8420
- contextWindow: 105e4,
8421
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
8422
- // Verified 2026-07-30 against the Standard tier at
8423
- // developers.openai.com/api/docs/pricing: short and long context input,
8424
- // cached input, cache writes, and output.
8425
- pricing: {
8426
- inputPerMillion: 0.2,
8427
- outputPerMillion: 1.2,
8428
- cacheReadPerMillion: 0.02,
8429
- cacheWritePerMillion: 0.25,
8430
- longContext: {
8431
- thresholdTokens: 272e3,
8432
- inputPerMillion: 0.4,
8433
- outputPerMillion: 1.8,
8434
- cacheReadPerMillion: 0.04,
8435
- cacheWritePerMillion: 0.5
8436
- }
8437
- },
8438
- sessionStart: { eligible: true }
8439
- },
8440
- {
8441
- id: OpenAIModel.GPT55,
8442
- name: "GPT-5.5",
8443
- provider: "openai",
8444
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8445
- capabilities: { codexToolSearch: true },
8446
- contextWindow: 1e6,
8447
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
8448
- pricing: {
8449
- inputPerMillion: 5,
8450
- outputPerMillion: 30,
8451
- cacheReadPerMillion: 0.5,
8452
- longContext: {
8453
- thresholdTokens: 272e3,
8454
- inputPerMillion: 10,
8455
- outputPerMillion: 45,
8456
- cacheReadPerMillion: 1
8457
- }
8458
- },
8459
- sessionStart: { eligible: true }
8460
- },
8461
- {
8462
- id: OpenAIModel.GPT54Mini,
8463
- name: "GPT-5.4 Mini",
8464
- provider: "openai",
8465
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8466
- capabilities: { codexToolSearch: true },
8467
- contextWindow: 4e5,
8468
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
8469
- pricing: {
8470
- inputPerMillion: 0.75,
8471
- outputPerMillion: 4.5,
8472
- cacheReadPerMillion: 0.075,
8473
- // Flex bills at Batch rates: a flat 50% off standard across input,
8474
- // cached input, and output (verified against the OpenAI pricing page,
8475
- // June 2026). Bridge cost estimates deliberately strip this gateway-only
8476
- // axis when deriving MODEL_PRICING.
8477
- flex: { inputPerMillion: 0.375, outputPerMillion: 2.25, cacheReadPerMillion: 0.0375 }
8478
- },
8479
- sessionStart: { eligible: true }
8480
- },
8481
- {
8482
- id: OpenAIModel.GPT54Nano,
8483
- name: "GPT-5.4 Nano",
8484
- provider: "openai",
8485
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8486
- capabilities: { codexToolSearch: false },
8487
- contextWindow: 4e5,
8488
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
8489
- pricing: { inputPerMillion: 0.2, outputPerMillion: 1.25, cacheReadPerMillion: 0.02 },
8490
- sessionStart: { eligible: true }
8491
- },
8492
- {
8493
- id: OpenAIModel.GPT53CodexSpark,
8494
- name: "GPT-5.3 Codex Spark",
8495
- provider: "openai",
8496
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8497
- capabilities: { codexToolSearch: true },
8498
- contextWindow: 128e3,
8499
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
8500
- costTracked: false,
8501
- requiresCodexSubscriptionAuth: true,
8502
- sessionStart: { eligible: true },
8503
- visibility: "internal_probe"
8504
- },
8505
- {
8506
- id: OpenAIModel.GPT53Codex,
8507
- name: "GPT-5.3 Codex",
8508
- provider: "openai",
8509
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8510
- capabilities: { codexToolSearch: true },
8511
- contextWindow: 4e5,
8512
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
8513
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
8514
- },
8515
- {
8516
- id: OpenAIModel.GPT52,
8517
- name: "GPT-5.2",
8518
- provider: "openai",
8519
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8520
- capabilities: { codexToolSearch: true },
8521
- contextWindow: 4e5,
8522
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
8523
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
8524
- },
8525
- {
8526
- id: OpenAIModel.GPT52ChatLatest,
8527
- name: "GPT-5.2 Chat",
8528
- provider: "openai",
8529
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8530
- capabilities: { codexToolSearch: true },
8531
- contextWindow: 128e3,
8532
- reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
8533
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
8534
- },
8535
- {
8536
- id: OpenAIModel.GPT52Codex,
8537
- name: "GPT-5.2 Codex",
8538
- provider: "openai",
8539
- backends: [CODEX_AGENT_RUNTIME_BACKEND],
8540
- capabilities: { codexToolSearch: true },
8541
- contextWindow: 4e5,
8542
- reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
8543
- pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
8544
- }
8545
- ];
8546
- var MODEL_PROVIDER_NAMES = {
8547
- openai: "OpenAI",
8548
- anthropic: "Anthropic"
8549
- };
8550
- function buildSessionStartModelIdsByBackend() {
8551
- const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
8552
- for (const model of MODEL_REGISTRY) {
8553
- if (!model.sessionStart?.eligible) continue;
8554
- for (const backend of model.backends) {
8555
- if (!modelSupportsRequiredSessionStartCapabilities(model, backend)) continue;
8556
- byBackend[backend].push(model.id);
8557
- }
8558
- }
8559
- return byBackend;
8560
- }
8561
- function modelSupportsRequiredSessionStartCapabilities(model, backend) {
8562
- if (backend !== CODEX_AGENT_RUNTIME_BACKEND) return true;
8563
- return model.capabilities?.codexToolSearch === true;
8564
- }
8565
- function buildDefaultSessionStartModelIdByBackend() {
8566
- const defaults = {};
8567
- for (const backend of AGENT_RUNTIME_BACKENDS) {
8568
- const backendDefaults = MODEL_REGISTRY.filter(
8569
- (model) => model.sessionStart?.eligible && model.sessionStart.isDefault === true && model.backends.includes(backend)
8570
- );
8571
- if (backendDefaults.length !== 1) {
8572
- throw new Error(`Expected exactly one default session-start model for ${backend}, got ${backendDefaults.length}`);
8573
- }
8574
- defaults[backend] = backendDefaults[0].id;
8575
- }
8576
- return defaults;
8577
- }
8578
- var SESSION_START_MODEL_IDS_BY_BACKEND = buildSessionStartModelIdsByBackend();
8579
- var DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND = buildDefaultSessionStartModelIdByBackend();
8580
- var DEFAULT_SESSION_START_MODEL_ID = DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND];
8581
- var VALID_MODEL_IDS = new Set(MODEL_REGISTRY.map((model) => model.id));
8582
- var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
8583
- [CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND])
8584
- };
8585
- var MODEL_CONTEXT_WINDOWS = {
8586
- ...Object.fromEntries(
8587
- MODEL_REGISTRY.flatMap((model) => model.contextWindow === void 0 ? [] : [[model.id, model.contextWindow]])
8588
- )
8589
- };
8590
- var MODEL_PROVIDERS = {
8591
- ...Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model.provider]))
8592
- };
8593
- var MODEL_REASONING_CONFIG = Object.fromEntries(
8594
- MODEL_REGISTRY.flatMap((model) => model.reasoning ? [[model.id, model.reasoning]] : [])
8595
- );
8596
- var MODEL_DEFINITIONS_BY_ID = Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model]));
8597
- function splitModelIdentifier(value) {
8598
- let hasSeparator = false;
8599
- for (const separator of [":", "/"]) {
8600
- const index = value.indexOf(separator);
8601
- if (index <= 0 || index >= value.length - 1) continue;
8602
- hasSeparator = true;
8603
- const providerID = value.slice(0, index);
8604
- const modelID = value.slice(index + 1);
8605
- if (MODEL_PROVIDERS_SET.has(providerID) && modelID.length > 0) {
8606
- return { providerID, modelID };
8607
- }
8608
- }
8609
- if (hasSeparator) return void 0;
8610
- return value.length > 0 ? { modelID: value } : void 0;
8611
- }
8612
- function getRawModelValue(raw) {
8613
- if (typeof raw === "string") return asNonEmptyString(raw);
8614
- if (!raw || typeof raw !== "object") return void 0;
8615
- const value = raw;
8616
- return asNonEmptyString(value.modelID) ?? asNonEmptyString(value.modelId) ?? asNonEmptyString(value.id);
8617
- }
8618
- function getExplicitProvider(raw) {
8619
- if (raw && typeof raw === "object") {
8620
- const value = raw;
8621
- const providerID = asNonEmptyString(value.providerID) ?? asNonEmptyString(value.providerId);
8622
- if (providerID && MODEL_PROVIDERS_SET.has(providerID)) {
8623
- return providerID;
8624
- }
8625
- }
8626
- const modelValue = getRawModelValue(raw);
8627
- if (!modelValue) return void 0;
8628
- return splitModelIdentifier(modelValue)?.providerID;
8629
- }
8630
- function extractModelId(raw) {
8631
- const value = getRawModelValue(raw);
8632
- if (!value) return void 0;
8633
- return splitModelIdentifier(value)?.modelID;
8634
- }
8635
- function getSessionStartModelIdsForBackend(backend) {
8636
- return SESSION_START_MODEL_IDS_BY_BACKEND[backend];
8637
- }
8638
- function isSessionStartModelAllowedForBackend(modelId, backend) {
8639
- return VALID_SESSION_START_MODEL_IDS_BY_BACKEND[backend].has(modelId);
8640
- }
8641
- function getProviderForModel(modelId) {
8642
- return MODEL_PROVIDERS[modelId] ?? "openai";
8643
- }
8644
- function toModelSelection(raw) {
8645
- const modelID = extractModelId(raw);
8646
- if (!modelID) return void 0;
8647
- return {
8648
- providerID: getExplicitProvider(raw) ?? getProviderForModel(modelID),
8649
- modelID
8650
- };
8651
- }
8652
- function getModelDefinition(raw) {
8653
- const modelID = extractModelId(raw);
8654
- if (!modelID) return void 0;
8655
- return MODEL_DEFINITIONS_BY_ID[modelID];
8656
- }
8657
- function formatModelLabel(raw, options = {}) {
8658
- const includeProvider = options.includeProvider ?? false;
8659
- const definition = getModelDefinition(raw);
8660
- if (definition) {
8661
- return includeProvider ? `${MODEL_PROVIDER_NAMES[definition.provider]} / ${definition.name}` : definition.name;
8662
- }
8663
- const selection = toModelSelection(raw);
8664
- if (!selection) return void 0;
8665
- const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
8666
- return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
8667
- }
8668
- function buildModelProviderGroups(models2) {
8669
- const groups = /* @__PURE__ */ new Map();
8670
- for (const model of models2) {
8671
- let group = groups.get(model.provider);
8672
- if (!group) {
8673
- group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
8674
- groups.set(model.provider, group);
8675
- }
8676
- group.models.push({
8677
- id: model.id,
8678
- name: model.name,
8679
- label: formatModelLabel(model, { includeProvider: true }) ?? model.name,
8680
- backends: model.backends,
8681
- reasoning: model.reasoning
8682
- });
8683
- }
8684
- const defaultModelIds = new Set(
8685
- models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
8686
- );
8687
- for (const group of groups.values()) {
8688
- group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
8689
- }
8690
- return [...groups.values()];
8691
- }
8692
- var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
8693
- AGENT_RUNTIME_BACKENDS.flatMap((backend) => [...SESSION_START_MODEL_IDS_BY_BACKEND[backend]])
8694
- );
8695
- var PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
8696
- MODEL_REGISTRY.filter(
8697
- (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && model.visibility !== "internal_probe"
8698
- )
8699
- );
8700
- var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
8701
- MODEL_REGISTRY.filter(
8702
- (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
8703
- )
8704
- );
8705
-
8706
- // src/commands/model-options.ts
8707
- function resolveModelAndBackend(options) {
8708
- let agentRuntimeBackend = CODEX_AGENT_RUNTIME_BACKEND;
8709
- const normalizedModel = options.model !== void 0 ? extractModelId(options.model) : void 0;
8710
- if (options.model !== void 0) {
8711
- if (normalizedModel === void 0 || !isSessionStartModelAllowedForBackend(normalizedModel, agentRuntimeBackend)) {
8712
- const allowed = getSessionStartModelIdsForBackend(agentRuntimeBackend).join(", ");
8713
- throw new CliError(
8714
- "user",
8715
- `Model '${options.model}' is not selectable for backend '${agentRuntimeBackend}'. Allowed: ${allowed}.`
8716
- );
8717
- }
8718
- }
8719
- return { agentRuntimeBackend, modelId: normalizedModel };
8720
- }
8721
-
8722
- // src/commands/automations.ts
8723
- var AUTOMATION_ERROR_HINTS = {
8724
- invalid_cron: "Use a standard five-field cron expression, for example: */15 * * * *",
8725
- invalid_model: "Model is unknown or not selectable for its backend. Run `arcanist automations create --help` for allowed models.",
8726
- repo_not_available: "Check that the token owner has GitHub access and Arcanist is installed for that repo.",
8727
- duplicate_rule: "An enabled automation already exists for this repo, cron, and prompt.",
8728
- invalid_skill: "Use a leading slash skill that exists in the selected repo, for example: /your-repo-skill",
8729
- invalid_slack_target: "Pass both --slack-team-id and --slack-channel-id to enable Slack delivery.",
8730
- slack_workspace_not_connected: "Connect that Slack workspace to Arcanist before creating the automation.",
8731
- slack_channel_unavailable: "Check the Slack channel ID and confirm the bot can see the channel.",
8732
- slack_bot_not_in_channel: "Invite the Arcanist bot to the channel, then retry.",
8733
- rule_cap_reached: "Delete or disable an existing automation before creating another one. The cap is 20 enabled rules.",
8734
- model_not_available: "That model is not available for this business; use the codex default or a codex model.",
8735
- repo_skills_unavailable: "Arcanist could not verify the repo's skills right now. Retry once GitHub skill discovery is healthy.",
8736
- unknown_skill: "That leading slash skill does not exist in the selected repository."
8737
- };
8738
- async function createAutomationCommand(repoUrl, promptArg, options, command) {
8739
- const repo = parseRepoArg(repoUrl, "repo-url");
8740
- const prompt = await resolvePromptInput(promptArg, options);
8741
- const slackTeamId = options.slackTeamId?.trim();
8742
- const slackChannelId = options.slackChannelId?.trim();
8743
- const slackDeliveryRequested = options.slackTeamId !== void 0 || options.slackChannelId !== void 0;
8744
- if (slackDeliveryRequested && (!slackTeamId || !slackChannelId)) {
8745
- throw new CliError(
8746
- "user",
8747
- "Slack delivery requires both --slack-team-id and --slack-channel-id with non-empty values."
8748
- );
8749
- }
8750
- const runtime = getRuntimeOptions(command, options);
8751
- const config = requireConfig(runtime);
8752
- const { modelId: normalizedModel } = resolveModelAndBackend(options);
8753
- const body = {
8754
- repoOwner: repo.owner,
8755
- repoName: repo.repo,
8756
- cron: options.cron,
8757
- prompt
8758
- };
8759
- if (options.name !== void 0) body.name = options.name;
8760
- if (options.model !== void 0) body.modelId = normalizedModel;
8761
- if (slackDeliveryRequested) {
8762
- body.slackTeamId = slackTeamId;
8763
- body.slackChannelId = slackChannelId;
8764
- }
8765
- const payload = await automationApiFetch(config, "/api/automation/schedules", {
8766
- method: "POST",
8767
- body: JSON.stringify(body)
8768
- });
8769
- if (isJson(command, options)) {
8770
- writeJson(payload.data);
8771
- return;
8772
- }
8773
- printAutomationRule(payload.data);
8774
- }
8775
- async function listAutomationsCommand(options, command) {
8776
- const runtime = getRuntimeOptions(command, options);
8777
- const config = requireConfig(runtime);
8778
- const { items, nextCursor } = await fetchPages(
8779
- "automations list --all",
8780
- options.all === true,
8781
- options.cursor,
8782
- async (cursor) => {
8783
- const query = new URLSearchParams();
8784
- if (options.limit) query.set("limit", options.limit);
8785
- if (cursor) query.set("cursor", cursor);
8786
- const payload = await automationApiFetch(
8787
- config,
8788
- `/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
8789
- );
8790
- return { items: payload.data.items, nextCursor: payload.data.nextCursor };
8791
- }
8792
- );
8793
- const output = { items, nextCursor };
8794
- if (isJson(command, options)) {
8795
- writeJson(output);
8796
- return;
8797
- }
8798
- if (items.length === 0) {
8799
- console.log("No automations found.");
8800
- return;
8801
- }
8802
- for (const rule of items) {
8803
- console.log(
8804
- `${rule.id} ${rule.repoOwner}/${rule.repoName} ${rule.normalizedCron} ${String(rule.enabled)} ${formatTime(rule.nextFireAt)}`
8805
- );
8806
- }
8807
- if (output.nextCursor) console.log(`Next cursor: ${output.nextCursor}`);
8808
- }
8809
- async function deleteAutomationCommand(id, options, command) {
8810
- if (isJson(command, options) && options.yes !== true) {
8811
- throw new CliError("user", "`automations delete --json` requires --yes.");
8812
- }
8813
- if (options.yes !== true) {
8814
- await confirmOrThrow(`Delete automation ${id}?`);
8815
- }
8816
- const runtime = getRuntimeOptions(command, options);
8817
- const config = requireConfig(runtime);
8818
- await automationApiFetchText(config, `/api/automation/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
8819
- if (isJson(command, options)) {
8820
- writeJson({ ok: true, id });
8821
- return;
8822
- }
8823
- console.log(`Deleted automation ${id}.`);
8824
- }
8825
- async function automationApiFetch(config, path, init) {
8826
- try {
8827
- return await apiFetch(config, path, init);
8828
- } catch (err) {
8829
- throw mapAutomationApiError(err);
8830
- }
8831
- }
8832
- async function automationApiFetchText(config, path, init) {
8833
- try {
8834
- return await apiFetchText(config, path, init);
8835
- } catch (err) {
8836
- throw mapAutomationApiError(err);
8837
- }
8838
- }
8839
- function mapAutomationApiError(err) {
8840
- if (!(err instanceof ApiError)) {
8841
- return err instanceof CliError ? err : new CliError("server", stringifyError(err));
8842
- }
8843
- const parsed = parseApiErrorBody(err.body);
8844
- const serverCode = parsed?.rawError ?? parsed?.serverCode;
8845
- return new CliError(codeForHttpStatus(err.status), parsed?.message || serverCode || err.message, {
8846
- exitCode: err.exitCode,
8847
- hint: serverCode ? AUTOMATION_ERROR_HINTS[serverCode] : void 0,
8848
- requestId: err.requestId
8849
- });
8850
- }
8851
- function printAutomationRule(rule) {
8852
- console.log(`ID: ${rule.id}`);
8853
- console.log(`Repo: ${rule.repoOwner}/${rule.repoName}`);
8854
- console.log(`Cron: ${rule.normalizedCron}`);
8855
- if (rule.modelId) console.log(`Model: ${rule.modelId}`);
8856
- if (rule.slackTeamId && rule.slackChannelId) {
8857
- console.log(`Slack delivery: ${rule.slackTeamId}/${rule.slackChannelId}`);
8858
- }
8859
- console.log(`Next fire: ${formatTime(rule.nextFireAt)}`);
8860
- }
8861
- function formatTime(value) {
8862
- return typeof value === "number" ? new Date(value).toISOString() : "none";
8863
- }
8864
-
8865
- // src/commands/codex.ts
8866
- import { readFile } from "fs/promises";
8867
- import { join as join4 } from "path";
8868
-
8869
- // src/vendor-login.ts
8870
- import { spawn } from "child_process";
8871
- import { rmSync } from "fs";
8872
- import { mkdtemp, rm } from "fs/promises";
8873
- import { tmpdir } from "os";
8874
- import { join as join3 } from "path";
8875
- function runVendorLoginProcess(spec) {
8876
- return new Promise((resolve3, reject) => {
8877
- const child = spawn(spec.binPath, spec.args, {
8878
- stdio: "inherit",
8879
- env: { ...process.env, ...spec.env }
8880
- });
8881
- child.on("error", (err) => {
8882
- if (err.code === "ENOENT") {
8883
- reject(
8884
- new CliError("user", `Could not find the \`${spec.binPath}\` executable.`, {
8885
- hint: spec.missingBinaryHint
8886
- })
8887
- );
8888
- return;
8889
- }
8890
- reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
8891
- });
8892
- child.on("close", (code) => {
8893
- if (code === 0) {
8894
- resolve3();
8895
- return;
8896
- }
8897
- reject(
8898
- new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
8899
- hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
8900
- })
8901
- );
8902
- });
8903
- });
8904
- }
8905
- async function withIsolatedLoginDir(prefix, fn) {
8906
- let tempDir;
8907
- const handleSigint = () => {
8908
- if (tempDir) {
8909
- try {
8910
- rmSync(tempDir, { recursive: true, force: true });
8911
- } catch {
8912
- }
8913
- }
8914
- process.exit(EXIT_CODE_INTERRUPTED);
8915
- };
8916
- process.on("SIGINT", handleSigint);
8917
- try {
8918
- tempDir = await mkdtemp(join3(tmpdir(), prefix));
8919
- return await fn(tempDir);
8920
- } finally {
8921
- try {
8922
- if (tempDir) {
8923
- await rm(tempDir, { recursive: true, force: true }).catch(() => {
8924
- });
8925
- }
8926
- } finally {
8927
- process.off("SIGINT", handleSigint);
8928
- }
8929
- }
8930
- }
8931
-
8932
- // src/commands/codex.ts
8933
- var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
8934
- var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
8935
- var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
8936
- function describeCredentialStatus(credential) {
8937
- if (!credential.isSet) return null;
8938
- if (credential.lastValidationStatus === "invalid") {
8939
- return "Expired. Upload a fresh auth.json in Settings, or run `arcanist codex login` if the Codex CLI is installed. `arcanist codex use off` falls back to your OpenAI key.";
8940
- }
8941
- if (credential.lastValidationStatus === "validated") return "Verified with OpenAI.";
8942
- return "Saved, but not verified with OpenAI yet.";
8943
- }
8944
- function resolveCodexPath(optionPath) {
8945
- return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
8946
- }
8947
- function runCodexDeviceLogin(codexPath, codexHome) {
8948
- return runVendorLoginProcess({
8949
- displayName: "Codex",
8950
- binPath: codexPath,
8951
- args: ["login", "--device-auth"],
8952
- env: { CODEX_HOME: codexHome },
8953
- missingBinaryHint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN.",
8954
- retryCommand: "arcanist codex login"
8955
- });
8956
- }
8957
- async function setCodexSubscriptionEnabled(config, enabled) {
8958
- return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
8959
- method: "PUT",
8960
- body: JSON.stringify({ enabled })
8961
- });
8962
- }
8963
- async function codexLoginCommand(options, command) {
8964
- const { config } = resolveBusinessContext(command, options);
8965
- const codexPath = resolveCodexPath(options.codexPath);
8966
- const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
8967
- await runCodexDeviceLogin(codexPath, codexHome);
8968
- let raw;
8969
- try {
8970
- raw = await readFile(join4(codexHome, "auth.json"), "utf8");
8971
- } catch {
8972
- throw new CliError("user", "Codex login completed but no auth.json was written.", {
8973
- hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
8974
- });
8975
- }
8976
- if (!raw.trim()) {
8977
- throw new CliError("user", "Codex login produced an empty auth.json.");
8978
- }
8979
- return raw;
8980
- });
8981
- const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
8982
- method: "PUT",
8983
- body: JSON.stringify({ authJson })
8984
- });
8985
- let activated = false;
8986
- let activationError;
8987
- try {
8988
- await setCodexSubscriptionEnabled(config, true);
8989
- activated = true;
8990
- } catch (err) {
8991
- activationError = err instanceof Error ? err.message : String(err);
8992
- }
8993
- emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
8994
- console.log(
8995
- activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
8996
- );
8997
- const status = describeCredentialStatus(payload);
8998
- if (status) console.log(`Status: ${status}`);
8999
- if (!activated) {
9000
- console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
9001
- console.log("Run `arcanist codex use on` to start using it.");
9002
- }
9003
- });
9004
- }
9005
- async function codexUseCommand(state, options, command) {
9006
- const normalized = state.trim().toLowerCase();
9007
- if (normalized !== "on" && normalized !== "off") {
9008
- throw new CliError("user", "Usage: arcanist codex use <on|off>");
9009
- }
9010
- const enabled = normalized === "on";
9011
- const { config } = resolveBusinessContext(command, options);
9012
- const result = await setCodexSubscriptionEnabled(config, enabled);
9013
- emit(
9014
- command,
9015
- options,
9016
- result,
9017
- () => console.log(
9018
- result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
9019
- )
9020
- );
9021
- }
9022
- async function codexStatusCommand(options, command) {
9023
- const { config } = resolveBusinessContext(command, options);
9024
- const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
9025
- emit(command, options, payload, (state) => {
9026
- console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
9027
- console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
9028
- const status = describeCredentialStatus(state.credential);
9029
- if (status) console.log(`Status: ${status}`);
9030
- });
9031
- }
9032
- async function codexLogoutCommand(options, command) {
9033
- const { config } = resolveBusinessContext(command, options);
9034
- await setCodexSubscriptionEnabled(config, false).catch(() => {
9035
- });
9036
- const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
9037
- method: "DELETE"
9038
- });
9039
- emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
9040
- }
9041
-
9042
- // src/uploads.ts
9043
- import { readFile as readFile2 } from "fs/promises";
9044
- import { basename as basename2, extname } from "path";
9045
-
9046
- // ../../shared/constants/uploads.ts
9047
- var MAX_UPLOADED_FILES = 5;
9048
- var MAX_UPLOADED_FILE_SIZE_BYTES = 102400;
9049
- var UPLOADED_FILE_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml", ".xml", ".html"];
9050
- var MAX_UPLOADED_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
9051
- var ALLOWED_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
9052
-
9053
- // ../../shared/utils/uploads.ts
9054
- var ALLOWED_IMAGE_MEDIA_TYPE_SET = new Set(ALLOWED_IMAGE_MEDIA_TYPES);
9055
- function ok(value) {
9056
- return { ok: true, value };
9057
- }
9058
- function fail(error) {
9059
- return { ok: false, error };
8403
+ function fail(error) {
8404
+ return { ok: false, error };
9060
8405
  }
9061
8406
  function validateUploadedName(name, kind) {
9062
8407
  const trimmed = name.trim();
@@ -9228,6 +8573,16 @@ function noChangeOutcomeCopy(reason) {
9228
8573
  }
9229
8574
  }
9230
8575
 
8576
+ // ../../shared/utils/type-guards.ts
8577
+ function isRecord(value) {
8578
+ return !!value && typeof value === "object" && !Array.isArray(value);
8579
+ }
8580
+ function asNonEmptyString(value) {
8581
+ if (typeof value !== "string") return void 0;
8582
+ const trimmed = value.trim();
8583
+ return trimmed.length > 0 ? trimmed : void 0;
8584
+ }
8585
+
9231
8586
  // ../../shared/transcript/malformed-search.ts
9232
8587
  var MALFORMED_SEARCH_BLOCKED_TRANSCRIPT_PREFIX = "Arcanist blocked this malformed search command before execution.";
9233
8588
  var BASH_UNMATCHED_QUOTE_EOF_RE = /\/bin\/bash:\s+-c:\s+line\s+\d+:\s+unexpected EOF while looking for matching [`'"][`'"]?/i;
@@ -10532,149 +9887,594 @@ function parseSsePayload(payload) {
10532
9887
  flush();
10533
9888
  continue;
10534
9889
  }
10535
- if (line.startsWith("event:")) {
10536
- currentEvent = line.slice("event:".length).trim();
10537
- continue;
9890
+ if (line.startsWith("event:")) {
9891
+ currentEvent = line.slice("event:".length).trim();
9892
+ continue;
9893
+ }
9894
+ if (line.startsWith("id:")) {
9895
+ const parsed = Number.parseInt(line.slice("id:".length).trim(), 10);
9896
+ currentId = Number.isFinite(parsed) ? parsed : void 0;
9897
+ continue;
9898
+ }
9899
+ if (line.startsWith("data:")) {
9900
+ currentData.push(line.slice("data:".length).trimStart());
9901
+ }
9902
+ }
9903
+ flush();
9904
+ let status = null;
9905
+ const events = [];
9906
+ for (const message of messages) {
9907
+ const data = message.data ? parseJsonObject(message.data) : {};
9908
+ if (message.event === "status") {
9909
+ const phaseRaw = data.phase;
9910
+ const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
9911
+ const entry = {
9912
+ phase
9913
+ };
9914
+ const sandboxSubstate = validateEnumField(data.sandboxSubstate, VALID_SANDBOX_SUBSTATES);
9915
+ if (sandboxSubstate !== void 0) entry.sandboxSubstate = sandboxSubstate;
9916
+ const stopMode = validateEnumField(data.stopMode, VALID_STOP_MODES);
9917
+ if (stopMode !== void 0) entry.stopMode = stopMode;
9918
+ const finalizingStep = validateEnumField(data.finalizingStep, VALID_FINALIZING_STEPS);
9919
+ if (finalizingStep !== void 0) entry.finalizingStep = finalizingStep;
9920
+ if (typeof data.title === "string") entry.title = data.title;
9921
+ if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
9922
+ entry.spawnDurationMs = data.spawnDurationMs;
9923
+ }
9924
+ status = entry;
9925
+ continue;
9926
+ }
9927
+ events.push({
9928
+ type: message.event,
9929
+ ...message.id !== void 0 ? { id: message.id } : {},
9930
+ data
9931
+ });
9932
+ }
9933
+ return { status, events };
9934
+ }
9935
+ function validateEnumField(value, validSet) {
9936
+ return typeof value === "string" && validSet.has(value) ? value : void 0;
9937
+ }
9938
+ function parseJsonObject(value) {
9939
+ try {
9940
+ const parsed = JSON.parse(value);
9941
+ return parsed && typeof parsed === "object" ? parsed : {};
9942
+ } catch (err) {
9943
+ throw new Error(`Malformed SSE JSON payload: ${stringifyError(err)}`);
9944
+ }
9945
+ }
9946
+ function formatPromptLabel(promptId, promptLabels) {
9947
+ if (!promptId) return "";
9948
+ const prompt = promptLabels.get(promptId);
9949
+ return prompt ? ` - ${prompt}` : "";
9950
+ }
9951
+ function buildPromptLabelMap(prompts) {
9952
+ const labels = /* @__PURE__ */ new Map();
9953
+ for (const prompt of prompts) {
9954
+ const text = derivePromptDisplayText({ prompt: prompt.prompt, replyToText: prompt.replyToText });
9955
+ labels.set(prompt.promptId, text.replace(/\s+/g, " ").trim().slice(0, PROMPT_LABEL_MAX_CHARS));
9956
+ }
9957
+ return labels;
9958
+ }
9959
+ function renderWatchEvent(event, state) {
9960
+ const data = event.data;
9961
+ switch (event.type) {
9962
+ case "text":
9963
+ return { kind: "text", text: String(data.text ?? "") };
9964
+ case "prompt_enqueued": {
9965
+ const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
9966
+ return {
9967
+ kind: "line",
9968
+ line: `[queued] ${promptId ?? "unknown"}${formatPromptLabel(promptId, state.promptLabels)}`
9969
+ };
9970
+ }
9971
+ case "prompt_processing": {
9972
+ const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
9973
+ return {
9974
+ kind: "line",
9975
+ line: `[prompt] ${promptId ?? "unknown"} started${formatPromptLabel(promptId, state.promptLabels)}`
9976
+ };
10538
9977
  }
10539
- if (line.startsWith("id:")) {
10540
- const parsed = Number.parseInt(line.slice("id:".length).trim(), 10);
10541
- currentId = Number.isFinite(parsed) ? parsed : void 0;
10542
- continue;
9978
+ case "prompt_completed": {
9979
+ const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
9980
+ return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} completed` };
10543
9981
  }
10544
- if (line.startsWith("data:")) {
10545
- currentData.push(line.slice("data:".length).trimStart());
9982
+ case "prompt_failed": {
9983
+ const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
9984
+ const error = typeof data.error === "string" ? data.error : "unknown error";
9985
+ return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} failed: ${error}` };
10546
9986
  }
10547
- }
10548
- flush();
10549
- let status = null;
10550
- const events = [];
10551
- for (const message of messages) {
10552
- const data = message.data ? parseJsonObject(message.data) : {};
10553
- if (message.event === "status") {
10554
- const phaseRaw = data.phase;
10555
- const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
10556
- const entry = {
10557
- phase
9987
+ case "tool_call": {
9988
+ const toolId = String(data.id ?? "");
9989
+ const tool = String(data.tool ?? "unknown");
9990
+ const summary = String(data.summary ?? "");
9991
+ state.toolCalls.set(toolId, { tool, summary });
9992
+ return { kind: "line", line: `[tool] ${tool}${summary ? ` - ${summary}` : ""}` };
9993
+ }
9994
+ case "tool_update": {
9995
+ const toolId = String(data.id ?? "");
9996
+ const status = typeof data.status === "string" ? data.status : null;
9997
+ if (!status) return null;
9998
+ const tool = state.toolCalls.get(toolId);
9999
+ if (!tool) return { kind: "line", line: `[tool] ${toolId} ${status}` };
10000
+ return { kind: "line", line: `[tool ${status}] ${tool.tool}${tool.summary ? ` - ${tool.summary}` : ""}` };
10001
+ }
10002
+ case "question":
10003
+ return { kind: "line", line: `[question] ${String(data.question ?? "")}` };
10004
+ case "patch": {
10005
+ const files = Array.isArray(data.files) ? data.files.filter((item) => typeof item === "string") : [];
10006
+ return { kind: "line", line: `[patch] ${files.join(", ")}` };
10007
+ }
10008
+ case "agent_timeline": {
10009
+ const eventType = String(data.eventType ?? "timeline");
10010
+ const status = typeof data.status === "string" ? ` ${data.status}` : "";
10011
+ return { kind: "line", line: `[agent] ${eventType}${status}: ${String(data.summary ?? "")}` };
10012
+ }
10013
+ case "session_error":
10014
+ return {
10015
+ kind: "line",
10016
+ line: `[error] ${formatSessionErrorMessage(String(data.error ?? "Unknown error"), typeof data.code === "string" ? data.code : null)}`
10558
10017
  };
10559
- const sandboxSubstate = validateEnumField(data.sandboxSubstate, VALID_SANDBOX_SUBSTATES);
10560
- if (sandboxSubstate !== void 0) entry.sandboxSubstate = sandboxSubstate;
10561
- const stopMode = validateEnumField(data.stopMode, VALID_STOP_MODES);
10562
- if (stopMode !== void 0) entry.stopMode = stopMode;
10563
- const finalizingStep = validateEnumField(data.finalizingStep, VALID_FINALIZING_STEPS);
10564
- if (finalizingStep !== void 0) entry.finalizingStep = finalizingStep;
10565
- if (typeof data.title === "string") entry.title = data.title;
10566
- if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
10567
- entry.spawnDurationMs = data.spawnDurationMs;
10018
+ case "pr_created":
10019
+ case "pr_updated":
10020
+ return { kind: "line", line: `[pr] ${String(data.prUrl ?? "")}` };
10021
+ case "pr_failed":
10022
+ return { kind: "line", line: `[pr error] ${String(data.error ?? "Unknown error")}` };
10023
+ case "session_idle":
10024
+ return { kind: "line", line: "[idle] waiting for next prompt" };
10025
+ default:
10026
+ return null;
10027
+ }
10028
+ }
10029
+
10030
+ // src/utils/session-payload.ts
10031
+ function unwrapSessionState(payload) {
10032
+ return payload.session && typeof payload.session === "object" ? payload.session : payload;
10033
+ }
10034
+
10035
+ // ../../shared/agent/agent-runtime-backend.ts
10036
+ var CODEX_AGENT_RUNTIME_BACKEND = "codex";
10037
+ var AGENT_RUNTIME_BACKENDS = [CODEX_AGENT_RUNTIME_BACKEND];
10038
+
10039
+ // ../../shared/constants/models.ts
10040
+ var OpenAIModel = {
10041
+ GPT56: "gpt-5.6",
10042
+ GPT56Sol: "gpt-5.6-sol",
10043
+ GPT56Terra: "gpt-5.6-terra",
10044
+ GPT56Luna: "gpt-5.6-luna",
10045
+ GPT55: "gpt-5.5",
10046
+ GPT54: "gpt-5.4",
10047
+ GPT54Pro: "gpt-5.4-pro",
10048
+ GPT54Mini: "gpt-5.4-mini",
10049
+ GPT54Nano: "gpt-5.4-nano",
10050
+ GPT53CodexSpark: "gpt-5.3-codex-spark",
10051
+ GPT53Codex: "gpt-5.3-codex",
10052
+ GPT52: "gpt-5.2",
10053
+ GPT52ChatLatest: "gpt-5.2-chat-latest",
10054
+ GPT52Codex: "gpt-5.2-codex"
10055
+ };
10056
+ var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
10057
+ var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
10058
+ [CODEX_AGENT_RUNTIME_BACKEND]: {
10059
+ backend: CODEX_AGENT_RUNTIME_BACKEND,
10060
+ fixture: "known_image_fixture",
10061
+ deliveryPath: "synthetic_image_context",
10062
+ verifiedAt: "2026-07-07"
10063
+ }
10064
+ };
10065
+ var MODEL_REGISTRY = [
10066
+ {
10067
+ id: OpenAIModel.GPT54,
10068
+ name: "GPT-5.4",
10069
+ provider: "openai",
10070
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10071
+ capabilities: { codexToolSearch: true },
10072
+ contextWindow: 1e6,
10073
+ // Default codex model after the gpt-5.5 downgrade; carries gpt-5.5's prior
10074
+ // "medium" default so default sessions keep the same reasoning effort.
10075
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
10076
+ // No cacheWritePerMillion: the pricing page shows a dash in the cache-writes
10077
+ // column for gpt-5.4 and gpt-5.5, so the absence is deliberate, not an
10078
+ // oversight (checked 2026-07-30). The 5.6 family does publish that rate.
10079
+ pricing: {
10080
+ inputPerMillion: 2.5,
10081
+ outputPerMillion: 15,
10082
+ cacheReadPerMillion: 0.25,
10083
+ longContext: {
10084
+ thresholdTokens: 272e3,
10085
+ inputPerMillion: 5,
10086
+ outputPerMillion: 22.5,
10087
+ cacheReadPerMillion: 0.5
10088
+ }
10089
+ },
10090
+ sessionStart: { eligible: true, isDefault: true }
10091
+ },
10092
+ {
10093
+ id: OpenAIModel.GPT56,
10094
+ name: "GPT-5.6",
10095
+ provider: "openai",
10096
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10097
+ capabilities: { codexToolSearch: true },
10098
+ contextWindow: 105e4,
10099
+ // Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
10100
+ // catalog: the alias routes to gpt-5.6-sol and supports max effort. Pricing
10101
+ // mirrors gpt-5.6-sol since the alias resolves to it. Cache-write rates
10102
+ // verified 2026-07-30 against the Standard tier at
10103
+ // developers.openai.com/api/docs/pricing.
10104
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
10105
+ pricing: {
10106
+ inputPerMillion: 5,
10107
+ outputPerMillion: 30,
10108
+ cacheReadPerMillion: 0.5,
10109
+ cacheWritePerMillion: 6.25,
10110
+ longContext: {
10111
+ thresholdTokens: 272e3,
10112
+ inputPerMillion: 10,
10113
+ outputPerMillion: 45,
10114
+ cacheReadPerMillion: 1,
10115
+ cacheWritePerMillion: 12.5
10116
+ }
10117
+ },
10118
+ sessionStart: { eligible: true }
10119
+ },
10120
+ {
10121
+ id: OpenAIModel.GPT56Sol,
10122
+ name: "GPT-5.6 Sol",
10123
+ provider: "openai",
10124
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10125
+ capabilities: { codexToolSearch: true },
10126
+ contextWindow: 105e4,
10127
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
10128
+ // Cache-write rates verified 2026-07-30 against the Standard tier at
10129
+ // developers.openai.com/api/docs/pricing.
10130
+ pricing: {
10131
+ inputPerMillion: 5,
10132
+ outputPerMillion: 30,
10133
+ cacheReadPerMillion: 0.5,
10134
+ cacheWritePerMillion: 6.25,
10135
+ longContext: {
10136
+ thresholdTokens: 272e3,
10137
+ inputPerMillion: 10,
10138
+ outputPerMillion: 45,
10139
+ cacheReadPerMillion: 1,
10140
+ cacheWritePerMillion: 12.5
10141
+ }
10142
+ },
10143
+ sessionStart: { eligible: true }
10144
+ },
10145
+ {
10146
+ id: OpenAIModel.GPT56Terra,
10147
+ name: "GPT-5.6 Terra",
10148
+ provider: "openai",
10149
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10150
+ capabilities: { codexToolSearch: true },
10151
+ contextWindow: 105e4,
10152
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
10153
+ // Verified 2026-07-30 against the Standard tier at
10154
+ // developers.openai.com/api/docs/pricing: short and long context input,
10155
+ // cached input, cache writes, and output.
10156
+ pricing: {
10157
+ inputPerMillion: 2,
10158
+ outputPerMillion: 12,
10159
+ cacheReadPerMillion: 0.2,
10160
+ cacheWritePerMillion: 2.5,
10161
+ longContext: {
10162
+ thresholdTokens: 272e3,
10163
+ inputPerMillion: 4,
10164
+ outputPerMillion: 18,
10165
+ cacheReadPerMillion: 0.4,
10166
+ cacheWritePerMillion: 5
10167
+ }
10168
+ },
10169
+ sessionStart: { eligible: true }
10170
+ },
10171
+ {
10172
+ id: OpenAIModel.GPT56Luna,
10173
+ name: "GPT-5.6 Luna",
10174
+ provider: "openai",
10175
+ overloadFallback: OpenAIModel.GPT56Terra,
10176
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10177
+ capabilities: { codexToolSearch: true },
10178
+ contextWindow: 105e4,
10179
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
10180
+ // Verified 2026-07-30 against the Standard tier at
10181
+ // developers.openai.com/api/docs/pricing: short and long context input,
10182
+ // cached input, cache writes, and output.
10183
+ pricing: {
10184
+ inputPerMillion: 0.2,
10185
+ outputPerMillion: 1.2,
10186
+ cacheReadPerMillion: 0.02,
10187
+ cacheWritePerMillion: 0.25,
10188
+ longContext: {
10189
+ thresholdTokens: 272e3,
10190
+ inputPerMillion: 0.4,
10191
+ outputPerMillion: 1.8,
10192
+ cacheReadPerMillion: 0.04,
10193
+ cacheWritePerMillion: 0.5
10194
+ }
10195
+ },
10196
+ sessionStart: { eligible: true }
10197
+ },
10198
+ {
10199
+ id: OpenAIModel.GPT55,
10200
+ name: "GPT-5.5",
10201
+ provider: "openai",
10202
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10203
+ capabilities: { codexToolSearch: true },
10204
+ contextWindow: 1e6,
10205
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
10206
+ pricing: {
10207
+ inputPerMillion: 5,
10208
+ outputPerMillion: 30,
10209
+ cacheReadPerMillion: 0.5,
10210
+ longContext: {
10211
+ thresholdTokens: 272e3,
10212
+ inputPerMillion: 10,
10213
+ outputPerMillion: 45,
10214
+ cacheReadPerMillion: 1
10568
10215
  }
10569
- status = entry;
10570
- continue;
10216
+ },
10217
+ sessionStart: { eligible: true }
10218
+ },
10219
+ {
10220
+ id: OpenAIModel.GPT54Mini,
10221
+ name: "GPT-5.4 Mini",
10222
+ provider: "openai",
10223
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10224
+ capabilities: { codexToolSearch: true },
10225
+ contextWindow: 4e5,
10226
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
10227
+ pricing: {
10228
+ inputPerMillion: 0.75,
10229
+ outputPerMillion: 4.5,
10230
+ cacheReadPerMillion: 0.075,
10231
+ // Flex bills at Batch rates: a flat 50% off standard across input,
10232
+ // cached input, and output (verified against the OpenAI pricing page,
10233
+ // June 2026). Bridge cost estimates deliberately strip this gateway-only
10234
+ // axis when deriving MODEL_PRICING.
10235
+ flex: { inputPerMillion: 0.375, outputPerMillion: 2.25, cacheReadPerMillion: 0.0375 }
10236
+ },
10237
+ sessionStart: { eligible: true }
10238
+ },
10239
+ {
10240
+ id: OpenAIModel.GPT54Nano,
10241
+ name: "GPT-5.4 Nano",
10242
+ provider: "openai",
10243
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10244
+ capabilities: { codexToolSearch: false },
10245
+ contextWindow: 4e5,
10246
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
10247
+ pricing: { inputPerMillion: 0.2, outputPerMillion: 1.25, cacheReadPerMillion: 0.02 },
10248
+ sessionStart: { eligible: true }
10249
+ },
10250
+ {
10251
+ id: OpenAIModel.GPT53CodexSpark,
10252
+ name: "GPT-5.3 Codex Spark",
10253
+ provider: "openai",
10254
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10255
+ capabilities: { codexToolSearch: true },
10256
+ contextWindow: 128e3,
10257
+ reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
10258
+ costTracked: false,
10259
+ requiresCodexSubscriptionAuth: true,
10260
+ sessionStart: { eligible: true },
10261
+ visibility: "internal_probe"
10262
+ },
10263
+ {
10264
+ id: OpenAIModel.GPT53Codex,
10265
+ name: "GPT-5.3 Codex",
10266
+ provider: "openai",
10267
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10268
+ capabilities: { codexToolSearch: true },
10269
+ contextWindow: 4e5,
10270
+ reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
10271
+ pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
10272
+ },
10273
+ {
10274
+ id: OpenAIModel.GPT52,
10275
+ name: "GPT-5.2",
10276
+ provider: "openai",
10277
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10278
+ capabilities: { codexToolSearch: true },
10279
+ contextWindow: 4e5,
10280
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
10281
+ pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
10282
+ },
10283
+ {
10284
+ id: OpenAIModel.GPT52ChatLatest,
10285
+ name: "GPT-5.2 Chat",
10286
+ provider: "openai",
10287
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10288
+ capabilities: { codexToolSearch: true },
10289
+ contextWindow: 128e3,
10290
+ reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
10291
+ pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
10292
+ },
10293
+ {
10294
+ id: OpenAIModel.GPT52Codex,
10295
+ name: "GPT-5.2 Codex",
10296
+ provider: "openai",
10297
+ backends: [CODEX_AGENT_RUNTIME_BACKEND],
10298
+ capabilities: { codexToolSearch: true },
10299
+ contextWindow: 4e5,
10300
+ reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
10301
+ pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
10302
+ }
10303
+ ];
10304
+ var MODEL_PROVIDER_NAMES = {
10305
+ openai: "OpenAI",
10306
+ anthropic: "Anthropic"
10307
+ };
10308
+ function buildSessionStartModelIdsByBackend() {
10309
+ const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
10310
+ for (const model of MODEL_REGISTRY) {
10311
+ if (!model.sessionStart?.eligible) continue;
10312
+ for (const backend of model.backends) {
10313
+ if (!modelSupportsRequiredSessionStartCapabilities(model, backend)) continue;
10314
+ byBackend[backend].push(model.id);
10571
10315
  }
10572
- events.push({
10573
- type: message.event,
10574
- ...message.id !== void 0 ? { id: message.id } : {},
10575
- data
10576
- });
10577
10316
  }
10578
- return { status, events };
10317
+ return byBackend;
10579
10318
  }
10580
- function validateEnumField(value, validSet) {
10581
- return typeof value === "string" && validSet.has(value) ? value : void 0;
10319
+ function modelSupportsRequiredSessionStartCapabilities(model, backend) {
10320
+ if (backend !== CODEX_AGENT_RUNTIME_BACKEND) return true;
10321
+ return model.capabilities?.codexToolSearch === true;
10582
10322
  }
10583
- function parseJsonObject(value) {
10584
- try {
10585
- const parsed = JSON.parse(value);
10586
- return parsed && typeof parsed === "object" ? parsed : {};
10587
- } catch (err) {
10588
- throw new Error(`Malformed SSE JSON payload: ${stringifyError(err)}`);
10323
+ function buildDefaultSessionStartModelIdByBackend() {
10324
+ const defaults = {};
10325
+ for (const backend of AGENT_RUNTIME_BACKENDS) {
10326
+ const backendDefaults = MODEL_REGISTRY.filter(
10327
+ (model) => model.sessionStart?.eligible && model.sessionStart.isDefault === true && model.backends.includes(backend)
10328
+ );
10329
+ if (backendDefaults.length !== 1) {
10330
+ throw new Error(`Expected exactly one default session-start model for ${backend}, got ${backendDefaults.length}`);
10331
+ }
10332
+ defaults[backend] = backendDefaults[0].id;
10589
10333
  }
10334
+ return defaults;
10590
10335
  }
10591
- function formatPromptLabel(promptId, promptLabels) {
10592
- if (!promptId) return "";
10593
- const prompt = promptLabels.get(promptId);
10594
- return prompt ? ` - ${prompt}` : "";
10595
- }
10596
- function buildPromptLabelMap(prompts) {
10597
- const labels = /* @__PURE__ */ new Map();
10598
- for (const prompt of prompts) {
10599
- const text = derivePromptDisplayText({ prompt: prompt.prompt, replyToText: prompt.replyToText });
10600
- labels.set(prompt.promptId, text.replace(/\s+/g, " ").trim().slice(0, PROMPT_LABEL_MAX_CHARS));
10336
+ var SESSION_START_MODEL_IDS_BY_BACKEND = buildSessionStartModelIdsByBackend();
10337
+ var DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND = buildDefaultSessionStartModelIdByBackend();
10338
+ var DEFAULT_SESSION_START_MODEL_ID = DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND];
10339
+ var VALID_MODEL_IDS = new Set(MODEL_REGISTRY.map((model) => model.id));
10340
+ var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
10341
+ [CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND])
10342
+ };
10343
+ var MODEL_CONTEXT_WINDOWS = {
10344
+ ...Object.fromEntries(
10345
+ MODEL_REGISTRY.flatMap((model) => model.contextWindow === void 0 ? [] : [[model.id, model.contextWindow]])
10346
+ )
10347
+ };
10348
+ var MODEL_PROVIDERS = {
10349
+ ...Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model.provider]))
10350
+ };
10351
+ var MODEL_REASONING_CONFIG = Object.fromEntries(
10352
+ MODEL_REGISTRY.flatMap((model) => model.reasoning ? [[model.id, model.reasoning]] : [])
10353
+ );
10354
+ var MODEL_DEFINITIONS_BY_ID = Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model]));
10355
+ function splitModelIdentifier(value) {
10356
+ let hasSeparator = false;
10357
+ for (const separator of [":", "/"]) {
10358
+ const index = value.indexOf(separator);
10359
+ if (index <= 0 || index >= value.length - 1) continue;
10360
+ hasSeparator = true;
10361
+ const providerID = value.slice(0, index);
10362
+ const modelID = value.slice(index + 1);
10363
+ if (MODEL_PROVIDERS_SET.has(providerID) && modelID.length > 0) {
10364
+ return { providerID, modelID };
10365
+ }
10601
10366
  }
10602
- return labels;
10367
+ if (hasSeparator) return void 0;
10368
+ return value.length > 0 ? { modelID: value } : void 0;
10603
10369
  }
10604
- function renderWatchEvent(event, state) {
10605
- const data = event.data;
10606
- switch (event.type) {
10607
- case "text":
10608
- return { kind: "text", text: String(data.text ?? "") };
10609
- case "prompt_enqueued": {
10610
- const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
10611
- return {
10612
- kind: "line",
10613
- line: `[queued] ${promptId ?? "unknown"}${formatPromptLabel(promptId, state.promptLabels)}`
10614
- };
10615
- }
10616
- case "prompt_processing": {
10617
- const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
10618
- return {
10619
- kind: "line",
10620
- line: `[prompt] ${promptId ?? "unknown"} started${formatPromptLabel(promptId, state.promptLabels)}`
10621
- };
10622
- }
10623
- case "prompt_completed": {
10624
- const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
10625
- return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} completed` };
10626
- }
10627
- case "prompt_failed": {
10628
- const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
10629
- const error = typeof data.error === "string" ? data.error : "unknown error";
10630
- return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} failed: ${error}` };
10631
- }
10632
- case "tool_call": {
10633
- const toolId = String(data.id ?? "");
10634
- const tool = String(data.tool ?? "unknown");
10635
- const summary = String(data.summary ?? "");
10636
- state.toolCalls.set(toolId, { tool, summary });
10637
- return { kind: "line", line: `[tool] ${tool}${summary ? ` - ${summary}` : ""}` };
10638
- }
10639
- case "tool_update": {
10640
- const toolId = String(data.id ?? "");
10641
- const status = typeof data.status === "string" ? data.status : null;
10642
- if (!status) return null;
10643
- const tool = state.toolCalls.get(toolId);
10644
- if (!tool) return { kind: "line", line: `[tool] ${toolId} ${status}` };
10645
- return { kind: "line", line: `[tool ${status}] ${tool.tool}${tool.summary ? ` - ${tool.summary}` : ""}` };
10646
- }
10647
- case "question":
10648
- return { kind: "line", line: `[question] ${String(data.question ?? "")}` };
10649
- case "patch": {
10650
- const files = Array.isArray(data.files) ? data.files.filter((item) => typeof item === "string") : [];
10651
- return { kind: "line", line: `[patch] ${files.join(", ")}` };
10370
+ function getRawModelValue(raw) {
10371
+ if (typeof raw === "string") return asNonEmptyString(raw);
10372
+ if (!raw || typeof raw !== "object") return void 0;
10373
+ const value = raw;
10374
+ return asNonEmptyString(value.modelID) ?? asNonEmptyString(value.modelId) ?? asNonEmptyString(value.id);
10375
+ }
10376
+ function getExplicitProvider(raw) {
10377
+ if (raw && typeof raw === "object") {
10378
+ const value = raw;
10379
+ const providerID = asNonEmptyString(value.providerID) ?? asNonEmptyString(value.providerId);
10380
+ if (providerID && MODEL_PROVIDERS_SET.has(providerID)) {
10381
+ return providerID;
10652
10382
  }
10653
- case "agent_timeline": {
10654
- const eventType = String(data.eventType ?? "timeline");
10655
- const status = typeof data.status === "string" ? ` ${data.status}` : "";
10656
- return { kind: "line", line: `[agent] ${eventType}${status}: ${String(data.summary ?? "")}` };
10383
+ }
10384
+ const modelValue = getRawModelValue(raw);
10385
+ if (!modelValue) return void 0;
10386
+ return splitModelIdentifier(modelValue)?.providerID;
10387
+ }
10388
+ function extractModelId(raw) {
10389
+ const value = getRawModelValue(raw);
10390
+ if (!value) return void 0;
10391
+ return splitModelIdentifier(value)?.modelID;
10392
+ }
10393
+ function getSessionStartModelIdsForBackend(backend) {
10394
+ return SESSION_START_MODEL_IDS_BY_BACKEND[backend];
10395
+ }
10396
+ function isSessionStartModelAllowedForBackend(modelId, backend) {
10397
+ return VALID_SESSION_START_MODEL_IDS_BY_BACKEND[backend].has(modelId);
10398
+ }
10399
+ function getProviderForModel(modelId) {
10400
+ return MODEL_PROVIDERS[modelId] ?? "openai";
10401
+ }
10402
+ function toModelSelection(raw) {
10403
+ const modelID = extractModelId(raw);
10404
+ if (!modelID) return void 0;
10405
+ return {
10406
+ providerID: getExplicitProvider(raw) ?? getProviderForModel(modelID),
10407
+ modelID
10408
+ };
10409
+ }
10410
+ function getModelDefinition(raw) {
10411
+ const modelID = extractModelId(raw);
10412
+ if (!modelID) return void 0;
10413
+ return MODEL_DEFINITIONS_BY_ID[modelID];
10414
+ }
10415
+ function formatModelLabel(raw, options = {}) {
10416
+ const includeProvider = options.includeProvider ?? false;
10417
+ const definition = getModelDefinition(raw);
10418
+ if (definition) {
10419
+ return includeProvider ? `${MODEL_PROVIDER_NAMES[definition.provider]} / ${definition.name}` : definition.name;
10420
+ }
10421
+ const selection = toModelSelection(raw);
10422
+ if (!selection) return void 0;
10423
+ const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
10424
+ return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
10425
+ }
10426
+ function buildModelProviderGroups(models2) {
10427
+ const groups = /* @__PURE__ */ new Map();
10428
+ for (const model of models2) {
10429
+ let group = groups.get(model.provider);
10430
+ if (!group) {
10431
+ group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
10432
+ groups.set(model.provider, group);
10657
10433
  }
10658
- case "session_error":
10659
- return {
10660
- kind: "line",
10661
- line: `[error] ${formatSessionErrorMessage(String(data.error ?? "Unknown error"), typeof data.code === "string" ? data.code : null)}`
10662
- };
10663
- case "pr_created":
10664
- case "pr_updated":
10665
- return { kind: "line", line: `[pr] ${String(data.prUrl ?? "")}` };
10666
- case "pr_failed":
10667
- return { kind: "line", line: `[pr error] ${String(data.error ?? "Unknown error")}` };
10668
- case "session_idle":
10669
- return { kind: "line", line: "[idle] waiting for next prompt" };
10670
- default:
10671
- return null;
10434
+ group.models.push({
10435
+ id: model.id,
10436
+ name: model.name,
10437
+ label: formatModelLabel(model, { includeProvider: true }) ?? model.name,
10438
+ backends: model.backends,
10439
+ reasoning: model.reasoning
10440
+ });
10441
+ }
10442
+ const defaultModelIds = new Set(
10443
+ models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
10444
+ );
10445
+ for (const group of groups.values()) {
10446
+ group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
10672
10447
  }
10448
+ return [...groups.values()];
10673
10449
  }
10450
+ var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
10451
+ AGENT_RUNTIME_BACKENDS.flatMap((backend) => [...SESSION_START_MODEL_IDS_BY_BACKEND[backend]])
10452
+ );
10453
+ var PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
10454
+ MODEL_REGISTRY.filter(
10455
+ (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && model.visibility !== "internal_probe"
10456
+ )
10457
+ );
10458
+ var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
10459
+ MODEL_REGISTRY.filter(
10460
+ (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
10461
+ )
10462
+ );
10674
10463
 
10675
- // src/utils/session-payload.ts
10676
- function unwrapSessionState(payload) {
10677
- return payload.session && typeof payload.session === "object" ? payload.session : payload;
10464
+ // src/commands/model-options.ts
10465
+ function resolveModelAndBackend(options) {
10466
+ let agentRuntimeBackend = CODEX_AGENT_RUNTIME_BACKEND;
10467
+ const normalizedModel = options.model !== void 0 ? extractModelId(options.model) : void 0;
10468
+ if (options.model !== void 0) {
10469
+ if (normalizedModel === void 0 || !isSessionStartModelAllowedForBackend(normalizedModel, agentRuntimeBackend)) {
10470
+ const allowed = getSessionStartModelIdsForBackend(agentRuntimeBackend).join(", ");
10471
+ throw new CliError(
10472
+ "user",
10473
+ `Model '${options.model}' is not selectable for backend '${agentRuntimeBackend}'. Allowed: ${allowed}.`
10474
+ );
10475
+ }
10476
+ }
10477
+ return { agentRuntimeBackend, modelId: normalizedModel };
10678
10478
  }
10679
10479
 
10680
10480
  // ../../shared/session/transient-disconnect.ts
@@ -10978,16 +10778,6 @@ async function createCommand(repoUrl, promptArg, options, command) {
10978
10778
  if (promptPreview) body.prompt = promptPreview;
10979
10779
  if (baseBranch) body.baseBranch = baseBranch;
10980
10780
  if (startBranch) body.startBranch = startBranch;
10981
- const linearIssue = options.linearIssue?.trim();
10982
- if (options.linearIssue !== void 0 && !linearIssue) {
10983
- throw new CliError("user", "--linear-issue must not be empty.");
10984
- }
10985
- if (linearIssue) body.linearIssue = linearIssue;
10986
- const jiraIssue = options.jiraIssue?.trim();
10987
- if (options.jiraIssue !== void 0 && !jiraIssue) {
10988
- throw new CliError("user", "--jira-issue must not be empty.");
10989
- }
10990
- if (jiraIssue) body.jiraIssue = jiraIssue;
10991
10781
  if (continuePr) body.continuePrUrl = continuePr;
10992
10782
  if (continueMode) body.continueMode = continueMode;
10993
10783
  if (options.onboarding) body.onboarding = true;
@@ -11062,8 +10852,6 @@ async function createCommand(repoUrl, promptArg, options, command) {
11062
10852
  if (options.autoVerify) output.autoVerify = true;
11063
10853
  if (baseBranch) output.baseBranch = baseBranch;
11064
10854
  if (startBranch) output.startBranch = startBranch;
11065
- if (linearIssue) output.linearIssue = linearIssue;
11066
- if (jiraIssue) output.jiraIssue = jiraIssue;
11067
10855
  if (continuePr) output.continuePrUrl = continuePr;
11068
10856
  if (continueMode) output.continueMode = continueMode;
11069
10857
  if (options.onboarding) output.onboarding = true;
@@ -11119,6 +10907,43 @@ function parseEgressAllowlistSourceFile(content, key = EGRESS_ALLOWLIST_SOURCE_P
11119
10907
  return normalizeEgressDomains(domains, key);
11120
10908
  }
11121
10909
 
10910
+ // ../../shared/github/repo-url.ts
10911
+ function parseGithubRepoFullName(value) {
10912
+ const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
10913
+ if (shorthand) return { owner: shorthand[1], repo: shorthand[2] };
10914
+ const ssh = value.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
10915
+ if (ssh) return { owner: ssh[1], repo: ssh[2] };
10916
+ const https = value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
10917
+ if (https) return { owner: https[1], repo: https[2] };
10918
+ return null;
10919
+ }
10920
+
10921
+ // src/git.ts
10922
+ import { execFileSync } from "child_process";
10923
+ function git(args) {
10924
+ try {
10925
+ return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
10926
+ } catch (err) {
10927
+ throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
10928
+ }
10929
+ }
10930
+ function currentRepo() {
10931
+ const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
10932
+ if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
10933
+ return { owner: parsed.owner, repo: parsed.repo };
10934
+ }
10935
+
10936
+ // src/utils/repo-arg.ts
10937
+ function parseRepoArg(value, argName = "repo") {
10938
+ const parsed = parseGithubRepoFullName(value);
10939
+ if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
10940
+ return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
10941
+ }
10942
+ function parseRepoArgOrCurrent(value, argName = "repo") {
10943
+ if (value === void 0) return currentRepo();
10944
+ return parseRepoArg(value, argName);
10945
+ }
10946
+
11122
10947
  // src/commands/egress.ts
11123
10948
  async function egressSourceSetCommand(sourceRepoArg, options = {}, command) {
11124
10949
  const { config } = resolveBusinessContext(command, options);
@@ -11312,142 +11137,6 @@ async function modelsListCommand(options = {}, command) {
11312
11137
  });
11313
11138
  }
11314
11139
 
11315
- // ../../shared/agent/verify-directive.ts
11316
- var MAX_TARGET_PR_URL_LENGTH = 500;
11317
- function normalizeGithubPullRequestUrl(rawUrl) {
11318
- if (typeof rawUrl !== "string") return null;
11319
- const trimmed = rawUrl.trim();
11320
- if (!trimmed || trimmed.length > MAX_TARGET_PR_URL_LENGTH) return null;
11321
- let url;
11322
- try {
11323
- url = new URL(trimmed);
11324
- } catch {
11325
- return null;
11326
- }
11327
- if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "github.com") return null;
11328
- const pathParts = url.pathname.split("/").filter(Boolean);
11329
- if (pathParts.length !== 4 || pathParts[2] !== "pull" || !/^\d+$/.test(pathParts[3])) return null;
11330
- if (!/^[A-Za-z0-9_.-]+$/.test(pathParts[0]) || !/^[A-Za-z0-9_.-]+$/.test(pathParts[1])) return null;
11331
- return `https://github.com/${pathParts[0]}/${pathParts[1]}/pull/${pathParts[3]}`;
11332
- }
11333
-
11334
- // src/commands/qa.ts
11335
- var QA_PROMPT = "Verify this pull request.";
11336
- function parseCanonicalPrUrl(prUrl) {
11337
- const targetPrUrl = normalizeGithubPullRequestUrl(prUrl);
11338
- if (!targetPrUrl) {
11339
- throw new CliError(
11340
- "user",
11341
- `Invalid pull request URL: "${prUrl}". Expected https://github.com/<owner>/<repo>/pull/<number>.`
11342
- );
11343
- }
11344
- const url = new URL(targetPrUrl);
11345
- const [owner, repo] = url.pathname.split("/").filter(Boolean);
11346
- return {
11347
- targetPrUrl,
11348
- repoUrl: `https://github.com/${owner}/${repo}`
11349
- };
11350
- }
11351
- function parseCreateConflict(error) {
11352
- let message = error.message;
11353
- let sessionId;
11354
- let sessionUrl;
11355
- const parsed = parseConflictBody(error);
11356
- if (parsed) {
11357
- const rawError = parsed.error;
11358
- const parsedMessage = typeof rawError === "string" ? rawError : rawError && typeof rawError === "object" && typeof rawError.message === "string" ? rawError.message : void 0;
11359
- if (parsedMessage) message = parsedMessage;
11360
- if (typeof parsed.sessionId === "string") sessionId = parsed.sessionId;
11361
- if (typeof parsed.sessionUrl === "string") sessionUrl = parsed.sessionUrl;
11362
- }
11363
- const location = sessionUrl ? ` (${sessionUrl})` : "";
11364
- const existing = sessionId ? ` Existing verifier session: ${sessionId}${location}.` : "";
11365
- return new CliError("conflict", `${message}${existing}`, {
11366
- hint: error.hint,
11367
- requestId: error.requestId
11368
- });
11369
- }
11370
- async function qaCommand(prUrl, options, command) {
11371
- const runtime = getRuntimeOptions(command, options);
11372
- assertArcanistSessionMutationAllowed("qa");
11373
- const config = requireConfig(runtime);
11374
- const { targetPrUrl, repoUrl } = parseCanonicalPrUrl(prUrl);
11375
- resolveModelAndBackend(options);
11376
- const idempotencyKey = options.idempotencyKey ?? randomIdempotencyKey();
11377
- const sessionIdempotencyKey = `${idempotencyKey}:session`;
11378
- const promptIdempotencyKey = `${idempotencyKey}:prompt`;
11379
- const body = {
11380
- context: { repoUrl },
11381
- qa: true,
11382
- targetPrUrl
11383
- };
11384
- if (options.model) body.model = options.model;
11385
- if (options.reasoningEffort) body.reasoningEffort = options.reasoningEffort;
11386
- let sessionData;
11387
- try {
11388
- sessionData = await apiFetch(config, "/api/sessions", {
11389
- method: "POST",
11390
- headers: { "Idempotency-Key": sessionIdempotencyKey },
11391
- body: JSON.stringify(body)
11392
- });
11393
- } catch (err) {
11394
- if (err instanceof ApiError && err.status === 409) throw parseCreateConflict(err);
11395
- throw err;
11396
- }
11397
- const sessionId = sessionData.sessionId;
11398
- let promptId;
11399
- if (!sessionData.promptAlreadyEnqueued) {
11400
- try {
11401
- const promptData = await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
11402
- method: "POST",
11403
- headers: { "Idempotency-Key": promptIdempotencyKey },
11404
- body: JSON.stringify({ prompt: QA_PROMPT })
11405
- });
11406
- promptId = promptData.prompt?.promptId ?? promptData.prompt?.id;
11407
- } catch (err) {
11408
- throw new CliError(
11409
- err instanceof CliError ? err.code : "server",
11410
- `QA session created (${sessionId}) but prompt enqueue failed: ${stringifyError(err)}`,
11411
- {
11412
- exitCode: err instanceof CliError ? err.exitCode : void 0,
11413
- hint: `Retry with: arcanist sessions qa ${targetPrUrl} --idempotency-key ${idempotencyKey}`,
11414
- requestId: err instanceof CliError ? err.requestId : void 0,
11415
- data: {
11416
- ...err instanceof CliError && err.data ? err.data : {},
11417
- sessionId,
11418
- ...sessionData.sessionUrl ? { sessionUrl: sessionData.sessionUrl } : {}
11419
- }
11420
- }
11421
- );
11422
- }
11423
- }
11424
- if (options.wait) {
11425
- const waitPollIntervalMs = parsePollInterval(options.pollInterval);
11426
- if (!isJson(command, options)) {
11427
- console.log(`Session: ${sessionId}`);
11428
- if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
11429
- }
11430
- await waitForCreatedPrompt(sessionId, promptId, sessionData.sessionUrl, waitPollIntervalMs, runtime, command);
11431
- if (!isJson(command, options)) {
11432
- console.log(`Target PR: ${targetPrUrl}`);
11433
- }
11434
- }
11435
- if (isJson(command, options)) {
11436
- const output = { sessionId, repoUrl, targetPrUrl };
11437
- if (sessionData.sessionUrl) output.sessionUrl = sessionData.sessionUrl;
11438
- if (options.model) output.model = options.model;
11439
- if (options.reasoningEffort) output.reasoningEffort = options.reasoningEffort;
11440
- if (promptId) output.promptId = promptId;
11441
- writeJson(output);
11442
- return;
11443
- }
11444
- if (options.wait) return;
11445
- console.log(`Session: ${sessionId}`);
11446
- if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
11447
- console.log(`Target PR: ${targetPrUrl}`);
11448
- console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
11449
- }
11450
-
11451
11140
  // src/commands/repos.ts
11452
11141
  async function reposListCommand(options = {}, command) {
11453
11142
  const { config } = resolveBusinessContext(command, options);
@@ -12657,6 +12346,26 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
12657
12346
  emit(command, options, payload, () => console.log(`Cleared sandbox layer assignment for ${repoPath(targetRepo)}.`));
12658
12347
  }
12659
12348
 
12349
+ // src/utils/pagination.ts
12350
+ var MAX_ALL_PAGES = 1e3;
12351
+ async function fetchPages(label, all, initialCursor, fetchPage) {
12352
+ const items = [];
12353
+ let cursor = initialCursor;
12354
+ let nextCursor = null;
12355
+ let pageCount = 0;
12356
+ do {
12357
+ pageCount += 1;
12358
+ if (all && pageCount > MAX_ALL_PAGES) {
12359
+ throw new CliError("user", `${label} exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
12360
+ }
12361
+ const page = await fetchPage(cursor);
12362
+ items.push(...page.items);
12363
+ nextCursor = page.nextCursor;
12364
+ cursor = nextCursor ?? void 0;
12365
+ } while (all && nextCursor);
12366
+ return { items, nextCursor: all ? null : nextCursor };
12367
+ }
12368
+
12660
12369
  // src/commands/sessions.ts
12661
12370
  async function listSessionsCommand(options, command) {
12662
12371
  const runtime = getRuntimeOptions(command, options);
@@ -13043,7 +12752,7 @@ function addCreateOptions(cmd) {
13043
12752
  return cmd.argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--auto-verify", "Opt this session into automatic QA verification after PR creation").option("--base-branch <branch>", "Base branch to create the session against (defaults to the repo default branch)").option(
13044
12753
  "--start-branch <branch>",
13045
12754
  "Resume an existing branch with its history instead of forking a new one off base"
13046
- ).option("--linear-issue <id|url>", "Associate the session with a Linear issue for pickup writeback").option("--jira-issue <key|url>", "Associate the session with a Jira issue for pickup writeback").option("--continue-pr <url>", "Continue from an existing same-repo pull request").option("--continue-mode <mode>", "Continuation mode: auto (default), update-pr, or new-pr").option("--browser-identity <id|none>", "Attach a business browser identity, or explicitly attach none").option("--prompt-stdin", "Read prompt from stdin").option(
12755
+ ).option("--continue-pr <url>", "Continue from an existing same-repo pull request").option("--continue-mode <mode>", "Continuation mode: auto (default), update-pr, or new-pr").option("--browser-identity <id|none>", "Attach a business browser identity, or explicitly attach none").option("--prompt-stdin", "Read prompt from stdin").option(
13047
12756
  "--uploaded-file <path>",
13048
12757
  "Attach a local text file to the prompt as uploadedFiles; repeat for multiple files",
13049
12758
  collectUploadedFileOption
@@ -13089,25 +12798,6 @@ JSON mode returns {sessionId, promptId?}
13089
12798
  `
13090
12799
  );
13091
12800
  }
13092
- function addQaOptions(cmd) {
13093
- return cmd.argument("<pr-url>", "GitHub pull request URL to QA").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--wait", "Wait for the QA prompt to finish and exit non-zero if it fails").option(
13094
- "--poll-interval <ms>",
13095
- "Polling interval in milliseconds while waiting",
13096
- String(DEFAULT_WATCH_POLL_INTERVAL_MS)
13097
- ).option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
13098
- "after",
13099
- `
13100
- Examples:
13101
- arcanist sessions qa https://github.com/org/repo/pull/123
13102
- arcanist sessions qa https://github.com/org/repo/pull/123 --model gpt-5.4 --wait
13103
- arcanist sessions qa https://github.com/org/repo/pull/123 --idempotency-key 1f0e6f1a-...
13104
-
13105
- JSON:
13106
- JSON mode returns {sessionId, sessionUrl?, repoUrl, targetPrUrl, model?, reasoningEffort?, promptId?}
13107
- Inspect progress with the session URL or the session events stream.
13108
- `
13109
- );
13110
- }
13111
12801
  var auth = program.command("auth").description("Authentication commands");
13112
12802
  auth.command("login").description("Authenticate with a personal access token").option("--token-stdin", "Read token from stdin instead of interactive prompt").option("--api-url <url>", "Set custom API URL").addHelpText(
13113
12803
  "after",
@@ -13177,9 +12867,6 @@ addCreateOptions(sessions.command("create").description("Create a session and se
13177
12867
  addSendOptions(sessions.command("send").description("Send a message to an existing session")).action(
13178
12868
  (sessionId, prompt, options, command) => messageCommand(sessionId, prompt, options, command)
13179
12869
  );
13180
- addQaOptions(sessions.command("qa").description("Start a QA verification session for a GitHub pull request")).action(
13181
- (prUrl, options, command) => qaCommand(prUrl, options, command)
13182
- );
13183
12870
  sessions.command("respond").description("Answer a pending session question").argument("<session-id>", "Session ID").argument("[answer]", "Answer text, or '-' to read stdin").option("--answer-stdin", "Read answer from stdin").requiredOption("--question-id <id>", "Question ID from the question event").addHelpText(
13184
12871
  "after",
13185
12872
  `
@@ -13312,40 +12999,6 @@ JSON:
13312
12999
  JSON mode returns {models}
13313
13000
  `
13314
13001
  ).action((options, command) => modelsListCommand(options, command));
13315
- var automations = program.command("automations").description("Automation commands");
13316
- automations.command("create").description("Create a scheduled automation").argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to run, or '-' to read stdin").requiredOption("--cron <expr>", "Cron expression").option("--name <name>", "Automation display name").option(
13317
- "--model <model>",
13318
- "Model to pin for this automation's sessions (e.g. gpt-5.5). Defaults to the codex backend default."
13319
- ).option(
13320
- "--slack-team-id <team-id>",
13321
- "Slack workspace/team ID for final digest delivery (requires --slack-channel-id)"
13322
- ).option("--slack-channel-id <channel-id>", "Slack channel ID for final digest delivery (requires --slack-team-id)").option("--prompt-stdin", "Read prompt from stdin").addHelpText(
13323
- "after",
13324
- `
13325
- Examples:
13326
- arcanist automations create tryarcanist/arcanist "summarize regressions" --cron "*/15 * * * *"
13327
- arcanist automations create tryarcanist/arcanist "audit N+1s" --cron "0 13 * * 5" --model gpt-5.5
13328
- arcanist automations create tryarcanist/arcanist "/audit-zeus-day yesterday" --cron "0 8 * * *" --slack-team-id T0123ABC --slack-channel-id C0456DEF
13329
- printf "summarize regressions" | arcanist automations create tryarcanist/arcanist --prompt-stdin --cron "*/15 * * * *" --json
13330
- `
13331
- ).action((repoUrl, prompt, options, command) => createAutomationCommand(repoUrl, prompt, options, command));
13332
- automations.command("list").description("List scheduled automations").option("--limit <n>", "Maximum automations to return").option("--cursor <cursor>", "Pagination cursor").option("--all", "Fetch all pages").addHelpText(
13333
- "after",
13334
- `
13335
- Examples:
13336
- arcanist automations list
13337
- arcanist automations list --limit 20 --json
13338
- arcanist automations list --all --json
13339
- `
13340
- ).action((options, command) => listAutomationsCommand(options, command));
13341
- automations.command("delete").description("Delete a scheduled automation").argument("<id>", "Automation ID").option("--yes", "Confirm deletion without prompting").addHelpText(
13342
- "after",
13343
- `
13344
- Examples:
13345
- arcanist automations delete auto_123
13346
- arcanist automations delete auto_123 --yes --json
13347
- `
13348
- ).action((id, options, command) => deleteAutomationCommand(id, options, command));
13349
13002
  var sandbox = program.command("sandbox").description("Sandbox layer commands");
13350
13003
  sandbox.command("init").description("Create sandbox layer source files").action((options, command) => sandboxInitCommand(options, command));
13351
13004
  sandbox.command("validate").description("Validate local sandbox layer source files").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").action((options, command) => sandboxValidateCommand(options, command));