chore: release v4.0.1

This commit is contained in:
xmanrui 2026-08-30 07:02:50 +08:00
parent 8794a46e81
commit 92ec91b7bd
7 changed files with 102 additions and 10 deletions

View file

@ -6,6 +6,13 @@ This file records the notable changes in each dsh-im release. Its format follows
## [Unreleased]
## [4.0.1] - 2026-08-30
### Fixed / 修复
- 新版 DSH 中,机器人卡片的工作区目录选择器现在使用 `uiWorkspace` 目录服务,不再调用已从 Workspace Controller 移除的 `workspaces.listDirectory` / `pickDirectory`;旧版 Host 仍保留原接口回退。
On current DSH releases, the bot-card workspace picker now uses the `uiWorkspace` directory service instead of the removed Workspace Controller `workspaces.listDirectory` / `pickDirectory` methods, while retaining the legacy Host fallback.
## [4.0.0] - 2026-08-29
### Fixed / 修复
@ -468,7 +475,8 @@ This file records the notable changes in each dsh-im release. Its format follows
- 改进 npm 发布包结构,保留 CLI 入口并避免安装脚本拦截。
Improved npm package contents to preserve the CLI entry point and avoid install-script blocking.
[Unreleased]: https://github.com/xmanrui/dsh-im/compare/v4.0.0...HEAD
[Unreleased]: https://github.com/xmanrui/dsh-im/compare/v4.0.1...HEAD
[4.0.1]: https://github.com/xmanrui/dsh-im/compare/v4.0.0...v4.0.1
[4.0.0]: https://github.com/xmanrui/dsh-im/compare/v3.2.0...v4.0.0
[3.2.0]: https://github.com/xmanrui/dsh-im/compare/v3.1.1...v3.2.0
[3.1.1]: https://github.com/xmanrui/dsh-im/compare/v3.1.0...v3.1.1

View file

@ -571,7 +571,7 @@ var React22 = __toESM(require("react"), 1);
// package.json
var package_default = {
name: "@xmanrui/dsh-im",
version: "4.0.0",
version: "4.0.1",
description: "\u628A\u4E5D\u79CD IM \u673A\u5668\u4EBA\u548C\u516C\u7F51 AI Office \u63A5\u5165\u672C\u673A DeepSeek Harness\u3002 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
keywords: [
"deepseek-harness",
@ -12487,6 +12487,14 @@ function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
var name = "im-settings";
var inject = ["slots", "connection", "locale", "workspaces"];
var IM_PLUGIN_VERSION = package_default.version;
function callWorkspaceDirectoryApi(ctx, method, ...args) {
const uiWorkspace = typeof ctx.get === "function" ? ctx.get("uiWorkspace") : void 0;
const service = typeof uiWorkspace?.[method] === "function" ? uiWorkspace : ctx.workspaces;
if (typeof service?.[method] !== "function") {
throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF0C\u8BF7\u91CD\u8BD5\u3002");
}
return service[method](...args);
}
var CHANNELS = Object.freeze([
{ id: "weixin", label: "\u5FAE\u4FE1" },
{ id: "feishu", label: "\u98DE\u4E66" },
@ -12785,8 +12793,8 @@ function apply(ctx) {
const officeRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(OFFICE_RPC_CHANNEL, endpoint, payload, signal);
const updateRpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(UPDATE_RPC_CHANNEL, endpoint, payload, signal);
const workspaceDirectoryPicker = Object.freeze({
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal),
pickDirectory: () => ctx.workspaces.pickDirectory()
listDirectory: (path, signal) => callWorkspaceDirectoryApi(ctx, "listDirectory", path, signal),
pickDirectory: () => callWorkspaceDirectoryApi(ctx, "pickDirectory")
});
ctx.slots.inject("settings.section", () => ctx.slots.register({
name: "settings.section",

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@xmanrui/dsh-im",
"version": "4.0.0",
"version": "4.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@xmanrui/dsh-im",
"version": "4.0.0",
"version": "4.0.1",
"license": "MIT",
"dependencies": {
"@tencent-connect/qqbot-connector": "1.2.0",

View file

@ -1,6 +1,6 @@
{
"name": "@xmanrui/dsh-im",
"version": "4.0.0",
"version": "4.0.1",
"description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
"keywords": [
"deepseek-harness",

View file

@ -55,6 +55,16 @@ export const name = 'im-settings';
export const inject = ['slots', 'connection', 'locale', 'workspaces'];
export const IM_PLUGIN_VERSION = manifest.version;
function callWorkspaceDirectoryApi(ctx, method, ...args) {
// Current DSH owns directory operations on uiWorkspace; legacy Hosts keep them on workspaces.
const uiWorkspace = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : undefined;
const service = typeof uiWorkspace?.[method] === 'function' ? uiWorkspace : ctx.workspaces;
if (typeof service?.[method] !== 'function') {
throw new Error('无法读取目录,请重试。');
}
return service[method](...args);
}
const CHANNELS = Object.freeze([
{ id: 'weixin', label: '微信' },
{ id: 'feishu', label: '飞书' },
@ -335,8 +345,9 @@ export function apply(ctx) {
const updateRpcCall = (endpoint, payload, signal) =>
ctx.connection.rpc.call(UPDATE_RPC_CHANNEL, endpoint, payload, signal);
const workspaceDirectoryPicker = Object.freeze({
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal),
pickDirectory: () => ctx.workspaces.pickDirectory(),
listDirectory: (path, signal) =>
callWorkspaceDirectoryApi(ctx, 'listDirectory', path, signal),
pickDirectory: () => callWorkspaceDirectoryApi(ctx, 'pickDirectory'),
});
ctx.slots.inject('settings.section', () => ctx.slots.register({

View file

@ -792,6 +792,71 @@ test('client registers one top-level bilingual IM settings section with a direct
}
});
test('client directory picker uses the current DSH uiWorkspace service', async () => {
const registrations = [];
const directoryCalls = [];
let uiWorkspace;
const ctx = {
effect() {},
get(name) {
assert.equal(name, 'uiWorkspace');
return uiWorkspace;
},
locale: {
bind: () => (key) => key,
register: () => () => {},
},
connection: { rpc: { call: async () => ({ ok: true, value: {} }) } },
workspaces: {
list: {
getSnapshot: () => ({ items: [] }),
subscribe: () => () => {},
},
},
slots: {
inject(name, install) {
assert.equal(name, 'settings.section');
install();
},
register(options, component) {
registrations.push({ options, component });
return () => {};
},
},
};
try {
applyClient(ctx);
uiWorkspace = {
async listDirectory(path, signal) {
directoryCalls.push({ operation: 'list', path, signal });
return { path, entries: [] };
},
async pickDirectory() {
directoryCalls.push({ operation: 'pick' });
return '/workspace/current-host';
},
};
const injected = registrations[0].options.inject();
const signal = new AbortController().signal;
assert.deepEqual(
await injected.workspaceDirectoryPicker.listDirectory('/workspace/current', signal),
{ path: '/workspace/current', entries: [] },
);
assert.equal(
await injected.workspaceDirectoryPicker.pickDirectory(),
'/workspace/current-host',
);
assert.deepEqual(directoryCalls, [
{ operation: 'list', path: '/workspace/current', signal },
{ operation: 'pick' },
]);
} finally {
setImTranslator(null);
}
});
test('all nine channel settings and connected cards render English copy', () => {
const rpcCall = async () => ({ ok: true, value: {} });
const noop = () => {};