@tanweilabs/daiti 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/THIRD_PARTY_NOTICES.md +3 -1
- package/artifacts/manifest.json +15 -9
- package/artifacts/tanweilabs-daiti-im-0.0.2-16f94aa853b3.tgz +0 -0
- package/artifacts/tanweilabs-daiti-loading-0.0.2-fc449733ecaa.tgz +0 -0
- package/artifacts/tanweilabs-daiti-preferences-0.0.2-a0b22ddd8594.tgz +0 -0
- package/artifacts/tanweilabs-daiti-ui-0.0.2-3c9ee486f937.tgz +0 -0
- package/docs/validation.md +12 -0
- package/lib/cli.mjs +15 -6
- package/lib/ports.mjs +23 -0
- package/lib/supervisor.mjs +6 -2
- package/npm-shrinkwrap.json +1691 -8
- package/package.json +9 -3
- package/artifacts/tanweilabs-daiti-loading-0.0.1.tgz +0 -0
- package/artifacts/tanweilabs-daiti-preferences-0.0.1.tgz +0 -0
- package/artifacts/tanweilabs-daiti-ui-0.0.1.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# 代体
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
在你的电脑上运行,通过网页或微信使用的个人助理。基于 DeepSeek Harness 和 DSH-IM,通过独立插件提供单主会话、助理设置和首次使用流程。
|
|
4
4
|
|
|
5
|
-
**0.0.
|
|
5
|
+
**0.0.2 为开发者预览版。** 本包不包含个人记忆、历史会话、密钥或 IM 登录数据。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
需要 Node.js `^22.19.0` 或 `>=24.0.0`:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install -g @tanweilabs/daiti@0.0.
|
|
12
|
+
npm install -g @tanweilabs/daiti@0.0.2
|
|
13
13
|
daiti start
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -36,7 +36,7 @@ node bin/daiti.mjs stop
|
|
|
36
36
|
node bin/daiti.mjs start --port 3086 --no-open
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
默认端口 3086
|
|
39
|
+
默认端口 3086,只监听本机;默认端口被占用时自动选择空闲端口,并打开实际地址。同一 DAITI_HOME 已有服务时直接复用;显式指定 `--port` 时不会自动换端口,占用则提示错误。可通过 `daiti status` 查看实际端口。关闭浏览器后后台服务继续运行;停止服务或电脑休眠、关机时不能保证 IM 响应。停止前请等待正在执行的任务完成。当前没有开机自启、系统服务或自动升级。
|
|
40
40
|
|
|
41
41
|
## 数据在哪里
|
|
42
42
|
|
|
@@ -55,11 +55,15 @@ DAITI_HOME="$HOME/.daiti-preview" node bin/daiti.mjs stop
|
|
|
55
55
|
|
|
56
56
|
PowerShell 对应设置 `$env:DAITI_HOME = "$HOME\.daiti-preview"`。测试时选择新的工作文件夹;不要在两个运行实例中连接同一个 IM 机器人。
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## 微信连接
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
首版仅支持微信,在“设置 → 助理 → 微信助理”中扫码。连接启动前,代体通过上游自身的绑定方法把扫码账号本人的私聊固定到网页主会话。缺少主会话或目录校验失败时连接报错,不创建替代会话;工作目录和模型由助理统一管理。群聊和非本人消息不接入个人主会话。
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
既有错误绑定可在重连时纠正,旧会话历史保留。绑定跨工作区时,上游可能重置该机器人其他会话关联,迁移前需备份。`/new`、`/session` 等切换或配置命令在单助理模式下不可用。
|
|
63
|
+
|
|
64
|
+
适配包固定 DSH-IM 4.21.0,构建时复用其源码和运行时构造扩展点;不修改原包文件,也不修改 Harness 核心。这些扩展点不是稳定公开 API,上游升级需要契约测试。原许可证保存在适配包中。
|
|
65
|
+
|
|
66
|
+
0.0.2 已验证主会话绑定与连接健康状态,微信与网页共用会话已由用户实际验收。
|
|
63
67
|
|
|
64
68
|
## 开发和本地打包
|
|
65
69
|
|
|
@@ -69,9 +73,9 @@ npm run build
|
|
|
69
73
|
npm run pack:local
|
|
70
74
|
```
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
构建将四个插件打包到 `artifacts/`,生成文件名和 SHA-256 清单。`pack:local` 在 `.local/releases/` 生成 CLI 安装包;包含运行入口、插件包和文档,不包含仓库工作目录、上游源码、个人配置或测试数据。
|
|
73
77
|
|
|
74
|
-
未发布的本地安装包可用 `npm install -g /path/to/tanweilabs-daiti-0.0.
|
|
78
|
+
未发布的本地安装包可用 `npm install -g /path/to/tanweilabs-daiti-0.0.2.tgz` 安装,随后使用 `daiti start`。安装仍需联网获取声明的运行依赖。
|
|
75
79
|
|
|
76
80
|
- `bin/`、`lib/`:CLI 和后台进程管理。
|
|
77
81
|
- `packages/assistant-ui/`:界面、首次设置和助理配置。
|
|
@@ -83,9 +87,9 @@ npm run pack:local
|
|
|
83
87
|
|
|
84
88
|
## 版本和限制
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
当前本地代码固定 Harness `0.1.6-alpha.2`、DSH-IM `4.21.0`。部分界面调整依赖这个版本的插槽、DOM 结构和中文标签;上游升级必须先验收,不能承诺跨版本无改动兼容。
|
|
87
91
|
|
|
88
|
-
完整验证记录见 [docs/validation.md](docs/validation.md)。Windows/Linux
|
|
92
|
+
完整验证记录见 [docs/validation.md](docs/validation.md)。Windows/Linux 尚需真实机器安装验证;微信跨端会话已在本机验收,其他平台不在本版支持范围内。升级机制、异常断电恢复和工作目录迁移仍待完善。
|
|
89
93
|
|
|
90
94
|
## 许可证
|
|
91
95
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -6,4 +6,6 @@
|
|
|
6
6
|
- [DSH-IM](https://github.com/xmanrui/dsh-im), 4.21.0, MIT. Installed as a separate, unmodified dependency with its original license.
|
|
7
7
|
- pnpm, 11.19.0, MIT; esbuild, 0.25.12, MIT (build-time).
|
|
8
8
|
|
|
9
|
-
The executable package declares the runtime dependencies; setup installs the
|
|
9
|
+
The executable package declares the runtime dependencies; setup installs the four local plugin archives and pinned DSH-IM into a separate data directory. No personal memory, session history, model credential or IM login data belongs in this repository or its release artifacts.
|
|
10
|
+
|
|
11
|
+
The local single-session WeChat fix bundles selected DSH-IM source through its version-pinned runtime constructor seams. The upstream package files are not edited. Its MIT notice is retained as UPSTREAM-LICENSE in the adapter and assistant UI packages.
|
package/artifacts/manifest.json
CHANGED
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
"plugins": [
|
|
3
3
|
{
|
|
4
4
|
"name": "@tanweilabs/daiti-ui",
|
|
5
|
-
"version": "0.0.
|
|
6
|
-
"file": "tanweilabs-daiti-ui-0.0.
|
|
7
|
-
"sha256": "
|
|
5
|
+
"version": "0.0.2",
|
|
6
|
+
"file": "tanweilabs-daiti-ui-0.0.2-3c9ee486f937.tgz",
|
|
7
|
+
"sha256": "3c9ee486f937aeed2250f013cd53c87d601dc4a102945da9e25cafa97a1c5562"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"name": "@tanweilabs/daiti-preferences",
|
|
11
|
-
"version": "0.0.
|
|
12
|
-
"file": "tanweilabs-daiti-preferences-0.0.
|
|
13
|
-
"sha256": "
|
|
11
|
+
"version": "0.0.2",
|
|
12
|
+
"file": "tanweilabs-daiti-preferences-0.0.2-a0b22ddd8594.tgz",
|
|
13
|
+
"sha256": "a0b22ddd85949e4e98054be8570bdd6d51661cde5ba33df9e36872c6105f4c2c"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"name": "@tanweilabs/daiti-loading",
|
|
17
|
-
"version": "0.0.
|
|
18
|
-
"file": "tanweilabs-daiti-loading-0.0.
|
|
19
|
-
"sha256": "
|
|
17
|
+
"version": "0.0.2",
|
|
18
|
+
"file": "tanweilabs-daiti-loading-0.0.2-fc449733ecaa.tgz",
|
|
19
|
+
"sha256": "fc449733ecaafe34452c9544af7cf33a8ed7b6d407b2a5e083f2048b7c5b78b7"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "@tanweilabs/daiti-im",
|
|
23
|
+
"version": "0.0.2",
|
|
24
|
+
"file": "tanweilabs-daiti-im-0.0.2-16f94aa853b3.tgz",
|
|
25
|
+
"sha256": "16f94aa853b344ff74f93517049e40d95713408a096b4fb60de814637b78256c"
|
|
20
26
|
}
|
|
21
27
|
]
|
|
22
28
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/validation.md
CHANGED
|
@@ -22,3 +22,15 @@
|
|
|
22
22
|
- UI适配依赖当前Harness版本的插槽及部分DOM结构,升级前需重复浏览器验收。
|
|
23
23
|
|
|
24
24
|
现有个人代体与历史测试实例不属于本次验收数据,未被迁移、重置或停止。公开发布仓库或安装包仍需单独确认。
|
|
25
|
+
|
|
26
|
+
## 微信单主会话本地修复
|
|
27
|
+
|
|
28
|
+
18项自动测试通过,其中9项为IM路由、本人私聊限制、缺主会话拒绝、目录同一性及真实上游 ask 路径。隔离 Host probe 已通过实际 modernHarnessApi、HarnessClient 和 BotWorkspaceStore 的绑定及持久化回读。真实账号启动时发现大小写不同但指向同目录的路径误判,改为文件系统身份比较并增加回归测试。修复后账号显示1/1在线,状态healthy且error为空,绑定ID与网页主ID一致;旧独立微信会话文件哈希仍与备份一致。未主动发送真实微信消息;跨端模型回复待用户验证。本地修复未发布npm。
|
|
29
|
+
|
|
30
|
+
## 2026-09-21 自动端口选择(待随 0.0.2 发布)
|
|
31
|
+
|
|
32
|
+
- 默认启动先尝试 3086;被占用时由操作系统分配空闲本机端口,实际端口用于就绪校验、状态查询和浏览器地址。显式 `--port` 冲突时明确报错。
|
|
33
|
+
- 同一 DAITI_HOME 的已认证服务优先复用,不因端口参数重复启动。
|
|
34
|
+
- `npm test` 20 项通过,`npm run build` 通过。
|
|
35
|
+
- 独立 `.local/acceptance` 实例真实启动:3086 已占用时选择 55957,重复启动保持同一实例;停止后显式 3086 报错且未创建服务;再次默认启动选择 56049,主会话 ID 不变;最后测试服务已停止,个人 3086 服务仍就绪。
|
|
36
|
+
- 端口探测与 Harness 绑定之间存在很短的竞争窗口;本次没有覆盖其他进程恰在该窗口抢占新端口的情形。
|
package/lib/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import { selectPort } from './ports.mjs';
|
|
8
9
|
export const root = fileURLToPath(new URL('../', import.meta.url));
|
|
9
10
|
export const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
10
11
|
export function paths(env = process.env) {
|
|
@@ -18,14 +19,14 @@ export async function atomicJson(file, value) {
|
|
|
18
19
|
await fs.rename(temp, file);
|
|
19
20
|
}
|
|
20
21
|
export function parseArgs(args) {
|
|
21
|
-
const command = args[0] || 'help'; let port = 3086, open = true;
|
|
22
|
+
const command = args[0] || 'help'; let port = 3086, open = true, portExplicit = false;
|
|
22
23
|
for (let i = 1; i < args.length; i++) {
|
|
23
24
|
if (args[i] === '--no-open') open = false;
|
|
24
|
-
else if (args[i] === '--port') { const value = args[++i]; if (!/^\d+$/.test(value || '') || +value < 1024 || +value > 65535) throw new Error('--port must be between 1024 and 65535'); port = +value; }
|
|
25
|
+
else if (args[i] === '--port') { const value = args[++i]; if (!/^\d+$/.test(value || '') || +value < 1024 || +value > 65535) throw new Error('--port must be between 1024 and 65535'); port = +value; portExplicit = true; }
|
|
25
26
|
else throw new Error(`Unknown option: ${args[i]}`);
|
|
26
27
|
}
|
|
27
28
|
if (!['setup','start','stop','status','open','help','--help','-h'].includes(command)) throw new Error(`Unknown command: ${command}`);
|
|
28
|
-
return { command, port, open };
|
|
29
|
+
return { command, port, open, portExplicit };
|
|
29
30
|
}
|
|
30
31
|
export function runtimeBin() {
|
|
31
32
|
const require = createRequire(path.join(root, 'package.json'));
|
|
@@ -72,15 +73,21 @@ export async function setup(p) {
|
|
|
72
73
|
await fs.copyFile(path.resolve(root, 'artifacts', plugin.file), destination);
|
|
73
74
|
return destination;
|
|
74
75
|
}));
|
|
75
|
-
await run(['plugin', '--profile', 'web', 'add', '@xmanrui/dsh-im@4.21.0', ...packages], p);
|
|
76
|
+
await run(['plugin', '--profile', 'web', 'add', '--ignore-scripts', '@xmanrui/dsh-im@4.21.0', ...packages], p);
|
|
76
77
|
const profile = path.join(p.runtime, 'profiles/web');
|
|
78
|
+
const profileManifest=path.join(profile,'package.json');
|
|
79
|
+
const profileData=await readJson(profileManifest);
|
|
80
|
+
const bundles=profileData.dsh.profile.bundles;
|
|
81
|
+
for(const plugin of manifest.plugins)if(!bundles.includes(plugin.name))bundles.push(plugin.name);
|
|
82
|
+
await atomicJson(profileManifest,profileData);
|
|
77
83
|
const patch = path.join(profile, 'cordis.patch.yml');
|
|
78
84
|
try { await fs.copyFile(patch, `${patch}.before-daiti-${Date.now()}`); } catch (e) { if (e.code !== 'ENOENT') throw e; }
|
|
79
85
|
// Our overlay is separate from the profile's user-owned patch.
|
|
80
86
|
await atomicJson(path.join(p.home, 'daiti.patch.json'), [
|
|
81
87
|
{ id: 'ui-sidebar', disabled: true },
|
|
88
|
+
{ id: 'daiti-single-session-im', disabled: false, config: { settingsPath: path.join(p.home, 'settings.json') } },
|
|
82
89
|
{ id: 'daiti-onboarding-preferences', disabled: false, config: { settingsPath: path.join(p.home, 'settings.json') } },
|
|
83
|
-
{ id: 'daiti-ui', disabled: false, config: { settingsPath: path.join(p.home, 'settings.json'), defaultWorkspacePath: path.join(homedir(), 'Daiti') } },
|
|
90
|
+
{ id: 'daiti-ui', disabled: false, config: { settingsPath: path.join(p.home, 'settings.json'), defaultWorkspacePath: path.join(homedir(), 'Daiti'), singleSessionIm: true } },
|
|
84
91
|
]);
|
|
85
92
|
await atomicJson(marker, { signature, installedAt: new Date().toISOString() });
|
|
86
93
|
}
|
|
@@ -104,9 +111,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
104
111
|
}
|
|
105
112
|
if (options.command === 'open' && !current?.ready) throw new Error('Daiti is not ready. Run daiti start first.');
|
|
106
113
|
if (options.command === 'start' && !current) {
|
|
114
|
+
// Fail clearly before installing anything when a fixed port is occupied.
|
|
115
|
+
if (options.portExplicit) await selectPort(options.port, false);
|
|
107
116
|
await setup(p);
|
|
108
117
|
const log = await fs.open(p.log, 'a', 0o600); await log.chmod(0o600);
|
|
109
|
-
const child = spawn(process.execPath, [path.join(root, 'lib/supervisor.mjs'), String(options.port)], { detached: true, stdio: ['ignore', log.fd, log.fd], env: { ...process.env, DAITI_HOME: p.home } });
|
|
118
|
+
const child = spawn(process.execPath, [path.join(root, 'lib/supervisor.mjs'), String(options.port), options.portExplicit ? 'fixed' : 'auto'], { detached: true, stdio: ['ignore', log.fd, log.fd], env: { ...process.env, DAITI_HOME: p.home } });
|
|
110
119
|
await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });
|
|
111
120
|
child.unref(); await log.close();
|
|
112
121
|
for (let i = 0; i < 240; i++) { current = await control(await readJson(p.state)); if (current?.ready) break; if (child.exitCode !== null) throw new Error(`Daiti exited during startup; inspect ${p.log}`); await pause(250); }
|
package/lib/ports.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
|
|
3
|
+
// Probe the same loopback address used by Harness. Port 0 lets the OS choose
|
|
4
|
+
// an available port; no assumptions about neighbouring ports being free.
|
|
5
|
+
async function probe(port) {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
await new Promise((resolve, reject) => {
|
|
8
|
+
server.once('error', reject);
|
|
9
|
+
server.listen({ port, host: '127.0.0.1', exclusive: true }, resolve);
|
|
10
|
+
});
|
|
11
|
+
const selected = server.address().port;
|
|
12
|
+
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
|
|
13
|
+
return selected;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function selectPort(port, automatic = false) {
|
|
17
|
+
try { return await probe(port); }
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code !== 'EADDRINUSE') throw error;
|
|
20
|
+
if (automatic) return probe(0);
|
|
21
|
+
throw new Error(`Port ${port} is already in use. Choose another --port, or omit --port to select automatically.`, { cause: error });
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/supervisor.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { promises as fs } from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
7
|
+
import { selectPort } from './ports.mjs';
|
|
7
8
|
import { paths, atomicJson, runtimeBin, runtimePath, root } from './cli.mjs';
|
|
8
9
|
|
|
9
10
|
|
|
@@ -36,7 +37,7 @@ export async function verifyLaunchUrl(value) {
|
|
|
36
37
|
|
|
37
38
|
// Only this parent signals its own ChildProcess. CLI clients never signal a PID
|
|
38
39
|
// obtained from disk, so stale state / PID reuse cannot terminate another app.
|
|
39
|
-
export async function supervise({ p = paths(), port, command = process.execPath, args, timeoutMs = 60000 }) {
|
|
40
|
+
export async function supervise({ p = paths(), port, autoPort = false, command = process.execPath, args, timeoutMs = 60000 }) {
|
|
40
41
|
const lockPath = path.join(p.home, 'service.lock');
|
|
41
42
|
let lock;
|
|
42
43
|
try { lock = await fs.open(lockPath, 'wx', 0o600); }
|
|
@@ -60,6 +61,9 @@ export async function supervise({ p = paths(), port, command = process.execPath,
|
|
|
60
61
|
} else server.close();
|
|
61
62
|
}
|
|
62
63
|
try {
|
|
64
|
+
const requestedPort = port;
|
|
65
|
+
port = await selectPort(port, autoPort);
|
|
66
|
+
if (port !== requestedPort) console.log(`Port ${requestedPort} is occupied; using ${port}.`);
|
|
63
67
|
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
|
64
68
|
await atomicJson(p.state, { instance, secret, controlPort: server.address().port, pid: process.pid });
|
|
65
69
|
child = spawn(command, args || [runtimeBin(), '--profile', 'web', '--patch', path.join(p.home, 'daiti.patch.json'), '--host', '127.0.0.1', '--port', String(port), '--no-open'], { env: { ...process.env, DSH_HOME: p.runtime, PATH: runtimePath() }, cwd: p.home, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
@@ -87,5 +91,5 @@ export async function supervise({ p = paths(), port, command = process.execPath,
|
|
|
87
91
|
} finally { server.close(); await cleanup(); }
|
|
88
92
|
}
|
|
89
93
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
90
|
-
supervise({ port: Number(process.argv[2]) }).catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
94
|
+
supervise({ port: Number(process.argv[2]), autoPort: process.argv[3] === 'auto' }).catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
91
95
|
}
|