@ljwei-stak/dsh-model-router 0.14.0-beta.1 → 0.14.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +59 -17
- package/.dsh-plugin/official-tools-remote-service.mjs +8 -1
- package/CHANGELOG.md +25 -2
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/package.json +2 -2
package/.dsh-plugin/client.js
CHANGED
|
@@ -11600,6 +11600,24 @@ var xterm_default = `/**
|
|
|
11600
11600
|
}
|
|
11601
11601
|
`;
|
|
11602
11602
|
|
|
11603
|
+
// .dsh-plugin/client/host-version.mjs
|
|
11604
|
+
var ROUTER_CLIENT_VERSION = true ? "0.14.0-beta.3" : "";
|
|
11605
|
+
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";
|
|
11606
|
+
function isMissingRemoteMethod(message) {
|
|
11607
|
+
const value = String(message ?? "");
|
|
11608
|
+
return /transport failure for [^:]+: HTTP 404\b/.test(value) || /Remote method \S+ is no longer mounted/.test(value);
|
|
11609
|
+
}
|
|
11610
|
+
function remoteErrorText(message, fallback = "") {
|
|
11611
|
+
if (isMissingRemoteMethod(message)) return STALE_HOST_MESSAGE;
|
|
11612
|
+
return String(message ?? "").trim() || fallback;
|
|
11613
|
+
}
|
|
11614
|
+
function staleHostNotice({ hostVersion, clientVersion = ROUTER_CLIENT_VERSION, loaded = true } = {}) {
|
|
11615
|
+
if (!loaded || !clientVersion) return "";
|
|
11616
|
+
if (typeof hostVersion !== "string" || !hostVersion) return `${STALE_HOST_MESSAGE}\uFF08\u754C\u9762\u4E3A ${clientVersion}\uFF0C\u540E\u53F0\u4E3A\u66F4\u65E9\u7248\u672C\uFF09`;
|
|
11617
|
+
if (hostVersion !== clientVersion) return `${STALE_HOST_MESSAGE}\uFF08\u754C\u9762\u4E3A ${clientVersion}\uFF0C\u540E\u53F0\u4E3A ${hostVersion}\uFF09`;
|
|
11618
|
+
return "";
|
|
11619
|
+
}
|
|
11620
|
+
|
|
11603
11621
|
// .dsh-plugin/shared/cli-terminal-protocol.mjs
|
|
11604
11622
|
var TERMINAL_TOOL_IDS = Object.freeze(["codex", "claude-code", "kimi-code", "minimax-code", "mimo-code", "grok-build", "gemini"]);
|
|
11605
11623
|
var TERMINAL_SHELL_ID = "shell";
|
|
@@ -11705,6 +11723,7 @@ function parseTerminalStop(value) {
|
|
|
11705
11723
|
const request = plainObject(value, "terminal stop request");
|
|
11706
11724
|
return { sessionId: sessionIdOf(request.sessionId) };
|
|
11707
11725
|
}
|
|
11726
|
+
var isTerminalSessionId = (value) => typeof value === "string" && SESSION_ID.test(value);
|
|
11708
11727
|
|
|
11709
11728
|
// .dsh-plugin/client/cli-terminal-state.mjs
|
|
11710
11729
|
var text3 = (value) => typeof value === "string" ? value.trim() : "";
|
|
@@ -11761,10 +11780,26 @@ var formatDuration = (ms2) => {
|
|
|
11761
11780
|
const minutes = Math.floor(seconds / 60);
|
|
11762
11781
|
return minutes < 60 ? `${minutes} \u5206 ${seconds % 60} \u79D2` : `${Math.floor(minutes / 60)} \u5C0F\u65F6 ${minutes % 60} \u5206`;
|
|
11763
11782
|
};
|
|
11764
|
-
|
|
11765
|
-
if (response?.ok)
|
|
11766
|
-
|
|
11767
|
-
|
|
11783
|
+
function unwrapTerminal(response, fallback) {
|
|
11784
|
+
if (!response?.ok) throw new Error(remoteErrorText(text3(response?.error?.message) || text3(response?.error), fallback));
|
|
11785
|
+
const inner = response.value;
|
|
11786
|
+
if (inner && typeof inner === "object" && typeof inner.ok === "boolean") {
|
|
11787
|
+
if (!inner.ok) throw new Error(remoteErrorText(text3(inner.error?.message) || text3(inner.error), fallback));
|
|
11788
|
+
return inner.value;
|
|
11789
|
+
}
|
|
11790
|
+
return inner;
|
|
11791
|
+
}
|
|
11792
|
+
var unwrap = unwrapTerminal;
|
|
11793
|
+
async function loadTerminalInfo(api) {
|
|
11794
|
+
const info = unwrap(await api.terminalInfo(), "\u65E0\u6CD5\u8BFB\u53D6\u7EC8\u7AEF\u72B6\u6001\u3002");
|
|
11795
|
+
if (!info || typeof info !== "object" || !["pty", "pipe"].includes(info.backend)) throw new Error("\u7EC8\u7AEF\u72B6\u6001\u683C\u5F0F\u65E0\u6548\u3002");
|
|
11796
|
+
return info;
|
|
11797
|
+
}
|
|
11798
|
+
async function startTerminal(api, request) {
|
|
11799
|
+
const session = unwrap(await api.terminalStart({ ...request, confirmed: true }), "\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\u3002");
|
|
11800
|
+
if (!isTerminalSessionId(session?.sessionId)) throw new Error("\u7EC8\u7AEF\u542F\u52A8\u7ED3\u679C\u7F3A\u5C11\u6709\u6548\u7684\u4F1A\u8BDD ID\u3002");
|
|
11801
|
+
return session;
|
|
11802
|
+
}
|
|
11768
11803
|
var TerminalConnection = class {
|
|
11769
11804
|
constructor({ api, sessionId, onData, onExit, onError, waitMs = 800, retryMs = 1e3, resizeDelayMs = 120, setTimer = setTimeout, clearTimer = clearTimeout }) {
|
|
11770
11805
|
Object.assign(this, { api, sessionId, onData, onExit, onError, waitMs, retryMs, resizeDelayMs });
|
|
@@ -11778,6 +11813,13 @@ var TerminalConnection = class {
|
|
|
11778
11813
|
this.failures = 0;
|
|
11779
11814
|
}
|
|
11780
11815
|
start() {
|
|
11816
|
+
if (!isTerminalSessionId(this.sessionId)) {
|
|
11817
|
+
this.closed = true;
|
|
11818
|
+
this.onError?.(new Error("\u7EC8\u7AEF\u4F1A\u8BDD ID \u65E0\u6548\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u8F93\u51FA\u3002"));
|
|
11819
|
+
this.onExit?.({ endReason: "lost", exitCode: null });
|
|
11820
|
+
this.loop = Promise.resolve();
|
|
11821
|
+
return this.loop;
|
|
11822
|
+
}
|
|
11781
11823
|
this.loop = this.readLoop();
|
|
11782
11824
|
return this.loop;
|
|
11783
11825
|
}
|
|
@@ -11831,7 +11873,9 @@ var TerminalConnection = class {
|
|
|
11831
11873
|
}
|
|
11832
11874
|
}
|
|
11833
11875
|
resize(cols, rows) {
|
|
11834
|
-
if (this.closed) return;
|
|
11876
|
+
if (this.closed || !Number.isFinite(cols) || !Number.isFinite(rows)) return;
|
|
11877
|
+
cols = Math.min(TERMINAL_LIMITS.maxCols, Math.max(TERMINAL_LIMITS.minCols, Math.round(cols)));
|
|
11878
|
+
rows = Math.min(TERMINAL_LIMITS.maxRows, Math.max(TERMINAL_LIMITS.minRows, Math.round(rows)));
|
|
11835
11879
|
if (this.resizeTimer) this.clearTimer(this.resizeTimer);
|
|
11836
11880
|
this.resizeTimer = this.setTimer(() => {
|
|
11837
11881
|
this.resizeTimer = null;
|
|
@@ -11856,7 +11900,7 @@ var TerminalConnection = class {
|
|
|
11856
11900
|
// .dsh-plugin/client/cli-terminal.jsx
|
|
11857
11901
|
var CWD_STORAGE_KEY = "model-router.terminal.cwd";
|
|
11858
11902
|
var text4 = (value) => typeof value === "string" ? value.trim() : "";
|
|
11859
|
-
var errorText = (error, fallback) => text4(error?.message)
|
|
11903
|
+
var errorText = (error, fallback) => remoteErrorText(text4(error?.message), fallback);
|
|
11860
11904
|
function storedCwd() {
|
|
11861
11905
|
try {
|
|
11862
11906
|
return globalThis.localStorage?.getItem(CWD_STORAGE_KEY) ?? "";
|
|
@@ -11957,10 +12001,9 @@ function CliTerminalCard({ api, health }) {
|
|
|
11957
12001
|
const mounted = import_react4.default.useRef(true);
|
|
11958
12002
|
const loadInfo = import_react4.default.useCallback(async () => {
|
|
11959
12003
|
try {
|
|
11960
|
-
const
|
|
12004
|
+
const value = await loadTerminalInfo(api);
|
|
11961
12005
|
if (!mounted.current) return;
|
|
11962
|
-
|
|
11963
|
-
setInfo({ status: "ready", value: response.value, error: "" });
|
|
12006
|
+
setInfo({ status: "ready", value, error: "" });
|
|
11964
12007
|
} catch (error) {
|
|
11965
12008
|
if (mounted.current) setInfo({ status: "error", value: null, error: errorText(error, "\u65E0\u6CD5\u8BFB\u53D6\u7EC8\u7AEF\u72B6\u6001\u3002") });
|
|
11966
12009
|
}
|
|
@@ -11989,15 +12032,14 @@ function CliTerminalCard({ api, health }) {
|
|
|
11989
12032
|
setStarting(true);
|
|
11990
12033
|
setStartError("");
|
|
11991
12034
|
try {
|
|
11992
|
-
const
|
|
11993
|
-
if (!response?.ok) throw new Error(text4(response?.error?.message) || "\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\u3002");
|
|
12035
|
+
const session = await startTerminal(api, { target: selected.id, mode, cwd: text4(cwd), cols: 100, rows: 30 });
|
|
11994
12036
|
storeCwd(text4(cwd));
|
|
11995
12037
|
if (!mounted.current) {
|
|
11996
|
-
void api.terminalStop({ sessionId:
|
|
12038
|
+
void api.terminalStop({ sessionId: session.sessionId });
|
|
11997
12039
|
return;
|
|
11998
12040
|
}
|
|
11999
|
-
setSessions((previous) => [...previous, { ...
|
|
12000
|
-
setActiveId(
|
|
12041
|
+
setSessions((previous) => [...previous, { ...session, ended: null }]);
|
|
12042
|
+
setActiveId(session.sessionId);
|
|
12001
12043
|
setConfirming(false);
|
|
12002
12044
|
} catch (error) {
|
|
12003
12045
|
if (mounted.current) setStartError(errorText(error, "\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\u3002"));
|
|
@@ -12046,7 +12088,7 @@ function CliTerminalCard({ api, health }) {
|
|
|
12046
12088
|
setConfirming(false);
|
|
12047
12089
|
}
|
|
12048
12090
|
}
|
|
12049
|
-
))), targets.length === 1 && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u4F53\u68C0\u5C1A\u672A\u53D1\u73B0\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI\uFF1B\u5B89\u88C5\u540E\u70B9\u201C\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0\u201D\u7684\u201C\u91CD\u65B0\u4F53\u68C0\u201D\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5BF9\u5E94\u9009\u9879\u3002"), !confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: Boolean(problem) || live >= maxSessions, onClick: () => {
|
|
12091
|
+
))), targets.length === 1 && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u4F53\u68C0\u5C1A\u672A\u53D1\u73B0\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI\uFF1B\u5B89\u88C5\u540E\u70B9\u201C\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0\u201D\u7684\u201C\u91CD\u65B0\u4F53\u68C0\u201D\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5BF9\u5E94\u9009\u9879\u3002"), !confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: Boolean(problem) || live >= maxSessions || info.status !== "ready", onClick: () => {
|
|
12050
12092
|
setStartError("");
|
|
12051
12093
|
setConfirming(true);
|
|
12052
12094
|
} }, "\u5F00\u59CB"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "mr-caption" }, problem || (live >= maxSessions ? `\u6700\u591A\u540C\u65F6\u8FD0\u884C ${maxSessions} \u4E2A\u4F1A\u8BDD\u3002` : `\u5C06\u8FD0\u884C\uFF1A${details.command}`))), confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-term-confirm", role: "dialog", "aria-label": "\u786E\u8BA4\u542F\u52A8\u7EC8\u7AEF" }, /* @__PURE__ */ import_react4.default.createElement("strong", null, details.title), /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u547D\u4EE4\uFF1A", /* @__PURE__ */ import_react4.default.createElement("code", null, details.command), /* @__PURE__ */ import_react4.default.createElement("br", null), "\u5DE5\u4F5C\u76EE\u5F55\uFF1A", /* @__PURE__ */ import_react4.default.createElement("code", null, details.cwd)), /* @__PURE__ */ import_react4.default.createElement("ul", null, details.points.map((point) => /* @__PURE__ */ import_react4.default.createElement("li", { key: point }, point))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: starting, onClick: () => {
|
|
@@ -12307,7 +12349,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
|
|
|
12307
12349
|
const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
|
|
12308
12350
|
const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
|
|
12309
12351
|
setProbeState({ status: "ready", probes, capabilities, readiness, error: "" });
|
|
12310
|
-
onProbes({ probes, capabilities, readiness });
|
|
12352
|
+
onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
|
|
12311
12353
|
} catch (error) {
|
|
12312
12354
|
if (!mounted.current || current !== request.current) return;
|
|
12313
12355
|
setProbeState({ status: "error", probes: [], capabilities: [], readiness: [], error: text5(error?.message) || "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002" });
|
|
@@ -12610,7 +12652,7 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
|
|
|
12610
12652
|
setPlanError(text5(error?.message) || "\u65E0\u6CD5\u751F\u6210\u8DEF\u7531\u5EFA\u8BAE\u3002");
|
|
12611
12653
|
}
|
|
12612
12654
|
};
|
|
12613
|
-
return /* @__PURE__ */ import_react5.default.createElement("main", { className: "mr-workspace" }, /* @__PURE__ */ import_react5.default.createElement("style", null, router_main_default), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-shell" }, /* @__PURE__ */ import_react5.default.createElement("header", { className: "mr-header" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-eyebrow" }, "Model Router \xB7 DeepSeek Harness"), /* @__PURE__ */ import_react5.default.createElement("h1", { className: "mr-title" }, "\u6A21\u578B\u8DEF\u7531\u5DE5\u4F5C\u53F0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-subtitle" }, "\u67E5\u770B\u5B98\u65B9\u6A21\u578B\u76EE\u5F55\uFF0C\u4E3A\u4EFB\u52A1\u751F\u6210\u8DEF\u7EBF\u5EFA\u8BAE\u4E0E\u56E2\u961F\u5DE5\u4F5C\u5305\u3002\u4E3B\u4F1A\u8BDD\u6A21\u578B\u4ECD\u7531\u5B98\u65B9\u9009\u62E9\u5668\u7BA1\u7406\u3002")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-status-dot ${catalogState.status === "loading" ? "loading" : catalogState.status === "error" ? "error" : ""}` }), catalogState.status === "ready" ? `${providerCount} \u4E2A\u4F9B\u5E94\u5546 \xB7 ${routes.length} \u6761\u8DEF\u7EBF` : catalogState.status === "loading" ? "\u6B63\u5728\u8BFB\u53D6\u6A21\u578B\u76EE\u5F55" : "\u6A21\u578B\u76EE\u5F55\u8BFB\u53D6\u5931\u8D25")), (workbench.health.report?.notices ?? []).map((notice) => /* @__PURE__ */ import_react5.default.createElement("div", { key: `${notice.kind}-${notice.at}`, className: "mr-error", role: "alert" }, notice.message)), workbench.health.report && !workbench.health.report.onboarding?.completedAt && /* @__PURE__ */ import_react5.default.createElement(
|
|
12655
|
+
return /* @__PURE__ */ import_react5.default.createElement("main", { className: "mr-workspace" }, /* @__PURE__ */ import_react5.default.createElement("style", null, router_main_default), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-shell" }, /* @__PURE__ */ import_react5.default.createElement("header", { className: "mr-header" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-eyebrow" }, "Model Router \xB7 DeepSeek Harness"), /* @__PURE__ */ import_react5.default.createElement("h1", { className: "mr-title" }, "\u6A21\u578B\u8DEF\u7531\u5DE5\u4F5C\u53F0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-subtitle" }, "\u67E5\u770B\u5B98\u65B9\u6A21\u578B\u76EE\u5F55\uFF0C\u4E3A\u4EFB\u52A1\u751F\u6210\u8DEF\u7EBF\u5EFA\u8BAE\u4E0E\u56E2\u961F\u5DE5\u4F5C\u5305\u3002\u4E3B\u4F1A\u8BDD\u6A21\u578B\u4ECD\u7531\u5B98\u65B9\u9009\u62E9\u5668\u7BA1\u7406\u3002")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-status-dot ${catalogState.status === "loading" ? "loading" : catalogState.status === "error" ? "error" : ""}` }), catalogState.status === "ready" ? `${providerCount} \u4E2A\u4F9B\u5E94\u5546 \xB7 ${routes.length} \u6761\u8DEF\u7EBF` : catalogState.status === "loading" ? "\u6B63\u5728\u8BFB\u53D6\u6A21\u578B\u76EE\u5F55" : "\u6A21\u578B\u76EE\u5F55\u8BFB\u53D6\u5931\u8D25")), staleHostNotice({ hostVersion: toolProbes?.hostVersion, loaded: Boolean(toolProbes?.probes?.length) }) && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-error", role: "alert" }, staleHostNotice({ hostVersion: toolProbes?.hostVersion })), (workbench.health.report?.notices ?? []).map((notice) => /* @__PURE__ */ import_react5.default.createElement("div", { key: `${notice.kind}-${notice.at}`, className: "mr-error", role: "alert" }, notice.message)), workbench.health.report && !workbench.health.report.onboarding?.completedAt && /* @__PURE__ */ import_react5.default.createElement(
|
|
12614
12656
|
OnboardingBanner,
|
|
12615
12657
|
{
|
|
12616
12658
|
health: workbench.health.report,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Host receiver for the official desktop's typed one-click installer RPC. */
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
2
3
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
3
4
|
import { getOfficialTool } from './shared/official-tool-registry.mjs'
|
|
4
5
|
import { officialToolExecutionCapabilities, officialToolReadiness } from './shared/official-tool-executor.mjs'
|
|
@@ -16,6 +17,12 @@ import {
|
|
|
16
17
|
OFFICIAL_TOOLS_REMOTE_NAMESPACE,
|
|
17
18
|
} from './shared/official-tools-remote.mjs'
|
|
18
19
|
|
|
20
|
+
/** Version of the plugin code this Host process actually imported. */
|
|
21
|
+
export const HOST_PLUGIN_VERSION = (() => {
|
|
22
|
+
try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ?? null }
|
|
23
|
+
catch { return null }
|
|
24
|
+
})()
|
|
25
|
+
|
|
19
26
|
function errorText(error) {
|
|
20
27
|
return error instanceof Error ? error.message : String(error)
|
|
21
28
|
}
|
|
@@ -77,7 +84,7 @@ export class OfficialToolsRemoteService extends TypertRemoteService {
|
|
|
77
84
|
const executionReadiness = await Promise.all(tools.map(tool => tool.installed
|
|
78
85
|
? officialToolReadiness(tool.id)
|
|
79
86
|
: Promise.resolve({ id: tool.id, ready: false, reason: 'CLI 尚未安装或版本检测失败。' })))
|
|
80
|
-
return { tools, executionCapabilities: officialToolExecutionCapabilities(), executionReadiness }
|
|
87
|
+
return { tools, executionCapabilities: officialToolExecutionCapabilities(), executionReadiness, hostVersion: HOST_PLUGIN_VERSION }
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/** Start one serialized fixed-registry install; return immediately for UI polling. */
|
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.14.0-beta.
|
|
3
|
+
## 0.14.0-beta.3 — 2026-10-03
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Prerelease under the npm `next` tag only (`latest` stays 0.13.3). **Quit Harness completely (including the tray icon) and start it again after updating**, or the Host keeps running the 0.14.0-beta.2 code.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- 官方工具终端: a session (e.g. Kimi Code 登录) opened a black terminal and then failed with `typert gateway: modelRouterOfficialTools/terminalRead: wire field "request" failed boundary validation`; the footer read `undefined · undefined`. The Host methods answer with their own `{ ok, value }` envelope and the Typert gateway wraps that again, so the client receives `{ ok: true, value: { ok: true, value: result } }`. The terminal card read only the outer level, so the session id, command and directory were `undefined` and every `terminalRead` carried no `sessionId`, which the strict request codec rejects at the gateway. Host-side start failures (such as a missing CLI) were hidden the same way. The card now unwraps both levels (`unwrapTerminal`, `loadTerminalInfo`, `startTerminal`) and reports inner errors. The schemas were correct, and the gateway does not strip response fields.
|
|
9
|
+
- The client never sends a read or write for an invalid session id, and it clamps resize requests from a collapsed panel into the accepted 10–500 × 3–300 range instead of failing validation.
|
|
10
|
+
|
|
11
|
+
### Tests
|
|
12
|
+
- `tests/helpers/typert-gateway.mjs` copies Harness's gateway boundary (`assertExactArguments`, `decode`, `assertJsonValue`, `encodeRpcResult`, JSON wire). `tests/cli-terminal-gateway.test.mjs` sends the card's real payloads for all six terminal methods through it, through the real `OfficialToolsRemoteService` envelope and the real Host services.
|
|
13
|
+
|
|
14
|
+
## 0.14.0-beta.2 — 2026-10-03
|
|
15
|
+
|
|
16
|
+
Prerelease under the npm `next` tag only (`latest` stays 0.13.3).
|
|
17
|
+
|
|
18
|
+
**After installing or updating, quit Harness completely (including the tray icon) and start it again.** Harness serves the new workbench UI immediately, but the running Host keeps the plugin code it already imported, so new Host methods are missing until a full restart.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- With 0.14.0-beta.1 installed into a running Harness, the 官方工具终端 card showed `transport failure for /api/modelRouterOfficialTools/terminalInfo: HTTP 404`. The Harness gateway only routes `/api/<namespace>/<method>` for descriptors the Host registered when it applied the plugin; the Host was still running the previous version's code. Nothing in the plugin's routes was wrong. The card now shows "插件后台版本较旧,请完全退出并重启 Harness(包括托盘图标)后再使用。" for a missing method and disables 开始.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `list()` reports `hostVersion`, the plugin version the Host actually loaded; the client embeds its own version at build time. When they differ (or the Host is too old to report one), the workbench shows the same restart advice at the top.
|
|
25
|
+
|
|
26
|
+
## 0.14.0-beta.1 — 2026-10-03
|
|
27
|
+
|
|
28
|
+
Prerelease under the npm `next` tag (branch `feat/cli-terminal`).
|
|
6
29
|
|
|
7
30
|
### Added
|
|
8
31
|
- **官方工具终端** workbench card: interactive sessions of the system shell (PowerShell on Windows) or an installed official CLI (codex, claude, kimi, mcode, mimo, grok, gemini), in tabs, rendered with xterm.js 6 (bundled into `client.js`). Each CLI also has a fixed **登录** mode (`codex login`, `claude auth login`, …). Sessions start only after a confirmation showing the command, the absolute working directory, and that the terminal runs outside the Harness process sandbox with the user's own CLI login and environment.
|
package/README.md
CHANGED
|
@@ -402,7 +402,7 @@ Claude Code, Codex, MiMo Code, and Grok Build support read-only and approved edi
|
|
|
402
402
|
|
|
403
403
|
The planned Harness model ID is not necessarily the vendor CLI's model name. A saved `cliModel`, or temporary `cliModelsJson` keyed by tool or work-package ID, can provide a known vendor CLI name. A package-specific temporary mapping takes priority over a tool mapping, then the saved profile. ZCode uses its configured default model. Most vendor CLIs do not provide a verifiable actual model ID in their results, so inspect vendor run records to confirm which model and price applied. The plugin's sequential CLI team runner is separate from Harness's built-in Agent Teams lifecycle.
|
|
404
404
|
|
|
405
|
-
### Interactive terminal (官方工具终端, 0.14.0-beta.
|
|
405
|
+
### Interactive terminal (官方工具终端, 0.14.0-beta.3, npm `next`)
|
|
406
406
|
|
|
407
407
|
The workbench has an **官方工具终端** card below the tool health check. It runs your system shell (PowerShell on Windows, your login `$SHELL` elsewhere) or one installed official CLI (`codex`, `claude`, `kimi`, `mcode`, `mimo`, `grok`, `gemini`) interactively, in tabs, with live output: multi-turn conversations, full-screen TUIs, and logins such as `codex login`, `claude auth login`, `kimi login`, `mcode login`, `mimo auth login`, `grok login` (choose **登录**).
|
|
408
408
|
|
|
@@ -412,6 +412,7 @@ The workbench has an **官方工具终端** card below the tool health check. It
|
|
|
412
412
|
- **Transport.** Output streams through long-poll reads over the plugin's existing Typert remote (`terminalRead`); keystrokes (`terminalWrite`) and resizes (`terminalResize`) are separate calls.
|
|
413
413
|
- **Lifetime.** Ending the tab, closing the workbench panel, unloading or reloading the plugin, or quitting Harness kills the process. A session that nobody reads for about 2 minutes is killed, and no session runs longer than 6 hours. At most 4 sessions run at once.
|
|
414
414
|
- **Privacy.** Input and output are never logged or saved. The card's history (and `state.json` → `terminalSessions`) stores only the tool, mode, working directory, start/end time, duration, exit code, and end reason.
|
|
415
|
+
- **Restart after installing.** Harness serves the new card immediately, but a running Host keeps the old plugin code; until you quit Harness completely (including the tray icon) and start it again, the card shows “插件后台版本较旧,请完全退出并重启 Harness”.
|
|
415
416
|
- **Keys.** With text selected, Ctrl+C copies; otherwise it interrupts. Ctrl+Shift+V pastes. Drag the terminal's lower-right corner to change its height.
|
|
416
417
|
|
|
417
418
|
## Optional GAL installation
|
package/README.zh.md
CHANGED
|
@@ -391,7 +391,7 @@ Claude 与 Codex 若本机已有经核验的签名入口,仍优先走原有沙
|
|
|
391
391
|
|
|
392
392
|
Harness 目录中的模型 ID 未必是厂商 CLI 接受的名字。逐模型设置可填写 `cliModel`;团队执行时临时的“工作包映射 > 工具映射 > 保存映射”。ZCode 3.14.3 不能逐次切换模型。多数 CLI 不回报可核验的实际模型 ID,执行后要对照厂商运行记录、权限和账单。Windows Harness 沙箱的 ACL 文件效果报告为部分隔离,涉及敏感仓库时应先用测试环境验证。
|
|
393
393
|
|
|
394
|
-
### 官方工具终端(0.14.0-beta.
|
|
394
|
+
### 官方工具终端(0.14.0-beta.3,npm `next`)
|
|
395
395
|
|
|
396
396
|
工作台“官方工具 · 体检”下方新增 **官方工具终端** 卡片:以标签页形式交互运行系统终端(Windows 为 PowerShell,其他系统为登录 `$SHELL`)或已安装的官方 CLI(`codex`、`claude`、`kimi`、`mcode`、`mimo`、`grok`、`gemini`),实时输出,可多轮对话、显示全屏界面,也可选 **登录** 直接运行 `codex login`、`claude auth login`、`kimi login`、`mcode login`、`mimo auth login`、`grok login`。
|
|
397
397
|
|
|
@@ -401,6 +401,7 @@ Harness 目录中的模型 ID 未必是厂商 CLI 接受的名字。逐模型设
|
|
|
401
401
|
- **传输**:输出通过插件现有 Typert 远程调用的长轮询读取(`terminalRead`)实时推送;按键(`terminalWrite`)和窗口大小(`terminalResize`)分别发送。
|
|
402
402
|
- **生命周期**:结束标签、关闭工作台面板、卸载或重新加载插件、退出 Harness 都会结束进程;约 2 分钟无人读取输出的会话会被结束;单个会话最长 6 小时;最多同时 4 个。
|
|
403
403
|
- **隐私**:输入和输出不会写入日志或保存。卡片中的历史(`state.json` 的 `terminalSessions`)只记录工具、方式、工作目录、开始/结束时间、时长、退出码和结束原因。
|
|
404
|
+
- **安装后需重启**:Harness 会立即加载新界面,但运行中的后台仍是旧插件代码;完全退出 Harness(包括托盘图标)并重新启动前,卡片会提示“插件后台版本较旧,请完全退出并重启 Harness”。
|
|
404
405
|
- **按键**:选中文字时 Ctrl+C 复制,否则发送中断;Ctrl+Shift+V 粘贴;拖动终端右下角调整高度。
|
|
405
406
|
|
|
406
407
|
## 可选安装 GAL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ljwei-stak/dsh-model-router",
|
|
3
|
-
"version": "0.14.0-beta.
|
|
3
|
+
"version": "0.14.0-beta.3",
|
|
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": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build:client": "node scripts/build-client.mjs",
|
|
81
81
|
"check:client": "node scripts/build-client.mjs --check",
|
|
82
|
-
"test": "node --test tests/router.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/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",
|
|
82
|
+
"test": "node --test tests/router.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/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",
|
|
83
83
|
"prepack": "node scripts/prepack.mjs",
|
|
84
84
|
"pack:local": "npm pack --pack-destination dist"
|
|
85
85
|
},
|