@eddyskywalker/dsh-chatgpt-subscription 0.56.0 → 0.58.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 (41) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +3 -3
  3. package/lib/client.js +56 -0
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +964 -491
  6. package/lib/tool-result-pruner.js +1 -1
  7. package/lib/types/client/locales-ru/progress-updates.d.ts.map +1 -1
  8. package/lib/types/client/minimax-code/commands-contract.d.ts +1 -1
  9. package/lib/types/client/progress-updates/locales.d.ts +8 -2
  10. package/lib/types/client/progress-updates/locales.d.ts.map +1 -1
  11. package/lib/types/client/progress-updates/settings-binding.d.ts +8 -1
  12. package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
  13. package/lib/types/client/progress-updates/settings-section.d.ts +2 -1
  14. package/lib/types/client/progress-updates/settings-section.d.ts.map +1 -1
  15. package/lib/types/compat.d.ts +1 -1
  16. package/lib/types/entry-tool-result-pruner.d.ts +1 -1
  17. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  18. package/lib/types/host/codex-adopt.d.ts.map +1 -1
  19. package/lib/types/host/codex-catalog.d.ts +5 -3
  20. package/lib/types/host/codex-catalog.d.ts.map +1 -1
  21. package/lib/types/host/codex-identity.d.ts +130 -0
  22. package/lib/types/host/codex-identity.d.ts.map +1 -0
  23. package/lib/types/host/kimi-code/repeat-breaker.d.ts +1 -1
  24. package/lib/types/host/oauth-service.d.ts +0 -16
  25. package/lib/types/host/oauth-service.d.ts.map +1 -1
  26. package/lib/types/host/team-policy-cache-guard.d.ts +44 -0
  27. package/lib/types/host/team-policy-cache-guard.d.ts.map +1 -0
  28. package/lib/types/host/token-store.d.ts +9 -0
  29. package/lib/types/host/token-store.d.ts.map +1 -1
  30. package/lib/types/host/usage-service.d.ts.map +1 -1
  31. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  32. package/lib/types/host/workbuddy/client.d.ts.map +1 -1
  33. package/lib/types/host/workbuddy/model-catalog.d.ts +56 -44
  34. package/lib/types/host/workbuddy/model-catalog.d.ts.map +1 -1
  35. package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
  36. package/lib/types/index.d.ts +14 -0
  37. package/lib/types/index.d.ts.map +1 -1
  38. package/lib/types/shared/workbuddy-contracts.d.ts +8 -8
  39. package/package.json +49 -46
  40. package/presets/dispatch/README.md +26 -6
  41. package/presets/dispatch/agent.cordis.yml +8 -8
package/lib/index.js CHANGED
@@ -807,7 +807,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
807
807
  * `test/responses-client.test.ts` asserts the two agree, so a release that
808
808
  * forgets this constant fails the suite instead of shipping quietly.
809
809
  */
810
- const PLUGIN_VERSION = "0.56.0";
810
+ const PLUGIN_VERSION = "0.58.0";
811
811
  const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
812
812
  const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
813
813
  const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
@@ -1345,6 +1345,144 @@ async function rehydrateCatalogCache(name, parseModels, accept, currentFetchedAt
1345
1345
  return snapshot.fetchedAt;
1346
1346
  }
1347
1347
  //#endregion
1348
+ //#region src/host/codex-identity.ts
1349
+ /**
1350
+ * Who one ChatGPT credential belongs to, and what makes two sign-ins the same
1351
+ * account.
1352
+ *
1353
+ * TWO IDENTITIES, AND THEY ARE NOT THE SAME THING.
1354
+ *
1355
+ * - The WORKSPACE identity is `chatgpt_account_id`, carried as
1356
+ * `credentials.accountId` and sent upstream as `chatgpt-account-id`. It
1357
+ * decides which workspace a request is billed to and what its listing serves,
1358
+ * and every member of that workspace shares it.
1359
+ * - The LOCAL LOGIN identity is (workspace, user): that same workspace id plus a
1360
+ * stable id for the person who signed in. Two members of one workspace are two
1361
+ * accounts here — separate tokens, separate rows, separately selectable — and
1362
+ * re-authorizing the same person updates the row that person already owns.
1363
+ *
1364
+ * Keying the account pool on the workspace alone is the bug this module exists to
1365
+ * prevent. Adding a second member of a workspace replaced the first: the card read
1366
+ * "1 account signed in", the earlier sign-in was gone from the list, and nothing in
1367
+ * the credential was wrong — the pool simply believed both sign-ins were one
1368
+ * account, because that is what the key said.
1369
+ *
1370
+ * The user id is read from the tokens themselves, most explicit claim first:
1371
+ * `chatgpt_user_id` (the field upstream states for exactly this purpose), then
1372
+ * the OIDC `sub` that an id token always carries, then the address. Only when none
1373
+ * of the three is present does the workspace stand in on its own, because nothing
1374
+ * better exists then and merging is what every row written before this module
1375
+ * already did. The workspace id alone is NEVER treated as a user identity while
1376
+ * some user claim is available.
1377
+ */
1378
+ /** Namespace of the ChatGPT auth claims inside an id or access token. */
1379
+ const CODEX_AUTH_CLAIMS_KEY = "https://api.openai.com/auth";
1380
+ /**
1381
+ * The claims of a JWT, decoded in process, or nothing.
1382
+ *
1383
+ * The payload is the second base64url segment; the signature is never touched and
1384
+ * the result is used only for the non-secret facts above. Never sent anywhere: a
1385
+ * token handed to a decoder service is a token handed away.
1386
+ *
1387
+ * No verification is claimed and none is possible here. What this does instead is
1388
+ * refuse to invent: a token that does not decode is treated as stating nothing,
1389
+ * which leaves the caller on its own fallback rather than on a guessed identity.
1390
+ */
1391
+ function parseJwtClaims(token) {
1392
+ if (token === void 0) return void 0;
1393
+ const parts = token.split(".");
1394
+ if (parts.length !== 3) return void 0;
1395
+ try {
1396
+ const value = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1397
+ return typeof value === "object" && value !== null ? value : void 0;
1398
+ } catch {
1399
+ return;
1400
+ }
1401
+ }
1402
+ /**
1403
+ * What the tokens state about their owner.
1404
+ *
1405
+ * The id token is read before the access token, because it is the document that
1406
+ * describes the sign-in; the access token is the fallback for credentials stored
1407
+ * without one. First statement wins, so a value the id token states is never
1408
+ * overwritten by the access token's copy of it.
1409
+ */
1410
+ function extractIdentity(credentials) {
1411
+ const result = {};
1412
+ let subject;
1413
+ for (const token of [credentials.idToken, credentials.accessToken]) {
1414
+ const claims = parseJwtClaims(token);
1415
+ if (claims === void 0) continue;
1416
+ const nested = claims[CODEX_AUTH_CLAIMS_KEY];
1417
+ result.email ??= stringClaim(claims.email);
1418
+ result.planType ??= stringClaim(claims.chatgpt_plan_type) ?? stringClaim(nested?.chatgpt_plan_type);
1419
+ result.userId ??= stringClaim(claims.chatgpt_user_id) ?? stringClaim(nested?.chatgpt_user_id);
1420
+ result.accountId ??= stringClaim(claims.chatgpt_account_id) ?? stringClaim(nested?.chatgpt_account_id) ?? stringClaim(claims.organizations?.[0]?.id) ?? stringClaim(nested?.organizations?.[0]?.id);
1421
+ subject ??= stringClaim(claims.sub);
1422
+ }
1423
+ result.userId ??= subject;
1424
+ return result;
1425
+ }
1426
+ /**
1427
+ * The stable id of the human behind a credential, or nothing.
1428
+ *
1429
+ * Explicit claim first, then `sub`, and deliberately NOT the email: an address is
1430
+ * the caller's fallback (see {@link codexAccountIdentity}), while this function
1431
+ * answers the narrower question "does this credential name its user outright?".
1432
+ * A persisted `userId` wins over anything re-derived from the tokens, so an opaque
1433
+ * access token handed back by a rotation cannot make an account look like a
1434
+ * different one.
1435
+ */
1436
+ function codexUserId(credentials) {
1437
+ const stored = nonEmpty$3(credentials.userId);
1438
+ if (stored !== void 0) return stored;
1439
+ return extractIdentity(credentials).userId;
1440
+ }
1441
+ /**
1442
+ * One opaque key per local login: the pool's dedupe key, and the scope of every
1443
+ * per-account cache this line keeps.
1444
+ *
1445
+ * A digest of an EXPLICIT serialization rather than a readable join, for three
1446
+ * reasons that are really one: the string is a label, not data.
1447
+ *
1448
+ * - A separator can be forged. `workspace:user` joins two values that may
1449
+ * themselves contain the separator, so `a` + `b:c` and `a:b` + `c` would have to
1450
+ * be the same key. A JSON array has no such ambiguity: quotes and escapes are
1451
+ * the boundary, and they cannot be re-cut by a value.
1452
+ * - The key becomes a FILE NAME. `catalogSnapshotName` folds everything outside
1453
+ * `[a-z0-9]` to `_`, so a readable key would collide with its neighbours after
1454
+ * folding *and* leave an email address on disk. Hex survives the fold unchanged.
1455
+ * - It is compared for equality only, so nothing is lost by making it opaque.
1456
+ *
1457
+ * `v1` is part of the serialized tuple, not decoration: a future change to the
1458
+ * composition has to produce different keys, and a version tag is what lets that
1459
+ * change be told apart from a corrupted row instead of silently merging two
1460
+ * accounts that the new rule would separate.
1461
+ *
1462
+ * Returns `undefined` when the credential states nothing at all about who it is.
1463
+ * The pool then mints its own `acc_<random>` and ADDS the row, which is the honest
1464
+ * answer: a credential nothing identifies cannot be recognized again, and merging
1465
+ * it with an unrelated one would be worse than a duplicate.
1466
+ */
1467
+ function codexAccountIdentity(credentials) {
1468
+ const workspace = nonEmpty$3(credentials.accountId);
1469
+ const user = codexUserId(credentials) ?? nonEmpty$3(credentials.email);
1470
+ if (workspace === void 0 && user === void 0) return void 0;
1471
+ const serialized = JSON.stringify([
1472
+ "v1",
1473
+ workspace ?? "",
1474
+ user ?? ""
1475
+ ]);
1476
+ return "v1-" + createHash("sha256").update(serialized).digest("hex").slice(0, 32);
1477
+ }
1478
+ function stringClaim(value) {
1479
+ return typeof value === "string" && value !== "" ? value : void 0;
1480
+ }
1481
+ function nonEmpty$3(value) {
1482
+ const trimmed = value?.trim();
1483
+ return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
1484
+ }
1485
+ //#endregion
1348
1486
  //#region src/host/wire-auth.ts
1349
1487
  /**
1350
1488
  * Turn-state header the Codex backend hands back on a responses response.
@@ -1441,18 +1579,36 @@ const CATALOG_TTL_MS = 15 * 6e4;
1441
1579
  * scope a rehydrated listing from one account would answer another's picker.
1442
1580
  */
1443
1581
  const SNAPSHOT_SCOPE = "chatgpt-codex";
1444
- let catalogCache$2 = null;
1582
+ /**
1583
+ * How many accounts' listings are held at once.
1584
+ *
1585
+ * One slot was enough while a workspace id was mistaken for an account, because
1586
+ * every member of a workspace then shared one. It is not enough now: a pool
1587
+ * rotates between accounts, and a single slot would have each rotation evict the
1588
+ * other account's listing and re-fetch it, which turns the 15-minute TTL into
1589
+ * "no cache at all" for exactly the multi-account setup this line supports. The
1590
+ * bound is the pool's own order of magnitude, so a real composition always fits
1591
+ * and a pathological one only loses the coldest entry.
1592
+ */
1593
+ const CATALOG_CACHE_ACCOUNTS = 8;
1594
+ /** Listings held per login identity; see {@link CATALOG_CACHE_ACCOUNTS}. */
1595
+ const catalogCache$2 = /* @__PURE__ */ new Map();
1596
+ /** The entry {@link getCachedCatalog} answers with: the one a load last wrote. */
1597
+ let latestKey = null;
1445
1598
  const catalogInFlight$6 = /* @__PURE__ */ new Map();
1446
- let snapshotRehydrated = null;
1599
+ /** Snapshots already read back from disk, per identity. */
1600
+ const snapshotRehydrated = /* @__PURE__ */ new Map();
1447
1601
  /**
1448
1602
  * The listing currently held, or undefined before the first load.
1449
1603
  *
1450
- * Callers that need a catalog with no credential read the shipped table
1451
- * directly; returning it here under a different entry type would force every
1452
- * one of them to convert it.
1604
+ * "Currently held" is the entry a load wrote last: a picker asks what the
1605
+ * account serving it can call, and that is the account whose listing was fetched
1606
+ * most recently. Callers that need a catalog with no credential read the shipped
1607
+ * table directly; returning it here under a different entry type would force
1608
+ * every one of them to convert it.
1453
1609
  */
1454
1610
  function getCachedCatalog$3() {
1455
- return catalogCache$2?.models;
1611
+ return latestKey === null ? void 0 : catalogCache$2.get(latestKey)?.models;
1456
1612
  }
1457
1613
  /**
1458
1614
  * Read the models this subscription can call.
@@ -1470,8 +1626,11 @@ function getCachedCatalog$3() {
1470
1626
  function loadCodexCatalog(credentials, options = {}) {
1471
1627
  const key = accountKeyFor$1(credentials);
1472
1628
  if (options.force !== true) {
1473
- const cached = catalogCache$2;
1474
- if (cached !== null && cached.key === key && Date.now() - cached.at < CATALOG_TTL_MS) return Promise.resolve(cached.models);
1629
+ const cached = catalogCache$2.get(key);
1630
+ if (cached !== void 0 && Date.now() - cached.at < CATALOG_TTL_MS) {
1631
+ latestKey = key;
1632
+ return Promise.resolve(cached.models);
1633
+ }
1475
1634
  }
1476
1635
  const pending = catalogInFlight$6.get(key);
1477
1636
  if (pending !== void 0) return pending;
@@ -1488,10 +1647,17 @@ function loadCodexCatalog(credentials, options = {}) {
1488
1647
  async function performCatalogLoad$1(credentials, options) {
1489
1648
  const key = accountKeyFor$1(credentials);
1490
1649
  const fetchFn = options.fetchFn ?? fetch;
1491
- snapshotRehydrated ??= rehydrateSnapshot(key);
1492
- await snapshotRehydrated;
1493
- const cached = catalogCache$2;
1494
- if (cached !== null && cached.key === key && Date.now() - cached.at < CATALOG_TTL_MS) return cached.models;
1650
+ let rehydrated = snapshotRehydrated.get(key);
1651
+ if (rehydrated === void 0) {
1652
+ rehydrated = rehydrateSnapshot(key);
1653
+ snapshotRehydrated.set(key, rehydrated);
1654
+ }
1655
+ await rehydrated;
1656
+ const cached = catalogCache$2.get(key);
1657
+ if (cached !== void 0 && Date.now() - cached.at < CATALOG_TTL_MS) {
1658
+ latestKey = key;
1659
+ return cached.models;
1660
+ }
1495
1661
  let models;
1496
1662
  let live = false;
1497
1663
  try {
@@ -1510,18 +1676,34 @@ async function performCatalogLoad$1(credentials, options) {
1510
1676
  live = true;
1511
1677
  writeCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), [...models], Date.now());
1512
1678
  } catch {
1513
- models = getCachedCatalog$3() ?? [];
1679
+ models = catalogCache$2.get(key)?.models ?? [];
1514
1680
  live = false;
1515
1681
  }
1516
- catalogCache$2 = {
1682
+ remember(key, {
1517
1683
  at: Date.now(),
1518
1684
  models,
1519
1685
  key,
1520
1686
  live
1521
- };
1687
+ });
1522
1688
  return models;
1523
1689
  }
1524
1690
  /**
1691
+ * File one listing under its identity, keeping the map bounded.
1692
+ *
1693
+ * Eviction is oldest-fetch-first, which is the entry a rotation is least likely to
1694
+ * ask for next; the bound exists so a composition that signs in far more accounts
1695
+ * than the pool holds cannot grow this map without end.
1696
+ */
1697
+ function remember(key, entry) {
1698
+ catalogCache$2.set(key, entry);
1699
+ latestKey = key;
1700
+ while (catalogCache$2.size > CATALOG_CACHE_ACCOUNTS) {
1701
+ const oldest = [...catalogCache$2.entries()].filter(([candidate]) => candidate !== key).sort((left, right) => left[1].at - right[1].at)[0];
1702
+ if (oldest === void 0) break;
1703
+ catalogCache$2.delete(oldest[0]);
1704
+ }
1705
+ }
1706
+ /**
1525
1707
  * Seed the in-memory cache from the last persisted listing.
1526
1708
  *
1527
1709
  * Best effort by construction: a missing or malformed snapshot leaves the cache
@@ -1530,13 +1712,13 @@ async function performCatalogLoad$1(credentials, options) {
1530
1712
  async function rehydrateSnapshot(key) {
1531
1713
  const snapshot = await readCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), (value) => Array.isArray(value) ? value.flatMap((item) => parseListingEntry$1(item)) : void 0).catch(() => void 0);
1532
1714
  if (snapshot === void 0 || snapshot.models.length === 0) return;
1533
- if (catalogCache$2 !== null) return;
1534
- catalogCache$2 = {
1715
+ if (catalogCache$2.has(key)) return;
1716
+ remember(key, {
1535
1717
  at: snapshot.fetchedAt,
1536
1718
  models: snapshot.models,
1537
1719
  key,
1538
1720
  live: true
1539
- };
1721
+ });
1540
1722
  }
1541
1723
  /**
1542
1724
  * Read the listing payload.
@@ -1605,12 +1787,19 @@ function parseEfforts(value) {
1605
1787
  /**
1606
1788
  * Cache identity for one credential.
1607
1789
  *
1608
- * The account id is the entitlement boundary: two accounts of the same user can
1609
- * see different listings, and a pool rotates between them, so the key has to
1610
- * follow the account rather than the person.
1790
+ * The listing is served per PLAN, and a plan belongs to the local login — the
1791
+ * workspace id alone is not an identity here either: two members of one workspace
1792
+ * can hold different plans, so filing both listings under the workspace would let
1793
+ * one member's picker answer with models the other member's plan does not serve.
1794
+ * The key is therefore the same composite identity the pool dedupes on, and its
1795
+ * digest form is what keeps the persisted snapshot's file name both distinct after
1796
+ * `catalogSnapshotName` folds it and free of an email address.
1797
+ *
1798
+ * `'default'` answers a credential that states nothing about itself, which is the
1799
+ * single pre-sign-in case this loader ever sees.
1611
1800
  */
1612
1801
  function accountKeyFor$1(credentials) {
1613
- return credentials.accountId || "default";
1802
+ return codexAccountIdentity(credentials) ?? "default";
1614
1803
  }
1615
1804
  function asRecord$20(value) {
1616
1805
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -1729,6 +1918,7 @@ function parseStoredCredentials(value) {
1729
1918
  expiresAt: record.expiresAt,
1730
1919
  idToken: optional("idToken"),
1731
1920
  accountId: optional("accountId"),
1921
+ userId: optional("userId"),
1732
1922
  email: optional("email"),
1733
1923
  planType: optional("planType")
1734
1924
  };
@@ -4498,17 +4688,6 @@ function buildAuthorizationUrl(verifier, state) {
4498
4688
  originator: OAUTH_ORIGINATOR
4499
4689
  }).toString()}`;
4500
4690
  }
4501
- function parseJwtClaims(token) {
4502
- if (token === void 0) return void 0;
4503
- const parts = token.split(".");
4504
- if (parts.length !== 3) return void 0;
4505
- try {
4506
- const value = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
4507
- return typeof value === "object" && value !== null ? value : void 0;
4508
- } catch {
4509
- return;
4510
- }
4511
- }
4512
4691
  function publicError(error, fallback = "internal") {
4513
4692
  if (error instanceof OAuthServiceError) return {
4514
4693
  code: error.code,
@@ -4535,28 +4714,25 @@ function credentialsFromTokenResponse(response, now, previous) {
4535
4714
  return {
4536
4715
  ...base,
4537
4716
  accountId: identity.accountId ?? previous?.accountId,
4717
+ userId: identity.userId ?? previous?.userId,
4538
4718
  email: identity.email ?? previous?.email,
4539
4719
  planType: identity.planType ?? previous?.planType
4540
4720
  };
4541
4721
  }
4542
- function extractIdentity(credentials) {
4543
- const result = {};
4544
- for (const token of [credentials.idToken, credentials.accessToken]) {
4545
- const claims = parseJwtClaims(token);
4546
- if (claims === void 0) continue;
4547
- const nested = claims["https://api.openai.com/auth"];
4548
- result.email ??= stringClaim(claims.email);
4549
- result.planType ??= stringClaim(claims.chatgpt_plan_type) ?? stringClaim(nested?.chatgpt_plan_type);
4550
- result.accountId ??= stringClaim(claims.chatgpt_account_id) ?? stringClaim(nested?.chatgpt_account_id) ?? stringClaim(claims.organizations?.[0]?.id) ?? stringClaim(nested?.organizations?.[0]?.id);
4551
- }
4552
- return result;
4553
- }
4554
- /** Identity one refresh is keyed by, so a rotation is never redeemed twice. */
4722
+ /**
4723
+ * Identity one refresh is keyed by, so a rotation is never redeemed twice.
4724
+ *
4725
+ * The SAME key the pool dedupes on, and deliberately so: it has to separate two
4726
+ * members of one workspace, because their grants are unrelated and sharing one
4727
+ * in-flight refresh between them would hand one account's rotated token to the
4728
+ * other. A workspace id alone cannot do that — it is what both members have in
4729
+ * common — so this is the composite local login identity, with the rotating
4730
+ * refresh token as the fallback for a credential that states nothing else. That
4731
+ * fallback is the conservative direction: a token that differs always gets its
4732
+ * own flight, and a flight nobody joined simply runs by itself.
4733
+ */
4555
4734
  function refreshKey(credentials) {
4556
- return credentials.accountId ?? credentials.email ?? credentials.refreshToken;
4557
- }
4558
- function stringClaim(value) {
4559
- return typeof value === "string" && value !== "" ? value : void 0;
4735
+ return codexAccountIdentity(credentials) ?? credentials.refreshToken;
4560
4736
  }
4561
4737
  function maskEmail(email) {
4562
4738
  if (email === void 0) return null;
@@ -4851,8 +5027,6 @@ const CODEX_CLI_CREDENTIAL_FILE = "auth.json";
4851
5027
  const CODEX_CLI_TOKENS_KEY = "tokens";
4852
5028
  /** Where an adopted snapshot came from, for the card and for the pool record. */
4853
5029
  const CODEX_CLI_CREDENTIAL_SOURCE = "codex";
4854
- /** Namespace the id token carries the ChatGPT account claims under. */
4855
- const CODEX_AUTH_CLAIMS_KEY = "https://api.openai.com/auth";
4856
5030
  /**
4857
5031
  * What the user is told when an adopted credential has aged out.
4858
5032
  *
@@ -5256,7 +5430,7 @@ var CodexAccountPool = class extends AccountPoolCore {
5256
5430
  poolFile: codexPoolPath(),
5257
5431
  keychainService: "dsh-chatgpt-subscription-pool",
5258
5432
  parsePoolData: parseCodexPoolData,
5259
- dedupeKey: (credentials) => credentials.accountId ?? credentials.email,
5433
+ dedupeKey: (credentials) => codexAccountIdentity(credentials),
5260
5434
  defaultAlias: (credentials, position) => credentials.email ?? `账号 ${position}`,
5261
5435
  createAccount: ({ id, alias, credentials, addedAt, isPrimary }) => {
5262
5436
  const adopted = isAdoptedCodexPoolCredential(credentials);
@@ -15123,8 +15297,17 @@ function readableLimitName(value) {
15123
15297
  function isWindow(value) {
15124
15298
  return value !== null;
15125
15299
  }
15300
+ /**
15301
+ * The identity a reading is filed under when no pool row names one.
15302
+ *
15303
+ * The composite local login identity, for the same reason the pool dedupes on it:
15304
+ * with several accounts in one composition, a workspace id alone would make two
15305
+ * members of that workspace share a slot — each showing the other's numbers. The
15306
+ * trailing fallbacks are for a credential that states nothing about itself, which
15307
+ * is the single-account case, where there is nobody to mix up.
15308
+ */
15126
15309
  function identityKey(credentials) {
15127
- return credentials.accountId ?? credentials.email ?? credentials.planType ?? "signed-in";
15310
+ return codexAccountIdentity(credentials) ?? credentials.planType ?? "signed-in";
15128
15311
  }
15129
15312
  //#endregion
15130
15313
  //#region src/host/routes.ts
@@ -86900,7 +87083,7 @@ function installToolResultPrunerGuard(ctx, options = {}) {
86900
87083
  return next === void 0 ? void 0 : next();
86901
87084
  };
86902
87085
  for (const event of GUARDED_EVENTS$1) {
86903
- const disposer = subscribe$3(host, event, handleAgentEvent);
87086
+ const disposer = subscribe$4(host, event, handleAgentEvent);
86904
87087
  if (disposer !== void 0) disposers.push(disposer);
86905
87088
  }
86906
87089
  scanAndWrap();
@@ -86930,7 +87113,7 @@ function installToolResultPrunerGuard(ctx, options = {}) {
86930
87113
  * @param listener - 回调。
86931
87114
  * @returns 移除监听器的 disposer;宿主不支持事件时为 `undefined`。
86932
87115
  */
86933
- function subscribe$3(host, event, listener) {
87116
+ function subscribe$4(host, event, listener) {
86934
87117
  const on = host?.on;
86935
87118
  if (typeof on !== "function") return void 0;
86936
87119
  for (const options of [
@@ -87107,7 +87290,7 @@ function markPatched$3(patched, holder, name) {
87107
87290
  //#endregion
87108
87291
  //#region src/host/plan-mode-cache-guard.ts
87109
87292
  /** 没有 scope 的装配(会话标题之类)用的桶键。 */
87110
- const GLOBAL_KEY$1 = Object.freeze({});
87293
+ const GLOBAL_KEY$2 = Object.freeze({});
87111
87294
  /**
87112
87295
  * 读一个可选服务。
87113
87296
  * @param ctx - 宿主上下文。
@@ -87128,8 +87311,8 @@ function serviceOf$1(ctx, service) {
87128
87311
  * @param value - scope / agent。
87129
87312
  * @returns WeakMap 键。
87130
87313
  */
87131
- function bucketOf(value) {
87132
- return typeof value === "object" && value !== null ? value : GLOBAL_KEY$1;
87314
+ function bucketOf$1(value) {
87315
+ return typeof value === "object" && value !== null ? value : GLOBAL_KEY$2;
87133
87316
  }
87134
87317
  /**
87135
87318
  * 订阅一个事件。
@@ -87138,7 +87321,7 @@ function bucketOf(value) {
87138
87321
  * @param listener - 监听器。
87139
87322
  * @returns 解绑函数或 undefined。
87140
87323
  */
87141
- function subscribe$2(ctx, event, listener) {
87324
+ function subscribe$3(ctx, event, listener) {
87142
87325
  const on = ctx?.on;
87143
87326
  if (typeof on !== "function") return void 0;
87144
87327
  try {
@@ -87191,7 +87374,7 @@ function installPlanModeCacheGuard(ctx) {
87191
87374
  }
87192
87375
  kept.push(section);
87193
87376
  }
87194
- const state = stateOf(bucketOf(context?.scope ?? context?.agent));
87377
+ const state = stateOf(bucketOf$1(context?.scope ?? context?.agent));
87195
87378
  if (policy !== void 0) if (policy.trim() === "") {
87196
87379
  state.pending = void 0;
87197
87380
  state.delivered = false;
@@ -87210,7 +87393,7 @@ function installPlanModeCacheGuard(ctx) {
87210
87393
  if (decision === void 0 || decision.kind === "reject") return decision;
87211
87394
  if (payload?.signal?.aborted === true) return decision;
87212
87395
  const agent = payload?.agent;
87213
- const key = bucketOf(agent ?? void 0);
87396
+ const key = bucketOf$1(agent ?? void 0);
87214
87397
  const state = states.get(key);
87215
87398
  if (state === void 0) return decision;
87216
87399
  if (state.delivered) if (planActiveOf(ctx, agent?.session) === false) state.delivered = false;
@@ -87225,8 +87408,8 @@ function installPlanModeCacheGuard(ctx) {
87225
87408
  messages: [...messages, createPlanNotice(text, void 0, "Plan mode guidance")]
87226
87409
  };
87227
87410
  };
87228
- const offAssemble = subscribe$2(ctx, "system-prompt/assemble", onAssemble);
87229
- const offPreStep = subscribe$2(ctx, "agent/pre-step", onPreStep);
87411
+ const offAssemble = subscribe$3(ctx, "system-prompt/assemble", onAssemble);
87412
+ const offPreStep = subscribe$3(ctx, "agent/pre-step", onPreStep);
87230
87413
  if (offAssemble !== void 0) restores.push(() => {
87231
87414
  offAssemble();
87232
87415
  });
@@ -87255,7 +87438,7 @@ function installPlanModeCacheGuard(ctx) {
87255
87438
  restoreMethod$2(target);
87256
87439
  });
87257
87440
  if (typeof tools.get === "function") try {
87258
- if (tools.get("exit_plan_mode") !== void 0) logWarn(ctx, "[dsh-chatgpt-subscription] plan-mode cache guard was installed after exit_plan_mode was already registered; this session keeps the conditional admission");
87441
+ 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");
87259
87442
  } catch {}
87260
87443
  };
87261
87444
  const tools = serviceOf$1(ctx, "tools");
@@ -87302,7 +87485,7 @@ function planActiveOf(ctx, session) {
87302
87485
  * @param ctx - 宿主上下文。
87303
87486
  * @param message - 正文。
87304
87487
  */
87305
- function logWarn(ctx, message) {
87488
+ function logWarn$1(ctx, message) {
87306
87489
  const logger = ctx?.logger;
87307
87490
  try {
87308
87491
  logger?.warn(message);
@@ -87415,7 +87598,7 @@ const DEFAULT_PTC_SDK_CACHE_PROVIDERS = Object.freeze(["kimi-code", "minimax-cod
87415
87598
  /** PTC 生成的 SDK 段名。 */
87416
87599
  const PTC_SDK_SECTION_NAME = "tools:sdk";
87417
87600
  /** 没有 scope 的装配(会话标题之类)用的桶键。 */
87418
- const GLOBAL_KEY = Object.freeze({});
87601
+ const GLOBAL_KEY$1 = Object.freeze({});
87419
87602
  /**
87420
87603
  * 读一个装配 scope 的 provider。
87421
87604
  *
@@ -87509,7 +87692,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
87509
87692
  * @param scope - 装配 scope。
87510
87693
  * @returns WeakMap 键。
87511
87694
  */
87512
- const bucketOf = (scope) => typeof scope === "object" && scope !== null ? scope : GLOBAL_KEY;
87695
+ const bucketOf = (scope) => typeof scope === "object" && scope !== null ? scope : GLOBAL_KEY$1;
87513
87696
  /**
87514
87697
  * 算未过滤工具集的名字指纹。
87515
87698
  *
@@ -87599,7 +87782,7 @@ function installPtcSdkCacheGuard(ctx, options = {}) {
87599
87782
  });
87600
87783
  return result;
87601
87784
  };
87602
- const offAssemble = subscribe$1(host, "system-prompt/assemble", onAssemble);
87785
+ const offAssemble = subscribe$2(host, "system-prompt/assemble", onAssemble);
87603
87786
  if (offAssemble !== void 0) disposers.push(offAssemble);
87604
87787
  const initialTools = serviceOf(host, "tools");
87605
87788
  if (initialTools !== void 0) {
@@ -87656,7 +87839,7 @@ function serviceOf(ctx, service) {
87656
87839
  * @param listener - 回调。
87657
87840
  * @returns 移除监听器的 disposer;宿主不支持事件时为 `undefined`。
87658
87841
  */
87659
- function subscribe$1(host, event, listener) {
87842
+ function subscribe$2(host, event, listener) {
87660
87843
  const on = host?.["on"];
87661
87844
  if (typeof on !== "function") return void 0;
87662
87845
  for (const options of [
@@ -87787,6 +87970,233 @@ function markPatched$1(patched, holder, name) {
87787
87970
  else names.add(name);
87788
87971
  }
87789
87972
  //#endregion
87973
+ //#region src/host/team-policy-cache-guard.ts
87974
+ /**
87975
+ * 「Agent Teams 策略段前缀缓存守卫」—— 把内置 `team:policy` 段从 system 里摘掉,
87976
+ * 改为切开关时**尾部投递**一次。
87977
+ *
87978
+ * ## 为什么需要它
87979
+ *
87980
+ * 内置 `@deepseek-ai/dsh-tool-agent-team` 注册一个段 `team:policy`,正文是
87981
+ * `enabled(agent) ? TEAM_POLICY : ''`:团队开关一开,system prompt 里凭空多出整段
87982
+ * 指引,从那里起的前缀全部作废。这与计划模式当初那处重建同构,本仓库已经用
87983
+ * 「摘段 + 尾部投递」把它压平过(见 `plan-mode-cache-guard.ts`),这里复用同一套
87984
+ * 做法,只是触发器从 `enter_plan_mode` 换成客户端远程调用 `agentTeams.setEnabled`
87985
+ * ——那一步只往会话里追加 `team/mode` 事件,段正文在**装配时**才翻面,所以守卫只要
87986
+ * 盯着装配结果就够,不需要订阅任何 team 服务。
87987
+ *
87988
+ * ## 三条动作
87989
+ *
87990
+ * 1. `system-prompt/assemble` 瀑布:把 `team:policy` 段从呈现里摘掉,并记下它的
87991
+ * 正文。system 段从此在开关前后逐字节恒定。
87992
+ * 2. `agent/pre-step`:把记下的正文作为**尾部用户消息**投一次(每次激活一次)。
87993
+ * 末尾追加不改变已发前缀,指引没有丢,只是从最前面的 system 段搬到了末尾。
87994
+ * 3. 段正文回到空串 = 本次激活结束:复位投递状态,并在下一步投一条**撤销**通知,
87995
+ * 免得历史里那句「这里有一块共享任务板」在关闭后继续误导模型。
87996
+ *
87997
+ * ## 抓的是官方原文,不是抄的副本
87998
+ *
87999
+ * 投递的正文永远是装配时从官方段里捕获到的那一份,不落任何本包的副本。harness 改
88000
+ * 措辞(它已经改过三次)不会让这里过期。代价是它依赖段名 `team:policy`:启用后
88001
+ * 若从未观察到该段,守卫记一条 warn 点名,而不是安静地什么都不做。
88002
+ *
88003
+ * ## 默认开启
88004
+ *
88005
+ * 会话中途切开关才会付一次重建;开会话时就是目标状态则本来就零成本。守卫本身不
88006
+ * 增加任何工具声明面,所以默认开着没有代价,由 `teamPolicyCacheGuard` 关闭,
88007
+ * 每次装配/每步重读活引用。
88008
+ *
88009
+ * @module @eddyskywalker/dsh-chatgpt-subscription/host/team-policy-cache-guard
88010
+ */
88011
+ /** 官方 Agent Teams 的指引段名。 */
88012
+ const TEAM_POLICY_SECTION = "team:policy";
88013
+ /** 尾部撤销通知的来源 kind(本包自有,见下方 MessageSourceMap 增补)。 */
88014
+ const TEAM_BOARD_NOTICE_SOURCE_KIND = "team-board-notice";
88015
+ /** 开关关掉时补投的那句话。 */
88016
+ const BOARD_OFF_NOTICE = "The shared team task board was turned off. Ignore earlier guidance describing an available board and coordinate through list_agents and send_message.";
88017
+ /**
88018
+ * 从插件配置里读开关。
88019
+ * @param config - 插件配置(结构化:只读一个键)。
88020
+ * @returns 是否启用本守卫;未声明时按开启处理(宿主 schema 的默认值)。
88021
+ */
88022
+ function resolveTeamPolicyCacheGuardEnabled(config) {
88023
+ return config?.teamPolicyCacheGuard !== false;
88024
+ }
88025
+ /** 没有 scope 的装配(会话标题之类)用的桶键。 */
88026
+ const GLOBAL_KEY = Object.freeze({});
88027
+ /**
88028
+ * 桶键:agent 对象本身,或全局桶。
88029
+ * @param value - scope / agent。
88030
+ * @returns WeakMap 键。
88031
+ */
88032
+ function bucketOf(value) {
88033
+ return typeof value === "object" && value !== null ? value : GLOBAL_KEY;
88034
+ }
88035
+ /**
88036
+ * 订阅一个事件。
88037
+ * @param ctx - 宿主上下文。
88038
+ * @param event - 事件名。
88039
+ * @param listener - 监听器。
88040
+ * @returns 解绑函数或 undefined。
88041
+ */
88042
+ function subscribe$1(ctx, event, listener) {
88043
+ const on = ctx?.on;
88044
+ if (typeof on !== "function") return void 0;
88045
+ try {
88046
+ return on.call(ctx, event, listener);
88047
+ } catch {
88048
+ return;
88049
+ }
88050
+ }
88051
+ /**
88052
+ * 记一条警告。
88053
+ * @param ctx - 宿主上下文。
88054
+ * @param message - 正文。
88055
+ */
88056
+ function logWarn(ctx, message) {
88057
+ try {
88058
+ ctx?.logger?.warn(message);
88059
+ } catch {}
88060
+ }
88061
+ /**
88062
+ * 造一条尾部通知消息。
88063
+ * @param text - 正文。
88064
+ * @param summary - 卡片上显示的一句摘要。
88065
+ * @returns 用户角色的通知消息。
88066
+ */
88067
+ function createBoardNotice(text, summary) {
88068
+ return createUserMessage({
88069
+ content: [{
88070
+ type: "text",
88071
+ text
88072
+ }],
88073
+ source: {
88074
+ kind: TEAM_BOARD_NOTICE_SOURCE_KIND,
88075
+ form: "notice",
88076
+ summary
88077
+ }
88078
+ });
88079
+ }
88080
+ /**
88081
+ * 装一台「Agent Teams 策略段缓存守卫」。
88082
+ *
88083
+ * 幂等性留给调用方:同一个 ctx 装两次会得到两条监听器,宿主入口应当只装一次。
88084
+ * @param ctx - 宿主上下文(结构化:老代 ctx 也认)。
88085
+ * @param options - 开关读取器。
88086
+ * @returns 句柄,dispose 摘掉全部挂载。
88087
+ */
88088
+ function installTeamPolicyCacheGuard(ctx, options = {}) {
88089
+ const states = /* @__PURE__ */ new WeakMap();
88090
+ const stats = {
88091
+ seen: false,
88092
+ warned: false
88093
+ };
88094
+ const restores = [];
88095
+ let closed = false;
88096
+ const enabled = () => {
88097
+ try {
88098
+ return options.enabled?.() === true;
88099
+ } catch {
88100
+ return false;
88101
+ }
88102
+ };
88103
+ const stateOf = (key) => {
88104
+ let state = states.get(key);
88105
+ if (state === void 0) {
88106
+ state = {
88107
+ policy: "",
88108
+ pending: void 0,
88109
+ delivered: false,
88110
+ revoked: false
88111
+ };
88112
+ states.set(key, state);
88113
+ }
88114
+ return state;
88115
+ };
88116
+ const onAssemble = async (_assembly, context, next) => {
88117
+ const result = await next();
88118
+ if (closed || !enabled()) return result;
88119
+ const sections = Array.isArray(result?.sections) ? result.sections : void 0;
88120
+ if (sections === void 0) return result;
88121
+ const kept = [];
88122
+ let policy;
88123
+ for (const section of sections) {
88124
+ if (section?.name === "team:policy") {
88125
+ stats.seen = true;
88126
+ policy = typeof section.text === "string" ? section.text : "";
88127
+ continue;
88128
+ }
88129
+ kept.push(section);
88130
+ }
88131
+ if (policy === void 0) {
88132
+ if (!stats.seen && !stats.warned) {
88133
+ stats.warned = true;
88134
+ logWarn(ctx, `[dsh-chatgpt-subscription] team policy cache guard is enabled but no "${TEAM_POLICY_SECTION}" section has appeared; the guard is inert on this deployment`);
88135
+ }
88136
+ return result;
88137
+ }
88138
+ const state = stateOf(bucketOf(context?.scope ?? context?.agent));
88139
+ if (policy.trim() === "") {
88140
+ if (state.delivered) state.revoked = true;
88141
+ state.delivered = false;
88142
+ state.pending = void 0;
88143
+ } else {
88144
+ state.policy = policy;
88145
+ state.revoked = false;
88146
+ if (!state.delivered) state.pending = policy;
88147
+ }
88148
+ if (kept.length === sections.length) return result;
88149
+ return {
88150
+ ...result,
88151
+ sections: kept
88152
+ };
88153
+ };
88154
+ const onPreStep = async (payload, next) => {
88155
+ const decision = await next();
88156
+ if (closed || decision === void 0 || decision.kind === "reject") return decision;
88157
+ if (payload?.signal?.aborted === true) return decision;
88158
+ if (!enabled()) return decision;
88159
+ const state = states.get(bucketOf(payload?.agent ?? void 0));
88160
+ if (state === void 0) return decision;
88161
+ const messages = Array.isArray(decision.messages) ? decision.messages : [];
88162
+ if (!state.delivered) {
88163
+ const text = state.pending;
88164
+ if (text !== void 0) {
88165
+ state.pending = void 0;
88166
+ state.delivered = true;
88167
+ state.revoked = false;
88168
+ return {
88169
+ ...decision,
88170
+ messages: [...messages, createBoardNotice(text, "Team board guidance")]
88171
+ };
88172
+ }
88173
+ }
88174
+ if (state.revoked) {
88175
+ state.revoked = false;
88176
+ return {
88177
+ ...decision,
88178
+ messages: [...messages, createBoardNotice(BOARD_OFF_NOTICE, "Team board off")]
88179
+ };
88180
+ }
88181
+ return decision;
88182
+ };
88183
+ const offAssemble = subscribe$1(ctx, "system-prompt/assemble", onAssemble);
88184
+ const offPreStep = subscribe$1(ctx, "agent/pre-step", onPreStep);
88185
+ if (offAssemble !== void 0) restores.push(() => {
88186
+ offAssemble();
88187
+ });
88188
+ if (offPreStep !== void 0) restores.push(() => {
88189
+ offPreStep();
88190
+ });
88191
+ return { dispose() {
88192
+ if (closed) return;
88193
+ closed = true;
88194
+ for (const restore of restores.splice(0)) try {
88195
+ restore();
88196
+ } catch {}
88197
+ } };
88198
+ }
88199
+ //#endregion
87790
88200
  //#region src/host/compaction-policy-guard.ts
87791
88201
  /**
87792
88202
  * 「压缩策略守卫」——把 `compaction-basic` 的预算换成 Kimi Code 的口径,
@@ -92150,13 +92560,13 @@ const DISPATCH_PRESET = {
92150
92560
  [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.
92151
92561
 
92152
92562
  ## R0.5 · Compatibility Gate (Once per New Session, Before R0)
92153
- Dispatch Mode is **incompatible** with Agent Teams and deliberately provides no fallback path: Agent Teams replaces send_message, list_agents, and interrupt_agent with team-scoped registrations, leaving spawned subagents invisible and unreachable.
92154
- Criterion: whether spawn_teammate and wait_agent exist (in PTC mode: tools.spawn_teammate / tools.wait_agent).
92155
- - Neither exists -> Write one line "Orchestration ready: subagent" and proceed to R0.
92156
- - Either exists -> This session is **not orchestratable**. Do these three things in order, and nothing else:
92157
- 1. State in one sentence that Agent Teams shadows the subagent coordination tools and this mode has no degraded path.
92158
- 2. Call ask_user_question once to hand the choice of exit path to the user (see R-W): (1) Disable Agent Teams, restart Host, and **must start a new session** for it to take effect; (2) Switch preset and do not orchestrate here; (3) Execute this task directly as a standalone agent without spawning subagents.
92159
- 3. **Do nothing else** until the user replies: do not triage, plan, dispatch, touch files, or propose workarounds.
92563
+ 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.
92564
+ 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.
92565
+ - 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.
92566
+ - Present -> This session runs the legacy runtime and is **not orchestratable**. In order, and nothing else:
92567
+ 1. State in one sentence that the legacy runtime shadows the coordination tools and this mode has no degraded path.
92568
+ 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.
92569
+ 3. **Do nothing else** until the user replies: no triage, plan, dispatch, file edits, or workarounds.
92160
92570
 
92161
92571
  ## R0 · Complexity Triage (Triage First, Then Choose the Workflow)
92162
92572
  Upon receiving a task, determine its tier by semantic impact and uncertainty first, write one line stating "Tier + Rationale", and run the matching workflow:
@@ -92226,7 +92636,7 @@ Fallback is a task-scoped, non-session-wide exception for delegated briefs: it c
92226
92636
 
92227
92637
  ## Anti-Patterns (Any Occurrence Is a Violation)
92228
92638
  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).
92229
- 2. Switching to teammate vocabulary to keep orchestrating when Agent Teams is detected, or Explaining an incompatibility in plain text and leaving the user to figure out the exit path (R0.5).
92639
+ 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).
92230
92640
  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).
92231
92641
  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).
92232
92642
  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).
@@ -95118,14 +95528,14 @@ const WORKBUDDY_STANDARD_EFFORTS = [
95118
95528
  * Snap a level onto the nearest rung a model's ladder actually exposes.
95119
95529
  *
95120
95530
  * The gateway names a default from a wider vocabulary than the ladder it
95121
- * publishes for the same model: an effort-only entry (minimax-m3, kimi-k3,
95122
- * `glm-5.3` in the cn region) reports `effort: 'medium'` while exposing only
95123
- * `low`/`high`/`max`. Letting that value through is not cosmetic — a default
95124
- * outside the ladder is discarded by the level-resolution step, the request
95125
- * then carries no `reasoning_effort`, and the model returns an EMPTY
95126
- * `reasoning_content` (measured on minimax-m3: 0 characters across three
95127
- * samples, versus 282-659 with the field). Ties resolve upward, which is the
95128
- * mapping the sibling Kimi line documents for `medium`. A ladder that names
95531
+ * publishes for the same model: an effort-only entry — the shape `kimi-k3`
95532
+ * publishes — names a single default while exposing only `low`/`high`/`max`.
95533
+ * Letting an unlisted value through is not cosmetic — a default outside the
95534
+ * ladder is discarded by the level-resolution step, the request then carries no
95535
+ * `reasoning_effort`, and the model returns an EMPTY `reasoning_content`
95536
+ * (measured on `minimax-m3` while that id was still served: 0 characters across
95537
+ * three samples, versus 282-659 with the field). Ties resolve upward, which is
95538
+ * the mapping the sibling Kimi line documents for `medium`. A ladder that names
95129
95539
  * no level (a text-only model) yields nothing to converge to.
95130
95540
  */
95131
95541
  function convergeWorkBuddyEffort(effort, ladder) {
@@ -95408,96 +95818,27 @@ function refreshSourceForDomain(domain) {
95408
95818
  /**
95409
95819
  * Models the gateway serves but does not publish in `/v3/config`.
95410
95820
  *
95411
- * The catalog is a published list, not the served one: measured on the
95412
- * international backend, `gpt-6-sol`, `gpt-6-luna` and `gemini-3.8-flash` all
95413
- * answer a normal streaming completion with 200 while being absent from
95821
+ * The catalog is a published list, not the served one: measured 2026-10-09 on
95822
+ * the international backend, `gpt-6-sol`, `gpt-6-luna` and `gemini-3.8-flash`
95823
+ * all answered a normal streaming completion with 200 while being absent from
95414
95824
  * `/v3/config` entirely, and a reachable gateway *replaces* the shipped table
95415
- * with that list. Without these rows the picker would simply never show them, so
95416
- * they are declared here and merged into a live catalog too
95417
- * ({@link withUnpublishedModels}) rather than living in the fallback alone.
95418
- *
95419
- * What each row records, and how it was established, differs by field:
95420
- *
95421
- * - **Existence, the reasoning ladder, image support** were measured against the
95422
- * live endpoint, not inferred from the name. A rejected level answers 400
95423
- * `code 11133` with `extError` `400002`; `gpt-6-sol` takes exactly the ladder
95424
- * `gpt-6-astra` publishes (`minimal` refused, `low`/`medium`/`high`/`xhigh`/
95425
- * `max` accepted) and both accept a 1x1 PNG.
95426
- * - **Context window, output cap and `canDisableThinking` are inherited from the
95427
- * published sibling in the same family** — `gpt-6-astra` for `gpt-6-sol` and
95428
- * `gpt-6-luna`, `gemini-3.5-flash` for `gemini-3.8-flash`. They are marked
95429
- * here rather than measured because the gateway publishes nothing for these
95430
- * ids and offers no cheap way to read the real figure: it accepts a
95431
- * `max_tokens` larger than any published window (even for `gpt-6-astra`, whose
95432
- * own catalog caps at 128000), so the number is neither enforced nor
95433
- * discoverable from the request side.
95434
- *
95435
- * Every field stays overridable per model from the settings card, and an
95436
- * inherited window errs low rather than high: over-declaring overflows the
95437
- * gateway mid-session, while under-declaring only compresses earlier.
95438
- */
95439
- const UNPUBLISHED_MODELS = [
95440
- {
95441
- id: "gpt-6-sol",
95442
- name: "GPT-6-Sol",
95443
- contextWindow: 4e5,
95444
- maxContextWindow: 1e6,
95445
- maxTokens: 128e3,
95446
- regions: ["intl"],
95447
- supportsImage: true,
95448
- reasoningEfforts: [
95449
- "low",
95450
- "medium",
95451
- "high",
95452
- "xhigh",
95453
- "max"
95454
- ],
95455
- defaultReasoningEffort: "high",
95456
- canDisableThinking: true,
95457
- description: "OpenAI 旗舰模型,擅长复杂推理与长程任务"
95458
- },
95459
- {
95460
- id: "gpt-6-luna",
95461
- name: "GPT-6-Luna",
95462
- contextWindow: 4e5,
95463
- maxContextWindow: 1e6,
95464
- maxTokens: 128e3,
95465
- regions: ["intl"],
95466
- supportsImage: true,
95467
- reasoningEfforts: [
95468
- "low",
95469
- "medium",
95470
- "high",
95471
- "xhigh",
95472
- "max"
95473
- ],
95474
- defaultReasoningEffort: "high",
95475
- canDisableThinking: true,
95476
- description: "OpenAI 轻量模型,响应快速,适合日常任务"
95477
- },
95478
- {
95479
- id: "gemini-3.8-flash",
95480
- name: "Gemini-3.8-Flash",
95481
- contextWindow: 1e6,
95482
- maxContextWindow: 1e6,
95483
- maxTokens: 65536,
95484
- regions: ["intl"],
95485
- supportsImage: true,
95486
- reasoningEfforts: [
95487
- "low",
95488
- "high",
95489
- "max"
95490
- ],
95491
- defaultReasoningEffort: "medium",
95492
- canDisableThinking: false,
95493
- description: "能力均衡,适合日常使用"
95494
- }
95495
- ];
95825
+ * with that list — so a row living only in the fallback would drop out of the
95826
+ * picker the moment the catalog loaded.
95827
+ *
95828
+ * The list is empty as of the 2026-11-04 read: all three ids are published now,
95829
+ * with figures measured on the wire, and their rows therefore live in
95830
+ * {@link FALLBACK_MODELS} like any other. The seam stays rather than being
95831
+ * deleted because the gap it covers belongs to the gateway, not to those three
95832
+ * models: the next id it serves without listing belongs here, and
95833
+ * {@link withUnpublishedModels} is what keeps such a row offered.
95834
+ */
95835
+ const UNPUBLISHED_MODELS = [];
95496
95836
  /**
95497
95837
  * Offline fallback catalog, transcribed from a live `/v3/config` read.
95498
95838
  *
95499
- * Used when the gateway cannot be reached (first run before sign-in, or a
95500
- * network failure); a reachable gateway always wins.
95839
+ * Last transcribed 2026-11-04 from both backends. Used when the gateway cannot
95840
+ * be reached (first run before sign-in, or a network failure); a reachable
95841
+ * gateway always wins.
95501
95842
  */
95502
95843
  const FALLBACK_MODELS$1 = [
95503
95844
  {
@@ -95513,20 +95854,6 @@ const FALLBACK_MODELS$1 = [
95513
95854
  canDisableThinking: false,
95514
95855
  description: "优秀的编码模型,适合日常使用"
95515
95856
  },
95516
- {
95517
- id: "default",
95518
- name: "Default",
95519
- contextWindow: 56e3,
95520
- maxContextWindow: 56e3,
95521
- maxTokens: 24e3,
95522
- regions: ["cn"],
95523
- credits: { cn: "2.00" },
95524
- supportsImage: false,
95525
- reasoningEfforts: [],
95526
- defaultReasoningEffort: null,
95527
- canDisableThinking: false,
95528
- description: ""
95529
- },
95530
95857
  {
95531
95858
  id: "fast-model",
95532
95859
  name: "Fast",
@@ -95541,7 +95868,7 @@ const FALLBACK_MODELS$1 = [
95541
95868
  "high",
95542
95869
  "max"
95543
95870
  ],
95544
- defaultReasoningEffort: "medium",
95871
+ defaultReasoningEffort: "high",
95545
95872
  canDisableThinking: false,
95546
95873
  description: "响应快,适合简单任务"
95547
95874
  },
@@ -95559,7 +95886,7 @@ const FALLBACK_MODELS$1 = [
95559
95886
  "high",
95560
95887
  "max"
95561
95888
  ],
95562
- defaultReasoningEffort: "medium",
95889
+ defaultReasoningEffort: "high",
95563
95890
  canDisableThinking: false,
95564
95891
  description: "速度与质量兼顾,日常工作首选"
95565
95892
  },
@@ -95582,20 +95909,65 @@ const FALLBACK_MODELS$1 = [
95582
95909
  description: "高质量输出,胜任复杂任务"
95583
95910
  },
95584
95911
  {
95585
- id: "deep-model",
95586
- name: "Deep",
95587
- contextWindow: 2e5,
95588
- maxContextWindow: 2e5,
95589
- maxTokens: 24e3,
95912
+ id: "deepseek-v4.1-flash",
95913
+ name: "Deepseek-V4.1-Flash",
95914
+ contextWindow: 3e5,
95915
+ maxContextWindow: 1e6,
95916
+ maxTokens: 128e3,
95917
+ regions: ["intl", "cn"],
95918
+ credits: {
95919
+ cn: "0.11",
95920
+ intl: "0.00"
95921
+ },
95922
+ supportsImage: true,
95923
+ reasoningEfforts: [
95924
+ "low",
95925
+ "high",
95926
+ "max"
95927
+ ],
95928
+ defaultReasoningEffort: "high",
95929
+ canDisableThinking: false,
95930
+ description: "DeepSeek 旗舰模型,支持 1M 上下文窗口,原生多模态"
95931
+ },
95932
+ {
95933
+ id: "deepseek-v4.1-flash-sg",
95934
+ name: "Deepseek-V4.1-Flash",
95935
+ contextWindow: 3e5,
95936
+ maxContextWindow: 1e6,
95937
+ maxTokens: 128e3,
95590
95938
  regions: ["intl"],
95591
- credits: { intl: "3.33" },
95939
+ credits: { intl: "0.03" },
95592
95940
  supportsImage: true,
95593
- reasoningEfforts: [],
95594
- defaultReasoningEffort: null,
95941
+ reasoningEfforts: [
95942
+ "low",
95943
+ "high",
95944
+ "max"
95945
+ ],
95946
+ defaultReasoningEffort: "high",
95595
95947
  canDisableThinking: false,
95596
- description: "深度推理,适合深度分析与难题"
95948
+ description: "DeepSeek 旗舰模型,支持 1M 上下文窗口,原生多模态"
95949
+ },
95950
+ {
95951
+ id: "glm-5.3-flash",
95952
+ name: "GLM-5.3-Flash",
95953
+ contextWindow: 3e5,
95954
+ maxContextWindow: 1e6,
95955
+ maxTokens: 32e3,
95956
+ regions: ["intl", "cn"],
95957
+ credits: {
95958
+ cn: "0.08",
95959
+ intl: "0.08"
95960
+ },
95961
+ supportsImage: true,
95962
+ reasoningEfforts: [
95963
+ "low",
95964
+ "high",
95965
+ "max"
95966
+ ],
95967
+ defaultReasoningEffort: "high",
95968
+ canDisableThinking: false,
95969
+ description: "原生多模态模型,擅长视觉理解与专业任务"
95597
95970
  },
95598
- ...UNPUBLISHED_MODELS.filter((model) => model.id === "gpt-6-sol" || model.id === "gpt-6-luna"),
95599
95971
  {
95600
95972
  id: "gpt-6-astra",
95601
95973
  name: "GPT-6-Astra",
@@ -95616,6 +95988,37 @@ const FALLBACK_MODELS$1 = [
95616
95988
  canDisableThinking: true,
95617
95989
  description: "OpenAI 旗舰模型,擅长复杂推理与长程任务"
95618
95990
  },
95991
+ {
95992
+ id: "hy4-preview",
95993
+ name: "Hy4 Preview",
95994
+ contextWindow: 2e5,
95995
+ maxContextWindow: 1e6,
95996
+ maxTokens: 64e3,
95997
+ regions: ["intl"],
95998
+ credits: { intl: "0.29" },
95999
+ supportsImage: true,
96000
+ reasoningEfforts: ["high"],
96001
+ defaultReasoningEffort: "high",
96002
+ canDisableThinking: false,
96003
+ description: "混元思考模型,具有增强的推理能力"
96004
+ },
96005
+ {
96006
+ id: "hy3",
96007
+ name: "Hy3",
96008
+ contextWindow: 192e3,
96009
+ maxContextWindow: 192e3,
96010
+ maxTokens: 64e3,
96011
+ regions: ["intl", "cn"],
96012
+ credits: {
96013
+ cn: "0.00",
96014
+ intl: "0.00"
96015
+ },
96016
+ supportsImage: true,
96017
+ reasoningEfforts: ["low", "high"],
96018
+ defaultReasoningEffort: "high",
96019
+ canDisableThinking: false,
96020
+ description: "混元思考模型,具有增强的推理能力"
96021
+ },
95619
96022
  {
95620
96023
  id: "gpt-5.6-sol",
95621
96024
  name: "GPT-5.6-Sol",
@@ -95722,8 +96125,8 @@ const FALLBACK_MODELS$1 = [
95722
96125
  maxTokens: 48e3,
95723
96126
  regions: ["intl", "cn"],
95724
96127
  credits: {
95725
- cn: "0.79",
95726
- intl: "0.79"
96128
+ cn: "0.69",
96129
+ intl: "0.69"
95727
96130
  },
95728
96131
  supportsImage: true,
95729
96132
  reasoningEfforts: [
@@ -95732,30 +96135,9 @@ const FALLBACK_MODELS$1 = [
95732
96135
  "max"
95733
96136
  ],
95734
96137
  defaultReasoningEffort: "high",
95735
- canDisableThinking: true,
96138
+ canDisableThinking: false,
95736
96139
  description: "能力均衡,适合日常使用"
95737
96140
  },
95738
- {
95739
- id: "glm-5.3-flash",
95740
- name: "GLM-5.3-Flash",
95741
- contextWindow: 3e5,
95742
- maxContextWindow: 1e6,
95743
- maxTokens: 32e3,
95744
- regions: ["intl", "cn"],
95745
- credits: {
95746
- cn: "0.06",
95747
- intl: "0.06"
95748
- },
95749
- supportsImage: true,
95750
- reasoningEfforts: [
95751
- "low",
95752
- "high",
95753
- "max"
95754
- ],
95755
- defaultReasoningEffort: "high",
95756
- canDisableThinking: true,
95757
- description: "原生多模态模型,擅长视觉理解与专业任务"
95758
- },
95759
96141
  {
95760
96142
  id: "glm-5.2",
95761
96143
  name: "GLM-5.2",
@@ -95768,101 +96150,63 @@ const FALLBACK_MODELS$1 = [
95768
96150
  intl: "0.79"
95769
96151
  },
95770
96152
  supportsImage: true,
95771
- reasoningEfforts: ["high", "xhigh"],
96153
+ reasoningEfforts: ["high"],
95772
96154
  defaultReasoningEffort: "high",
95773
- canDisableThinking: true,
96155
+ canDisableThinking: false,
95774
96156
  description: "1M 上下文,擅长长程任务"
95775
96157
  },
95776
96158
  {
95777
- id: "glm-5.1",
95778
- name: "GLM-5.1",
95779
- contextWindow: 2e5,
95780
- maxContextWindow: 2e5,
95781
- maxTokens: 48e3,
95782
- regions: ["cn"],
95783
- credits: { cn: "0.79" },
96159
+ id: "minimax-m3.1-flash",
96160
+ name: "MiniMax-M3.1-Flash",
96161
+ contextWindow: 3e5,
96162
+ maxContextWindow: 1e6,
96163
+ maxTokens: 128e3,
96164
+ regions: ["intl", "cn"],
96165
+ credits: {
96166
+ cn: "0.08",
96167
+ intl: "0.08"
96168
+ },
95784
96169
  supportsImage: true,
95785
96170
  reasoningEfforts: [
95786
96171
  "low",
96172
+ "medium",
95787
96173
  "high",
96174
+ "xhigh",
95788
96175
  "max"
95789
96176
  ],
95790
- defaultReasoningEffort: "medium",
96177
+ defaultReasoningEffort: "max",
95791
96178
  canDisableThinking: false,
95792
- description: ""
96179
+ description: "原生多模态,擅长office、agentic、编程任务,推理速度优异"
95793
96180
  },
95794
96181
  {
95795
- id: "glm-5v-turbo",
95796
- name: "GLM-5v-Turbo",
95797
- contextWindow: 2e5,
95798
- maxContextWindow: 2e5,
95799
- maxTokens: 64e3,
95800
- regions: ["cn"],
95801
- credits: { cn: "0.71" },
96182
+ id: "kimi-k3.1-pro",
96183
+ name: "Kimi-K3.1-Pro",
96184
+ contextWindow: 3e5,
96185
+ maxContextWindow: 1e6,
96186
+ maxTokens: 32e3,
96187
+ regions: ["intl", "cn"],
96188
+ credits: {
96189
+ cn: "0.97",
96190
+ intl: "0.97"
96191
+ },
95802
96192
  supportsImage: true,
95803
96193
  reasoningEfforts: [
95804
96194
  "low",
95805
96195
  "high",
95806
96196
  "max"
95807
96197
  ],
95808
- defaultReasoningEffort: "medium",
95809
- canDisableThinking: false,
95810
- description: ""
95811
- },
95812
- {
95813
- id: "glm-5.0",
95814
- name: "GLM-5.0",
95815
- contextWindow: 2e5,
95816
- maxContextWindow: 2e5,
95817
- maxTokens: 48e3,
95818
- regions: ["cn"],
95819
- credits: { cn: "0.80" },
95820
- supportsImage: false,
95821
- reasoningEfforts: [],
95822
- defaultReasoningEffort: null,
95823
- canDisableThinking: false,
95824
- description: ""
95825
- },
95826
- {
95827
- id: "glm-5.0-turbo",
95828
- name: "GLM-5.0-Turbo",
95829
- contextWindow: 2e5,
95830
- maxContextWindow: 2e5,
95831
- maxTokens: 48e3,
95832
- regions: ["cn"],
95833
- credits: { cn: "0.95" },
95834
- supportsImage: false,
95835
- reasoningEfforts: [
95836
- "low",
95837
- "high",
95838
- "max"
95839
- ],
95840
- defaultReasoningEffort: "medium",
95841
- canDisableThinking: false,
95842
- description: ""
95843
- },
95844
- {
95845
- id: "glm-4.7",
95846
- name: "GLM-4.7",
95847
- contextWindow: 2e5,
95848
- maxContextWindow: 2e5,
95849
- maxTokens: 48e3,
95850
- regions: ["cn"],
95851
- credits: { cn: "0.23" },
95852
- supportsImage: false,
95853
- reasoningEfforts: [],
95854
- defaultReasoningEffort: null,
96198
+ defaultReasoningEffort: "high",
95855
96199
  canDisableThinking: false,
95856
- description: ""
96200
+ description: "Kimi 全新旗舰模型,3万亿级参数。办公、代码、3D 创作与长程 Agent 能力全面升级。"
95857
96201
  },
95858
96202
  {
95859
- id: "glm-4.6v",
95860
- name: "GLM-4.6V",
95861
- contextWindow: 128e3,
95862
- maxContextWindow: 128e3,
96203
+ id: "kimi-k3.1-pro-highspeed",
96204
+ name: "Kimi-K3.1-Pro-Highspeed",
96205
+ contextWindow: 3e5,
96206
+ maxContextWindow: 1e6,
95863
96207
  maxTokens: 32e3,
95864
- regions: ["cn"],
95865
- credits: { cn: "0.11" },
96208
+ regions: ["intl"],
96209
+ credits: { intl: "1.56" },
95866
96210
  supportsImage: true,
95867
96211
  reasoningEfforts: [
95868
96212
  "low",
@@ -95871,21 +96215,7 @@ const FALLBACK_MODELS$1 = [
95871
96215
  ],
95872
96216
  defaultReasoningEffort: "high",
95873
96217
  canDisableThinking: false,
95874
- description: ""
95875
- },
95876
- {
95877
- id: "glm-4.6",
95878
- name: "GLM-4.6",
95879
- contextWindow: 168e3,
95880
- maxContextWindow: 168e3,
95881
- maxTokens: 32e3,
95882
- regions: ["cn"],
95883
- credits: { cn: "0.23" },
95884
- supportsImage: false,
95885
- reasoningEfforts: [],
95886
- defaultReasoningEffort: null,
95887
- canDisableThinking: false,
95888
- description: ""
96218
+ description: "K3.1 Pro 高速版本,响应速度提升至 K3.1 Pro 的 2–3 倍,交互更流畅。"
95889
96219
  },
95890
96220
  {
95891
96221
  id: "kimi-k3",
@@ -95901,166 +96231,121 @@ const FALLBACK_MODELS$1 = [
95901
96231
  "high",
95902
96232
  "max"
95903
96233
  ],
95904
- defaultReasoningEffort: "medium",
96234
+ defaultReasoningEffort: "high",
95905
96235
  canDisableThinking: false,
95906
96236
  description: "擅长处理复杂的长程自主任务,前端开发能力突出,同时在知识工作与科研推理上表现出色。"
95907
96237
  },
95908
96238
  {
95909
- id: "kimi-k3-1",
95910
- name: "Kimi-K3",
95911
- contextWindow: 1e6,
95912
- maxContextWindow: 1e6,
95913
- maxTokens: 32e3,
95914
- regions: ["cn"],
95915
- credits: { cn: "1.62" },
96239
+ id: "grok-4.7",
96240
+ name: "Grok-4.7",
96241
+ contextWindow: 2e5,
96242
+ maxContextWindow: 5e5,
96243
+ maxTokens: 128e3,
96244
+ regions: ["intl"],
96245
+ credits: { intl: "1.90" },
95916
96246
  supportsImage: true,
95917
96247
  reasoningEfforts: [
95918
96248
  "low",
96249
+ "medium",
95919
96250
  "high",
95920
- "max"
96251
+ "xhigh"
95921
96252
  ],
95922
- defaultReasoningEffort: "medium",
96253
+ defaultReasoningEffort: "high",
95923
96254
  canDisableThinking: false,
95924
- description: "擅长处理复杂的长程自主任务,前端开发能力突出,同时在知识工作与科研推理上表现出色。"
96255
+ description: "SpaceXAI 打造的前沿模型,专为编程、智能体任务及知识型工作而设计。"
95925
96256
  },
95926
96257
  {
95927
- id: "kimi-k2.8-preview",
95928
- name: "Kimi-K2.8-Preview",
96258
+ id: "gemini-3.8-flash",
96259
+ name: "Gemini-3.8-Flash",
95929
96260
  contextWindow: 3e5,
95930
96261
  maxContextWindow: 1e6,
95931
- maxTokens: 32e3,
95932
- regions: ["intl", "cn"],
95933
- credits: {
95934
- cn: "0.77",
95935
- intl: "0.77"
95936
- },
95937
- supportsImage: true,
95938
- reasoningEfforts: [
95939
- "low",
95940
- "high",
95941
- "max"
95942
- ],
95943
- defaultReasoningEffort: "high",
95944
- canDisableThinking: true,
95945
- description: "擅长处理复杂的长程自主任务,前端开发能力突出,同时在知识工作与科研推理上表现出色。"
95946
- },
95947
- {
95948
- id: "kimi-k2.7",
95949
- name: "Kimi-K2.7-Code",
95950
- contextWindow: 256e3,
95951
- maxContextWindow: 256e3,
95952
- maxTokens: 32e3,
95953
- regions: ["cn"],
95954
- credits: { cn: "0.57" },
95955
- supportsImage: true,
95956
- reasoningEfforts: [
95957
- "low",
95958
- "high",
95959
- "max"
95960
- ],
95961
- defaultReasoningEffort: "medium",
95962
- canDisableThinking: false,
95963
- description: ""
95964
- },
95965
- {
95966
- id: "kimi-k2.6",
95967
- name: "Kimi-K2.6",
95968
- contextWindow: 256e3,
95969
- maxContextWindow: 256e3,
95970
- maxTokens: 32e3,
95971
- regions: ["intl", "cn"],
95972
- credits: {
95973
- cn: "0.52",
95974
- intl: "0.52"
95975
- },
96262
+ maxTokens: 65536,
96263
+ regions: ["intl"],
96264
+ credits: { intl: "0.95" },
95976
96265
  supportsImage: true,
95977
96266
  reasoningEfforts: [
95978
96267
  "low",
95979
- "high",
95980
- "max"
96268
+ "medium",
96269
+ "high"
95981
96270
  ],
95982
96271
  defaultReasoningEffort: "medium",
95983
96272
  canDisableThinking: false,
95984
- description: "多模态模型,适合日常任务"
96273
+ description: "能力均衡,适合日常使用"
95985
96274
  },
95986
96275
  {
95987
- id: "kimi-k2.5",
95988
- name: "Kimi-K2.5",
95989
- contextWindow: 164e3,
95990
- maxContextWindow: 164e3,
95991
- maxTokens: 32e3,
95992
- regions: ["cn"],
95993
- credits: { cn: "0.45" },
96276
+ id: "gpt-6-sol",
96277
+ name: "GPT-6-Sol",
96278
+ contextWindow: 1e6,
96279
+ maxContextWindow: 1e6,
96280
+ maxTokens: 128e3,
96281
+ regions: ["intl"],
96282
+ credits: { intl: "1.33" },
95994
96283
  supportsImage: true,
95995
96284
  reasoningEfforts: [
95996
96285
  "low",
96286
+ "medium",
95997
96287
  "high",
95998
- "max"
95999
- ],
96000
- defaultReasoningEffort: "high",
96001
- canDisableThinking: false,
96002
- description: ""
96003
- },
96004
- {
96005
- id: "kimi-k2-thinking",
96006
- name: "Kimi-K2-Thinking",
96007
- contextWindow: 164e3,
96008
- maxContextWindow: 164e3,
96009
- maxTokens: 32e3,
96010
- regions: ["cn"],
96011
- credits: { cn: "0.54" },
96012
- supportsImage: false,
96013
- reasoningEfforts: [
96014
- "low",
96015
- "high",
96016
- "max"
96288
+ "xhigh"
96017
96289
  ],
96018
96290
  defaultReasoningEffort: "high",
96019
96291
  canDisableThinking: false,
96020
- description: ""
96292
+ description: "专为复杂编码和智能体工作流构建的模型"
96021
96293
  },
96022
96294
  {
96023
- id: "deepseek-v4.1-flash",
96024
- name: "Deepseek-V4.1-Flash",
96025
- contextWindow: 3e5,
96295
+ id: "gpt-6-luna",
96296
+ name: "GPT-6-Luna",
96297
+ contextWindow: 1e6,
96026
96298
  maxContextWindow: 1e6,
96027
96299
  maxTokens: 128e3,
96028
- regions: ["intl", "cn"],
96029
- credits: {
96030
- cn: "0.11",
96031
- intl: "0.00"
96032
- },
96300
+ regions: ["intl"],
96301
+ credits: { intl: "0.07" },
96033
96302
  supportsImage: true,
96034
96303
  reasoningEfforts: [
96035
96304
  "low",
96305
+ "medium",
96036
96306
  "high",
96037
- "max"
96307
+ "xhigh"
96038
96308
  ],
96039
96309
  defaultReasoningEffort: "high",
96040
96310
  canDisableThinking: false,
96041
- description: "DeepSeek 旗舰模型,支持 1M 上下文窗口,原生多模态"
96311
+ description: "专为单一聚焦、高吞吐量任务打造的高效模型"
96042
96312
  },
96043
96313
  {
96044
- id: "deepseek-v4.1-flash-sg",
96045
- name: "Deepseek-V4.1-Flash",
96046
- contextWindow: 3e5,
96314
+ id: "gpt-6.1-sol",
96315
+ name: "GPT-6.1-Sol",
96316
+ contextWindow: 272e3,
96047
96317
  maxContextWindow: 1e6,
96048
96318
  maxTokens: 128e3,
96049
96319
  regions: ["intl"],
96050
- credits: { intl: "0.03" },
96320
+ credits: { intl: "1.33" },
96051
96321
  supportsImage: true,
96052
96322
  reasoningEfforts: [
96053
96323
  "low",
96324
+ "medium",
96054
96325
  "high",
96055
- "max"
96326
+ "xhigh"
96056
96327
  ],
96057
96328
  defaultReasoningEffort: "high",
96058
96329
  canDisableThinking: false,
96059
- description: "DeepSeek 旗舰模型,支持 1M 上下文窗口,原生多模态"
96330
+ description: "专为复杂编码和智能体工作流构建的模型"
96331
+ },
96332
+ {
96333
+ id: "default",
96334
+ name: "Default",
96335
+ contextWindow: 56e3,
96336
+ maxContextWindow: 56e3,
96337
+ maxTokens: 24e3,
96338
+ regions: ["cn"],
96339
+ credits: { cn: "2.00" },
96340
+ supportsImage: false,
96341
+ reasoningEfforts: [],
96342
+ defaultReasoningEffort: null,
96343
+ canDisableThinking: false,
96344
+ description: ""
96060
96345
  },
96061
96346
  {
96062
96347
  id: "deepseek-v4-pro",
96063
- name: "Deepseek-V4-Pro",
96348
+ name: "DeepSeek-V4-Pro",
96064
96349
  contextWindow: 1e6,
96065
96350
  maxContextWindow: 1e6,
96066
96351
  maxTokens: 128e3,
@@ -96113,52 +96398,190 @@ const FALLBACK_MODELS$1 = [
96113
96398
  description: ""
96114
96399
  },
96115
96400
  {
96116
- id: "hy4-preview",
96117
- name: "Hy4 preview",
96401
+ id: "minimax-m2.5",
96402
+ name: "MiniMax-M2.5",
96118
96403
  contextWindow: 2e5,
96119
- maxContextWindow: 1e6,
96120
- maxTokens: 64e3,
96121
- regions: ["intl", "cn"],
96122
- credits: {
96123
- cn: "0.29",
96124
- intl: "0.29"
96125
- },
96404
+ maxContextWindow: 2e5,
96405
+ maxTokens: 48e3,
96406
+ regions: ["cn"],
96407
+ credits: { cn: "0.18" },
96408
+ supportsImage: false,
96409
+ reasoningEfforts: [
96410
+ "low",
96411
+ "high",
96412
+ "max"
96413
+ ],
96414
+ defaultReasoningEffort: "high",
96415
+ canDisableThinking: false,
96416
+ description: ""
96417
+ },
96418
+ {
96419
+ id: "minimax-m2.7",
96420
+ name: "MiniMax-M2.7",
96421
+ contextWindow: 2e5,
96422
+ maxContextWindow: 2e5,
96423
+ maxTokens: 48e3,
96424
+ regions: ["cn"],
96425
+ credits: { cn: "0.19" },
96126
96426
  supportsImage: true,
96127
- reasoningEfforts: ["high"],
96427
+ reasoningEfforts: [
96428
+ "low",
96429
+ "high",
96430
+ "max"
96431
+ ],
96128
96432
  defaultReasoningEffort: "high",
96129
96433
  canDisableThinking: false,
96130
- description: "混元思考模型,具有增强的推理能力"
96434
+ description: ""
96131
96435
  },
96132
96436
  {
96133
- id: "hy4-preview-f",
96134
- name: "Hy4 preview",
96437
+ id: "glm-5.0",
96438
+ name: "GLM-5.0",
96439
+ contextWindow: 2e5,
96440
+ maxContextWindow: 2e5,
96441
+ maxTokens: 48e3,
96442
+ regions: ["cn"],
96443
+ credits: { cn: "0.80" },
96444
+ supportsImage: false,
96445
+ reasoningEfforts: [],
96446
+ defaultReasoningEffort: null,
96447
+ canDisableThinking: false,
96448
+ description: ""
96449
+ },
96450
+ {
96451
+ id: "glm-5.0-turbo",
96452
+ name: "GLM-5.0-Turbo",
96453
+ contextWindow: 2e5,
96454
+ maxContextWindow: 2e5,
96455
+ maxTokens: 48e3,
96456
+ regions: ["cn"],
96457
+ credits: { cn: "0.95" },
96458
+ supportsImage: false,
96459
+ reasoningEfforts: [
96460
+ "low",
96461
+ "high",
96462
+ "max"
96463
+ ],
96464
+ defaultReasoningEffort: "high",
96465
+ canDisableThinking: false,
96466
+ description: ""
96467
+ },
96468
+ {
96469
+ id: "glm-4.7",
96470
+ name: "GLM-4.7",
96471
+ contextWindow: 2e5,
96472
+ maxContextWindow: 2e5,
96473
+ maxTokens: 48e3,
96474
+ regions: ["cn"],
96475
+ credits: { cn: "0.23" },
96476
+ supportsImage: false,
96477
+ reasoningEfforts: [],
96478
+ defaultReasoningEffort: null,
96479
+ canDisableThinking: false,
96480
+ description: ""
96481
+ },
96482
+ {
96483
+ id: "glm-4.6",
96484
+ name: "GLM-4.6",
96485
+ contextWindow: 168e3,
96486
+ maxContextWindow: 168e3,
96487
+ maxTokens: 32e3,
96488
+ regions: ["cn"],
96489
+ credits: { cn: "0.23" },
96490
+ supportsImage: false,
96491
+ reasoningEfforts: [],
96492
+ defaultReasoningEffort: null,
96493
+ canDisableThinking: false,
96494
+ description: ""
96495
+ },
96496
+ {
96497
+ id: "glm-4.6v",
96498
+ name: "GLM-4.6V",
96499
+ contextWindow: 128e3,
96500
+ maxContextWindow: 128e3,
96501
+ maxTokens: 32e3,
96502
+ regions: ["cn"],
96503
+ credits: { cn: "0.11" },
96504
+ supportsImage: true,
96505
+ reasoningEfforts: [
96506
+ "low",
96507
+ "high",
96508
+ "max"
96509
+ ],
96510
+ defaultReasoningEffort: "high",
96511
+ canDisableThinking: false,
96512
+ description: ""
96513
+ },
96514
+ {
96515
+ id: "kimi-k3-1",
96516
+ name: "Kimi-K3",
96135
96517
  contextWindow: 1e6,
96136
96518
  maxContextWindow: 1e6,
96137
- maxTokens: 64e3,
96519
+ maxTokens: 32e3,
96138
96520
  regions: ["cn"],
96139
- credits: { cn: "0.00" },
96521
+ credits: { cn: "1.62" },
96140
96522
  supportsImage: true,
96141
- reasoningEfforts: ["high"],
96523
+ reasoningEfforts: [
96524
+ "low",
96525
+ "high",
96526
+ "max"
96527
+ ],
96142
96528
  defaultReasoningEffort: "high",
96143
96529
  canDisableThinking: false,
96144
- description: "混元思考模型,具有增强的推理能力"
96530
+ description: "擅长处理复杂的长程自主任务,前端开发能力突出,同时在知识工作与科研推理上表现出色。"
96145
96531
  },
96146
96532
  {
96147
- id: "hy3",
96148
- name: "Hy3",
96149
- contextWindow: 192e3,
96150
- maxContextWindow: 192e3,
96533
+ id: "kimi-k2.8-preview",
96534
+ name: "Kimi-K2.8-Preview",
96535
+ contextWindow: 1e6,
96536
+ maxContextWindow: 1e6,
96151
96537
  maxTokens: 64e3,
96152
- regions: ["intl", "cn"],
96153
- credits: {
96154
- cn: "0.00",
96155
- intl: "0.00"
96156
- },
96538
+ regions: ["cn"],
96539
+ credits: { cn: "0.77" },
96157
96540
  supportsImage: true,
96158
- reasoningEfforts: ["low", "high"],
96541
+ reasoningEfforts: [
96542
+ "low",
96543
+ "high",
96544
+ "max"
96545
+ ],
96546
+ defaultReasoningEffort: "high",
96547
+ canDisableThinking: true,
96548
+ description: "Moonshot AI · 擅长复杂长程自主任务、知识工作与科研推理,前端开发突出"
96549
+ },
96550
+ {
96551
+ id: "kimi-k2.5",
96552
+ name: "Kimi-K2.5",
96553
+ contextWindow: 164e3,
96554
+ maxContextWindow: 164e3,
96555
+ maxTokens: 32e3,
96556
+ regions: ["cn"],
96557
+ credits: { cn: "0.45" },
96558
+ supportsImage: true,
96559
+ reasoningEfforts: [
96560
+ "low",
96561
+ "high",
96562
+ "max"
96563
+ ],
96159
96564
  defaultReasoningEffort: "high",
96160
96565
  canDisableThinking: false,
96161
- description: "混元思考模型,具有增强的推理能力"
96566
+ description: ""
96567
+ },
96568
+ {
96569
+ id: "kimi-k2-thinking",
96570
+ name: "Kimi-K2-Thinking",
96571
+ contextWindow: 164e3,
96572
+ maxContextWindow: 164e3,
96573
+ maxTokens: 32e3,
96574
+ regions: ["cn"],
96575
+ credits: { cn: "0.54" },
96576
+ supportsImage: false,
96577
+ reasoningEfforts: [
96578
+ "low",
96579
+ "high",
96580
+ "max"
96581
+ ],
96582
+ defaultReasoningEffort: "high",
96583
+ canDisableThinking: false,
96584
+ description: ""
96162
96585
  },
96163
96586
  {
96164
96587
  id: "hy3-x",
@@ -96178,6 +96601,20 @@ const FALLBACK_MODELS$1 = [
96178
96601
  canDisableThinking: false,
96179
96602
  description: "混元思考模型,具有增强的推理能力"
96180
96603
  },
96604
+ {
96605
+ id: "hy4-preview-x",
96606
+ name: "Hy4 preview",
96607
+ contextWindow: 1e6,
96608
+ maxContextWindow: 1e6,
96609
+ maxTokens: 64e3,
96610
+ regions: ["cn"],
96611
+ credits: { cn: "0.29" },
96612
+ supportsImage: true,
96613
+ reasoningEfforts: ["high"],
96614
+ defaultReasoningEffort: "high",
96615
+ canDisableThinking: false,
96616
+ description: "混元思考模型,具有增强的推理能力"
96617
+ },
96181
96618
  {
96182
96619
  id: "hunyuan-chat",
96183
96620
  name: "Hunyuan-Turbos",
@@ -96213,77 +96650,68 @@ const FALLBACK_MODELS$1 = [
96213
96650
  description: "推理速度极快,编码能力强劲,并支持原生多模态输入的匿名大模型"
96214
96651
  },
96215
96652
  {
96216
- id: "minimax-m3",
96217
- name: "MiniMax-M3",
96218
- contextWindow: 512e3,
96219
- maxContextWindow: 512e3,
96653
+ id: "step-5-preview",
96654
+ name: "Step-5-Preview",
96655
+ contextWindow: 1e6,
96656
+ maxContextWindow: 1e6,
96220
96657
  maxTokens: 64e3,
96221
96658
  regions: ["cn"],
96222
- credits: { cn: "0.25" },
96659
+ credits: { cn: "0.33" },
96223
96660
  supportsImage: true,
96224
96661
  reasoningEfforts: [
96225
96662
  "low",
96226
- "high",
96227
- "max"
96663
+ "medium",
96664
+ "high"
96228
96665
  ],
96229
- defaultReasoningEffort: "medium",
96666
+ defaultReasoningEffort: "high",
96230
96667
  canDisableThinking: false,
96231
- description: "原生多模态,擅长代码、智能体任务"
96668
+ description: "阶跃 StepFun 面向真实任务的新一代旗舰基模"
96232
96669
  },
96233
96670
  {
96234
- id: "minimax-m2.7",
96235
- name: "MiniMax-M2.7",
96236
- contextWindow: 2e5,
96237
- maxContextWindow: 2e5,
96238
- maxTokens: 48e3,
96671
+ id: "mimo-v2.6-pro",
96672
+ name: "Xiaomi MiMo-V2.6-Pro",
96673
+ contextWindow: 1e6,
96674
+ maxContextWindow: 1e6,
96675
+ maxTokens: 128e3,
96239
96676
  regions: ["cn"],
96240
- credits: { cn: "0.19" },
96677
+ credits: { cn: "0.13" },
96241
96678
  supportsImage: true,
96242
96679
  reasoningEfforts: [
96243
96680
  "low",
96244
96681
  "high",
96245
96682
  "max"
96246
96683
  ],
96247
- defaultReasoningEffort: "medium",
96684
+ defaultReasoningEffort: "high",
96248
96685
  canDisableThinking: false,
96249
- description: ""
96686
+ description: "Xiaomi · 旗舰推理模型,面向专业、复杂长程工作流,兼顾高阶科研与网络安全"
96250
96687
  },
96251
96688
  {
96252
- id: "minimax-m2.5",
96253
- name: "MiniMax-M2.5",
96254
- contextWindow: 2e5,
96255
- maxContextWindow: 2e5,
96256
- maxTokens: 48e3,
96689
+ id: "hy4free",
96690
+ name: "Hy4 Free",
96691
+ contextWindow: 1e6,
96692
+ maxContextWindow: 1e6,
96693
+ maxTokens: 64e3,
96257
96694
  regions: ["cn"],
96258
- credits: { cn: "0.18" },
96259
- supportsImage: false,
96260
- reasoningEfforts: [
96261
- "low",
96262
- "high",
96263
- "max"
96264
- ],
96695
+ credits: { cn: "0.00" },
96696
+ supportsImage: true,
96697
+ reasoningEfforts: ["high"],
96265
96698
  defaultReasoningEffort: "high",
96266
96699
  canDisableThinking: false,
96267
- description: ""
96700
+ description: "最新旗舰模型,Agent与多模专长"
96268
96701
  },
96269
- ...UNPUBLISHED_MODELS.filter((model) => model.id === "gemini-3.8-flash"),
96270
96702
  {
96271
- id: "gemini-3.5-flash",
96272
- name: "Gemini-3.5-Flash",
96703
+ id: "hy4std",
96704
+ name: "Hy4",
96273
96705
  contextWindow: 1e6,
96274
96706
  maxContextWindow: 1e6,
96275
- maxTokens: 65536,
96276
- regions: ["intl"],
96277
- credits: { intl: "0.99" },
96707
+ maxTokens: 64e3,
96708
+ regions: ["cn"],
96709
+ credits: { cn: "0.29" },
96278
96710
  supportsImage: true,
96279
- reasoningEfforts: [
96280
- "low",
96281
- "high",
96282
- "max"
96283
- ],
96284
- defaultReasoningEffort: "medium",
96711
+ reasoningEfforts: ["high"],
96712
+ defaultReasoningEffort: "high",
96285
96713
  canDisableThinking: false,
96286
- description: "能力均衡,适合日常使用"
96714
+ description: "最新旗舰模型,Agent与多模专长"
96287
96715
  }
96288
96716
  ];
96289
96717
  /**
@@ -96295,11 +96723,41 @@ const FALLBACK_MODELS$1 = [
96295
96723
  * the account's region can actually call.
96296
96724
  */
96297
96725
  const DEFAULT_VISIBLE_MODEL_IDS$1 = [
96726
+ "glm-5.3",
96727
+ "deepseek-v4.1-flash",
96728
+ "hy4std",
96729
+ "kimi-k3.1-pro"
96730
+ ];
96731
+ /**
96732
+ * Default selections this table used to ship.
96733
+ *
96734
+ * "Never edited" is recognised by matching the CURRENT shipped set, so replacing
96735
+ * the set outright would turn every existing install into what looks like a user
96736
+ * who hand-picked a short list — and the picker would quietly narrow to it,
96737
+ * because the 2026-11-04 refresh dropped two of the retired ids outright
96738
+ * (`kimi-k2.6` is gone from both regions, `hy4-preview` is intl-only now).
96739
+ * Keeping the retired sets here is what lets the default move without that.
96740
+ */
96741
+ const RETIRED_DEFAULT_SELECTIONS = [[
96298
96742
  "glm-5.3",
96299
96743
  "deepseek-v4.1-flash",
96300
96744
  "hy4-preview",
96301
96745
  "kimi-k2.6"
96302
- ];
96746
+ ]];
96747
+ /**
96748
+ * Whether a stored selection is still a shipped default, i.e. the user never
96749
+ * touched the picker.
96750
+ *
96751
+ * Same size and same members, not order-sensitive: the card writes the list in
96752
+ * catalog order, which one default can spell two ways.
96753
+ */
96754
+ function isUntouchedModelSelection(stored) {
96755
+ if (stored.length === 0) return false;
96756
+ return [DEFAULT_VISIBLE_MODEL_IDS$1, ...RETIRED_DEFAULT_SELECTIONS].some((shipped) => {
96757
+ const known = new Set(shipped);
96758
+ return stored.length === known.size && stored.every((id) => known.has(id));
96759
+ });
96760
+ }
96303
96761
  /** Every id the fallback knows, in declaration order. */
96304
96762
  const WORKBUDDY_MODEL_IDS = FALLBACK_MODELS$1.map((model) => model.id);
96305
96763
  /**
@@ -96404,6 +96862,18 @@ const WORKBUDDY_VENDOR_RULES = [
96404
96862
  re: /^minimax-/,
96405
96863
  family: "minimax"
96406
96864
  },
96865
+ {
96866
+ re: /^mimo-/,
96867
+ family: "xiaomi"
96868
+ },
96869
+ {
96870
+ re: /^step-/,
96871
+ family: "stepfun"
96872
+ },
96873
+ {
96874
+ re: /^grok-/,
96875
+ family: "xai"
96876
+ },
96407
96877
  {
96408
96878
  re: /^space-bunny$/,
96409
96879
  family: "space-bunny"
@@ -100325,8 +100795,7 @@ function isPoolPausedError(error) {
100325
100795
  */
100326
100796
  function resolveEnabledModelIds$2(stored, available, enabled = true) {
100327
100797
  if (!enabled) return [];
100328
- const shippedDefaults = new Set(DEFAULT_VISIBLE_MODEL_IDS$1);
100329
- if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return [...available];
100798
+ if (isUntouchedModelSelection(stored)) return [...available];
100330
100799
  const known = new Set(available);
100331
100800
  return stored.filter((id) => known.has(id));
100332
100801
  }
@@ -101993,8 +102462,7 @@ async function readRequestJson(request) {
101993
102462
  */
101994
102463
  function resolveEnabledModelIds$1(stored, available, enabled = true) {
101995
102464
  if (!enabled) return [];
101996
- const shippedDefaults = new Set(DEFAULT_VISIBLE_MODEL_IDS$1);
101997
- if (stored.length > 0 && stored.length === shippedDefaults.size && stored.every((id) => shippedDefaults.has(id))) return [...available];
102465
+ if (isUntouchedModelSelection(stored)) return [...available];
101998
102466
  const known = new Set(available);
101999
102467
  return stored.filter((id) => known.has(id));
102000
102468
  }
@@ -103039,6 +103507,7 @@ const Config = z$1.object({
103039
103507
  ]).default("auto").volatile().description("进展更新的输出语言:auto 跟随对话语言,zh 中文,en 英文。"),
103040
103508
  planModePipeline: z$1.boolean().default(true).volatile().description("启用计划模式管线:允许调度模式在澄清访谈收敛后用 enter_plan_mode 进入计划模式,放弃规划时用 leave_plan_mode 退出。"),
103041
103509
  dispatchExecutionGuard: z$1.boolean().default(true).volatile().description("启用调度执行守卫:在执行期拦截子代理越权调用主控专用工具,并拦截等待子代理期间的纯 sleep 空等命令;不修改工具声明面,零损耗保护 Kimi 缓存。"),
103510
+ teamPolicyCacheGuard: z$1.boolean().default(true).volatile().description("把内置 Agent Teams 的 team:policy 段从 system prompt 摘掉,改为开关切换时以尾部用户消息投递一次,避免中途开团队协作时刷掉整段前缀缓存;默认开启。"),
103042
103511
  cacheWarmup: z$1.boolean().default(true).volatile().description(CACHE_WARMUP_SETTING_DESCRIPTION),
103043
103512
  cacheWarmupRetrieval: z$1.boolean().default(true).volatile().description(CACHE_WARMUP_RETRIEVAL_SETTING_DESCRIPTION),
103044
103513
  cacheWaitWarmup: z$1.boolean().default(true).volatile().description(CACHE_WAIT_WARMUP_SETTING_DESCRIPTION),
@@ -103107,6 +103576,10 @@ function apply(ctx, pluginConfig = {}) {
103107
103576
  const guard = installPtcSdkCacheGuard(ctx);
103108
103577
  return () => guard.dispose();
103109
103578
  }, "dsh-chatgpt-subscription: PTC SDK cache guard");
103579
+ ctx.effect(() => {
103580
+ const guard = installTeamPolicyCacheGuard(ctx, { enabled: () => resolveTeamPolicyCacheGuardEnabled(pluginConfig) });
103581
+ return () => guard.dispose();
103582
+ }, "dsh-chatgpt-subscription: team policy cache guard");
103110
103583
  ctx.effect(() => {
103111
103584
  const guard = installCompactionPolicyGuard(ctx);
103112
103585
  return () => guard.dispose();