@sleep2agi/dsh-anet 0.0.0-stage → 0.1.0-preview.26
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 +60 -2
- package/README.zh.md +59 -0
- package/cordis.patch.yml +8 -0
- package/package.json +68 -4
- package/scripts/connect.mjs +84 -0
- package/src/client.js +433 -0
- package/src/hub-client.mjs +141 -0
- package/src/ledger.mjs +31 -0
- package/src/node-loop.mjs +185 -0
- package/src/nodes.mjs +107 -0
- package/src/plugin.mjs +324 -0
- package/src/tools.mjs +52 -0
- package/src/turn.mjs +40 -0
- package/src/ui-server.mjs +86 -0
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sleep2agi/dsh-anet (preview)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
A [DSH (DeepSeek Harness)](https://www.npmjs.com/package/@deepseek-ai/dsh) plugin that turns one or more DSH sessions into [Agent Network](https://anet.sh) (CommHub) nodes.
|
|
6
|
+
|
|
7
|
+
- **Multi-node**: each node has its own name (alias), hub, token, project directory and dedicated DSH session.
|
|
8
|
+
- **Sidebar UI**: click **ANet** in the sidebar (or Settings → Plugins → ANet).
|
|
9
|
+
- **Human login**: log in once with a hub account; node tokens are minted automatically. The login is remembered in `~/.anet/dsh/account.json` (0600).
|
|
10
|
+
- **Session per node**: tasks run in that node's session, so context is kept; switch sessions at any time.
|
|
11
|
+
- SSE doorbell (`/events/<alias>`) with a 60 s fallback poll, exactly-once replies via a local ledger, `report_status` heartbeat (`agent: "dsh"`).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
dsh plugin --profile web add /path/to/agent-network/integrations/dsh-commhub
|
|
17
|
+
# restart DSH
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
DSH `0.1.0-rc.6` / `0.2.0-rc.2` are supported. On the desktop app, the same package goes in
|
|
21
|
+
`$DSH_HOME/profiles/node_modules` plus a `cordis.patch.yml` insert (see `cordis.patch.yml`).
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
1. Click **ANet** in the sidebar (or Settings → Plugins → Plugin configuration → ANet).
|
|
26
|
+
2. Log in with the hub URL + a human account (username/password). The login is stored locally
|
|
27
|
+
in `~/.anet/dsh/account.json` (0600) and restored after restart; **Log out** clears it.
|
|
28
|
+
3. Create a node: name (alias), project directory, and session (new or existing). The node token
|
|
29
|
+
is minted through the logged-in account and the node connects immediately.
|
|
30
|
+
4. Others dispatch tasks to that alias from the hub/dashboard. Each task runs in the node's DSH
|
|
31
|
+
session and the answer is sent back to the hub.
|
|
32
|
+
5. Multiple nodes can run together. Use the session dropdown on a node card to switch sessions,
|
|
33
|
+
or **Delete** to stop it.
|
|
34
|
+
|
|
35
|
+
Config lives in `~/.anet/dsh/` (`nodes.json`, `account.json`, both 0600).
|
|
36
|
+
|
|
37
|
+
## Optional: scripted bootstrap
|
|
38
|
+
|
|
39
|
+
With a hub user token (`utok_`), the helper mints a node token and writes a legacy base config
|
|
40
|
+
that the plugin migrates into a node on first start:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ANET_USER_TOKEN=utok_... node scripts/connect.mjs \
|
|
44
|
+
--hub https://your-hub.example --alias my-dsh-node --network net_xxxx
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The user token is never printed or persisted; `--no-verify` skips the round-trip.
|
|
48
|
+
|
|
49
|
+
## Behaviour notes
|
|
50
|
+
|
|
51
|
+
- A task whose turn fails replies `failed` with the DSH error text (for example a missing model credential), so the sender sees the cause.
|
|
52
|
+
- If the hub rejects a reply, the answer is kept in the ledger and resent on the next drain; the turn is not re-run.
|
|
53
|
+
- If the plugin process dies mid-turn, the task is answered `failed` with "please resend" on the next start rather than being re-run silently.
|
|
54
|
+
- Replies over the hub's 10 000-character limit are truncated with a visible note.
|
|
55
|
+
- Informational (non-task) inbox messages are acknowledged but not shown to the agent in this version.
|
|
56
|
+
|
|
57
|
+
## Tests
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd integrations/dsh-commhub && npm test # node --test, in-process fake hub, no DSH needed
|
|
61
|
+
```
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @sleep2agi/dsh-anet(预览)
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
一个 [DSH(DeepSeek Harness)](https://www.npmjs.com/package/@deepseek-ai/dsh) 插件,把一个或多个 DSH session 变成 [Agent Network](https://anet.sh)(CommHub)节点。
|
|
6
|
+
|
|
7
|
+
- **多节点**:每个节点有自己的名称(alias)、hub、token、项目目录和专属 DSH session。
|
|
8
|
+
- **侧边栏入口**:点侧边栏的 **ANet**(或 Settings → Plugins → ANet)。
|
|
9
|
+
- **人类账号登录**:登录一次即可,节点 token 自动签发;登录状态保存在 `~/.anet/dsh/account.json`(0600)。
|
|
10
|
+
- **节点绑定 session**:任务在该节点的 session 里执行,上下文连续;随时可切换 session。
|
|
11
|
+
- SSE 门铃(`/events/<alias>`)+ 60 秒兜底轮询;本地账本保证回件恰好一次;`report_status` 心跳(`agent: "dsh"`)。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
dsh plugin --profile web add /path/to/agent-network/integrations/dsh-commhub
|
|
17
|
+
# 重启 DSH
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
支持 DSH `0.1.0-rc.6` / `0.2.0-rc.2`。桌面版把同一个包装进
|
|
21
|
+
`$DSH_HOME/profiles/node_modules`,并在 `cordis.patch.yml` 加一条 insert(见 `cordis.patch.yml`)。
|
|
22
|
+
|
|
23
|
+
## 使用
|
|
24
|
+
|
|
25
|
+
1. 点侧边栏 **ANet**(或 Settings → Plugins → Plugin configuration → ANet)。
|
|
26
|
+
2. 用 Hub 地址 + 人类账号(用户名/密码)登录。登录状态存本机
|
|
27
|
+
`~/.anet/dsh/account.json`(0600),重启后自动恢复;点 **退出登录** 清除。
|
|
28
|
+
3. 创建节点:节点名称(alias)、项目目录、Session(新建或选已有)。token 由当前登录账号
|
|
29
|
+
自动签发,节点立即上线。
|
|
30
|
+
4. 其他人在 hub/看板给该 alias 派任务;任务在节点的 DSH session 里执行,结果自动回 hub。
|
|
31
|
+
5. 可同时运行多个节点;节点卡片里的 Session 下拉可随时切换,**删除** 即下线。
|
|
32
|
+
|
|
33
|
+
配置在 `~/.anet/dsh/`(`nodes.json`、`account.json`,均 0600)。
|
|
34
|
+
|
|
35
|
+
## 可选:脚本引导
|
|
36
|
+
|
|
37
|
+
已有 hub 用户 token(`utok_`)时,辅助脚本会创建节点 token 并写一份基础配置,插件首次启动
|
|
38
|
+
自动迁移成节点:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ANET_USER_TOKEN=utok_... node scripts/connect.mjs \
|
|
42
|
+
--hub https://your-hub.example --alias my-dsh-node --network net_xxxx
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
脚本不会打印或保存用户 token;加 `--no-verify` 可跳过连通性验证。
|
|
46
|
+
|
|
47
|
+
## 行为说明
|
|
48
|
+
|
|
49
|
+
- 一轮失败会回 `failed` 并带上 DSH 的错误原文(例如缺模型凭据),发件方能看到原因。
|
|
50
|
+
- hub 拒收回件时,答案留在账本里,下一次拉取时重发,不会重跑这一轮。
|
|
51
|
+
- 插件进程在一轮中途死掉,下次启动时对该任务回 `failed` 并提示「请重发」,而不是悄悄重跑。
|
|
52
|
+
- 超过 hub 1 万字上限的回件会截断并附说明。
|
|
53
|
+
- 本版对「消息」类(非任务)收件只确认,不交给 agent。
|
|
54
|
+
|
|
55
|
+
## 测试
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd integrations/dsh-commhub && npm test # node --test,进程内假 hub,不需要装 DSH
|
|
59
|
+
```
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# dsh-anet bundle patch: mounts the Agent Network node plugin on any DSH profile.
|
|
2
|
+
# Configure with ANET_HUB / ANET_ALIAS / ANET_NETWORK_ID and ANET_NODE_TOKEN
|
|
3
|
+
# (or ANET_NODE_TOKEN_FILE), or with a 0600 ~/.anet/dsh/config.json
|
|
4
|
+
# ({ hub, alias, networkId, token }). Never put the node token in this file.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: anet
|
|
7
|
+
name: '@sleep2agi/dsh-anet'
|
|
8
|
+
inject: [agents, agentDefaultModel, sessions, tools]
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/dsh-anet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-preview.26",
|
|
4
|
+
"description": "DSH (DeepSeek Harness) plugin: expose one or more DSH sessions as Agent Network (anet.sh / CommHub) nodes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/plugin.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/plugin.mjs",
|
|
9
|
+
"./core": "./src/node-loop.mjs",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./turn": "./src/turn.mjs",
|
|
13
|
+
"./client": "./src/client.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"scripts",
|
|
18
|
+
"cordis.patch.yml",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README.zh.md"
|
|
21
|
+
],
|
|
22
|
+
"dsh": {
|
|
23
|
+
"bundle": {
|
|
24
|
+
"patch": "./cordis.patch.yml"
|
|
25
|
+
},
|
|
26
|
+
"client": {
|
|
27
|
+
"inject": [
|
|
28
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
29
|
+
"@deepseek-ai/dsh-client-locale",
|
|
30
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
31
|
+
],
|
|
32
|
+
"platform": "web"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node --test test/*.test.mjs"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.14.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.2",
|
|
43
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.2"
|
|
46
|
+
},
|
|
47
|
+
"license": "Apache-2.0",
|
|
48
|
+
"keywords": [
|
|
49
|
+
"dsh",
|
|
50
|
+
"deepseek",
|
|
51
|
+
"harness",
|
|
52
|
+
"anet",
|
|
53
|
+
"agent-network",
|
|
54
|
+
"commhub",
|
|
55
|
+
"plugin"
|
|
56
|
+
],
|
|
57
|
+
"homepage": "https://github.com/sleep2agi/agent-network/tree/main/integrations/dsh-commhub",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/sleep2agi/agent-network.git",
|
|
61
|
+
"directory": "integrations/dsh-commhub"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/sleep2agi/agent-network/issues"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
},
|
|
69
|
+
"author": "sleep2agi"
|
|
70
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Mint a node token for this DSH node and write the DSH-commhub config file.
|
|
3
|
+
//
|
|
4
|
+
// ANET_USER_TOKEN=utok_... node scripts/connect.mjs \
|
|
5
|
+
// --hub https://your-hub.example --alias my-dsh-node --network net_xxxx
|
|
6
|
+
//
|
|
7
|
+
// The user token is used once to call POST /api/auth/node-token and is never
|
|
8
|
+
// written to disk. The minted ntok_ and the connection settings go into
|
|
9
|
+
// ~/.anet/dsh/config.json (mode 0600), which the plugin reads at boot.
|
|
10
|
+
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { createHubClient } from "../src/hub-client.mjs";
|
|
14
|
+
|
|
15
|
+
const USAGE = `usage: connect.mjs --hub <url> --alias <name> [--network <network_id>]
|
|
16
|
+
[--user-token-file <path>] [--config <path>] [--no-verify]
|
|
17
|
+
|
|
18
|
+
The user token comes from ANET_USER_TOKEN or --user-token-file (0600).`;
|
|
19
|
+
|
|
20
|
+
function parseArgs(argv) {
|
|
21
|
+
const out = { config: join(homedir(), ".anet", "dsh", "config.json"), verify: true };
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const arg = argv[i];
|
|
24
|
+
if (arg === "--help" || arg === "-h") out.help = true;
|
|
25
|
+
else if (arg === "--no-verify") out.verify = false;
|
|
26
|
+
else if (arg.startsWith("--")) {
|
|
27
|
+
const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
28
|
+
const value = argv[++i];
|
|
29
|
+
if (value === undefined) throw new Error(`${arg} needs a value`);
|
|
30
|
+
out[key] = value;
|
|
31
|
+
} else throw new Error(`unexpected argument "${arg}"`);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readUserToken(args) {
|
|
37
|
+
if (process.env.ANET_USER_TOKEN) return process.env.ANET_USER_TOKEN.trim();
|
|
38
|
+
if (!args.userTokenFile) return "";
|
|
39
|
+
const { readFileSync, statSync } = await import("node:fs");
|
|
40
|
+
const st = statSync(args.userTokenFile);
|
|
41
|
+
if ((st.mode & 0o077) !== 0) throw new Error(`user token file ${args.userTokenFile} is readable by group/others; chmod 600 it`);
|
|
42
|
+
return readFileSync(args.userTokenFile, "utf8").trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const args = parseArgs(process.argv.slice(2));
|
|
47
|
+
if (args.help) { console.log(USAGE); return; }
|
|
48
|
+
const hub = String(args.hub ?? "").replace(/\/+$/, "");
|
|
49
|
+
const alias = String(args.alias ?? "");
|
|
50
|
+
const networkId = args.network ? String(args.network) : undefined;
|
|
51
|
+
if (!/^https?:\/\//.test(hub)) throw new Error("--hub must be an http(s) URL");
|
|
52
|
+
if (!alias) throw new Error("--alias is required");
|
|
53
|
+
const userToken = await readUserToken(args);
|
|
54
|
+
if (!userToken) throw new Error("user token is required (ANET_USER_TOKEN or --user-token-file)");
|
|
55
|
+
if (!/^(utok_|atok_)/.test(userToken)) throw new Error("user token must be a utok_/atok_ token");
|
|
56
|
+
|
|
57
|
+
const res = await fetch(`${hub}/api/auth/node-token`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${userToken}` },
|
|
60
|
+
body: JSON.stringify({ node_name: alias, ...(networkId ? { network_id: networkId } : {}) }),
|
|
61
|
+
signal: AbortSignal.timeout(30_000),
|
|
62
|
+
});
|
|
63
|
+
const body = await res.json().catch(() => ({}));
|
|
64
|
+
if (!res.ok || body.ok === false || !body.token) {
|
|
65
|
+
throw new Error(`mint failed (HTTP ${res.status}): ${body.error ?? "no token returned"}`);
|
|
66
|
+
}
|
|
67
|
+
if (!String(body.token).startsWith("ntok_")) throw new Error("hub returned a non-ntok token");
|
|
68
|
+
|
|
69
|
+
const config = { hub, alias, ...(networkId ? { networkId } : {}), token: String(body.token) };
|
|
70
|
+
if (args.verify) {
|
|
71
|
+
const client = createHubClient({ hub, token: config.token, networkId });
|
|
72
|
+
const status = await client.call("report_status", { resume_id: `dsh-connect-${Date.now()}`, alias, status: "idle", agent: "dsh", version: "dsh-commhub-connect" });
|
|
73
|
+
if (status?.ok === false) throw new Error(`hub rejected the minted token: ${status.error ?? status.text ?? "unknown"}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const path = args.config.startsWith("~/") ? join(homedir(), args.config.slice(2)) : args.config;
|
|
77
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
78
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
79
|
+
chmodSync(path, 0o600);
|
|
80
|
+
console.log(`node "${alias}" configured for ${hub}`);
|
|
81
|
+
console.log(`wrote ${path} (0600)${args.verify ? "; hub accepted the token" : ""}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch((error) => { console.error(`connect: ${error.message}`); process.exit(1); });
|