claudish 7.67.1 → 8.1.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 (2) hide show
  1. package/dist/index.js +612 -393
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -731,7 +731,7 @@ var init_onepassword_config = __esm(() => {
731
731
  });
732
732
 
733
733
  // src/version.ts
734
- var VERSION = "7.67.1";
734
+ var VERSION = "8.1.0";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -27458,6 +27458,271 @@ var init_stdio2 = __esm(() => {
27458
27458
  init_stdio();
27459
27459
  });
27460
27460
 
27461
+ // src/providers/all-models-cache.ts
27462
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
27463
+ import { homedir as homedir7 } from "os";
27464
+ import { dirname as dirname4, join as join7 } from "path";
27465
+ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
27466
+ if (!existsSync5(path))
27467
+ return null;
27468
+ let raw;
27469
+ try {
27470
+ raw = JSON.parse(readFileSync5(path, "utf-8"));
27471
+ } catch {
27472
+ return null;
27473
+ }
27474
+ if (!raw || typeof raw !== "object")
27475
+ return null;
27476
+ const data = raw;
27477
+ const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
27478
+ const models = Array.isArray(data.models) ? data.models : [];
27479
+ const entries = Array.isArray(data.entries) ? data.entries : [];
27480
+ return {
27481
+ version: 2,
27482
+ lastUpdated,
27483
+ entries,
27484
+ models
27485
+ };
27486
+ }
27487
+ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
27488
+ const existing = readAllModelsCache(path);
27489
+ const merged = {
27490
+ version: 2,
27491
+ lastUpdated: data.lastUpdated ?? new Date().toISOString(),
27492
+ entries: data.entries ?? existing?.entries ?? [],
27493
+ models: data.models ?? existing?.models ?? []
27494
+ };
27495
+ mkdirSync5(dirname4(path), { recursive: true });
27496
+ writeFileSync5(path, JSON.stringify(merged), "utf-8");
27497
+ }
27498
+ var ALL_MODELS_CACHE_PATH;
27499
+ var init_all_models_cache = __esm(() => {
27500
+ ALL_MODELS_CACHE_PATH = join7(homedir7(), ".claudish", "all-models.json");
27501
+ });
27502
+
27503
+ // src/providers/model-ordering.ts
27504
+ function extractVersionParts(modelId) {
27505
+ const tokens = modelId.toLowerCase().split(/[\/_-]+/);
27506
+ let started = false;
27507
+ const parts = [];
27508
+ for (const token of tokens) {
27509
+ const match = token.match(/\d+(?:\.\d+)*/);
27510
+ if (!match) {
27511
+ if (started)
27512
+ break;
27513
+ continue;
27514
+ }
27515
+ if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
27516
+ continue;
27517
+ }
27518
+ if (!started) {
27519
+ started = true;
27520
+ for (const part of match[0].split(".")) {
27521
+ parts.push(Number.parseInt(part, 10));
27522
+ }
27523
+ if (!/^\d+(?:\.\d+)*$/.test(token)) {
27524
+ break;
27525
+ }
27526
+ continue;
27527
+ }
27528
+ if (!/^\d{1,2}(?:\.\d+)?$/.test(token)) {
27529
+ break;
27530
+ }
27531
+ for (const part of token.split(".")) {
27532
+ parts.push(Number.parseInt(part, 10));
27533
+ }
27534
+ }
27535
+ return parts;
27536
+ }
27537
+ function compareVersionPartsDesc(a, b) {
27538
+ const maxLength = Math.max(a.length, b.length);
27539
+ for (let i = 0;i < maxLength; i++) {
27540
+ const aPart = a[i] ?? -1;
27541
+ const bPart = b[i] ?? -1;
27542
+ if (aPart !== bPart) {
27543
+ return bPart - aPart;
27544
+ }
27545
+ }
27546
+ return 0;
27547
+ }
27548
+ function compareByReleaseDateDesc(a, b) {
27549
+ const aReleaseRaw = a.releaseDate ? Date.parse(a.releaseDate) : 0;
27550
+ const bReleaseRaw = b.releaseDate ? Date.parse(b.releaseDate) : 0;
27551
+ const aRelease = Number.isNaN(aReleaseRaw) ? 0 : aReleaseRaw;
27552
+ const bRelease = Number.isNaN(bReleaseRaw) ? 0 : bReleaseRaw;
27553
+ if (aRelease !== bRelease) {
27554
+ return bRelease - aRelease;
27555
+ }
27556
+ const aId = a.id ?? a.modelId ?? "";
27557
+ const bId = b.id ?? b.modelId ?? "";
27558
+ const versionCompare = compareVersionPartsDesc(extractVersionParts(aId), extractVersionParts(bId));
27559
+ if (versionCompare !== 0) {
27560
+ return versionCompare;
27561
+ }
27562
+ return aId.localeCompare(bId);
27563
+ }
27564
+
27565
+ // src/adapters/model-catalog.ts
27566
+ function lookupModel(modelId, cachePath) {
27567
+ const entry = findCacheEntry(modelId, cachePath);
27568
+ if (!entry || entry.contextWindow === undefined)
27569
+ return;
27570
+ return {
27571
+ modelId: entry.modelId,
27572
+ contextWindow: entry.contextWindow,
27573
+ supportsVision: entry.supportsVision,
27574
+ releaseDate: entry.releaseDate
27575
+ };
27576
+ }
27577
+ function lookupModelReasoning(modelId, cachePath) {
27578
+ return findCacheEntry(modelId, cachePath)?.reasoning;
27579
+ }
27580
+ function lookupModelTokenParam(modelId, cachePath) {
27581
+ return findCacheEntry(modelId, cachePath)?.tokenParam;
27582
+ }
27583
+ function lookupRouteReasoningMode(modelId, provider, cachePath) {
27584
+ return findCacheEntry(modelId, cachePath)?.aggregators?.find((aggregator) => aggregator.provider === provider)?.reasoning?.mode;
27585
+ }
27586
+ function lookupFamilyDefaultVariant(familyId, provider, cachePath) {
27587
+ const cache = readAllModelsCache(cachePath);
27588
+ if (!cache)
27589
+ return;
27590
+ for (const entry of cache.entries) {
27591
+ const rv = entry.routeVariant;
27592
+ if (!rv?.isDefault)
27593
+ continue;
27594
+ if (rv.provider !== provider)
27595
+ continue;
27596
+ if (rv.familyId === familyId || rv.baseModelId === familyId)
27597
+ return entry.modelId;
27598
+ }
27599
+ return;
27600
+ }
27601
+ function lookupVariantPresets(baseModelId, provider, cachePath) {
27602
+ const cache = readAllModelsCache(cachePath);
27603
+ if (!cache)
27604
+ return [];
27605
+ const wanted = stripVendorPrefix(baseModelId.toLowerCase());
27606
+ const found = [];
27607
+ for (const entry of cache.entries) {
27608
+ const rv = entry.routeVariant;
27609
+ if (!rv?.preset || !rv.baseModelId)
27610
+ continue;
27611
+ if (stripVendorPrefix(rv.baseModelId.toLowerCase()) !== wanted)
27612
+ continue;
27613
+ if (provider !== undefined && rv.provider !== provider)
27614
+ continue;
27615
+ found.push({ modelId: entry.modelId, preset: rv.preset, provider: rv.provider });
27616
+ }
27617
+ return found;
27618
+ }
27619
+ function lookupModelCapabilities(modelId, cachePath) {
27620
+ const entry = findCacheEntry(modelId, cachePath);
27621
+ if (!entry)
27622
+ return;
27623
+ return { supportsTools: entry.supportsTools, supportsThinking: entry.supportsThinking };
27624
+ }
27625
+ function lookupModelForProvider(modelId, provider, cachePath) {
27626
+ const entry = findCacheEntry(modelId, cachePath);
27627
+ if (!entry)
27628
+ return;
27629
+ return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
27630
+ }
27631
+ function resolveSubscriptionRouting(modelId, provider, cachePath) {
27632
+ const entry = findCacheEntry(modelId, cachePath);
27633
+ if (!entry)
27634
+ return { kind: "unknown" };
27635
+ if (entry.subscriptionPlans?.includes(provider)) {
27636
+ const agg = entry.aggregators?.find((a) => a.provider === provider);
27637
+ return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
27638
+ }
27639
+ return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
27640
+ }
27641
+ function isSubscriptionPlan(provider, cachePath) {
27642
+ const cache = readAllModelsCache(cachePath);
27643
+ if (!cache)
27644
+ return false;
27645
+ return cache.entries.some((e) => e.subscriptionPlans?.includes(provider));
27646
+ }
27647
+ function stripVendorPrefix(lowerId) {
27648
+ return lowerId.includes("/") ? lowerId.substring(lowerId.lastIndexOf("/") + 1) : lowerId;
27649
+ }
27650
+ function findCacheEntry(modelId, cachePath) {
27651
+ if (modelId.includes("@")) {
27652
+ throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
27653
+ }
27654
+ const cache = readAllModelsCache(cachePath);
27655
+ if (!cache || cache.entries.length === 0)
27656
+ return;
27657
+ const lower = modelId.toLowerCase();
27658
+ const unprefixed = stripVendorPrefix(lower);
27659
+ for (const entry of cache.entries) {
27660
+ const entryId = entry.modelId.toLowerCase();
27661
+ const exactMatch = entryId === unprefixed || entryId === lower;
27662
+ const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
27663
+ if (exactMatch || aliasMatch) {
27664
+ return entry;
27665
+ }
27666
+ }
27667
+ return;
27668
+ }
27669
+ function classifyCatalogHit(entry, q) {
27670
+ const id = entry.modelId.toLowerCase();
27671
+ if (id === q || stripVendorPrefix(id) === q)
27672
+ return { bucket: "exact" };
27673
+ const aliases = entry.aliases ?? [];
27674
+ const exactAlias = aliases.find((a) => a.toLowerCase() === q || stripVendorPrefix(a.toLowerCase()) === q);
27675
+ if (exactAlias)
27676
+ return { bucket: "exact", matchedAlias: exactAlias };
27677
+ if (id.includes(q))
27678
+ return { bucket: "id" };
27679
+ const partialAlias = aliases.find((a) => a.toLowerCase().includes(q));
27680
+ return partialAlias ? { bucket: "alias", matchedAlias: partialAlias } : undefined;
27681
+ }
27682
+ function searchCatalogModels(query, limit = 10, cachePath) {
27683
+ const q = query.trim().toLowerCase();
27684
+ if (!q)
27685
+ return [];
27686
+ const cache = readAllModelsCache(cachePath);
27687
+ if (!cache || cache.entries.length === 0)
27688
+ return [];
27689
+ const exact = [];
27690
+ const idPartial = [];
27691
+ const aliasPartial = [];
27692
+ for (const entry of cache.entries) {
27693
+ const hit = classifyCatalogHit(entry, q);
27694
+ if (!hit)
27695
+ continue;
27696
+ const ranked = {
27697
+ entry,
27698
+ match: {
27699
+ modelId: entry.modelId,
27700
+ aliases: entry.aliases ?? [],
27701
+ subscriptionPlans: entry.subscriptionPlans ?? [],
27702
+ ...hit.matchedAlias ? { matchedAlias: hit.matchedAlias } : {}
27703
+ }
27704
+ };
27705
+ if (hit.bucket === "exact")
27706
+ exact.push(ranked);
27707
+ else if (hit.bucket === "id")
27708
+ idPartial.push(ranked);
27709
+ else
27710
+ aliasPartial.push(ranked);
27711
+ }
27712
+ const byRelevance = (a, b) => {
27713
+ const lengthDelta = a.entry.modelId.length - b.entry.modelId.length;
27714
+ if (lengthDelta !== 0)
27715
+ return lengthDelta;
27716
+ return compareByReleaseDateDesc(a.entry, b.entry);
27717
+ };
27718
+ idPartial.sort(byRelevance);
27719
+ aliasPartial.sort(byRelevance);
27720
+ return [...exact, ...idPartial, ...aliasPartial].slice(0, limit).map((r) => r.match);
27721
+ }
27722
+ var init_model_catalog = __esm(() => {
27723
+ init_all_models_cache();
27724
+ });
27725
+
27461
27726
  // src/agent-availability.ts
27462
27727
  import { spawn } from "child_process";
27463
27728
  function parseAvailableAgents(output) {
@@ -27574,26 +27839,26 @@ __export(exports_profile_config, {
27574
27839
  setKeychainEnabled: () => setKeychainEnabled,
27575
27840
  setProfile: () => setProfile
27576
27841
  });
27577
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
27578
- import { homedir as homedir7 } from "os";
27579
- import { dirname as dirname4, join as join7, parse as parse6 } from "path";
27842
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
27843
+ import { homedir as homedir8 } from "os";
27844
+ import { dirname as dirname5, join as join8, parse as parse6 } from "path";
27580
27845
  function activeConfigFile() {
27581
27846
  return activeGlobalConfigFile(CONFIG_FILE);
27582
27847
  }
27583
27848
  function ensureConfigDir() {
27584
- if (!existsSync5(CONFIG_DIR)) {
27585
- mkdirSync5(CONFIG_DIR, { recursive: true });
27849
+ if (!existsSync6(CONFIG_DIR)) {
27850
+ mkdirSync6(CONFIG_DIR, { recursive: true });
27586
27851
  }
27587
27852
  }
27588
27853
  function loadConfig() {
27589
27854
  const activeFile = activeConfigFile();
27590
27855
  if (!getConfigFileOverride())
27591
27856
  ensureConfigDir();
27592
- if (!existsSync5(activeFile)) {
27857
+ if (!existsSync6(activeFile)) {
27593
27858
  return { ...DEFAULT_CONFIG };
27594
27859
  }
27595
27860
  try {
27596
- const content = readFileSync5(activeFile, "utf-8");
27861
+ const content = readFileSync6(activeFile, "utf-8");
27597
27862
  const config2 = JSON.parse(content);
27598
27863
  const merged = {
27599
27864
  version: config2.version || DEFAULT_CONFIG.version,
@@ -27663,39 +27928,39 @@ function loadConfig() {
27663
27928
  function saveConfig(config2) {
27664
27929
  if (!getConfigFileOverride())
27665
27930
  ensureConfigDir();
27666
- writeFileSync5(activeConfigFile(), JSON.stringify(config2, null, 2), "utf-8");
27931
+ writeFileSync6(activeConfigFile(), JSON.stringify(config2, null, 2), "utf-8");
27667
27932
  }
27668
27933
  function configExists() {
27669
- return existsSync5(CONFIG_FILE);
27934
+ return existsSync6(CONFIG_FILE);
27670
27935
  }
27671
27936
  function getConfigPath() {
27672
27937
  return CONFIG_FILE;
27673
27938
  }
27674
27939
  function getLocalConfigPath() {
27675
- const home = homedir7();
27940
+ const home = homedir8();
27676
27941
  let dir = process.cwd();
27677
27942
  const root = parse6(dir).root;
27678
27943
  while (dir !== root && dir !== home) {
27679
- const candidate = join7(dir, LOCAL_CONFIG_FILENAME);
27680
- if (existsSync5(candidate))
27944
+ const candidate = join8(dir, LOCAL_CONFIG_FILENAME);
27945
+ if (existsSync6(candidate))
27681
27946
  return candidate;
27682
- if (existsSync5(join7(dir, ".git"))) {
27947
+ if (existsSync6(join8(dir, ".git"))) {
27683
27948
  return candidate;
27684
27949
  }
27685
- dir = dirname4(dir);
27950
+ dir = dirname5(dir);
27686
27951
  }
27687
- return join7(process.cwd(), LOCAL_CONFIG_FILENAME);
27952
+ return join8(process.cwd(), LOCAL_CONFIG_FILENAME);
27688
27953
  }
27689
27954
  function localConfigExists() {
27690
- return existsSync5(getLocalConfigPath());
27955
+ return existsSync6(getLocalConfigPath());
27691
27956
  }
27692
27957
  function readProOnUltracode(paths = defaultScopedConfigPaths) {
27693
27958
  for (const pathFn of [paths.project, paths.global]) {
27694
27959
  try {
27695
27960
  const path = pathFn();
27696
- if (!existsSync5(path))
27961
+ if (!existsSync6(path))
27697
27962
  continue;
27698
- const parsed = JSON.parse(readFileSync5(path, "utf-8"));
27963
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
27699
27964
  if (typeof parsed?.proOnUltracode === "boolean")
27700
27965
  return parsed.proOnUltracode;
27701
27966
  } catch {}
@@ -27704,17 +27969,17 @@ function readProOnUltracode(paths = defaultScopedConfigPaths) {
27704
27969
  }
27705
27970
  function isProjectDirectory() {
27706
27971
  const cwd = process.cwd();
27707
- return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync5(join7(cwd, f)));
27972
+ return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync6(join8(cwd, f)));
27708
27973
  }
27709
27974
  function loadLocalConfig() {
27710
27975
  if (getConfigFileOverride())
27711
27976
  return null;
27712
27977
  const localPath = getLocalConfigPath();
27713
- if (!existsSync5(localPath)) {
27978
+ if (!existsSync6(localPath)) {
27714
27979
  return null;
27715
27980
  }
27716
27981
  try {
27717
- const content = readFileSync5(localPath, "utf-8");
27982
+ const content = readFileSync6(localPath, "utf-8");
27718
27983
  const config2 = JSON.parse(content);
27719
27984
  return {
27720
27985
  ...config2,
@@ -27732,7 +27997,7 @@ function saveLocalConfig(config2) {
27732
27997
  if (toWrite.routing !== undefined && Object.keys(toWrite.routing).length === 0) {
27733
27998
  delete toWrite.routing;
27734
27999
  }
27735
- writeFileSync5(getLocalConfigPath(), JSON.stringify(toWrite, null, 2), "utf-8");
28000
+ writeFileSync6(getLocalConfigPath(), JSON.stringify(toWrite, null, 2), "utf-8");
27736
28001
  }
27737
28002
  function loadConfigForScope(scope) {
27738
28003
  if (scope === "local") {
@@ -27987,8 +28252,8 @@ function disableLocalProvider(providerName) {
27987
28252
  }
27988
28253
  var CONFIG_DIR, CONFIG_FILE, LOCAL_CONFIG_FILENAME = ".claudish.json", DEFAULT_CONFIG, defaultScopedConfigPaths;
27989
28254
  var init_profile_config = __esm(() => {
27990
- CONFIG_DIR = join7(homedir7(), ".claudish");
27991
- CONFIG_FILE = join7(CONFIG_DIR, "config.json");
28255
+ CONFIG_DIR = join8(homedir8(), ".claudish");
28256
+ CONFIG_FILE = join8(CONFIG_DIR, "config.json");
27992
28257
  DEFAULT_CONFIG = {
27993
28258
  version: "1.0.0",
27994
28259
  defaultProfile: "default",
@@ -28095,153 +28360,6 @@ var init_remote_provider_types = __esm(() => {
28095
28360
  };
28096
28361
  });
28097
28362
 
28098
- // src/providers/all-models-cache.ts
28099
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
28100
- import { homedir as homedir8 } from "os";
28101
- import { dirname as dirname5, join as join8 } from "path";
28102
- function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
28103
- if (!existsSync6(path))
28104
- return null;
28105
- let raw;
28106
- try {
28107
- raw = JSON.parse(readFileSync6(path, "utf-8"));
28108
- } catch {
28109
- return null;
28110
- }
28111
- if (!raw || typeof raw !== "object")
28112
- return null;
28113
- const data = raw;
28114
- const lastUpdated = typeof data.lastUpdated === "string" ? data.lastUpdated : new Date(0).toISOString();
28115
- const models = Array.isArray(data.models) ? data.models : [];
28116
- const entries = Array.isArray(data.entries) ? data.entries : [];
28117
- return {
28118
- version: 2,
28119
- lastUpdated,
28120
- entries,
28121
- models
28122
- };
28123
- }
28124
- function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
28125
- const existing = readAllModelsCache(path);
28126
- const merged = {
28127
- version: 2,
28128
- lastUpdated: data.lastUpdated ?? new Date().toISOString(),
28129
- entries: data.entries ?? existing?.entries ?? [],
28130
- models: data.models ?? existing?.models ?? []
28131
- };
28132
- mkdirSync6(dirname5(path), { recursive: true });
28133
- writeFileSync6(path, JSON.stringify(merged), "utf-8");
28134
- }
28135
- var ALL_MODELS_CACHE_PATH;
28136
- var init_all_models_cache = __esm(() => {
28137
- ALL_MODELS_CACHE_PATH = join8(homedir8(), ".claudish", "all-models.json");
28138
- });
28139
-
28140
- // src/adapters/model-catalog.ts
28141
- function lookupModel(modelId, cachePath) {
28142
- const entry = findCacheEntry(modelId, cachePath);
28143
- if (!entry || entry.contextWindow === undefined)
28144
- return;
28145
- return {
28146
- modelId: entry.modelId,
28147
- contextWindow: entry.contextWindow,
28148
- supportsVision: entry.supportsVision,
28149
- releaseDate: entry.releaseDate
28150
- };
28151
- }
28152
- function lookupModelReasoning(modelId, cachePath) {
28153
- return findCacheEntry(modelId, cachePath)?.reasoning;
28154
- }
28155
- function lookupModelTokenParam(modelId, cachePath) {
28156
- return findCacheEntry(modelId, cachePath)?.tokenParam;
28157
- }
28158
- function lookupFamilyDefaultVariant(familyId, provider, cachePath) {
28159
- const cache2 = readAllModelsCache(cachePath);
28160
- if (!cache2)
28161
- return;
28162
- for (const entry of cache2.entries) {
28163
- const rv = entry.routeVariant;
28164
- if (!rv?.isDefault)
28165
- continue;
28166
- if (rv.provider !== provider)
28167
- continue;
28168
- if (rv.familyId === familyId || rv.baseModelId === familyId)
28169
- return entry.modelId;
28170
- }
28171
- return;
28172
- }
28173
- function lookupVariantPresets(baseModelId, provider, cachePath) {
28174
- const cache2 = readAllModelsCache(cachePath);
28175
- if (!cache2)
28176
- return [];
28177
- const wanted = stripVendorPrefix(baseModelId.toLowerCase());
28178
- const found = [];
28179
- for (const entry of cache2.entries) {
28180
- const rv = entry.routeVariant;
28181
- if (!rv?.preset || !rv.baseModelId)
28182
- continue;
28183
- if (stripVendorPrefix(rv.baseModelId.toLowerCase()) !== wanted)
28184
- continue;
28185
- if (provider !== undefined && rv.provider !== provider)
28186
- continue;
28187
- found.push({ modelId: entry.modelId, preset: rv.preset, provider: rv.provider });
28188
- }
28189
- return found;
28190
- }
28191
- function lookupModelCapabilities(modelId, cachePath) {
28192
- const entry = findCacheEntry(modelId, cachePath);
28193
- if (!entry)
28194
- return;
28195
- return { supportsTools: entry.supportsTools, supportsThinking: entry.supportsThinking };
28196
- }
28197
- function lookupModelForProvider(modelId, provider, cachePath) {
28198
- const entry = findCacheEntry(modelId, cachePath);
28199
- if (!entry)
28200
- return;
28201
- return entry.aggregators?.find((a) => a.provider === provider)?.contextWindow ?? entry.contextWindow;
28202
- }
28203
- function resolveSubscriptionRouting(modelId, provider, cachePath) {
28204
- const entry = findCacheEntry(modelId, cachePath);
28205
- if (!entry)
28206
- return { kind: "unknown" };
28207
- if (entry.subscriptionPlans?.includes(provider)) {
28208
- const agg = entry.aggregators?.find((a) => a.provider === provider);
28209
- return agg?.externalId ? { kind: "serves", externalId: agg.externalId } : { kind: "unknown" };
28210
- }
28211
- return isSubscriptionPlan(provider, cachePath) ? { kind: "not-served" } : { kind: "unknown" };
28212
- }
28213
- function isSubscriptionPlan(provider, cachePath) {
28214
- const cache2 = readAllModelsCache(cachePath);
28215
- if (!cache2)
28216
- return false;
28217
- return cache2.entries.some((e) => e.subscriptionPlans?.includes(provider));
28218
- }
28219
- function stripVendorPrefix(lowerId) {
28220
- return lowerId.includes("/") ? lowerId.substring(lowerId.lastIndexOf("/") + 1) : lowerId;
28221
- }
28222
- function findCacheEntry(modelId, cachePath) {
28223
- if (modelId.includes("@")) {
28224
- throw new Error(`model-catalog lookup received provider-routed ID "${modelId}" \u2014 callers must strip the "@" prefix before calling`);
28225
- }
28226
- const cache2 = readAllModelsCache(cachePath);
28227
- if (!cache2 || cache2.entries.length === 0)
28228
- return;
28229
- const lower = modelId.toLowerCase();
28230
- const unprefixed = stripVendorPrefix(lower);
28231
- for (const entry of cache2.entries) {
28232
- const entryId = entry.modelId.toLowerCase();
28233
- const exactMatch = entryId === unprefixed || entryId === lower;
28234
- const aliasMatch = entry.aliases?.some((a) => a.toLowerCase() === unprefixed || a.toLowerCase() === lower);
28235
- if (exactMatch || aliasMatch) {
28236
- return entry;
28237
- }
28238
- }
28239
- return;
28240
- }
28241
- var init_model_catalog = __esm(() => {
28242
- init_all_models_cache();
28243
- });
28244
-
28245
28363
  // src/adapters/tool-name-utils.ts
28246
28364
  function hashToolName(name) {
28247
28365
  let h1 = 3735928559;
@@ -39642,6 +39760,18 @@ var init_session_events = __esm(() => {
39642
39760
 
39643
39761
  // src/session-events/pro-injection.ts
39644
39762
  function resolveVariantPreset(bareModelName, provider, cachePath) {
39763
+ const routeMode = lookupRouteReasoningMode(bareModelName, provider, cachePath);
39764
+ if (routeMode) {
39765
+ if (routeMode.status !== "supported" || !routeMode.values.includes("pro")) {
39766
+ return;
39767
+ }
39768
+ return {
39769
+ params: { reasoning: { mode: "pro" } },
39770
+ provider,
39771
+ preset: "reasoning.mode=pro",
39772
+ sourceLabel: `route capability @ ${provider}`
39773
+ };
39774
+ }
39645
39775
  for (const variant of lookupVariantPresets(bareModelName, provider, cachePath)) {
39646
39776
  try {
39647
39777
  const params = parseModelParams(variant.preset);
@@ -39649,9 +39779,9 @@ function resolveVariantPreset(bareModelName, provider, cachePath) {
39649
39779
  continue;
39650
39780
  return {
39651
39781
  params,
39652
- variantModelId: variant.modelId,
39653
39782
  provider: variant.provider,
39654
- preset: variant.preset
39783
+ preset: variant.preset,
39784
+ sourceLabel: `variant ${variant.modelId} @ ${variant.provider}`
39655
39785
  };
39656
39786
  } catch {}
39657
39787
  }
@@ -39675,7 +39805,7 @@ function applyProInjection(requestPayload, opts) {
39675
39805
  if (!resolved)
39676
39806
  return false;
39677
39807
  deepMergeParams(requestPayload, resolved.params);
39678
- log(`[SessionEvents] ultracode active \u2192 preset ${resolved.preset} for ${opts.targetModel} ` + `(catalog variant ${resolved.variantModelId} @ ${resolved.provider}, session ${opts.sessionId})`);
39808
+ log(`[SessionEvents] ultracode active \u2192 preset ${resolved.preset} for ${opts.targetModel} ` + `(catalog ${resolved.sourceLabel}, session ${opts.sessionId})`);
39679
39809
  return true;
39680
39810
  } catch {
39681
39811
  return false;
@@ -48403,10 +48533,12 @@ var init_team_stream_capture = __esm(() => {
48403
48533
  });
48404
48534
 
48405
48535
  // src/channel/stream-json-reducer.ts
48406
- function reachesAnswer(parsedType8, isJson) {
48536
+ function reachesAnswer(parsedType8, isJson, keepUnrecognizedJson) {
48407
48537
  if (!isJson)
48408
48538
  return true;
48409
- return parsedType8 !== null && STREAM_JSON_EVENT_TYPES.has(parsedType8);
48539
+ if (parsedType8 !== null && STREAM_JSON_EVENT_TYPES.has(parsedType8))
48540
+ return true;
48541
+ return keepUnrecognizedJson;
48410
48542
  }
48411
48543
  function labelFor(type, subtype) {
48412
48544
  if (type === null)
@@ -48478,6 +48610,9 @@ class StreamJsonReducer {
48478
48610
  get toolUseCount() {
48479
48611
  return this._toolUseCount;
48480
48612
  }
48613
+ get idleMs() {
48614
+ return Math.max(0, Date.now() - this.lastFrameAt);
48615
+ }
48481
48616
  get terminalReason() {
48482
48617
  return this._terminalReason;
48483
48618
  }
@@ -48570,7 +48705,7 @@ class StreamJsonReducer {
48570
48705
  this.opts.onSemanticLine?.(line, labelFor(type, frame ? asString(frame.subtype) : null));
48571
48706
  if (frame && type !== null)
48572
48707
  this.applyFrame(frame, type);
48573
- if (!reachesAnswer(type, frame !== null))
48708
+ if (!reachesAnswer(type, frame !== null, this.opts.keepUnrecognizedJson ?? false))
48574
48709
  return "";
48575
48710
  const prose = this.capture.write(terminated ? `${line}
48576
48711
  ` : line);
@@ -49319,6 +49454,16 @@ function resolveClaudishSpawn(env = process.env) {
49319
49454
  }
49320
49455
  var CLAUDISH_BIN_ENV = "CLAUDISH_BIN";
49321
49456
 
49457
+ // src/stdio-decode.ts
49458
+ import { StringDecoder } from "string_decoder";
49459
+ function decodeChunk(decoder, chunk) {
49460
+ return typeof chunk === "string" ? chunk : decoder.write(chunk);
49461
+ }
49462
+ function newStdioDecoder() {
49463
+ return new StringDecoder("utf8");
49464
+ }
49465
+ var init_stdio_decode = () => {};
49466
+
49322
49467
  // src/team-stats.ts
49323
49468
  import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
49324
49469
  import { join as join28 } from "path";
@@ -49494,22 +49639,25 @@ var init_team_stats = () => {};
49494
49639
  var exports_team_orchestrator = {};
49495
49640
  __export(exports_team_orchestrator, {
49496
49641
  DEFAULT_MIN_OUTPUT_BYTES: () => DEFAULT_MIN_OUTPUT_BYTES,
49497
- DEFAULT_STALL_SECONDS: () => DEFAULT_STALL_SECONDS2,
49498
- DRAIN_TIMEOUT_MS: () => DRAIN_TIMEOUT_MS,
49499
- GRACE_INTERVAL_MS: () => GRACE_INTERVAL_MS,
49500
49642
  STDOUT_TAIL_LIMIT: () => STDOUT_TAIL_LIMIT,
49501
49643
  TEAM_CAPTURE_ENV_VAR: () => TEAM_CAPTURE_ENV_VAR,
49502
49644
  aggregateVerdict: () => aggregateVerdict,
49503
49645
  buildJudgePrompt: () => buildJudgePrompt,
49646
+ cancelTeamRun: () => cancelTeamRun,
49504
49647
  classifyRunOutput: () => classifyRunOutput,
49505
49648
  fisherYatesShuffle: () => fisherYatesShuffle,
49506
49649
  getStatus: () => getStatus,
49507
49650
  judgeResponses: () => judgeResponses,
49508
49651
  meaningfulStderr: () => meaningfulStderr,
49509
49652
  parseJudgeVotes: () => parseJudgeVotes,
49653
+ readTeamInputFile: () => readTeamInputFile,
49510
49654
  resolveCaptureMode: () => resolveCaptureMode,
49511
49655
  runModels: () => runModels,
49512
49656
  setupSession: () => setupSession,
49657
+ shutdownAllTeamRuns: () => shutdownAllTeamRuns,
49658
+ startModels: () => startModels,
49659
+ teamSlotActivity: () => teamSlotActivity,
49660
+ teamSlotIdleSeconds: () => teamSlotIdleSeconds,
49513
49661
  validateSessionPath: () => validateSessionPath
49514
49662
  });
49515
49663
  import { spawn as spawn2 } from "child_process";
@@ -49521,12 +49669,57 @@ import {
49521
49669
  readdirSync as readdirSync5,
49522
49670
  writeFileSync as writeFileSync11
49523
49671
  } from "fs";
49524
- import { join as join29, resolve as resolve3 } from "path";
49672
+ import { basename as basename2, join as join29, resolve as resolve3 } from "path";
49525
49673
  function resolveCaptureMode(explicit, env = process.env) {
49526
49674
  if (explicit)
49527
49675
  return explicit;
49528
49676
  return env[TEAM_CAPTURE_ENV_VAR]?.trim().toLowerCase() === "print" ? "print" : "stream-json";
49529
49677
  }
49678
+ function teamSlotIdleSeconds(teamSessionId) {
49679
+ const run = liveTeamRuns.get(teamSessionId);
49680
+ if (!run)
49681
+ return null;
49682
+ const out = {};
49683
+ for (const slotId of run.processes.keys()) {
49684
+ const idle = run.idleMsFor(slotId);
49685
+ if (idle !== null)
49686
+ out[slotId] = Math.round(idle / 1000);
49687
+ }
49688
+ return out;
49689
+ }
49690
+ function teamSlotActivity(teamSessionId) {
49691
+ const run = liveTeamRuns.get(teamSessionId);
49692
+ if (!run)
49693
+ return null;
49694
+ const out = {};
49695
+ for (const slotId of run.processes.keys()) {
49696
+ const activity = run.activityFor(slotId);
49697
+ if (activity !== null)
49698
+ out[slotId] = activity;
49699
+ }
49700
+ return out;
49701
+ }
49702
+ async function cancelTeamRun(teamSessionId, slotId) {
49703
+ const run = liveTeamRuns.get(teamSessionId);
49704
+ if (!run)
49705
+ return { found: false, cancelled: [] };
49706
+ const targets = slotId ? run.processes.has(slotId) ? [slotId] : [] : [...run.processes.keys()];
49707
+ const cancelled = [];
49708
+ for (const id of targets) {
49709
+ const proc = run.processes.get(id);
49710
+ if (!proc)
49711
+ continue;
49712
+ run.cancelledSlots.add(id);
49713
+ await terminateChildTree(proc);
49714
+ cancelled.push(id);
49715
+ }
49716
+ return { found: true, cancelled };
49717
+ }
49718
+ async function shutdownAllTeamRuns() {
49719
+ await Promise.all([...liveTeamRuns.keys()].map((id) => cancelTeamRun(id).catch(() => {
49720
+ return;
49721
+ })));
49722
+ }
49530
49723
  function classifyRunOutput(opts) {
49531
49724
  const {
49532
49725
  outputSize,
@@ -49606,6 +49799,21 @@ function validateSessionPath(sessionPath) {
49606
49799
  }
49607
49800
  return resolved;
49608
49801
  }
49802
+ function readTeamInputFile(inputPath) {
49803
+ const resolved = resolve3(inputPath);
49804
+ const cwd = process.cwd();
49805
+ if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
49806
+ throw new Error(`Input file must be within current directory: ${inputPath}`);
49807
+ }
49808
+ if (!existsSync20(resolved)) {
49809
+ throw new Error(`Input file not found: ${resolved}`);
49810
+ }
49811
+ const text = readFileSync19(resolved, "utf-8");
49812
+ if (text.trim().length === 0) {
49813
+ throw new Error(`Input file is empty: ${resolved}`);
49814
+ }
49815
+ return text;
49816
+ }
49609
49817
  function setupSession(sessionPath, models, input) {
49610
49818
  if (models.length === 0) {
49611
49819
  throw new Error("At least one model is required");
@@ -49672,8 +49880,7 @@ function readFullOutputIfNeeded(opts) {
49672
49880
  return;
49673
49881
  }
49674
49882
  }
49675
- async function runModels(sessionPath, opts = {}) {
49676
- const timeoutMs = (opts.timeout ?? 300) * 1000;
49883
+ async function startModels(sessionPath, opts = {}) {
49677
49884
  assertValidRequirePattern(opts.requirePattern);
49678
49885
  const manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
49679
49886
  const statusPath = join29(sessionPath, "status.json");
@@ -49719,6 +49926,7 @@ async function runModels(sessionPath, opts = {}) {
49719
49926
  mkdirSync11(statsDir(sessionPath), { recursive: true });
49720
49927
  const processes = new Map;
49721
49928
  const runtimes = new Map;
49929
+ const cancelledSlots = new Set;
49722
49930
  const sigintHandler = () => {
49723
49931
  for (const [, proc] of processes) {
49724
49932
  signalProcessTree(proc, "SIGTERM");
@@ -49730,6 +49938,7 @@ async function runModels(sessionPath, opts = {}) {
49730
49938
  for (const [anonId, entry] of Object.entries(manifest.models)) {
49731
49939
  const outputPath = join29(sessionPath, `response-${anonId}.md`);
49732
49940
  const errorLogPath = join29(sessionPath, "errors", `${anonId}.log`);
49941
+ const upstreamErrorLogPath = join29(sessionPath, "errors", `${anonId}-upstream.jsonl`);
49733
49942
  const spawnModel = spawnPlan.pinned.get(entry.model) ?? entry.model;
49734
49943
  const args = [
49735
49944
  "--model",
@@ -49750,21 +49959,37 @@ async function runModels(sessionPath, opts = {}) {
49750
49959
  detached: KILL_PROCESS_GROUP,
49751
49960
  env: {
49752
49961
  ...process.env,
49753
- CLAUDISH_TOKEN_FILE: tokenFileFor(sessionPath, anonId)
49962
+ [ENV.CLAUDISH_TOKEN_FILE]: tokenFileFor(sessionPath, anonId),
49963
+ [UPSTREAM_ERROR_LOG_ENV]: upstreamErrorLogPath
49754
49964
  }
49755
49965
  });
49966
+ let lastOutputAt = Date.now();
49967
+ const stampLiveness = () => {
49968
+ lastOutputAt = Date.now();
49969
+ };
49970
+ const stdoutDecoder = newStdioDecoder();
49971
+ const stderrDecoder = newStdioDecoder();
49972
+ proc.stdout?.on("data", stampLiveness);
49973
+ proc.stderr?.on("data", stampLiveness);
49756
49974
  let byteCount = 0;
49757
49975
  let stdoutTail = "";
49758
49976
  const outputStream = createWriteStream(outputPath);
49759
49977
  let flushPartial = () => {};
49978
+ let reducer = null;
49760
49979
  if (captureMode === "print") {
49761
49980
  proc.stdout?.on("data", (chunk) => {
49762
49981
  byteCount += chunk.length;
49763
- stdoutTail = (stdoutTail + chunk.toString()).slice(-STDOUT_TAIL_LIMIT);
49982
+ stdoutTail = (stdoutTail + decodeChunk(stdoutDecoder, chunk)).slice(-STDOUT_TAIL_LIMIT);
49764
49983
  });
49765
49984
  proc.stdout?.pipe(outputStream);
49766
49985
  } else {
49767
- const capture = createAssistantTextCapture();
49986
+ const slotReducer = new StreamJsonReducer({
49987
+ sessionId: anonId,
49988
+ stallSeconds: 0,
49989
+ keepUnrecognizedJson: true,
49990
+ callback: () => {}
49991
+ });
49992
+ reducer = slotReducer;
49768
49993
  const absorb = (text) => {
49769
49994
  if (text.length === 0)
49770
49995
  return;
@@ -49772,14 +49997,15 @@ async function runModels(sessionPath, opts = {}) {
49772
49997
  stdoutTail = (stdoutTail + text).slice(-STDOUT_TAIL_LIMIT);
49773
49998
  outputStream.write(text);
49774
49999
  };
49775
- proc.stdout?.on("data", (chunk) => absorb(capture.write(chunk.toString())));
49776
- flushPartial = () => absorb(capture.end());
50000
+ proc.stdout?.on("data", (chunk) => absorb(slotReducer.feed(decodeChunk(stdoutDecoder, chunk))));
50001
+ flushPartial = () => absorb(slotReducer.end());
49777
50002
  let captureFinalized = false;
49778
50003
  const finalizeCapture = () => {
49779
50004
  if (captureFinalized)
49780
50005
  return;
49781
50006
  captureFinalized = true;
49782
- absorb(capture.end());
50007
+ absorb(slotReducer.end());
50008
+ slotReducer.dispose();
49783
50009
  outputStream.end();
49784
50010
  };
49785
50011
  proc.stdout?.on("end", finalizeCapture);
@@ -49787,7 +50013,7 @@ async function runModels(sessionPath, opts = {}) {
49787
50013
  }
49788
50014
  let stderr = "";
49789
50015
  proc.stderr?.on("data", (chunk) => {
49790
- stderr += chunk.toString();
50016
+ stderr += decodeChunk(stderrDecoder, chunk);
49791
50017
  });
49792
50018
  const command = `claudish ${args.join(" ")}`;
49793
50019
  runtimes.set(anonId, {
@@ -49796,6 +50022,8 @@ async function runModels(sessionPath, opts = {}) {
49796
50022
  getStderr: () => stderr,
49797
50023
  getStdoutTail: () => stdoutTail,
49798
50024
  getByteCount: () => byteCount,
50025
+ getIdleMs: () => Math.max(0, Date.now() - lastOutputAt),
50026
+ getActivity: () => reducer?.state ?? null,
49799
50027
  flushPartial: () => flushPartial()
49800
50028
  });
49801
50029
  proc.stdin?.write(inputContent);
@@ -49833,8 +50061,9 @@ async function runModels(sessionPath, opts = {}) {
49833
50061
  const failed = crashed || degraded !== null;
49834
50062
  const state = crashed ? "FAILED" : degraded ? "EMPTY" : "COMPLETED";
49835
50063
  if (failed) {
49836
- const reason = crashed ? "nonzero_exit" : degraded.reason;
49837
- const detail = crashed ? `Child exited with code ${exitCode}.` : degraded.detail;
50064
+ const wasCancelled = cancelledSlots.has(anonId);
50065
+ const reason = wasCancelled ? "cancelled" : crashed ? "nonzero_exit" : degraded.reason;
50066
+ const detail = wasCancelled ? `Stopped on the caller's instruction via team(mode:"cancel"). ` + "Whatever the child had written up to that point is in its response file." : crashed ? `Child exited with code ${exitCode}.` : degraded.detail;
49838
50067
  persistErrorLog(errorLogPath, `${state}: ${detail}`, stderr, stdoutTail);
49839
50068
  updateModelStatus(anonId, {
49840
50069
  state,
@@ -49849,6 +50078,7 @@ async function runModels(sessionPath, opts = {}) {
49849
50078
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49850
50079
  stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49851
50080
  errorLogPath,
50081
+ upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
49852
50082
  workDir: sessionPath
49853
50083
  }
49854
50084
  });
@@ -49909,102 +50139,36 @@ async function runModels(sessionPath, opts = {}) {
49909
50139
  emitProgress();
49910
50140
  const progressHandle = setInterval(() => emitProgress("running"), POLL_MS);
49911
50141
  progressHandle.unref?.();
49912
- const graceEnabled = opts.graceExtension ?? true;
49913
- const maxGraceMs = Math.max(0, (opts.maxGraceSeconds ?? timeoutMs / 1000) * 1000);
49914
- const stallMs = Math.max(0, (opts.stallSeconds ?? DEFAULT_STALL_SECONDS2) * 1000);
49915
- const idleMsFor = (id) => {
49916
- const s = readTokenStats(sessionPath, id);
49917
- if (!s || typeof s.updated_at !== "number" || s.updated_at <= 0)
49918
- return null;
49919
- return Math.max(0, Date.now() - s.updated_at);
49920
- };
49921
- const graceStartedAt = new Map;
49922
- const graceUsedMs = (id, now2) => {
49923
- const start = graceStartedAt.get(id);
49924
- return start === undefined ? 0 : Math.max(0, now2 - start);
49925
- };
49926
- const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
49927
- const timeoutModel = async (id, why) => {
49928
- const proc = processes.get(id);
49929
- if (!proc || statusCache.models[id]?.state !== "RUNNING")
49930
- return;
49931
- const rt = runtimes.get(id);
49932
- rt?.flushPartial();
49933
- const stderr = rt?.getStderr() ?? "";
49934
- const stdoutTail = rt?.getStdoutTail() ?? "";
49935
- const bytes2 = rt?.getByteCount() ?? 0;
49936
- const grace = graceUsedMs(id, Date.now());
49937
- const detail = `Killed by the orchestrator after ${(timeoutMs + grace) / 1000}s ` + `(deadline ${timeoutMs / 1000}s${grace ? ` + ${grace / 1000}s grace` : ""}) ` + `with ${bytes2} B of stdout \u2014 ${why}. ` + "That figure counts the ANSWER, not the wire format, so 0 B means the child had " + `not produced an assistant message yet \u2014 "did not finish", not "produced nothing".`;
49938
- if (rt)
49939
- persistErrorLog(rt.errorLogPath, `TIMEOUT: ${detail}`, stderr, stdoutTail);
49940
- updateModelStatus(id, {
49941
- state: "TIMEOUT",
49942
- completedAt: new Date().toISOString(),
49943
- outputSize: bytes2,
49944
- error: rt ? {
49945
- model: id,
49946
- command: rt.command,
49947
- reason: "timeout",
49948
- detail,
49949
- stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49950
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49951
- errorLogPath: rt.errorLogPath,
49952
- workDir: sessionPath
49953
- } : undefined
49954
- });
49955
- opts.onStatusChange?.(id, statusCache.models[id]);
49956
- const stopped = await terminateChildTree(proc);
49957
- if (!stopped) {
49958
- persistErrorLog(rt?.errorLogPath ?? join29(sessionPath, "errors", `${id}.log`), "TIMEOUT: child survived SIGKILL \u2014 it may still be running and billing", stderr, stdoutTail);
50142
+ const teamSessionId = basename2(sessionPath);
50143
+ liveTeamRuns.set(teamSessionId, {
50144
+ sessionPath,
50145
+ processes,
50146
+ idleMsFor: (slotId) => runtimes.get(slotId)?.getIdleMs() ?? null,
50147
+ activityFor: (slotId) => runtimes.get(slotId)?.getActivity() ?? null,
50148
+ cancelledSlots
50149
+ });
50150
+ const done = (async () => {
50151
+ try {
50152
+ await Promise.all(completionPromises);
50153
+ } finally {
50154
+ clearInterval(progressHandle);
50155
+ emitProgress("settled");
50156
+ process.off("SIGINT", sigintHandler);
50157
+ liveTeamRuns.delete(teamSessionId);
49959
50158
  }
50159
+ return statusCache;
50160
+ })();
50161
+ done.catch(() => {});
50162
+ return {
50163
+ teamSessionId,
50164
+ sessionPath,
50165
+ slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
50166
+ done
49960
50167
  };
49961
- const allDone = Promise.all(completionPromises);
49962
- let settled = false;
49963
- allDone.then(() => {
49964
- settled = true;
49965
- }, () => {
49966
- settled = true;
49967
- });
49968
- const deadlineWatcher = (async () => {
49969
- await delay(timeoutMs);
49970
- for (;; ) {
49971
- if (settled)
49972
- return;
49973
- const running = runningIds();
49974
- if (running.length === 0)
49975
- return;
49976
- const extended = [];
49977
- const now2 = Date.now();
49978
- for (const id of running) {
49979
- const idleMs = idleMsFor(id);
49980
- const usedGrace = graceUsedMs(id, now2);
49981
- if (!graceEnabled) {
49982
- await timeoutModel(id, "deadline reached (grace extension disabled)");
49983
- } else if (usedGrace >= maxGraceMs) {
49984
- await timeoutModel(id, `grace exhausted after ${Math.round(usedGrace / 1000)}s of extra time`);
49985
- } else if (idleMs === null) {
49986
- await timeoutModel(id, "deadline reached with no measurable progress to extend for");
49987
- } else if (idleMs >= stallMs) {
49988
- await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
49989
- } else {
49990
- if (!graceStartedAt.has(id))
49991
- graceStartedAt.set(id, now2);
49992
- extended.push(id);
49993
- }
49994
- }
49995
- if (extended.length === 0)
49996
- return;
49997
- emitProgress("running");
49998
- await delay(Math.min(GRACE_INTERVAL_MS, Math.max(1000, stallMs)));
49999
- }
50000
- })().catch(() => {});
50001
- await Promise.race([allDone, deadlineWatcher]);
50002
- if (!settled)
50003
- await Promise.race([allDone, delay(DRAIN_TIMEOUT_MS)]);
50004
- clearInterval(progressHandle);
50005
- emitProgress("settled");
50006
- process.off("SIGINT", sigintHandler);
50007
- return statusCache;
50168
+ }
50169
+ async function runModels(sessionPath, opts = {}) {
50170
+ const handle = await startModels(sessionPath, opts);
50171
+ return handle.done;
50008
50172
  }
50009
50173
  async function judgeResponses(sessionPath, opts = {}) {
50010
50174
  const responseFiles = readdirSync5(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
@@ -50186,16 +50350,17 @@ function formatVerdict(verdict, sessionPath) {
50186
50350
  }
50187
50351
  return output;
50188
50352
  }
50189
- var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, DRAIN_TIMEOUT_MS = 1e4, GRACE_INTERVAL_MS = 60000, DEFAULT_STALL_SECONDS2 = 90, delay = (ms) => new Promise((resolve4) => {
50190
- const t = setTimeout(resolve4, ms);
50191
- t.unref?.();
50192
- }), BENIGN_STDERR_PATTERNS;
50353
+ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
50193
50354
  var init_team_orchestrator = __esm(() => {
50194
50355
  init_prehydrate();
50356
+ init_stream_json_reducer();
50357
+ init_config2();
50358
+ init_upstream_error_capture();
50195
50359
  init_process_tree();
50196
50360
  init_redact();
50361
+ init_stdio_decode();
50197
50362
  init_team_stats();
50198
- init_team_stream_capture();
50363
+ liveTeamRuns = new Map;
50199
50364
  API_ERROR_RE = /\[API Error:\s*([^\]]{0,300})\]/i;
50200
50365
  BG_CEILING_RE = /Background tasks still running after (\d+)s; terminating/i;
50201
50366
  BENIGN_STDERR_PATTERNS = [/^\s*\[claude-code:unrecognized_model\]/];
@@ -50217,7 +50382,7 @@ import {
50217
50382
  } from "fs";
50218
50383
  import { homedir as homedir28 } from "os";
50219
50384
  import { join as join30, resolve as resolve4, sep } from "path";
50220
- import { StringDecoder } from "string_decoder";
50385
+ import { StringDecoder as StringDecoder2 } from "string_decoder";
50221
50386
  function buildChannelSpawnArgs(opts) {
50222
50387
  return [
50223
50388
  "--model",
@@ -50248,9 +50413,6 @@ function assertNoReservedFlags(flags) {
50248
50413
  }
50249
50414
  }
50250
50415
  }
50251
- function decodeChunk(decoder, chunk) {
50252
- return typeof chunk === "string" ? chunk : decoder.write(chunk);
50253
- }
50254
50416
  function readTailText(path, maxBytes) {
50255
50417
  let fd = null;
50256
50418
  try {
@@ -50359,11 +50521,14 @@ class SessionManager {
50359
50521
  throw new Error(`Max sessions (${this.maxSessions}) reached`);
50360
50522
  }
50361
50523
  assertNoReservedFlags(opts.claudishFlags);
50362
- const sessionId2 = randomUUID4().slice(0, 8);
50524
+ if (opts.sessionId !== undefined && this.sessions.has(opts.sessionId)) {
50525
+ throw new Error(`Session id already in use: ${opts.sessionId}`);
50526
+ }
50527
+ const sessionId2 = opts.sessionId ?? randomUUID4().slice(0, 8);
50363
50528
  const claudeSessionId = randomUUID4();
50364
50529
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
50365
50530
  const startedAt = new Date().toISOString();
50366
- const sessionDir = join30(this.sessionsDir, sessionId2);
50531
+ const sessionDir = opts.sessionDir ?? join30(this.sessionsDir, sessionId2);
50367
50532
  mkdirSync12(sessionDir, { recursive: true });
50368
50533
  if (opts.prompt) {
50369
50534
  writeFileSync12(join30(sessionDir, "prompt.md"), opts.prompt, "utf-8");
@@ -50373,7 +50538,7 @@ class SessionManager {
50373
50538
  claudeSessionId,
50374
50539
  claudishFlags: opts.claudishFlags
50375
50540
  });
50376
- const tokenFile = join30(sessionDir, "tokens.json");
50541
+ const tokenFile = opts.tokenFile ?? join30(sessionDir, "tokens.json");
50377
50542
  const eventLogPath = join30(sessionDir, "events.jsonl");
50378
50543
  const upstreamErrorLogPath = join30(sessionDir, "upstream-errors.jsonl");
50379
50544
  const cwd = opts.cwd ?? process.cwd();
@@ -50399,6 +50564,7 @@ class SessionManager {
50399
50564
  status: "starting",
50400
50565
  pid: proc.pid ?? null,
50401
50566
  startedAt,
50567
+ idleSeconds: 0,
50402
50568
  completedAt: null,
50403
50569
  exitCode: null,
50404
50570
  turnsCompleted: 0,
@@ -50419,8 +50585,8 @@ class SessionManager {
50419
50585
  evictHandle: null,
50420
50586
  stderr: "",
50421
50587
  stderrTruncated: false,
50422
- stdoutDecoder: new StringDecoder("utf8"),
50423
- stderrDecoder: new StringDecoder("utf8"),
50588
+ stdoutDecoder: new StringDecoder2("utf8"),
50589
+ stderrDecoder: new StringDecoder2("utf8"),
50424
50590
  outputLogStream,
50425
50591
  sessionDir,
50426
50592
  eventLogPath,
@@ -50441,6 +50607,7 @@ class SessionManager {
50441
50607
  entry.reducer = new StreamJsonReducer({
50442
50608
  sessionId: sessionId2,
50443
50609
  stallSeconds: this.stallSeconds,
50610
+ keepUnrecognizedJson: opts.keepUnrecognizedJson,
50444
50611
  callback: (sid, data) => {
50445
50612
  const current = this.sessions.get(sid);
50446
50613
  if (!current)
@@ -50527,6 +50694,7 @@ class SessionManager {
50527
50694
  if (!entry)
50528
50695
  return this.diskOutput(this.requireDiskRecord(sessionId2), tailLines);
50529
50696
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50697
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50530
50698
  this.refreshAccounting(entry);
50531
50699
  const lines = entry.scrollback.getLines(tailLines);
50532
50700
  return {
@@ -50537,7 +50705,8 @@ class SessionManager {
50537
50705
  totalLines: entry.scrollback.totalLines,
50538
50706
  turnsCompleted: entry.info.turnsCompleted,
50539
50707
  tokensUsed: entry.info.tokensUsed,
50540
- elapsedSeconds: entry.info.elapsedSeconds
50708
+ elapsedSeconds: entry.info.elapsedSeconds,
50709
+ idleSeconds: entry.info.idleSeconds
50541
50710
  };
50542
50711
  }
50543
50712
  getDiagnostics(sessionId2, eventLimit = DEFAULT_EVENT_LIMIT) {
@@ -50547,6 +50716,7 @@ class SessionManager {
50547
50716
  return this.diskDiagnostics(this.requireDiskRecord(sessionId2), limit);
50548
50717
  this.refreshAccounting(entry);
50549
50718
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50719
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50550
50720
  return {
50551
50721
  sessionId: sessionId2,
50552
50722
  status: entry.info.status,
@@ -50555,6 +50725,7 @@ class SessionManager {
50555
50725
  exitCode: entry.info.exitCode,
50556
50726
  terminalReason: entry.info.terminalReason,
50557
50727
  elapsedSeconds: entry.info.elapsedSeconds,
50728
+ idleSeconds: entry.info.idleSeconds,
50558
50729
  timeoutSeconds: entry.timeoutSeconds,
50559
50730
  outputBytes: entry.proseBytes,
50560
50731
  turnsCompleted: entry.info.turnsCompleted,
@@ -50613,6 +50784,7 @@ class SessionManager {
50613
50784
  continue;
50614
50785
  if (!isTerminal2) {
50615
50786
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50787
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50616
50788
  this.refreshAccounting(entry);
50617
50789
  }
50618
50790
  sessions2.push({ ...entry.info });
@@ -50624,6 +50796,7 @@ class SessionManager {
50624
50796
  if (!entry)
50625
50797
  return this.requireDiskRecord(sessionId2).info;
50626
50798
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50799
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50627
50800
  this.refreshAccounting(entry);
50628
50801
  return { ...entry.info };
50629
50802
  }
@@ -50706,6 +50879,7 @@ class SessionManager {
50706
50879
  model: metaString(meta3?.model) ?? "unknown",
50707
50880
  spawnModel: metaString(meta3?.spawnModel),
50708
50881
  status: metaStatus(meta3?.status) ?? "failed",
50882
+ idleSeconds: null,
50709
50883
  pid: null,
50710
50884
  startedAt,
50711
50885
  completedAt,
@@ -50735,7 +50909,8 @@ class SessionManager {
50735
50909
  totalLines: buffer.totalLines,
50736
50910
  turnsCompleted: record4.info.turnsCompleted,
50737
50911
  tokensUsed: record4.info.tokensUsed,
50738
- elapsedSeconds: record4.info.elapsedSeconds
50912
+ elapsedSeconds: record4.info.elapsedSeconds,
50913
+ idleSeconds: null
50739
50914
  };
50740
50915
  }
50741
50916
  diskDiagnostics(record4, limit) {
@@ -50753,6 +50928,7 @@ class SessionManager {
50753
50928
  exitCode: info.exitCode,
50754
50929
  terminalReason: info.terminalReason,
50755
50930
  elapsedSeconds: info.elapsedSeconds,
50931
+ idleSeconds: null,
50756
50932
  timeoutSeconds: 0,
50757
50933
  outputBytes: diskProseBytes(outputTail, fileSize(outputLogPath)),
50758
50934
  turnsCompleted: info.turnsCompleted,
@@ -50912,6 +51088,7 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
50912
51088
  entry.info.exitCode = code;
50913
51089
  entry.info.completedAt = at;
50914
51090
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
51091
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50915
51092
  entry.stdinClosed = true;
50916
51093
  this.flushDecoders(entry);
50917
51094
  const priorVerdict = TERMINAL_STATUSES.includes(entry.info.status) ? entry.info.status : null;
@@ -51076,13 +51253,14 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
51076
51253
  }
51077
51254
  }
51078
51255
  }
51079
- var DEFAULT_MAX_SESSIONS = 20, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000, TERMINAL_RETENTION_MS, MAX_TERMINAL_SESSIONS = 50, STDERR_SIDE_LIMIT, EVENT_LOG_LIMIT, EVENT_RING_SIZE = 200, EVENT_PREVIEW_CHARS = 800, DEFAULT_EVENT_LIMIT = 40, UPSTREAM_ERROR_TAIL_BYTES, STDERR_TRUNCATION_MARKER = "[claudish] \u2026 stderr truncated to", TERMINAL_STATUSES, KNOWN_STATUSES, SESSION_ID_RE, META_READ_LIMIT, OUTPUT_TAIL_BYTES, STDERR_READ_BYTES, EVENT_TAIL_BYTES, NO_TERMINAL_RECORD = "claudish_no_terminal_record", RESERVED_FLAG_ALIASES, TRANSPORT_BREAKING_FLAGS, RESERVED_CHILD_FLAGS, metaString = (v) => typeof v === "string" && v.length > 0 ? v : null, metaNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : null, metaStatus = (v) => typeof v === "string" && KNOWN_STATUSES.includes(v) ? v : null, CLAUDISH_NOTE_PREFIX = "[claudish] ";
51256
+ var DEFAULT_MAX_SESSIONS = 20, DRAIN_TIMEOUT_MS = 1e4, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000, TERMINAL_RETENTION_MS, MAX_TERMINAL_SESSIONS = 50, STDERR_SIDE_LIMIT, EVENT_LOG_LIMIT, EVENT_RING_SIZE = 200, EVENT_PREVIEW_CHARS = 800, DEFAULT_EVENT_LIMIT = 40, UPSTREAM_ERROR_TAIL_BYTES, STDERR_TRUNCATION_MARKER = "[claudish] \u2026 stderr truncated to", TERMINAL_STATUSES, KNOWN_STATUSES, SESSION_ID_RE, META_READ_LIMIT, OUTPUT_TAIL_BYTES, STDERR_READ_BYTES, EVENT_TAIL_BYTES, NO_TERMINAL_RECORD = "claudish_no_terminal_record", RESERVED_FLAG_ALIASES, TRANSPORT_BREAKING_FLAGS, RESERVED_CHILD_FLAGS, metaString = (v) => typeof v === "string" && v.length > 0 ? v : null, metaNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : null, metaStatus = (v) => typeof v === "string" && KNOWN_STATUSES.includes(v) ? v : null, CLAUDISH_NOTE_PREFIX = "[claudish] ";
51080
51257
  var init_session_manager = __esm(() => {
51081
51258
  init_config2();
51082
51259
  init_upstream_error_capture();
51083
51260
  init_process_tree();
51084
51261
  init_redact();
51085
51262
  init_session_discovery();
51263
+ init_stdio_decode();
51086
51264
  init_team_orchestrator();
51087
51265
  init_team_stats();
51088
51266
  init_scrollback_buffer();
@@ -51218,68 +51396,6 @@ var init_cache_ttl = __esm(() => {
51218
51396
  FIREBASE_CACHE_TTL_MS = FIREBASE_CACHE_TTL_HOURS * 60 * 60 * 1000;
51219
51397
  });
51220
51398
 
51221
- // src/providers/model-ordering.ts
51222
- function extractVersionParts(modelId) {
51223
- const tokens = modelId.toLowerCase().split(/[\/_-]+/);
51224
- let started = false;
51225
- const parts = [];
51226
- for (const token of tokens) {
51227
- const match = token.match(/\d+(?:\.\d+)*/);
51228
- if (!match) {
51229
- if (started)
51230
- break;
51231
- continue;
51232
- }
51233
- if (!started && /^\d+b$/.test(token) && Number.parseInt(match[0], 10) > 10) {
51234
- continue;
51235
- }
51236
- if (!started) {
51237
- started = true;
51238
- for (const part of match[0].split(".")) {
51239
- parts.push(Number.parseInt(part, 10));
51240
- }
51241
- if (!/^\d+(?:\.\d+)*$/.test(token)) {
51242
- break;
51243
- }
51244
- continue;
51245
- }
51246
- if (!/^\d{1,2}(?:\.\d+)?$/.test(token)) {
51247
- break;
51248
- }
51249
- for (const part of token.split(".")) {
51250
- parts.push(Number.parseInt(part, 10));
51251
- }
51252
- }
51253
- return parts;
51254
- }
51255
- function compareVersionPartsDesc(a, b) {
51256
- const maxLength = Math.max(a.length, b.length);
51257
- for (let i = 0;i < maxLength; i++) {
51258
- const aPart = a[i] ?? -1;
51259
- const bPart = b[i] ?? -1;
51260
- if (aPart !== bPart) {
51261
- return bPart - aPart;
51262
- }
51263
- }
51264
- return 0;
51265
- }
51266
- function compareByReleaseDateDesc(a, b) {
51267
- const aReleaseRaw = a.releaseDate ? Date.parse(a.releaseDate) : 0;
51268
- const bReleaseRaw = b.releaseDate ? Date.parse(b.releaseDate) : 0;
51269
- const aRelease = Number.isNaN(aReleaseRaw) ? 0 : aReleaseRaw;
51270
- const bRelease = Number.isNaN(bReleaseRaw) ? 0 : bReleaseRaw;
51271
- if (aRelease !== bRelease) {
51272
- return bRelease - aRelease;
51273
- }
51274
- const aId = a.id ?? a.modelId ?? "";
51275
- const bId = b.id ?? b.modelId ?? "";
51276
- const versionCompare = compareVersionPartsDesc(extractVersionParts(aId), extractVersionParts(bId));
51277
- if (versionCompare !== 0) {
51278
- return versionCompare;
51279
- }
51280
- return aId.localeCompare(bId);
51281
- }
51282
-
51283
51399
  // src/model-loader.ts
51284
51400
  import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
51285
51401
  import { homedir as homedir29 } from "os";
@@ -56440,7 +56556,7 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56440
56556
  });
56441
56557
  tools.push({
56442
56558
  name: "search_models",
56443
- description: "Search all OpenRouter models by name, provider, or capability",
56559
+ description: "Search OpenRouter's listing by name, provider, or capability, and cross-reference " + "claudish's own catalog. SCOPE: the listing covers OpenRouter only, so a name's " + "absence from it is NOT evidence the name is unroutable \u2014 subscription wire ids " + "(`k3`) and catalog aliases live outside that namespace and are reported separately " + "here. This tool cannot tell you which provider will serve a model or whether the " + "hop is subscription or metered; call `preflight` for that.",
56444
56560
  inputSchema: {
56445
56561
  type: "object",
56446
56562
  properties: {
@@ -56475,13 +56591,40 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56475
56591
  return b.score - a.score;
56476
56592
  return compareByReleaseDateDesc(orderingKey(a.model), orderingKey(b.model));
56477
56593
  }).slice(0, maxResults);
56594
+ const catalogMatches = searchCatalogModels(query, Math.max(maxResults, 5));
56595
+ const renderCatalogSection = () => {
56596
+ if (catalogMatches.length === 0)
56597
+ return "";
56598
+ let s = `
56599
+ ## Catalog names (what claudish routes)
56600
+
56601
+ `;
56602
+ s += `| Bare name | Matched alias | Subscription plan |
56603
+ `;
56604
+ s += `|-----------|---------------|-------------------|
56605
+ `;
56606
+ for (const m of catalogMatches) {
56607
+ const plans = m.subscriptionPlans.length > 0 ? m.subscriptionPlans.join(", ") : "-";
56608
+ s += `| ${m.modelId} | ${m.matchedAlias ?? "-"} | ${plans} |
56609
+ `;
56610
+ }
56611
+ s += `
56612
+ Pass the **bare name**. Routing puts a subscription ahead of the metered API and ` + "rewrites the model to that plan's wire id for you. An aggregator-qualified id " + "(`moonshotai/...`, `accounts/fireworks/...`) pins that aggregator and bills per token.\n";
56613
+ return s;
56614
+ };
56478
56615
  if (results.length === 0) {
56479
- return {
56480
- content: [{ type: "text", text: `No models found matching "${query}"` }]
56481
- };
56616
+ const catalog = renderCatalogSection();
56617
+ const text = catalog ? `No OpenRouter listing matches "${query}", but claudish's catalog knows these:
56618
+ ${catalog}` : `No models found matching "${query}".
56619
+
56620
+ ` + "This searched OpenRouter's listing only. Subscription wire ids and catalog " + "aliases are not in it, so this is not proof the name is unroutable. Call " + "`list_models` for the recommended set, or `preflight` to test a specific name " + "against real routing.";
56621
+ return { content: [{ type: "text", text }] };
56482
56622
  }
56483
56623
  let output = `# Search Results for "${query}"
56484
56624
 
56625
+ `;
56626
+ output += `## OpenRouter listing
56627
+
56485
56628
  `;
56486
56629
  output += `| Model | Provider | Pricing | Context |
56487
56630
  `;
@@ -56497,8 +56640,13 @@ Tokens: ${result.usage.input} input, ${result.usage.output} output`;
56497
56640
  output += `| ${model.id} | ${provider} | ${pricing} | ${context} |
56498
56641
  `;
56499
56642
  }
56643
+ output += renderCatalogSection();
56644
+ const suggested = catalogMatches[0]?.modelId ?? results[0].model.id;
56500
56645
  output += `
56501
- Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56646
+ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
56647
+ output += `
56648
+
56649
+ To learn which provider would actually serve \`${suggested}\`, and whether that ` + "hop is covered by a subscription or billed per token, call " + `\`preflight({models: ["${suggested}"]})\`. This listing cannot answer that.`;
56502
56650
  return { content: [{ type: "text", text: output }] };
56503
56651
  }
56504
56652
  });
@@ -56687,14 +56835,18 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56687
56835
  });
56688
56836
  tools.push({
56689
56837
  name: "team",
56690
- description: "Run AI models on a task with anonymized outputs and optional blind judging. Modes: 'run' (execute models), 'judge' (blind-vote on existing outputs), 'run-and-judge' (full pipeline), 'status' (check progress).",
56838
+ description: "Run AI models on a task with anonymized outputs and optional blind judging. " + "Modes: 'run' (START the models and return a slot map immediately \u2014 it does NOT " + "wait), 'status' (per-slot state, plus how long each slot has been silent), " + "'cancel' (stop one slot or the whole run), 'judge' (blind-vote on existing " + "outputs), 'run-and-judge' (the blocking pipeline). " + "NO SLOT IS EVER KILLED ON A TIMER. A team slot is a full Claude Code session and " + "may work for a long time; a slot inside a build or test suite emits nothing for " + "minutes and is working, not stuck. Poll 'status', judge the silence against the " + "task you set, and use 'cancel' if you decide a slot is wedged.",
56691
56839
  inputSchema: {
56692
56840
  type: "object",
56693
56841
  properties: {
56694
56842
  mode: {
56695
56843
  type: "string",
56696
- enum: ["run", "judge", "run-and-judge", "status"],
56697
- description: "Operation mode"
56844
+ enum: ["run", "judge", "run-and-judge", "status", "cancel"],
56845
+ description: "Operation mode. 'run' STARTS the models and returns immediately with a " + "slot map \u2014 it does not wait. Poll 'status' for progress, then 'judge' once " + "the slots have finished. 'run-and-judge' is the blocking pipeline and holds " + "the call open for the whole run. 'cancel' stops one slot or the whole run."
56846
+ },
56847
+ slot: {
56848
+ type: "string",
56849
+ description: "For 'cancel': the anonymised slot id to stop (e.g. '02'), from the slot map " + "'run' returned. Omit to cancel every slot in the run."
56698
56850
  },
56699
56851
  path: {
56700
56852
  type: "string",
@@ -56710,11 +56862,14 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56710
56862
  items: { type: "string" },
56711
56863
  description: "Model IDs to use as judges (default: same as runners)"
56712
56864
  },
56865
+ input_file: {
56866
+ type: "string",
56867
+ description: "PREFERRED. Path to a file holding the task prompt, relative to the working " + "directory. Use this rather than `input` for anything longer than a sentence: " + "a prompt passed inline is echoed verbatim in the caller's terminal, where a " + "200-line review brief buries every other argument and makes the call " + "unreadable. Write the brief to the session directory first (input.md is the " + "conventional name) and point here."
56868
+ },
56713
56869
  input: {
56714
56870
  type: "string",
56715
- description: "Task prompt text (or place input.md in the session directory before calling)"
56871
+ description: "Task prompt as inline text. Prefer `input_file` \u2014 inline text is rendered in " + "full in the caller's terminal. Passing both is an error. If neither is given, " + "an input.md already present in the session directory is used."
56716
56872
  },
56717
- timeout: { type: "number", description: "Per-model timeout in seconds (default: 300)" },
56718
56873
  require_pattern: {
56719
56874
  type: "string",
56720
56875
  description: "Regex the response MUST match, or the slot is reported FAILED (state EMPTY, " + "reason 'shape_mismatch') instead of succeeded. Strongly recommended whenever " + "your prompt mandates an output shape \u2014 e.g. '```vote' for a voting panel. " + "Exit code 0 is not a success oracle: it is 0 on API errors and on a child " + "that simply never followed the format. Answers are no longer LOST to print " + "mode (every assistant message is captured), so a mismatch now means the model " + "did not produce the shape, not that the shape was discarded."
@@ -56742,8 +56897,12 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56742
56897
  const path = args.path;
56743
56898
  const models = args.models;
56744
56899
  const judges = args.judges;
56745
- const input = args.input;
56746
- const timeout = args.timeout;
56900
+ const inlineInput = args.input;
56901
+ const inputFile = args.input_file;
56902
+ if (inlineInput !== undefined && inputFile !== undefined) {
56903
+ throw new Error("Pass `input_file` or `input`, not both. Prefer `input_file` \u2014 inline text is " + "rendered verbatim in the caller's terminal.");
56904
+ }
56905
+ const input = inputFile !== undefined ? readTeamInputFile(inputFile) : inlineInput;
56747
56906
  const requirePattern = args.require_pattern;
56748
56907
  const minOutputBytes = args.min_output_bytes;
56749
56908
  const childFlags = buildChildClaudeFlags(args.agent, args.claude_flags);
@@ -56752,7 +56911,6 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56752
56911
  const teamSessionId = resolved.split("/").filter(Boolean).pop() ?? "team";
56753
56912
  const teamCreatedAt = new Date().toISOString();
56754
56913
  const runOpts = {
56755
- timeout,
56756
56914
  requirePattern,
56757
56915
  minOutputBytes,
56758
56916
  claudeFlags: childFlags,
@@ -56773,9 +56931,51 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56773
56931
  if (!models?.length)
56774
56932
  throw new Error("'models' is required for 'run' mode");
56775
56933
  setupSession(resolved, models, input);
56776
- const status = await runModels(resolved, runOpts);
56934
+ const handle = await startModels(resolved, runOpts);
56935
+ return {
56936
+ content: [
56937
+ {
56938
+ type: "text",
56939
+ text: JSON.stringify({
56940
+ started: true,
56941
+ team_session_id: handle.teamSessionId,
56942
+ session_path: handle.sessionPath,
56943
+ slots: handle.slots,
56944
+ next: {
56945
+ status: `team(mode:"status", path:"${handle.sessionPath}")`,
56946
+ cancel: `team(mode:"cancel", path:"${handle.sessionPath}", slot:"<id>")`,
56947
+ judge: `team(mode:"judge", path:"${handle.sessionPath}") once every slot has finished`
56948
+ },
56949
+ note: "Nothing terminates a slot on a timer. `status` reports how many " + "seconds each slot has been silent; a slot inside a long build is " + "quiet and working. You decide whether to cancel."
56950
+ }, null, 2)
56951
+ }
56952
+ ]
56953
+ };
56954
+ }
56955
+ case "cancel": {
56956
+ const slot = args.slot;
56957
+ const teamSessionId2 = resolved.split("/").filter(Boolean).pop() ?? "team";
56958
+ const result = await cancelTeamRun(teamSessionId2, slot);
56959
+ if (!result.found) {
56960
+ return {
56961
+ content: [
56962
+ {
56963
+ type: "text",
56964
+ text: JSON.stringify({
56965
+ cancelled: [],
56966
+ note: "No live run for that path. It already settled (read `status`), or " + "it was started by a different process \u2014 this server can only stop " + "children it spawned."
56967
+ })
56968
+ }
56969
+ ]
56970
+ };
56971
+ }
56777
56972
  return {
56778
- content: [{ type: "text", text: formatTeamResult(status, resolved) }]
56973
+ content: [
56974
+ {
56975
+ type: "text",
56976
+ text: JSON.stringify({ cancelled: result.cancelled }, null, 2)
56977
+ }
56978
+ ]
56779
56979
  };
56780
56980
  }
56781
56981
  case "judge": {
@@ -56792,7 +56992,25 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56792
56992
  }
56793
56993
  case "status": {
56794
56994
  const status = getStatus(resolved);
56795
- return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
56995
+ const teamSessionId2 = resolved.split("/").filter(Boolean).pop() ?? "team";
56996
+ const idle = teamSlotIdleSeconds(teamSessionId2);
56997
+ const settled = !Object.values(status.models).some((m) => m.state === "RUNNING");
56998
+ return {
56999
+ content: [
57000
+ {
57001
+ type: "text",
57002
+ text: JSON.stringify({
57003
+ ...status,
57004
+ idle_seconds_by_slot: idle,
57005
+ activity_by_slot: teamSlotActivity(teamSessionId2),
57006
+ ...idle ? {
57007
+ note: "idle_seconds_by_slot is how long each slot has been silent; " + "read it against activity_by_slot. Silence in tool_executing " + "is a build or test suite running, and is not a failure " + 'signal. Nothing cancels on your behalf \u2014 use mode:"cancel" ' + "if you decide to."
57008
+ } : {},
57009
+ ...settled ? { summary: formatTeamResult(status, resolved) } : {}
57010
+ }, null, 2)
57011
+ }
57012
+ ]
57013
+ };
56796
57014
  }
56797
57015
  default:
56798
57016
  throw new Error(`Unknown mode: ${mode}`);
@@ -57112,7 +57330,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57112
57330
  });
57113
57331
  tools.push({
57114
57332
  name: "list_sessions",
57115
- description: "List all active channel sessions. Optionally include completed sessions.",
57333
+ description: "List all active channel sessions. Optionally include completed sessions. " + "Each session reports `idleSeconds`: how long since the child last emitted " + "anything. Nothing kills a session for being idle \u2014 a child inside a long " + "Bash call is silent and working \u2014 so this is yours to judge against the " + "task you set, and `cancel_session` is yours to call if the answer is no.",
57116
57334
  inputSchema: {
57117
57335
  type: "object",
57118
57336
  properties: {
@@ -57132,7 +57350,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57132
57350
  });
57133
57351
  tools.push({
57134
57352
  name: "get_diagnostics",
57135
- description: "Explain what a channel session actually did \u2014 stderr, upstream error bodies, the " + "recent event frames, the resolved model chain, accounting, and the paths to the " + "full records. Call this FIRST whenever a session fails, times out, or completes " + "with empty or surprising output; it needs no re-run and no debug flag.",
57353
+ description: "Explain what a channel session actually did \u2014 stderr, upstream error bodies, the " + "recent event frames, the resolved model chain, accounting, and the paths to the " + "full records. Call this FIRST whenever a session fails, times out, or completes " + "with empty or surprising output; it needs no re-run and no debug flag. " + "`idleSeconds` reports how long since the child last emitted a frame, and is " + "null once the session is no longer live. It is information, never a verdict: " + "claudish does not terminate a session for silence.",
57136
57354
  inputSchema: {
57137
57355
  type: "object",
57138
57356
  properties: {
@@ -57289,6 +57507,7 @@ Call get_diagnostics with session_id: "${sessionId2}" for the stderr, the upstre
57289
57507
  await server.connect(transport);
57290
57508
  process.on("SIGTERM", () => {
57291
57509
  sessionManager.shutdownAll().catch(() => {});
57510
+ shutdownAllTeamRuns().catch(() => {});
57292
57511
  });
57293
57512
  }
57294
57513
  function startMcpServer() {
@@ -57331,6 +57550,7 @@ var init_mcp_server = __esm(() => {
57331
57550
  init_server2();
57332
57551
  init_stdio2();
57333
57552
  init_types();
57553
+ init_model_catalog();
57334
57554
  init_agent_availability();
57335
57555
  init_prehydrate();
57336
57556
  init_diagnostics();
@@ -57356,7 +57576,8 @@ var init_mcp_server = __esm(() => {
57356
57576
  ALL_MODELS_CACHE_PATH2 = join34(CLAUDISH_CACHE_DIR, "all-models.json");
57357
57577
  NEXT_STEP = {
57358
57578
  nonzero_exit: "read the evidence log, then retry or drop the model",
57359
- timeout: "raise `timeout`, or pick a faster model",
57579
+ cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
57580
+ timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
57360
57581
  api_error: "retry once, or route via a different provider (or@<model>)",
57361
57582
  background_task_ceiling: "set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 for children, or forbid background work in the prompt",
57362
57583
  empty_output: "retry once; if it repeats, drop the model",
@@ -58239,7 +58460,8 @@ Options (run / run-and-judge):
58239
58460
  --path <dir> Session directory (default: .)
58240
58461
  --models <a,b,...> Comma-separated model IDs to run
58241
58462
  --input <text> Task prompt (or create input.md in --path beforehand)
58242
- --timeout <secs> Timeout per model in seconds (default: 300)
58463
+ --timeout <secs> Grid modes only: magmux's own per-pane timeout (default: 300).
58464
+ json mode has no deadline \u2014 nothing kills a working model.
58243
58465
  --grid Show all models in a magmux grid with live output + status bar
58244
58466
 
58245
58467
  Options (judge / run-and-judge):
@@ -58306,7 +58528,6 @@ async function teamCommand(args) {
58306
58528
  if (effectiveMode === "json") {
58307
58529
  setupSession(sessionPath, models, input);
58308
58530
  const runStatus = await runModels(sessionPath, {
58309
- timeout,
58310
58531
  onStatusChange: (id, s) => {
58311
58532
  process.stderr.write(`[team] ${id}: ${s.state}
58312
58533
  `);
@@ -58335,7 +58556,6 @@ async function teamCommand(args) {
58335
58556
  }
58336
58557
  setupSession(sessionPath, models, input);
58337
58558
  const status = await runModels(sessionPath, {
58338
- timeout,
58339
58559
  onStatusChange: (id, s) => {
58340
58560
  process.stderr.write(`[team] ${id}: ${s.state}
58341
58561
  `);
@@ -66218,9 +66438,9 @@ var require_internal = __commonJS(function(exports, module) {
66218
66438
  }
66219
66439
  InternalCodec.prototype.encoder = InternalEncoder;
66220
66440
  InternalCodec.prototype.decoder = InternalDecoder;
66221
- var StringDecoder2 = __require("string_decoder").StringDecoder;
66441
+ var StringDecoder3 = __require("string_decoder").StringDecoder;
66222
66442
  function InternalDecoder(options, codec2) {
66223
- this.decoder = new StringDecoder2(codec2.enc);
66443
+ this.decoder = new StringDecoder3(codec2.enc);
66224
66444
  }
66225
66445
  InternalDecoder.prototype.write = function(buf) {
66226
66446
  if (!Buffer2.isBuffer(buf)) {
@@ -86111,7 +86331,7 @@ var init_widgets = __esm(() => {
86111
86331
 
86112
86332
  // src/session/conversation.ts
86113
86333
  import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, statSync as statSync9 } from "fs";
86114
- import { StringDecoder as StringDecoder2 } from "string_decoder";
86334
+ import { StringDecoder as StringDecoder3 } from "string_decoder";
86115
86335
  function looksLikeTurn(line) {
86116
86336
  const assistant = line.includes('"type":"assistant"');
86117
86337
  if (!assistant && !line.includes('"type":"user"'))
@@ -86168,7 +86388,7 @@ function readConversation(file2, opts = {}) {
86168
86388
  const size = statSync9(file2).size;
86169
86389
  fd = openSync9(file2, "r");
86170
86390
  const buf = Buffer.allocUnsafe(chunkBytes);
86171
- const decoder = new StringDecoder2("utf-8");
86391
+ const decoder = new StringDecoder3("utf-8");
86172
86392
  let pending = "";
86173
86393
  let pos = 0;
86174
86394
  const consume = (line) => {
@@ -88463,7 +88683,6 @@ async function runCli() {
88463
88683
  const { setupSession: setupSession2, runModels: runModels2 } = await Promise.resolve().then(() => (init_team_orchestrator(), exports_team_orchestrator));
88464
88684
  setupSession2(sessionPath, cliConfig.team, prompt);
88465
88685
  const status2 = await runModels2(sessionPath, {
88466
- timeout: 300,
88467
88686
  claudeFlags: ["--json"]
88468
88687
  });
88469
88688
  const result = { ...status2, responses: {} };