@eddyskywalker/dsh-chatgpt-subscription 0.59.0 → 0.60.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 (34) hide show
  1. package/CHANGELOG.md +16 -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 +537 -150
  6. package/lib/index.js +177 -183
  7. package/lib/llm-compat-u9mor0uu.js +110 -0
  8. package/lib/types/client/codemode/CodeRunToolView.d.ts +68 -0
  9. package/lib/types/client/codemode/CodeRunToolView.d.ts.map +1 -0
  10. package/lib/types/client/codemode/highlight.d.ts +25 -0
  11. package/lib/types/client/codemode/highlight.d.ts.map +1 -0
  12. package/lib/types/client/codemode/styles.d.ts +11 -0
  13. package/lib/types/client/codemode/styles.d.ts.map +1 -0
  14. package/lib/types/client/codemode/tool-name.d.ts +10 -0
  15. package/lib/types/client/codemode/tool-name.d.ts.map +1 -0
  16. package/lib/types/client/index.d.ts.map +1 -1
  17. package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
  18. package/lib/types/client/locales.d.ts +23 -1
  19. package/lib/types/client/locales.d.ts.map +1 -1
  20. package/lib/types/compat.d.ts +1 -1
  21. package/lib/types/entry-codemode.d.ts.map +1 -1
  22. package/lib/types/host/codemode/executor.d.ts.map +1 -1
  23. package/lib/types/host/codemode/rules.d.ts +16 -6
  24. package/lib/types/host/codemode/rules.d.ts.map +1 -1
  25. package/lib/types/host/codemode/transport.d.ts +82 -41
  26. package/lib/types/host/codemode/transport.d.ts.map +1 -1
  27. package/lib/types/host/command-code/model-catalog.d.ts +14 -14
  28. package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
  29. package/lib/types/host/command-code/routes.d.ts.map +1 -1
  30. package/lib/types/host/preset-sync.d.ts +19 -4
  31. package/lib/types/host/preset-sync.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/presets/dispatch/README.md +1 -3
  34. package/presets/dispatch/agent.cordis.yml +9 -7
package/lib/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { a as resolveProgressUpdatesSettings, n as attachProgressSettings } from "./entry-progress-updates-CxQewio9.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
5
  import { PLAN_PROJECTION_KEY, createPlanNotice } from "./plan-mode.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.60.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`;
@@ -4434,7 +4435,7 @@ var OAuthService = class {
4434
4435
  throw new OAuthServiceError("oauth-token-exchange-failed", `The ChatGPT device code could not be issued (${response.status}${detail === null ? "" : `, ${detail}`}).`, response.status);
4435
4436
  }
4436
4437
  const payload = await response.json().catch(() => null);
4437
- const record = isRecord$68(payload) ? payload : {};
4438
+ const record = isRecord$67(payload) ? payload : {};
4438
4439
  const authId = nonEmptyString$11(record.device_auth_id);
4439
4440
  const userCode = nonEmptyString$11(record.user_code) ?? nonEmptyString$11(record.usercode);
4440
4441
  if (authId === void 0 || userCode === void 0) throw new OAuthServiceError("oauth-token-exchange-failed", "ChatGPT returned no device code.");
@@ -4501,7 +4502,7 @@ var OAuthService = class {
4501
4502
  const detail = deviceErrorIdentifier(payload);
4502
4503
  throw new OAuthServiceError("oauth-token-exchange-failed", `The ChatGPT device confirmation was refused (${response.status}${detail === void 0 ? "" : `, ${detail}`}).`, response.status);
4503
4504
  }
4504
- const record = isRecord$68(payload) ? payload : {};
4505
+ const record = isRecord$67(payload) ? payload : {};
4505
4506
  const authorizationCode = nonEmptyString$11(record.authorization_code);
4506
4507
  const codeVerifier = nonEmptyString$11(record.code_verifier);
4507
4508
  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 +4835,11 @@ const DEVICE_PENDING_STATUSES = /* @__PURE__ */ new Set([
4834
4835
  function isPendingDeviceAnswer(response, payload) {
4835
4836
  if (response.status === 403 || response.status === 404) return true;
4836
4837
  if (!response.ok && response.status !== 400) return false;
4837
- if (!isRecord$68(payload)) return false;
4838
+ if (!isRecord$67(payload)) return false;
4838
4839
  const status = payload.status;
4839
4840
  if (typeof status === "string" && DEVICE_PENDING_STATUSES.has(status.toLowerCase())) return true;
4840
4841
  const error = payload.error;
4841
- if (isRecord$68(error)) {
4842
+ if (isRecord$67(error)) {
4842
4843
  const code = error.code;
4843
4844
  if (typeof code === "string" && DEVICE_PENDING_STATUSES.has(code.toLowerCase())) return true;
4844
4845
  }
@@ -4852,18 +4853,18 @@ function isPendingDeviceAnswer(response, payload) {
4852
4853
  * wrong.
4853
4854
  */
4854
4855
  function deviceErrorIdentifier(payload) {
4855
- if (!isRecord$68(payload)) return void 0;
4856
+ if (!isRecord$67(payload)) return void 0;
4856
4857
  const candidates = [payload.code];
4857
4858
  const error = payload.error;
4858
4859
  if (typeof error === "string") candidates.push(error);
4859
- else if (isRecord$68(error)) candidates.push(error.code, error.type);
4860
+ else if (isRecord$67(error)) candidates.push(error.code, error.type);
4860
4861
  candidates.push(payload.status);
4861
4862
  for (const candidate of candidates) if (typeof candidate === "string" && /^[a-z0-9_.-]{1,64}$/i.test(candidate)) return candidate;
4862
4863
  }
4863
4864
  function nonEmptyString$11(value) {
4864
4865
  return typeof value === "string" && value !== "" ? value : void 0;
4865
4866
  }
4866
- function isRecord$68(value) {
4867
+ function isRecord$67(value) {
4867
4868
  return typeof value === "object" && value !== null && !Array.isArray(value);
4868
4869
  }
4869
4870
  async function oauthErrorIdentifier(response) {
@@ -5044,7 +5045,7 @@ const CODEX_CLI_CREDENTIAL_SOURCE = "codex";
5044
5045
  * here, and the import has to be repeated afterwards.
5045
5046
  */
5046
5047
  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) {
5048
+ function isRecord$66(value) {
5048
5049
  return typeof value === "object" && value !== null && !Array.isArray(value);
5049
5050
  }
5050
5051
  /** A trimmed, non-empty string, or nothing. */
@@ -5146,7 +5147,7 @@ function decodeJwtClaims$1(token) {
5146
5147
  if (parts.length !== 3) return void 0;
5147
5148
  try {
5148
5149
  const value = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
5149
- return isRecord$67(value) ? value : void 0;
5150
+ return isRecord$66(value) ? value : void 0;
5150
5151
  } catch {
5151
5152
  return;
5152
5153
  }
@@ -5191,10 +5192,10 @@ function expiryFromAccessToken(accessToken) {
5191
5192
  * the file carries cannot reach the result.
5192
5193
  */
5193
5194
  function parseAdoptedCodexCredential(value, sourcePath) {
5194
- if (!isRecord$67(value)) return void 0;
5195
+ if (!isRecord$66(value)) return void 0;
5195
5196
  if (value.auth_mode !== "chatgpt") return void 0;
5196
5197
  const tokens = value[CODEX_CLI_TOKENS_KEY];
5197
- if (!isRecord$67(tokens)) return void 0;
5198
+ if (!isRecord$66(tokens)) return void 0;
5198
5199
  const accessToken = nonEmptyString$10(tokens.access_token);
5199
5200
  if (accessToken === void 0) return void 0;
5200
5201
  const refreshToken = nonEmptyString$10(tokens.refresh_token);
@@ -5213,7 +5214,7 @@ function parseAdoptedCodexCredential(value, sourcePath) {
5213
5214
  const email = claims === void 0 ? void 0 : nonEmptyString$10(claims.email);
5214
5215
  if (email !== void 0) credentials.email = email;
5215
5216
  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;
5217
+ const planType = isRecord$66(authClaims) ? nonEmptyString$10(authClaims.chatgpt_plan_type) : void 0;
5217
5218
  if (planType !== void 0) credentials.planType = planType;
5218
5219
  }
5219
5220
  const accountId = nonEmptyString$10(tokens.account_id);
@@ -5999,114 +6000,6 @@ function webServerOrigin(server) {
5999
6000
  return `http://${server.host === "0.0.0.0" ? "127.0.0.1" : server.host}:${server.port}`;
6000
6001
  }
6001
6002
  //#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
6003
  //#region src/host/codex-images.ts
6111
6004
  /**
6112
6005
  * Output canvases the Codex image endpoint accepts in `size`.
@@ -20079,6 +19972,21 @@ const COMMAND_CODE_MODELS = [
20079
19972
  maxTokens: 256e3,
20080
19973
  hidden: false
20081
19974
  },
19975
+ {
19976
+ id: "stealth/crouton-alpha",
19977
+ name: "Crouton Alpha",
19978
+ inputModalities: ["text", "image"],
19979
+ reasoningEfforts: [
19980
+ "low",
19981
+ "medium",
19982
+ "high",
19983
+ "xhigh",
19984
+ "max"
19985
+ ],
19986
+ contextWindow: 1048576,
19987
+ maxTokens: 943718,
19988
+ hidden: false
19989
+ },
20082
19990
  {
20083
19991
  id: "reflection/beam:free",
20084
19992
  name: "Beam 1.0",
@@ -20096,7 +20004,7 @@ const COMMAND_CODE_MODELS = [
20096
20004
  },
20097
20005
  {
20098
20006
  id: "poolside/laguna-s-2.1-free",
20099
- name: "Laguna S 2.1",
20007
+ name: "Laguna S 2.1 (Free)",
20100
20008
  inputModalities: ["text"],
20101
20009
  reasoningEfforts: [],
20102
20010
  contextWindow: 256e3,
@@ -20125,6 +20033,32 @@ const COMMAND_CODE_MODELS = [
20125
20033
  maxTokens: 32768,
20126
20034
  hidden: false
20127
20035
  },
20036
+ {
20037
+ id: "inclusionai/ling-3.1-flash-1019",
20038
+ name: "Ling 3.1 Flash 1019",
20039
+ inputModalities: ["text"],
20040
+ reasoningEfforts: [
20041
+ "low",
20042
+ "medium",
20043
+ "high"
20044
+ ],
20045
+ contextWindow: 1e6,
20046
+ maxTokens: 262144,
20047
+ hidden: false
20048
+ },
20049
+ {
20050
+ id: "inclusionai/ling-3.1-flash-1019:free",
20051
+ name: "Ling 3.1 Flash 1019",
20052
+ inputModalities: ["text"],
20053
+ reasoningEfforts: [
20054
+ "low",
20055
+ "medium",
20056
+ "high"
20057
+ ],
20058
+ contextWindow: 1e6,
20059
+ maxTokens: 262144,
20060
+ hidden: true
20061
+ },
20128
20062
  {
20129
20063
  id: "meta/muse-spark-1.1",
20130
20064
  name: "Muse Spark 1.1",
@@ -20286,42 +20220,37 @@ const COMMAND_CODE_MODELS = [
20286
20220
  function commandCodeModelDef(modelId) {
20287
20221
  return COMMAND_CODE_MODELS.find((model) => model.id === modelId);
20288
20222
  }
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
- ];
20223
+ /** Default-selection deltas recognized by `resolveEnabledModelIds` in `./routes.ts`. */
20224
+ const SHIPPED_DEFAULT_MIGRATIONS = [{
20225
+ version: "1.88.4",
20226
+ added: ["stealth/crouton-alpha", "inclusionai/ling-3.1-flash-1019"],
20227
+ retired: []
20228
+ }, {
20229
+ version: "1.88.2",
20230
+ added: [
20231
+ "claude-haiku-5-5",
20232
+ "gpt-6.1-terra",
20233
+ "moonshotai/Kimi-K3.1-Pro",
20234
+ "minimax/minimax-m3.1-flash",
20235
+ "ornith/ornith-1.5-122b",
20236
+ "Qwen/Qwen3.8-Max-Prime",
20237
+ "google/gemini-4-argon",
20238
+ "nvidia/nemotron-3.5-super-vl",
20239
+ "stealth/glyph-cluster:free",
20240
+ "reflection/beam:free",
20241
+ "inclusionai/ling-3.1-flash",
20242
+ "meta/muse-spark-open",
20243
+ "meta/muse-wonder-1.0",
20244
+ "mistral/mistral-large-4"
20245
+ ],
20246
+ retired: [
20247
+ "xiaomi/mimo-v2.5-pro",
20248
+ "xiaomi/mimo-v2.5",
20249
+ "stealth/space-bunny-alpha",
20250
+ "inclusionai/ling-3.0-flash-sante:free",
20251
+ "inclusionai/ling-3.1-flash:free"
20252
+ ]
20253
+ }];
20325
20254
  //#endregion
20326
20255
  //#region src/host/command-code/types.ts
20327
20256
  const PROVIDER_ID$11 = "command-code";
@@ -24692,16 +24621,20 @@ function resolveEnabledModelIds$5(stored, catalog, enabled = true) {
24692
24621
  /**
24693
24622
  * Whether a stored selection still is a shipped default.
24694
24623
  *
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.
24624
+ * Walk the recorded catalog refreshes newest-first. Each step reconstructs one
24625
+ * earlier default, so users who never edited either the immediate previous
24626
+ * release or an older recorded release still receive the current catalog.
24698
24627
  */
24699
24628
  function isUneditedSelection(stored, shipped) {
24700
24629
  if (stored.length === 0) return false;
24701
24630
  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);
24631
+ let previous = new Set(shipped);
24632
+ for (const migration of SHIPPED_DEFAULT_MIGRATIONS) {
24633
+ previous = new Set([...previous].filter((id) => !migration.added.includes(id)));
24634
+ for (const id of migration.retired) previous.add(id);
24635
+ if (sameSelection(stored, previous)) return true;
24636
+ }
24637
+ return false;
24705
24638
  }
24706
24639
  /** Whether two id lists name exactly the same models, in any order. */
24707
24640
  function sameSelection(stored, other) {
@@ -92408,6 +92341,7 @@ const RENAMED_PACKAGES = /* @__PURE__ */ new Map([["@deepseek-ai/dsh-workflow-wo
92408
92341
  */
92409
92342
  const OPTIONALLY_ABSENT_PACKAGES = [
92410
92343
  "@deepseek-ai/dsh-tool-present",
92344
+ "@deepseek-ai/dsh-tool-subagent-control/list-agents",
92411
92345
  "@eddyskywalker/dsh-chatgpt-subscription/plan-mode-pipeline",
92412
92346
  "@eddyskywalker/dsh-chatgpt-subscription/plan-mode",
92413
92347
  "@eddyskywalker/dsh-chatgpt-subscription/tool-result-pruner"
@@ -92470,12 +92404,20 @@ function candidatePackageRoots(entryScript = process.argv[1]) {
92470
92404
  } catch {}
92471
92405
  return [...roots];
92472
92406
  }
92407
+ /** The package-name portion of a bare package or package-subpath specifier. */
92408
+ function packageNameOf(specifier) {
92409
+ return specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
92410
+ }
92473
92411
  /**
92474
92412
  * Whether a package or exported subpath is installed in any candidate node_modules tree.
92475
92413
  * A subpath needs its own exact `exports` key; the package root alone is not enough.
92414
+ *
92415
+ * The nearest manifest on each Node resolution chain is authoritative. Looking
92416
+ * farther out after that manifest rejects a subpath would let an unrelated old
92417
+ * installation resurrect an export the running package deliberately removed.
92476
92418
  */
92477
92419
  function packageInstalled(specifier, roots = candidatePackageRoots()) {
92478
- const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
92420
+ const pkg = packageNameOf(specifier);
92479
92421
  const subpath = specifier.slice(pkg.length);
92480
92422
  for (const root of roots) {
92481
92423
  let dir = root;
@@ -92485,9 +92427,9 @@ function packageInstalled(specifier, roots = candidatePackageRoots()) {
92485
92427
  if (subpath === "") return true;
92486
92428
  try {
92487
92429
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
92488
- if (manifest.exports !== void 0 && Object.prototype.hasOwnProperty.call(manifest.exports, `.${subpath}`)) return true;
92430
+ return manifest.exports !== void 0 && Object.prototype.hasOwnProperty.call(manifest.exports, `.${subpath}`);
92489
92431
  } catch {
92490
- continue;
92432
+ return false;
92491
92433
  }
92492
92434
  }
92493
92435
  const parent = dirname(dir);
@@ -92498,20 +92440,72 @@ function packageInstalled(specifier, roots = candidatePackageRoots()) {
92498
92440
  return false;
92499
92441
  }
92500
92442
  /**
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.
92443
+ * Packages the preset intentionally loads from the plugin's own dependency
92444
+ * closure rather than the harness installation. Every other harness row must
92445
+ * resolve where the registry mounts it, not merely beside this plugin.
92446
+ */
92447
+ const PLUGIN_RESOLVED_PACKAGES = ["@eddyskywalker/dsh-chatgpt-subscription", "@deepseek-ai/dsh-tool-executor-native"];
92448
+ /** Whether `candidate` is `root` itself or one of its descendants. */
92449
+ function pathInside(root, candidate) {
92450
+ const rel = relative(root, candidate);
92451
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
92452
+ }
92453
+ /**
92454
+ * Whether a plugin-side resolution points into the package the harness's own
92455
+ * entry script reaches. Walking ancestors mirrors Node's node_modules lookup;
92456
+ * the CLI commonly sits in `bin/`, beside rather than inside node_modules.
92457
+ */
92458
+ function resolvedFromHarness(resolved, entryDir, specifier) {
92459
+ if (typeof resolved !== "string") return false;
92460
+ let path;
92461
+ try {
92462
+ path = fileURLToPath(resolved);
92463
+ } catch {
92464
+ return false;
92465
+ }
92466
+ const pkg = packageNameOf(specifier);
92467
+ let dir = entryDir;
92468
+ for (;;) {
92469
+ if (pathInside(join(dir, "node_modules", pkg), path)) return true;
92470
+ const parent = dirname(dir);
92471
+ if (parent === dir) return false;
92472
+ dir = parent;
92473
+ }
92474
+ }
92475
+ /**
92476
+ * Resolve against the installation that will actually mount the preset.
92477
+ *
92478
+ * `import.meta.resolve` is based at this plugin, while the harness mounts
92479
+ * runtime-declared preset rows from its own context. A successful plugin-side
92480
+ * resolution is therefore not sufficient for a harness package: an old copy in
92481
+ * the profile tree must not override the running harness's missing export.
92482
+ * Plugin-owned rows and the plugin-borne native executor are the deliberate
92483
+ * exceptions. With no harness entry, retain the legacy local probe behavior.
92484
+ */
92485
+ function resolvesFromInstallation(specifier, entryScript, resolveFromPlugin) {
92486
+ const entryDir = harnessEntryDir(entryScript);
92487
+ if (entryDir !== void 0 && packageInstalled(specifier, [entryDir])) return true;
92488
+ let resolved;
92489
+ let didResolve = false;
92490
+ if (resolveFromPlugin !== void 0) try {
92491
+ resolved = resolveFromPlugin(specifier);
92492
+ didResolve = true;
92493
+ } catch {}
92494
+ if (entryDir === void 0) {
92495
+ if (didResolve) return true;
92496
+ return packageInstalled(specifier, candidatePackageRoots(entryScript));
92497
+ }
92498
+ if (didResolve && PLUGIN_RESOLVED_PACKAGES.includes(packageNameOf(specifier))) return true;
92499
+ return didResolve && resolvedFromHarness(resolved, entryDir, specifier);
92500
+ }
92501
+ /**
92502
+ * Whether a bare specifier resolves in the installation that mounts presets.
92505
92503
  * @param specifier - bare package specifier.
92506
92504
  * @returns whether the specifier resolves.
92507
92505
  */
92508
92506
  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);
92507
+ const resolveFromPlugin = import.meta.resolve;
92508
+ return resolvesFromInstallation(specifier, process.argv[1], resolveFromPlugin);
92515
92509
  }
92516
92510
  /**
92517
92511
  * Point every renamed package name at the spelling this installation ships.
@@ -92900,8 +92894,8 @@ const DISPATCH_PRESET = {
92900
92894
 
92901
92895
  ## R0.5 · Compatibility Gate (Once per New Session, Before R0)
92902
92896
  A legacy Agent Teams runtime (pre-#7519) registers spawn_teammate and replaces send_message / list_agents / interrupt_agent with team-scoped variants, leaving spawned subagents unreachable. The current runtime only adds a shared task board (team_task_create and its list/get/update siblings) and reuses this mode's tools.
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.
92897
+ Criterion: whether spawn_teammate exists (check via code_run). Never gate on wait_agent, send_message, list_agents, or interrupt_agent: this mode mounts those itself.
92898
+ - Absent -> Write "Orchestration ready: subagent" and proceed to R0. The board tools stay declared whether or not the board is on - frozen declaration, not availability - so confirm with team_task_list before relying on it. When the board is on it is bookkeeping, not a second dispatcher: the dispatch ledger stays the dispatch authority and R5 briefs stay the task contract; publish cross-agent dependencies (blocked_by) and write scopes on one team task per brief, claimed and completed by the child that owns it; never start work because a task is ready, and never reassign a task to skip R6.
92905
92899
  - Present -> This session runs the legacy runtime and is **not orchestratable**. In order, and nothing else:
92906
92900
  1. State in one sentence that the legacy runtime shadows the coordination tools and this mode has no degraded path.
92907
92901
  2. Call ask_user_question once (see R-W) offering: disable the legacy runtime, restart Host, **start a new session**; switch preset; or execute this task directly.
@@ -92931,7 +92925,7 @@ L0/L1: execute directly (or 1 subagent for L1). L2: R-G, R9, and R6 are mandator
92931
92925
  - [Media generation] Generate/revise/retry images directly; delegate complex creative work, never a bare call.
92932
92926
 
92933
92927
  ## 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:
92928
+ 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
92929
  - 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
92930
  - [State: Listed] Output ends with "Authorized child routes for this Session": enforcement is active.
92937
92931
  - 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 +92941,7 @@ A subagent has full authority to plan, execute, and self-verify within its assig
92947
92941
  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
92942
  - [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
92943
  - [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.
92944
+ - 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
92945
 
92952
92946
  ## R6 · Review & Acceptance
92953
92947
  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.
@@ -0,0 +1,110 @@
1
+ import { LlmError as LlmError$1 } from "@deepseek-ai/dsh-llm";
2
+ //#region src/host/common/llm-compat.ts
3
+ /** This package's kind in the harness message-source map. */
4
+ const PLUGIN_MESSAGE_SOURCE_KIND = "dsh-chatgpt-subscription";
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ /** Read a message body's blocks, keeping every entry that carries a block tag. */
9
+ function readBlocks(raw) {
10
+ if (!Array.isArray(raw)) return [];
11
+ return raw.filter((block) => isRecord(block) && typeof block.type === "string");
12
+ }
13
+ /**
14
+ * The first value that is a non-empty string.
15
+ *
16
+ * Used for fields the two generations spell in different places: a value that
17
+ * is present but empty must not shadow a real one further down the chain, which
18
+ * is what `??` alone does.
19
+ */
20
+ function firstNonEmptyString(...values) {
21
+ for (const value of values) if (typeof value === "string" && value !== "") return value;
22
+ return "";
23
+ }
24
+ /**
25
+ * The result block a `role: 'tool'` message already carries inside its body.
26
+ *
27
+ * Some harness generations deliver the call id inside the result block rather
28
+ * than on the message, and one shape carries both: `role: 'tool'` with a
29
+ * `tool-result` block holding the id. Reading only the message-level fields
30
+ * leaves {@link toolCallIdOf} nothing to find.
31
+ */
32
+ function embeddedToolResult(content) {
33
+ if (content.length !== 1) return void 0;
34
+ const [block] = content;
35
+ return block?.type === "tool-result" ? block : void 0;
36
+ }
37
+ /**
38
+ * The call id one `role: 'tool'` message associates with its `tool-call`.
39
+ *
40
+ * The id is the only thing tying a result to the call it answers: a mapper that
41
+ * sends it empty makes every result an orphan and tells the repair pass that no
42
+ * call was answered, which some gateways reject outright (WorkBuddy answers
43
+ * `11148`, "tool calls and tool results do not match"). It therefore has to be
44
+ * read from every place the harness generations put it — the message, the
45
+ * message's source, and the result block inside the body — and an empty string
46
+ * must not shadow a later, real one.
47
+ */
48
+ function toolCallIdOf(raw, source, embedded) {
49
+ return firstNonEmptyString(raw.toolCallId, raw.callId, source?.callId, embedded?.toolCallId);
50
+ }
51
+ /**
52
+ * Normalize one harness message list into {@link Message}.
53
+ *
54
+ * A 0.1.7 `role: 'tool'` message is re-wrapped as the user-role message holding
55
+ * one {@link ToolResultBlock} that the mappers expect, keeping the tool
56
+ * provenance (`kind: 'tool'`) they use to tell a real tool result from a user
57
+ * turn. A message that already carries that block keeps its own body instead of
58
+ * nesting a second `tool-result` around it. Everything a newer harness adds
59
+ * beside that keeps its own shape.
60
+ * @param messages - Messages straight off the request options, in either generation's shape.
61
+ * @returns The same conversation in the pre-0.1.7 vocabulary.
62
+ */
63
+ function normalizeMessages(messages) {
64
+ const normalized = [];
65
+ for (const raw of messages ?? []) {
66
+ if (!isRecord(raw)) continue;
67
+ const content = readBlocks(raw.content);
68
+ if (raw.role === "tool") {
69
+ const source = isRecord(raw.source) ? raw.source : void 0;
70
+ const embedded = embeddedToolResult(content);
71
+ const callId = toolCallIdOf(raw, source, embedded);
72
+ const isError = raw.isError === true || embedded?.isError === true;
73
+ normalized.push({
74
+ id: raw.id,
75
+ role: "user",
76
+ content: [{
77
+ type: "tool-result",
78
+ toolCallId: callId,
79
+ content: embedded === void 0 ? content : embedded.content,
80
+ ...isError ? { isError: true } : {}
81
+ }],
82
+ source: {
83
+ kind: "tool",
84
+ callId
85
+ }
86
+ });
87
+ continue;
88
+ }
89
+ normalized.push({
90
+ id: raw.id,
91
+ role: typeof raw.role === "string" ? raw.role : "user",
92
+ content,
93
+ ...isRecord(raw.source) ? { source: raw.source } : {}
94
+ });
95
+ }
96
+ return normalized;
97
+ }
98
+ /**
99
+ * Normalize request options for the mappers.
100
+ * @param options - Request options the harness handed the adapter.
101
+ * @returns The same options with {@link Message} history.
102
+ */
103
+ function normalizeGenerateOptions(options) {
104
+ return {
105
+ ...options,
106
+ messages: normalizeMessages(options.messages)
107
+ };
108
+ }
109
+ //#endregion
110
+ export { PLUGIN_MESSAGE_SOURCE_KIND as n, normalizeGenerateOptions as r, LlmError$1 as t };