@eddyskywalker/dsh-chatgpt-subscription 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -145,7 +145,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
145
145
  * `test/responses-client.test.ts` asserts the two agree, so a release that
146
146
  * forgets this constant fails the suite instead of shipping quietly.
147
147
  */
148
- const PLUGIN_VERSION = "0.49.0";
148
+ const PLUGIN_VERSION = "0.50.0";
149
149
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
150
150
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
151
151
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
@@ -5289,9 +5289,9 @@ function requestImage$1(fetchFn, credentials, prompt, turnId, signal) {
5289
5289
  });
5290
5290
  }
5291
5291
  function readBase64Image(value) {
5292
- const root = record$6(value);
5292
+ const root = record$7(value);
5293
5293
  const data = Array.isArray(root?.data) ? root.data[0] : null;
5294
- const image = string$3(record$6(data)?.b64_json) ?? string$3(record$6(data)?.image_base64) ?? string$3(root?.b64_json) ?? string$3(root?.image);
5294
+ const image = string$3(record$7(data)?.b64_json) ?? string$3(record$7(data)?.image_base64) ?? string$3(root?.b64_json) ?? string$3(root?.image);
5295
5295
  if (image === null) throw new HarnessError("Codex image response did not include image data.", "CODEX_IMAGE_RESPONSE_INVALID");
5296
5296
  return image;
5297
5297
  }
@@ -5305,7 +5305,7 @@ function decodeImageBytes(base64, maxBytes) {
5305
5305
  function maxGeneratedImageBytes(attachments) {
5306
5306
  return Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
5307
5307
  }
5308
- function record$6(value) {
5308
+ function record$7(value) {
5309
5309
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
5310
5310
  }
5311
5311
  function string$3(value) {
@@ -9043,7 +9043,7 @@ function buildImageRequest(prompt, projectId, aspectRatio) {
9043
9043
  requestId: `img_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
9044
9044
  };
9045
9045
  }
9046
- function record$5(value) {
9046
+ function record$6(value) {
9047
9047
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
9048
9048
  }
9049
9049
  function string$2(value) {
@@ -9073,16 +9073,16 @@ function parseImageResponse(body) {
9073
9073
  } catch {
9074
9074
  continue;
9075
9075
  }
9076
- const root = record$5(frame);
9077
- const response = record$5(root?.response) ?? root;
9078
- const error = record$5(root?.error) ?? record$5(response?.error);
9076
+ const root = record$6(frame);
9077
+ const response = record$6(root?.response) ?? root;
9078
+ const error = record$6(root?.error) ?? record$6(response?.error);
9079
9079
  if (error) throw new HarnessError(`Antigravity image generation failed: ${string$2(error.message) ?? "unknown error"}`, "ANTIGRAVITY_IMAGE_FAILED");
9080
- const candidate = record$5(Array.isArray(response?.candidates) ? response.candidates[0] : void 0);
9081
- const parts = Array.isArray(record$5(candidate?.content)?.parts) ? record$5(candidate?.content).parts : [];
9080
+ const candidate = record$6(Array.isArray(response?.candidates) ? response.candidates[0] : void 0);
9081
+ const parts = Array.isArray(record$6(candidate?.content)?.parts) ? record$6(candidate?.content).parts : [];
9082
9082
  for (const raw of parts) {
9083
- const part = record$5(raw);
9083
+ const part = record$6(raw);
9084
9084
  if (!part || part.thought === true) continue;
9085
- const inline = record$5(part.inlineData) ?? record$5(part.inline_data);
9085
+ const inline = record$6(part.inlineData) ?? record$6(part.inline_data);
9086
9086
  const data = string$2(inline?.data);
9087
9087
  if (inline && data) {
9088
9088
  result.images.push({
@@ -9093,7 +9093,7 @@ function parseImageResponse(body) {
9093
9093
  }
9094
9094
  if (typeof part.text === "string") result.text += part.text;
9095
9095
  }
9096
- const blockReason = string$2(record$5(response?.promptFeedback)?.blockReason);
9096
+ const blockReason = string$2(record$6(response?.promptFeedback)?.blockReason);
9097
9097
  result.finishReason = string$2(candidate?.finishReason) ?? (blockReason ? `PROMPT_BLOCKED:${blockReason}` : result.finishReason);
9098
9098
  result.finishMessage = string$2(candidate?.finishMessage) ?? result.finishMessage;
9099
9099
  }
@@ -9429,13 +9429,13 @@ function normalizeSearchResult(data, maxResults) {
9429
9429
  };
9430
9430
  }
9431
9431
  function readSources(data) {
9432
- const root = record$4(data);
9432
+ const root = record$5(data);
9433
9433
  if (root === null) return [];
9434
9434
  const candidates = [
9435
9435
  root.sources,
9436
9436
  root.results,
9437
- record$4(root.search_result)?.sources,
9438
- record$4(root.web_search)?.sources
9437
+ record$5(root.search_result)?.sources,
9438
+ record$5(root.web_search)?.sources
9439
9439
  ];
9440
9440
  for (const candidate of candidates) {
9441
9441
  if (!Array.isArray(candidate)) continue;
@@ -9445,7 +9445,7 @@ function readSources(data) {
9445
9445
  return [];
9446
9446
  }
9447
9447
  function readSource(value) {
9448
- const data = record$4(value);
9448
+ const data = record$5(value);
9449
9449
  if (data === null) return null;
9450
9450
  const url = string$1(data.url) ?? string$1(data.link) ?? string$1(data.uri);
9451
9451
  if (url === null || !isHttpUrl(url)) return null;
@@ -9474,7 +9474,7 @@ function readSource(value) {
9474
9474
  * counterpart of `output`, not a fallback for it.
9475
9475
  */
9476
9476
  function readContent(data) {
9477
- const root = record$4(data);
9477
+ const root = record$5(data);
9478
9478
  if (root === null) return void 0;
9479
9479
  return string$1(root.content) ?? string$1(root.output_text) ?? string$1(root.summary) ?? string$1(root.output) ?? void 0;
9480
9480
  }
@@ -9489,7 +9489,7 @@ function dedupeSources(sources) {
9489
9489
  }
9490
9490
  return result;
9491
9491
  }
9492
- function record$4(value) {
9492
+ function record$5(value) {
9493
9493
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
9494
9494
  }
9495
9495
  function string$1(value) {
@@ -11292,7 +11292,7 @@ function offloadOldestInputImages(input, maxBytes = maxRequestImageBytes$2()) {
11292
11292
  for (const item of input) {
11293
11293
  if (!Array.isArray(item.content)) continue;
11294
11294
  for (const [index, block] of item.content.entries()) {
11295
- if (record$3(block) === null || block.type !== "input_image") continue;
11295
+ if (record$4(block) === null || block.type !== "input_image") continue;
11296
11296
  const url = block.image_url;
11297
11297
  if (typeof url !== "string") continue;
11298
11298
  retained.push({
@@ -11492,14 +11492,14 @@ function toolParametersForCodex(toolName, parameters, allowSandboxRetry) {
11492
11492
  const augmentCommandTool = isCommandTool(toolName);
11493
11493
  if (!hideSandboxControls && toolName !== "run_code" && !augmentCommandTool) return parameters;
11494
11494
  const cloned = structuredClone(parameters);
11495
- const properties = record$3(cloned.properties);
11495
+ const properties = record$4(cloned.properties);
11496
11496
  if (properties !== null && hideSandboxControls) {
11497
11497
  delete properties.sandbox_permissions;
11498
11498
  delete properties.justification;
11499
11499
  }
11500
11500
  if (hideSandboxControls && Array.isArray(cloned.required)) cloned.required = cloned.required.filter((name) => name !== "sandbox_permissions" && name !== "justification");
11501
11501
  if (toolName === "run_code" && properties !== null) {
11502
- const code = record$3(properties.code);
11502
+ const code = record$4(properties.code);
11503
11503
  if (code !== null) {
11504
11504
  const current = typeof code.description === "string" ? code.description.trim() : "";
11505
11505
  const compatibility = "Strict JavaScript/TypeScript source. Nested shell commands are string data: template literals may consume ${...}, backticks, backslashes, and escape sequences before PowerShell, Bash, or POSIX sh sees them; String.raw still performs ${...} interpolation. Prefer ordinary quoted string arrays joined with \"\\n\", or write a script file with a dedicated file tool.";
@@ -11517,7 +11517,7 @@ function toolParametersForCodex(toolName, parameters, allowSandboxRetry) {
11517
11517
  return cloned;
11518
11518
  }
11519
11519
  function appendPropertyDescription(value, addition) {
11520
- const property = record$3(value);
11520
+ const property = record$4(value);
11521
11521
  if (property === null) return;
11522
11522
  const current = typeof property.description === "string" ? property.description.trim() : "";
11523
11523
  if (current.includes(addition)) return;
@@ -11528,7 +11528,7 @@ function isCommandTool(name) {
11528
11528
  return normalized === "pwsh" || normalized === "powershell" || normalized === "bash" || normalized === "sh" || normalized === "shell";
11529
11529
  }
11530
11530
  function hasSandboxControls(parameters) {
11531
- const properties = record$3(parameters.properties);
11531
+ const properties = record$4(parameters.properties);
11532
11532
  return properties !== null && ("sandbox_permissions" in properties || "justification" in properties);
11533
11533
  }
11534
11534
  function recentSandboxRetryToolNames(messages) {
@@ -11740,11 +11740,11 @@ function replayOutputItems(message) {
11740
11740
  const replay = message.source?.replayState;
11741
11741
  if (typeof replay !== "object" || replay === null || Array.isArray(replay)) return null;
11742
11742
  const envelope = replay;
11743
- const items = Array.isArray(envelope.outputItems) ? envelope.outputItems : Array.isArray(record$3(envelope.response)?.outputItems) ? record$3(envelope.response).outputItems : null;
11743
+ const items = Array.isArray(envelope.outputItems) ? envelope.outputItems : Array.isArray(record$4(envelope.response)?.outputItems) ? record$4(envelope.response).outputItems : null;
11744
11744
  if (!Array.isArray(items)) return null;
11745
11745
  return structuredClone(items.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)));
11746
11746
  }
11747
- function record$3(value) {
11747
+ function record$4(value) {
11748
11748
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
11749
11749
  }
11750
11750
  //#endregion
@@ -12145,7 +12145,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
12145
12145
  return;
12146
12146
  }
12147
12147
  if (type === "response.content_part.done") {
12148
- const part = record$2(event.part);
12148
+ const part = record$3(event.part);
12149
12149
  const partType = string(part?.type);
12150
12150
  if (partType === "output_text" || partType === "refusal") yield* adoptText(string(part?.text) ?? "");
12151
12151
  return;
@@ -12177,7 +12177,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
12177
12177
  return;
12178
12178
  }
12179
12179
  if (type === "response.output_item.added" || type === "response.output_item.done") {
12180
- const item = record$2(event.item);
12180
+ const item = record$3(event.item);
12181
12181
  if (item !== null && type === "response.output_item.done") replayOutput.push(structuredClone(item));
12182
12182
  if (string(item?.type) !== "function_call") {
12183
12183
  if (type === "response.output_item.done") yield* adoptText(messageItemText(item));
@@ -12237,18 +12237,18 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
12237
12237
  return;
12238
12238
  }
12239
12239
  if (type === "response.completed" || type === "response.incomplete") {
12240
- const completed = record$2(event.response);
12241
- usage = mapUsage$2(record$2(completed?.usage));
12240
+ const completed = record$3(event.response);
12241
+ usage = mapUsage$2(record$3(completed?.usage));
12242
12242
  const output = completed?.output;
12243
12243
  if (Array.isArray(output)) {
12244
- replayOutput = output.filter((item) => record$2(item) !== null).map((item) => structuredClone(item));
12245
- for (const item of output) yield* adoptText(messageItemText(record$2(item)));
12244
+ replayOutput = output.filter((item) => record$3(item) !== null).map((item) => structuredClone(item));
12245
+ for (const item of output) yield* adoptText(messageItemText(record$3(item)));
12246
12246
  }
12247
12247
  terminal = type === "response.incomplete" ? { kind: "max-tokens" } : { kind: "stop" };
12248
12248
  return;
12249
12249
  }
12250
12250
  if (type === "response.failed" || type === "error") {
12251
- const error = record$2(event.error) ?? record$2(record$2(event.response)?.error);
12251
+ const error = record$3(event.error) ?? record$3(record$3(event.response)?.error);
12252
12252
  const message = string(error?.message) ?? "Codex generation failed.";
12253
12253
  const rawCode = string(error?.code)?.toLowerCase();
12254
12254
  const isOverload = message.toLowerCase().includes("overload") || message.toLowerCase().includes("server error") || rawCode === "server_error" || rawCode === "service_unavailable" || rawCode === "internal_error";
@@ -12274,7 +12274,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
12274
12274
  } catch {
12275
12275
  throw new LlmError("Codex returned malformed streaming JSON.", "PROTOCOL_ERROR");
12276
12276
  }
12277
- const valueRecord = record$2(event);
12277
+ const valueRecord = record$3(event);
12278
12278
  if (valueRecord !== null) yield* consume(valueRecord);
12279
12279
  }
12280
12280
  }
@@ -12380,10 +12380,10 @@ function mapUsage$2(value) {
12380
12380
  if (value === null) return null;
12381
12381
  const totalInput = number(value.input_tokens) ?? 0;
12382
12382
  const outputTokens = number(value.output_tokens) ?? 0;
12383
- const details = record$2(value.input_tokens_details);
12383
+ const details = record$3(value.input_tokens_details);
12384
12384
  const cached = number(details?.cached_tokens);
12385
12385
  const written = number(details?.cache_write_tokens);
12386
- const reasoning = number(record$2(value.output_tokens_details)?.reasoning_tokens);
12386
+ const reasoning = number(record$3(value.output_tokens_details)?.reasoning_tokens);
12387
12387
  return {
12388
12388
  inputTokens: Math.max(0, totalInput - (cached ?? 0) - (written ?? 0)),
12389
12389
  outputTokens,
@@ -12402,7 +12402,7 @@ function isSafeJsonArguments(value) {
12402
12402
  }
12403
12403
  function stripSandboxControls(value) {
12404
12404
  try {
12405
- const parsedRecord = record$2(JSON.parse(value));
12405
+ const parsedRecord = record$3(JSON.parse(value));
12406
12406
  if (parsedRecord === null) return value;
12407
12407
  let changed = false;
12408
12408
  for (const name of ["sandbox_permissions", "justification"]) if (name in parsedRecord) {
@@ -12439,14 +12439,14 @@ function messageItemText(item) {
12439
12439
  if (item === null || string(item.type) !== "message" || !Array.isArray(item.content)) return "";
12440
12440
  let text = "";
12441
12441
  for (const part of item.content) {
12442
- const value = record$2(part);
12442
+ const value = record$3(part);
12443
12443
  const kind = string(value?.type);
12444
12444
  if (kind !== "output_text" && kind !== "refusal") continue;
12445
12445
  text += string(value?.text) ?? "";
12446
12446
  }
12447
12447
  return text;
12448
12448
  }
12449
- function record$2(value) {
12449
+ function record$3(value) {
12450
12450
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
12451
12451
  }
12452
12452
  function string(value) {
@@ -13539,7 +13539,7 @@ function mapCodexUsage(value) {
13539
13539
  return parseCodexUsage(value).buckets;
13540
13540
  }
13541
13541
  function parseCodexUsage(value) {
13542
- const data = record$1(value);
13542
+ const data = record$2(value);
13543
13543
  if (data === null) return structuredClone(EMPTY_USAGE);
13544
13544
  const planType = typeof data.plan_type === "string" ? data.plan_type : null;
13545
13545
  const buckets = [];
@@ -13548,21 +13548,21 @@ function parseCodexUsage(value) {
13548
13548
  addBucket(buckets, usedIds, "code-review", "Code review", planType, data.code_review_rate_limit);
13549
13549
  const additional = Array.isArray(data.additional_rate_limits) ? data.additional_rate_limits : [];
13550
13550
  for (const [index, value] of additional.entries()) {
13551
- const limit = record$1(value);
13551
+ const limit = record$2(value);
13552
13552
  if (limit === null) continue;
13553
- const idSource = text$3(limit.limit_name) ?? text$3(limit.metered_feature) ?? `additional-${index + 1}`;
13554
- addBucket(buckets, usedIds, uniqueId(slug(idSource), usedIds), readableLimitName(text$3(limit.limit_name) ?? text$3(limit.metered_feature) ?? idSource), planType, limit.rate_limit);
13553
+ const idSource = text$4(limit.limit_name) ?? text$4(limit.metered_feature) ?? `additional-${index + 1}`;
13554
+ addBucket(buckets, usedIds, uniqueId(slug(idSource), usedIds), readableLimitName(text$4(limit.limit_name) ?? text$4(limit.metered_feature) ?? idSource), planType, limit.rate_limit);
13555
13555
  }
13556
13556
  return {
13557
13557
  buckets,
13558
13558
  credits: mapCredits(data.credits),
13559
- individualLimit: mapIndividualLimit(record$1(data.spend_control)?.individual_limit),
13560
- spendControlReached: boolean(record$1(data.spend_control)?.reached),
13559
+ individualLimit: mapIndividualLimit(record$2(data.spend_control)?.individual_limit),
13560
+ spendControlReached: boolean(record$2(data.spend_control)?.reached),
13561
13561
  resetCredits: mapResetCredits(data.rate_limit_reset_credits)
13562
13562
  };
13563
13563
  }
13564
13564
  function addBucket(result, usedIds, id, name, planType, value) {
13565
- const source = record$1(value);
13565
+ const source = record$2(value);
13566
13566
  if (source === null) return;
13567
13567
  const primary = mapWindow(source.primary_window);
13568
13568
  const secondary = mapWindow(source.secondary_window);
@@ -13578,7 +13578,7 @@ function addBucket(result, usedIds, id, name, planType, value) {
13578
13578
  usedIds.add(id);
13579
13579
  }
13580
13580
  function mapWindow(value) {
13581
- const data = record$1(value);
13581
+ const data = record$2(value);
13582
13582
  if (data === null) return null;
13583
13583
  const used = numeric(data.used_percent);
13584
13584
  if (used === void 0) return null;
@@ -13591,7 +13591,7 @@ function mapWindow(value) {
13591
13591
  };
13592
13592
  }
13593
13593
  function mapCredits(value) {
13594
- const data = record$1(value);
13594
+ const data = record$2(value);
13595
13595
  if (data === null) return null;
13596
13596
  const hasCredits = boolean(data.has_credits);
13597
13597
  const unlimited = boolean(data.unlimited);
@@ -13604,7 +13604,7 @@ function mapCredits(value) {
13604
13604
  };
13605
13605
  }
13606
13606
  function mapIndividualLimit(value) {
13607
- const data = record$1(value);
13607
+ const data = record$2(value);
13608
13608
  if (data === null) return null;
13609
13609
  const remaining = numeric(data.remaining_percent);
13610
13610
  const reset = numeric(data.reset_at);
@@ -13619,7 +13619,7 @@ function mapIndividualLimit(value) {
13619
13619
  };
13620
13620
  }
13621
13621
  function mapResetCredits(value) {
13622
- const data = record$1(value);
13622
+ const data = record$2(value);
13623
13623
  if (data === null) return null;
13624
13624
  const available = numeric(data.available_count);
13625
13625
  if (available === void 0) return null;
@@ -13629,14 +13629,14 @@ function mapResetCredits(value) {
13629
13629
  };
13630
13630
  }
13631
13631
  function parseResetCredits(value) {
13632
- const data = record$1(value);
13632
+ const data = record$2(value);
13633
13633
  if (data === null) return {
13634
13634
  availableCount: 0,
13635
13635
  expiresAt: null,
13636
13636
  availableCreditIds: []
13637
13637
  };
13638
- const available = (Array.isArray(data.credits) ? data.credits : []).map(record$1).filter((credit) => credit !== null && credit.status === "available").map((credit) => ({
13639
- id: text$3(credit.id),
13638
+ const available = (Array.isArray(data.credits) ? data.credits : []).map(record$2).filter((credit) => credit !== null && credit.status === "available").map((credit) => ({
13639
+ id: text$4(credit.id),
13640
13640
  expiresAt: timestamp(credit.expires_at)
13641
13641
  })).filter((credit) => credit.id !== null).sort((left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER));
13642
13642
  const reported = numeric(data.available_count);
@@ -13653,7 +13653,7 @@ function timestamp(value) {
13653
13653
  const parsed = Date.parse(value);
13654
13654
  return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : null;
13655
13655
  }
13656
- function record$1(value) {
13656
+ function record$2(value) {
13657
13657
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
13658
13658
  }
13659
13659
  function boolean(value) {
@@ -13670,7 +13670,7 @@ function decimalText(value) {
13670
13670
  const trimmed = value.trim();
13671
13671
  return trimmed !== "" ? trimmed : null;
13672
13672
  }
13673
- function text$3(value) {
13673
+ function text$4(value) {
13674
13674
  return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
13675
13675
  }
13676
13676
  function slug(value) {
@@ -25877,11 +25877,73 @@ const TOOL_SUBAGENT = /(?:^|[-_/])(?:subagent|agent|task|delegate|ralph|teammate
25877
25877
  const TOOL_SOURCE = /(?:^|[-_/])(?:read|view|cat|open|edit|write|patch|apply_patch|notebook|document|diff)(?:[-_/]|$)/i;
25878
25878
  /** 独立开关覆盖的检索工具;只有明确导航任务才允许进入新投影。 */
25879
25879
  const TOOL_RETRIEVAL = /^(?:read|web_fetch|grep|glob)$/;
25880
+ /**
25881
+ * 多节/多读输出进入新投影的最小长度门槛(字符)。
25882
+ *
25883
+ * 短于这个长度、又没有 spill 的普通源码与契约读一律逐字保留:
25884
+ * 只有「确定已经大到装不进预算」的输出才值得换成摘要。
25885
+ */
25886
+ const MULTI_OUTPUT_MIN_CHARS = 2e4;
25880
25887
  /** 一行文本的裁剪(超长行才裁剪,并留下省略号)。 */
25881
25888
  function clip(text, max) {
25882
25889
  return text.length <= max ? text : `${text.slice(0, max)}…`;
25883
25890
  }
25884
25891
  /**
25892
+ * DSH 的输出上限提示:正文被截断,全文落在临时文件里。
25893
+ *
25894
+ * 省略说明可能是精确或未知字节数,还可能跟着一段图片计数;这些形态都由 DSH 的 formatSpillNotice 产生。
25895
+ * 路径本身可能带 `.txt` 之类的后缀,所以路径用**非贪婪**捕获,末尾的句点单独留给正则。
25896
+ */
25897
+ const SPILL_NOTICE = /\n\n\((?:Omitted \d+ bytes\.|More bytes were omitted\.)(?: Omitted \d+ images\.)? Full formatted result stored at: ([^\r\n]+?)(?:\. Use read with offset\/limit, or grep this path to search within it)?\.\)\s*$/;
25898
+ /**
25899
+ * 读回一个 spill 文件的完整 UTF-8 内容。
25900
+ *
25901
+ * 文件系统依赖在这里动态引入:`contracts.ts` 必须保持零 Node 依赖,而投影是宿主侧代码。
25902
+ * 读不到、不是普通文件、或超过单结果字节上限时一律返回 undefined —— 宁可少投影,
25903
+ * 也不能把一份读了一半的原文当成全文入库。
25904
+ * @param filePath - spill 提示里给出的路径。
25905
+ * @returns 完整内容,或 undefined。
25906
+ */
25907
+ async function readSpillFile(filePath) {
25908
+ try {
25909
+ const { stat, readFile } = await import("node:fs/promises");
25910
+ const stats = await stat(filePath);
25911
+ if (!stats.isFile()) return void 0;
25912
+ if (stats.size > CACHE_WARMUP_DEFAULTS.maxResultBytes) return void 0;
25913
+ return await readFile(filePath, "utf8");
25914
+ } catch {
25915
+ return;
25916
+ }
25917
+ }
25918
+ /**
25919
+ * 处理 DSH 的 spill 提示。
25920
+ *
25921
+ * 读得到全文:投影与入库都用全文,`cache_warmup_result` 才能翻到**未截断**的输出。
25922
+ * 读不到:剥掉提示尾部,剩下的正文照常参与投影与入库。
25923
+ * @param raw - 工具结果原文。
25924
+ * @returns 解析结果。
25925
+ */
25926
+ async function resolveSpillText(raw) {
25927
+ const match = SPILL_NOTICE.exec(raw);
25928
+ if (match === null) return {
25929
+ text: raw,
25930
+ spilled: false,
25931
+ recovered: false
25932
+ };
25933
+ const filePath = match[1].trim();
25934
+ const full = filePath === "" ? void 0 : await readSpillFile(filePath);
25935
+ if (full !== void 0) return {
25936
+ text: full,
25937
+ spilled: true,
25938
+ recovered: true
25939
+ };
25940
+ return {
25941
+ text: raw.slice(0, match.index),
25942
+ spilled: true,
25943
+ recovered: false
25944
+ };
25945
+ }
25946
+ /**
25885
25947
  * 剥离终端 ANSI/CSI 转义序列(如 `\x1b[31mFAIL\x1b[0m`)与回车符 `\r`。
25886
25948
  *
25887
25949
  * JSON 解码后,原先源码/日志里的 `\u001b[31m` 会还原成控制字符 `\x1b`;
@@ -25959,6 +26021,10 @@ function buildTestSections(evidence) {
25959
26021
  const SHELL_ERROR_LINE = /(?:error|fatal|panic|traceback|exception|EACCES|EPERM|ENOENT|EEXIST|ELIFECYCLE|ERR!|FAILED|permission denied|cannot |could not |not found)/i;
25960
26022
  /** TypeScript 编译器报错首行:`path(line,col): error TSxxxx:` 或 `path:line:col - error TSxxxx:`。 */
25961
26023
  const TS_DIAGNOSTIC_HEADER = /^(?:.+?\(\d+,\d+\):\s+error\s+TS\d+:|.+?:\d+:\d+\s+-\s+error\s+TS\d+:)/;
26024
+ /** shell 预览里最多列出的错误行数;超出的部分用 ref 提示补读。 */
26025
+ const SHELL_ERROR_MAX_LINES = 25;
26026
+ /** shell 预览里错误集最多占用的字符数(行数上限先到,这个只是兜底)。 */
26027
+ const SHELL_ERROR_MAX_CHARS = 6500;
25962
26028
  /**
25963
26029
  * 紧跟在 TypeScript 诊断首行后面的续行(缩进的类型比对、代码高亮行、波浪线指示行),
25964
26030
  * 最多连带收集 6 行,使模型无需补读就能看全 `Type A is not assignable to Type B` 的完整原因。
@@ -26085,15 +26151,27 @@ function buildShellSections(text) {
26085
26151
  }
26086
26152
  if (SHELL_ERROR_LINE.test(trimmed)) {
26087
26153
  errorCount += 1;
26088
- errors.push(` ${clip(trimmed, 360)}`);
26154
+ errors.push(` ${clip(trimmed, 240)}`);
26089
26155
  }
26090
26156
  }
26091
26157
  if (errorCount > 300 || errors.length > 600) return void 0;
26092
26158
  const exitMatch = /(?:exit code|exited with code|process exited with code)[:=\s]+(-?\d+)/i.exec(text) ?? /\[exit code: (-?\d+)\]/i.exec(text);
26093
26159
  const sections = [];
26094
26160
  if (exitMatch !== null) sections.push({ lines: [`exit code: ${exitMatch[1]}`] });
26095
- sections.push({ lines: [`errors: ${errorCount} (complete set)`] });
26096
- if (errors.length > 0) sections.push({ lines: errors });
26161
+ const shownErrors = [];
26162
+ let shownChars = 0;
26163
+ for (const line of errors) {
26164
+ if (shownErrors.length >= SHELL_ERROR_MAX_LINES) break;
26165
+ if (shownChars + line.length + 1 > SHELL_ERROR_MAX_CHARS) break;
26166
+ shownErrors.push(line);
26167
+ shownChars += line.length + 1;
26168
+ }
26169
+ const truncated = shownErrors.length < errors.length;
26170
+ sections.push({ lines: [truncated ? `errors: ${errorCount} (first ${shownErrors.length} shown; full set via ref)` : `errors: ${errorCount} (complete set)`] });
26171
+ if (shownErrors.length > 0) {
26172
+ const body = truncated ? [...shownErrors, ` … ${errors.length - shownErrors.length} more error lines (full list via ref)`] : shownErrors;
26173
+ sections.push({ lines: body });
26174
+ }
26097
26175
  const nonEmpty = lines.map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
26098
26176
  const head = nonEmpty.slice(0, 5).map((line) => ` ${clip(line.trim(), 160)}`);
26099
26177
  const tail = nonEmpty.slice(-5).map((line) => ` ${clip(line.trim(), 160)}`);
@@ -26309,6 +26387,42 @@ function planRetrievalOutline(text, toolName) {
26309
26387
  };
26310
26388
  }
26311
26389
  }
26390
+ /**
26391
+ * 读出装着日志字段的那个对象的元数据。
26392
+ * @param holder - 装着日志字段的对象(顶层信封或 1 层嵌套的内层对象)。
26393
+ * @returns 读到的元数据;两者都没有时 undefined。
26394
+ */
26395
+ function shellMetadataOf(holder) {
26396
+ const job = isPlainRecord(holder.job) ? holder.job : void 0;
26397
+ let exitCode;
26398
+ if (typeof holder.exitCode === "number" && Number.isFinite(holder.exitCode)) exitCode = holder.exitCode;
26399
+ if (exitCode === void 0 && typeof job?.exitCode === "number" && Number.isFinite(job.exitCode)) exitCode = job.exitCode;
26400
+ if (exitCode === void 0 && typeof job?.detail === "string") {
26401
+ const matched = /(?:exit code|exited with code|process exited with code)\s*[:=]?\s*(-?\d+)/i.exec(job.detail);
26402
+ if (matched !== null) exitCode = Number(matched[1]);
26403
+ }
26404
+ const status = typeof job?.status === "string" ? job.status : void 0;
26405
+ if (exitCode === void 0 && status === void 0) return void 0;
26406
+ return {
26407
+ ...exitCode === void 0 ? {} : { exitCode },
26408
+ ...status === void 0 ? {} : { status }
26409
+ };
26410
+ }
26411
+ /**
26412
+ * 把信封里的退出码与状态补到 shell 预览的最前面。
26413
+ * @param sections - `buildShellSections` 交出来的段落。
26414
+ * @param meta - 信封元数据。
26415
+ * @returns 补过元数据的段落。
26416
+ */
26417
+ function withShellMetadata(sections, meta) {
26418
+ if (meta === void 0) return sections;
26419
+ const lines = [];
26420
+ const hasExit = sections.some((section) => section.lines[0]?.startsWith("exit code:"));
26421
+ if (meta.exitCode !== void 0 && !hasExit) lines.push(`exit code: ${meta.exitCode}`);
26422
+ if (meta.status !== void 0) lines.push(`status: ${meta.status}`);
26423
+ if (lines.length === 0) return sections;
26424
+ return [{ lines }, ...sections];
26425
+ }
26312
26426
  /** 契约/类型定义:命中两行以上声明就当它是契约,保持原样。 */
26313
26427
  function looksLikeContract(text) {
26314
26428
  let declarations = 0;
@@ -26368,16 +26482,53 @@ function isJsonDocument(text) {
26368
26482
  }
26369
26483
  return false;
26370
26484
  }
26371
- /** PTC `run_code` JSON envelope 中允许独立投影日志的字段白名单。 */
26485
+ /**
26486
+ * PTC `run_code` JSON envelope 中允许独立投影日志的字段白名单。
26487
+ *
26488
+ * `'text'` 在列:`tools.job_output` 返回的正是 `{ text, job }`,日志正文就叫 `text`。
26489
+ */
26372
26490
  const JSON_ENVELOPE_LOG_KEYS = [
26373
26491
  "stdout",
26374
26492
  "stderr",
26375
26493
  "output",
26376
26494
  "logs",
26377
- "log"
26495
+ "log",
26496
+ "text"
26378
26497
  ];
26379
26498
  /** JSON envelope 其余字段允许占用的最大序列化字符数(防止把含批量源码的混合 envelope 压掉)。 */
26380
26499
  const JSON_ENVELOPE_MAX_OTHER_CHARS = 800;
26500
+ /** 判断一个值是不是普通 JSON 对象(不是数组,也不是 null)。 */
26501
+ function isPlainRecord(value) {
26502
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26503
+ }
26504
+ /** 重建信封:只把预览写回被替换的键,其余字段原样保留。 */
26505
+ function rebuildEnvelope(binding, innerPreview) {
26506
+ if (binding.nested === void 0) return JSON.stringify({
26507
+ ...binding.record,
26508
+ [binding.key]: innerPreview
26509
+ });
26510
+ return JSON.stringify({
26511
+ ...binding.record,
26512
+ [binding.nested.key]: {
26513
+ ...binding.nested.record,
26514
+ [binding.key]: innerPreview
26515
+ }
26516
+ });
26517
+ }
26518
+ /** 信封外壳的序列化长度:被替换的字段先按空串算。 */
26519
+ function envelopeShellLength(binding) {
26520
+ if (binding.nested === void 0) return JSON.stringify({
26521
+ ...binding.record,
26522
+ [binding.key]: ""
26523
+ }).length;
26524
+ return JSON.stringify({
26525
+ ...binding.record,
26526
+ [binding.nested.key]: {
26527
+ ...binding.nested.record,
26528
+ [binding.key]: ""
26529
+ }
26530
+ }).length;
26531
+ }
26381
26532
  /**
26382
26533
  * 解开一层或两层 JSON 包装(支持顶层 JSON 字符串 `"..."` 或双重序列化的 `"{...}"`)。
26383
26534
  */
@@ -26421,10 +26572,10 @@ function parseJsonCandidate(text) {
26421
26572
  *
26422
26573
  * 规则刻意保守:
26423
26574
  * - 截断 JSON、数组一律返回 undefined;
26575
+ * - 白名单字段可以出现在**顶层**(`{ stdout: "..." }`),也可以出现在**1 层嵌套**里
26576
+ * (`{ testOutput: { text: "...", job: {...} } }`,即 `tools.job_output` 的形状);
26424
26577
  * - 对象 envelope 除白名单字段外,其余字段序列化后的总长度不超过 `JSON_ENVELOPE_MAX_OTHER_CHARS`;
26425
- * - 解码并剥离 ANSI 后的日志文本自身必须能通过非源码/非契约的结构化日志投影计划;
26426
- * - 返回的 `storeContent` 把解码后的多行日志直接展开(对象 envelope 还会把其余元数据缩进在头部),
26427
- * 彻底避免单行 minified JSON 因没有真实 `\n` 导致 `store.page` 的按行分页失效。
26578
+ * - 解码并剥离 ANSI 后的日志文本自身必须能通过非源码/非契约的结构化日志投影计划。
26428
26579
  */
26429
26580
  function extractJsonEnvelopeLogField(text, toolName) {
26430
26581
  const candidate = parseJsonCandidate(text);
@@ -26437,33 +26588,498 @@ function extractJsonEnvelopeLogField(text, toolName) {
26437
26588
  return { plan };
26438
26589
  }
26439
26590
  const { record } = candidate;
26440
- let candidateKey;
26441
- let candidateText = "";
26591
+ const candidates = [];
26442
26592
  for (const key of JSON_ENVELOPE_LOG_KEYS) {
26443
- const val = record[key];
26444
- if (typeof val === "string" && val.length > candidateText.length) {
26445
- candidateKey = key;
26446
- candidateText = val;
26593
+ const value = record[key];
26594
+ if (typeof value === "string" && value.length > 0) candidates.push({
26595
+ binding: {
26596
+ record,
26597
+ key
26598
+ },
26599
+ text: value
26600
+ });
26601
+ }
26602
+ for (const [outerKey, outerValue] of Object.entries(record)) {
26603
+ if (!isPlainRecord(outerValue)) continue;
26604
+ for (const key of JSON_ENVELOPE_LOG_KEYS) {
26605
+ const value = outerValue[key];
26606
+ if (typeof value === "string" && value.length > 0) candidates.push({
26607
+ binding: {
26608
+ record,
26609
+ key,
26610
+ nested: {
26611
+ key: outerKey,
26612
+ record: outerValue
26613
+ }
26614
+ },
26615
+ text: value
26616
+ });
26447
26617
  }
26448
26618
  }
26449
- if (candidateKey === void 0 || candidateText.length === 0) return void 0;
26619
+ if (candidates.length === 0) return void 0;
26620
+ candidates.sort((left, right) => right.text.length - left.text.length);
26621
+ const picked = candidates[0];
26622
+ const binding = picked.binding;
26623
+ const candidateText = picked.text;
26624
+ const boundKey = binding.key;
26625
+ const nested = binding.nested;
26626
+ const holder = nested === void 0 ? record : nested.record;
26450
26627
  const rest = {};
26451
26628
  for (const [key, value] of Object.entries(record)) {
26452
- if (key === candidateKey) continue;
26629
+ if (nested === void 0 ? key === boundKey : key === nested.key) continue;
26453
26630
  if (typeof value === "string" && (value.length > 400 || looksLikeSourceCode(value) || looksLikeContract(value))) return;
26454
26631
  rest[key] = value;
26455
26632
  }
26456
- if (JSON.stringify(rest).length > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
26633
+ let restJson;
26634
+ if (nested === void 0) restJson = JSON.stringify(rest);
26635
+ else {
26636
+ const innerRest = {};
26637
+ for (const [key, value] of Object.entries(nested.record)) {
26638
+ if (key === boundKey) continue;
26639
+ if (typeof value === "string" && (value.length > 400 || looksLikeSourceCode(value) || looksLikeContract(value))) return;
26640
+ innerRest[key] = value;
26641
+ }
26642
+ restJson = JSON.stringify({
26643
+ ...rest,
26644
+ [nested.key]: innerRest
26645
+ });
26646
+ }
26647
+ if (restJson.length > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
26457
26648
  const cleanField = stripAnsi(candidateText);
26458
26649
  if (isJsonDocument(cleanField) || isMixedEvidenceOutput(cleanField)) return void 0;
26459
26650
  const plan = planPlainTextProjection(cleanField, toolName);
26460
- if (plan.kind !== "preview") return void 0;
26651
+ if (plan.kind !== "preview") {
26652
+ if (plan.strategy === "verbatim-unparsed") {
26653
+ const meta = shellMetadataOf(holder);
26654
+ if (meta !== void 0) {
26655
+ const sections = buildShellSections(cleanField);
26656
+ if (sections !== void 0) return {
26657
+ envelope: binding,
26658
+ plan: {
26659
+ kind: "preview",
26660
+ strategy: "shell-output",
26661
+ sections: withShellMetadata(sections, meta)
26662
+ }
26663
+ };
26664
+ }
26665
+ }
26666
+ const multiSection = planMultiSectionProjection(cleanField);
26667
+ if (multiSection !== void 0) return {
26668
+ envelope: binding,
26669
+ plan: multiSection
26670
+ };
26671
+ return;
26672
+ }
26673
+ return {
26674
+ envelope: binding,
26675
+ plan
26676
+ };
26677
+ }
26678
+ /** 规范 glob 路径表。 */
26679
+ function canonicalGlobPaths(value) {
26680
+ if (!Array.isArray(value) || value.length === 0) return void 0;
26681
+ const paths = [];
26682
+ for (const entry of value) {
26683
+ if (typeof entry !== "string") return void 0;
26684
+ paths.push(entry);
26685
+ }
26686
+ return paths;
26687
+ }
26688
+ /** 规范 grep 命中表。 */
26689
+ function canonicalGrepMatches(value) {
26690
+ if (!Array.isArray(value) || value.length === 0) return void 0;
26691
+ const matches = [];
26692
+ for (const entry of value) {
26693
+ if (!isPlainRecord(entry)) return void 0;
26694
+ if (typeof entry.path !== "string") return void 0;
26695
+ if (!Number.isSafeInteger(entry.lineNumber)) return void 0;
26696
+ if (typeof entry.line !== "string") return void 0;
26697
+ matches.push({
26698
+ path: entry.path,
26699
+ lineNumber: entry.lineNumber,
26700
+ line: entry.line
26701
+ });
26702
+ }
26703
+ return matches;
26704
+ }
26705
+ /** 规范读取结果。 */
26706
+ function canonicalReadResult(value) {
26707
+ if (!isPlainRecord(value)) return void 0;
26708
+ if (typeof value.path !== "string" || value.path.trim() === "") return void 0;
26709
+ if (!Array.isArray(value.lines) || value.lines.length === 0) return void 0;
26710
+ const lines = [];
26711
+ for (const entry of value.lines) {
26712
+ if (!isPlainRecord(entry)) return void 0;
26713
+ if (!Number.isSafeInteger(entry.number)) return void 0;
26714
+ if (typeof entry.text !== "string") return void 0;
26715
+ lines.push({
26716
+ number: entry.number,
26717
+ text: entry.text
26718
+ });
26719
+ }
26461
26720
  return {
26462
- envelope: {
26721
+ path: value.path,
26722
+ ...typeof value.offset === "number" && Number.isFinite(value.offset) ? { offset: value.offset } : {},
26723
+ lines
26724
+ };
26725
+ }
26726
+ /** 在顶层或 1 层嵌套里找规范 grep 命中表。 */
26727
+ function findGrepBinding(record) {
26728
+ const top = canonicalGrepMatches(record.matches);
26729
+ if (top !== void 0) return {
26730
+ binding: {
26463
26731
  record,
26464
- key: candidateKey
26732
+ key: "matches"
26465
26733
  },
26466
- plan
26734
+ matches: top
26735
+ };
26736
+ for (const [outerKey, outerValue] of Object.entries(record)) {
26737
+ if (!isPlainRecord(outerValue)) continue;
26738
+ const nested = canonicalGrepMatches(outerValue.matches);
26739
+ if (nested !== void 0) return {
26740
+ binding: {
26741
+ record,
26742
+ key: "matches",
26743
+ nested: {
26744
+ key: outerKey,
26745
+ record: outerValue
26746
+ }
26747
+ },
26748
+ matches: nested
26749
+ };
26750
+ }
26751
+ }
26752
+ /** 在顶层或 1 层嵌套里找规范 glob 路径表。 */
26753
+ function findGlobBinding(record) {
26754
+ const top = canonicalGlobPaths(record.paths);
26755
+ if (top !== void 0) return {
26756
+ binding: {
26757
+ record,
26758
+ key: "paths"
26759
+ },
26760
+ paths: top,
26761
+ ...typeof record.root === "string" ? { root: record.root } : {}
26762
+ };
26763
+ for (const [outerKey, outerValue] of Object.entries(record)) {
26764
+ if (!isPlainRecord(outerValue)) continue;
26765
+ const nested = canonicalGlobPaths(outerValue.paths);
26766
+ if (nested !== void 0) return {
26767
+ binding: {
26768
+ record,
26769
+ key: "paths",
26770
+ nested: {
26771
+ key: outerKey,
26772
+ record: outerValue
26773
+ }
26774
+ },
26775
+ paths: nested,
26776
+ ...typeof outerValue.root === "string" ? { root: outerValue.root } : {}
26777
+ };
26778
+ }
26779
+ }
26780
+ /** grep 信封的预览段落:复用已有的完整计数策略。 */
26781
+ function buildGrepEnvelopeSections(matches) {
26782
+ const files = /* @__PURE__ */ new Map();
26783
+ for (const match of matches) {
26784
+ const entry = files.get(match.path);
26785
+ if (entry === void 0) files.set(match.path, {
26786
+ count: 1,
26787
+ first: match.line
26788
+ });
26789
+ else entry.count += 1;
26790
+ }
26791
+ return buildGrepSections({
26792
+ files,
26793
+ total: matches.length
26794
+ });
26795
+ }
26796
+ /** glob 信封的预览段落:复用已有的扩展名分布策略。 */
26797
+ function buildGlobEnvelopeSections(paths, root) {
26798
+ const sections = buildGlobSections(paths.join("\n"));
26799
+ if (root === void 0) return sections;
26800
+ return [{ lines: [`root: ${clip(root, 160)}`] }, ...sections];
26801
+ }
26802
+ /**
26803
+ * PTC 的结构化工具返回信封:`grep` 的 `matches` 与 `glob` 的 `paths`。
26804
+ *
26805
+ * 两种形状都认:顶层 `{ matches }` / `{ root, paths }`,以及 1 层嵌套
26806
+ * `{ batch: { matches } }` / `{ batch: { root, paths } }`。
26807
+ *
26808
+ * 落库用缩进 JSON 而不是原始的 minified 文本:minified 文本里没有真换行,
26809
+ * `store.page` 的「按行翻页」会退化成一次吐完一整行;缩进后每行一个字段,
26810
+ * 翻页干净,而且外层与内层的同级元数据一个不少。
26811
+ */
26812
+ function extractStructuredToolEnvelope(text) {
26813
+ const candidate = parseJsonCandidate(text);
26814
+ if (candidate === void 0 || candidate.kind !== "object") return void 0;
26815
+ const { record } = candidate;
26816
+ if (collectCompositeEntries(record).length !== 1) return void 0;
26817
+ const grep = findGrepBinding(record);
26818
+ if (grep !== void 0) {
26819
+ if (envelopeShellLength(grep.binding) > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
26820
+ return {
26821
+ binding: grep.binding,
26822
+ plan: {
26823
+ kind: "preview",
26824
+ strategy: "grep-results",
26825
+ sections: buildGrepEnvelopeSections(grep.matches)
26826
+ },
26827
+ storeContent: JSON.stringify(record, null, 2)
26828
+ };
26829
+ }
26830
+ const glob = findGlobBinding(record);
26831
+ if (glob !== void 0) {
26832
+ if (envelopeShellLength(glob.binding) > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
26833
+ return {
26834
+ binding: glob.binding,
26835
+ plan: {
26836
+ kind: "preview",
26837
+ strategy: "glob-results",
26838
+ sections: buildGlobEnvelopeSections(glob.paths, glob.root)
26839
+ },
26840
+ storeContent: JSON.stringify(record, null, 2)
26841
+ };
26842
+ }
26843
+ }
26844
+ /** 日志正文的错误行数与尾部若干行。 */
26845
+ function logSummaryOf(text) {
26846
+ const lines = text.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
26847
+ const errorLines = lines.filter((line) => SHELL_ERROR_LINE.test(line)).length;
26848
+ const summary = [];
26849
+ if (errorLines > 0) summary.push(`errors: ${errorLines}`);
26850
+ for (const line of lines.slice(-2)) summary.push(clip(line.trim(), 160));
26851
+ return summary;
26852
+ }
26853
+ /**
26854
+ * 把一个普通 JSON 对象分类成复合信封里的一个成员。
26855
+ *
26856
+ * 认得四种形状:读取结果、grep 命中表、glob 路径表、日志正文。
26857
+ * 分不出来就返回 undefined —— 宁可少认一个成员,也不把不相干的数据写进摘要。
26858
+ * @param key - 该成员所在的键。
26859
+ * @param value - 该成员的值。
26860
+ * @returns 识别出的成员,或 undefined。
26861
+ */
26862
+ function classifyCompositeEntry(key, value) {
26863
+ const jsonLength = JSON.stringify(value)?.length ?? 0;
26864
+ const matches = canonicalGrepMatches(value.matches);
26865
+ if (matches !== void 0) {
26866
+ const counts = /* @__PURE__ */ new Map();
26867
+ for (const match of matches) counts.set(match.path, (counts.get(match.path) ?? 0) + 1);
26868
+ const files = [...counts.entries()].map(([path, count]) => ({
26869
+ path,
26870
+ count
26871
+ })).sort((left, right) => right.count - left.count);
26872
+ return {
26873
+ kind: "grep",
26874
+ key,
26875
+ jsonLength,
26876
+ total: matches.length,
26877
+ files
26878
+ };
26879
+ }
26880
+ const paths = canonicalGlobPaths(value.paths);
26881
+ if (paths !== void 0) return {
26882
+ kind: "glob",
26883
+ key,
26884
+ jsonLength,
26885
+ count: paths.length,
26886
+ ...typeof value.root === "string" ? { root: value.root } : {},
26887
+ sample: paths.slice(0, 3)
26888
+ };
26889
+ const read = canonicalReadResult(value);
26890
+ if (read !== void 0) {
26891
+ let first = Number.POSITIVE_INFINITY;
26892
+ let last = Number.NEGATIVE_INFINITY;
26893
+ for (const line of read.lines) {
26894
+ if (line.number < first) first = line.number;
26895
+ if (line.number > last) last = line.number;
26896
+ }
26897
+ return {
26898
+ kind: "read",
26899
+ key,
26900
+ jsonLength,
26901
+ path: read.path,
26902
+ lineCount: read.lines.length,
26903
+ first,
26904
+ last,
26905
+ firstLines: read.lines.slice(0, 2).map((line) => `${line.number}: ${line.text}`)
26906
+ };
26907
+ }
26908
+ for (const logKey of JSON_ENVELOPE_LOG_KEYS) {
26909
+ const logText = value[logKey];
26910
+ if (typeof logText !== "string" || logText.length === 0) continue;
26911
+ if (looksLikeSourceCode(logText) || looksLikeContract(logText) || isMixedEvidenceOutput(logText)) return void 0;
26912
+ const meta = shellMetadataOf(value);
26913
+ return {
26914
+ kind: "log",
26915
+ key,
26916
+ jsonLength,
26917
+ chars: logText.length,
26918
+ ...meta?.exitCode === void 0 ? {} : { exitCode: meta.exitCode },
26919
+ ...meta?.status === void 0 ? {} : { status: meta.status },
26920
+ summary: logSummaryOf(logText)
26921
+ };
26922
+ }
26923
+ }
26924
+ /**
26925
+ * 收集一份 JSON 信封里的全部成员(顶层、1 层嵌套、数组元素)。
26926
+ * @param record - 顶层记录。
26927
+ * @returns 识别出的成员。
26928
+ */
26929
+ function collectCompositeEntries(record) {
26930
+ const entries = [];
26931
+ const push = (key, value) => {
26932
+ const entry = classifyCompositeEntry(key, value);
26933
+ if (entry === void 0) return false;
26934
+ entries.push(entry);
26935
+ return true;
26936
+ };
26937
+ const root = classifyCompositeEntry("(root)", record);
26938
+ if (root !== void 0) entries.push(root);
26939
+ for (const [key, value] of Object.entries(record)) {
26940
+ if (Array.isArray(value)) {
26941
+ value.forEach((item, index) => {
26942
+ if (isPlainRecord(item)) push(`${key}[${index}]`, item);
26943
+ });
26944
+ continue;
26945
+ }
26946
+ if (!isPlainRecord(value)) continue;
26947
+ if (push(key, value)) continue;
26948
+ for (const [subKey, subValue] of Object.entries(value)) if (isPlainRecord(subValue)) push(`${key}.${subKey}`, subValue);
26949
+ }
26950
+ return entries;
26951
+ }
26952
+ /** 复合信封的预览段落:逐个成员给出它是什么、有多大。 */
26953
+ function buildCompositeSections(entries) {
26954
+ const allReads = entries.every((entry) => entry.kind === "read");
26955
+ const shown = entries.slice(0, 20);
26956
+ const summaries = [];
26957
+ const firstLines = [];
26958
+ const details = [];
26959
+ for (const entry of shown) {
26960
+ if (entry.kind === "read") {
26961
+ summaries.push(` ${clip(entry.key, 60)}: ${clip(entry.path, 120)} lines ${entry.first}-${entry.last} (${entry.lineCount})`);
26962
+ continue;
26963
+ }
26964
+ if (entry.kind === "grep") {
26965
+ summaries.push(` ${clip(entry.key, 60)}: grep matches=${entry.total} across ${entry.files.length} files`);
26966
+ for (const file of entry.files.slice(0, 3)) summaries.push(` ${clip(file.path, 100)}: ${file.count}`);
26967
+ continue;
26968
+ }
26969
+ if (entry.kind === "glob") {
26970
+ const root = entry.root === void 0 ? "" : ` root=${clip(entry.root, 80)}`;
26971
+ summaries.push(` ${clip(entry.key, 60)}: glob paths=${entry.count}${root}`);
26972
+ for (const sample of entry.sample) summaries.push(` ${clip(sample, 140)}`);
26973
+ continue;
26974
+ }
26975
+ const state = [entry.exitCode === void 0 ? "" : `exit code=${entry.exitCode}`, entry.status === void 0 ? "" : `status=${entry.status}`].filter((part) => part !== "").join(" ");
26976
+ summaries.push(` ${clip(entry.key, 60)}: log chars=${entry.chars}${state === "" ? "" : ` (${state})`}`);
26977
+ for (const line of entry.summary) details.push(` ${clip(line, 160)}`);
26978
+ }
26979
+ if (entries.length > shown.length) summaries.push(` … ${entries.length - shown.length} more entries (full list via ref)`);
26980
+ for (const entry of shown.slice(0, 3)) {
26981
+ if (entry.kind !== "read") continue;
26982
+ firstLines.push(` ${clip(entry.path, 100)}`);
26983
+ for (const line of entry.firstLines) firstLines.push(` ${clip(line, 160)}`);
26984
+ }
26985
+ const sections = [{ lines: [allReads ? `reads: ${entries.length} (complete set)` : `results: ${entries.length} (complete set)`] }, { lines: summaries }];
26986
+ const extra = [...firstLines, ...details];
26987
+ if (extra.length > 0) sections.push({
26988
+ lines: [allReads ? "first lines:" : "detail:", ...extra],
26989
+ optional: true
26990
+ });
26991
+ return sections;
26992
+ }
26993
+ /**
26994
+ * 复合 JSON 信封的投影计划。
26995
+ *
26996
+ * 一份结果里混着多个成员(grep + read + glob + log)时,逐个成员摘要:
26997
+ * 模型要的是「这一趟拿到了什么、各自多大」,而不是某一份成员的完整正文。
26998
+ *
26999
+ * 触发条件:已经超长(或被 spill 截断)、成员不止一个、或单个成员本身就装不下预算。
27000
+ * @param text - 工具结果原文。
27001
+ * @param budgetMaxChars - 当前预览预算的字符上限。
27002
+ * @param oversized - 是否已经超长或被 spill 截断。
27003
+ * @returns 投影计划,或 undefined。
27004
+ */
27005
+ function extractCompositeEnvelope(text, budgetMaxChars, oversized) {
27006
+ const candidate = parseJsonCandidate(text);
27007
+ if (candidate === void 0 || candidate.kind !== "object") return void 0;
27008
+ const { record } = candidate;
27009
+ const entries = collectCompositeEntries(record);
27010
+ if (entries.length === 0) return void 0;
27011
+ if (!(oversized || entries.length >= 2 || entries.some((entry) => entry.jsonLength > budgetMaxChars))) return void 0;
27012
+ return {
27013
+ kind: "preview",
27014
+ strategy: "file-outline",
27015
+ sections: buildCompositeSections(entries),
27016
+ storeContent: JSON.stringify(record, null, 2)
27017
+ };
27018
+ }
27019
+ /** 多节输出的分节头:整行大写,且至少含一个分隔符(空格、斜杠、连字符)。 */
27020
+ const MULTI_SECTION_HEADER = /^[A-Z][A-Z0-9]*(?:[ _/()-][A-Z0-9]+)*$/;
27021
+ /** 判断一行是不是分节头;光秃秃一个大写词不算一节。 */
27022
+ function isMultiSectionHeader(trimmed) {
27023
+ if (trimmed.length < 4) return false;
27024
+ if (!MULTI_SECTION_HEADER.test(trimmed)) return false;
27025
+ return /[ _/-]/.test(trimmed);
27026
+ }
27027
+ /** 多节输出的预览段落:分了几节、每节多少行与行范围、每节前几行。 */
27028
+ function buildMultiSectionSections(text) {
27029
+ const lines = text.split("\n");
27030
+ const headers = [];
27031
+ for (let index = 0; index < lines.length; index += 1) {
27032
+ const trimmed = lines[index].trim();
27033
+ if (!isMultiSectionHeader(trimmed)) continue;
27034
+ if ((lines[index + 1] ?? "").trim() === "") continue;
27035
+ headers.push({
27036
+ name: trimmed,
27037
+ start: index
27038
+ });
27039
+ }
27040
+ if (headers.length < 2) return void 0;
27041
+ const summaries = [];
27042
+ const firstLines = [];
27043
+ for (let index = 0; index < headers.length; index += 1) {
27044
+ const header = headers[index];
27045
+ const end = headers[index + 1]?.start ?? lines.length;
27046
+ const body = lines.slice(header.start + 1, end).map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
27047
+ let first = Number.POSITIVE_INFINITY;
27048
+ let last = Number.NEGATIVE_INFINITY;
27049
+ for (const line of body) {
27050
+ const matched = /^\s*(\d+)(?::|\t|\|)/.exec(line);
27051
+ if (matched === null) continue;
27052
+ const number = Number(matched[1]);
27053
+ if (number < first) first = number;
27054
+ if (number > last) last = number;
27055
+ }
27056
+ const range = Number.isFinite(first) && Number.isFinite(last) ? ` (${first}-${last})` : "";
27057
+ summaries.push(` ${clip(header.name, 80)}: ${body.length} lines${range}`);
27058
+ }
27059
+ for (let index = 0; index < Math.min(headers.length, 3); index += 1) {
27060
+ const header = headers[index];
27061
+ const end = headers[index + 1]?.start ?? lines.length;
27062
+ const body = lines.slice(header.start + 1, end).map((line) => line.trim()).filter((line) => line !== "");
27063
+ firstLines.push(` ${clip(header.name, 80)}`);
27064
+ for (const line of body.slice(0, 2)) firstLines.push(` ${clip(line, 160)}`);
27065
+ }
27066
+ return [
27067
+ { lines: [`sections: ${headers.length} (complete set)`] },
27068
+ { lines: summaries },
27069
+ {
27070
+ lines: ["first lines:", ...firstLines],
27071
+ optional: true
27072
+ }
27073
+ ];
27074
+ }
27075
+ /** 多节输出的投影计划。 */
27076
+ function planMultiSectionProjection(text) {
27077
+ const sections = buildMultiSectionSections(text);
27078
+ if (sections === void 0) return void 0;
27079
+ return {
27080
+ kind: "preview",
27081
+ strategy: "structured-rows",
27082
+ sections
26467
27083
  };
26468
27084
  }
26469
27085
  /** 对一段纯文本(非 JSON 文档)做投影计划判定。 */
@@ -26524,8 +27140,11 @@ function planPlainTextProjection(rawText, toolName) {
26524
27140
  * 然后是 JSON envelope(只允许纯日志字段投影,混合证据一律原样),
26525
27141
  * 接着是测试失败集、grep、结构化、glob、shell 这些有明确形状的,
26526
27142
  * 最后落到「无法解析 → 原样」。
27143
+ *
27144
+ * `spilled` 与超长门槛决定多节/多读输出还有没有资格进新投影:
27145
+ * 短于门槛、又没有 spill 的普通源码与契约读一律逐字保留。
26527
27146
  */
26528
- function planProjection(text, toolName, navigation, retrievalOnly) {
27147
+ function planProjection(text, toolName, navigation, retrievalOnly, spilled = false, budgetMaxChars = Number.POSITIVE_INFINITY) {
26529
27148
  if (TOOL_SUBAGENT.test(toolName)) return {
26530
27149
  kind: "verbatim",
26531
27150
  strategy: "verbatim-subagent-report"
@@ -26535,10 +27154,21 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
26535
27154
  kind: "verbatim",
26536
27155
  strategy: "verbatim-intent"
26537
27156
  };
26538
- if (TOOL_SOURCE.test(toolName) && navigation !== true) return {
26539
- kind: "verbatim",
26540
- strategy: "verbatim-source-code"
26541
- };
27157
+ const oversized = text.length >= MULTI_OUTPUT_MIN_CHARS || spilled;
27158
+ if (TOOL_SOURCE.test(toolName) && navigation !== true) {
27159
+ if (oversized && !isJsonDocument(text)) {
27160
+ const multiSection = planMultiSectionProjection(text);
27161
+ if (multiSection !== void 0) return multiSection;
27162
+ }
27163
+ return {
27164
+ kind: "verbatim",
27165
+ strategy: "verbatim-source-code"
27166
+ };
27167
+ }
27168
+ if (oversized && !isJsonDocument(text)) {
27169
+ const multiSection = planMultiSectionProjection(text);
27170
+ if (multiSection !== void 0) return multiSection;
27171
+ }
26542
27172
  if (looksLikeContract(text)) return {
26543
27173
  kind: "verbatim",
26544
27174
  strategy: "verbatim-contract"
@@ -26556,6 +27186,14 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
26556
27186
  if (outline !== void 0) return outline;
26557
27187
  }
26558
27188
  if (isJsonDocument(text)) {
27189
+ const structuredTool = extractStructuredToolEnvelope(text);
27190
+ if (structuredTool !== void 0) return {
27191
+ kind: "preview",
27192
+ strategy: structuredTool.plan.strategy,
27193
+ sections: structuredTool.plan.sections,
27194
+ envelope: structuredTool.binding,
27195
+ storeContent: structuredTool.storeContent
27196
+ };
26559
27197
  const extracted = extractJsonEnvelopeLogField(text, toolName);
26560
27198
  if (extracted !== void 0) return {
26561
27199
  kind: "preview",
@@ -26563,6 +27201,8 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
26563
27201
  sections: extracted.plan.sections,
26564
27202
  ...extracted.envelope === void 0 ? {} : { envelope: extracted.envelope }
26565
27203
  };
27204
+ const composite = extractCompositeEnvelope(text, budgetMaxChars, oversized);
27205
+ if (composite !== void 0) return composite;
26566
27206
  return {
26567
27207
  kind: "verbatim",
26568
27208
  strategy: "verbatim-unparsed"
@@ -26581,34 +27221,30 @@ function footerFor(refId) {
26581
27221
  * 入库失败一律降级为「保持原样」—— 没有 ref 的预览等于把原文丢了。
26582
27222
  */
26583
27223
  async function decide(args) {
26584
- const { text, input, budget } = args;
27224
+ const { input, budget } = args;
27225
+ const spill = await resolveSpillText(args.text);
27226
+ const text = spill.text;
26585
27227
  if (text.length <= budget.maxChars) return { reason: "verbatim-small" };
26586
- const plan = planProjection(text, input.toolName, input.navigation, input.retrievalOnly);
27228
+ const plan = planProjection(text, input.toolName, input.navigation, input.retrievalOnly, spill.spilled, budget.maxChars);
26587
27229
  if (plan.kind === "verbatim") return { reason: plan.strategy };
26588
27230
  if (input.isError === true && plan.strategy !== "test-failure" && plan.strategy !== "shell-output") return { reason: "verbatim-unparsed" };
26589
27231
  const header = `[cache-warmup preview] strategy=${plan.strategy} input=${text.length}ch`;
26590
27232
  const footerBudget = footerFor(`ref://${input.sessionId}/${"0".repeat(64)}`).length;
26591
27233
  const jsonEscaped = plan.envelope !== void 0;
26592
- const envelopeShellLength = plan.envelope === void 0 ? 0 : JSON.stringify({
26593
- ...plan.envelope.record,
26594
- [plan.envelope.key]: ""
26595
- }).length;
26596
- const bodyBudget = budget.maxChars - header.length - footerBudget - envelopeShellLength - (jsonEscaped ? 4 : 2);
27234
+ const envelopeShell = plan.envelope === void 0 ? 0 : envelopeShellLength(plan.envelope);
27235
+ const bodyBudget = budget.maxChars - header.length - footerBudget - envelopeShell - (jsonEscaped ? 4 : 2);
26597
27236
  if (bodyBudget <= 0) return { reason: "verbatim-budget" };
26598
27237
  const body = fitSections(plan.sections, bodyBudget, jsonEscaped);
26599
27238
  if (body === void 0) return { reason: "verbatim-budget" };
26600
27239
  const store = input.store;
26601
27240
  if (store === void 0) return { reason: "store-unavailable" };
26602
27241
  const placeholderInner = `${header}\n${body}\n${footerFor(`ref://${input.sessionId}/${"0".repeat(64)}`)}`;
26603
- if ((plan.envelope === void 0 ? placeholderInner : JSON.stringify({
26604
- ...plan.envelope.record,
26605
- [plan.envelope.key]: placeholderInner
26606
- })).length > budget.maxChars) return { reason: "verbatim-budget" };
27242
+ if ((plan.envelope === void 0 ? placeholderInner : rebuildEnvelope(plan.envelope, placeholderInner)).length > budget.maxChars) return { reason: "verbatim-budget" };
26607
27243
  let reference;
26608
27244
  try {
26609
27245
  reference = await store.put({
26610
27246
  sessionId: input.sessionId,
26611
- content: text,
27247
+ content: plan.storeContent ?? text,
26612
27248
  toolName: input.toolName,
26613
27249
  at: args.at
26614
27250
  });
@@ -26616,15 +27252,13 @@ async function decide(args) {
26616
27252
  return { reason: "store-unavailable" };
26617
27253
  }
26618
27254
  const innerPreview = `${header}\n${body}\n${footerFor(reference.refId)}`;
26619
- const preview = plan.envelope === void 0 ? innerPreview : JSON.stringify({
26620
- ...plan.envelope.record,
26621
- [plan.envelope.key]: innerPreview
26622
- });
27255
+ const preview = plan.envelope === void 0 ? innerPreview : rebuildEnvelope(plan.envelope, innerPreview);
26623
27256
  if (preview.length > budget.maxChars) return { reason: "verbatim-budget" };
26624
27257
  return {
26625
27258
  preview,
26626
27259
  refId: reference.refId,
26627
- strategy: plan.strategy
27260
+ strategy: plan.strategy,
27261
+ inputChars: text.length
26628
27262
  };
26629
27263
  }
26630
27264
  /** 块数组里文本字符数的合计。 */
@@ -26651,9 +27285,8 @@ async function projectToolResult(input) {
26651
27285
  at
26652
27286
  };
26653
27287
  if (typeof input.content === "string") {
26654
- const text = input.content;
26655
27288
  const decision = await decide({
26656
- text,
27289
+ text: input.content,
26657
27290
  input,
26658
27291
  budget,
26659
27292
  at
@@ -26663,8 +27296,8 @@ async function projectToolResult(input) {
26663
27296
  content: input.content,
26664
27297
  changed: false,
26665
27298
  metrics: {
26666
- inputChars: text.length,
26667
- outputChars: text.length,
27299
+ inputChars: input.content.length,
27300
+ outputChars: input.content.length,
26668
27301
  omittedChars: 0,
26669
27302
  strategy: decision.reason
26670
27303
  }
@@ -26676,9 +27309,9 @@ async function projectToolResult(input) {
26676
27309
  refId: decision.refId,
26677
27310
  preview: decision.preview,
26678
27311
  metrics: {
26679
- inputChars: text.length,
27312
+ inputChars: decision.inputChars,
26680
27313
  outputChars: decision.preview.length,
26681
- omittedChars: Math.max(0, text.length - decision.preview.length),
27314
+ omittedChars: Math.max(0, decision.inputChars - decision.preview.length),
26682
27315
  strategy: decision.strategy
26683
27316
  }
26684
27317
  };
@@ -26728,9 +27361,9 @@ async function projectToolResult(input) {
26728
27361
  refId: decision.refId,
26729
27362
  preview: decision.preview,
26730
27363
  metrics: {
26731
- inputChars: text.length,
27364
+ inputChars: decision.inputChars,
26732
27365
  outputChars: decision.preview.length,
26733
- omittedChars: Math.max(0, text.length - decision.preview.length),
27366
+ omittedChars: Math.max(0, decision.inputChars - decision.preview.length),
26734
27367
  strategy: decision.strategy
26735
27368
  }
26736
27369
  };
@@ -28295,7 +28928,7 @@ async function persistRegion(region) {
28295
28928
  */
28296
28929
  const lastActiveBySession = /* @__PURE__ */ new Map();
28297
28930
  /** Bound on remembered sessions. */
28298
- const MAX_TRACKED_SESSIONS$1 = 256;
28931
+ const MAX_TRACKED_SESSIONS$2 = 256;
28299
28932
  /**
28300
28933
  * Note that a session just did something.
28301
28934
  *
@@ -28308,7 +28941,7 @@ function markSessionActive(sessionId, now = Date.now()) {
28308
28941
  const key = sessionId.trim();
28309
28942
  lastActiveBySession.delete(key);
28310
28943
  lastActiveBySession.set(key, now);
28311
- if (lastActiveBySession.size <= MAX_TRACKED_SESSIONS$1) return;
28944
+ if (lastActiveBySession.size <= MAX_TRACKED_SESSIONS$2) return;
28312
28945
  const oldest = lastActiveBySession.keys().next();
28313
28946
  if (oldest.done !== true) lastActiveBySession.delete(oldest.value);
28314
28947
  }
@@ -31542,6 +32175,110 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$3, preserveThin
31542
32175
  return body;
31543
32176
  }
31544
32177
  /**
32178
+ * How the side-channel title request opens its prompt.
32179
+ *
32180
+ * Matching the opening identifies the request even when the provenance stamp
32181
+ * is absent - a harness generation that does not stamp it still asks for the
32182
+ * title in exactly this wording.
32183
+ */
32184
+ const TITLE_REQUEST_PROMPT_PREFIX = "Generate the session title from this JSON array of human messages:";
32185
+ /**
32186
+ * How the side-channel title request opens its system prompt.
32187
+ *
32188
+ * Only a request that carries NO tools can be the title request: the real
32189
+ * conversation always carries the session's tool set, so the (absent) tool
32190
+ * list is what separates the two shapes.
32191
+ */
32192
+ const TITLE_REQUEST_SYSTEM_PREFIX = "Create a concise title for an AI coding-assistant session";
32193
+ /**
32194
+ * The first non-empty text a message body contributes.
32195
+ *
32196
+ * The same ordering {@link textOf} folds a body in, but it stops at the first
32197
+ * contribution instead of joining all of them: a caller that only needs to know
32198
+ * where the text STARTS must not pay for the whole conversation. The join in
32199
+ * {@link textOf} puts the blocks in order and the empty ones contribute
32200
+ * nothing, so the first non-empty contribution is where the joined text begins.
32201
+ *
32202
+ * @param content - The body of one message.
32203
+ * @returns The first non-empty text it contributes, or undefined.
32204
+ */
32205
+ function firstTextOf(content) {
32206
+ if (typeof content === "string") {
32207
+ const text = sanitizeText$7(content);
32208
+ return text === "" ? void 0 : text;
32209
+ }
32210
+ if (!Array.isArray(content)) return void 0;
32211
+ for (const block of content) {
32212
+ if (!isRecord$45(block)) continue;
32213
+ if (block.type === "text" && typeof block.text === "string") {
32214
+ const text = sanitizeText$7(block.text);
32215
+ if (text !== "") return text;
32216
+ continue;
32217
+ }
32218
+ if (block.type === "tool-result") {
32219
+ const nested = firstTextOf(block.content);
32220
+ if (nested !== void 0) return nested;
32221
+ }
32222
+ }
32223
+ }
32224
+ /**
32225
+ * The text the folded system prompt starts with.
32226
+ *
32227
+ * Same ordering as {@link leadingSystemText}: `options.system` leads, and the
32228
+ * system-role messages follow in history order. Only the first contribution
32229
+ * matters here, so the scan stops at it.
32230
+ *
32231
+ * @param options - The request being inspected.
32232
+ * @returns The leading text of the system prompt, or undefined when there is none.
32233
+ */
32234
+ function leadingSystemTextStart(options) {
32235
+ if (typeof options.system === "string" && options.system.trim() !== "") return options.system;
32236
+ for (const message of options.messages) {
32237
+ if (message.role !== "system") continue;
32238
+ if (messageToolsOf(message) !== void 0) continue;
32239
+ const text = firstTextOf(message.content);
32240
+ if (text !== void 0) return text;
32241
+ }
32242
+ }
32243
+ /**
32244
+ * Whether this request is the side-channel one that titles a session.
32245
+ *
32246
+ * A title request is asked on the same adapter, under the same session id, and
32247
+ * with the same credential as the conversation it titles - but it shares
32248
+ * nothing else with it. Left alone it would:
32249
+ *
32250
+ * - send the session's prompt cache key, so the service would route the title
32251
+ * prompt at the entry the conversation cached;
32252
+ * - overwrite the session's prefix snapshot, so the next real turn would be
32253
+ * told its system prompt or its tool list changed; and
32254
+ * - file its usage under the session, so the session's cache ratio would
32255
+ * describe a prompt the session never sent.
32256
+ *
32257
+ * The detection is deliberately structural rather than a flag on the options:
32258
+ * the caller is the harness, and the harness states a title request by its
32259
+ * CONTENT, not by anything this route could switch on.
32260
+ *
32261
+ * @param options - The request about to be mapped.
32262
+ * @returns Whether the request is the side-channel title request.
32263
+ */
32264
+ function isSideChannelTitleRequest(options) {
32265
+ if (options === void 0 || options === null) return false;
32266
+ const messages = Array.isArray(options.messages) ? options.messages : [];
32267
+ const tools = options.tools;
32268
+ const toolLess = tools === void 0 || tools.length === 0;
32269
+ for (const message of messages) {
32270
+ if (!isRecord$45(message)) continue;
32271
+ if (message.source?.kind === "dsh-session-title-llm") return true;
32272
+ const text = firstTextOf(message.content);
32273
+ if (toolLess && text !== void 0 && text.startsWith(TITLE_REQUEST_PROMPT_PREFIX)) return true;
32274
+ }
32275
+ if (toolLess) {
32276
+ const systemText = leadingSystemTextStart(options);
32277
+ if (systemText !== void 0 && systemText.startsWith(TITLE_REQUEST_SYSTEM_PREFIX)) return true;
32278
+ }
32279
+ return false;
32280
+ }
32281
+ /**
31545
32282
  * Stable identifier for the conversation this request belongs to.
31546
32283
  *
31547
32284
  * The key is derived from the session identifier alone — never from message
@@ -31554,6 +32291,7 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$3, preserveThin
31554
32291
  * all rather than a key that will drift.
31555
32292
  */
31556
32293
  function promptCacheKey(options) {
32294
+ if (isSideChannelTitleRequest(options)) return void 0;
31557
32295
  if (typeof options.sessionId === "string" && options.sessionId.trim() !== "") return `dsh-${options.sessionId.trim()}`;
31558
32296
  }
31559
32297
  function anthropicUserContent$2(message, images, toolCallIds) {
@@ -31696,8 +32434,9 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$3, media = {
31696
32434
  }
31697
32435
  /** Build the body for whichever endpoint serves `wire`. */
31698
32436
  function buildRequest(options, wire, images = NO_RESOLVED_IMAGES$3, preserveThinking = preserveThinkingEnabled(), media = {}) {
32437
+ if (isSideChannelTitleRequest(options)) return wire === "anthropic" ? buildAnthropicRequest(options, images, media) : buildOpenAIRequest(options, images, preserveThinking, media);
31699
32438
  const cause = trackPrefixStability(options);
31700
- recordDriftCause(cause === "stable" && (pendingImageOffloads.get(cacheScopeKey(options.sessionId)) ?? 0) > 0 ? "image-offload" : cause, options.sessionId);
32439
+ recordDriftCause(cause === "stable" && (pendingImageOffloads.get(cacheScopeKey(options.sessionId)) ?? 0) > 0 ? "image-offload" : cause, options.sessionId, options);
31701
32440
  return wire === "anthropic" ? buildAnthropicRequest(options, images, media) : buildOpenAIRequest(options, images, preserveThinking, media);
31702
32441
  }
31703
32442
  /**
@@ -31794,10 +32533,11 @@ function oversizedBodyMessage(bytes, limit, body, context) {
31794
32533
  const parts = requestBodyBreakdown(body);
31795
32534
  return `Kimi Code rejected the request before sending: the serialized body is ${bytes} bytes, above the ${limit}-byte limit this route enforces. Composition: ${`images ${parts.imageBytes} bytes, tool schemas ${parts.toolSchemaBytes} bytes, conversation text and system prompt ${parts.otherBytes} bytes`}. ${bodyFitRemedy(parts, context)}`;
31796
32535
  }
31797
- function createStreamState$5(wire, sessionId) {
32536
+ function createStreamState$5(wire, sessionId, sideChannel = false) {
31798
32537
  return {
31799
32538
  wire,
31800
32539
  sessionId: typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : void 0,
32540
+ sideChannel,
31801
32541
  blocks: [],
31802
32542
  current: null,
31803
32543
  reasoningRuns: /* @__PURE__ */ new Map(),
@@ -32224,7 +32964,7 @@ const UNSCOPED_CACHE_KEY = "";
32224
32964
  * count: eviction only begins once a host has served hundreds of them, and it
32225
32965
  * evicts the least recently written key, which is a stale session by then.
32226
32966
  */
32227
- const MAX_TRACKED_SESSIONS = 256;
32967
+ const MAX_TRACKED_SESSIONS$1 = 256;
32228
32968
  /**
32229
32969
  * Composite key separating one session's numbers from another's.
32230
32970
  *
@@ -32242,7 +32982,7 @@ function cacheScopeKey(sessionId, accountId) {
32242
32982
  function rememberScoped(store, key, value) {
32243
32983
  store.delete(key);
32244
32984
  store.set(key, value);
32245
- if (store.size <= MAX_TRACKED_SESSIONS) return;
32985
+ if (store.size <= MAX_TRACKED_SESSIONS$1) return;
32246
32986
  const oldest = store.keys().next();
32247
32987
  if (oldest.done !== true) store.delete(oldest.value);
32248
32988
  }
@@ -32257,19 +32997,31 @@ function rememberScoped(store, key, value) {
32257
32997
  */
32258
32998
  const prefixSnapshots = /* @__PURE__ */ new Map();
32259
32999
  /**
33000
+ * Hash the prefix-breaking inputs of one request.
33001
+ *
33002
+ * @param options - The request about to be built.
33003
+ * @returns The hashes of its system prompt and its tool list.
33004
+ */
33005
+ function prefixFingerprints(options) {
33006
+ const systemText = leadingSystemText$7(options) ?? "";
33007
+ const tools = options.tools ?? [];
33008
+ return {
33009
+ systemPromptHash: systemText === "" ? null : fingerprint(systemText),
33010
+ toolsHash: tools.length === 0 ? null : fingerprint(JSON.stringify(tools.map((tool) => [
33011
+ tool.name,
33012
+ tool.description ?? "",
33013
+ tool.parameters ?? {}
33014
+ ])))
33015
+ };
33016
+ }
33017
+ /**
32260
33018
  * Fingerprint the current request's stability inputs and diff them against the
32261
33019
  * last request. Called once per request build; the result is read back when the
32262
33020
  * stream reports usage so a low-or-zero cache read can be explained.
32263
33021
  */
32264
33022
  function trackPrefixStability(options) {
32265
- const systemText = leadingSystemText$7(options) ?? "";
32266
- const systemPromptHash = systemText === "" ? null : fingerprint(systemText);
32267
- const tools = options.tools ?? [];
32268
- const toolsHash = tools.length === 0 ? null : fingerprint(JSON.stringify(tools.map((tool) => [
32269
- tool.name,
32270
- tool.description ?? "",
32271
- tool.parameters ?? {}
32272
- ])));
33023
+ if (isSideChannelTitleRequest(options)) return "stable";
33024
+ const { systemPromptHash, toolsHash } = prefixFingerprints(options);
32273
33025
  const cacheKey = promptCacheKey(options) ?? null;
32274
33026
  const scope = cacheScopeKey(options.sessionId);
32275
33027
  const previous = prefixSnapshots.get(scope);
@@ -32287,8 +33039,16 @@ function trackPrefixStability(options) {
32287
33039
  }
32288
33040
  /** Drift cause recorded for each session. */
32289
33041
  const driftCauses = /* @__PURE__ */ new Map();
32290
- /** Record the cause computed for one in-flight request. */
32291
- function recordDriftCause(cause, sessionId) {
33042
+ /**
33043
+ * Record the cause computed for one in-flight request.
33044
+ *
33045
+ * The options are optional and only used to recognise a side-channel request:
33046
+ * a title request carries its own prefix, so attributing it to the session
33047
+ * would blame the session's next cache miss on the title request's system
33048
+ * prompt or on its (absent) tool list.
33049
+ */
33050
+ function recordDriftCause(cause, sessionId, options) {
33051
+ if (options !== void 0 && isSideChannelTitleRequest(options)) return;
32292
33052
  rememberScoped(driftCauses, cacheScopeKey(sessionId), cause);
32293
33053
  }
32294
33054
  /**
@@ -32363,7 +33123,7 @@ function markPendingImageOffload(sessionId, newlyDroppedCount) {
32363
33123
  * so an image given back stays given back, and the next turn has to know
32364
33124
  * which ones those are.
32365
33125
  */
32366
- function recordSessionImageBudget(sessionId, input) {
33126
+ function recordSessionImageBudget(sessionId, input, options) {
32367
33127
  const scope = cacheScopeKey(sessionId);
32368
33128
  const named = sessionId !== void 0 && sessionId.trim() !== "";
32369
33129
  const keys = named ? sessionDroppedImageKeys.get(scope) ?? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set();
@@ -32377,7 +33137,7 @@ function recordSessionImageBudget(sessionId, input) {
32377
33137
  droppedKeys: named ? [...keys] : []
32378
33138
  });
32379
33139
  markPendingImageOffload(sessionId, input.newlyDroppedCount);
32380
- if (input.newlyDroppedCount > 0 && getLastDriftCause(sessionId) === "stable") recordDriftCause("image-offload", sessionId);
33140
+ if (input.newlyDroppedCount > 0 && getLastDriftCause(sessionId) === "stable") recordDriftCause("image-offload", sessionId, options);
32381
33141
  }
32382
33142
  /**
32383
33143
  * What one session's last request carried of images, when it is known.
@@ -32466,6 +33226,7 @@ const EMPTY_CACHE_STATS = {
32466
33226
  /** Record one request's usage into its own session's totals. */
32467
33227
  function recordCacheStats(state, sessionId, accountId) {
32468
33228
  if (!state.sawUsage) return;
33229
+ if (state.sideChannel) return;
32469
33230
  const scope = cacheScopeKey(sessionId ?? state.sessionId, accountId);
32470
33231
  const previous = cacheStatsByScope.get(scope) ?? EMPTY_CACHE_STATS;
32471
33232
  rememberScoped(cacheStatsByScope, scope, {
@@ -33931,6 +34692,155 @@ function toolUpdateMode$1(modelId, catalog) {
33931
34692
  ].includes(raw)) return void 0;
33932
34693
  return "in-history";
33933
34694
  }
34695
+ /**
34696
+ * How long a cold-start follower waits for the leader warming the same prefix.
34697
+ *
34698
+ * The leader only has to reach the response headers: past that the service is
34699
+ * already processing the prefix, and a follower that waited out the whole
34700
+ * budget would have lost more than the shared warm-up was worth.
34701
+ */
34702
+ const COLD_START_WARMUP_WAIT_MS = 2500;
34703
+ /** How many prefix groups are remembered as warm. */
34704
+ const MAX_WARM_PREFIX_GROUPS = 256;
34705
+ /**
34706
+ * When each prefix group was last warmed.
34707
+ *
34708
+ * An entry older than the cache lifetime is treated as cold, because the
34709
+ * service's own entry is gone by then too. Bounded, so a long-lived process
34710
+ * cannot accumulate one entry per distinct prefix forever.
34711
+ */
34712
+ const warmPrefixTimestamps = /* @__PURE__ */ new Map();
34713
+ /**
34714
+ * The request currently warming each prefix group.
34715
+ *
34716
+ * The promise is resolved when that leader reaches the response headers, which
34717
+ * is what releases the followers waiting behind it.
34718
+ */
34719
+ const inFlightColdLeaders = /* @__PURE__ */ new Map();
34720
+ /** When each leader started, so a hung leader does not own its group forever. */
34721
+ const inFlightColdLeaderStartedAt = /* @__PURE__ */ new Map();
34722
+ /**
34723
+ * The resolver belonging to each in-flight leader.
34724
+ *
34725
+ * Kept beside the promise it settles, because a promise cannot be resolved
34726
+ * from the outside.
34727
+ */
34728
+ const coldLeaderReleases = /* @__PURE__ */ new Map();
34729
+ /** The gate of a request that neither waits for nor leads a cold start. */
34730
+ const NO_COLD_START_GATE = {
34731
+ isLeader: false,
34732
+ release: () => void 0,
34733
+ markWarm: () => void 0
34734
+ };
34735
+ /**
34736
+ * The group of requests that address the same cached prefix.
34737
+ *
34738
+ * The service derives its cache from the request content, so two requests with
34739
+ * the same model, the same system prompt and the same tool list hit the same
34740
+ * entry. The region is part of the key because each region is a separate
34741
+ * service with a cache of its own.
34742
+ *
34743
+ * A request with no system prompt has no prefix worth warming, and gets no
34744
+ * group at all.
34745
+ *
34746
+ * @param region - The region the request will be answered from.
34747
+ * @param options - The request about to be sent.
34748
+ * @returns The group key, or undefined when the request has no prefix.
34749
+ */
34750
+ function coldStartGroupKey(region, options) {
34751
+ const { systemPromptHash, toolsHash } = prefixFingerprints(options);
34752
+ if (systemPromptHash === null) return void 0;
34753
+ return `${region}:${options.model}:${systemPromptHash}:${toolsHash ?? "none"}`;
34754
+ }
34755
+ /**
34756
+ * Remember that a prefix group is warm.
34757
+ *
34758
+ * @param groupKey - The group that was just warmed.
34759
+ */
34760
+ function rememberWarmPrefix(groupKey) {
34761
+ warmPrefixTimestamps.delete(groupKey);
34762
+ warmPrefixTimestamps.set(groupKey, Date.now());
34763
+ while (warmPrefixTimestamps.size > MAX_WARM_PREFIX_GROUPS) {
34764
+ const oldest = warmPrefixTimestamps.keys().next();
34765
+ if (oldest.done === true) break;
34766
+ warmPrefixTimestamps.delete(oldest.value);
34767
+ }
34768
+ }
34769
+ /**
34770
+ * Wait for the leader of a prefix group, for the wait budget, or for an abort.
34771
+ *
34772
+ * The timer and the abort listener are removed whichever way the race ends, so
34773
+ * a follower that proceeds leaves neither behind.
34774
+ *
34775
+ * @param leader - The promise the in-flight leader settles.
34776
+ * @param signal - The caller's abort signal; an abort ends the wait.
34777
+ */
34778
+ function raceColdStartLeader(leader, signal) {
34779
+ const cleanups = [];
34780
+ const budget = new Promise((resolve) => {
34781
+ const timer = setTimeout(() => resolve(), COLD_START_WARMUP_WAIT_MS);
34782
+ cleanups.push(() => clearTimeout(timer));
34783
+ });
34784
+ const aborted = new Promise((resolve) => {
34785
+ if (signal.aborted) {
34786
+ resolve();
34787
+ return;
34788
+ }
34789
+ const onAbort = () => resolve();
34790
+ signal.addEventListener("abort", onAbort, { once: true });
34791
+ cleanups.push(() => signal.removeEventListener("abort", onAbort));
34792
+ });
34793
+ return Promise.race([
34794
+ leader,
34795
+ budget,
34796
+ aborted
34797
+ ]).finally(() => {
34798
+ for (const cleanup of cleanups) cleanup();
34799
+ });
34800
+ }
34801
+ /**
34802
+ * Join the cold-start singleflight for one prefix group.
34803
+ *
34804
+ * A warm group returns at once. A cold group that already has a leader waits
34805
+ * for that leader — but never longer than {@link COLD_START_WARMUP_WAIT_MS}.
34806
+ * A cold group with no leader becomes the leader itself.
34807
+ *
34808
+ * @param groupKey - The prefix group this request belongs to.
34809
+ * @param signal - The caller's abort signal; an abort ends the wait.
34810
+ * @returns The gate this request holds.
34811
+ */
34812
+ async function acquireColdStartWarmup(groupKey, signal) {
34813
+ const warmedAt = warmPrefixTimestamps.get(groupKey);
34814
+ if (warmedAt !== void 0 && Date.now() - warmedAt < 15e5) return NO_COLD_START_GATE;
34815
+ if (inFlightColdLeaders.get(groupKey) !== void 0) {
34816
+ const startedAt = inFlightColdLeaderStartedAt.get(groupKey);
34817
+ if (startedAt !== void 0 && Date.now() - startedAt >= 2500) coldLeaderReleases.get(groupKey)?.();
34818
+ }
34819
+ const leader = inFlightColdLeaders.get(groupKey);
34820
+ if (leader !== void 0) {
34821
+ await raceColdStartLeader(leader, signal);
34822
+ return NO_COLD_START_GATE;
34823
+ }
34824
+ let resolveLeader;
34825
+ const promise = new Promise((resolve) => {
34826
+ resolveLeader = resolve;
34827
+ });
34828
+ inFlightColdLeaders.set(groupKey, promise);
34829
+ inFlightColdLeaderStartedAt.set(groupKey, Date.now());
34830
+ const release = () => {
34831
+ resolveLeader();
34832
+ if (coldLeaderReleases.get(groupKey) !== release) return;
34833
+ coldLeaderReleases.delete(groupKey);
34834
+ inFlightColdLeaders.delete(groupKey);
34835
+ inFlightColdLeaderStartedAt.delete(groupKey);
34836
+ };
34837
+ coldLeaderReleases.set(groupKey, release);
34838
+ return {
34839
+ isLeader: true,
34840
+ release,
34841
+ markWarm: () => rememberWarmPrefix(groupKey)
34842
+ };
34843
+ }
33934
34844
  /** Sessions already told that their prefix broke, so the log stays readable. */
33935
34845
  const driftWarned = /* @__PURE__ */ new Set();
33936
34846
  /**
@@ -34204,11 +35114,12 @@ var KimiCodeAdapter = class extends LlmAdapter {
34204
35114
  const wire = wireForCatalogEntry(options.model, catalog);
34205
35115
  const entry = catalog.find((model) => model.id === options.model);
34206
35116
  const normalized = normalizeGenerateOptions(options);
35117
+ const sideChannel = isSideChannelTitleRequest(normalized);
34207
35118
  const images = await resolveRequestImages$1(normalized, this.options.attachments, signal, KIMI_CODE_REQUEST_IMAGE_MAX_EDGE);
34208
35119
  const priorDroppedKeys = getSessionDroppedImageKeys(options.sessionId, options.sessionId === void 0 ? void 0 : sessionCacheState.read(options.sessionId)?.budgetDropped);
34209
35120
  const stickyOffload = offloadRequestImagesWithStickyBudget(normalized, maxRequestImageBytes$1(), lowRequestImageBytes(), images, priorDroppedKeys);
34210
35121
  const imageTrimmed = stickyOffload.options;
34211
- markPendingImageOffload(options.sessionId, stickyOffload.newlyDroppedKeys.length);
35122
+ if (!sideChannel) markPendingImageOffload(options.sessionId, stickyOffload.newlyDroppedKeys.length);
34212
35123
  const gatedEffort = coerceThinkingEffort(options.model, mapReasoningEffort(options.reasoningEffort === void 0 || options.reasoningEffort === null ? void 0 : String(options.reasoningEffort)));
34213
35124
  if (gatedEffort.coercedFrom !== null) recordThinkingCoercion({
34214
35125
  model: options.model,
@@ -34384,7 +35295,22 @@ var KimiCodeAdapter = class extends LlmAdapter {
34384
35295
  throw new LlmError(`Kimi Code request failed: ${error instanceof Error ? error.message : String(error)}`, "TRANSPORT", { cause: error });
34385
35296
  }
34386
35297
  };
34387
- response = await post(body);
35298
+ const coldGroupKey = sideChannel ? void 0 : coldStartGroupKey(region, requestOptions);
35299
+ const coldGate = coldGroupKey === void 0 ? void 0 : await acquireColdStartWarmup(coldGroupKey, signal);
35300
+ let coldGateSettled = false;
35301
+ const settleColdGate = (warm) => {
35302
+ if (coldGateSettled) return;
35303
+ coldGateSettled = true;
35304
+ coldGate?.release();
35305
+ if (warm) coldGate?.markWarm();
35306
+ };
35307
+ response = await post(body).then((value) => {
35308
+ settleColdGate(value.ok);
35309
+ return value;
35310
+ }, (error) => {
35311
+ settleColdGate(false);
35312
+ throw error;
35313
+ });
34388
35314
  let detail = response.ok ? "" : (await response.text().catch(() => "")).slice(0, 2e3);
34389
35315
  if (response.status === 404 && uploads !== void 0 && uploads.cited.length > 0 && isMissingResourceBody(detail)) {
34390
35316
  const uploader = this.mediaFiles();
@@ -34428,7 +35354,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
34428
35354
  ...after === void 0 ? {} : { providerRetryAfterMs: after }
34429
35355
  });
34430
35356
  }
34431
- if (measured !== void 0) {
35357
+ if (measured !== void 0 && !sideChannel) {
34432
35358
  const requestImageBytes = requestImagePayloadLengths(measured.options, images).reduce((sum, bytes) => sum + bytes, 0);
34433
35359
  const trimmedKeys = new Set(collectRemainingImageKeys(imageTrimmed, images));
34434
35360
  const measuredKeys = new Set(collectRemainingImageKeys(measured.options, images));
@@ -34439,14 +35365,14 @@ var KimiCodeAdapter = class extends LlmAdapter {
34439
35365
  newlyDroppedCount: stickyOffload.newlyDroppedKeys.length + bodyFitDroppedKeys.length,
34440
35366
  requestImageBytes,
34441
35367
  droppedImages: stickyOffload.droppedCount + bodyFitDroppedKeys.length
34442
- });
35368
+ }, requestOptions);
34443
35369
  }
34444
35370
  if (response.body === null) throw new LlmError("Kimi Code returned an empty response body", "PROVIDER_ERROR");
34445
35371
  const reader = response.body.getReader();
34446
35372
  const decoder = new TextDecoder();
34447
- const state = createStreamState$5(wire, requestOptions.sessionId);
35373
+ const state = createStreamState$5(wire, requestOptions.sessionId, sideChannel);
34448
35374
  applyCacheWriteTiers(state, response.headers);
34449
- markSessionActive(requestOptions.sessionId);
35375
+ if (!sideChannel) markSessionActive(requestOptions.sessionId);
34450
35376
  let buffer = "";
34451
35377
  /**
34452
35378
  * Set the moment ANY chunk reaches the caller, and never cleared.
@@ -34492,7 +35418,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
34492
35418
  throw streamFailure(error, signal);
34493
35419
  }
34494
35420
  } finally {
34495
- if (state.sawUsage) noteSessionCacheState(requestOptions.sessionId ?? state.sessionId, state);
35421
+ if (state.sawUsage && !sideChannel) noteSessionCacheState(requestOptions.sessionId ?? state.sessionId, state);
34496
35422
  reader.cancel().catch(() => void 0);
34497
35423
  }
34498
35424
  }
@@ -44240,7 +45166,7 @@ function ms(value) {
44240
45166
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
44241
45167
  }
44242
45168
  /** A non-empty string, or undefined. */
44243
- function text$2(value) {
45169
+ function text$3(value) {
44244
45170
  return typeof value === "string" && value !== "" ? value : void 0;
44245
45171
  }
44246
45172
  function withTimeout$6(signal, ms_) {
@@ -44335,9 +45261,9 @@ async function callAcrossHosts(input) {
44335
45261
  function parseResetCard(raw) {
44336
45262
  const row = asRecord$9(raw);
44337
45263
  if (row === void 0) return void 0;
44338
- const cardId = text$2(row.card_id);
44339
- const sourceType = text$2(row.source_type);
44340
- const outBizNo = text$2(row.out_biz_no);
45264
+ const cardId = text$3(row.card_id);
45265
+ const sourceType = text$3(row.source_type);
45266
+ const outBizNo = text$3(row.out_biz_no);
44341
45267
  const expiresAtMs = ms(row.expire_time);
44342
45268
  const createdAtMs = ms(row.create_time);
44343
45269
  if (cardId === void 0 || sourceType === void 0 || outBizNo === void 0) return void 0;
@@ -44360,8 +45286,8 @@ function parseResetCard(raw) {
44360
45286
  function parseResetCardFlow(raw) {
44361
45287
  const row = asRecord$9(raw);
44362
45288
  if (row === void 0) return void 0;
44363
- const cardId = text$2(row.card_id);
44364
- const eventType = text$2(row.event_type);
45289
+ const cardId = text$3(row.card_id);
45290
+ const eventType = text$3(row.event_type);
44365
45291
  const createdAtMs = ms(row.create_time);
44366
45292
  if (cardId === void 0 || createdAtMs === void 0) return void 0;
44367
45293
  if (eventType !== "GRANT" && eventType !== "USE") return void 0;
@@ -44459,7 +45385,7 @@ async function useResetCard(credentials, card, options = {}) {
44459
45385
  }),
44460
45386
  options
44461
45387
  });
44462
- const outcome = text$2(asRecord$9(payload)?.outcome);
45388
+ const outcome = text$3(asRecord$9(payload)?.outcome);
44463
45389
  if (outcome === void 0 || !RESET_OUTCOMES.has(outcome)) throw new MinimaxCodeResetCardError("reset-card use answered without a usable outcome", null, readStatusCode(payload));
44464
45390
  return outcome;
44465
45391
  }
@@ -69293,7 +70219,7 @@ function createClinePassStore({ historyLimit = 100 } = {}) {
69293
70219
  *
69294
70220
  * @module dsh-chatgpt-subscription/cline-pass-tools
69295
70221
  */
69296
- function text$1(value) {
70222
+ function text$2(value) {
69297
70223
  return [{
69298
70224
  type: "text",
69299
70225
  text: value
@@ -69567,7 +70493,7 @@ function registerClinePassTools(ctx, options) {
69567
70493
  }
69568
70494
  }
69569
70495
  },
69570
- render: (_args, value) => text$1([
70496
+ render: (_args, value) => text$2([
69571
70497
  `${value.displayName} provider "cline-pass" @ ${value.baseURL}`,
69572
70498
  `route registered=${value.routeRegistered}`,
69573
70499
  `accountMode=${value.accountMode}`,
@@ -69661,7 +70587,7 @@ function registerClinePassTools(ctx, options) {
69661
70587
  if (model.lastProvider !== "") lines.push(` last=${model.lastProvider} (${model.lastMs}ms)`);
69662
70588
  return lines.join("\n");
69663
70589
  });
69664
- return text$1([
70590
+ return text$2([
69665
70591
  `${value.models.length} Cline Pass model(s); official catalog=${value.catalogCount}${value.catalogSources.length === 0 ? "" : ` via ${value.catalogSources.join(",")}`}`,
69666
70592
  ...value.added.length === 0 ? [] : [`newly adopted: ${value.added.join(", ")}`],
69667
70593
  ...blocks
@@ -69798,8 +70724,8 @@ function registerClinePassTools(ctx, options) {
69798
70724
  }
69799
70725
  },
69800
70726
  render: (_args, value) => {
69801
- if (!value.ok) return text$1(`probe ${value.model} failed: ${value.error}`);
69802
- return text$1([
70727
+ if (!value.ok) return text$2(`probe ${value.model} failed: ${value.error}`);
70728
+ return text$2([
69803
70729
  `probe ${value.model}: ok in ${value.ms}ms`,
69804
70730
  `pipeline=${value.pipeline} pinnable=${value.pinnable} canonicalSlug=${value.canonicalSlug || "-"} lastProvider=${value.lastProvider || "-"}`,
69805
70731
  value.upstreams.length === 0 ? "upstreams: (none discovered)" : `upstreams (${value.upstreams.length}): ${value.upstreams.join(", ")}`,
@@ -69886,8 +70812,8 @@ function registerClinePassTools(ctx, options) {
69886
70812
  }
69887
70813
  },
69888
70814
  render: (_args, value) => {
69889
- if (!value.ok) return text$1(`validate ${value.model} failed: ${value.error}`);
69890
- return text$1([
70815
+ if (!value.ok) return text$2(`validate ${value.model} failed: ${value.error}`);
70816
+ return text$2([
69891
70817
  `validate ${value.model}: ${value.results.length} upstream(s)`,
69892
70818
  `available=${value.summary.ok} rate-limited=${value.summary.limited} not-pinnable=${value.summary.bad} auth-failed=${value.summary.auth} unknown=${value.summary.unknown}`,
69893
70819
  ...value.results.map((entry) => `${entry.upstream}: ${statusLabel(entry.status)} (${entry.ms}ms)${entry.note === "" ? "" : ` — ${entry.note}`}`)
@@ -70007,7 +70933,7 @@ function registerClinePassTools(ctx, options) {
70007
70933
  },
70008
70934
  render: (_args, value) => {
70009
70935
  const trace = value.trace.map((entry) => ` ${entry.upstream} -> HTTP ${entry.status} in ${entry.ms}ms${entry.note === "" ? "" : ` — ${entry.note}`}`);
70010
- return text$1([
70936
+ return text$2([
70011
70937
  value.ok ? `test ${value.model}: ok in ${value.ms}ms via ${value.actual || "(unknown)"}` : `test ${value.model}: FAILED — ${value.error}`,
70012
70938
  ...value.warnings.map((warning) => `⚠ ${warning}`),
70013
70939
  trace.length === 0 ? "" : `attempts (${trace.length}):`,
@@ -70127,7 +71053,7 @@ function registerClinePassTools(ctx, options) {
70127
71053
  }
70128
71054
  }
70129
71055
  },
70130
- render: (_args, value) => text$1([
71056
+ render: (_args, value) => text$2([
70131
71057
  `pinned ${value.model}`,
70132
71058
  `upstreams: ${value.pinned.length === 0 ? "(auto)" : value.pinned.join(" > ")}`,
70133
71059
  `exclude: ${value.excluded.length === 0 ? "(none)" : value.excluded.join(", ")}`,
@@ -70237,7 +71163,7 @@ function registerClinePassTools(ctx, options) {
70237
71163
  }
70238
71164
  }
70239
71165
  },
70240
- render: (_args, value) => text$1([
71166
+ render: (_args, value) => text$2([
70241
71167
  `accounts.${value.action}: ${value.ok ? "ok" : "FAILED"}`,
70242
71168
  value.note === "" ? "" : `note: ${value.note}`,
70243
71169
  ...value.accounts.map((account) => ` ${account.alias} (${account.id})${account.enabled ? "" : " (disabled)"}${account.authStatus ? ` [${account.authStatus}]` : ""}`)
@@ -70372,8 +71298,8 @@ function registerClinePassTools(ctx, options) {
70372
71298
  }
70373
71299
  },
70374
71300
  render: (_args, value) => {
70375
- if (value.entries.length === 0) return text$1("No Cline Pass model calls recorded yet in this process.");
70376
- return text$1([`last ${value.entries.length} of ${value.total} call(s):`, ...value.entries.map((entry) => `${entry.ts === 0 ? "-" : new Date(entry.ts).toISOString()} ${entry.model} -> ${entry.provider || "(auto)"} ${entry.ttft > 0 ? `${entry.ttft}ms` : "—"}/${entry.ms}ms account=${entry.account || "-"}${entry.error === "" ? "" : ` error=${entry.error}`}`)].join("\n"));
71301
+ if (value.entries.length === 0) return text$2("No Cline Pass model calls recorded yet in this process.");
71302
+ return text$2([`last ${value.entries.length} of ${value.total} call(s):`, ...value.entries.map((entry) => `${entry.ts === 0 ? "-" : new Date(entry.ts).toISOString()} ${entry.model} -> ${entry.provider || "(auto)"} ${entry.ttft > 0 ? `${entry.ttft}ms` : "—"}/${entry.ms}ms account=${entry.account || "-"}${entry.error === "" ? "" : ` error=${entry.error}`}`)].join("\n"));
70377
71303
  }
70378
71304
  },
70379
71305
  async execute(args) {
@@ -83906,7 +84832,7 @@ When in doubt, clarify before escalating; if a task turns out more complex than
83906
84832
  - G6 Prohibited: Answering your own questions, making product/design decisions on the user's behalf, skipping the interview to plan directly, or treating a casual remark about one detail as full interview sign-off.
83907
84833
 
83908
84834
  ## R1 · Responsibility Boundary (Layered by Tier)
83909
- For L2 tasks, never execute work yourself outside these six categories: (1) Planning & decomposition; (2) Dispatching tasks; (3) Review; (4) Acceptance verification; (5) Documentation & delivery summary; (6) Tasks subagents cannot perform.
84835
+ For L2 tasks, never execute work yourself outside these seven categories: (1) Planning & decomposition; (2) Dispatching tasks; (3) Review; (4) Acceptance verification; (5) Documentation & delivery summary; (6) Tasks subagents cannot perform; (7) Independent parallel-module implementation that shares no files, unfinished interfaces, or acceptance dependencies with active subagents and can be verified independently.
83910
84836
  For L0/L1 tasks, execute directly—do not spawn subagents just to look like you are orchestrating.
83911
84837
 
83912
84838
  ## R2 · Model Routing (Check Availability First, Then Decide How to Dispatch)
@@ -83962,7 +84888,7 @@ All three rules govern the boundary of a turn: after dispatching, before continu
83962
84888
  [Waiting for Subagents] By default, end your turn without calling any more tools and let the subagent completion notification wake you—notifications are built-in; do not manufacture your own wait.
83963
84889
  - The notification is a post-hoc signal. It tells you a child stopped running; it does not tell you the work is correct, and nothing about it arrives before the work is done. Treat it as "there is now something to review", never as "this passed". The pre-hoc control is the acceptance criteria in the brief (R5); the review itself is R6.
83964
84890
  - Never run sleep, polling loops, watch, or any long-running placeholder command just to "hang around": they burn context and budget for zero information.
83965
- - If you have independent coordinator work in hand (the six categories in R1, reviewing another subagent's output, reading evidence you need), do it first and then end your turn.
84891
+ - If you have independent coordinator work in hand (the seven categories in R1, including non-overlapping parallel-module development, reviewing another subagent's output, or reading evidence you need), do it first and then end your turn. Do not manufacture work merely to keep a provider cache warm.
83966
84892
  - Only when you are genuinely blocked and must collect a result immediately should you call job_output({ wait: true }) for a bounded wait; that is a collection tool, not a timer.
83967
84893
  - Before ending the turn, state clearly: who was dispatched, what you are waiting for, and what you will do once the evidence arrives. Never leave the user guessing progress from silence.
83968
84894
 
@@ -84575,10 +85501,10 @@ const DELIVERY_FAILURE_MARKERS = [
84575
85501
  "is closing",
84576
85502
  "ACTIVATION_CLOSING"
84577
85503
  ];
84578
- function record(value) {
85504
+ function record$1(value) {
84579
85505
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
84580
85506
  }
84581
- function text(value) {
85507
+ function text$1(value) {
84582
85508
  return typeof value === "string" ? value : void 0;
84583
85509
  }
84584
85510
  function count(value) {
@@ -84593,8 +85519,8 @@ function oneLine(value, max) {
84593
85519
  function sessionTag(session) {
84594
85520
  if (session === void 0) return "session=?";
84595
85521
  const id = typeof session.id === "string" && session.id !== "" ? session.id : "?";
84596
- const origin = text(session.header?.origin);
84597
- const parent = text(session.header?.parentSession);
85522
+ const origin = text$1(session.header?.origin);
85523
+ const parent = text$1(session.header?.parentSession);
84598
85524
  return `session=${id}${parent === void 0 || parent === "" ? "" : ` parent=${parent}`}${origin === void 0 ? "" : ` origin=${origin}`}`;
84599
85525
  }
84600
85526
  /** Block-type histogram plus whether a message carries anything the user can see. */
@@ -84604,11 +85530,11 @@ function blocksSummary(content) {
84604
85530
  let visible = false;
84605
85531
  let chars = 0;
84606
85532
  for (const block of content) {
84607
- const value = record(block);
84608
- const type = text(value?.type) ?? "unknown";
85533
+ const value = record$1(block);
85534
+ const type = text$1(value?.type) ?? "unknown";
84609
85535
  counts.set(type, (counts.get(type) ?? 0) + 1);
84610
85536
  if (type === "tool-call") visible = true;
84611
- const body = text(value?.text);
85537
+ const body = text$1(value?.text);
84612
85538
  if (body !== void 0) {
84613
85539
  chars += body.length;
84614
85540
  if (type === "text" && body.trim().length > 0) visible = true;
@@ -84645,9 +85571,9 @@ var RelayProbe = class {
84645
85571
  }
84646
85572
  }
84647
85573
  step(session, event) {
84648
- const type = text(event?.type);
85574
+ const type = text$1(event?.type);
84649
85575
  if (type === void 0) return;
84650
- const data = record(event.data);
85576
+ const data = record$1(event.data);
84651
85577
  if (data === void 0) return;
84652
85578
  switch (type) {
84653
85579
  case "agent/inbox/spliced":
@@ -84680,9 +85606,9 @@ var RelayProbe = class {
84680
85606
  this.observedRequest(data);
84681
85607
  return;
84682
85608
  case "assistant/message": {
84683
- const message = record(data.message);
84684
- const source = record(message?.source);
84685
- const model = source === void 0 ? "" : ` model=${text(source.provider) ?? "?"}/${text(source.model) ?? "?"}`;
85609
+ const message = record$1(data.message);
85610
+ const source = record$1(message?.source);
85611
+ const model = source === void 0 ? "" : ` model=${text$1(source.provider) ?? "?"}/${text$1(source.model) ?? "?"}`;
84686
85612
  this.line(`assistant turn=${count(data.turn) ?? "?"} step=${count(data.step) ?? "?"}${model} ${blocksSummary(message?.content)}`);
84687
85613
  return;
84688
85614
  }
@@ -84691,16 +85617,16 @@ var RelayProbe = class {
84691
85617
  }
84692
85618
  /** A delivery entering (or leaving) the parent's inbox. */
84693
85619
  onSplice(session, data) {
84694
- const target = text(data.target) ?? "?";
85620
+ const target = text$1(data.target) ?? "?";
84695
85621
  const inserted = Array.isArray(data.inserted) ? data.inserted : [];
84696
85622
  const removed = count(data.removedCount) ?? 0;
84697
85623
  const outcomes = [];
84698
85624
  for (const entry of inserted) {
84699
- const message = record(entry);
84700
- const source = record(message?.source);
84701
- const kind = text(source?.kind) ?? "";
85625
+ const message = record$1(entry);
85626
+ const source = record$1(message?.source);
85627
+ const kind = text$1(source?.kind) ?? "";
84702
85628
  if (!RELAY_SOURCE_KINDS.includes(kind)) continue;
84703
- outcomes.push(`kind=${kind}${this.formTag(source)} msg=${text(message?.id) ?? "?"}`);
85629
+ outcomes.push(`kind=${kind}${this.formTag(source)} msg=${text$1(message?.id) ?? "?"}`);
84704
85630
  }
84705
85631
  if (outcomes.length > 0) {
84706
85632
  this.watch(session);
@@ -84709,84 +85635,84 @@ var RelayProbe = class {
84709
85635
  }
84710
85636
  if (!this.observed.has(session.id)) return;
84711
85637
  if (removed === 0 && inserted.length === 0) return;
84712
- const outcome = text(data.outcome);
85638
+ const outcome = text$1(data.outcome);
84713
85639
  this.line(`inbox-splice ${sessionTag(session)} target=${target} inserted=${inserted.length} removed=${removed}${outcome === void 0 ? "" : ` outcome=${outcome}`} pending=${this.pending(session)}`);
84714
85640
  }
84715
85641
  /** A message committed to the session surface — for a relay, the claim. */
84716
85642
  onUserMessage(session, data) {
84717
- const message = record(data.message);
84718
- const source = record(message?.source);
84719
- const kind = text(source?.kind) ?? "?";
85643
+ const message = record$1(data.message);
85644
+ const source = record$1(message?.source);
85645
+ const kind = text$1(source?.kind) ?? "?";
84720
85646
  const boundary = RELAY_SOURCE_KINDS.includes(kind);
84721
85647
  if (boundary) this.watch(session);
84722
85648
  if (!boundary && !this.observed.has(session.id)) return;
84723
- const sender = text(source?.senderSessionId);
85649
+ const sender = text$1(source?.senderSessionId);
84724
85650
  const senderTag = sender === void 0 || sender === "" ? "" : ` sender=${sender}`;
84725
- this.line(`user-committed ${sessionTag(session)} kind=${kind}${this.formTag(source)} msg=${text(message?.id) ?? "?"}${senderTag} turn=${count(data.turn) ?? "-"} ${blocksSummary(message?.content)} pending=${this.pending(session)}`);
85651
+ this.line(`user-committed ${sessionTag(session)} kind=${kind}${this.formTag(source)} msg=${text$1(message?.id) ?? "?"}${senderTag} turn=${count(data.turn) ?? "-"} ${blocksSummary(message?.content)} pending=${this.pending(session)}`);
84726
85652
  if (boundary) this.line(`agent-state ${this.agentState(session)}`);
84727
85653
  }
84728
85654
  /** A child attempting to report to its parent. */
84729
85655
  onToolCall(session, data) {
84730
- const message = record(data.message);
85656
+ const message = record$1(data.message);
84731
85657
  if (!Array.isArray(message?.content)) return;
84732
85658
  for (const block of message.content) {
84733
- const value = record(block);
84734
- if (text(value?.type) !== "tool-call") continue;
84735
- const name = text(value?.name) ?? "";
85659
+ const value = record$1(block);
85660
+ if (text$1(value?.type) !== "tool-call") continue;
85661
+ const name = text$1(value?.name) ?? "";
84736
85662
  if (!CHILD_SEND_TOOLS.includes(name)) continue;
84737
- this.line(`child-send ${sessionTag(session)} tool=${name} call=${text(value?.id) ?? "?"}`);
85663
+ this.line(`child-send ${sessionTag(session)} tool=${name} call=${text$1(value?.id) ?? "?"}`);
84738
85664
  }
84739
85665
  }
84740
85666
  /** A tool result that may be a refused delivery. */
84741
85667
  onToolResult(session, data) {
84742
- const message = record(data.message);
85668
+ const message = record$1(data.message);
84743
85669
  if (!Array.isArray(message?.content)) return;
84744
85670
  for (const block of message.content) {
84745
- const value = record(block);
84746
- if (value === void 0 || text(value.type) !== "tool-result") continue;
85671
+ const value = record$1(block);
85672
+ if (value === void 0 || text$1(value.type) !== "tool-result") continue;
84747
85673
  const body = this.toolResultText(value.content);
84748
85674
  const marker = DELIVERY_FAILURE_MARKERS.find((candidate) => body.includes(candidate));
84749
85675
  if (marker === void 0) continue;
84750
- this.line(`child-send-failed ${sessionTag(session)} call=${text(value.toolCallId) ?? "?"} marker=${JSON.stringify(marker)} excerpt=${JSON.stringify(oneLine(body, EXCERPT_MAX))}`);
85676
+ this.line(`child-send-failed ${sessionTag(session)} call=${text$1(value.toolCallId) ?? "?"} marker=${JSON.stringify(marker)} excerpt=${JSON.stringify(oneLine(body, EXCERPT_MAX))}`);
84751
85677
  }
84752
85678
  }
84753
85679
  toolResultText(content) {
84754
85680
  if (!Array.isArray(content)) return "";
84755
85681
  const parts = [];
84756
85682
  for (const block of content) {
84757
- const body = text(record(block)?.text);
85683
+ const body = text$1(record$1(block)?.text);
84758
85684
  if (body !== void 0) parts.push(body);
84759
85685
  }
84760
85686
  return parts.join(" ");
84761
85687
  }
84762
85688
  observedRequest(data) {
84763
- const config = record(record(data.header)?.config);
85689
+ const config = record$1(record$1(data.header)?.config);
84764
85690
  if (config === void 0) return;
84765
85691
  const series = data.startsSeries === true ? " startsSeries=yes" : "";
84766
- this.line(`request provider=${text(config.provider) ?? "?"} model=${text(config.model) ?? "?"} reason=${text(data.reason) ?? "-"}${series}`);
85692
+ this.line(`request provider=${text$1(config.provider) ?? "?"} model=${text$1(config.model) ?? "?"} reason=${text$1(data.reason) ?? "-"}${series}`);
84767
85693
  }
84768
85694
  turnReason(reason) {
84769
- const value = record(reason);
85695
+ const value = record$1(reason);
84770
85696
  if (value === void 0) return oneLine(String(reason ?? "?"), SUMMARY_MAX);
84771
- const kind = text(value.kind) ?? "?";
84772
- const failure = record(value.error);
85697
+ const kind = text$1(value.kind) ?? "?";
85698
+ const failure = record$1(value.error);
84773
85699
  if (failure === void 0) return kind;
84774
- const code = text(failure.code) ?? "?";
84775
- const message = text(failure.message);
85700
+ const code = text$1(failure.code) ?? "?";
85701
+ const message = text$1(failure.message);
84776
85702
  return message === void 0 ? `${kind} code=${code}` : `${kind} code=${code} message=${JSON.stringify(oneLine(message, SUMMARY_MAX))}`;
84777
85703
  }
84778
85704
  formTag(source) {
84779
85705
  if (source === void 0) return "";
84780
- const form = text(source.form);
84781
- const summary = text(source.summary);
84782
- const plugin = text(source.plugin);
85706
+ const form = text$1(source.form);
85707
+ const summary = text$1(source.summary);
85708
+ const plugin = text$1(source.plugin);
84783
85709
  return `${form === void 0 ? "" : ` form=${form}`}${plugin === void 0 ? "" : ` plugin=${plugin}`}${summary === void 0 ? "" : ` summary=${JSON.stringify(oneLine(summary, SUMMARY_MAX))}`}`;
84784
85710
  }
84785
85711
  /** Live snapshot of the Agent behind this Session, when it is resident. */
84786
85712
  agentState(session) {
84787
85713
  const agent = this.options.agents?.get(session.id);
84788
85714
  if (agent === void 0) return `${sessionTag(session)} resident=no`;
84789
- const status = text(agent.status) ?? "?";
85715
+ const status = text$1(agent.status) ?? "?";
84790
85716
  return `${sessionTag(session)} resident=yes status=${status} pending=${agent.inbox === void 0 ? "?" : `nextStep:${agent.inbox.nextStep?.length ?? 0},nextTurn:${agent.inbox.nextTurn?.length ?? 0}`}`;
84791
85717
  }
84792
85718
  pending(session) {
@@ -85211,7 +86137,7 @@ function asRegistry$1(value) {
85211
86137
  * Both reads are guarded: a host without the reader, and one without the
85212
86138
  * service, must leave this plugin loadable rather than failing its import.
85213
86139
  */
85214
- function agentRegistry$1(ctx) {
86140
+ function agentRegistry$2(ctx) {
85215
86141
  try {
85216
86142
  const registry = asRegistry$1(typeof ctx.get === "function" ? ctx.get("agents") : void 0);
85217
86143
  if (registry !== void 0) return registry;
@@ -85290,7 +86216,7 @@ function installReasoningCollapseGuard(ctx, options = {}) {
85290
86216
  function resolveAgent(sessionId) {
85291
86217
  if (sessionId === void 0) return void 0;
85292
86218
  try {
85293
- const registered = agentRegistry$1(ctx)?.get(sessionId);
86219
+ const registered = agentRegistry$2(ctx)?.get(sessionId);
85294
86220
  if (registered !== void 0 && registered !== null) return registered;
85295
86221
  } catch {}
85296
86222
  return known.get(sessionId);
@@ -85678,7 +86604,7 @@ function asRegistry(value) {
85678
86604
  return value !== null && typeof value === "object" && typeof value.get === "function" ? value : void 0;
85679
86605
  }
85680
86606
  /** The host's Agent registry, read defensively through both available routes. */
85681
- function agentRegistry(ctx) {
86607
+ function agentRegistry$1(ctx) {
85682
86608
  try {
85683
86609
  const registry = asRegistry(typeof ctx.get === "function" ? ctx.get("agents") : void 0);
85684
86610
  if (registry !== void 0) return registry;
@@ -85748,7 +86674,7 @@ function installNoAnswerResume(ctx, options = {}) {
85748
86674
  function resolveAgent(sessionId) {
85749
86675
  if (sessionId === void 0) return void 0;
85750
86676
  try {
85751
- const registered = agentRegistry(ctx)?.get(sessionId);
86677
+ const registered = agentRegistry$1(ctx)?.get(sessionId);
85752
86678
  if (registered !== void 0 && registered !== null) return registered;
85753
86679
  } catch {}
85754
86680
  }
@@ -86575,11 +87501,13 @@ function installCacheWarmup(ctx, options) {
86575
87501
  *
86576
87502
  * 决策取舍写在代码里,因为它全是「什么时候**不动**」:
86577
87503
  * - 不是 `accept`(`block` 的 feedback 是别人的东西);
86578
- * - 已经带 `value`(执行本地的 canonical 值,replay 与结构化消费方读它,
86579
- * 替换掉等于把一份 JSON 换成字符串);
86580
87504
  * - 工具名不是 `run_code`、线路不是本线、会话拿不到;
86581
87505
  * - 没有存储、内容为空。
86582
87506
  * 上面任何一条成立就原样返回同一个决策对象。
87507
+ *
87508
+ * 决策带了 `value` 不再是「不动」的理由:核心没真正生成预览时,末尾仍然
87509
+ * 原样交回同一个决策对象(`value` 一点不丢);只有核心真的换了内容,
87510
+ * 才换成带预览的 `accept` 决策。
86583
87511
  * @param exec - 工具执行上下文。
86584
87512
  * @param result - 归一化后的执行结果。
86585
87513
  * @param decision - `next()` 交回来的决策。
@@ -86597,7 +87525,6 @@ function installCacheWarmup(ctx, options) {
86597
87525
  const navigation = retrievalTool || ptcRetrieval ? cacheWarmupNavigationIntentOf(exec.agent) : void 0;
86598
87526
  if (retrievalTool && navigation !== true) return decision;
86599
87527
  if (sourceTool && !settings.enabled && navigation !== true) return decision;
86600
- if (sourceTool && decision.value !== void 0 && navigation !== true) return decision;
86601
87528
  const route = cacheWarmupRouteOf(exec.agent);
86602
87529
  if (route.provider !== "kimi-code") return decision;
86603
87530
  const sessionId = route.sessionId;
@@ -86672,14 +87599,292 @@ function blocksOf(content) {
86672
87599
  }];
86673
87600
  return content;
86674
87601
  }
86675
- /**
86676
- * 本文件刻意**不保留**任何"把异常压成一句话"的辅助函数。
86677
- *
86678
- * 这里曾经有一个 `describe(error)`,把 `error.message` 截断后拼进宿主日志。它看上去
86679
- * 无害(只取第一行、还截了长度),实际上是一条原文出口:异常消息里常常带着绝对
86680
- * 路径、命令行片段、凭据,或者被读取结果的一段正文。三处降级日志现在统一走
86681
- * {@link cacheWarmupErrorLabel}——只输出稳定的 name/code。
86682
- */
87602
+ /** Opt-in switch for one bounded warmup during a delegation batch. */
87603
+ const CACHE_WAIT_WARMUP_ENV = "DSH_KIMI_CODE_CACHE_WAIT_WARMUP";
87604
+ const MAX_TRACKED_SESSIONS = 256;
87605
+ /** Pure cache-wait policy; no child deadline or forced progress is involved. */
87606
+ function evaluateCacheWait(input) {
87607
+ if (!input.waiting || !input.hasChildren || input.lastActiveAt === void 0) return { kind: "none" };
87608
+ if (!Number.isFinite(input.lastActiveAt) || !Number.isFinite(input.now)) return { kind: "none" };
87609
+ const decisionMs = input.decisionMs ?? 12e5;
87610
+ const ttlMs = input.ttlMs ?? 15e5;
87611
+ const minWarmupTokens = input.minWarmupTokens ?? 1e5;
87612
+ const idleMs = Math.max(0, input.now - input.lastActiveAt);
87613
+ if (idleMs >= ttlMs) return { kind: "cold" };
87614
+ if (idleMs < decisionMs) return {
87615
+ kind: "schedule",
87616
+ delayMs: decisionMs - idleMs
87617
+ };
87618
+ if (input.warmupEnabled && !input.warmupUsed && input.totalTokens !== void 0 && Number.isFinite(input.totalTokens) && input.totalTokens >= minWarmupTokens) return { kind: "warmup" };
87619
+ return { kind: "cold" };
87620
+ }
87621
+ /** The one opt-in message, built through the generation-safe harness factory. */
87622
+ function createCacheWaitWarmupMessage() {
87623
+ return createUserMessage$1({
87624
+ content: [{
87625
+ type: "text",
87626
+ text: "Automatic cache maintenance while delegated work continues. This is not a new task and must not change priorities or cause tool calls. Reply with one brief acknowledgment, then continue waiting for existing results. Do not request or manufacture progress from any subagent."
87627
+ }],
87628
+ source: {
87629
+ kind: PLUGIN_MESSAGE_SOURCE_KIND,
87630
+ form: "notice",
87631
+ summary: boundContextSummary("Cache maintenance only; delegated work continues unchanged.")
87632
+ }
87633
+ });
87634
+ }
87635
+ /** Whether the deployment explicitly opted into bounded warmup. */
87636
+ function cacheWaitWarmupEnabled(env = process.env) {
87637
+ const raw = env[CACHE_WAIT_WARMUP_ENV]?.trim().toLowerCase();
87638
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
87639
+ }
87640
+ function record(value) {
87641
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
87642
+ }
87643
+ function text(value) {
87644
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
87645
+ }
87646
+ function defaultTimer() {
87647
+ return {
87648
+ set(callback, delayMs) {
87649
+ const handle = setTimeout(callback, delayMs);
87650
+ handle.unref?.();
87651
+ return handle;
87652
+ },
87653
+ clear(handle) {
87654
+ clearTimeout(handle);
87655
+ }
87656
+ };
87657
+ }
87658
+ function defaultActivity(sessionId) {
87659
+ const durable = sessionCacheState.read(sessionId);
87660
+ const lastActiveAt = sessionLastActiveAt(sessionId) ?? durable?.lastActiveAt;
87661
+ if (lastActiveAt === void 0) return void 0;
87662
+ return {
87663
+ lastActiveAt,
87664
+ totalTokens: durable?.totalTokens
87665
+ };
87666
+ }
87667
+ function agentRegistry(ctx) {
87668
+ try {
87669
+ const value = ctx.get?.("agents") ?? ctx.agents;
87670
+ if (value !== null && typeof value === "object" && typeof value.get === "function") return value;
87671
+ } catch {}
87672
+ }
87673
+ /** Host-side cache-wait state machine. It never sends anything to a child. */
87674
+ var KimiCacheWaitController = class {
87675
+ options;
87676
+ states = /* @__PURE__ */ new Map();
87677
+ parentByChild = /* @__PURE__ */ new Map();
87678
+ providerBySession = /* @__PURE__ */ new Map();
87679
+ now;
87680
+ timer;
87681
+ agents;
87682
+ activity;
87683
+ kimiSession;
87684
+ warmup;
87685
+ logger;
87686
+ decisionMs;
87687
+ ttlMs;
87688
+ minWarmupTokens;
87689
+ disposed = false;
87690
+ constructor(options = {}) {
87691
+ this.options = options;
87692
+ this.now = options.now ?? (() => Date.now());
87693
+ this.timer = options.timer ?? defaultTimer();
87694
+ this.agents = options.agents;
87695
+ this.activity = options.activity ?? defaultActivity;
87696
+ this.kimiSession = options.kimiSession;
87697
+ this.warmup = options.warmup === true;
87698
+ this.logger = options.logger;
87699
+ this.decisionMs = options.decisionMs ?? 12e5;
87700
+ this.ttlMs = options.ttlMs ?? 15e5;
87701
+ this.minWarmupTokens = options.minWarmupTokens ?? 1e5;
87702
+ }
87703
+ /** Observe one durable Session event. */
87704
+ observe(session, event) {
87705
+ if (this.disposed) return;
87706
+ try {
87707
+ const type = text(event.type);
87708
+ if (type === void 0) return;
87709
+ const data = record(event.data);
87710
+ if (type === "request/header") {
87711
+ const provider = text(record(record(data?.header)?.config)?.provider);
87712
+ if (provider !== void 0) this.providerBySession.set(session.id, provider);
87713
+ return;
87714
+ }
87715
+ if (type === "subagent/catalog") {
87716
+ const childId = text(data?.childId);
87717
+ if (childId === void 0 || childId === session.id) return;
87718
+ const state = this.stateFor(session.id);
87719
+ const newChild = !state.children.has(childId);
87720
+ state.children.add(childId);
87721
+ this.parentByChild.set(childId, session.id);
87722
+ if (newChild) {
87723
+ state.warmupUsed = false;
87724
+ state.handled = false;
87725
+ }
87726
+ this.reconcile(session, state);
87727
+ return;
87728
+ }
87729
+ if (type === "subagent/end") {
87730
+ this.removeChild(text(data?.id));
87731
+ return;
87732
+ }
87733
+ if (type === "user/message") {
87734
+ const source = record(record(data?.message)?.source);
87735
+ if (text(source?.kind) === "subagent-settled") this.removeChild(text(source?.senderSessionId));
87736
+ return;
87737
+ }
87738
+ const state = this.stateFor(session.id);
87739
+ if (type === "turn/start") {
87740
+ state.waiting = false;
87741
+ this.clearTimer(state);
87742
+ return;
87743
+ }
87744
+ if (type === "turn/end") {
87745
+ state.waiting = true;
87746
+ state.handled = false;
87747
+ this.reconcile(session, state);
87748
+ }
87749
+ } catch (error) {
87750
+ this.logger?.warn(`kimi-cache-wait: ignored an invalid event: ${error instanceof Error ? error.message : String(error)}`);
87751
+ }
87752
+ }
87753
+ /** Forget a disposed Agent and any parent/child links it owned. */
87754
+ disposeAgent(agent) {
87755
+ const id = text(agent.id);
87756
+ if (id === void 0) return;
87757
+ this.dropState(id);
87758
+ this.removeChild(id);
87759
+ this.providerBySession.delete(id);
87760
+ }
87761
+ /** Test/diagnostic view; no mutable state escapes. */
87762
+ snapshot(sessionId) {
87763
+ const state = this.states.get(sessionId);
87764
+ return {
87765
+ waiting: state?.waiting ?? false,
87766
+ childCount: state?.children.size ?? 0,
87767
+ handled: state?.handled ?? false,
87768
+ warmupUsed: state?.warmupUsed ?? false,
87769
+ scheduled: state?.timer !== void 0,
87770
+ ...state?.lastDecision === void 0 ? {} : { lastDecision: state.lastDecision }
87771
+ };
87772
+ }
87773
+ dispose() {
87774
+ if (this.disposed) return;
87775
+ this.disposed = true;
87776
+ for (const state of this.states.values()) this.clearTimer(state);
87777
+ this.states.clear();
87778
+ this.parentByChild.clear();
87779
+ this.providerBySession.clear();
87780
+ }
87781
+ stateFor(sessionId) {
87782
+ const existing = this.states.get(sessionId);
87783
+ if (existing !== void 0) {
87784
+ this.states.delete(sessionId);
87785
+ this.states.set(sessionId, existing);
87786
+ return existing;
87787
+ }
87788
+ const state = {
87789
+ children: /* @__PURE__ */ new Set(),
87790
+ waiting: false,
87791
+ handled: false,
87792
+ warmupUsed: false
87793
+ };
87794
+ this.states.set(sessionId, state);
87795
+ if (this.states.size > MAX_TRACKED_SESSIONS) {
87796
+ const oldest = this.states.keys().next();
87797
+ if (oldest.done !== true) this.dropState(oldest.value);
87798
+ }
87799
+ return state;
87800
+ }
87801
+ dropState(sessionId) {
87802
+ const state = this.states.get(sessionId);
87803
+ if (state === void 0) return;
87804
+ this.clearTimer(state);
87805
+ for (const child of state.children) this.parentByChild.delete(child);
87806
+ this.states.delete(sessionId);
87807
+ }
87808
+ removeChild(childId) {
87809
+ if (childId === void 0) return;
87810
+ const parent = this.parentByChild.get(childId);
87811
+ if (parent === void 0) return;
87812
+ this.parentByChild.delete(childId);
87813
+ const state = this.states.get(parent);
87814
+ if (state === void 0) return;
87815
+ state.children.delete(childId);
87816
+ if (state.children.size === 0) this.clearTimer(state);
87817
+ }
87818
+ clearTimer(state) {
87819
+ if (state.timer === void 0) return;
87820
+ this.timer.clear(state.timer);
87821
+ state.timer = void 0;
87822
+ }
87823
+ isKimiSession(session) {
87824
+ if (this.kimiSession !== void 0) return this.kimiSession(session);
87825
+ try {
87826
+ const current = text(session.requestHeader?.()?.config?.provider);
87827
+ if (current !== void 0) return current === "kimi-code";
87828
+ } catch {}
87829
+ const latest = this.providerBySession.get(session.id);
87830
+ if (latest !== void 0) return latest === "kimi-code";
87831
+ return this.activity(session.id)?.lastActiveAt !== void 0;
87832
+ }
87833
+ reconcile(session, state) {
87834
+ this.clearTimer(state);
87835
+ if (this.disposed || !state.waiting || state.handled || state.children.size === 0) return;
87836
+ if (!this.isKimiSession(session)) return;
87837
+ const activity = this.activity(session.id);
87838
+ if (activity === void 0) return;
87839
+ const action = evaluateCacheWait({
87840
+ waiting: state.waiting,
87841
+ hasChildren: state.children.size > 0,
87842
+ now: this.now(),
87843
+ lastActiveAt: activity.lastActiveAt,
87844
+ totalTokens: activity.totalTokens,
87845
+ warmupEnabled: this.warmup,
87846
+ warmupUsed: state.warmupUsed,
87847
+ decisionMs: this.decisionMs,
87848
+ ttlMs: this.ttlMs,
87849
+ minWarmupTokens: this.minWarmupTokens
87850
+ });
87851
+ if (action.kind === "none") return;
87852
+ if (action.kind === "schedule") {
87853
+ state.timer = this.timer.set(() => {
87854
+ state.timer = void 0;
87855
+ this.reconcile(session, state);
87856
+ }, action.delayMs);
87857
+ return;
87858
+ }
87859
+ state.handled = true;
87860
+ state.lastDecision = action.kind;
87861
+ if (action.kind === "cold") return;
87862
+ state.warmupUsed = true;
87863
+ try {
87864
+ const agent = this.agents?.get(session.id);
87865
+ if (agent?.steer === void 0) throw new Error("the parent Agent is not available for warmup");
87866
+ agent.steer(createCacheWaitWarmupMessage());
87867
+ this.logger?.info?.(`kimi-cache-wait: queued one bounded warmup for session ${session.id}`);
87868
+ } catch (error) {
87869
+ this.logger?.warn(`kimi-cache-wait: warmup skipped for session ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
87870
+ }
87871
+ }
87872
+ };
87873
+ /** Install the host listener. Older hosts without the event bus remain inert. */
87874
+ function installKimiCacheWait(ctx, options = {}) {
87875
+ if (typeof ctx.on !== "function") return void 0;
87876
+ const agents = options.agents ?? agentRegistry(ctx);
87877
+ const controller = new KimiCacheWaitController({
87878
+ ...options,
87879
+ agents
87880
+ });
87881
+ const disposers = [ctx.on("session/event", (session, event) => controller.observe(session, event)), ctx.on("agent/disposed", ({ agent }) => controller.disposeAgent(agent))];
87882
+ return () => {
87883
+ controller.dispose();
87884
+ for (const handle of disposers) if (typeof handle === "function") handle();
87885
+ else handle?.dispose?.();
87886
+ };
87887
+ }
86683
87888
  //#endregion
86684
87889
  //#region src/host/cache-warmup/settings.ts
86685
87890
  /**
@@ -94847,6 +96052,14 @@ function apply(ctx, pluginConfig = {}) {
94847
96052
  };
94848
96053
  }
94849
96054
  }), "dsh-chatgpt-subscription: cache warmup");
96055
+ ctx.effect(() => {
96056
+ const dispose = installKimiCacheWait(ctx, { warmup: cacheWaitWarmupEnabled(process.env) });
96057
+ if (dispose === void 0) {
96058
+ ctx.logger.warn("[dsh-chatgpt-subscription] Kimi cache wait skipped: this harness exposes no event bus");
96059
+ return () => void 0;
96060
+ }
96061
+ return dispose;
96062
+ }, "dsh-chatgpt-subscription: Kimi cache wait");
94850
96063
  const probeEnvFile = relayProbeEnvFile();
94851
96064
  if (relayProbeEnabled(process.env, { envFile: probeEnvFile })) {
94852
96065
  const probePath = relayProbeLogPath(process.env, { envFile: probeEnvFile });