@eddyskywalker/dsh-chatgpt-subscription 0.60.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/lib/client.js.map +1 -1
  4. package/lib/codemode.js +319 -89
  5. package/lib/entry-plan-mode-CLRKkgYG.js +1787 -0
  6. package/lib/{entry-progress-updates-CxQewio9.js → entry-progress-updates-DZ0ZRus4.js} +14 -1
  7. package/lib/index.js +975 -576
  8. package/lib/plan-mode.js +2 -779
  9. package/lib/progress-updates.js +1 -1
  10. package/lib/types/compat.d.ts +1 -1
  11. package/lib/types/entry-codemode.d.ts.map +1 -1
  12. package/lib/types/entry-plan-mode.d.ts +136 -0
  13. package/lib/types/entry-plan-mode.d.ts.map +1 -1
  14. package/lib/types/host/agent-preset.d.ts.map +1 -1
  15. package/lib/types/host/claude/adapter.d.ts +16 -0
  16. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  17. package/lib/types/host/claude/client.d.ts +7 -1
  18. package/lib/types/host/claude/client.d.ts.map +1 -1
  19. package/lib/types/host/claude/mapper.d.ts +9 -0
  20. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  21. package/lib/types/host/claude/types.d.ts +10 -0
  22. package/lib/types/host/claude/types.d.ts.map +1 -1
  23. package/lib/types/host/cline-pass/mapper.d.ts.map +1 -1
  24. package/lib/types/host/codemode/discovery.d.ts.map +1 -1
  25. package/lib/types/host/codemode/rules.d.ts.map +1 -1
  26. package/lib/types/host/codemode/transport.d.ts +38 -0
  27. package/lib/types/host/codemode/transport.d.ts.map +1 -1
  28. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  29. package/lib/types/host/common/orphan-tool-calls.d.ts +48 -0
  30. package/lib/types/host/common/orphan-tool-calls.d.ts.map +1 -0
  31. package/lib/types/host/dispatch-execution-guard.d.ts +32 -2
  32. package/lib/types/host/dispatch-execution-guard.d.ts.map +1 -1
  33. package/lib/types/host/glm-coding-plan/mapper-anthropic.d.ts.map +1 -1
  34. package/lib/types/host/glm-coding-plan/mapper-openai.d.ts.map +1 -1
  35. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  36. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  37. package/lib/types/host/model-catalog.d.ts +21 -0
  38. package/lib/types/host/model-catalog.d.ts.map +1 -1
  39. package/lib/types/host/opendesign/mapper.d.ts.map +1 -1
  40. package/lib/types/host/opendesign/types.d.ts +7 -2
  41. package/lib/types/host/opendesign/types.d.ts.map +1 -1
  42. package/lib/types/host/plan-mode-cache-guard.d.ts.map +1 -1
  43. package/lib/types/host/progress-updates/nudge.d.ts +13 -1
  44. package/lib/types/host/progress-updates/nudge.d.ts.map +1 -1
  45. package/lib/types/host/qoder/mapper.d.ts.map +1 -1
  46. package/lib/types/host/responses-client.d.ts.map +1 -1
  47. package/lib/types/host/responses-mapper.d.ts +12 -0
  48. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  49. package/lib/types/host/settled-job-guard.d.ts +77 -0
  50. package/lib/types/host/settled-job-guard.d.ts.map +1 -0
  51. package/lib/types/host/step-code/mapper.d.ts.map +1 -1
  52. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  53. package/lib/types/index.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/presets/dispatch/README.md +7 -10
  56. package/presets/dispatch/agent.cordis.yml +4 -11
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { a as resolveProgressUpdatesSettings, n as attachProgressSettings } from "./entry-progress-updates-CxQewio9.js";
1
+ import { a as resolveProgressUpdatesSettings, n as attachProgressSettings } from "./entry-progress-updates-DZ0ZRus4.js";
2
2
  import { attachPlanModePipelineSettings, resolvePlanModePipelineEnabled } from "./plan-mode-pipeline.js";
3
3
  import { n as PLUGIN_MESSAGE_SOURCE_KIND, r as normalizeGenerateOptions, t as LlmError$1 } from "./llm-compat-u9mor0uu.js";
4
4
  import { DEFAULT_SKIP_PROVIDERS, PRUNER_SERVICE_NAME, resolveSkipProviders, shouldSkipPruningForSession } from "./tool-result-pruner.js";
5
- import { PLAN_PROJECTION_KEY, createPlanNotice } from "./plan-mode.js";
5
+ import { b as installDispatchExecutionGuard, h as installPlanModeExitReminder, l as PLAN_PROJECTION_KEY, p as createPlanNotice, v as registeredPlanControllerActive, x as resolveDispatchExecutionGuardEnabled } from "./entry-plan-mode-CLRKkgYG.js";
6
6
  import { createRequire } from "node:module";
7
7
  import * as LlmModule from "@deepseek-ai/dsh-llm";
8
8
  import { EMPTY_RESPONSE_CODE, HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, ToolCallId, attributionHeaders, createUserMessage, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
@@ -808,7 +808,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
808
808
  * `test/responses-client.test.ts` asserts the two agree, so a release that
809
809
  * forgets this constant fails the suite instead of shipping quietly.
810
810
  */
811
- const PLUGIN_VERSION = "0.60.0";
811
+ const PLUGIN_VERSION = "0.62.0";
812
812
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
813
813
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
814
814
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
@@ -1193,6 +1193,7 @@ function resolveCodexModel(model, preferences, live) {
1193
1193
  inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
1194
1194
  context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
1195
1195
  ...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
1196
+ ...codexToolUpdateMode() === void 0 ? {} : { toolUpdate: codexToolUpdateMode() },
1196
1197
  reasoning: {
1197
1198
  efforts: orderReasoningEfforts(efforts, GPT_56_REASONING_EFFORTS).map((effort) => ({
1198
1199
  id: ReasoningEffortId(effort),
@@ -1203,6 +1204,46 @@ function resolveCodexModel(model, preferences, live) {
1203
1204
  };
1204
1205
  }
1205
1206
  /**
1207
+ * Whether this line may state a tool change in the history instead of rewriting
1208
+ * the head.
1209
+ *
1210
+ * `in-history` rather than `addition-only`: this wire has an `additional_tools`
1211
+ * item to add a tool but nothing to withdraw one, so a removal has to keep its
1212
+ * declaration and travel as a note. Deleting the declaration would shorten the
1213
+ * head and throw the cached prefix away, which is the cost this mode exists to
1214
+ * avoid.
1215
+ *
1216
+ * `DSH_CODEX_TOOL_UPDATE=0` turns it off for one account without a code change,
1217
+ * the same escape hatch kimi-code carries for its own gate.
1218
+ *
1219
+ * @returns the mode to declare, or undefined when every request must restate the
1220
+ * complete tool list.
1221
+ */
1222
+ function codexToolUpdateMode() {
1223
+ if (toolUpdateRejected) return void 0;
1224
+ const raw = (process.env.DSH_CODEX_TOOL_UPDATE ?? "").trim().toLowerCase();
1225
+ if (raw !== "" && [
1226
+ "0",
1227
+ "false",
1228
+ "no",
1229
+ "off",
1230
+ "none"
1231
+ ].includes(raw)) return void 0;
1232
+ return "in-history";
1233
+ }
1234
+ /**
1235
+ * Set when a request that carried a tool change was refused with a 400.
1236
+ *
1237
+ * Process-wide and one-way: the failure mode of a stale probe is a lost cache
1238
+ * optimisation, and the failure mode of NOT latching is the same 400 on every
1239
+ * later turn. The asymmetry decides it.
1240
+ */
1241
+ let toolUpdateRejected = false;
1242
+ /** Record a refusal, so every later request restates the complete tool list. */
1243
+ function disableCodexToolUpdate() {
1244
+ toolUpdateRejected = true;
1245
+ }
1246
+ /**
1206
1247
  * Resolve the wire capabilities of one model.
1207
1248
  *
1208
1249
  * The live listing outranks the shipped table, because the backend is the
@@ -1753,7 +1794,7 @@ function parseListing(payload) {
1753
1794
  function parseListingEntry$1(value) {
1754
1795
  const record = asRecord$20(value);
1755
1796
  if (record === void 0) return [];
1756
- const id = asString$30(record.slug) ?? asString$30(record.id);
1797
+ const id = asString$31(record.slug) ?? asString$31(record.id);
1757
1798
  if (id === void 0) return [];
1758
1799
  const contextWindow = asNumber$8(record.context_window) ?? asNumber$8(record.contextWindow);
1759
1800
  const modalities = parseModalities(record.input_modalities);
@@ -1762,11 +1803,11 @@ function parseListingEntry$1(value) {
1762
1803
  const fallback = CODEX_MODEL_CATALOG.find((entry) => entry.id === id);
1763
1804
  return [{
1764
1805
  id,
1765
- name: asString$30(record.display_name) ?? asString$30(record.displayName) ?? fallback?.name ?? id,
1806
+ name: asString$31(record.display_name) ?? asString$31(record.displayName) ?? fallback?.name ?? id,
1766
1807
  contextWindow: contextWindow !== void 0 && contextWindow > 0 ? contextWindow : null,
1767
1808
  inputModalities: modalities ?? (fallback ? [...fallback.inputModalities] : ["text"]),
1768
1809
  ...efforts !== null ? { reasoningEfforts: efforts } : {},
1769
- defaultReasoningEffort: asString$30(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null,
1810
+ defaultReasoningEffort: asString$31(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null,
1770
1811
  ...useResponsesLite !== void 0 ? { useResponsesLite } : {}
1771
1812
  }];
1772
1813
  }
@@ -1774,7 +1815,7 @@ function parseModalities(value) {
1774
1815
  if (!Array.isArray(value)) return null;
1775
1816
  const out = [];
1776
1817
  for (const item of value) {
1777
- const name = asString$30(item);
1818
+ const name = asString$31(item);
1778
1819
  if (name === "text" || name === "image") out.push(name);
1779
1820
  }
1780
1821
  return out.length > 0 ? out : null;
@@ -1789,7 +1830,7 @@ function parseEfforts(value) {
1789
1830
  if (!Array.isArray(value)) return null;
1790
1831
  const out = [];
1791
1832
  for (const item of value) {
1792
- const effort = asString$30(item) ?? asString$30(asRecord$20(item)?.effort);
1833
+ const effort = asString$31(item) ?? asString$31(asRecord$20(item)?.effort);
1793
1834
  if (effort !== void 0 && !out.includes(effort)) out.push(effort);
1794
1835
  }
1795
1836
  return out.length > 0 ? out : null;
@@ -1814,7 +1855,7 @@ function accountKeyFor$1(credentials) {
1814
1855
  function asRecord$20(value) {
1815
1856
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1816
1857
  }
1817
- function asString$30(value) {
1858
+ function asString$31(value) {
1818
1859
  return typeof value === "string" && value !== "" ? value : void 0;
1819
1860
  }
1820
1861
  function asNumber$8(value) {
@@ -8178,7 +8219,7 @@ function sanitizeText$9(text) {
8178
8219
  function isRecord$64(value) {
8179
8220
  return typeof value === "object" && value !== null && !Array.isArray(value);
8180
8221
  }
8181
- function asString$29(value) {
8222
+ function asString$30(value) {
8182
8223
  return typeof value === "string" ? value : void 0;
8183
8224
  }
8184
8225
  function safeJsonParse$9(text) {
@@ -8208,7 +8249,7 @@ function attachmentOf$5(block) {
8208
8249
  }
8209
8250
  function attachmentLabel$6(block) {
8210
8251
  const attachment = isRecord$64(block.attachment) ? block.attachment : void 0;
8211
- return asString$29(attachment?.name) || asString$29(attachment?.attachmentId);
8252
+ return asString$30(attachment?.name) || asString$30(attachment?.attachmentId);
8212
8253
  }
8213
8254
  function collectImageRefs$3(content, refs) {
8214
8255
  if (!Array.isArray(content)) return;
@@ -8345,10 +8386,10 @@ function unavailableImageText$6(block) {
8345
8386
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
8346
8387
  }
8347
8388
  function imageBlockToPart(block, images) {
8348
- let data = asString$29(block.data) || asString$29(block.base64);
8389
+ let data = asString$30(block.data) || asString$30(block.base64);
8349
8390
  const source = isRecord$64(block.source) ? block.source : void 0;
8350
- if (!data && source) data = asString$29(source.data) || asString$29(source.base64);
8351
- let mimeType = asString$29(block.mimeType) || asString$29(block.mediaType) || (source ? asString$29(source.mimeType) || asString$29(source.mediaType) : void 0) || "image/png";
8391
+ if (!data && source) data = asString$30(source.data) || asString$30(source.base64);
8392
+ let mimeType = asString$30(block.mimeType) || asString$30(block.mediaType) || (source ? asString$30(source.mimeType) || asString$30(source.mediaType) : void 0) || "image/png";
8352
8393
  if (data?.startsWith("data:")) {
8353
8394
  const match = data.match(/^data:([^;,]+);base64,(.*)$/s);
8354
8395
  if (match) {
@@ -8422,7 +8463,7 @@ function replayBlockFor$1(message, index) {
8422
8463
  }
8423
8464
  }
8424
8465
  function thoughtSignature(part) {
8425
- return asString$29(part?.thoughtSignature) || asString$29(part?.thought_signature) || asString$29(part?.thinkingSignature) || asString$29(part?.textSignature);
8466
+ return asString$30(part?.thoughtSignature) || asString$30(part?.thought_signature) || asString$30(part?.thinkingSignature) || asString$30(part?.textSignature);
8426
8467
  }
8427
8468
  function replayPart(part) {
8428
8469
  const copy = { ...part };
@@ -8521,7 +8562,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
8521
8562
  if (block.type === "reasoning" && runtimeModel.startsWith("claude-") && (message.source?.kind !== "model" || message.source.provider !== "antigravity" || !("model" in message.source) || message.source.model !== model.id)) continue;
8522
8563
  const replay = replayBlockFor$1(message, index);
8523
8564
  const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$64) : [];
8524
- if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$29(part.text) || "").join("") === sanitizeText$9(String(block.text || ""))) {
8565
+ if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$30(part.text) || "").join("") === sanitizeText$9(String(block.text || ""))) {
8525
8566
  parts.push(...originalParts.map(replayPart));
8526
8567
  continue;
8527
8568
  }
@@ -8542,7 +8583,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
8542
8583
  const toolId = String(block.id || "");
8543
8584
  const toolName = String(block.name || "");
8544
8585
  const originalCall = originalParts.find((part) => isRecord$64(part.functionCall));
8545
- const wireId = asString$29((isRecord$64(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
8586
+ const wireId = asString$30((isRecord$64(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
8546
8587
  toolCalls.set(toolId, {
8547
8588
  name: toolName,
8548
8589
  id: wireId
@@ -8787,7 +8828,7 @@ function processStreamLine$4(line, state) {
8787
8828
  if (!isRecord$64(chunk)) return [];
8788
8829
  const responseData = isRecord$64(chunk.response) ? chunk.response : chunk;
8789
8830
  const error = isRecord$64(chunk.error) ? chunk.error : isRecord$64(responseData.error) ? responseData.error : void 0;
8790
- if (error !== void 0) throw new LlmError$1(`Antigravity stream error: ${asString$29(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
8831
+ if (error !== void 0) throw new LlmError$1(`Antigravity stream error: ${asString$30(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
8791
8832
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
8792
8833
  const candidate = isRecord$64(candidates[0]) ? candidates[0] : void 0;
8793
8834
  const content = isRecord$64(candidate?.content) ? candidate.content : void 0;
@@ -8853,8 +8894,8 @@ function processStreamLine$4(line, state) {
8853
8894
  if (isRecord$64(part.functionCall)) {
8854
8895
  out.push(...closeCurrentBlock(state));
8855
8896
  const fc = part.functionCall;
8856
- const toolName = asString$29(fc.name) || "";
8857
- const toolId = asString$29(fc.id) || sanitizeToolCallId("", toolName);
8897
+ const toolName = asString$30(fc.name) || "";
8898
+ const toolId = asString$30(fc.id) || sanitizeToolCallId("", toolName);
8858
8899
  const argsText = JSON.stringify(isRecord$64(fc.args) ? fc.args : {});
8859
8900
  const index = state.blocks.length;
8860
8901
  const block = {
@@ -8892,10 +8933,10 @@ function processStreamLine$4(line, state) {
8892
8933
  }
8893
8934
  collectUsage(chunk.usageMetadata, state);
8894
8935
  if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
8895
- const finishMessage = asString$29(candidate?.finishMessage);
8936
+ const finishMessage = asString$30(candidate?.finishMessage);
8896
8937
  if (finishMessage) state.finishMessage = finishMessage;
8897
- const blockReason = asString$29((isRecord$64(responseData.promptFeedback) ? responseData.promptFeedback : void 0)?.blockReason);
8898
- const finishReason = asString$29(candidate?.finishReason) || asString$29(responseData.finishReason) || (blockReason ? `PROMPT_BLOCKED:${blockReason}` : void 0);
8938
+ const blockReason = asString$30((isRecord$64(responseData.promptFeedback) ? responseData.promptFeedback : void 0)?.blockReason);
8939
+ const finishReason = asString$30(candidate?.finishReason) || asString$30(responseData.finishReason) || (blockReason ? `PROMPT_BLOCKED:${blockReason}` : void 0);
8899
8940
  if (finishReason) {
8900
8941
  state.finishReason = finishReason;
8901
8942
  out.push(...closeCurrentBlock(state));
@@ -8970,7 +9011,7 @@ function closeStream$7(state) {
8970
9011
  function isRecord$63(value) {
8971
9012
  return typeof value === "object" && value !== null && !Array.isArray(value);
8972
9013
  }
8973
- function asString$28(value) {
9014
+ function asString$29(value) {
8974
9015
  return typeof value === "string" ? value : void 0;
8975
9016
  }
8976
9017
  /**
@@ -9031,7 +9072,7 @@ function attachmentOf$4(block) {
9031
9072
  }
9032
9073
  function attachmentLabel$5(block) {
9033
9074
  const attachment = isRecord$63(block.attachment) ? block.attachment : void 0;
9034
- return asString$28(attachment?.name) || asString$28(attachment?.attachmentId);
9075
+ return asString$29(attachment?.name) || asString$29(attachment?.attachmentId);
9035
9076
  }
9036
9077
  function collectImageRefs$2(content, refs) {
9037
9078
  if (!Array.isArray(content)) return;
@@ -9065,9 +9106,9 @@ function base64Length$2(bytes) {
9065
9106
  * has no resolved map behaves exactly as before.
9066
9107
  */
9067
9108
  function inlinePayloadOf$1(block) {
9068
- let data = asString$28(block.data) || asString$28(block.base64);
9109
+ let data = asString$29(block.data) || asString$29(block.base64);
9069
9110
  const source = isRecord$63(block.source) ? block.source : void 0;
9070
- if (!data && source) data = asString$28(source.data) || asString$28(source.base64);
9111
+ if (!data && source) data = asString$29(source.data) || asString$29(source.base64);
9071
9112
  if (data?.startsWith("data:")) {
9072
9113
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
9073
9114
  if (matched) data = matched[2] ?? "";
@@ -9530,10 +9571,10 @@ function unavailableImageText$5(block) {
9530
9571
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
9531
9572
  }
9532
9573
  function imageBlockToInline$3(block, images) {
9533
- let data = asString$28(block.data) || asString$28(block.base64);
9574
+ let data = asString$29(block.data) || asString$29(block.base64);
9534
9575
  const source = isRecord$63(block.source) ? block.source : void 0;
9535
- if (!data && source) data = asString$28(source.data) || asString$28(source.base64);
9536
- let mediaType = asString$28(block.mimeType) || asString$28(block.mediaType) || (source ? asString$28(source.mimeType) || asString$28(source.mediaType) : void 0) || "image/png";
9576
+ if (!data && source) data = asString$29(source.data) || asString$29(source.base64);
9577
+ let mediaType = asString$29(block.mimeType) || asString$29(block.mediaType) || (source ? asString$29(source.mimeType) || asString$29(source.mediaType) : void 0) || "image/png";
9537
9578
  if (data?.startsWith("data:")) {
9538
9579
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
9539
9580
  if (matched) {
@@ -13110,6 +13151,21 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
13110
13151
  });
13111
13152
  continue;
13112
13153
  }
13154
+ if (message.role === "developer") {
13155
+ input.push(...toolChangeItems(message, options.tools, wire.idNamespace));
13156
+ const rest = message.content.filter((block) => block.type !== "tool-addition" && block.type !== "tool-removal");
13157
+ if (rest.length > 0) {
13158
+ const content = await mapContent({
13159
+ ...message,
13160
+ content: rest
13161
+ }, attachments, options.signal, localRawImages, localImageStats, resolveLocalRawImages);
13162
+ if (content.length > 0) input.push({
13163
+ role: "developer",
13164
+ content
13165
+ });
13166
+ }
13167
+ continue;
13168
+ }
13113
13169
  const content = await mapContent(message, attachments, options.signal, localRawImages, localImageStats, resolveLocalRawImages);
13114
13170
  if (content.length > 0) input.push({
13115
13171
  role: message.role,
@@ -13120,8 +13176,9 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
13120
13176
  offloadOldestInputImages(input);
13121
13177
  const instructionText = [...instructionParts, localRawImageInstruction(localImageStats)].filter((value) => Boolean(value)).join("\n\n");
13122
13178
  const prefix = [];
13123
- const tools = options.tools ?? [];
13124
- if (useResponsesLite && tools.length > 0) prefix.push(additionalToolsItem(tools, wire.idNamespace));
13179
+ const allTools = options.tools ?? [];
13180
+ const tools = allTools.filter((tool) => tool.deferLoading !== true);
13181
+ if (useResponsesLite && allTools.length > 0) prefix.push(additionalToolsItem(allTools, wire.idNamespace));
13125
13182
  if (instructionText !== "") prefix.push(developerMessageItem(instructionText, wire.idNamespace, useResponsesLite));
13126
13183
  input.unshift(...prefix);
13127
13184
  const toolDeclarations = tools.length > 0 ? tools.map(toolDeclaration) : void 0;
@@ -13193,6 +13250,87 @@ function additionalToolsItem(tools, idNamespace) {
13193
13250
  };
13194
13251
  }
13195
13252
  /**
13253
+ * The input items one mid-conversation tool change becomes.
13254
+ *
13255
+ * An addition travels as an `additional_tools` item at the point in the history
13256
+ * where it happened. The item carries the full definition, so the head never has
13257
+ * to grow it: everything before this position keeps its cached prefix and the
13258
+ * backend reads the new tool from here onward.
13259
+ *
13260
+ * A removal has no wire form of its own — there is no `tool_removal` item — so
13261
+ * the declaration stays exactly where it is and this note tells the model the
13262
+ * tool is gone. Dropping the declaration instead would shorten the head and
13263
+ * throw the prefix away, which is the cost `in-history` exists to avoid.
13264
+ *
13265
+ * Measured on the subscription backend by
13266
+ * `scripts/probe-codex-tool-updates.probe.ts`: the item is accepted, the model
13267
+ * calls a tool that only ever appeared in it, and a padded prefix stays cached
13268
+ * across the addition.
13269
+ *
13270
+ * @param message - the developer message carrying the change.
13271
+ * @param tools - the active declaration set the change resolves against.
13272
+ * @param idNamespace - the conversation identity, for stable item ids.
13273
+ * @returns the items to append at this position, in order.
13274
+ */
13275
+ function toolChangeItems(message, tools, idNamespace) {
13276
+ const byName = new Map((tools ?? []).map((tool) => [tool.name, tool]));
13277
+ const added = [];
13278
+ const removed = [];
13279
+ for (const block of message.content) {
13280
+ const name = typeof block["toolName"] === "string" ? block["toolName"] : "";
13281
+ if (name === "") continue;
13282
+ if (block["type"] === "tool-addition") {
13283
+ const tool = byName.get(name);
13284
+ if (tool !== void 0) added.push(toolDeclaration(tool));
13285
+ } else if (block["type"] === "tool-removal") removed.push(name);
13286
+ }
13287
+ const items = [];
13288
+ if (added.length > 0) items.push({
13289
+ type: "additional_tools",
13290
+ id: stableItemId("at", idNamespace, JSON.stringify(added)),
13291
+ role: "developer",
13292
+ tools: added
13293
+ });
13294
+ if (removed.length > 0) {
13295
+ const text = toolRemovalNotice(removed);
13296
+ items.push({
13297
+ type: "message",
13298
+ id: stableItemId("msg", idNamespace, text),
13299
+ role: "developer",
13300
+ content: [{
13301
+ type: "input_text",
13302
+ text
13303
+ }]
13304
+ });
13305
+ }
13306
+ return items;
13307
+ }
13308
+ /**
13309
+ * The same request with every tool change taken back out.
13310
+ *
13311
+ * This is the shape a backend that refuses the update channel gets: the complete
13312
+ * current declaration list at the head, no change messages, no deferred markers.
13313
+ * It is exactly what the line sent before the capability existed, so the fallback
13314
+ * costs a cache miss and nothing else.
13315
+ *
13316
+ * @param options - the request that was refused.
13317
+ * @returns a request the line can always send.
13318
+ */
13319
+ function withoutCodexToolChanges(options) {
13320
+ const messages = options.messages.filter((message) => !(message.role === "developer" && Array.isArray(message.content) && message.content.some((block) => block.type === "tool-addition" || block.type === "tool-removal")));
13321
+ const tools = options.tools?.map(({ deferLoading: _deferred, ...tool }) => tool);
13322
+ return {
13323
+ ...options,
13324
+ messages,
13325
+ ...tools === void 0 ? {} : { tools }
13326
+ };
13327
+ }
13328
+ /** How a withdrawn tool is stated to the model, imperatively. */
13329
+ function toolRemovalNotice(names) {
13330
+ const list = names.map((name) => `"${name}"`).join(", ");
13331
+ return names.length === 1 ? `[The tool ${list} is no longer available. Do not call it again: any call will fail.]` : `[The tools ${list} are no longer available. Do not call them again: any call will fail.]`;
13332
+ }
13333
+ /**
13196
13334
  * The base instructions as a developer message.
13197
13335
  *
13198
13336
  * The official client marks the item with the `model.base_instructions` kind, so
@@ -13579,6 +13717,8 @@ var ResponsesClient = class {
13579
13717
  const turnKey = turnKeyFor(sessionId);
13580
13718
  let currentModel = options.model;
13581
13719
  let attemptOptions = options;
13720
+ let strippedToolChanges = false;
13721
+ const statesToolChange = (candidate) => candidate.messages.some((message) => message.role === "developer" && Array.isArray(message.content) && message.content.some((block) => block["type"] === "tool-addition" || block["type"] === "tool-removal"));
13582
13722
  let response;
13583
13723
  let release;
13584
13724
  while (true) {
@@ -13604,6 +13744,12 @@ var ResponsesClient = class {
13604
13744
  continue;
13605
13745
  }
13606
13746
  }
13747
+ if (!strippedToolChanges && statesToolChange(attemptOptions) && error.status === 400) {
13748
+ disableCodexToolUpdate();
13749
+ attemptOptions = withoutCodexToolChanges(attemptOptions);
13750
+ strippedToolChanges = true;
13751
+ continue;
13752
+ }
13607
13753
  throw error;
13608
13754
  }
13609
13755
  }
@@ -20868,7 +21014,7 @@ function isRecord$61(value) {
20868
21014
  function asRecord$16(value) {
20869
21015
  return isRecord$61(value) ? value : void 0;
20870
21016
  }
20871
- function asString$27(value) {
21017
+ function asString$28(value) {
20872
21018
  if (typeof value === "string" && value.trim() !== "") return value;
20873
21019
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
20874
21020
  }
@@ -20881,7 +21027,7 @@ function asNumber$6(value) {
20881
21027
  }
20882
21028
  function firstString$2(record, keys) {
20883
21029
  for (const key of keys) {
20884
- const value = asString$27(record[key]);
21030
+ const value = asString$28(record[key]);
20885
21031
  if (value !== void 0) return value;
20886
21032
  }
20887
21033
  }
@@ -21128,7 +21274,7 @@ function parseCreditBalances(payload, consumed = []) {
21128
21274
  /** Resolve the plan id the subscription or credits payload reports, when either does. */
21129
21275
  function parsePlanId(payloads) {
21130
21276
  for (const payload of payloads) {
21131
- const id = asString$27(deepValue(payload, [
21277
+ const id = asString$28(deepValue(payload, [
21132
21278
  "planId",
21133
21279
  "plan_id",
21134
21280
  "priceId",
@@ -21147,7 +21293,7 @@ function parsePlanId(payloads) {
21147
21293
  */
21148
21294
  function parseSubscriptionStatus(payload) {
21149
21295
  const root = asRecord$16(payload) ?? {};
21150
- return asString$27(deepValue(asRecord$16(root.data) ?? root, ["status"])) ?? null;
21296
+ return asString$28(deepValue(asRecord$16(root.data) ?? root, ["status"])) ?? null;
21151
21297
  }
21152
21298
  /** End of the current billing period, in Unix milliseconds. */
21153
21299
  function parseSubscriptionPeriodEnd(payload) {
@@ -21723,6 +21869,92 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
21723
21869
  });
21724
21870
  }
21725
21871
  //#endregion
21872
+ //#region src/host/common/orphan-tool-calls.ts
21873
+ /**
21874
+ * 读一个未知字段为字符串。
21875
+ * @param value - 候选值。
21876
+ * @returns 字符串,或任何其它类型的 undefined。
21877
+ */
21878
+ function asString$27(value) {
21879
+ return typeof value === "string" ? value : void 0;
21880
+ }
21881
+ /**
21882
+ * 清掉没有配对的工具调用。
21883
+ *
21884
+ * 配对规则只有一条:`tool_use` 必须被**后面第一条** user 轮里的同名 `tool_result` 应答;
21885
+ * 到了下一条 assistant 轮还没被应答,它就是孤儿。反向也一样——`tool_result` 找不到仍在
21886
+ * 等待的同名调用,它就是无主结果(线上报的是「tool call result does not follow tool call」)。
21887
+ *
21888
+ * @param messages - 已折叠过的消息列表。
21889
+ * @param refold - 调用方自己的相邻同角色折叠,清理后需要再折一次。
21890
+ * @returns 配对完整、且没有空消息的消息列表;清理会把整段历史清空时原样返回。
21891
+ */
21892
+ function cleanOrphanedToolCalls(messages, refold) {
21893
+ const result = [];
21894
+ let pending = [];
21895
+ /**
21896
+ * 丢掉没有被结果应答的调用。
21897
+ * @param answered - 已经被结果应答的 id 集合。
21898
+ */
21899
+ const dropUnanswered = (answered) => {
21900
+ const byMessage = /* @__PURE__ */ new Map();
21901
+ for (const call of pending) {
21902
+ if (answered.has(call.id)) continue;
21903
+ const indices = byMessage.get(call.messageIndex);
21904
+ if (indices === void 0) byMessage.set(call.messageIndex, [call.blockIndex]);
21905
+ else indices.push(call.blockIndex);
21906
+ }
21907
+ for (const [messageIndex, indices] of byMessage) {
21908
+ const message = result[messageIndex];
21909
+ if (message === void 0) continue;
21910
+ message.content = message.content.filter((_block, index) => !indices.includes(index));
21911
+ }
21912
+ pending = [];
21913
+ };
21914
+ for (const message of messages) {
21915
+ if (message.role === "assistant") {
21916
+ dropUnanswered(/* @__PURE__ */ new Set());
21917
+ const messageIndex = result.length;
21918
+ for (let blockIndex = 0; blockIndex < message.content.length; blockIndex += 1) {
21919
+ const block = message.content[blockIndex];
21920
+ if (block.type !== "tool_use") continue;
21921
+ const id = asString$27(block.id);
21922
+ if (id === void 0) continue;
21923
+ pending.push({
21924
+ messageIndex,
21925
+ blockIndex,
21926
+ id
21927
+ });
21928
+ }
21929
+ result.push({
21930
+ ...message,
21931
+ content: [...message.content]
21932
+ });
21933
+ continue;
21934
+ }
21935
+ const answered = /* @__PURE__ */ new Set();
21936
+ const content = [];
21937
+ for (const block of message.content) {
21938
+ if (block.type === "tool_result") {
21939
+ const id = asString$27(block.tool_use_id);
21940
+ if (id === void 0 || !pending.some((call) => call.id === id)) continue;
21941
+ answered.add(id);
21942
+ content.push(block);
21943
+ continue;
21944
+ }
21945
+ content.push(block);
21946
+ }
21947
+ dropUnanswered(answered);
21948
+ result.push({
21949
+ ...message,
21950
+ content
21951
+ });
21952
+ }
21953
+ dropUnanswered(/* @__PURE__ */ new Set());
21954
+ const nonEmpty = result.filter((message) => message.content.length > 0);
21955
+ return nonEmpty.length === 0 ? messages : refold(nonEmpty);
21956
+ }
21957
+ //#endregion
21726
21958
  //#region src/host/command-code/mapper.ts
21727
21959
  /**
21728
21960
  * Provider-wire mapping for the two Command Code provider endpoints.
@@ -22279,7 +22511,7 @@ function anthropicAssistantContent$3(message) {
22279
22511
  * the Messages wire wants one user turn, so consecutive turns of one role are
22280
22512
  * folded together in order.
22281
22513
  */
22282
- function mergeAnthropicMessages$3(entries) {
22514
+ function mergeAnthropicMessages$4(entries) {
22283
22515
  const merged = [];
22284
22516
  for (const entry of entries) {
22285
22517
  if (entry.content.length === 0) continue;
@@ -22370,7 +22602,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$3) {
22370
22602
  return {
22371
22603
  model: options.model,
22372
22604
  max_tokens: maxTokens,
22373
- messages: mergeAnthropicMessages$3(entries),
22605
+ messages: cleanOrphanedToolCalls(mergeAnthropicMessages$4(entries), mergeAnthropicMessages$4),
22374
22606
  ...system === void 0 ? {} : { system: anthropicSystemBlocks(system) },
22375
22607
  ...options.temperature === void 0 || budget !== void 0 ? {} : { temperature: options.temperature },
22376
22608
  ...options.stop && options.stop.length > 0 ? { stop_sequences: options.stop } : {},
@@ -22672,6 +22904,23 @@ function applyOpenAIToolDelta$1(entry, state) {
22672
22904
  const fn = isRecord$60(entry.function) ? entry.function : {};
22673
22905
  const out = [];
22674
22906
  let call = state.toolCalls.get(wireIndex);
22907
+ const incomingId = asString$26(entry.id);
22908
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
22909
+ const closedBlock = {
22910
+ type: "tool-call",
22911
+ id: toToolCallId(call.id),
22912
+ name: call.name,
22913
+ arguments: call.arguments === "" ? "{}" : call.arguments
22914
+ };
22915
+ state.blocks[call.blockIndex] = closedBlock;
22916
+ out.push({
22917
+ type: "block-end",
22918
+ index: call.blockIndex,
22919
+ block: closedBlock
22920
+ });
22921
+ state.toolCalls.delete(wireIndex);
22922
+ call = void 0;
22923
+ }
22675
22924
  if (call === void 0) {
22676
22925
  out.push(...closeCurrent$6(state));
22677
22926
  call = {
@@ -34457,7 +34706,7 @@ function anthropicAssistantContent$2(message, toolCallIds) {
34457
34706
  * the Messages wire wants one user turn, so consecutive turns of one role are
34458
34707
  * folded together in order.
34459
34708
  */
34460
- function mergeAnthropicMessages$2(entries) {
34709
+ function mergeAnthropicMessages$3(entries) {
34461
34710
  const merged = [];
34462
34711
  for (const entry of entries) {
34463
34712
  if (entry.content.length === 0) continue;
@@ -34510,7 +34759,7 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$4, media = {
34510
34759
  type: CLEAR_THINKING_EDIT,
34511
34760
  keep: "all"
34512
34761
  }] } : void 0;
34513
- const messages = mergeAnthropicMessages$2(entries);
34762
+ const messages = cleanOrphanedToolCalls(mergeAnthropicMessages$3(entries), mergeAnthropicMessages$3);
34514
34763
  const lastMessage = messages[messages.length - 1];
34515
34764
  if (lastMessage !== void 0 && Array.isArray(lastMessage.content) && lastMessage.content.length > 0) {
34516
34765
  const lastBlock = lastMessage.content[lastMessage.content.length - 1];
@@ -34823,6 +35072,23 @@ function applyOpenAIToolDelta(entry, state) {
34823
35072
  const fn = isRecord$53(entry.function) ? entry.function : {};
34824
35073
  const out = [];
34825
35074
  let call = state.toolCalls.get(wireIndex);
35075
+ const incomingId = asString$21(entry.id);
35076
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
35077
+ const closedBlock = {
35078
+ type: "tool-call",
35079
+ id: toToolCallId(state.toolCallIds.normalize(call.id)),
35080
+ name: call.name,
35081
+ arguments: call.arguments === "" ? "{}" : call.arguments
35082
+ };
35083
+ state.blocks[call.blockIndex] = closedBlock;
35084
+ out.push({
35085
+ type: "block-end",
35086
+ index: call.blockIndex,
35087
+ block: closedBlock
35088
+ });
35089
+ state.toolCalls.delete(wireIndex);
35090
+ call = void 0;
35091
+ }
34826
35092
  if (call === void 0) {
34827
35093
  out.push(...closeCurrent$5(state));
34828
35094
  call = {
@@ -36809,7 +37075,7 @@ const KIMI_CODE_REQUEST_IMAGE_MAX_EDGE = 2e3;
36809
37075
  * @param catalog - live catalog, possibly empty when offline.
36810
37076
  * @returns the tool-update mode, or undefined when this route must resend everything.
36811
37077
  */
36812
- function toolUpdateMode$1(modelId, catalog) {
37078
+ function toolUpdateMode$2(modelId, catalog) {
36813
37079
  if (!dynamicToolsForEntry(modelId, catalog)) return void 0;
36814
37080
  if (wireForCatalogEntry(modelId, catalog) !== "openai") return void 0;
36815
37081
  const raw = (process.env.DSH_KIMI_CODE_TOOL_UPDATE ?? "").trim().toLowerCase();
@@ -37212,7 +37478,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
37212
37478
  id: modelId,
37213
37479
  name: entry?.name ?? modelId,
37214
37480
  inputModalities: inputModalitiesForEntry(modelId, catalog),
37215
- ...toolUpdateMode$1(modelId, catalog) === void 0 ? {} : { toolUpdate: toolUpdateMode$1(modelId, catalog) },
37481
+ ...toolUpdateMode$2(modelId, catalog) === void 0 ? {} : { toolUpdate: toolUpdateMode$2(modelId, catalog) },
37216
37482
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
37217
37483
  ...efforts.length === 0 ? {} : { reasoning: {
37218
37484
  efforts: efforts.map((effort) => ({
@@ -40865,7 +41131,7 @@ function anthropicAssistantContent$1(message, expectedScope) {
40865
41131
  * Messages wire wants one user turn, so consecutive turns of one role are folded
40866
41132
  * together in order.
40867
41133
  */
40868
- function mergeAnthropicMessages$1(entries) {
41134
+ function mergeAnthropicMessages$2(entries) {
40869
41135
  const merged = [];
40870
41136
  for (const entry of entries) {
40871
41137
  if (entry.content.length === 0) continue;
@@ -41087,7 +41353,7 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {
41087
41353
  model: options.model,
41088
41354
  max_tokens: maxTokens,
41089
41355
  stream: true,
41090
- messages: mergeAnthropicMessages$1(entries),
41356
+ messages: cleanOrphanedToolCalls(mergeAnthropicMessages$2(entries), mergeAnthropicMessages$2),
41091
41357
  ...system === void 0 ? {} : { system: caching ? [{
41092
41358
  type: "text",
41093
41359
  text: system
@@ -49630,6 +49896,16 @@ function isClaudeCacheTtl$1(value) {
49630
49896
  * models (`THINKING_BINDING_CONTROLS_BETA`, pushed when `supportsMidConvoEffort`).
49631
49897
  */
49632
49898
  const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
49899
+ /**
49900
+ * Licenses a mid-conversation `tool_addition` / `tool_removal` block.
49901
+ *
49902
+ * A tool change stated inside a `role: "system"` message is a beta shape: without
49903
+ * this marker the blocks are an unknown content type and the request is refused.
49904
+ * The name carries its own date because the vendor versions the shape, so a
49905
+ * future revision arrives as a new constant rather than as a silent behaviour
49906
+ * change under the old one.
49907
+ */
49908
+ const INLINE_TOOLS_BETA = "inline-tools-2026-09-15";
49633
49909
  /** Anthropic protocol revision sent as `anthropic-version`. */
49634
49910
  const ANTHROPIC_VERSION$1 = "2023-06-01";
49635
49911
  /**
@@ -51256,6 +51532,7 @@ function claudeBetas(options = {}) {
51256
51532
  if (options.thinking === true) betas.push(INTERLEAVED_THINKING_BETA);
51257
51533
  if (options.thinkingBinding === true) betas.push(THINKING_BINDING_CONTROLS_BETA);
51258
51534
  if (options.cacheTtl === "1h") betas.push(EXTENDED_CACHE_TTL_BETA);
51535
+ if (options.toolChanges === true) betas.push(INLINE_TOOLS_BETA);
51259
51536
  return betas;
51260
51537
  }
51261
51538
  /** The `User-Agent` value this line reports for one surface. */
@@ -53356,10 +53633,85 @@ function wireTool(tool, names) {
53356
53633
  return {
53357
53634
  name: claudeWireToolName(tool.name, names),
53358
53635
  description: tool.description ?? "",
53359
- input_schema: schema
53636
+ input_schema: schema,
53637
+ ...tool.deferLoading === true ? { defer_loading: true } : {}
53360
53638
  };
53361
53639
  }
53362
53640
  /**
53641
+ * The stand-in declaration a request needs when every real tool is deferred.
53642
+ *
53643
+ * The vendor's rule is that at least one tool must stay active: a request whose
53644
+ * declarations are all deferred has no active declaration, and a tool change
53645
+ * stated in that state would move the start of the rendered prompt. Transcribed
53646
+ * from the harness's own anthropic-messages adapter, which inserts the same
53647
+ * placeholder for the same reason.
53648
+ */
53649
+ const CLAUDE_TOOL_UPDATE_PLACEHOLDER = {
53650
+ name: "tool_update_placeholder",
53651
+ description: "Reserved. Keeps one tool declaration active so a mid-conversation tool change stays in the history. Never call it.",
53652
+ parameters: {
53653
+ type: "object",
53654
+ properties: {}
53655
+ }
53656
+ };
53657
+ /** Whether one message states a tool change this wire can carry. */
53658
+ function claudeMessageStatesToolChange(message) {
53659
+ return Array.isArray(message.content) && message.content.some((block) => isRecord$41(block) && (block.type === "tool-addition" || block.type === "tool-removal"));
53660
+ }
53661
+ /**
53662
+ * The fallback statement of one tool change, for a history position the vendor
53663
+ * will not accept a mid-conversation system message in.
53664
+ *
53665
+ * The declarations are untouched in both shapes: this only tells the model what
53666
+ * changed, and it is deliberately imperative for a removal because the
53667
+ * declaration is still there and could otherwise still be called.
53668
+ */
53669
+ function claudeToolChangeNote(message) {
53670
+ const added = [];
53671
+ const removed = [];
53672
+ for (const block of message.content) {
53673
+ if (!isRecord$41(block)) continue;
53674
+ const name = typeof block.toolName === "string" ? block.toolName.trim() : "";
53675
+ if (name === "") continue;
53676
+ if (block.type === "tool-addition") added.push(name);
53677
+ else if (block.type === "tool-removal") removed.push(name);
53678
+ }
53679
+ const notes = [];
53680
+ if (added.length > 0) notes.push(`[The tool(s) ${added.map((name) => `"${name}"`).join(", ")} are now available.]`);
53681
+ if (removed.length > 0) notes.push(`[The tool(s) ${removed.map((name) => `"${name}"`).join(", ")} are no longer available. Do not call them again: any call will fail.]`);
53682
+ return notes.join("\n");
53683
+ }
53684
+ /** Whether any message states a tool change that this wire can carry. */
53685
+ function claudeHasToolChanges(options) {
53686
+ return options.messages.some((message) => message.role === "developer" && Array.isArray(message.content) && message.content.some((block) => isRecord$41(block) && (block.type === "tool-addition" || block.type === "tool-removal")));
53687
+ }
53688
+ /**
53689
+ * Whether a BUILT payload carries a `tool_addition` / `tool_removal` block.
53690
+ *
53691
+ * Read off the body rather than from the request, so the beta header and the
53692
+ * blocks it licenses cannot disagree about one request.
53693
+ */
53694
+ function claudeBodyHasToolChanges(payload) {
53695
+ const messages = payload["messages"];
53696
+ if (!Array.isArray(messages)) return false;
53697
+ return messages.some((message) => {
53698
+ const content = message["content"];
53699
+ return Array.isArray(content) && content.some((block) => block["type"] === "tool_addition" || block["type"] === "tool_removal");
53700
+ });
53701
+ }
53702
+ /**
53703
+ * The wire tool list for one request.
53704
+ *
53705
+ * Deferred declarations travel with `defer_loading` so they stay out of the
53706
+ * prefix. When EVERY declaration is deferred and the history states a change,
53707
+ * one placeholder is prepended: the vendor refuses a tool change on a request
53708
+ * that has no active declaration at all.
53709
+ */
53710
+ function claudeWireTools(options, names) {
53711
+ const tools = options.tools ?? [];
53712
+ return (tools.length > 0 && tools.every((tool) => tool.deferLoading === true) && claudeHasToolChanges(options) ? [CLAUDE_TOOL_UPDATE_PLACEHOLDER, ...tools] : tools).map((tool) => wireTool(tool, names));
53713
+ }
53714
+ /**
53363
53715
  * One system-role turn's blocks.
53364
53716
  *
53365
53717
  * The vendor documents a mid-conversation system message as carrying "a plain
@@ -53385,6 +53737,17 @@ function claudeSystemContent(message, images) {
53385
53737
  continue;
53386
53738
  }
53387
53739
  if (block.type === "image") blocks.push(imageBlockFor(block, images));
53740
+ if (block.type === "tool-addition" || block.type === "tool-removal") {
53741
+ const name = typeof block.toolName === "string" ? block.toolName.trim() : "";
53742
+ if (name === "") continue;
53743
+ blocks.push({
53744
+ type: block.type === "tool-addition" ? "tool_addition" : "tool_removal",
53745
+ tool: {
53746
+ type: "tool_reference",
53747
+ name: claudeWireToolName(name)
53748
+ }
53749
+ });
53750
+ }
53388
53751
  }
53389
53752
  return blocks;
53390
53753
  }
@@ -53588,7 +53951,9 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53588
53951
  const maxTokens = claudeRequestThinks(options.model, effort, catalog) ? adjustMaxTokensForThinking(baseMax, modelMax, level, request.thinkingBudgets).maxTokens : baseMax;
53589
53952
  const { thinking, outputConfig } = claudeThinking(options.model, effort, level, maxTokens, request.thinkingBudgets, catalog);
53590
53953
  const entries = [];
53591
- for (const message of messagesForTheWire(options)) {
53954
+ const wire = messagesForTheWire(options);
53955
+ for (let index = 0; index < wire.length; index += 1) {
53956
+ const message = wire[index];
53592
53957
  if (message.role === "system") {
53593
53958
  entries.push({
53594
53959
  role: "system",
@@ -53596,6 +53961,26 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53596
53961
  });
53597
53962
  continue;
53598
53963
  }
53964
+ if (message.role === "developer" && claudeMessageStatesToolChange(message)) {
53965
+ const previous = wire[index - 1];
53966
+ const next = wire[index + 1];
53967
+ if (previous !== void 0 && wireRoleOf(previous) === "user" && (next === void 0 || wireRoleOf(next) === "assistant")) {
53968
+ entries.push({
53969
+ role: "system",
53970
+ content: claudeSystemContent(message, images)
53971
+ });
53972
+ continue;
53973
+ }
53974
+ const note = claudeToolChangeNote(message);
53975
+ if (note !== "") entries.push({
53976
+ role: "user",
53977
+ content: [{
53978
+ type: "text",
53979
+ text: note
53980
+ }]
53981
+ });
53982
+ continue;
53983
+ }
53599
53984
  if (isToolResultMessage$5(message)) {
53600
53985
  entries.push({
53601
53986
  role: "user",
@@ -53617,8 +54002,8 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53617
54002
  max_tokens: maxTokens,
53618
54003
  stream: true,
53619
54004
  system: buildClaudeSystemBlocks(options, caching, cacheTtl),
53620
- messages: mergeClaudeMessages(entries),
53621
- ...options.tools !== void 0 && options.tools.length > 0 ? { tools: options.tools.map((tool) => wireTool(tool, names)) } : {},
54005
+ messages: cleanOrphanedToolCalls(mergeClaudeMessages(entries), mergeClaudeMessages),
54006
+ ...options.tools !== void 0 && options.tools.length > 0 ? { tools: claudeWireTools(options, names) } : {},
53622
54007
  ...thinking === void 0 ? {} : { thinking },
53623
54008
  ...outputConfig === void 0 ? {} : { output_config: outputConfig },
53624
54009
  ...temperatureAllowed && options.temperature !== void 0 ? { temperature: options.temperature } : {},
@@ -55444,6 +55829,33 @@ var ClaudeAccountTokenStore = class {
55444
55829
  await this.store.saveAccount(credentials, { internalId: this.internalId });
55445
55830
  }
55446
55831
  };
55832
+ /**
55833
+ * Whether this model may state a tool change in the history.
55834
+ *
55835
+ * Gated on the same capability that licenses a mid-conversation system message,
55836
+ * because that message is what carries the change: a model that folds those
55837
+ * messages back into the top-level `system` field cannot take a tool change
55838
+ * without moving the head, which is the cost the capability exists to avoid.
55839
+ *
55840
+ * `DSH_CLAUDE_TOOL_UPDATE=0` turns it off for one account without a code change,
55841
+ * mirroring the escape hatch kimi-code and the codex line carry.
55842
+ *
55843
+ * @param modelId - exact model id.
55844
+ * @returns the mode to declare, or undefined when every request must restate the
55845
+ * complete tool list.
55846
+ */
55847
+ function toolUpdateMode$1(modelId) {
55848
+ if (!claudeSupportsMidConversationSystemMessages(modelId)) return void 0;
55849
+ const raw = (process.env.DSH_CLAUDE_TOOL_UPDATE ?? "").trim().toLowerCase();
55850
+ if (raw !== "" && [
55851
+ "0",
55852
+ "false",
55853
+ "no",
55854
+ "off",
55855
+ "none"
55856
+ ].includes(raw)) return void 0;
55857
+ return "in-history";
55858
+ }
55447
55859
  var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55448
55860
  store;
55449
55861
  modelSettings;
@@ -55559,6 +55971,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55559
55971
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
55560
55972
  context: { contextWindow: contextWindow || 2e5 },
55561
55973
  ...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
55974
+ ...toolUpdateMode$1(modelId) === void 0 ? {} : { toolUpdate: toolUpdateMode$1(modelId) },
55562
55975
  ...efforts.length === 0 ? {} : { reasoning: {
55563
55976
  efforts: efforts.map((effort) => ({
55564
55977
  id: ReasoningEffortId(effort),
@@ -55657,6 +56070,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55657
56070
  model: requestOptions.model,
55658
56071
  thinking,
55659
56072
  thinkingBinding: claudeBodyBindsThinking(payload),
56073
+ toolChanges: claudeBodyHasToolChanges(payload),
55660
56074
  cacheTtl,
55661
56075
  method: "POST"
55662
56076
  }),
@@ -59161,7 +59575,7 @@ function safeStringify(value) {
59161
59575
  * DSH 历史里可能连续两条 user(注入的上下文提示 + 真正的用户轮),而工具结果本身就是
59162
59576
  * user 角色消息;Messages 线要的是一个 user 轮,所以同角色的连续轮按顺序折叠。
59163
59577
  */
59164
- function mergeAnthropicMessages(entries) {
59578
+ function mergeAnthropicMessages$1(entries) {
59165
59579
  const merged = [];
59166
59580
  for (const entry of entries) {
59167
59581
  if (entry.content.length === 0) continue;
@@ -59310,7 +59724,7 @@ function buildGlmAnthropicRequest(options, request = {}) {
59310
59724
  }
59311
59725
  const system = glmAnthropicSystemBlocks(options);
59312
59726
  const maxTokens = options.maxTokens ?? request.defaultMaxTokens ?? 32768;
59313
- const merged = mergeAnthropicMessages(entries);
59727
+ const merged = cleanOrphanedToolCalls(mergeAnthropicMessages$1(entries), mergeAnthropicMessages$1);
59314
59728
  const lastMessageIndex = merged.length - 1;
59315
59729
  const messages = lastMessageIndex < 0 ? merged : merged.map((message, index) => index === lastMessageIndex ? {
59316
59730
  ...message,
@@ -60637,6 +61051,23 @@ function applyToolDelta$3(entry, state) {
60637
61051
  const fn = isRecord$37(entry.function) ? entry.function : {};
60638
61052
  const out = [];
60639
61053
  let call = state.toolCalls.get(wireIndex);
61054
+ const incomingId = asString$14(entry.id);
61055
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
61056
+ const closedBlock = {
61057
+ type: "tool-call",
61058
+ id: toToolCallId(call.id),
61059
+ name: call.name,
61060
+ arguments: call.arguments === "" ? "{}" : call.arguments
61061
+ };
61062
+ state.blocks[call.blockIndex] = closedBlock;
61063
+ out.push({
61064
+ type: "block-end",
61065
+ index: call.blockIndex,
61066
+ block: closedBlock
61067
+ });
61068
+ state.toolCalls.delete(wireIndex);
61069
+ call = void 0;
61070
+ }
60640
61071
  if (call === void 0) {
60641
61072
  out.push(...closeCurrent$3(state));
60642
61073
  call = {
@@ -65470,6 +65901,23 @@ function applyToolDelta$2(entry, state) {
65470
65901
  const fn = isRecord$33(entry.function) ? entry.function : void 0;
65471
65902
  const out = [];
65472
65903
  let call = state.toolCalls.get(wireIndex);
65904
+ const incomingId = asString$10(entry.id);
65905
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
65906
+ const closedBlock = {
65907
+ type: "tool-call",
65908
+ id: toToolCallId(call.id),
65909
+ name: call.name,
65910
+ arguments: call.arguments === "" ? "{}" : call.arguments
65911
+ };
65912
+ state.blocks[call.blockIndex] = closedBlock;
65913
+ out.push({
65914
+ type: "block-end",
65915
+ index: call.blockIndex,
65916
+ block: closedBlock
65917
+ });
65918
+ state.toolCalls.delete(wireIndex);
65919
+ call = void 0;
65920
+ }
65473
65921
  if (call === void 0) {
65474
65922
  const opened = openToolCall$1(state, entry, wireIndex);
65475
65923
  out.push(...opened.chunks);
@@ -71734,6 +72182,7 @@ async function* translate(payloads) {
71734
72182
  if (!isRecord$26(call)) continue;
71735
72183
  const index = Number(call.index ?? 0);
71736
72184
  let block = toolBlocks.get(index);
72185
+ if (block !== void 0 && typeof call.id === "string" && call.id.length > 0 && block.callId !== void 0 && block.callId.length > 0 && block.callId !== call.id) block = void 0;
71737
72186
  if (block === void 0) {
71738
72187
  block = open("tool-call");
71739
72188
  toolBlocks.set(index, block);
@@ -71745,7 +72194,7 @@ async function* translate(payloads) {
71745
72194
  }
71746
72195
  const fn = isRecord$26(call.function) ? call.function : {};
71747
72196
  block.callId = acceptIdentity$1(block.callId, call.id);
71748
- block.name = acceptIdentity$1(block.name, fn.name);
72197
+ if (block.name === void 0 || block.name === "") block.name = acceptIdentity$1(block.name, fn.name);
71749
72198
  const fragment = typeof fn.arguments === "string" ? fn.arguments : "";
71750
72199
  block.text += fragment;
71751
72200
  yield {
@@ -78472,6 +78921,23 @@ function applyToolDelta$1(entry, state) {
78472
78921
  const fn = isRecord$21(entry.function) ? entry.function : void 0;
78473
78922
  const out = [];
78474
78923
  let call = state.toolCalls.get(wireIndex);
78924
+ const incomingId = asString$7(entry.id);
78925
+ if (call !== void 0 && incomingId !== void 0 && call.id !== "call_" + wireIndex && incomingId !== call.id) {
78926
+ const closedBlock = {
78927
+ type: "tool-call",
78928
+ id: toToolCallId(call.id),
78929
+ name: call.name,
78930
+ arguments: call.arguments === "" ? "{}" : call.arguments
78931
+ };
78932
+ state.blocks[call.blockIndex] = closedBlock;
78933
+ out.push({
78934
+ type: "block-end",
78935
+ index: call.blockIndex,
78936
+ block: closedBlock
78937
+ });
78938
+ state.toolCalls.delete(wireIndex);
78939
+ call = void 0;
78940
+ }
78475
78941
  if (call === void 0) {
78476
78942
  const opened = openToolCall(state, entry, wireIndex);
78477
78943
  out.push(...opened.chunks);
@@ -80982,8 +81448,13 @@ const POOL_COOLDOWN_MS$1 = 15 * 6e4;
80982
81448
  * The subscription's models at release time, keyed by wire id.
80983
81449
  *
80984
81450
  * Id spellings and prices are transcribed from MODEL_PRICES_URL (fetched
80985
- * 2026-10-21, 37 entries, all chat models — the plan's image models are priced
80986
- * per call and are not in it). Display names use the pricing page's spelling
81451
+ * 2026-11-07, 41 entries, all chat models — the plan's image models are priced
81452
+ * per call and are not in it). A variant the list prices but no other source
81453
+ * describes separately (`deepseek-v4.1-flash-eco`, `gpt-6.1-terra`, `hy4`,
81454
+ * `kimi-k3.1-pro`, added 2026-11-07) transcribes only its price from that list;
81455
+ * its window, output cap, modalities and ladder are inherited from the sibling
81456
+ * row of the same family, which is the rule `deepseek-v4-flash-vision-exp`
81457
+ * already follows. Display names use the pricing page's spelling
80987
81458
  * where that page lists the model (https://open-design.ai/pricing/) and are
80988
81459
  * derived from the id otherwise; the live catalog's own name wins once one is
80989
81460
  * read. Windows, output caps, modalities and ladders come from the sibling
@@ -81137,6 +81608,23 @@ const MODEL_CATALOG$1 = Object.freeze({
81137
81608
  ],
81138
81609
  defaultEffort: "high"
81139
81610
  },
81611
+ "deepseek-v4.1-flash-eco": {
81612
+ name: "DeepSeek V4.1 Flash Eco",
81613
+ pricing: {
81614
+ input: .3,
81615
+ output: 1.2
81616
+ },
81617
+ contextWindow: 1e6,
81618
+ maxTokens: 384e3,
81619
+ input: ["text", "image"],
81620
+ efforts: [
81621
+ "none",
81622
+ "low",
81623
+ "high",
81624
+ "max"
81625
+ ],
81626
+ defaultEffort: "high"
81627
+ },
81140
81628
  "deepseek-v4-flash": {
81141
81629
  name: "DeepSeek V4 Flash",
81142
81630
  pricing: {
@@ -81339,6 +81827,23 @@ const MODEL_CATALOG$1 = Object.freeze({
81339
81827
  "max"
81340
81828
  ]
81341
81829
  },
81830
+ "gpt-6.1-terra": {
81831
+ name: "GPT-6.1 Terra",
81832
+ pricing: {
81833
+ input: 1,
81834
+ output: 5
81835
+ },
81836
+ contextWindow: 105e4,
81837
+ maxTokens: 128e3,
81838
+ input: ["text", "image"],
81839
+ efforts: [
81840
+ "low",
81841
+ "medium",
81842
+ "high",
81843
+ "xhigh",
81844
+ "max"
81845
+ ]
81846
+ },
81342
81847
  "gpt-6-astra": {
81343
81848
  name: "GPT-6 Astra",
81344
81849
  pricing: {
@@ -81434,6 +81939,20 @@ const MODEL_CATALOG$1 = Object.freeze({
81434
81939
  "xhigh"
81435
81940
  ]
81436
81941
  },
81942
+ "hy4": {
81943
+ name: "Hunyuan H4",
81944
+ pricing: {
81945
+ input: .417,
81946
+ output: 1.2505
81947
+ },
81948
+ contextWindow: 1048576,
81949
+ input: ["text"],
81950
+ efforts: [
81951
+ "low",
81952
+ "medium",
81953
+ "high"
81954
+ ]
81955
+ },
81437
81956
  "hy4-preview": {
81438
81957
  name: "Hunyuan H4 Preview",
81439
81958
  pricing: {
@@ -81479,6 +81998,21 @@ const MODEL_CATALOG$1 = Object.freeze({
81479
81998
  "max"
81480
81999
  ]
81481
82000
  },
82001
+ "kimi-k3.1-pro": {
82002
+ name: "Kimi K3.1 Pro",
82003
+ pricing: {
82004
+ input: 2,
82005
+ output: 10
82006
+ },
82007
+ contextWindow: 1048576,
82008
+ maxTokens: 131072,
82009
+ input: ["text", "image"],
82010
+ efforts: [
82011
+ "low",
82012
+ "high",
82013
+ "max"
82014
+ ]
82015
+ },
81482
82016
  "mimo-v2.6-flash": {
81483
82017
  name: "MiMo V2.6 Flash",
81484
82018
  pricing: {
@@ -83690,6 +84224,7 @@ async function* translate$1(payloads) {
83690
84224
  if (!isRecord$17(call)) continue;
83691
84225
  const index = Number(call.index ?? 0);
83692
84226
  let block = toolBlocks.get(index);
84227
+ if (block !== void 0 && typeof call.id === "string" && call.id.length > 0 && block.callId !== void 0 && block.callId.length > 0 && block.callId !== call.id) block = void 0;
83693
84228
  if (block === void 0) {
83694
84229
  block = open("tool-call");
83695
84230
  toolBlocks.set(index, block);
@@ -83701,7 +84236,7 @@ async function* translate$1(payloads) {
83701
84236
  }
83702
84237
  const fn = isRecord$17(call.function) ? call.function : {};
83703
84238
  block.callId = acceptIdentity(block.callId, call.id);
83704
- block.name = acceptIdentity(block.name, fn.name);
84239
+ if (block.name === void 0 || block.name === "") block.name = acceptIdentity(block.name, fn.name);
83705
84240
  const fragment = typeof fn.arguments === "string" ? fn.arguments : "";
83706
84241
  block.text += fragment;
83707
84242
  yield {
@@ -87228,9 +87763,9 @@ function installToolResultPrunerGuard(ctx, options = {}) {
87228
87763
  const wrapPruner = (pruner) => {
87229
87764
  if (disposed || !isRecord$14(pruner)) return false;
87230
87765
  if (alreadyWrapped.has(pruner)) return false;
87231
- const target = resolveMethodTarget$3(pruner, "pruneSession");
87766
+ const target = resolveMethodTarget$4(pruner, "pruneSession");
87232
87767
  if (target === void 0) return false;
87233
- if (alreadyPatched$3(patchedMethods, target.holder, target.name)) {
87768
+ if (alreadyPatched$4(patchedMethods, target.holder, target.name)) {
87234
87769
  alreadyWrapped.add(pruner);
87235
87770
  return false;
87236
87771
  }
@@ -87242,11 +87777,11 @@ function installToolResultPrunerGuard(ctx, options = {}) {
87242
87777
  };
87243
87778
  return original.call(this ?? pruner, session);
87244
87779
  };
87245
- if (!installMethod$3(target, replacement)) return false;
87246
- markPatched$3(patchedMethods, target.holder, target.name);
87780
+ if (!installMethod$4(target, replacement)) return false;
87781
+ markPatched$4(patchedMethods, target.holder, target.name);
87247
87782
  alreadyWrapped.add(pruner);
87248
87783
  restores.push(() => {
87249
- restoreMethod$3(target);
87784
+ restoreMethod$4(target);
87250
87785
  });
87251
87786
  return true;
87252
87787
  };
@@ -87466,7 +88001,7 @@ function isRecord$14(value) {
87466
88001
  * @param name - 方法名。
87467
88002
  * @returns 目标;方法不存在时为 `undefined`。
87468
88003
  */
87469
- function resolveMethodTarget$3(service, name) {
88004
+ function resolveMethodTarget$4(service, name) {
87470
88005
  if (typeof service !== "object" || service === null) return void 0;
87471
88006
  const own = service;
87472
88007
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -87498,7 +88033,7 @@ function resolveMethodTarget$3(service, name) {
87498
88033
  * @param replacement - 包装实现。
87499
88034
  * @returns 是否装上了。
87500
88035
  */
87501
- function installMethod$3(target, replacement) {
88036
+ function installMethod$4(target, replacement) {
87502
88037
  try {
87503
88038
  Object.defineProperty(target.holder, target.name, {
87504
88039
  value: replacement,
@@ -87516,7 +88051,7 @@ function installMethod$3(target, replacement) {
87516
88051
  *
87517
88052
  * @param target - 目标。
87518
88053
  */
87519
- function restoreMethod$3(target) {
88054
+ function restoreMethod$4(target) {
87520
88055
  try {
87521
88056
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
87522
88057
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -87530,7 +88065,7 @@ function restoreMethod$3(target) {
87530
88065
  * @param name - 方法名。
87531
88066
  * @returns 是否已经包过。
87532
88067
  */
87533
- function alreadyPatched$3(patched, holder, name) {
88068
+ function alreadyPatched$4(patched, holder, name) {
87534
88069
  return patched.get(holder)?.has(name) === true;
87535
88070
  }
87536
88071
  /**
@@ -87540,7 +88075,7 @@ function alreadyPatched$3(patched, holder, name) {
87540
88075
  * @param holder - 方法宿主。
87541
88076
  * @param name - 方法名。
87542
88077
  */
87543
- function markPatched$3(patched, holder, name) {
88078
+ function markPatched$4(patched, holder, name) {
87544
88079
  const names = patched.get(holder);
87545
88080
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
87546
88081
  else names.add(name);
@@ -87555,7 +88090,7 @@ const GLOBAL_KEY$2 = Object.freeze({});
87555
88090
  * @param service - 服务名。
87556
88091
  * @returns 服务实例或 undefined。
87557
88092
  */
87558
- function serviceOf$1(ctx, service) {
88093
+ function serviceOf$2(ctx, service) {
87559
88094
  const read = ctx?.get;
87560
88095
  if (typeof read !== "function") return void 0;
87561
88096
  try {
@@ -87577,11 +88112,15 @@ function bucketOf$1(value) {
87577
88112
  * @param ctx - 宿主上下文。
87578
88113
  * @param event - 事件名。
87579
88114
  * @param listener - 监听器。
88115
+ * @param options - cordis 订阅选项;省略时按两参数订阅。
87580
88116
  * @returns 解绑函数或 undefined。
87581
88117
  */
87582
- function subscribe$3(ctx, event, listener) {
88118
+ function subscribe$3(ctx, event, listener, options) {
87583
88119
  const on = ctx?.on;
87584
88120
  if (typeof on !== "function") return void 0;
88121
+ if (options !== void 0) try {
88122
+ return on.call(ctx, event, listener, options);
88123
+ } catch {}
87585
88124
  try {
87586
88125
  return on.call(ctx, event, listener);
87587
88126
  } catch {
@@ -87674,11 +88213,15 @@ function installPlanModeCacheGuard(ctx) {
87674
88213
  if (offPreStep !== void 0) restores.push(() => {
87675
88214
  offPreStep();
87676
88215
  });
88216
+ const offReminder = installPlanModeExitReminder(ctx, { isActive: (agent) => planActiveOfAgent(ctx, agent) });
88217
+ if (offReminder !== void 0) restores.push(() => {
88218
+ offReminder();
88219
+ });
87677
88220
  const wrap = (tools) => {
87678
88221
  if (tools === void 0) return;
87679
- const target = resolveMethodTarget$2(tools, "register");
88222
+ const target = resolveMethodTarget$3(tools, "register");
87680
88223
  if (target === void 0) return;
87681
- if (alreadyPatched$2(patchedMethods, target.holder, target.name)) return;
88224
+ if (alreadyPatched$3(patchedMethods, target.holder, target.name)) return;
87682
88225
  const original = target.original;
87683
88226
  const wrapped = function(...args) {
87684
88227
  const receiver = this ?? tools;
@@ -87690,16 +88233,16 @@ function installPlanModeCacheGuard(ctx) {
87690
88233
  }
87691
88234
  return original.call(receiver, definition, options);
87692
88235
  };
87693
- if (!installMethod$2(target, wrapped)) return;
87694
- markPatched$2(patchedMethods, target.holder, target.name);
88236
+ if (!installMethod$3(target, wrapped)) return;
88237
+ markPatched$3(patchedMethods, target.holder, target.name);
87695
88238
  restores.push(() => {
87696
- restoreMethod$2(target);
88239
+ restoreMethod$3(target);
87697
88240
  });
87698
88241
  if (typeof tools.get === "function") try {
87699
88242
  if (tools.get("exit_plan_mode") !== void 0) logWarn$1(ctx, "[dsh-chatgpt-subscription] plan-mode cache guard was installed after exit_plan_mode was already registered; this session keeps the conditional admission");
87700
88243
  } catch {}
87701
88244
  };
87702
- const tools = serviceOf$1(ctx, "tools");
88245
+ const tools = serviceOf$2(ctx, "tools");
87703
88246
  if (tools !== void 0) wrap(tools);
87704
88247
  else {
87705
88248
  const inject = ctx?.inject;
@@ -87709,7 +88252,7 @@ function installPlanModeCacheGuard(ctx) {
87709
88252
  inject.call(ctx, ["tools"], (scope) => {
87710
88253
  if (wrapped || closed) return;
87711
88254
  wrapped = true;
87712
- wrap(serviceOf$1(scope, "tools"));
88255
+ wrap(serviceOf$2(scope, "tools"));
87713
88256
  });
87714
88257
  } catch {}
87715
88258
  }
@@ -87729,7 +88272,7 @@ function installPlanModeCacheGuard(ctx) {
87729
88272
  * @returns true / false;读不到时 undefined。
87730
88273
  */
87731
88274
  function planActiveOf(ctx, session) {
87732
- const projections = serviceOf$1(ctx, "sessionProjections");
88275
+ const projections = serviceOf$2(ctx, "sessionProjections");
87733
88276
  if (typeof projections?.stateOf !== "function") return void 0;
87734
88277
  try {
87735
88278
  const state = projections.stateOf(session, PLAN_PROJECTION_KEY);
@@ -87739,6 +88282,35 @@ function planActiveOf(ctx, session) {
87739
88282
  }
87740
88283
  }
87741
88284
  /**
88285
+ * 读一个 agent 此刻是否在计划模式。
88286
+ *
88287
+ * 三级降级,顺序不可颠倒:
88288
+ *
88289
+ * 1. 进程级登记册({@link registeredPlanControllerActive}):本包的 fork 会把每个
88290
+ * 会话登记进去,**跨 realm 可见**。`ctx.get()` 看不见 `isolate: { planMode: true }`
88291
+ * 的子 realm,而隔离正是宿主守卫最常见的部署(守卫在根作用域、控制器在 preset 里),
88292
+ * 所以这一级必须先问。
88293
+ * 2. 同一作用域里的 `planMode` 服务:本包的 fork 有 `selectedActive`(含未决选择),
88294
+ * 所以 `exit_plan_mode` 刚排队 `{ active: false }`、日志还没落进投影的那一刻不会被
88295
+ * 误判成「还在计划模式」。
88296
+ * 3. plan 投影:内置 preset 挂的是官方服务,它的 `/plan` 只写日志,投影是那边唯一的
88297
+ * 事实来源。
88298
+ * @param ctx - 宿主上下文。
88299
+ * @param agent - 调用方 agent。
88300
+ * @returns true / false;读不到时 undefined。
88301
+ */
88302
+ function planActiveOfAgent(ctx, agent) {
88303
+ const session = agent?.session;
88304
+ const registered = registeredPlanControllerActive(session);
88305
+ if (registered !== void 0) return registered;
88306
+ const controller = serviceOf$2(ctx, "planMode");
88307
+ if (typeof controller?.selectedActive === "function") try {
88308
+ const active = controller.selectedActive(session);
88309
+ if (typeof active === "boolean") return active;
88310
+ } catch {}
88311
+ return planActiveOf(ctx, session);
88312
+ }
88313
+ /**
87742
88314
  * 记一条警告。
87743
88315
  * @param ctx - 宿主上下文。
87744
88316
  * @param message - 正文。
@@ -87767,7 +88339,7 @@ function logWarn$1(ctx, message) {
87767
88339
  * @param name - 方法名。
87768
88340
  * @returns 目标;方法不存在时为 `undefined`。
87769
88341
  */
87770
- function resolveMethodTarget$2(service, name) {
88342
+ function resolveMethodTarget$3(service, name) {
87771
88343
  if (typeof service !== "object" || service === null) return void 0;
87772
88344
  const own = service;
87773
88345
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -87799,7 +88371,7 @@ function resolveMethodTarget$2(service, name) {
87799
88371
  * @param replacement - 包装实现。
87800
88372
  * @returns 是否装上了。
87801
88373
  */
87802
- function installMethod$2(target, replacement) {
88374
+ function installMethod$3(target, replacement) {
87803
88375
  try {
87804
88376
  Object.defineProperty(target.holder, target.name, {
87805
88377
  value: replacement,
@@ -87817,7 +88389,7 @@ function installMethod$2(target, replacement) {
87817
88389
  *
87818
88390
  * @param target - 目标。
87819
88391
  */
87820
- function restoreMethod$2(target) {
88392
+ function restoreMethod$3(target) {
87821
88393
  try {
87822
88394
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
87823
88395
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -87831,7 +88403,7 @@ function restoreMethod$2(target) {
87831
88403
  * @param name - 方法名。
87832
88404
  * @returns 是否已经包过。
87833
88405
  */
87834
- function alreadyPatched$2(patched, holder, name) {
88406
+ function alreadyPatched$3(patched, holder, name) {
87835
88407
  return patched.get(holder)?.has(name) === true;
87836
88408
  }
87837
88409
  /**
@@ -87841,7 +88413,7 @@ function alreadyPatched$2(patched, holder, name) {
87841
88413
  * @param holder - 方法宿主。
87842
88414
  * @param name - 方法名。
87843
88415
  */
87844
- function markPatched$2(patched, holder, name) {
88416
+ function markPatched$3(patched, holder, name) {
87845
88417
  const names = patched.get(holder);
87846
88418
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
87847
88419
  else names.add(name);
@@ -87963,7 +88535,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
87963
88535
  */
87964
88536
  const toolFingerprint = (tools, scope) => {
87965
88537
  if (!isRecord$13(tools)) return "";
87966
- const target = resolveMethodTarget$1(tools, "definitions");
88538
+ const target = resolveMethodTarget$2(tools, "definitions");
87967
88539
  if (target === void 0) return "";
87968
88540
  let list;
87969
88541
  try {
@@ -87983,9 +88555,9 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
87983
88555
  const wrapToolsService = (tools) => {
87984
88556
  if (disposed || !isRecord$13(tools)) return false;
87985
88557
  if (alreadyWrapped.has(tools)) return false;
87986
- const target = resolveMethodTarget$1(tools, "definitions");
88558
+ const target = resolveMethodTarget$2(tools, "definitions");
87987
88559
  if (target === void 0) return false;
87988
- if (alreadyPatched$1(patchedMethods, target.holder, target.name)) {
88560
+ if (alreadyPatched$2(patchedMethods, target.holder, target.name)) {
87989
88561
  alreadyWrapped.add(tools);
87990
88562
  return false;
87991
88563
  }
@@ -87998,11 +88570,11 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
87998
88570
  }
87999
88571
  return original.call(receiver, scope, toolSet);
88000
88572
  };
88001
- if (!installMethod$1(target, replacement)) return false;
88002
- markPatched$1(patchedMethods, target.holder, target.name);
88573
+ if (!installMethod$2(target, replacement)) return false;
88574
+ markPatched$2(patchedMethods, target.holder, target.name);
88003
88575
  alreadyWrapped.add(tools);
88004
88576
  restores.push(() => {
88005
- restoreMethod$1(target);
88577
+ restoreMethod$2(target);
88006
88578
  });
88007
88579
  return true;
88008
88580
  };
@@ -88042,7 +88614,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88042
88614
  };
88043
88615
  const offAssemble = subscribe$2(host, "system-prompt/assemble", onAssemble);
88044
88616
  if (offAssemble !== void 0) disposers.push(offAssemble);
88045
- const initialTools = serviceOf(host, "tools");
88617
+ const initialTools = serviceOf$1(host, "tools");
88046
88618
  if (initialTools !== void 0) {
88047
88619
  currentTools = initialTools;
88048
88620
  wrapToolsService(initialTools);
@@ -88051,7 +88623,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88051
88623
  if (typeof inject === "function") try {
88052
88624
  inject.call(host, ["tools"], (scope) => {
88053
88625
  if (disposed) return;
88054
- const tools = serviceOf(scope, "tools");
88626
+ const tools = serviceOf$1(scope, "tools");
88055
88627
  if (tools === void 0) return;
88056
88628
  currentTools = tools;
88057
88629
  wrapToolsService(tools);
@@ -88079,7 +88651,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88079
88651
  * @param service - 服务名。
88080
88652
  * @returns 服务实例;读不到时为 `undefined`。
88081
88653
  */
88082
- function serviceOf(ctx, service) {
88654
+ function serviceOf$1(ctx, service) {
88083
88655
  if (!isRecord$13(ctx)) return void 0;
88084
88656
  const read = ctx["get"];
88085
88657
  if (typeof read !== "function") return void 0;
@@ -88148,7 +88720,7 @@ function isRecord$13(value) {
88148
88720
  * @param name - 方法名。
88149
88721
  * @returns 目标;方法不存在时为 `undefined`。
88150
88722
  */
88151
- function resolveMethodTarget$1(service, name) {
88723
+ function resolveMethodTarget$2(service, name) {
88152
88724
  if (typeof service !== "object" || service === null) return void 0;
88153
88725
  const own = service;
88154
88726
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -88180,7 +88752,7 @@ function resolveMethodTarget$1(service, name) {
88180
88752
  * @param replacement - 包装实现。
88181
88753
  * @returns 是否装上了。
88182
88754
  */
88183
- function installMethod$1(target, replacement) {
88755
+ function installMethod$2(target, replacement) {
88184
88756
  try {
88185
88757
  Object.defineProperty(target.holder, target.name, {
88186
88758
  value: replacement,
@@ -88198,7 +88770,7 @@ function installMethod$1(target, replacement) {
88198
88770
  *
88199
88771
  * @param target - 目标。
88200
88772
  */
88201
- function restoreMethod$1(target) {
88773
+ function restoreMethod$2(target) {
88202
88774
  try {
88203
88775
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
88204
88776
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -88212,7 +88784,7 @@ function restoreMethod$1(target) {
88212
88784
  * @param name - 方法名。
88213
88785
  * @returns 是否已经包过。
88214
88786
  */
88215
- function alreadyPatched$1(patched, holder, name) {
88787
+ function alreadyPatched$2(patched, holder, name) {
88216
88788
  return patched.get(holder)?.has(name) === true;
88217
88789
  }
88218
88790
  /**
@@ -88222,12 +88794,245 @@ function alreadyPatched$1(patched, holder, name) {
88222
88794
  * @param holder - 方法宿主。
88223
88795
  * @param name - 方法名。
88224
88796
  */
88225
- function markPatched$1(patched, holder, name) {
88797
+ function markPatched$2(patched, holder, name) {
88226
88798
  const names = patched.get(holder);
88227
88799
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
88228
88800
  else names.add(name);
88229
88801
  }
88230
88802
  //#endregion
88803
+ //#region src/host/settled-job-guard.ts
88804
+ function isRecord$12(value) {
88805
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88806
+ }
88807
+ function serviceOf(ctx, name) {
88808
+ const get = ctx?.get;
88809
+ if (typeof get !== "function") return void 0;
88810
+ try {
88811
+ return get.call(ctx, name);
88812
+ } catch {
88813
+ return;
88814
+ }
88815
+ }
88816
+ function isTerminalStatus(value) {
88817
+ return value === "completed" || value === "failed" || value === "killed";
88818
+ }
88819
+ function isUnknownJobError(error, id) {
88820
+ if (!(error instanceof Error)) return false;
88821
+ return error.message === `unknown job ${id}`;
88822
+ }
88823
+ /**
88824
+ * 通过 `agents` 或 `sessions` 服务定位 `caller` 对应的会话对象。
88825
+ */
88826
+ function resolveCallerSession(ctx, caller) {
88827
+ const agents = serviceOf(ctx, "agents");
88828
+ if (typeof agents?.get === "function") try {
88829
+ const agent = agents.get(caller);
88830
+ if (agent?.session !== void 0 && agent.session !== null) return agent.session;
88831
+ } catch {}
88832
+ const sessions = serviceOf(ctx, "sessions");
88833
+ if (typeof sessions?.get === "function") try {
88834
+ const session = sessions.get(caller);
88835
+ if (session !== void 0 && session !== null) return session;
88836
+ } catch {
88837
+ return;
88838
+ }
88839
+ }
88840
+ /**
88841
+ * 从调用方自己的持久会话日志里倒序查找已落盘的 `job/settled` 记录。
88842
+ *
88843
+ * 严格校验 `owner === caller`:无主或归属其它会话的任务绝不跨会话恢复。
88844
+ */
88845
+ function findSettledJobInSession(session, jobId, caller) {
88846
+ if (!isRecord$12(session)) return void 0;
88847
+ const snapshot = session["snapshotEvents"];
88848
+ if (typeof snapshot !== "function") return void 0;
88849
+ let events;
88850
+ try {
88851
+ events = snapshot.call(session);
88852
+ } catch {
88853
+ return;
88854
+ }
88855
+ if (!Array.isArray(events)) return void 0;
88856
+ for (let index = events.length - 1; index >= 0; index -= 1) {
88857
+ const event = events[index];
88858
+ if (!isRecord$12(event) || event["type"] !== "job/settled") continue;
88859
+ const data = event["data"];
88860
+ if (!isRecord$12(data)) continue;
88861
+ if (data["jobId"] !== jobId) continue;
88862
+ if (data["owner"] !== caller) return void 0;
88863
+ const kind = typeof data["kind"] === "string" && data["kind"].length > 0 ? data["kind"] : "job";
88864
+ const label = typeof data["label"] === "string" && data["label"].length > 0 ? data["label"] : jobId;
88865
+ const status = isTerminalStatus(data["status"]) ? data["status"] : "killed";
88866
+ const detail = typeof data["detail"] === "string" && data["detail"].length > 0 ? data["detail"] : void 0;
88867
+ const startedAt = typeof data["startedAt"] === "number" && Number.isFinite(data["startedAt"]) ? data["startedAt"] : 0;
88868
+ const finishedAt = typeof data["finishedAt"] === "number" && Number.isFinite(data["finishedAt"]) ? data["finishedAt"] : void 0;
88869
+ return {
88870
+ id: jobId,
88871
+ kind,
88872
+ label,
88873
+ owner: caller,
88874
+ status,
88875
+ ...detail === void 0 ? {} : { detail },
88876
+ startedAt,
88877
+ ...finishedAt === void 0 ? {} : { finishedAt },
88878
+ output: {
88879
+ total: 0,
88880
+ earliest: 0
88881
+ }
88882
+ };
88883
+ }
88884
+ }
88885
+ function resolveMethodTarget$1(service, name) {
88886
+ if (typeof service !== "object" || service === null) return void 0;
88887
+ const own = service;
88888
+ if (Object.prototype.hasOwnProperty.call(own, name)) {
88889
+ const original = own[name];
88890
+ if (typeof original !== "function") return void 0;
88891
+ return {
88892
+ holder: own,
88893
+ name,
88894
+ original,
88895
+ descriptor: Object.getOwnPropertyDescriptor(own, name)
88896
+ };
88897
+ }
88898
+ const proto = Object.getPrototypeOf(service);
88899
+ if (proto === null || proto === Object.prototype || proto === Function.prototype) return void 0;
88900
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
88901
+ if (descriptor === void 0 || typeof descriptor.value !== "function") return void 0;
88902
+ return {
88903
+ holder: proto,
88904
+ name,
88905
+ original: descriptor.value,
88906
+ descriptor
88907
+ };
88908
+ }
88909
+ function installMethod$1(target, replacement) {
88910
+ try {
88911
+ Object.defineProperty(target.holder, target.name, {
88912
+ configurable: target.descriptor?.configurable ?? true,
88913
+ enumerable: target.descriptor?.enumerable ?? false,
88914
+ writable: target.descriptor?.writable ?? true,
88915
+ value: replacement
88916
+ });
88917
+ return true;
88918
+ } catch {
88919
+ return false;
88920
+ }
88921
+ }
88922
+ function restoreMethod$1(target) {
88923
+ try {
88924
+ if (target.descriptor !== void 0) Object.defineProperty(target.holder, target.name, target.descriptor);
88925
+ else delete target.holder[target.name];
88926
+ } catch {}
88927
+ }
88928
+ function alreadyPatched$1(patched, holder, name) {
88929
+ return patched.get(holder)?.has(name) === true;
88930
+ }
88931
+ function markPatched$1(patched, holder, name) {
88932
+ let set = patched.get(holder);
88933
+ if (set === void 0) {
88934
+ set = /* @__PURE__ */ new Set();
88935
+ patched.set(holder, set);
88936
+ }
88937
+ set.add(name);
88938
+ }
88939
+ /**
88940
+ * 安装「已结算后台任务跨回合幂等守卫」。
88941
+ *
88942
+ * @param ctx - 宿主上下文。
88943
+ * @returns 守卫句柄。
88944
+ */
88945
+ function installSettledJobGuard(ctx) {
88946
+ const restores = [];
88947
+ const patchedMethods = /* @__PURE__ */ new WeakMap();
88948
+ let closed = false;
88949
+ const recoverForCaller = (rawId, rawCaller) => {
88950
+ if (closed) return void 0;
88951
+ if (typeof rawId !== "string" || rawId.length === 0) return void 0;
88952
+ if (typeof rawCaller !== "string" || rawCaller.length === 0) return void 0;
88953
+ return findSettledJobInSession(resolveCallerSession(ctx, rawCaller), rawId, rawCaller);
88954
+ };
88955
+ const patchSync = (jobs, methodName, onRecovered) => {
88956
+ const target = resolveMethodTarget$1(jobs, methodName);
88957
+ if (target === void 0 || alreadyPatched$1(patchedMethods, target.holder, target.name)) return;
88958
+ const original = target.original;
88959
+ const wrapped = function(...args) {
88960
+ const receiver = this ?? jobs;
88961
+ try {
88962
+ return original.call(receiver, ...args);
88963
+ } catch (error) {
88964
+ const id = typeof args[0] === "string" ? args[0] : "";
88965
+ if (id !== "" && isUnknownJobError(error, id)) {
88966
+ const recovered = recoverForCaller(id, args[1]);
88967
+ if (recovered !== void 0) return onRecovered(recovered);
88968
+ }
88969
+ throw error;
88970
+ }
88971
+ };
88972
+ if (!installMethod$1(target, wrapped)) return;
88973
+ markPatched$1(patchedMethods, target.holder, target.name);
88974
+ restores.push(() => {
88975
+ restoreMethod$1(target);
88976
+ });
88977
+ };
88978
+ const patchWait = (jobs) => {
88979
+ const target = resolveMethodTarget$1(jobs, "wait");
88980
+ if (target === void 0 || alreadyPatched$1(patchedMethods, target.holder, target.name)) return;
88981
+ const original = target.original;
88982
+ const wrapped = async function(...args) {
88983
+ const receiver = this ?? jobs;
88984
+ try {
88985
+ return await original.call(receiver, ...args);
88986
+ } catch (error) {
88987
+ const id = typeof args[0] === "string" ? args[0] : "";
88988
+ if (id !== "" && isUnknownJobError(error, id)) {
88989
+ const recovered = recoverForCaller(id, args[2]);
88990
+ if (recovered !== void 0) return recovered;
88991
+ }
88992
+ throw error;
88993
+ }
88994
+ };
88995
+ if (!installMethod$1(target, wrapped)) return;
88996
+ markPatched$1(patchedMethods, target.holder, target.name);
88997
+ restores.push(() => {
88998
+ restoreMethod$1(target);
88999
+ });
89000
+ };
89001
+ const wrap = (jobs) => {
89002
+ if (jobs === void 0) return;
89003
+ patchSync(jobs, "kill", () => "already-finished");
89004
+ patchSync(jobs, "get", (view) => view);
89005
+ patchSync(jobs, "read", (view) => ({
89006
+ chunks: [],
89007
+ lossy: false,
89008
+ job: view
89009
+ }));
89010
+ patchWait(jobs);
89011
+ };
89012
+ const immediate = serviceOf(ctx, "jobs");
89013
+ if (immediate !== void 0) wrap(immediate);
89014
+ else {
89015
+ const inject = ctx?.inject;
89016
+ if (typeof inject === "function") {
89017
+ let wrapped = false;
89018
+ try {
89019
+ inject.call(ctx, ["jobs"], (scope) => {
89020
+ if (wrapped || closed) return;
89021
+ wrapped = true;
89022
+ wrap(serviceOf(scope, "jobs"));
89023
+ });
89024
+ } catch {}
89025
+ }
89026
+ }
89027
+ return { dispose() {
89028
+ if (closed) return;
89029
+ closed = true;
89030
+ for (const restore of restores.splice(0)) try {
89031
+ restore();
89032
+ } catch {}
89033
+ } };
89034
+ }
89035
+ //#endregion
88231
89036
  //#region src/host/team-policy-cache-guard.ts
88232
89037
  /**
88233
89038
  * 「Agent Teams 策略段前缀缓存守卫」—— 把内置 `team:policy` 段从 system 里摘掉,
@@ -88567,7 +89372,7 @@ const GUARDED_EVENTS = [
88567
89372
  * @returns 可销毁的句柄。
88568
89373
  */
88569
89374
  function installCompactionPolicyGuard(ctx, options = {}) {
88570
- const host = isRecord$12(ctx) ? ctx : void 0;
89375
+ const host = isRecord$11(ctx) ? ctx : void 0;
88571
89376
  const provider = options.provider ?? "kimi-code";
88572
89377
  const defaults = {
88573
89378
  provider,
@@ -88642,9 +89447,9 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88642
89447
  * @param source - agent、header 或配置对象。
88643
89448
  */
88644
89449
  const observeFrom = (source) => {
88645
- if (!isRecord$12(source)) return;
89450
+ if (!isRecord$11(source)) return;
88646
89451
  const session = source["session"];
88647
- if (isRecord$12(session)) {
89452
+ if (isRecord$11(session)) {
88648
89453
  const requestHeader = session["requestHeader"];
88649
89454
  if (typeof requestHeader === "function") {
88650
89455
  let header;
@@ -88653,16 +89458,16 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88653
89458
  } catch {
88654
89459
  header = void 0;
88655
89460
  }
88656
- if (isRecord$12(header)) {
89461
+ if (isRecord$11(header)) {
88657
89462
  const config = header["config"];
88658
- if (isRecord$12(config)) observe(readString(config["provider"]), readString(config["model"]));
89463
+ if (isRecord$11(config)) observe(readString(config["provider"]), readString(config["model"]));
88659
89464
  }
88660
89465
  }
88661
89466
  }
88662
89467
  const optionsField = source["options"];
88663
- if (isRecord$12(optionsField)) observe(readString(optionsField["provider"]), readString(optionsField["model"]));
89468
+ if (isRecord$11(optionsField)) observe(readString(optionsField["provider"]), readString(optionsField["model"]));
88664
89469
  const config = source["config"];
88665
- if (isRecord$12(config)) observe(readString(config["provider"]), readString(config["model"]));
89470
+ if (isRecord$11(config)) observe(readString(config["provider"]), readString(config["model"]));
88666
89471
  };
88667
89472
  /**
88668
89473
  * 读一个字符串字段。
@@ -88692,10 +89497,10 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88692
89497
  * @returns 是否真的改写了。
88693
89498
  */
88694
89499
  const patchEngine = (engine) => {
88695
- if (disposed || !isRecord$12(engine)) return false;
89500
+ if (disposed || !isRecord$11(engine)) return false;
88696
89501
  if (patched.has(engine)) return false;
88697
89502
  const config = engine["config"];
88698
- if (!isRecord$12(config)) return false;
89503
+ if (!isRecord$11(config)) return false;
88699
89504
  const originalPolicies = config["modelPolicies"];
88700
89505
  if (!Array.isArray(originalPolicies)) return false;
88701
89506
  const originalConfig = config;
@@ -88765,7 +89570,7 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88765
89570
  let nextArgs = args;
88766
89571
  if (name === "summarize") {
88767
89572
  const input = args[0];
88768
- if (isRecord$12(input)) {
89573
+ if (isRecord$11(input)) {
88769
89574
  const messages = input["messages"];
88770
89575
  if (Array.isArray(messages)) {
88771
89576
  const replaced = replaceCompactionInstruction(messages);
@@ -88800,12 +89605,12 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88800
89605
  * @returns 是否改写了新引擎。
88801
89606
  */
88802
89607
  const patchStoreEntry = (entry) => {
88803
- if (!isRecord$12(entry)) return false;
89608
+ if (!isRecord$11(entry)) return false;
88804
89609
  const record = entry;
88805
89610
  const name = readServiceName(record);
88806
89611
  if (name !== void 0 && name !== "compaction") return false;
88807
89612
  const value = record.value ?? record.impl;
88808
- if (isRecord$12(value)) {
89613
+ if (isRecord$11(value)) {
88809
89614
  if (patchEngine(value)) return true;
88810
89615
  const nested = value["engine"];
88811
89616
  if (nested !== void 0) return patchEngine(nested);
@@ -88820,11 +89625,11 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88820
89625
  * @returns 本次新改写的引擎数。
88821
89626
  */
88822
89627
  const scanStore = (target) => {
88823
- if (!isRecord$12(target)) return 0;
89628
+ if (!isRecord$11(target)) return 0;
88824
89629
  const reflect = target["reflect"];
88825
- if (!isRecord$12(reflect)) return 0;
89630
+ if (!isRecord$11(reflect)) return 0;
88826
89631
  const store = reflect["store"];
88827
- if (!isRecord$12(store)) return 0;
89632
+ if (!isRecord$11(store)) return 0;
88828
89633
  const services = store;
88829
89634
  let count = 0;
88830
89635
  for (const key of Object.getOwnPropertySymbols(services)) if (patchStoreEntry(services[key])) count += 1;
@@ -88838,12 +89643,12 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88838
89643
  * @returns 是否改写了新引擎。
88839
89644
  */
88840
89645
  const patchServiceFrom = (target, name) => {
88841
- if (!isRecord$12(target)) return false;
89646
+ if (!isRecord$11(target)) return false;
88842
89647
  const get = target["get"];
88843
89648
  if (typeof get !== "function") return false;
88844
89649
  try {
88845
89650
  const service = get.call(target, name);
88846
- if (!isRecord$12(service)) return false;
89651
+ if (!isRecord$11(service)) return false;
88847
89652
  if (patchEngine(service)) return true;
88848
89653
  const nested = service["engine"];
88849
89654
  return nested === void 0 ? false : patchEngine(nested);
@@ -88886,7 +89691,7 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88886
89691
  try {
88887
89692
  for (const arg of args) {
88888
89693
  observeFrom(arg);
88889
- if (isRecord$12(arg)) observeFrom(arg["agent"]);
89694
+ if (isRecord$11(arg)) observeFrom(arg["agent"]);
88890
89695
  }
88891
89696
  scanAndPatch();
88892
89697
  const agentCtx = findAgentContext(args);
@@ -88925,9 +89730,9 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88925
89730
  function readServiceName(record) {
88926
89731
  if (typeof record.name === "string") return record.name;
88927
89732
  const impl = record.impl;
88928
- if (isRecord$12(impl) && typeof impl["name"] === "string") return impl["name"];
89733
+ if (isRecord$11(impl) && typeof impl["name"] === "string") return impl["name"];
88929
89734
  const value = record.value;
88930
- if (isRecord$12(value) && typeof value["name"] === "string") return value["name"];
89735
+ if (isRecord$11(value) && typeof value["name"] === "string") return value["name"];
88931
89736
  }
88932
89737
  /**
88933
89738
  * 从一次事件派发的参数里找出 agent 的上下文。
@@ -88939,7 +89744,7 @@ function findAgentContext(args) {
88939
89744
  for (const arg of args) {
88940
89745
  const direct = readContext(arg);
88941
89746
  if (direct !== void 0) return direct;
88942
- if (isRecord$12(arg)) {
89747
+ if (isRecord$11(arg)) {
88943
89748
  const nested = readContext(arg["agent"]);
88944
89749
  if (nested !== void 0) return nested;
88945
89750
  }
@@ -88952,10 +89757,10 @@ function findAgentContext(args) {
88952
89757
  * @returns 上下文;不像时返回 `undefined`。
88953
89758
  */
88954
89759
  function readContext(value) {
88955
- if (!isRecord$12(value)) return void 0;
89760
+ if (!isRecord$11(value)) return void 0;
88956
89761
  const ctx = value["ctx"];
88957
- if (!isRecord$12(ctx)) return void 0;
88958
- if (typeof ctx["get"] === "function" || isRecord$12(ctx["reflect"])) return ctx;
89762
+ if (!isRecord$11(ctx)) return void 0;
89763
+ if (typeof ctx["get"] === "function" || isRecord$11(ctx["reflect"])) return ctx;
88959
89764
  }
88960
89765
  /**
88961
89766
  * 订阅一个事件,优先用 `prepend: true`。
@@ -88995,7 +89800,7 @@ function subscribe(host, event, listener) {
88995
89800
  * @param value - 任意值。
88996
89801
  * @returns 是非 null 的对象时为 `true`。
88997
89802
  */
88998
- function isRecord$12(value) {
89803
+ function isRecord$11(value) {
88999
89804
  return typeof value === "object" && value !== null;
89000
89805
  }
89001
89806
  /**
@@ -89095,449 +89900,6 @@ function markPatched(patched, holder, name) {
89095
89900
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
89096
89901
  else names.add(name);
89097
89902
  }
89098
- //#endregion
89099
- //#region src/host/dispatch-execution-guard.ts
89100
- /**
89101
- * 「调度执行守卫」——在**执行期**拦住两类越界调用。
89102
- *
89103
- * ## 为什么需要它
89104
- *
89105
- * 调度模式(dispatch preset)里主控与子代理跑在同一进程、同一套工具注册面上,
89106
- * 于是有两件事会悄悄坏掉:
89107
- *
89108
- * 1. **子代理越权调用主控专用工具**。`ask_user_question`、`exit_plan_mode`、
89109
- * `create_goal` 这些是**主控的**决定口:让子代理自己问用户、自己进/出计划模式、
89110
- * 自己建目标,都会把「一个决定」拆成两个决定——子代理问完,用户答了,主控却
89111
- * 不知道问过什么。子代理本来就该把未决问题**带回**主控,而不是自己去问。
89112
- * 2. **等待子代理期间空等**。子代理还在跑的时候,主控用 `sleep` 空转,既烧 token
89113
- * 又错过通知到达的那一刻。正确做法是**结束这一轮**,让内置的子代理完成通知把
89114
- * 它叫醒(R-W)。
89115
- *
89116
- * ## 零 Kimi Code 缓存漂移
89117
- *
89118
- * 守卫**只**用 `ctx.tools.guard(exec => …)` 这一条执行期接缝:
89119
- *
89120
- * - 不包 `tools.definitions`,不包 `tools.register`,不听
89121
- * `system-prompt/assemble`——工具声明面(`tools:sdk` 段与顶层工具 schema)
89122
- * 保持逐字节恒定,Kimi Code 的前缀缓存不会因为这个守卫重建一次。
89123
- * - 拦截发生在**调用执行时**,模型看到的工具表完全没变,所以「看得见」与
89124
- * 「叫得动」仍然被拆开:工具照常出现在表里,只是越界的调用被拒。
89125
- *
89126
- * ## 判定从哪来
89127
- *
89128
- * 两条判定都走**结构探测**,不 import 任何 harness 代次专属的类型:
89129
- *
89130
- * - 「你是不是子代理」:先看**持久会话血缘**(`session.header.origin ===
89131
- * 'subagent'`),再看宿主活着的**存活根**(`agents.roots()`)。两条都是
89132
- * 读得着的既有事实,不新增任何状态。
89133
- * - 「你还有没有在跑的子代理」:依次问 `subagents.list` 与 `agents.list`,
89134
- * 看有没有 `status === 'running'` 的直接子代。
89135
- *
89136
- * @module @eddyskywalker/dsh-chatgpt-subscription/host/dispatch-execution-guard
89137
- */
89138
- /**
89139
- * 子代理**不得**直接调用的主控专用工具。
89140
- *
89141
- * 这七个都是「决定口」或「交付口」,不是「干活口」:
89142
- *
89143
- * - `ask_user_question` / `exit_plan_mode` / `enter_plan_mode` /
89144
- * `leave_plan_mode`:计划与提问的决定权在主控;
89145
- * - `create_goal` / `update_goal`:目标由主控持有;
89146
- * - `present`:交付口由主控掌握。
89147
- *
89148
- * 子代理遇到需要决定的地方,正确动作是**把问题带回主控**,让主控去调这些工具。
89149
- */
89150
- const DEFAULT_DELEGATED_BLOCKED_TOOLS = Object.freeze([
89151
- "ask_user_question",
89152
- "exit_plan_mode",
89153
- "enter_plan_mode",
89154
- "leave_plan_mode",
89155
- "create_goal",
89156
- "update_goal",
89157
- "present"
89158
- ]);
89159
- /**
89160
- * 承载「等待」语义的 shell 工具。
89161
- *
89162
- * 这三个是本包与宿主都会用到的 shell 入口:它们的 `command` 参数里可能藏着
89163
- * `sleep`。空等只在**确实有在跑的子代理**时才是问题(有子代理时应当结束这一
89164
- * 轮等通知),没有子代理时 `sleep 1` 只是普通命令,不该拦。
89165
- */
89166
- const DEFAULT_SHELL_TOOLS = Object.freeze([
89167
- "bash",
89168
- "pwsh",
89169
- "win_bash"
89170
- ]);
89171
- /** 一次执行里被判定为「纯等待」的命令形态。 */
89172
- const SLEEP_COMMAND_PATTERN = /^(?:sleep\s+\d+(?:\.\d+)?[smhd]?|Start-Sleep\s+(?:-(?:Seconds|Milliseconds|s|m)\s+)?\d+(?:\.\d+)?|timeout(?:\.exe)?\s+(?:\/t\s+)?\d+(?:\s+\/nobreak)?|ping(?:\.exe)?\s+-(?:n|c)\s+\d+\s+(?:127\.0\.0\.1|localhost)(?:\s+>|\s*$).*)$/i;
89173
- /** 无害的空转语句(回显、真、冒号)。 */
89174
- const NOOP_COMMAND_PATTERN = /^(?:echo\b.*|Write-Host\b.*|Write-Output\b.*|true|:)$/;
89175
- /** 注释行。 */
89176
- const COMMENT_COMMAND_PATTERN = /^#/;
89177
- /**
89178
- * 读宿主上下文上「直接注入」的 running-child 判定回调。
89179
- *
89180
- * 这一步必须**try/catch**:真实 Cordis 的上下文代理对任何没有 inject 的属性直读
89181
- * 都当场抛 `cannot get property "..." without inject`,一次读失败不该让守卫在
89182
- * 每次工具调用上抛错。
89183
- *
89184
- * @param ctx - 宿主上下文。
89185
- * @returns 注入的回调;没有时为 undefined。
89186
- */
89187
- function injectedHasRunningChildren(ctx) {
89188
- if (!isRecord$11(ctx)) return void 0;
89189
- let injected;
89190
- try {
89191
- injected = ctx["hasRunningChildren"];
89192
- } catch {
89193
- return;
89194
- }
89195
- return typeof injected === "function" ? injected : void 0;
89196
- }
89197
- /**
89198
- * 判断一个值是不是可读的记录对象。
89199
- *
89200
- * @param value - 候选值。
89201
- * @returns 是记录对象时为 true。
89202
- */
89203
- function isRecord$11(value) {
89204
- return typeof value === "object" && value !== null;
89205
- }
89206
- /**
89207
- * 从宿主上下文里读一个**可选**服务。
89208
- *
89209
- * 一律走 `ctx.get()`:没写进 `inject` 的服务名不能当属性读,真实 Cordis 会当场
89210
- * 抛 `cannot get property "..." without inject`,整个插件降级。读不到就是读不到,
89211
- * 守卫据此放行,绝不因此让插件挂不上。
89212
- *
89213
- * @param ctx - 宿主上下文。
89214
- * @param name - 服务名。
89215
- * @returns 服务实例;不存在或读取抛错时为 undefined。
89216
- */
89217
- function readService(ctx, name) {
89218
- const reader = ctx?.get;
89219
- if (typeof reader !== "function") return void 0;
89220
- try {
89221
- return reader.call(ctx, name);
89222
- } catch {
89223
- return;
89224
- }
89225
- }
89226
- /**
89227
- * 读一个宿主上下文里的工具注册面。
89228
- *
89229
- * @param ctx - 宿主上下文。
89230
- * @returns 带 `guard` 接缝的注册面;没有时为 undefined。
89231
- */
89232
- function toolsOf$2(ctx) {
89233
- if (!isRecord$11(ctx)) return void 0;
89234
- let tools;
89235
- try {
89236
- tools = ctx["tools"];
89237
- } catch {
89238
- tools = void 0;
89239
- }
89240
- if (!isRecord$11(tools)) tools = readService(ctx, "tools");
89241
- return isRecord$11(tools) ? tools : void 0;
89242
- }
89243
- /**
89244
- * 读一条记录的 `id`。
89245
- *
89246
- * @param value - 候选记录。
89247
- * @returns id 字符串;没有时为 undefined。
89248
- */
89249
- function idOf(value) {
89250
- if (!isRecord$11(value)) return void 0;
89251
- const id = value["id"];
89252
- return typeof id === "string" ? id : void 0;
89253
- }
89254
- /**
89255
- * 读一条会话记录头里的 `id`。
89256
- *
89257
- * @param agent - 候选 agent。
89258
- * @returns 会话 id;没有时为 undefined。
89259
- */
89260
- function sessionIdOf$1(agent) {
89261
- if (!isRecord$11(agent)) return void 0;
89262
- const session = agent["session"];
89263
- if (!isRecord$11(session)) return void 0;
89264
- const header = session["header"];
89265
- return idOf(header);
89266
- }
89267
- /**
89268
- * 这个调用方是不是**子代理**。
89269
- *
89270
- * 判定分两级,先看能独立成立的持久事实,再看宿主当前活着的存活根:
89271
- *
89272
- * 1. **持久会话血缘**:`session.header.origin === 'subagent'`。这是子代理会话
89273
- * 创建时就写下的血缘,跨宿主代次都在,不依赖任何服务是否挂载。
89274
- * 2. **存活根判定**:宿主提供 `agents.roots()` 时,一个**不在**存活根集合里的
89275
- * agent 就是别人的孩子。先核对**存活**(`agents.get(id) === agent`):不是
89276
- * 那个 id 当前登记的存活 agent,说明这个调用方已经过期或已被顶替——判它是不是
89277
- * 子代理没有意义,放行。
89278
- *
89279
- * 宿主没有 `agents` 服务(或老代没有 `roots`)时,判不了就**放行**:把一个
89280
- * 读不出来的身份当成子代理去拦,会拦掉主控自己的正常调用。
89281
- *
89282
- * @param agent - 调用方 agent。
89283
- * @param ctx - 宿主上下文(可选;用于读 `agents` 服务)。
89284
- * @returns 是子代理时为 true。
89285
- */
89286
- function isDelegatedCaller(agent, ctx) {
89287
- if (!isRecord$11(agent)) return false;
89288
- const session = agent["session"];
89289
- if (isRecord$11(session)) {
89290
- const header = session["header"];
89291
- if (isRecord$11(header) && header["origin"] === "subagent") return true;
89292
- }
89293
- const agents = readService(ctx, "agents");
89294
- if (!isRecord$11(agents)) return false;
89295
- const roots = rootsOf(agents);
89296
- if (roots === void 0) return false;
89297
- const id = idOf(agent);
89298
- if (typeof id === "string" && typeof agents["get"] === "function") {
89299
- let live;
89300
- try {
89301
- live = agents["get"].call(agents, id);
89302
- } catch {
89303
- live = void 0;
89304
- }
89305
- if (live !== agent) return false;
89306
- }
89307
- return !roots.includes(agent);
89308
- }
89309
- /**
89310
- * 读宿主存活根集合。
89311
- *
89312
- * @param agents - `agents` 服务。
89313
- * @returns 存活根数组;服务没有 `roots` 或读取抛错时为 undefined。
89314
- */
89315
- function rootsOf(agents) {
89316
- const roots = agents["roots"];
89317
- if (typeof roots !== "function") return void 0;
89318
- try {
89319
- const listed = roots.call(agents);
89320
- return Array.isArray(listed) ? listed : void 0;
89321
- } catch {
89322
- return;
89323
- }
89324
- }
89325
- /**
89326
- * 判断一条命令是不是**纯等待**。
89327
- *
89328
- * 判据:整条命令**按分隔符切开后**,至少有一条是 sleep/wait,而**每一条**都是
89329
- * sleep/wait 或者无害的空转(回显、真、冒号、注释)。只要有一条在做真事
89330
- * (`sleep 1 && npm test`、`git status`、`node …`),整条命令就不是纯等待——
89331
- * 这类命令**必须放行**,否则守卫会拦掉正当的后台工作。
89332
- *
89333
- * @param command - 候选命令。
89334
- * @returns 是纯等待时为 true。
89335
- */
89336
- function isPureSleepCommand(command) {
89337
- if (typeof command !== "string") return false;
89338
- const trimmed = command.trim();
89339
- if (trimmed.length === 0) return false;
89340
- const statements = trimmed.split(/;|&&|\n/).map((statement) => statement.trim()).filter((statement) => statement.length > 0);
89341
- if (statements.length === 0) return false;
89342
- let sawWait = false;
89343
- for (const statement of statements) {
89344
- if (SLEEP_COMMAND_PATTERN.test(statement)) {
89345
- sawWait = true;
89346
- continue;
89347
- }
89348
- if (!isNoopStatement(statement)) return false;
89349
- }
89350
- return sawWait;
89351
- }
89352
- /**
89353
- * 判断一条语句是不是无害的空转。
89354
- *
89355
- * @param statement - 候选语句。
89356
- * @returns 是空转时为 true。
89357
- */
89358
- function isNoopStatement(statement) {
89359
- return NOOP_COMMAND_PATTERN.test(statement) || COMMENT_COMMAND_PATTERN.test(statement);
89360
- }
89361
- /**
89362
- * 这个调用方当前**还有没有在跑的子代理**。
89363
- *
89364
- * 两条宿主探测,任何一条给出答案就算数:
89365
- *
89366
- * 1. `subagents.list(agent, 'children')`(老代退化成 `subagents.list(agent)`):
89367
- * 直接子代里有没有 `status === 'running'` 的。
89368
- * 2. `agents.list()`:活着且 `status === 'running'`、并且 `parent` 就是这个
89369
- * 调用方的那些 agent。
89370
- *
89371
- * 宿主提供 `hasRunningChildren` 回调时以它为准(直接注入口,见
89372
- * {@link DispatchExecutionGuardOptions.hasRunningChildren})。
89373
- *
89374
- * @param agent - 调用方 agent。
89375
- * @param ctx - 宿主上下文(可选;用于读 `subagents` / `agents` 服务)。
89376
- * @returns 有在跑的子代理时为 true。
89377
- */
89378
- function hasRunningChildSubagent(agent, ctx) {
89379
- if (!isRecord$11(agent)) return false;
89380
- const injected = injectedHasRunningChildren(ctx);
89381
- if (injected !== void 0) try {
89382
- return injected(agent) === true;
89383
- } catch {
89384
- return false;
89385
- }
89386
- const subagents = readService(ctx, "subagents");
89387
- if (isRecord$11(subagents) && typeof subagents["list"] === "function") {
89388
- if (entriesOf(callList(subagents["list"], subagents, agent)).some((entry) => entry["status"] === "running")) return true;
89389
- }
89390
- const agents = readService(ctx, "agents");
89391
- if (isRecord$11(agents) && typeof agents["list"] === "function") {
89392
- const listed = callList(agents["list"], agents, agent, false);
89393
- const parentIds = /* @__PURE__ */ new Set();
89394
- const ownId = idOf(agent);
89395
- if (ownId !== void 0) parentIds.add(ownId);
89396
- const headerId = sessionIdOf$1(agent);
89397
- if (headerId !== void 0) parentIds.add(headerId);
89398
- if (entriesOf(listed).some((entry) => entry["status"] === "running" && typeof entry["parent"] === "string" && parentIds.has(entry["parent"]))) return true;
89399
- }
89400
- return false;
89401
- }
89402
- /**
89403
- * 调用一个「列出子代」的宿主方法。
89404
- *
89405
- * 先试带 scope 的形态(`list(agent, 'children')`),老代只认 `list(agent)`,
89406
- * 再老一代的 `agents.list()` 不接参数——三种都试,谁先成功用谁。
89407
- *
89408
- * @param fn - 宿主方法。
89409
- * @param self - 调用宿主。
89410
- * @param agent - 调用方 agent。
89411
- * @param scoped - 是否先试带 scope 的形态。
89412
- * @returns 宿主返回值;全部失败时为 undefined。
89413
- */
89414
- function callList(fn, self, agent, scoped = true) {
89415
- if (typeof fn !== "function") return void 0;
89416
- const call = fn;
89417
- const attempts = scoped ? [
89418
- [agent, "children"],
89419
- [agent],
89420
- []
89421
- ] : [
89422
- [],
89423
- [agent],
89424
- [agent, "children"]
89425
- ];
89426
- for (const args of attempts) try {
89427
- const listed = call.apply(self, args);
89428
- if (listed !== void 0) return listed;
89429
- } catch {}
89430
- }
89431
- /**
89432
- * 从宿主返回值里取出「条目数组」。
89433
- *
89434
- * 宿主可能直接给数组,也可能给一个包着数组的记录(`children` / `agents`)。
89435
- *
89436
- * @param listed - 宿主返回值。
89437
- * @returns 条目数组;取不到时是空数组。
89438
- */
89439
- function entriesOf(listed) {
89440
- if (Array.isArray(listed)) return listed.filter(isRecord$11);
89441
- if (!isRecord$11(listed)) return [];
89442
- const entries = [];
89443
- for (const key of [
89444
- "children",
89445
- "agents",
89446
- "entries",
89447
- "list"
89448
- ]) {
89449
- const candidate = listed[key];
89450
- if (!Array.isArray(candidate)) continue;
89451
- for (const entry of candidate) if (isRecord$11(entry)) entries.push(entry);
89452
- }
89453
- return entries;
89454
- }
89455
- /**
89456
- * 解析「调度执行守卫」当前是否启用。
89457
- *
89458
- * 沿 volatile 活引用取当前值,所以设置保存后立刻生效。缺省、非法值、读取抛错都
89459
- * 按**开启**处理:守卫是保护,不是功能开关,读失败不该把保护关掉。
89460
- *
89461
- * @param config - 插件配置(活引用或普通值)。
89462
- * @returns 是否启用。
89463
- */
89464
- function resolveDispatchExecutionGuardEnabled(config) {
89465
- const field = config?.dispatchExecutionGuard;
89466
- if (field === void 0 || field === null) return true;
89467
- if (typeof field === "object" && typeof field.get === "function") try {
89468
- return field.get() !== false;
89469
- } catch {
89470
- return true;
89471
- }
89472
- return field !== false;
89473
- }
89474
- /**
89475
- * 子代理越权调用时的拒绝文案。
89476
- *
89477
- * 文案要**可执行**:光说「不许调」模型只会换个花样再试一次,所以这里写清楚
89478
- * 它该做什么——把未决问题带回主控,并带上报告状态。
89479
- *
89480
- * @param toolName - 被拦下的工具名。
89481
- * @returns 拒绝理由。
89482
- */
89483
- function delegatedDenial(toolName) {
89484
- return `DELEGATED_CALLER: "${toolName}" is a dispatch-level tool and a delegated subagent must not call it directly. Report back to the dispatcher instead: include status: "hand_back" or status: "blocked" and the unresolved question or decision in your report, and let the dispatcher act on it.`;
89485
- }
89486
- /**
89487
- * 子代理还在跑时用 sleep 空等的拒绝文案。
89488
- *
89489
- * @returns 拒绝理由。
89490
- */
89491
- function activeChildSleepDenial() {
89492
- return "ACTIVE_CHILD_SLEEP_FORBIDDEN: a child subagent is still running, so sleeping to wait for it is not allowed. End your turn now and let the built-in subagent completion notification wake you.";
89493
- }
89494
- /**
89495
- * 装一台「调度执行守卫」。
89496
- *
89497
- * 只用 `ctx.tools.guard()` 这一条执行期接缝:工具声明面(`tools:sdk` 段与顶层
89498
- * 工具 schema)一个字节都不改,Kimi Code 的前缀缓存不会因为这个守卫重建。
89499
- *
89500
- * @param ctx - 宿主上下文。
89501
- * @param options - 守卫选项。
89502
- * @returns 注销守卫的函数;宿主没有 `guard` 接缝时是一个空操作。
89503
- */
89504
- function installDispatchExecutionGuard(ctx, options = {}) {
89505
- const tools = toolsOf$2(ctx);
89506
- const register = tools?.guard;
89507
- if (typeof register !== "function") return () => void 0;
89508
- const enabled = options.enabled ?? (() => true);
89509
- const delegatedBlocked = new Set(DEFAULT_DELEGATED_BLOCKED_TOOLS);
89510
- const shellTools = new Set(DEFAULT_SHELL_TOOLS);
89511
- const hasRunningChildren = options.hasRunningChildren;
89512
- const guard = (execution) => {
89513
- if (!isEnabled(enabled)) return void 0;
89514
- const toolName = typeof execution?.name === "string" ? execution.name : void 0;
89515
- if (toolName === void 0) return void 0;
89516
- const agent = execution?.agent;
89517
- if (delegatedBlocked.has(toolName) && isDelegatedCaller(agent, ctx)) return delegatedDenial(toolName);
89518
- if (shellTools.has(toolName)) {
89519
- const args = isRecord$11(execution?.arguments) ? execution.arguments : void 0;
89520
- if (!(args?.["run_in_background"] === true) && isPureSleepCommand(args?.["command"])) {
89521
- if (hasRunningChildren !== void 0 ? hasRunningChildren(agent) === true : hasRunningChildSubagent(agent, ctx)) return activeChildSleepDenial();
89522
- }
89523
- }
89524
- };
89525
- const dispose = register.call(tools, guard);
89526
- return typeof dispose === "function" ? dispose : () => void 0;
89527
- }
89528
- /**
89529
- * 读一次开关。读失败按**开启**处理:保护不该因为一次读失败而失效。
89530
- *
89531
- * @param enabled - 开关读取函数。
89532
- * @returns 是否启用。
89533
- */
89534
- function isEnabled(enabled) {
89535
- try {
89536
- return enabled() !== false;
89537
- } catch {
89538
- return true;
89539
- }
89540
- }
89541
89903
  const ID_SCHEMA = z.string().min(1).max(64).regex(/^[A-Za-z0-9._:-]+$/);
89542
89904
  const TASK_REFERENCE_SCHEMA = z.string().min(1).max(160);
89543
89905
  const BOUNDARY_SCHEMA = z.string().min(1).max(120);
@@ -92883,23 +93245,16 @@ const DISPATCH_PRESET = {
92883
93245
  name: "@deepseek-ai/dsh-persona",
92884
93246
  config: {
92885
93247
  suffix: "Your working directory is {{cwd}}.",
92886
- prefix: `[Role Identification] If your first user message is a task brief (rather than a direct request from the human user), you are a subagent: the coordinator-facing rules in R0, R0.5, R-G, R1, R2, R4, R5, and R-W do not apply to you. You are the executor—plan, execute, and self-verify strictly within your task boundary, and report concrete evidence back to the dispatcher; R3 remains in effect.
93248
+ prefix: `[Role Identification] If your first user message is a task brief (rather than a direct request from the human user), you are a subagent: the coordinator-facing rules in R0, R-G, R1, R2, R4, R5, and R-W do not apply to you. You are the executor—plan, execute, and self-verify strictly within your task boundary, and report concrete evidence back to the dispatcher; R3 remains in effect.
92887
93249
  - [Standing Subagent Contract] Unless overridden by the brief, every subagent inherits these standing limits: retry attempts at most 3 total per brief; file edits bounded strictly to the declared Boundary module or files; read, search, verification, and test commands unlimited within that boundary; no per-cycle banking (all limits are single-run task totals, and unused allowance is not banked).
92888
93250
  - [Subagent Report Protocol] Every subagent completion report states status: ready | hand_back | blocked | failed | dropped, a summary, and evidence (diffs, test commands and outputs, before/after captures for UI). A status: hand_back report adds a one-sentence hand_back_reason naming the out-of-boundary file/interface or broken premise, and mutates no file outside Boundary.
92889
93251
  - [Coordinator-Only Tools] ask_user_question, enter_plan_mode, leave_plan_mode, exit_plan_mode, create_goal, update_goal, and present are coordinator-only: a subagent call is hard-blocked at execution time (DELEGATED_CALLER), while declarations remain visible to preserve the provider’s content-prefix cache. Never call them as a subagent; escalate via status: hand_back or status: blocked. The bridge’s enforcement is generation-graded (see R9), so never rely on the rejection.
92890
93252
  - [Dispatch Ledger] Call dispatch_ledger {action:"self"} once before your first delegation — it prints the v2 mandate protocol at the tail of its result — and follow that protocol verbatim for every dispatch, report, and steer.
92891
93253
 
92892
93254
  [Coordinator Identity] You are the Lead & Dispatch Agent: converge requirements, plan architecture, select R1 topology (Lead-Core / Hybrid / Fan-out), orchestrate subagents where ROI is positive, and own final acceptance.
92893
- [Execution Pipeline] R0.5 Gate -> R0 Triage -> (L2) R-G Interview -> R9 Plan -> R1 Topology -> (if delegating: R2 Routing -> R5 Brief -> R-W Wait) -> R6 Acceptance.
93255
+ [Execution Pipeline] R0 Triage -> (L2) R-G Interview -> R9 Plan -> R1 Topology -> (if delegating: R2 Routing -> R5 Brief -> R-W Wait) -> R6 Acceptance.
92894
93256
 
92895
- ## R0.5 · Compatibility Gate (Once per New Session, Before R0)
92896
- A legacy Agent Teams runtime (pre-#7519) registers spawn_teammate and replaces send_message / list_agents / interrupt_agent with team-scoped variants, leaving spawned subagents unreachable. The current runtime only adds a shared task board (team_task_create and its list/get/update siblings) and reuses this mode's tools.
92897
- Criterion: whether spawn_teammate exists (check via code_run). Never gate on wait_agent, send_message, list_agents, or interrupt_agent: this mode mounts those itself.
92898
- - Absent -> Write "Orchestration ready: subagent" and proceed to R0. The board tools stay declared whether or not the board is on - frozen declaration, not availability - so confirm with team_task_list before relying on it. When the board is on it is bookkeeping, not a second dispatcher: the dispatch ledger stays the dispatch authority and R5 briefs stay the task contract; publish cross-agent dependencies (blocked_by) and write scopes on one team task per brief, claimed and completed by the child that owns it; never start work because a task is ready, and never reassign a task to skip R6.
92899
- - Present -> This session runs the legacy runtime and is **not orchestratable**. In order, and nothing else:
92900
- 1. State in one sentence that the legacy runtime shadows the coordination tools and this mode has no degraded path.
92901
- 2. Call ask_user_question once (see R-W) offering: disable the legacy runtime, restart Host, **start a new session**; switch preset; or execute this task directly.
92902
- 3. **Do nothing else** until the user replies: no triage, plan, dispatch, file edits, or workarounds.
93257
+ [Agent Teams Board] A current harness adds exactly one Agent Teams surface beside this mode: the shared task board (team_task_create and its list/get/update siblings). It is bookkeeping, not a second dispatcher. The board tools stay declared whether or not the board is on - frozen declaration, not availability - so confirm with team_task_list before relying on it. When it is on, the dispatch ledger stays the dispatch authority and R5 briefs stay the task contract; publish cross-agent dependencies (blocked_by) and write scopes on one team task per brief, claimed and completed by the child that owns it; never start work because a task is ready, and never reassign a task to skip R6.
92903
93258
 
92904
93259
  ## R0 · Complexity Triage (Triage First, Then Choose the Workflow)
92905
93260
  Upon receiving a task, determine its tier by semantic impact and uncertainty first, write one line stating "Tier + Rationale", and run the matching workflow:
@@ -92970,7 +93325,7 @@ Fallback is a task-scoped, non-session-wide exception for delegated briefs: it c
92970
93325
 
92971
93326
  ## Anti-Patterns (Any Occurrence Is a Violation)
92972
93327
  1. Orchestrating or opening Plan Mode for an L0/L1 task, or before the R-G interview has converged (21. Entering Plan Mode for an L0/L1 task, or before the R-G interview has converged; 22. Using leave_plan_mode to bypass exit_plan_mode approval).
92973
- 2. Switching to teammate vocabulary to keep orchestrating under the legacy team runtime, or Treating the team task board as a substitute for R1 topology, R2 routing, R5 briefs, R6 acceptance, or R-W waiting (R0.5).
93328
+ 2. Switching to teammate vocabulary to keep orchestrating, or Treating the team task board as a substitute for R1 topology, R2 routing, R5 briefs, R6 acceptance, or R-W waiting (R1, R6).
92974
93329
  3. Omitting provider/model under [State: Listed], Second-guessing list_subagent_models output by hunting through local config or source files just to check which subagent models are allowed, or Reporting a route as "authorized", or claiming per-task routing is in force, when the session recorded no allowlist (R2).
92975
93330
  4. Writing a task brief with a range where a cap belongs ("2–5 files", "a few attempts"), or Asking a subagent to restate its rules as a correctness check (R0, R5).
92976
93331
  5. Running sleep, polling loops, or placeholder long commands to wait, Proactively creating a goal at the start of a task, or Ending with a paragraph of prose to wait for user sign-off instead of calling ask_user_question (R-W).
@@ -99769,6 +100124,23 @@ function applyToolDelta(entry, state) {
99769
100124
  const fn = isRecord$3(entry.function) ? entry.function : {};
99770
100125
  const out = [];
99771
100126
  let call = state.toolCalls.get(wireIndex);
100127
+ const incomingId = asString$1(entry.id);
100128
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
100129
+ const closedBlock = {
100130
+ type: "tool-call",
100131
+ id: toToolCallId(call.id),
100132
+ name: call.name,
100133
+ arguments: call.arguments === "" ? "{}" : call.arguments
100134
+ };
100135
+ state.blocks[call.blockIndex] = closedBlock;
100136
+ out.push({
100137
+ type: "block-end",
100138
+ index: call.blockIndex,
100139
+ block: closedBlock
100140
+ });
100141
+ state.toolCalls.delete(wireIndex);
100142
+ call = void 0;
100143
+ }
99772
100144
  if (call === void 0) {
99773
100145
  out.push(...closeCurrent(state));
99774
100146
  call = {
@@ -100138,6 +100510,30 @@ function messagesAssistantContent(message) {
100138
100510
  return blocks;
100139
100511
  }
100140
100512
  /**
100513
+ * Merge neighbouring same-role turns.
100514
+ *
100515
+ * DSH history can hold two user messages in a row (an injected context notice
100516
+ * followed by the real turn) and a tool result is itself a user-role message;
100517
+ * the Messages wire wants one user turn, so consecutive turns of one role are
100518
+ * folded together in order.
100519
+ */
100520
+ function mergeAnthropicMessages(entries) {
100521
+ const merged = [];
100522
+ for (const entry of entries) {
100523
+ if (entry.content.length === 0) continue;
100524
+ const last = merged[merged.length - 1];
100525
+ if (last !== void 0 && last.role === entry.role) {
100526
+ last.content = [...last.content, ...entry.content];
100527
+ continue;
100528
+ }
100529
+ merged.push({
100530
+ role: entry.role,
100531
+ content: [...entry.content]
100532
+ });
100533
+ }
100534
+ return merged;
100535
+ }
100536
+ /**
100141
100537
  * Build one `/v1/messages` body.
100142
100538
  *
100143
100539
  * `stream: true` is mandatory: the adapter reads every response as SSE, and a
@@ -100155,13 +100551,12 @@ function buildMessagesRequest(options, request = {}) {
100155
100551
  const role = isToolResultMessage(message) || message.role !== "assistant" ? "user" : "assistant";
100156
100552
  const content = role === "user" ? messagesUserContent(message, images) : messagesAssistantContent(message);
100157
100553
  if (content.length === 0) continue;
100158
- const previous = entries[entries.length - 1];
100159
- if (previous !== void 0 && previous.role === role) previous.content.push(...content);
100160
- else entries.push({
100554
+ entries.push({
100161
100555
  role,
100162
100556
  content
100163
100557
  });
100164
100558
  }
100559
+ const messages = cleanOrphanedToolCalls(mergeAnthropicMessages(entries), mergeAnthropicMessages);
100165
100560
  const system = leadingSystemTextRaw(options);
100166
100561
  const tools = options.tools ?? [];
100167
100562
  const effort = request.effort !== void 0 && (request.efforts ?? []).length > 0 ? request.effort : void 0;
@@ -100170,7 +100565,7 @@ function buildMessagesRequest(options, request = {}) {
100170
100565
  model: options.model,
100171
100566
  max_tokens: maxTokens,
100172
100567
  stream: true,
100173
- messages: entries,
100568
+ messages,
100174
100569
  ...system === void 0 ? {} : { system },
100175
100570
  ...tools.length > 0 ? { tools: tools.map((tool) => ({
100176
100571
  name: tool.name,
@@ -103920,6 +104315,10 @@ function apply(ctx, pluginConfig = {}) {
103920
104315
  const guard = installPtcSdkCacheGuard(ctx);
103921
104316
  return () => guard.dispose();
103922
104317
  }, "dsh-chatgpt-subscription: PTC SDK cache guard");
104318
+ ctx.effect(() => {
104319
+ const guard = installSettledJobGuard(ctx);
104320
+ return () => guard.dispose();
104321
+ }, "dsh-chatgpt-subscription: settled job guard");
103923
104322
  ctx.effect(() => {
103924
104323
  const guard = installTeamPolicyCacheGuard(ctx, { enabled: () => resolveTeamPolicyCacheGuardEnabled(pluginConfig) });
103925
104324
  return () => guard.dispose();