dsh-tidings 0.0.0-stage → 0.4.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-tidings contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,161 @@
1
- # Temporary Holding Version
1
+ # dsh-tidings-bridge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [English](README.md) | [中文](README.zh.md)
4
+
5
+ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that bridges a DSH session to the local **Tidings (捎话)** node — the resident peer of the Tidings desktop app that owns the LAN messaging protocol.
6
+
7
+ The plugin talks **only** to the agentNode loopback HTTP control plane (`http://127.0.0.1:8788` by default). It never touches the wire protocol itself: discovery, handshakes, and the offline replay queue all live in agentNode. This package is a projection of that control plane into DSH, plus a pusher that feeds DSH's own session and workspace state back into it.
8
+
9
+ What you get after installing:
10
+
11
+ - **Nine agent tools** — an agent inside a DSH session can read node status, list peers, read the inbox, send messages, and announce its own sessions and workspaces to your phone (PPE-1).
12
+ - **One settings card** — since 2026-10-14 the plugin no longer owns a sidebar panel. What remains is a single card under DSH's settings navigation: connection state, the auto-announce switch, the control-plane address, and the access token. Configure once, then it works silently.
13
+
14
+ ## Prerequisites
15
+
16
+ The plugin is a client of a running agentNode. Two ways to have one:
17
+
18
+ - **Tidings desktop app** (recommended) — it listens on `127.0.0.1:8788` natively from its tray, so there is no process to babysit.
19
+ Get it from [**kexuejin/relay**](https://github.com/kexuejin/relay) — the repository this plugin serves (protocol, runtime and the `agentNode` control plane all live there).
20
+ - **Headless agentNode** — build the `agentNode` module from the Tidings source tree and run it with `--http-port 8788`. The `--http-port` listener binds loopback only.
21
+
22
+ By default no token is required (loopback is trusted). If you start agentNode with `--token` or `RELAY_AGENT_TOKEN`, enter the same token in the settings card; it is stored in DSH's credential store and never echoed back to the page.
23
+
24
+ ## Install
25
+
26
+ ### Option 1: Official npm Registry (Recommended)
27
+
28
+ ```sh
29
+ dsh plugin --profile web add dsh-tidings-bridge
30
+ ```
31
+
32
+ ### Option 2: As a Git Bundle (Builds on Install)
33
+
34
+ ```sh
35
+ dsh plugin --profile web add github:kexuejin/dsh-tidings-bridge
36
+ ```
37
+
38
+ pnpm refuses to run build scripts for git-hosted dependencies until you allow it, so the first `add` fails with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`. Copy the exact key pnpm prints into your profile's `pnpm-workspace.yaml` and run `add` again:
39
+
40
+ ```yaml
41
+ allowBuilds:
42
+ dsh-tidings-bridge@git+https://github.com/kexuejin/dsh-tidings-bridge.git#<commit-sha>: true
43
+ ```
44
+
45
+ Treat that allowance as *permission for this package's code to run on your machine at install time*, outside any agent sandbox. Pin the commit (`#<sha>`) so a later push cannot silently change what gets executed.
46
+
47
+ ### Option 3: Prebuilt Tarball
48
+
49
+ ```sh
50
+ dsh plugin --profile web add ./dsh-tidings-bridge-0.1.0.tgz
51
+ ```
52
+
53
+ For development, link a checkout:
54
+
55
+ ```sh
56
+ dsh plugin --profile web add link:/path/to/dsh-tidings-bridge
57
+ ```
58
+
59
+ ## Agent tools
60
+
61
+ | Tool | Purpose |
62
+ | --- | --- |
63
+ | `relay_status` | This node's status: nodeId, nickname, platform, ports, peer and online counts. Read-only, loopback only. |
64
+ | `relay_list_peers` | Discovered peers: nickname, address, platform, online flag, last activity. |
65
+ | `relay_read_inbox` | Incremental message reads by cursor (`since` / `limit`). |
66
+ | `relay_send_message` | Send one text. `to` accepts a nickname, an IP, or a nodeId. |
67
+ | `relay_agent_session` | Announce or update an agent session state (PPE-1 §6.1). |
68
+ | `relay_agent_event` | Deliver a monotonically-cursorised structured event to a session (PPE-1 §6.2). |
69
+ | `relay_agent_workspace` | Announce one of this host's workspaces (PPE-1 supplement D3). |
70
+ | `relay_agent_grants` | Read or change which peers may see this host's agent data (PPE-1 supplement D7). |
71
+ | `relay_announce_status` | Diagnostics for the auto-announcer: is it on, how many workspaces were announced, who the current audience is. Read-only. |
72
+
73
+ Two behaviours worth knowing before you rely on a result:
74
+
75
+ - **`queued` is not "read".** `sent` means the peer ACKed. `queued` means the peer is offline and the message waits in its replay queue.
76
+ - **A bare IP costs a handshake.** When `to` is an address rather than a known peer, agentNode sends an `entry` handshake and waits up to 3 seconds; if it fails you get `peer-unreachable` instead of a silent queue.
77
+
78
+ ## Settings card
79
+
80
+ Registered at DSH's `settings.section` slot (id `relay`, ordered early). It shows:
81
+
82
+ - **Connection state** — `已连接 · <nick> · N 个邻居在线`, or one of loading / unreachable / unauthorized. When unreachable, the card states the cause and offers the acquisition link rather than only an error.
83
+ - **Advanced (collapsed by default)** — the auto-announce switch and the control-plane address.
84
+ - **Refresh / close**, plus a one-line save notice.
85
+
86
+ Token entry goes through the credential store; the card reports only whether a token is present.
87
+
88
+ ## Configuration
89
+
90
+ Config keys are DSH plugin config (patch layer or profile); the two the card writes live in `$DSH_HOME/relay/config-v1.json` (atomic write, mode `0600`).
91
+
92
+ | Key | Default | Notes |
93
+ | --- | --- | --- |
94
+ | `baseUrl` | `http://127.0.0.1:8788` | agentNode control plane. |
95
+ | `tokenCredentialRef` | `RELAY_AGENT_TOKEN` | Credential ref; falls back to `process.env` when no credential provider is present. |
96
+ | `timeoutMs` | `8000` | Minimum 1000. |
97
+ | `defaultPort` | `17878` | Used when `to` is a bare address. |
98
+ | `inboxLimit` | `30` | 1–200. |
99
+ | `autoAnnounce` | `true` | Off = pure manual mode: no listeners registered, no frames sent. |
100
+ | `announceTargets` | `[]` | Audience allowlist (nodeId / IP / nickname). Empty = every peer the control plane currently reports online. |
101
+
102
+ **Privacy note.** With the default audience, an announcing host pushes session titles and workspace paths to every online peer. A misconfigured or unknown device that comes online would receive them too. Set `announceTargets` to narrow the exposure, or turn `autoAnnounce` off.
103
+
104
+ ## Repository layout
105
+
106
+ ```text
107
+ src/
108
+ index.ts host half: plugin meta, Config schema, the nine tools
109
+ relay.ts HTTP client for the agentNode control plane + token resolution
110
+ announce.ts automatic announcement of DSH sessions and workspaces
111
+ session-pages.ts session-page entry point
112
+ session-page-responder.ts answers the phone's paged session requests (PPE-1 D8)
113
+ workspace-registry.ts stable workspace and agent identity for this host
114
+ model.ts shared wire surface: RPC channel, endpoints, every cross-boundary view
115
+ rpc.ts host half of the loopback RPC channel (13 endpoints)
116
+ config-store.ts non-secret settings persistence
117
+ client/
118
+ index.ts browser half: registers the settings card and its dictionaries
119
+ RelaySettingsSection.tsx the card itself
120
+ RelaySettingsSection.module.css
121
+ locales.ts zh / en copy (a missing translation is a compile error)
122
+ settings-slots.d.ts the one slot this plugin occupies, declared locally
123
+ css-modules.d.ts / host-services.d.ts ambient declarations
124
+ scripts/check-*.mjs seven gate scripts (see Development)
125
+ check-real.mjs round-trip against a live agentNode
126
+ docs/ design notes and the panel prototype with its probe scripts
127
+ tsdown.config.ts two builds: host ESM (lib/) and browser CJS loader (client/)
128
+ ```
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ pnpm install
134
+ pnpm run build # lib/index.js (host ESM) + client/client.js (browser CJS)
135
+ pnpm run typecheck # tsconfig.host.json and tsconfig.client.json
136
+ pnpm run check # typecheck + seven gates + build — what CI runs
137
+ ```
138
+
139
+ Each gate exists because of a specific failure, and each one says so in its header comment:
140
+
141
+ | Gate | What it pins |
142
+ | --- | --- |
143
+ | `check:inject` | `inject` may only name baseline host services. A wrong hard dependency makes cordis refuse the **whole plugin with no log line**. |
144
+ | `check:announce` | Announcement accounting asks about **results**, not intentions, and state snapshots must re-assert within a window. |
145
+ | `check:title` | Title reads must follow the service's real return shape; a guard that is silently always false shows session ids everywhere. |
146
+ | `check:order` | Session order must come from DSH's own activity order, not creation order. |
147
+ | `check:activity` | The activity timestamp the phone sorts by must come from a real user action. |
148
+ | `check:visibility` | What the phone may show must match what DSH itself displays (no subagent sessions). |
149
+ | `check:css` | CSS module class names must exist on both sides — a typo must fail red, not render unstyled. |
150
+
151
+ ```sh
152
+ pnpm run check:real # build, then health/peers/inbox/send against a live agentNode
153
+ ```
154
+
155
+ `check:real` is deliberately outside `check`: it needs a running agentNode (override the target with `RELAY_BASE_URL`). It uses the same `createRelayClient` the plugin ships, so it proves the production code path works against a real control plane rather than a fake shell.
156
+
157
+ Design history lives in `docs/`: the 2026-10-14 panel-simplification decision, and `docs/prototype/` — the interactive prototype the IA was argued in, with a Chrome-headless screenshot script, a geometry probe, and 21 reference renders.
158
+
159
+ ## License
160
+
161
+ MIT — see [LICENSE](LICENSE).
package/README.zh.md ADDED
@@ -0,0 +1,161 @@
1
+ # dsh-tidings-bridge
2
+
3
+ [English](README.md) | [中文](README.zh.md)
4
+
5
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:把 DSH 会话桥接到本机的**捎话 (Tidings)** 节点 —— 即捎话桌面端常驻的 agentNode,局域网消息协议的持有者。
6
+
7
+ 插件**只**说 agentNode 的回环 HTTP 控制面(默认 `http://127.0.0.1:8788`),从不直接碰线协议:发现、握手、离线补发队列全在 agentNode 里。本包做两件事 —— 把控制面投影进 DSH,再把 DSH 自己的会话/工作区状态推回去。
8
+
9
+ 装完之后你得到:
10
+
11
+ - **九个 agent 工具** —— 会话里的 agent 能读节点状态、列邻居、收发消息,并把会话与工作区宣告给手机(PPE-1)。
12
+ - **一张设置卡片** —— 2026-10-14 起插件不再拥有侧栏面板,只剩 DSH 设置导航里的一张卡片:连接状态、自动宣告开关、控制面地址、访问令牌。配置一次,之后静默工作。
13
+
14
+ ## 前提
15
+
16
+ 插件是活的 agentNode 的客户端。两种就绪方式:
17
+
18
+ - **捎话桌面端**(推荐)—— 它由托盘原生固定监听 `127.0.0.1:8788`,没有需要人看护的进程。
19
+ 到 [**kexuejin/relay**](https://github.com/kexuejin/relay) 获取(本插件服务的那个仓:协议、运行时与 `agentNode` 控制面都在那里)。
20
+ - **无头 agentNode** —— 从捎话源码构建 `agentNode` 模块,以 `--http-port 8788` 运行;该监听只绑回环。
21
+
22
+ 默认不需要令牌(回环被信任)。若 agentNode 以 `--token` 或 `RELAY_AGENT_TOKEN` 启动,请在设置卡片里录入同一个令牌;它存在 DSH 凭据库里,页面不会回显。
23
+
24
+ ## 安装
25
+
26
+ ### 方式 1:npm 官方发布物(推荐)
27
+
28
+ ```sh
29
+ dsh plugin --profile web add dsh-tidings-bridge
30
+ ```
31
+
32
+ ### 方式 2:作为 Git 组合包安装(安装时现场构建)
33
+
34
+ ```sh
35
+ dsh plugin --profile web add github:kexuejin/dsh-tidings-bridge
36
+ ```
37
+
38
+ pnpm 默认拒绝执行 git 依赖的构建脚本,所以第一次 `add` 会以 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` 失败。把 pnpm 打印的确切包键复制进 profile 的 `pnpm-workspace.yaml`,再执行一次 `add`:
39
+
40
+ ```yaml
41
+ allowBuilds:
42
+ dsh-tidings-bridge@git+https://github.com/kexuejin/dsh-tidings-bridge.git#<commit-sha>: true
43
+ ```
44
+
45
+ 请把这项授权理解为**允许该包的代码在安装时于你的机器上执行**,且不在 agent 的任何沙箱之内。建议锁定 commit(`#<sha>`),让后续推送无法悄悄改变实际运行的内容。
46
+
47
+ ### 方式 3:离线 Tarball
48
+
49
+ ```sh
50
+ dsh plugin --profile web add ./dsh-tidings-bridge-0.1.0.tgz
51
+ ```
52
+
53
+ 开发时链接本地 checkout:
54
+
55
+ ```sh
56
+ dsh plugin --profile web add link:/path/to/dsh-tidings-bridge
57
+ ```
58
+
59
+ ## agent 工具
60
+
61
+ | 工具 | 作用 |
62
+ | --- | --- |
63
+ | `relay_status` | 本机节点状态:nodeId、昵称、平台、端口、邻居数与在线数。只读,只走回环。 |
64
+ | `relay_list_peers` | 邻居列表:昵称、地址、平台、在线与否、最后活跃。 |
65
+ | `relay_read_inbox` | 按游标增量读消息(`since` / `limit`)。 |
66
+ | `relay_send_message` | 发一条文本;`to` 可以是昵称、IP 或 nodeId。 |
67
+ | `relay_agent_session` | 宣告或更新 Agent 会话状态(PPE-1 §6.1)。 |
68
+ | `relay_agent_event` | 向会话投递单调游标的结构化事件(PPE-1 §6.2)。 |
69
+ | `relay_agent_workspace` | 宣告本机一个工作区(PPE-1 增补 D3)。 |
70
+ | `relay_agent_grants` | 读取或修改哪些对端可见本机 agent 数据(PPE-1 增补 D7)。 |
71
+ | `relay_announce_status` | 自动宣告器诊断:开关状态、已宣告工作区数、当前受众。只读。 |
72
+
73
+ 两个使用前的关键语义:
74
+
75
+ - **`queued` 不等于已读。** `sent` 表示对方 ACK 了;`queued` 只表示对方离线、消息在补发队列里等。
76
+ - **裸 IP 要先握手。** `to` 写地址而非已知邻居时,agentNode 先发 `entry` 握手、最多等 3 秒;握不上返回 `peer-unreachable`,不会静默入队。
77
+
78
+ ## 设置卡片
79
+
80
+ 注册在 DSH 的 `settings.section` 槽位(id `relay`,排序靠前)。卡片内容:
81
+
82
+ - **连接状态** —— `已连接 · <nick> · N 个邻居在线`,或检查中 / 未检测到 / 需要令牌。未连接时同时给出**原因与出路**(获取桌面端的链接),不只是一行报错。
83
+ - **高级(默认收起)** —— 自动宣告开关、控制面地址。
84
+ - **刷新 / 关闭**,以及一行保存回执。
85
+
86
+ 令牌录入走凭据库;卡片只报「有没有配」。
87
+
88
+ ## 配置
89
+
90
+ 配置键走 DSH 插件配置(patch 层或 profile);卡片可写的两项落 `$DSH_HOME/relay/config-v1.json`(原子写,`0600`)。
91
+
92
+ | 键 | 默认值 | 说明 |
93
+ | --- | --- | --- |
94
+ | `baseUrl` | `http://127.0.0.1:8788` | agentNode 控制面地址。 |
95
+ | `tokenCredentialRef` | `RELAY_AGENT_TOKEN` | 凭据引用;没有凭据服务时回落到 `process.env`。 |
96
+ | `timeoutMs` | `8000` | 下限 1000。 |
97
+ | `defaultPort` | `17878` | `to` 是裸地址时使用。 |
98
+ | `inboxLimit` | `30` | 1–200。 |
99
+ | `autoAnnounce` | `true` | 关掉 = 纯手动模式:不注册监听、不发任何帧。 |
100
+ | `announceTargets` | `[]` | 宣告受众白名单(nodeId / IP / 昵称)。留空 = 控制面当前报出的**全部在线邻居**。 |
101
+
102
+ **隐私说明。** 默认受众下,宣告中的主机会把会话标题与工作区路径推给每个在线邻居;一台误配或陌生的设备上线时同样收得到。要收敛暴露面就填 `announceTargets`,或关掉 `autoAnnounce`。
103
+
104
+ ## 仓库结构
105
+
106
+ ```text
107
+ src/
108
+ index.ts host 半:插件元信息、Config schema、九个工具
109
+ relay.ts agentNode 控制面 HTTP 客户端 + 令牌解析
110
+ announce.ts DSH 会话/工作区的自动宣告
111
+ session-pages.ts 会话分页入口
112
+ session-page-responder.ts 应答手机的历史会话分页请求(PPE-1 D8)
113
+ workspace-registry.ts 本宿主稳定的工作区与 agent 身份
114
+ model.ts 共享线表面:RPC 频道、endpoint、所有跨界视图
115
+ rpc.ts 回环 RPC 通道的 host 半(13 个 endpoint)
116
+ config-store.ts 非机密设置的持久化
117
+ client/
118
+ index.ts browser 半:注册设置卡片与词典
119
+ RelaySettingsSection.tsx 卡片本体
120
+ RelaySettingsSection.module.css
121
+ locales.ts zh / en 文案(漏一个翻译是编译错误)
122
+ settings-slots.d.ts 本插件占用的那一席槽位的本地声明
123
+ css-modules.d.ts / host-services.d.ts 环境声明
124
+ scripts/check-*.mjs 七条门禁(见「开发」)
125
+ check-real.mjs 对真实 agentNode 的往返验证
126
+ docs/ 设计决策与面板原型稿(含探针脚本)
127
+ tsdown.config.ts 两套构建:host ESM(lib/)+ browser CJS loader(client/)
128
+ ```
129
+
130
+ ## 开发
131
+
132
+ ```sh
133
+ pnpm install
134
+ pnpm run build # lib/index.js(host ESM)+ client/client.js(browser CJS)
135
+ pnpm run typecheck # tsconfig.host.json 与 tsconfig.client.json
136
+ pnpm run check # typecheck + 七条门禁 + build —— CI 跑的就是这条
137
+ ```
138
+
139
+ 每条门禁都源于一次真实故障,头部注释里写清了是哪一次:
140
+
141
+ | 门禁 | 钉住的东西 |
142
+ | --- | --- |
143
+ | `check:inject` | `inject` 只能写宿主一定提供的基线服务。硬依赖写错会让 cordis **静默拒绝整个插件,日志里连一行都没有**。 |
144
+ | `check:announce` | 宣告记账必须问**结果**而不是意图;状态快照必须会在窗口内重申。 |
145
+ | `check:title` | 读标题必须按服务的**真实返回形状**取值;恒为 false 的守卫会让手机端满屏 session id。 |
146
+ | `check:order` | 会话顺序必须来自 DSH 自己的活动序,不是创建序。 |
147
+ | `check:activity` | 手机端排序用的活动时刻必须来自真实的用户动作。 |
148
+ | `check:visibility` | 手机上能看到的会话必须与 DSH 自己显示的口径一致(子代理会话不推)。 |
149
+ | `check:css` | CSS Module 类名两侧必须都存在 —— 拼错必须当场判红,而不是默默掉样式。 |
150
+
151
+ ```sh
152
+ pnpm run check:real # 先 build,再对真实 agentNode 打 health/peers/inbox/send
153
+ ```
154
+
155
+ `check:real` 故意不进 `check`:它需要一个活着的 agentNode(用 `RELAY_BASE_URL` 覆盖地址)。它用的是插件生产代码里的同一个 `createRelayClient`,因此证明的是真实代码路径在真实控制面上跑得通,而不是假壳自洽。
156
+
157
+ 设计沿革在 `docs/`:2026-10-14 的面板精简决策,以及 `docs/prototype/` —— 当年用来论证信息架构的可交互原型稿,配 Chrome headless 截图脚本、几何探针与 21 张参考图。
158
+
159
+ ## 许可
160
+
161
+ MIT,见 [LICENSE](LICENSE)。