@eddyskywalker/dsh-chatgpt-subscription 0.59.0 → 0.61.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 (75) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/lib/client.js +1578 -232
  4. package/lib/client.js.map +1 -1
  5. package/lib/codemode.js +795 -178
  6. package/lib/entry-plan-mode-CLRKkgYG.js +1787 -0
  7. package/lib/{entry-progress-updates-CxQewio9.js → entry-progress-updates-DZ0ZRus4.js} +14 -1
  8. package/lib/index.js +1079 -754
  9. package/lib/llm-compat-u9mor0uu.js +110 -0
  10. package/lib/plan-mode.js +2 -779
  11. package/lib/progress-updates.js +1 -1
  12. package/lib/types/client/codemode/CodeRunToolView.d.ts +68 -0
  13. package/lib/types/client/codemode/CodeRunToolView.d.ts.map +1 -0
  14. package/lib/types/client/codemode/highlight.d.ts +25 -0
  15. package/lib/types/client/codemode/highlight.d.ts.map +1 -0
  16. package/lib/types/client/codemode/styles.d.ts +11 -0
  17. package/lib/types/client/codemode/styles.d.ts.map +1 -0
  18. package/lib/types/client/codemode/tool-name.d.ts +10 -0
  19. package/lib/types/client/codemode/tool-name.d.ts.map +1 -0
  20. package/lib/types/client/index.d.ts.map +1 -1
  21. package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
  22. package/lib/types/client/locales.d.ts +23 -1
  23. package/lib/types/client/locales.d.ts.map +1 -1
  24. package/lib/types/compat.d.ts +1 -1
  25. package/lib/types/entry-codemode.d.ts.map +1 -1
  26. package/lib/types/entry-plan-mode.d.ts +136 -0
  27. package/lib/types/entry-plan-mode.d.ts.map +1 -1
  28. package/lib/types/host/agent-preset.d.ts.map +1 -1
  29. package/lib/types/host/claude/adapter.d.ts +16 -0
  30. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  31. package/lib/types/host/claude/client.d.ts +7 -1
  32. package/lib/types/host/claude/client.d.ts.map +1 -1
  33. package/lib/types/host/claude/mapper.d.ts +9 -0
  34. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  35. package/lib/types/host/claude/types.d.ts +10 -0
  36. package/lib/types/host/claude/types.d.ts.map +1 -1
  37. package/lib/types/host/cline-pass/mapper.d.ts.map +1 -1
  38. package/lib/types/host/codemode/discovery.d.ts.map +1 -1
  39. package/lib/types/host/codemode/executor.d.ts.map +1 -1
  40. package/lib/types/host/codemode/rules.d.ts +16 -6
  41. package/lib/types/host/codemode/rules.d.ts.map +1 -1
  42. package/lib/types/host/codemode/transport.d.ts +120 -41
  43. package/lib/types/host/codemode/transport.d.ts.map +1 -1
  44. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  45. package/lib/types/host/command-code/model-catalog.d.ts +14 -14
  46. package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
  47. package/lib/types/host/command-code/routes.d.ts.map +1 -1
  48. package/lib/types/host/common/orphan-tool-calls.d.ts +48 -0
  49. package/lib/types/host/common/orphan-tool-calls.d.ts.map +1 -0
  50. package/lib/types/host/dispatch-execution-guard.d.ts +32 -2
  51. package/lib/types/host/dispatch-execution-guard.d.ts.map +1 -1
  52. package/lib/types/host/glm-coding-plan/mapper-anthropic.d.ts.map +1 -1
  53. package/lib/types/host/glm-coding-plan/mapper-openai.d.ts.map +1 -1
  54. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  55. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  56. package/lib/types/host/model-catalog.d.ts +21 -0
  57. package/lib/types/host/model-catalog.d.ts.map +1 -1
  58. package/lib/types/host/opendesign/mapper.d.ts.map +1 -1
  59. package/lib/types/host/plan-mode-cache-guard.d.ts.map +1 -1
  60. package/lib/types/host/preset-sync.d.ts +19 -4
  61. package/lib/types/host/preset-sync.d.ts.map +1 -1
  62. package/lib/types/host/progress-updates/nudge.d.ts +13 -1
  63. package/lib/types/host/progress-updates/nudge.d.ts.map +1 -1
  64. package/lib/types/host/qoder/mapper.d.ts.map +1 -1
  65. package/lib/types/host/responses-client.d.ts.map +1 -1
  66. package/lib/types/host/responses-mapper.d.ts +12 -0
  67. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  68. package/lib/types/host/settled-job-guard.d.ts +77 -0
  69. package/lib/types/host/settled-job-guard.d.ts.map +1 -0
  70. package/lib/types/host/step-code/mapper.d.ts.map +1 -1
  71. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  72. package/lib/types/index.d.ts.map +1 -1
  73. package/package.json +1 -1
  74. package/presets/dispatch/README.md +8 -13
  75. package/presets/dispatch/agent.cordis.yml +11 -16
package/lib/index.js CHANGED
@@ -1,15 +1,16 @@
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
+ import { n as PLUGIN_MESSAGE_SOURCE_KIND, r as normalizeGenerateOptions, t as LlmError$1 } from "./llm-compat-u9mor0uu.js";
3
4
  import { DEFAULT_SKIP_PROVIDERS, PRUNER_SERVICE_NAME, resolveSkipProviders, shouldSkipPruningForSession } from "./tool-result-pruner.js";
4
- 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";
5
6
  import { createRequire } from "node:module";
6
7
  import * as LlmModule from "@deepseek-ai/dsh-llm";
7
- import { EMPTY_RESPONSE_CODE, HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, ToolCallId, attributionHeaders, createUserMessage, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
8
+ import { EMPTY_RESPONSE_CODE, HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, ToolCallId, attributionHeaders, createUserMessage, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
8
9
  import { defineTool } from "@deepseek-ai/dsh-tools";
9
10
  import { z } from "zod";
10
11
  import z$1 from "@deepseek-ai/schemastery";
11
12
  import fs, { chmod, lstat, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
12
- import path, { basename, dirname, extname, join, relative, resolve } from "node:path";
13
+ import path, { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
14
  import os, { homedir, platform, userInfo } from "node:os";
14
15
  import { constants, createCipheriv, createDecipheriv, createHash, createHmac, publicEncrypt, randomBytes, randomUUID } from "node:crypto";
15
16
  import { isDeepStrictEqual } from "node:util";
@@ -807,7 +808,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
807
808
  * `test/responses-client.test.ts` asserts the two agree, so a release that
808
809
  * forgets this constant fails the suite instead of shipping quietly.
809
810
  */
810
- const PLUGIN_VERSION = "0.59.0";
811
+ const PLUGIN_VERSION = "0.61.0";
811
812
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
812
813
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
813
814
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
@@ -1192,6 +1193,7 @@ function resolveCodexModel(model, preferences, live) {
1192
1193
  inputModalities: [...liveEntry?.inputModalities ?? entry.inputModalities],
1193
1194
  context: { contextWindow: configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow },
1194
1195
  ...outputReservation(configuredContextWindow ?? liveEntry?.contextWindow ?? entry.contextWindow, codexModelMaxTokens(model)),
1196
+ ...codexToolUpdateMode() === void 0 ? {} : { toolUpdate: codexToolUpdateMode() },
1195
1197
  reasoning: {
1196
1198
  efforts: orderReasoningEfforts(efforts, GPT_56_REASONING_EFFORTS).map((effort) => ({
1197
1199
  id: ReasoningEffortId(effort),
@@ -1202,6 +1204,46 @@ function resolveCodexModel(model, preferences, live) {
1202
1204
  };
1203
1205
  }
1204
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
+ /**
1205
1247
  * Resolve the wire capabilities of one model.
1206
1248
  *
1207
1249
  * The live listing outranks the shipped table, because the backend is the
@@ -1752,7 +1794,7 @@ function parseListing(payload) {
1752
1794
  function parseListingEntry$1(value) {
1753
1795
  const record = asRecord$20(value);
1754
1796
  if (record === void 0) return [];
1755
- const id = asString$30(record.slug) ?? asString$30(record.id);
1797
+ const id = asString$31(record.slug) ?? asString$31(record.id);
1756
1798
  if (id === void 0) return [];
1757
1799
  const contextWindow = asNumber$8(record.context_window) ?? asNumber$8(record.contextWindow);
1758
1800
  const modalities = parseModalities(record.input_modalities);
@@ -1761,11 +1803,11 @@ function parseListingEntry$1(value) {
1761
1803
  const fallback = CODEX_MODEL_CATALOG.find((entry) => entry.id === id);
1762
1804
  return [{
1763
1805
  id,
1764
- 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,
1765
1807
  contextWindow: contextWindow !== void 0 && contextWindow > 0 ? contextWindow : null,
1766
1808
  inputModalities: modalities ?? (fallback ? [...fallback.inputModalities] : ["text"]),
1767
1809
  ...efforts !== null ? { reasoningEfforts: efforts } : {},
1768
- defaultReasoningEffort: asString$30(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null,
1810
+ defaultReasoningEffort: asString$31(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null,
1769
1811
  ...useResponsesLite !== void 0 ? { useResponsesLite } : {}
1770
1812
  }];
1771
1813
  }
@@ -1773,7 +1815,7 @@ function parseModalities(value) {
1773
1815
  if (!Array.isArray(value)) return null;
1774
1816
  const out = [];
1775
1817
  for (const item of value) {
1776
- const name = asString$30(item);
1818
+ const name = asString$31(item);
1777
1819
  if (name === "text" || name === "image") out.push(name);
1778
1820
  }
1779
1821
  return out.length > 0 ? out : null;
@@ -1788,7 +1830,7 @@ function parseEfforts(value) {
1788
1830
  if (!Array.isArray(value)) return null;
1789
1831
  const out = [];
1790
1832
  for (const item of value) {
1791
- const effort = asString$30(item) ?? asString$30(asRecord$20(item)?.effort);
1833
+ const effort = asString$31(item) ?? asString$31(asRecord$20(item)?.effort);
1792
1834
  if (effort !== void 0 && !out.includes(effort)) out.push(effort);
1793
1835
  }
1794
1836
  return out.length > 0 ? out : null;
@@ -1813,7 +1855,7 @@ function accountKeyFor$1(credentials) {
1813
1855
  function asRecord$20(value) {
1814
1856
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1815
1857
  }
1816
- function asString$30(value) {
1858
+ function asString$31(value) {
1817
1859
  return typeof value === "string" && value !== "" ? value : void 0;
1818
1860
  }
1819
1861
  function asNumber$8(value) {
@@ -4434,7 +4476,7 @@ var OAuthService = class {
4434
4476
  throw new OAuthServiceError("oauth-token-exchange-failed", `The ChatGPT device code could not be issued (${response.status}${detail === null ? "" : `, ${detail}`}).`, response.status);
4435
4477
  }
4436
4478
  const payload = await response.json().catch(() => null);
4437
- const record = isRecord$68(payload) ? payload : {};
4479
+ const record = isRecord$67(payload) ? payload : {};
4438
4480
  const authId = nonEmptyString$11(record.device_auth_id);
4439
4481
  const userCode = nonEmptyString$11(record.user_code) ?? nonEmptyString$11(record.usercode);
4440
4482
  if (authId === void 0 || userCode === void 0) throw new OAuthServiceError("oauth-token-exchange-failed", "ChatGPT returned no device code.");
@@ -4501,7 +4543,7 @@ var OAuthService = class {
4501
4543
  const detail = deviceErrorIdentifier(payload);
4502
4544
  throw new OAuthServiceError("oauth-token-exchange-failed", `The ChatGPT device confirmation was refused (${response.status}${detail === void 0 ? "" : `, ${detail}`}).`, response.status);
4503
4545
  }
4504
- const record = isRecord$68(payload) ? payload : {};
4546
+ const record = isRecord$67(payload) ? payload : {};
4505
4547
  const authorizationCode = nonEmptyString$11(record.authorization_code);
4506
4548
  const codeVerifier = nonEmptyString$11(record.code_verifier);
4507
4549
  if (authorizationCode === void 0 || codeVerifier === void 0) throw new OAuthServiceError("oauth-token-exchange-failed", "ChatGPT confirmed the device code but returned no authorization code.");
@@ -4834,11 +4876,11 @@ const DEVICE_PENDING_STATUSES = /* @__PURE__ */ new Set([
4834
4876
  function isPendingDeviceAnswer(response, payload) {
4835
4877
  if (response.status === 403 || response.status === 404) return true;
4836
4878
  if (!response.ok && response.status !== 400) return false;
4837
- if (!isRecord$68(payload)) return false;
4879
+ if (!isRecord$67(payload)) return false;
4838
4880
  const status = payload.status;
4839
4881
  if (typeof status === "string" && DEVICE_PENDING_STATUSES.has(status.toLowerCase())) return true;
4840
4882
  const error = payload.error;
4841
- if (isRecord$68(error)) {
4883
+ if (isRecord$67(error)) {
4842
4884
  const code = error.code;
4843
4885
  if (typeof code === "string" && DEVICE_PENDING_STATUSES.has(code.toLowerCase())) return true;
4844
4886
  }
@@ -4852,18 +4894,18 @@ function isPendingDeviceAnswer(response, payload) {
4852
4894
  * wrong.
4853
4895
  */
4854
4896
  function deviceErrorIdentifier(payload) {
4855
- if (!isRecord$68(payload)) return void 0;
4897
+ if (!isRecord$67(payload)) return void 0;
4856
4898
  const candidates = [payload.code];
4857
4899
  const error = payload.error;
4858
4900
  if (typeof error === "string") candidates.push(error);
4859
- else if (isRecord$68(error)) candidates.push(error.code, error.type);
4901
+ else if (isRecord$67(error)) candidates.push(error.code, error.type);
4860
4902
  candidates.push(payload.status);
4861
4903
  for (const candidate of candidates) if (typeof candidate === "string" && /^[a-z0-9_.-]{1,64}$/i.test(candidate)) return candidate;
4862
4904
  }
4863
4905
  function nonEmptyString$11(value) {
4864
4906
  return typeof value === "string" && value !== "" ? value : void 0;
4865
4907
  }
4866
- function isRecord$68(value) {
4908
+ function isRecord$67(value) {
4867
4909
  return typeof value === "object" && value !== null && !Array.isArray(value);
4868
4910
  }
4869
4911
  async function oauthErrorIdentifier(response) {
@@ -5044,7 +5086,7 @@ const CODEX_CLI_CREDENTIAL_SOURCE = "codex";
5044
5086
  * here, and the import has to be repeated afterwards.
5045
5087
  */
5046
5088
  const ADOPTED_CODEX_CREDENTIAL_EXPIRED_HINT = "The sign-in imported from the Codex CLI has expired. Sign in again in the Codex CLI, then import it again from this settings card.";
5047
- function isRecord$67(value) {
5089
+ function isRecord$66(value) {
5048
5090
  return typeof value === "object" && value !== null && !Array.isArray(value);
5049
5091
  }
5050
5092
  /** A trimmed, non-empty string, or nothing. */
@@ -5146,7 +5188,7 @@ function decodeJwtClaims$1(token) {
5146
5188
  if (parts.length !== 3) return void 0;
5147
5189
  try {
5148
5190
  const value = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
5149
- return isRecord$67(value) ? value : void 0;
5191
+ return isRecord$66(value) ? value : void 0;
5150
5192
  } catch {
5151
5193
  return;
5152
5194
  }
@@ -5191,10 +5233,10 @@ function expiryFromAccessToken(accessToken) {
5191
5233
  * the file carries cannot reach the result.
5192
5234
  */
5193
5235
  function parseAdoptedCodexCredential(value, sourcePath) {
5194
- if (!isRecord$67(value)) return void 0;
5236
+ if (!isRecord$66(value)) return void 0;
5195
5237
  if (value.auth_mode !== "chatgpt") return void 0;
5196
5238
  const tokens = value[CODEX_CLI_TOKENS_KEY];
5197
- if (!isRecord$67(tokens)) return void 0;
5239
+ if (!isRecord$66(tokens)) return void 0;
5198
5240
  const accessToken = nonEmptyString$10(tokens.access_token);
5199
5241
  if (accessToken === void 0) return void 0;
5200
5242
  const refreshToken = nonEmptyString$10(tokens.refresh_token);
@@ -5213,7 +5255,7 @@ function parseAdoptedCodexCredential(value, sourcePath) {
5213
5255
  const email = claims === void 0 ? void 0 : nonEmptyString$10(claims.email);
5214
5256
  if (email !== void 0) credentials.email = email;
5215
5257
  const authClaims = claims === void 0 ? void 0 : claims[CODEX_AUTH_CLAIMS_KEY];
5216
- const planType = isRecord$67(authClaims) ? nonEmptyString$10(authClaims.chatgpt_plan_type) : void 0;
5258
+ const planType = isRecord$66(authClaims) ? nonEmptyString$10(authClaims.chatgpt_plan_type) : void 0;
5217
5259
  if (planType !== void 0) credentials.planType = planType;
5218
5260
  }
5219
5261
  const accountId = nonEmptyString$10(tokens.account_id);
@@ -5999,114 +6041,6 @@ function webServerOrigin(server) {
5999
6041
  return `http://${server.host === "0.0.0.0" ? "127.0.0.1" : server.host}:${server.port}`;
6000
6042
  }
6001
6043
  //#endregion
6002
- //#region src/host/common/llm-compat.ts
6003
- /** This package's kind in the harness message-source map. */
6004
- const PLUGIN_MESSAGE_SOURCE_KIND = "dsh-chatgpt-subscription";
6005
- function isRecord$66(value) {
6006
- return typeof value === "object" && value !== null && !Array.isArray(value);
6007
- }
6008
- /** Read a message body's blocks, keeping every entry that carries a block tag. */
6009
- function readBlocks(raw) {
6010
- if (!Array.isArray(raw)) return [];
6011
- return raw.filter((block) => isRecord$66(block) && typeof block.type === "string");
6012
- }
6013
- /**
6014
- * The first value that is a non-empty string.
6015
- *
6016
- * Used for fields the two generations spell in different places: a value that
6017
- * is present but empty must not shadow a real one further down the chain, which
6018
- * is what `??` alone does.
6019
- */
6020
- function firstNonEmptyString(...values) {
6021
- for (const value of values) if (typeof value === "string" && value !== "") return value;
6022
- return "";
6023
- }
6024
- /**
6025
- * The result block a `role: 'tool'` message already carries inside its body.
6026
- *
6027
- * Some harness generations deliver the call id inside the result block rather
6028
- * than on the message, and one shape carries both: `role: 'tool'` with a
6029
- * `tool-result` block holding the id. Reading only the message-level fields
6030
- * leaves {@link toolCallIdOf} nothing to find.
6031
- */
6032
- function embeddedToolResult(content) {
6033
- if (content.length !== 1) return void 0;
6034
- const [block] = content;
6035
- return block?.type === "tool-result" ? block : void 0;
6036
- }
6037
- /**
6038
- * The call id one `role: 'tool'` message associates with its `tool-call`.
6039
- *
6040
- * The id is the only thing tying a result to the call it answers: a mapper that
6041
- * sends it empty makes every result an orphan and tells the repair pass that no
6042
- * call was answered, which some gateways reject outright (WorkBuddy answers
6043
- * `11148`, "tool calls and tool results do not match"). It therefore has to be
6044
- * read from every place the harness generations put it — the message, the
6045
- * message's source, and the result block inside the body — and an empty string
6046
- * must not shadow a later, real one.
6047
- */
6048
- function toolCallIdOf(raw, source, embedded) {
6049
- return firstNonEmptyString(raw.toolCallId, raw.callId, source?.callId, embedded?.toolCallId);
6050
- }
6051
- /**
6052
- * Normalize one harness message list into {@link Message}.
6053
- *
6054
- * A 0.1.7 `role: 'tool'` message is re-wrapped as the user-role message holding
6055
- * one {@link ToolResultBlock} that the mappers expect, keeping the tool
6056
- * provenance (`kind: 'tool'`) they use to tell a real tool result from a user
6057
- * turn. A message that already carries that block keeps its own body instead of
6058
- * nesting a second `tool-result` around it. Everything a newer harness adds
6059
- * beside that keeps its own shape.
6060
- * @param messages - Messages straight off the request options, in either generation's shape.
6061
- * @returns The same conversation in the pre-0.1.7 vocabulary.
6062
- */
6063
- function normalizeMessages(messages) {
6064
- const normalized = [];
6065
- for (const raw of messages ?? []) {
6066
- if (!isRecord$66(raw)) continue;
6067
- const content = readBlocks(raw.content);
6068
- if (raw.role === "tool") {
6069
- const source = isRecord$66(raw.source) ? raw.source : void 0;
6070
- const embedded = embeddedToolResult(content);
6071
- const callId = toolCallIdOf(raw, source, embedded);
6072
- const isError = raw.isError === true || embedded?.isError === true;
6073
- normalized.push({
6074
- id: raw.id,
6075
- role: "user",
6076
- content: [{
6077
- type: "tool-result",
6078
- toolCallId: callId,
6079
- content: embedded === void 0 ? content : embedded.content,
6080
- ...isError ? { isError: true } : {}
6081
- }],
6082
- source: {
6083
- kind: "tool",
6084
- callId
6085
- }
6086
- });
6087
- continue;
6088
- }
6089
- normalized.push({
6090
- id: raw.id,
6091
- role: typeof raw.role === "string" ? raw.role : "user",
6092
- content,
6093
- ...isRecord$66(raw.source) ? { source: raw.source } : {}
6094
- });
6095
- }
6096
- return normalized;
6097
- }
6098
- /**
6099
- * Normalize request options for the mappers.
6100
- * @param options - Request options the harness handed the adapter.
6101
- * @returns The same options with {@link Message} history.
6102
- */
6103
- function normalizeGenerateOptions(options) {
6104
- return {
6105
- ...options,
6106
- messages: normalizeMessages(options.messages)
6107
- };
6108
- }
6109
- //#endregion
6110
6044
  //#region src/host/codex-images.ts
6111
6045
  /**
6112
6046
  * Output canvases the Codex image endpoint accepts in `size`.
@@ -8285,7 +8219,7 @@ function sanitizeText$9(text) {
8285
8219
  function isRecord$64(value) {
8286
8220
  return typeof value === "object" && value !== null && !Array.isArray(value);
8287
8221
  }
8288
- function asString$29(value) {
8222
+ function asString$30(value) {
8289
8223
  return typeof value === "string" ? value : void 0;
8290
8224
  }
8291
8225
  function safeJsonParse$9(text) {
@@ -8315,7 +8249,7 @@ function attachmentOf$5(block) {
8315
8249
  }
8316
8250
  function attachmentLabel$6(block) {
8317
8251
  const attachment = isRecord$64(block.attachment) ? block.attachment : void 0;
8318
- return asString$29(attachment?.name) || asString$29(attachment?.attachmentId);
8252
+ return asString$30(attachment?.name) || asString$30(attachment?.attachmentId);
8319
8253
  }
8320
8254
  function collectImageRefs$3(content, refs) {
8321
8255
  if (!Array.isArray(content)) return;
@@ -8452,10 +8386,10 @@ function unavailableImageText$6(block) {
8452
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]`;
8453
8387
  }
8454
8388
  function imageBlockToPart(block, images) {
8455
- let data = asString$29(block.data) || asString$29(block.base64);
8389
+ let data = asString$30(block.data) || asString$30(block.base64);
8456
8390
  const source = isRecord$64(block.source) ? block.source : void 0;
8457
- if (!data && source) data = asString$29(source.data) || asString$29(source.base64);
8458
- 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";
8459
8393
  if (data?.startsWith("data:")) {
8460
8394
  const match = data.match(/^data:([^;,]+);base64,(.*)$/s);
8461
8395
  if (match) {
@@ -8529,7 +8463,7 @@ function replayBlockFor$1(message, index) {
8529
8463
  }
8530
8464
  }
8531
8465
  function thoughtSignature(part) {
8532
- 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);
8533
8467
  }
8534
8468
  function replayPart(part) {
8535
8469
  const copy = { ...part };
@@ -8628,7 +8562,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
8628
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;
8629
8563
  const replay = replayBlockFor$1(message, index);
8630
8564
  const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$64) : [];
8631
- 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 || ""))) {
8632
8566
  parts.push(...originalParts.map(replayPart));
8633
8567
  continue;
8634
8568
  }
@@ -8649,7 +8583,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
8649
8583
  const toolId = String(block.id || "");
8650
8584
  const toolName = String(block.name || "");
8651
8585
  const originalCall = originalParts.find((part) => isRecord$64(part.functionCall));
8652
- 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);
8653
8587
  toolCalls.set(toolId, {
8654
8588
  name: toolName,
8655
8589
  id: wireId
@@ -8894,7 +8828,7 @@ function processStreamLine$4(line, state) {
8894
8828
  if (!isRecord$64(chunk)) return [];
8895
8829
  const responseData = isRecord$64(chunk.response) ? chunk.response : chunk;
8896
8830
  const error = isRecord$64(chunk.error) ? chunk.error : isRecord$64(responseData.error) ? responseData.error : void 0;
8897
- 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");
8898
8832
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
8899
8833
  const candidate = isRecord$64(candidates[0]) ? candidates[0] : void 0;
8900
8834
  const content = isRecord$64(candidate?.content) ? candidate.content : void 0;
@@ -8960,8 +8894,8 @@ function processStreamLine$4(line, state) {
8960
8894
  if (isRecord$64(part.functionCall)) {
8961
8895
  out.push(...closeCurrentBlock(state));
8962
8896
  const fc = part.functionCall;
8963
- const toolName = asString$29(fc.name) || "";
8964
- const toolId = asString$29(fc.id) || sanitizeToolCallId("", toolName);
8897
+ const toolName = asString$30(fc.name) || "";
8898
+ const toolId = asString$30(fc.id) || sanitizeToolCallId("", toolName);
8965
8899
  const argsText = JSON.stringify(isRecord$64(fc.args) ? fc.args : {});
8966
8900
  const index = state.blocks.length;
8967
8901
  const block = {
@@ -8999,10 +8933,10 @@ function processStreamLine$4(line, state) {
8999
8933
  }
9000
8934
  collectUsage(chunk.usageMetadata, state);
9001
8935
  if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
9002
- const finishMessage = asString$29(candidate?.finishMessage);
8936
+ const finishMessage = asString$30(candidate?.finishMessage);
9003
8937
  if (finishMessage) state.finishMessage = finishMessage;
9004
- const blockReason = asString$29((isRecord$64(responseData.promptFeedback) ? responseData.promptFeedback : void 0)?.blockReason);
9005
- 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);
9006
8940
  if (finishReason) {
9007
8941
  state.finishReason = finishReason;
9008
8942
  out.push(...closeCurrentBlock(state));
@@ -9077,7 +9011,7 @@ function closeStream$7(state) {
9077
9011
  function isRecord$63(value) {
9078
9012
  return typeof value === "object" && value !== null && !Array.isArray(value);
9079
9013
  }
9080
- function asString$28(value) {
9014
+ function asString$29(value) {
9081
9015
  return typeof value === "string" ? value : void 0;
9082
9016
  }
9083
9017
  /**
@@ -9138,7 +9072,7 @@ function attachmentOf$4(block) {
9138
9072
  }
9139
9073
  function attachmentLabel$5(block) {
9140
9074
  const attachment = isRecord$63(block.attachment) ? block.attachment : void 0;
9141
- return asString$28(attachment?.name) || asString$28(attachment?.attachmentId);
9075
+ return asString$29(attachment?.name) || asString$29(attachment?.attachmentId);
9142
9076
  }
9143
9077
  function collectImageRefs$2(content, refs) {
9144
9078
  if (!Array.isArray(content)) return;
@@ -9172,9 +9106,9 @@ function base64Length$2(bytes) {
9172
9106
  * has no resolved map behaves exactly as before.
9173
9107
  */
9174
9108
  function inlinePayloadOf$1(block) {
9175
- let data = asString$28(block.data) || asString$28(block.base64);
9109
+ let data = asString$29(block.data) || asString$29(block.base64);
9176
9110
  const source = isRecord$63(block.source) ? block.source : void 0;
9177
- 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);
9178
9112
  if (data?.startsWith("data:")) {
9179
9113
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
9180
9114
  if (matched) data = matched[2] ?? "";
@@ -9637,10 +9571,10 @@ function unavailableImageText$5(block) {
9637
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]`;
9638
9572
  }
9639
9573
  function imageBlockToInline$3(block, images) {
9640
- let data = asString$28(block.data) || asString$28(block.base64);
9574
+ let data = asString$29(block.data) || asString$29(block.base64);
9641
9575
  const source = isRecord$63(block.source) ? block.source : void 0;
9642
- if (!data && source) data = asString$28(source.data) || asString$28(source.base64);
9643
- 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";
9644
9578
  if (data?.startsWith("data:")) {
9645
9579
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
9646
9580
  if (matched) {
@@ -13217,6 +13151,21 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
13217
13151
  });
13218
13152
  continue;
13219
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
+ }
13220
13169
  const content = await mapContent(message, attachments, options.signal, localRawImages, localImageStats, resolveLocalRawImages);
13221
13170
  if (content.length > 0) input.push({
13222
13171
  role: message.role,
@@ -13227,8 +13176,9 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
13227
13176
  offloadOldestInputImages(input);
13228
13177
  const instructionText = [...instructionParts, localRawImageInstruction(localImageStats)].filter((value) => Boolean(value)).join("\n\n");
13229
13178
  const prefix = [];
13230
- const tools = options.tools ?? [];
13231
- 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));
13232
13182
  if (instructionText !== "") prefix.push(developerMessageItem(instructionText, wire.idNamespace, useResponsesLite));
13233
13183
  input.unshift(...prefix);
13234
13184
  const toolDeclarations = tools.length > 0 ? tools.map(toolDeclaration) : void 0;
@@ -13300,6 +13250,87 @@ function additionalToolsItem(tools, idNamespace) {
13300
13250
  };
13301
13251
  }
13302
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
+ /**
13303
13334
  * The base instructions as a developer message.
13304
13335
  *
13305
13336
  * The official client marks the item with the `model.base_instructions` kind, so
@@ -13686,6 +13717,8 @@ var ResponsesClient = class {
13686
13717
  const turnKey = turnKeyFor(sessionId);
13687
13718
  let currentModel = options.model;
13688
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"));
13689
13722
  let response;
13690
13723
  let release;
13691
13724
  while (true) {
@@ -13711,6 +13744,12 @@ var ResponsesClient = class {
13711
13744
  continue;
13712
13745
  }
13713
13746
  }
13747
+ if (!strippedToolChanges && statesToolChange(attemptOptions) && error.status === 400) {
13748
+ disableCodexToolUpdate();
13749
+ attemptOptions = withoutCodexToolChanges(attemptOptions);
13750
+ strippedToolChanges = true;
13751
+ continue;
13752
+ }
13714
13753
  throw error;
13715
13754
  }
13716
13755
  }
@@ -20079,6 +20118,21 @@ const COMMAND_CODE_MODELS = [
20079
20118
  maxTokens: 256e3,
20080
20119
  hidden: false
20081
20120
  },
20121
+ {
20122
+ id: "stealth/crouton-alpha",
20123
+ name: "Crouton Alpha",
20124
+ inputModalities: ["text", "image"],
20125
+ reasoningEfforts: [
20126
+ "low",
20127
+ "medium",
20128
+ "high",
20129
+ "xhigh",
20130
+ "max"
20131
+ ],
20132
+ contextWindow: 1048576,
20133
+ maxTokens: 943718,
20134
+ hidden: false
20135
+ },
20082
20136
  {
20083
20137
  id: "reflection/beam:free",
20084
20138
  name: "Beam 1.0",
@@ -20096,7 +20150,7 @@ const COMMAND_CODE_MODELS = [
20096
20150
  },
20097
20151
  {
20098
20152
  id: "poolside/laguna-s-2.1-free",
20099
- name: "Laguna S 2.1",
20153
+ name: "Laguna S 2.1 (Free)",
20100
20154
  inputModalities: ["text"],
20101
20155
  reasoningEfforts: [],
20102
20156
  contextWindow: 256e3,
@@ -20125,6 +20179,32 @@ const COMMAND_CODE_MODELS = [
20125
20179
  maxTokens: 32768,
20126
20180
  hidden: false
20127
20181
  },
20182
+ {
20183
+ id: "inclusionai/ling-3.1-flash-1019",
20184
+ name: "Ling 3.1 Flash 1019",
20185
+ inputModalities: ["text"],
20186
+ reasoningEfforts: [
20187
+ "low",
20188
+ "medium",
20189
+ "high"
20190
+ ],
20191
+ contextWindow: 1e6,
20192
+ maxTokens: 262144,
20193
+ hidden: false
20194
+ },
20195
+ {
20196
+ id: "inclusionai/ling-3.1-flash-1019:free",
20197
+ name: "Ling 3.1 Flash 1019",
20198
+ inputModalities: ["text"],
20199
+ reasoningEfforts: [
20200
+ "low",
20201
+ "medium",
20202
+ "high"
20203
+ ],
20204
+ contextWindow: 1e6,
20205
+ maxTokens: 262144,
20206
+ hidden: true
20207
+ },
20128
20208
  {
20129
20209
  id: "meta/muse-spark-1.1",
20130
20210
  name: "Muse Spark 1.1",
@@ -20286,42 +20366,37 @@ const COMMAND_CODE_MODELS = [
20286
20366
  function commandCodeModelDef(modelId) {
20287
20367
  return COMMAND_CODE_MODELS.find((model) => model.id === modelId);
20288
20368
  }
20289
- /**
20290
- * Model ids the 1.88.2 catalog refresh added to the shipped default selection.
20291
- *
20292
- * The shipped default is the set of models the settings card offers before the
20293
- * user edits it, and a stored selection that still equals it counts as "never
20294
- * edited" (see `resolveEnabledModelIds` in `./routes.ts`). A refresh that
20295
- * changed the default without saying so would make every untouched selection
20296
- * from the previous release look edited, and the models this release adds
20297
- * would stay hidden behind a list nobody touched. These two lists carry that
20298
- * change: the retired ids are the ones the previous default had and this one
20299
- * does not, and the added ids are the ones it gained.
20300
- */
20301
- const SHIPPED_DEFAULT_ADDED_MODEL_IDS = [
20302
- "claude-haiku-5-5",
20303
- "gpt-6.1-terra",
20304
- "moonshotai/Kimi-K3.1-Pro",
20305
- "minimax/minimax-m3.1-flash",
20306
- "ornith/ornith-1.5-122b",
20307
- "Qwen/Qwen3.8-Max-Prime",
20308
- "google/gemini-4-argon",
20309
- "nvidia/nemotron-3.5-super-vl",
20310
- "stealth/glyph-cluster:free",
20311
- "reflection/beam:free",
20312
- "inclusionai/ling-3.1-flash",
20313
- "meta/muse-spark-open",
20314
- "meta/muse-wonder-1.0",
20315
- "mistral/mistral-large-4"
20316
- ];
20317
- /** Model ids the 1.88.2 catalog refresh dropped from the shipped default selection. */
20318
- const SHIPPED_DEFAULT_RETIRED_MODEL_IDS = [
20319
- "xiaomi/mimo-v2.5-pro",
20320
- "xiaomi/mimo-v2.5",
20321
- "stealth/space-bunny-alpha",
20322
- "inclusionai/ling-3.0-flash-sante:free",
20323
- "inclusionai/ling-3.1-flash:free"
20324
- ];
20369
+ /** Default-selection deltas recognized by `resolveEnabledModelIds` in `./routes.ts`. */
20370
+ const SHIPPED_DEFAULT_MIGRATIONS = [{
20371
+ version: "1.88.4",
20372
+ added: ["stealth/crouton-alpha", "inclusionai/ling-3.1-flash-1019"],
20373
+ retired: []
20374
+ }, {
20375
+ version: "1.88.2",
20376
+ added: [
20377
+ "claude-haiku-5-5",
20378
+ "gpt-6.1-terra",
20379
+ "moonshotai/Kimi-K3.1-Pro",
20380
+ "minimax/minimax-m3.1-flash",
20381
+ "ornith/ornith-1.5-122b",
20382
+ "Qwen/Qwen3.8-Max-Prime",
20383
+ "google/gemini-4-argon",
20384
+ "nvidia/nemotron-3.5-super-vl",
20385
+ "stealth/glyph-cluster:free",
20386
+ "reflection/beam:free",
20387
+ "inclusionai/ling-3.1-flash",
20388
+ "meta/muse-spark-open",
20389
+ "meta/muse-wonder-1.0",
20390
+ "mistral/mistral-large-4"
20391
+ ],
20392
+ retired: [
20393
+ "xiaomi/mimo-v2.5-pro",
20394
+ "xiaomi/mimo-v2.5",
20395
+ "stealth/space-bunny-alpha",
20396
+ "inclusionai/ling-3.0-flash-sante:free",
20397
+ "inclusionai/ling-3.1-flash:free"
20398
+ ]
20399
+ }];
20325
20400
  //#endregion
20326
20401
  //#region src/host/command-code/types.ts
20327
20402
  const PROVIDER_ID$11 = "command-code";
@@ -20939,7 +21014,7 @@ function isRecord$61(value) {
20939
21014
  function asRecord$16(value) {
20940
21015
  return isRecord$61(value) ? value : void 0;
20941
21016
  }
20942
- function asString$27(value) {
21017
+ function asString$28(value) {
20943
21018
  if (typeof value === "string" && value.trim() !== "") return value;
20944
21019
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
20945
21020
  }
@@ -20952,7 +21027,7 @@ function asNumber$6(value) {
20952
21027
  }
20953
21028
  function firstString$2(record, keys) {
20954
21029
  for (const key of keys) {
20955
- const value = asString$27(record[key]);
21030
+ const value = asString$28(record[key]);
20956
21031
  if (value !== void 0) return value;
20957
21032
  }
20958
21033
  }
@@ -21199,7 +21274,7 @@ function parseCreditBalances(payload, consumed = []) {
21199
21274
  /** Resolve the plan id the subscription or credits payload reports, when either does. */
21200
21275
  function parsePlanId(payloads) {
21201
21276
  for (const payload of payloads) {
21202
- const id = asString$27(deepValue(payload, [
21277
+ const id = asString$28(deepValue(payload, [
21203
21278
  "planId",
21204
21279
  "plan_id",
21205
21280
  "priceId",
@@ -21218,7 +21293,7 @@ function parsePlanId(payloads) {
21218
21293
  */
21219
21294
  function parseSubscriptionStatus(payload) {
21220
21295
  const root = asRecord$16(payload) ?? {};
21221
- return asString$27(deepValue(asRecord$16(root.data) ?? root, ["status"])) ?? null;
21296
+ return asString$28(deepValue(asRecord$16(root.data) ?? root, ["status"])) ?? null;
21222
21297
  }
21223
21298
  /** End of the current billing period, in Unix milliseconds. */
21224
21299
  function parseSubscriptionPeriodEnd(payload) {
@@ -21794,6 +21869,92 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
21794
21869
  });
21795
21870
  }
21796
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
21797
21958
  //#region src/host/command-code/mapper.ts
21798
21959
  /**
21799
21960
  * Provider-wire mapping for the two Command Code provider endpoints.
@@ -22350,7 +22511,7 @@ function anthropicAssistantContent$3(message) {
22350
22511
  * the Messages wire wants one user turn, so consecutive turns of one role are
22351
22512
  * folded together in order.
22352
22513
  */
22353
- function mergeAnthropicMessages$3(entries) {
22514
+ function mergeAnthropicMessages$4(entries) {
22354
22515
  const merged = [];
22355
22516
  for (const entry of entries) {
22356
22517
  if (entry.content.length === 0) continue;
@@ -22441,7 +22602,7 @@ function buildAnthropicRequest$1(options, images = NO_RESOLVED_IMAGES$3) {
22441
22602
  return {
22442
22603
  model: options.model,
22443
22604
  max_tokens: maxTokens,
22444
- messages: mergeAnthropicMessages$3(entries),
22605
+ messages: cleanOrphanedToolCalls(mergeAnthropicMessages$4(entries), mergeAnthropicMessages$4),
22445
22606
  ...system === void 0 ? {} : { system: anthropicSystemBlocks(system) },
22446
22607
  ...options.temperature === void 0 || budget !== void 0 ? {} : { temperature: options.temperature },
22447
22608
  ...options.stop && options.stop.length > 0 ? { stop_sequences: options.stop } : {},
@@ -22743,6 +22904,23 @@ function applyOpenAIToolDelta$1(entry, state) {
22743
22904
  const fn = isRecord$60(entry.function) ? entry.function : {};
22744
22905
  const out = [];
22745
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
+ }
22746
22924
  if (call === void 0) {
22747
22925
  out.push(...closeCurrent$6(state));
22748
22926
  call = {
@@ -24692,16 +24870,20 @@ function resolveEnabledModelIds$5(stored, catalog, enabled = true) {
24692
24870
  /**
24693
24871
  * Whether a stored selection still is a shipped default.
24694
24872
  *
24695
- * The current one, and the one the previous release shipped: the refresh moved
24696
- * a handful of models in and out of the default, and the difference between the
24697
- * two defaults is exactly the pair of id lists the catalog records.
24873
+ * Walk the recorded catalog refreshes newest-first. Each step reconstructs one
24874
+ * earlier default, so users who never edited either the immediate previous
24875
+ * release or an older recorded release still receive the current catalog.
24698
24876
  */
24699
24877
  function isUneditedSelection(stored, shipped) {
24700
24878
  if (stored.length === 0) return false;
24701
24879
  if (sameSelection(stored, shipped)) return true;
24702
- const previous = new Set([...shipped].filter((id) => !SHIPPED_DEFAULT_ADDED_MODEL_IDS.includes(id)));
24703
- for (const id of SHIPPED_DEFAULT_RETIRED_MODEL_IDS) previous.add(id);
24704
- return sameSelection(stored, previous);
24880
+ let previous = new Set(shipped);
24881
+ for (const migration of SHIPPED_DEFAULT_MIGRATIONS) {
24882
+ previous = new Set([...previous].filter((id) => !migration.added.includes(id)));
24883
+ for (const id of migration.retired) previous.add(id);
24884
+ if (sameSelection(stored, previous)) return true;
24885
+ }
24886
+ return false;
24705
24887
  }
24706
24888
  /** Whether two id lists name exactly the same models, in any order. */
24707
24889
  function sameSelection(stored, other) {
@@ -34524,7 +34706,7 @@ function anthropicAssistantContent$2(message, toolCallIds) {
34524
34706
  * the Messages wire wants one user turn, so consecutive turns of one role are
34525
34707
  * folded together in order.
34526
34708
  */
34527
- function mergeAnthropicMessages$2(entries) {
34709
+ function mergeAnthropicMessages$3(entries) {
34528
34710
  const merged = [];
34529
34711
  for (const entry of entries) {
34530
34712
  if (entry.content.length === 0) continue;
@@ -34577,7 +34759,7 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$4, media = {
34577
34759
  type: CLEAR_THINKING_EDIT,
34578
34760
  keep: "all"
34579
34761
  }] } : void 0;
34580
- const messages = mergeAnthropicMessages$2(entries);
34762
+ const messages = cleanOrphanedToolCalls(mergeAnthropicMessages$3(entries), mergeAnthropicMessages$3);
34581
34763
  const lastMessage = messages[messages.length - 1];
34582
34764
  if (lastMessage !== void 0 && Array.isArray(lastMessage.content) && lastMessage.content.length > 0) {
34583
34765
  const lastBlock = lastMessage.content[lastMessage.content.length - 1];
@@ -34890,6 +35072,23 @@ function applyOpenAIToolDelta(entry, state) {
34890
35072
  const fn = isRecord$53(entry.function) ? entry.function : {};
34891
35073
  const out = [];
34892
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
+ }
34893
35092
  if (call === void 0) {
34894
35093
  out.push(...closeCurrent$5(state));
34895
35094
  call = {
@@ -36876,7 +37075,7 @@ const KIMI_CODE_REQUEST_IMAGE_MAX_EDGE = 2e3;
36876
37075
  * @param catalog - live catalog, possibly empty when offline.
36877
37076
  * @returns the tool-update mode, or undefined when this route must resend everything.
36878
37077
  */
36879
- function toolUpdateMode$1(modelId, catalog) {
37078
+ function toolUpdateMode$2(modelId, catalog) {
36880
37079
  if (!dynamicToolsForEntry(modelId, catalog)) return void 0;
36881
37080
  if (wireForCatalogEntry(modelId, catalog) !== "openai") return void 0;
36882
37081
  const raw = (process.env.DSH_KIMI_CODE_TOOL_UPDATE ?? "").trim().toLowerCase();
@@ -37279,7 +37478,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
37279
37478
  id: modelId,
37280
37479
  name: entry?.name ?? modelId,
37281
37480
  inputModalities: inputModalitiesForEntry(modelId, catalog),
37282
- ...toolUpdateMode$1(modelId, catalog) === void 0 ? {} : { toolUpdate: toolUpdateMode$1(modelId, catalog) },
37481
+ ...toolUpdateMode$2(modelId, catalog) === void 0 ? {} : { toolUpdate: toolUpdateMode$2(modelId, catalog) },
37283
37482
  context: { contextWindow: this.contextWindowFor(modelId, entry, settings.contextWindowOverrides) },
37284
37483
  ...efforts.length === 0 ? {} : { reasoning: {
37285
37484
  efforts: efforts.map((effort) => ({
@@ -40932,7 +41131,7 @@ function anthropicAssistantContent$1(message, expectedScope) {
40932
41131
  * Messages wire wants one user turn, so consecutive turns of one role are folded
40933
41132
  * together in order.
40934
41133
  */
40935
- function mergeAnthropicMessages$1(entries) {
41134
+ function mergeAnthropicMessages$2(entries) {
40936
41135
  const merged = [];
40937
41136
  for (const entry of entries) {
40938
41137
  if (entry.content.length === 0) continue;
@@ -41154,7 +41353,7 @@ function buildMinimaxRequest(options, images = NO_RESOLVED_IMAGES$2, request = {
41154
41353
  model: options.model,
41155
41354
  max_tokens: maxTokens,
41156
41355
  stream: true,
41157
- messages: mergeAnthropicMessages$1(entries),
41356
+ messages: cleanOrphanedToolCalls(mergeAnthropicMessages$2(entries), mergeAnthropicMessages$2),
41158
41357
  ...system === void 0 ? {} : { system: caching ? [{
41159
41358
  type: "text",
41160
41359
  text: system
@@ -49697,6 +49896,16 @@ function isClaudeCacheTtl$1(value) {
49697
49896
  * models (`THINKING_BINDING_CONTROLS_BETA`, pushed when `supportsMidConvoEffort`).
49698
49897
  */
49699
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";
49700
49909
  /** Anthropic protocol revision sent as `anthropic-version`. */
49701
49910
  const ANTHROPIC_VERSION$1 = "2023-06-01";
49702
49911
  /**
@@ -51323,6 +51532,7 @@ function claudeBetas(options = {}) {
51323
51532
  if (options.thinking === true) betas.push(INTERLEAVED_THINKING_BETA);
51324
51533
  if (options.thinkingBinding === true) betas.push(THINKING_BINDING_CONTROLS_BETA);
51325
51534
  if (options.cacheTtl === "1h") betas.push(EXTENDED_CACHE_TTL_BETA);
51535
+ if (options.toolChanges === true) betas.push(INLINE_TOOLS_BETA);
51326
51536
  return betas;
51327
51537
  }
51328
51538
  /** The `User-Agent` value this line reports for one surface. */
@@ -53423,10 +53633,85 @@ function wireTool(tool, names) {
53423
53633
  return {
53424
53634
  name: claudeWireToolName(tool.name, names),
53425
53635
  description: tool.description ?? "",
53426
- input_schema: schema
53636
+ input_schema: schema,
53637
+ ...tool.deferLoading === true ? { defer_loading: true } : {}
53427
53638
  };
53428
53639
  }
53429
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
+ /**
53430
53715
  * One system-role turn's blocks.
53431
53716
  *
53432
53717
  * The vendor documents a mid-conversation system message as carrying "a plain
@@ -53452,6 +53737,17 @@ function claudeSystemContent(message, images) {
53452
53737
  continue;
53453
53738
  }
53454
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
+ }
53455
53751
  }
53456
53752
  return blocks;
53457
53753
  }
@@ -53655,7 +53951,9 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53655
53951
  const maxTokens = claudeRequestThinks(options.model, effort, catalog) ? adjustMaxTokensForThinking(baseMax, modelMax, level, request.thinkingBudgets).maxTokens : baseMax;
53656
53952
  const { thinking, outputConfig } = claudeThinking(options.model, effort, level, maxTokens, request.thinkingBudgets, catalog);
53657
53953
  const entries = [];
53658
- 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];
53659
53957
  if (message.role === "system") {
53660
53958
  entries.push({
53661
53959
  role: "system",
@@ -53663,6 +53961,26 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53663
53961
  });
53664
53962
  continue;
53665
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
+ }
53666
53984
  if (isToolResultMessage$5(message)) {
53667
53985
  entries.push({
53668
53986
  role: "user",
@@ -53684,8 +54002,8 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES$1, request
53684
54002
  max_tokens: maxTokens,
53685
54003
  stream: true,
53686
54004
  system: buildClaudeSystemBlocks(options, caching, cacheTtl),
53687
- messages: mergeClaudeMessages(entries),
53688
- ...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) } : {},
53689
54007
  ...thinking === void 0 ? {} : { thinking },
53690
54008
  ...outputConfig === void 0 ? {} : { output_config: outputConfig },
53691
54009
  ...temperatureAllowed && options.temperature !== void 0 ? { temperature: options.temperature } : {},
@@ -55511,6 +55829,33 @@ var ClaudeAccountTokenStore = class {
55511
55829
  await this.store.saveAccount(credentials, { internalId: this.internalId });
55512
55830
  }
55513
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
+ }
55514
55859
  var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55515
55860
  store;
55516
55861
  modelSettings;
@@ -55626,6 +55971,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55626
55971
  inputModalities: entry.supportsImage ? ["text", "image"] : ["text"],
55627
55972
  context: { contextWindow: contextWindow || 2e5 },
55628
55973
  ...outputReservation(contextWindow || 2e5, maxOutputTokensFor(modelId, catalog)),
55974
+ ...toolUpdateMode$1(modelId) === void 0 ? {} : { toolUpdate: toolUpdateMode$1(modelId) },
55629
55975
  ...efforts.length === 0 ? {} : { reasoning: {
55630
55976
  efforts: efforts.map((effort) => ({
55631
55977
  id: ReasoningEffortId(effort),
@@ -55724,6 +56070,7 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
55724
56070
  model: requestOptions.model,
55725
56071
  thinking,
55726
56072
  thinkingBinding: claudeBodyBindsThinking(payload),
56073
+ toolChanges: claudeBodyHasToolChanges(payload),
55727
56074
  cacheTtl,
55728
56075
  method: "POST"
55729
56076
  }),
@@ -59228,7 +59575,7 @@ function safeStringify(value) {
59228
59575
  * DSH 历史里可能连续两条 user(注入的上下文提示 + 真正的用户轮),而工具结果本身就是
59229
59576
  * user 角色消息;Messages 线要的是一个 user 轮,所以同角色的连续轮按顺序折叠。
59230
59577
  */
59231
- function mergeAnthropicMessages(entries) {
59578
+ function mergeAnthropicMessages$1(entries) {
59232
59579
  const merged = [];
59233
59580
  for (const entry of entries) {
59234
59581
  if (entry.content.length === 0) continue;
@@ -59377,7 +59724,7 @@ function buildGlmAnthropicRequest(options, request = {}) {
59377
59724
  }
59378
59725
  const system = glmAnthropicSystemBlocks(options);
59379
59726
  const maxTokens = options.maxTokens ?? request.defaultMaxTokens ?? 32768;
59380
- const merged = mergeAnthropicMessages(entries);
59727
+ const merged = cleanOrphanedToolCalls(mergeAnthropicMessages$1(entries), mergeAnthropicMessages$1);
59381
59728
  const lastMessageIndex = merged.length - 1;
59382
59729
  const messages = lastMessageIndex < 0 ? merged : merged.map((message, index) => index === lastMessageIndex ? {
59383
59730
  ...message,
@@ -60704,6 +61051,23 @@ function applyToolDelta$3(entry, state) {
60704
61051
  const fn = isRecord$37(entry.function) ? entry.function : {};
60705
61052
  const out = [];
60706
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
+ }
60707
61071
  if (call === void 0) {
60708
61072
  out.push(...closeCurrent$3(state));
60709
61073
  call = {
@@ -65537,6 +65901,23 @@ function applyToolDelta$2(entry, state) {
65537
65901
  const fn = isRecord$33(entry.function) ? entry.function : void 0;
65538
65902
  const out = [];
65539
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
+ }
65540
65921
  if (call === void 0) {
65541
65922
  const opened = openToolCall$1(state, entry, wireIndex);
65542
65923
  out.push(...opened.chunks);
@@ -71801,6 +72182,7 @@ async function* translate(payloads) {
71801
72182
  if (!isRecord$26(call)) continue;
71802
72183
  const index = Number(call.index ?? 0);
71803
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;
71804
72186
  if (block === void 0) {
71805
72187
  block = open("tool-call");
71806
72188
  toolBlocks.set(index, block);
@@ -71812,7 +72194,7 @@ async function* translate(payloads) {
71812
72194
  }
71813
72195
  const fn = isRecord$26(call.function) ? call.function : {};
71814
72196
  block.callId = acceptIdentity$1(block.callId, call.id);
71815
- block.name = acceptIdentity$1(block.name, fn.name);
72197
+ if (block.name === void 0 || block.name === "") block.name = acceptIdentity$1(block.name, fn.name);
71816
72198
  const fragment = typeof fn.arguments === "string" ? fn.arguments : "";
71817
72199
  block.text += fragment;
71818
72200
  yield {
@@ -78539,6 +78921,23 @@ function applyToolDelta$1(entry, state) {
78539
78921
  const fn = isRecord$21(entry.function) ? entry.function : void 0;
78540
78922
  const out = [];
78541
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
+ }
78542
78941
  if (call === void 0) {
78543
78942
  const opened = openToolCall(state, entry, wireIndex);
78544
78943
  out.push(...opened.chunks);
@@ -83757,6 +84156,7 @@ async function* translate$1(payloads) {
83757
84156
  if (!isRecord$17(call)) continue;
83758
84157
  const index = Number(call.index ?? 0);
83759
84158
  let block = toolBlocks.get(index);
84159
+ 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;
83760
84160
  if (block === void 0) {
83761
84161
  block = open("tool-call");
83762
84162
  toolBlocks.set(index, block);
@@ -83768,7 +84168,7 @@ async function* translate$1(payloads) {
83768
84168
  }
83769
84169
  const fn = isRecord$17(call.function) ? call.function : {};
83770
84170
  block.callId = acceptIdentity(block.callId, call.id);
83771
- block.name = acceptIdentity(block.name, fn.name);
84171
+ if (block.name === void 0 || block.name === "") block.name = acceptIdentity(block.name, fn.name);
83772
84172
  const fragment = typeof fn.arguments === "string" ? fn.arguments : "";
83773
84173
  block.text += fragment;
83774
84174
  yield {
@@ -87295,9 +87695,9 @@ function installToolResultPrunerGuard(ctx, options = {}) {
87295
87695
  const wrapPruner = (pruner) => {
87296
87696
  if (disposed || !isRecord$14(pruner)) return false;
87297
87697
  if (alreadyWrapped.has(pruner)) return false;
87298
- const target = resolveMethodTarget$3(pruner, "pruneSession");
87698
+ const target = resolveMethodTarget$4(pruner, "pruneSession");
87299
87699
  if (target === void 0) return false;
87300
- if (alreadyPatched$3(patchedMethods, target.holder, target.name)) {
87700
+ if (alreadyPatched$4(patchedMethods, target.holder, target.name)) {
87301
87701
  alreadyWrapped.add(pruner);
87302
87702
  return false;
87303
87703
  }
@@ -87309,11 +87709,11 @@ function installToolResultPrunerGuard(ctx, options = {}) {
87309
87709
  };
87310
87710
  return original.call(this ?? pruner, session);
87311
87711
  };
87312
- if (!installMethod$3(target, replacement)) return false;
87313
- markPatched$3(patchedMethods, target.holder, target.name);
87712
+ if (!installMethod$4(target, replacement)) return false;
87713
+ markPatched$4(patchedMethods, target.holder, target.name);
87314
87714
  alreadyWrapped.add(pruner);
87315
87715
  restores.push(() => {
87316
- restoreMethod$3(target);
87716
+ restoreMethod$4(target);
87317
87717
  });
87318
87718
  return true;
87319
87719
  };
@@ -87533,7 +87933,7 @@ function isRecord$14(value) {
87533
87933
  * @param name - 方法名。
87534
87934
  * @returns 目标;方法不存在时为 `undefined`。
87535
87935
  */
87536
- function resolveMethodTarget$3(service, name) {
87936
+ function resolveMethodTarget$4(service, name) {
87537
87937
  if (typeof service !== "object" || service === null) return void 0;
87538
87938
  const own = service;
87539
87939
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -87565,7 +87965,7 @@ function resolveMethodTarget$3(service, name) {
87565
87965
  * @param replacement - 包装实现。
87566
87966
  * @returns 是否装上了。
87567
87967
  */
87568
- function installMethod$3(target, replacement) {
87968
+ function installMethod$4(target, replacement) {
87569
87969
  try {
87570
87970
  Object.defineProperty(target.holder, target.name, {
87571
87971
  value: replacement,
@@ -87583,7 +87983,7 @@ function installMethod$3(target, replacement) {
87583
87983
  *
87584
87984
  * @param target - 目标。
87585
87985
  */
87586
- function restoreMethod$3(target) {
87986
+ function restoreMethod$4(target) {
87587
87987
  try {
87588
87988
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
87589
87989
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -87597,7 +87997,7 @@ function restoreMethod$3(target) {
87597
87997
  * @param name - 方法名。
87598
87998
  * @returns 是否已经包过。
87599
87999
  */
87600
- function alreadyPatched$3(patched, holder, name) {
88000
+ function alreadyPatched$4(patched, holder, name) {
87601
88001
  return patched.get(holder)?.has(name) === true;
87602
88002
  }
87603
88003
  /**
@@ -87607,7 +88007,7 @@ function alreadyPatched$3(patched, holder, name) {
87607
88007
  * @param holder - 方法宿主。
87608
88008
  * @param name - 方法名。
87609
88009
  */
87610
- function markPatched$3(patched, holder, name) {
88010
+ function markPatched$4(patched, holder, name) {
87611
88011
  const names = patched.get(holder);
87612
88012
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
87613
88013
  else names.add(name);
@@ -87622,7 +88022,7 @@ const GLOBAL_KEY$2 = Object.freeze({});
87622
88022
  * @param service - 服务名。
87623
88023
  * @returns 服务实例或 undefined。
87624
88024
  */
87625
- function serviceOf$1(ctx, service) {
88025
+ function serviceOf$2(ctx, service) {
87626
88026
  const read = ctx?.get;
87627
88027
  if (typeof read !== "function") return void 0;
87628
88028
  try {
@@ -87644,11 +88044,15 @@ function bucketOf$1(value) {
87644
88044
  * @param ctx - 宿主上下文。
87645
88045
  * @param event - 事件名。
87646
88046
  * @param listener - 监听器。
88047
+ * @param options - cordis 订阅选项;省略时按两参数订阅。
87647
88048
  * @returns 解绑函数或 undefined。
87648
88049
  */
87649
- function subscribe$3(ctx, event, listener) {
88050
+ function subscribe$3(ctx, event, listener, options) {
87650
88051
  const on = ctx?.on;
87651
88052
  if (typeof on !== "function") return void 0;
88053
+ if (options !== void 0) try {
88054
+ return on.call(ctx, event, listener, options);
88055
+ } catch {}
87652
88056
  try {
87653
88057
  return on.call(ctx, event, listener);
87654
88058
  } catch {
@@ -87741,11 +88145,15 @@ function installPlanModeCacheGuard(ctx) {
87741
88145
  if (offPreStep !== void 0) restores.push(() => {
87742
88146
  offPreStep();
87743
88147
  });
88148
+ const offReminder = installPlanModeExitReminder(ctx, { isActive: (agent) => planActiveOfAgent(ctx, agent) });
88149
+ if (offReminder !== void 0) restores.push(() => {
88150
+ offReminder();
88151
+ });
87744
88152
  const wrap = (tools) => {
87745
88153
  if (tools === void 0) return;
87746
- const target = resolveMethodTarget$2(tools, "register");
88154
+ const target = resolveMethodTarget$3(tools, "register");
87747
88155
  if (target === void 0) return;
87748
- if (alreadyPatched$2(patchedMethods, target.holder, target.name)) return;
88156
+ if (alreadyPatched$3(patchedMethods, target.holder, target.name)) return;
87749
88157
  const original = target.original;
87750
88158
  const wrapped = function(...args) {
87751
88159
  const receiver = this ?? tools;
@@ -87757,16 +88165,16 @@ function installPlanModeCacheGuard(ctx) {
87757
88165
  }
87758
88166
  return original.call(receiver, definition, options);
87759
88167
  };
87760
- if (!installMethod$2(target, wrapped)) return;
87761
- markPatched$2(patchedMethods, target.holder, target.name);
88168
+ if (!installMethod$3(target, wrapped)) return;
88169
+ markPatched$3(patchedMethods, target.holder, target.name);
87762
88170
  restores.push(() => {
87763
- restoreMethod$2(target);
88171
+ restoreMethod$3(target);
87764
88172
  });
87765
88173
  if (typeof tools.get === "function") try {
87766
88174
  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");
87767
88175
  } catch {}
87768
88176
  };
87769
- const tools = serviceOf$1(ctx, "tools");
88177
+ const tools = serviceOf$2(ctx, "tools");
87770
88178
  if (tools !== void 0) wrap(tools);
87771
88179
  else {
87772
88180
  const inject = ctx?.inject;
@@ -87776,7 +88184,7 @@ function installPlanModeCacheGuard(ctx) {
87776
88184
  inject.call(ctx, ["tools"], (scope) => {
87777
88185
  if (wrapped || closed) return;
87778
88186
  wrapped = true;
87779
- wrap(serviceOf$1(scope, "tools"));
88187
+ wrap(serviceOf$2(scope, "tools"));
87780
88188
  });
87781
88189
  } catch {}
87782
88190
  }
@@ -87796,7 +88204,7 @@ function installPlanModeCacheGuard(ctx) {
87796
88204
  * @returns true / false;读不到时 undefined。
87797
88205
  */
87798
88206
  function planActiveOf(ctx, session) {
87799
- const projections = serviceOf$1(ctx, "sessionProjections");
88207
+ const projections = serviceOf$2(ctx, "sessionProjections");
87800
88208
  if (typeof projections?.stateOf !== "function") return void 0;
87801
88209
  try {
87802
88210
  const state = projections.stateOf(session, PLAN_PROJECTION_KEY);
@@ -87806,6 +88214,35 @@ function planActiveOf(ctx, session) {
87806
88214
  }
87807
88215
  }
87808
88216
  /**
88217
+ * 读一个 agent 此刻是否在计划模式。
88218
+ *
88219
+ * 三级降级,顺序不可颠倒:
88220
+ *
88221
+ * 1. 进程级登记册({@link registeredPlanControllerActive}):本包的 fork 会把每个
88222
+ * 会话登记进去,**跨 realm 可见**。`ctx.get()` 看不见 `isolate: { planMode: true }`
88223
+ * 的子 realm,而隔离正是宿主守卫最常见的部署(守卫在根作用域、控制器在 preset 里),
88224
+ * 所以这一级必须先问。
88225
+ * 2. 同一作用域里的 `planMode` 服务:本包的 fork 有 `selectedActive`(含未决选择),
88226
+ * 所以 `exit_plan_mode` 刚排队 `{ active: false }`、日志还没落进投影的那一刻不会被
88227
+ * 误判成「还在计划模式」。
88228
+ * 3. plan 投影:内置 preset 挂的是官方服务,它的 `/plan` 只写日志,投影是那边唯一的
88229
+ * 事实来源。
88230
+ * @param ctx - 宿主上下文。
88231
+ * @param agent - 调用方 agent。
88232
+ * @returns true / false;读不到时 undefined。
88233
+ */
88234
+ function planActiveOfAgent(ctx, agent) {
88235
+ const session = agent?.session;
88236
+ const registered = registeredPlanControllerActive(session);
88237
+ if (registered !== void 0) return registered;
88238
+ const controller = serviceOf$2(ctx, "planMode");
88239
+ if (typeof controller?.selectedActive === "function") try {
88240
+ const active = controller.selectedActive(session);
88241
+ if (typeof active === "boolean") return active;
88242
+ } catch {}
88243
+ return planActiveOf(ctx, session);
88244
+ }
88245
+ /**
87809
88246
  * 记一条警告。
87810
88247
  * @param ctx - 宿主上下文。
87811
88248
  * @param message - 正文。
@@ -87834,7 +88271,7 @@ function logWarn$1(ctx, message) {
87834
88271
  * @param name - 方法名。
87835
88272
  * @returns 目标;方法不存在时为 `undefined`。
87836
88273
  */
87837
- function resolveMethodTarget$2(service, name) {
88274
+ function resolveMethodTarget$3(service, name) {
87838
88275
  if (typeof service !== "object" || service === null) return void 0;
87839
88276
  const own = service;
87840
88277
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -87866,7 +88303,7 @@ function resolveMethodTarget$2(service, name) {
87866
88303
  * @param replacement - 包装实现。
87867
88304
  * @returns 是否装上了。
87868
88305
  */
87869
- function installMethod$2(target, replacement) {
88306
+ function installMethod$3(target, replacement) {
87870
88307
  try {
87871
88308
  Object.defineProperty(target.holder, target.name, {
87872
88309
  value: replacement,
@@ -87884,7 +88321,7 @@ function installMethod$2(target, replacement) {
87884
88321
  *
87885
88322
  * @param target - 目标。
87886
88323
  */
87887
- function restoreMethod$2(target) {
88324
+ function restoreMethod$3(target) {
87888
88325
  try {
87889
88326
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
87890
88327
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -87898,7 +88335,7 @@ function restoreMethod$2(target) {
87898
88335
  * @param name - 方法名。
87899
88336
  * @returns 是否已经包过。
87900
88337
  */
87901
- function alreadyPatched$2(patched, holder, name) {
88338
+ function alreadyPatched$3(patched, holder, name) {
87902
88339
  return patched.get(holder)?.has(name) === true;
87903
88340
  }
87904
88341
  /**
@@ -87908,7 +88345,7 @@ function alreadyPatched$2(patched, holder, name) {
87908
88345
  * @param holder - 方法宿主。
87909
88346
  * @param name - 方法名。
87910
88347
  */
87911
- function markPatched$2(patched, holder, name) {
88348
+ function markPatched$3(patched, holder, name) {
87912
88349
  const names = patched.get(holder);
87913
88350
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
87914
88351
  else names.add(name);
@@ -88030,7 +88467,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88030
88467
  */
88031
88468
  const toolFingerprint = (tools, scope) => {
88032
88469
  if (!isRecord$13(tools)) return "";
88033
- const target = resolveMethodTarget$1(tools, "definitions");
88470
+ const target = resolveMethodTarget$2(tools, "definitions");
88034
88471
  if (target === void 0) return "";
88035
88472
  let list;
88036
88473
  try {
@@ -88050,9 +88487,9 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88050
88487
  const wrapToolsService = (tools) => {
88051
88488
  if (disposed || !isRecord$13(tools)) return false;
88052
88489
  if (alreadyWrapped.has(tools)) return false;
88053
- const target = resolveMethodTarget$1(tools, "definitions");
88490
+ const target = resolveMethodTarget$2(tools, "definitions");
88054
88491
  if (target === void 0) return false;
88055
- if (alreadyPatched$1(patchedMethods, target.holder, target.name)) {
88492
+ if (alreadyPatched$2(patchedMethods, target.holder, target.name)) {
88056
88493
  alreadyWrapped.add(tools);
88057
88494
  return false;
88058
88495
  }
@@ -88065,11 +88502,11 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88065
88502
  }
88066
88503
  return original.call(receiver, scope, toolSet);
88067
88504
  };
88068
- if (!installMethod$1(target, replacement)) return false;
88069
- markPatched$1(patchedMethods, target.holder, target.name);
88505
+ if (!installMethod$2(target, replacement)) return false;
88506
+ markPatched$2(patchedMethods, target.holder, target.name);
88070
88507
  alreadyWrapped.add(tools);
88071
88508
  restores.push(() => {
88072
- restoreMethod$1(target);
88509
+ restoreMethod$2(target);
88073
88510
  });
88074
88511
  return true;
88075
88512
  };
@@ -88109,7 +88546,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88109
88546
  };
88110
88547
  const offAssemble = subscribe$2(host, "system-prompt/assemble", onAssemble);
88111
88548
  if (offAssemble !== void 0) disposers.push(offAssemble);
88112
- const initialTools = serviceOf(host, "tools");
88549
+ const initialTools = serviceOf$1(host, "tools");
88113
88550
  if (initialTools !== void 0) {
88114
88551
  currentTools = initialTools;
88115
88552
  wrapToolsService(initialTools);
@@ -88118,7 +88555,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88118
88555
  if (typeof inject === "function") try {
88119
88556
  inject.call(host, ["tools"], (scope) => {
88120
88557
  if (disposed) return;
88121
- const tools = serviceOf(scope, "tools");
88558
+ const tools = serviceOf$1(scope, "tools");
88122
88559
  if (tools === void 0) return;
88123
88560
  currentTools = tools;
88124
88561
  wrapToolsService(tools);
@@ -88146,7 +88583,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
88146
88583
  * @param service - 服务名。
88147
88584
  * @returns 服务实例;读不到时为 `undefined`。
88148
88585
  */
88149
- function serviceOf(ctx, service) {
88586
+ function serviceOf$1(ctx, service) {
88150
88587
  if (!isRecord$13(ctx)) return void 0;
88151
88588
  const read = ctx["get"];
88152
88589
  if (typeof read !== "function") return void 0;
@@ -88215,7 +88652,7 @@ function isRecord$13(value) {
88215
88652
  * @param name - 方法名。
88216
88653
  * @returns 目标;方法不存在时为 `undefined`。
88217
88654
  */
88218
- function resolveMethodTarget$1(service, name) {
88655
+ function resolveMethodTarget$2(service, name) {
88219
88656
  if (typeof service !== "object" || service === null) return void 0;
88220
88657
  const own = service;
88221
88658
  if (Object.prototype.hasOwnProperty.call(own, name)) {
@@ -88247,7 +88684,7 @@ function resolveMethodTarget$1(service, name) {
88247
88684
  * @param replacement - 包装实现。
88248
88685
  * @returns 是否装上了。
88249
88686
  */
88250
- function installMethod$1(target, replacement) {
88687
+ function installMethod$2(target, replacement) {
88251
88688
  try {
88252
88689
  Object.defineProperty(target.holder, target.name, {
88253
88690
  value: replacement,
@@ -88265,7 +88702,7 @@ function installMethod$1(target, replacement) {
88265
88702
  *
88266
88703
  * @param target - 目标。
88267
88704
  */
88268
- function restoreMethod$1(target) {
88705
+ function restoreMethod$2(target) {
88269
88706
  try {
88270
88707
  if (target.descriptor === void 0) Reflect.deleteProperty(target.holder, target.name);
88271
88708
  else Object.defineProperty(target.holder, target.name, target.descriptor);
@@ -88279,7 +88716,7 @@ function restoreMethod$1(target) {
88279
88716
  * @param name - 方法名。
88280
88717
  * @returns 是否已经包过。
88281
88718
  */
88282
- function alreadyPatched$1(patched, holder, name) {
88719
+ function alreadyPatched$2(patched, holder, name) {
88283
88720
  return patched.get(holder)?.has(name) === true;
88284
88721
  }
88285
88722
  /**
@@ -88289,12 +88726,245 @@ function alreadyPatched$1(patched, holder, name) {
88289
88726
  * @param holder - 方法宿主。
88290
88727
  * @param name - 方法名。
88291
88728
  */
88292
- function markPatched$1(patched, holder, name) {
88729
+ function markPatched$2(patched, holder, name) {
88293
88730
  const names = patched.get(holder);
88294
88731
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
88295
88732
  else names.add(name);
88296
88733
  }
88297
88734
  //#endregion
88735
+ //#region src/host/settled-job-guard.ts
88736
+ function isRecord$12(value) {
88737
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88738
+ }
88739
+ function serviceOf(ctx, name) {
88740
+ const get = ctx?.get;
88741
+ if (typeof get !== "function") return void 0;
88742
+ try {
88743
+ return get.call(ctx, name);
88744
+ } catch {
88745
+ return;
88746
+ }
88747
+ }
88748
+ function isTerminalStatus(value) {
88749
+ return value === "completed" || value === "failed" || value === "killed";
88750
+ }
88751
+ function isUnknownJobError(error, id) {
88752
+ if (!(error instanceof Error)) return false;
88753
+ return error.message === `unknown job ${id}`;
88754
+ }
88755
+ /**
88756
+ * 通过 `agents` 或 `sessions` 服务定位 `caller` 对应的会话对象。
88757
+ */
88758
+ function resolveCallerSession(ctx, caller) {
88759
+ const agents = serviceOf(ctx, "agents");
88760
+ if (typeof agents?.get === "function") try {
88761
+ const agent = agents.get(caller);
88762
+ if (agent?.session !== void 0 && agent.session !== null) return agent.session;
88763
+ } catch {}
88764
+ const sessions = serviceOf(ctx, "sessions");
88765
+ if (typeof sessions?.get === "function") try {
88766
+ const session = sessions.get(caller);
88767
+ if (session !== void 0 && session !== null) return session;
88768
+ } catch {
88769
+ return;
88770
+ }
88771
+ }
88772
+ /**
88773
+ * 从调用方自己的持久会话日志里倒序查找已落盘的 `job/settled` 记录。
88774
+ *
88775
+ * 严格校验 `owner === caller`:无主或归属其它会话的任务绝不跨会话恢复。
88776
+ */
88777
+ function findSettledJobInSession(session, jobId, caller) {
88778
+ if (!isRecord$12(session)) return void 0;
88779
+ const snapshot = session["snapshotEvents"];
88780
+ if (typeof snapshot !== "function") return void 0;
88781
+ let events;
88782
+ try {
88783
+ events = snapshot.call(session);
88784
+ } catch {
88785
+ return;
88786
+ }
88787
+ if (!Array.isArray(events)) return void 0;
88788
+ for (let index = events.length - 1; index >= 0; index -= 1) {
88789
+ const event = events[index];
88790
+ if (!isRecord$12(event) || event["type"] !== "job/settled") continue;
88791
+ const data = event["data"];
88792
+ if (!isRecord$12(data)) continue;
88793
+ if (data["jobId"] !== jobId) continue;
88794
+ if (data["owner"] !== caller) return void 0;
88795
+ const kind = typeof data["kind"] === "string" && data["kind"].length > 0 ? data["kind"] : "job";
88796
+ const label = typeof data["label"] === "string" && data["label"].length > 0 ? data["label"] : jobId;
88797
+ const status = isTerminalStatus(data["status"]) ? data["status"] : "killed";
88798
+ const detail = typeof data["detail"] === "string" && data["detail"].length > 0 ? data["detail"] : void 0;
88799
+ const startedAt = typeof data["startedAt"] === "number" && Number.isFinite(data["startedAt"]) ? data["startedAt"] : 0;
88800
+ const finishedAt = typeof data["finishedAt"] === "number" && Number.isFinite(data["finishedAt"]) ? data["finishedAt"] : void 0;
88801
+ return {
88802
+ id: jobId,
88803
+ kind,
88804
+ label,
88805
+ owner: caller,
88806
+ status,
88807
+ ...detail === void 0 ? {} : { detail },
88808
+ startedAt,
88809
+ ...finishedAt === void 0 ? {} : { finishedAt },
88810
+ output: {
88811
+ total: 0,
88812
+ earliest: 0
88813
+ }
88814
+ };
88815
+ }
88816
+ }
88817
+ function resolveMethodTarget$1(service, name) {
88818
+ if (typeof service !== "object" || service === null) return void 0;
88819
+ const own = service;
88820
+ if (Object.prototype.hasOwnProperty.call(own, name)) {
88821
+ const original = own[name];
88822
+ if (typeof original !== "function") return void 0;
88823
+ return {
88824
+ holder: own,
88825
+ name,
88826
+ original,
88827
+ descriptor: Object.getOwnPropertyDescriptor(own, name)
88828
+ };
88829
+ }
88830
+ const proto = Object.getPrototypeOf(service);
88831
+ if (proto === null || proto === Object.prototype || proto === Function.prototype) return void 0;
88832
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
88833
+ if (descriptor === void 0 || typeof descriptor.value !== "function") return void 0;
88834
+ return {
88835
+ holder: proto,
88836
+ name,
88837
+ original: descriptor.value,
88838
+ descriptor
88839
+ };
88840
+ }
88841
+ function installMethod$1(target, replacement) {
88842
+ try {
88843
+ Object.defineProperty(target.holder, target.name, {
88844
+ configurable: target.descriptor?.configurable ?? true,
88845
+ enumerable: target.descriptor?.enumerable ?? false,
88846
+ writable: target.descriptor?.writable ?? true,
88847
+ value: replacement
88848
+ });
88849
+ return true;
88850
+ } catch {
88851
+ return false;
88852
+ }
88853
+ }
88854
+ function restoreMethod$1(target) {
88855
+ try {
88856
+ if (target.descriptor !== void 0) Object.defineProperty(target.holder, target.name, target.descriptor);
88857
+ else delete target.holder[target.name];
88858
+ } catch {}
88859
+ }
88860
+ function alreadyPatched$1(patched, holder, name) {
88861
+ return patched.get(holder)?.has(name) === true;
88862
+ }
88863
+ function markPatched$1(patched, holder, name) {
88864
+ let set = patched.get(holder);
88865
+ if (set === void 0) {
88866
+ set = /* @__PURE__ */ new Set();
88867
+ patched.set(holder, set);
88868
+ }
88869
+ set.add(name);
88870
+ }
88871
+ /**
88872
+ * 安装「已结算后台任务跨回合幂等守卫」。
88873
+ *
88874
+ * @param ctx - 宿主上下文。
88875
+ * @returns 守卫句柄。
88876
+ */
88877
+ function installSettledJobGuard(ctx) {
88878
+ const restores = [];
88879
+ const patchedMethods = /* @__PURE__ */ new WeakMap();
88880
+ let closed = false;
88881
+ const recoverForCaller = (rawId, rawCaller) => {
88882
+ if (closed) return void 0;
88883
+ if (typeof rawId !== "string" || rawId.length === 0) return void 0;
88884
+ if (typeof rawCaller !== "string" || rawCaller.length === 0) return void 0;
88885
+ return findSettledJobInSession(resolveCallerSession(ctx, rawCaller), rawId, rawCaller);
88886
+ };
88887
+ const patchSync = (jobs, methodName, onRecovered) => {
88888
+ const target = resolveMethodTarget$1(jobs, methodName);
88889
+ if (target === void 0 || alreadyPatched$1(patchedMethods, target.holder, target.name)) return;
88890
+ const original = target.original;
88891
+ const wrapped = function(...args) {
88892
+ const receiver = this ?? jobs;
88893
+ try {
88894
+ return original.call(receiver, ...args);
88895
+ } catch (error) {
88896
+ const id = typeof args[0] === "string" ? args[0] : "";
88897
+ if (id !== "" && isUnknownJobError(error, id)) {
88898
+ const recovered = recoverForCaller(id, args[1]);
88899
+ if (recovered !== void 0) return onRecovered(recovered);
88900
+ }
88901
+ throw error;
88902
+ }
88903
+ };
88904
+ if (!installMethod$1(target, wrapped)) return;
88905
+ markPatched$1(patchedMethods, target.holder, target.name);
88906
+ restores.push(() => {
88907
+ restoreMethod$1(target);
88908
+ });
88909
+ };
88910
+ const patchWait = (jobs) => {
88911
+ const target = resolveMethodTarget$1(jobs, "wait");
88912
+ if (target === void 0 || alreadyPatched$1(patchedMethods, target.holder, target.name)) return;
88913
+ const original = target.original;
88914
+ const wrapped = async function(...args) {
88915
+ const receiver = this ?? jobs;
88916
+ try {
88917
+ return await original.call(receiver, ...args);
88918
+ } catch (error) {
88919
+ const id = typeof args[0] === "string" ? args[0] : "";
88920
+ if (id !== "" && isUnknownJobError(error, id)) {
88921
+ const recovered = recoverForCaller(id, args[2]);
88922
+ if (recovered !== void 0) return recovered;
88923
+ }
88924
+ throw error;
88925
+ }
88926
+ };
88927
+ if (!installMethod$1(target, wrapped)) return;
88928
+ markPatched$1(patchedMethods, target.holder, target.name);
88929
+ restores.push(() => {
88930
+ restoreMethod$1(target);
88931
+ });
88932
+ };
88933
+ const wrap = (jobs) => {
88934
+ if (jobs === void 0) return;
88935
+ patchSync(jobs, "kill", () => "already-finished");
88936
+ patchSync(jobs, "get", (view) => view);
88937
+ patchSync(jobs, "read", (view) => ({
88938
+ chunks: [],
88939
+ lossy: false,
88940
+ job: view
88941
+ }));
88942
+ patchWait(jobs);
88943
+ };
88944
+ const immediate = serviceOf(ctx, "jobs");
88945
+ if (immediate !== void 0) wrap(immediate);
88946
+ else {
88947
+ const inject = ctx?.inject;
88948
+ if (typeof inject === "function") {
88949
+ let wrapped = false;
88950
+ try {
88951
+ inject.call(ctx, ["jobs"], (scope) => {
88952
+ if (wrapped || closed) return;
88953
+ wrapped = true;
88954
+ wrap(serviceOf(scope, "jobs"));
88955
+ });
88956
+ } catch {}
88957
+ }
88958
+ }
88959
+ return { dispose() {
88960
+ if (closed) return;
88961
+ closed = true;
88962
+ for (const restore of restores.splice(0)) try {
88963
+ restore();
88964
+ } catch {}
88965
+ } };
88966
+ }
88967
+ //#endregion
88298
88968
  //#region src/host/team-policy-cache-guard.ts
88299
88969
  /**
88300
88970
  * 「Agent Teams 策略段前缀缓存守卫」—— 把内置 `team:policy` 段从 system 里摘掉,
@@ -88634,7 +89304,7 @@ const GUARDED_EVENTS = [
88634
89304
  * @returns 可销毁的句柄。
88635
89305
  */
88636
89306
  function installCompactionPolicyGuard(ctx, options = {}) {
88637
- const host = isRecord$12(ctx) ? ctx : void 0;
89307
+ const host = isRecord$11(ctx) ? ctx : void 0;
88638
89308
  const provider = options.provider ?? "kimi-code";
88639
89309
  const defaults = {
88640
89310
  provider,
@@ -88709,9 +89379,9 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88709
89379
  * @param source - agent、header 或配置对象。
88710
89380
  */
88711
89381
  const observeFrom = (source) => {
88712
- if (!isRecord$12(source)) return;
89382
+ if (!isRecord$11(source)) return;
88713
89383
  const session = source["session"];
88714
- if (isRecord$12(session)) {
89384
+ if (isRecord$11(session)) {
88715
89385
  const requestHeader = session["requestHeader"];
88716
89386
  if (typeof requestHeader === "function") {
88717
89387
  let header;
@@ -88720,16 +89390,16 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88720
89390
  } catch {
88721
89391
  header = void 0;
88722
89392
  }
88723
- if (isRecord$12(header)) {
89393
+ if (isRecord$11(header)) {
88724
89394
  const config = header["config"];
88725
- if (isRecord$12(config)) observe(readString(config["provider"]), readString(config["model"]));
89395
+ if (isRecord$11(config)) observe(readString(config["provider"]), readString(config["model"]));
88726
89396
  }
88727
89397
  }
88728
89398
  }
88729
89399
  const optionsField = source["options"];
88730
- if (isRecord$12(optionsField)) observe(readString(optionsField["provider"]), readString(optionsField["model"]));
89400
+ if (isRecord$11(optionsField)) observe(readString(optionsField["provider"]), readString(optionsField["model"]));
88731
89401
  const config = source["config"];
88732
- if (isRecord$12(config)) observe(readString(config["provider"]), readString(config["model"]));
89402
+ if (isRecord$11(config)) observe(readString(config["provider"]), readString(config["model"]));
88733
89403
  };
88734
89404
  /**
88735
89405
  * 读一个字符串字段。
@@ -88759,10 +89429,10 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88759
89429
  * @returns 是否真的改写了。
88760
89430
  */
88761
89431
  const patchEngine = (engine) => {
88762
- if (disposed || !isRecord$12(engine)) return false;
89432
+ if (disposed || !isRecord$11(engine)) return false;
88763
89433
  if (patched.has(engine)) return false;
88764
89434
  const config = engine["config"];
88765
- if (!isRecord$12(config)) return false;
89435
+ if (!isRecord$11(config)) return false;
88766
89436
  const originalPolicies = config["modelPolicies"];
88767
89437
  if (!Array.isArray(originalPolicies)) return false;
88768
89438
  const originalConfig = config;
@@ -88832,7 +89502,7 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88832
89502
  let nextArgs = args;
88833
89503
  if (name === "summarize") {
88834
89504
  const input = args[0];
88835
- if (isRecord$12(input)) {
89505
+ if (isRecord$11(input)) {
88836
89506
  const messages = input["messages"];
88837
89507
  if (Array.isArray(messages)) {
88838
89508
  const replaced = replaceCompactionInstruction(messages);
@@ -88867,12 +89537,12 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88867
89537
  * @returns 是否改写了新引擎。
88868
89538
  */
88869
89539
  const patchStoreEntry = (entry) => {
88870
- if (!isRecord$12(entry)) return false;
89540
+ if (!isRecord$11(entry)) return false;
88871
89541
  const record = entry;
88872
89542
  const name = readServiceName(record);
88873
89543
  if (name !== void 0 && name !== "compaction") return false;
88874
89544
  const value = record.value ?? record.impl;
88875
- if (isRecord$12(value)) {
89545
+ if (isRecord$11(value)) {
88876
89546
  if (patchEngine(value)) return true;
88877
89547
  const nested = value["engine"];
88878
89548
  if (nested !== void 0) return patchEngine(nested);
@@ -88887,11 +89557,11 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88887
89557
  * @returns 本次新改写的引擎数。
88888
89558
  */
88889
89559
  const scanStore = (target) => {
88890
- if (!isRecord$12(target)) return 0;
89560
+ if (!isRecord$11(target)) return 0;
88891
89561
  const reflect = target["reflect"];
88892
- if (!isRecord$12(reflect)) return 0;
89562
+ if (!isRecord$11(reflect)) return 0;
88893
89563
  const store = reflect["store"];
88894
- if (!isRecord$12(store)) return 0;
89564
+ if (!isRecord$11(store)) return 0;
88895
89565
  const services = store;
88896
89566
  let count = 0;
88897
89567
  for (const key of Object.getOwnPropertySymbols(services)) if (patchStoreEntry(services[key])) count += 1;
@@ -88905,12 +89575,12 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88905
89575
  * @returns 是否改写了新引擎。
88906
89576
  */
88907
89577
  const patchServiceFrom = (target, name) => {
88908
- if (!isRecord$12(target)) return false;
89578
+ if (!isRecord$11(target)) return false;
88909
89579
  const get = target["get"];
88910
89580
  if (typeof get !== "function") return false;
88911
89581
  try {
88912
89582
  const service = get.call(target, name);
88913
- if (!isRecord$12(service)) return false;
89583
+ if (!isRecord$11(service)) return false;
88914
89584
  if (patchEngine(service)) return true;
88915
89585
  const nested = service["engine"];
88916
89586
  return nested === void 0 ? false : patchEngine(nested);
@@ -88953,7 +89623,7 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88953
89623
  try {
88954
89624
  for (const arg of args) {
88955
89625
  observeFrom(arg);
88956
- if (isRecord$12(arg)) observeFrom(arg["agent"]);
89626
+ if (isRecord$11(arg)) observeFrom(arg["agent"]);
88957
89627
  }
88958
89628
  scanAndPatch();
88959
89629
  const agentCtx = findAgentContext(args);
@@ -88992,9 +89662,9 @@ function installCompactionPolicyGuard(ctx, options = {}) {
88992
89662
  function readServiceName(record) {
88993
89663
  if (typeof record.name === "string") return record.name;
88994
89664
  const impl = record.impl;
88995
- if (isRecord$12(impl) && typeof impl["name"] === "string") return impl["name"];
89665
+ if (isRecord$11(impl) && typeof impl["name"] === "string") return impl["name"];
88996
89666
  const value = record.value;
88997
- if (isRecord$12(value) && typeof value["name"] === "string") return value["name"];
89667
+ if (isRecord$11(value) && typeof value["name"] === "string") return value["name"];
88998
89668
  }
88999
89669
  /**
89000
89670
  * 从一次事件派发的参数里找出 agent 的上下文。
@@ -89006,7 +89676,7 @@ function findAgentContext(args) {
89006
89676
  for (const arg of args) {
89007
89677
  const direct = readContext(arg);
89008
89678
  if (direct !== void 0) return direct;
89009
- if (isRecord$12(arg)) {
89679
+ if (isRecord$11(arg)) {
89010
89680
  const nested = readContext(arg["agent"]);
89011
89681
  if (nested !== void 0) return nested;
89012
89682
  }
@@ -89019,10 +89689,10 @@ function findAgentContext(args) {
89019
89689
  * @returns 上下文;不像时返回 `undefined`。
89020
89690
  */
89021
89691
  function readContext(value) {
89022
- if (!isRecord$12(value)) return void 0;
89692
+ if (!isRecord$11(value)) return void 0;
89023
89693
  const ctx = value["ctx"];
89024
- if (!isRecord$12(ctx)) return void 0;
89025
- if (typeof ctx["get"] === "function" || isRecord$12(ctx["reflect"])) return ctx;
89694
+ if (!isRecord$11(ctx)) return void 0;
89695
+ if (typeof ctx["get"] === "function" || isRecord$11(ctx["reflect"])) return ctx;
89026
89696
  }
89027
89697
  /**
89028
89698
  * 订阅一个事件,优先用 `prepend: true`。
@@ -89062,7 +89732,7 @@ function subscribe(host, event, listener) {
89062
89732
  * @param value - 任意值。
89063
89733
  * @returns 是非 null 的对象时为 `true`。
89064
89734
  */
89065
- function isRecord$12(value) {
89735
+ function isRecord$11(value) {
89066
89736
  return typeof value === "object" && value !== null;
89067
89737
  }
89068
89738
  /**
@@ -89162,449 +89832,6 @@ function markPatched(patched, holder, name) {
89162
89832
  if (names === void 0) patched.set(holder, /* @__PURE__ */ new Set([name]));
89163
89833
  else names.add(name);
89164
89834
  }
89165
- //#endregion
89166
- //#region src/host/dispatch-execution-guard.ts
89167
- /**
89168
- * 「调度执行守卫」——在**执行期**拦住两类越界调用。
89169
- *
89170
- * ## 为什么需要它
89171
- *
89172
- * 调度模式(dispatch preset)里主控与子代理跑在同一进程、同一套工具注册面上,
89173
- * 于是有两件事会悄悄坏掉:
89174
- *
89175
- * 1. **子代理越权调用主控专用工具**。`ask_user_question`、`exit_plan_mode`、
89176
- * `create_goal` 这些是**主控的**决定口:让子代理自己问用户、自己进/出计划模式、
89177
- * 自己建目标,都会把「一个决定」拆成两个决定——子代理问完,用户答了,主控却
89178
- * 不知道问过什么。子代理本来就该把未决问题**带回**主控,而不是自己去问。
89179
- * 2. **等待子代理期间空等**。子代理还在跑的时候,主控用 `sleep` 空转,既烧 token
89180
- * 又错过通知到达的那一刻。正确做法是**结束这一轮**,让内置的子代理完成通知把
89181
- * 它叫醒(R-W)。
89182
- *
89183
- * ## 零 Kimi Code 缓存漂移
89184
- *
89185
- * 守卫**只**用 `ctx.tools.guard(exec => …)` 这一条执行期接缝:
89186
- *
89187
- * - 不包 `tools.definitions`,不包 `tools.register`,不听
89188
- * `system-prompt/assemble`——工具声明面(`tools:sdk` 段与顶层工具 schema)
89189
- * 保持逐字节恒定,Kimi Code 的前缀缓存不会因为这个守卫重建一次。
89190
- * - 拦截发生在**调用执行时**,模型看到的工具表完全没变,所以「看得见」与
89191
- * 「叫得动」仍然被拆开:工具照常出现在表里,只是越界的调用被拒。
89192
- *
89193
- * ## 判定从哪来
89194
- *
89195
- * 两条判定都走**结构探测**,不 import 任何 harness 代次专属的类型:
89196
- *
89197
- * - 「你是不是子代理」:先看**持久会话血缘**(`session.header.origin ===
89198
- * 'subagent'`),再看宿主活着的**存活根**(`agents.roots()`)。两条都是
89199
- * 读得着的既有事实,不新增任何状态。
89200
- * - 「你还有没有在跑的子代理」:依次问 `subagents.list` 与 `agents.list`,
89201
- * 看有没有 `status === 'running'` 的直接子代。
89202
- *
89203
- * @module @eddyskywalker/dsh-chatgpt-subscription/host/dispatch-execution-guard
89204
- */
89205
- /**
89206
- * 子代理**不得**直接调用的主控专用工具。
89207
- *
89208
- * 这七个都是「决定口」或「交付口」,不是「干活口」:
89209
- *
89210
- * - `ask_user_question` / `exit_plan_mode` / `enter_plan_mode` /
89211
- * `leave_plan_mode`:计划与提问的决定权在主控;
89212
- * - `create_goal` / `update_goal`:目标由主控持有;
89213
- * - `present`:交付口由主控掌握。
89214
- *
89215
- * 子代理遇到需要决定的地方,正确动作是**把问题带回主控**,让主控去调这些工具。
89216
- */
89217
- const DEFAULT_DELEGATED_BLOCKED_TOOLS = Object.freeze([
89218
- "ask_user_question",
89219
- "exit_plan_mode",
89220
- "enter_plan_mode",
89221
- "leave_plan_mode",
89222
- "create_goal",
89223
- "update_goal",
89224
- "present"
89225
- ]);
89226
- /**
89227
- * 承载「等待」语义的 shell 工具。
89228
- *
89229
- * 这三个是本包与宿主都会用到的 shell 入口:它们的 `command` 参数里可能藏着
89230
- * `sleep`。空等只在**确实有在跑的子代理**时才是问题(有子代理时应当结束这一
89231
- * 轮等通知),没有子代理时 `sleep 1` 只是普通命令,不该拦。
89232
- */
89233
- const DEFAULT_SHELL_TOOLS = Object.freeze([
89234
- "bash",
89235
- "pwsh",
89236
- "win_bash"
89237
- ]);
89238
- /** 一次执行里被判定为「纯等待」的命令形态。 */
89239
- 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;
89240
- /** 无害的空转语句(回显、真、冒号)。 */
89241
- const NOOP_COMMAND_PATTERN = /^(?:echo\b.*|Write-Host\b.*|Write-Output\b.*|true|:)$/;
89242
- /** 注释行。 */
89243
- const COMMENT_COMMAND_PATTERN = /^#/;
89244
- /**
89245
- * 读宿主上下文上「直接注入」的 running-child 判定回调。
89246
- *
89247
- * 这一步必须**try/catch**:真实 Cordis 的上下文代理对任何没有 inject 的属性直读
89248
- * 都当场抛 `cannot get property "..." without inject`,一次读失败不该让守卫在
89249
- * 每次工具调用上抛错。
89250
- *
89251
- * @param ctx - 宿主上下文。
89252
- * @returns 注入的回调;没有时为 undefined。
89253
- */
89254
- function injectedHasRunningChildren(ctx) {
89255
- if (!isRecord$11(ctx)) return void 0;
89256
- let injected;
89257
- try {
89258
- injected = ctx["hasRunningChildren"];
89259
- } catch {
89260
- return;
89261
- }
89262
- return typeof injected === "function" ? injected : void 0;
89263
- }
89264
- /**
89265
- * 判断一个值是不是可读的记录对象。
89266
- *
89267
- * @param value - 候选值。
89268
- * @returns 是记录对象时为 true。
89269
- */
89270
- function isRecord$11(value) {
89271
- return typeof value === "object" && value !== null;
89272
- }
89273
- /**
89274
- * 从宿主上下文里读一个**可选**服务。
89275
- *
89276
- * 一律走 `ctx.get()`:没写进 `inject` 的服务名不能当属性读,真实 Cordis 会当场
89277
- * 抛 `cannot get property "..." without inject`,整个插件降级。读不到就是读不到,
89278
- * 守卫据此放行,绝不因此让插件挂不上。
89279
- *
89280
- * @param ctx - 宿主上下文。
89281
- * @param name - 服务名。
89282
- * @returns 服务实例;不存在或读取抛错时为 undefined。
89283
- */
89284
- function readService(ctx, name) {
89285
- const reader = ctx?.get;
89286
- if (typeof reader !== "function") return void 0;
89287
- try {
89288
- return reader.call(ctx, name);
89289
- } catch {
89290
- return;
89291
- }
89292
- }
89293
- /**
89294
- * 读一个宿主上下文里的工具注册面。
89295
- *
89296
- * @param ctx - 宿主上下文。
89297
- * @returns 带 `guard` 接缝的注册面;没有时为 undefined。
89298
- */
89299
- function toolsOf$2(ctx) {
89300
- if (!isRecord$11(ctx)) return void 0;
89301
- let tools;
89302
- try {
89303
- tools = ctx["tools"];
89304
- } catch {
89305
- tools = void 0;
89306
- }
89307
- if (!isRecord$11(tools)) tools = readService(ctx, "tools");
89308
- return isRecord$11(tools) ? tools : void 0;
89309
- }
89310
- /**
89311
- * 读一条记录的 `id`。
89312
- *
89313
- * @param value - 候选记录。
89314
- * @returns id 字符串;没有时为 undefined。
89315
- */
89316
- function idOf(value) {
89317
- if (!isRecord$11(value)) return void 0;
89318
- const id = value["id"];
89319
- return typeof id === "string" ? id : void 0;
89320
- }
89321
- /**
89322
- * 读一条会话记录头里的 `id`。
89323
- *
89324
- * @param agent - 候选 agent。
89325
- * @returns 会话 id;没有时为 undefined。
89326
- */
89327
- function sessionIdOf$1(agent) {
89328
- if (!isRecord$11(agent)) return void 0;
89329
- const session = agent["session"];
89330
- if (!isRecord$11(session)) return void 0;
89331
- const header = session["header"];
89332
- return idOf(header);
89333
- }
89334
- /**
89335
- * 这个调用方是不是**子代理**。
89336
- *
89337
- * 判定分两级,先看能独立成立的持久事实,再看宿主当前活着的存活根:
89338
- *
89339
- * 1. **持久会话血缘**:`session.header.origin === 'subagent'`。这是子代理会话
89340
- * 创建时就写下的血缘,跨宿主代次都在,不依赖任何服务是否挂载。
89341
- * 2. **存活根判定**:宿主提供 `agents.roots()` 时,一个**不在**存活根集合里的
89342
- * agent 就是别人的孩子。先核对**存活**(`agents.get(id) === agent`):不是
89343
- * 那个 id 当前登记的存活 agent,说明这个调用方已经过期或已被顶替——判它是不是
89344
- * 子代理没有意义,放行。
89345
- *
89346
- * 宿主没有 `agents` 服务(或老代没有 `roots`)时,判不了就**放行**:把一个
89347
- * 读不出来的身份当成子代理去拦,会拦掉主控自己的正常调用。
89348
- *
89349
- * @param agent - 调用方 agent。
89350
- * @param ctx - 宿主上下文(可选;用于读 `agents` 服务)。
89351
- * @returns 是子代理时为 true。
89352
- */
89353
- function isDelegatedCaller(agent, ctx) {
89354
- if (!isRecord$11(agent)) return false;
89355
- const session = agent["session"];
89356
- if (isRecord$11(session)) {
89357
- const header = session["header"];
89358
- if (isRecord$11(header) && header["origin"] === "subagent") return true;
89359
- }
89360
- const agents = readService(ctx, "agents");
89361
- if (!isRecord$11(agents)) return false;
89362
- const roots = rootsOf(agents);
89363
- if (roots === void 0) return false;
89364
- const id = idOf(agent);
89365
- if (typeof id === "string" && typeof agents["get"] === "function") {
89366
- let live;
89367
- try {
89368
- live = agents["get"].call(agents, id);
89369
- } catch {
89370
- live = void 0;
89371
- }
89372
- if (live !== agent) return false;
89373
- }
89374
- return !roots.includes(agent);
89375
- }
89376
- /**
89377
- * 读宿主存活根集合。
89378
- *
89379
- * @param agents - `agents` 服务。
89380
- * @returns 存活根数组;服务没有 `roots` 或读取抛错时为 undefined。
89381
- */
89382
- function rootsOf(agents) {
89383
- const roots = agents["roots"];
89384
- if (typeof roots !== "function") return void 0;
89385
- try {
89386
- const listed = roots.call(agents);
89387
- return Array.isArray(listed) ? listed : void 0;
89388
- } catch {
89389
- return;
89390
- }
89391
- }
89392
- /**
89393
- * 判断一条命令是不是**纯等待**。
89394
- *
89395
- * 判据:整条命令**按分隔符切开后**,至少有一条是 sleep/wait,而**每一条**都是
89396
- * sleep/wait 或者无害的空转(回显、真、冒号、注释)。只要有一条在做真事
89397
- * (`sleep 1 && npm test`、`git status`、`node …`),整条命令就不是纯等待——
89398
- * 这类命令**必须放行**,否则守卫会拦掉正当的后台工作。
89399
- *
89400
- * @param command - 候选命令。
89401
- * @returns 是纯等待时为 true。
89402
- */
89403
- function isPureSleepCommand(command) {
89404
- if (typeof command !== "string") return false;
89405
- const trimmed = command.trim();
89406
- if (trimmed.length === 0) return false;
89407
- const statements = trimmed.split(/;|&&|\n/).map((statement) => statement.trim()).filter((statement) => statement.length > 0);
89408
- if (statements.length === 0) return false;
89409
- let sawWait = false;
89410
- for (const statement of statements) {
89411
- if (SLEEP_COMMAND_PATTERN.test(statement)) {
89412
- sawWait = true;
89413
- continue;
89414
- }
89415
- if (!isNoopStatement(statement)) return false;
89416
- }
89417
- return sawWait;
89418
- }
89419
- /**
89420
- * 判断一条语句是不是无害的空转。
89421
- *
89422
- * @param statement - 候选语句。
89423
- * @returns 是空转时为 true。
89424
- */
89425
- function isNoopStatement(statement) {
89426
- return NOOP_COMMAND_PATTERN.test(statement) || COMMENT_COMMAND_PATTERN.test(statement);
89427
- }
89428
- /**
89429
- * 这个调用方当前**还有没有在跑的子代理**。
89430
- *
89431
- * 两条宿主探测,任何一条给出答案就算数:
89432
- *
89433
- * 1. `subagents.list(agent, 'children')`(老代退化成 `subagents.list(agent)`):
89434
- * 直接子代里有没有 `status === 'running'` 的。
89435
- * 2. `agents.list()`:活着且 `status === 'running'`、并且 `parent` 就是这个
89436
- * 调用方的那些 agent。
89437
- *
89438
- * 宿主提供 `hasRunningChildren` 回调时以它为准(直接注入口,见
89439
- * {@link DispatchExecutionGuardOptions.hasRunningChildren})。
89440
- *
89441
- * @param agent - 调用方 agent。
89442
- * @param ctx - 宿主上下文(可选;用于读 `subagents` / `agents` 服务)。
89443
- * @returns 有在跑的子代理时为 true。
89444
- */
89445
- function hasRunningChildSubagent(agent, ctx) {
89446
- if (!isRecord$11(agent)) return false;
89447
- const injected = injectedHasRunningChildren(ctx);
89448
- if (injected !== void 0) try {
89449
- return injected(agent) === true;
89450
- } catch {
89451
- return false;
89452
- }
89453
- const subagents = readService(ctx, "subagents");
89454
- if (isRecord$11(subagents) && typeof subagents["list"] === "function") {
89455
- if (entriesOf(callList(subagents["list"], subagents, agent)).some((entry) => entry["status"] === "running")) return true;
89456
- }
89457
- const agents = readService(ctx, "agents");
89458
- if (isRecord$11(agents) && typeof agents["list"] === "function") {
89459
- const listed = callList(agents["list"], agents, agent, false);
89460
- const parentIds = /* @__PURE__ */ new Set();
89461
- const ownId = idOf(agent);
89462
- if (ownId !== void 0) parentIds.add(ownId);
89463
- const headerId = sessionIdOf$1(agent);
89464
- if (headerId !== void 0) parentIds.add(headerId);
89465
- if (entriesOf(listed).some((entry) => entry["status"] === "running" && typeof entry["parent"] === "string" && parentIds.has(entry["parent"]))) return true;
89466
- }
89467
- return false;
89468
- }
89469
- /**
89470
- * 调用一个「列出子代」的宿主方法。
89471
- *
89472
- * 先试带 scope 的形态(`list(agent, 'children')`),老代只认 `list(agent)`,
89473
- * 再老一代的 `agents.list()` 不接参数——三种都试,谁先成功用谁。
89474
- *
89475
- * @param fn - 宿主方法。
89476
- * @param self - 调用宿主。
89477
- * @param agent - 调用方 agent。
89478
- * @param scoped - 是否先试带 scope 的形态。
89479
- * @returns 宿主返回值;全部失败时为 undefined。
89480
- */
89481
- function callList(fn, self, agent, scoped = true) {
89482
- if (typeof fn !== "function") return void 0;
89483
- const call = fn;
89484
- const attempts = scoped ? [
89485
- [agent, "children"],
89486
- [agent],
89487
- []
89488
- ] : [
89489
- [],
89490
- [agent],
89491
- [agent, "children"]
89492
- ];
89493
- for (const args of attempts) try {
89494
- const listed = call.apply(self, args);
89495
- if (listed !== void 0) return listed;
89496
- } catch {}
89497
- }
89498
- /**
89499
- * 从宿主返回值里取出「条目数组」。
89500
- *
89501
- * 宿主可能直接给数组,也可能给一个包着数组的记录(`children` / `agents`)。
89502
- *
89503
- * @param listed - 宿主返回值。
89504
- * @returns 条目数组;取不到时是空数组。
89505
- */
89506
- function entriesOf(listed) {
89507
- if (Array.isArray(listed)) return listed.filter(isRecord$11);
89508
- if (!isRecord$11(listed)) return [];
89509
- const entries = [];
89510
- for (const key of [
89511
- "children",
89512
- "agents",
89513
- "entries",
89514
- "list"
89515
- ]) {
89516
- const candidate = listed[key];
89517
- if (!Array.isArray(candidate)) continue;
89518
- for (const entry of candidate) if (isRecord$11(entry)) entries.push(entry);
89519
- }
89520
- return entries;
89521
- }
89522
- /**
89523
- * 解析「调度执行守卫」当前是否启用。
89524
- *
89525
- * 沿 volatile 活引用取当前值,所以设置保存后立刻生效。缺省、非法值、读取抛错都
89526
- * 按**开启**处理:守卫是保护,不是功能开关,读失败不该把保护关掉。
89527
- *
89528
- * @param config - 插件配置(活引用或普通值)。
89529
- * @returns 是否启用。
89530
- */
89531
- function resolveDispatchExecutionGuardEnabled(config) {
89532
- const field = config?.dispatchExecutionGuard;
89533
- if (field === void 0 || field === null) return true;
89534
- if (typeof field === "object" && typeof field.get === "function") try {
89535
- return field.get() !== false;
89536
- } catch {
89537
- return true;
89538
- }
89539
- return field !== false;
89540
- }
89541
- /**
89542
- * 子代理越权调用时的拒绝文案。
89543
- *
89544
- * 文案要**可执行**:光说「不许调」模型只会换个花样再试一次,所以这里写清楚
89545
- * 它该做什么——把未决问题带回主控,并带上报告状态。
89546
- *
89547
- * @param toolName - 被拦下的工具名。
89548
- * @returns 拒绝理由。
89549
- */
89550
- function delegatedDenial(toolName) {
89551
- 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.`;
89552
- }
89553
- /**
89554
- * 子代理还在跑时用 sleep 空等的拒绝文案。
89555
- *
89556
- * @returns 拒绝理由。
89557
- */
89558
- function activeChildSleepDenial() {
89559
- 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.";
89560
- }
89561
- /**
89562
- * 装一台「调度执行守卫」。
89563
- *
89564
- * 只用 `ctx.tools.guard()` 这一条执行期接缝:工具声明面(`tools:sdk` 段与顶层
89565
- * 工具 schema)一个字节都不改,Kimi Code 的前缀缓存不会因为这个守卫重建。
89566
- *
89567
- * @param ctx - 宿主上下文。
89568
- * @param options - 守卫选项。
89569
- * @returns 注销守卫的函数;宿主没有 `guard` 接缝时是一个空操作。
89570
- */
89571
- function installDispatchExecutionGuard(ctx, options = {}) {
89572
- const tools = toolsOf$2(ctx);
89573
- const register = tools?.guard;
89574
- if (typeof register !== "function") return () => void 0;
89575
- const enabled = options.enabled ?? (() => true);
89576
- const delegatedBlocked = new Set(DEFAULT_DELEGATED_BLOCKED_TOOLS);
89577
- const shellTools = new Set(DEFAULT_SHELL_TOOLS);
89578
- const hasRunningChildren = options.hasRunningChildren;
89579
- const guard = (execution) => {
89580
- if (!isEnabled(enabled)) return void 0;
89581
- const toolName = typeof execution?.name === "string" ? execution.name : void 0;
89582
- if (toolName === void 0) return void 0;
89583
- const agent = execution?.agent;
89584
- if (delegatedBlocked.has(toolName) && isDelegatedCaller(agent, ctx)) return delegatedDenial(toolName);
89585
- if (shellTools.has(toolName)) {
89586
- const args = isRecord$11(execution?.arguments) ? execution.arguments : void 0;
89587
- if (!(args?.["run_in_background"] === true) && isPureSleepCommand(args?.["command"])) {
89588
- if (hasRunningChildren !== void 0 ? hasRunningChildren(agent) === true : hasRunningChildSubagent(agent, ctx)) return activeChildSleepDenial();
89589
- }
89590
- }
89591
- };
89592
- const dispose = register.call(tools, guard);
89593
- return typeof dispose === "function" ? dispose : () => void 0;
89594
- }
89595
- /**
89596
- * 读一次开关。读失败按**开启**处理:保护不该因为一次读失败而失效。
89597
- *
89598
- * @param enabled - 开关读取函数。
89599
- * @returns 是否启用。
89600
- */
89601
- function isEnabled(enabled) {
89602
- try {
89603
- return enabled() !== false;
89604
- } catch {
89605
- return true;
89606
- }
89607
- }
89608
89835
  const ID_SCHEMA = z.string().min(1).max(64).regex(/^[A-Za-z0-9._:-]+$/);
89609
89836
  const TASK_REFERENCE_SCHEMA = z.string().min(1).max(160);
89610
89837
  const BOUNDARY_SCHEMA = z.string().min(1).max(120);
@@ -92408,6 +92635,7 @@ const RENAMED_PACKAGES = /* @__PURE__ */ new Map([["@deepseek-ai/dsh-workflow-wo
92408
92635
  */
92409
92636
  const OPTIONALLY_ABSENT_PACKAGES = [
92410
92637
  "@deepseek-ai/dsh-tool-present",
92638
+ "@deepseek-ai/dsh-tool-subagent-control/list-agents",
92411
92639
  "@eddyskywalker/dsh-chatgpt-subscription/plan-mode-pipeline",
92412
92640
  "@eddyskywalker/dsh-chatgpt-subscription/plan-mode",
92413
92641
  "@eddyskywalker/dsh-chatgpt-subscription/tool-result-pruner"
@@ -92470,12 +92698,20 @@ function candidatePackageRoots(entryScript = process.argv[1]) {
92470
92698
  } catch {}
92471
92699
  return [...roots];
92472
92700
  }
92701
+ /** The package-name portion of a bare package or package-subpath specifier. */
92702
+ function packageNameOf(specifier) {
92703
+ return specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
92704
+ }
92473
92705
  /**
92474
92706
  * Whether a package or exported subpath is installed in any candidate node_modules tree.
92475
92707
  * A subpath needs its own exact `exports` key; the package root alone is not enough.
92708
+ *
92709
+ * The nearest manifest on each Node resolution chain is authoritative. Looking
92710
+ * farther out after that manifest rejects a subpath would let an unrelated old
92711
+ * installation resurrect an export the running package deliberately removed.
92476
92712
  */
92477
92713
  function packageInstalled(specifier, roots = candidatePackageRoots()) {
92478
- const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
92714
+ const pkg = packageNameOf(specifier);
92479
92715
  const subpath = specifier.slice(pkg.length);
92480
92716
  for (const root of roots) {
92481
92717
  let dir = root;
@@ -92485,9 +92721,9 @@ function packageInstalled(specifier, roots = candidatePackageRoots()) {
92485
92721
  if (subpath === "") return true;
92486
92722
  try {
92487
92723
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
92488
- if (manifest.exports !== void 0 && Object.prototype.hasOwnProperty.call(manifest.exports, `.${subpath}`)) return true;
92724
+ return manifest.exports !== void 0 && Object.prototype.hasOwnProperty.call(manifest.exports, `.${subpath}`);
92489
92725
  } catch {
92490
- continue;
92726
+ return false;
92491
92727
  }
92492
92728
  }
92493
92729
  const parent = dirname(dir);
@@ -92498,20 +92734,72 @@ function packageInstalled(specifier, roots = candidatePackageRoots()) {
92498
92734
  return false;
92499
92735
  }
92500
92736
  /**
92501
- * Whether a bare specifier resolves from this module in the running
92502
- * installation. A harness whose Node build exposes no `import.meta.resolve`
92503
- * or whose plugin is executed from outside the profile tree falls through to
92504
- * checking candidate `node_modules` trees across the running harness home.
92737
+ * Packages the preset intentionally loads from the plugin's own dependency
92738
+ * closure rather than the harness installation. Every other harness row must
92739
+ * resolve where the registry mounts it, not merely beside this plugin.
92740
+ */
92741
+ const PLUGIN_RESOLVED_PACKAGES = ["@eddyskywalker/dsh-chatgpt-subscription", "@deepseek-ai/dsh-tool-executor-native"];
92742
+ /** Whether `candidate` is `root` itself or one of its descendants. */
92743
+ function pathInside(root, candidate) {
92744
+ const rel = relative(root, candidate);
92745
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
92746
+ }
92747
+ /**
92748
+ * Whether a plugin-side resolution points into the package the harness's own
92749
+ * entry script reaches. Walking ancestors mirrors Node's node_modules lookup;
92750
+ * the CLI commonly sits in `bin/`, beside rather than inside node_modules.
92751
+ */
92752
+ function resolvedFromHarness(resolved, entryDir, specifier) {
92753
+ if (typeof resolved !== "string") return false;
92754
+ let path;
92755
+ try {
92756
+ path = fileURLToPath(resolved);
92757
+ } catch {
92758
+ return false;
92759
+ }
92760
+ const pkg = packageNameOf(specifier);
92761
+ let dir = entryDir;
92762
+ for (;;) {
92763
+ if (pathInside(join(dir, "node_modules", pkg), path)) return true;
92764
+ const parent = dirname(dir);
92765
+ if (parent === dir) return false;
92766
+ dir = parent;
92767
+ }
92768
+ }
92769
+ /**
92770
+ * Resolve against the installation that will actually mount the preset.
92771
+ *
92772
+ * `import.meta.resolve` is based at this plugin, while the harness mounts
92773
+ * runtime-declared preset rows from its own context. A successful plugin-side
92774
+ * resolution is therefore not sufficient for a harness package: an old copy in
92775
+ * the profile tree must not override the running harness's missing export.
92776
+ * Plugin-owned rows and the plugin-borne native executor are the deliberate
92777
+ * exceptions. With no harness entry, retain the legacy local probe behavior.
92778
+ */
92779
+ function resolvesFromInstallation(specifier, entryScript, resolveFromPlugin) {
92780
+ const entryDir = harnessEntryDir(entryScript);
92781
+ if (entryDir !== void 0 && packageInstalled(specifier, [entryDir])) return true;
92782
+ let resolved;
92783
+ let didResolve = false;
92784
+ if (resolveFromPlugin !== void 0) try {
92785
+ resolved = resolveFromPlugin(specifier);
92786
+ didResolve = true;
92787
+ } catch {}
92788
+ if (entryDir === void 0) {
92789
+ if (didResolve) return true;
92790
+ return packageInstalled(specifier, candidatePackageRoots(entryScript));
92791
+ }
92792
+ if (didResolve && PLUGIN_RESOLVED_PACKAGES.includes(packageNameOf(specifier))) return true;
92793
+ return didResolve && resolvedFromHarness(resolved, entryDir, specifier);
92794
+ }
92795
+ /**
92796
+ * Whether a bare specifier resolves in the installation that mounts presets.
92505
92797
  * @param specifier - bare package specifier.
92506
92798
  * @returns whether the specifier resolves.
92507
92799
  */
92508
92800
  function resolvesFromHere(specifier) {
92509
- const resolve = import.meta.resolve;
92510
- if (resolve !== void 0) try {
92511
- resolve(specifier);
92512
- return true;
92513
- } catch {}
92514
- return packageInstalled(specifier);
92801
+ const resolveFromPlugin = import.meta.resolve;
92802
+ return resolvesFromInstallation(specifier, process.argv[1], resolveFromPlugin);
92515
92803
  }
92516
92804
  /**
92517
92805
  * Point every renamed package name at the spelling this installation ships.
@@ -92889,23 +93177,16 @@ const DISPATCH_PRESET = {
92889
93177
  name: "@deepseek-ai/dsh-persona",
92890
93178
  config: {
92891
93179
  suffix: "Your working directory is {{cwd}}.",
92892
- 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.
93180
+ 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.
92893
93181
  - [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).
92894
93182
  - [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.
92895
93183
  - [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.
92896
93184
  - [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.
92897
93185
 
92898
93186
  [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.
92899
- [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.
93187
+ [Execution Pipeline] R0 Triage -> (L2) R-G Interview -> R9 Plan -> R1 Topology -> (if delegating: R2 Routing -> R5 Brief -> R-W Wait) -> R6 Acceptance.
92900
93188
 
92901
- ## R0.5 · Compatibility Gate (Once per New Session, Before R0)
92902
- 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.
92903
- Criterion: whether spawn_teammate exists (PTC: tools.spawn_teammate). Never gate on wait_agent, send_message, list_agents, or interrupt_agent: this mode mounts those itself.
92904
- - Absent -> Write "Orchestration ready: subagent" and proceed to R0. The board tools stay visible in the PTC SDK 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.
92905
- - Present -> This session runs the legacy runtime and is **not orchestratable**. In order, and nothing else:
92906
- 1. State in one sentence that the legacy runtime shadows the coordination tools and this mode has no degraded path.
92907
- 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.
92908
- 3. **Do nothing else** until the user replies: no triage, plan, dispatch, file edits, or workarounds.
93189
+ [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.
92909
93190
 
92910
93191
  ## R0 · Complexity Triage (Triage First, Then Choose the Workflow)
92911
93192
  Upon receiving a task, determine its tier by semantic impact and uncertainty first, write one line stating "Tier + Rationale", and run the matching workflow:
@@ -92931,7 +93212,7 @@ L0/L1: execute directly (or 1 subagent for L1). L2: R-G, R9, and R6 are mandator
92931
93212
  - [Media generation] Generate/revise/retry images directly; delegate complex creative work, never a bare call.
92932
93213
 
92933
93214
  ## R2 · Model Routing (Check Availability First, Then Decide How to Dispatch)
92934
- Check whether list_subagent_models exists (in PTC mode: tools.list_subagent_models). The tool row and provider/model/reasoning_effort parameters are mounted statically, so they are present even in a session that authorized no route at all. Read that fact instead of inferring it from the tool's output:
93215
+ Check whether list_subagent_models exists. The tool row and provider/model/reasoning_effort parameters are mounted statically, so they are present even in a session that authorized no route at all. Read that fact instead of inferring it from the tool's output:
92935
93216
  - If the output says neither—no authorized-routes block and no "No allowlist is in force" sentence—the annotation is absent and you cannot read the posture. Treat it as [State: Unlisted] for behaviour, but say "unconfirmed" rather than claiming either posture, and never report a route as authorized.
92936
93217
  - [State: Listed] Output ends with "Authorized child routes for this Session": enforcement is active.
92937
93218
  - The sole source of truth for the allowlist is list_subagent_models: call list_subagent_models({}) to list authorized providers, and call list_subagent_models({ provider }) to list authorized models under that provider; trust its output directly without cross-checking local config files or workspace code (even if upstream tool text says "Catalog membership is advisory", the runtime output is already filtered to this session's allowlist).
@@ -92947,7 +93228,7 @@ A subagent has full authority to plan, execute, and self-verify within its assig
92947
93228
  Every task brief covers six elements: Goal / Boundary (which files or modules may be touched) / Constraints / Limits (see below) / Acceptance Criteria / Required Evidence to Report.
92948
93229
  - [Compact Delta Brief] Standing default limits and the report schema already live in [Role Identification], so use a Compact Delta Brief by default: state Goal, original file/line Source Pointers (do not paraphrase code into long prose), Module-Interface Boundary, Constraints, Acceptance Criteria, and Required Evidence, and omit repeating standing limits unless overriding them.
92949
93230
  - [Limits] When overriding any standing limit, write one line per limit spelling out four things: **what it bounds, in what unit, the cap, and whether it accumulates**. Write explicit caps ("at most 3 attempts", "edits at most 5 files", "test runs: unlimited"), never ranges ("2–5 files"); where an override dimension is left unstated, silence is read as a limit of zero, and a quota that resets per cycle is lost, not banked.
92950
- - Do not ask the child to restate your rules back as a correctness check; if a limit is genuinely unclear, instruct the child to spend one minimal tool call to verify it. Dispatch independent tasks concurrently in one message (via Promise.all in PTC mode) only after the interview has converged.
93231
+ - Do not ask the child to restate your rules back as a correctness check; if a limit is genuinely unclear, instruct the child to spend one minimal tool call to verify it. Dispatch independent tasks concurrently in one message (native calls; use code_run Promise.all for batch filter/MCP) only after the interview has converged.
92951
93232
 
92952
93233
  ## R6 · Review & Acceptance
92953
93234
  Two layers: subagent self-check -> your independent review (reading diffs, running tests, checking each criterion). The completion notification is a post-hoc signal: it tells you a child stopped, not that the work passed.
@@ -92976,7 +93257,7 @@ Fallback is a task-scoped, non-session-wide exception for delegated briefs: it c
92976
93257
 
92977
93258
  ## Anti-Patterns (Any Occurrence Is a Violation)
92978
93259
  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).
92979
- 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).
93260
+ 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).
92980
93261
  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).
92981
93262
  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).
92982
93263
  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).
@@ -99775,6 +100056,23 @@ function applyToolDelta(entry, state) {
99775
100056
  const fn = isRecord$3(entry.function) ? entry.function : {};
99776
100057
  const out = [];
99777
100058
  let call = state.toolCalls.get(wireIndex);
100059
+ const incomingId = asString$1(entry.id);
100060
+ if (call !== void 0 && incomingId !== void 0 && call.id !== `call_${wireIndex}` && incomingId !== call.id) {
100061
+ const closedBlock = {
100062
+ type: "tool-call",
100063
+ id: toToolCallId(call.id),
100064
+ name: call.name,
100065
+ arguments: call.arguments === "" ? "{}" : call.arguments
100066
+ };
100067
+ state.blocks[call.blockIndex] = closedBlock;
100068
+ out.push({
100069
+ type: "block-end",
100070
+ index: call.blockIndex,
100071
+ block: closedBlock
100072
+ });
100073
+ state.toolCalls.delete(wireIndex);
100074
+ call = void 0;
100075
+ }
99778
100076
  if (call === void 0) {
99779
100077
  out.push(...closeCurrent(state));
99780
100078
  call = {
@@ -100144,6 +100442,30 @@ function messagesAssistantContent(message) {
100144
100442
  return blocks;
100145
100443
  }
100146
100444
  /**
100445
+ * Merge neighbouring same-role turns.
100446
+ *
100447
+ * DSH history can hold two user messages in a row (an injected context notice
100448
+ * followed by the real turn) and a tool result is itself a user-role message;
100449
+ * the Messages wire wants one user turn, so consecutive turns of one role are
100450
+ * folded together in order.
100451
+ */
100452
+ function mergeAnthropicMessages(entries) {
100453
+ const merged = [];
100454
+ for (const entry of entries) {
100455
+ if (entry.content.length === 0) continue;
100456
+ const last = merged[merged.length - 1];
100457
+ if (last !== void 0 && last.role === entry.role) {
100458
+ last.content = [...last.content, ...entry.content];
100459
+ continue;
100460
+ }
100461
+ merged.push({
100462
+ role: entry.role,
100463
+ content: [...entry.content]
100464
+ });
100465
+ }
100466
+ return merged;
100467
+ }
100468
+ /**
100147
100469
  * Build one `/v1/messages` body.
100148
100470
  *
100149
100471
  * `stream: true` is mandatory: the adapter reads every response as SSE, and a
@@ -100161,13 +100483,12 @@ function buildMessagesRequest(options, request = {}) {
100161
100483
  const role = isToolResultMessage(message) || message.role !== "assistant" ? "user" : "assistant";
100162
100484
  const content = role === "user" ? messagesUserContent(message, images) : messagesAssistantContent(message);
100163
100485
  if (content.length === 0) continue;
100164
- const previous = entries[entries.length - 1];
100165
- if (previous !== void 0 && previous.role === role) previous.content.push(...content);
100166
- else entries.push({
100486
+ entries.push({
100167
100487
  role,
100168
100488
  content
100169
100489
  });
100170
100490
  }
100491
+ const messages = cleanOrphanedToolCalls(mergeAnthropicMessages(entries), mergeAnthropicMessages);
100171
100492
  const system = leadingSystemTextRaw(options);
100172
100493
  const tools = options.tools ?? [];
100173
100494
  const effort = request.effort !== void 0 && (request.efforts ?? []).length > 0 ? request.effort : void 0;
@@ -100176,7 +100497,7 @@ function buildMessagesRequest(options, request = {}) {
100176
100497
  model: options.model,
100177
100498
  max_tokens: maxTokens,
100178
100499
  stream: true,
100179
- messages: entries,
100500
+ messages,
100180
100501
  ...system === void 0 ? {} : { system },
100181
100502
  ...tools.length > 0 ? { tools: tools.map((tool) => ({
100182
100503
  name: tool.name,
@@ -103926,6 +104247,10 @@ function apply(ctx, pluginConfig = {}) {
103926
104247
  const guard = installPtcSdkCacheGuard(ctx);
103927
104248
  return () => guard.dispose();
103928
104249
  }, "dsh-chatgpt-subscription: PTC SDK cache guard");
104250
+ ctx.effect(() => {
104251
+ const guard = installSettledJobGuard(ctx);
104252
+ return () => guard.dispose();
104253
+ }, "dsh-chatgpt-subscription: settled job guard");
103929
104254
  ctx.effect(() => {
103930
104255
  const guard = installTeamPolicyCacheGuard(ctx, { enabled: () => resolveTeamPolicyCacheGuardEnabled(pluginConfig) });
103931
104256
  return () => guard.dispose();