@ljwei-stak/dsh-model-router 0.18.0 → 0.18.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/.dsh-plugin/client.js +168 -31
- package/.dsh-plugin/index.mjs +29 -7
- package/.dsh-plugin/official-tools-remote-service.mjs +15 -1
- package/.dsh-plugin/shared/official-tools-remote.mjs +1 -0
- package/.dsh-plugin/shared/official-tools-runtime.mjs +1 -1
- package/CHANGELOG.md +9 -0
- package/README.md +7 -5
- package/README.zh.md +7 -5
- package/docs/INSTALLATION_GUIDE.zh.md +12 -12
- package/docs/WORKBENCH_USER_GUIDE.zh.md +2 -2
- package/package.json +2 -2
package/.dsh-plugin/client.js
CHANGED
|
@@ -2348,6 +2348,65 @@ var INSTALL_DEFAULTS = Object.freeze({
|
|
|
2348
2348
|
toolScriptUrlsJson: "{}",
|
|
2349
2349
|
toolAllowScriptInstall: true
|
|
2350
2350
|
});
|
|
2351
|
+
function toolInstallSettingsSignature(value = {}) {
|
|
2352
|
+
try {
|
|
2353
|
+
const preferences = parseInstallPreferences({ ...value, toolInstallDir: "" }, { tools: OFFICIAL_TOOLS });
|
|
2354
|
+
const sorted = (map) => Object.entries(map).sort(([left], [right]) => left.localeCompare(right));
|
|
2355
|
+
return JSON.stringify([
|
|
2356
|
+
String(value.toolInstallDir ?? "").trim(),
|
|
2357
|
+
preferences.registry,
|
|
2358
|
+
sorted(preferences.methods),
|
|
2359
|
+
sorted(preferences.scriptUrls),
|
|
2360
|
+
preferences.allowScriptInstall
|
|
2361
|
+
]);
|
|
2362
|
+
} catch {
|
|
2363
|
+
return JSON.stringify([
|
|
2364
|
+
"invalid",
|
|
2365
|
+
value.toolInstallDir,
|
|
2366
|
+
value.toolNpmRegistry,
|
|
2367
|
+
value.toolInstallMethodsJson,
|
|
2368
|
+
value.toolScriptUrlsJson,
|
|
2369
|
+
value.toolAllowScriptInstall
|
|
2370
|
+
]);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
function installSummaryMatchesSettings(summary, value) {
|
|
2374
|
+
if (!summary) return false;
|
|
2375
|
+
return toolInstallSettingsSignature(value) === toolInstallSettingsSignature({
|
|
2376
|
+
toolInstallDir: summary.requestedInstallDir ?? summary.installDir ?? "",
|
|
2377
|
+
toolNpmRegistry: summary.registry || DEFAULT_NPM_REGISTRY,
|
|
2378
|
+
toolInstallMethodsJson: JSON.stringify(summary.methods ?? {}),
|
|
2379
|
+
toolScriptUrlsJson: JSON.stringify(summary.scriptUrls ?? {}),
|
|
2380
|
+
toolAllowScriptInstall: summary.allowScriptInstall !== false
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
function savedToolInstallMethods(value = {}) {
|
|
2384
|
+
try {
|
|
2385
|
+
return parseInstallPreferences({ toolInstallMethodsJson: value.toolInstallMethodsJson }, { tools: OFFICIAL_TOOLS }).methods;
|
|
2386
|
+
} catch {
|
|
2387
|
+
return {};
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
async function mutateToolInstallSettings(scope, operations, revision, { timeoutMs = 15e3, onSettled } = {}) {
|
|
2391
|
+
let timer;
|
|
2392
|
+
try {
|
|
2393
|
+
const writing = Promise.resolve().then(() => scope.mutate(operations, revision)).then((accepted) => {
|
|
2394
|
+
onSettled?.({ accepted });
|
|
2395
|
+
return accepted;
|
|
2396
|
+
}, (error) => {
|
|
2397
|
+
onSettled?.({ accepted: false, error });
|
|
2398
|
+
throw error;
|
|
2399
|
+
});
|
|
2400
|
+
return await Promise.race([
|
|
2401
|
+
writing,
|
|
2402
|
+
new Promise((_2, reject) => {
|
|
2403
|
+
timer = setTimeout(() => reject(new Error("\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u672A\u6536\u5230\u786E\u8BA4\uFF0C\u8BF7\u5728\u8FDE\u63A5\u6062\u590D\u540E\u91CD\u8BD5\uFF1B\u8FDF\u5230\u7ED3\u679C\u4F1A\u6309\u5B9E\u9645\u5DF2\u4FDD\u5B58\u8BBE\u7F6E\u663E\u793A\u3002")), timeoutMs);
|
|
2404
|
+
})
|
|
2405
|
+
]);
|
|
2406
|
+
} finally {
|
|
2407
|
+
clearTimeout(timer);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2351
2410
|
function validateToolInstallSetting(key, value) {
|
|
2352
2411
|
try {
|
|
2353
2412
|
parseInstallPreferences({ [key]: value }, { tools: OFFICIAL_TOOLS });
|
|
@@ -12645,7 +12704,7 @@ var xterm_default = `/**
|
|
|
12645
12704
|
`;
|
|
12646
12705
|
|
|
12647
12706
|
// .dsh-plugin/client/host-version.mjs
|
|
12648
|
-
var ROUTER_CLIENT_VERSION = true ? "0.18.
|
|
12707
|
+
var ROUTER_CLIENT_VERSION = true ? "0.18.1" : "";
|
|
12649
12708
|
var STALE_HOST_MESSAGE = "\u63D2\u4EF6\u540E\u53F0\u7248\u672C\u8F83\u65E7\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F Harness\uFF08\u5305\u62EC\u6258\u76D8\u56FE\u6807\uFF09\u540E\u518D\u4F7F\u7528\u3002";
|
|
12650
12709
|
function isMissingRemoteMethod(message) {
|
|
12651
12710
|
const value = String(message ?? "");
|
|
@@ -13498,7 +13557,7 @@ var INSTALL_SETTING_FIELDS = [
|
|
|
13498
13557
|
hint: "\u6309\u5DE5\u5177 ID \u6307\u5B9A https \u955C\u50CF\u5730\u5740\u3002\u811A\u672C\u4E0B\u8F7D\u540E\u4ECD\u4F1A\u6838\u5BF9\u5382\u5546\u6807\u8BB0\uFF0C\u518D\u6267\u884C\u672C\u5730\u526F\u672C\u3002"
|
|
13499
13558
|
}
|
|
13500
13559
|
];
|
|
13501
|
-
function InstallSettings({ settingsScope, install, onPendingChange }) {
|
|
13560
|
+
function InstallSettings({ settingsScope, install, onPendingChange, onMutationStart }) {
|
|
13502
13561
|
const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
|
|
13503
13562
|
const [drafts, setDrafts] = import_react5.default.useState({});
|
|
13504
13563
|
const [saving, setSaving] = import_react5.default.useState(false);
|
|
@@ -13535,7 +13594,12 @@ function InstallSettings({ settingsScope, install, onPendingChange }) {
|
|
|
13535
13594
|
setNotice("");
|
|
13536
13595
|
try {
|
|
13537
13596
|
const entries = Object.entries(drafts).map(([key, value]) => ({ op: "set", path: [key], value }));
|
|
13538
|
-
const
|
|
13597
|
+
const finishMutation = onMutationStart?.(entries);
|
|
13598
|
+
const onSettled = (outcome) => {
|
|
13599
|
+
const current = finishMutation?.(outcome);
|
|
13600
|
+
if (current && outcome.accepted) setNotice("\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002");
|
|
13601
|
+
};
|
|
13602
|
+
const accepted = await mutateToolInstallSettings(settingsScope, entries, snapshot.revision, { onSettled });
|
|
13539
13603
|
if (!accepted) throw new Error("\u8BBE\u7F6E\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\uFF1B\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u91CD\u65B0\u6838\u5BF9\u3002");
|
|
13540
13604
|
const next = settingsScope.getSnapshot();
|
|
13541
13605
|
setSnapshot(next);
|
|
@@ -13575,7 +13639,7 @@ function InstallSettings({ settingsScope, install, onPendingChange }) {
|
|
|
13575
13639
|
onPendingChange?.(false);
|
|
13576
13640
|
} }, "\u653E\u5F03\u66F4\u6539"), dirty && /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-caption" }, invalid ? "\u8BF7\u4FEE\u6B63\u8F93\u5165\u540E\u4FDD\u5B58\u3002" : "\u66F4\u6539\u5C1A\u672A\u4FDD\u5B58\uFF0C\u5B89\u88C5\u4E0E\u66F4\u65B0\u6682\u4E0D\u53EF\u7528\u3002")), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 8 }, role: notice ? "status" : void 0 }, "\u5F53\u524D\u751F\u6548\uFF1A\u5B89\u88C5\u76EE\u5F55 ", install?.installDir || "\uFF08\u5404\u5382\u5546\u9ED8\u8BA4\uFF09", " \xB7 npm \u6E90 ", install?.registry || "\uFF08\u9ED8\u8BA4\uFF09", notice ? ` \xB7 ${notice}` : ""));
|
|
13577
13641
|
}
|
|
13578
|
-
function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
|
|
13642
|
+
function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
|
|
13579
13643
|
const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
|
|
13580
13644
|
const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
|
|
13581
13645
|
const [jobs, setJobs] = import_react5.default.useState({});
|
|
@@ -13583,12 +13647,17 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13583
13647
|
const [settingsPending, setSettingsPending] = import_react5.default.useState(false);
|
|
13584
13648
|
const [methodSaving, setMethodSaving] = import_react5.default.useState(null);
|
|
13585
13649
|
const [previewPending, setPreviewPending] = import_react5.default.useState(false);
|
|
13586
|
-
const [
|
|
13650
|
+
const [settingsSnapshot, setSettingsSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
|
|
13651
|
+
const [previewError, setPreviewError] = import_react5.default.useState("");
|
|
13652
|
+
const [probeSettingsStale, setProbeSettingsStale] = import_react5.default.useState(false);
|
|
13587
13653
|
const settingsPendingRef = import_react5.default.useRef(false);
|
|
13588
13654
|
const methodSavingRef = import_react5.default.useRef(null);
|
|
13589
13655
|
const previewPendingRef = import_react5.default.useRef(false);
|
|
13590
13656
|
const mounted = import_react5.default.useRef(false);
|
|
13591
13657
|
const request = import_react5.default.useRef(0);
|
|
13658
|
+
const previewRequest = import_react5.default.useRef(0);
|
|
13659
|
+
const mutationGeneration = import_react5.default.useRef(0);
|
|
13660
|
+
const mutationPendingRef = import_react5.default.useRef(null);
|
|
13592
13661
|
const submitting = import_react5.default.useRef(/* @__PURE__ */ new Set());
|
|
13593
13662
|
const polling = import_react5.default.useRef(/* @__PURE__ */ new Set());
|
|
13594
13663
|
const jobsRef = import_react5.default.useRef({});
|
|
@@ -13606,29 +13675,46 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13606
13675
|
const refresh = async () => {
|
|
13607
13676
|
onRefreshHealth?.();
|
|
13608
13677
|
const current = ++request.current;
|
|
13678
|
+
const settingsSignature = toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value);
|
|
13609
13679
|
setProbeState((previous) => ({
|
|
13610
13680
|
...previous,
|
|
13611
|
-
status: previous.probes.length > 0 ? "refreshing" : "loading",
|
|
13681
|
+
status: previous.probes.length > 0 || previous.install ? "refreshing" : "loading",
|
|
13612
13682
|
error: ""
|
|
13613
13683
|
}));
|
|
13614
13684
|
try {
|
|
13615
13685
|
if (typeof listOfficialTools !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
|
|
13616
13686
|
const response = await listOfficialTools();
|
|
13617
13687
|
if (!mounted.current || current !== request.current) return;
|
|
13688
|
+
if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
|
|
13618
13689
|
if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002"));
|
|
13619
13690
|
const probes = Array.isArray(response.value?.tools) ? response.value.tools : [];
|
|
13620
13691
|
const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
|
|
13621
13692
|
const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
|
|
13622
|
-
|
|
13623
|
-
|
|
13624
|
-
|
|
13693
|
+
const install2 = response.value?.install;
|
|
13694
|
+
const matches = settingsSignature === toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value) && installSummaryMatchesSettings(install2, settingsScope?.getSnapshot?.()?.value);
|
|
13695
|
+
if (!matches) return;
|
|
13696
|
+
setProbeState((previous) => ({
|
|
13697
|
+
status: "ready",
|
|
13698
|
+
probes,
|
|
13699
|
+
capabilities,
|
|
13700
|
+
readiness,
|
|
13701
|
+
install: matches ? install2 : previous.install,
|
|
13702
|
+
error: ""
|
|
13703
|
+
}));
|
|
13704
|
+
setProbeSettingsStale(false);
|
|
13705
|
+
if (matches && !mutationPendingRef.current) {
|
|
13706
|
+
previewPendingRef.current = false;
|
|
13707
|
+
setPreviewPending(false);
|
|
13708
|
+
setPreviewError("");
|
|
13709
|
+
}
|
|
13625
13710
|
onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
|
|
13626
13711
|
} catch (error) {
|
|
13627
13712
|
if (!mounted.current || current !== request.current) return;
|
|
13713
|
+
if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
|
|
13628
13714
|
const message = remoteErrorText(text5(error?.message), "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002");
|
|
13629
13715
|
let kept = false;
|
|
13630
13716
|
setProbeState((previous) => {
|
|
13631
|
-
if (previous.probes.length > 0) {
|
|
13717
|
+
if (previous.probes.length > 0 || previous.install) {
|
|
13632
13718
|
kept = true;
|
|
13633
13719
|
return { ...previous, status: "ready", error: message };
|
|
13634
13720
|
}
|
|
@@ -13637,9 +13723,38 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13637
13723
|
if (!kept) onProbes({ probes: [], capabilities: [], readiness: [] });
|
|
13638
13724
|
}
|
|
13639
13725
|
};
|
|
13726
|
+
const refreshInstallSettings = async () => {
|
|
13727
|
+
const current = ++previewRequest.current;
|
|
13728
|
+
const snapshot = settingsScope?.getSnapshot?.();
|
|
13729
|
+
const signature2 = toolInstallSettingsSignature(snapshot?.value);
|
|
13730
|
+
previewPendingRef.current = true;
|
|
13731
|
+
setPreviewPending(true);
|
|
13732
|
+
setPreviewError("");
|
|
13733
|
+
try {
|
|
13734
|
+
if (typeof loadOfficialInstallSettings !== "function") throw new Error("\u5B89\u88C5\u8BBE\u7F6E\u6865\u5C1A\u672A\u52A0\u8F7D\uFF0C\u8BF7\u66F4\u65B0 Host \u540E\u91CD\u65B0\u6253\u5F00\u5DE5\u4F5C\u53F0\u3002");
|
|
13735
|
+
const response = await loadOfficialInstallSettings();
|
|
13736
|
+
if (!mounted.current || current !== previewRequest.current || signature2 !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
|
|
13737
|
+
if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u8BFB\u53D6\u5B89\u88C5\u8BBE\u7F6E\u3002"));
|
|
13738
|
+
if (!installSummaryMatchesSettings(response.value?.install, settingsScope?.getSnapshot?.()?.value)) {
|
|
13739
|
+
throw new Error("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46 Host \u5B89\u88C5\u914D\u7F6E\u5C1A\u672A\u540C\u6B65\uFF1B\u8BF7\u5237\u65B0\u5B89\u88C5\u547D\u4EE4\u540E\u518D\u5B89\u88C5\u3002");
|
|
13740
|
+
}
|
|
13741
|
+
setProbeState((previous) => ({
|
|
13742
|
+
...previous,
|
|
13743
|
+
status: previous.status === "loading" || previous.status === "error" ? "ready" : previous.status,
|
|
13744
|
+
install: response.value.install
|
|
13745
|
+
}));
|
|
13746
|
+
if (!mutationPendingRef.current) {
|
|
13747
|
+
previewPendingRef.current = false;
|
|
13748
|
+
setPreviewPending(false);
|
|
13749
|
+
}
|
|
13750
|
+
} catch (error) {
|
|
13751
|
+
if (mounted.current && current === previewRequest.current) setPreviewError(text5(error?.message) || "\u5B89\u88C5\u547D\u4EE4\u5237\u65B0\u5931\u8D25\u3002");
|
|
13752
|
+
}
|
|
13753
|
+
};
|
|
13640
13754
|
import_react5.default.useEffect(() => {
|
|
13641
13755
|
mounted.current = true;
|
|
13642
13756
|
void refresh();
|
|
13757
|
+
void refreshInstallSettings();
|
|
13643
13758
|
if (typeof officialToolInstallStatus === "function") {
|
|
13644
13759
|
void Promise.all(OFFICIAL_TOOLS.map(async (tool) => {
|
|
13645
13760
|
try {
|
|
@@ -13664,29 +13779,40 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13664
13779
|
return () => {
|
|
13665
13780
|
mounted.current = false;
|
|
13666
13781
|
request.current += 1;
|
|
13782
|
+
previewRequest.current += 1;
|
|
13783
|
+
};
|
|
13784
|
+
}, []);
|
|
13785
|
+
const refreshInstallSettingsRef = import_react5.default.useRef(refreshInstallSettings);
|
|
13786
|
+
refreshInstallSettingsRef.current = refreshInstallSettings;
|
|
13787
|
+
const beginSettingsMutation = import_react5.default.useCallback((operations) => {
|
|
13788
|
+
const generation = ++mutationGeneration.current;
|
|
13789
|
+
mutationPendingRef.current = { generation, operations };
|
|
13790
|
+
previewPendingRef.current = true;
|
|
13791
|
+
setPreviewPending(true);
|
|
13792
|
+
return () => {
|
|
13793
|
+
if (!mounted.current || mutationPendingRef.current?.generation !== generation) return false;
|
|
13794
|
+
mutationPendingRef.current = null;
|
|
13795
|
+
void refreshInstallSettingsRef.current();
|
|
13796
|
+
return true;
|
|
13667
13797
|
};
|
|
13668
13798
|
}, []);
|
|
13669
|
-
const refreshRef = import_react5.default.useRef(refresh);
|
|
13670
|
-
refreshRef.current = refresh;
|
|
13671
13799
|
import_react5.default.useEffect(() => {
|
|
13672
13800
|
if (typeof settingsScope?.subscribe !== "function") return void 0;
|
|
13673
|
-
const signature2 = (snapshot) =>
|
|
13674
|
-
snapshot?.value?.toolInstallDir,
|
|
13675
|
-
snapshot?.value?.toolNpmRegistry,
|
|
13676
|
-
snapshot?.value?.toolInstallMethodsJson,
|
|
13677
|
-
snapshot?.value?.toolScriptUrlsJson,
|
|
13678
|
-
snapshot?.value?.toolAllowScriptInstall
|
|
13679
|
-
]);
|
|
13801
|
+
const signature2 = (snapshot) => toolInstallSettingsSignature(snapshot?.value);
|
|
13680
13802
|
let previous = signature2(settingsScope.getSnapshot());
|
|
13681
13803
|
return settingsScope.subscribe(() => {
|
|
13682
13804
|
const snapshot = settingsScope.getSnapshot();
|
|
13683
|
-
|
|
13805
|
+
setSettingsSnapshot(snapshot);
|
|
13684
13806
|
const next = signature2(snapshot);
|
|
13685
13807
|
if (next === previous) return;
|
|
13686
13808
|
previous = next;
|
|
13809
|
+
request.current += 1;
|
|
13810
|
+
setProbeSettingsStale(true);
|
|
13811
|
+
setProbeState((previous2) => ({ ...previous2, status: "ready", probes: [], readiness: [], error: "" }));
|
|
13812
|
+
onProbes({ probes: [], capabilities: [], readiness: [] });
|
|
13687
13813
|
previewPendingRef.current = true;
|
|
13688
13814
|
setPreviewPending(true);
|
|
13689
|
-
void
|
|
13815
|
+
void refreshInstallSettingsRef.current();
|
|
13690
13816
|
});
|
|
13691
13817
|
}, [settingsScope]);
|
|
13692
13818
|
import_react5.default.useEffect(() => {
|
|
@@ -13797,11 +13923,14 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13797
13923
|
if (methodId) map[toolId] = methodId;
|
|
13798
13924
|
else delete map[toolId];
|
|
13799
13925
|
try {
|
|
13800
|
-
const
|
|
13926
|
+
const operations = [{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }];
|
|
13927
|
+
const finishMutation = beginSettingsMutation(operations);
|
|
13928
|
+
const onSettled = (outcome) => {
|
|
13929
|
+
if (finishMutation(outcome) && outcome.accepted) setRowErrors((previous) => ({ ...previous, [toolId]: "" }));
|
|
13930
|
+
};
|
|
13931
|
+
const accepted = await mutateToolInstallSettings(settingsScope, operations, snapshot.revision, { onSettled });
|
|
13801
13932
|
if (!accepted) throw new Error("\u5B89\u88C5\u65B9\u5F0F\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\u3002");
|
|
13802
|
-
|
|
13803
|
-
setPreviewPending(true);
|
|
13804
|
-
await refresh();
|
|
13933
|
+
setSettingsSnapshot(settingsScope.getSnapshot());
|
|
13805
13934
|
} catch (error) {
|
|
13806
13935
|
if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
|
|
13807
13936
|
} finally {
|
|
@@ -13826,9 +13955,14 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13826
13955
|
const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
|
|
13827
13956
|
const readinessById = Object.fromEntries(probeState.readiness.map((item) => [item.id, item]));
|
|
13828
13957
|
const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
|
|
13958
|
+
const savedMethods = savedToolInstallMethods(settingsSnapshot.value);
|
|
13829
13959
|
return /* @__PURE__ */ import_react5.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-card-copy" }, "\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u3001\u7248\u672C\u548C\u767B\u5F55\u72B6\u6001\uFF0C\u5E76\u4ECE\u56FA\u5B9A\u6CE8\u518C\u8868\u4E00\u952E\u5B89\u88C5\u6216\u66F4\u65B0\u5230\u5404\u5382\u5546\u6700\u65B0\u7248\uFF08\u65B0\u7248\u672C\u672A\u7ECF\u63D2\u4EF6\u6D4B\u8BD5\uFF09\u3002\u53EF\u6309\u5DE5\u5177\u9009\u62E9 npm\u3001pnpm \u6216\u5382\u5546\u5B89\u88C5\u811A\u672C\u65B9\u5F0F\uFF0C\u5E76\u7EDF\u4E00\u8BBE\u7F6E\u5B89\u88C5\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\uFF1B\u5378\u8F7D\u53EA\u5220\u9664\u7A0B\u5E8F\uFF0C\u914D\u7F6E\u4E0E\u767B\u5F55\u4FDD\u7559\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u547D\u4EE4\u3002ZCode \u4F1A\u6253\u5F00\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u4F9B\u4F60\u9009\u62E9\u76EE\u5F55\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u4F53\u68C0\u3002")), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: probeState.status === "loading", onClick: () => {
|
|
13830
13960
|
void refresh();
|
|
13831
|
-
} }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react5.default.createElement(InstallSettings, { settingsScope, install: probeState.install, onPendingChange: handleSettingsPending }), previewPending && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u53D8\u66F4\uFF0C\u6B63\u5728\u5237\u65B0\u5B9E\u9645\u5B89\u88C5\u547D\u4EE4\uFF1B\u5237\u65B0\u5B8C\u6210\u540E\u53EF\
|
|
13961
|
+
} }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react5.default.createElement(InstallSettings, { settingsScope, install: probeState.install, onPendingChange: handleSettingsPending, onMutationStart: beginSettingsMutation }), previewPending && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u53D8\u66F4\uFF0C\u6B63\u5728\u5237\u65B0\u5B9E\u9645\u5B89\u88C5\u547D\u4EE4\uFF1B\u4ECD\u53EF\u5207\u6362\u65B9\u5F0F\uFF0C\u5237\u65B0\u5B8C\u6210\u540E\u53EF\u5B89\u88C5\u4E0E\u66F4\u65B0\u3002", !previewError && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
|
|
13962
|
+
void refreshInstallSettings();
|
|
13963
|
+
} }, "\u5237\u65B0\u5B89\u88C5\u547D\u4EE4")), previewError && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, previewError, " ", /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
|
|
13964
|
+
void refreshInstallSettings();
|
|
13965
|
+
} }, "\u5237\u65B0\u5B89\u88C5\u547D\u4EE4")), probeSettingsStale && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u66F4\u6539\uFF0C\u65E7\u76EE\u6807\u7684\u7248\u672C\u4E0E\u5B89\u88C5\u72B6\u6001\u5DF2\u8FC7\u671F\u3002\u53EF\u5B89\u88C5\u6240\u9009\u76EE\u6807\uFF1B\u5378\u8F7D\u524D\u8BF7\u91CD\u65B0\u4F53\u68C0\u3002"), probeState.status === "loading" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), probeState.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, probeState.error), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tools", role: "list", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u6CE8\u518C\u8868" }, OFFICIAL_TOOLS.map((tool) => {
|
|
13832
13966
|
const summary = installById[tool.id];
|
|
13833
13967
|
const command = summary ? summary.command : installCommandLine(tool);
|
|
13834
13968
|
const updateCommand = summary?.updateCommand ?? command;
|
|
@@ -13850,15 +13984,15 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
|
|
|
13850
13984
|
removable: !(summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error))
|
|
13851
13985
|
}
|
|
13852
13986
|
});
|
|
13853
|
-
const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
|
|
13987
|
+
const verified = Boolean(probe) && job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
|
|
13854
13988
|
const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
|
|
13855
|
-
const status = running ? job.cancelRequested ? `\u6B63\u5728\u53D6\u6D88${operation}\u2026` : `${operation}\u4E2D\u2026` : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? `${operation}\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B` : verified ? `${operation}\u6210\u529F\u5E76\u9A8C\u8BC1` : probeLabel(probe);
|
|
13989
|
+
const status = running ? job.cancelRequested ? `\u6B63\u5728\u53D6\u6D88${operation}\u2026` : `${operation}\u4E2D\u2026` : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? `${operation}\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B` : verified ? `${operation}\u6210\u529F\u5E76\u9A8C\u8BC1` : probe ? probeLabel(probe) : "\u6240\u9009\u5B89\u88C5\u76EE\u6807\u5C1A\u672A\u4F53\u68C0";
|
|
13856
13990
|
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status, probe?.installed && probe.version ? ` \xB7 ${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), probe?.installed && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, tool.headlessAdapter ? "\u5DF2\u53EF\u7531 model_router_execute \u4EE5\u65E0\u754C\u9762\u65B9\u5F0F\u8C03\u7528\u3002\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002\u7B7E\u540D\u6C99\u7BB1\u5165\u53E3\u4E0D\u542F\u52A8\u6B64 CLI\u3002" : readiness?.ready ? `\u5B98\u65B9\u6267\u884C\u5165\u53E3\u5DF2\u6838\u9A8C\uFF0C\u53EF\u5728\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_tool_run\uFF1B${capability?.modes?.includes("read-only") ? "\u652F\u6301\u53EA\u8BFB\u548C\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u4EFB\u52A1" : "\u4EC5\u652F\u6301\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u9694\u79BB\u5DE5\u4F5C\u533A\u4EFB\u52A1"}\uFF0C\u8D26\u53F7\u53CA\u6A21\u578B\u4ECD\u9700\u5B9E\u6D4B\u3002` : `\u5DF2\u5B89\u88C5\uFF0C\u4F46\u5F53\u524D\u4E0D\u53EF\u6258\u7BA1\u6267\u884C\uFF1A${readiness?.reason || capability?.reason || "\u6267\u884C\u5165\u53E3\u5C1A\u672A\u6838\u9A8C\u3002"}`), command ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "\u5B89\u88C5\u65B9\u5F0F\uFF1A", methods.find((method) => method.id === summary?.methodId)?.label ?? tool.manager, " \xB7 \u5B9E\u9645\u5B89\u88C5\u76EE\u5F55\uFF1A", summary?.installDir || "\u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55"), /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command", "aria-label": `${tool.label} \u5B89\u88C5\u547D\u4EE4` }, command), updateCommand !== command && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "\u66F4\u65B0\u6CBF\u7528\u539F\u5B89\u88C5\u4F4D\u7F6E\uFF1A", summary?.updateInstallDir || "\u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55"), /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command", "aria-label": `${tool.label} \u66F4\u65B0\u547D\u4EE4` }, updateCommand))) : /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { margin: "6px 0 0" } }, summary?.error ?? tool.unsupportedReason), (summary?.notices ?? []).map((notice) => /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail", key: notice }, notice)), probe?.detail && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, probe.detail), (rowErrors[tool.id] || job?.error) && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error mr-tool-error", role: "alert" }, rowErrors[tool.id] || job.error), Array.isArray(job?.outputTail) && job.outputTail.length > 0 && /* @__PURE__ */ import_react5.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, "\u5B89\u88C5\u65E5\u5FD7"), /* @__PURE__ */ import_react5.default.createElement("pre", null, job.outputTail.slice(-6).join("\n")))), (command || methods.length > 0) && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, methods.length > 0 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-methods" }, /* @__PURE__ */ import_react5.default.createElement("label", null, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5B89\u88C5\u65B9\u5F0F"), /* @__PURE__ */ import_react5.default.createElement(
|
|
13857
13991
|
"select",
|
|
13858
13992
|
{
|
|
13859
13993
|
className: "mr-mini-select",
|
|
13860
|
-
value:
|
|
13861
|
-
disabled: running ||
|
|
13994
|
+
value: savedMethods[tool.id] ?? "",
|
|
13995
|
+
disabled: running || settingsPending || Boolean(methodSaving) || settingsSnapshot.writable !== true,
|
|
13862
13996
|
onChange: (event) => {
|
|
13863
13997
|
void setInstallMethod(tool.id, event.target.value);
|
|
13864
13998
|
}
|
|
@@ -14001,7 +14135,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
|
|
|
14001
14135
|
}, []);
|
|
14002
14136
|
return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
|
|
14003
14137
|
}
|
|
14004
|
-
function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
|
|
14138
|
+
function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
|
|
14005
14139
|
const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
|
|
14006
14140
|
const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
|
|
14007
14141
|
const [task, setTask] = import_react5.default.useState("");
|
|
@@ -14223,6 +14357,7 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
|
|
|
14223
14357
|
OfficialToolsCard,
|
|
14224
14358
|
{
|
|
14225
14359
|
listOfficialTools,
|
|
14360
|
+
loadOfficialInstallSettings,
|
|
14226
14361
|
installOfficialTool,
|
|
14227
14362
|
uninstallOfficialTool,
|
|
14228
14363
|
repairOfficialTool,
|
|
@@ -14413,6 +14548,7 @@ var toolIdParameter = Object.freeze({
|
|
|
14413
14548
|
var jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: "json", codec });
|
|
14414
14549
|
var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
|
|
14415
14550
|
descriptor("list", [], listResultCodec),
|
|
14551
|
+
descriptor("installSettings", [], anyObjectCodec("OfficialInstallSettings")),
|
|
14416
14552
|
descriptor("installTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
|
|
14417
14553
|
descriptor("uninstallTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
|
|
14418
14554
|
descriptor("repairTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
|
|
@@ -14836,6 +14972,7 @@ function registerUi(ctx) {
|
|
|
14836
14972
|
loadCatalog: () => ctx.remote.session.modelCatalog(),
|
|
14837
14973
|
settingsScope,
|
|
14838
14974
|
listOfficialTools: () => officialToolsRemote.list(),
|
|
14975
|
+
loadOfficialInstallSettings: () => officialToolsRemote.installSettings(),
|
|
14839
14976
|
installOfficialTool: (request) => officialToolsRemote.installTool(request),
|
|
14840
14977
|
uninstallOfficialTool: (request) => officialToolsRemote.uninstallTool(request),
|
|
14841
14978
|
repairOfficialTool: (request) => officialToolsRemote.repairTool(request),
|
package/.dsh-plugin/index.mjs
CHANGED
|
@@ -1434,11 +1434,25 @@ export function routerRemoteServices(ctx, config, { terminals = null } = {}) {
|
|
|
1434
1434
|
/** Register model-facing tools and the human /router command. */
|
|
1435
1435
|
export function apply(ctx, config = {}) {
|
|
1436
1436
|
// Install addresses (directory, npm source, per-tool method and script source)
|
|
1437
|
-
//
|
|
1438
|
-
//
|
|
1437
|
+
// are volatile refs: Loader changes them without re-applying the plugin.
|
|
1438
|
+
// Read them after the owning fiber's live update, before a saved view is sent.
|
|
1439
1439
|
let installPreferenceError = null
|
|
1440
|
-
|
|
1441
|
-
|
|
1440
|
+
const syncInstallPreferences = () => {
|
|
1441
|
+
try {
|
|
1442
|
+
const next = parseInstallPreferences(config, { tools: OFFICIAL_TOOLS, ...currentInstallOptions() })
|
|
1443
|
+
setInstallPreferences(next)
|
|
1444
|
+
installPreferenceError = null
|
|
1445
|
+
} catch (error) { installPreferenceError = errorText(error) }
|
|
1446
|
+
}
|
|
1447
|
+
const installFields = new Set(['toolInstallDir', 'toolNpmRegistry', 'toolInstallMethodsJson', 'toolScriptUrlsJson', 'toolAllowScriptInstall'])
|
|
1448
|
+
syncInstallPreferences()
|
|
1449
|
+
// Loader scopes this event to the fiber whose refs were committed.
|
|
1450
|
+
ctx.on('loader/volatile-update', paths => {
|
|
1451
|
+
if (Array.isArray(paths) && paths.some(path => installFields.has(path?.[0]))) syncInstallPreferences()
|
|
1452
|
+
})
|
|
1453
|
+
const assertInstallPreferences = () => {
|
|
1454
|
+
if (installPreferenceError) throw new Error(`官方工具安装设置无效:${installPreferenceError}`)
|
|
1455
|
+
}
|
|
1442
1456
|
// The official Host injects typert; direct lightweight uses of apply may
|
|
1443
1457
|
// supply only the model/command services and do not expose the Desktop RPC.
|
|
1444
1458
|
if (ctx.typert) {
|
|
@@ -1449,7 +1463,11 @@ export function apply(ctx, config = {}) {
|
|
|
1449
1463
|
process.once('exit', onExit)
|
|
1450
1464
|
return () => { process.off('exit', onExit); terminals.disposeAll('dispose') }
|
|
1451
1465
|
}, 'model-router-galgame: interactive terminals')
|
|
1452
|
-
registerOfficialToolsRemote(ctx,
|
|
1466
|
+
registerOfficialToolsRemote(ctx, {
|
|
1467
|
+
...routerRemoteServices(ctx, config, { terminals }),
|
|
1468
|
+
installSettingsError: () => installPreferenceError,
|
|
1469
|
+
installRequestedDirectory: () => valueOf(config, 'toolInstallDir', ''),
|
|
1470
|
+
})
|
|
1453
1471
|
}
|
|
1454
1472
|
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
1455
1473
|
const decision = await next()
|
|
@@ -1498,7 +1516,7 @@ export function apply(ctx, config = {}) {
|
|
|
1498
1516
|
if (reasons.length) return combinedAsk(reasons)
|
|
1499
1517
|
return decision
|
|
1500
1518
|
})
|
|
1501
|
-
registerOfficialToolModels(ctx, config)
|
|
1519
|
+
registerOfficialToolModels(ctx, config, assertInstallPreferences)
|
|
1502
1520
|
ctx.tools.register(defineTool({
|
|
1503
1521
|
name: 'model_router_routes',
|
|
1504
1522
|
description: 'List provider/model routes registered in the official DeepSeek Harness model directory. Credential and network availability are not verified. No API keys or endpoints are returned.',
|
|
@@ -1611,6 +1629,7 @@ export function apply(ctx, config = {}) {
|
|
|
1611
1629
|
const toolId = matched[2]
|
|
1612
1630
|
const method = matched[3]
|
|
1613
1631
|
try {
|
|
1632
|
+
assertInstallPreferences()
|
|
1614
1633
|
const job = action === 'uninstall' ? startUninstall(toolId, { method })
|
|
1615
1634
|
: action === 'repair' ? startRepair(toolId, { method })
|
|
1616
1635
|
: startInstall(toolId, { method })
|
|
@@ -1649,7 +1668,7 @@ async function waitForInstall(toolId, signal) {
|
|
|
1649
1668
|
}
|
|
1650
1669
|
|
|
1651
1670
|
/** Register the model-facing official-tool probes and installer. */
|
|
1652
|
-
function registerOfficialToolModels(ctx, config) {
|
|
1671
|
+
function registerOfficialToolModels(ctx, config, assertInstallPreferences) {
|
|
1653
1672
|
ctx.tools.register(defineTool({
|
|
1654
1673
|
name: 'model_router_tools',
|
|
1655
1674
|
description: 'Probe the fixed registry of official model tools (Kimi Code, Claude Code, Codex, MiniMax Code, MiMo Code, Grok Build, Gemini CLI, OpenCode, Step Code, ZCode) and report which are installed with their versions, plus each tool\'s available install methods and the effective install directory and npm source. Never reads credentials.',
|
|
@@ -1680,6 +1699,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1680
1699
|
},
|
|
1681
1700
|
output: JSON_OUTPUT,
|
|
1682
1701
|
async execute(args, exec) {
|
|
1702
|
+
assertInstallPreferences()
|
|
1683
1703
|
const job = startInstall(args.tool, { method: text(args.method) || undefined })
|
|
1684
1704
|
const settled = await waitForInstall(job.tool, exec.signal)
|
|
1685
1705
|
const tool = getOfficialTool(args.tool)
|
|
@@ -1704,6 +1724,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1704
1724
|
},
|
|
1705
1725
|
output: JSON_OUTPUT,
|
|
1706
1726
|
async execute(args, exec) {
|
|
1727
|
+
assertInstallPreferences()
|
|
1707
1728
|
const job = startRepair(args.tool, { method: text(args.method) || undefined })
|
|
1708
1729
|
const settled = await waitForInstall(job.tool, exec.signal)
|
|
1709
1730
|
const tool = getOfficialTool(args.tool)
|
|
@@ -1726,6 +1747,7 @@ function registerOfficialToolModels(ctx, config) {
|
|
|
1726
1747
|
},
|
|
1727
1748
|
output: JSON_OUTPUT,
|
|
1728
1749
|
async execute(args, exec) {
|
|
1750
|
+
assertInstallPreferences()
|
|
1729
1751
|
const job = startUninstall(args.tool, { method: text(args.method) || undefined })
|
|
1730
1752
|
const settled = await waitForInstall(job.tool, exec.signal)
|
|
1731
1753
|
const tool = getOfficialTool(args.tool)
|
|
@@ -179,13 +179,25 @@ export class OfficialToolsRemoteService extends TypertRemoteService {
|
|
|
179
179
|
executionCapabilities: officialToolExecutionCapabilities(),
|
|
180
180
|
executionReadiness,
|
|
181
181
|
hostVersion: HOST_PLUGIN_VERSION,
|
|
182
|
-
install:
|
|
182
|
+
install: this.installSettings().install,
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/** Refresh saved methods and commands without spawning CLIs or checking downloads. */
|
|
187
|
+
installSettings() {
|
|
188
|
+
const error = this.services?.installSettingsError?.()
|
|
189
|
+
if (error) throw new Error(`官方工具安装设置无效:${error}`)
|
|
190
|
+
const install = installSummary()
|
|
191
|
+
// The saved input can contain Host-only home/environment expansions. Keep
|
|
192
|
+
// it separate from the resolved directory used by the actual command.
|
|
193
|
+
install.requestedInstallDir = this.services?.installRequestedDirectory?.() ?? install.installDir
|
|
194
|
+
return { install, hostVersion: HOST_PLUGIN_VERSION }
|
|
195
|
+
}
|
|
196
|
+
|
|
186
197
|
/** Start one serialized fixed-registry install; return immediately for UI polling. */
|
|
187
198
|
async installTool(request) {
|
|
188
199
|
return settled(async () => {
|
|
200
|
+
this.installSettings()
|
|
189
201
|
await ensureNpmPrefixOnPath()
|
|
190
202
|
return { accepted: true, job: startInstall(request.tool, { method: request.method }) }
|
|
191
203
|
})
|
|
@@ -197,6 +209,7 @@ export class OfficialToolsRemoteService extends TypertRemoteService {
|
|
|
197
209
|
*/
|
|
198
210
|
async uninstallTool(request) {
|
|
199
211
|
return settled(async () => {
|
|
212
|
+
this.installSettings()
|
|
200
213
|
await ensureNpmPrefixOnPath()
|
|
201
214
|
return { accepted: true, job: startUninstall(request.tool, { method: request.method }) }
|
|
202
215
|
})
|
|
@@ -205,6 +218,7 @@ export class OfficialToolsRemoteService extends TypertRemoteService {
|
|
|
205
218
|
/** Reinstall the tool even when it already reports the latest version. */
|
|
206
219
|
async repairTool(request) {
|
|
207
220
|
return settled(async () => {
|
|
221
|
+
this.installSettings()
|
|
208
222
|
await ensureNpmPrefixOnPath()
|
|
209
223
|
return { accepted: true, job: startRepair(request.tool, { method: request.method }) }
|
|
210
224
|
})
|
|
@@ -193,6 +193,7 @@ const jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source:
|
|
|
193
193
|
|
|
194
194
|
export const OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
|
|
195
195
|
descriptor('list', [], listResultCodec),
|
|
196
|
+
descriptor('installSettings', [], anyObjectCodec('OfficialInstallSettings')),
|
|
196
197
|
descriptor('installTool', [jsonParameter('request', toolActionCodec)], installResultCodec),
|
|
197
198
|
descriptor('uninstallTool', [jsonParameter('request', toolActionCodec)], installResultCodec),
|
|
198
199
|
descriptor('repairTool', [jsonParameter('request', toolActionCodec)], installResultCodec),
|
|
@@ -56,7 +56,7 @@ export const CLAUDE_UPDATE_ARGS = Object.freeze(['update'])
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Host-side install preferences (install directory, npm source, per-tool method
|
|
59
|
-
* and script source). Refreshed
|
|
59
|
+
* and script source). Refreshed on apply and live config updates, so a client
|
|
60
60
|
* can only ever pick a method id — these addresses never arrive over the wire.
|
|
61
61
|
*/
|
|
62
62
|
let preferences = { installDir: '', registry: '', methods: {}, scriptUrls: {}, allowScriptInstall: true }
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.1 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
Stable patch under the npm latest tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading.
|
|
6
|
+
|
|
7
|
+
- Fix official-tool method selection stuck on saving: configuration persistence no longer waits for CLI inspection, and selectors reflect the acknowledged settings snapshot.
|
|
8
|
+
- Refresh install commands through a lightweight Host endpoint; delayed tool inspections cannot overwrite a newer install preview. Install actions wait for confirmed settings while method selection remains available.
|
|
9
|
+
- Synchronize volatile install settings into Host preferences without requiring plugin reload. Invalid configuration is reported and cannot start an operation with older settings.
|
|
10
|
+
- Add regression coverage using the real settings controller and Loader, plus a mock UI scenario with a hanging tool inspection. Release validation is recorded in the release notes.
|
|
11
|
+
|
|
3
12
|
## 0.18.0 — 2026-10-12
|
|
4
13
|
|
|
5
14
|
Stable minor under the npm latest tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading. Existing releases remain unchanged.
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness Desktop
|
|
2
2
|
|
|
3
|
-
Version: **0.18.
|
|
3
|
+
Version: **0.18.1**. See [routing research](docs/ROUTING_RESEARCH.zh.md), [mathematical derivation and experiments](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md), and the [LiveBench evidence audit and validation protocol](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md). The latest public 2026-06-25 score/cost tables are aggregates; its paired question-quality/cost matrix has not been obtained, and no real routing gain is claimed.
|
|
4
4
|
|
|
5
5
|
A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe a task, compare your configured models, inspect the plan, and explicitly choose when to execute it.
|
|
6
6
|
|
|
7
|
+
**0.18.1:** fixes install-method selection stuck on saving. Saved volatile settings now update Host install preferences immediately; command previews refresh separately from tool inspection.
|
|
8
|
+
|
|
7
9
|
**0.18.0:** expands official vendor installer choices alongside npm/pnpm, preserves the configured install destination during updates, and verifies the target copy instead of an older PATH copy. MiniMax Windows scripts beginning with PowerShell `<# ... #>` help comments are accepted; HTML error pages remain rejected. Save unified install settings before installing or updating.
|
|
8
10
|
|
|
9
11
|
**0.16.4:** PATH probes keep command output even when no install log callback is attached. 0.16.3 captured stdout only inside `onOutput?.(...)`, so `where` and `--version` looked empty and every PATH CLI was reported not-installed. MiniMax and ZCode were unaffected because they do not use that probe.
|
|
@@ -16,7 +18,7 @@ A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe
|
|
|
16
18
|
|
|
17
19
|
**0.16.0:** [dynamic data and adaptive feedback](docs/ROUTING_ADAPTIVE_LEARNING.zh.md) adds opt-in public benchmark/curated fixed-USD pricing refresh and task-specific, time-decayed, shrinkage-regularized subjective utility. Objective proxy quality floors remain separate. Public sources default off; preferences are shared within one local DSH_HOME and limited to the latest 200 runs. This is not a universal vendor-price scraper or evidence of real quality–cost gains.
|
|
18
20
|
|
|
19
|
-
Current version: **0.18.
|
|
21
|
+
Current version: **0.18.1** (`@ljwei-stak/dsh-model-router`). Its declared host compatibility is **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**. The router and [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) have been independent plugins since 0.12.0.
|
|
20
22
|
|
|
21
23
|
**0.15.0 changes:** a four-tab workbench with routing advice directly below task planning, protection against stale execution previews, preserved spending totals across history pruning and retries, and a standalone mock UI preview for source development.
|
|
22
24
|
|
|
@@ -48,16 +50,16 @@ Generating a route recommendation is local: it does not start a vendor CLI or ma
|
|
|
48
50
|
Open **Plugins → Add plugin** and enter:
|
|
49
51
|
|
|
50
52
|
```text
|
|
51
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
53
|
+
@ljwei-stak/dsh-model-router@0.18.1
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.18.
|
|
56
|
+
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.18.1** and the sidebar should contain **模型路由** (Model Router). A global `npm install -g` alone does not register a Desktop plugin.
|
|
55
57
|
|
|
56
58
|
You can instead install a local `.tgz` archive or its extracted inner `package` directory through the plugin manager. Both must contain `package.json` and `.dsh-plugin`. See [releases](https://github.com/Alice-Marx/dsh-model-router/releases) for archived builds, or build your own checkout below.
|
|
57
59
|
|
|
58
60
|
### Upgrade from the old package name
|
|
59
61
|
|
|
60
|
-
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.18.
|
|
62
|
+
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.18.1`. Do not keep both entries installed: they share the internal plugin id `model-router-galgame` and tool names. The retained id lets existing router settings and history carry over.
|
|
61
63
|
|
|
62
64
|
For the combined 0.11.x package, back up the profile and export the GAL saves you want to preserve before upgrading. Story playback, artwork, music and saves now belong to the independent `@ljwei-stak/dsh-galgame` package. The detailed save limitations are in [MIGRATION.md](docs/archive/MIGRATION.md).
|
|
63
65
|
|
package/README.zh.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness 模型路由插件
|
|
2
2
|
|
|
3
|
-
版本:**0.18.
|
|
3
|
+
版本:**0.18.1**。选模求解器概览见[路由研究说明](docs/ROUTING_RESEARCH.zh.md);公式、条件证明与实验见[数学推导与实验记录](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md)。[LiveBench 最新证据与验证协议](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md)已核查 2026-06-25 聚合评分/费用;最新逐题质量—费用矩阵尚未取得,不声称真实路由收益。
|
|
4
4
|
|
|
5
5
|
为 **DeepSeek Harness Desktop** 提供任务规划、成本感知的模型分配和官方工具执行工作台。输入任务,比较已配置模型,审阅计划,再明确选择是否执行。
|
|
6
6
|
|
|
7
|
+
**0.18.1**:修复安装方式切换卡在“保存方式中…”的问题。已保存设置立即同步到宿主安装配置,命令预览单独刷新;工具体检延迟时仍能继续切换方式。
|
|
8
|
+
|
|
7
9
|
**0.18.0**:补齐厂商提供的 curl/irm 安装方式,安装与更新复用已保存的目录、方式和下载源,并核验目标目录里的版本。修复 MiniMax 官方 PowerShell 脚本以 `<# ... #>` 帮助注释开头时被误判为 HTML 的问题。统一安装设置改为明确保存后生效。
|
|
8
10
|
|
|
9
11
|
**0.16.4**:PATH 探测在没有安装日志回调时也会保留命令输出。0.16.3 把 stdout 写进了 `onOutput?.(...)`,`where` 和 `--version` 因此变成空字符串,PATH 上的 CLI 全被报成未安装。MiniMax 与 ZCode 不走这条探测,所以仍能显示。
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
|
|
17
19
|
**0.16.0 新增**:[动态数据与持续反馈](docs/ROUTING_ADAPTIVE_LEARNING.zh.md):可选公开价格/LiveBench快照刷新;按任务类型、时间衰减和低样本收缩调整个人偏好,质量门槛独立保留。设置在“预算与安全”,公开源默认关闭;价格源需提供按官方文档核对的固定USD单价JSON,不是通用官网抓价器。本地DSH_HOME共享、最近200运行窗口,暂无真实收益证明。
|
|
18
20
|
|
|
19
|
-
当前版本为 **0.18.
|
|
21
|
+
当前版本为 **0.18.1**,包名为 `@ljwei-stak/dsh-model-router`;声明兼容的宿主为 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**。自 0.12.0 起,路由与 [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) 已拆成两个独立插件。
|
|
20
22
|
|
|
21
23
|
**0.15.0 更新**:工作台分为四页,路由建议紧接任务规划展示;执行输入变化时丢弃旧预览;裁剪历史或重试后仍保留费用累计;源码开发新增独立模拟 UI 预览。
|
|
22
24
|
|
|
@@ -48,16 +50,16 @@
|
|
|
48
50
|
打开 **插件 → 添加插件**,填写:
|
|
49
51
|
|
|
50
52
|
```text
|
|
51
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
53
|
+
@ljwei-stak/dsh-model-router@0.18.1
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.18.
|
|
56
|
+
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.18.1**,侧栏出现**模型路由**。仅运行全局 `npm install -g` 不会把插件注册到桌面版 profile。
|
|
55
57
|
|
|
56
58
|
也可在插件管理器填写本地 `.tgz` 文件的绝对路径,或解压后的内层 `package` 目录;其中应包含 `package.json` 与 `.dsh-plugin`。[历史发布页](https://github.com/Alice-Marx/dsh-model-router/releases)提供归档;已克隆源码的开发方式见下文。
|
|
57
59
|
|
|
58
60
|
### 从旧包名升级
|
|
59
61
|
|
|
60
|
-
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.18.
|
|
62
|
+
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.18.1`。不要同时安装两个包:它们共用内部插件 id `model-router-galgame` 与工具名;id 保留使已有路由设置和执行历史能够延续。
|
|
61
63
|
|
|
62
64
|
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
63
65
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
模型路由从 **0.12.0** 起独立发布;GAL 是新的 **0.1.0** 插件。两者均声明支持官方 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**,可分别安装和更新。
|
|
4
4
|
|
|
5
|
-
当前路由正式版为 **0.18.0
|
|
5
|
+
当前路由正式版为 **0.18.1**。本版修复安装方式切换卡在“保存方式中…”及宿主继续使用旧安装设置的问题。方式保存和命令预览不再等待全量工具体检。保留 0.18.0 的官方安装方法、MiniMax 脚本校验和统一目录改进。统一安装设置需点击保存,再选择工具的安装方式。公开数据默认关闭;安装时选择下方精确版本。
|
|
6
6
|
|
|
7
7
|
[路由 README](../README.zh.md) · [迁移说明](archive/MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
| 需求 | 添加插件输入框 | 安装后的入口 |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| 分析复杂度、按质量和费用选模型、调用官方工具 | `@ljwei-stak/dsh-model-router@0.18.
|
|
13
|
+
| 分析复杂度、按质量和费用选模型、调用官方工具 | `@ljwei-stak/dsh-model-router@0.18.1` | 模型路由 |
|
|
14
14
|
| 剧情、立绘、音乐、存档、自由对话 | `@ljwei-stak/dsh-galgame@0.1.0` | Gal 模块 |
|
|
15
15
|
| 两者都要 | 分别安装上面两个包 | 模型路由 + Gal 模块 |
|
|
16
16
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
2. 在“包名、GitHub 仓库地址或本地目录路径”输入框填一个完整包名,例如:
|
|
25
25
|
|
|
26
26
|
```text
|
|
27
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
27
|
+
@ljwei-stak/dsh-model-router@0.18.1
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
只想安装 GAL 时填写:
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
4. 核对预览的包名、版本和宿主兼容信息,安装并启用。完全退出 Harness(包括托盘进程)再启动,确保 Host 加载新代码。
|
|
43
|
-
5. 检查插件详情与侧边栏:路由应为 **0.18.
|
|
43
|
+
5. 检查插件详情与侧边栏:路由应为 **0.18.1 / 模型路由**;GAL 应为 **0.1.0 / Gal 模块**。仅装路由时没有 GAL 入口是预期行为。
|
|
44
44
|
|
|
45
45
|
普通用户无需 `npm install -g`,该命令不会把插件注册到当前 Harness profile。
|
|
46
46
|
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
|
|
49
49
|
进入对应 Release 下载 `.tgz`:
|
|
50
50
|
|
|
51
|
-
- [模型路由 Releases](https://github.com/Alice-Marx/dsh-model-router/releases)(也可运行 `npm pack @ljwei-stak/dsh-model-router@0.18.
|
|
51
|
+
- [模型路由 Releases](https://github.com/Alice-Marx/dsh-model-router/releases)(也可运行 `npm pack @ljwei-stak/dsh-model-router@0.18.1` 获取同一安装包)
|
|
52
52
|
- [独立 GAL v0.1.0](https://github.com/Alice-Marx/deepseek-harness-galgame/releases/tag/v0.1.0)
|
|
53
53
|
|
|
54
54
|
在“添加插件”填写下载文件的**绝对路径**,例如:
|
|
55
55
|
|
|
56
56
|
```text
|
|
57
|
-
D:\Plugins\ljwei-stak-dsh-model-router-0.18.
|
|
57
|
+
D:\Plugins\ljwei-stak-dsh-model-router-0.18.1.tgz
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
```text
|
|
@@ -64,7 +64,7 @@ D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz
|
|
|
64
64
|
如附有 `.sha256` 文件,用 PowerShell 计算并比较摘要:
|
|
65
65
|
|
|
66
66
|
```powershell
|
|
67
|
-
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-model-router-0.18.
|
|
67
|
+
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-model-router-0.18.1.tgz'
|
|
68
68
|
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz'
|
|
69
69
|
```
|
|
70
70
|
|
|
@@ -74,7 +74,7 @@ Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-galgame-0
|
|
|
74
74
|
|
|
75
75
|
1. 在旧合并插件的 GAL 播放器导出需要保留的剧情进度,并备份整个 Harness profile。每份 JSON 仅保存**当前剧情状态**,不含所有手动槽、设置、已读记录或本地音乐;重要槽位可依次读档再导出。
|
|
76
76
|
2. 在**同一个 profile**安装独立 `@ljwei-stak/dsh-galgame@0.1.0`。
|
|
77
|
-
3. 0.13.0 起包名改为 `@ljwei-stak/dsh-model-router`(旧包名最后版本为 0.13.0,内容相同,之后不再更新):在插件管理器卸载原 `@ljwei-stak/model-router-galgame`(保留 profile 与应用数据),再添加 `@ljwei-stak/dsh-model-router@0.18.
|
|
77
|
+
3. 0.13.0 起包名改为 `@ljwei-stak/dsh-model-router`(旧包名最后版本为 0.13.0,内容相同,之后不再更新):在插件管理器卸载原 `@ljwei-stak/model-router-galgame`(保留 profile 与应用数据),再添加 `@ljwei-stak/dsh-model-router@0.18.1`。不要两个同时安装(条目 id 与工具名相同);条目 id 未变,路由设置与执行记录会保留。完成两项更新后再玩剧情,避免同时操作旧合并入口和新入口。
|
|
78
78
|
4. 现在路由与 GAL 各有一个入口。独立 GAL 沿用旧存档 localStorage 键,核对当前进度和三槽;更换 profile 或进度未恢复时,先切换到匹配剧目,再导入 JSON 备份。
|
|
79
79
|
5. 后续分别更新需要的插件;只用路由可以不启用或不安装 GAL。保留 profile 与应用数据,卸载插件时不要清理故事数据。
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
82
82
|
|
|
83
83
|
## 5. 模型路由第一次使用
|
|
84
84
|
|
|
85
|
-
工作台自 0.15.0 起分成**任务与执行、模型配置、官方工具、预算与安全**四页;0.18.
|
|
85
|
+
工作台自 0.15.0 起分成**任务与执行、模型配置、官方工具、预算与安全**四页;0.18.1 沿用该布局。切换页面保留安装任务、终端会话和未完成的输入。
|
|
86
86
|
|
|
87
87
|
1. 在 Harness 官方**模型**页配置供应商、模型与凭据。插件读取准确的 `provider/model` 目录,模型档案不保存 API Key。
|
|
88
88
|
2. 在**模型配置**刷新目录,逐条填写自报质量评分、核对过的输入/输出单价(USD / 百万 token)和擅长方向;缺价留空。可选 `cliModel` 必须是该厂商 CLI 已验证接受的名字。可选 LiveBench 名称须准确对应版本与推理档位;它是用户确认的映射,不自动证明实际执行档位。
|
|
@@ -114,7 +114,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
114
114
|
| 检查 | 预期 |
|
|
115
115
|
| --- | --- |
|
|
116
116
|
| 宿主版本 | 0.2.0-rc.1 或 0.2.0-rc.2;包括 `rc` 后缀。 |
|
|
117
|
-
| 路由独立安装 | 0.18.
|
|
117
|
+
| 路由独立安装 | 0.18.1 详情、模型路由入口、目录和模型档案可用;不出现旧合并 GAL 入口。 |
|
|
118
118
|
| GAL 独立安装 | 0.1.0 详情、Gal 模块入口、标题与两部核心剧情可用;不要求安装路由。 |
|
|
119
119
|
| 共同安装 | 两个入口各一份,路由工具和 GAL 播放器均能使用。 |
|
|
120
120
|
| 存档迁移 | 同 profile 可读旧键;跨 profile 用匹配剧目 JSON 导入;旧篇目的数据不覆盖。 |
|
|
@@ -125,11 +125,11 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
125
125
|
| 动态公开数据 | 默认关闭;自行启用后核对快照版本、校验时间及失败/过期状态;人工单价仍优先。 |
|
|
126
126
|
| 可编辑执行 | 在干净测试仓库核对审批、隔离工作区、补丁整合、厂商实际模型与计费。 |
|
|
127
127
|
|
|
128
|
-
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.18.
|
|
128
|
+
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.18.1 的发布验证见[更新记录](CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
129
129
|
|
|
130
130
|
## 8. 开发者从完整源码构建
|
|
131
131
|
|
|
132
|
-
路由需要 Node.js **22.19+** 与 **pnpm 10.34.6**。从完整源码仓库根目录构建;复现正式版 0.18.
|
|
132
|
+
路由需要 Node.js **22.19+** 与 **pnpm 10.34.6**。从完整源码仓库根目录构建;复现正式版 0.18.1 时检出对应发布标签。旧版(如 0.15.0)标签保留其当时内容。运行:
|
|
133
133
|
|
|
134
134
|
```powershell
|
|
135
135
|
pnpm install --frozen-lockfile --strict-peer-dependencies
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 模型路由工作台:配置、规划与实际执行
|
|
2
2
|
|
|
3
|
-
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.18.
|
|
3
|
+
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.18.1`;声明兼容 DeepSeek Harness Desktop `0.2.0-rc.1 / 0.2.0-rc.2`。安装与开发见[中文 README](../README.zh.md),旧包迁移见[迁移说明](archive/MIGRATION.md)。
|
|
4
4
|
|
|
5
5
|
0.15.0 新增四页布局、规划后直接展示路由建议、执行输入变化时丢弃旧预览,以及历史裁剪和重试后的费用累计保留。源码中的独立 UI 预览使用模拟数据,用于组件检查,不代表真实账号调用或桌面端到端验收。
|
|
6
6
|
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
|
|
110
110
|
### 统一安装设置
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
卡片顶部**统一安装设置**一次设定所有工具的安装地址,修改后点击**保存安装设置**;保存及命令刷新完成后,下一次安装、更新、修复、卸载使用已保存配置。未保存的输入可以**放弃更改**。插件设置页里有同样的字段。方式保存与命令预览独立于工具体检,体检较慢时也能继续选择方式;命令预览确认前暂不能执行安装操作。
|
|
113
113
|
|
|
114
114
|
| 设置 | 作用 | 限制 |
|
|
115
115
|
| --- | --- | --- |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ljwei-stak/dsh-model-router",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Model Router for official DeepSeek Harness Desktop: cost-aware task routing, dependent work packages and official vendor CLI discovery, installation and execution. GAL is available separately as @ljwei-stak/dsh-galgame.",
|
|
6
6
|
"repository": {
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"experiment:routing-summary": "node scripts/summarize-routing-search.mjs",
|
|
104
104
|
"experiment:routing-adaptive": "node scripts/adaptive-routing-study.mjs",
|
|
105
105
|
"audit:livebench": "node scripts/livebench-aggregate-audit.mjs",
|
|
106
|
-
"test": "node --test tests/adaptive-*.test.mjs tests/dynamic-data.test.mjs tests/router.test.mjs tests/routing-optimizer.test.mjs tests/routing-evaluation.test.mjs tests/routing-paired-statistics.test.mjs tests/routing-selftest-tools.test.mjs tests/livebench-aggregate-audit.test.mjs tests/livebench-prior-study.test.mjs tests/routing-search-experiment.test.mjs tests/routing-regression-study.test.mjs tests/routing-search-summary.test.mjs tests/harness-*.test.mjs tests/official-tools.test.mjs tests/official-team-runtime.test.mjs tests/model-profiles.test.mjs tests/model-profile-editor.test.mjs tests/tool-install-state.test.mjs tests/vendor-adapters.test.mjs tests/desktop-compat.test.mjs tests/plugin-split.test.mjs tests/task-executors.test.mjs tests/tool-install-preferences.test.mjs tests/roadmap.test.mjs tests/insights-state.test.mjs tests/billing-team-ledger.test.mjs tests/subscription-billing.test.mjs tests/subscription-host.test.mjs tests/test-pass-regressions.test.mjs tests/roadmap-gaps.test.mjs tests/cordis-inject.test.mjs tests/cli-terminal.test.mjs tests/cli-terminal-gateway.test.mjs tests/latest-versions.test.mjs tests/vendor-installer-validation.test.mjs tests/install-entry-selection.test.mjs",
|
|
106
|
+
"test": "node --test tests/adaptive-*.test.mjs tests/dynamic-data.test.mjs tests/router.test.mjs tests/routing-optimizer.test.mjs tests/routing-evaluation.test.mjs tests/routing-paired-statistics.test.mjs tests/routing-selftest-tools.test.mjs tests/livebench-aggregate-audit.test.mjs tests/livebench-prior-study.test.mjs tests/routing-search-experiment.test.mjs tests/routing-regression-study.test.mjs tests/routing-search-summary.test.mjs tests/harness-*.test.mjs tests/official-tools.test.mjs tests/official-team-runtime.test.mjs tests/model-profiles.test.mjs tests/model-profile-editor.test.mjs tests/tool-install-state.test.mjs tests/tool-install-sdk-ui.test.mjs tests/vendor-adapters.test.mjs tests/desktop-compat.test.mjs tests/plugin-split.test.mjs tests/install-settings-host.test.mjs tests/task-executors.test.mjs tests/tool-install-preferences.test.mjs tests/roadmap.test.mjs tests/insights-state.test.mjs tests/billing-team-ledger.test.mjs tests/subscription-billing.test.mjs tests/subscription-host.test.mjs tests/test-pass-regressions.test.mjs tests/roadmap-gaps.test.mjs tests/cordis-inject.test.mjs tests/cli-terminal.test.mjs tests/cli-terminal-gateway.test.mjs tests/latest-versions.test.mjs tests/vendor-installer-validation.test.mjs tests/install-entry-selection.test.mjs",
|
|
107
107
|
"prepack": "node scripts/prepack.mjs",
|
|
108
108
|
"pack:local": "npm pack --pack-destination dist"
|
|
109
109
|
},
|