dsh-crossterm 0.7.4 → 0.7.6
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/CHANGELOG.md +39 -0
- package/README.md +135 -14
- package/lib/bus.js +1 -1
- package/lib/index.js +42 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.7.6 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### 收信尾行不再重复:跨设备邮件只给一条回信提示
|
|
6
|
+
|
|
7
|
+
- 症状(自测发现):0.7.5 的跨设备邮件末尾出现两条回信提示 ——
|
|
8
|
+
`(回信:调用 term_send,参数 to="yoce-c-4fb38e";…)`(`renderMailList()` 给的)
|
|
9
|
+
和 `(这封信由云端中转站投递到本机,收件人:…。)(回复用 term_send。)`(`relayDeliver()` 又补了一条)。
|
|
10
|
+
- 根因:0.7.5 把中转站投递改成走 `renderMailList()` 后,`relayDeliver()` 里 0.7.4 留下的那句手写尾行没删。
|
|
11
|
+
- 改法(`lib/index.js` 的 `relayDeliver()`):删掉那句固定尾行,只保留条件提示(附件没下载下来、信封没写收件人的回落说明)。
|
|
12
|
+
- 文档:`README.md` 3.3 的跨设备例子去掉重复尾行;3.4 由「三条硬规则」改为「四条硬规则」,新增
|
|
13
|
+
「**尾行只给一次**:回信提示和 `经由` 由 `renderMailList()` 统一给,中转站不再另加尾行」。
|
|
14
|
+
- 探针 `probe-mail-label.mjs` 增断言:`lib/index.js` 里不再出现「中转站投递到本机」这句尾行、README 例子不再有重复尾行。
|
|
15
|
+
- 要不要跟着动手:**不用** —— 只删一句重复文案,协议与数据格式没变。
|
|
16
|
+
|
|
17
|
+
## 0.7.5 — 2026-10-19
|
|
18
|
+
|
|
19
|
+
### 邮件来源四档标签:「跨会话 / 跨工作区 / 跨终端 / 跨设备」,标题行改「发件人」
|
|
20
|
+
|
|
21
|
+
- 症状(用户报):收到的信只显示「跨会话邮件」或「跨终端邮件」两档,**云端中转来的信没有「跨设备」这一档**;
|
|
22
|
+
抬头那一行写的是「会话:xxx」,很容易被读成「收件会话」,看不出这行其实是发信的那个会话。
|
|
23
|
+
- 根因:`renderMailList()` 只有两级判定(`from.nodeId === this.nodeId` 就「跨会话」,否则「跨终端」);
|
|
24
|
+
中转站投递走的是另一套手写抬头(`[云端中转站] 收到 1 封` / `来自:…`),和本机邮件渲染不统一。
|
|
25
|
+
- 改法(`lib/index.js`):
|
|
26
|
+
- `renderMailList()` 加第三档:任一封带 `relay` 标记 → **「跨设备邮件」**;否则同一 nodeId → 「跨会话邮件」;其余 → 「跨终端邮件」。
|
|
27
|
+
- 再加一档:同一 nodeId 但**发信工作区 ≠ 收件会话工作区** → **「跨工作区邮件」**(四档优先级:跨设备 > 跨终端 > 跨工作区 > 跨会话)。
|
|
28
|
+
`renderIncoming()` / `renderBatch()` / `deliverBatch()` / `deliverMsg()` / `deliverTo()` 把目标 `sessionId` 一路传进去,
|
|
29
|
+
用 `cwdOf(sessionId)` 取收件会话的工作目录做比较(忽略大小写;任一侧拿不到 cwd 按同工作区处理,不误判)。
|
|
30
|
+
- 抬头行 `会话:` → **`发件人:`**(值仍是发信会话名,拿不到名字时回落 `labelOf(sessionId)`);云端来的信多一行 `经由:云端中转站`。
|
|
31
|
+
- `relayDeliver()` 不再手写抬头,改成走 `mailView()` + `renderMailList([view], landed)`,并把 `relay: true` 标进 view ——
|
|
32
|
+
这样云端邮件与本机邮件共用同一套渲染:正文同样落盘 `mail/`、附件清单同样列出、结尾同样给回信提示,
|
|
33
|
+
末尾再补「由云端中转站投递 / 收件人 / 回复用 term_send」和「没写收件人按最近活跃会话投给 X」的说明。
|
|
34
|
+
- `term_inbox` 的清单也把「会话:」改成「发件人:」,两处口径一致。
|
|
35
|
+
- 文档:`README.md` 新增 **「三、收信长什么样:邮件格式(固定契约)」** —— 结构顺序、四档标签判定表、四个例子(跨会话 / 跨工作区 / 跨终端 / 跨设备)、
|
|
36
|
+
三条硬规则(格式不许各写各的、正文只落盘、收件人为空不丢信)。原三~十节顺延为四~十一节。
|
|
37
|
+
- 探针 `probe-mail-label.mjs`:**25 项全 PASS**(四档判定代码、目标会话一路透传、发件人/经由/`term_inbox` 口径、旧抬头已移除、
|
|
38
|
+
README 契约节与节号顺延,以及标签复算:同工作区=跨会话 / 不同工作区=跨工作区 / 大小写不敏感 / 拿不到 cwd 不误判 /
|
|
39
|
+
别的终端=跨终端 / 云端=跨设备(优先级最高))。
|
|
40
|
+
- 要不要跟着动手:**不用** —— 纯展示层(标签与抬头文案),协议与数据格式没变,各版本互通不受影响。
|
|
41
|
+
|
|
3
42
|
## 0.7.4 — 2026-10-19
|
|
4
43
|
|
|
5
44
|
### 没写收件人的信封不再卡死:回落「最近活跃会话」
|
package/README.md
CHANGED
|
@@ -28,13 +28,24 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
28
28
|
> `locatePkgJson()` 对子路径行返回 undefined),而插件页顶部标题与这一行共用同一份 `locale/zh.json` 的 meta,
|
|
29
29
|
> 所以它单独做总开关,标题带「(总开关)」;卡片头用 `shortTitle` 显示短名「跨端互通」。
|
|
30
30
|
|
|
31
|
-
**本版更新(0.7.
|
|
31
|
+
**本版更新(0.7.6)**
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
33
|
+
- **跨设备邮件的尾巴不再重复**:0.7.5 里同一封信末尾会出现两条回复提示(一条 `renderMailList()` 给的、
|
|
34
|
+
一条中转站又补的)。现在只留一条,`经由:云端中转站` 照旧在抬头。
|
|
35
|
+
- 条件提示照旧追加在最后(附件没下载下来、信封没写收件人时的回落说明)。
|
|
36
|
+
- 邮件格式契约补第 4 条硬规则:**尾行只给一次**。
|
|
37
|
+
- 要不要跟着动手:**不用** —— 只删一句重复文案,协议与数据格式没变。
|
|
38
|
+
|
|
39
|
+
**上一版(0.7.5)**
|
|
40
|
+
|
|
41
|
+
- **邮件来源分四档**:同终端同工作区 → 「跨会话邮件」;同终端不同工作区 → **「跨工作区邮件」**;
|
|
42
|
+
同一台机器别的 DSH 进程 → 「跨终端邮件」;云端中转站投进来的(别的设备)→ **「跨设备邮件」**。
|
|
43
|
+
以前只有两档,云端来的信被标成「跨终端」。
|
|
44
|
+
- **抬头那行改成「发件人:」**:以前写「会话:xxx」,容易被读成「收件会话」;现在明确是发信的那个会话,
|
|
45
|
+
云端来的信还多一行「经由:云端中转站」。`term_inbox` 清单同步改成「发件人:」。
|
|
46
|
+
- **云端邮件与本机邮件同一套渲染**:中转站投递不再手写抬头,正文照样落盘 `mail/`、附件清单照样列、结尾照样给回信提示。
|
|
47
|
+
- **邮件格式写进说明**:新增「三、收信长什么样:邮件格式(固定契约)」一节,列清字段顺序、四档标签判定与四个例子。
|
|
48
|
+
- 要不要跟着动手:**不用** —— 纯展示层,协议与数据格式没变。
|
|
38
49
|
|
|
39
50
|
## 一、安装与卸载
|
|
40
51
|
|
|
@@ -68,7 +79,117 @@ dsh plugin --profile desktop remove dsh-crossterm
|
|
|
68
79
|
7. 同一台机器:B 直接拿到**源文件的绝对路径**(不复制、不占双份空间);
|
|
69
80
|
跨机器(总线在共享盘上):文件会**拷贝进总线再落到 B 的会话目录** `crossterm-inbox\<时间戳>\`。
|
|
70
81
|
|
|
71
|
-
##
|
|
82
|
+
## 三、收信长什么样:邮件格式(固定契约)
|
|
83
|
+
|
|
84
|
+
不管信从哪来,投进会话的**都是同一套格式**——统一由 `renderMailList()` 生成(0.7.5 起云端中转的信也走这一套,
|
|
85
|
+
不再是另写一套抬头)。字段顺序固定,缺数据的行直接不出现。
|
|
86
|
+
|
|
87
|
+
### 3.1 结构(从上到下固定顺序)
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
[<来源标签>] 收到 1 封 ← 多封时:[<来源标签>] 本批 N 封(…)
|
|
91
|
+
|
|
92
|
+
特征码:<code> ← 只有「链上广播」的信才有
|
|
93
|
+
终端:<终端名> (<nodeId>) ← 恒有
|
|
94
|
+
工作区:<发信会话的工作目录> ← 发信会话有 cwd 时才有
|
|
95
|
+
发件人:<发信会话名> ← 发信会话能解析出名字时才有(回落到短号)
|
|
96
|
+
经由:云端中转站 ← 只有跨设备的信才有
|
|
97
|
+
主题:<主题> ← 恒有(没主题就取正文首行,最多 60 字)
|
|
98
|
+
正文:<落盘路径>(用 read 工具打开,N B) ← 恒有(落盘失败时写「见 term_inbox」)
|
|
99
|
+
附件:<文件名>(N B)、… ← 有附件时才有
|
|
100
|
+
|
|
101
|
+
附件已保存到本机: ← 附件真的落到本地时才有
|
|
102
|
+
- <绝对路径>(N B)
|
|
103
|
+
|
|
104
|
+
(回信:调用 term_broadcast,参数 code="…";…) ← 链上来的信给这一行
|
|
105
|
+
(回信:调用 term_send,参数 to="…";…) ← 单点来的信给这一行
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
要点:**正文一律落盘、不内联**(最多给你一个路径),所以 `正文:` 那行永远指向 `mail\` 下的 `.md` 文件;
|
|
109
|
+
`发件人:` 写的是**发信的那个会话**,不是收件会话;一封一批多条时会带 `── 1/N ──` 分隔并提示「先全部 read 完再统一回一封」。
|
|
110
|
+
|
|
111
|
+
### 3.2 四档来源标签
|
|
112
|
+
|
|
113
|
+
| 场景 | 标签 | 判定 |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| 同一终端、同一工作区的会话之间 | `跨会话邮件` | 所有发信方都是本机 `nodeId`,且发信工作区 = 收件会话工作区 |
|
|
116
|
+
| 同一终端、不同工作区的会话之间(跨工作区) | `跨工作区邮件` | 发信方都是本机 `nodeId`,但发信工作区 ≠ 收件会话工作区 |
|
|
117
|
+
| 同一台机器、不同 DSH 终端/进程 | `跨终端邮件` | 有发信方不是本机 `nodeId`,且不是云端中转 |
|
|
118
|
+
| 不同机器、经云端中转站 | `跨设备邮件` | 信封带 `relay` 标记(由云端中转站投递进来) |
|
|
119
|
+
|
|
120
|
+
判定优先级:**跨设备 > 跨终端 > 跨工作区 > 跨会话**。工作区取会话的 `cwd`(`cwdOf(sessionId)`),
|
|
121
|
+
比较时忽略大小写;任一侧拿不到 cwd 就按「同工作区」处理,不会误判成跨工作区。
|
|
122
|
+
|
|
123
|
+
### 3.3 四个例子
|
|
124
|
+
|
|
125
|
+
跨会话(同一终端同一工作区):
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
[跨会话邮件] 收到 1 封
|
|
129
|
+
|
|
130
|
+
终端:TS-01 (ts-01-a52535)
|
|
131
|
+
工作区:F:\dsh\001X
|
|
132
|
+
发件人:会话群聊插件
|
|
133
|
+
主题:转:YOCE_C 已升 0.7.4,cloudRoot 保持空,旧云端目录可整删
|
|
134
|
+
正文:F:\dsh\001X\crossterm-data\mail\af9c3bc7-….md(用 read 工具打开,1.1 KB)
|
|
135
|
+
|
|
136
|
+
(回信:调用 term_send,参数 to="ts-01-a52535";消息会投进对方终端当时打开的会话。)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
跨工作区(同一终端、另一个工作目录里的会话):
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
[跨工作区邮件] 收到 1 封
|
|
143
|
+
|
|
144
|
+
终端:TS-01 (ts-01-a52535)
|
|
145
|
+
工作区:F:\dsh\001X\other-project
|
|
146
|
+
发件人:会话群聊插件
|
|
147
|
+
主题:那批图的尺寸表在附件里
|
|
148
|
+
正文:…\mail\….md(用 read 工具打开,2.4 KB)
|
|
149
|
+
附件:sizes.csv(1.2 KB)
|
|
150
|
+
|
|
151
|
+
附件已保存到本机:
|
|
152
|
+
- F:\dsh\001X\other-project\crossterm-inbox\20261019-101500\sizes.csv(1.2 KB)
|
|
153
|
+
|
|
154
|
+
(回信:调用 term_send,参数 to="ts-01-a52535";…)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
跨终端(同一台机器、另一个 DSH 进程):
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
[跨终端邮件] 收到 1 封
|
|
161
|
+
|
|
162
|
+
终端:TS-02 (ts-02-1f2a3b)
|
|
163
|
+
工作区:F:\dsh\001X
|
|
164
|
+
发件人:验证跨终端插件
|
|
165
|
+
主题:收到后在 TS-02 上无需回复
|
|
166
|
+
正文:…\mail\….md(用 read 工具打开,753 B)
|
|
167
|
+
|
|
168
|
+
(回信:调用 term_send,参数 to="ts-02-1f2a3b";…)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
跨设备(经云端中转站):
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
[跨设备邮件] 收到 1 封
|
|
175
|
+
|
|
176
|
+
终端:YOCE_C (yoce-c-4fb38e)
|
|
177
|
+
工作区:F:\dsh\project
|
|
178
|
+
发件人:验证跨终端插件
|
|
179
|
+
经由:云端中转站
|
|
180
|
+
主题:旧云端目录已整删(/dsh云盘/crossterm/bus)
|
|
181
|
+
正文:…\mail\….md(用 read 工具打开,1.1 KB)
|
|
182
|
+
|
|
183
|
+
(回信:调用 term_send,参数 to="yoce-c-4fb38e";…)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 3.4 四条硬规则
|
|
187
|
+
|
|
188
|
+
1. **格式不许各写各的**:本机总线投递、云端中转投递都调 `renderMailList()`;新增字段只加在固定位置,别再另起抬头。
|
|
189
|
+
2. **正文只落盘**:`mail\` 下的 `.md` 是唯一正文载体(`term_inbox` 里显示同一个路径),删文件即删正文。
|
|
190
|
+
3. **收件人为空不丢信**:云端信封没写收件会话时,投给「最近活跃会话」并在正文注明;连会话都没有才留件等下次。
|
|
191
|
+
4. **尾行只给一次**:回信提示和「经由」由 `renderMailList()` 统一给,中转站不再另加尾行(0.7.6 起);条件提示(附件没下下来、回落投递)才追加在最后。
|
|
192
|
+
## 四、九个工具
|
|
72
193
|
|
|
73
194
|
| 工具 | 作用 | 主要参数 |
|
|
74
195
|
| --- | --- | --- |
|
|
@@ -183,7 +304,7 @@ dsh plugin --profile desktop remove dsh-crossterm
|
|
|
183
304
|
兜底顺序:标题事件 → (还没有标题时)首条用户消息截一行 → 8 位短码。派生结果缓存在总线根目录的
|
|
184
305
|
`names.json`(`{ "短码": "名字" }`);有名字的会话 5 分钟复检一次(改标题能跟上),没名字的 1 分钟试一次。
|
|
185
306
|
|
|
186
|
-
##
|
|
307
|
+
## 五、一串特征码:把多个会话串起来
|
|
187
308
|
|
|
188
309
|
这一节是「同账号不同终端」最常用的姿势:**不用记 nodeId、不用管对方是桌面端还是 dsh web,一串码就是一个房间。**
|
|
189
310
|
|
|
@@ -228,7 +349,7 @@ dsh plugin --profile desktop remove dsh-crossterm
|
|
|
228
349
|
|
|
229
350
|
成员心跳超过 `offlineAfterMs × 3`(默认 60 秒)没刷新,会被自动从链上摘掉,避免对着早就关掉的终端一直投。
|
|
230
351
|
|
|
231
|
-
##
|
|
352
|
+
## 六、配置
|
|
232
353
|
|
|
233
354
|
配置文件:`<DSH_HOME>\crossterm\config.json`(首次运行自动生成),也可以用 `term_config` 工具改。
|
|
234
355
|
|
|
@@ -324,7 +445,7 @@ dsh/
|
|
|
324
445
|
不想要的 `term_inbox action="drop"`。`config.json` 是同一个 DSH_HOME 共享的,所以 `term_config autoDeliver=false` 会写进共享配置;
|
|
325
446
|
只有调用它的那个进程立即生效,别的终端要等下次重启或自己再调一次 `term_config`。
|
|
326
447
|
|
|
327
|
-
##
|
|
448
|
+
## 七、工作原理(排错时看这里)
|
|
328
449
|
|
|
329
450
|
```
|
|
330
451
|
<busDir>\
|
|
@@ -360,7 +481,7 @@ dsh/
|
|
|
360
481
|
核对请用 `[System.IO.File]::ReadAllText($p,[System.Text.Encoding]::UTF8)`。
|
|
361
482
|
- 子代理(subagent)会话不接受直接投递,需要投给它所属的 root 会话。
|
|
362
483
|
|
|
363
|
-
##
|
|
484
|
+
## 八、安全边界
|
|
364
485
|
|
|
365
486
|
- 只在本机/共享目录内工作,不联网、不连云端,不依赖任何外部服务。
|
|
366
487
|
- 收到消息后**会作为用户消息进入会话**,agent 会据此行动——所以总线目录只应放在你信任的位置;
|
|
@@ -369,7 +490,7 @@ dsh/
|
|
|
369
490
|
- **谁能改这个插件**:主维护会话 = 桌面端那条长会话(`session-6ef6da30-…`)。其他会话(QQ 会话、别的终端/窗口)
|
|
370
491
|
默认只读,可协助测试与报 bug;**要改代码必须经用户明确点名允许**(见工作区 `AGENTS.md` 第 7 条)。
|
|
371
492
|
|
|
372
|
-
##
|
|
493
|
+
## 九、开发自测
|
|
373
494
|
|
|
374
495
|
```powershell
|
|
375
496
|
$node = 'C:\Users\Administrator\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
|
|
@@ -381,7 +502,7 @@ $node = 'C:\Users\Administrator\.dsh\dsh-runtimes\dsh-primary-runtime\dependenci
|
|
|
381
502
|
`test/register.mjs` + `test/resolve-hooks.mjs` 把该包解析到本地替身 `test/dsh-tools-stub.mjs`。
|
|
382
503
|
**不要在插件目录里放假的 `node_modules/@deepseek-ai/dsh-tools`**——链接安装后那会盖掉宿主的真实实现。
|
|
383
504
|
|
|
384
|
-
##
|
|
505
|
+
## 十、两终端实测
|
|
385
506
|
|
|
386
507
|
### 1. 起第二个终端
|
|
387
508
|
|
|
@@ -470,7 +591,7 @@ offline-send.log target nodeId = ts-01-a52535
|
|
|
470
591
|
是把消息**交给 B 去投**,而不是 A 直接写 B 的会话。
|
|
471
592
|
- 两个实例同时开着同一会话时,以最后持有写锁的那个为准;跨终端沟通建议发到对方**当前活跃会话**。
|
|
472
593
|
|
|
473
|
-
##
|
|
594
|
+
## 十一、故障排查:`[exit code: 3221225794]`
|
|
474
595
|
|
|
475
596
|
**现象**:某个会话里所有 `pwsh` 命令都返回空输出 + `[exit code: 3221225794]`(`0xC0000142`),
|
|
476
597
|
连 `Write-Output "alive"`、`$PSVersionTable` 都起不来;但同一个进程里的 `read`/`glob`/`term_*` 全都正常。
|
package/lib/bus.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -455,10 +455,21 @@ class CrossTerm {
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
/** 邮件清单:只给主题 + 正文路径 + 附件清单,正文不内联 */
|
|
458
|
-
renderMailList(views, landed = []) {
|
|
458
|
+
renderMailList(views, landed = [], targetSessionId) {
|
|
459
459
|
const total = views.length
|
|
460
|
-
//
|
|
461
|
-
const
|
|
460
|
+
// 四档来源:云端中转=跨设备;别的 DSH 进程=跨终端;本终端但工作区不同=跨工作区;本终端同工作区=跨会话
|
|
461
|
+
const selfNode = (view) => (view.from?.nodeId || '') === this.nodeId
|
|
462
|
+
let label
|
|
463
|
+
if (views.some((view) => view.relay)) label = '跨设备邮件'
|
|
464
|
+
else if (!views.every(selfNode)) label = '跨终端邮件'
|
|
465
|
+
else {
|
|
466
|
+
const here = String((targetSessionId && this.cwdOf(targetSessionId)) || '').toLowerCase()
|
|
467
|
+
const acrossWorkspace = views.some((view) => {
|
|
468
|
+
const ws = String(view.from?.workspace || '').trim().toLowerCase()
|
|
469
|
+
return Boolean(here && ws && ws !== here)
|
|
470
|
+
})
|
|
471
|
+
label = acrossWorkspace ? '跨工作区邮件' : '跨会话邮件'
|
|
472
|
+
}
|
|
462
473
|
const lines = total === 1
|
|
463
474
|
? [`[${label}] 收到 1 封`]
|
|
464
475
|
: [`[${label}] 本批 ${total} 封(每封都是独立的一封;先把这 ${total} 封全部 read 完,再统一回一封)`]
|
|
@@ -472,7 +483,10 @@ class CrossTerm {
|
|
|
472
483
|
if (view.chain) lines.push(`特征码:${view.chain}`)
|
|
473
484
|
lines.push(`终端:${from.name || '未知终端'}${from.nodeId ? ` (${from.nodeId})` : ''}`)
|
|
474
485
|
if (from.workspace) lines.push(`工作区:${from.workspace}`)
|
|
475
|
-
|
|
486
|
+
// 这一行写的是「发件人」(发信的会话),不是收件会话——以前标题是「会话:」,常被误读成收件方
|
|
487
|
+
const senderSession = from.sessionLabel || (from.sessionId ? this.labelOf(from.sessionId) : '')
|
|
488
|
+
if (senderSession) lines.push(`发件人:${senderSession}`)
|
|
489
|
+
if (view.relay) lines.push('经由:云端中转站')
|
|
476
490
|
lines.push(`主题:${view.subject}`)
|
|
477
491
|
if (view.mail) lines.push(`正文:${view.mail}(用 read 工具打开,${formatBytes(view.bytes)})`)
|
|
478
492
|
else lines.push('正文:见 term_inbox(落盘失败,正文仍在消息体里)')
|
|
@@ -498,8 +512,8 @@ class CrossTerm {
|
|
|
498
512
|
return lines.join('\n')
|
|
499
513
|
}
|
|
500
514
|
|
|
501
|
-
renderIncoming(msg, landed) {
|
|
502
|
-
return this.renderMailList([this.mailView(msg)], landed)
|
|
515
|
+
renderIncoming(msg, landed, targetSessionId) {
|
|
516
|
+
return this.renderMailList([this.mailView(msg)], landed, targetSessionId)
|
|
503
517
|
}
|
|
504
518
|
|
|
505
519
|
/**
|
|
@@ -527,10 +541,10 @@ class CrossTerm {
|
|
|
527
541
|
* 而不是一条一条堆成很多轮对话——信箱里攒着的消息也能一把清空。
|
|
528
542
|
*/
|
|
529
543
|
/** 成批渲染:同样只给邮件清单(主题 + 正文路径) */
|
|
530
|
-
renderBatch(entries) {
|
|
544
|
+
renderBatch(entries, targetSessionId) {
|
|
531
545
|
const views = entries.map((entry) => this.mailView(entry.msg))
|
|
532
546
|
const landed = entries.flatMap((entry) => (Array.isArray(entry.landed) ? entry.landed : []))
|
|
533
|
-
return this.renderMailList(views, landed)
|
|
547
|
+
return this.renderMailList(views, landed, targetSessionId)
|
|
534
548
|
}
|
|
535
549
|
|
|
536
550
|
/**
|
|
@@ -567,8 +581,8 @@ class CrossTerm {
|
|
|
567
581
|
entries.push({ msg, landed })
|
|
568
582
|
}
|
|
569
583
|
const text = entries.length === 1
|
|
570
|
-
? this.renderIncoming(entries[0].msg, entries[0].landed || [])
|
|
571
|
-
: this.renderBatch(entries)
|
|
584
|
+
? this.renderIncoming(entries[0].msg, entries[0].landed || [], targetId)
|
|
585
|
+
: this.renderBatch(entries, targetId)
|
|
572
586
|
try {
|
|
573
587
|
await this.promptSession(controller, { id: list[0].id }, targetId, text)
|
|
574
588
|
} catch (error) {
|
|
@@ -604,7 +618,7 @@ class CrossTerm {
|
|
|
604
618
|
return { ok: false, retry: true, reason: `会话 ${this.labelOf(targetId)} 在本终端没打开,先留在收件箱` }
|
|
605
619
|
}
|
|
606
620
|
const landed = await this.landFiles(msg, targetId)
|
|
607
|
-
const text = this.renderIncoming(msg, landed)
|
|
621
|
+
const text = this.renderIncoming(msg, landed, targetId)
|
|
608
622
|
await this.promptSession(controller, msg, targetId, text)
|
|
609
623
|
await this.bus.dropMsgFiles(msg.id)
|
|
610
624
|
return { ok: true, sessionId: targetId, files: landed }
|
|
@@ -793,7 +807,7 @@ class CrossTerm {
|
|
|
793
807
|
try { agent = this.ctx.agents?.get?.(sessionId) } catch { agent = undefined }
|
|
794
808
|
if (!agent) return { ok: false, reason: `会话 ${this.labelOf(sessionId)} 不在本终端` }
|
|
795
809
|
const landed = await this.landFiles(msg, sessionId)
|
|
796
|
-
const text = this.renderIncoming(msg, landed)
|
|
810
|
+
const text = this.renderIncoming(msg, landed, sessionId)
|
|
797
811
|
await this.promptSession(controller, msg, sessionId, text)
|
|
798
812
|
await this.bus.dropMsgFiles(msg.id)
|
|
799
813
|
return { ok: true, sessionId, files: landed }
|
|
@@ -833,20 +847,23 @@ class CrossTerm {
|
|
|
833
847
|
return { ok: false, reason: '本 Host 没有 sessionController.prompt,中转站只能留件' }
|
|
834
848
|
}
|
|
835
849
|
const from = envelope.from || {}
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
for (const f of landed) lines.push(`- ${f.local}(${formatBytes(f.bytes || 0)})`)
|
|
850
|
+
const at = Number(envelope.createdAt) || Date.now()
|
|
851
|
+
const msg = {
|
|
852
|
+
id: String(envelope.id || `relay-${at}`),
|
|
853
|
+
subject: envelope.subject || '',
|
|
854
|
+
text: envelope.text || '',
|
|
855
|
+
from,
|
|
856
|
+
createdAt: at,
|
|
844
857
|
}
|
|
858
|
+
// 走和本机邮件同一套渲染:这样云端来的信也带「跨设备邮件」标签、正文同样落盘、发件人写在标题行
|
|
859
|
+
const view = { ...this.mailView(msg), relay: true }
|
|
860
|
+
const landed = files.filter((f) => f && f.local)
|
|
845
861
|
const bad = files.filter((f) => !f || !f.local)
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (fallbackNote)
|
|
849
|
-
|
|
862
|
+
const extra = []
|
|
863
|
+
if (bad.length) extra.push(`(有 ${bad.length} 个附件没下载下来:${bad.map((f) => (f && f.name) || '未知').join('、')})`)
|
|
864
|
+
if (fallbackNote) extra.push(fallbackNote)
|
|
865
|
+
// 回复提示与「经由」已由 renderMailList() 统一给出,这里不再重复追加(0.7.6:去掉重复尾行)
|
|
866
|
+
const text = [this.renderMailList([view], landed), '', ...extra].join('\n')
|
|
850
867
|
const delivered = []
|
|
851
868
|
const failed = []
|
|
852
869
|
for (const target of targets) {
|
|
@@ -1762,7 +1779,7 @@ function buildTools(term, agent, part = 'all') {
|
|
|
1762
1779
|
if (m.chain) lines.push(` 特征码:${m.chain}`)
|
|
1763
1780
|
lines.push(` 终端:${m.from || '未知'}${m.fromNodeId ? ` (${m.fromNodeId})` : ''}`)
|
|
1764
1781
|
if (m.workspace) lines.push(` 工作区:${m.workspace}`)
|
|
1765
|
-
if (m.sessionId) lines.push(`
|
|
1782
|
+
if (m.sessionId) lines.push(` 发件人:${m.sessionLabel || term.labelOf(m.sessionId)}`)
|
|
1766
1783
|
lines.push(` 主题:${m.subject}${m.files.length ? `(${m.files.length} 个附件)` : ''}${m.silent ? ' [静默]' : ''}`)
|
|
1767
1784
|
if (m.mail) lines.push(` 正文:${m.mail}(read 查看)`)
|
|
1768
1785
|
if (m.lastError) lines.push(` 上次失败:${m.lastError}`)
|