@clawos-dev/clawd 0.2.112-beta.223.6ddebf5 → 0.2.112-beta.225.5330d3a
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/dist/cli.cjs +645 -482
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -133,7 +133,7 @@ var init_methods = __esm({
|
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
135
|
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
|
|
136
|
-
//
|
|
136
|
+
// device:connect 已完整替代(中心 server 签发自包含 connect token)。capability:list / capability:delete
|
|
137
137
|
// 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
|
|
138
138
|
"capability:list",
|
|
139
139
|
"capability:delete",
|
|
@@ -147,7 +147,7 @@ var init_methods = __esm({
|
|
|
147
147
|
// ---- contact:* (联系人列表) ----
|
|
148
148
|
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名为 contact:*——Phase 2 后
|
|
149
149
|
// 联系人身份由中心 server introspect 背书,不再是"对方颁发的 capability"。
|
|
150
|
-
// 写入路径:
|
|
150
|
+
// 写入路径:device:connect 落 store + 自动反向(决策 #11);list / remove 作联系人列表读删。
|
|
151
151
|
"contact:list",
|
|
152
152
|
"contact:remove",
|
|
153
153
|
// ---- whoami (v2 capability platform) ----
|
|
@@ -181,11 +181,11 @@ var init_methods = __esm({
|
|
|
181
181
|
"auth:login:start",
|
|
182
182
|
"auth:getIdentity",
|
|
183
183
|
"auth:logout",
|
|
184
|
-
// ----
|
|
185
|
-
// owner-only
|
|
186
|
-
// 中央字面量见 protocol/feishu-auth.ts
|
|
187
|
-
"
|
|
188
|
-
"
|
|
184
|
+
// ---- device:* 设备目录 + 连接(决策 #15:deviceId + 自包含 token) ----
|
|
185
|
+
// owner-only:公共设备目录 + audience 绑定设备的签名 token 连接(daemon 代理中心 server)。
|
|
186
|
+
// 中央字面量见 protocol/feishu-auth.ts DEVICE_METHODS。
|
|
187
|
+
"device:list",
|
|
188
|
+
"device:connect"
|
|
189
189
|
];
|
|
190
190
|
}
|
|
191
191
|
});
|
|
@@ -681,8 +681,8 @@ var init_parseUtil = __esm({
|
|
|
681
681
|
init_errors2();
|
|
682
682
|
init_en();
|
|
683
683
|
makeIssue = (params) => {
|
|
684
|
-
const { data, path:
|
|
685
|
-
const fullPath = [...
|
|
684
|
+
const { data, path: path60, errorMaps, issueData } = params;
|
|
685
|
+
const fullPath = [...path60, ...issueData.path || []];
|
|
686
686
|
const fullIssue = {
|
|
687
687
|
...issueData,
|
|
688
688
|
path: fullPath
|
|
@@ -993,11 +993,11 @@ var init_types = __esm({
|
|
|
993
993
|
init_parseUtil();
|
|
994
994
|
init_util();
|
|
995
995
|
ParseInputLazyPath = class {
|
|
996
|
-
constructor(parent, value,
|
|
996
|
+
constructor(parent, value, path60, key) {
|
|
997
997
|
this._cachedPath = [];
|
|
998
998
|
this.parent = parent;
|
|
999
999
|
this.data = value;
|
|
1000
|
-
this._path =
|
|
1000
|
+
this._path = path60;
|
|
1001
1001
|
this._key = key;
|
|
1002
1002
|
}
|
|
1003
1003
|
get path() {
|
|
@@ -4634,53 +4634,41 @@ var init_schemas = __esm({
|
|
|
4634
4634
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4635
4635
|
chatId: external_exports.string().min(1).optional(),
|
|
4636
4636
|
/**
|
|
4637
|
-
* 创建该 session
|
|
4638
|
-
*
|
|
4639
|
-
*
|
|
4640
|
-
*
|
|
4637
|
+
* 创建该 session 的设备 id(决策 #16:从 ctx.principal.id 派生 = deviceId)。
|
|
4638
|
+
* UI 端 self 判定收敛为单一比较 `!creatorPrincipalId || creatorPrincipalId === 本机 deviceId`
|
|
4639
|
+
* → 「我自己创建」vs「别的设备接入创建」。MyPersonas 二级分组按 creatorPrincipalId(设备)
|
|
4640
|
+
* 分桶、按 contact(deviceId→ownerName) 反查显示名。
|
|
4641
4641
|
*
|
|
4642
|
-
*
|
|
4643
|
-
|
|
4644
|
-
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4645
|
-
/**
|
|
4646
|
-
* 创建该 session 的接入者 owner identity(auth 帧 selfPrincipalId)。global personal token
|
|
4647
|
-
* (#735) 后 creatorPrincipalId 在 guest ctx 永远是同一张 personal cap.id,UI 端无法区分
|
|
4648
|
-
* 接入者;这个字段把"是哪个人"的信息固定下来。
|
|
4649
|
-
*
|
|
4650
|
-
* 派生规则:
|
|
4651
|
-
* - owner ctx 创建:= 本机 ownerPrincipalId
|
|
4652
|
-
* - guest ctx + auth 帧带 selfPrincipalId:= ctx.peerOwnerPrincipalId
|
|
4653
|
-
* - 旧 per-peer cap 路径 / 老 session:undefined(UI 视作 self 兼容)
|
|
4642
|
+
* 命名保留 creatorPrincipalId(命名债,语义已是 creatorDeviceId);值由 connect token
|
|
4643
|
+
* subDevice 背书(guest ctx)或本机 deviceId(owner ctx)。optional 兼容老 session。
|
|
4654
4644
|
*
|
|
4655
|
-
*
|
|
4656
|
-
*
|
|
4657
|
-
*
|
|
4658
|
-
*
|
|
4659
|
-
* 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
|
|
4645
|
+
* 决策 #16 删除了 creatorOwnerPrincipalId(personal token #735 遗留)——当年 guest 的
|
|
4646
|
+
* principal.id 都是同张 cap.id 区分不了人才加它;现在 principal.id = 设备 deviceId、
|
|
4647
|
+
* 归属人由 contact(deviceId→ownerId) 反查,该字段理由消失。
|
|
4660
4648
|
*/
|
|
4661
|
-
|
|
4649
|
+
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4662
4650
|
/**
|
|
4663
|
-
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon
|
|
4664
|
-
*
|
|
4665
|
-
*
|
|
4651
|
+
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上创建的 session**,
|
|
4652
|
+
* 远端是 source of truth,本机是持久化副本,方便 sidebar 跨设备聚合。
|
|
4653
|
+
* 字段值 = 远端 daemon 的 deviceId(决策 #16:朋友设备标识,即我接入的那台机器)。
|
|
4666
4654
|
*
|
|
4667
4655
|
* 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
|
|
4668
4656
|
* 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
|
|
4669
4657
|
*
|
|
4670
4658
|
* - undefined / 缺省 → 本机 native session(在我自己机器上跑)
|
|
4671
|
-
* - 非空 → 我的会话在该
|
|
4659
|
+
* - 非空 → 我的会话在该 deviceId 朋友的 daemon 上跑,本机为持久化副本
|
|
4672
4660
|
*
|
|
4673
|
-
*
|
|
4674
|
-
* | 形态 | creatorPrincipalId
|
|
4675
|
-
*
|
|
4676
|
-
* | 本机 owner 自创 | 本机
|
|
4677
|
-
* |
|
|
4678
|
-
* | 我在朋友 daemon
|
|
4661
|
+
* 四类 session 取值矩阵(决策 #16 deviceId 版,creatorOwnerPrincipalId 列已删):
|
|
4662
|
+
* | 形态 | creatorPrincipalId(=设备) | originOwnerPrincipalId(=朋友设备) |
|
|
4663
|
+
* |-------------------------------------|--------------------------|----------------------------------|
|
|
4664
|
+
* | 本机 owner 自创 | 本机 deviceId | undefined |
|
|
4665
|
+
* | 本机被别的设备接入创建 | 接入者 deviceId(token 背书) | undefined |
|
|
4666
|
+
* | 我在朋友 daemon 上创建(mirror 回本机) | 我的 deviceId | 朋友 deviceId |
|
|
4679
4667
|
*
|
|
4680
4668
|
* UI 用法:
|
|
4681
4669
|
* - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
|
|
4682
4670
|
* - 行内显示 `@<朋友>` chip 暗示位置
|
|
4683
|
-
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)`
|
|
4671
|
+
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端(按 deviceId)
|
|
4684
4672
|
*
|
|
4685
4673
|
* daemon 用法:
|
|
4686
4674
|
* - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
|
|
@@ -5107,32 +5095,12 @@ var init_schemas = __esm({
|
|
|
5107
5095
|
token: external_exports.string().min(1),
|
|
5108
5096
|
scheme: external_exports.literal("bearer").optional(),
|
|
5109
5097
|
/**
|
|
5110
|
-
*
|
|
5111
|
-
*
|
|
5112
|
-
*
|
|
5113
|
-
* tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
|
|
5114
|
-
*
|
|
5115
|
-
* 飞书统一身份 Phase 1 (2026-06-01):取值升级为飞书 union_id(owner 已飞书登录时;
|
|
5116
|
-
* 来自 whoami.owner.id → daemon ownerPrincipalId → owner-identity.json unionId)。
|
|
5117
|
-
* 未登录 daemon 仍为 auth.json 的 owner-<uuid>。daemon 端不验证该身份真实性
|
|
5118
|
-
* (Phase 1 信任模型,见 spec §9)。Phase 2 将以 connect token 替代(introspect
|
|
5119
|
-
* 返回身份,删除自报字段,见 spec §10)。
|
|
5120
|
-
*/
|
|
5121
|
-
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5122
|
-
/**
|
|
5123
|
-
* 同 selfPrincipalId:自报 owner displayName(人类可读),让对端 daemon 把
|
|
5124
|
-
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5125
|
-
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5126
|
-
*/
|
|
5127
|
-
selfDisplayName: external_exports.string().optional(),
|
|
5128
|
-
/**
|
|
5129
|
-
* 飞书统一身份 Phase 2(spec 决策 #11,自动反向建立联系人):guest 自报自己可达的
|
|
5130
|
-
* ws 地址(tunnel 地址优先)。hub introspect 验票成功后,若 guest 带 selfUrl,hub 自动
|
|
5131
|
-
* 把 guest 落入本机 ContactStore(remoteUrl = selfUrl)+ 广播 contact:added,
|
|
5132
|
-
* 让 hub 的 People 立即出现该 guest——双向互为联系人,身份由 introspect 背书不可伪造。
|
|
5098
|
+
* 决策 #16:自报身份字段(selfPrincipalId / selfDisplayName / selfDeviceId)全部删除——
|
|
5099
|
+
* guest 的设备 id / 人 id / 来源全部由 connect token 的 subDevice/subOwner/provider 签名背书
|
|
5100
|
+
* (server 签、目标设备本地验签,不可伪造),不再接受任何自报身份。
|
|
5133
5101
|
*
|
|
5134
|
-
*
|
|
5135
|
-
*
|
|
5102
|
+
* selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
|
|
5103
|
+
* 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
|
|
5136
5104
|
*/
|
|
5137
5105
|
selfUrl: external_exports.string().min(1).optional()
|
|
5138
5106
|
});
|
|
@@ -5276,7 +5244,11 @@ var init_capability = __esm({
|
|
|
5276
5244
|
owner: external_exports.object({
|
|
5277
5245
|
id: external_exports.string(),
|
|
5278
5246
|
kind: external_exports.enum(["owner", "guest"]),
|
|
5279
|
-
displayName: external_exports.string()
|
|
5247
|
+
displayName: external_exports.string(),
|
|
5248
|
+
/** 归属人 id(决策 #16:TTC unique_id 等,落 contact.ownerId) */
|
|
5249
|
+
ownerId: external_exports.string(),
|
|
5250
|
+
/** 身份来源(决策 #16:'ttc'/'google',落 contact.provider) */
|
|
5251
|
+
provider: external_exports.string()
|
|
5280
5252
|
}).strict(),
|
|
5281
5253
|
capability: CapabilityWireSchema,
|
|
5282
5254
|
grantedPersonas: external_exports.array(
|
|
@@ -5303,24 +5275,24 @@ var init_inbox = __esm({
|
|
|
5303
5275
|
init_zod();
|
|
5304
5276
|
InboxMessageSchema = external_exports.object({
|
|
5305
5277
|
id: external_exports.string().min(1),
|
|
5306
|
-
|
|
5307
|
-
|
|
5278
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5279
|
+
senderDeviceId: external_exports.string().min(1),
|
|
5308
5280
|
text: external_exports.string().min(1),
|
|
5309
5281
|
createdAt: external_exports.number().int().nonnegative(),
|
|
5310
5282
|
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5311
5283
|
}).strict();
|
|
5312
5284
|
InboxPostMessageArgsSchema = external_exports.object({
|
|
5313
|
-
|
|
5285
|
+
peerDeviceId: external_exports.string().min(1).optional(),
|
|
5314
5286
|
id: external_exports.string().min(1),
|
|
5315
5287
|
text: external_exports.string().min(1),
|
|
5316
5288
|
createdAt: external_exports.number().int().nonnegative()
|
|
5317
5289
|
}).strict();
|
|
5318
5290
|
InboxListArgsSchema = external_exports.object({
|
|
5319
|
-
|
|
5291
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5320
5292
|
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5321
5293
|
}).strict();
|
|
5322
5294
|
InboxMarkReadArgsSchema = external_exports.object({
|
|
5323
|
-
|
|
5295
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5324
5296
|
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5325
5297
|
}).strict();
|
|
5326
5298
|
}
|
|
@@ -5334,14 +5306,25 @@ var init_contact = __esm({
|
|
|
5334
5306
|
init_zod();
|
|
5335
5307
|
init_capability();
|
|
5336
5308
|
ContactSchema = external_exports.object({
|
|
5337
|
-
/**
|
|
5338
|
-
|
|
5339
|
-
|
|
5309
|
+
/**
|
|
5310
|
+
* 对方设备标识(主键):对方 daemon 自己生成并持久化的标识(auth.json deviceId,
|
|
5311
|
+
* 如 dev-<uuid>),provider 无关。同 deviceId 重复 upsert 覆盖。
|
|
5312
|
+
*/
|
|
5313
|
+
deviceId: external_exports.string().min(1),
|
|
5314
|
+
/**
|
|
5315
|
+
* 归属人 id(决策 #16):TTC unique_id 等,由 connect token subOwner 签名背书。
|
|
5316
|
+
* 与 provider 一起唯一标识一个人。DM 不再按它路由(路由用 deviceId),仅显示/分组用。
|
|
5317
|
+
*/
|
|
5318
|
+
ownerId: external_exports.string().min(1),
|
|
5319
|
+
/** 身份来源(决策 #16):'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...,token 背书 */
|
|
5320
|
+
provider: external_exports.string().min(1),
|
|
5321
|
+
/** 联系人显示名 */
|
|
5340
5322
|
displayName: external_exports.string(),
|
|
5323
|
+
/** 该设备可达的 ws/tunnel 地址 */
|
|
5341
5324
|
remoteUrl: external_exports.string().min(1),
|
|
5342
|
-
//
|
|
5343
|
-
//
|
|
5344
|
-
//
|
|
5325
|
+
// 自动反向建立联系人(spec 决策 #11):被连接方本地验票成功后自动落 A 为
|
|
5326
|
+
// 联系人,但它不持有 A 颁发的 token(A 从未给它换票)→ connectToken 为空串。
|
|
5327
|
+
// device:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5345
5328
|
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5346
5329
|
connectToken: external_exports.string(),
|
|
5347
5330
|
grants: external_exports.array(GrantSchema),
|
|
@@ -5349,11 +5332,11 @@ var init_contact = __esm({
|
|
|
5349
5332
|
}).strict();
|
|
5350
5333
|
ContactWireSchema = ContactSchema;
|
|
5351
5334
|
ContactRemoveArgsSchema = external_exports.object({
|
|
5352
|
-
|
|
5335
|
+
deviceId: external_exports.string().min(1)
|
|
5353
5336
|
}).strict();
|
|
5354
5337
|
ContactRemoveOkSchema = external_exports.object({
|
|
5355
5338
|
type: external_exports.literal("contact:remove:ok"),
|
|
5356
|
-
|
|
5339
|
+
deviceId: external_exports.string().min(1)
|
|
5357
5340
|
}).strict();
|
|
5358
5341
|
ContactListOkSchema = external_exports.object({
|
|
5359
5342
|
type: external_exports.literal("contact:list:ok"),
|
|
@@ -5365,7 +5348,7 @@ var init_contact = __esm({
|
|
|
5365
5348
|
}).strict();
|
|
5366
5349
|
ContactRemovedFrameSchema = external_exports.object({
|
|
5367
5350
|
type: external_exports.literal("contact:removed"),
|
|
5368
|
-
|
|
5351
|
+
deviceId: external_exports.string().min(1)
|
|
5369
5352
|
}).strict();
|
|
5370
5353
|
}
|
|
5371
5354
|
});
|
|
@@ -5570,7 +5553,7 @@ var init_extension = __esm({
|
|
|
5570
5553
|
});
|
|
5571
5554
|
|
|
5572
5555
|
// ../protocol/src/feishu-auth.ts
|
|
5573
|
-
var FEISHU_GATED_METHODS,
|
|
5556
|
+
var FEISHU_GATED_METHODS, DeviceEntrySchema, DeviceListResponseSchema, DeviceConnectArgsSchema, DeviceConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
|
|
5574
5557
|
var init_feishu_auth = __esm({
|
|
5575
5558
|
"../protocol/src/feishu-auth.ts"() {
|
|
5576
5559
|
"use strict";
|
|
@@ -5579,7 +5562,7 @@ var init_feishu_auth = __esm({
|
|
|
5579
5562
|
// capability(调试 / 撤销,仍属远程身份体系)
|
|
5580
5563
|
"capability:list",
|
|
5581
5564
|
"capability:delete",
|
|
5582
|
-
// 联系人列表(
|
|
5565
|
+
// 联系人列表(device:connect 落同一 store;list 读 / remove 删)
|
|
5583
5566
|
"contact:list",
|
|
5584
5567
|
"contact:remove",
|
|
5585
5568
|
// DM / 跨用户通知
|
|
@@ -5589,44 +5572,51 @@ var init_feishu_auth = __esm({
|
|
|
5589
5572
|
// mirror peer sessions(远端会话镜像)
|
|
5590
5573
|
"peerSession:upsert",
|
|
5591
5574
|
"peerSession:remove",
|
|
5592
|
-
//
|
|
5593
|
-
"
|
|
5594
|
-
"
|
|
5575
|
+
// 设备目录 + 连接(决策 #15:daemon 代理中心 server,需要 owner 的 ttcJwt)
|
|
5576
|
+
"device:list",
|
|
5577
|
+
"device:connect"
|
|
5595
5578
|
];
|
|
5596
|
-
|
|
5597
|
-
/**
|
|
5598
|
-
|
|
5579
|
+
DeviceEntrySchema = external_exports.object({
|
|
5580
|
+
/**
|
|
5581
|
+
* 设备标识(决策 #15:daemon 本地生成的 dev-<uuid>,与人的身份 unionId 无关)。
|
|
5582
|
+
* 中心 server clawd_devices 注册表仅作服务发现(url 下发 + 目录),不在安全路径上。
|
|
5583
|
+
*/
|
|
5584
|
+
deviceId: external_exports.string().min(1),
|
|
5599
5585
|
/** 显示名 */
|
|
5600
5586
|
name: external_exports.string().min(1),
|
|
5601
5587
|
/** ws/tunnel 地址 */
|
|
5602
5588
|
url: external_exports.string().min(1)
|
|
5603
5589
|
});
|
|
5604
|
-
|
|
5605
|
-
type: external_exports.literal("
|
|
5606
|
-
|
|
5590
|
+
DeviceListResponseSchema = external_exports.object({
|
|
5591
|
+
type: external_exports.literal("device:list:ok"),
|
|
5592
|
+
devices: external_exports.array(DeviceEntrySchema)
|
|
5607
5593
|
});
|
|
5608
|
-
|
|
5609
|
-
/**
|
|
5610
|
-
|
|
5594
|
+
DeviceConnectArgsSchema = external_exports.object({
|
|
5595
|
+
/** 目标设备的 deviceId(对方 daemon 生成的 dev-<uuid>) */
|
|
5596
|
+
deviceId: external_exports.string().min(1),
|
|
5611
5597
|
/**
|
|
5612
|
-
*
|
|
5613
|
-
* - 公开目录点击:带 url(
|
|
5614
|
-
* -
|
|
5598
|
+
* 目标设备的 ws/tunnel 地址(可选):
|
|
5599
|
+
* - 公开目录点击:带 url(device:list 已下发,省一次 server 查询)
|
|
5600
|
+
* - 凭 deviceId 连接:缺省 url → daemon exchange 时由中心 server 下发该设备上报的 url
|
|
5601
|
+
* - 注册表不在安全路径上:知道对方 url 时显式传入即可连接(不依赖中心 server 服务发现)
|
|
5615
5602
|
*/
|
|
5616
5603
|
url: external_exports.string().min(1).optional(),
|
|
5617
|
-
/**
|
|
5604
|
+
/** 显示名(公共设备来自目录;缺省时连上后用 whoami 回填) */
|
|
5618
5605
|
name: external_exports.string().optional()
|
|
5619
5606
|
});
|
|
5620
|
-
|
|
5621
|
-
type: external_exports.literal("
|
|
5622
|
-
|
|
5607
|
+
DeviceConnectResponseSchema = external_exports.object({
|
|
5608
|
+
type: external_exports.literal("device:connect:ok"),
|
|
5609
|
+
/** 连接的目标设备标识(= 请求 args.deviceId) */
|
|
5610
|
+
deviceId: external_exports.string(),
|
|
5623
5611
|
name: external_exports.string(),
|
|
5624
5612
|
url: external_exports.string()
|
|
5625
5613
|
});
|
|
5626
5614
|
FeishuIdentitySchema = external_exports.object({
|
|
5627
|
-
/**
|
|
5628
|
-
|
|
5629
|
-
/**
|
|
5615
|
+
/** 归属人 id(决策 #16:TTC unique_id,取代飞书 union_id;跨登录方式稳定) */
|
|
5616
|
+
ownerId: external_exports.string().min(1),
|
|
5617
|
+
/** 身份来源:'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...(决策 #16,拓展用) */
|
|
5618
|
+
provider: external_exports.string().min(1),
|
|
5619
|
+
/** 显示名。TTC user_info 接口 data.name */
|
|
5630
5620
|
displayName: external_exports.string().min(1),
|
|
5631
5621
|
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
5632
5622
|
avatarUrl: external_exports.string().optional()
|
|
@@ -5641,7 +5631,13 @@ var init_feishu_auth = __esm({
|
|
|
5641
5631
|
AuthGetIdentityResponseSchema = external_exports.object({
|
|
5642
5632
|
type: external_exports.literal("auth:getIdentity:ok"),
|
|
5643
5633
|
/** null = 未登录 */
|
|
5644
|
-
identity: FeishuIdentitySchema.nullable()
|
|
5634
|
+
identity: FeishuIdentitySchema.nullable(),
|
|
5635
|
+
/**
|
|
5636
|
+
* 本机设备标识(决策 #15:daemon 生成并持久化在 auth.json,如 dev-<uuid>)。
|
|
5637
|
+
* 设备属性,与登录态无关、登录登出不变。UI"我的设备标识"用它拼
|
|
5638
|
+
* clawos://device/<deviceId>(替代旧的 unionId——不再向外暴露人的身份标识)。
|
|
5639
|
+
*/
|
|
5640
|
+
deviceId: external_exports.string().min(1)
|
|
5645
5641
|
});
|
|
5646
5642
|
AuthLogoutResponseSchema = external_exports.object({
|
|
5647
5643
|
type: external_exports.literal("auth:logout:ok")
|
|
@@ -5949,8 +5945,8 @@ var require_req = __commonJS({
|
|
|
5949
5945
|
if (req.originalUrl) {
|
|
5950
5946
|
_req.url = req.originalUrl;
|
|
5951
5947
|
} else {
|
|
5952
|
-
const
|
|
5953
|
-
_req.url = typeof
|
|
5948
|
+
const path60 = req.path;
|
|
5949
|
+
_req.url = typeof path60 === "string" ? path60 : req.url ? req.url.path || req.url : void 0;
|
|
5954
5950
|
}
|
|
5955
5951
|
if (req.query) {
|
|
5956
5952
|
_req.query = req.query;
|
|
@@ -6115,14 +6111,14 @@ var require_redact = __commonJS({
|
|
|
6115
6111
|
}
|
|
6116
6112
|
return obj;
|
|
6117
6113
|
}
|
|
6118
|
-
function parsePath(
|
|
6114
|
+
function parsePath(path60) {
|
|
6119
6115
|
const parts = [];
|
|
6120
6116
|
let current = "";
|
|
6121
6117
|
let inBrackets = false;
|
|
6122
6118
|
let inQuotes = false;
|
|
6123
6119
|
let quoteChar = "";
|
|
6124
|
-
for (let i = 0; i <
|
|
6125
|
-
const char =
|
|
6120
|
+
for (let i = 0; i < path60.length; i++) {
|
|
6121
|
+
const char = path60[i];
|
|
6126
6122
|
if (!inBrackets && char === ".") {
|
|
6127
6123
|
if (current) {
|
|
6128
6124
|
parts.push(current);
|
|
@@ -6253,10 +6249,10 @@ var require_redact = __commonJS({
|
|
|
6253
6249
|
return current;
|
|
6254
6250
|
}
|
|
6255
6251
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6256
|
-
for (const
|
|
6257
|
-
const parts = parsePath(
|
|
6252
|
+
for (const path60 of paths) {
|
|
6253
|
+
const parts = parsePath(path60);
|
|
6258
6254
|
if (parts.includes("*")) {
|
|
6259
|
-
redactWildcardPath(obj, parts, censor,
|
|
6255
|
+
redactWildcardPath(obj, parts, censor, path60, remove);
|
|
6260
6256
|
} else {
|
|
6261
6257
|
if (remove) {
|
|
6262
6258
|
removeKey(obj, parts);
|
|
@@ -6341,8 +6337,8 @@ var require_redact = __commonJS({
|
|
|
6341
6337
|
}
|
|
6342
6338
|
} else {
|
|
6343
6339
|
if (afterWildcard.includes("*")) {
|
|
6344
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6345
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6340
|
+
const wrappedCensor = typeof censor === "function" ? (value, path60) => {
|
|
6341
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path60];
|
|
6346
6342
|
return censor(value, fullPath);
|
|
6347
6343
|
} : censor;
|
|
6348
6344
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6377,8 +6373,8 @@ var require_redact = __commonJS({
|
|
|
6377
6373
|
return null;
|
|
6378
6374
|
}
|
|
6379
6375
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6380
|
-
for (const
|
|
6381
|
-
const parts = parsePath(
|
|
6376
|
+
for (const path60 of pathsToClone) {
|
|
6377
|
+
const parts = parsePath(path60);
|
|
6382
6378
|
let current = pathStructure;
|
|
6383
6379
|
for (let i = 0; i < parts.length; i++) {
|
|
6384
6380
|
const part = parts[i];
|
|
@@ -6430,24 +6426,24 @@ var require_redact = __commonJS({
|
|
|
6430
6426
|
}
|
|
6431
6427
|
return cloneSelectively(obj, pathStructure);
|
|
6432
6428
|
}
|
|
6433
|
-
function validatePath(
|
|
6434
|
-
if (typeof
|
|
6429
|
+
function validatePath(path60) {
|
|
6430
|
+
if (typeof path60 !== "string") {
|
|
6435
6431
|
throw new Error("Paths must be (non-empty) strings");
|
|
6436
6432
|
}
|
|
6437
|
-
if (
|
|
6433
|
+
if (path60 === "") {
|
|
6438
6434
|
throw new Error("Invalid redaction path ()");
|
|
6439
6435
|
}
|
|
6440
|
-
if (
|
|
6441
|
-
throw new Error(`Invalid redaction path (${
|
|
6436
|
+
if (path60.includes("..")) {
|
|
6437
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6442
6438
|
}
|
|
6443
|
-
if (
|
|
6444
|
-
throw new Error(`Invalid redaction path (${
|
|
6439
|
+
if (path60.includes(",")) {
|
|
6440
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6445
6441
|
}
|
|
6446
6442
|
let bracketCount = 0;
|
|
6447
6443
|
let inQuotes = false;
|
|
6448
6444
|
let quoteChar = "";
|
|
6449
|
-
for (let i = 0; i <
|
|
6450
|
-
const char =
|
|
6445
|
+
for (let i = 0; i < path60.length; i++) {
|
|
6446
|
+
const char = path60[i];
|
|
6451
6447
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6452
6448
|
if (!inQuotes) {
|
|
6453
6449
|
inQuotes = true;
|
|
@@ -6461,20 +6457,20 @@ var require_redact = __commonJS({
|
|
|
6461
6457
|
} else if (char === "]" && !inQuotes) {
|
|
6462
6458
|
bracketCount--;
|
|
6463
6459
|
if (bracketCount < 0) {
|
|
6464
|
-
throw new Error(`Invalid redaction path (${
|
|
6460
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6465
6461
|
}
|
|
6466
6462
|
}
|
|
6467
6463
|
}
|
|
6468
6464
|
if (bracketCount !== 0) {
|
|
6469
|
-
throw new Error(`Invalid redaction path (${
|
|
6465
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6470
6466
|
}
|
|
6471
6467
|
}
|
|
6472
6468
|
function validatePaths(paths) {
|
|
6473
6469
|
if (!Array.isArray(paths)) {
|
|
6474
6470
|
throw new TypeError("paths must be an array");
|
|
6475
6471
|
}
|
|
6476
|
-
for (const
|
|
6477
|
-
validatePath(
|
|
6472
|
+
for (const path60 of paths) {
|
|
6473
|
+
validatePath(path60);
|
|
6478
6474
|
}
|
|
6479
6475
|
}
|
|
6480
6476
|
function slowRedact(options = {}) {
|
|
@@ -6642,8 +6638,8 @@ var require_redaction = __commonJS({
|
|
|
6642
6638
|
if (shape[k2] === null) {
|
|
6643
6639
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6644
6640
|
} else {
|
|
6645
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6646
|
-
return censor(value, [k2, ...
|
|
6641
|
+
const wrappedCensor = typeof censor === "function" ? (value, path60) => {
|
|
6642
|
+
return censor(value, [k2, ...path60]);
|
|
6647
6643
|
} : censor;
|
|
6648
6644
|
o[k2] = Redact({
|
|
6649
6645
|
paths: shape[k2],
|
|
@@ -6861,10 +6857,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6861
6857
|
var require_sonic_boom = __commonJS({
|
|
6862
6858
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6863
6859
|
"use strict";
|
|
6864
|
-
var
|
|
6860
|
+
var fs48 = require("fs");
|
|
6865
6861
|
var EventEmitter2 = require("events");
|
|
6866
6862
|
var inherits = require("util").inherits;
|
|
6867
|
-
var
|
|
6863
|
+
var path60 = require("path");
|
|
6868
6864
|
var sleep = require_atomic_sleep();
|
|
6869
6865
|
var assert = require("assert");
|
|
6870
6866
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6918,20 +6914,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6918
6914
|
const mode = sonic.mode;
|
|
6919
6915
|
if (sonic.sync) {
|
|
6920
6916
|
try {
|
|
6921
|
-
if (sonic.mkdir)
|
|
6922
|
-
const fd =
|
|
6917
|
+
if (sonic.mkdir) fs48.mkdirSync(path60.dirname(file), { recursive: true });
|
|
6918
|
+
const fd = fs48.openSync(file, flags, mode);
|
|
6923
6919
|
fileOpened(null, fd);
|
|
6924
6920
|
} catch (err) {
|
|
6925
6921
|
fileOpened(err);
|
|
6926
6922
|
throw err;
|
|
6927
6923
|
}
|
|
6928
6924
|
} else if (sonic.mkdir) {
|
|
6929
|
-
|
|
6925
|
+
fs48.mkdir(path60.dirname(file), { recursive: true }, (err) => {
|
|
6930
6926
|
if (err) return fileOpened(err);
|
|
6931
|
-
|
|
6927
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
6932
6928
|
});
|
|
6933
6929
|
} else {
|
|
6934
|
-
|
|
6930
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
6935
6931
|
}
|
|
6936
6932
|
}
|
|
6937
6933
|
function SonicBoom(opts) {
|
|
@@ -6972,8 +6968,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6972
6968
|
this.flush = flushBuffer;
|
|
6973
6969
|
this.flushSync = flushBufferSync;
|
|
6974
6970
|
this._actualWrite = actualWriteBuffer;
|
|
6975
|
-
fsWriteSync = () =>
|
|
6976
|
-
fsWrite = () =>
|
|
6971
|
+
fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
|
|
6972
|
+
fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
|
|
6977
6973
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6978
6974
|
this._writingBuf = "";
|
|
6979
6975
|
this.write = write;
|
|
@@ -6982,15 +6978,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6982
6978
|
this._actualWrite = actualWrite;
|
|
6983
6979
|
fsWriteSync = () => {
|
|
6984
6980
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6985
|
-
return
|
|
6981
|
+
return fs48.writeSync(this.fd, this._writingBuf);
|
|
6986
6982
|
}
|
|
6987
|
-
return
|
|
6983
|
+
return fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6988
6984
|
};
|
|
6989
6985
|
fsWrite = () => {
|
|
6990
6986
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6991
|
-
return
|
|
6987
|
+
return fs48.write(this.fd, this._writingBuf, this.release);
|
|
6992
6988
|
}
|
|
6993
|
-
return
|
|
6989
|
+
return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6994
6990
|
};
|
|
6995
6991
|
} else {
|
|
6996
6992
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7047,7 +7043,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7047
7043
|
}
|
|
7048
7044
|
}
|
|
7049
7045
|
if (this._fsync) {
|
|
7050
|
-
|
|
7046
|
+
fs48.fsyncSync(this.fd);
|
|
7051
7047
|
}
|
|
7052
7048
|
const len = this._len;
|
|
7053
7049
|
if (this._reopening) {
|
|
@@ -7161,7 +7157,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7161
7157
|
const onDrain = () => {
|
|
7162
7158
|
if (!this._fsync) {
|
|
7163
7159
|
try {
|
|
7164
|
-
|
|
7160
|
+
fs48.fsync(this.fd, (err) => {
|
|
7165
7161
|
this._flushPending = false;
|
|
7166
7162
|
cb(err);
|
|
7167
7163
|
});
|
|
@@ -7263,7 +7259,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7263
7259
|
const fd = this.fd;
|
|
7264
7260
|
this.once("ready", () => {
|
|
7265
7261
|
if (fd !== this.fd) {
|
|
7266
|
-
|
|
7262
|
+
fs48.close(fd, (err) => {
|
|
7267
7263
|
if (err) {
|
|
7268
7264
|
return this.emit("error", err);
|
|
7269
7265
|
}
|
|
@@ -7312,7 +7308,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7312
7308
|
buf = this._bufs[0];
|
|
7313
7309
|
}
|
|
7314
7310
|
try {
|
|
7315
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7311
|
+
const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
|
|
7316
7312
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7317
7313
|
buf = releasedBufObj.writingBuf;
|
|
7318
7314
|
this._len = releasedBufObj.len;
|
|
@@ -7328,7 +7324,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7328
7324
|
}
|
|
7329
7325
|
}
|
|
7330
7326
|
try {
|
|
7331
|
-
|
|
7327
|
+
fs48.fsyncSync(this.fd);
|
|
7332
7328
|
} catch {
|
|
7333
7329
|
}
|
|
7334
7330
|
}
|
|
@@ -7349,7 +7345,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7349
7345
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7350
7346
|
}
|
|
7351
7347
|
try {
|
|
7352
|
-
const n =
|
|
7348
|
+
const n = fs48.writeSync(this.fd, buf);
|
|
7353
7349
|
buf = buf.subarray(n);
|
|
7354
7350
|
this._len = Math.max(this._len - n, 0);
|
|
7355
7351
|
if (buf.length <= 0) {
|
|
@@ -7377,13 +7373,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7377
7373
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7378
7374
|
if (this.sync) {
|
|
7379
7375
|
try {
|
|
7380
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7376
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7381
7377
|
release(null, written);
|
|
7382
7378
|
} catch (err) {
|
|
7383
7379
|
release(err);
|
|
7384
7380
|
}
|
|
7385
7381
|
} else {
|
|
7386
|
-
|
|
7382
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7387
7383
|
}
|
|
7388
7384
|
}
|
|
7389
7385
|
function actualWriteBuffer() {
|
|
@@ -7392,7 +7388,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7392
7388
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7393
7389
|
if (this.sync) {
|
|
7394
7390
|
try {
|
|
7395
|
-
const written =
|
|
7391
|
+
const written = fs48.writeSync(this.fd, this._writingBuf);
|
|
7396
7392
|
release(null, written);
|
|
7397
7393
|
} catch (err) {
|
|
7398
7394
|
release(err);
|
|
@@ -7401,7 +7397,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7401
7397
|
if (kCopyBuffer) {
|
|
7402
7398
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7403
7399
|
}
|
|
7404
|
-
|
|
7400
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7405
7401
|
}
|
|
7406
7402
|
}
|
|
7407
7403
|
function actualClose(sonic) {
|
|
@@ -7417,12 +7413,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7417
7413
|
sonic._lens = [];
|
|
7418
7414
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7419
7415
|
try {
|
|
7420
|
-
|
|
7416
|
+
fs48.fsync(sonic.fd, closeWrapped);
|
|
7421
7417
|
} catch {
|
|
7422
7418
|
}
|
|
7423
7419
|
function closeWrapped() {
|
|
7424
7420
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7425
|
-
|
|
7421
|
+
fs48.close(sonic.fd, done);
|
|
7426
7422
|
} else {
|
|
7427
7423
|
done();
|
|
7428
7424
|
}
|
|
@@ -7679,7 +7675,7 @@ var require_thread_stream = __commonJS({
|
|
|
7679
7675
|
var { version: version2 } = require_package();
|
|
7680
7676
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7681
7677
|
var { Worker } = require("worker_threads");
|
|
7682
|
-
var { join:
|
|
7678
|
+
var { join: join12 } = require("path");
|
|
7683
7679
|
var { pathToFileURL } = require("url");
|
|
7684
7680
|
var { wait } = require_wait();
|
|
7685
7681
|
var {
|
|
@@ -7715,7 +7711,7 @@ var require_thread_stream = __commonJS({
|
|
|
7715
7711
|
function createWorker(stream, opts) {
|
|
7716
7712
|
const { filename, workerData } = opts;
|
|
7717
7713
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7718
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7714
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join12(__dirname, "lib", "worker.js");
|
|
7719
7715
|
const worker = new Worker(toExecute, {
|
|
7720
7716
|
...opts.workerOpts,
|
|
7721
7717
|
trackUnmanagedFds: false,
|
|
@@ -8101,7 +8097,7 @@ var require_transport = __commonJS({
|
|
|
8101
8097
|
"use strict";
|
|
8102
8098
|
var { createRequire } = require("module");
|
|
8103
8099
|
var getCallers = require_caller();
|
|
8104
|
-
var { join:
|
|
8100
|
+
var { join: join12, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8105
8101
|
var sleep = require_atomic_sleep();
|
|
8106
8102
|
var onExit = require_on_exit_leak_free();
|
|
8107
8103
|
var ThreadStream = require_thread_stream();
|
|
@@ -8164,7 +8160,7 @@ var require_transport = __commonJS({
|
|
|
8164
8160
|
throw new Error("only one of target or targets can be specified");
|
|
8165
8161
|
}
|
|
8166
8162
|
if (targets) {
|
|
8167
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8163
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8168
8164
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8169
8165
|
return {
|
|
8170
8166
|
...dest,
|
|
@@ -8182,7 +8178,7 @@ var require_transport = __commonJS({
|
|
|
8182
8178
|
});
|
|
8183
8179
|
});
|
|
8184
8180
|
} else if (pipeline3) {
|
|
8185
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8181
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8186
8182
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8187
8183
|
return {
|
|
8188
8184
|
...dest,
|
|
@@ -8204,7 +8200,7 @@ var require_transport = __commonJS({
|
|
|
8204
8200
|
return origin;
|
|
8205
8201
|
}
|
|
8206
8202
|
if (origin === "pino/file") {
|
|
8207
|
-
return
|
|
8203
|
+
return join12(__dirname, "..", "file.js");
|
|
8208
8204
|
}
|
|
8209
8205
|
let fixTarget2;
|
|
8210
8206
|
for (const filePath of callers) {
|
|
@@ -9194,7 +9190,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9194
9190
|
return circularValue;
|
|
9195
9191
|
}
|
|
9196
9192
|
let res = "";
|
|
9197
|
-
let
|
|
9193
|
+
let join12 = ",";
|
|
9198
9194
|
const originalIndentation = indentation;
|
|
9199
9195
|
if (Array.isArray(value)) {
|
|
9200
9196
|
if (value.length === 0) {
|
|
@@ -9208,7 +9204,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9208
9204
|
indentation += spacer;
|
|
9209
9205
|
res += `
|
|
9210
9206
|
${indentation}`;
|
|
9211
|
-
|
|
9207
|
+
join12 = `,
|
|
9212
9208
|
${indentation}`;
|
|
9213
9209
|
}
|
|
9214
9210
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9216,13 +9212,13 @@ ${indentation}`;
|
|
|
9216
9212
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9217
9213
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9218
9214
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9219
|
-
res +=
|
|
9215
|
+
res += join12;
|
|
9220
9216
|
}
|
|
9221
9217
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9222
9218
|
res += tmp !== void 0 ? tmp : "null";
|
|
9223
9219
|
if (value.length - 1 > maximumBreadth) {
|
|
9224
9220
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9225
|
-
res += `${
|
|
9221
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9226
9222
|
}
|
|
9227
9223
|
if (spacer !== "") {
|
|
9228
9224
|
res += `
|
|
@@ -9243,7 +9239,7 @@ ${originalIndentation}`;
|
|
|
9243
9239
|
let separator = "";
|
|
9244
9240
|
if (spacer !== "") {
|
|
9245
9241
|
indentation += spacer;
|
|
9246
|
-
|
|
9242
|
+
join12 = `,
|
|
9247
9243
|
${indentation}`;
|
|
9248
9244
|
whitespace = " ";
|
|
9249
9245
|
}
|
|
@@ -9257,13 +9253,13 @@ ${indentation}`;
|
|
|
9257
9253
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9258
9254
|
if (tmp !== void 0) {
|
|
9259
9255
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9260
|
-
separator =
|
|
9256
|
+
separator = join12;
|
|
9261
9257
|
}
|
|
9262
9258
|
}
|
|
9263
9259
|
if (keyLength > maximumBreadth) {
|
|
9264
9260
|
const removedKeys = keyLength - maximumBreadth;
|
|
9265
9261
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9266
|
-
separator =
|
|
9262
|
+
separator = join12;
|
|
9267
9263
|
}
|
|
9268
9264
|
if (spacer !== "" && separator.length > 1) {
|
|
9269
9265
|
res = `
|
|
@@ -9304,7 +9300,7 @@ ${originalIndentation}`;
|
|
|
9304
9300
|
}
|
|
9305
9301
|
const originalIndentation = indentation;
|
|
9306
9302
|
let res = "";
|
|
9307
|
-
let
|
|
9303
|
+
let join12 = ",";
|
|
9308
9304
|
if (Array.isArray(value)) {
|
|
9309
9305
|
if (value.length === 0) {
|
|
9310
9306
|
return "[]";
|
|
@@ -9317,7 +9313,7 @@ ${originalIndentation}`;
|
|
|
9317
9313
|
indentation += spacer;
|
|
9318
9314
|
res += `
|
|
9319
9315
|
${indentation}`;
|
|
9320
|
-
|
|
9316
|
+
join12 = `,
|
|
9321
9317
|
${indentation}`;
|
|
9322
9318
|
}
|
|
9323
9319
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9325,13 +9321,13 @@ ${indentation}`;
|
|
|
9325
9321
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9326
9322
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9327
9323
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9328
|
-
res +=
|
|
9324
|
+
res += join12;
|
|
9329
9325
|
}
|
|
9330
9326
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9331
9327
|
res += tmp !== void 0 ? tmp : "null";
|
|
9332
9328
|
if (value.length - 1 > maximumBreadth) {
|
|
9333
9329
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9334
|
-
res += `${
|
|
9330
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9335
9331
|
}
|
|
9336
9332
|
if (spacer !== "") {
|
|
9337
9333
|
res += `
|
|
@@ -9344,7 +9340,7 @@ ${originalIndentation}`;
|
|
|
9344
9340
|
let whitespace = "";
|
|
9345
9341
|
if (spacer !== "") {
|
|
9346
9342
|
indentation += spacer;
|
|
9347
|
-
|
|
9343
|
+
join12 = `,
|
|
9348
9344
|
${indentation}`;
|
|
9349
9345
|
whitespace = " ";
|
|
9350
9346
|
}
|
|
@@ -9353,7 +9349,7 @@ ${indentation}`;
|
|
|
9353
9349
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9354
9350
|
if (tmp !== void 0) {
|
|
9355
9351
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9356
|
-
separator =
|
|
9352
|
+
separator = join12;
|
|
9357
9353
|
}
|
|
9358
9354
|
}
|
|
9359
9355
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9411,20 +9407,20 @@ ${originalIndentation}`;
|
|
|
9411
9407
|
indentation += spacer;
|
|
9412
9408
|
let res2 = `
|
|
9413
9409
|
${indentation}`;
|
|
9414
|
-
const
|
|
9410
|
+
const join13 = `,
|
|
9415
9411
|
${indentation}`;
|
|
9416
9412
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9417
9413
|
let i = 0;
|
|
9418
9414
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9419
9415
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9420
9416
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9421
|
-
res2 +=
|
|
9417
|
+
res2 += join13;
|
|
9422
9418
|
}
|
|
9423
9419
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9424
9420
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9425
9421
|
if (value.length - 1 > maximumBreadth) {
|
|
9426
9422
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9427
|
-
res2 += `${
|
|
9423
|
+
res2 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9428
9424
|
}
|
|
9429
9425
|
res2 += `
|
|
9430
9426
|
${originalIndentation}`;
|
|
@@ -9440,16 +9436,16 @@ ${originalIndentation}`;
|
|
|
9440
9436
|
return '"[Object]"';
|
|
9441
9437
|
}
|
|
9442
9438
|
indentation += spacer;
|
|
9443
|
-
const
|
|
9439
|
+
const join12 = `,
|
|
9444
9440
|
${indentation}`;
|
|
9445
9441
|
let res = "";
|
|
9446
9442
|
let separator = "";
|
|
9447
9443
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9448
9444
|
if (isTypedArrayWithEntries(value)) {
|
|
9449
|
-
res += stringifyTypedArray(value,
|
|
9445
|
+
res += stringifyTypedArray(value, join12, maximumBreadth);
|
|
9450
9446
|
keys = keys.slice(value.length);
|
|
9451
9447
|
maximumPropertiesToStringify -= value.length;
|
|
9452
|
-
separator =
|
|
9448
|
+
separator = join12;
|
|
9453
9449
|
}
|
|
9454
9450
|
if (deterministic) {
|
|
9455
9451
|
keys = sort(keys, comparator);
|
|
@@ -9460,13 +9456,13 @@ ${indentation}`;
|
|
|
9460
9456
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9461
9457
|
if (tmp !== void 0) {
|
|
9462
9458
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9463
|
-
separator =
|
|
9459
|
+
separator = join12;
|
|
9464
9460
|
}
|
|
9465
9461
|
}
|
|
9466
9462
|
if (keyLength > maximumBreadth) {
|
|
9467
9463
|
const removedKeys = keyLength - maximumBreadth;
|
|
9468
9464
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9469
|
-
separator =
|
|
9465
|
+
separator = join12;
|
|
9470
9466
|
}
|
|
9471
9467
|
if (separator !== "") {
|
|
9472
9468
|
res = `
|
|
@@ -10557,11 +10553,11 @@ var init_lib = __esm({
|
|
|
10557
10553
|
}
|
|
10558
10554
|
}
|
|
10559
10555
|
},
|
|
10560
|
-
addToPath: function addToPath(
|
|
10561
|
-
var last =
|
|
10556
|
+
addToPath: function addToPath(path60, added, removed, oldPosInc, options) {
|
|
10557
|
+
var last = path60.lastComponent;
|
|
10562
10558
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10563
10559
|
return {
|
|
10564
|
-
oldPos:
|
|
10560
|
+
oldPos: path60.oldPos + oldPosInc,
|
|
10565
10561
|
lastComponent: {
|
|
10566
10562
|
count: last.count + 1,
|
|
10567
10563
|
added,
|
|
@@ -10571,7 +10567,7 @@ var init_lib = __esm({
|
|
|
10571
10567
|
};
|
|
10572
10568
|
} else {
|
|
10573
10569
|
return {
|
|
10574
|
-
oldPos:
|
|
10570
|
+
oldPos: path60.oldPos + oldPosInc,
|
|
10575
10571
|
lastComponent: {
|
|
10576
10572
|
count: 1,
|
|
10577
10573
|
added,
|
|
@@ -11017,10 +11013,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11017
11013
|
const memories = raw.map((m2) => {
|
|
11018
11014
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11019
11015
|
const rec = m2;
|
|
11020
|
-
const
|
|
11016
|
+
const path60 = typeof rec.path === "string" ? rec.path : null;
|
|
11021
11017
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11022
|
-
if (!
|
|
11023
|
-
const entry = { path:
|
|
11018
|
+
if (!path60 || content == null) return null;
|
|
11019
|
+
const entry = { path: path60, content };
|
|
11024
11020
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
11025
11021
|
return entry;
|
|
11026
11022
|
}).filter((m2) => m2 !== null);
|
|
@@ -11851,10 +11847,10 @@ function parseAttachment(obj) {
|
|
|
11851
11847
|
const memories = raw.map((m2) => {
|
|
11852
11848
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11853
11849
|
const rec = m2;
|
|
11854
|
-
const
|
|
11850
|
+
const path60 = typeof rec.path === "string" ? rec.path : null;
|
|
11855
11851
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11856
|
-
if (!
|
|
11857
|
-
const out = { path:
|
|
11852
|
+
if (!path60 || content == null) return null;
|
|
11853
|
+
const out = { path: path60, content };
|
|
11858
11854
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11859
11855
|
return out;
|
|
11860
11856
|
}).filter((m2) => m2 !== null);
|
|
@@ -21053,7 +21049,7 @@ var require_BufferList = __commonJS({
|
|
|
21053
21049
|
this.head = this.tail = null;
|
|
21054
21050
|
this.length = 0;
|
|
21055
21051
|
};
|
|
21056
|
-
BufferList.prototype.join = function
|
|
21052
|
+
BufferList.prototype.join = function join12(s) {
|
|
21057
21053
|
if (this.length === 0) return "";
|
|
21058
21054
|
var p2 = this.head;
|
|
21059
21055
|
var ret = "" + p2.data;
|
|
@@ -23643,8 +23639,8 @@ var require_utils = __commonJS({
|
|
|
23643
23639
|
var result = transform[inputType][outputType](input);
|
|
23644
23640
|
return result;
|
|
23645
23641
|
};
|
|
23646
|
-
exports2.resolve = function(
|
|
23647
|
-
var parts =
|
|
23642
|
+
exports2.resolve = function(path60) {
|
|
23643
|
+
var parts = path60.split("/");
|
|
23648
23644
|
var result = [];
|
|
23649
23645
|
for (var index = 0; index < parts.length; index++) {
|
|
23650
23646
|
var part = parts[index];
|
|
@@ -29497,18 +29493,18 @@ var require_object = __commonJS({
|
|
|
29497
29493
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
29498
29494
|
this.files[name] = object;
|
|
29499
29495
|
};
|
|
29500
|
-
var parentFolder = function(
|
|
29501
|
-
if (
|
|
29502
|
-
|
|
29496
|
+
var parentFolder = function(path60) {
|
|
29497
|
+
if (path60.slice(-1) === "/") {
|
|
29498
|
+
path60 = path60.substring(0, path60.length - 1);
|
|
29503
29499
|
}
|
|
29504
|
-
var lastSlash =
|
|
29505
|
-
return lastSlash > 0 ?
|
|
29500
|
+
var lastSlash = path60.lastIndexOf("/");
|
|
29501
|
+
return lastSlash > 0 ? path60.substring(0, lastSlash) : "";
|
|
29506
29502
|
};
|
|
29507
|
-
var forceTrailingSlash = function(
|
|
29508
|
-
if (
|
|
29509
|
-
|
|
29503
|
+
var forceTrailingSlash = function(path60) {
|
|
29504
|
+
if (path60.slice(-1) !== "/") {
|
|
29505
|
+
path60 += "/";
|
|
29510
29506
|
}
|
|
29511
|
-
return
|
|
29507
|
+
return path60;
|
|
29512
29508
|
};
|
|
29513
29509
|
var folderAdd = function(name, createFolders) {
|
|
29514
29510
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -32972,7 +32968,7 @@ var SessionManager = class {
|
|
|
32972
32968
|
}
|
|
32973
32969
|
}
|
|
32974
32970
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
32975
|
-
create(args, creatorPrincipalId
|
|
32971
|
+
create(args, creatorPrincipalId) {
|
|
32976
32972
|
let cwd;
|
|
32977
32973
|
if (args.ownerPersonaId) {
|
|
32978
32974
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -33014,7 +33010,6 @@ var SessionManager = class {
|
|
|
33014
33010
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
33015
33011
|
ownerPersonaId: args.ownerPersonaId,
|
|
33016
33012
|
creatorPrincipalId,
|
|
33017
|
-
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
33018
33013
|
createdAt: iso,
|
|
33019
33014
|
updatedAt: iso
|
|
33020
33015
|
};
|
|
@@ -36634,14 +36629,8 @@ var LocalWsServer = class {
|
|
|
36634
36629
|
}
|
|
36635
36630
|
} else if (frame.type === "auth") {
|
|
36636
36631
|
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
36637
|
-
const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
|
|
36638
|
-
const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
|
|
36639
36632
|
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
36640
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36641
|
-
token,
|
|
36642
|
-
selfPrincipalId || void 0,
|
|
36643
|
-
selfDisplayName || void 0
|
|
36644
|
-
);
|
|
36633
|
+
const guestCtx = this.opts.tryVerifyCapabilityToken(token);
|
|
36645
36634
|
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
36646
36635
|
}
|
|
36647
36636
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
@@ -36768,12 +36757,7 @@ var AuthGate = class {
|
|
|
36768
36757
|
st.pending = true;
|
|
36769
36758
|
let r;
|
|
36770
36759
|
try {
|
|
36771
|
-
r = await this.opts.authenticate(
|
|
36772
|
-
parsed.data.token,
|
|
36773
|
-
parsed.data.selfPrincipalId,
|
|
36774
|
-
parsed.data.selfDisplayName,
|
|
36775
|
-
parsed.data.selfUrl
|
|
36776
|
-
);
|
|
36760
|
+
r = await this.opts.authenticate(parsed.data.token, parsed.data.selfUrl);
|
|
36777
36761
|
} finally {
|
|
36778
36762
|
st.pending = false;
|
|
36779
36763
|
}
|
|
@@ -36880,14 +36864,15 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
36880
36864
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
36881
36865
|
};
|
|
36882
36866
|
}
|
|
36883
|
-
function connectGuestContext(
|
|
36867
|
+
function connectGuestContext(subDevice, subOwner, provider, displayName) {
|
|
36884
36868
|
return {
|
|
36885
|
-
principal: { id:
|
|
36869
|
+
principal: { id: subDevice, kind: "guest", displayName },
|
|
36886
36870
|
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
36887
36871
|
resource: { ...g2.resource },
|
|
36888
36872
|
actions: [...g2.actions]
|
|
36889
36873
|
})),
|
|
36890
|
-
|
|
36874
|
+
ownerId: subOwner,
|
|
36875
|
+
provider
|
|
36891
36876
|
};
|
|
36892
36877
|
}
|
|
36893
36878
|
async function authenticate(token, deps) {
|
|
@@ -36895,17 +36880,17 @@ async function authenticate(token, deps) {
|
|
|
36895
36880
|
if (deps.isOwnerToken(token)) {
|
|
36896
36881
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
36897
36882
|
}
|
|
36898
|
-
if (!deps.
|
|
36899
|
-
|
|
36900
|
-
|
|
36901
|
-
result
|
|
36902
|
-
|
|
36883
|
+
if (!deps.verifyConnectToken) return { ok: false, code: "BAD_TOKEN" };
|
|
36884
|
+
const result = deps.verifyConnectToken(token);
|
|
36885
|
+
if (!result.ok) {
|
|
36886
|
+
if (result.reason === "EXPIRED") return { ok: false, code: "TOKEN_EXPIRED" };
|
|
36887
|
+
if (result.reason === "WRONG_AUDIENCE") return { ok: false, code: "TOKEN_REVOKED" };
|
|
36903
36888
|
return { ok: false, code: "BAD_TOKEN" };
|
|
36904
36889
|
}
|
|
36905
|
-
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
|
|
36890
|
+
return {
|
|
36891
|
+
ok: true,
|
|
36892
|
+
context: connectGuestContext(result.subDevice, result.subOwner, result.provider, result.displayName)
|
|
36893
|
+
};
|
|
36909
36894
|
}
|
|
36910
36895
|
|
|
36911
36896
|
// src/permission/capability-store.ts
|
|
@@ -37100,8 +37085,8 @@ var InboxStore = class {
|
|
|
37100
37085
|
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
37101
37086
|
* 文件不存在时返回 [] (不抛, 不创建).
|
|
37102
37087
|
*/
|
|
37103
|
-
list(
|
|
37104
|
-
const file = this.filePath(
|
|
37088
|
+
list(peerDeviceId, sinceCreatedAt) {
|
|
37089
|
+
const file = this.filePath(peerDeviceId);
|
|
37105
37090
|
let raw;
|
|
37106
37091
|
try {
|
|
37107
37092
|
raw = fs19.readFileSync(file, "utf8");
|
|
@@ -37114,9 +37099,9 @@ var InboxStore = class {
|
|
|
37114
37099
|
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
37115
37100
|
}
|
|
37116
37101
|
/**
|
|
37117
|
-
* 列出所有 peer 的
|
|
37102
|
+
* 列出所有 peer 的 peerDeviceId (扫目录所有 *.jsonl 文件名).
|
|
37118
37103
|
*/
|
|
37119
|
-
|
|
37104
|
+
listAllPeerDeviceIds() {
|
|
37120
37105
|
const dir = this.dirPath();
|
|
37121
37106
|
let entries;
|
|
37122
37107
|
try {
|
|
@@ -37132,9 +37117,9 @@ var InboxStore = class {
|
|
|
37132
37117
|
* 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
|
|
37133
37118
|
*/
|
|
37134
37119
|
append(message) {
|
|
37135
|
-
const existing = this.list(message.
|
|
37120
|
+
const existing = this.list(message.peerDeviceId);
|
|
37136
37121
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37137
|
-
const file = this.filePath(message.
|
|
37122
|
+
const file = this.filePath(message.peerDeviceId);
|
|
37138
37123
|
const line = JSON.stringify(message) + "\n";
|
|
37139
37124
|
fs19.appendFileSync(file, line, { mode: 384 });
|
|
37140
37125
|
try {
|
|
@@ -37143,38 +37128,38 @@ var InboxStore = class {
|
|
|
37143
37128
|
}
|
|
37144
37129
|
}
|
|
37145
37130
|
/**
|
|
37146
|
-
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=
|
|
37147
|
-
* readBy[
|
|
37131
|
+
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=readerDeviceId 的消息
|
|
37132
|
+
* readBy[readerDeviceId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
|
|
37148
37133
|
* 返写入了几条.
|
|
37149
37134
|
*/
|
|
37150
|
-
markRead(
|
|
37151
|
-
const messages = this.list(
|
|
37135
|
+
markRead(peerDeviceId, readerDeviceId, upToCreatedAt) {
|
|
37136
|
+
const messages = this.list(peerDeviceId);
|
|
37152
37137
|
let changed = 0;
|
|
37153
37138
|
const next = messages.map((m2) => {
|
|
37154
|
-
if (m2.
|
|
37139
|
+
if (m2.senderDeviceId === readerDeviceId) return m2;
|
|
37155
37140
|
if (m2.createdAt > upToCreatedAt) return m2;
|
|
37156
|
-
const cur = m2.readBy[
|
|
37141
|
+
const cur = m2.readBy[readerDeviceId];
|
|
37157
37142
|
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
37158
37143
|
changed++;
|
|
37159
|
-
return { ...m2, readBy: { ...m2.readBy, [
|
|
37144
|
+
return { ...m2, readBy: { ...m2.readBy, [readerDeviceId]: upToCreatedAt } };
|
|
37160
37145
|
});
|
|
37161
37146
|
if (changed === 0) return 0;
|
|
37162
|
-
this.rewriteFile(
|
|
37147
|
+
this.rewriteFile(peerDeviceId, next);
|
|
37163
37148
|
return changed;
|
|
37164
37149
|
}
|
|
37165
37150
|
/**
|
|
37166
37151
|
* 删整个 peer 的 jsonl (peer:remove cascade). 不存在 no-op.
|
|
37167
37152
|
*/
|
|
37168
|
-
|
|
37169
|
-
const file = this.filePath(
|
|
37153
|
+
removeByPeerDeviceId(peerDeviceId) {
|
|
37154
|
+
const file = this.filePath(peerDeviceId);
|
|
37170
37155
|
try {
|
|
37171
37156
|
fs19.unlinkSync(file);
|
|
37172
37157
|
} catch (err) {
|
|
37173
37158
|
if (err?.code === "ENOENT") return;
|
|
37174
37159
|
}
|
|
37175
37160
|
}
|
|
37176
|
-
rewriteFile(
|
|
37177
|
-
const file = this.filePath(
|
|
37161
|
+
rewriteFile(peerDeviceId, messages) {
|
|
37162
|
+
const file = this.filePath(peerDeviceId);
|
|
37178
37163
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37179
37164
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37180
37165
|
fs19.writeFileSync(tmp, content, { mode: 384 });
|
|
@@ -37187,8 +37172,8 @@ var InboxStore = class {
|
|
|
37187
37172
|
dirPath() {
|
|
37188
37173
|
return path21.join(this.dataDir, INBOX_SUBDIR);
|
|
37189
37174
|
}
|
|
37190
|
-
filePath(
|
|
37191
|
-
return path21.join(this.dirPath(), `${
|
|
37175
|
+
filePath(peerDeviceId) {
|
|
37176
|
+
return path21.join(this.dirPath(), `${peerDeviceId}.jsonl`);
|
|
37192
37177
|
}
|
|
37193
37178
|
};
|
|
37194
37179
|
function parseAllLines(raw) {
|
|
@@ -37224,53 +37209,53 @@ var InboxManager = class {
|
|
|
37224
37209
|
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
37225
37210
|
*/
|
|
37226
37211
|
postMessage(args) {
|
|
37227
|
-
const beforeLen = this.store.list(args.
|
|
37212
|
+
const beforeLen = this.store.list(args.peerDeviceId).length;
|
|
37228
37213
|
const msg = {
|
|
37229
37214
|
id: args.id,
|
|
37230
|
-
|
|
37231
|
-
|
|
37215
|
+
peerDeviceId: args.peerDeviceId,
|
|
37216
|
+
senderDeviceId: args.senderDeviceId,
|
|
37232
37217
|
text: args.text,
|
|
37233
37218
|
createdAt: args.createdAt,
|
|
37234
37219
|
readBy: {}
|
|
37235
37220
|
};
|
|
37236
37221
|
this.store.append(msg);
|
|
37237
|
-
const afterLen = this.store.list(args.
|
|
37222
|
+
const afterLen = this.store.list(args.peerDeviceId).length;
|
|
37238
37223
|
if (afterLen === beforeLen) return msg;
|
|
37239
|
-
this.broadcast(args.
|
|
37224
|
+
this.broadcast(args.peerDeviceId, {
|
|
37240
37225
|
type: "inbox:event",
|
|
37241
|
-
|
|
37226
|
+
peerDeviceId: args.peerDeviceId,
|
|
37242
37227
|
message: msg
|
|
37243
37228
|
});
|
|
37244
37229
|
return msg;
|
|
37245
37230
|
}
|
|
37246
|
-
list(
|
|
37247
|
-
return this.store.list(
|
|
37231
|
+
list(peerDeviceId, sinceCreatedAt) {
|
|
37232
|
+
return this.store.list(peerDeviceId, sinceCreatedAt);
|
|
37248
37233
|
}
|
|
37249
37234
|
/**
|
|
37250
37235
|
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
37251
37236
|
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
|
|
37252
37237
|
*/
|
|
37253
37238
|
markRead(args) {
|
|
37254
|
-
const before = this.store.list(args.
|
|
37255
|
-
const changed = this.store.markRead(args.
|
|
37239
|
+
const before = this.store.list(args.peerDeviceId);
|
|
37240
|
+
const changed = this.store.markRead(args.peerDeviceId, args.readerDeviceId, args.upToCreatedAt);
|
|
37256
37241
|
if (changed === 0) return 0;
|
|
37257
|
-
const after = this.store.list(args.
|
|
37242
|
+
const after = this.store.list(args.peerDeviceId);
|
|
37258
37243
|
const beforeById = new Map(before.map((m2) => [m2.id, m2]));
|
|
37259
37244
|
for (const m2 of after) {
|
|
37260
37245
|
const prev = beforeById.get(m2.id);
|
|
37261
37246
|
if (!prev) continue;
|
|
37262
|
-
if (prev.readBy[args.
|
|
37263
|
-
this.broadcast(args.
|
|
37247
|
+
if (prev.readBy[args.readerDeviceId] === m2.readBy[args.readerDeviceId]) continue;
|
|
37248
|
+
this.broadcast(args.peerDeviceId, {
|
|
37264
37249
|
type: "inbox:event",
|
|
37265
|
-
|
|
37250
|
+
peerDeviceId: args.peerDeviceId,
|
|
37266
37251
|
message: m2
|
|
37267
37252
|
});
|
|
37268
37253
|
}
|
|
37269
37254
|
return changed;
|
|
37270
37255
|
}
|
|
37271
37256
|
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
37272
|
-
removeChannel(
|
|
37273
|
-
this.store.
|
|
37257
|
+
removeChannel(peerDeviceId) {
|
|
37258
|
+
this.store.removeByPeerDeviceId(peerDeviceId);
|
|
37274
37259
|
}
|
|
37275
37260
|
};
|
|
37276
37261
|
|
|
@@ -37305,25 +37290,31 @@ var ContactStore = class {
|
|
|
37305
37290
|
if (!Array.isArray(arr)) return;
|
|
37306
37291
|
for (const entry of arr) {
|
|
37307
37292
|
const r = ContactSchema.safeParse(entry);
|
|
37308
|
-
if (r.success) this.contacts.set(r.data.
|
|
37293
|
+
if (r.success) this.contacts.set(r.data.deviceId, r.data);
|
|
37309
37294
|
}
|
|
37310
37295
|
}
|
|
37311
37296
|
list() {
|
|
37312
37297
|
return Array.from(this.contacts.values());
|
|
37313
37298
|
}
|
|
37314
|
-
get(
|
|
37315
|
-
return this.contacts.get(
|
|
37299
|
+
get(deviceId) {
|
|
37300
|
+
return this.contacts.get(deviceId) ?? null;
|
|
37316
37301
|
}
|
|
37317
|
-
has(
|
|
37318
|
-
return this.contacts.has(
|
|
37302
|
+
has(deviceId) {
|
|
37303
|
+
return this.contacts.has(deviceId);
|
|
37304
|
+
}
|
|
37305
|
+
/** 某个人(ownerId, provider)的所有设备记录——人级视图(显示/分组用,不参与路由) */
|
|
37306
|
+
findByOwner(ownerId, provider) {
|
|
37307
|
+
return this.list().filter((c) => c.ownerId === ownerId && c.provider === provider);
|
|
37319
37308
|
}
|
|
37320
37309
|
upsert(contact) {
|
|
37321
|
-
this.contacts.set(contact.
|
|
37310
|
+
this.contacts.set(contact.deviceId, contact);
|
|
37322
37311
|
this.flush();
|
|
37323
37312
|
}
|
|
37324
|
-
remove
|
|
37325
|
-
|
|
37326
|
-
this.
|
|
37313
|
+
/** 删单台设备(contact:remove 语义,决策 #16):删该 deviceId 一条,返回是否删到 */
|
|
37314
|
+
removeByDeviceId(deviceId) {
|
|
37315
|
+
const existed = this.contacts.delete(deviceId);
|
|
37316
|
+
if (existed) this.flush();
|
|
37317
|
+
return existed;
|
|
37327
37318
|
}
|
|
37328
37319
|
flush() {
|
|
37329
37320
|
const file = path22.join(this.dataDir, FILE_NAME);
|
|
@@ -37363,10 +37354,10 @@ async function connectRemote(args) {
|
|
|
37363
37354
|
JSON.stringify({
|
|
37364
37355
|
type: "auth",
|
|
37365
37356
|
token: args.token,
|
|
37366
|
-
|
|
37367
|
-
|
|
37368
|
-
//
|
|
37369
|
-
//
|
|
37357
|
+
// 自动反向(spec 决策 #11):带本机可达地址时对方反向建联系人;
|
|
37358
|
+
// 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
|
|
37359
|
+
// 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
|
|
37360
|
+
// 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
|
|
37370
37361
|
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37371
37362
|
})
|
|
37372
37363
|
);
|
|
@@ -37378,7 +37369,7 @@ async function connectRemote(args) {
|
|
|
37378
37369
|
} catch {
|
|
37379
37370
|
return;
|
|
37380
37371
|
}
|
|
37381
|
-
if (f.type === "auth:ok"
|
|
37372
|
+
if (f.type === "auth:ok") {
|
|
37382
37373
|
ws.off("message", onMessage);
|
|
37383
37374
|
resolve6();
|
|
37384
37375
|
return;
|
|
@@ -37424,7 +37415,8 @@ async function connectRemote(args) {
|
|
|
37424
37415
|
whoami: async () => {
|
|
37425
37416
|
const f = await call("whoami", {});
|
|
37426
37417
|
return {
|
|
37427
|
-
ownerId: f.owner.
|
|
37418
|
+
ownerId: f.owner.ownerId,
|
|
37419
|
+
provider: f.owner.provider,
|
|
37428
37420
|
displayName: f.owner.displayName,
|
|
37429
37421
|
grants: f.capability.grants
|
|
37430
37422
|
};
|
|
@@ -37440,23 +37432,36 @@ async function connectRemote(args) {
|
|
|
37440
37432
|
|
|
37441
37433
|
// src/contact/auto-reverse.ts
|
|
37442
37434
|
init_protocol();
|
|
37443
|
-
function autoReverseContact(args) {
|
|
37435
|
+
async function autoReverseContact(args) {
|
|
37444
37436
|
if (!args.selfUrl) return;
|
|
37445
37437
|
const now = args.now ?? Date.now;
|
|
37446
|
-
const
|
|
37447
|
-
|
|
37438
|
+
const grants = PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37439
|
+
resource: { ...g2.resource },
|
|
37440
|
+
actions: [...g2.actions]
|
|
37441
|
+
}));
|
|
37442
|
+
const base = {
|
|
37443
|
+
deviceId: args.deviceId,
|
|
37444
|
+
ownerId: args.ownerId,
|
|
37445
|
+
provider: args.provider,
|
|
37448
37446
|
displayName: args.displayName,
|
|
37449
37447
|
remoteUrl: args.selfUrl,
|
|
37450
|
-
// B 不持有 A 颁的 token:空串。schema 已放宽允许空(不造假数据)。
|
|
37451
37448
|
connectToken: "",
|
|
37452
|
-
grants
|
|
37453
|
-
resource: { ...g2.resource },
|
|
37454
|
-
actions: [...g2.actions]
|
|
37455
|
-
})),
|
|
37449
|
+
grants,
|
|
37456
37450
|
addedAt: now()
|
|
37457
37451
|
};
|
|
37458
|
-
args.store.upsert(
|
|
37459
|
-
args.broadcast({ type: "contact:added", contact });
|
|
37452
|
+
args.store.upsert(base);
|
|
37453
|
+
args.broadcast({ type: "contact:added", contact: base });
|
|
37454
|
+
if (!args.exchangeToken) return;
|
|
37455
|
+
let token;
|
|
37456
|
+
try {
|
|
37457
|
+
token = await args.exchangeToken(args.deviceId);
|
|
37458
|
+
} catch {
|
|
37459
|
+
return;
|
|
37460
|
+
}
|
|
37461
|
+
if (!token) return;
|
|
37462
|
+
const upgraded = { ...base, connectToken: token };
|
|
37463
|
+
args.store.upsert(upgraded);
|
|
37464
|
+
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
37460
37465
|
}
|
|
37461
37466
|
|
|
37462
37467
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
@@ -39163,21 +39168,24 @@ function loadOrCreateAuthFile(opts) {
|
|
|
39163
39168
|
const file = authFilePath(opts.dataDir);
|
|
39164
39169
|
const generate = opts.generate ?? defaultGenerateToken;
|
|
39165
39170
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39171
|
+
const genDevice = opts.genDeviceId ?? defaultGenerateDeviceId;
|
|
39166
39172
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39167
39173
|
const existing = readAuthFile(file);
|
|
39168
|
-
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
|
|
39174
|
+
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId && existing.deviceId) {
|
|
39169
39175
|
return {
|
|
39170
39176
|
token: existing.token,
|
|
39171
39177
|
signSecret: existing.signSecret,
|
|
39172
39178
|
ownerPrincipalId: existing.ownerPrincipalId,
|
|
39179
|
+
deviceId: existing.deviceId,
|
|
39173
39180
|
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39174
39181
|
};
|
|
39175
39182
|
}
|
|
39176
39183
|
const token = existing?.token || generate();
|
|
39177
39184
|
const signSecret = existing?.signSecret || generate();
|
|
39178
39185
|
const ownerPrincipalId = existing?.ownerPrincipalId || genId();
|
|
39186
|
+
const deviceId = existing?.deviceId || genDevice();
|
|
39179
39187
|
const createdAt = existing?.createdAt || now().toISOString();
|
|
39180
|
-
const next = { token, signSecret, ownerPrincipalId, createdAt };
|
|
39188
|
+
const next = { token, signSecret, ownerPrincipalId, deviceId, createdAt };
|
|
39181
39189
|
writeAuthFile(file, next);
|
|
39182
39190
|
return next;
|
|
39183
39191
|
}
|
|
@@ -39187,6 +39195,9 @@ function defaultGenerateToken() {
|
|
|
39187
39195
|
function defaultGenerateOwnerPrincipalId() {
|
|
39188
39196
|
return `owner-${import_node_crypto9.default.randomUUID()}`;
|
|
39189
39197
|
}
|
|
39198
|
+
function defaultGenerateDeviceId() {
|
|
39199
|
+
return `dev-${import_node_crypto9.default.randomUUID()}`;
|
|
39200
|
+
}
|
|
39190
39201
|
function readAuthFile(file) {
|
|
39191
39202
|
try {
|
|
39192
39203
|
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
@@ -39198,6 +39209,7 @@ function readAuthFile(file) {
|
|
|
39198
39209
|
token: parsed.token,
|
|
39199
39210
|
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
39200
39211
|
ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
|
|
39212
|
+
deviceId: typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? parsed.deviceId : void 0,
|
|
39201
39213
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
39202
39214
|
};
|
|
39203
39215
|
} catch (err) {
|
|
@@ -39270,12 +39282,13 @@ var OwnerIdentityStore = class {
|
|
|
39270
39282
|
return null;
|
|
39271
39283
|
}
|
|
39272
39284
|
const r = parsed;
|
|
39273
|
-
if (!r.identity || typeof r.identity.
|
|
39285
|
+
if (!r.identity || typeof r.identity.ownerId !== "string" || r.identity.ownerId.length === 0 || typeof r.identity.provider !== "string" || r.identity.provider.length === 0 || typeof r.identity.displayName !== "string" || r.identity.displayName.length === 0 || typeof r.ttcToken !== "string" || r.ttcToken.length === 0) {
|
|
39274
39286
|
return null;
|
|
39275
39287
|
}
|
|
39276
39288
|
return {
|
|
39277
39289
|
identity: {
|
|
39278
|
-
|
|
39290
|
+
ownerId: r.identity.ownerId,
|
|
39291
|
+
provider: r.identity.provider,
|
|
39279
39292
|
displayName: r.identity.displayName,
|
|
39280
39293
|
...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
|
|
39281
39294
|
},
|
|
@@ -39391,11 +39404,12 @@ async function fetchTtcUserInfo(opts) {
|
|
|
39391
39404
|
throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
|
|
39392
39405
|
}
|
|
39393
39406
|
const d = body.data;
|
|
39394
|
-
if (!d || typeof d.
|
|
39395
|
-
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing
|
|
39407
|
+
if (!d || typeof d.unique_id !== "string" || d.unique_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
|
|
39408
|
+
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing unique_id or name");
|
|
39396
39409
|
}
|
|
39397
39410
|
return {
|
|
39398
|
-
|
|
39411
|
+
ownerId: d.unique_id,
|
|
39412
|
+
provider: "ttc",
|
|
39399
39413
|
displayName: d.name,
|
|
39400
39414
|
...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
|
|
39401
39415
|
};
|
|
@@ -39412,9 +39426,9 @@ var CentralClientError = class extends Error {
|
|
|
39412
39426
|
code;
|
|
39413
39427
|
cause;
|
|
39414
39428
|
};
|
|
39415
|
-
async function centralRequest(opts,
|
|
39429
|
+
async function centralRequest(opts, path60, init) {
|
|
39416
39430
|
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
39417
|
-
const url = `${opts.api.replace(/\/+$/, "")}${
|
|
39431
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path60}`;
|
|
39418
39432
|
const ctrl = new AbortController();
|
|
39419
39433
|
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
39420
39434
|
let res;
|
|
@@ -39448,58 +39462,154 @@ async function centralRequest(opts, path59, init) {
|
|
|
39448
39462
|
async function centralExchange(opts) {
|
|
39449
39463
|
const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
|
|
39450
39464
|
method: "POST",
|
|
39451
|
-
body: {
|
|
39465
|
+
body: {
|
|
39466
|
+
jwt: opts.jwt,
|
|
39467
|
+
provider: opts.provider,
|
|
39468
|
+
deviceId: opts.deviceId,
|
|
39469
|
+
selfDeviceId: opts.selfDeviceId
|
|
39470
|
+
}
|
|
39452
39471
|
});
|
|
39453
|
-
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.
|
|
39454
|
-
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/
|
|
39472
|
+
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
|
|
39473
|
+
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
|
|
39455
39474
|
}
|
|
39456
|
-
const
|
|
39457
|
-
return { token: body.token,
|
|
39475
|
+
const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
|
|
39476
|
+
return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
|
|
39458
39477
|
}
|
|
39459
|
-
async function
|
|
39460
|
-
const body = await centralRequest(opts, "/api/clawd-identity/
|
|
39478
|
+
async function centralGetPublicKey(opts) {
|
|
39479
|
+
const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
|
|
39480
|
+
method: "GET"
|
|
39481
|
+
});
|
|
39482
|
+
if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
|
|
39483
|
+
throw new CentralClientError("BAD_RESPONSE", "public-key: missing or malformed publicKeyPem");
|
|
39484
|
+
}
|
|
39485
|
+
return { publicKeyPem: body.publicKeyPem };
|
|
39486
|
+
}
|
|
39487
|
+
async function centralUpsertDevice(opts) {
|
|
39488
|
+
const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
|
|
39461
39489
|
method: "PUT",
|
|
39462
39490
|
bearer: opts.ttcJwt,
|
|
39463
|
-
body: {
|
|
39491
|
+
body: {
|
|
39492
|
+
deviceId: opts.deviceId,
|
|
39493
|
+
provider: opts.provider,
|
|
39494
|
+
...opts.url ? { url: opts.url } : {},
|
|
39495
|
+
...opts.name ? { name: opts.name } : {}
|
|
39496
|
+
}
|
|
39464
39497
|
});
|
|
39465
39498
|
if (body.ok !== true) {
|
|
39466
|
-
throw new CentralClientError("BAD_RESPONSE", "
|
|
39499
|
+
throw new CentralClientError("BAD_RESPONSE", "upsertDevice: server did not ack ok");
|
|
39467
39500
|
}
|
|
39468
39501
|
return { ok: true };
|
|
39469
39502
|
}
|
|
39470
|
-
async function
|
|
39471
|
-
const body = await centralRequest(
|
|
39472
|
-
|
|
39473
|
-
|
|
39474
|
-
|
|
39475
|
-
|
|
39476
|
-
if (body.
|
|
39477
|
-
|
|
39478
|
-
}
|
|
39479
|
-
if (typeof body.unionId !== "string" || typeof body.displayName !== "string") {
|
|
39480
|
-
throw new CentralClientError("BAD_RESPONSE", "introspect: active but missing identity");
|
|
39481
|
-
}
|
|
39482
|
-
return { active: true, unionId: body.unionId, displayName: body.displayName };
|
|
39483
|
-
}
|
|
39484
|
-
async function centralListHubs(opts) {
|
|
39485
|
-
const body = await centralRequest(opts, "/api/clawd-identity/hubs", {
|
|
39486
|
-
method: "GET",
|
|
39487
|
-
bearer: opts.ttcJwt
|
|
39488
|
-
});
|
|
39489
|
-
if (!Array.isArray(body.hubs)) {
|
|
39490
|
-
throw new CentralClientError("BAD_RESPONSE", "hubs: missing hubs array");
|
|
39503
|
+
async function centralListDevices(opts) {
|
|
39504
|
+
const body = await centralRequest(
|
|
39505
|
+
opts,
|
|
39506
|
+
`/api/clawd-identity/devices?provider=${encodeURIComponent(opts.provider)}`,
|
|
39507
|
+
{ method: "GET", bearer: opts.ttcJwt }
|
|
39508
|
+
);
|
|
39509
|
+
if (!Array.isArray(body.devices)) {
|
|
39510
|
+
throw new CentralClientError("BAD_RESPONSE", "devices: missing devices array");
|
|
39491
39511
|
}
|
|
39492
39512
|
const out = [];
|
|
39493
|
-
for (const raw of body.
|
|
39494
|
-
const
|
|
39495
|
-
if (typeof
|
|
39496
|
-
throw new CentralClientError("BAD_RESPONSE", "
|
|
39513
|
+
for (const raw of body.devices) {
|
|
39514
|
+
const d = raw;
|
|
39515
|
+
if (typeof d.deviceId !== "string" || typeof d.name !== "string" || typeof d.url !== "string") {
|
|
39516
|
+
throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
|
|
39497
39517
|
}
|
|
39498
|
-
out.push({
|
|
39518
|
+
out.push({ deviceId: d.deviceId, name: d.name, url: d.url });
|
|
39499
39519
|
}
|
|
39500
39520
|
return out;
|
|
39501
39521
|
}
|
|
39502
39522
|
|
|
39523
|
+
// src/feishu-auth/verify-token.ts
|
|
39524
|
+
var crypto13 = __toESM(require("crypto"), 1);
|
|
39525
|
+
var CONNECT_TOKEN_PREFIX = "clawdtk1";
|
|
39526
|
+
function verifyConnectToken(args) {
|
|
39527
|
+
const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
39528
|
+
const parts = args.token.split(".");
|
|
39529
|
+
if (parts.length !== 3 || parts[0] !== CONNECT_TOKEN_PREFIX) {
|
|
39530
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39531
|
+
}
|
|
39532
|
+
const [prefix, payloadB64, sigB64] = parts;
|
|
39533
|
+
const data = `${prefix}.${payloadB64}`;
|
|
39534
|
+
let signatureValid = false;
|
|
39535
|
+
try {
|
|
39536
|
+
signatureValid = crypto13.verify(
|
|
39537
|
+
null,
|
|
39538
|
+
Buffer.from(data, "utf8"),
|
|
39539
|
+
args.publicKeyPem,
|
|
39540
|
+
Buffer.from(sigB64, "base64url")
|
|
39541
|
+
);
|
|
39542
|
+
} catch {
|
|
39543
|
+
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
39544
|
+
}
|
|
39545
|
+
if (!signatureValid) {
|
|
39546
|
+
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
39547
|
+
}
|
|
39548
|
+
let payload;
|
|
39549
|
+
try {
|
|
39550
|
+
payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
39551
|
+
} catch {
|
|
39552
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39553
|
+
}
|
|
39554
|
+
if (typeof payload.aud !== "string" || typeof payload.subDevice !== "string" || payload.subDevice.length === 0 || typeof payload.subOwner !== "string" || payload.subOwner.length === 0 || typeof payload.provider !== "string" || payload.provider.length === 0 || typeof payload.name !== "string" || typeof payload.exp !== "number") {
|
|
39555
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39556
|
+
}
|
|
39557
|
+
if (payload.aud !== args.expectedDeviceId) {
|
|
39558
|
+
return { ok: false, reason: "WRONG_AUDIENCE" };
|
|
39559
|
+
}
|
|
39560
|
+
if (payload.exp <= now) {
|
|
39561
|
+
return { ok: false, reason: "EXPIRED" };
|
|
39562
|
+
}
|
|
39563
|
+
return {
|
|
39564
|
+
ok: true,
|
|
39565
|
+
subDevice: payload.subDevice,
|
|
39566
|
+
subOwner: payload.subOwner,
|
|
39567
|
+
provider: payload.provider,
|
|
39568
|
+
displayName: payload.name
|
|
39569
|
+
};
|
|
39570
|
+
}
|
|
39571
|
+
|
|
39572
|
+
// src/feishu-auth/server-key.ts
|
|
39573
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
39574
|
+
var path39 = __toESM(require("path"), 1);
|
|
39575
|
+
var FILE_NAME2 = "server-signing-key.json";
|
|
39576
|
+
var ServerKeyStore = class {
|
|
39577
|
+
constructor(dataDir) {
|
|
39578
|
+
this.dataDir = dataDir;
|
|
39579
|
+
}
|
|
39580
|
+
dataDir;
|
|
39581
|
+
filePath() {
|
|
39582
|
+
return path39.join(this.dataDir, FILE_NAME2);
|
|
39583
|
+
}
|
|
39584
|
+
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
39585
|
+
read() {
|
|
39586
|
+
try {
|
|
39587
|
+
const raw = fs35.readFileSync(this.filePath(), "utf8");
|
|
39588
|
+
const parsed = JSON.parse(raw);
|
|
39589
|
+
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
39590
|
+
return parsed.publicKeyPem;
|
|
39591
|
+
}
|
|
39592
|
+
return null;
|
|
39593
|
+
} catch {
|
|
39594
|
+
return null;
|
|
39595
|
+
}
|
|
39596
|
+
}
|
|
39597
|
+
write(publicKeyPem) {
|
|
39598
|
+
const content = {
|
|
39599
|
+
publicKeyPem,
|
|
39600
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
39601
|
+
};
|
|
39602
|
+
fs35.mkdirSync(this.dataDir, { recursive: true });
|
|
39603
|
+
fs35.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
39604
|
+
}
|
|
39605
|
+
clear() {
|
|
39606
|
+
try {
|
|
39607
|
+
fs35.unlinkSync(this.filePath());
|
|
39608
|
+
} catch {
|
|
39609
|
+
}
|
|
39610
|
+
}
|
|
39611
|
+
};
|
|
39612
|
+
|
|
39503
39613
|
// src/index.ts
|
|
39504
39614
|
init_protocol();
|
|
39505
39615
|
|
|
@@ -39630,23 +39740,15 @@ function buildSessionHandlers(deps) {
|
|
|
39630
39740
|
}
|
|
39631
39741
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
39632
39742
|
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
39633
|
-
const
|
|
39634
|
-
const { response, broadcast } = manager.create(
|
|
39635
|
-
args,
|
|
39636
|
-
creatorPrincipalId,
|
|
39637
|
-
creatorOwnerPrincipalId
|
|
39638
|
-
);
|
|
39743
|
+
const { response, broadcast } = manager.create(args, creatorPrincipalId);
|
|
39639
39744
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39640
39745
|
};
|
|
39641
39746
|
const list = async (_frame, _client, ctx) => {
|
|
39642
39747
|
const { response } = manager.list();
|
|
39643
39748
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39644
39749
|
const sessions = response.sessions ?? [];
|
|
39645
|
-
const
|
|
39646
|
-
const
|
|
39647
|
-
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39648
|
-
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === guestId
|
|
39649
|
-
);
|
|
39750
|
+
const guestDeviceId = ctx.principal.id;
|
|
39751
|
+
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter((s) => s.creatorPrincipalId === guestDeviceId);
|
|
39650
39752
|
return { response: { type: "session:list", sessions: filtered } };
|
|
39651
39753
|
}
|
|
39652
39754
|
return { response: { type: "session:list", ...response } };
|
|
@@ -39912,7 +40014,7 @@ function buildPermissionHandlers(deps) {
|
|
|
39912
40014
|
}
|
|
39913
40015
|
|
|
39914
40016
|
// src/handlers/history.ts
|
|
39915
|
-
var
|
|
40017
|
+
var path42 = __toESM(require("path"), 1);
|
|
39916
40018
|
init_protocol();
|
|
39917
40019
|
|
|
39918
40020
|
// src/session/recent-dirs.ts
|
|
@@ -39930,7 +40032,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
39930
40032
|
}
|
|
39931
40033
|
|
|
39932
40034
|
// src/permission/persona-paths.ts
|
|
39933
|
-
var
|
|
40035
|
+
var path41 = __toESM(require("path"), 1);
|
|
39934
40036
|
function getAllowedPersonaIds(grants, action) {
|
|
39935
40037
|
const ids = /* @__PURE__ */ new Set();
|
|
39936
40038
|
for (const g2 of grants) {
|
|
@@ -39943,26 +40045,26 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
39943
40045
|
return ids;
|
|
39944
40046
|
}
|
|
39945
40047
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
39946
|
-
const root =
|
|
39947
|
-
const target =
|
|
39948
|
-
const sep3 = root.endsWith(
|
|
40048
|
+
const root = path41.resolve(personaRoot);
|
|
40049
|
+
const target = path41.resolve(absPath);
|
|
40050
|
+
const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
39949
40051
|
if (!target.startsWith(root + sep3)) return false;
|
|
39950
|
-
const rel =
|
|
40052
|
+
const rel = path41.relative(root, target);
|
|
39951
40053
|
if (!rel || rel.startsWith("..")) return false;
|
|
39952
|
-
const personaId = rel.split(
|
|
40054
|
+
const personaId = rel.split(path41.sep)[0];
|
|
39953
40055
|
if (!personaId) return false;
|
|
39954
40056
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
39955
40057
|
if (allowed === "*") return true;
|
|
39956
40058
|
return allowed.has(personaId);
|
|
39957
40059
|
}
|
|
39958
40060
|
function personaIdFromPath(absPath, personaRoot) {
|
|
39959
|
-
const root =
|
|
39960
|
-
const target =
|
|
39961
|
-
const sep3 = root.endsWith(
|
|
40061
|
+
const root = path41.resolve(personaRoot);
|
|
40062
|
+
const target = path41.resolve(absPath);
|
|
40063
|
+
const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
39962
40064
|
if (!target.startsWith(root + sep3)) return null;
|
|
39963
|
-
const rel =
|
|
40065
|
+
const rel = path41.relative(root, target);
|
|
39964
40066
|
if (!rel || rel.startsWith("..")) return null;
|
|
39965
|
-
const id = rel.split(
|
|
40067
|
+
const id = rel.split(path41.sep)[0];
|
|
39966
40068
|
return id || null;
|
|
39967
40069
|
}
|
|
39968
40070
|
|
|
@@ -39987,7 +40089,7 @@ function buildHistoryHandlers(deps) {
|
|
|
39987
40089
|
if (!pid) return false;
|
|
39988
40090
|
return isGuestPathAllowed(
|
|
39989
40091
|
ctx.grants,
|
|
39990
|
-
|
|
40092
|
+
path42.join(personaRoot, pid),
|
|
39991
40093
|
personaRoot,
|
|
39992
40094
|
"read"
|
|
39993
40095
|
);
|
|
@@ -39998,7 +40100,7 @@ function buildHistoryHandlers(deps) {
|
|
|
39998
40100
|
};
|
|
39999
40101
|
const list = async (frame, _client, ctx) => {
|
|
40000
40102
|
const args = HistoryListArgs.parse(frame);
|
|
40001
|
-
assertGuestPath(ctx,
|
|
40103
|
+
assertGuestPath(ctx, path42.resolve(args.projectPath), personaRoot, "history:list");
|
|
40002
40104
|
const sessions = await history.listSessions(args);
|
|
40003
40105
|
return { response: { type: "history:list", sessions } };
|
|
40004
40106
|
};
|
|
@@ -40030,13 +40132,13 @@ function buildHistoryHandlers(deps) {
|
|
|
40030
40132
|
};
|
|
40031
40133
|
const subagents = async (frame, _client, ctx) => {
|
|
40032
40134
|
const args = HistorySubagentsArgs.parse(frame);
|
|
40033
|
-
assertGuestPath(ctx,
|
|
40135
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
|
|
40034
40136
|
const subs = await history.listSubagents(args);
|
|
40035
40137
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
40036
40138
|
};
|
|
40037
40139
|
const subagentRead = async (frame, _client, ctx) => {
|
|
40038
40140
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
40039
|
-
assertGuestPath(ctx,
|
|
40141
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
|
|
40040
40142
|
const res = await history.readSubagent(args);
|
|
40041
40143
|
return { response: { type: "history:subagent-read", ...res } };
|
|
40042
40144
|
};
|
|
@@ -40044,7 +40146,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40044
40146
|
const dirs = listRecentDirs(store);
|
|
40045
40147
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40046
40148
|
const filtered = dirs.filter(
|
|
40047
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
40149
|
+
(d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
|
|
40048
40150
|
);
|
|
40049
40151
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
40050
40152
|
}
|
|
@@ -40061,7 +40163,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40061
40163
|
}
|
|
40062
40164
|
|
|
40063
40165
|
// src/handlers/workspace.ts
|
|
40064
|
-
var
|
|
40166
|
+
var path43 = __toESM(require("path"), 1);
|
|
40065
40167
|
var os15 = __toESM(require("os"), 1);
|
|
40066
40168
|
init_protocol();
|
|
40067
40169
|
init_protocol();
|
|
@@ -40102,22 +40204,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40102
40204
|
const args = WorkspaceListArgs.parse(frame);
|
|
40103
40205
|
const isGuest = ctx?.principal.kind === "guest";
|
|
40104
40206
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
|
|
40105
|
-
const resolvedCwd =
|
|
40106
|
-
const target = args.path ?
|
|
40207
|
+
const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
|
|
40208
|
+
const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
40107
40209
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list");
|
|
40108
40210
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
40109
40211
|
return { response: { type: "workspace:list", ...res } };
|
|
40110
40212
|
};
|
|
40111
40213
|
const read = async (frame, _client, ctx) => {
|
|
40112
40214
|
const args = WorkspaceReadArgs.parse(frame);
|
|
40113
|
-
const target =
|
|
40215
|
+
const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
|
|
40114
40216
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read");
|
|
40115
40217
|
const res = workspace.read(args);
|
|
40116
40218
|
return { response: { type: "workspace:read", ...res } };
|
|
40117
40219
|
};
|
|
40118
40220
|
const skillsList = async (frame, _client, ctx) => {
|
|
40119
40221
|
const args = SkillsListArgs.parse(frame);
|
|
40120
|
-
const cwdAbs =
|
|
40222
|
+
const cwdAbs = path43.resolve(args.cwd);
|
|
40121
40223
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
|
|
40122
40224
|
const list2 = skills.list(args);
|
|
40123
40225
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40129,7 +40231,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40129
40231
|
};
|
|
40130
40232
|
const agentsList = async (frame, _client, ctx) => {
|
|
40131
40233
|
const args = AgentsListArgs.parse(frame);
|
|
40132
|
-
const cwdAbs =
|
|
40234
|
+
const cwdAbs = path43.resolve(args.cwd);
|
|
40133
40235
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
|
|
40134
40236
|
const list2 = agents.list(args);
|
|
40135
40237
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40148,7 +40250,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40148
40250
|
}
|
|
40149
40251
|
|
|
40150
40252
|
// src/handlers/git.ts
|
|
40151
|
-
var
|
|
40253
|
+
var path45 = __toESM(require("path"), 1);
|
|
40152
40254
|
init_protocol();
|
|
40153
40255
|
init_protocol();
|
|
40154
40256
|
|
|
@@ -40234,7 +40336,7 @@ async function listGitBranches(cwd) {
|
|
|
40234
40336
|
// src/handlers/git.ts
|
|
40235
40337
|
function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
40236
40338
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40237
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
40339
|
+
if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
|
|
40238
40340
|
throw new ClawdError(
|
|
40239
40341
|
ERROR_CODES.UNAUTHORIZED,
|
|
40240
40342
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -40323,27 +40425,21 @@ function buildCapabilityHandlers(deps) {
|
|
|
40323
40425
|
|
|
40324
40426
|
// src/handlers/inbox.ts
|
|
40325
40427
|
init_protocol();
|
|
40326
|
-
function
|
|
40428
|
+
function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
|
|
40327
40429
|
if (ctx.principal.kind === "owner") {
|
|
40328
|
-
if (!
|
|
40430
|
+
if (!argsPeerDeviceId) {
|
|
40329
40431
|
throw new ClawdError(
|
|
40330
40432
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40331
|
-
"inbox: owner ctx requires
|
|
40433
|
+
"inbox: owner ctx requires peerDeviceId"
|
|
40332
40434
|
);
|
|
40333
40435
|
}
|
|
40334
|
-
return
|
|
40335
|
-
}
|
|
40336
|
-
const resolved = ctx.peerOwnerPrincipalId;
|
|
40337
|
-
if (!resolved) {
|
|
40338
|
-
throw new ClawdError(
|
|
40339
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40340
|
-
`inbox: guest ${ctx.principal.id} has no peerOwnerPrincipalId (auth frame missing selfPrincipalId)`
|
|
40341
|
-
);
|
|
40436
|
+
return argsPeerDeviceId;
|
|
40342
40437
|
}
|
|
40343
|
-
|
|
40438
|
+
const resolved = ctx.principal.id;
|
|
40439
|
+
if (argsPeerDeviceId && argsPeerDeviceId !== resolved) {
|
|
40344
40440
|
throw new ClawdError(
|
|
40345
40441
|
ERROR_CODES.UNAUTHORIZED,
|
|
40346
|
-
`inbox: guest args.
|
|
40442
|
+
`inbox: guest args.peerDeviceId mismatch (resolved=${resolved}, args=${argsPeerDeviceId})`
|
|
40347
40443
|
);
|
|
40348
40444
|
}
|
|
40349
40445
|
return resolved;
|
|
@@ -40356,10 +40452,10 @@ function buildInboxHandlers(deps) {
|
|
|
40356
40452
|
if (!ctx) {
|
|
40357
40453
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40358
40454
|
}
|
|
40359
|
-
const
|
|
40455
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40360
40456
|
const message = manager.postMessage({
|
|
40361
|
-
|
|
40362
|
-
|
|
40457
|
+
peerDeviceId,
|
|
40458
|
+
senderDeviceId: ctx.principal.id,
|
|
40363
40459
|
text: args.text,
|
|
40364
40460
|
id: args.id,
|
|
40365
40461
|
createdAt: args.createdAt
|
|
@@ -40374,10 +40470,10 @@ function buildInboxHandlers(deps) {
|
|
|
40374
40470
|
if (!ctx) {
|
|
40375
40471
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40376
40472
|
}
|
|
40377
|
-
const
|
|
40378
|
-
const messages = manager.list(
|
|
40473
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40474
|
+
const messages = manager.list(peerDeviceId, args.sinceCreatedAt);
|
|
40379
40475
|
return {
|
|
40380
|
-
response: { type: "inbox:list:ok",
|
|
40476
|
+
response: { type: "inbox:list:ok", peerDeviceId, messages }
|
|
40381
40477
|
};
|
|
40382
40478
|
};
|
|
40383
40479
|
const markRead = async (frame, _client, ctx) => {
|
|
@@ -40386,16 +40482,16 @@ function buildInboxHandlers(deps) {
|
|
|
40386
40482
|
if (!ctx) {
|
|
40387
40483
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40388
40484
|
}
|
|
40389
|
-
const
|
|
40485
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40390
40486
|
const updated = manager.markRead({
|
|
40391
|
-
|
|
40392
|
-
|
|
40487
|
+
peerDeviceId,
|
|
40488
|
+
readerDeviceId: ctx.principal.id,
|
|
40393
40489
|
upToCreatedAt: args.upToCreatedAt
|
|
40394
40490
|
});
|
|
40395
40491
|
return {
|
|
40396
40492
|
response: {
|
|
40397
40493
|
type: "inbox:markRead:ok",
|
|
40398
|
-
|
|
40494
|
+
peerDeviceId,
|
|
40399
40495
|
upToCreatedAt: args.upToCreatedAt,
|
|
40400
40496
|
updated
|
|
40401
40497
|
}
|
|
@@ -40432,15 +40528,15 @@ function buildContactHandlers(deps) {
|
|
|
40432
40528
|
ensureOwner(ctx);
|
|
40433
40529
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40434
40530
|
const args = ContactRemoveArgsSchema.parse(rest);
|
|
40435
|
-
deps.store.
|
|
40531
|
+
deps.store.removeByDeviceId(args.deviceId);
|
|
40436
40532
|
deps.broadcast({
|
|
40437
40533
|
type: "contact:removed",
|
|
40438
|
-
|
|
40534
|
+
deviceId: args.deviceId
|
|
40439
40535
|
});
|
|
40440
40536
|
return {
|
|
40441
40537
|
response: {
|
|
40442
40538
|
type: "contact:remove:ok",
|
|
40443
|
-
|
|
40539
|
+
deviceId: args.deviceId
|
|
40444
40540
|
}
|
|
40445
40541
|
};
|
|
40446
40542
|
};
|
|
@@ -40457,7 +40553,11 @@ function buildWhoamiHandler(deps) {
|
|
|
40457
40553
|
if (!ctx) {
|
|
40458
40554
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
40459
40555
|
}
|
|
40460
|
-
const owner =
|
|
40556
|
+
const owner = {
|
|
40557
|
+
...makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName),
|
|
40558
|
+
ownerId: deps.ownerId,
|
|
40559
|
+
provider: deps.ownerProvider
|
|
40560
|
+
};
|
|
40461
40561
|
let capability;
|
|
40462
40562
|
if (ctx.principal.kind === "owner") {
|
|
40463
40563
|
capability = {
|
|
@@ -40518,7 +40618,9 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
40518
40618
|
return {
|
|
40519
40619
|
response: {
|
|
40520
40620
|
type: "auth:getIdentity:ok",
|
|
40521
|
-
identity: record ? record.identity : null
|
|
40621
|
+
identity: record ? record.identity : null,
|
|
40622
|
+
// 决策 #15:设备属性,与登录态无关——UI"分享我的 Hub 标识"用
|
|
40623
|
+
deviceId: deps.deviceId
|
|
40522
40624
|
}
|
|
40523
40625
|
};
|
|
40524
40626
|
};
|
|
@@ -40535,32 +40637,32 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
40535
40637
|
};
|
|
40536
40638
|
}
|
|
40537
40639
|
|
|
40538
|
-
// src/handlers/
|
|
40640
|
+
// src/handlers/device.ts
|
|
40539
40641
|
init_protocol();
|
|
40540
40642
|
function ensureOwner2(ctx) {
|
|
40541
40643
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40542
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED:
|
|
40644
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
40543
40645
|
}
|
|
40544
40646
|
}
|
|
40545
|
-
function
|
|
40647
|
+
function buildDeviceHandlers(deps) {
|
|
40546
40648
|
const now = deps.now ?? Date.now;
|
|
40547
40649
|
const list = async (_frame, _client, ctx) => {
|
|
40548
40650
|
ensureOwner2(ctx);
|
|
40549
|
-
const
|
|
40651
|
+
const devices = await deps.listDevices();
|
|
40550
40652
|
return {
|
|
40551
|
-
response: { type: "
|
|
40653
|
+
response: { type: "device:list:ok", devices }
|
|
40552
40654
|
};
|
|
40553
40655
|
};
|
|
40554
40656
|
const connect = async (frame, _client, ctx) => {
|
|
40555
40657
|
ensureOwner2(ctx);
|
|
40556
40658
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40557
|
-
const args =
|
|
40558
|
-
const exchanged = await deps.exchange(args.
|
|
40559
|
-
const url = args.url ?? exchanged.
|
|
40659
|
+
const args = DeviceConnectArgsSchema.parse(rest);
|
|
40660
|
+
const exchanged = await deps.exchange(args.deviceId);
|
|
40661
|
+
const url = args.url ?? exchanged.deviceUrl;
|
|
40560
40662
|
if (!url) {
|
|
40561
40663
|
throw new ClawdError(
|
|
40562
40664
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40563
|
-
"
|
|
40665
|
+
"DEVICE_URL_UNKNOWN: \u5BF9\u65B9\u8BBE\u5907\u672A\u4E0A\u62A5\u5730\u5740\uFF08\u672A\u767B\u5F55\u6216\u672A\u5F00 tunnel\uFF09\uFF0C\u8BF7\u663E\u5F0F\u63D0\u4F9B url \u6216\u7B49\u5BF9\u65B9\u4E0A\u7EBF"
|
|
40564
40666
|
);
|
|
40565
40667
|
}
|
|
40566
40668
|
let conn;
|
|
@@ -40568,26 +40670,30 @@ function buildHubHandlers(deps) {
|
|
|
40568
40670
|
conn = await deps.connectRemote({
|
|
40569
40671
|
url,
|
|
40570
40672
|
token: exchanged.token,
|
|
40571
|
-
|
|
40572
|
-
|
|
40573
|
-
//
|
|
40574
|
-
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)
|
|
40673
|
+
// 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
|
|
40674
|
+
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
|
|
40675
|
+
// 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
|
|
40575
40676
|
selfUrl: deps.selfUrl() ?? void 0
|
|
40576
40677
|
});
|
|
40577
40678
|
} catch (e) {
|
|
40578
40679
|
throw new ClawdError(
|
|
40579
40680
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40580
|
-
`
|
|
40681
|
+
`DEVICE_UNREACHABLE: ${e.message}`
|
|
40581
40682
|
);
|
|
40582
40683
|
}
|
|
40583
40684
|
try {
|
|
40584
40685
|
const wh = await conn.whoami();
|
|
40585
40686
|
const contact = {
|
|
40586
|
-
|
|
40587
|
-
|
|
40687
|
+
deviceId: args.deviceId,
|
|
40688
|
+
ownerId: wh.ownerId,
|
|
40689
|
+
provider: wh.provider,
|
|
40690
|
+
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
40691
|
+
// 如"公共1");"输入标识"连接不带 name → 回落 whoami 报的对方 owner 真名。
|
|
40692
|
+
// 归属人身份不丢(ownerId/provider 仍来自 whoami 背书)。
|
|
40693
|
+
displayName: args.name || wh.displayName || args.deviceId,
|
|
40588
40694
|
remoteUrl: url,
|
|
40589
|
-
//
|
|
40590
|
-
//
|
|
40695
|
+
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
40696
|
+
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
40591
40697
|
connectToken: exchanged.token,
|
|
40592
40698
|
grants: wh.grants,
|
|
40593
40699
|
addedAt: now()
|
|
@@ -40596,8 +40702,9 @@ function buildHubHandlers(deps) {
|
|
|
40596
40702
|
deps.broadcast({ type: "contact:added", contact });
|
|
40597
40703
|
return {
|
|
40598
40704
|
response: {
|
|
40599
|
-
type: "
|
|
40600
|
-
|
|
40705
|
+
type: "device:connect:ok",
|
|
40706
|
+
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
40707
|
+
deviceId: args.deviceId,
|
|
40601
40708
|
name: contact.displayName,
|
|
40602
40709
|
url
|
|
40603
40710
|
}
|
|
@@ -40610,8 +40717,8 @@ function buildHubHandlers(deps) {
|
|
|
40610
40717
|
}
|
|
40611
40718
|
};
|
|
40612
40719
|
return {
|
|
40613
|
-
"
|
|
40614
|
-
"
|
|
40720
|
+
"device:list": list,
|
|
40721
|
+
"device:connect": connect
|
|
40615
40722
|
};
|
|
40616
40723
|
}
|
|
40617
40724
|
|
|
@@ -41687,11 +41794,13 @@ function buildMethodHandlers(deps) {
|
|
|
41687
41794
|
whoami: buildWhoamiHandler({
|
|
41688
41795
|
ownerDisplayName: deps.ownerDisplayName,
|
|
41689
41796
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
41797
|
+
ownerId: deps.ownerId,
|
|
41798
|
+
ownerProvider: deps.ownerProvider,
|
|
41690
41799
|
personaStore: deps.personaStore,
|
|
41691
41800
|
capabilityManager: deps.capabilityManager
|
|
41692
41801
|
}),
|
|
41693
41802
|
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
41694
|
-
...
|
|
41803
|
+
...buildDeviceHandlers(deps.feishuDevice),
|
|
41695
41804
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
41696
41805
|
...buildExtensionHandlers({
|
|
41697
41806
|
loadAll,
|
|
@@ -41726,14 +41835,14 @@ var METHOD_GRANT_MAP = {
|
|
|
41726
41835
|
"capability:list": ADMIN_ANY,
|
|
41727
41836
|
"capability:delete": ADMIN_ANY,
|
|
41728
41837
|
// ---- inbox: 双投 P2P IM ----
|
|
41729
|
-
// 三条都 'public' — 能连上 =
|
|
41730
|
-
//
|
|
41838
|
+
// 三条都 'public' — 能连上 = 已通过本地验票. handler 内额外校验 guest 的
|
|
41839
|
+
// args.peerDeviceId 与 ctx.principal.id(=deviceId) 一致, 防 guest 越权操作别的 DM 桶.
|
|
41731
41840
|
"inbox:list": { kind: "public" },
|
|
41732
41841
|
"inbox:markRead": { kind: "public" },
|
|
41733
41842
|
"inbox:postMessage": { kind: "public" },
|
|
41734
41843
|
// ---- contact:* (联系人列表) ----
|
|
41735
41844
|
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名 contact:*。
|
|
41736
|
-
// 写入路径在
|
|
41845
|
+
// 写入路径在 device:connect / 自动反向;list / remove 作联系人读 / 删,owner-only。
|
|
41737
41846
|
"contact:list": ADMIN_ANY,
|
|
41738
41847
|
"contact:remove": ADMIN_ANY,
|
|
41739
41848
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
@@ -41838,10 +41947,10 @@ var METHOD_GRANT_MAP = {
|
|
|
41838
41947
|
"auth:login:start": ADMIN_ANY,
|
|
41839
41948
|
"auth:getIdentity": ADMIN_ANY,
|
|
41840
41949
|
"auth:logout": ADMIN_ANY,
|
|
41841
|
-
// ----
|
|
41842
|
-
//
|
|
41843
|
-
"
|
|
41844
|
-
"
|
|
41950
|
+
// ---- device:* 设备目录 + 连接(决策 #15,owner-only) ----
|
|
41951
|
+
// 设备目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
41952
|
+
"device:list": ADMIN_ANY,
|
|
41953
|
+
"device:connect": ADMIN_ANY
|
|
41845
41954
|
};
|
|
41846
41955
|
function computeGrantForFrame(method, frame) {
|
|
41847
41956
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -42234,15 +42343,18 @@ async function startDaemon(config) {
|
|
|
42234
42343
|
const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
|
|
42235
42344
|
const feishuIdentity = ownerIdentityStore.read();
|
|
42236
42345
|
let feishuActive = feishuIdentity !== null;
|
|
42237
|
-
|
|
42346
|
+
const ownerPrincipalId = authFile.deviceId;
|
|
42347
|
+
let ownerId = feishuIdentity?.identity.ownerId ?? "";
|
|
42348
|
+
let ownerProvider = feishuIdentity?.identity.provider ?? "";
|
|
42238
42349
|
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
42239
42350
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
42240
42351
|
const inboxStore = new InboxStore(config.dataDir);
|
|
42241
|
-
const inboxManager = new InboxManager(inboxStore, (
|
|
42352
|
+
const inboxManager = new InboxManager(inboxStore, (_peerDeviceId, frame) => {
|
|
42242
42353
|
wsServer?.broadcastToOwners(frame);
|
|
42243
42354
|
});
|
|
42244
42355
|
const contactStore = new ContactStore(config.dataDir);
|
|
42245
42356
|
contactStore.load();
|
|
42357
|
+
const serverKeyStore = new ServerKeyStore(config.dataDir);
|
|
42246
42358
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
42247
42359
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
42248
42360
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
@@ -42270,33 +42382,57 @@ async function startDaemon(config) {
|
|
|
42270
42382
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
42271
42383
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
42272
42384
|
expectedToken: resolvedAuthToken,
|
|
42273
|
-
authenticate: async (t,
|
|
42385
|
+
authenticate: async (t, selfUrl) => {
|
|
42274
42386
|
const result = await authenticate(t, {
|
|
42275
42387
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
42276
42388
|
ownerPrincipalId,
|
|
42277
42389
|
ownerDisplayName,
|
|
42278
|
-
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest
|
|
42279
|
-
// → 一律 BAD_TOKEN
|
|
42280
|
-
//
|
|
42390
|
+
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入验签函数
|
|
42391
|
+
// → 一律 BAD_TOKEN)。已激活时用缓存的 server 公钥本地验签;
|
|
42392
|
+
// 公钥缓存缺失(登录后拉取失败)→ fail-closed 拒绝 guest。
|
|
42281
42393
|
...feishuActive ? {
|
|
42282
|
-
|
|
42283
|
-
const
|
|
42284
|
-
if (!
|
|
42285
|
-
return
|
|
42286
|
-
|
|
42287
|
-
|
|
42288
|
-
|
|
42394
|
+
verifyConnectToken: (token) => {
|
|
42395
|
+
const publicKeyPem = serverKeyStore.read();
|
|
42396
|
+
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
42397
|
+
return verifyConnectToken({
|
|
42398
|
+
token,
|
|
42399
|
+
publicKeyPem,
|
|
42400
|
+
expectedDeviceId: authFile.deviceId
|
|
42289
42401
|
});
|
|
42290
42402
|
}
|
|
42291
42403
|
} : {}
|
|
42292
42404
|
});
|
|
42293
42405
|
if (result.ok && result.context.principal.kind === "guest") {
|
|
42294
|
-
autoReverseContact({
|
|
42406
|
+
void autoReverseContact({
|
|
42295
42407
|
store: contactStore,
|
|
42296
42408
|
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42297
|
-
|
|
42409
|
+
deviceId: result.context.principal.id,
|
|
42410
|
+
ownerId: result.context.ownerId ?? "",
|
|
42411
|
+
provider: result.context.provider ?? "",
|
|
42298
42412
|
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
42299
|
-
selfUrl
|
|
42413
|
+
selfUrl,
|
|
42414
|
+
// 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
|
|
42415
|
+
// 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
|
|
42416
|
+
exchangeToken: async (targetDeviceId) => {
|
|
42417
|
+
const record = ownerIdentityStore.read();
|
|
42418
|
+
if (!record) return null;
|
|
42419
|
+
try {
|
|
42420
|
+
const r = await centralExchange({
|
|
42421
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42422
|
+
jwt: record.ttcToken,
|
|
42423
|
+
provider: record.identity.provider,
|
|
42424
|
+
deviceId: targetDeviceId,
|
|
42425
|
+
selfDeviceId: authFile.deviceId
|
|
42426
|
+
});
|
|
42427
|
+
return r.token;
|
|
42428
|
+
} catch (e) {
|
|
42429
|
+
logger.warn("auto-reverse exchange token failed", {
|
|
42430
|
+
targetDeviceId,
|
|
42431
|
+
err: e.message
|
|
42432
|
+
});
|
|
42433
|
+
return null;
|
|
42434
|
+
}
|
|
42435
|
+
}
|
|
42300
42436
|
});
|
|
42301
42437
|
}
|
|
42302
42438
|
return result;
|
|
@@ -42449,20 +42585,40 @@ async function startDaemon(config) {
|
|
|
42449
42585
|
}
|
|
42450
42586
|
return `http://${config.host}:${config.port}`;
|
|
42451
42587
|
};
|
|
42452
|
-
const
|
|
42453
|
-
if (!feishuActive
|
|
42588
|
+
const reportDevice = async () => {
|
|
42589
|
+
if (!feishuActive) return;
|
|
42454
42590
|
const record = ownerIdentityStore.read();
|
|
42455
42591
|
if (!record) return;
|
|
42456
42592
|
try {
|
|
42457
|
-
await
|
|
42593
|
+
await centralUpsertDevice({
|
|
42458
42594
|
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42459
42595
|
ttcJwt: record.ttcToken,
|
|
42460
|
-
|
|
42596
|
+
provider: record.identity.provider,
|
|
42597
|
+
deviceId: authFile.deviceId,
|
|
42598
|
+
url: currentTunnelUrl ?? void 0,
|
|
42461
42599
|
name: ownerDisplayName
|
|
42462
42600
|
});
|
|
42463
|
-
logger.info("
|
|
42601
|
+
logger.info("device registered to central server", {
|
|
42602
|
+
deviceId: authFile.deviceId,
|
|
42603
|
+
url: currentTunnelUrl ?? "(none)"
|
|
42604
|
+
});
|
|
42464
42605
|
} catch (err) {
|
|
42465
|
-
logger.warn("
|
|
42606
|
+
logger.warn("device register failed (best-effort)", { err: err.message });
|
|
42607
|
+
}
|
|
42608
|
+
};
|
|
42609
|
+
const fetchServerKey = async () => {
|
|
42610
|
+
if (!feishuActive) return;
|
|
42611
|
+
if (serverKeyStore.read()) return;
|
|
42612
|
+
try {
|
|
42613
|
+
const { publicKeyPem } = await centralGetPublicKey({
|
|
42614
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API
|
|
42615
|
+
});
|
|
42616
|
+
serverKeyStore.write(publicKeyPem);
|
|
42617
|
+
logger.info("server signing key cached");
|
|
42618
|
+
} catch (err) {
|
|
42619
|
+
logger.warn("server signing key fetch failed (guest \u5165\u7AD9\u5C06\u88AB\u62D2\u7EDD)", {
|
|
42620
|
+
err: err.message
|
|
42621
|
+
});
|
|
42466
42622
|
}
|
|
42467
42623
|
};
|
|
42468
42624
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
@@ -42522,8 +42678,10 @@ async function startDaemon(config) {
|
|
|
42522
42678
|
capabilityManager,
|
|
42523
42679
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
42524
42680
|
ownerDisplayName,
|
|
42525
|
-
//
|
|
42681
|
+
// 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
|
|
42526
42682
|
ownerPrincipalId,
|
|
42683
|
+
ownerId,
|
|
42684
|
+
ownerProvider,
|
|
42527
42685
|
personaStore,
|
|
42528
42686
|
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
42529
42687
|
capabilityRegistry,
|
|
@@ -42531,7 +42689,7 @@ async function startDaemon(config) {
|
|
|
42531
42689
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
42532
42690
|
inboxManager,
|
|
42533
42691
|
inboxStore,
|
|
42534
|
-
// 联系人列表 store(
|
|
42692
|
+
// 联系人列表 store(device:connect / 自动反向落同一 store)
|
|
42535
42693
|
contactStore,
|
|
42536
42694
|
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42537
42695
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
@@ -42542,40 +42700,43 @@ async function startDaemon(config) {
|
|
|
42542
42700
|
publishedChannelStore,
|
|
42543
42701
|
bundleCache,
|
|
42544
42702
|
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
42545
|
-
|
|
42546
|
-
|
|
42547
|
-
//
|
|
42703
|
+
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
42704
|
+
feishuAuth: { loginFlow, ownerIdentityStore, deviceId: authFile.deviceId },
|
|
42705
|
+
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
42706
|
+
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
42548
42707
|
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
42549
42708
|
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
42550
|
-
|
|
42709
|
+
feishuDevice: {
|
|
42551
42710
|
store: contactStore,
|
|
42552
|
-
exchange: async (
|
|
42711
|
+
exchange: async (deviceId) => {
|
|
42553
42712
|
const record = ownerIdentityStore.read();
|
|
42554
42713
|
if (!record) {
|
|
42555
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "
|
|
42714
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
42556
42715
|
}
|
|
42557
42716
|
return centralExchange({
|
|
42558
42717
|
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42559
|
-
|
|
42560
|
-
|
|
42718
|
+
jwt: record.ttcToken,
|
|
42719
|
+
provider: record.identity.provider,
|
|
42720
|
+
deviceId,
|
|
42721
|
+
selfDeviceId: authFile.deviceId
|
|
42561
42722
|
});
|
|
42562
42723
|
},
|
|
42563
|
-
|
|
42724
|
+
listDevices: async () => {
|
|
42564
42725
|
const record = ownerIdentityStore.read();
|
|
42565
42726
|
if (!record) {
|
|
42566
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "
|
|
42727
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
42567
42728
|
}
|
|
42568
|
-
return
|
|
42729
|
+
return centralListDevices({
|
|
42569
42730
|
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42570
|
-
ttcJwt: record.ttcToken
|
|
42731
|
+
ttcJwt: record.ttcToken,
|
|
42732
|
+
provider: record.identity.provider
|
|
42571
42733
|
});
|
|
42572
42734
|
},
|
|
42573
42735
|
connectRemote,
|
|
42574
42736
|
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42575
|
-
|
|
42576
|
-
|
|
42577
|
-
//
|
|
42578
|
-
// 透传让 hub 反向加我为联系人。无 tunnel(仅本机 ws)→ null,hub 不自动反向(不造假数据)。
|
|
42737
|
+
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
42738
|
+
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
42739
|
+
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
42579
42740
|
selfUrl: () => currentTunnelUrl
|
|
42580
42741
|
}
|
|
42581
42742
|
});
|
|
@@ -42616,12 +42777,14 @@ async function startDaemon(config) {
|
|
|
42616
42777
|
const result = await loginFlow.handleCallback(params);
|
|
42617
42778
|
if (result.ok) {
|
|
42618
42779
|
feishuActive = true;
|
|
42619
|
-
|
|
42780
|
+
ownerId = result.identity.ownerId;
|
|
42781
|
+
ownerProvider = result.identity.provider;
|
|
42620
42782
|
ownerDisplayName = result.identity.displayName;
|
|
42621
42783
|
handlers = makeHandlers();
|
|
42622
42784
|
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
42623
42785
|
setImmediate(() => wsServer?.closeAllClients());
|
|
42624
|
-
void
|
|
42786
|
+
void reportDevice();
|
|
42787
|
+
void fetchServerKey();
|
|
42625
42788
|
} else {
|
|
42626
42789
|
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
42627
42790
|
}
|
|
@@ -42652,13 +42815,12 @@ async function startDaemon(config) {
|
|
|
42652
42815
|
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
42653
42816
|
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
42654
42817
|
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
42655
|
-
tryVerifyCapabilityToken: (token
|
|
42818
|
+
tryVerifyCapabilityToken: (token) => {
|
|
42656
42819
|
const v2 = capabilityRegistry.verifyToken(token);
|
|
42657
42820
|
if (!v2.ok) return null;
|
|
42658
42821
|
return {
|
|
42659
42822
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
42660
|
-
grants: v2.capability.grants
|
|
42661
|
-
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
42823
|
+
grants: v2.capability.grants
|
|
42662
42824
|
};
|
|
42663
42825
|
},
|
|
42664
42826
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
@@ -42807,7 +42969,6 @@ ${bar}
|
|
|
42807
42969
|
import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
42808
42970
|
} catch {
|
|
42809
42971
|
}
|
|
42810
|
-
void reportHubUrl();
|
|
42811
42972
|
} catch (err) {
|
|
42812
42973
|
const tunnelError = err?.message ?? String(err);
|
|
42813
42974
|
try {
|
|
@@ -42830,6 +42991,8 @@ ${bar}
|
|
|
42830
42991
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
42831
42992
|
}
|
|
42832
42993
|
}
|
|
42994
|
+
void reportDevice();
|
|
42995
|
+
void fetchServerKey();
|
|
42833
42996
|
const tickAttachmentGc = () => {
|
|
42834
42997
|
try {
|
|
42835
42998
|
runAttachmentGc({
|