@eddyskywalker/dsh-chatgpt-subscription 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/lib/client.js +136 -23
  3. package/lib/client.js.map +1 -1
  4. package/lib/codemode.js +1565 -0
  5. package/lib/index.js +414 -64
  6. package/lib/types/client/CodexImageToolView.d.ts.map +1 -1
  7. package/lib/types/client/progress-updates/progress-row.d.ts.map +1 -1
  8. package/lib/types/client/styles.d.ts.map +1 -1
  9. package/lib/types/compat.d.ts +10 -1
  10. package/lib/types/compat.d.ts.map +1 -1
  11. package/lib/types/entry-codemode.d.ts +37 -0
  12. package/lib/types/entry-codemode.d.ts.map +1 -0
  13. package/lib/types/host/agent-preset.d.ts +24 -2
  14. package/lib/types/host/agent-preset.d.ts.map +1 -1
  15. package/lib/types/host/antigravity/image-tool.d.ts +10 -2
  16. package/lib/types/host/antigravity/image-tool.d.ts.map +1 -1
  17. package/lib/types/host/cache-warmup/install.d.ts +10 -4
  18. package/lib/types/host/cache-warmup/install.d.ts.map +1 -1
  19. package/lib/types/host/cache-warmup/projection.d.ts +17 -0
  20. package/lib/types/host/cache-warmup/projection.d.ts.map +1 -1
  21. package/lib/types/host/codemode/discovery.d.ts +33 -0
  22. package/lib/types/host/codemode/discovery.d.ts.map +1 -0
  23. package/lib/types/host/codemode/executor.d.ts +140 -0
  24. package/lib/types/host/codemode/executor.d.ts.map +1 -0
  25. package/lib/types/host/codemode/retention.d.ts +34 -0
  26. package/lib/types/host/codemode/retention.d.ts.map +1 -0
  27. package/lib/types/host/codemode/rules.d.ts +183 -0
  28. package/lib/types/host/codemode/rules.d.ts.map +1 -0
  29. package/lib/types/host/codemode/scope.d.ts +59 -0
  30. package/lib/types/host/codemode/scope.d.ts.map +1 -0
  31. package/lib/types/host/codemode/transport.d.ts +258 -0
  32. package/lib/types/host/codemode/transport.d.ts.map +1 -0
  33. package/lib/types/host/codex-images.d.ts +12 -0
  34. package/lib/types/host/codex-images.d.ts.map +1 -1
  35. package/package.json +6 -1
  36. package/presets/dispatch/README.md +12 -3
  37. package/presets/dispatch/agent.cordis.yml +10 -9
  38. package/presets/dispatch/preset.yml +1 -1
package/lib/index.js CHANGED
@@ -9,7 +9,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
9
9
  import { z } from "zod";
10
10
  import z$1 from "@deepseek-ai/schemastery";
11
11
  import fs, { chmod, lstat, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
12
- import path, { basename, dirname, join, relative, resolve } from "node:path";
12
+ import path, { basename, dirname, extname, join, relative, resolve } from "node:path";
13
13
  import os, { homedir, platform, userInfo } from "node:os";
14
14
  import { constants, createCipheriv, createDecipheriv, createHash, createHmac, publicEncrypt, randomBytes, randomUUID } from "node:crypto";
15
15
  import { isDeepStrictEqual } from "node:util";
@@ -807,11 +807,20 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
807
807
  * `test/responses-client.test.ts` asserts the two agree, so a release that
808
808
  * forgets this constant fails the suite instead of shipping quietly.
809
809
  */
810
- const PLUGIN_VERSION = "0.58.0";
810
+ const PLUGIN_VERSION = "0.59.0";
811
811
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
812
812
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
813
813
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
814
814
  const CODEX_IMAGE_GENERATION_URL = `${CODEX_API_BASE}/images/generations`;
815
+ /**
816
+ * The edit route: the same image surface, addressed for reference-image input.
817
+ *
818
+ * A generation has nothing to modify and goes to `/images/generations`; a
819
+ * request that names reference images is an EDIT and goes here, with those
820
+ * images carried inline in a JSON `images` array. It is a separate route, not a
821
+ * flag on the generation body, so the two are stated as two constants.
822
+ */
823
+ const CODEX_IMAGE_EDIT_URL = `${CODEX_API_BASE}/images/edits`;
815
824
  const CODEX_SEARCH_URL = `${CODEX_API_BASE}/alpha/search`;
816
825
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
817
826
  /**
@@ -6099,6 +6108,33 @@ function normalizeGenerateOptions(options) {
6099
6108
  }
6100
6109
  //#endregion
6101
6110
  //#region src/host/codex-images.ts
6111
+ /**
6112
+ * Output canvases the Codex image endpoint accepts in `size`.
6113
+ *
6114
+ * These are the official client's own three canvases plus the default `auto`,
6115
+ * which lets the service pick. A size outside this set is not negotiated: the
6116
+ * backend answers it with a 400, so the tool refuses it before the request.
6117
+ */
6118
+ const CODEX_IMAGE_SIZES = [
6119
+ "auto",
6120
+ "1024x1024",
6121
+ "1536x1024",
6122
+ "1024x1536"
6123
+ ];
6124
+ /** Render effort the endpoint accepts in `quality`; `auto` is the service's own default. */
6125
+ const CODEX_IMAGE_QUALITIES = [
6126
+ "auto",
6127
+ "low",
6128
+ "medium",
6129
+ "high"
6130
+ ];
6131
+ /** Frame budget of an animated GIF the endpoint accepts. */
6132
+ const GIF_MIN_FRAMES = 2;
6133
+ const GIF_MAX_FRAMES = 16;
6134
+ const GIF_MIN_FRAME_MS = 20;
6135
+ const GIF_MAX_FRAME_MS = 2e3;
6136
+ /** How much of an upstream error body is quoted back to the caller. */
6137
+ const MAX_UPSTREAM_EXCERPT_CHARS = 300;
6102
6138
  const PNG_SIGNATURE = [
6103
6139
  137,
6104
6140
  80,
@@ -6109,16 +6145,78 @@ const PNG_SIGNATURE = [
6109
6145
  26,
6110
6146
  10
6111
6147
  ];
6148
+ /**
6149
+ * Magic-byte checks per media type.
6150
+ *
6151
+ * An extension is a claim about a file's name; the bytes are the claim the file
6152
+ * itself makes. Both are checked, because sending a file whose declared type the
6153
+ * service cannot decode returns a plain upstream error rather than an image.
6154
+ */
6155
+ const SIGNATURES$1 = {
6156
+ "image/png": (bytes) => bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71,
6157
+ "image/jpeg": (bytes) => bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255,
6158
+ "image/webp": (bytes) => bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80,
6159
+ "image/gif": (bytes) => bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70
6160
+ };
6161
+ /**
6162
+ * Extensions a reference image may carry.
6163
+ *
6164
+ * Deliberately narrower than {@link SIGNATURES}: an edit accepts stills only.
6165
+ * Animated GIF input is not part of the edit wire contract, so it is refused
6166
+ * here instead of being sent to an endpoint that does not take it.
6167
+ */
6168
+ const REFERENCE_EXTENSIONS = {
6169
+ ".png": "image/png",
6170
+ ".jpg": "image/jpeg",
6171
+ ".jpeg": "image/jpeg",
6172
+ ".webp": "image/webp"
6173
+ };
6112
6174
  function createCodexImageTool(oauth, attachments, options = {}) {
6113
6175
  const fetchFn = options.fetchFn ?? fetch;
6114
6176
  return defineTool({
6115
6177
  name: CODEX_IMAGE_TOOL_NAME,
6116
- description: "Generate a PNG image using the signed-in ChatGPT subscription-backed Codex image endpoint.",
6117
- parameters: { prompt: {
6118
- type: "string",
6119
- required: true,
6120
- description: "A detailed image generation prompt."
6121
- } },
6178
+ description: "Generate or edit a PNG or GIF image with the signed-in ChatGPT subscription-backed Codex image endpoint. Describe the subject, style, composition and any text to render in the prompt; pass referenceImagePaths to edit local PNG/JPEG/WebP images, and gif to animate the result. Scheduling: call this tool yourself for a single generation, an edit, or a retry - never create a subagent just to generate one image. Delegate only when the creative task needs several candidate images or is large enough to be a multi-step task of its own.",
6179
+ parameters: {
6180
+ prompt: {
6181
+ type: "string",
6182
+ required: true,
6183
+ description: "A detailed image generation prompt."
6184
+ },
6185
+ size: {
6186
+ type: "string",
6187
+ enum: CODEX_IMAGE_SIZES,
6188
+ description: "Output canvas. Omit or pass auto to let the service choose. 1024x1024 is square, 1536x1024 landscape and 1024x1536 portrait."
6189
+ },
6190
+ quality: {
6191
+ type: "string",
6192
+ enum: CODEX_IMAGE_QUALITIES,
6193
+ description: "Render effort. Omit or pass auto for the service default; low is fastest and high is most detailed."
6194
+ },
6195
+ transparentBackground: {
6196
+ type: "boolean",
6197
+ description: "true for a transparent background (PNG or GIF alpha), false for an explicitly opaque one. Omit it to leave the background decision to the service."
6198
+ },
6199
+ referenceImagePaths: {
6200
+ type: "array",
6201
+ items: { type: "string" },
6202
+ description: "Up to 5 local PNG, JPEG or WebP files to edit. Absolute paths recommended. The request becomes an edit against those images; nothing else on disk is read."
6203
+ },
6204
+ gif: {
6205
+ type: "object",
6206
+ additionalProperties: false,
6207
+ description: "Ask for an animated GIF. An empty object is allowed and lets the service choose the frames.",
6208
+ properties: {
6209
+ frameCount: {
6210
+ type: "integer",
6211
+ description: "Number of frames, 2 to 16."
6212
+ },
6213
+ frameDurationMs: {
6214
+ type: "integer",
6215
+ description: "Milliseconds each frame is shown, 20 to 2000."
6216
+ }
6217
+ }
6218
+ }
6219
+ },
6122
6220
  output: {
6123
6221
  schema: {
6124
6222
  type: "object",
@@ -6143,7 +6241,7 @@ function createCodexImageTool(oauth, attachments, options = {}) {
6143
6241
  },
6144
6242
  mediaType: {
6145
6243
  type: "string",
6146
- enum: ["image/png"],
6244
+ enum: ["image/png", "image/gif"],
6147
6245
  required: true
6148
6246
  },
6149
6247
  bytes: {
@@ -6190,34 +6288,52 @@ function createCodexImageTool(oauth, attachments, options = {}) {
6190
6288
  async execute(args, exec) {
6191
6289
  const prompt = args.prompt.trim();
6192
6290
  if (prompt === "") throw new HarnessError("Image prompt cannot be empty.", "CODEX_IMAGE_INVALID_PROMPT");
6193
- if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new HarnessError("PNG image attachments are not enabled in this DSH environment.", "CODEX_IMAGE_ATTACHMENT_UNSUPPORTED");
6291
+ const gif = readGifOptions(args.gif);
6292
+ const references = await readReferenceImages(args.referenceImagePaths ?? [], attachments);
6293
+ assertOutputTypeSupported(attachments, gif === void 0 ? "image/png" : "image/gif");
6294
+ const body = buildImageBody({
6295
+ prompt,
6296
+ size: args.size ?? "auto",
6297
+ quality: args.quality ?? "auto",
6298
+ ...args.transparentBackground !== void 0 ? { transparentBackground: args.transparentBackground } : {},
6299
+ ...gif !== void 0 ? { gif } : {},
6300
+ references
6301
+ });
6302
+ const url = references.length > 0 ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL;
6303
+ const turnId = String(exec.callId);
6194
6304
  let credentials = await imageCredentials(oauth);
6195
- let response = await requestImage$1(fetchFn, credentials, prompt, String(exec.callId), exec.signal);
6305
+ let response = await requestImage$1(fetchFn, credentials, url, body, turnId, exec.signal);
6196
6306
  if (response.status === 401) {
6197
6307
  await response.body?.cancel().catch(() => void 0);
6198
6308
  credentials = await imageCredentials(oauth, true);
6199
- response = await requestImage$1(fetchFn, credentials, prompt, String(exec.callId), exec.signal);
6309
+ response = await requestImage$1(fetchFn, credentials, url, body, turnId, exec.signal);
6200
6310
  }
6201
6311
  if (response.status === 429) {
6202
- await response.body?.cancel().catch(() => void 0);
6203
- throw new HarnessError("Codex image generation was rate limited.", "CODEX_IMAGE_RATE_LIMITED");
6312
+ const detail = upstreamExcerpt(await response.text().catch(() => ""));
6313
+ throw new HarnessError(`Codex image generation was rate limited (429).${detail === "" ? "" : ` ${detail}`}`, "CODEX_IMAGE_RATE_LIMITED");
6314
+ }
6315
+ if (response.status === 400) {
6316
+ const detail = upstreamExcerpt(await response.text().catch(() => ""));
6317
+ throw new HarnessError(`Codex rejected the image request (400). The parameters this call stated were: ${statedParameterSummary(args, gif, references.length)}. One of them is not accepted as given, and no field was dropped to retry.` + (detail === "" ? "" : ` Upstream said: ${detail}`), "CODEX_IMAGE_REQUEST_REJECTED");
6204
6318
  }
6205
6319
  if (!response.ok) {
6206
- await response.body?.cancel().catch(() => void 0);
6207
- throw new HarnessError(`Codex image generation failed (${response.status}).`, "CODEX_IMAGE_FAILED");
6320
+ const detail = upstreamExcerpt(await response.text().catch(() => ""));
6321
+ throw new HarnessError(`Codex image generation failed (${response.status}).${detail === "" ? "" : ` ${detail}`}`, "CODEX_IMAGE_FAILED");
6208
6322
  }
6209
- const bytes = decodeImageBytes(readBase64Image(await response.json()), maxGeneratedImageBytes(attachments));
6323
+ const decoded = decodeImageBytes(readBase64Image(await response.json()), maxGeneratedImageBytes(attachments));
6324
+ if (gif !== void 0 && decoded.mediaType !== "image/gif") throw new HarnessError(`Codex was asked for an animated GIF but returned ${decoded.mediaType}. Nothing was saved: the answer is not the animation that was requested. Omit the gif parameter to accept a still image.`, "CODEX_IMAGE_RESPONSE_INVALID");
6325
+ if (!attachments.imageLimits.mediaTypes.includes(decoded.mediaType)) throw new HarnessError(`Codex returned ${decoded.mediaType} but this DSH environment does not accept that attachment type.`, "CODEX_IMAGE_ATTACHMENT_UNSUPPORTED");
6210
6326
  const image = await attachments.saveImage({
6211
- data: bytes,
6212
- mediaType: "image/png",
6213
- name: "codex-generated-image.png"
6327
+ data: decoded.data,
6328
+ mediaType: decoded.mediaType,
6329
+ name: `codex-generated-image.${decoded.mediaType === "image/gif" ? "gif" : "png"}`
6214
6330
  });
6215
6331
  const output = {
6216
6332
  prompt,
6217
6333
  model: CODEX_IMAGE_MODEL,
6218
6334
  image: {
6219
6335
  attachmentId: image.attachmentId,
6220
- mediaType: "image/png",
6336
+ mediaType: decoded.mediaType,
6221
6337
  bytes: image.bytes,
6222
6338
  width: image.width,
6223
6339
  height: image.height,
@@ -6239,6 +6355,124 @@ function createCodexImageTool(oauth, attachments, options = {}) {
6239
6355
  }
6240
6356
  });
6241
6357
  }
6358
+ /**
6359
+ * Validate the frame options of a GIF request.
6360
+ *
6361
+ * An empty object is legal: it asks for an animation and leaves both numbers to
6362
+ * the service. A stated number is checked here rather than by the parameter
6363
+ * schema because the schema DSL carries no numeric bounds, and because
6364
+ * `execute` is reachable directly.
6365
+ */
6366
+ function readGifOptions(gif) {
6367
+ if (gif === void 0) return void 0;
6368
+ const result = {};
6369
+ const frameCount = gif.frameCount;
6370
+ if (frameCount !== void 0) {
6371
+ if (!Number.isInteger(frameCount) || frameCount < GIF_MIN_FRAMES || frameCount > GIF_MAX_FRAMES) throw new HarnessError(`gif.frameCount must be a whole number from ${GIF_MIN_FRAMES} to ${GIF_MAX_FRAMES} (got ${String(frameCount)}).`, "CODEX_IMAGE_GIF_INVALID");
6372
+ result.frameCount = frameCount;
6373
+ }
6374
+ const frameDurationMs = gif.frameDurationMs;
6375
+ if (frameDurationMs !== void 0) {
6376
+ if (!Number.isInteger(frameDurationMs) || frameDurationMs < GIF_MIN_FRAME_MS || frameDurationMs > GIF_MAX_FRAME_MS) throw new HarnessError(`gif.frameDurationMs must be a whole number of milliseconds from ${GIF_MIN_FRAME_MS} to ${GIF_MAX_FRAME_MS} (got ${String(frameDurationMs)}).`, "CODEX_IMAGE_GIF_INVALID");
6377
+ result.frameDurationMs = frameDurationMs;
6378
+ }
6379
+ return result;
6380
+ }
6381
+ /** Refuse an output type this deployment cannot store, before anything is requested. */
6382
+ function assertOutputTypeSupported(attachments, outputType) {
6383
+ if (attachments.imageLimits.mediaTypes.includes(outputType)) return;
6384
+ throw new HarnessError(outputType === "image/gif" ? "GIF output was requested, but this DSH environment does not accept GIF attachments, so nothing was requested. Omit the gif parameter to generate a PNG instead." : "PNG image attachments are not enabled in this DSH environment.", "CODEX_IMAGE_ATTACHMENT_UNSUPPORTED");
6385
+ }
6386
+ /**
6387
+ * Build the wire body of one request.
6388
+ *
6389
+ * Field order and the defaults are deliberate: a call with no new options
6390
+ * produces exactly `prompt/background auto/model/quality auto/size auto`, which
6391
+ * is the body this tool has always sent. Every new field appears only when the
6392
+ * caller stated it, so an old call site keeps its old bytes on the wire.
6393
+ */
6394
+ function buildImageBody(request) {
6395
+ const body = {
6396
+ prompt: request.prompt,
6397
+ background: request.transparentBackground === true ? "transparent" : request.transparentBackground === false ? "opaque" : "auto",
6398
+ model: CODEX_IMAGE_MODEL,
6399
+ quality: request.quality,
6400
+ size: request.size
6401
+ };
6402
+ if (request.gif !== void 0) {
6403
+ body.output_format = "gif";
6404
+ if (request.gif.frameCount !== void 0) body.frame_count = request.gif.frameCount;
6405
+ if (request.gif.frameDurationMs !== void 0) body.frame_duration_ms = request.gif.frameDurationMs;
6406
+ }
6407
+ if (request.references.length > 0) body.images = request.references.map((image) => ({ image_url: `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}` }));
6408
+ return body;
6409
+ }
6410
+ /**
6411
+ * The optional parameters one request actually stated, in words.
6412
+ *
6413
+ * A 400 says only that the request was invalid, which is useless on a tool with
6414
+ * seven parameters: the caller has to be told WHICH ones it sent, or it has no
6415
+ * way to know what to change. Only stated options are named - a default that the
6416
+ * tool has always sent cannot be the field to fix - and each GIF sub-field is
6417
+ * named separately, because `gif: {}` is a legal call that stated neither number.
6418
+ */
6419
+ function statedParameterSummary(args, gif, referenceCount) {
6420
+ const stated = [];
6421
+ if (args.size !== void 0) stated.push(`size=${args.size}`);
6422
+ if (args.quality !== void 0) stated.push(`quality=${args.quality}`);
6423
+ if (args.transparentBackground !== void 0) stated.push(`transparentBackground=${String(args.transparentBackground)}`);
6424
+ if (referenceCount > 0) stated.push(`referenceImagePaths (${referenceCount} image${referenceCount === 1 ? "" : "s"})`);
6425
+ if (gif !== void 0) {
6426
+ const frames = [];
6427
+ if (gif.frameCount !== void 0) frames.push(`frameCount=${String(gif.frameCount)}`);
6428
+ if (gif.frameDurationMs !== void 0) frames.push(`frameDurationMs=${String(gif.frameDurationMs)}`);
6429
+ stated.push(frames.length === 0 ? "gif (frame numbers left to the service)" : `gif (${frames.join(", ")})`);
6430
+ }
6431
+ return stated.length === 0 ? "no optional parameter was stated" : stated.join(", ");
6432
+ }
6433
+ /**
6434
+ * Read and validate the reference images one edit request names.
6435
+ *
6436
+ * Every named file is checked as a local still image: the path must not be a
6437
+ * URL, the extension must be PNG/JPEG/WebP, the file must be a regular non-empty
6438
+ * file inside the attachment byte limits, and its leading bytes must match the
6439
+ * declared type. Nothing else on disk is read, and the returned bytes are the
6440
+ * ONLY image data that reaches the request.
6441
+ */
6442
+ async function readReferenceImages(files, attachments) {
6443
+ if (files.length === 0) return [];
6444
+ const limit = Math.min(5, attachments.imageLimits.maxImagesPerMessage);
6445
+ if (files.length > limit) throw new HarnessError(`At most ${limit} reference images are accepted; ${files.length} were given.`, "CODEX_IMAGE_REFERENCE_LIMIT");
6446
+ const perImageBytes = attachments.imageLimits.maxImageBytes;
6447
+ const totalBytes = attachments.imageLimits.maxMessageImageBytes;
6448
+ const images = [];
6449
+ let sentBytes = 0;
6450
+ for (const [index, raw] of files.entries()) {
6451
+ const label = `referenceImagePaths[${index}]`;
6452
+ const value = raw.trim();
6453
+ if (value === "") throw new HarnessError(`${label} is empty; every reference image must name a local file.`, "CODEX_IMAGE_REFERENCE_INVALID_PATH");
6454
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value) || /^data:/i.test(value)) throw new HarnessError(`${label} is a URL (${value}). Reference images must be local PNG, JPEG or WebP files; nothing is fetched.`, "CODEX_IMAGE_REFERENCE_INVALID_PATH");
6455
+ const extension = extname(value).toLowerCase();
6456
+ const mediaType = REFERENCE_EXTENSIONS[extension];
6457
+ if (mediaType === void 0) throw new HarnessError(extension === ".gif" ? `${label} (${value}) is a GIF. Reference images must be still PNG, JPEG or WebP files; animated input is not part of the edit request.` : `${label} (${value}) is not a PNG, JPEG or WebP file (extension ${extension === "" ? "(none)" : `"${extension}"`}).`, "CODEX_IMAGE_REFERENCE_UNSUPPORTED");
6458
+ if (!attachments.imageLimits.mediaTypes.includes(mediaType)) throw new HarnessError(`${label} is ${mediaType}, which this DSH environment does not accept as an attachment.`, "CODEX_IMAGE_ATTACHMENT_UNSUPPORTED");
6459
+ const absolute = resolve(value);
6460
+ const stats = await stat(absolute).catch(() => void 0);
6461
+ if (stats === void 0) throw new HarnessError(`${label} (${value}) does not exist or cannot be read.`, "CODEX_IMAGE_REFERENCE_NOT_FOUND");
6462
+ if (!stats.isFile()) throw new HarnessError(`${label} (${value}) is not a regular file; pass a path to one PNG, JPEG or WebP file.`, "CODEX_IMAGE_REFERENCE_NOT_A_FILE");
6463
+ if (stats.size === 0) throw new HarnessError(`${label} (${value}) is empty.`, "CODEX_IMAGE_REFERENCE_EMPTY");
6464
+ if (stats.size > perImageBytes) throw new HarnessError(`${label} (${value}) is ${stats.size} bytes, above the ${perImageBytes} byte limit for one attachment.`, "CODEX_IMAGE_REFERENCE_TOO_LARGE");
6465
+ const data = new Uint8Array(await readFile(absolute));
6466
+ if (!SIGNATURES$1[mediaType](data)) throw new HarnessError(`${label} (${value}) does not start like a ${mediaType} file, so its bytes contradict its extension.`, "CODEX_IMAGE_REFERENCE_INVALID_CONTENT");
6467
+ sentBytes += data.byteLength;
6468
+ if (sentBytes > totalBytes) throw new HarnessError(`The reference images total ${sentBytes} bytes, above the ${totalBytes} byte limit for one message.`, "CODEX_IMAGE_REFERENCE_TOO_LARGE");
6469
+ images.push({
6470
+ mediaType,
6471
+ data
6472
+ });
6473
+ }
6474
+ return images;
6475
+ }
6242
6476
  async function imageCredentials(oauth, force = false) {
6243
6477
  try {
6244
6478
  return await oauth.credentials(force, { purpose: "tool" });
@@ -6246,8 +6480,8 @@ async function imageCredentials(oauth, force = false) {
6246
6480
  throw new HarnessError(`ChatGPT subscription credentials are required for Codex image generation.${error instanceof Error && error.message !== "" ? ` (${error.message})` : ""}`, "CODEX_IMAGE_CREDENTIAL_MISSING", { cause: error });
6247
6481
  }
6248
6482
  }
6249
- function requestImage$1(fetchFn, credentials, prompt, turnId, signal) {
6250
- return fetchFn(CODEX_IMAGE_GENERATION_URL, {
6483
+ function requestImage$1(fetchFn, credentials, url, body, turnId, signal) {
6484
+ return fetchFn(url, {
6251
6485
  method: "POST",
6252
6486
  headers: {
6253
6487
  ...codexHeaders(credentials),
@@ -6255,16 +6489,19 @@ function requestImage$1(fetchFn, credentials, prompt, turnId, signal) {
6255
6489
  "content-type": "application/json",
6256
6490
  "x-codex-image-turn-id": turnId
6257
6491
  },
6258
- body: JSON.stringify({
6259
- prompt,
6260
- background: "auto",
6261
- model: CODEX_IMAGE_MODEL,
6262
- quality: "auto",
6263
- size: "auto"
6264
- }),
6492
+ body: JSON.stringify(body),
6265
6493
  signal
6266
6494
  });
6267
6495
  }
6496
+ /**
6497
+ * The upstream error body, short enough to read in a message and stripped of
6498
+ * anything that must never be echoed: bearer credentials, base64 image payloads
6499
+ * and any long base64-shaped run.
6500
+ */
6501
+ function upstreamExcerpt(text) {
6502
+ const collapsed = text.replace(/data:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]*/gi, "data:<redacted>").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{6,}/gi, "Bearer <redacted>").replace(/[A-Za-z0-9+/]{200,}={0,2}/g, "<redacted>").replace(/\s+/g, " ").trim();
6503
+ return collapsed.length > MAX_UPSTREAM_EXCERPT_CHARS ? `${collapsed.slice(0, MAX_UPSTREAM_EXCERPT_CHARS)}…` : collapsed;
6504
+ }
6268
6505
  function readBase64Image(value) {
6269
6506
  const root = record$7(value);
6270
6507
  const data = Array.isArray(root?.data) ? root.data[0] : null;
@@ -6272,12 +6509,27 @@ function readBase64Image(value) {
6272
6509
  if (image === null) throw new HarnessError("Codex image response did not include image data.", "CODEX_IMAGE_RESPONSE_INVALID");
6273
6510
  return image;
6274
6511
  }
6512
+ /**
6513
+ * Decode one returned image and identify it by its own magic bytes.
6514
+ *
6515
+ * The endpoint answers a requested GIF with GIF bytes in the same `b64_json`
6516
+ * field it uses for PNG, and states nothing about which one it produced, so the
6517
+ * bytes are the only reliable source for the media type – and storing a GIF
6518
+ * under `image/png` would fail at save time or, worse, mislabel the file.
6519
+ */
6275
6520
  function decodeImageBytes(base64, maxBytes) {
6276
6521
  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(base64) || base64.length % 4 !== 0) throw new HarnessError("Codex image response was not valid base64.", "CODEX_IMAGE_RESPONSE_INVALID");
6277
6522
  const buffer = Buffer.from(base64, "base64");
6278
6523
  if (buffer.length > maxBytes) throw new HarnessError("Generated image exceeds the configured attachment size limit.", "CODEX_IMAGE_TOO_LARGE");
6279
- for (const [index, byte] of PNG_SIGNATURE.entries()) if (buffer[index] !== byte) throw new HarnessError("Codex image response was not a PNG image.", "CODEX_IMAGE_RESPONSE_INVALID");
6280
- return buffer;
6524
+ if (SIGNATURES$1["image/gif"](buffer)) return {
6525
+ mediaType: "image/gif",
6526
+ data: buffer
6527
+ };
6528
+ for (const [index, byte] of PNG_SIGNATURE.entries()) if (buffer[index] !== byte) throw new HarnessError("Codex image response was neither PNG nor GIF image data.", "CODEX_IMAGE_RESPONSE_INVALID");
6529
+ return {
6530
+ mediaType: "image/png",
6531
+ data: buffer
6532
+ };
6281
6533
  }
6282
6534
  function maxGeneratedImageBytes(attachments) {
6283
6535
  return Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
@@ -10451,6 +10703,9 @@ var AntigravityAdapter = class extends LlmAdapter {
10451
10703
  /**
10452
10704
  * Aspect ratios Gemini image models accept in `imageConfig.aspectRatio`.
10453
10705
  * Omitted, the model picks one itself, which is what the request then does.
10706
+ *
10707
+ * The last four are the extreme strips: a wider or taller frame than the square
10708
+ * family covers, useful for banners, headers and phone wallpapers.
10454
10709
  */
10455
10710
  const ANTIGRAVITY_IMAGE_ASPECT_RATIOS = [
10456
10711
  "1:1",
@@ -10462,7 +10717,21 @@ const ANTIGRAVITY_IMAGE_ASPECT_RATIOS = [
10462
10717
  "5:4",
10463
10718
  "9:16",
10464
10719
  "16:9",
10465
- "21:9"
10720
+ "21:9",
10721
+ "1:4",
10722
+ "4:1",
10723
+ "1:8",
10724
+ "8:1"
10725
+ ];
10726
+ /**
10727
+ * Image sizes Gemini image models accept in `imageConfig.imageSize`.
10728
+ * Omitted, the model picks one itself, which is what the request then does.
10729
+ */
10730
+ const ANTIGRAVITY_IMAGE_SIZES = [
10731
+ "0.5K",
10732
+ "1K",
10733
+ "2K",
10734
+ "4K"
10466
10735
  ];
10467
10736
  /** Images one call keeps; the model normally returns one. */
10468
10737
  const MAX_IMAGES_PER_CALL = 4;
@@ -10476,7 +10745,11 @@ const MAX_IMAGES_PER_CALL = 4;
10476
10745
  * Antigravity clients send for image runtimes; it has not been captured from
10477
10746
  * the official client.
10478
10747
  */
10479
- function buildImageRequest(prompt, projectId, aspectRatio) {
10748
+ function buildImageRequest(prompt, projectId, aspectRatio, imageSize) {
10749
+ const imageConfig = {
10750
+ ...aspectRatio ? { aspectRatio } : {},
10751
+ ...imageSize ? { imageSize } : {}
10752
+ };
10480
10753
  return {
10481
10754
  project: projectId,
10482
10755
  model: ANTIGRAVITY_IMAGE_MODEL,
@@ -10487,7 +10760,7 @@ function buildImageRequest(prompt, projectId, aspectRatio) {
10487
10760
  }],
10488
10761
  generationConfig: {
10489
10762
  responseModalities: ["TEXT", "IMAGE"],
10490
- ...aspectRatio ? { imageConfig: { aspectRatio } } : {}
10763
+ ...Object.keys(imageConfig).length > 0 ? { imageConfig } : {}
10491
10764
  }
10492
10765
  },
10493
10766
  requestType: "image_gen",
@@ -10599,7 +10872,7 @@ function createAntigravityImageTool(accountPool, store, attachments, options = {
10599
10872
  const fetchFn = options.fetchFn ?? fetch;
10600
10873
  return defineTool({
10601
10874
  name: ANTIGRAVITY_IMAGE_TOOL_NAME,
10602
- description: "Generate an image with Gemini image generation through the signed-in Antigravity account. Describe the subject, style, composition and any text to render in the prompt.",
10875
+ description: "Generate an image with Gemini image generation through the signed-in Antigravity account. Describe the subject, style, composition and any text to render in the prompt. Scheduling: a single generation, a change to an image already generated, and a retry of a failed attempt are direct calls from the agent that needs the image — do not create a subagent just to generate one image. Delegate only a task that genuinely needs several alternative directions or complex creative planning, and pass the prompt that comes back to this tool.",
10603
10876
  parameters: {
10604
10877
  prompt: {
10605
10878
  type: "string",
@@ -10609,7 +10882,12 @@ function createAntigravityImageTool(accountPool, store, attachments, options = {
10609
10882
  aspectRatio: {
10610
10883
  type: "string",
10611
10884
  enum: ANTIGRAVITY_IMAGE_ASPECT_RATIOS,
10612
- description: "Optional aspect ratio. Omit it to let the model choose."
10885
+ description: "Optional aspect ratio, one of the listed values. Omit it to let the model choose. If the upstream rejects the value, re-issue the call with another value from the list rather than the same one."
10886
+ },
10887
+ imageSize: {
10888
+ type: "string",
10889
+ enum: ANTIGRAVITY_IMAGE_SIZES,
10890
+ description: "Optional output size, one of 0.5K, 1K, 2K or 4K. Omit it to let the model choose. It is sent only when given, so it never overrides an aspect ratio the caller did not set."
10613
10891
  }
10614
10892
  },
10615
10893
  output: {
@@ -10693,7 +10971,7 @@ function createAntigravityImageTool(accountPool, store, attachments, options = {
10693
10971
  const prompt = args.prompt.trim();
10694
10972
  if (prompt === "") throw new HarnessError("Image prompt cannot be empty.", "ANTIGRAVITY_IMAGE_INVALID_PROMPT");
10695
10973
  if (options.isEnabled?.() === false) throw new HarnessError("The Antigravity line is switched off; enable it in Settings → Antigravity to generate images.", "ANTIGRAVITY_IMAGE_DISABLED");
10696
- const response = await requestImage(accountPool, store, fetchFn, prompt, args.aspectRatio, exec.signal);
10974
+ const response = await requestImage(accountPool, store, fetchFn, prompt, args.aspectRatio, args.imageSize, exec.signal);
10697
10975
  if (response.images.length === 0) throw new HarnessError(noImageMessage(response), "ANTIGRAVITY_IMAGE_EMPTY");
10698
10976
  const keep = Math.min(MAX_IMAGES_PER_CALL, attachments.imageLimits.maxImagesPerMessage, response.images.length);
10699
10977
  const saved = [];
@@ -10752,12 +11030,37 @@ async function acquireAccount(accountPool, store, tried, fetchFn) {
10752
11030
  }
10753
11031
  }
10754
11032
  }
10755
- async function requestImage(accountPool, store, fetchFn, prompt, aspectRatio, signal) {
11033
+ /**
11034
+ * Whether a status means the upstream refused the SHAPE of the request rather
11035
+ * than the account, the allowance or the transient state of the service.
11036
+ */
11037
+ const PARAMETER_REJECTION_STATUSES = /* @__PURE__ */ new Set([400, 422]);
11038
+ /**
11039
+ * What a failed image request reports: the status, a bounded excerpt of what
11040
+ * the upstream said, and — when the refusal was about the request — which
11041
+ * optional fields were sent.
11042
+ *
11043
+ * A parameter rejection is never retried without the field it names. Dropping
11044
+ * `aspectRatio` or `imageSize` would answer with an image that silently has a
11045
+ * shape nobody asked for, so the caller is told which fields to change and
11046
+ * makes that call itself.
11047
+ */
11048
+ function imageFailureMessage(status, errorText, sent) {
11049
+ const parts = [`Antigravity image generation failed (${status ?? "no response"}).`];
11050
+ if (status !== void 0 && PARAMETER_REJECTION_STATUSES.has(status)) {
11051
+ const fields = [sent.aspectRatio === void 0 ? void 0 : `aspectRatio "${sent.aspectRatio}"`, sent.imageSize === void 0 ? void 0 : `imageSize "${sent.imageSize}"`].filter((field) => field !== void 0);
11052
+ parts.push(fields.length > 0 ? `The request itself was refused, and these optional fields were what it carried: ${fields.join(", ")}. It is not retried without them: re-issue the call with a different candidate value, or omit the field, because the upstream refused this request shape.` : "The request itself was refused while no aspectRatio or imageSize was set, so a different value for either cannot fix it; change the prompt or check the account before retrying.");
11053
+ }
11054
+ const excerpt = upstreamBodyExcerpt(errorText);
11055
+ if (excerpt !== "") parts.push(`Upstream said: ${excerpt}`);
11056
+ return parts.join(" ");
11057
+ }
11058
+ async function requestImage(accountPool, store, fetchFn, prompt, aspectRatio, imageSize, signal) {
10756
11059
  const tried = /* @__PURE__ */ new Set();
10757
11060
  while (true) {
10758
11061
  const { account, token, projectId } = await acquireAccount(accountPool, store, tried, fetchFn);
10759
11062
  tried.add(account.id);
10760
- const body = JSON.stringify(buildImageRequest(prompt, projectId || "antigravity-default", aspectRatio));
11063
+ const body = JSON.stringify(buildImageRequest(prompt, projectId || "antigravity-default", aspectRatio, imageSize));
10761
11064
  let response;
10762
11065
  let firstResponse;
10763
11066
  for (const endpoint of endpointCandidates()) try {
@@ -10789,7 +11092,10 @@ async function requestImage(accountPool, store, fetchFn, prompt, aspectRatio, si
10789
11092
  }
10790
11093
  throw new HarnessError(`Antigravity image generation was rate limited (429) without a stated reset time, so it is handled as a transient rate limit rather than a spent quota. ${upstreamBodyExcerpt(errorText)}`.trim(), "ANTIGRAVITY_IMAGE_RATE_LIMITED");
10791
11094
  }
10792
- throw new HarnessError(`Antigravity image generation failed (${status ?? "no response"}).${errorText ? ` ${errorText}` : ""}`, "ANTIGRAVITY_IMAGE_FAILED");
11095
+ throw new HarnessError(imageFailureMessage(status, errorText, {
11096
+ aspectRatio,
11097
+ imageSize
11098
+ }), "ANTIGRAVITY_IMAGE_FAILED");
10793
11099
  }
10794
11100
  }
10795
11101
  //#endregion
@@ -27971,6 +28277,25 @@ const TOOL_SOURCE = /(?:^|[-_/])(?:read|view|cat|open|edit|write|patch|apply_pat
27971
28277
  /** 独立开关覆盖的检索工具;只有明确导航任务才允许进入新投影。 */
27972
28278
  const TOOL_RETRIEVAL = /^(?:read|web_fetch|grep|glob)$/;
27973
28279
  /**
28280
+ * PTC 家族的**程序传输**工具名(一个集合,两个名字)。
28281
+ *
28282
+ * 「模型写一段程序、工具调用发生在程序内部」是同一种传输,只是不同预设给主工具起
28283
+ * 了不同名字:PTC 预设叫 `run_code`,Code Mode 预设叫 `code_run`。凡是按工具名判断
28284
+ * 「这条结果来自程序传输」的地方都必须认这**整个集合**——名字只写一个,预设一换,
28285
+ * 大结果的 `ref://` 预览与精确补读就会静默失效。
28286
+ */
28287
+ const CACHE_WARMUP_PTC_TRANSPORT_TOOLS = /* @__PURE__ */ new Set(["run_code", "code_run"]);
28288
+ /**
28289
+ * 这个名字是否属于 PTC 家族的程序传输工具。
28290
+ *
28291
+ * 只做**精确**匹配:`code_runner` 之类的近似名不算,宁可少投影也不能误伤。
28292
+ * @param name - 工具名。
28293
+ * @returns 属于 {@link CACHE_WARMUP_PTC_TRANSPORT_TOOLS} 时为 true。
28294
+ */
28295
+ function isPtcTransportName(name) {
28296
+ return CACHE_WARMUP_PTC_TRANSPORT_TOOLS.has(name);
28297
+ }
28298
+ /**
27974
28299
  * 多节/多读输出进入新投影的最小长度门槛(字符)。
27975
28300
  *
27976
28301
  * 短于这个长度、又没有 spill 的普通源码与契约读一律逐字保留:
@@ -29266,7 +29591,7 @@ function planProjection(text, toolName, navigation, retrievalOnly, spilled = fal
29266
29591
  kind: "verbatim",
29267
29592
  strategy: "verbatim-contract"
29268
29593
  };
29269
- if (toolName === "run_code" && navigation === true) {
29594
+ if (isPtcTransportName(toolName) && navigation === true) {
29270
29595
  const outline = planRetrievalOutline(text, "read");
29271
29596
  if (outline !== void 0) return outline;
29272
29597
  if (retrievalOnly === true) return {
@@ -92527,6 +92852,20 @@ const PRESET_REGISTRY_PACKAGE = "@deepseek-ai/dsh-agent-preset";
92527
92852
  const WORKING_DIRECTORY_PACKAGE = "@deepseek-ai/dsh-working-directory";
92528
92853
  /** The standalone PTC executor introduced when tool presentation split into executor plugins. */
92529
92854
  const PTC_EXECUTOR_PACKAGE = "@deepseek-ai/dsh-tool-executor-ptc";
92855
+ /**
92856
+ * The native executor this package delegates Code Mode execution to.
92857
+ *
92858
+ * A program sub-call must run through the harness's own policy, approval, and
92859
+ * post-execute pipeline; re-implementing that pipeline is how a plugin silently
92860
+ * drops policy. A profile plugin cannot resolve the host's kernel packages, so
92861
+ * this package carries the executor as a real dependency and drives the host
92862
+ * registry with it.
92863
+ */
92864
+ const NATIVE_EXECUTOR_PACKAGE = "@deepseek-ai/dsh-tool-executor-native";
92865
+ /** This package's Code Mode row: the preset's own tools stay native, the rest is program-only. */
92866
+ const CODEMODE_PACKAGE = "@eddyskywalker/dsh-chatgpt-subscription/codemode";
92867
+ /** The deprecated presentation row generations without a native executor keep. */
92868
+ const AGENT_TOOL_PRESENTATION_PACKAGE = "@deepseek-ai/dsh-agent-tool-presentation";
92530
92869
  /** The id the `dispatch` preset is selectable under. */
92531
92870
  const DISPATCH_PRESET_ID = "dispatch";
92532
92871
  /**
@@ -92542,7 +92881,7 @@ const DISPATCH_PRESET_ID = "dispatch";
92542
92881
  const DISPATCH_PRESET = {
92543
92882
  id: DISPATCH_PRESET_ID,
92544
92883
  name: "调度模式",
92545
- description: "基于 PTC 模式的调度 Agent:先分诊任务复杂度,L2 任务在规划前先做澄清访谈,再由主代理规划、派发子代理(显式选择子代理模型)、审查与验收。",
92884
+ description: "基于 Code Mode 的调度 Agent:先分诊任务复杂度,L2 任务在规划前先做澄清访谈,再由主代理规划、派发子代理(显式选择子代理模型)、审查与验收。",
92546
92885
  order: 5,
92547
92886
  plugins: [
92548
92887
  {
@@ -92589,6 +92928,7 @@ L0/L1: execute directly (or 1 subagent for L1). L2: R-G, R9, and R6 are mandator
92589
92928
  - Lead-Core: tightly coupled cross-module/contract changes or sequential chains where splitting duplicates reads or causes hand_back loops. -> Implement directly in continuous steps (track in todo_write, no self-briefs) and run R6 verification.
92590
92929
  - Hybrid: unsettled shared contracts/seams plus orthogonal downstream work. -> Implement & freeze core seams yourself first, then delegate orthogonal adapters/tests/noisy exploration per R2/R5 (or subagent_fork for warm-context isolation).
92591
92930
  - Fan-out: >=3 orthogonal subtasks with disjoint files, settled interfaces, and clear parallel/routing gain. -> Coordinate (plan, R2/R5 dispatch, R6 review/acceptance, docs, non-delegable or disjoint parallel modules); never edit an active child's Boundary.
92931
+ - [Media generation] Generate/revise/retry images directly; delegate complex creative work, never a bare call.
92592
92932
 
92593
92933
  ## R2 · Model Routing (Check Availability First, Then Decide How to Dispatch)
92594
92934
  Check whether list_subagent_models exists (in PTC mode: tools.list_subagent_models). The tool row and provider/model/reasoning_effort parameters are mounted statically, so they are present even in a session that authorized no route at all. Read that fact instead of inferring it from the tool's output:
@@ -92833,9 +93173,8 @@ Fallback is a task-scoped, non-session-wide exception for delegated briefs: it c
92833
93173
  }
92834
93174
  },
92835
93175
  {
92836
- id: "tool-presentation",
92837
- name: "@deepseek-ai/dsh-agent-tool-presentation",
92838
- config: { mode: "ptc" }
93176
+ id: "code-mode",
93177
+ name: CODEMODE_PACKAGE
92839
93178
  },
92840
93179
  {
92841
93180
  id: "present",
@@ -92859,8 +93198,16 @@ Fallback is a task-scoped, non-session-wide exception for delegated briefs: it c
92859
93198
  * the export is absent; older harnesses keep it because their main entry does not
92860
93199
  * register the tool.
92861
93200
  *
92862
- * When the standalone PTC executor exists, it replaces the deprecated
92863
- * agent-tool-presentation compatibility row and its `{ mode: 'ptc' }` config.
93201
+ * Code Mode needs the native tool executor (it is the pipeline the program's
93202
+ * sub-calls are delegated to), so that package's presence decides the
93203
+ * presentation rows:
93204
+ *
93205
+ * - resolvable: the `code-mode` row mounts as-is and no PTC executor row exists
93206
+ * beside it (one executor per scope);
93207
+ * - absent: the row falls back to the deprecated agent-tool-presentation row and,
93208
+ * when the standalone PTC executor exists, to that executor instead — the
93209
+ * behaviour of every generation before Code Mode.
93210
+ *
92864
93211
  * @param resolves - whether a bare specifier resolves in this installation.
92865
93212
  * @returns the preset definition for the running harness generation.
92866
93213
  */
@@ -92868,7 +93215,8 @@ function resolveDispatchPreset(resolves = resolvesFromHere) {
92868
93215
  const hasWorkingDirectory = resolves(WORKING_DIRECTORY_PACKAGE);
92869
93216
  const hasLegacyListAgents = resolves("@deepseek-ai/dsh-tool-subagent-control/list-agents");
92870
93217
  const hasPtcExecutor = resolves(PTC_EXECUTOR_PACKAGE);
92871
- if (!hasWorkingDirectory && hasLegacyListAgents && !hasPtcExecutor) return DISPATCH_PRESET;
93218
+ const hasNativeExecutor = resolves(NATIVE_EXECUTOR_PACKAGE);
93219
+ if (!hasWorkingDirectory && hasLegacyListAgents && !hasPtcExecutor && hasNativeExecutor) return DISPATCH_PRESET;
92872
93220
  return {
92873
93221
  ...DISPATCH_PRESET,
92874
93222
  plugins: DISPATCH_PRESET.plugins.map((row) => {
@@ -92876,14 +93224,14 @@ function resolveDispatchPreset(resolves = resolvesFromHere) {
92876
93224
  ...row,
92877
93225
  config: row.config.filter((nested) => nested.id !== "tool-subagent-list-agents")
92878
93226
  };
92879
- if (row.id === "tool-presentation" && hasPtcExecutor) {
92880
- const { config: _omitted, ...rest } = row;
92881
- return {
92882
- ...rest,
92883
- id: "tool-executor-ptc",
92884
- name: PTC_EXECUTOR_PACKAGE
92885
- };
92886
- }
93227
+ if (row.id === "code-mode" && !hasNativeExecutor) return hasPtcExecutor ? {
93228
+ id: "tool-executor-ptc",
93229
+ name: PTC_EXECUTOR_PACKAGE
93230
+ } : {
93231
+ id: "tool-presentation",
93232
+ name: AGENT_TOOL_PRESENTATION_PACKAGE,
93233
+ config: { mode: "ptc" }
93234
+ };
92887
93235
  if (row.id !== "persona" || !hasWorkingDirectory || typeof row.config !== "object" || row.config === null) return row;
92888
93236
  const { suffix: _omitted, ...rest } = row.config;
92889
93237
  return {
@@ -94760,12 +95108,13 @@ const CACHE_WARMUP_PROVIDER = "kimi-code";
94760
95108
  /**
94761
95109
  * 允许进入结果投影的工具集合。
94762
95110
  *
94763
- * 除了 PTC 的 `run_code`,宿主的独立 Shell 与后台任务输出工具(`win_bash`、
94764
- * `bash`、`pwsh`、`job_output`)同样经常吐出整屏测试/构建日志。源码读取/编辑类
94765
- * 工具(`read`、`edit`、`grep`、`glob`、CodeGraph 等)不在此列,保持 100% 原样。
95111
+ * 除了 PTC 家族的程序传输(`run_code` 与 Code Mode 的 `code_run`),宿主的独立
95112
+ * Shell 与后台任务输出工具(`win_bash`、`bash`、`pwsh`、`job_output`)同样经常
95113
+ * 吐出整屏测试/构建日志。源码读取/编辑类工具(`read`、`edit`、`grep`、`glob`、
95114
+ * CodeGraph 等)不在此列,保持 100% 原样。
94766
95115
  */
94767
95116
  const CACHE_WARMUP_SOURCE_TOOLS = /* @__PURE__ */ new Set([
94768
- "run_code",
95117
+ ...CACHE_WARMUP_PTC_TRANSPORT_TOOLS,
94769
95118
  "win_bash",
94770
95119
  "bash",
94771
95120
  "pwsh",
@@ -94782,7 +95131,7 @@ const CACHE_WARMUP_SECTION_NAME = "plugin:cache-warmup";
94782
95131
  const CACHE_WARMUP_GUIDANCE = [
94783
95132
  "## Result previews",
94784
95133
  "",
94785
- "On large `run_code` results this line may replace the output with a preview: a short description plus one or more `ref://<session>/<sha256>` handles.",
95134
+ "On large `run_code`/`code_run` results this line may replace the output with a preview: a short description plus one or more `ref://<session>/<sha256>` handles.",
94786
95135
  "",
94787
95136
  "- A preview is **not** the full result. It states `complete`, `omitted` and `nextOffset` explicitly; treat an incomplete preview as incomplete data, not as the whole output.",
94788
95137
  "- To read the original, call `cache_warmup_result` with `action: \"page\"` and that `refId`, then follow `nextOffset` until `complete` is true.",
@@ -95001,7 +95350,8 @@ function installCacheWarmup(ctx, options) {
95001
95350
  *
95002
95351
  * 决策取舍写在代码里,因为它全是「什么时候**不动**」:
95003
95352
  * - 不是 `accept`(`block` 的 feedback 是别人的东西);
95004
- * - 工具名不是 `run_code`、线路不是本线、会话拿不到;
95353
+ * - 工具名不在源工具集合里(`run_code`/`code_run`/Shell/`job_output`)、线路不是
95354
+ * 本线、会话拿不到;
95005
95355
  * - 没有存储、内容为空。
95006
95356
  * 上面任何一条成立就原样返回同一个决策对象。
95007
95357
  *
@@ -95019,7 +95369,7 @@ function installCacheWarmup(ctx, options) {
95019
95369
  const retrievalTool = CACHE_WARMUP_RETRIEVAL_TOOLS.has(exec.name);
95020
95370
  if (!sourceTool && !retrievalTool) return decision;
95021
95371
  const settings = options.settings();
95022
- const ptcRetrieval = exec.name === "run_code" && settings.retrievalEnabled === true;
95372
+ const ptcRetrieval = isPtcTransportName(exec.name) && settings.retrievalEnabled === true;
95023
95373
  if (sourceTool && !settings.enabled && !ptcRetrieval) return decision;
95024
95374
  if (retrievalTool && settings.retrievalEnabled !== true) return decision;
95025
95375
  const navigation = retrievalTool || ptcRetrieval ? cacheWarmupNavigationIntentOf(exec.agent) : void 0;