@yuanchilin/dsh-mailbox 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 +21 -4
- package/bin/mailbox.mjs +25 -2
- package/lib/client.js +16 -5
- package/lib/command.js +14 -7
- package/lib/core.js +251 -21
- package/lib/index.js +61 -6
- package/lib/types/index.d.ts +13 -1
- package/lib/watcher.js +87 -28
- package/package.json +1 -1
- package/skill/README.md +16 -0
- package/skill/SKILL.md +16 -0
- package/skill/mailbox.mjs +106 -14
- package/skill/mailbox.psm1 +93 -14
package/skill/mailbox.psm1
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# - layout=dirs (兼容旧双目录): 显式 dirs 映射 { id -> 目录 }
|
|
7
7
|
# - 消息格式: { id, from, to, type, topic, payload, ts, reply_to }
|
|
8
8
|
# - 路由: to=<id> 定向 / to=all 广播 (写一份, 各人自取)
|
|
9
|
-
# - seen 去重: 每参与者独立 seen
|
|
9
|
+
# - seen 去重: 每参与者独立 seen 目录 (默认 <outDir>/.seen/, 每消息一个 .seen 标记, 原子写)
|
|
10
10
|
#
|
|
11
11
|
# 用法:
|
|
12
12
|
# Import-Module <dir>/mailbox.psm1
|
|
@@ -32,7 +32,7 @@ function Get-MailboxConfig {
|
|
|
32
32
|
participants = @() # layout=root 可选的显式参与者列表 (默认自动扫描)
|
|
33
33
|
intervalSec = 2
|
|
34
34
|
timeoutSec = 0
|
|
35
|
-
seenFile = "" #
|
|
35
|
+
seenFile = "" # 显式时用单文件兼容; 默认目录化 <outDir>/.seen/
|
|
36
36
|
patchRoot = "" # 供示例补丁 handler 使用
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -124,12 +124,38 @@ function Get-MailboxSeenFile {
|
|
|
124
124
|
return Join-Path (Resolve-MailboxDirs $Cfg).Out ".seen.json"
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
# 目录化 seen 的存储目录; 显式 seenFile 时返回 "" (走单文件兼容)
|
|
128
|
+
function Get-MailboxSeenDir {
|
|
129
|
+
param($Cfg)
|
|
130
|
+
if ($Cfg.seenFile -ne "") { return "" }
|
|
131
|
+
return Join-Path (Resolve-MailboxDirs $Cfg).Out ".seen"
|
|
132
|
+
}
|
|
133
|
+
|
|
127
134
|
function Get-MailboxSeen {
|
|
128
135
|
param($Cfg)
|
|
129
|
-
$
|
|
136
|
+
$dir = Get-MailboxSeenDir $Cfg
|
|
137
|
+
if ($dir -eq "") {
|
|
138
|
+
$f = Get-MailboxSeenFile $Cfg
|
|
139
|
+
$seen = @()
|
|
140
|
+
if (Test-Path -LiteralPath $f) {
|
|
141
|
+
try { $seen = @((Get-Content -LiteralPath $f -Raw | ConvertFrom-Json)) } catch { $seen = @() }
|
|
142
|
+
}
|
|
143
|
+
return ,$seen
|
|
144
|
+
}
|
|
145
|
+
# 目录模式: 先迁移旧单文件 .seen.json (幂等)
|
|
146
|
+
$legacy = Get-MailboxSeenFile $Cfg
|
|
147
|
+
if ((Test-Path -LiteralPath $legacy) -and -not (Test-Path -LiteralPath $dir)) {
|
|
148
|
+
try {
|
|
149
|
+
$old = @((Get-Content -LiteralPath $legacy -Raw | ConvertFrom-Json))
|
|
150
|
+
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
151
|
+
foreach ($id in $old) { if ($id) { Set-Content -LiteralPath (Join-Path $dir "$id.seen") -Value "" -Encoding UTF8 } }
|
|
152
|
+
Remove-Item -LiteralPath $legacy -Force -ErrorAction SilentlyContinue
|
|
153
|
+
} catch { }
|
|
154
|
+
}
|
|
130
155
|
$seen = @()
|
|
131
|
-
if (Test-Path -LiteralPath $
|
|
132
|
-
|
|
156
|
+
if (Test-Path -LiteralPath $dir) {
|
|
157
|
+
$seen = @(Get-ChildItem -LiteralPath $dir -Filter "*.seen" -ErrorAction SilentlyContinue |
|
|
158
|
+
ForEach-Object { $_.BaseName })
|
|
133
159
|
}
|
|
134
160
|
# 逗号运算符: 防止单元素数组在函数输出边界被解包成裸字符串 (PowerShell 经典坑)
|
|
135
161
|
return ,$seen
|
|
@@ -137,12 +163,61 @@ function Get-MailboxSeen {
|
|
|
137
163
|
|
|
138
164
|
function Save-MailboxSeen {
|
|
139
165
|
param($Cfg, [string[]]$Seen)
|
|
140
|
-
$
|
|
141
|
-
$dir
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
$dir = Get-MailboxSeenDir $Cfg
|
|
167
|
+
if ($dir -eq "") {
|
|
168
|
+
$f = Get-MailboxSeenFile $Cfg
|
|
169
|
+
$d = Split-Path $f -Parent
|
|
170
|
+
if ($d -and -not (Test-Path -LiteralPath $d)) { New-Item -ItemType Directory -Force -Path $d | Out-Null }
|
|
171
|
+
# 参数形式 (-InputObject) 保证单元素也输出数组 JSON ["a"], 避免管道解包成 "a"
|
|
172
|
+
$unique = @($Seen | Select-Object -Unique)
|
|
173
|
+
ConvertTo-Json -InputObject $unique | Set-Content -LiteralPath $f -Encoding UTF8
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
# 目录模式: 逐条原子标记 (并发安全, 不互不覆盖)
|
|
177
|
+
foreach ($id in ($Seen | Select-Object -Unique)) { if ($id) { Add-MailboxSeenMark $Cfg $id } }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# 目录化 seen: 每条已读消息一个 `.seen` 标记文件, 临时文件 + Move 原子提交
|
|
181
|
+
function Add-MailboxSeenMark {
|
|
182
|
+
param($Cfg, [string]$Id)
|
|
183
|
+
if (-not $Id) { return }
|
|
184
|
+
$dir = Get-MailboxSeenDir $Cfg
|
|
185
|
+
if ($dir -eq "") {
|
|
186
|
+
# 单文件兼容: 读改写
|
|
187
|
+
$seen = Get-MailboxSeen $Cfg
|
|
188
|
+
if ($seen -notcontains $Id) { Save-MailboxSeen $Cfg @($seen + $Id) }
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
192
|
+
$mark = Join-Path $dir ("$Id.seen")
|
|
193
|
+
if (Test-Path -LiteralPath $mark) { return }
|
|
194
|
+
$tmp = Join-Path $dir (".tmp-$Id")
|
|
195
|
+
Set-Content -LiteralPath $tmp -Value "" -Encoding UTF8
|
|
196
|
+
Move-Item -LiteralPath $tmp -Destination $mark -Force
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# 回执协议 (ack): request 被消费自动回 delivered; 处理完回 done/error (幂等防重)
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
function Add-MailboxAck {
|
|
203
|
+
param($Cfg, $Request, [string]$Status, $Extra = @{})
|
|
204
|
+
if (-not $Request -or -not $Request.id -or -not $Request.from) { return $false }
|
|
205
|
+
if ($Request.from -eq $Cfg.identity) { return $false }
|
|
206
|
+
if ($Status -notin @("delivered", "processing", "done", "error")) { return $false }
|
|
207
|
+
# 幂等: 自己发件目录已有同 requestId+status 的回执则跳过
|
|
208
|
+
$dirs = Resolve-MailboxDirs $Cfg
|
|
209
|
+
if (Test-Path -LiteralPath $dirs.Out) {
|
|
210
|
+
$existing = Get-ChildItem -LiteralPath $dirs.Out -Filter "msg_*.json" -ErrorAction SilentlyContinue |
|
|
211
|
+
ForEach-Object {
|
|
212
|
+
try { Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json } catch { $null }
|
|
213
|
+
} |
|
|
214
|
+
Where-Object { $_ -and $_.reply_to -eq $Request.id -and $_.payload.status -eq $Status }
|
|
215
|
+
if ($existing) { return $false }
|
|
216
|
+
}
|
|
217
|
+
$payload = @{ status = $Status; requestId = $Request.id }
|
|
218
|
+
foreach ($k in $Extra.Keys) { $payload[$k] = $Extra[$k] }
|
|
219
|
+
$id = Send-Mailbox -To $Request.from -Type reply -Topic "status" -Payload $payload -ReplyTo $Request.id -Cfg $Cfg
|
|
220
|
+
return ($id -ne $null -and $id -ne "")
|
|
146
221
|
}
|
|
147
222
|
|
|
148
223
|
# ---------------------------------------------------------------------------
|
|
@@ -183,7 +258,8 @@ function Send-Mailbox {
|
|
|
183
258
|
function Recv-Mailbox {
|
|
184
259
|
param(
|
|
185
260
|
$Cfg,
|
|
186
|
-
[switch]$KeepSeen # 置位时只读不更新 seen (wait 场景由调用方决定)
|
|
261
|
+
[switch]$KeepSeen, # 置位时只读不更新 seen (wait 场景由调用方决定)
|
|
262
|
+
[switch]$NoAck # 置位时不自动回执 delivered (纯只读监控)
|
|
187
263
|
)
|
|
188
264
|
|
|
189
265
|
# Get-MailboxSeen 已用逗号保证返回扁平数组, 这里不要再 @() 包装 (会变成嵌套数组)
|
|
@@ -198,12 +274,15 @@ function Recv-Mailbox {
|
|
|
198
274
|
$m = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json
|
|
199
275
|
if (($m.to -eq $Cfg.identity -or $m.to -eq "all") -and ($seen -notcontains $m.id)) {
|
|
200
276
|
$new += $m
|
|
201
|
-
if (-not $KeepSeen) { $
|
|
277
|
+
if (-not $KeepSeen) { Add-MailboxSeenMark $Cfg $m.id }
|
|
202
278
|
}
|
|
203
279
|
} catch { }
|
|
204
280
|
}
|
|
205
281
|
}
|
|
206
|
-
|
|
282
|
+
# 回执协议: request 型消息自动回 delivered (幂等防重)
|
|
283
|
+
if (-not $KeepSeen -and -not $NoAck) {
|
|
284
|
+
foreach ($m in $new) { if ($m.type -eq "request") { Add-MailboxAck $Cfg $m "delivered" | Out-Null } }
|
|
285
|
+
}
|
|
207
286
|
return $new
|
|
208
287
|
}
|
|
209
288
|
|