@ordewell/web 0.11.1 → 0.11.3

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/AUTHORS.md CHANGED
@@ -27,6 +27,10 @@ record of who built what, rather than `git log`.
27
27
  - [@haltingstate](https://github.com/haltingstate) — made the terminal UI's
28
28
  stepper say what a saved step does next, instead of encoding it in a
29
29
  sentinel index (#123).
30
+ - [@Logic769](https://github.com/Logic769) — made `/resume` an alias of
31
+ `/sessions` in the planner chat (#139).
32
+ - Hanbin Tan ([@hskl18](https://github.com/hskl18)) — documented how
33
+ planners and runners get their models and credentials (#140).
30
34
 
31
35
  Thank you. Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
32
36
 
@@ -407,6 +407,8 @@ var OrchestratorPool = class {
407
407
  sequence = /* @__PURE__ */ new Map();
408
408
  registry = new core.RunnerRegistry();
409
409
  modelResolver;
410
+ /** A host that supplied its own resolver answers for every workspace, as a supplied probe does. */
411
+ suppliedModelResolver;
410
412
  runnerInstallation;
411
413
  /**
412
414
  * A host that supplied its own probe (a test, or a surface that owns one).
@@ -419,11 +421,19 @@ var OrchestratorPool = class {
419
421
  settingsService = new core.SettingsService();
420
422
  plannerModelMemory = new core.PlannerModelMemory(this.settingsService);
421
423
  /**
422
- * The model a settings write picked in this daemon. The environment's model
423
- * is otherwise inherited, and an agent planning only because detection
424
- * found it does not plan with an inherited model it cannot be shown to run.
424
+ * The model a settings write picked in this daemon, and the workspace it was
425
+ * picked in. The environment's model is otherwise inherited, and an agent
426
+ * planning only because detection found it does not plan with an inherited
427
+ * model it cannot be shown to run. The pick is that workspace's: another
428
+ * may name another account, whose catalog judges the model afresh.
425
429
  */
426
430
  pickedModel;
431
+ /**
432
+ * The last planner write a route made, so each lands after the ones made
433
+ * before it: a switch still waiting for its catalog must not write over a
434
+ * pick made while it waited (ADR-0040).
435
+ */
436
+ plannerWrites = Promise.resolve();
427
437
  runner;
428
438
  /** One probe per daemon, asked again whenever the runner list is refreshed (ADR-0033). */
429
439
  codexSandbox;
@@ -438,7 +448,10 @@ var OrchestratorPool = class {
438
448
  env: () => core.workspaceEnvOf(process.cwd()),
439
449
  workspace: () => process.cwd()
440
450
  });
441
- this.modelResolver = deps.modelResolver ?? new core.ModelResolver(this.registry, new WebConfig());
451
+ this.suppliedModelResolver = deps.modelResolver !== void 0;
452
+ this.modelResolver = deps.modelResolver ?? new core.ModelResolver(this.registry, new WebConfig(), {
453
+ env: async () => ({ ...process.env, ...await core.workspaceEnvOf(process.cwd()) })
454
+ });
442
455
  this.codexSandbox = deps.codexSandbox ?? new core.CodexSandboxAvailability(this.registry, { spawn: child_process.spawn, fetch: globalThis.fetch }, { cwd: process.cwd(), env: process.env });
443
456
  this.leaseHost = deps.leaseHost;
444
457
  this.isolationPreview = deps.isolationPreview ?? core.createIsolationPreview({ config: new WebConfig() });
@@ -618,7 +631,7 @@ var OrchestratorPool = class {
618
631
  core.ensureStateDirIgnored(workspace);
619
632
  core.assertWorkspaceIsProject(workspace);
620
633
  }
621
- const config = this.plannerConfig(modelOverride);
634
+ const config = this.plannerConfig(modelOverride, workspace);
622
635
  const fsAdapter = new PoolFileSystem(workspace);
623
636
  const broadcast = (msg) => this.broadcast(sessionId, msg);
624
637
  const runner = new PoolAwareRunner(sessionId, broadcast, this.runner);
@@ -637,7 +650,7 @@ var OrchestratorPool = class {
637
650
  broadcast,
638
651
  // The notification channel above is silent here; this is how a client hears how the run isolates.
639
652
  onNotice: (notice) => this.broadcast(sessionId, notice),
640
- modelResolver: this.modelResolver,
653
+ modelResolver: this.resolverFor(workspace),
641
654
  settings: () => this.runtimeSettings(),
642
655
  sessionId,
643
656
  ...this.leaseHost ? { leaseHost: this.leaseHost } : {}
@@ -650,13 +663,13 @@ var OrchestratorPool = class {
650
663
  * lists are handed over as a reader, not a snapshot, so a credential change
651
664
  * reaches a session that is already open.
652
665
  */
653
- plannerConfig(modelOverride) {
666
+ plannerConfig(modelOverride, workspace, catalog = (provider) => this.plannerCatalogFor(provider, workspace)) {
654
667
  return new WebConfig({
655
668
  enabledRunners: this.enabledRunnerOverride(),
656
669
  modelOverride,
657
670
  providerModelLists: () => this.cachedProviderLists,
658
- pickedModel: () => this.pickedModel,
659
- agentCatalog: (provider) => this.plannerCatalogFor(provider).map((model) => model.id)
671
+ pickedModel: () => this.pickedModel?.workspace === workspaceKey(workspace) ? this.pickedModel : void 0,
672
+ agentCatalog: (provider) => catalog(provider).map((model) => model.id)
660
673
  });
661
674
  }
662
675
  /**
@@ -679,8 +692,13 @@ var OrchestratorPool = class {
679
692
  return enabled ? current.includes(id) ? current : [...current, id] : current.filter((r) => r !== id);
680
693
  });
681
694
  }
682
- getSettings(sessionId) {
683
- const config = this.plannerConfig();
695
+ /**
696
+ * What `sessionId`, or a client in `workspace`, plans with: the planner's
697
+ * model is judged against that workspace's runner catalog (see
698
+ * {@link settingsWorkspace}).
699
+ */
700
+ getSettings(sessionId, workspace) {
701
+ const config = this.plannerConfig(void 0, this.settingsWorkspace(sessionId, workspace));
684
702
  const userSettings = this.settingsService.getAll();
685
703
  const live = sessionId ? this.sessions.get(sessionId) : void 0;
686
704
  return {
@@ -709,45 +727,106 @@ var OrchestratorPool = class {
709
727
  * they were opened with. Throws a {@link RunSettingsError}, saving nothing,
710
728
  * for a value out of range.
711
729
  */
712
- updateRunSettings(changes, sessionId) {
730
+ updateRunSettings(changes, sessionId, workspace) {
713
731
  this.settingsService.updateRunSettings(changes);
714
732
  if (sessionId) this.sessions.get(sessionId)?.applyRunSettings(changes);
715
- return this.getSettings(sessionId);
733
+ return this.getSettings(sessionId, workspace);
734
+ }
735
+ /**
736
+ * The workspace whose runner catalog a settings read or write is judged
737
+ * against: an open session's own, else the one the client says it is in.
738
+ * Without either the daemon's cwd stands in. One daemon serves several
739
+ * workspaces, and each may name another Claude account in its environment.
740
+ */
741
+ settingsWorkspace(sessionId, workspace) {
742
+ return (sessionId ? this.sessions.get(sessionId)?.workspaceRoot : void 0) ?? workspace;
716
743
  }
717
744
  /**
718
745
  * The catalog `selectPlanner` should judge `provider`'s
719
746
  * remembered model against — a harness planner's own runner catalog, or the
720
747
  * cross-provider picker list for a vendor (ADR-0009), same split as
721
748
  * `plannerModelItems` in the TUI reducer. Read from whatever the resolver
722
- * has already cached so this stays synchronous; a cold cache degrades to an
723
- * empty catalog rather than kicking off discovery mid-settings-write.
749
+ * has already cached so this stays synchronous; the routes discover it
750
+ * first where it decides the answer ({@link discoverPlannerCatalogs},
751
+ * {@link writeSettings}), and a catalog still cold degrades to an empty one
752
+ * rather than kicking off discovery mid-settings-write.
724
753
  */
725
- plannerCatalogFor(provider) {
726
- const runner = core.runnerForProvider(provider);
727
- if (runner) {
728
- return this.modelResolver.getCachedRunnerModels(runner).map((m) => ({ id: m.modelId, variants: m.variants }));
729
- }
730
- return this.modelResolver.getCachedPickerOptions().map((o) => ({ id: o.id }));
754
+ plannerCatalogFor(provider, workspace) {
755
+ return this.resolverFor(workspace).cachedPlannerCatalog(provider);
756
+ }
757
+ /**
758
+ * Discover the runner catalog a settings read for `workspace` is judged
759
+ * against, when the read consults one not yet held. Most reads consult
760
+ * none — a vendor planner, a model picked in that workspace, an agent's
761
+ * alias or remembered pick — and answer at once, however slow the runner
762
+ * is to list its models (ADR-0040). Only a configured model whose owner
763
+ * only the catalog can tell waits: a cold one would report the wrong model.
764
+ * Never throws: a catalog that cannot be discovered stays empty.
765
+ */
766
+ async discoverPlannerCatalogs(sessionId, requestWorkspace) {
767
+ const workspace = this.settingsWorkspace(sessionId, requestWorkspace);
768
+ const consulted = [];
769
+ void this.plannerConfig(void 0, workspace, (provider) => {
770
+ consulted.push(provider);
771
+ return this.plannerCatalogFor(provider, workspace);
772
+ }).planner;
773
+ await this.discoverUnheld(workspace, consulted);
774
+ }
775
+ /**
776
+ * {@link updateSettings} as a route makes it. A write that changes who plans
777
+ * lands after the planner writes made before it and, where its judgement
778
+ * reads a catalog not yet held, waits for that one; every other write — a
779
+ * key, an allowlist, a limit — lands at once (ADR-0040).
780
+ */
781
+ async writeSettings(changes, sessionId, requestWorkspace) {
782
+ const change = plannerChangeOf(changes, admittedEnv(changes)?.accepted);
783
+ if (!changesPlanner(change)) return this.updateSettings(changes, sessionId, requestWorkspace);
784
+ const turn = this.plannerWrites.then(async () => {
785
+ const workspace = this.settingsWorkspace(sessionId, requestWorkspace);
786
+ const judging = this.catalogJudging(change, workspace);
787
+ await this.discoverUnheld(workspace, judging ? [judging] : []);
788
+ return this.updateSettings(changes, sessionId, requestWorkspace);
789
+ });
790
+ this.plannerWrites = turn.catch(() => void 0);
791
+ return turn;
792
+ }
793
+ /**
794
+ * The provider whose catalog `selectPlanner` reads to judge `change`: a
795
+ * switch with no model of its own restores the remembered pick only from
796
+ * the incoming catalog, and an agent's effort alone survives only where its
797
+ * model in effect offers it. An explicit model is the user's word whatever
798
+ * the catalog lists, and a cold catalog refuses no effort, so it reads none.
799
+ */
800
+ catalogJudging(change, workspace) {
801
+ const current = this.plannerProviderInEffect(workspace);
802
+ const provider = change.provider ?? current;
803
+ const model = change.model?.trim();
804
+ if (provider !== current) return model ? void 0 : provider;
805
+ return model === void 0 && change.effort !== void 0 && core.runnerForProvider(provider) ? provider : void 0;
806
+ }
807
+ /**
808
+ * Discover the catalogs of `providers` that `workspace`'s resolver does not
809
+ * hold yet; a held one, or a degraded one discovery still remembers,
810
+ * answers at once. Never throws.
811
+ */
812
+ async discoverUnheld(workspace, providers) {
813
+ const resolver = this.resolverFor(workspace);
814
+ await Promise.all([...new Set(providers)].map((provider) => resolver.plannerCatalog(provider)));
731
815
  }
732
816
  /**
733
817
  * The provider planning right now. A vendor planner is resolved from its
734
818
  * model id, so this is read again after a write rather than carried over.
735
819
  */
736
- plannerProviderInEffect() {
737
- return this.plannerConfig().aiProvider;
820
+ plannerProviderInEffect(workspace) {
821
+ return this.plannerConfig(void 0, workspace).aiProvider;
738
822
  }
739
- updateSettings(changes, sessionId) {
740
- const current = this.plannerConfig().planner;
741
- const rawEnv = changes.env && typeof changes.env === "object" && !Array.isArray(changes.env) ? changes.env : void 0;
742
- const admission = rawEnv ? core.admitSettingsEnv(rawEnv) : void 0;
823
+ updateSettings(changes, sessionId, requestWorkspace) {
824
+ const workspace = this.settingsWorkspace(sessionId, requestWorkspace);
825
+ const current = this.plannerConfig(void 0, workspace).planner;
826
+ const admission = admittedEnv(changes);
743
827
  const envChanges = admission ? admission.accepted : void 0;
744
- const plannerChange = changes.inherited === true ? {} : {
745
- provider: envChanges?.AI_PROVIDER,
746
- model: envChanges?.ORCHESTRATOR_MODEL ?? stringField(changes.orchestratorModel),
747
- effort: envChanges?.ORDEWELL_PLANNER_EFFORT ?? stringField(changes.plannerThinkingEffort)
748
- };
749
- const touchesPlanner = Object.values(plannerChange).some((v) => v !== void 0);
750
- const selection = touchesPlanner ? core.selectPlanner(current, plannerChange, this.plannerModelMemory, this.plannerCatalogFor(plannerChange.provider ?? current.provider)) : void 0;
828
+ const plannerChange = plannerChangeOf(changes, envChanges);
829
+ const selection = changesPlanner(plannerChange) ? core.selectPlanner(current, plannerChange, this.plannerModelMemory, this.plannerCatalogFor(plannerChange.provider ?? current.provider, workspace)) : void 0;
751
830
  if (envChanges) {
752
831
  let touched = false;
753
832
  for (const [key, value] of Object.entries(envChanges)) {
@@ -763,14 +842,15 @@ var OrchestratorPool = class {
763
842
  }
764
843
  if (touched) {
765
844
  this.modelResolver.invalidate();
766
- this.modelResolver.refreshRunnerModels();
767
845
  this.dropUnkeyedProviderLists();
768
846
  }
769
847
  }
770
848
  if (selection) {
771
849
  if (plannerChange.provider !== void 0 || plannerChange.model !== void 0) process.env.ORCHESTRATOR_MODEL = selection.next.model;
772
850
  process.env.ORDEWELL_PLANNER_EFFORT = selection.next.effort;
773
- if (plannerChange.model !== void 0) this.pickedModel = { model: selection.next.model, provider: this.plannerProviderInEffect() };
851
+ if (plannerChange.model !== void 0) {
852
+ this.pickedModel = { model: selection.next.model, provider: this.plannerProviderInEffect(workspace), workspace: workspaceKey(workspace) };
853
+ }
774
854
  }
775
855
  if (changes.modelAllowlist && typeof changes.modelAllowlist === "object") {
776
856
  const allowlist = changes.modelAllowlist;
@@ -786,10 +866,10 @@ var OrchestratorPool = class {
786
866
  if (selection?.remember) {
787
867
  const { provider, model, effort } = selection.remember;
788
868
  const switched = provider !== current.provider;
789
- this.plannerModelMemory.remember(switched ? provider : this.plannerProviderInEffect(), model, effort);
869
+ this.plannerModelMemory.remember(switched ? provider : this.plannerProviderInEffect(workspace), model, effort);
790
870
  }
791
871
  const rejectedEnvKeys = admission?.rejected ?? [];
792
- const settings = this.getSettings(sessionId);
872
+ const settings = this.getSettings(sessionId, workspace);
793
873
  if (rejectedEnvKeys.length > 0) settings.rejectedEnvKeys = rejectedEnvKeys;
794
874
  if (selection?.recall) settings.switchRecall = selection.recall;
795
875
  return settings;
@@ -812,16 +892,22 @@ var OrchestratorPool = class {
812
892
  process.env.ORCHESTRATOR_MODEL = model;
813
893
  return model;
814
894
  }
815
- async getProviderModels() {
895
+ /**
896
+ * The catalog as `workspace`'s runners see it: Claude Code's picker lineup
897
+ * follows the `CLAUDE_CONFIG_DIR` that workspace's environment sets.
898
+ * Without a workspace the daemon's cwd stands in.
899
+ */
900
+ async getProviderModels(workspace) {
816
901
  const config = new WebConfig();
902
+ const resolver = this.resolverFor(workspace);
817
903
  const runnerIds = this.registry.list().map((p) => p.manifest.name);
818
904
  const [runnerModels, providerLists] = await Promise.all([
819
- this.modelResolver.modelsForRunners(runnerIds),
820
- this.modelResolver.refresh(),
905
+ resolver.modelsForRunners(runnerIds),
906
+ resolver.refresh(),
821
907
  this.codexSandbox.ensure()
822
908
  ]);
823
- const orchestratorModels = await this.modelResolver.pickerOptions();
824
- const providerErrors = this.modelResolver.getDiscoveryErrors();
909
+ const orchestratorModels = await resolver.pickerOptions();
910
+ const providerErrors = resolver.getDiscoveryErrors();
825
911
  this.cachedProviderLists = providerLists;
826
912
  const allModels = [];
827
913
  const seen = /* @__PURE__ */ new Set();
@@ -840,16 +926,28 @@ var OrchestratorPool = class {
840
926
  models: allModels,
841
927
  modelsByRunner,
842
928
  modesByRunner: core.runnerModesFrom(this.registry, runnerIds),
843
- orchestratorModel: this.plannerConfig().orchestratorModel,
929
+ orchestratorModel: this.plannerConfig(void 0, workspace).orchestratorModel,
844
930
  providers,
845
931
  orchestratorModels,
846
932
  providerErrors
847
933
  };
848
934
  }
849
- getRunnerState() {
850
- const config = this.plannerConfig();
935
+ getRunnerState(workspace) {
936
+ const config = this.plannerConfig(void 0, workspace);
851
937
  return { enabledRunners: config.enabledRunners, orchestratorModel: config.orchestratorModel };
852
938
  }
939
+ /**
940
+ * The resolver whose runner catalogs are discovered under `workspace`'s
941
+ * environment, the one its tasks are launched with. Kept per workspace,
942
+ * since one daemon serves several and a catalog is cached until refreshed;
943
+ * the vendor catalogs are shared by all of them.
944
+ */
945
+ resolverFor(workspace) {
946
+ if (this.suppliedModelResolver || !workspace) return this.modelResolver;
947
+ const key = workspaceKey(workspace);
948
+ if (key === workspaceKey(void 0)) return this.modelResolver;
949
+ return this.modelResolver.withRunnerEnv(key, async () => ({ ...process.env, ...await core.workspaceEnvOf(key) }));
950
+ }
853
951
  /**
854
952
  * The probe that judges a login for `workspace`, cached so its install and
855
953
  * auth verdicts survive between runner-list refreshes. Without a workspace
@@ -1079,9 +1177,26 @@ function getSessionList(workspace) {
1079
1177
  function removeSession(sessionId, workspace) {
1080
1178
  return core.deleteSession(sessionId, workspace);
1081
1179
  }
1180
+ function admittedEnv(changes) {
1181
+ const raw = changes.env;
1182
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? core.admitSettingsEnv(raw) : void 0;
1183
+ }
1184
+ function plannerChangeOf(changes, envChanges) {
1185
+ return changes.inherited === true ? {} : {
1186
+ provider: envChanges?.AI_PROVIDER,
1187
+ model: envChanges?.ORCHESTRATOR_MODEL ?? stringField(changes.orchestratorModel),
1188
+ effort: envChanges?.ORDEWELL_PLANNER_EFFORT ?? stringField(changes.plannerThinkingEffort)
1189
+ };
1190
+ }
1191
+ function changesPlanner(change) {
1192
+ return Object.values(change).some((v) => v !== void 0);
1193
+ }
1082
1194
  function stringField(value) {
1083
1195
  return typeof value === "string" ? value : void 0;
1084
1196
  }
1197
+ function workspaceKey(workspace) {
1198
+ return path.resolve(workspace ?? process.cwd());
1199
+ }
1085
1200
  var InvalidBodyError = class extends Error {
1086
1201
  constructor(message) {
1087
1202
  super(message);
@@ -1224,7 +1339,7 @@ function plansRoute(pool2) {
1224
1339
  const queryRunners = c.req.query("runners");
1225
1340
  const runnerList = Array.isArray(runners) ? runners : runners ? [runners] : queryRunners ? queryRunners.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1226
1341
  const plan = await pool2.generatePlan(c.req.param("sessionId"), goal, runnerList, ws, model, { allowInit });
1227
- const { models, modelsByRunner } = await pool2.getProviderModels();
1342
+ const { models, modelsByRunner } = await pool2.getProviderModels(ws);
1228
1343
  const notes = (pool2.session(c.req.param("sessionId")).planState?.conversationHistory ?? []).filter((e) => e.kind === "system").map((e) => e.content);
1229
1344
  return c.json({ plan: core.surfacePlanState(plan), models, modelsByRunner, ...notes.length > 0 ? { notes } : {} });
1230
1345
  } catch (err) {
@@ -1483,8 +1598,10 @@ function plansRoute(pool2) {
1483
1598
  function runnersRoute(pool2) {
1484
1599
  const router = new hono.Hono();
1485
1600
  router.get("/", async (c) => {
1486
- const runners = await pool2.getInstalledRunners(c.req.query("workspace"));
1487
- const state = pool2.getRunnerState();
1601
+ const workspace = c.req.query("workspace");
1602
+ const runners = await pool2.getInstalledRunners(workspace);
1603
+ await pool2.discoverPlannerCatalogs(void 0, workspace);
1604
+ const state = pool2.getRunnerState(workspace);
1488
1605
  return c.json({
1489
1606
  runners,
1490
1607
  orchestratorModel: state.orchestratorModel
@@ -1507,7 +1624,7 @@ function runnersRoute(pool2) {
1507
1624
  function modelsRoute(pool2) {
1508
1625
  const router = new hono.Hono();
1509
1626
  router.get("/", async (c) => {
1510
- const result = await pool2.getProviderModels();
1627
+ const result = await pool2.getProviderModels(c.req.query("workspace"));
1511
1628
  return c.json(result);
1512
1629
  });
1513
1630
  return router;
@@ -1572,29 +1689,34 @@ function settingsRoute(pool2) {
1572
1689
  const router = new hono.Hono();
1573
1690
  router.get("/", async (c) => {
1574
1691
  await core.installedPlannerDetected();
1575
- const settings = pool2.getSettings(c.req.query("sessionId") || void 0);
1692
+ const sessionId = c.req.query("sessionId") || void 0;
1693
+ const workspace = c.req.query("workspace") || void 0;
1694
+ await pool2.discoverPlannerCatalogs(sessionId, workspace);
1695
+ const settings = pool2.getSettings(sessionId, workspace);
1576
1696
  return c.json(settings);
1577
1697
  });
1578
1698
  router.patch("/", async (c) => {
1579
1699
  try {
1580
1700
  await core.installedPlannerDetected();
1581
1701
  const body = await jsonObject(c);
1582
- if (Object.keys(body).length === 0) {
1702
+ if (Object.keys(body).every((key) => key === "workspace")) {
1583
1703
  return refuse(c, 400, "At least one setting field is required");
1584
1704
  }
1585
- const { run: run2, sessionId: namedSession, ...rest } = body;
1705
+ const { run: run2, sessionId: namedSession, workspace: namedWorkspace, ...rest } = body;
1586
1706
  if (namedSession !== void 0 && typeof namedSession !== "string") return refuse(c, 400, "sessionId must be a string");
1707
+ if (namedWorkspace !== void 0 && typeof namedWorkspace !== "string") return refuse(c, 400, "workspace must be a string");
1587
1708
  const sessionId = namedSession || void 0;
1709
+ const workspace = namedWorkspace || void 0;
1588
1710
  if (run2 !== void 0) {
1589
1711
  if (!run2 || typeof run2 !== "object" || Array.isArray(run2)) return refuse(c, 400, "run must be an object of run settings");
1590
1712
  try {
1591
- pool2.updateRunSettings(run2, sessionId);
1713
+ pool2.updateRunSettings(run2, sessionId, workspace);
1592
1714
  } catch (err) {
1593
1715
  if (err instanceof core.RunSettingsError) return refuse(c, 400, err.message);
1594
1716
  throw err;
1595
1717
  }
1596
1718
  }
1597
- const result = Object.keys(rest).length > 0 ? sessionId ? pool2.updateSettings(rest, sessionId) : pool2.updateSettings(rest) : pool2.getSettings(sessionId);
1719
+ const result = Object.keys(rest).length > 0 ? await pool2.writeSettings(rest, sessionId, workspace) : pool2.getSettings(sessionId, workspace);
1598
1720
  return c.json(result);
1599
1721
  } catch (err) {
1600
1722
  return failure(c, err, "settings update", { fallback: 400 });