dsh-theone 0.3.12 → 0.3.13

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/README.md CHANGED
@@ -47,7 +47,7 @@ Each project gets its own background session that reasons, runs tools and compac
47
47
  - **Related work connects; unrelated work stays out.** Related topics share progress automatically and TheOne learns which ones belong together from how you use them. A topic's constraints (say, "budget figures stay out of the paper") are attached verbatim every time, so compaction never drops them.
48
48
  - **Gets better with use.** Say "wrong topic" or click **Move to…** in the directory, and similar messages go to the right place from then on.
49
49
  - **Your old sessions become a topic directory.** After install it reads your existing sessions in the background, turns them into topics grouped into workspaces, and you pick up where you left off.
50
- - **Nothing extra to configure.** No separate API key: routing and the background sessions use the model you chose in DSH. To switch, pick **TheOne · <model>** in main chat's model menu; it still routes through TheOne, with that model doing the work.
50
+ - **Nothing extra to configure.** No separate API key: routing and the background sessions use the model you chose in DSH. When TheOne is the selected model, a **Background** button beside the model menu shows which model does the routing and the work, and switches it.
51
51
 
52
52
  ![TheOne main chat and topic workspaces](https://raw.githubusercontent.com/YunongDai2005/dsh-theone/main/docs/images/theone-topic-workspaces-en.png)
53
53
 
package/README.zh.md CHANGED
@@ -47,7 +47,7 @@
47
47
  - **相关的事会互通,无关的事互不打扰**:相关话题自动共享进展,并从你的使用中学习哪些话题有关联;话题的约束(比如「预算数字不能写进论文」)每次都原样带上,压缩不会把它丢掉。
48
48
  - **越用越准**:说「分错了」或在目录里点「改到…」,TheOne 会记住,类似的消息以后直接分对。
49
49
  - **旧会话自动变成话题目录**:装上后它会在后台读取你已有的会话,整理成话题并按工作区分组,直接接着聊。
50
- - **零额外配置**:不用另填 API Key,路由和后台都用你在 DSH 里选好的模型。想换模型,在主聊天的模型菜单里选 **TheOne · 某个模型**,仍然由 TheOne 分配话题,干活的换成那个模型。
50
+ - **零额外配置**:不用另填 API Key,路由和后台都用你在 DSH 里选好的模型。选中 TheOne 时,模型按钮旁边会出现 **后台** 按钮,显示并切换分配话题和干活用的模型。
51
51
 
52
52
  ![TheOne 主聊天与话题工作区](https://raw.githubusercontent.com/YunongDai2005/dsh-theone/main/docs/images/theone-topic-workspaces-en.png)
53
53
 
@@ -5,6 +5,15 @@ export declare const zh: {
5
5
  readonly 'gateway.opening': "正在打开 TheOne 主聊天…";
6
6
  readonly 'gateway.error': "主聊天暂时无法打开,请检查 DSH 连接和 TheOne 插件状态。";
7
7
  readonly retry: "重试";
8
+ readonly 'bg.label': "后台";
9
+ readonly 'bg.follow': "默认";
10
+ readonly 'bg.title': "后台模型:分配话题和干活都用它";
11
+ readonly 'bg.followItem': "跟随 DSH 默认模型";
12
+ readonly 'bg.hint': "后台模型:{model}。TheOne 分配话题和后台干活都用这个模型。";
13
+ readonly 'bg.hintFollow': "后台模型:{model}(跟随 DSH 默认模型)。点击可以固定为其他模型。";
14
+ readonly 'bg.search': "搜索模型";
15
+ readonly 'bg.none': "没有匹配的模型";
16
+ readonly 'bg.error': "后台模型没有切换成功,请重试。";
8
17
  readonly 'update.available': "更新";
9
18
  readonly 'update.installing': "更新中…";
10
19
  readonly 'update.restart': "重启生效";
@@ -5,6 +5,15 @@ export const zh = {
5
5
  'gateway.opening': '正在打开 TheOne 主聊天…',
6
6
  'gateway.error': '主聊天暂时无法打开,请检查 DSH 连接和 TheOne 插件状态。',
7
7
  'retry': '重试',
8
+ 'bg.label': '后台',
9
+ 'bg.follow': '默认',
10
+ 'bg.title': '后台模型:分配话题和干活都用它',
11
+ 'bg.followItem': '跟随 DSH 默认模型',
12
+ 'bg.hint': '后台模型:{model}。TheOne 分配话题和后台干活都用这个模型。',
13
+ 'bg.hintFollow': '后台模型:{model}(跟随 DSH 默认模型)。点击可以固定为其他模型。',
14
+ 'bg.search': '搜索模型',
15
+ 'bg.none': '没有匹配的模型',
16
+ 'bg.error': '后台模型没有切换成功,请重试。',
8
17
  'update.available': '更新',
9
18
  'update.installing': '更新中…',
10
19
  'update.restart': '重启生效',
@@ -185,6 +194,15 @@ export const en = {
185
194
  'gateway.opening': 'Opening TheOne main chat…',
186
195
  'gateway.error': 'Main chat could not open. Check your DSH connection and TheOne plugin status.',
187
196
  'retry': 'Retry',
197
+ 'bg.label': 'Background',
198
+ 'bg.follow': 'Default',
199
+ 'bg.title': 'Background model: routes and does the work',
200
+ 'bg.followItem': 'Follow DSH\'s default model',
201
+ 'bg.hint': 'Background model: {model}. TheOne routes and does the background work with it.',
202
+ 'bg.hintFollow': "Background model: {model} (following DSH's default). Click to pin another model.",
203
+ 'bg.search': 'Search models',
204
+ 'bg.none': 'No matching models',
205
+ 'bg.error': 'The background model was not switched. Try again.',
188
206
  'update.available': 'Update',
189
207
  'update.installing': 'Updating…',
190
208
  'update.restart': 'Restart to apply',
package/dist/client.js CHANGED
@@ -88,6 +88,15 @@ var zh = {
88
88
  "gateway.opening": "\u6B63\u5728\u6253\u5F00 TheOne \u4E3B\u804A\u5929\u2026",
89
89
  "gateway.error": "\u4E3B\u804A\u5929\u6682\u65F6\u65E0\u6CD5\u6253\u5F00\uFF0C\u8BF7\u68C0\u67E5 DSH \u8FDE\u63A5\u548C TheOne \u63D2\u4EF6\u72B6\u6001\u3002",
90
90
  "retry": "\u91CD\u8BD5",
91
+ "bg.label": "\u540E\u53F0",
92
+ "bg.follow": "\u9ED8\u8BA4",
93
+ "bg.title": "\u540E\u53F0\u6A21\u578B\uFF1A\u5206\u914D\u8BDD\u9898\u548C\u5E72\u6D3B\u90FD\u7528\u5B83",
94
+ "bg.followItem": "\u8DDF\u968F DSH \u9ED8\u8BA4\u6A21\u578B",
95
+ "bg.hint": "\u540E\u53F0\u6A21\u578B\uFF1A{model}\u3002TheOne \u5206\u914D\u8BDD\u9898\u548C\u540E\u53F0\u5E72\u6D3B\u90FD\u7528\u8FD9\u4E2A\u6A21\u578B\u3002",
96
+ "bg.hintFollow": "\u540E\u53F0\u6A21\u578B\uFF1A{model}\uFF08\u8DDF\u968F DSH \u9ED8\u8BA4\u6A21\u578B\uFF09\u3002\u70B9\u51FB\u53EF\u4EE5\u56FA\u5B9A\u4E3A\u5176\u4ED6\u6A21\u578B\u3002",
97
+ "bg.search": "\u641C\u7D22\u6A21\u578B",
98
+ "bg.none": "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B",
99
+ "bg.error": "\u540E\u53F0\u6A21\u578B\u6CA1\u6709\u5207\u6362\u6210\u529F\uFF0C\u8BF7\u91CD\u8BD5\u3002",
91
100
  "update.available": "\u66F4\u65B0",
92
101
  "update.installing": "\u66F4\u65B0\u4E2D\u2026",
93
102
  "update.restart": "\u91CD\u542F\u751F\u6548",
@@ -268,6 +277,15 @@ var en = {
268
277
  "gateway.opening": "Opening TheOne main chat\u2026",
269
278
  "gateway.error": "Main chat could not open. Check your DSH connection and TheOne plugin status.",
270
279
  "retry": "Retry",
280
+ "bg.label": "Background",
281
+ "bg.follow": "Default",
282
+ "bg.title": "Background model: routes and does the work",
283
+ "bg.followItem": "Follow DSH's default model",
284
+ "bg.hint": "Background model: {model}. TheOne routes and does the background work with it.",
285
+ "bg.hintFollow": "Background model: {model} (following DSH's default). Click to pin another model.",
286
+ "bg.search": "Search models",
287
+ "bg.none": "No matching models",
288
+ "bg.error": "The background model was not switched. Try again.",
271
289
  "update.available": "Update",
272
290
  "update.installing": "Updating\u2026",
273
291
  "update.restart": "Restart to apply",
@@ -733,6 +751,210 @@ function apply(ctx) {
733
751
  size === 16 && (0, import_react.createElement)(UpdateButton)
734
752
  );
735
753
  }
754
+ const readSettings = async () => {
755
+ const response = await fetch("/api/theone/settings", { signal: lifetime.signal, cache: "no-store" });
756
+ if (!response.ok) throw new Error("Settings unavailable");
757
+ return await response.json();
758
+ };
759
+ const saveBackgroundModel = async (choice) => {
760
+ for (let attempt = 0; attempt < 2; attempt++) {
761
+ const current = await readSettings();
762
+ const response = await fetch("/api/theone/settings", {
763
+ method: "PUT",
764
+ signal: lifetime.signal,
765
+ cache: "no-store",
766
+ headers: { "content-type": "application/json" },
767
+ body: JSON.stringify({ revision: current.revision, values: { ...current.savedValues, workerProvider: choice?.provider ?? null, workerModel: choice?.model ?? null } })
768
+ });
769
+ if (response.ok) return await response.json();
770
+ if (response.status !== 409) break;
771
+ }
772
+ throw new Error("Background model not saved");
773
+ };
774
+ function BackgroundModel({ directory }) {
775
+ const t2 = useText();
776
+ const state = (0, import_react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot());
777
+ const selection = state.pending ?? state.current;
778
+ const theone = selection?.provider === "theone";
779
+ const [settings, setSettings] = (0, import_react.useState)();
780
+ const [failed, setFailed] = (0, import_react.useState)(false);
781
+ const [saving, setSaving] = (0, import_react.useState)(false);
782
+ const button = (0, import_react.useRef)(null);
783
+ const closeMenu = (0, import_react.useRef)();
784
+ (0, import_react.useEffect)(() => {
785
+ if (!theone) return;
786
+ let alive = true;
787
+ readSettings().then((value) => {
788
+ if (alive) setSettings(value);
789
+ }, () => {
790
+ });
791
+ return () => {
792
+ alive = false;
793
+ closeMenu.current?.();
794
+ };
795
+ }, [theone]);
796
+ if (!theone) return null;
797
+ const pinned = settings?.values.workerProvider && settings.values.workerModel ? { provider: settings.values.workerProvider, model: settings.values.workerModel } : null;
798
+ const nameOf = (provider, model) => settings?.models.find((item) => item.provider === provider && item.id === model)?.name ?? model;
799
+ const effective = settings?.model ? nameOf(settings.model.provider, settings.model.model) : void 0;
800
+ const label = pinned ? nameOf(pinned.provider, pinned.model) : effective ?? t2("bg.follow");
801
+ const choose = async (choice) => {
802
+ closeMenu.current?.();
803
+ setSaving(true);
804
+ setFailed(false);
805
+ try {
806
+ setSettings(await saveBackgroundModel(choice));
807
+ } catch {
808
+ setFailed(true);
809
+ } finally {
810
+ setSaving(false);
811
+ }
812
+ };
813
+ const open = () => {
814
+ const anchor = button.current;
815
+ if (!anchor || closeMenu.current) {
816
+ closeMenu.current?.();
817
+ return;
818
+ }
819
+ const menu = document.createElement("div");
820
+ menu.className = "theone-bg-menu";
821
+ menu.setAttribute("role", "menu");
822
+ menu.setAttribute("translate", "no");
823
+ menu.setAttribute("aria-label", t2("bg.title"));
824
+ const heading = document.createElement("div");
825
+ heading.className = "theone-bg-heading";
826
+ heading.textContent = t2("bg.title");
827
+ const list = document.createElement("div");
828
+ list.className = "theone-bg-list";
829
+ const models = (settings?.models ?? []).filter((item2) => item2.provider !== "theone");
830
+ const item = (text, sub, checked, pick) => {
831
+ const row = document.createElement("button");
832
+ row.type = "button";
833
+ row.setAttribute("role", "menuitemradio");
834
+ row.setAttribute("aria-checked", String(checked));
835
+ row.className = "theone-bg-item";
836
+ const name = document.createElement("span");
837
+ name.textContent = text;
838
+ row.append(name);
839
+ if (sub) {
840
+ const small = document.createElement("small");
841
+ small.textContent = sub;
842
+ row.append(small);
843
+ }
844
+ const mark = document.createElement("span");
845
+ mark.className = "theone-bg-check";
846
+ mark.textContent = checked ? "\u2713" : "";
847
+ row.append(mark);
848
+ row.addEventListener("click", pick);
849
+ return row;
850
+ };
851
+ const render = (query) => {
852
+ list.replaceChildren(item(t2("bg.followItem"), !pinned && effective ? effective : void 0, !pinned, () => {
853
+ void choose(null);
854
+ }));
855
+ const needle = query.trim().toLowerCase();
856
+ const matched = models.filter((model) => !needle || `${model.name} ${model.id} ${model.provider}`.toLowerCase().includes(needle));
857
+ for (const provider of [...new Set(matched.map((model) => model.provider))]) {
858
+ const group = document.createElement("div");
859
+ group.className = "theone-bg-group";
860
+ group.textContent = provider;
861
+ list.append(group);
862
+ for (const model of matched.filter((entry) => entry.provider === provider))
863
+ list.append(item(model.name, model.name !== model.id ? model.id : void 0, pinned?.provider === model.provider && pinned.model === model.id, () => {
864
+ void choose({ provider: model.provider, model: model.id });
865
+ }));
866
+ }
867
+ if (!matched.length && needle) {
868
+ const empty = document.createElement("p");
869
+ empty.className = "theone-bg-empty";
870
+ empty.textContent = t2("bg.none");
871
+ list.append(empty);
872
+ }
873
+ };
874
+ menu.append(heading);
875
+ let search;
876
+ if (models.length > 8) {
877
+ search = document.createElement("input");
878
+ search.type = "search";
879
+ search.className = "theone-bg-search";
880
+ search.placeholder = t2("bg.search");
881
+ search.addEventListener("input", () => render(search.value));
882
+ menu.append(search);
883
+ }
884
+ menu.append(list);
885
+ render("");
886
+ document.body.append(menu);
887
+ anchor.setAttribute("aria-expanded", "true");
888
+ const place = () => {
889
+ const bounds = anchor.getBoundingClientRect(), size = menu.getBoundingClientRect();
890
+ menu.style.left = `${Math.max(8, Math.min(bounds.right - size.width, window.innerWidth - size.width - 8))}px`;
891
+ menu.style.top = `${Math.max(8, bounds.top - size.height - 8)}px`;
892
+ };
893
+ place();
894
+ const rows = () => [...menu.querySelectorAll(".theone-bg-item")];
895
+ const outside = (event) => {
896
+ if (!menu.contains(event.target) && !anchor.contains(event.target)) close();
897
+ };
898
+ const keydown = (event) => {
899
+ if (event.key === "Escape") {
900
+ event.preventDefault();
901
+ close();
902
+ anchor.focus();
903
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
904
+ event.preventDefault();
905
+ const all = rows(), index = all.indexOf(document.activeElement);
906
+ all[(index + (event.key === "ArrowDown" ? 1 : all.length - 1) + (index < 0 && event.key === "ArrowUp" ? 1 : 0)) % all.length]?.focus();
907
+ }
908
+ };
909
+ const close = () => {
910
+ menu.remove();
911
+ closeMenu.current = void 0;
912
+ anchor.setAttribute("aria-expanded", "false");
913
+ document.removeEventListener("pointerdown", outside, true);
914
+ document.removeEventListener("keydown", keydown, true);
915
+ window.removeEventListener("resize", close);
916
+ window.removeEventListener("blur", close);
917
+ };
918
+ closeMenu.current = close;
919
+ document.addEventListener("pointerdown", outside, true);
920
+ document.addEventListener("keydown", keydown, true);
921
+ window.addEventListener("resize", close);
922
+ window.addEventListener("blur", close);
923
+ (search ?? rows().find((row) => row.getAttribute("aria-checked") === "true") ?? rows()[0])?.focus();
924
+ };
925
+ const title = failed ? t2("bg.error") : t2(pinned ? "bg.hint" : "bg.hintFollow", { model: label });
926
+ return (0, import_react.createElement)(
927
+ "button",
928
+ {
929
+ ref: button,
930
+ type: "button",
931
+ className: "theone-bg",
932
+ title,
933
+ "aria-label": title,
934
+ "aria-haspopup": "menu",
935
+ "data-failed": failed,
936
+ disabled: saving,
937
+ onClick: () => {
938
+ if (settings) open();
939
+ else readSettings().then((value) => {
940
+ setSettings(value);
941
+ }, () => setFailed(true));
942
+ }
943
+ },
944
+ (0, import_react.createElement)("span", { className: "theone-bg-caption" }, t2("bg.label")),
945
+ (0, import_react.createElement)("span", { className: "theone-bg-text" }, label),
946
+ (0, import_react.createElement)("span", { className: "theone-bg-chevron", "aria-hidden": true }, saving ? "\u2026" : "\u2303")
947
+ );
948
+ }
949
+ ctx.inject(["slots", "modelDirectories"], (scope) => {
950
+ const slots = scope.slots;
951
+ slots.inject("conversation.input.right", () => slots.register({
952
+ name: "conversation.input.right",
953
+ id: "theone.background-model",
954
+ order: 100,
955
+ inject: (sessionId) => ({ directory: scope.modelDirectories.directoryFor(sessionId).store })
956
+ }, BackgroundModel));
957
+ });
736
958
  function GatewayPanel() {
737
959
  const t2 = useText();
738
960
  const [error, setError] = (0, import_react.useState)();
@@ -1415,7 +1637,7 @@ function apply(ctx) {
1415
1637
  ctx.effect(() => {
1416
1638
  const style = document.createElement("style");
1417
1639
  style.dataset.plugin = "dsh-theone";
1418
- style.textContent = sidebarCss + catalogCss + settingsCss;
1640
+ style.textContent = sidebarCss + catalogCss + settingsCss + composerCss;
1419
1641
  document.head.append(style);
1420
1642
  return () => {
1421
1643
  lifetime.abort();
@@ -1494,6 +1716,28 @@ button:has(.theone-catalog-entry)>span:not(:has(.theone-catalog-entry)){display:
1494
1716
  .theone-catalog{padding:32px;max-width:1180px;margin:auto;box-sizing:border-box;height:100%;overflow:auto;color:var(--dsw-alias-label-primary)}
1495
1717
  .theone-catalog-header{display:flex;justify-content:space-between;align-items:center;gap:16px}.theone-catalog h1{font-size:24px;margin:0 0 8px}.theone-catalog-header p,.theone-catalog-status,.theone-group-summary{opacity:.65;margin:0 0 18px;line-height:1.6}.theone-catalog button{border:1px solid #8883;border-radius:9px;padding:8px 13px;background:transparent;color:inherit;cursor:pointer;font:inherit;white-space:nowrap}.theone-catalog button:disabled{opacity:.5;cursor:default}.theone-catalog-groups{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,310px),1fr));gap:20px}.theone-topic-group{border:1px solid #8882;border-radius:16px;padding:20px;background:#88805}.theone-topic-group h2{font-size:18px;margin:0 0 8px}.theone-topic-group h2 span{font-size:13px;opacity:.5}.theone-topic-card{border-top:1px solid #8882;padding:16px 0}.theone-topic-card:last-child{padding-bottom:0}.theone-topic-card h3{font-size:15px;line-height:1.5;margin:0 0 7px}.theone-topic-card p{font-size:13px;line-height:1.7;opacity:.75;margin:0 0 12px;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.theone-topic-actions{display:flex;gap:8px;flex-wrap:wrap}.theone-topic-actions button{font-size:12px}.theone-topic-actions .theone-source-link{border-color:transparent;opacity:.6}.theone-catalog-warning{background:#ff900011;padding:12px;border-radius:10px;font-size:13px}.theone-catalog-entry{display:flex;align-items:center;gap:10px;font-size:14px}.theone-catalog-empty{padding:40px 0;opacity:.65;line-height:1.8}.theone-catalog-tools{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.theone-topic-links{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 12px;font-size:12px}.theone-links-label,.theone-links-empty{opacity:.6}.theone-link-chip{display:inline-flex;align-items:center;gap:2px;border:1px solid #8883;border-radius:999px;padding:2px 4px 2px 9px}.theone-catalog .theone-link-chip button{border:0;padding:0 5px;opacity:.6;font-size:13px;line-height:1}.theone-topic-links select{font:inherit;font-size:12px;color:inherit;background:transparent;border:1px solid #8883;border-radius:8px;padding:2px 6px}.theone-link-private{display:inline-flex;align-items:center;gap:4px;opacity:.75;cursor:pointer}.theone-topic-card .theone-topic-state{font-size:12px;opacity:.8;-webkit-line-clamp:3}.theone-topic-state span{opacity:.6;margin-right:4px}.theone-manage{display:flex;flex-direction:column;gap:10px;margin-top:12px;padding:14px;border:1px solid #8883;border-radius:12px;font-size:12px}.theone-create{flex-direction:row;flex-wrap:wrap;align-items:center;margin:0 0 18px}.theone-create input{flex:1;min-width:180px}.theone-manage-field{display:flex;flex-direction:column;gap:5px}.theone-manage-field>span{opacity:.65}.theone-manage input,.theone-manage textarea,.theone-manage select,.theone-routes select{font:inherit;font-size:12px;color:inherit;background:transparent;border:1px solid #8883;border-radius:8px;padding:6px 8px;box-sizing:border-box;min-width:0}.theone-manage textarea{resize:vertical;width:100%}.theone-manage-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.theone-manage-row select,.theone-manage-row input{flex:1;min-width:140px}.theone-manage-hint{opacity:.6;font-size:12px;line-height:1.6;margin:0}.theone-catalog .theone-danger{color:#c4402f;border-color:#c4402f55}.theone-routes{border:1px solid #8882;border-radius:16px;padding:14px 20px;margin:0 0 20px}.theone-routes summary{cursor:pointer;font-weight:500}.theone-routes summary span{opacity:.5;font-size:13px}.theone-routes ol{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:10px}.theone-routes li{border-top:1px solid #8882;padding-top:10px;font-size:12px;display:flex;flex-direction:column;gap:5px}.theone-route-head{display:flex;gap:10px;min-width:0}.theone-route-head time{opacity:.55;flex:none}.theone-route-head q{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.theone-route-body{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.theone-route-body small{opacity:.6}.theone-route-fixed{opacity:.75}@media(max-width:640px){.theone-catalog{padding:20px}.theone-catalog-header{align-items:flex-start}.theone-catalog-header h1{font-size:21px}}
1496
1718
  `;
1719
+ var composerCss = `
1720
+ .theone-bg{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 10px;border:0;border-radius:10px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;white-space:nowrap;cursor:pointer;max-width:220px}
1721
+ .theone-bg:hover,.theone-bg[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}
1722
+ .theone-bg:focus-visible{outline:2px solid var(--dsw-focus-ring-color,#4a7fb5);outline-offset:1px}
1723
+ .theone-bg:disabled{opacity:.6;cursor:default}
1724
+ .theone-bg-caption,.theone-bg-chevron{color:var(--dsw-alias-label-secondary)}
1725
+ .theone-bg-text{overflow:hidden;text-overflow:ellipsis;display:var(--dsh-composer-model-text-display,inline)}
1726
+ .theone-bg-chevron{font-size:11px}
1727
+ .theone-bg[data-failed=true] .theone-bg-caption{color:#d9480f}
1728
+ .theone-bg-menu{position:fixed;z-index:10000;width:280px;max-height:min(420px,70vh);display:flex;flex-direction:column;padding:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-specific-sidebar-fill);color:var(--dsw-alias-label-primary);box-shadow:0 10px 32px #0003;font:inherit;font-size:14px;box-sizing:border-box}
1729
+ .theone-bg-heading{padding:8px 10px 6px;font-size:12px;color:var(--dsw-alias-label-secondary)}
1730
+ .theone-bg-search{margin:0 4px 6px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none}
1731
+ .theone-bg-search:focus{border-color:var(--dsw-focus-ring-color,#4a7fb5)}
1732
+ .theone-bg-list{overflow:auto;min-height:0}
1733
+ .theone-bg-group{padding:8px 10px 4px;font-size:12px;color:var(--dsw-alias-label-secondary)}
1734
+ .theone-bg-item{display:flex;align-items:center;gap:8px;width:100%;padding:9px 10px;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
1735
+ .theone-bg-item:hover,.theone-bg-item:focus-visible{outline:none;background:var(--dsw-alias-interactive-bg-hover)}
1736
+ .theone-bg-item span:first-child{flex:0 1 auto;min-width:40px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1737
+ .theone-bg-item small{flex:0 100 auto;color:var(--dsw-alias-label-secondary);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
1738
+ .theone-bg-check{margin-left:auto;flex:none;width:14px;text-align:center;color:var(--dsw-alias-label-primary)}
1739
+ .theone-bg-empty{margin:8px 10px;font-size:13px;color:var(--dsw-alias-label-secondary)}
1740
+ `;
1497
1741
  var settingsCss = `
1498
1742
  .theone-settings{--one-settings-accent:#a75b1e}[data-ds-dark-theme] .theone-settings{--one-settings-accent:#93c8f3}
1499
1743
  .theone-settings input,.theone-settings select{width:100%;box-sizing:border-box;min-height:38px;padding:8px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;font:inherit;color:inherit;background:var(--dsw-specific-sidebar-fill)}.theone-settings input:focus-visible,.theone-settings select:focus-visible{outline:2px solid var(--one-settings-accent);outline-offset:2px}.theone-settings input:disabled,.theone-settings select:disabled,.theone-settings button:disabled{opacity:.5;cursor:default}.theone-settings small{display:block;margin-top:5px;color:var(--dsw-alias-label-secondary);font-size:12px}
package/dist/index.d.ts CHANGED
@@ -76,6 +76,7 @@ export default class TheOne extends Service {
76
76
  private readonly closing;
77
77
  /** A model the user picked in main chat's own model selector; it answers through the Workers. */
78
78
  private pickedModel?;
79
+ private adapter?;
79
80
  /** Update checks and one-click install through DSH's plugin manager. */
80
81
  readonly updater: Updater;
81
82
  /** Sessions whose current step answers through TheOne; only these refuse to run tools themselves. */
@@ -97,7 +98,10 @@ export default class TheOne extends Service {
97
98
  private backingModel;
98
99
  /** Every model DSH offers besides TheOne; a provider that cannot list its models in time offers none. */
99
100
  private offeredModels;
100
- /** Main chat's model menu: plain TheOne (follow DSH), then "TheOne · <model>" for every model DSH offers. */
101
+ /**
102
+ * Main chat's model menu has one TheOne entry; the background model is chosen with the button
103
+ * beside it. Selections of the older "TheOne · <model>" entries still resolve.
104
+ */
101
105
  gatewayModels(provider: string): Promise<LlmModelInfo[]>;
102
106
  /** The entry has exactly the backing model's capacity, including DSH overrides. */
103
107
  gatewayModelInfo(provider?: string, signal?: AbortSignal, model?: string): Promise<LlmResolvedModelInfo>;
package/dist/index.js CHANGED
@@ -166,6 +166,7 @@ export default class TheOne extends Service {
166
166
  closing = new Map();
167
167
  /** A model the user picked in main chat's own model selector; it answers through the Workers. */
168
168
  pickedModel;
169
+ adapter;
169
170
  /** Update checks and one-click install through DSH's plugin manager. */
170
171
  updater;
171
172
  /** Sessions whose current step answers through TheOne; only these refuse to run tools themselves. */
@@ -231,7 +232,7 @@ export default class TheOne extends Service {
231
232
  this.catalog.start();
232
233
  }
233
234
  this.registerCatalogChannel();
234
- ctx.llm.registerAdapter(['theone'], new GatewayAdapter(this));
235
+ this.adapter = ctx.llm.registerAdapter(['theone'], new GatewayAdapter(this));
235
236
  ctx.on('session/event', (session, event) => {
236
237
  if (event.type === 'turn/end') {
237
238
  if (session.id === this.reservedGateway)
@@ -801,7 +802,14 @@ export default class TheOne extends Service {
801
802
  const { historyCatalog: _catalog, catalogIntervalMs: _interval, ...live } = settingsConfig(values);
802
803
  if (live.routerMode !== this.config.routerMode)
803
804
  this.router = this.routerFor(live.routerMode);
805
+ const modelChanged = live.workerProvider !== this.config.workerProvider || live.workerModel !== this.config.workerModel;
804
806
  this.config = { ...this.config, ...live };
807
+ // Main chat's reasoning levels and image input are the background model's; DSH re-reads them on this signal.
808
+ if (modelChanged)
809
+ try {
810
+ this.adapter?.replace(['theone']);
811
+ }
812
+ catch { /* Released during shutdown. */ }
805
813
  this.store.seed(descriptors);
806
814
  }
807
815
  /** Capture before Web saves the gateway itself as DSH's new default. */
@@ -828,18 +836,14 @@ export default class TheOne extends Service {
828
836
  .catch(() => [])));
829
837
  return listed.flat();
830
838
  }
831
- /** Main chat's model menu: plain TheOne (follow DSH), then "TheOne · <model>" for every model DSH offers. */
839
+ /**
840
+ * Main chat's model menu has one TheOne entry; the background model is chosen with the button
841
+ * beside it. Selections of the older "TheOne · <model>" entries still resolve.
842
+ */
832
843
  async gatewayModels(provider) {
833
844
  // The entry accepts whatever the backing model accepts (e.g. images).
834
845
  const info = await this.gatewayModelInfo(provider).catch(() => undefined);
835
- const models = [{ provider, id: 'gateway', name: 'TheOne', inputModalities: [...(info?.inputModalities ?? ['text'])] }];
836
- // A model pinned in TheOne's settings decides for the Workers, so choices here would do nothing.
837
- if (this.config.workerProvider && this.config.workerModel)
838
- return models;
839
- for (const model of await this.offeredModels())
840
- models.push({ provider, id: viaModel({ provider: model.provider, model: model.id }), name: `TheOne · ${model.name}`,
841
- ...(model.description ? { description: model.description } : {}), ...(model.inputModalities ? { inputModalities: [...model.inputModalities] } : {}) });
842
- return models;
846
+ return [{ provider, id: 'gateway', name: 'TheOne', inputModalities: [...(info?.inputModalities ?? ['text'])] }];
843
847
  }
844
848
  /** The entry has exactly the backing model's capacity, including DSH overrides. */
845
849
  async gatewayModelInfo(provider = 'theone', signal, model = 'gateway') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theone",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Automatic context routing for DeepSeek Harness",