agent-shim 11.18.0 → 11.19.1

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.
package/dist/index.mjs CHANGED
@@ -561,26 +561,103 @@ var init_schema = __esm({
561
561
  }
562
562
  });
563
563
 
564
+ // src/config/unrecognisedKeys.ts
565
+ function isRecord(value) {
566
+ return typeof value === "object" && value !== null;
567
+ }
568
+ function parseIgnoringUnrecognisedKeys(schema, raw) {
569
+ const first = schema.safeParse(raw);
570
+ if (first.success) {
571
+ return { success: true, data: first.data, ignored: [] };
572
+ }
573
+ const unrecognised = first.error.issues.filter((issue) => issue.code === "unrecognized_keys");
574
+ const others = first.error.issues.filter((issue) => issue.code !== "unrecognized_keys");
575
+ if (others.length > 0) {
576
+ return { success: false, issues: others };
577
+ }
578
+ const stripped = structuredClone(raw);
579
+ const ignored = [];
580
+ for (const issue of unrecognised) {
581
+ let holder = stripped;
582
+ for (const step of issue.path) {
583
+ holder = isRecord(holder) ? holder[step] : void 0;
584
+ }
585
+ if (isRecord(holder)) {
586
+ const values = Object.fromEntries(issue.keys.map((key) => [key, holder[key]]));
587
+ for (const key of issue.keys) {
588
+ Reflect.deleteProperty(holder, key);
589
+ }
590
+ ignored.push({ path: issue.path, keys: issue.keys, values });
591
+ }
592
+ }
593
+ const second = schema.safeParse(stripped);
594
+ return second.success ? { success: true, data: second.data, ignored } : { success: false, issues: second.error.issues };
595
+ }
596
+ function describeIgnoredKeys(filePath, ignored) {
597
+ const places = ignored.map(({ path: path41, keys }) => `${path41.length === 0 ? "" : `${path41.map(String).join(".")}.`}${keys.join(", ")}`);
598
+ return `${filePath} has settings this version of agent-shim does not know and ignores: ${places.join("; ")}. A newer release may read them.`;
599
+ }
600
+ function noteIgnoredKeys(filePath, ignored) {
601
+ if (ignored.length === 0) {
602
+ ignoredByFile.delete(filePath);
603
+ return;
604
+ }
605
+ ignoredByFile.set(filePath, ignored);
606
+ const message = `agent-shim: warning: ${describeIgnoredKeys(filePath, ignored)}
607
+ `;
608
+ if (!warned.has(message)) {
609
+ warned.add(message);
610
+ process.stderr.write(message);
611
+ }
612
+ }
613
+ function restoreIgnoredKeys(filePath, value) {
614
+ const ignored = ignoredByFile.get(filePath);
615
+ if (ignored === void 0) {
616
+ return value;
617
+ }
618
+ const restored = structuredClone(value);
619
+ for (const { path: path41, values } of ignored) {
620
+ let holder = restored;
621
+ for (const step of path41) {
622
+ holder = isRecord(holder) ? holder[step] : void 0;
623
+ }
624
+ if (isRecord(holder)) {
625
+ for (const [key, held] of Object.entries(values)) {
626
+ holder[key] = held;
627
+ }
628
+ }
629
+ }
630
+ return restored;
631
+ }
632
+ var warned, ignoredByFile;
633
+ var init_unrecognisedKeys = __esm({
634
+ "src/config/unrecognisedKeys.ts"() {
635
+ "use strict";
636
+ warned = /* @__PURE__ */ new Set();
637
+ ignoredByFile = /* @__PURE__ */ new Map();
638
+ }
639
+ });
640
+
564
641
  // src/config/load.ts
565
642
  import { cosmiconfigSync } from "cosmiconfig";
566
643
  function createExplorer(moduleName = "agent-shim") {
567
644
  return cosmiconfigSync(moduleName, { searchPlaces: [] });
568
645
  }
569
- function isRecord(value) {
646
+ function isRecord2(value) {
570
647
  return typeof value === "object" && value !== null && !Array.isArray(value);
571
648
  }
572
649
  function captureEntryOrder(value) {
573
- if (!isRecord(value)) {
650
+ if (!isRecord2(value)) {
574
651
  return [];
575
652
  }
576
653
  const entries = value.entries;
577
- if (!isRecord(entries)) {
654
+ if (!isRecord2(entries)) {
578
655
  return [];
579
656
  }
580
657
  return Object.keys(entries);
581
658
  }
582
659
  function captureRuleEntryOrders(value) {
583
- if (!isRecord(value)) {
660
+ if (!isRecord2(value)) {
584
661
  return [];
585
662
  }
586
663
  const rules = value.rules;
@@ -595,7 +672,7 @@ function cosmiconfigReader(explorer = createExplorer()) {
595
672
  try {
596
673
  result = explorer.load(filepath);
597
674
  } catch (error) {
598
- const code = isRecord(error) ? error.code : void 0;
675
+ const code = isRecord2(error) ? error.code : void 0;
599
676
  if (code === "ENOENT") {
600
677
  return void 0;
601
678
  }
@@ -612,10 +689,11 @@ function loadConfigFile(filepath, schema, read = cosmiconfigReader()) {
612
689
  if (raw === void 0) {
613
690
  return void 0;
614
691
  }
615
- const parsed = schema.safeParse(raw);
692
+ const parsed = parseIgnoringUnrecognisedKeys(schema, raw);
616
693
  if (!parsed.success) {
617
- throw new ConfigValidationError(filepath, parsed.error.issues);
694
+ throw new ConfigValidationError(filepath, parsed.issues);
618
695
  }
696
+ noteIgnoredKeys(filepath, parsed.ignored);
619
697
  return {
620
698
  filepath,
621
699
  config: parsed.data,
@@ -628,6 +706,7 @@ var init_load = __esm({
628
706
  "src/config/load.ts"() {
629
707
  "use strict";
630
708
  init_cliError();
709
+ init_unrecognisedKeys();
631
710
  ConfigValidationError = class extends CliError {
632
711
  constructor(filepath, issues) {
633
712
  const detail = issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
@@ -656,10 +735,11 @@ function readJson(filePath, schema, storeFs = nodeStoreFs) {
656
735
  return void 0;
657
736
  }
658
737
  const parsed = JSON.parse(raw);
659
- const result = schema.safeParse(parsed);
738
+ const result = parseIgnoringUnrecognisedKeys(schema, parsed);
660
739
  if (!result.success) {
661
- throw new ConfigValidationError(filePath, result.error.issues);
740
+ throw new ConfigValidationError(filePath, result.issues);
662
741
  }
742
+ noteIgnoredKeys(filePath, result.ignored);
663
743
  return result.data;
664
744
  }
665
745
  function writeTextAtomic(filePath, contents, storeFs = nodeStoreFs) {
@@ -675,7 +755,7 @@ function writeTextAtomic(filePath, contents, storeFs = nodeStoreFs) {
675
755
  }
676
756
  }
677
757
  function writeJsonAtomic(filePath, data, storeFs = nodeStoreFs) {
678
- writeTextAtomic(filePath, `${JSON.stringify(data, null, 2)}
758
+ writeTextAtomic(filePath, `${JSON.stringify(restoreIgnoredKeys(filePath, data), null, 2)}
679
759
  `, storeFs);
680
760
  }
681
761
  function applyPatch(filePath, schema, patch, options = {}) {
@@ -698,6 +778,7 @@ var init_store = __esm({
698
778
  "src/config/store.ts"() {
699
779
  "use strict";
700
780
  init_load();
781
+ init_unrecognisedKeys();
701
782
  nodeStoreFs = {
702
783
  readFileUtf8(filePath) {
703
784
  try {
@@ -2815,7 +2896,7 @@ var init_package = __esm({
2815
2896
  "package.json"() {
2816
2897
  package_default = {
2817
2898
  name: "agent-shim",
2818
- version: "11.18.0",
2899
+ version: "11.19.1",
2819
2900
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
2820
2901
  license: "MIT",
2821
2902
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -6176,7 +6257,7 @@ function formatCredentialSummary(summary) {
6176
6257
  function describeCredential(credential) {
6177
6258
  return formatCredentialSummary(summariseCredential(credential));
6178
6259
  }
6179
- function isRecord5(value) {
6260
+ function isRecord6(value) {
6180
6261
  return typeof value === "object" && value !== null && !Array.isArray(value);
6181
6262
  }
6182
6263
  function unpackKeyringToken(raw) {
@@ -6188,7 +6269,7 @@ function unpackKeyringToken(raw) {
6188
6269
  const base64Str = trimmed.slice(GO_KEYRING_BASE64_PREFIX.length);
6189
6270
  const decoded = Buffer.from(base64Str, "base64").toString("utf8");
6190
6271
  const parsed = JSON.parse(decoded);
6191
- if (isRecord5(parsed) && isRecord5(parsed.token)) {
6272
+ if (isRecord6(parsed) && isRecord6(parsed.token)) {
6192
6273
  const accessToken = parsed.token.access_token;
6193
6274
  if (typeof accessToken === "string" && accessToken.trim() !== "") {
6194
6275
  return accessToken.trim();
@@ -6304,14 +6385,14 @@ function providerJsonPath(paths, name) {
6304
6385
  function providerExists(paths, name) {
6305
6386
  return fs7.existsSync(providerJsonPath(paths, name));
6306
6387
  }
6307
- function isRecord6(value) {
6388
+ function isRecord7(value) {
6308
6389
  return typeof value === "object" && value !== null && !Array.isArray(value);
6309
6390
  }
6310
6391
  function legacyProviderConversion(raw) {
6311
- if (!isRecord6(raw) || "credential" in raw) {
6392
+ if (!isRecord7(raw) || "credential" in raw) {
6312
6393
  return void 0;
6313
6394
  }
6314
- const env = isRecord6(raw.env) ? raw.env : void 0;
6395
+ const env = isRecord7(raw.env) ? raw.env : void 0;
6315
6396
  const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
6316
6397
  const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
6317
6398
  if (fields.length === 0) {
@@ -6344,10 +6425,11 @@ function parseProviderFile(filePath, raw) {
6344
6425
  if (conversion !== void 0) {
6345
6426
  throw new LegacyProviderFileError(filePath, conversion);
6346
6427
  }
6347
- const parsed = ProviderSchema.safeParse(raw);
6428
+ const parsed = parseIgnoringUnrecognisedKeys(ProviderSchema, raw);
6348
6429
  if (!parsed.success) {
6349
- throw new ConfigValidationError(filePath, parsed.error.issues);
6430
+ throw new ConfigValidationError(filePath, parsed.issues);
6350
6431
  }
6432
+ noteIgnoredKeys(filePath, parsed.ignored);
6351
6433
  return parsed.data;
6352
6434
  }
6353
6435
  function readProvider(paths, name) {
@@ -6555,6 +6637,7 @@ var init_providersStore = __esm({
6555
6637
  "use strict";
6556
6638
  init_load();
6557
6639
  init_store();
6640
+ init_unrecognisedKeys();
6558
6641
  init_schema();
6559
6642
  init_cliError();
6560
6643
  init_credential();
@@ -7537,7 +7620,7 @@ function resolvePoolLaunch(params) {
7537
7620
  if (stickyRead.problem !== void 0) {
7538
7621
  log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
7539
7622
  }
7540
- const warned = /* @__PURE__ */ new Set();
7623
+ const warned2 = /* @__PURE__ */ new Set();
7541
7624
  for (; ; ) {
7542
7625
  const nowMs = params.now();
7543
7626
  let graph;
@@ -7561,8 +7644,8 @@ function resolvePoolLaunch(params) {
7561
7644
  }
7562
7645
  for (const gone of graph.missing) {
7563
7646
  const key = `${gone.pool}:${gone.identity}`;
7564
- if (!warned.has(key)) {
7565
- warned.add(key);
7647
+ if (!warned2.has(key)) {
7648
+ warned2.add(key);
7566
7649
  log.warn(`agent-shim: pool "${gone.pool}" names identity "${gone.identity}", which does not exist; skipping it`);
7567
7650
  }
7568
7651
  }
@@ -9891,7 +9974,7 @@ var LiveTaskSchema = z5.strictObject({
9891
9974
  observedAt: z5.number()
9892
9975
  });
9893
9976
  var SYSTEM_SUBTYPES = ["task_started", "task_progress", "task_updated", "task_notification", "background_tasks_changed"];
9894
- function isRecord2(value) {
9977
+ function isRecord3(value) {
9895
9978
  return typeof value === "object" && value !== null && !Array.isArray(value);
9896
9979
  }
9897
9980
  function stringField(record, field) {
@@ -9908,7 +9991,7 @@ function booleanField(record, field) {
9908
9991
  }
9909
9992
  function usageOf(record) {
9910
9993
  const raw = record.usage;
9911
- if (!isRecord2(raw)) {
9994
+ if (!isRecord3(raw)) {
9912
9995
  return void 0;
9913
9996
  }
9914
9997
  const totalTokens = numberField(raw, "total_tokens");
@@ -9940,7 +10023,7 @@ function bounded(book) {
9940
10023
  return book;
9941
10024
  }
9942
10025
  function applyAgentTaskEvent(book, sessionId, payload, observedAt2) {
9943
- if (!isRecord2(payload) || payload.type !== "system") {
10026
+ if (!isRecord3(payload) || payload.type !== "system") {
9944
10027
  return book;
9945
10028
  }
9946
10029
  const subtype = payload.subtype;
@@ -9956,8 +10039,8 @@ function applyAgentTaskEvent(book, sessionId, payload, observedAt2) {
9956
10039
  if (subtype === "background_tasks_changed") {
9957
10040
  const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];
9958
10041
  for (const raw of tasks) {
9959
- const taskId2 = isRecord2(raw) ? stringField(raw, "task_id") : void 0;
9960
- if (isRecord2(raw) && taskId2 !== void 0) {
10042
+ const taskId2 = isRecord3(raw) ? stringField(raw, "task_id") : void 0;
10043
+ if (isRecord3(raw) && taskId2 !== void 0) {
9961
10044
  upsert(taskId2, { runId: stringField(raw, "run_id"), taskType: stringField(raw, "task_type"), description: stringField(raw, "description") });
9962
10045
  }
9963
10046
  }
@@ -9980,7 +10063,7 @@ function applyAgentTaskEvent(book, sessionId, payload, observedAt2) {
9980
10063
  } else if (subtype === "task_progress") {
9981
10064
  upsert(taskId, { ...common, subagentType: stringField(payload, "subagent_type"), lastToolName: stringField(payload, "last_tool_name"), usage: usageOf(payload) });
9982
10065
  } else if (subtype === "task_updated") {
9983
- const patch = isRecord2(payload.patch) ? payload.patch : {};
10066
+ const patch = isRecord3(payload.patch) ? payload.patch : {};
9984
10067
  upsert(taskId, { runId: stringField(payload, "run_id"), status: stringField(patch, "status") });
9985
10068
  } else {
9986
10069
  upsert(taskId, { ...common, status: stringField(payload, "status"), outputFile: stringField(payload, "output_file"), summary: stringField(payload, "summary"), usage: usageOf(payload) });
@@ -10062,7 +10145,7 @@ function sessionCreateId(body) {
10062
10145
  const id = session.id;
10063
10146
  return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
10064
10147
  }
10065
- function isRecord3(value) {
10148
+ function isRecord4(value) {
10066
10149
  return typeof value === "object" && value !== null && !Array.isArray(value);
10067
10150
  }
10068
10151
  function parseJsonObject(body) {
@@ -10072,7 +10155,7 @@ function parseJsonObject(body) {
10072
10155
  } catch {
10073
10156
  return void 0;
10074
10157
  }
10075
- return isRecord3(parsed) ? parsed : void 0;
10158
+ return isRecord4(parsed) ? parsed : void 0;
10076
10159
  }
10077
10160
  function nonEmptyString(record, field) {
10078
10161
  const value = record === void 0 ? void 0 : record[field];
@@ -10093,11 +10176,11 @@ function payloadEventsOf(body) {
10093
10176
  }
10094
10177
  const payloads = [];
10095
10178
  for (const event of events) {
10096
- if (!isRecord3(event)) {
10179
+ if (!isRecord4(event)) {
10097
10180
  continue;
10098
10181
  }
10099
10182
  const payload = event.payload;
10100
- if (!isRecord3(payload)) {
10183
+ if (!isRecord4(payload)) {
10101
10184
  continue;
10102
10185
  }
10103
10186
  payloads.push(payload);
@@ -10112,7 +10195,7 @@ function pendingOfControlRequestPayload(payload) {
10112
10195
  if (requestId === void 0) {
10113
10196
  return void 0;
10114
10197
  }
10115
- const request4 = isRecord3(payload.request) ? payload.request : void 0;
10198
+ const request4 = isRecord4(payload.request) ? payload.request : void 0;
10116
10199
  const subtype = nonEmptyString(request4, "subtype");
10117
10200
  const type = subtype ?? "control_request";
10118
10201
  let summary = "";
@@ -10129,7 +10212,7 @@ function answeredRequestIdsOfControlResponsePayload(payload) {
10129
10212
  if (payload.type !== "control_response") {
10130
10213
  return [];
10131
10214
  }
10132
- const response = isRecord3(payload.response) ? payload.response : void 0;
10215
+ const response = isRecord4(payload.response) ? payload.response : void 0;
10133
10216
  const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
10134
10217
  return id === void 0 ? [] : [id];
10135
10218
  }
@@ -10358,7 +10441,7 @@ function createRcSessionTracker(deps) {
10358
10441
  return;
10359
10442
  }
10360
10443
  entry.sequenceNum = entry.sequenceNum === void 0 || event.sequence_num > entry.sequenceNum ? event.sequence_num : entry.sequenceNum;
10361
- if (!isRecord3(event.payload)) {
10444
+ if (!isRecord4(event.payload)) {
10362
10445
  return;
10363
10446
  }
10364
10447
  entry.liveTasks = applyAgentTaskEvent(entry.liveTasks, sessionId, event.payload, deps.now());
@@ -11781,11 +11864,11 @@ import path10 from "node:path";
11781
11864
  function isSafeSessionId(sessionId) {
11782
11865
  return /^[A-Za-z0-9_-]+$/.test(sessionId);
11783
11866
  }
11784
- function isRecord4(value) {
11867
+ function isRecord5(value) {
11785
11868
  return typeof value === "object" && value !== null && !Array.isArray(value);
11786
11869
  }
11787
11870
  function credentialOfJson(parsed) {
11788
- if (!isRecord4(parsed)) {
11871
+ if (!isRecord5(parsed)) {
11789
11872
  return void 0;
11790
11873
  }
11791
11874
  const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
@@ -11815,7 +11898,7 @@ function createRcCredentialStore(fs13, dir) {
11815
11898
  } catch {
11816
11899
  return void 0;
11817
11900
  }
11818
- return isRecord4(parsed) ? parsed : void 0;
11901
+ return isRecord5(parsed) ? parsed : void 0;
11819
11902
  };
11820
11903
  return {
11821
11904
  read: (sessionId) => {
@@ -12751,7 +12834,13 @@ function validateMembers(pools, name, members) {
12751
12834
  throw new UsageError(`Member "pool:${cycle[1] ?? name}" would make pool "${name}" reach itself: ${cycle.join(" -> ")}. A pool cannot nest itself, directly or through another pool.`);
12752
12835
  }
12753
12836
  }
12754
- function addPool(paths, name, identities, preference) {
12837
+ function poolPolicyOf(policy) {
12838
+ return { ...policy.preference === void 0 ? {} : { preference: policy.preference } };
12839
+ }
12840
+ function isPoolChange(change) {
12841
+ return change.identities !== void 0 || change.preference !== void 0;
12842
+ }
12843
+ function addPool(paths, name, identities, policy = {}) {
12755
12844
  if (!PoolNameSchema.safeParse(name).success) {
12756
12845
  throw new InvalidPoolNameError(name);
12757
12846
  }
@@ -12760,14 +12849,16 @@ function addPool(paths, name, identities, preference) {
12760
12849
  throw new PoolAlreadyExistsError(name);
12761
12850
  }
12762
12851
  validateMembers(pools, name, identities);
12763
- const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
12852
+ const pool = { identities: [...identities], ...poolPolicyOf(policy) };
12764
12853
  writePools(paths, { ...pools, [name]: pool });
12765
12854
  return pool;
12766
12855
  }
12767
- function setPool(paths, name, identities, preference) {
12768
- requirePool(paths, name);
12856
+ function changePool(paths, name, change) {
12857
+ const existing = requirePool(paths, name);
12858
+ const identities = change.identities ?? existing.identities;
12769
12859
  validateMembers(readPools(paths), name, identities);
12770
- const pool = { identities: [...identities], ...preference === void 0 ? {} : { preference } };
12860
+ const preference = change.preference === void 0 ? existing.preference : change.preference === false ? void 0 : change.preference;
12861
+ const pool = { identities: [...identities], ...poolPolicyOf({ preference }) };
12771
12862
  writePools(paths, { ...readPools(paths), [name]: pool });
12772
12863
  return pool;
12773
12864
  }
@@ -13752,6 +13843,7 @@ function parseSiwcFile(raw, filePath) {
13752
13843
 
13753
13844
  // src/doctorReport.ts
13754
13845
  init_load();
13846
+ init_unrecognisedKeys();
13755
13847
  init_schema();
13756
13848
  init_credential();
13757
13849
  init_credentialCache();
@@ -13962,11 +14054,11 @@ function validateJson(schema, input) {
13962
14054
  } catch (error) {
13963
14055
  return { ok: false, message: `${input.path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
13964
14056
  }
13965
- const result = schema.safeParse(parsed);
14057
+ const result = parseIgnoringUnrecognisedKeys(schema, parsed);
13966
14058
  if (!result.success) {
13967
- return { ok: false, message: new ConfigValidationError(input.path, result.error.issues).message };
14059
+ return { ok: false, message: new ConfigValidationError(input.path, result.issues).message };
13968
14060
  }
13969
- return { ok: true, data: result.data };
14061
+ return { ok: true, data: result.data, ...result.ignored.length === 0 ? {} : { warning: describeIgnoredKeys(input.path, result.ignored) } };
13970
14062
  }
13971
14063
  function tcpPortEraRecord(raw) {
13972
14064
  let parsed;
@@ -14059,11 +14151,14 @@ function pushProvider(push, entry, cacheEnv, signIn) {
14059
14151
  push("provider", "fail", new LegacyProviderFileError(entry.path, conversion).message, entry.name);
14060
14152
  return;
14061
14153
  }
14062
- const validated3 = ProviderSchema.safeParse(parsed);
14154
+ const validated3 = parseIgnoringUnrecognisedKeys(ProviderSchema, parsed);
14063
14155
  if (!validated3.success) {
14064
- push("provider", "fail", new ConfigValidationError(entry.path, validated3.error.issues).message, entry.name);
14156
+ push("provider", "fail", new ConfigValidationError(entry.path, validated3.issues).message, entry.name);
14065
14157
  return;
14066
14158
  }
14159
+ if (validated3.ignored.length > 0) {
14160
+ push("provider", "warn", describeIgnoredKeys(entry.path, validated3.ignored), entry.name);
14161
+ }
14067
14162
  const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated3.data.credential);
14068
14163
  push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated3.data)}, credential ${describeCredential(validated3.data.credential)})${cache.text}.`, entry.name);
14069
14164
  if (validated3.data.env?.ENABLE_TOOL_SEARCH === void 0) {
@@ -14098,6 +14193,13 @@ function runDoctor(params) {
14098
14193
  const push = (section, severity, message, subject) => {
14099
14194
  findings.push({ section, severity, message, ...subject === void 0 ? {} : { subject } });
14100
14195
  };
14196
+ const validate = (schema, input, section, subject) => {
14197
+ const validated3 = validateJson(schema, input);
14198
+ if (validated3.ok && validated3.warning !== void 0) {
14199
+ push(section, "warn", validated3.warning, subject);
14200
+ }
14201
+ return validated3;
14202
+ };
14101
14203
  const checkPin = (section, subject, where, version) => {
14102
14204
  const installed = params.installedClaudeVersions;
14103
14205
  if (version === void 0 || installed === void 0 || installed.includes(version)) {
@@ -14137,7 +14239,7 @@ function runDoctor(params) {
14137
14239
  pushLegacyName(push, params);
14138
14240
  const profileSources = /* @__PURE__ */ new Map();
14139
14241
  for (const entry of params.configProfiles) {
14140
- const validated3 = validateJson(ConfigProfileSchema, entry);
14242
+ const validated3 = validate(ConfigProfileSchema, entry, "config-profile", entry.name);
14141
14243
  if (!validated3.ok) {
14142
14244
  push("config-profile", "fail", validated3.message, entry.name);
14143
14245
  continue;
@@ -14159,7 +14261,7 @@ function runDoctor(params) {
14159
14261
  const validProfileNames = new Set(profileSources.keys());
14160
14262
  const validIdentityNames = /* @__PURE__ */ new Set();
14161
14263
  for (const entry of params.identities) {
14162
- const validated3 = validateJson(IdentitySchema, entry);
14264
+ const validated3 = validate(IdentitySchema, entry, "identity", entry.name);
14163
14265
  if (!validated3.ok) {
14164
14266
  push("identity", "fail", validated3.message, entry.name);
14165
14267
  continue;
@@ -14245,7 +14347,7 @@ function runDoctor(params) {
14245
14347
  if (params.directoryRules.raw === void 0) {
14246
14348
  push("directory-rules", "pass", "No directory-rules.json configured.");
14247
14349
  } else {
14248
- const validated3 = validateJson(DirectoryRulesSchema, params.directoryRules);
14350
+ const validated3 = validate(DirectoryRulesSchema, params.directoryRules, "directory-rules");
14249
14351
  if (!validated3.ok) {
14250
14352
  push("directory-rules", "fail", validated3.message);
14251
14353
  } else {
@@ -14270,7 +14372,7 @@ function runDoctor(params) {
14270
14372
  if (params.globalConfig.raw === void 0) {
14271
14373
  push("global-config", "pass", "No config.json configured.");
14272
14374
  } else {
14273
- const validated3 = validateJson(GlobalConfigSchema, params.globalConfig);
14375
+ const validated3 = validate(GlobalConfigSchema, params.globalConfig, "global-config");
14274
14376
  if (!validated3.ok) {
14275
14377
  push("global-config", "fail", validated3.message);
14276
14378
  } else {
@@ -14290,7 +14392,7 @@ function runDoctor(params) {
14290
14392
  if (params.categoriesLocal.raw === void 0) {
14291
14393
  push("categories-local", "pass", "No categories.local.json configured.");
14292
14394
  } else {
14293
- const validated3 = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
14395
+ const validated3 = validate(CategoryClassificationOverlaySchema, params.categoriesLocal, "categories-local");
14294
14396
  push(
14295
14397
  "categories-local",
14296
14398
  validated3.ok ? "pass" : "fail",
@@ -15104,20 +15206,19 @@ function createConfigApiRouter(deps) {
15104
15206
  add: route("/rest/config/pool/add", "Define a pool").input(ConfigPoolAddInputSchema).output(ConfigPoolAddOutputSchema).handler(
15105
15207
  ({ input }) => guarded(() => {
15106
15208
  requireIdentityNames(paths, input.identities);
15107
- const created = addPool(paths, input.name, input.identities, input.preference);
15209
+ const created = addPool(paths, input.name, input.identities, { preference: input.preference });
15108
15210
  return { action: "created", kind: "pool", name: input.name, value: created };
15109
15211
  })
15110
15212
  ),
15111
15213
  set: route("/rest/config/pool/set", "Replace a pool's members, its preference or both").input(ConfigPoolSetInputSchema).output(ConfigPoolSetOutputSchema).handler(
15112
15214
  ({ input }) => guarded(() => {
15113
- requireChange(input.identities !== void 0 || input.preference !== void 0, "identities or preference");
15114
- const existing = requirePool(paths, input.name);
15215
+ const change = { identities: input.identities, preference: input.preference };
15216
+ requireChange(isPoolChange(change), "identities or preference");
15217
+ requirePool(paths, input.name);
15115
15218
  if (input.identities !== void 0) {
15116
15219
  requireIdentityNames(paths, input.identities);
15117
15220
  }
15118
- const members = input.identities ?? existing.identities;
15119
- const preference = input.preference === void 0 ? existing.preference : input.preference === false ? void 0 : input.preference;
15120
- return { action: "updated", kind: "pool", name: input.name, value: setPool(paths, input.name, members, preference) };
15221
+ return { action: "updated", kind: "pool", name: input.name, value: changePool(paths, input.name, change) };
15121
15222
  })
15122
15223
  ),
15123
15224
  remove: route("/rest/config/pool/remove", "Delete a pool").input(ConfigPoolRemoveInputSchema).output(ConfigPoolRemoveOutputSchema).handler(({ input }) => {
@@ -15901,15 +16002,15 @@ function createQuotaExpiringPublisher(hub, deps) {
15901
16002
 
15902
16003
  // src/frontdoor/rcLiveUsage.ts
15903
16004
  var RC_RATE_LIMIT_EVENT_TYPE = "rate_limit_event";
15904
- function isRecord7(value) {
16005
+ function isRecord8(value) {
15905
16006
  return typeof value === "object" && value !== null && !Array.isArray(value);
15906
16007
  }
15907
16008
  function rcRateLimitInfoOf(event) {
15908
16009
  const payload = event.envelope.payload;
15909
- if (!isRecord7(payload) || payload.type !== RC_RATE_LIMIT_EVENT_TYPE) {
16010
+ if (!isRecord8(payload) || payload.type !== RC_RATE_LIMIT_EVENT_TYPE) {
15910
16011
  return void 0;
15911
16012
  }
15912
- if (!isRecord7(payload.rate_limit_info)) {
16013
+ if (!isRecord8(payload.rate_limit_info)) {
15913
16014
  return void 0;
15914
16015
  }
15915
16016
  const parsed = RcRateLimitInfoSchema.safeParse(payload.rate_limit_info);
@@ -15997,7 +16098,7 @@ function createSseParser() {
15997
16098
  }
15998
16099
  };
15999
16100
  }
16000
- function isRecord8(value) {
16101
+ function isRecord9(value) {
16001
16102
  return typeof value === "object" && value !== null && !Array.isArray(value);
16002
16103
  }
16003
16104
  function sequenceNumOf(value) {
@@ -16019,7 +16120,7 @@ function parseRcStreamEnvelope(parsed) {
16019
16120
  } catch {
16020
16121
  return void 0;
16021
16122
  }
16022
- if (!isRecord8(data)) {
16123
+ if (!isRecord9(data)) {
16023
16124
  return void 0;
16024
16125
  }
16025
16126
  const eventType = data.event_type;
@@ -16260,7 +16361,7 @@ init_capability();
16260
16361
  // src/frontdoor/rcSelfHostEndpoints.ts
16261
16362
  var RC_SOURCE_WORKER = "worker";
16262
16363
  var RC_SOURCE_CLIENT = "client";
16263
- function isRecord9(value) {
16364
+ function isRecord10(value) {
16264
16365
  return typeof value === "object" && value !== null && !Array.isArray(value);
16265
16366
  }
16266
16367
  function parseJsonObject2(body) {
@@ -16270,7 +16371,7 @@ function parseJsonObject2(body) {
16270
16371
  } catch {
16271
16372
  return void 0;
16272
16373
  }
16273
- return isRecord9(parsed) ? parsed : void 0;
16374
+ return isRecord10(parsed) ? parsed : void 0;
16274
16375
  }
16275
16376
  function nonEmptyString2(record, field) {
16276
16377
  const value = record === void 0 ? void 0 : record[field];
@@ -16344,7 +16445,7 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
16344
16445
  }
16345
16446
  const results = [];
16346
16447
  for (const event of events) {
16347
- if (!isRecord9(event) || !isRecord9(event.payload)) {
16448
+ if (!isRecord10(event) || !isRecord10(event.payload)) {
16348
16449
  await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a teleport write is an object carrying one payload object" } });
16349
16450
  return true;
16350
16451
  }
@@ -16444,7 +16545,7 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
16444
16545
  // src/frontdoor/rcSelfHostConversations.ts
16445
16546
  var RC_CONVERSATIONS_PATH_PREFIX = "/v1/code/conversations";
16446
16547
  var RC_CONVERSATION_ID_PREFIX = "conv_";
16447
- function isRecord10(value) {
16548
+ function isRecord11(value) {
16448
16549
  return typeof value === "object" && value !== null && !Array.isArray(value);
16449
16550
  }
16450
16551
  function parseJsonObject3(body) {
@@ -16454,7 +16555,7 @@ function parseJsonObject3(body) {
16454
16555
  } catch {
16455
16556
  return void 0;
16456
16557
  }
16457
- return isRecord10(parsed) ? parsed : void 0;
16558
+ return isRecord11(parsed) ? parsed : void 0;
16458
16559
  }
16459
16560
  function nonEmptyString3(record, field) {
16460
16561
  const value = record === void 0 ? void 0 : record[field];
@@ -16508,7 +16609,7 @@ async function serveRcConversations(ctx, pathname, method) {
16508
16609
  }
16509
16610
  const results = [];
16510
16611
  for (const event of events) {
16511
- if (!isRecord10(event) || !isRecord10(event.payload)) {
16612
+ if (!isRecord11(event) || !isRecord11(event.payload)) {
16512
16613
  await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
16513
16614
  return;
16514
16615
  }
@@ -16540,7 +16641,7 @@ var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:
16540
16641
  var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
16541
16642
  var MS_PER_SECOND5 = 1e3;
16542
16643
  var HTTP_SERVICE_UNAVAILABLE = 503;
16543
- function isRecord11(value) {
16644
+ function isRecord12(value) {
16544
16645
  return typeof value === "object" && value !== null && !Array.isArray(value);
16545
16646
  }
16546
16647
  function parseJsonObject4(body) {
@@ -16550,7 +16651,7 @@ function parseJsonObject4(body) {
16550
16651
  } catch {
16551
16652
  return void 0;
16552
16653
  }
16553
- return isRecord11(parsed) ? parsed : void 0;
16654
+ return isRecord12(parsed) ? parsed : void 0;
16554
16655
  }
16555
16656
  function growthBookFeatures() {
16556
16657
  return {
@@ -16904,7 +17005,7 @@ var RC_SELF_HOST_WORKER_EPOCH = 1;
16904
17005
  var RC_STREAM_EVENT_NAME2 = "client_event";
16905
17006
  var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
16906
17007
  var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
16907
- function isRecord12(value) {
17008
+ function isRecord13(value) {
16908
17009
  return typeof value === "object" && value !== null && !Array.isArray(value);
16909
17010
  }
16910
17011
  function isWebSearchRefusal(value) {
@@ -16988,7 +17089,7 @@ function parseJsonObject5(body) {
16988
17089
  } catch {
16989
17090
  return void 0;
16990
17091
  }
16991
- return isRecord12(parsed) ? parsed : void 0;
17092
+ return isRecord13(parsed) ? parsed : void 0;
16992
17093
  }
16993
17094
  function mintWorkerJwt(randomToken, issuedAt) {
16994
17095
  const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
@@ -17248,7 +17349,7 @@ data: ${JSON.stringify(envelope)}
17248
17349
  if (status !== void 0) {
17249
17350
  session.workerStatus = status;
17250
17351
  }
17251
- if (isRecord12(body?.external_metadata)) {
17352
+ if (isRecord13(body?.external_metadata)) {
17252
17353
  session.externalMetadata = body.external_metadata;
17253
17354
  }
17254
17355
  await answerJson2(response, HTTP_STATUS.ok, {});
@@ -17278,7 +17379,7 @@ data: ${JSON.stringify(envelope)}
17278
17379
  }
17279
17380
  const results = [];
17280
17381
  for (const event of events) {
17281
- if (!isRecord12(event) || !isRecord12(event.payload)) {
17382
+ if (!isRecord13(event) || !isRecord13(event.payload)) {
17282
17383
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a batch is an object carrying one payload object" } });
17283
17384
  return;
17284
17385
  }
@@ -17311,7 +17412,7 @@ data: ${JSON.stringify(envelope)}
17311
17412
  return;
17312
17413
  }
17313
17414
  for (const event of events) {
17314
- if (isRecord12(event)) {
17415
+ if (isRecord13(event)) {
17315
17416
  session.internalEvents.push({ eventId: deps.newUuid(), body: event });
17316
17417
  }
17317
17418
  }
@@ -17407,7 +17508,7 @@ data: ${JSON.stringify(envelope)}
17407
17508
  }
17408
17509
  const results = [];
17409
17510
  for (const event of events) {
17410
- if (!isRecord12(event) || !isRecord12(event.payload)) {
17511
+ if (!isRecord13(event) || !isRecord13(event.payload)) {
17411
17512
  await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
17412
17513
  return;
17413
17514
  }
@@ -17544,7 +17645,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
17544
17645
  var RC_SELF_HOST_RECORD_FILE = "credential.json";
17545
17646
  var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
17546
17647
  var SELF_HOST_SUBSCRIPTION_TYPE = "max";
17547
- function isRecord13(value) {
17648
+ function isRecord14(value) {
17548
17649
  return typeof value === "object" && value !== null && !Array.isArray(value);
17549
17650
  }
17550
17651
  function readJsonObject(fs13, file) {
@@ -17558,7 +17659,7 @@ function readJsonObject(fs13, file) {
17558
17659
  } catch {
17559
17660
  return void 0;
17560
17661
  }
17561
- return isRecord13(parsed) ? parsed : void 0;
17662
+ return isRecord14(parsed) ? parsed : void 0;
17562
17663
  }
17563
17664
  function readRcSelfHostRecord(fs13, frontdoorDir) {
17564
17665
  const parsed = readJsonObject(fs13, path33.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
@@ -17582,7 +17683,7 @@ function mintRcSelfHostCredential(deps) {
17582
17683
  const recordFile = path33.join(recordDir, RC_SELF_HOST_RECORD_FILE);
17583
17684
  const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
17584
17685
  const existingCredentials = readJsonObject(deps.fs, credentialsFile);
17585
- const existingOauth = isRecord13(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
17686
+ const existingOauth = isRecord14(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
17586
17687
  const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
17587
17688
  const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
17588
17689
  if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
@@ -17599,7 +17700,7 @@ function mintRcSelfHostCredential(deps) {
17599
17700
  deps.fs.writeFilePrivate(credentialsFile, `${JSON.stringify({ ...existingCredentials, claudeAiOauth: { accessToken, refreshToken, expiresAt: null, scopes: [...RC_SELF_HOST_SCOPE_LIST], subscriptionType: SELF_HOST_SUBSCRIPTION_TYPE, rateLimitTier: null } }, null, 2)}
17600
17701
  `);
17601
17702
  const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
17602
- const existingBlock = (value) => isRecord13(value) ? value : {};
17703
+ const existingBlock = (value) => isRecord14(value) ? value : {};
17603
17704
  deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
17604
17705
  ...claudeJson,
17605
17706
  oauthAccount: {
@@ -18333,6 +18434,7 @@ export {
18333
18434
  buildRcSetPermissionModePayload,
18334
18435
  buildRcUserMessagePayload,
18335
18436
  carryOver,
18437
+ changePool,
18336
18438
  checkReportHasWarnings,
18337
18439
  checkReportToJson,
18338
18440
  collectCheckReport,
@@ -18490,7 +18592,6 @@ export {
18490
18592
  setDefaultConfigProfile,
18491
18593
  setGlobalDefaultProfile,
18492
18594
  setIdentityCredential,
18493
- setPool,
18494
18595
  setProfileCategories,
18495
18596
  setProfileEntries,
18496
18597
  setProfileLaunchFlags,