@yuanchilin/dsh-mailbox 1.0.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 +21 -0
- package/README.md +69 -0
- package/bin/mailbox.mjs +183 -0
- package/cordis.patch.yml +12 -0
- package/lib/core.js +186 -0
- package/lib/index.js +199 -0
- package/lib/types/index.d.ts +44 -0
- package/package.json +60 -0
- package/skill/README.md +117 -0
- package/skill/SKILL.md +83 -0
- package/skill/examples/handlers.patch.ps1 +154 -0
- package/skill/legacy-mcp.config.json +14 -0
- package/skill/mailbox.config.json +11 -0
- package/skill/mailbox.mjs +344 -0
- package/skill/mailbox.ps1 +172 -0
- package/skill/mailbox.psm1 +270 -0
- package/skill/self-test.ps1 +105 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# mailbox.psm1 — 通用跨会话文件信箱 v1 (泛化自 mcp/RP 联调工具)
|
|
3
|
+
#
|
|
4
|
+
# 模型: N 个对等参与者, 每人一个信箱目录, 各写各的, 互读对方的。
|
|
5
|
+
# - layout=root (标准): 共享根目录 <root>/<id>/ 每人一个子目录
|
|
6
|
+
# - layout=dirs (兼容旧双目录): 显式 dirs 映射 { id -> 目录 }
|
|
7
|
+
# - 消息格式: { id, from, to, type, topic, payload, ts, reply_to }
|
|
8
|
+
# - 路由: to=<id> 定向 / to=all 广播 (写一份, 各人自取)
|
|
9
|
+
# - seen 去重: 每参与者独立 seen 文件 (默认 <outDir>/.seen.json)
|
|
10
|
+
#
|
|
11
|
+
# 用法:
|
|
12
|
+
# Import-Module <dir>/mailbox.psm1
|
|
13
|
+
# $cfg = Get-MailboxConfig # 配置解析 (env > 文件 > 默认)
|
|
14
|
+
# Send-Mailbox -Cfg $cfg -To "agent-b" -Topic "hello" -Payload @{x=1}
|
|
15
|
+
# $msgs = Recv-Mailbox -Cfg $cfg # 读取新消息 (自动更新 seen)
|
|
16
|
+
# Remove-MailboxMsg -Cfg $cfg -Id $msg.id -InInbox
|
|
17
|
+
# ============================================================================
|
|
18
|
+
|
|
19
|
+
Set-StrictMode -Version Latest
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# 配置解析: 默认值 < 环境变量 < 配置文件 (CLI 参数由调用方覆盖)
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
function Get-MailboxConfig {
|
|
25
|
+
param([string]$ConfigPath = "")
|
|
26
|
+
|
|
27
|
+
$cfg = @{
|
|
28
|
+
identity = ""
|
|
29
|
+
layout = "root" # root | dirs
|
|
30
|
+
root = ""
|
|
31
|
+
dirs = @{} # layout=dirs: { id -> 目录 }
|
|
32
|
+
participants = @() # layout=root 可选的显式参与者列表 (默认自动扫描)
|
|
33
|
+
intervalSec = 2
|
|
34
|
+
timeoutSec = 0
|
|
35
|
+
seenFile = "" # 默认 <outDir>/.seen.json
|
|
36
|
+
patchRoot = "" # 供示例补丁 handler 使用
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ($ConfigPath -eq "") { $ConfigPath = $env:MAILBOX_CONFIG }
|
|
40
|
+
if ($ConfigPath -eq "") { $ConfigPath = Join-Path $PSScriptRoot "mailbox.config.json" }
|
|
41
|
+
if (Test-Path -LiteralPath $ConfigPath) {
|
|
42
|
+
try {
|
|
43
|
+
$file = Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
|
|
44
|
+
foreach ($k in @($cfg.Keys)) {
|
|
45
|
+
if ($null -ne $file.$k) { $cfg[$k] = $file.$k }
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
Write-Warning "读取配置失败: $ConfigPath ($($_.Exception.Message))"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ($env:MAILBOX_ID) { $cfg.identity = $env:MAILBOX_ID }
|
|
53
|
+
if ($env:MAILBOX_ROOT) { $cfg.root = $env:MAILBOX_ROOT; $cfg.layout = "root" }
|
|
54
|
+
if ($env:MAILBOX_INTERVAL) { $cfg.intervalSec = [int]$env:MAILBOX_INTERVAL }
|
|
55
|
+
if ($env:MAILBOX_TIMEOUT) { $cfg.timeoutSec = [int]$env:MAILBOX_TIMEOUT }
|
|
56
|
+
|
|
57
|
+
# 规范化: JSON 解析出的 dirs 是 PSCustomObject, participants 可能是 $null/空数组
|
|
58
|
+
if ($cfg.dirs -and $cfg.dirs -isnot [System.Collections.IDictionary]) {
|
|
59
|
+
$h = @{}
|
|
60
|
+
foreach ($p in $cfg.dirs.PSObject.Properties) { $h[$p.Name] = [string]$p.Value }
|
|
61
|
+
$cfg.dirs = $h
|
|
62
|
+
}
|
|
63
|
+
if ($null -eq $cfg.dirs) { $cfg.dirs = @{} }
|
|
64
|
+
$cfg.participants = @(@($cfg.participants) | Where-Object { $_ })
|
|
65
|
+
|
|
66
|
+
return $cfg
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# 目录解析: 返回 @{ Out = <自己的目录>; In = @(<对方目录...>) }
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
function Resolve-MailboxDirs {
|
|
73
|
+
param($Cfg)
|
|
74
|
+
|
|
75
|
+
if ($Cfg.layout -eq "dirs") {
|
|
76
|
+
# dirs 可能是程序构造的 hashtable 或配置文件解析出的 PSCustomObject, 统一为 hashtable
|
|
77
|
+
$dirMap = @{}
|
|
78
|
+
if ($Cfg.dirs -is [System.Collections.IDictionary]) {
|
|
79
|
+
$dirMap = $Cfg.dirs
|
|
80
|
+
} elseif ($Cfg.dirs) {
|
|
81
|
+
foreach ($p in $Cfg.dirs.PSObject.Properties) { $dirMap[$p.Name] = [string]$p.Value }
|
|
82
|
+
}
|
|
83
|
+
if (-not $dirMap.ContainsKey($Cfg.identity)) {
|
|
84
|
+
throw "layout=dirs 但配置缺少 identity '$($Cfg.identity)' 的目录映射"
|
|
85
|
+
}
|
|
86
|
+
$out = $dirMap[$Cfg.identity]
|
|
87
|
+
$in = @()
|
|
88
|
+
foreach ($k in @($dirMap.Keys)) {
|
|
89
|
+
if ($k -ne $Cfg.identity) { $in += $dirMap[$k] }
|
|
90
|
+
}
|
|
91
|
+
return @{ Out = $out; In = @($in | Select-Object -Unique) }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# layout=root
|
|
95
|
+
if (-not $Cfg.root) { throw "layout=root 需要配置 root" }
|
|
96
|
+
$out = Join-Path $Cfg.root $Cfg.identity
|
|
97
|
+
$in = @()
|
|
98
|
+
$participants = @(@($Cfg.participants) | Where-Object { $_ })
|
|
99
|
+
if ($participants.Count -eq 0) {
|
|
100
|
+
$participants = @(Get-ChildItem -LiteralPath $Cfg.root -Directory -ErrorAction SilentlyContinue |
|
|
101
|
+
Select-Object -ExpandProperty Name)
|
|
102
|
+
}
|
|
103
|
+
foreach ($p in $participants) {
|
|
104
|
+
if ($p -ne $Cfg.identity -and $p -ne "") { $in += (Join-Path $Cfg.root $p) }
|
|
105
|
+
}
|
|
106
|
+
return @{ Out = $out; In = @($in | Select-Object -Unique) }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# seen 读写
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
function Get-MailboxSeenFile {
|
|
113
|
+
param($Cfg)
|
|
114
|
+
if ($Cfg.seenFile -ne "") { return $Cfg.seenFile }
|
|
115
|
+
return Join-Path (Resolve-MailboxDirs $Cfg).Out ".seen.json"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function Get-MailboxSeen {
|
|
119
|
+
param($Cfg)
|
|
120
|
+
$f = Get-MailboxSeenFile $Cfg
|
|
121
|
+
$seen = @()
|
|
122
|
+
if (Test-Path -LiteralPath $f) {
|
|
123
|
+
try { $seen = @((Get-Content -LiteralPath $f -Raw | ConvertFrom-Json)) } catch { $seen = @() }
|
|
124
|
+
}
|
|
125
|
+
# 逗号运算符: 防止单元素数组在函数输出边界被解包成裸字符串 (PowerShell 经典坑)
|
|
126
|
+
return ,$seen
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function Save-MailboxSeen {
|
|
130
|
+
param($Cfg, [string[]]$Seen)
|
|
131
|
+
$f = Get-MailboxSeenFile $Cfg
|
|
132
|
+
$dir = Split-Path $f -Parent
|
|
133
|
+
if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
134
|
+
# 参数形式 (-InputObject) 保证单元素也输出数组 JSON ["a"], 避免管道解包成 "a"
|
|
135
|
+
$unique = @($Seen | Select-Object -Unique)
|
|
136
|
+
ConvertTo-Json -InputObject $unique | Set-Content -LiteralPath $f -Encoding UTF8
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# 发送: 写到自己的目录 (对方读你的目录)
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
function Send-Mailbox {
|
|
143
|
+
param(
|
|
144
|
+
[Parameter(Mandatory)][string]$To, # 参与者 id 或 "all"
|
|
145
|
+
[ValidateSet("request","response","notify","reply")][string]$Type = "notify",
|
|
146
|
+
[string]$Topic = "",
|
|
147
|
+
$Payload = @{},
|
|
148
|
+
[string]$ReplyTo = "",
|
|
149
|
+
$Cfg
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
$dirs = Resolve-MailboxDirs $Cfg
|
|
153
|
+
if (-not (Test-Path -LiteralPath $dirs.Out)) { New-Item -ItemType Directory -Force -Path $dirs.Out | Out-Null }
|
|
154
|
+
|
|
155
|
+
$id = "$(Get-Date -Format 'yyyyMMddHHmmss')-$(Get-Random -Minimum 1000 -Maximum 9999)-$([guid]::NewGuid().ToString('N').Substring(0,4))"
|
|
156
|
+
$msg = [ordered]@{
|
|
157
|
+
id = $id
|
|
158
|
+
from = $Cfg.identity
|
|
159
|
+
to = $To
|
|
160
|
+
type = $Type
|
|
161
|
+
topic = $Topic
|
|
162
|
+
payload = $Payload
|
|
163
|
+
ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
164
|
+
reply_to = $ReplyTo
|
|
165
|
+
}
|
|
166
|
+
$file = Join-Path $dirs.Out "msg_$id.json"
|
|
167
|
+
$msg | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $file -Encoding UTF8
|
|
168
|
+
return $id
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# 接收: 扫描所有对方目录, 取 to=自己 或 to=all 且未 seen 的消息
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
function Recv-Mailbox {
|
|
175
|
+
param(
|
|
176
|
+
$Cfg,
|
|
177
|
+
[switch]$KeepSeen # 置位时只读不更新 seen (wait 场景由调用方决定)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Get-MailboxSeen 已用逗号保证返回扁平数组, 这里不要再 @() 包装 (会变成嵌套数组)
|
|
181
|
+
$seen = Get-MailboxSeen $Cfg
|
|
182
|
+
$new = @()
|
|
183
|
+
foreach ($dir in (Resolve-MailboxDirs $Cfg).In) {
|
|
184
|
+
if (-not (Test-Path -LiteralPath $dir)) { continue }
|
|
185
|
+
Get-ChildItem -LiteralPath $dir -Filter "msg_*.json" -ErrorAction SilentlyContinue |
|
|
186
|
+
Sort-Object Name |
|
|
187
|
+
ForEach-Object {
|
|
188
|
+
try {
|
|
189
|
+
$m = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json
|
|
190
|
+
if (($m.to -eq $Cfg.identity -or $m.to -eq "all") -and ($seen -notcontains $m.id)) {
|
|
191
|
+
$new += $m
|
|
192
|
+
if (-not $KeepSeen) { $seen += $m.id }
|
|
193
|
+
}
|
|
194
|
+
} catch { }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (-not $KeepSeen) { Save-MailboxSeen $Cfg $seen }
|
|
198
|
+
return $new
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# 删除消息: -InInbox 删对方目录(已处理), 默认删自己的目录(已发送)
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
function Remove-MailboxMsg {
|
|
205
|
+
param(
|
|
206
|
+
[Parameter(Mandatory)][string]$Id,
|
|
207
|
+
$Cfg,
|
|
208
|
+
[switch]$InInbox
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
$dirs = Resolve-MailboxDirs $Cfg
|
|
212
|
+
$dirsToScan = if ($InInbox) { $dirs.In } else { @($dirs.Out) }
|
|
213
|
+
foreach ($dir in $dirsToScan) {
|
|
214
|
+
if (-not (Test-Path -LiteralPath $dir)) { continue }
|
|
215
|
+
Get-ChildItem -LiteralPath $dir -Filter "msg_*.json" -ErrorAction SilentlyContinue |
|
|
216
|
+
ForEach-Object {
|
|
217
|
+
try {
|
|
218
|
+
$m = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json
|
|
219
|
+
if ($m.id -eq $Id) { Remove-Item -LiteralPath $_.FullName -Force; return }
|
|
220
|
+
} catch { }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# TTL 清理: 删除自己 OutDir 中超过 TtlHours 的已发送消息 (收方应已读过)
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
function Clear-MailboxTTL {
|
|
229
|
+
param($Cfg, [int]$TtlHours = 24, [switch]$DryRun)
|
|
230
|
+
|
|
231
|
+
$dirs = Resolve-MailboxDirs $Cfg
|
|
232
|
+
if (-not (Test-Path -LiteralPath $dirs.Out)) { return 0 }
|
|
233
|
+
$cutoff = (Get-Date).AddHours(-$TtlHours)
|
|
234
|
+
$removed = 0
|
|
235
|
+
Get-ChildItem -LiteralPath $dirs.Out -Filter "msg_*.json" -ErrorAction SilentlyContinue |
|
|
236
|
+
ForEach-Object {
|
|
237
|
+
if ($_.LastWriteTime -lt $cutoff) {
|
|
238
|
+
if (-not $DryRun) { Remove-Item -LiteralPath $_.FullName -Force }
|
|
239
|
+
$removed++
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return $removed
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# 状态: 身份/目录/各参与者消息数/未读数
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
function Get-MailboxStatus {
|
|
249
|
+
param($Cfg)
|
|
250
|
+
|
|
251
|
+
$dirs = Resolve-MailboxDirs $Cfg
|
|
252
|
+
$seen = Get-MailboxSeen $Cfg
|
|
253
|
+
$inInfo = @()
|
|
254
|
+
foreach ($dir in $dirs.In) {
|
|
255
|
+
$count = if (Test-Path -LiteralPath $dir) {
|
|
256
|
+
@(Get-ChildItem -LiteralPath $dir -Filter "msg_*.json" -ErrorAction SilentlyContinue).Count
|
|
257
|
+
} else { 0 }
|
|
258
|
+
$inInfo += [pscustomobject]@{ Dir = $dir; MsgCount = $count }
|
|
259
|
+
}
|
|
260
|
+
return [pscustomobject]@{
|
|
261
|
+
identity = $Cfg.identity
|
|
262
|
+
layout = $Cfg.layout
|
|
263
|
+
outDir = $dirs.Out
|
|
264
|
+
outCount = @(Get-ChildItem -LiteralPath $dirs.Out -Filter "msg_*.json" -ErrorAction SilentlyContinue).Count
|
|
265
|
+
seen = @($seen).Count
|
|
266
|
+
inboxes = $inInfo
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Export-ModuleMember -Function Get-MailboxConfig, Resolve-MailboxDirs, Send-Mailbox, Recv-Mailbox, Remove-MailboxMsg, Clear-MailboxTTL, Get-MailboxStatus
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# self-test.ps1 — mailbox 泛化工具自测
|
|
3
|
+
# 覆盖: 往返 / 广播 / wait 唤醒 / TTL 清理 / pwsh↔node 互通 / 旧 dirs 布局 / seen 去重
|
|
4
|
+
# 用法: .\self-test.ps1 (全部通过 exit 0)
|
|
5
|
+
# ============================================================================
|
|
6
|
+
$ErrorActionPreference = "Stop"
|
|
7
|
+
|
|
8
|
+
$toolDir = $PSScriptRoot
|
|
9
|
+
$testRoot = Join-Path $toolDir ".selftest"
|
|
10
|
+
if (Test-Path -LiteralPath $testRoot) { Remove-Item -Recurse -Force -LiteralPath $testRoot }
|
|
11
|
+
New-Item -ItemType Directory -Force -Path $testRoot | Out-Null
|
|
12
|
+
|
|
13
|
+
$pass = 0; $fail = 0
|
|
14
|
+
function Check($name, $cond) {
|
|
15
|
+
if ($cond) { Write-Host "PASS $name"; $script:pass++ }
|
|
16
|
+
else { Write-Host "FAIL $name"; $script:fail++ }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Import-Module (Join-Path $toolDir "mailbox.psm1") -Force
|
|
20
|
+
|
|
21
|
+
# ---- 配置: root 布局, 三参与者 agent-a / agent-b / rp ----
|
|
22
|
+
$cfgA = Get-MailboxConfig; $cfgA.identity = "agent-a"; $cfgA.root = $testRoot; $cfgA.layout = "root"
|
|
23
|
+
$cfgB = Get-MailboxConfig; $cfgB.identity = "agent-b"; $cfgB.root = $testRoot; $cfgB.layout = "root"
|
|
24
|
+
$cfgR = Get-MailboxConfig; $cfgR.identity = "rp"; $cfgR.root = $testRoot; $cfgR.layout = "root"
|
|
25
|
+
New-Item -ItemType Directory -Force -Path (Join-Path $testRoot "rp") | Out-Null # 让自动扫描包含 rp
|
|
26
|
+
|
|
27
|
+
# 供 CLI 使用的配置文件
|
|
28
|
+
$cfgAFile = Join-Path $testRoot "agent-a.config.json"
|
|
29
|
+
$cfgBFile = Join-Path $testRoot "agent-b.config.json"
|
|
30
|
+
@{ identity="agent-a"; layout="root"; root=$testRoot; dirs=@{}; participants=@(); intervalSec=1; timeoutSec=0; seenFile=""; patchRoot="" } | ConvertTo-Json | Set-Content $cfgAFile -Encoding UTF8
|
|
31
|
+
@{ identity="agent-b"; layout="root"; root=$testRoot; dirs=@{}; participants=@(); intervalSec=1; timeoutSec=0; seenFile=""; patchRoot="" } | ConvertTo-Json | Set-Content $cfgBFile -Encoding UTF8
|
|
32
|
+
|
|
33
|
+
$ps1 = Join-Path $toolDir "mailbox.ps1"
|
|
34
|
+
$mjs = Join-Path $toolDir "mailbox.mjs"
|
|
35
|
+
|
|
36
|
+
# ================= 1. 模块级: 往返 + seen 去重 =================
|
|
37
|
+
$id1 = Send-Mailbox -Cfg $cfgA -To "agent-b" -Topic "hello" -Payload @{ x = 1 }
|
|
38
|
+
$msgs = @(Recv-Mailbox -Cfg $cfgB)
|
|
39
|
+
Check "往返: B 收到 A 的消息" ($msgs.Count -eq 1 -and $msgs[0].from -eq "agent-a" -and $msgs[0].topic -eq "hello")
|
|
40
|
+
Check "往返: payload 正确" ($msgs[0].payload.x -eq 1)
|
|
41
|
+
$msgs2 = @(Recv-Mailbox -Cfg $cfgB)
|
|
42
|
+
Check "seen 去重: 第二次 recv 无新消息" ($msgs2.Count -eq 0)
|
|
43
|
+
|
|
44
|
+
# ================= 2. 广播 to=all =================
|
|
45
|
+
Send-Mailbox -Cfg $cfgR -To "all" -Topic "broadcast" -Payload @{ n = 42 } | Out-Null
|
|
46
|
+
$b1 = @(Recv-Mailbox -Cfg $cfgA)
|
|
47
|
+
$b2 = @(Recv-Mailbox -Cfg $cfgB)
|
|
48
|
+
Check "广播: A 和 B 都收到 to=all" ($b1.Count -eq 1 -and $b2.Count -eq 1 -and $b1[0].topic -eq "broadcast")
|
|
49
|
+
|
|
50
|
+
# ================= 3. wait 唤醒 (pwsh CLI) =================
|
|
51
|
+
Send-Mailbox -Cfg $cfgA -To "agent-b" -Topic "wake" | Out-Null
|
|
52
|
+
$waitOut = & $ps1 wait -Config $cfgBFile -Timeout 5 6>&1 2>&1 | Out-String
|
|
53
|
+
Check "wait: 有新消息时输出唤醒标记" ($waitOut -match "WAKE-UP")
|
|
54
|
+
|
|
55
|
+
# ================= 4. TTL 清理 =================
|
|
56
|
+
$idOld = Send-Mailbox -Cfg $cfgA -To "agent-b" -Topic "old" | Out-Null
|
|
57
|
+
$oldFile = Get-ChildItem -LiteralPath (Join-Path $testRoot "agent-a") -Filter "msg_*.json" | Sort-Object LastWriteTime | Select-Object -Last 1
|
|
58
|
+
$oldFile.LastWriteTime = (Get-Date).AddDays(-2)
|
|
59
|
+
$dry = Clear-MailboxTTL -Cfg $cfgA -TtlHours 24 -DryRun
|
|
60
|
+
Check "TTL dry-run: 计数 1" ($dry -eq 1)
|
|
61
|
+
$real = Clear-MailboxTTL -Cfg $cfgA -TtlHours 24
|
|
62
|
+
Check "TTL: 实际删除 1" ($real -eq 1)
|
|
63
|
+
Check "TTL: 文件已消失" (-not (Test-Path -LiteralPath $oldFile.FullName))
|
|
64
|
+
|
|
65
|
+
# ================= 5. pwsh ↔ node 互通 =================
|
|
66
|
+
& node $mjs send --config $cfgBFile --to agent-a --topic from-node --payload '{"n":2}' 2>&1 | Out-Null
|
|
67
|
+
$recvA = @(Recv-Mailbox -Cfg $cfgA)
|
|
68
|
+
Check "node 发 → pwsh 收" ($recvA.Count -eq 1 -and $recvA[0].from -eq "agent-b" -and $recvA[0].topic -eq "from-node" -and $recvA[0].payload.n -eq 2)
|
|
69
|
+
|
|
70
|
+
Send-Mailbox -Cfg $cfgA -To "agent-b" -Topic "to-node" -Payload @{ k = "v" } | Out-Null
|
|
71
|
+
$nodeRecv = & node $mjs recv --config $cfgBFile --format json 2>&1 | Out-String
|
|
72
|
+
Check "pwsh 发 → node 收" ($nodeRecv -match "to-node")
|
|
73
|
+
|
|
74
|
+
$nodeStatus = & node $mjs status --config $cfgAFile 2>&1 | Out-String
|
|
75
|
+
Check "node status 正常" ($nodeStatus -match "agent-a")
|
|
76
|
+
|
|
77
|
+
# ================= 6. 旧 dirs 布局兼容 (CLI) =================
|
|
78
|
+
$legacyRoot = Join-Path $testRoot "legacy"
|
|
79
|
+
New-Item -ItemType Directory -Force -Path (Join-Path $legacyRoot "mcp") | Out-Null
|
|
80
|
+
New-Item -ItemType Directory -Force -Path (Join-Path $legacyRoot "rp") | Out-Null
|
|
81
|
+
$legacyCfgM = Join-Path $testRoot "legacy-mcp.config.json"
|
|
82
|
+
$legacyCfgR = Join-Path $testRoot "legacy-rp.config.json"
|
|
83
|
+
@{ identity="mcp"; layout="dirs"; root=""; dirs=@{ mcp=(Join-Path $legacyRoot "mcp"); rp=(Join-Path $legacyRoot "rp") }; participants=@(); intervalSec=1; timeoutSec=0; seenFile=""; patchRoot="" } | ConvertTo-Json | Set-Content $legacyCfgM -Encoding UTF8
|
|
84
|
+
@{ identity="rp"; layout="dirs"; root=""; dirs=@{ mcp=(Join-Path $legacyRoot "mcp"); rp=(Join-Path $legacyRoot "rp") }; participants=@(); intervalSec=1; timeoutSec=0; seenFile=""; patchRoot="" } | ConvertTo-Json | Set-Content $legacyCfgR -Encoding UTF8
|
|
85
|
+
& $ps1 send -Config $legacyCfgM -To "rp" -Topic "legacy-test" -Payload '{"L":1}' 2>&1 | Out-Null
|
|
86
|
+
$legacyRecv = & node $mjs recv --config $legacyCfgR --format json 2>&1 | Out-String
|
|
87
|
+
Check "旧 dirs 布局: mcp 发 → rp (node) 收" ($legacyRecv -match "legacy-test")
|
|
88
|
+
Check "旧 dirs 布局: 不创建幽灵目录" (-not (Test-Path -LiteralPath (Join-Path $testRoot "legacy\mcp\.seen.json")))
|
|
89
|
+
|
|
90
|
+
# ================= 7. CLI status / recv 空 =================
|
|
91
|
+
$st = & $ps1 status -Config $cfgAFile 6>&1 2>&1 | Out-String
|
|
92
|
+
Check "pwsh status 正常" ($st -match "agent-a")
|
|
93
|
+
$empty = & $ps1 recv -Config $cfgBFile 6>&1 2>&1 | Out-String
|
|
94
|
+
Check "recv 无新消息提示" ($empty -match "无新消息")
|
|
95
|
+
|
|
96
|
+
# ================= 8. 泛化代码无硬编码路径 =================
|
|
97
|
+
$hardcoded = Select-String -Path (Join-Path $toolDir "mailbox.psm1"),(Join-Path $toolDir "mailbox.ps1"),(Join-Path $toolDir "mailbox.mjs") -Pattern 'D:\\Downloads|Agent\\Soc|Agent\\RP' -ErrorAction SilentlyContinue
|
|
98
|
+
Check "泛化代码无硬编码路径 (Soc/RP)" ($null -eq $hardcoded)
|
|
99
|
+
|
|
100
|
+
# ================= 清理 =================
|
|
101
|
+
Remove-Item -Recurse -Force -LiteralPath $testRoot
|
|
102
|
+
Write-Host ""
|
|
103
|
+
Write-Host "结果: PASS=$pass FAIL=$fail"
|
|
104
|
+
if ($fail -gt 0) { exit 1 }
|
|
105
|
+
exit 0
|