@clawos-dev/clawd 0.2.113-beta.230.380ae15 → 0.2.114-beta.231.042ed43
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 +1473 -866
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -132,15 +132,11 @@ var init_methods = __esm({
|
|
|
132
132
|
"attachment.groupRemove",
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
|
|
135
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
|
|
136
|
+
// device:connect 已完整替代(中心 server 签发自包含 connect token)。capability:list / capability:delete
|
|
137
|
+
// 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
|
|
139
138
|
"capability:list",
|
|
140
139
|
"capability:delete",
|
|
141
|
-
// global personal token (2026-05-25): UI invite/accept dialog 挂载即调,拿 personal
|
|
142
|
-
// token + capability + shareBaseUrl 用于拼 inviteUrl / autoAttach 反向推。owner-only。
|
|
143
|
-
"personal-cap:get",
|
|
144
140
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
145
141
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
146
142
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -148,14 +144,12 @@ var init_methods = __esm({
|
|
|
148
144
|
"inbox:list",
|
|
149
145
|
"inbox:markRead",
|
|
150
146
|
"inbox:postMessage",
|
|
151
|
-
// ----
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"received-capability:remove",
|
|
158
|
-
"received-capability:autoAttach",
|
|
147
|
+
// ---- contact:* (联系人列表) ----
|
|
148
|
+
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名为 contact:*——Phase 2 后
|
|
149
|
+
// 联系人身份由中心 server introspect 背书,不再是"对方颁发的 capability"。
|
|
150
|
+
// 写入路径:device:connect 落 store + 自动反向(决策 #11);list / remove 作联系人列表读删。
|
|
151
|
+
"contact:list",
|
|
152
|
+
"contact:remove",
|
|
159
153
|
// ---- whoami (v2 capability platform) ----
|
|
160
154
|
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
161
155
|
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
@@ -181,6 +175,17 @@ var init_methods = __esm({
|
|
|
181
175
|
// 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
|
|
182
176
|
"extension.install-from-channel",
|
|
183
177
|
"extension.update-from-channel",
|
|
178
|
+
// ---- auth:* 飞书统一身份 Phase 1 (spec 2026-06-01-feishu-identity-design) ----
|
|
179
|
+
// owner-only:daemon loopback 登录流程(auth:login:start 起 state → HTTP /auth/callback
|
|
180
|
+
// 回调落盘 → auth:login:done 事件)。中央字面量见 protocol/feishu-auth.ts FEISHU_AUTH_METHODS。
|
|
181
|
+
"auth:login:start",
|
|
182
|
+
"auth:getIdentity",
|
|
183
|
+
"auth:logout",
|
|
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",
|
|
184
189
|
// ---- app-builder Project (spec 2026-06-01 picker → 2026-06-02 单栏默认 refactor) ----
|
|
185
190
|
// persona-app-builder Project 管理;schemas: ProjectMetadataSchema + 5 个 args/result 对。
|
|
186
191
|
// listProjects: readdir projects/,按目录读 .clawd-project.json
|
|
@@ -265,6 +270,10 @@ var init_errors = __esm({
|
|
|
265
270
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
266
271
|
FORBIDDEN: "FORBIDDEN",
|
|
267
272
|
INVALID_PARAM: "INVALID_PARAM",
|
|
273
|
+
// 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
|
|
274
|
+
// daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
|
|
275
|
+
// UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
|
|
276
|
+
FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED",
|
|
268
277
|
// app-builder 单栏默认 refactor (spec 2026-06-02-app-builder-single-pane-default-design):
|
|
269
278
|
// appBuilder:createProject 加 personaId + session 未绑校验后抛的两个错码。
|
|
270
279
|
WRONG_PERSONA: "WRONG_PERSONA",
|
|
@@ -698,8 +707,8 @@ var init_parseUtil = __esm({
|
|
|
698
707
|
init_errors2();
|
|
699
708
|
init_en();
|
|
700
709
|
makeIssue = (params) => {
|
|
701
|
-
const { data, path:
|
|
702
|
-
const fullPath = [...
|
|
710
|
+
const { data, path: path60, errorMaps, issueData } = params;
|
|
711
|
+
const fullPath = [...path60, ...issueData.path || []];
|
|
703
712
|
const fullIssue = {
|
|
704
713
|
...issueData,
|
|
705
714
|
path: fullPath
|
|
@@ -1010,11 +1019,11 @@ var init_types = __esm({
|
|
|
1010
1019
|
init_parseUtil();
|
|
1011
1020
|
init_util();
|
|
1012
1021
|
ParseInputLazyPath = class {
|
|
1013
|
-
constructor(parent, value,
|
|
1022
|
+
constructor(parent, value, path60, key) {
|
|
1014
1023
|
this._cachedPath = [];
|
|
1015
1024
|
this.parent = parent;
|
|
1016
1025
|
this.data = value;
|
|
1017
|
-
this._path =
|
|
1026
|
+
this._path = path60;
|
|
1018
1027
|
this._key = key;
|
|
1019
1028
|
}
|
|
1020
1029
|
get path() {
|
|
@@ -4655,53 +4664,41 @@ var init_schemas = __esm({
|
|
|
4655
4664
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4656
4665
|
chatId: external_exports.string().min(1).optional(),
|
|
4657
4666
|
/**
|
|
4658
|
-
* 创建该 session
|
|
4659
|
-
*
|
|
4660
|
-
*
|
|
4661
|
-
*
|
|
4667
|
+
* 创建该 session 的设备 id(决策 #16:从 ctx.principal.id 派生 = deviceId)。
|
|
4668
|
+
* UI 端 self 判定收敛为单一比较 `!creatorPrincipalId || creatorPrincipalId === 本机 deviceId`
|
|
4669
|
+
* → 「我自己创建」vs「别的设备接入创建」。MyPersonas 二级分组按 creatorPrincipalId(设备)
|
|
4670
|
+
* 分桶、按 contact(deviceId→ownerName) 反查显示名。
|
|
4662
4671
|
*
|
|
4663
|
-
*
|
|
4664
|
-
|
|
4665
|
-
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4666
|
-
/**
|
|
4667
|
-
* 创建该 session 的接入者 owner identity(auth 帧 selfPrincipalId)。global personal token
|
|
4668
|
-
* (#735) 后 creatorPrincipalId 在 guest ctx 永远是同一张 personal cap.id,UI 端无法区分
|
|
4669
|
-
* 接入者;这个字段把"是哪个人"的信息固定下来。
|
|
4672
|
+
* 命名保留 creatorPrincipalId(命名债,语义已是 creatorDeviceId);值由 connect token
|
|
4673
|
+
* subDevice 背书(guest ctx)或本机 deviceId(owner ctx)。optional 兼容老 session。
|
|
4670
4674
|
*
|
|
4671
|
-
*
|
|
4672
|
-
*
|
|
4673
|
-
*
|
|
4674
|
-
* - 旧 per-peer cap 路径 / 老 session:undefined(UI 视作 self 兼容)
|
|
4675
|
-
*
|
|
4676
|
-
* UI 用法:
|
|
4677
|
-
* - Recents/Active/Pinned 过滤:`!creatorOwnerPrincipalId || === ownerPrincipalId` 才显示
|
|
4678
|
-
* - MyPersonas 二级分组:non-self → 按 contacts 反查 displayName 分桶
|
|
4679
|
-
*
|
|
4680
|
-
* 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
|
|
4675
|
+
* 决策 #16 删除了 creatorOwnerPrincipalId(personal token #735 遗留)——当年 guest 的
|
|
4676
|
+
* principal.id 都是同张 cap.id 区分不了人才加它;现在 principal.id = 设备 deviceId、
|
|
4677
|
+
* 归属人由 contact(deviceId→ownerId) 反查,该字段理由消失。
|
|
4681
4678
|
*/
|
|
4682
|
-
|
|
4679
|
+
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4683
4680
|
/**
|
|
4684
|
-
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon
|
|
4685
|
-
*
|
|
4686
|
-
*
|
|
4681
|
+
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上创建的 session**,
|
|
4682
|
+
* 远端是 source of truth,本机是持久化副本,方便 sidebar 跨设备聚合。
|
|
4683
|
+
* 字段值 = 远端 daemon 的 deviceId(决策 #16:朋友设备标识,即我接入的那台机器)。
|
|
4687
4684
|
*
|
|
4688
4685
|
* 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
|
|
4689
4686
|
* 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
|
|
4690
4687
|
*
|
|
4691
4688
|
* - undefined / 缺省 → 本机 native session(在我自己机器上跑)
|
|
4692
|
-
* - 非空 → 我的会话在该
|
|
4689
|
+
* - 非空 → 我的会话在该 deviceId 朋友的 daemon 上跑,本机为持久化副本
|
|
4693
4690
|
*
|
|
4694
|
-
*
|
|
4695
|
-
* | 形态 | creatorPrincipalId
|
|
4696
|
-
*
|
|
4697
|
-
* | 本机 owner 自创 | 本机
|
|
4698
|
-
* |
|
|
4699
|
-
* | 我在朋友 daemon
|
|
4691
|
+
* 四类 session 取值矩阵(决策 #16 deviceId 版,creatorOwnerPrincipalId 列已删):
|
|
4692
|
+
* | 形态 | creatorPrincipalId(=设备) | originOwnerPrincipalId(=朋友设备) |
|
|
4693
|
+
* |-------------------------------------|--------------------------|----------------------------------|
|
|
4694
|
+
* | 本机 owner 自创 | 本机 deviceId | undefined |
|
|
4695
|
+
* | 本机被别的设备接入创建 | 接入者 deviceId(token 背书) | undefined |
|
|
4696
|
+
* | 我在朋友 daemon 上创建(mirror 回本机) | 我的 deviceId | 朋友 deviceId |
|
|
4700
4697
|
*
|
|
4701
4698
|
* UI 用法:
|
|
4702
4699
|
* - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
|
|
4703
4700
|
* - 行内显示 `@<朋友>` chip 暗示位置
|
|
4704
|
-
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)`
|
|
4701
|
+
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端(按 deviceId)
|
|
4705
4702
|
*
|
|
4706
4703
|
* daemon 用法:
|
|
4707
4704
|
* - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
|
|
@@ -5135,18 +5132,14 @@ var init_schemas = __esm({
|
|
|
5135
5132
|
token: external_exports.string().min(1),
|
|
5136
5133
|
scheme: external_exports.literal("bearer").optional(),
|
|
5137
5134
|
/**
|
|
5138
|
-
*
|
|
5139
|
-
*
|
|
5140
|
-
*
|
|
5141
|
-
*
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
/**
|
|
5145
|
-
* 同 selfPrincipalId:自报 owner displayName(人类可读),让对端 daemon 把
|
|
5146
|
-
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5147
|
-
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5135
|
+
* 决策 #16:自报身份字段(selfPrincipalId / selfDisplayName / selfDeviceId)全部删除——
|
|
5136
|
+
* guest 的设备 id / 人 id / 来源全部由 connect token 的 subDevice/subOwner/provider 签名背书
|
|
5137
|
+
* (server 签、目标设备本地验签,不可伪造),不再接受任何自报身份。
|
|
5138
|
+
*
|
|
5139
|
+
* selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
|
|
5140
|
+
* 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
|
|
5148
5141
|
*/
|
|
5149
|
-
|
|
5142
|
+
selfUrl: external_exports.string().min(1).optional()
|
|
5150
5143
|
});
|
|
5151
5144
|
AuthOkFrameSchema = external_exports.object({
|
|
5152
5145
|
type: external_exports.literal("auth:ok"),
|
|
@@ -5359,7 +5352,7 @@ function stripSecretHash(cap) {
|
|
|
5359
5352
|
const { secretHash: _hash, ...wire } = cap;
|
|
5360
5353
|
return wire;
|
|
5361
5354
|
}
|
|
5362
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS
|
|
5355
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5363
5356
|
var init_capability = __esm({
|
|
5364
5357
|
"../protocol/src/capability.ts"() {
|
|
5365
5358
|
"use strict";
|
|
@@ -5411,7 +5404,11 @@ var init_capability = __esm({
|
|
|
5411
5404
|
owner: external_exports.object({
|
|
5412
5405
|
id: external_exports.string(),
|
|
5413
5406
|
kind: external_exports.enum(["owner", "guest"]),
|
|
5414
|
-
displayName: external_exports.string()
|
|
5407
|
+
displayName: external_exports.string(),
|
|
5408
|
+
/** 归属人 id(决策 #16:TTC unique_id 等,落 contact.ownerId) */
|
|
5409
|
+
ownerId: external_exports.string(),
|
|
5410
|
+
/** 身份来源(决策 #16:'ttc'/'google',落 contact.provider) */
|
|
5411
|
+
provider: external_exports.string()
|
|
5415
5412
|
}).strict(),
|
|
5416
5413
|
capability: CapabilityWireSchema,
|
|
5417
5414
|
grantedPersonas: external_exports.array(
|
|
@@ -5427,13 +5424,6 @@ var init_capability = __esm({
|
|
|
5427
5424
|
actions: Object.freeze(["read", "send"])
|
|
5428
5425
|
})
|
|
5429
5426
|
]);
|
|
5430
|
-
PersonalCapGetResponseSchema = external_exports.object({
|
|
5431
|
-
type: external_exports.literal("personal-cap:get:ok"),
|
|
5432
|
-
token: external_exports.string().min(1),
|
|
5433
|
-
capability: CapabilityWireSchema,
|
|
5434
|
-
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5435
|
-
shareBaseUrl: external_exports.string().min(1)
|
|
5436
|
-
}).strict();
|
|
5437
5427
|
}
|
|
5438
5428
|
});
|
|
5439
5429
|
|
|
@@ -5445,83 +5435,80 @@ var init_inbox = __esm({
|
|
|
5445
5435
|
init_zod();
|
|
5446
5436
|
InboxMessageSchema = external_exports.object({
|
|
5447
5437
|
id: external_exports.string().min(1),
|
|
5448
|
-
|
|
5449
|
-
|
|
5438
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5439
|
+
senderDeviceId: external_exports.string().min(1),
|
|
5450
5440
|
text: external_exports.string().min(1),
|
|
5451
5441
|
createdAt: external_exports.number().int().nonnegative(),
|
|
5452
5442
|
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5453
5443
|
}).strict();
|
|
5454
5444
|
InboxPostMessageArgsSchema = external_exports.object({
|
|
5455
|
-
|
|
5445
|
+
peerDeviceId: external_exports.string().min(1).optional(),
|
|
5456
5446
|
id: external_exports.string().min(1),
|
|
5457
5447
|
text: external_exports.string().min(1),
|
|
5458
5448
|
createdAt: external_exports.number().int().nonnegative()
|
|
5459
5449
|
}).strict();
|
|
5460
5450
|
InboxListArgsSchema = external_exports.object({
|
|
5461
|
-
|
|
5451
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5462
5452
|
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5463
5453
|
}).strict();
|
|
5464
5454
|
InboxMarkReadArgsSchema = external_exports.object({
|
|
5465
|
-
|
|
5455
|
+
peerDeviceId: external_exports.string().min(1),
|
|
5466
5456
|
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5467
5457
|
}).strict();
|
|
5468
5458
|
}
|
|
5469
5459
|
});
|
|
5470
5460
|
|
|
5471
|
-
// ../protocol/src/
|
|
5472
|
-
var
|
|
5473
|
-
var
|
|
5474
|
-
"../protocol/src/
|
|
5461
|
+
// ../protocol/src/contact.ts
|
|
5462
|
+
var ContactSchema, ContactWireSchema, ContactRemoveArgsSchema, ContactRemoveOkSchema, ContactListOkSchema, ContactAddedFrameSchema, ContactRemovedFrameSchema;
|
|
5463
|
+
var init_contact = __esm({
|
|
5464
|
+
"../protocol/src/contact.ts"() {
|
|
5475
5465
|
"use strict";
|
|
5476
5466
|
init_zod();
|
|
5477
5467
|
init_capability();
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5468
|
+
ContactSchema = external_exports.object({
|
|
5469
|
+
/**
|
|
5470
|
+
* 对方设备标识(主键):对方 daemon 自己生成并持久化的标识(auth.json deviceId,
|
|
5471
|
+
* 如 dev-<uuid>),provider 无关。同 deviceId 重复 upsert 覆盖。
|
|
5472
|
+
*/
|
|
5473
|
+
deviceId: external_exports.string().min(1),
|
|
5474
|
+
/**
|
|
5475
|
+
* 归属人 id(决策 #16):TTC unique_id 等,由 connect token subOwner 签名背书。
|
|
5476
|
+
* 与 provider 一起唯一标识一个人。DM 不再按它路由(路由用 deviceId),仅显示/分组用。
|
|
5477
|
+
*/
|
|
5478
|
+
ownerId: external_exports.string().min(1),
|
|
5479
|
+
/** 身份来源(决策 #16):'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...,token 背书 */
|
|
5480
|
+
provider: external_exports.string().min(1),
|
|
5481
|
+
/** 联系人显示名 */
|
|
5482
|
+
displayName: external_exports.string(),
|
|
5483
|
+
/** 该设备可达的 ws/tunnel 地址 */
|
|
5481
5484
|
remoteUrl: external_exports.string().min(1),
|
|
5482
|
-
|
|
5483
|
-
|
|
5485
|
+
// 自动反向建立联系人(spec 决策 #11):被连接方本地验票成功后自动落 A 为
|
|
5486
|
+
// 联系人,但它不持有 A 颁发的 token(A 从未给它换票)→ connectToken 为空串。
|
|
5487
|
+
// device:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5488
|
+
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5489
|
+
connectToken: external_exports.string(),
|
|
5484
5490
|
grants: external_exports.array(GrantSchema),
|
|
5485
|
-
|
|
5486
|
-
}).strict();
|
|
5487
|
-
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5488
|
-
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5489
|
-
remoteUrl: external_exports.string().min(1),
|
|
5490
|
-
token: external_exports.string().min(1)
|
|
5491
|
+
addedAt: external_exports.number().int().nonnegative()
|
|
5491
5492
|
}).strict();
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5493
|
+
ContactWireSchema = ContactSchema;
|
|
5494
|
+
ContactRemoveArgsSchema = external_exports.object({
|
|
5495
|
+
deviceId: external_exports.string().min(1)
|
|
5495
5496
|
}).strict();
|
|
5496
|
-
|
|
5497
|
-
|
|
5497
|
+
ContactRemoveOkSchema = external_exports.object({
|
|
5498
|
+
type: external_exports.literal("contact:remove:ok"),
|
|
5499
|
+
deviceId: external_exports.string().min(1)
|
|
5498
5500
|
}).strict();
|
|
5499
|
-
|
|
5500
|
-
type: external_exports.literal("
|
|
5501
|
-
|
|
5501
|
+
ContactListOkSchema = external_exports.object({
|
|
5502
|
+
type: external_exports.literal("contact:list:ok"),
|
|
5503
|
+
contacts: external_exports.array(ContactWireSchema)
|
|
5502
5504
|
}).strict();
|
|
5503
|
-
|
|
5504
|
-
type: external_exports.literal("
|
|
5505
|
-
|
|
5505
|
+
ContactAddedFrameSchema = external_exports.object({
|
|
5506
|
+
type: external_exports.literal("contact:added"),
|
|
5507
|
+
contact: ContactWireSchema
|
|
5506
5508
|
}).strict();
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
remoteUrl: external_exports.string().min(1),
|
|
5511
|
-
capabilityId: external_exports.string().min(1),
|
|
5512
|
-
token: external_exports.string().min(1),
|
|
5513
|
-
grants: external_exports.array(GrantSchema)
|
|
5514
|
-
}).strict();
|
|
5515
|
-
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5516
|
-
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5517
|
-
}).strict();
|
|
5518
|
-
ReceivedCapabilityAddedFrameSchema = external_exports.object({
|
|
5519
|
-
type: external_exports.literal("received-capability:added"),
|
|
5520
|
-
receivedCap: ReceivedCapabilityWireSchema
|
|
5521
|
-
}).strict();
|
|
5522
|
-
ReceivedCapabilityRemovedFrameSchema = external_exports.object({
|
|
5523
|
-
type: external_exports.literal("received-capability:removed"),
|
|
5524
|
-
ownerPrincipalId: external_exports.string().min(1)
|
|
5509
|
+
ContactRemovedFrameSchema = external_exports.object({
|
|
5510
|
+
type: external_exports.literal("contact:removed"),
|
|
5511
|
+
deviceId: external_exports.string().min(1)
|
|
5525
5512
|
}).strict();
|
|
5526
5513
|
}
|
|
5527
5514
|
});
|
|
@@ -5725,6 +5712,107 @@ var init_extension = __esm({
|
|
|
5725
5712
|
}
|
|
5726
5713
|
});
|
|
5727
5714
|
|
|
5715
|
+
// ../protocol/src/feishu-auth.ts
|
|
5716
|
+
var FEISHU_GATED_METHODS, DeviceEntrySchema, DeviceListResponseSchema, DeviceConnectArgsSchema, DeviceConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
|
|
5717
|
+
var init_feishu_auth = __esm({
|
|
5718
|
+
"../protocol/src/feishu-auth.ts"() {
|
|
5719
|
+
"use strict";
|
|
5720
|
+
init_zod();
|
|
5721
|
+
FEISHU_GATED_METHODS = [
|
|
5722
|
+
// capability(调试 / 撤销,仍属远程身份体系)
|
|
5723
|
+
"capability:list",
|
|
5724
|
+
"capability:delete",
|
|
5725
|
+
// 联系人列表(device:connect 落同一 store;list 读 / remove 删)
|
|
5726
|
+
"contact:list",
|
|
5727
|
+
"contact:remove",
|
|
5728
|
+
// DM / 跨用户通知
|
|
5729
|
+
"inbox:list",
|
|
5730
|
+
"inbox:markRead",
|
|
5731
|
+
"inbox:postMessage",
|
|
5732
|
+
// mirror peer sessions(远端会话镜像)
|
|
5733
|
+
"peerSession:upsert",
|
|
5734
|
+
"peerSession:remove",
|
|
5735
|
+
// 设备目录 + 连接(决策 #15:daemon 代理中心 server,需要 owner 的 ttcJwt)
|
|
5736
|
+
"device:list",
|
|
5737
|
+
"device:connect"
|
|
5738
|
+
];
|
|
5739
|
+
DeviceEntrySchema = external_exports.object({
|
|
5740
|
+
/**
|
|
5741
|
+
* 设备标识(决策 #15:daemon 本地生成的 dev-<uuid>,与人的身份 unionId 无关)。
|
|
5742
|
+
* 中心 server clawd_devices 注册表仅作服务发现(url 下发 + 目录),不在安全路径上。
|
|
5743
|
+
*/
|
|
5744
|
+
deviceId: external_exports.string().min(1),
|
|
5745
|
+
/** 显示名 */
|
|
5746
|
+
name: external_exports.string().min(1),
|
|
5747
|
+
/** ws/tunnel 地址 */
|
|
5748
|
+
url: external_exports.string().min(1)
|
|
5749
|
+
});
|
|
5750
|
+
DeviceListResponseSchema = external_exports.object({
|
|
5751
|
+
type: external_exports.literal("device:list:ok"),
|
|
5752
|
+
devices: external_exports.array(DeviceEntrySchema)
|
|
5753
|
+
});
|
|
5754
|
+
DeviceConnectArgsSchema = external_exports.object({
|
|
5755
|
+
/** 目标设备的 deviceId(对方 daemon 生成的 dev-<uuid>) */
|
|
5756
|
+
deviceId: external_exports.string().min(1),
|
|
5757
|
+
/**
|
|
5758
|
+
* 目标设备的 ws/tunnel 地址(可选):
|
|
5759
|
+
* - 公开目录点击:带 url(device:list 已下发,省一次 server 查询)
|
|
5760
|
+
* - 凭 deviceId 连接:缺省 url → daemon exchange 时由中心 server 下发该设备上报的 url
|
|
5761
|
+
* - 注册表不在安全路径上:知道对方 url 时显式传入即可连接(不依赖中心 server 服务发现)
|
|
5762
|
+
*/
|
|
5763
|
+
url: external_exports.string().min(1).optional(),
|
|
5764
|
+
/** 显示名(公共设备来自目录;缺省时连上后用 whoami 回填) */
|
|
5765
|
+
name: external_exports.string().optional()
|
|
5766
|
+
});
|
|
5767
|
+
DeviceConnectResponseSchema = external_exports.object({
|
|
5768
|
+
type: external_exports.literal("device:connect:ok"),
|
|
5769
|
+
/** 连接的目标设备标识(= 请求 args.deviceId) */
|
|
5770
|
+
deviceId: external_exports.string(),
|
|
5771
|
+
name: external_exports.string(),
|
|
5772
|
+
url: external_exports.string()
|
|
5773
|
+
});
|
|
5774
|
+
FeishuIdentitySchema = external_exports.object({
|
|
5775
|
+
/** 归属人 id(决策 #16:TTC unique_id,取代飞书 union_id;跨登录方式稳定) */
|
|
5776
|
+
ownerId: external_exports.string().min(1),
|
|
5777
|
+
/** 身份来源:'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...(决策 #16,拓展用) */
|
|
5778
|
+
provider: external_exports.string().min(1),
|
|
5779
|
+
/** 显示名。TTC user_info 接口 data.name */
|
|
5780
|
+
displayName: external_exports.string().min(1),
|
|
5781
|
+
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
5782
|
+
avatarUrl: external_exports.string().optional()
|
|
5783
|
+
});
|
|
5784
|
+
AuthLoginStartResponseSchema = external_exports.object({
|
|
5785
|
+
type: external_exports.literal("auth:login:start:ok"),
|
|
5786
|
+
/** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
|
|
5787
|
+
authUrl: external_exports.string().min(1),
|
|
5788
|
+
/** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
|
|
5789
|
+
state: external_exports.string().min(1)
|
|
5790
|
+
});
|
|
5791
|
+
AuthGetIdentityResponseSchema = external_exports.object({
|
|
5792
|
+
type: external_exports.literal("auth:getIdentity:ok"),
|
|
5793
|
+
/** null = 未登录 */
|
|
5794
|
+
identity: FeishuIdentitySchema.nullable(),
|
|
5795
|
+
/**
|
|
5796
|
+
* 本机设备标识(决策 #15:daemon 生成并持久化在 auth.json,如 dev-<uuid>)。
|
|
5797
|
+
* 设备属性,与登录态无关、登录登出不变。UI"我的设备标识"用它拼
|
|
5798
|
+
* clawos://device/<deviceId>(替代旧的 unionId——不再向外暴露人的身份标识)。
|
|
5799
|
+
*/
|
|
5800
|
+
deviceId: external_exports.string().min(1)
|
|
5801
|
+
});
|
|
5802
|
+
AuthLogoutResponseSchema = external_exports.object({
|
|
5803
|
+
type: external_exports.literal("auth:logout:ok")
|
|
5804
|
+
});
|
|
5805
|
+
AuthLoginDoneEventSchema = external_exports.object({
|
|
5806
|
+
type: external_exports.literal("auth:login:done"),
|
|
5807
|
+
identity: FeishuIdentitySchema
|
|
5808
|
+
});
|
|
5809
|
+
AuthLoginFailedEventSchema = external_exports.object({
|
|
5810
|
+
type: external_exports.literal("auth:login:failed"),
|
|
5811
|
+
reason: external_exports.string()
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
});
|
|
5815
|
+
|
|
5728
5816
|
// ../protocol/src/runtime.ts
|
|
5729
5817
|
var init_runtime = __esm({
|
|
5730
5818
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5740,8 +5828,9 @@ var init_runtime = __esm({
|
|
|
5740
5828
|
init_principal();
|
|
5741
5829
|
init_capability();
|
|
5742
5830
|
init_inbox();
|
|
5743
|
-
|
|
5831
|
+
init_contact();
|
|
5744
5832
|
init_extension();
|
|
5833
|
+
init_feishu_auth();
|
|
5745
5834
|
}
|
|
5746
5835
|
});
|
|
5747
5836
|
|
|
@@ -6016,8 +6105,8 @@ var require_req = __commonJS({
|
|
|
6016
6105
|
if (req.originalUrl) {
|
|
6017
6106
|
_req.url = req.originalUrl;
|
|
6018
6107
|
} else {
|
|
6019
|
-
const
|
|
6020
|
-
_req.url = typeof
|
|
6108
|
+
const path60 = req.path;
|
|
6109
|
+
_req.url = typeof path60 === "string" ? path60 : req.url ? req.url.path || req.url : void 0;
|
|
6021
6110
|
}
|
|
6022
6111
|
if (req.query) {
|
|
6023
6112
|
_req.query = req.query;
|
|
@@ -6182,14 +6271,14 @@ var require_redact = __commonJS({
|
|
|
6182
6271
|
}
|
|
6183
6272
|
return obj;
|
|
6184
6273
|
}
|
|
6185
|
-
function parsePath(
|
|
6274
|
+
function parsePath(path60) {
|
|
6186
6275
|
const parts = [];
|
|
6187
6276
|
let current = "";
|
|
6188
6277
|
let inBrackets = false;
|
|
6189
6278
|
let inQuotes = false;
|
|
6190
6279
|
let quoteChar = "";
|
|
6191
|
-
for (let i = 0; i <
|
|
6192
|
-
const char =
|
|
6280
|
+
for (let i = 0; i < path60.length; i++) {
|
|
6281
|
+
const char = path60[i];
|
|
6193
6282
|
if (!inBrackets && char === ".") {
|
|
6194
6283
|
if (current) {
|
|
6195
6284
|
parts.push(current);
|
|
@@ -6320,10 +6409,10 @@ var require_redact = __commonJS({
|
|
|
6320
6409
|
return current;
|
|
6321
6410
|
}
|
|
6322
6411
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6323
|
-
for (const
|
|
6324
|
-
const parts = parsePath(
|
|
6412
|
+
for (const path60 of paths) {
|
|
6413
|
+
const parts = parsePath(path60);
|
|
6325
6414
|
if (parts.includes("*")) {
|
|
6326
|
-
redactWildcardPath(obj, parts, censor,
|
|
6415
|
+
redactWildcardPath(obj, parts, censor, path60, remove);
|
|
6327
6416
|
} else {
|
|
6328
6417
|
if (remove) {
|
|
6329
6418
|
removeKey(obj, parts);
|
|
@@ -6408,8 +6497,8 @@ var require_redact = __commonJS({
|
|
|
6408
6497
|
}
|
|
6409
6498
|
} else {
|
|
6410
6499
|
if (afterWildcard.includes("*")) {
|
|
6411
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6412
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6500
|
+
const wrappedCensor = typeof censor === "function" ? (value, path60) => {
|
|
6501
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path60];
|
|
6413
6502
|
return censor(value, fullPath);
|
|
6414
6503
|
} : censor;
|
|
6415
6504
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6444,8 +6533,8 @@ var require_redact = __commonJS({
|
|
|
6444
6533
|
return null;
|
|
6445
6534
|
}
|
|
6446
6535
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6447
|
-
for (const
|
|
6448
|
-
const parts = parsePath(
|
|
6536
|
+
for (const path60 of pathsToClone) {
|
|
6537
|
+
const parts = parsePath(path60);
|
|
6449
6538
|
let current = pathStructure;
|
|
6450
6539
|
for (let i = 0; i < parts.length; i++) {
|
|
6451
6540
|
const part = parts[i];
|
|
@@ -6497,24 +6586,24 @@ var require_redact = __commonJS({
|
|
|
6497
6586
|
}
|
|
6498
6587
|
return cloneSelectively(obj, pathStructure);
|
|
6499
6588
|
}
|
|
6500
|
-
function validatePath(
|
|
6501
|
-
if (typeof
|
|
6589
|
+
function validatePath(path60) {
|
|
6590
|
+
if (typeof path60 !== "string") {
|
|
6502
6591
|
throw new Error("Paths must be (non-empty) strings");
|
|
6503
6592
|
}
|
|
6504
|
-
if (
|
|
6593
|
+
if (path60 === "") {
|
|
6505
6594
|
throw new Error("Invalid redaction path ()");
|
|
6506
6595
|
}
|
|
6507
|
-
if (
|
|
6508
|
-
throw new Error(`Invalid redaction path (${
|
|
6596
|
+
if (path60.includes("..")) {
|
|
6597
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6509
6598
|
}
|
|
6510
|
-
if (
|
|
6511
|
-
throw new Error(`Invalid redaction path (${
|
|
6599
|
+
if (path60.includes(",")) {
|
|
6600
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6512
6601
|
}
|
|
6513
6602
|
let bracketCount = 0;
|
|
6514
6603
|
let inQuotes = false;
|
|
6515
6604
|
let quoteChar = "";
|
|
6516
|
-
for (let i = 0; i <
|
|
6517
|
-
const char =
|
|
6605
|
+
for (let i = 0; i < path60.length; i++) {
|
|
6606
|
+
const char = path60[i];
|
|
6518
6607
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6519
6608
|
if (!inQuotes) {
|
|
6520
6609
|
inQuotes = true;
|
|
@@ -6528,20 +6617,20 @@ var require_redact = __commonJS({
|
|
|
6528
6617
|
} else if (char === "]" && !inQuotes) {
|
|
6529
6618
|
bracketCount--;
|
|
6530
6619
|
if (bracketCount < 0) {
|
|
6531
|
-
throw new Error(`Invalid redaction path (${
|
|
6620
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6532
6621
|
}
|
|
6533
6622
|
}
|
|
6534
6623
|
}
|
|
6535
6624
|
if (bracketCount !== 0) {
|
|
6536
|
-
throw new Error(`Invalid redaction path (${
|
|
6625
|
+
throw new Error(`Invalid redaction path (${path60})`);
|
|
6537
6626
|
}
|
|
6538
6627
|
}
|
|
6539
6628
|
function validatePaths(paths) {
|
|
6540
6629
|
if (!Array.isArray(paths)) {
|
|
6541
6630
|
throw new TypeError("paths must be an array");
|
|
6542
6631
|
}
|
|
6543
|
-
for (const
|
|
6544
|
-
validatePath(
|
|
6632
|
+
for (const path60 of paths) {
|
|
6633
|
+
validatePath(path60);
|
|
6545
6634
|
}
|
|
6546
6635
|
}
|
|
6547
6636
|
function slowRedact(options = {}) {
|
|
@@ -6709,8 +6798,8 @@ var require_redaction = __commonJS({
|
|
|
6709
6798
|
if (shape[k2] === null) {
|
|
6710
6799
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6711
6800
|
} else {
|
|
6712
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6713
|
-
return censor(value, [k2, ...
|
|
6801
|
+
const wrappedCensor = typeof censor === "function" ? (value, path60) => {
|
|
6802
|
+
return censor(value, [k2, ...path60]);
|
|
6714
6803
|
} : censor;
|
|
6715
6804
|
o[k2] = Redact({
|
|
6716
6805
|
paths: shape[k2],
|
|
@@ -6928,10 +7017,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6928
7017
|
var require_sonic_boom = __commonJS({
|
|
6929
7018
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6930
7019
|
"use strict";
|
|
6931
|
-
var
|
|
7020
|
+
var fs49 = require("fs");
|
|
6932
7021
|
var EventEmitter3 = require("events");
|
|
6933
7022
|
var inherits = require("util").inherits;
|
|
6934
|
-
var
|
|
7023
|
+
var path60 = require("path");
|
|
6935
7024
|
var sleep = require_atomic_sleep();
|
|
6936
7025
|
var assert = require("assert");
|
|
6937
7026
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6985,20 +7074,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6985
7074
|
const mode = sonic.mode;
|
|
6986
7075
|
if (sonic.sync) {
|
|
6987
7076
|
try {
|
|
6988
|
-
if (sonic.mkdir)
|
|
6989
|
-
const fd =
|
|
7077
|
+
if (sonic.mkdir) fs49.mkdirSync(path60.dirname(file), { recursive: true });
|
|
7078
|
+
const fd = fs49.openSync(file, flags, mode);
|
|
6990
7079
|
fileOpened(null, fd);
|
|
6991
7080
|
} catch (err) {
|
|
6992
7081
|
fileOpened(err);
|
|
6993
7082
|
throw err;
|
|
6994
7083
|
}
|
|
6995
7084
|
} else if (sonic.mkdir) {
|
|
6996
|
-
|
|
7085
|
+
fs49.mkdir(path60.dirname(file), { recursive: true }, (err) => {
|
|
6997
7086
|
if (err) return fileOpened(err);
|
|
6998
|
-
|
|
7087
|
+
fs49.open(file, flags, mode, fileOpened);
|
|
6999
7088
|
});
|
|
7000
7089
|
} else {
|
|
7001
|
-
|
|
7090
|
+
fs49.open(file, flags, mode, fileOpened);
|
|
7002
7091
|
}
|
|
7003
7092
|
}
|
|
7004
7093
|
function SonicBoom(opts) {
|
|
@@ -7039,8 +7128,8 @@ var require_sonic_boom = __commonJS({
|
|
|
7039
7128
|
this.flush = flushBuffer;
|
|
7040
7129
|
this.flushSync = flushBufferSync;
|
|
7041
7130
|
this._actualWrite = actualWriteBuffer;
|
|
7042
|
-
fsWriteSync = () =>
|
|
7043
|
-
fsWrite = () =>
|
|
7131
|
+
fsWriteSync = () => fs49.writeSync(this.fd, this._writingBuf);
|
|
7132
|
+
fsWrite = () => fs49.write(this.fd, this._writingBuf, this.release);
|
|
7044
7133
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
7045
7134
|
this._writingBuf = "";
|
|
7046
7135
|
this.write = write;
|
|
@@ -7049,15 +7138,15 @@ var require_sonic_boom = __commonJS({
|
|
|
7049
7138
|
this._actualWrite = actualWrite;
|
|
7050
7139
|
fsWriteSync = () => {
|
|
7051
7140
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7052
|
-
return
|
|
7141
|
+
return fs49.writeSync(this.fd, this._writingBuf);
|
|
7053
7142
|
}
|
|
7054
|
-
return
|
|
7143
|
+
return fs49.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7055
7144
|
};
|
|
7056
7145
|
fsWrite = () => {
|
|
7057
7146
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7058
|
-
return
|
|
7147
|
+
return fs49.write(this.fd, this._writingBuf, this.release);
|
|
7059
7148
|
}
|
|
7060
|
-
return
|
|
7149
|
+
return fs49.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
7061
7150
|
};
|
|
7062
7151
|
} else {
|
|
7063
7152
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7114,7 +7203,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7114
7203
|
}
|
|
7115
7204
|
}
|
|
7116
7205
|
if (this._fsync) {
|
|
7117
|
-
|
|
7206
|
+
fs49.fsyncSync(this.fd);
|
|
7118
7207
|
}
|
|
7119
7208
|
const len = this._len;
|
|
7120
7209
|
if (this._reopening) {
|
|
@@ -7228,7 +7317,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7228
7317
|
const onDrain = () => {
|
|
7229
7318
|
if (!this._fsync) {
|
|
7230
7319
|
try {
|
|
7231
|
-
|
|
7320
|
+
fs49.fsync(this.fd, (err) => {
|
|
7232
7321
|
this._flushPending = false;
|
|
7233
7322
|
cb(err);
|
|
7234
7323
|
});
|
|
@@ -7330,7 +7419,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7330
7419
|
const fd = this.fd;
|
|
7331
7420
|
this.once("ready", () => {
|
|
7332
7421
|
if (fd !== this.fd) {
|
|
7333
|
-
|
|
7422
|
+
fs49.close(fd, (err) => {
|
|
7334
7423
|
if (err) {
|
|
7335
7424
|
return this.emit("error", err);
|
|
7336
7425
|
}
|
|
@@ -7379,7 +7468,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7379
7468
|
buf = this._bufs[0];
|
|
7380
7469
|
}
|
|
7381
7470
|
try {
|
|
7382
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7471
|
+
const n = Buffer.isBuffer(buf) ? fs49.writeSync(this.fd, buf) : fs49.writeSync(this.fd, buf, "utf8");
|
|
7383
7472
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7384
7473
|
buf = releasedBufObj.writingBuf;
|
|
7385
7474
|
this._len = releasedBufObj.len;
|
|
@@ -7395,7 +7484,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7395
7484
|
}
|
|
7396
7485
|
}
|
|
7397
7486
|
try {
|
|
7398
|
-
|
|
7487
|
+
fs49.fsyncSync(this.fd);
|
|
7399
7488
|
} catch {
|
|
7400
7489
|
}
|
|
7401
7490
|
}
|
|
@@ -7416,7 +7505,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7416
7505
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7417
7506
|
}
|
|
7418
7507
|
try {
|
|
7419
|
-
const n =
|
|
7508
|
+
const n = fs49.writeSync(this.fd, buf);
|
|
7420
7509
|
buf = buf.subarray(n);
|
|
7421
7510
|
this._len = Math.max(this._len - n, 0);
|
|
7422
7511
|
if (buf.length <= 0) {
|
|
@@ -7444,13 +7533,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7444
7533
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7445
7534
|
if (this.sync) {
|
|
7446
7535
|
try {
|
|
7447
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7536
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs49.writeSync(this.fd, this._writingBuf) : fs49.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7448
7537
|
release(null, written);
|
|
7449
7538
|
} catch (err) {
|
|
7450
7539
|
release(err);
|
|
7451
7540
|
}
|
|
7452
7541
|
} else {
|
|
7453
|
-
|
|
7542
|
+
fs49.write(this.fd, this._writingBuf, release);
|
|
7454
7543
|
}
|
|
7455
7544
|
}
|
|
7456
7545
|
function actualWriteBuffer() {
|
|
@@ -7459,7 +7548,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7459
7548
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7460
7549
|
if (this.sync) {
|
|
7461
7550
|
try {
|
|
7462
|
-
const written =
|
|
7551
|
+
const written = fs49.writeSync(this.fd, this._writingBuf);
|
|
7463
7552
|
release(null, written);
|
|
7464
7553
|
} catch (err) {
|
|
7465
7554
|
release(err);
|
|
@@ -7468,7 +7557,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7468
7557
|
if (kCopyBuffer) {
|
|
7469
7558
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7470
7559
|
}
|
|
7471
|
-
|
|
7560
|
+
fs49.write(this.fd, this._writingBuf, release);
|
|
7472
7561
|
}
|
|
7473
7562
|
}
|
|
7474
7563
|
function actualClose(sonic) {
|
|
@@ -7484,12 +7573,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7484
7573
|
sonic._lens = [];
|
|
7485
7574
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7486
7575
|
try {
|
|
7487
|
-
|
|
7576
|
+
fs49.fsync(sonic.fd, closeWrapped);
|
|
7488
7577
|
} catch {
|
|
7489
7578
|
}
|
|
7490
7579
|
function closeWrapped() {
|
|
7491
7580
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7492
|
-
|
|
7581
|
+
fs49.close(sonic.fd, done);
|
|
7493
7582
|
} else {
|
|
7494
7583
|
done();
|
|
7495
7584
|
}
|
|
@@ -7746,7 +7835,7 @@ var require_thread_stream = __commonJS({
|
|
|
7746
7835
|
var { version: version2 } = require_package();
|
|
7747
7836
|
var { EventEmitter: EventEmitter3 } = require("events");
|
|
7748
7837
|
var { Worker } = require("worker_threads");
|
|
7749
|
-
var { join:
|
|
7838
|
+
var { join: join13 } = require("path");
|
|
7750
7839
|
var { pathToFileURL } = require("url");
|
|
7751
7840
|
var { wait } = require_wait();
|
|
7752
7841
|
var {
|
|
@@ -7782,7 +7871,7 @@ var require_thread_stream = __commonJS({
|
|
|
7782
7871
|
function createWorker(stream, opts) {
|
|
7783
7872
|
const { filename, workerData } = opts;
|
|
7784
7873
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7785
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7874
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join13(__dirname, "lib", "worker.js");
|
|
7786
7875
|
const worker = new Worker(toExecute, {
|
|
7787
7876
|
...opts.workerOpts,
|
|
7788
7877
|
trackUnmanagedFds: false,
|
|
@@ -8168,7 +8257,7 @@ var require_transport = __commonJS({
|
|
|
8168
8257
|
"use strict";
|
|
8169
8258
|
var { createRequire } = require("module");
|
|
8170
8259
|
var getCallers = require_caller();
|
|
8171
|
-
var { join:
|
|
8260
|
+
var { join: join13, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8172
8261
|
var sleep = require_atomic_sleep();
|
|
8173
8262
|
var onExit = require_on_exit_leak_free();
|
|
8174
8263
|
var ThreadStream = require_thread_stream();
|
|
@@ -8231,7 +8320,7 @@ var require_transport = __commonJS({
|
|
|
8231
8320
|
throw new Error("only one of target or targets can be specified");
|
|
8232
8321
|
}
|
|
8233
8322
|
if (targets) {
|
|
8234
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8323
|
+
target = bundlerOverrides["pino-worker"] || join13(__dirname, "worker.js");
|
|
8235
8324
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8236
8325
|
return {
|
|
8237
8326
|
...dest,
|
|
@@ -8249,7 +8338,7 @@ var require_transport = __commonJS({
|
|
|
8249
8338
|
});
|
|
8250
8339
|
});
|
|
8251
8340
|
} else if (pipeline3) {
|
|
8252
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8341
|
+
target = bundlerOverrides["pino-worker"] || join13(__dirname, "worker.js");
|
|
8253
8342
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8254
8343
|
return {
|
|
8255
8344
|
...dest,
|
|
@@ -8271,7 +8360,7 @@ var require_transport = __commonJS({
|
|
|
8271
8360
|
return origin;
|
|
8272
8361
|
}
|
|
8273
8362
|
if (origin === "pino/file") {
|
|
8274
|
-
return
|
|
8363
|
+
return join13(__dirname, "..", "file.js");
|
|
8275
8364
|
}
|
|
8276
8365
|
let fixTarget2;
|
|
8277
8366
|
for (const filePath of callers) {
|
|
@@ -9261,7 +9350,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9261
9350
|
return circularValue;
|
|
9262
9351
|
}
|
|
9263
9352
|
let res = "";
|
|
9264
|
-
let
|
|
9353
|
+
let join13 = ",";
|
|
9265
9354
|
const originalIndentation = indentation;
|
|
9266
9355
|
if (Array.isArray(value)) {
|
|
9267
9356
|
if (value.length === 0) {
|
|
@@ -9275,7 +9364,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9275
9364
|
indentation += spacer;
|
|
9276
9365
|
res += `
|
|
9277
9366
|
${indentation}`;
|
|
9278
|
-
|
|
9367
|
+
join13 = `,
|
|
9279
9368
|
${indentation}`;
|
|
9280
9369
|
}
|
|
9281
9370
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9283,13 +9372,13 @@ ${indentation}`;
|
|
|
9283
9372
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9284
9373
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9285
9374
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9286
|
-
res +=
|
|
9375
|
+
res += join13;
|
|
9287
9376
|
}
|
|
9288
9377
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9289
9378
|
res += tmp !== void 0 ? tmp : "null";
|
|
9290
9379
|
if (value.length - 1 > maximumBreadth) {
|
|
9291
9380
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9292
|
-
res += `${
|
|
9381
|
+
res += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9293
9382
|
}
|
|
9294
9383
|
if (spacer !== "") {
|
|
9295
9384
|
res += `
|
|
@@ -9310,7 +9399,7 @@ ${originalIndentation}`;
|
|
|
9310
9399
|
let separator = "";
|
|
9311
9400
|
if (spacer !== "") {
|
|
9312
9401
|
indentation += spacer;
|
|
9313
|
-
|
|
9402
|
+
join13 = `,
|
|
9314
9403
|
${indentation}`;
|
|
9315
9404
|
whitespace = " ";
|
|
9316
9405
|
}
|
|
@@ -9324,13 +9413,13 @@ ${indentation}`;
|
|
|
9324
9413
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9325
9414
|
if (tmp !== void 0) {
|
|
9326
9415
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9327
|
-
separator =
|
|
9416
|
+
separator = join13;
|
|
9328
9417
|
}
|
|
9329
9418
|
}
|
|
9330
9419
|
if (keyLength > maximumBreadth) {
|
|
9331
9420
|
const removedKeys = keyLength - maximumBreadth;
|
|
9332
9421
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9333
|
-
separator =
|
|
9422
|
+
separator = join13;
|
|
9334
9423
|
}
|
|
9335
9424
|
if (spacer !== "" && separator.length > 1) {
|
|
9336
9425
|
res = `
|
|
@@ -9371,7 +9460,7 @@ ${originalIndentation}`;
|
|
|
9371
9460
|
}
|
|
9372
9461
|
const originalIndentation = indentation;
|
|
9373
9462
|
let res = "";
|
|
9374
|
-
let
|
|
9463
|
+
let join13 = ",";
|
|
9375
9464
|
if (Array.isArray(value)) {
|
|
9376
9465
|
if (value.length === 0) {
|
|
9377
9466
|
return "[]";
|
|
@@ -9384,7 +9473,7 @@ ${originalIndentation}`;
|
|
|
9384
9473
|
indentation += spacer;
|
|
9385
9474
|
res += `
|
|
9386
9475
|
${indentation}`;
|
|
9387
|
-
|
|
9476
|
+
join13 = `,
|
|
9388
9477
|
${indentation}`;
|
|
9389
9478
|
}
|
|
9390
9479
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9392,13 +9481,13 @@ ${indentation}`;
|
|
|
9392
9481
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9393
9482
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9394
9483
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9395
|
-
res +=
|
|
9484
|
+
res += join13;
|
|
9396
9485
|
}
|
|
9397
9486
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9398
9487
|
res += tmp !== void 0 ? tmp : "null";
|
|
9399
9488
|
if (value.length - 1 > maximumBreadth) {
|
|
9400
9489
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9401
|
-
res += `${
|
|
9490
|
+
res += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9402
9491
|
}
|
|
9403
9492
|
if (spacer !== "") {
|
|
9404
9493
|
res += `
|
|
@@ -9411,7 +9500,7 @@ ${originalIndentation}`;
|
|
|
9411
9500
|
let whitespace = "";
|
|
9412
9501
|
if (spacer !== "") {
|
|
9413
9502
|
indentation += spacer;
|
|
9414
|
-
|
|
9503
|
+
join13 = `,
|
|
9415
9504
|
${indentation}`;
|
|
9416
9505
|
whitespace = " ";
|
|
9417
9506
|
}
|
|
@@ -9420,7 +9509,7 @@ ${indentation}`;
|
|
|
9420
9509
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9421
9510
|
if (tmp !== void 0) {
|
|
9422
9511
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9423
|
-
separator =
|
|
9512
|
+
separator = join13;
|
|
9424
9513
|
}
|
|
9425
9514
|
}
|
|
9426
9515
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9478,20 +9567,20 @@ ${originalIndentation}`;
|
|
|
9478
9567
|
indentation += spacer;
|
|
9479
9568
|
let res2 = `
|
|
9480
9569
|
${indentation}`;
|
|
9481
|
-
const
|
|
9570
|
+
const join14 = `,
|
|
9482
9571
|
${indentation}`;
|
|
9483
9572
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9484
9573
|
let i = 0;
|
|
9485
9574
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9486
9575
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9487
9576
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9488
|
-
res2 +=
|
|
9577
|
+
res2 += join14;
|
|
9489
9578
|
}
|
|
9490
9579
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9491
9580
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9492
9581
|
if (value.length - 1 > maximumBreadth) {
|
|
9493
9582
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9494
|
-
res2 += `${
|
|
9583
|
+
res2 += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9495
9584
|
}
|
|
9496
9585
|
res2 += `
|
|
9497
9586
|
${originalIndentation}`;
|
|
@@ -9507,16 +9596,16 @@ ${originalIndentation}`;
|
|
|
9507
9596
|
return '"[Object]"';
|
|
9508
9597
|
}
|
|
9509
9598
|
indentation += spacer;
|
|
9510
|
-
const
|
|
9599
|
+
const join13 = `,
|
|
9511
9600
|
${indentation}`;
|
|
9512
9601
|
let res = "";
|
|
9513
9602
|
let separator = "";
|
|
9514
9603
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9515
9604
|
if (isTypedArrayWithEntries(value)) {
|
|
9516
|
-
res += stringifyTypedArray(value,
|
|
9605
|
+
res += stringifyTypedArray(value, join13, maximumBreadth);
|
|
9517
9606
|
keys = keys.slice(value.length);
|
|
9518
9607
|
maximumPropertiesToStringify -= value.length;
|
|
9519
|
-
separator =
|
|
9608
|
+
separator = join13;
|
|
9520
9609
|
}
|
|
9521
9610
|
if (deterministic) {
|
|
9522
9611
|
keys = sort(keys, comparator);
|
|
@@ -9527,13 +9616,13 @@ ${indentation}`;
|
|
|
9527
9616
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9528
9617
|
if (tmp !== void 0) {
|
|
9529
9618
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9530
|
-
separator =
|
|
9619
|
+
separator = join13;
|
|
9531
9620
|
}
|
|
9532
9621
|
}
|
|
9533
9622
|
if (keyLength > maximumBreadth) {
|
|
9534
9623
|
const removedKeys = keyLength - maximumBreadth;
|
|
9535
9624
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9536
|
-
separator =
|
|
9625
|
+
separator = join13;
|
|
9537
9626
|
}
|
|
9538
9627
|
if (separator !== "") {
|
|
9539
9628
|
res = `
|
|
@@ -10624,11 +10713,11 @@ var init_lib = __esm({
|
|
|
10624
10713
|
}
|
|
10625
10714
|
}
|
|
10626
10715
|
},
|
|
10627
|
-
addToPath: function addToPath(
|
|
10628
|
-
var last =
|
|
10716
|
+
addToPath: function addToPath(path60, added, removed, oldPosInc, options) {
|
|
10717
|
+
var last = path60.lastComponent;
|
|
10629
10718
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10630
10719
|
return {
|
|
10631
|
-
oldPos:
|
|
10720
|
+
oldPos: path60.oldPos + oldPosInc,
|
|
10632
10721
|
lastComponent: {
|
|
10633
10722
|
count: last.count + 1,
|
|
10634
10723
|
added,
|
|
@@ -10638,7 +10727,7 @@ var init_lib = __esm({
|
|
|
10638
10727
|
};
|
|
10639
10728
|
} else {
|
|
10640
10729
|
return {
|
|
10641
|
-
oldPos:
|
|
10730
|
+
oldPos: path60.oldPos + oldPosInc,
|
|
10642
10731
|
lastComponent: {
|
|
10643
10732
|
count: 1,
|
|
10644
10733
|
added,
|
|
@@ -11084,10 +11173,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11084
11173
|
const memories = raw.map((m2) => {
|
|
11085
11174
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11086
11175
|
const rec = m2;
|
|
11087
|
-
const
|
|
11176
|
+
const path60 = typeof rec.path === "string" ? rec.path : null;
|
|
11088
11177
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11089
|
-
if (!
|
|
11090
|
-
const entry = { path:
|
|
11178
|
+
if (!path60 || content == null) return null;
|
|
11179
|
+
const entry = { path: path60, content };
|
|
11091
11180
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
11092
11181
|
return entry;
|
|
11093
11182
|
}).filter((m2) => m2 !== null);
|
|
@@ -11918,10 +12007,10 @@ function parseAttachment(obj) {
|
|
|
11918
12007
|
const memories = raw.map((m2) => {
|
|
11919
12008
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11920
12009
|
const rec = m2;
|
|
11921
|
-
const
|
|
12010
|
+
const path60 = typeof rec.path === "string" ? rec.path : null;
|
|
11922
12011
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11923
|
-
if (!
|
|
11924
|
-
const out = { path:
|
|
12012
|
+
if (!path60 || content == null) return null;
|
|
12013
|
+
const out = { path: path60, content };
|
|
11925
12014
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11926
12015
|
return out;
|
|
11927
12016
|
}).filter((m2) => m2 !== null);
|
|
@@ -21120,7 +21209,7 @@ var require_BufferList = __commonJS({
|
|
|
21120
21209
|
this.head = this.tail = null;
|
|
21121
21210
|
this.length = 0;
|
|
21122
21211
|
};
|
|
21123
|
-
BufferList.prototype.join = function
|
|
21212
|
+
BufferList.prototype.join = function join13(s) {
|
|
21124
21213
|
if (this.length === 0) return "";
|
|
21125
21214
|
var p2 = this.head;
|
|
21126
21215
|
var ret = "" + p2.data;
|
|
@@ -23710,8 +23799,8 @@ var require_utils = __commonJS({
|
|
|
23710
23799
|
var result = transform[inputType][outputType](input);
|
|
23711
23800
|
return result;
|
|
23712
23801
|
};
|
|
23713
|
-
exports2.resolve = function(
|
|
23714
|
-
var parts =
|
|
23802
|
+
exports2.resolve = function(path60) {
|
|
23803
|
+
var parts = path60.split("/");
|
|
23715
23804
|
var result = [];
|
|
23716
23805
|
for (var index = 0; index < parts.length; index++) {
|
|
23717
23806
|
var part = parts[index];
|
|
@@ -29564,18 +29653,18 @@ var require_object = __commonJS({
|
|
|
29564
29653
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
29565
29654
|
this.files[name] = object;
|
|
29566
29655
|
};
|
|
29567
|
-
var parentFolder = function(
|
|
29568
|
-
if (
|
|
29569
|
-
|
|
29656
|
+
var parentFolder = function(path60) {
|
|
29657
|
+
if (path60.slice(-1) === "/") {
|
|
29658
|
+
path60 = path60.substring(0, path60.length - 1);
|
|
29570
29659
|
}
|
|
29571
|
-
var lastSlash =
|
|
29572
|
-
return lastSlash > 0 ?
|
|
29660
|
+
var lastSlash = path60.lastIndexOf("/");
|
|
29661
|
+
return lastSlash > 0 ? path60.substring(0, lastSlash) : "";
|
|
29573
29662
|
};
|
|
29574
|
-
var forceTrailingSlash = function(
|
|
29575
|
-
if (
|
|
29576
|
-
|
|
29663
|
+
var forceTrailingSlash = function(path60) {
|
|
29664
|
+
if (path60.slice(-1) !== "/") {
|
|
29665
|
+
path60 += "/";
|
|
29577
29666
|
}
|
|
29578
|
-
return
|
|
29667
|
+
return path60;
|
|
29579
29668
|
};
|
|
29580
29669
|
var folderAdd = function(name, createFolders) {
|
|
29581
29670
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -33053,7 +33142,7 @@ var SessionManager = class {
|
|
|
33053
33142
|
}
|
|
33054
33143
|
}
|
|
33055
33144
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
33056
|
-
create(args, creatorPrincipalId
|
|
33145
|
+
create(args, creatorPrincipalId) {
|
|
33057
33146
|
let cwd;
|
|
33058
33147
|
if (args.ownerPersonaId) {
|
|
33059
33148
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -33096,7 +33185,6 @@ var SessionManager = class {
|
|
|
33096
33185
|
ownerPersonaId: args.ownerPersonaId,
|
|
33097
33186
|
appBuilderProject: args.appBuilderProject,
|
|
33098
33187
|
creatorPrincipalId,
|
|
33099
|
-
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
33100
33188
|
createdAt: iso,
|
|
33101
33189
|
updatedAt: iso
|
|
33102
33190
|
};
|
|
@@ -36537,9 +36625,10 @@ var LocalWsServer = class {
|
|
|
36537
36625
|
httpServer = null;
|
|
36538
36626
|
frameHandler = null;
|
|
36539
36627
|
clients = /* @__PURE__ */ new Map();
|
|
36540
|
-
//
|
|
36541
|
-
//
|
|
36542
|
-
|
|
36628
|
+
// guest principal id → Set<clientId>,撤销时 O(1) 找到该 guest 的所有活跃连接做关闭级联。
|
|
36629
|
+
// 同一 guest 可能多端同时连(多设备 / 多 tab)。
|
|
36630
|
+
// Phase 3 (决策 #14): 索引键从 ctx.capabilityId 改为 guest principal.id(两者恒等,纯正名)。
|
|
36631
|
+
guestIdToClients = /* @__PURE__ */ new Map();
|
|
36543
36632
|
logger;
|
|
36544
36633
|
pingIntervalMs;
|
|
36545
36634
|
async start() {
|
|
@@ -36657,38 +36746,6 @@ var LocalWsServer = class {
|
|
|
36657
36746
|
this.safeSend(c.ws, frame);
|
|
36658
36747
|
}
|
|
36659
36748
|
}
|
|
36660
|
-
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
36661
|
-
// - 所有 owner ws (owner 看自家所有 channel)
|
|
36662
|
-
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
36663
|
-
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
36664
|
-
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
36665
|
-
this.broadcastToOwners(frame);
|
|
36666
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36667
|
-
if (!set) return;
|
|
36668
|
-
for (const id of set) {
|
|
36669
|
-
const c = this.clients.get(id);
|
|
36670
|
-
if (!c) continue;
|
|
36671
|
-
this.safeSend(c.ws, frame);
|
|
36672
|
-
}
|
|
36673
|
-
}
|
|
36674
|
-
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
36675
|
-
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
36676
|
-
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
36677
|
-
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36678
|
-
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36679
|
-
broadcastToPrincipal(principalId, frame) {
|
|
36680
|
-
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
36681
|
-
this.broadcastToOwners(frame);
|
|
36682
|
-
return;
|
|
36683
|
-
}
|
|
36684
|
-
const set = this.capabilityIdToClients.get(principalId);
|
|
36685
|
-
if (!set) return;
|
|
36686
|
-
for (const id of set) {
|
|
36687
|
-
const c = this.clients.get(id);
|
|
36688
|
-
if (!c) continue;
|
|
36689
|
-
this.safeSend(c.ws, frame);
|
|
36690
|
-
}
|
|
36691
|
-
}
|
|
36692
36749
|
firstSubscriber(sessionId) {
|
|
36693
36750
|
for (const c of this.clients.values()) {
|
|
36694
36751
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -36711,16 +36768,16 @@ var LocalWsServer = class {
|
|
|
36711
36768
|
this.safeSend(c.ws, frame);
|
|
36712
36769
|
}
|
|
36713
36770
|
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
36714
|
-
// 绑到 client;guest 连接同时进
|
|
36771
|
+
// 绑到 client;guest 连接同时进 guest 索引(撤销级联用)。
|
|
36715
36772
|
attachClientContext(clientId, ctx) {
|
|
36716
36773
|
const c = this.clients.get(clientId);
|
|
36717
36774
|
if (!c) return;
|
|
36718
36775
|
c.ctx = ctx;
|
|
36719
|
-
if (ctx.
|
|
36720
|
-
let set = this.
|
|
36776
|
+
if (ctx.principal.kind === "guest") {
|
|
36777
|
+
let set = this.guestIdToClients.get(ctx.principal.id);
|
|
36721
36778
|
if (!set) {
|
|
36722
36779
|
set = /* @__PURE__ */ new Set();
|
|
36723
|
-
this.
|
|
36780
|
+
this.guestIdToClients.set(ctx.principal.id, set);
|
|
36724
36781
|
}
|
|
36725
36782
|
set.add(clientId);
|
|
36726
36783
|
}
|
|
@@ -36729,10 +36786,27 @@ var LocalWsServer = class {
|
|
|
36729
36786
|
getClientContext(clientId) {
|
|
36730
36787
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36731
36788
|
}
|
|
36732
|
-
//
|
|
36789
|
+
// 飞书热切换(spec 决策 #9):身份切换后踢掉所有连接强制重连——在线连接的 ctx
|
|
36790
|
+
// (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
|
|
36791
|
+
// close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
|
|
36792
|
+
// 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
|
|
36793
|
+
closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
|
|
36794
|
+
const ids = [...this.clients.keys()];
|
|
36795
|
+
for (const id of ids) {
|
|
36796
|
+
const c = this.clients.get(id);
|
|
36797
|
+
if (!c) continue;
|
|
36798
|
+
try {
|
|
36799
|
+
c.ws.close(code, reason);
|
|
36800
|
+
} catch {
|
|
36801
|
+
}
|
|
36802
|
+
}
|
|
36803
|
+
}
|
|
36804
|
+
// Task 1.10 撤销级联:关闭某 guest principal 的所有活跃 ws 连接。close code 4401
|
|
36733
36805
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36734
|
-
|
|
36735
|
-
|
|
36806
|
+
// capability:delete 级联用 cap.id 调本方法——noAuth 旧 per-peer cap guest 的
|
|
36807
|
+
// principal.id ≡ cap.id,级联语义不变(Phase 3 决策 #14 正名)。
|
|
36808
|
+
closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36809
|
+
const set = this.guestIdToClients.get(guestPrincipalId);
|
|
36736
36810
|
if (!set) return;
|
|
36737
36811
|
const ids = [...set];
|
|
36738
36812
|
for (const id of ids) {
|
|
@@ -36797,16 +36871,18 @@ var LocalWsServer = class {
|
|
|
36797
36871
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36798
36872
|
}
|
|
36799
36873
|
socket.on("message", (data) => {
|
|
36800
|
-
this.onMessage(handle, data, remoteAddress)
|
|
36874
|
+
void this.onMessage(handle, data, remoteAddress).catch((err) => {
|
|
36875
|
+
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36876
|
+
});
|
|
36801
36877
|
});
|
|
36802
36878
|
socket.on("close", () => {
|
|
36803
36879
|
const c = this.clients.get(id);
|
|
36804
36880
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
36805
|
-
const
|
|
36806
|
-
if (
|
|
36807
|
-
const set = this.
|
|
36881
|
+
const guestId = c?.ctx?.principal.kind === "guest" ? c.ctx.principal.id : void 0;
|
|
36882
|
+
if (guestId) {
|
|
36883
|
+
const set = this.guestIdToClients.get(guestId);
|
|
36808
36884
|
set?.delete(id);
|
|
36809
|
-
if (set && set.size === 0) this.
|
|
36885
|
+
if (set && set.size === 0) this.guestIdToClients.delete(guestId);
|
|
36810
36886
|
}
|
|
36811
36887
|
this.clients.delete(id);
|
|
36812
36888
|
authGate?.unregister(id);
|
|
@@ -36816,7 +36892,7 @@ var LocalWsServer = class {
|
|
|
36816
36892
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36817
36893
|
});
|
|
36818
36894
|
}
|
|
36819
|
-
onMessage(client, data, remoteAddress) {
|
|
36895
|
+
async onMessage(client, data, remoteAddress) {
|
|
36820
36896
|
let frame;
|
|
36821
36897
|
try {
|
|
36822
36898
|
frame = JSON.parse(data.toString());
|
|
@@ -36827,7 +36903,7 @@ var LocalWsServer = class {
|
|
|
36827
36903
|
const authGate = this.opts.authGate;
|
|
36828
36904
|
if (authGate) {
|
|
36829
36905
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36830
|
-
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36906
|
+
const verdict = await authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36831
36907
|
if (verdict !== "pass") {
|
|
36832
36908
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36833
36909
|
try {
|
|
@@ -36842,14 +36918,8 @@ var LocalWsServer = class {
|
|
|
36842
36918
|
}
|
|
36843
36919
|
} else if (frame.type === "auth") {
|
|
36844
36920
|
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
36845
|
-
const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
|
|
36846
|
-
const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
|
|
36847
36921
|
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
36848
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36849
|
-
token,
|
|
36850
|
-
selfPrincipalId || void 0,
|
|
36851
|
-
selfDisplayName || void 0
|
|
36852
|
-
);
|
|
36922
|
+
const guestCtx = this.opts.tryVerifyCapabilityToken(token);
|
|
36853
36923
|
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
36854
36924
|
}
|
|
36855
36925
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
@@ -36930,7 +37000,7 @@ var AuthGate = class {
|
|
|
36930
37000
|
registerConnection(handle) {
|
|
36931
37001
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
36932
37002
|
if (!enforce) {
|
|
36933
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
37003
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null, pending: false });
|
|
36934
37004
|
return true;
|
|
36935
37005
|
}
|
|
36936
37006
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -36939,7 +37009,7 @@ var AuthGate = class {
|
|
|
36939
37009
|
if (!st || st.authed) return;
|
|
36940
37010
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
36941
37011
|
}, this.opts.timeoutMs);
|
|
36942
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
37012
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer, pending: false });
|
|
36943
37013
|
return false;
|
|
36944
37014
|
}
|
|
36945
37015
|
unregister(handleId) {
|
|
@@ -36955,12 +37025,15 @@ var AuthGate = class {
|
|
|
36955
37025
|
}
|
|
36956
37026
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
36957
37027
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
36958
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
37028
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
|
|
36959
37029
|
// - pass:已 authed,调用方继续走业务路由
|
|
36960
|
-
|
|
37030
|
+
//
|
|
37031
|
+
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
37032
|
+
async handleFrame(handle, frame) {
|
|
36961
37033
|
const st = this.states.get(handle.id);
|
|
36962
37034
|
if (!st) return "reject";
|
|
36963
37035
|
if (st.authed) return "pass";
|
|
37036
|
+
if (st.pending) return "reject";
|
|
36964
37037
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
36965
37038
|
if (!parsed.success) {
|
|
36966
37039
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -36970,11 +37043,14 @@ var AuthGate = class {
|
|
|
36970
37043
|
}
|
|
36971
37044
|
let ctx = null;
|
|
36972
37045
|
if (this.opts.authenticate) {
|
|
36973
|
-
|
|
36974
|
-
|
|
36975
|
-
|
|
36976
|
-
parsed.data.
|
|
36977
|
-
|
|
37046
|
+
st.pending = true;
|
|
37047
|
+
let r;
|
|
37048
|
+
try {
|
|
37049
|
+
r = await this.opts.authenticate(parsed.data.token, parsed.data.selfUrl);
|
|
37050
|
+
} finally {
|
|
37051
|
+
st.pending = false;
|
|
37052
|
+
}
|
|
37053
|
+
if (!this.states.has(handle.id)) return "reject";
|
|
36978
37054
|
if (!r.ok) {
|
|
36979
37055
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
36980
37056
|
this.markFailed(handle.id);
|
|
@@ -37077,26 +37153,33 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
37077
37153
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
37078
37154
|
};
|
|
37079
37155
|
}
|
|
37080
|
-
function
|
|
37156
|
+
function connectGuestContext(subDevice, subOwner, provider, displayName) {
|
|
37081
37157
|
return {
|
|
37082
|
-
principal: { id:
|
|
37083
|
-
grants:
|
|
37084
|
-
|
|
37085
|
-
|
|
37158
|
+
principal: { id: subDevice, kind: "guest", displayName },
|
|
37159
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37160
|
+
resource: { ...g2.resource },
|
|
37161
|
+
actions: [...g2.actions]
|
|
37162
|
+
})),
|
|
37163
|
+
ownerId: subOwner,
|
|
37164
|
+
provider
|
|
37086
37165
|
};
|
|
37087
37166
|
}
|
|
37088
|
-
function authenticate(token, deps) {
|
|
37167
|
+
async function authenticate(token, deps) {
|
|
37089
37168
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
37090
37169
|
if (deps.isOwnerToken(token)) {
|
|
37091
37170
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
37092
37171
|
}
|
|
37093
|
-
if (!deps.
|
|
37094
|
-
const
|
|
37095
|
-
if (!
|
|
37096
|
-
if (
|
|
37097
|
-
return { ok: false, code:
|
|
37172
|
+
if (!deps.verifyConnectToken) return { ok: false, code: "BAD_TOKEN" };
|
|
37173
|
+
const result = deps.verifyConnectToken(token);
|
|
37174
|
+
if (!result.ok) {
|
|
37175
|
+
if (result.reason === "EXPIRED") return { ok: false, code: "TOKEN_EXPIRED" };
|
|
37176
|
+
if (result.reason === "WRONG_AUDIENCE") return { ok: false, code: "TOKEN_REVOKED" };
|
|
37177
|
+
return { ok: false, code: "BAD_TOKEN" };
|
|
37098
37178
|
}
|
|
37099
|
-
return {
|
|
37179
|
+
return {
|
|
37180
|
+
ok: true,
|
|
37181
|
+
context: connectGuestContext(result.subDevice, result.subOwner, result.provider, result.displayName)
|
|
37182
|
+
};
|
|
37100
37183
|
}
|
|
37101
37184
|
|
|
37102
37185
|
// src/permission/capability-store.ts
|
|
@@ -37242,8 +37325,8 @@ var CapabilityManager = class {
|
|
|
37242
37325
|
list() {
|
|
37243
37326
|
return this.registry.list();
|
|
37244
37327
|
}
|
|
37245
|
-
//
|
|
37246
|
-
// 返回 Capability
|
|
37328
|
+
// 调试 / 旧 per-peer cap 反查用(Phase 3 决策 #14 后无生产调用方,保留属三件套范围)。
|
|
37329
|
+
// 返回 Capability 持久化形态;调用方需 stripSecretHash 后再上 wire。
|
|
37247
37330
|
findById(id) {
|
|
37248
37331
|
return this.registry.findById(id);
|
|
37249
37332
|
}
|
|
@@ -37276,89 +37359,14 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37276
37359
|
return { removed };
|
|
37277
37360
|
}
|
|
37278
37361
|
|
|
37279
|
-
// src/permission/personal-capability.ts
|
|
37280
|
-
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37281
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
37282
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37283
|
-
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37284
|
-
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37285
|
-
function personalCapFilePath(dataDir) {
|
|
37286
|
-
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37287
|
-
}
|
|
37288
|
-
function loadOrCreatePersonalCapability(opts) {
|
|
37289
|
-
const file = personalCapFilePath(opts.dataDir);
|
|
37290
|
-
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37291
|
-
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37292
|
-
const now = opts.now ?? Date.now;
|
|
37293
|
-
const existing = readFile(file);
|
|
37294
|
-
if (existing) return existing;
|
|
37295
|
-
const token = generateToken();
|
|
37296
|
-
const id = generateId();
|
|
37297
|
-
const capability = {
|
|
37298
|
-
id,
|
|
37299
|
-
secretHash: sha256Hex2(token),
|
|
37300
|
-
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37301
|
-
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37302
|
-
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37303
|
-
resource: { ...g2.resource },
|
|
37304
|
-
actions: [...g2.actions]
|
|
37305
|
-
})),
|
|
37306
|
-
issuedAt: now(),
|
|
37307
|
-
usedCount: 0
|
|
37308
|
-
};
|
|
37309
|
-
const content = { token, capability };
|
|
37310
|
-
writeFile(file, content);
|
|
37311
|
-
return content;
|
|
37312
|
-
}
|
|
37313
|
-
function readFile(file) {
|
|
37314
|
-
let raw;
|
|
37315
|
-
try {
|
|
37316
|
-
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37317
|
-
} catch (err) {
|
|
37318
|
-
const code = err?.code;
|
|
37319
|
-
if (code === "ENOENT") return null;
|
|
37320
|
-
return null;
|
|
37321
|
-
}
|
|
37322
|
-
let parsed;
|
|
37323
|
-
try {
|
|
37324
|
-
parsed = JSON.parse(raw);
|
|
37325
|
-
} catch {
|
|
37326
|
-
return null;
|
|
37327
|
-
}
|
|
37328
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
37329
|
-
const obj = parsed;
|
|
37330
|
-
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37331
|
-
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37332
|
-
if (!capParsed.success) return null;
|
|
37333
|
-
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37334
|
-
return { token: obj.token, capability: capParsed.data };
|
|
37335
|
-
}
|
|
37336
|
-
function writeFile(file, content) {
|
|
37337
|
-
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37338
|
-
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37339
|
-
try {
|
|
37340
|
-
import_node_fs15.default.chmodSync(file, 384);
|
|
37341
|
-
} catch {
|
|
37342
|
-
}
|
|
37343
|
-
}
|
|
37344
|
-
function defaultGenerateToken() {
|
|
37345
|
-
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37346
|
-
}
|
|
37347
|
-
function defaultGenerateId() {
|
|
37348
|
-
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37349
|
-
}
|
|
37350
|
-
function sha256Hex2(s) {
|
|
37351
|
-
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37352
|
-
}
|
|
37353
|
-
|
|
37354
37362
|
// src/inbox/inbox-store.ts
|
|
37355
|
-
var
|
|
37356
|
-
var
|
|
37363
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
37364
|
+
var path21 = __toESM(require("path"), 1);
|
|
37357
37365
|
var INBOX_SUBDIR = "inbox";
|
|
37358
37366
|
var InboxStore = class {
|
|
37359
37367
|
constructor(dataDir) {
|
|
37360
37368
|
this.dataDir = dataDir;
|
|
37361
|
-
|
|
37369
|
+
fs19.mkdirSync(this.dirPath(), { recursive: true });
|
|
37362
37370
|
}
|
|
37363
37371
|
dataDir;
|
|
37364
37372
|
/**
|
|
@@ -37366,11 +37374,11 @@ var InboxStore = class {
|
|
|
37366
37374
|
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
37367
37375
|
* 文件不存在时返回 [] (不抛, 不创建).
|
|
37368
37376
|
*/
|
|
37369
|
-
list(
|
|
37370
|
-
const file = this.filePath(
|
|
37377
|
+
list(peerDeviceId, sinceCreatedAt) {
|
|
37378
|
+
const file = this.filePath(peerDeviceId);
|
|
37371
37379
|
let raw;
|
|
37372
37380
|
try {
|
|
37373
|
-
raw =
|
|
37381
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
37374
37382
|
} catch (err) {
|
|
37375
37383
|
if (err?.code === "ENOENT") return [];
|
|
37376
37384
|
return [];
|
|
@@ -37380,13 +37388,13 @@ var InboxStore = class {
|
|
|
37380
37388
|
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
37381
37389
|
}
|
|
37382
37390
|
/**
|
|
37383
|
-
* 列出所有 peer 的
|
|
37391
|
+
* 列出所有 peer 的 peerDeviceId (扫目录所有 *.jsonl 文件名).
|
|
37384
37392
|
*/
|
|
37385
|
-
|
|
37393
|
+
listAllPeerDeviceIds() {
|
|
37386
37394
|
const dir = this.dirPath();
|
|
37387
37395
|
let entries;
|
|
37388
37396
|
try {
|
|
37389
|
-
entries =
|
|
37397
|
+
entries = fs19.readdirSync(dir);
|
|
37390
37398
|
} catch (err) {
|
|
37391
37399
|
if (err?.code === "ENOENT") return [];
|
|
37392
37400
|
return [];
|
|
@@ -37398,63 +37406,63 @@ var InboxStore = class {
|
|
|
37398
37406
|
* 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
|
|
37399
37407
|
*/
|
|
37400
37408
|
append(message) {
|
|
37401
|
-
const existing = this.list(message.
|
|
37409
|
+
const existing = this.list(message.peerDeviceId);
|
|
37402
37410
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37403
|
-
const file = this.filePath(message.
|
|
37411
|
+
const file = this.filePath(message.peerDeviceId);
|
|
37404
37412
|
const line = JSON.stringify(message) + "\n";
|
|
37405
|
-
|
|
37413
|
+
fs19.appendFileSync(file, line, { mode: 384 });
|
|
37406
37414
|
try {
|
|
37407
|
-
|
|
37415
|
+
fs19.chmodSync(file, 384);
|
|
37408
37416
|
} catch {
|
|
37409
37417
|
}
|
|
37410
37418
|
}
|
|
37411
37419
|
/**
|
|
37412
|
-
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=
|
|
37413
|
-
* readBy[
|
|
37420
|
+
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=readerDeviceId 的消息
|
|
37421
|
+
* readBy[readerDeviceId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
|
|
37414
37422
|
* 返写入了几条.
|
|
37415
37423
|
*/
|
|
37416
|
-
markRead(
|
|
37417
|
-
const messages = this.list(
|
|
37424
|
+
markRead(peerDeviceId, readerDeviceId, upToCreatedAt) {
|
|
37425
|
+
const messages = this.list(peerDeviceId);
|
|
37418
37426
|
let changed = 0;
|
|
37419
37427
|
const next = messages.map((m2) => {
|
|
37420
|
-
if (m2.
|
|
37428
|
+
if (m2.senderDeviceId === readerDeviceId) return m2;
|
|
37421
37429
|
if (m2.createdAt > upToCreatedAt) return m2;
|
|
37422
|
-
const cur = m2.readBy[
|
|
37430
|
+
const cur = m2.readBy[readerDeviceId];
|
|
37423
37431
|
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
37424
37432
|
changed++;
|
|
37425
|
-
return { ...m2, readBy: { ...m2.readBy, [
|
|
37433
|
+
return { ...m2, readBy: { ...m2.readBy, [readerDeviceId]: upToCreatedAt } };
|
|
37426
37434
|
});
|
|
37427
37435
|
if (changed === 0) return 0;
|
|
37428
|
-
this.rewriteFile(
|
|
37436
|
+
this.rewriteFile(peerDeviceId, next);
|
|
37429
37437
|
return changed;
|
|
37430
37438
|
}
|
|
37431
37439
|
/**
|
|
37432
37440
|
* 删整个 peer 的 jsonl (peer:remove cascade). 不存在 no-op.
|
|
37433
37441
|
*/
|
|
37434
|
-
|
|
37435
|
-
const file = this.filePath(
|
|
37442
|
+
removeByPeerDeviceId(peerDeviceId) {
|
|
37443
|
+
const file = this.filePath(peerDeviceId);
|
|
37436
37444
|
try {
|
|
37437
|
-
|
|
37445
|
+
fs19.unlinkSync(file);
|
|
37438
37446
|
} catch (err) {
|
|
37439
37447
|
if (err?.code === "ENOENT") return;
|
|
37440
37448
|
}
|
|
37441
37449
|
}
|
|
37442
|
-
rewriteFile(
|
|
37443
|
-
const file = this.filePath(
|
|
37450
|
+
rewriteFile(peerDeviceId, messages) {
|
|
37451
|
+
const file = this.filePath(peerDeviceId);
|
|
37444
37452
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37445
37453
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37446
|
-
|
|
37447
|
-
|
|
37454
|
+
fs19.writeFileSync(tmp, content, { mode: 384 });
|
|
37455
|
+
fs19.renameSync(tmp, file);
|
|
37448
37456
|
try {
|
|
37449
|
-
|
|
37457
|
+
fs19.chmodSync(file, 384);
|
|
37450
37458
|
} catch {
|
|
37451
37459
|
}
|
|
37452
37460
|
}
|
|
37453
37461
|
dirPath() {
|
|
37454
|
-
return
|
|
37462
|
+
return path21.join(this.dataDir, INBOX_SUBDIR);
|
|
37455
37463
|
}
|
|
37456
|
-
filePath(
|
|
37457
|
-
return
|
|
37464
|
+
filePath(peerDeviceId) {
|
|
37465
|
+
return path21.join(this.dirPath(), `${peerDeviceId}.jsonl`);
|
|
37458
37466
|
}
|
|
37459
37467
|
};
|
|
37460
37468
|
function parseAllLines(raw) {
|
|
@@ -37490,72 +37498,72 @@ var InboxManager = class {
|
|
|
37490
37498
|
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
37491
37499
|
*/
|
|
37492
37500
|
postMessage(args) {
|
|
37493
|
-
const beforeLen = this.store.list(args.
|
|
37501
|
+
const beforeLen = this.store.list(args.peerDeviceId).length;
|
|
37494
37502
|
const msg = {
|
|
37495
37503
|
id: args.id,
|
|
37496
|
-
|
|
37497
|
-
|
|
37504
|
+
peerDeviceId: args.peerDeviceId,
|
|
37505
|
+
senderDeviceId: args.senderDeviceId,
|
|
37498
37506
|
text: args.text,
|
|
37499
37507
|
createdAt: args.createdAt,
|
|
37500
37508
|
readBy: {}
|
|
37501
37509
|
};
|
|
37502
37510
|
this.store.append(msg);
|
|
37503
|
-
const afterLen = this.store.list(args.
|
|
37511
|
+
const afterLen = this.store.list(args.peerDeviceId).length;
|
|
37504
37512
|
if (afterLen === beforeLen) return msg;
|
|
37505
|
-
this.broadcast(args.
|
|
37513
|
+
this.broadcast(args.peerDeviceId, {
|
|
37506
37514
|
type: "inbox:event",
|
|
37507
|
-
|
|
37515
|
+
peerDeviceId: args.peerDeviceId,
|
|
37508
37516
|
message: msg
|
|
37509
37517
|
});
|
|
37510
37518
|
return msg;
|
|
37511
37519
|
}
|
|
37512
|
-
list(
|
|
37513
|
-
return this.store.list(
|
|
37520
|
+
list(peerDeviceId, sinceCreatedAt) {
|
|
37521
|
+
return this.store.list(peerDeviceId, sinceCreatedAt);
|
|
37514
37522
|
}
|
|
37515
37523
|
/**
|
|
37516
37524
|
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
37517
37525
|
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
|
|
37518
37526
|
*/
|
|
37519
37527
|
markRead(args) {
|
|
37520
|
-
const before = this.store.list(args.
|
|
37521
|
-
const changed = this.store.markRead(args.
|
|
37528
|
+
const before = this.store.list(args.peerDeviceId);
|
|
37529
|
+
const changed = this.store.markRead(args.peerDeviceId, args.readerDeviceId, args.upToCreatedAt);
|
|
37522
37530
|
if (changed === 0) return 0;
|
|
37523
|
-
const after = this.store.list(args.
|
|
37531
|
+
const after = this.store.list(args.peerDeviceId);
|
|
37524
37532
|
const beforeById = new Map(before.map((m2) => [m2.id, m2]));
|
|
37525
37533
|
for (const m2 of after) {
|
|
37526
37534
|
const prev = beforeById.get(m2.id);
|
|
37527
37535
|
if (!prev) continue;
|
|
37528
|
-
if (prev.readBy[args.
|
|
37529
|
-
this.broadcast(args.
|
|
37536
|
+
if (prev.readBy[args.readerDeviceId] === m2.readBy[args.readerDeviceId]) continue;
|
|
37537
|
+
this.broadcast(args.peerDeviceId, {
|
|
37530
37538
|
type: "inbox:event",
|
|
37531
|
-
|
|
37539
|
+
peerDeviceId: args.peerDeviceId,
|
|
37532
37540
|
message: m2
|
|
37533
37541
|
});
|
|
37534
37542
|
}
|
|
37535
37543
|
return changed;
|
|
37536
37544
|
}
|
|
37537
37545
|
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
37538
|
-
removeChannel(
|
|
37539
|
-
this.store.
|
|
37546
|
+
removeChannel(peerDeviceId) {
|
|
37547
|
+
this.store.removeByPeerDeviceId(peerDeviceId);
|
|
37540
37548
|
}
|
|
37541
37549
|
};
|
|
37542
37550
|
|
|
37543
|
-
// src/state/
|
|
37544
|
-
var
|
|
37545
|
-
var
|
|
37546
|
-
var FILE_NAME = "
|
|
37547
|
-
var
|
|
37551
|
+
// src/state/contact-store.ts
|
|
37552
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37553
|
+
var path22 = __toESM(require("path"), 1);
|
|
37554
|
+
var FILE_NAME = "contacts.json";
|
|
37555
|
+
var ContactStore = class {
|
|
37548
37556
|
constructor(dataDir) {
|
|
37549
37557
|
this.dataDir = dataDir;
|
|
37550
37558
|
}
|
|
37551
37559
|
dataDir;
|
|
37552
|
-
|
|
37560
|
+
contacts = /* @__PURE__ */ new Map();
|
|
37553
37561
|
load() {
|
|
37554
|
-
this.
|
|
37555
|
-
const file =
|
|
37562
|
+
this.contacts.clear();
|
|
37563
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37556
37564
|
let raw;
|
|
37557
37565
|
try {
|
|
37558
|
-
raw =
|
|
37566
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37559
37567
|
} catch (err) {
|
|
37560
37568
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37561
37569
|
return;
|
|
@@ -37567,52 +37575,58 @@ var ReceivedCapabilityStore = class {
|
|
|
37567
37575
|
this.renameBak(file);
|
|
37568
37576
|
return;
|
|
37569
37577
|
}
|
|
37570
|
-
const arr = parsed?.
|
|
37578
|
+
const arr = parsed?.contacts;
|
|
37571
37579
|
if (!Array.isArray(arr)) return;
|
|
37572
37580
|
for (const entry of arr) {
|
|
37573
|
-
const r =
|
|
37574
|
-
if (r.success) this.
|
|
37581
|
+
const r = ContactSchema.safeParse(entry);
|
|
37582
|
+
if (r.success) this.contacts.set(r.data.deviceId, r.data);
|
|
37575
37583
|
}
|
|
37576
37584
|
}
|
|
37577
37585
|
list() {
|
|
37578
|
-
return Array.from(this.
|
|
37586
|
+
return Array.from(this.contacts.values());
|
|
37579
37587
|
}
|
|
37580
|
-
get(
|
|
37581
|
-
return this.
|
|
37588
|
+
get(deviceId) {
|
|
37589
|
+
return this.contacts.get(deviceId) ?? null;
|
|
37582
37590
|
}
|
|
37583
|
-
|
|
37584
|
-
return this.
|
|
37591
|
+
has(deviceId) {
|
|
37592
|
+
return this.contacts.has(deviceId);
|
|
37585
37593
|
}
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
this.
|
|
37594
|
+
/** 某个人(ownerId, provider)的所有设备记录——人级视图(显示/分组用,不参与路由) */
|
|
37595
|
+
findByOwner(ownerId, provider) {
|
|
37596
|
+
return this.list().filter((c) => c.ownerId === ownerId && c.provider === provider);
|
|
37589
37597
|
}
|
|
37590
|
-
|
|
37591
|
-
|
|
37598
|
+
upsert(contact) {
|
|
37599
|
+
this.contacts.set(contact.deviceId, contact);
|
|
37592
37600
|
this.flush();
|
|
37593
37601
|
}
|
|
37602
|
+
/** 删单台设备(contact:remove 语义,决策 #16):删该 deviceId 一条,返回是否删到 */
|
|
37603
|
+
removeByDeviceId(deviceId) {
|
|
37604
|
+
const existed = this.contacts.delete(deviceId);
|
|
37605
|
+
if (existed) this.flush();
|
|
37606
|
+
return existed;
|
|
37607
|
+
}
|
|
37594
37608
|
flush() {
|
|
37595
|
-
const file =
|
|
37609
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37596
37610
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37597
37611
|
const content = JSON.stringify(
|
|
37598
|
-
{
|
|
37612
|
+
{ contacts: Array.from(this.contacts.values()) },
|
|
37599
37613
|
null,
|
|
37600
37614
|
2
|
|
37601
37615
|
);
|
|
37602
|
-
|
|
37603
|
-
|
|
37604
|
-
|
|
37616
|
+
fs20.mkdirSync(this.dataDir, { recursive: true });
|
|
37617
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37618
|
+
fs20.renameSync(tmp, file);
|
|
37605
37619
|
}
|
|
37606
37620
|
renameBak(file) {
|
|
37607
37621
|
try {
|
|
37608
|
-
|
|
37622
|
+
fs20.renameSync(file, `${file}.bak`);
|
|
37609
37623
|
} catch {
|
|
37610
37624
|
}
|
|
37611
37625
|
}
|
|
37612
37626
|
};
|
|
37613
37627
|
|
|
37614
|
-
// src/
|
|
37615
|
-
var
|
|
37628
|
+
// src/contact/connect-remote.ts
|
|
37629
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
37616
37630
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37617
37631
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37618
37632
|
async function connectRemote(args) {
|
|
@@ -37629,8 +37643,11 @@ async function connectRemote(args) {
|
|
|
37629
37643
|
JSON.stringify({
|
|
37630
37644
|
type: "auth",
|
|
37631
37645
|
token: args.token,
|
|
37632
|
-
|
|
37633
|
-
|
|
37646
|
+
// 自动反向(spec 决策 #11):带本机可达地址时对方反向建联系人;
|
|
37647
|
+
// 缺省时省略字段(不发空串),对方 schema selfUrl 是 .min(1).optional()。
|
|
37648
|
+
// 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
|
|
37649
|
+
// 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
|
|
37650
|
+
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37634
37651
|
})
|
|
37635
37652
|
);
|
|
37636
37653
|
await new Promise((resolve6, reject) => {
|
|
@@ -37641,7 +37658,7 @@ async function connectRemote(args) {
|
|
|
37641
37658
|
} catch {
|
|
37642
37659
|
return;
|
|
37643
37660
|
}
|
|
37644
|
-
if (f.type === "auth:ok"
|
|
37661
|
+
if (f.type === "auth:ok") {
|
|
37645
37662
|
ws.off("message", onMessage);
|
|
37646
37663
|
resolve6();
|
|
37647
37664
|
return;
|
|
@@ -37658,7 +37675,7 @@ async function connectRemote(args) {
|
|
|
37658
37675
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37659
37676
|
});
|
|
37660
37677
|
function call(method, payload) {
|
|
37661
|
-
const requestId =
|
|
37678
|
+
const requestId = crypto5.randomUUID();
|
|
37662
37679
|
return new Promise((resolve6, reject) => {
|
|
37663
37680
|
const onMessage = (raw) => {
|
|
37664
37681
|
let f;
|
|
@@ -37687,9 +37704,9 @@ async function connectRemote(args) {
|
|
|
37687
37704
|
whoami: async () => {
|
|
37688
37705
|
const f = await call("whoami", {});
|
|
37689
37706
|
return {
|
|
37690
|
-
ownerId: f.owner.
|
|
37707
|
+
ownerId: f.owner.ownerId,
|
|
37708
|
+
provider: f.owner.provider,
|
|
37691
37709
|
displayName: f.owner.displayName,
|
|
37692
|
-
capabilityId: f.capability.id,
|
|
37693
37710
|
grants: f.capability.grants
|
|
37694
37711
|
};
|
|
37695
37712
|
},
|
|
@@ -37702,62 +37719,96 @@ async function connectRemote(args) {
|
|
|
37702
37719
|
};
|
|
37703
37720
|
}
|
|
37704
37721
|
|
|
37722
|
+
// src/contact/auto-reverse.ts
|
|
37723
|
+
init_protocol();
|
|
37724
|
+
async function autoReverseContact(args) {
|
|
37725
|
+
if (!args.selfUrl) return;
|
|
37726
|
+
const now = args.now ?? Date.now;
|
|
37727
|
+
const grants = PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37728
|
+
resource: { ...g2.resource },
|
|
37729
|
+
actions: [...g2.actions]
|
|
37730
|
+
}));
|
|
37731
|
+
const base = {
|
|
37732
|
+
deviceId: args.deviceId,
|
|
37733
|
+
ownerId: args.ownerId,
|
|
37734
|
+
provider: args.provider,
|
|
37735
|
+
displayName: args.displayName,
|
|
37736
|
+
remoteUrl: args.selfUrl,
|
|
37737
|
+
connectToken: "",
|
|
37738
|
+
grants,
|
|
37739
|
+
addedAt: now()
|
|
37740
|
+
};
|
|
37741
|
+
args.store.upsert(base);
|
|
37742
|
+
args.broadcast({ type: "contact:added", contact: base });
|
|
37743
|
+
if (!args.exchangeToken) return;
|
|
37744
|
+
let token;
|
|
37745
|
+
try {
|
|
37746
|
+
token = await args.exchangeToken(args.deviceId);
|
|
37747
|
+
} catch {
|
|
37748
|
+
return;
|
|
37749
|
+
}
|
|
37750
|
+
if (!token) return;
|
|
37751
|
+
const upgraded = { ...base, connectToken: token };
|
|
37752
|
+
args.store.upsert(upgraded);
|
|
37753
|
+
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
37754
|
+
}
|
|
37755
|
+
|
|
37705
37756
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37706
|
-
var
|
|
37707
|
-
var
|
|
37757
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37758
|
+
var path23 = __toESM(require("path"), 1);
|
|
37708
37759
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37709
37760
|
function migrateFlattenSessions(opts) {
|
|
37710
37761
|
const dataDir = opts.dataDir;
|
|
37711
37762
|
const now = opts.now ?? Date.now;
|
|
37712
|
-
const sessionsDir =
|
|
37713
|
-
const flagPath =
|
|
37763
|
+
const sessionsDir = path23.join(dataDir, "sessions");
|
|
37764
|
+
const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37714
37765
|
if (existsSync3(flagPath)) {
|
|
37715
37766
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37716
37767
|
}
|
|
37717
37768
|
let movedBare = 0;
|
|
37718
37769
|
let movedVmOwner = 0;
|
|
37719
37770
|
let archivedListener = 0;
|
|
37720
|
-
const defaultDir =
|
|
37771
|
+
const defaultDir = path23.join(sessionsDir, "default");
|
|
37721
37772
|
if (existsSync3(defaultDir)) {
|
|
37722
37773
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37723
37774
|
if (!entry.endsWith(".json")) continue;
|
|
37724
|
-
const src =
|
|
37725
|
-
const dst =
|
|
37726
|
-
|
|
37775
|
+
const src = path23.join(defaultDir, entry);
|
|
37776
|
+
const dst = path23.join(sessionsDir, entry);
|
|
37777
|
+
fs21.renameSync(src, dst);
|
|
37727
37778
|
movedBare += 1;
|
|
37728
37779
|
}
|
|
37729
37780
|
rmdirIfEmpty(defaultDir);
|
|
37730
37781
|
}
|
|
37731
37782
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37732
|
-
const personaDir =
|
|
37783
|
+
const personaDir = path23.join(sessionsDir, pid);
|
|
37733
37784
|
if (!isDir(personaDir)) continue;
|
|
37734
37785
|
if (pid === "default") continue;
|
|
37735
|
-
const ownerSrc =
|
|
37786
|
+
const ownerSrc = path23.join(personaDir, "owner");
|
|
37736
37787
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37737
|
-
const ownerDst =
|
|
37738
|
-
|
|
37788
|
+
const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37789
|
+
fs21.mkdirSync(ownerDst, { recursive: true });
|
|
37739
37790
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37740
37791
|
if (!file.endsWith(".json")) continue;
|
|
37741
|
-
|
|
37792
|
+
fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
|
|
37742
37793
|
movedVmOwner += 1;
|
|
37743
37794
|
}
|
|
37744
37795
|
rmdirIfEmpty(ownerSrc);
|
|
37745
37796
|
}
|
|
37746
|
-
const listenerSrc =
|
|
37797
|
+
const listenerSrc = path23.join(personaDir, "listener");
|
|
37747
37798
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37748
|
-
const archiveDst =
|
|
37749
|
-
|
|
37799
|
+
const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37800
|
+
fs21.mkdirSync(archiveDst, { recursive: true });
|
|
37750
37801
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37751
37802
|
if (!file.endsWith(".json")) continue;
|
|
37752
|
-
|
|
37803
|
+
fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
|
|
37753
37804
|
archivedListener += 1;
|
|
37754
37805
|
}
|
|
37755
37806
|
rmdirIfEmpty(listenerSrc);
|
|
37756
37807
|
}
|
|
37757
37808
|
rmdirIfEmpty(personaDir);
|
|
37758
37809
|
}
|
|
37759
|
-
|
|
37760
|
-
|
|
37810
|
+
fs21.mkdirSync(sessionsDir, { recursive: true });
|
|
37811
|
+
fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37761
37812
|
return {
|
|
37762
37813
|
skipped: false,
|
|
37763
37814
|
flagWritten: true,
|
|
@@ -37768,7 +37819,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37768
37819
|
}
|
|
37769
37820
|
function existsSync3(p2) {
|
|
37770
37821
|
try {
|
|
37771
|
-
|
|
37822
|
+
fs21.statSync(p2);
|
|
37772
37823
|
return true;
|
|
37773
37824
|
} catch {
|
|
37774
37825
|
return false;
|
|
@@ -37776,31 +37827,31 @@ function existsSync3(p2) {
|
|
|
37776
37827
|
}
|
|
37777
37828
|
function isDir(p2) {
|
|
37778
37829
|
try {
|
|
37779
|
-
return
|
|
37830
|
+
return fs21.statSync(p2).isDirectory();
|
|
37780
37831
|
} catch {
|
|
37781
37832
|
return false;
|
|
37782
37833
|
}
|
|
37783
37834
|
}
|
|
37784
37835
|
function readdirSafe(p2) {
|
|
37785
37836
|
try {
|
|
37786
|
-
return
|
|
37837
|
+
return fs21.readdirSync(p2);
|
|
37787
37838
|
} catch {
|
|
37788
37839
|
return [];
|
|
37789
37840
|
}
|
|
37790
37841
|
}
|
|
37791
37842
|
function rmdirIfEmpty(p2) {
|
|
37792
37843
|
try {
|
|
37793
|
-
|
|
37844
|
+
fs21.rmdirSync(p2);
|
|
37794
37845
|
} catch {
|
|
37795
37846
|
}
|
|
37796
37847
|
}
|
|
37797
37848
|
|
|
37798
37849
|
// src/transport/http-router.ts
|
|
37799
|
-
var
|
|
37800
|
-
var
|
|
37850
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37851
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
37801
37852
|
|
|
37802
37853
|
// src/attachment/mime.ts
|
|
37803
|
-
var
|
|
37854
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37804
37855
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37805
37856
|
var EXT_TO_NATIVE_MIME = {
|
|
37806
37857
|
// 图片
|
|
@@ -37907,14 +37958,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37907
37958
|
".mk"
|
|
37908
37959
|
]);
|
|
37909
37960
|
function lookupMime(filePathOrName) {
|
|
37910
|
-
const ext =
|
|
37961
|
+
const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
|
|
37911
37962
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37912
37963
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37913
37964
|
return "application/octet-stream";
|
|
37914
37965
|
}
|
|
37915
37966
|
|
|
37916
37967
|
// src/attachment/sign-url.ts
|
|
37917
|
-
var
|
|
37968
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37918
37969
|
var HMAC_ALGO = "sha256";
|
|
37919
37970
|
function base64urlEncode(buf) {
|
|
37920
37971
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37931,7 +37982,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37931
37982
|
}
|
|
37932
37983
|
function computeSig(secret, absPath, e) {
|
|
37933
37984
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37934
|
-
return
|
|
37985
|
+
return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37935
37986
|
}
|
|
37936
37987
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37937
37988
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -37966,7 +38017,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37966
38017
|
if (provided.length !== expected.length) {
|
|
37967
38018
|
return { ok: false, code: "BAD_SIG" };
|
|
37968
38019
|
}
|
|
37969
|
-
if (!
|
|
38020
|
+
if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
|
|
37970
38021
|
return { ok: false, code: "BAD_SIG" };
|
|
37971
38022
|
}
|
|
37972
38023
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -37976,9 +38027,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37976
38027
|
}
|
|
37977
38028
|
|
|
37978
38029
|
// src/attachment/upload.ts
|
|
37979
|
-
var
|
|
37980
|
-
var
|
|
37981
|
-
var
|
|
38030
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
38031
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
38032
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
37982
38033
|
var import_promises = require("stream/promises");
|
|
37983
38034
|
var UploadError = class extends Error {
|
|
37984
38035
|
constructor(code, message) {
|
|
@@ -37989,24 +38040,24 @@ var UploadError = class extends Error {
|
|
|
37989
38040
|
code;
|
|
37990
38041
|
};
|
|
37991
38042
|
function assertValidFileName(fileName) {
|
|
37992
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
38043
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
|
|
37993
38044
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
37994
38045
|
}
|
|
37995
38046
|
}
|
|
37996
38047
|
var HASH_PREFIX_LEN = 16;
|
|
37997
38048
|
async function writeUploadedAttachment(args) {
|
|
37998
38049
|
assertValidFileName(args.fileName);
|
|
37999
|
-
const attachmentsRoot =
|
|
38050
|
+
const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
|
|
38000
38051
|
try {
|
|
38001
|
-
|
|
38052
|
+
import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
38002
38053
|
} catch (err) {
|
|
38003
38054
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
38004
38055
|
}
|
|
38005
|
-
const hasher =
|
|
38056
|
+
const hasher = import_node_crypto5.default.createHash("sha256");
|
|
38006
38057
|
let actualSize = 0;
|
|
38007
|
-
const tmpPath =
|
|
38058
|
+
const tmpPath = import_node_path17.default.join(
|
|
38008
38059
|
attachmentsRoot,
|
|
38009
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
38060
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto5.default.randomBytes(4).toString("hex")}`
|
|
38010
38061
|
);
|
|
38011
38062
|
try {
|
|
38012
38063
|
await (0, import_promises.pipeline)(
|
|
@@ -38019,18 +38070,18 @@ async function writeUploadedAttachment(args) {
|
|
|
38019
38070
|
yield buf;
|
|
38020
38071
|
}
|
|
38021
38072
|
},
|
|
38022
|
-
|
|
38073
|
+
import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
|
|
38023
38074
|
);
|
|
38024
38075
|
} catch (err) {
|
|
38025
38076
|
try {
|
|
38026
|
-
|
|
38077
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
38027
38078
|
} catch {
|
|
38028
38079
|
}
|
|
38029
38080
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
38030
38081
|
}
|
|
38031
38082
|
if (actualSize !== args.contentLength) {
|
|
38032
38083
|
try {
|
|
38033
|
-
|
|
38084
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
38034
38085
|
} catch {
|
|
38035
38086
|
}
|
|
38036
38087
|
throw new UploadError(
|
|
@@ -38039,35 +38090,35 @@ async function writeUploadedAttachment(args) {
|
|
|
38039
38090
|
);
|
|
38040
38091
|
}
|
|
38041
38092
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
38042
|
-
const hashDir =
|
|
38093
|
+
const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
|
|
38043
38094
|
let finalFileName;
|
|
38044
38095
|
let hashDirExists = false;
|
|
38045
38096
|
try {
|
|
38046
|
-
hashDirExists =
|
|
38097
|
+
hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
|
|
38047
38098
|
} catch {
|
|
38048
38099
|
}
|
|
38049
38100
|
if (hashDirExists) {
|
|
38050
|
-
const existing =
|
|
38101
|
+
const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
38051
38102
|
finalFileName = existing[0] ?? args.fileName;
|
|
38052
38103
|
try {
|
|
38053
|
-
|
|
38104
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
38054
38105
|
} catch {
|
|
38055
38106
|
}
|
|
38056
38107
|
} else {
|
|
38057
38108
|
try {
|
|
38058
|
-
|
|
38109
|
+
import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
|
|
38059
38110
|
finalFileName = args.fileName;
|
|
38060
|
-
|
|
38111
|
+
import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
|
|
38061
38112
|
} catch (err) {
|
|
38062
38113
|
try {
|
|
38063
|
-
|
|
38114
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
38064
38115
|
} catch {
|
|
38065
38116
|
}
|
|
38066
38117
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
38067
38118
|
}
|
|
38068
38119
|
}
|
|
38069
|
-
const absPath =
|
|
38070
|
-
const relPath =
|
|
38120
|
+
const absPath = import_node_path17.default.join(hashDir, finalFileName);
|
|
38121
|
+
const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
|
|
38071
38122
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
38072
38123
|
relPath: absPath,
|
|
38073
38124
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -38081,7 +38132,7 @@ async function writeUploadedAttachment(args) {
|
|
|
38081
38132
|
|
|
38082
38133
|
// src/extension/import.ts
|
|
38083
38134
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
38084
|
-
var
|
|
38135
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
38085
38136
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
38086
38137
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
38087
38138
|
var ImportError = class extends Error {
|
|
@@ -38099,7 +38150,7 @@ async function importZip(buf, root) {
|
|
|
38099
38150
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
38100
38151
|
}
|
|
38101
38152
|
for (const name of Object.keys(zip.files)) {
|
|
38102
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
38153
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path18.default.isAbsolute(name)) {
|
|
38103
38154
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
38104
38155
|
}
|
|
38105
38156
|
}
|
|
@@ -38132,7 +38183,7 @@ async function importZip(buf, root) {
|
|
|
38132
38183
|
);
|
|
38133
38184
|
}
|
|
38134
38185
|
}
|
|
38135
|
-
const destDir =
|
|
38186
|
+
const destDir = import_node_path18.default.join(root, manifest.id);
|
|
38136
38187
|
let destExists = false;
|
|
38137
38188
|
try {
|
|
38138
38189
|
await import_promises2.default.access(destDir);
|
|
@@ -38142,15 +38193,15 @@ async function importZip(buf, root) {
|
|
|
38142
38193
|
if (destExists) {
|
|
38143
38194
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
38144
38195
|
}
|
|
38145
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
38196
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path18.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
38146
38197
|
try {
|
|
38147
38198
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38148
|
-
const dest =
|
|
38199
|
+
const dest = import_node_path18.default.join(stage, name);
|
|
38149
38200
|
if (entry.dir) {
|
|
38150
38201
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38151
38202
|
continue;
|
|
38152
38203
|
}
|
|
38153
|
-
await import_promises2.default.mkdir(
|
|
38204
|
+
await import_promises2.default.mkdir(import_node_path18.default.dirname(dest), { recursive: true });
|
|
38154
38205
|
const content = await entry.async("nodebuffer");
|
|
38155
38206
|
await import_promises2.default.writeFile(dest, content);
|
|
38156
38207
|
}
|
|
@@ -38264,7 +38315,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38264
38315
|
if (fileName === "." || fileName === "..") return false;
|
|
38265
38316
|
if (fileName.startsWith(".")) return false;
|
|
38266
38317
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38267
|
-
return fileName ===
|
|
38318
|
+
return fileName === import_node_path19.default.basename(fileName);
|
|
38268
38319
|
}
|
|
38269
38320
|
function createHttpRouter(deps) {
|
|
38270
38321
|
return async (req, res) => {
|
|
@@ -38279,6 +38330,25 @@ function createHttpRouter(deps) {
|
|
|
38279
38330
|
});
|
|
38280
38331
|
return true;
|
|
38281
38332
|
}
|
|
38333
|
+
if (url.pathname === "/auth/callback") {
|
|
38334
|
+
if (req.method !== "GET") {
|
|
38335
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
38336
|
+
return true;
|
|
38337
|
+
}
|
|
38338
|
+
if (!deps.feishuLogin) {
|
|
38339
|
+
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
38340
|
+
return true;
|
|
38341
|
+
}
|
|
38342
|
+
const result = await deps.feishuLogin.handleLoginCallback({
|
|
38343
|
+
token: url.searchParams.get("token") ?? "",
|
|
38344
|
+
state: url.searchParams.get("state") ?? "",
|
|
38345
|
+
expiresAt: url.searchParams.get("expires_at")
|
|
38346
|
+
});
|
|
38347
|
+
const html = result.ok ? `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#222}</style><h2>\u767B\u5F55\u6210\u529F\uFF0C\u53EF\u5173\u95ED\u6B64\u9875\u3002</h2><p>\u8EAB\u4EFD\uFF1A${escapeHtml(result.identity.displayName)}</p><script>setTimeout(()=>window.close(),1500)</script>` : `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#922}</style><h2>\u767B\u5F55\u5931\u8D25</h2><p>${escapeHtml(result.reason)}</p>`;
|
|
38348
|
+
res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
38349
|
+
res.end(html);
|
|
38350
|
+
return true;
|
|
38351
|
+
}
|
|
38282
38352
|
if (url.pathname.startsWith(RPC_ROUTE_PREFIX)) {
|
|
38283
38353
|
if (!deps.rpcDispatcher) {
|
|
38284
38354
|
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "HTTP RPC adapter not wired" });
|
|
@@ -38474,7 +38544,7 @@ function createHttpRouter(deps) {
|
|
|
38474
38544
|
return true;
|
|
38475
38545
|
}
|
|
38476
38546
|
let absPath;
|
|
38477
|
-
if (
|
|
38547
|
+
if (import_node_path19.default.isAbsolute(pathParam)) {
|
|
38478
38548
|
absPath = pathParam;
|
|
38479
38549
|
} else if (deps.sessionStore) {
|
|
38480
38550
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38482,7 +38552,7 @@ function createHttpRouter(deps) {
|
|
|
38482
38552
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38483
38553
|
return true;
|
|
38484
38554
|
}
|
|
38485
|
-
absPath =
|
|
38555
|
+
absPath = import_node_path19.default.join(file.cwd, pathParam);
|
|
38486
38556
|
} else {
|
|
38487
38557
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38488
38558
|
return true;
|
|
@@ -38549,6 +38619,9 @@ function parseUrl(rawUrl) {
|
|
|
38549
38619
|
return null;
|
|
38550
38620
|
}
|
|
38551
38621
|
}
|
|
38622
|
+
function escapeHtml(s) {
|
|
38623
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38624
|
+
}
|
|
38552
38625
|
function sendJson(res, status, body, extraHeaders) {
|
|
38553
38626
|
res.writeHead(status, {
|
|
38554
38627
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38562,7 +38635,7 @@ function withCtx(ctx, body) {
|
|
|
38562
38635
|
function streamFile(res, absPath, logger) {
|
|
38563
38636
|
let stat;
|
|
38564
38637
|
try {
|
|
38565
|
-
stat =
|
|
38638
|
+
stat = import_node_fs16.default.statSync(absPath);
|
|
38566
38639
|
} catch (err) {
|
|
38567
38640
|
const code = err?.code;
|
|
38568
38641
|
if (code === "ENOENT") {
|
|
@@ -38577,7 +38650,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38577
38650
|
return;
|
|
38578
38651
|
}
|
|
38579
38652
|
const mime = lookupMime(absPath);
|
|
38580
|
-
const basename =
|
|
38653
|
+
const basename = import_node_path19.default.basename(absPath);
|
|
38581
38654
|
res.writeHead(200, {
|
|
38582
38655
|
"Content-Type": mime,
|
|
38583
38656
|
"Content-Length": String(stat.size),
|
|
@@ -38585,7 +38658,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38585
38658
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38586
38659
|
"X-Content-Type-Options": "nosniff"
|
|
38587
38660
|
});
|
|
38588
|
-
const stream =
|
|
38661
|
+
const stream = import_node_fs16.default.createReadStream(absPath);
|
|
38589
38662
|
stream.on("error", (err) => {
|
|
38590
38663
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38591
38664
|
res.destroy();
|
|
@@ -38594,8 +38667,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38594
38667
|
}
|
|
38595
38668
|
|
|
38596
38669
|
// src/attachment/gc.ts
|
|
38597
|
-
var
|
|
38598
|
-
var
|
|
38670
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
38671
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
38599
38672
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38600
38673
|
function runAttachmentGc(args) {
|
|
38601
38674
|
const now = (args.now ?? Date.now)();
|
|
@@ -38604,38 +38677,38 @@ function runAttachmentGc(args) {
|
|
|
38604
38677
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38605
38678
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38606
38679
|
if (entry.stale) continue;
|
|
38607
|
-
if (
|
|
38680
|
+
if (import_node_path20.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38608
38681
|
}
|
|
38609
38682
|
}
|
|
38610
38683
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38611
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38684
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path20.default.join(
|
|
38612
38685
|
args.dataDir,
|
|
38613
38686
|
"sessions",
|
|
38614
38687
|
...scopeSubPath(scope).map(safeFileName),
|
|
38615
38688
|
safeFileName(sessionId)
|
|
38616
38689
|
);
|
|
38617
|
-
const attRoot =
|
|
38690
|
+
const attRoot = import_node_path20.default.join(sessionDir, ".attachments");
|
|
38618
38691
|
let hashDirs;
|
|
38619
38692
|
try {
|
|
38620
|
-
hashDirs =
|
|
38693
|
+
hashDirs = import_node_fs17.default.readdirSync(attRoot);
|
|
38621
38694
|
} catch (err) {
|
|
38622
38695
|
if (err.code === "ENOENT") continue;
|
|
38623
38696
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38624
38697
|
continue;
|
|
38625
38698
|
}
|
|
38626
38699
|
for (const hashDir of hashDirs) {
|
|
38627
|
-
const hashDirAbs =
|
|
38700
|
+
const hashDirAbs = import_node_path20.default.join(attRoot, hashDir);
|
|
38628
38701
|
let files;
|
|
38629
38702
|
try {
|
|
38630
|
-
files =
|
|
38703
|
+
files = import_node_fs17.default.readdirSync(hashDirAbs);
|
|
38631
38704
|
} catch {
|
|
38632
38705
|
continue;
|
|
38633
38706
|
}
|
|
38634
38707
|
for (const name of files) {
|
|
38635
|
-
const file =
|
|
38708
|
+
const file = import_node_path20.default.join(hashDirAbs, name);
|
|
38636
38709
|
let stat;
|
|
38637
38710
|
try {
|
|
38638
|
-
stat =
|
|
38711
|
+
stat = import_node_fs17.default.statSync(file);
|
|
38639
38712
|
} catch {
|
|
38640
38713
|
continue;
|
|
38641
38714
|
}
|
|
@@ -38644,27 +38717,27 @@ function runAttachmentGc(args) {
|
|
|
38644
38717
|
if (age < ttlMs) continue;
|
|
38645
38718
|
if (liveAbs.has(file)) continue;
|
|
38646
38719
|
try {
|
|
38647
|
-
|
|
38720
|
+
import_node_fs17.default.unlinkSync(file);
|
|
38648
38721
|
} catch (err) {
|
|
38649
38722
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38650
38723
|
}
|
|
38651
38724
|
}
|
|
38652
38725
|
try {
|
|
38653
|
-
if (
|
|
38726
|
+
if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
|
|
38654
38727
|
} catch {
|
|
38655
38728
|
}
|
|
38656
38729
|
}
|
|
38657
38730
|
try {
|
|
38658
|
-
if (
|
|
38731
|
+
if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
|
|
38659
38732
|
} catch {
|
|
38660
38733
|
}
|
|
38661
38734
|
}
|
|
38662
38735
|
}
|
|
38663
38736
|
|
|
38664
38737
|
// src/attachment/group.ts
|
|
38665
|
-
var
|
|
38666
|
-
var
|
|
38667
|
-
var
|
|
38738
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38739
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38740
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
38668
38741
|
init_protocol();
|
|
38669
38742
|
var GroupFileStore = class {
|
|
38670
38743
|
dataDir;
|
|
@@ -38675,11 +38748,11 @@ var GroupFileStore = class {
|
|
|
38675
38748
|
this.logger = opts.logger;
|
|
38676
38749
|
}
|
|
38677
38750
|
rootForScope(scope) {
|
|
38678
|
-
return
|
|
38751
|
+
return import_node_path21.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38679
38752
|
}
|
|
38680
38753
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38681
38754
|
filePath(scope, sessionId) {
|
|
38682
|
-
return
|
|
38755
|
+
return import_node_path21.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38683
38756
|
}
|
|
38684
38757
|
cacheKey(scope, sessionId) {
|
|
38685
38758
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38688,7 +38761,7 @@ var GroupFileStore = class {
|
|
|
38688
38761
|
readFile(scope, sessionId) {
|
|
38689
38762
|
const file = this.filePath(scope, sessionId);
|
|
38690
38763
|
try {
|
|
38691
|
-
const raw =
|
|
38764
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
38692
38765
|
const parsed = JSON.parse(raw);
|
|
38693
38766
|
if (!Array.isArray(parsed)) {
|
|
38694
38767
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38714,10 +38787,10 @@ var GroupFileStore = class {
|
|
|
38714
38787
|
}
|
|
38715
38788
|
writeFile(scope, sessionId, entries) {
|
|
38716
38789
|
const file = this.filePath(scope, sessionId);
|
|
38717
|
-
|
|
38790
|
+
import_node_fs18.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true });
|
|
38718
38791
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38719
|
-
|
|
38720
|
-
|
|
38792
|
+
import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38793
|
+
import_node_fs18.default.renameSync(tmp, file);
|
|
38721
38794
|
}
|
|
38722
38795
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38723
38796
|
list(scope, sessionId) {
|
|
@@ -38753,7 +38826,7 @@ var GroupFileStore = class {
|
|
|
38753
38826
|
entries[idx] = next;
|
|
38754
38827
|
} else {
|
|
38755
38828
|
next = {
|
|
38756
|
-
id: `gf-${
|
|
38829
|
+
id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
|
|
38757
38830
|
relPath: input.relPath,
|
|
38758
38831
|
from: input.from,
|
|
38759
38832
|
label: input.label,
|
|
@@ -38803,10 +38876,10 @@ var GroupFileStore = class {
|
|
|
38803
38876
|
};
|
|
38804
38877
|
|
|
38805
38878
|
// src/discovery/state-file.ts
|
|
38806
|
-
var
|
|
38807
|
-
var
|
|
38879
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38880
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38808
38881
|
function defaultStateFilePath(dataDir) {
|
|
38809
|
-
return
|
|
38882
|
+
return import_node_path22.default.join(dataDir, "state.json");
|
|
38810
38883
|
}
|
|
38811
38884
|
function isPidAlive(pid) {
|
|
38812
38885
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38828,7 +38901,7 @@ var StateFileManager = class {
|
|
|
38828
38901
|
}
|
|
38829
38902
|
read() {
|
|
38830
38903
|
try {
|
|
38831
|
-
const raw =
|
|
38904
|
+
const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
|
|
38832
38905
|
const parsed = JSON.parse(raw);
|
|
38833
38906
|
return parsed;
|
|
38834
38907
|
} catch {
|
|
@@ -38842,34 +38915,34 @@ var StateFileManager = class {
|
|
|
38842
38915
|
return { status: "stale", existing };
|
|
38843
38916
|
}
|
|
38844
38917
|
write(state) {
|
|
38845
|
-
|
|
38918
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(this.file), { recursive: true });
|
|
38846
38919
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38847
|
-
|
|
38848
|
-
|
|
38920
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38921
|
+
import_node_fs19.default.renameSync(tmp, this.file);
|
|
38849
38922
|
if (process.platform !== "win32") {
|
|
38850
38923
|
try {
|
|
38851
|
-
|
|
38924
|
+
import_node_fs19.default.chmodSync(this.file, 384);
|
|
38852
38925
|
} catch {
|
|
38853
38926
|
}
|
|
38854
38927
|
}
|
|
38855
38928
|
}
|
|
38856
38929
|
delete() {
|
|
38857
38930
|
try {
|
|
38858
|
-
|
|
38931
|
+
import_node_fs19.default.unlinkSync(this.file);
|
|
38859
38932
|
} catch {
|
|
38860
38933
|
}
|
|
38861
38934
|
}
|
|
38862
38935
|
};
|
|
38863
38936
|
|
|
38864
38937
|
// src/tunnel/tunnel-manager.ts
|
|
38865
|
-
var
|
|
38866
|
-
var
|
|
38867
|
-
var
|
|
38938
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
38939
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
38940
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38868
38941
|
var import_node_child_process5 = require("child_process");
|
|
38869
38942
|
|
|
38870
38943
|
// src/tunnel/tunnel-store.ts
|
|
38871
|
-
var
|
|
38872
|
-
var
|
|
38944
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38945
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38873
38946
|
var TunnelStore = class {
|
|
38874
38947
|
constructor(filePath) {
|
|
38875
38948
|
this.filePath = filePath;
|
|
@@ -38877,7 +38950,7 @@ var TunnelStore = class {
|
|
|
38877
38950
|
filePath;
|
|
38878
38951
|
async get() {
|
|
38879
38952
|
try {
|
|
38880
|
-
const raw = await
|
|
38953
|
+
const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
|
|
38881
38954
|
const obj = JSON.parse(raw);
|
|
38882
38955
|
if (!isPersistedTunnel(obj)) return null;
|
|
38883
38956
|
return obj;
|
|
@@ -38888,22 +38961,22 @@ var TunnelStore = class {
|
|
|
38888
38961
|
}
|
|
38889
38962
|
}
|
|
38890
38963
|
async set(v2) {
|
|
38891
|
-
const dir =
|
|
38892
|
-
await
|
|
38964
|
+
const dir = import_node_path23.default.dirname(this.filePath);
|
|
38965
|
+
await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
|
|
38893
38966
|
const data = JSON.stringify(v2, null, 2);
|
|
38894
38967
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38895
|
-
await
|
|
38968
|
+
await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38896
38969
|
if (process.platform !== "win32") {
|
|
38897
38970
|
try {
|
|
38898
|
-
await
|
|
38971
|
+
await import_node_fs20.default.promises.chmod(tmp, 384);
|
|
38899
38972
|
} catch {
|
|
38900
38973
|
}
|
|
38901
38974
|
}
|
|
38902
|
-
await
|
|
38975
|
+
await import_node_fs20.default.promises.rename(tmp, this.filePath);
|
|
38903
38976
|
}
|
|
38904
38977
|
async clear() {
|
|
38905
38978
|
try {
|
|
38906
|
-
await
|
|
38979
|
+
await import_node_fs20.default.promises.unlink(this.filePath);
|
|
38907
38980
|
} catch (err) {
|
|
38908
38981
|
const code = err?.code;
|
|
38909
38982
|
if (code !== "ENOENT") throw err;
|
|
@@ -38998,9 +39071,9 @@ function escape(v2) {
|
|
|
38998
39071
|
}
|
|
38999
39072
|
|
|
39000
39073
|
// src/tunnel/frpc-binary.ts
|
|
39001
|
-
var
|
|
39074
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
39002
39075
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
39003
|
-
var
|
|
39076
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
39004
39077
|
var import_node_child_process3 = require("child_process");
|
|
39005
39078
|
var import_node_stream2 = require("stream");
|
|
39006
39079
|
var import_promises3 = require("stream/promises");
|
|
@@ -39032,20 +39105,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
39032
39105
|
}
|
|
39033
39106
|
async function ensureFrpcBinary(opts) {
|
|
39034
39107
|
if (opts.override) {
|
|
39035
|
-
if (!
|
|
39108
|
+
if (!import_node_fs21.default.existsSync(opts.override)) {
|
|
39036
39109
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
39037
39110
|
}
|
|
39038
39111
|
return opts.override;
|
|
39039
39112
|
}
|
|
39040
39113
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
39041
39114
|
const platform = opts.platform ?? detectPlatform();
|
|
39042
|
-
const binDir =
|
|
39043
|
-
|
|
39115
|
+
const binDir = import_node_path24.default.join(opts.dataDir, "bin");
|
|
39116
|
+
import_node_fs21.default.mkdirSync(binDir, { recursive: true });
|
|
39044
39117
|
cleanupStaleArtifacts(binDir);
|
|
39045
|
-
const stableBin =
|
|
39046
|
-
if (
|
|
39118
|
+
const stableBin = import_node_path24.default.join(binDir, "frpc");
|
|
39119
|
+
if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
|
|
39047
39120
|
const partialBin = `${stableBin}.partial`;
|
|
39048
|
-
const tarballPath =
|
|
39121
|
+
const tarballPath = import_node_path24.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
39049
39122
|
try {
|
|
39050
39123
|
const url = frpcDownloadUrl(version2, platform);
|
|
39051
39124
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -39054,8 +39127,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
39054
39127
|
} else {
|
|
39055
39128
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
39056
39129
|
}
|
|
39057
|
-
|
|
39058
|
-
|
|
39130
|
+
import_node_fs21.default.chmodSync(partialBin, 493);
|
|
39131
|
+
import_node_fs21.default.renameSync(partialBin, stableBin);
|
|
39059
39132
|
} finally {
|
|
39060
39133
|
safeUnlink(tarballPath);
|
|
39061
39134
|
safeUnlink(partialBin);
|
|
@@ -39065,15 +39138,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
39065
39138
|
function cleanupStaleArtifacts(binDir) {
|
|
39066
39139
|
let entries;
|
|
39067
39140
|
try {
|
|
39068
|
-
entries =
|
|
39141
|
+
entries = import_node_fs21.default.readdirSync(binDir);
|
|
39069
39142
|
} catch {
|
|
39070
39143
|
return;
|
|
39071
39144
|
}
|
|
39072
39145
|
for (const name of entries) {
|
|
39073
39146
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
39074
|
-
const full =
|
|
39147
|
+
const full = import_node_path24.default.join(binDir, name);
|
|
39075
39148
|
try {
|
|
39076
|
-
|
|
39149
|
+
import_node_fs21.default.rmSync(full, { recursive: true, force: true });
|
|
39077
39150
|
} catch {
|
|
39078
39151
|
}
|
|
39079
39152
|
}
|
|
@@ -39081,7 +39154,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
39081
39154
|
}
|
|
39082
39155
|
function safeUnlink(p2) {
|
|
39083
39156
|
try {
|
|
39084
|
-
|
|
39157
|
+
import_node_fs21.default.unlinkSync(p2);
|
|
39085
39158
|
} catch {
|
|
39086
39159
|
}
|
|
39087
39160
|
}
|
|
@@ -39092,13 +39165,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
39092
39165
|
if (!res.ok || !res.body) {
|
|
39093
39166
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
39094
39167
|
}
|
|
39095
|
-
const out =
|
|
39168
|
+
const out = import_node_fs21.default.createWriteStream(dest);
|
|
39096
39169
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
39097
39170
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
39098
39171
|
}
|
|
39099
39172
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
39100
|
-
const work =
|
|
39101
|
-
|
|
39173
|
+
const work = import_node_path24.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
39174
|
+
import_node_fs21.default.mkdirSync(work, { recursive: true });
|
|
39102
39175
|
try {
|
|
39103
39176
|
await new Promise((resolve6, reject) => {
|
|
39104
39177
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -39106,32 +39179,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
39106
39179
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
39107
39180
|
});
|
|
39108
39181
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
39109
|
-
const src =
|
|
39110
|
-
if (!
|
|
39182
|
+
const src = import_node_path24.default.join(work, dirName, "frpc");
|
|
39183
|
+
if (!import_node_fs21.default.existsSync(src)) {
|
|
39111
39184
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
39112
39185
|
}
|
|
39113
|
-
|
|
39186
|
+
import_node_fs21.default.copyFileSync(src, destBin);
|
|
39114
39187
|
} finally {
|
|
39115
|
-
|
|
39188
|
+
import_node_fs21.default.rmSync(work, { recursive: true, force: true });
|
|
39116
39189
|
}
|
|
39117
39190
|
}
|
|
39118
39191
|
|
|
39119
39192
|
// src/tunnel/frpc-process.ts
|
|
39120
|
-
var
|
|
39121
|
-
var
|
|
39193
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
39194
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
39122
39195
|
var import_node_child_process4 = require("child_process");
|
|
39123
39196
|
function frpcPidFilePath(dataDir) {
|
|
39124
|
-
return
|
|
39197
|
+
return import_node_path25.default.join(dataDir, "frpc.pid");
|
|
39125
39198
|
}
|
|
39126
39199
|
function writeFrpcPid(dataDir, pid) {
|
|
39127
39200
|
try {
|
|
39128
|
-
|
|
39201
|
+
import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
39129
39202
|
} catch {
|
|
39130
39203
|
}
|
|
39131
39204
|
}
|
|
39132
39205
|
function clearFrpcPid(dataDir) {
|
|
39133
39206
|
try {
|
|
39134
|
-
|
|
39207
|
+
import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
39135
39208
|
} catch {
|
|
39136
39209
|
}
|
|
39137
39210
|
}
|
|
@@ -39147,7 +39220,7 @@ function defaultIsPidAlive(pid) {
|
|
|
39147
39220
|
}
|
|
39148
39221
|
function defaultReadPidFile(file) {
|
|
39149
39222
|
try {
|
|
39150
|
-
return
|
|
39223
|
+
return import_node_fs22.default.readFileSync(file, "utf8");
|
|
39151
39224
|
} catch {
|
|
39152
39225
|
return null;
|
|
39153
39226
|
}
|
|
@@ -39163,7 +39236,7 @@ function defaultSleep(ms) {
|
|
|
39163
39236
|
}
|
|
39164
39237
|
async function killStaleFrpc(deps) {
|
|
39165
39238
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
39166
|
-
const tomlPath =
|
|
39239
|
+
const tomlPath = import_node_path25.default.join(deps.dataDir, "frpc.toml");
|
|
39167
39240
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
39168
39241
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
39169
39242
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -39187,7 +39260,7 @@ async function killStaleFrpc(deps) {
|
|
|
39187
39260
|
}
|
|
39188
39261
|
if (victims.size === 0) {
|
|
39189
39262
|
try {
|
|
39190
|
-
|
|
39263
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
39191
39264
|
} catch {
|
|
39192
39265
|
}
|
|
39193
39266
|
return;
|
|
@@ -39198,7 +39271,7 @@ async function killStaleFrpc(deps) {
|
|
|
39198
39271
|
}
|
|
39199
39272
|
await sleep(deps.reapWaitMs ?? 300);
|
|
39200
39273
|
try {
|
|
39201
|
-
|
|
39274
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
39202
39275
|
} catch {
|
|
39203
39276
|
}
|
|
39204
39277
|
}
|
|
@@ -39235,7 +39308,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
39235
39308
|
var TunnelManager = class {
|
|
39236
39309
|
constructor(deps) {
|
|
39237
39310
|
this.deps = deps;
|
|
39238
|
-
this.store = deps.store ?? new TunnelStore(
|
|
39311
|
+
this.store = deps.store ?? new TunnelStore(import_node_path26.default.join(deps.dataDir, "tunnel.json"));
|
|
39239
39312
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39240
39313
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39241
39314
|
}
|
|
@@ -39362,8 +39435,8 @@ var TunnelManager = class {
|
|
|
39362
39435
|
dataDir: this.deps.dataDir,
|
|
39363
39436
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39364
39437
|
});
|
|
39365
|
-
const tomlPath =
|
|
39366
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39438
|
+
const tomlPath = import_node_path26.default.join(this.deps.dataDir, "frpc.toml");
|
|
39439
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
|
|
39367
39440
|
const toml = buildFrpcToml({
|
|
39368
39441
|
serverAddr: t.frpsHost,
|
|
39369
39442
|
serverPort: t.frpsPort,
|
|
@@ -39373,12 +39446,12 @@ var TunnelManager = class {
|
|
|
39373
39446
|
localPort,
|
|
39374
39447
|
logLevel: "info"
|
|
39375
39448
|
});
|
|
39376
|
-
await
|
|
39449
|
+
await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39377
39450
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39378
39451
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39379
39452
|
});
|
|
39380
|
-
const logFilePath =
|
|
39381
|
-
const logStream =
|
|
39453
|
+
const logFilePath = import_node_path26.default.join(this.deps.dataDir, "frpc.log");
|
|
39454
|
+
const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39382
39455
|
logStream.on("error", () => {
|
|
39383
39456
|
});
|
|
39384
39457
|
const tee = (chunk) => {
|
|
@@ -39461,59 +39534,65 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39461
39534
|
|
|
39462
39535
|
// src/tunnel/device-key.ts
|
|
39463
39536
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39464
|
-
var
|
|
39465
|
-
var
|
|
39537
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
39538
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
39466
39539
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39467
39540
|
function deriveStableDeviceKey(opts = {}) {
|
|
39468
39541
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39469
39542
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39470
39543
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39471
|
-
const defaultDataDir =
|
|
39472
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39544
|
+
const defaultDataDir = import_node_path27.default.resolve(import_node_path27.default.join(home, ".clawd"));
|
|
39545
|
+
const normalizedDataDir = opts.dataDir ? import_node_path27.default.resolve(opts.dataDir) : null;
|
|
39473
39546
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39474
39547
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39475
|
-
return
|
|
39548
|
+
return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39476
39549
|
}
|
|
39477
39550
|
|
|
39478
39551
|
// src/auth-store.ts
|
|
39479
|
-
var
|
|
39480
|
-
var
|
|
39481
|
-
var
|
|
39552
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
39553
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39554
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39482
39555
|
var AUTH_FILE_NAME = "auth.json";
|
|
39483
39556
|
function authFilePath(dataDir) {
|
|
39484
|
-
return
|
|
39557
|
+
return import_node_path28.default.join(dataDir, AUTH_FILE_NAME);
|
|
39485
39558
|
}
|
|
39486
39559
|
function loadOrCreateAuthFile(opts) {
|
|
39487
39560
|
const file = authFilePath(opts.dataDir);
|
|
39488
|
-
const generate = opts.generate ??
|
|
39561
|
+
const generate = opts.generate ?? defaultGenerateToken;
|
|
39489
39562
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39563
|
+
const genDevice = opts.genDeviceId ?? defaultGenerateDeviceId;
|
|
39490
39564
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39491
39565
|
const existing = readAuthFile(file);
|
|
39492
|
-
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
|
|
39566
|
+
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId && existing.deviceId) {
|
|
39493
39567
|
return {
|
|
39494
39568
|
token: existing.token,
|
|
39495
39569
|
signSecret: existing.signSecret,
|
|
39496
39570
|
ownerPrincipalId: existing.ownerPrincipalId,
|
|
39571
|
+
deviceId: existing.deviceId,
|
|
39497
39572
|
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39498
39573
|
};
|
|
39499
39574
|
}
|
|
39500
39575
|
const token = existing?.token || generate();
|
|
39501
39576
|
const signSecret = existing?.signSecret || generate();
|
|
39502
39577
|
const ownerPrincipalId = existing?.ownerPrincipalId || genId();
|
|
39578
|
+
const deviceId = existing?.deviceId || genDevice();
|
|
39503
39579
|
const createdAt = existing?.createdAt || now().toISOString();
|
|
39504
|
-
const next = { token, signSecret, ownerPrincipalId, createdAt };
|
|
39580
|
+
const next = { token, signSecret, ownerPrincipalId, deviceId, createdAt };
|
|
39505
39581
|
writeAuthFile(file, next);
|
|
39506
39582
|
return next;
|
|
39507
39583
|
}
|
|
39508
|
-
function
|
|
39509
|
-
return
|
|
39584
|
+
function defaultGenerateToken() {
|
|
39585
|
+
return import_node_crypto9.default.randomBytes(32).toString("base64url");
|
|
39510
39586
|
}
|
|
39511
39587
|
function defaultGenerateOwnerPrincipalId() {
|
|
39512
|
-
return `owner-${
|
|
39588
|
+
return `owner-${import_node_crypto9.default.randomUUID()}`;
|
|
39589
|
+
}
|
|
39590
|
+
function defaultGenerateDeviceId() {
|
|
39591
|
+
return `dev-${import_node_crypto9.default.randomUUID()}`;
|
|
39513
39592
|
}
|
|
39514
39593
|
function readAuthFile(file) {
|
|
39515
39594
|
try {
|
|
39516
|
-
const raw =
|
|
39595
|
+
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
39517
39596
|
const parsed = JSON.parse(raw);
|
|
39518
39597
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39519
39598
|
return null;
|
|
@@ -39522,6 +39601,7 @@ function readAuthFile(file) {
|
|
|
39522
39601
|
token: parsed.token,
|
|
39523
39602
|
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
39524
39603
|
ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
|
|
39604
|
+
deviceId: typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? parsed.deviceId : void 0,
|
|
39525
39605
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
39526
39606
|
};
|
|
39527
39607
|
} catch (err) {
|
|
@@ -39531,25 +39611,25 @@ function readAuthFile(file) {
|
|
|
39531
39611
|
}
|
|
39532
39612
|
}
|
|
39533
39613
|
function writeAuthFile(file, content) {
|
|
39534
|
-
|
|
39535
|
-
|
|
39614
|
+
import_node_fs24.default.mkdirSync(import_node_path28.default.dirname(file), { recursive: true });
|
|
39615
|
+
import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39536
39616
|
try {
|
|
39537
|
-
|
|
39617
|
+
import_node_fs24.default.chmodSync(file, 384);
|
|
39538
39618
|
} catch {
|
|
39539
39619
|
}
|
|
39540
39620
|
}
|
|
39541
39621
|
|
|
39542
39622
|
// src/owner-profile.ts
|
|
39543
|
-
var
|
|
39623
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39544
39624
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39545
|
-
var
|
|
39625
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39546
39626
|
var PROFILE_FILENAME = "profile.json";
|
|
39547
39627
|
function loadOwnerDisplayName(dataDir) {
|
|
39548
39628
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39549
|
-
const profilePath =
|
|
39629
|
+
const profilePath = import_node_path29.default.join(dataDir, PROFILE_FILENAME);
|
|
39550
39630
|
let raw;
|
|
39551
39631
|
try {
|
|
39552
|
-
raw =
|
|
39632
|
+
raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
|
|
39553
39633
|
} catch {
|
|
39554
39634
|
return fallback;
|
|
39555
39635
|
}
|
|
@@ -39571,6 +39651,357 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39571
39651
|
return displayName;
|
|
39572
39652
|
}
|
|
39573
39653
|
|
|
39654
|
+
// src/feishu-auth/owner-identity-store.ts
|
|
39655
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39656
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
39657
|
+
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
39658
|
+
var OwnerIdentityStore = class {
|
|
39659
|
+
file;
|
|
39660
|
+
constructor(dataDir) {
|
|
39661
|
+
this.file = import_node_path30.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
39662
|
+
}
|
|
39663
|
+
read() {
|
|
39664
|
+
let raw;
|
|
39665
|
+
try {
|
|
39666
|
+
raw = import_node_fs26.default.readFileSync(this.file, "utf8");
|
|
39667
|
+
} catch {
|
|
39668
|
+
return null;
|
|
39669
|
+
}
|
|
39670
|
+
let parsed;
|
|
39671
|
+
try {
|
|
39672
|
+
parsed = JSON.parse(raw);
|
|
39673
|
+
} catch {
|
|
39674
|
+
return null;
|
|
39675
|
+
}
|
|
39676
|
+
const r = parsed;
|
|
39677
|
+
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) {
|
|
39678
|
+
return null;
|
|
39679
|
+
}
|
|
39680
|
+
return {
|
|
39681
|
+
identity: {
|
|
39682
|
+
ownerId: r.identity.ownerId,
|
|
39683
|
+
provider: r.identity.provider,
|
|
39684
|
+
displayName: r.identity.displayName,
|
|
39685
|
+
...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
|
|
39686
|
+
},
|
|
39687
|
+
ttcToken: r.ttcToken,
|
|
39688
|
+
ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
|
|
39689
|
+
loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39690
|
+
};
|
|
39691
|
+
}
|
|
39692
|
+
write(record) {
|
|
39693
|
+
import_node_fs26.default.mkdirSync(import_node_path30.default.dirname(this.file), { recursive: true });
|
|
39694
|
+
import_node_fs26.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
|
|
39695
|
+
try {
|
|
39696
|
+
import_node_fs26.default.chmodSync(this.file, 384);
|
|
39697
|
+
} catch {
|
|
39698
|
+
}
|
|
39699
|
+
}
|
|
39700
|
+
clear() {
|
|
39701
|
+
try {
|
|
39702
|
+
import_node_fs26.default.unlinkSync(this.file);
|
|
39703
|
+
} catch (err) {
|
|
39704
|
+
const code = err?.code;
|
|
39705
|
+
if (code !== "ENOENT") throw err;
|
|
39706
|
+
}
|
|
39707
|
+
}
|
|
39708
|
+
};
|
|
39709
|
+
|
|
39710
|
+
// src/feishu-auth/login-flow.ts
|
|
39711
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39712
|
+
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
39713
|
+
var LoginFlow = class {
|
|
39714
|
+
constructor(deps) {
|
|
39715
|
+
this.deps = deps;
|
|
39716
|
+
}
|
|
39717
|
+
deps;
|
|
39718
|
+
pendingStates = /* @__PURE__ */ new Map();
|
|
39719
|
+
start() {
|
|
39720
|
+
const state = import_node_crypto10.default.randomBytes(16).toString("base64url");
|
|
39721
|
+
const now = (this.deps.now ?? Date.now)();
|
|
39722
|
+
this.pendingStates.set(state, now);
|
|
39723
|
+
this.gcExpired(now);
|
|
39724
|
+
const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
|
|
39725
|
+
url.searchParams.set("callback_url", this.deps.getCallbackUrl());
|
|
39726
|
+
url.searchParams.set("state", state);
|
|
39727
|
+
url.searchParams.set("auto", "1");
|
|
39728
|
+
return { authUrl: url.toString(), state };
|
|
39729
|
+
}
|
|
39730
|
+
async handleCallback(params) {
|
|
39731
|
+
const now = (this.deps.now ?? Date.now)();
|
|
39732
|
+
const issuedAt = this.pendingStates.get(params.state);
|
|
39733
|
+
if (issuedAt === void 0) {
|
|
39734
|
+
return { ok: false, reason: "state mismatch" };
|
|
39735
|
+
}
|
|
39736
|
+
this.pendingStates.delete(params.state);
|
|
39737
|
+
if (now - issuedAt > STATE_TTL_MS) {
|
|
39738
|
+
return { ok: false, reason: "state expired" };
|
|
39739
|
+
}
|
|
39740
|
+
if (!params.token) {
|
|
39741
|
+
return { ok: false, reason: "missing token" };
|
|
39742
|
+
}
|
|
39743
|
+
let identity;
|
|
39744
|
+
try {
|
|
39745
|
+
identity = await this.deps.fetchUserInfo(params.token);
|
|
39746
|
+
} catch (err) {
|
|
39747
|
+
return { ok: false, reason: `user_info failed: ${err.message}` };
|
|
39748
|
+
}
|
|
39749
|
+
const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
|
|
39750
|
+
this.deps.store.write({
|
|
39751
|
+
identity,
|
|
39752
|
+
ttcToken: params.token,
|
|
39753
|
+
ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
|
|
39754
|
+
loginAt: new Date(now).toISOString()
|
|
39755
|
+
});
|
|
39756
|
+
return { ok: true, identity };
|
|
39757
|
+
}
|
|
39758
|
+
gcExpired(now) {
|
|
39759
|
+
for (const [state, issuedAt] of this.pendingStates) {
|
|
39760
|
+
if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
|
|
39761
|
+
}
|
|
39762
|
+
}
|
|
39763
|
+
};
|
|
39764
|
+
|
|
39765
|
+
// src/feishu-auth/ttc-client.ts
|
|
39766
|
+
var TTC_HOSTS = {
|
|
39767
|
+
auth: "https://app.ttcadvisory.com",
|
|
39768
|
+
api: "https://api.ttcadvisory.com"
|
|
39769
|
+
};
|
|
39770
|
+
var TtcUserInfoError = class extends Error {
|
|
39771
|
+
constructor(code, message) {
|
|
39772
|
+
super(message);
|
|
39773
|
+
this.code = code;
|
|
39774
|
+
this.name = "TtcUserInfoError";
|
|
39775
|
+
}
|
|
39776
|
+
code;
|
|
39777
|
+
};
|
|
39778
|
+
async function fetchTtcUserInfo(opts) {
|
|
39779
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
39780
|
+
let res;
|
|
39781
|
+
try {
|
|
39782
|
+
res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
|
|
39783
|
+
headers: { Authorization: `Bearer ${opts.token}` }
|
|
39784
|
+
});
|
|
39785
|
+
} catch (err) {
|
|
39786
|
+
throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
|
|
39787
|
+
}
|
|
39788
|
+
if (!res.ok) {
|
|
39789
|
+
if (res.status === 401) {
|
|
39790
|
+
throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
|
|
39791
|
+
}
|
|
39792
|
+
throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
|
|
39793
|
+
}
|
|
39794
|
+
const body = await res.json();
|
|
39795
|
+
if (body.code !== 0) {
|
|
39796
|
+
throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
|
|
39797
|
+
}
|
|
39798
|
+
const d = body.data;
|
|
39799
|
+
if (!d || typeof d.unique_id !== "string" || d.unique_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
|
|
39800
|
+
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing unique_id or name");
|
|
39801
|
+
}
|
|
39802
|
+
return {
|
|
39803
|
+
ownerId: d.unique_id,
|
|
39804
|
+
provider: "ttc",
|
|
39805
|
+
displayName: d.name,
|
|
39806
|
+
...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
|
|
39807
|
+
};
|
|
39808
|
+
}
|
|
39809
|
+
|
|
39810
|
+
// src/feishu-auth/central-client.ts
|
|
39811
|
+
var CentralClientError = class extends Error {
|
|
39812
|
+
constructor(code, message, cause) {
|
|
39813
|
+
super(message);
|
|
39814
|
+
this.code = code;
|
|
39815
|
+
this.cause = cause;
|
|
39816
|
+
this.name = "CentralClientError";
|
|
39817
|
+
}
|
|
39818
|
+
code;
|
|
39819
|
+
cause;
|
|
39820
|
+
};
|
|
39821
|
+
async function centralRequest(opts, path60, init) {
|
|
39822
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
39823
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path60}`;
|
|
39824
|
+
const ctrl = new AbortController();
|
|
39825
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
39826
|
+
let res;
|
|
39827
|
+
try {
|
|
39828
|
+
res = await f(url, {
|
|
39829
|
+
method: init.method,
|
|
39830
|
+
headers: {
|
|
39831
|
+
"content-type": "application/json",
|
|
39832
|
+
...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
|
|
39833
|
+
},
|
|
39834
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
39835
|
+
signal: ctrl.signal
|
|
39836
|
+
});
|
|
39837
|
+
} catch (err) {
|
|
39838
|
+
clearTimeout(timer);
|
|
39839
|
+
throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
|
|
39840
|
+
}
|
|
39841
|
+
clearTimeout(timer);
|
|
39842
|
+
if (res.status === 401) {
|
|
39843
|
+
throw new CentralClientError("UNAUTHORIZED", "TTC JWT invalid or expired");
|
|
39844
|
+
}
|
|
39845
|
+
if (!res.ok) {
|
|
39846
|
+
throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}`);
|
|
39847
|
+
}
|
|
39848
|
+
try {
|
|
39849
|
+
return await res.json();
|
|
39850
|
+
} catch (err) {
|
|
39851
|
+
throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
|
|
39852
|
+
}
|
|
39853
|
+
}
|
|
39854
|
+
async function centralExchange(opts) {
|
|
39855
|
+
const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
|
|
39856
|
+
method: "POST",
|
|
39857
|
+
body: {
|
|
39858
|
+
jwt: opts.jwt,
|
|
39859
|
+
provider: opts.provider,
|
|
39860
|
+
deviceId: opts.deviceId,
|
|
39861
|
+
selfDeviceId: opts.selfDeviceId
|
|
39862
|
+
}
|
|
39863
|
+
});
|
|
39864
|
+
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
|
|
39865
|
+
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
|
|
39866
|
+
}
|
|
39867
|
+
const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
|
|
39868
|
+
return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
|
|
39869
|
+
}
|
|
39870
|
+
async function centralGetPublicKey(opts) {
|
|
39871
|
+
const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
|
|
39872
|
+
method: "GET"
|
|
39873
|
+
});
|
|
39874
|
+
if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
|
|
39875
|
+
throw new CentralClientError("BAD_RESPONSE", "public-key: missing or malformed publicKeyPem");
|
|
39876
|
+
}
|
|
39877
|
+
return { publicKeyPem: body.publicKeyPem };
|
|
39878
|
+
}
|
|
39879
|
+
async function centralUpsertDevice(opts) {
|
|
39880
|
+
const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
|
|
39881
|
+
method: "PUT",
|
|
39882
|
+
bearer: opts.ttcJwt,
|
|
39883
|
+
body: {
|
|
39884
|
+
deviceId: opts.deviceId,
|
|
39885
|
+
provider: opts.provider,
|
|
39886
|
+
...opts.url ? { url: opts.url } : {},
|
|
39887
|
+
...opts.name ? { name: opts.name } : {}
|
|
39888
|
+
}
|
|
39889
|
+
});
|
|
39890
|
+
if (body.ok !== true) {
|
|
39891
|
+
throw new CentralClientError("BAD_RESPONSE", "upsertDevice: server did not ack ok");
|
|
39892
|
+
}
|
|
39893
|
+
return { ok: true };
|
|
39894
|
+
}
|
|
39895
|
+
async function centralListDevices(opts) {
|
|
39896
|
+
const body = await centralRequest(
|
|
39897
|
+
opts,
|
|
39898
|
+
`/api/clawd-identity/devices?provider=${encodeURIComponent(opts.provider)}`,
|
|
39899
|
+
{ method: "GET", bearer: opts.ttcJwt }
|
|
39900
|
+
);
|
|
39901
|
+
if (!Array.isArray(body.devices)) {
|
|
39902
|
+
throw new CentralClientError("BAD_RESPONSE", "devices: missing devices array");
|
|
39903
|
+
}
|
|
39904
|
+
const out = [];
|
|
39905
|
+
for (const raw of body.devices) {
|
|
39906
|
+
const d = raw;
|
|
39907
|
+
if (typeof d.deviceId !== "string" || typeof d.name !== "string" || typeof d.url !== "string") {
|
|
39908
|
+
throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
|
|
39909
|
+
}
|
|
39910
|
+
out.push({ deviceId: d.deviceId, name: d.name, url: d.url });
|
|
39911
|
+
}
|
|
39912
|
+
return out;
|
|
39913
|
+
}
|
|
39914
|
+
|
|
39915
|
+
// src/feishu-auth/verify-token.ts
|
|
39916
|
+
var crypto13 = __toESM(require("crypto"), 1);
|
|
39917
|
+
var CONNECT_TOKEN_PREFIX = "clawdtk1";
|
|
39918
|
+
function verifyConnectToken(args) {
|
|
39919
|
+
const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
39920
|
+
const parts = args.token.split(".");
|
|
39921
|
+
if (parts.length !== 3 || parts[0] !== CONNECT_TOKEN_PREFIX) {
|
|
39922
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39923
|
+
}
|
|
39924
|
+
const [prefix, payloadB64, sigB64] = parts;
|
|
39925
|
+
const data = `${prefix}.${payloadB64}`;
|
|
39926
|
+
let signatureValid = false;
|
|
39927
|
+
try {
|
|
39928
|
+
signatureValid = crypto13.verify(
|
|
39929
|
+
null,
|
|
39930
|
+
Buffer.from(data, "utf8"),
|
|
39931
|
+
args.publicKeyPem,
|
|
39932
|
+
Buffer.from(sigB64, "base64url")
|
|
39933
|
+
);
|
|
39934
|
+
} catch {
|
|
39935
|
+
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
39936
|
+
}
|
|
39937
|
+
if (!signatureValid) {
|
|
39938
|
+
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
39939
|
+
}
|
|
39940
|
+
let payload;
|
|
39941
|
+
try {
|
|
39942
|
+
payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
39943
|
+
} catch {
|
|
39944
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39945
|
+
}
|
|
39946
|
+
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") {
|
|
39947
|
+
return { ok: false, reason: "BAD_FORMAT" };
|
|
39948
|
+
}
|
|
39949
|
+
if (payload.aud !== args.expectedDeviceId) {
|
|
39950
|
+
return { ok: false, reason: "WRONG_AUDIENCE" };
|
|
39951
|
+
}
|
|
39952
|
+
if (payload.exp <= now) {
|
|
39953
|
+
return { ok: false, reason: "EXPIRED" };
|
|
39954
|
+
}
|
|
39955
|
+
return {
|
|
39956
|
+
ok: true,
|
|
39957
|
+
subDevice: payload.subDevice,
|
|
39958
|
+
subOwner: payload.subOwner,
|
|
39959
|
+
provider: payload.provider,
|
|
39960
|
+
displayName: payload.name
|
|
39961
|
+
};
|
|
39962
|
+
}
|
|
39963
|
+
|
|
39964
|
+
// src/feishu-auth/server-key.ts
|
|
39965
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
39966
|
+
var path39 = __toESM(require("path"), 1);
|
|
39967
|
+
var FILE_NAME2 = "server-signing-key.json";
|
|
39968
|
+
var ServerKeyStore = class {
|
|
39969
|
+
constructor(dataDir) {
|
|
39970
|
+
this.dataDir = dataDir;
|
|
39971
|
+
}
|
|
39972
|
+
dataDir;
|
|
39973
|
+
filePath() {
|
|
39974
|
+
return path39.join(this.dataDir, FILE_NAME2);
|
|
39975
|
+
}
|
|
39976
|
+
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
39977
|
+
read() {
|
|
39978
|
+
try {
|
|
39979
|
+
const raw = fs35.readFileSync(this.filePath(), "utf8");
|
|
39980
|
+
const parsed = JSON.parse(raw);
|
|
39981
|
+
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
39982
|
+
return parsed.publicKeyPem;
|
|
39983
|
+
}
|
|
39984
|
+
return null;
|
|
39985
|
+
} catch {
|
|
39986
|
+
return null;
|
|
39987
|
+
}
|
|
39988
|
+
}
|
|
39989
|
+
write(publicKeyPem) {
|
|
39990
|
+
const content = {
|
|
39991
|
+
publicKeyPem,
|
|
39992
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
39993
|
+
};
|
|
39994
|
+
fs35.mkdirSync(this.dataDir, { recursive: true });
|
|
39995
|
+
fs35.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
39996
|
+
}
|
|
39997
|
+
clear() {
|
|
39998
|
+
try {
|
|
39999
|
+
fs35.unlinkSync(this.filePath());
|
|
40000
|
+
} catch {
|
|
40001
|
+
}
|
|
40002
|
+
}
|
|
40003
|
+
};
|
|
40004
|
+
|
|
39574
40005
|
// src/index.ts
|
|
39575
40006
|
init_protocol();
|
|
39576
40007
|
|
|
@@ -39701,23 +40132,15 @@ function buildSessionHandlers(deps) {
|
|
|
39701
40132
|
}
|
|
39702
40133
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
39703
40134
|
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
39704
|
-
const
|
|
39705
|
-
const { response, broadcast } = manager.create(
|
|
39706
|
-
args,
|
|
39707
|
-
creatorPrincipalId,
|
|
39708
|
-
creatorOwnerPrincipalId
|
|
39709
|
-
);
|
|
40135
|
+
const { response, broadcast } = manager.create(args, creatorPrincipalId);
|
|
39710
40136
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39711
40137
|
};
|
|
39712
40138
|
const list = async (_frame, _client, ctx) => {
|
|
39713
40139
|
const { response } = manager.list();
|
|
39714
40140
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39715
40141
|
const sessions = response.sessions ?? [];
|
|
39716
|
-
const
|
|
39717
|
-
const
|
|
39718
|
-
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39719
|
-
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === capId
|
|
39720
|
-
);
|
|
40142
|
+
const guestDeviceId = ctx.principal.id;
|
|
40143
|
+
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter((s) => s.creatorPrincipalId === guestDeviceId);
|
|
39721
40144
|
return { response: { type: "session:list", sessions: filtered } };
|
|
39722
40145
|
}
|
|
39723
40146
|
return { response: { type: "session:list", ...response } };
|
|
@@ -40005,7 +40428,7 @@ function buildPermissionHandlers(deps) {
|
|
|
40005
40428
|
}
|
|
40006
40429
|
|
|
40007
40430
|
// src/handlers/history.ts
|
|
40008
|
-
var
|
|
40431
|
+
var path42 = __toESM(require("path"), 1);
|
|
40009
40432
|
init_protocol();
|
|
40010
40433
|
|
|
40011
40434
|
// src/session/recent-dirs.ts
|
|
@@ -40023,7 +40446,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
40023
40446
|
}
|
|
40024
40447
|
|
|
40025
40448
|
// src/permission/persona-paths.ts
|
|
40026
|
-
var
|
|
40449
|
+
var path41 = __toESM(require("path"), 1);
|
|
40027
40450
|
function getAllowedPersonaIds(grants, action) {
|
|
40028
40451
|
const ids = /* @__PURE__ */ new Set();
|
|
40029
40452
|
for (const g2 of grants) {
|
|
@@ -40036,26 +40459,26 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
40036
40459
|
return ids;
|
|
40037
40460
|
}
|
|
40038
40461
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
40039
|
-
const root =
|
|
40040
|
-
const target =
|
|
40041
|
-
const sep3 = root.endsWith(
|
|
40462
|
+
const root = path41.resolve(personaRoot);
|
|
40463
|
+
const target = path41.resolve(absPath);
|
|
40464
|
+
const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
40042
40465
|
if (!target.startsWith(root + sep3)) return false;
|
|
40043
|
-
const rel =
|
|
40466
|
+
const rel = path41.relative(root, target);
|
|
40044
40467
|
if (!rel || rel.startsWith("..")) return false;
|
|
40045
|
-
const personaId = rel.split(
|
|
40468
|
+
const personaId = rel.split(path41.sep)[0];
|
|
40046
40469
|
if (!personaId) return false;
|
|
40047
40470
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
40048
40471
|
if (allowed === "*") return true;
|
|
40049
40472
|
return allowed.has(personaId);
|
|
40050
40473
|
}
|
|
40051
40474
|
function personaIdFromPath(absPath, personaRoot) {
|
|
40052
|
-
const root =
|
|
40053
|
-
const target =
|
|
40054
|
-
const sep3 = root.endsWith(
|
|
40475
|
+
const root = path41.resolve(personaRoot);
|
|
40476
|
+
const target = path41.resolve(absPath);
|
|
40477
|
+
const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
40055
40478
|
if (!target.startsWith(root + sep3)) return null;
|
|
40056
|
-
const rel =
|
|
40479
|
+
const rel = path41.relative(root, target);
|
|
40057
40480
|
if (!rel || rel.startsWith("..")) return null;
|
|
40058
|
-
const id = rel.split(
|
|
40481
|
+
const id = rel.split(path41.sep)[0];
|
|
40059
40482
|
return id || null;
|
|
40060
40483
|
}
|
|
40061
40484
|
|
|
@@ -40080,7 +40503,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40080
40503
|
if (!pid) return false;
|
|
40081
40504
|
return isGuestPathAllowed(
|
|
40082
40505
|
ctx.grants,
|
|
40083
|
-
|
|
40506
|
+
path42.join(personaRoot, pid),
|
|
40084
40507
|
personaRoot,
|
|
40085
40508
|
"read"
|
|
40086
40509
|
);
|
|
@@ -40091,7 +40514,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40091
40514
|
};
|
|
40092
40515
|
const list = async (frame, _client, ctx) => {
|
|
40093
40516
|
const args = HistoryListArgs.parse(frame);
|
|
40094
|
-
assertGuestPath(ctx,
|
|
40517
|
+
assertGuestPath(ctx, path42.resolve(args.projectPath), personaRoot, "history:list");
|
|
40095
40518
|
const sessions = await history.listSessions(args);
|
|
40096
40519
|
return { response: { type: "history:list", sessions } };
|
|
40097
40520
|
};
|
|
@@ -40100,7 +40523,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40100
40523
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
40101
40524
|
const f = file;
|
|
40102
40525
|
if (ctx && ctx.principal.kind === "guest") {
|
|
40103
|
-
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.
|
|
40526
|
+
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.principal.id;
|
|
40104
40527
|
if (!ok) {
|
|
40105
40528
|
throw new ClawdError(
|
|
40106
40529
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -40123,13 +40546,13 @@ function buildHistoryHandlers(deps) {
|
|
|
40123
40546
|
};
|
|
40124
40547
|
const subagents = async (frame, _client, ctx) => {
|
|
40125
40548
|
const args = HistorySubagentsArgs.parse(frame);
|
|
40126
|
-
assertGuestPath(ctx,
|
|
40549
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
|
|
40127
40550
|
const subs = await history.listSubagents(args);
|
|
40128
40551
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
40129
40552
|
};
|
|
40130
40553
|
const subagentRead = async (frame, _client, ctx) => {
|
|
40131
40554
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
40132
|
-
assertGuestPath(ctx,
|
|
40555
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
|
|
40133
40556
|
const res = await history.readSubagent(args);
|
|
40134
40557
|
return { response: { type: "history:subagent-read", ...res } };
|
|
40135
40558
|
};
|
|
@@ -40137,7 +40560,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40137
40560
|
const dirs = listRecentDirs(store);
|
|
40138
40561
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40139
40562
|
const filtered = dirs.filter(
|
|
40140
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
40563
|
+
(d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
|
|
40141
40564
|
);
|
|
40142
40565
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
40143
40566
|
}
|
|
@@ -40154,7 +40577,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40154
40577
|
}
|
|
40155
40578
|
|
|
40156
40579
|
// src/handlers/workspace.ts
|
|
40157
|
-
var
|
|
40580
|
+
var path43 = __toESM(require("path"), 1);
|
|
40158
40581
|
var os15 = __toESM(require("os"), 1);
|
|
40159
40582
|
init_protocol();
|
|
40160
40583
|
init_protocol();
|
|
@@ -40195,22 +40618,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40195
40618
|
const args = WorkspaceListArgs.parse(frame);
|
|
40196
40619
|
const isGuest = ctx?.principal.kind === "guest";
|
|
40197
40620
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
|
|
40198
|
-
const resolvedCwd =
|
|
40199
|
-
const target = args.path ?
|
|
40621
|
+
const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
|
|
40622
|
+
const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
40200
40623
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list");
|
|
40201
40624
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
40202
40625
|
return { response: { type: "workspace:list", ...res } };
|
|
40203
40626
|
};
|
|
40204
40627
|
const read = async (frame, _client, ctx) => {
|
|
40205
40628
|
const args = WorkspaceReadArgs.parse(frame);
|
|
40206
|
-
const target =
|
|
40629
|
+
const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
|
|
40207
40630
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read");
|
|
40208
40631
|
const res = workspace.read(args);
|
|
40209
40632
|
return { response: { type: "workspace:read", ...res } };
|
|
40210
40633
|
};
|
|
40211
40634
|
const skillsList = async (frame, _client, ctx) => {
|
|
40212
40635
|
const args = SkillsListArgs.parse(frame);
|
|
40213
|
-
const cwdAbs =
|
|
40636
|
+
const cwdAbs = path43.resolve(args.cwd);
|
|
40214
40637
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
|
|
40215
40638
|
const list2 = skills.list(args);
|
|
40216
40639
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40222,7 +40645,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40222
40645
|
};
|
|
40223
40646
|
const agentsList = async (frame, _client, ctx) => {
|
|
40224
40647
|
const args = AgentsListArgs.parse(frame);
|
|
40225
|
-
const cwdAbs =
|
|
40648
|
+
const cwdAbs = path43.resolve(args.cwd);
|
|
40226
40649
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
|
|
40227
40650
|
const list2 = agents.list(args);
|
|
40228
40651
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40241,7 +40664,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40241
40664
|
}
|
|
40242
40665
|
|
|
40243
40666
|
// src/handlers/git.ts
|
|
40244
|
-
var
|
|
40667
|
+
var path45 = __toESM(require("path"), 1);
|
|
40245
40668
|
init_protocol();
|
|
40246
40669
|
init_protocol();
|
|
40247
40670
|
|
|
@@ -40327,7 +40750,7 @@ async function listGitBranches(cwd) {
|
|
|
40327
40750
|
// src/handlers/git.ts
|
|
40328
40751
|
function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
40329
40752
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40330
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
40753
|
+
if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
|
|
40331
40754
|
throw new ClawdError(
|
|
40332
40755
|
ERROR_CODES.UNAUTHORIZED,
|
|
40333
40756
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -40382,7 +40805,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40382
40805
|
capabilityId: external_exports.string().min(1)
|
|
40383
40806
|
}).strict();
|
|
40384
40807
|
function buildCapabilityHandlers(deps) {
|
|
40385
|
-
const { manager
|
|
40808
|
+
const { manager } = deps;
|
|
40386
40809
|
const list = async () => {
|
|
40387
40810
|
return {
|
|
40388
40811
|
response: {
|
|
@@ -40408,70 +40831,45 @@ function buildCapabilityHandlers(deps) {
|
|
|
40408
40831
|
}
|
|
40409
40832
|
};
|
|
40410
40833
|
};
|
|
40411
|
-
const personalCapGet = async () => {
|
|
40412
|
-
const { token, capability } = getPersonalCapability();
|
|
40413
|
-
return {
|
|
40414
|
-
response: {
|
|
40415
|
-
type: "personal-cap:get:ok",
|
|
40416
|
-
token,
|
|
40417
|
-
capability: stripSecretHash(capability),
|
|
40418
|
-
shareBaseUrl: getShareBaseUrl()
|
|
40419
|
-
}
|
|
40420
|
-
};
|
|
40421
|
-
};
|
|
40422
40834
|
return {
|
|
40423
40835
|
"capability:list": list,
|
|
40424
|
-
"capability:delete": del
|
|
40425
|
-
"personal-cap:get": personalCapGet
|
|
40836
|
+
"capability:delete": del
|
|
40426
40837
|
};
|
|
40427
40838
|
}
|
|
40428
40839
|
|
|
40429
40840
|
// src/handlers/inbox.ts
|
|
40430
40841
|
init_protocol();
|
|
40431
|
-
function
|
|
40842
|
+
function resolvePeerDeviceId(ctx, argsPeerDeviceId) {
|
|
40432
40843
|
if (ctx.principal.kind === "owner") {
|
|
40433
|
-
if (!
|
|
40844
|
+
if (!argsPeerDeviceId) {
|
|
40434
40845
|
throw new ClawdError(
|
|
40435
40846
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40436
|
-
"inbox: owner ctx requires
|
|
40847
|
+
"inbox: owner ctx requires peerDeviceId"
|
|
40437
40848
|
);
|
|
40438
40849
|
}
|
|
40439
|
-
return
|
|
40440
|
-
}
|
|
40441
|
-
if (!ctx.capabilityId) {
|
|
40442
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
40443
|
-
}
|
|
40444
|
-
let resolved = ctx.peerOwnerPrincipalId;
|
|
40445
|
-
if (!resolved) {
|
|
40446
|
-
const cap = capManager.findById(ctx.capabilityId);
|
|
40447
|
-
resolved = cap?.peerOwnerId;
|
|
40850
|
+
return argsPeerDeviceId;
|
|
40448
40851
|
}
|
|
40449
|
-
|
|
40852
|
+
const resolved = ctx.principal.id;
|
|
40853
|
+
if (argsPeerDeviceId && argsPeerDeviceId !== resolved) {
|
|
40450
40854
|
throw new ClawdError(
|
|
40451
40855
|
ERROR_CODES.UNAUTHORIZED,
|
|
40452
|
-
`inbox: guest
|
|
40453
|
-
);
|
|
40454
|
-
}
|
|
40455
|
-
if (argsPeerOwnerId && argsPeerOwnerId !== resolved) {
|
|
40456
|
-
throw new ClawdError(
|
|
40457
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40458
|
-
`inbox: guest args.peerOwnerId mismatch (resolved=${resolved}, args=${argsPeerOwnerId})`
|
|
40856
|
+
`inbox: guest args.peerDeviceId mismatch (resolved=${resolved}, args=${argsPeerDeviceId})`
|
|
40459
40857
|
);
|
|
40460
40858
|
}
|
|
40461
40859
|
return resolved;
|
|
40462
40860
|
}
|
|
40463
40861
|
function buildInboxHandlers(deps) {
|
|
40464
|
-
const { manager
|
|
40862
|
+
const { manager } = deps;
|
|
40465
40863
|
const postMessage = async (frame, _client, ctx) => {
|
|
40466
40864
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40467
40865
|
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
40468
40866
|
if (!ctx) {
|
|
40469
40867
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40470
40868
|
}
|
|
40471
|
-
const
|
|
40869
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40472
40870
|
const message = manager.postMessage({
|
|
40473
|
-
|
|
40474
|
-
|
|
40871
|
+
peerDeviceId,
|
|
40872
|
+
senderDeviceId: ctx.principal.id,
|
|
40475
40873
|
text: args.text,
|
|
40476
40874
|
id: args.id,
|
|
40477
40875
|
createdAt: args.createdAt
|
|
@@ -40486,10 +40884,10 @@ function buildInboxHandlers(deps) {
|
|
|
40486
40884
|
if (!ctx) {
|
|
40487
40885
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40488
40886
|
}
|
|
40489
|
-
const
|
|
40490
|
-
const messages = manager.list(
|
|
40887
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40888
|
+
const messages = manager.list(peerDeviceId, args.sinceCreatedAt);
|
|
40491
40889
|
return {
|
|
40492
|
-
response: { type: "inbox:list:ok",
|
|
40890
|
+
response: { type: "inbox:list:ok", peerDeviceId, messages }
|
|
40493
40891
|
};
|
|
40494
40892
|
};
|
|
40495
40893
|
const markRead = async (frame, _client, ctx) => {
|
|
@@ -40498,16 +40896,16 @@ function buildInboxHandlers(deps) {
|
|
|
40498
40896
|
if (!ctx) {
|
|
40499
40897
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40500
40898
|
}
|
|
40501
|
-
const
|
|
40899
|
+
const peerDeviceId = resolvePeerDeviceId(ctx, args.peerDeviceId);
|
|
40502
40900
|
const updated = manager.markRead({
|
|
40503
|
-
|
|
40504
|
-
|
|
40901
|
+
peerDeviceId,
|
|
40902
|
+
readerDeviceId: ctx.principal.id,
|
|
40505
40903
|
upToCreatedAt: args.upToCreatedAt
|
|
40506
40904
|
});
|
|
40507
40905
|
return {
|
|
40508
40906
|
response: {
|
|
40509
40907
|
type: "inbox:markRead:ok",
|
|
40510
|
-
|
|
40908
|
+
peerDeviceId,
|
|
40511
40909
|
upToCreatedAt: args.upToCreatedAt,
|
|
40512
40910
|
updated
|
|
40513
40911
|
}
|
|
@@ -40520,116 +40918,45 @@ function buildInboxHandlers(deps) {
|
|
|
40520
40918
|
};
|
|
40521
40919
|
}
|
|
40522
40920
|
|
|
40523
|
-
// src/handlers/
|
|
40921
|
+
// src/handlers/contact.ts
|
|
40524
40922
|
init_protocol();
|
|
40525
40923
|
function ensureOwner(ctx) {
|
|
40526
40924
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40527
40925
|
throw new ClawdError(
|
|
40528
40926
|
ERROR_CODES.UNAUTHORIZED,
|
|
40529
|
-
"UNAUTHORIZED:
|
|
40927
|
+
"UNAUTHORIZED: contact:* requires owner ctx"
|
|
40530
40928
|
);
|
|
40531
40929
|
}
|
|
40532
40930
|
}
|
|
40533
|
-
function
|
|
40534
|
-
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40535
|
-
throw new ClawdError(
|
|
40536
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40537
|
-
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
40538
|
-
);
|
|
40539
|
-
}
|
|
40540
|
-
}
|
|
40541
|
-
function buildReceivedCapabilityHandlers(deps) {
|
|
40542
|
-
const now = deps.now ?? Date.now;
|
|
40931
|
+
function buildContactHandlers(deps) {
|
|
40543
40932
|
const list = async (_frame, _client, ctx) => {
|
|
40544
40933
|
ensureOwner(ctx);
|
|
40545
40934
|
return {
|
|
40546
40935
|
response: {
|
|
40547
|
-
type: "
|
|
40548
|
-
|
|
40936
|
+
type: "contact:list:ok",
|
|
40937
|
+
contacts: deps.store.list()
|
|
40549
40938
|
}
|
|
40550
40939
|
};
|
|
40551
40940
|
};
|
|
40552
|
-
const add = async (frame, _client, ctx) => {
|
|
40553
|
-
ensureOwner(ctx);
|
|
40554
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40555
|
-
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40556
|
-
let conn;
|
|
40557
|
-
try {
|
|
40558
|
-
conn = await deps.connectRemote({
|
|
40559
|
-
url: args.remoteUrl,
|
|
40560
|
-
token: args.token,
|
|
40561
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
40562
|
-
selfDisplayName: deps.selfDisplayName()
|
|
40563
|
-
});
|
|
40564
|
-
} catch (e) {
|
|
40565
|
-
throw new ClawdError(
|
|
40566
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40567
|
-
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40568
|
-
);
|
|
40569
|
-
}
|
|
40570
|
-
try {
|
|
40571
|
-
const wh = await conn.whoami();
|
|
40572
|
-
const rc = {
|
|
40573
|
-
ownerPrincipalId: wh.ownerId,
|
|
40574
|
-
ownerDisplayName: wh.displayName,
|
|
40575
|
-
remoteUrl: args.remoteUrl,
|
|
40576
|
-
capabilityId: wh.capabilityId,
|
|
40577
|
-
capabilityToken: args.token,
|
|
40578
|
-
grants: wh.grants,
|
|
40579
|
-
receivedAt: now()
|
|
40580
|
-
};
|
|
40581
|
-
deps.store.upsert(rc);
|
|
40582
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40583
|
-
return {
|
|
40584
|
-
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40585
|
-
};
|
|
40586
|
-
} finally {
|
|
40587
|
-
try {
|
|
40588
|
-
conn.close();
|
|
40589
|
-
} catch {
|
|
40590
|
-
}
|
|
40591
|
-
}
|
|
40592
|
-
};
|
|
40593
40941
|
const remove = async (frame, _client, ctx) => {
|
|
40594
40942
|
ensureOwner(ctx);
|
|
40595
40943
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40596
|
-
const args =
|
|
40597
|
-
deps.store.
|
|
40944
|
+
const args = ContactRemoveArgsSchema.parse(rest);
|
|
40945
|
+
deps.store.removeByDeviceId(args.deviceId);
|
|
40598
40946
|
deps.broadcast({
|
|
40599
|
-
type: "
|
|
40600
|
-
|
|
40947
|
+
type: "contact:removed",
|
|
40948
|
+
deviceId: args.deviceId
|
|
40601
40949
|
});
|
|
40602
40950
|
return {
|
|
40603
40951
|
response: {
|
|
40604
|
-
type: "
|
|
40605
|
-
|
|
40952
|
+
type: "contact:remove:ok",
|
|
40953
|
+
deviceId: args.deviceId
|
|
40606
40954
|
}
|
|
40607
40955
|
};
|
|
40608
40956
|
};
|
|
40609
|
-
const autoAttach = async (frame, _client, ctx) => {
|
|
40610
|
-
ensureGuestCtx(ctx);
|
|
40611
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40612
|
-
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40613
|
-
const rc = {
|
|
40614
|
-
ownerPrincipalId: args.ownerPrincipalId,
|
|
40615
|
-
ownerDisplayName: args.ownerDisplayName,
|
|
40616
|
-
remoteUrl: args.remoteUrl,
|
|
40617
|
-
capabilityId: args.capabilityId,
|
|
40618
|
-
capabilityToken: args.token,
|
|
40619
|
-
grants: args.grants,
|
|
40620
|
-
receivedAt: now()
|
|
40621
|
-
};
|
|
40622
|
-
deps.store.upsert(rc);
|
|
40623
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40624
|
-
return {
|
|
40625
|
-
response: { type: "received-capability:autoAttach:ok" }
|
|
40626
|
-
};
|
|
40627
|
-
};
|
|
40628
40957
|
return {
|
|
40629
|
-
"
|
|
40630
|
-
"
|
|
40631
|
-
"received-capability:remove": remove,
|
|
40632
|
-
"received-capability:autoAttach": autoAttach
|
|
40958
|
+
"contact:list": list,
|
|
40959
|
+
"contact:remove": remove
|
|
40633
40960
|
};
|
|
40634
40961
|
}
|
|
40635
40962
|
|
|
@@ -40640,7 +40967,11 @@ function buildWhoamiHandler(deps) {
|
|
|
40640
40967
|
if (!ctx) {
|
|
40641
40968
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
40642
40969
|
}
|
|
40643
|
-
const owner =
|
|
40970
|
+
const owner = {
|
|
40971
|
+
...makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName),
|
|
40972
|
+
ownerId: deps.ownerId,
|
|
40973
|
+
provider: deps.ownerProvider
|
|
40974
|
+
};
|
|
40644
40975
|
let capability;
|
|
40645
40976
|
if (ctx.principal.kind === "owner") {
|
|
40646
40977
|
capability = {
|
|
@@ -40651,14 +40982,13 @@ function buildWhoamiHandler(deps) {
|
|
|
40651
40982
|
usedCount: 0
|
|
40652
40983
|
};
|
|
40653
40984
|
} else {
|
|
40654
|
-
|
|
40655
|
-
|
|
40656
|
-
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
40660
|
-
}
|
|
40661
|
-
capability = stripSecretHash(cap);
|
|
40985
|
+
capability = {
|
|
40986
|
+
id: ctx.principal.id,
|
|
40987
|
+
displayName: ctx.principal.displayName ?? "guest",
|
|
40988
|
+
grants: ctx.grants,
|
|
40989
|
+
issuedAt: 0,
|
|
40990
|
+
usedCount: 0
|
|
40991
|
+
};
|
|
40662
40992
|
}
|
|
40663
40993
|
const grantedPersonas = [];
|
|
40664
40994
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -40689,6 +41019,123 @@ function buildWhoamiHandler(deps) {
|
|
|
40689
41019
|
};
|
|
40690
41020
|
}
|
|
40691
41021
|
|
|
41022
|
+
// src/handlers/feishu-auth.ts
|
|
41023
|
+
function buildFeishuAuthHandlers(deps) {
|
|
41024
|
+
const loginStart = async () => {
|
|
41025
|
+
const { authUrl, state } = deps.loginFlow.start();
|
|
41026
|
+
return {
|
|
41027
|
+
response: { type: "auth:login:start:ok", authUrl, state }
|
|
41028
|
+
};
|
|
41029
|
+
};
|
|
41030
|
+
const getIdentity = async () => {
|
|
41031
|
+
const record = deps.ownerIdentityStore.read();
|
|
41032
|
+
return {
|
|
41033
|
+
response: {
|
|
41034
|
+
type: "auth:getIdentity:ok",
|
|
41035
|
+
identity: record ? record.identity : null,
|
|
41036
|
+
// 决策 #15:设备属性,与登录态无关——UI"分享我的 Hub 标识"用
|
|
41037
|
+
deviceId: deps.deviceId
|
|
41038
|
+
}
|
|
41039
|
+
};
|
|
41040
|
+
};
|
|
41041
|
+
const logout = async () => {
|
|
41042
|
+
deps.ownerIdentityStore.clear();
|
|
41043
|
+
return {
|
|
41044
|
+
response: { type: "auth:logout:ok" }
|
|
41045
|
+
};
|
|
41046
|
+
};
|
|
41047
|
+
return {
|
|
41048
|
+
"auth:login:start": loginStart,
|
|
41049
|
+
"auth:getIdentity": getIdentity,
|
|
41050
|
+
"auth:logout": logout
|
|
41051
|
+
};
|
|
41052
|
+
}
|
|
41053
|
+
|
|
41054
|
+
// src/handlers/device.ts
|
|
41055
|
+
init_protocol();
|
|
41056
|
+
function ensureOwner2(ctx) {
|
|
41057
|
+
if (!ctx || ctx.principal.kind !== "owner") {
|
|
41058
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
41059
|
+
}
|
|
41060
|
+
}
|
|
41061
|
+
function buildDeviceHandlers(deps) {
|
|
41062
|
+
const now = deps.now ?? Date.now;
|
|
41063
|
+
const list = async (_frame, _client, ctx) => {
|
|
41064
|
+
ensureOwner2(ctx);
|
|
41065
|
+
const devices = await deps.listDevices();
|
|
41066
|
+
return {
|
|
41067
|
+
response: { type: "device:list:ok", devices }
|
|
41068
|
+
};
|
|
41069
|
+
};
|
|
41070
|
+
const connect = async (frame, _client, ctx) => {
|
|
41071
|
+
ensureOwner2(ctx);
|
|
41072
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
41073
|
+
const args = DeviceConnectArgsSchema.parse(rest);
|
|
41074
|
+
const exchanged = await deps.exchange(args.deviceId);
|
|
41075
|
+
const url = args.url ?? exchanged.deviceUrl;
|
|
41076
|
+
if (!url) {
|
|
41077
|
+
throw new ClawdError(
|
|
41078
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
41079
|
+
"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"
|
|
41080
|
+
);
|
|
41081
|
+
}
|
|
41082
|
+
let conn;
|
|
41083
|
+
try {
|
|
41084
|
+
conn = await deps.connectRemote({
|
|
41085
|
+
url,
|
|
41086
|
+
token: exchanged.token,
|
|
41087
|
+
// 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
|
|
41088
|
+
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
|
|
41089
|
+
// 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
|
|
41090
|
+
selfUrl: deps.selfUrl() ?? void 0
|
|
41091
|
+
});
|
|
41092
|
+
} catch (e) {
|
|
41093
|
+
throw new ClawdError(
|
|
41094
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
41095
|
+
`DEVICE_UNREACHABLE: ${e.message}`
|
|
41096
|
+
);
|
|
41097
|
+
}
|
|
41098
|
+
try {
|
|
41099
|
+
const wh = await conn.whoami();
|
|
41100
|
+
const contact = {
|
|
41101
|
+
deviceId: args.deviceId,
|
|
41102
|
+
ownerId: wh.ownerId,
|
|
41103
|
+
provider: wh.provider,
|
|
41104
|
+
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
41105
|
+
// 如"公共1");"输入标识"连接不带 name → 回落 whoami 报的对方 owner 真名。
|
|
41106
|
+
// 归属人身份不丢(ownerId/provider 仍来自 whoami 背书)。
|
|
41107
|
+
displayName: args.name || wh.displayName || args.deviceId,
|
|
41108
|
+
remoteUrl: url,
|
|
41109
|
+
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
41110
|
+
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
41111
|
+
connectToken: exchanged.token,
|
|
41112
|
+
grants: wh.grants,
|
|
41113
|
+
addedAt: now()
|
|
41114
|
+
};
|
|
41115
|
+
deps.store.upsert(contact);
|
|
41116
|
+
deps.broadcast({ type: "contact:added", contact });
|
|
41117
|
+
return {
|
|
41118
|
+
response: {
|
|
41119
|
+
type: "device:connect:ok",
|
|
41120
|
+
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
41121
|
+
deviceId: args.deviceId,
|
|
41122
|
+
name: contact.displayName,
|
|
41123
|
+
url
|
|
41124
|
+
}
|
|
41125
|
+
};
|
|
41126
|
+
} finally {
|
|
41127
|
+
try {
|
|
41128
|
+
conn.close();
|
|
41129
|
+
} catch {
|
|
41130
|
+
}
|
|
41131
|
+
}
|
|
41132
|
+
};
|
|
41133
|
+
return {
|
|
41134
|
+
"device:list": list,
|
|
41135
|
+
"device:connect": connect
|
|
41136
|
+
};
|
|
41137
|
+
}
|
|
41138
|
+
|
|
40692
41139
|
// src/handlers/meta.ts
|
|
40693
41140
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
40694
41141
|
init_protocol();
|
|
@@ -41904,14 +42351,12 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41904
42351
|
`session ${f.sessionId} is not bound to any project; cannot stop dev server`
|
|
41905
42352
|
);
|
|
41906
42353
|
}
|
|
41907
|
-
|
|
41908
|
-
name: projectName
|
|
41909
|
-
|
|
41910
|
-
isRunning: devServerLifecycle.isRunning(projectName)
|
|
41911
|
-
});
|
|
41912
|
-
if (devServerLifecycle.isRunning(projectName)) {
|
|
41913
|
-
await devServerLifecycle.stopForProject(projectName);
|
|
42354
|
+
if (!devServerLifecycle.isRunning(projectName)) {
|
|
42355
|
+
deps.logger?.info("app-builder.stop-dev-server.already-stopped", { name: projectName });
|
|
42356
|
+
return { response: { ok: true } };
|
|
41914
42357
|
}
|
|
42358
|
+
deps.logger?.info("app-builder.stop-dev-server.called", { name: projectName, sessionId: f.sessionId });
|
|
42359
|
+
await devServerLifecycle.stopForProject(projectName);
|
|
41915
42360
|
await setStageAndBroadcast(projectName, "stopped");
|
|
41916
42361
|
return { response: { ok: true } };
|
|
41917
42362
|
};
|
|
@@ -42033,27 +42478,25 @@ function buildMethodHandlers(deps) {
|
|
|
42033
42478
|
personaRegistry: deps.personaRegistry
|
|
42034
42479
|
}),
|
|
42035
42480
|
...buildCapabilityHandlers({
|
|
42036
|
-
manager: deps.capabilityManager
|
|
42037
|
-
getShareBaseUrl: deps.getShareBaseUrl,
|
|
42038
|
-
getPersonalCapability: deps.getPersonalCapability
|
|
42481
|
+
manager: deps.capabilityManager
|
|
42039
42482
|
}),
|
|
42040
42483
|
...buildInboxHandlers({
|
|
42041
|
-
manager: deps.inboxManager
|
|
42042
|
-
capManager: deps.capabilityManager
|
|
42484
|
+
manager: deps.inboxManager
|
|
42043
42485
|
}),
|
|
42044
|
-
...
|
|
42045
|
-
store: deps.
|
|
42046
|
-
|
|
42047
|
-
broadcast: deps.broadcastToOwners,
|
|
42048
|
-
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
42049
|
-
selfDisplayName: () => deps.ownerDisplayName
|
|
42486
|
+
...buildContactHandlers({
|
|
42487
|
+
store: deps.contactStore,
|
|
42488
|
+
broadcast: deps.broadcastToOwners
|
|
42050
42489
|
}),
|
|
42051
42490
|
whoami: buildWhoamiHandler({
|
|
42052
42491
|
ownerDisplayName: deps.ownerDisplayName,
|
|
42053
42492
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
42493
|
+
ownerId: deps.ownerId,
|
|
42494
|
+
ownerProvider: deps.ownerProvider,
|
|
42054
42495
|
personaStore: deps.personaStore,
|
|
42055
42496
|
capabilityManager: deps.capabilityManager
|
|
42056
42497
|
}),
|
|
42498
|
+
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
42499
|
+
...buildDeviceHandlers(deps.feishuDevice),
|
|
42057
42500
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
42058
42501
|
...buildExtensionHandlers({
|
|
42059
42502
|
loadAll,
|
|
@@ -42500,23 +42943,22 @@ var METHOD_GRANT_MAP = {
|
|
|
42500
42943
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
42501
42944
|
"whoami": { kind: "public" },
|
|
42502
42945
|
// ---- capability platform(admin-only) ----
|
|
42503
|
-
//
|
|
42504
|
-
// personal
|
|
42946
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal-cap:get / capability:issue /
|
|
42947
|
+
// bindPeer 已下线(personal token / 邀请码全链路删除)。capability:list / delete 保留供调试 +
|
|
42948
|
+
// 撤销级联 + 清旧 per-peer cap。
|
|
42505
42949
|
"capability:list": ADMIN_ANY,
|
|
42506
42950
|
"capability:delete": ADMIN_ANY,
|
|
42507
|
-
|
|
42508
|
-
//
|
|
42509
|
-
//
|
|
42510
|
-
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
42951
|
+
// ---- inbox: 双投 P2P IM ----
|
|
42952
|
+
// 三条都 'public' — 能连上 = 已通过本地验票. handler 内额外校验 guest 的
|
|
42953
|
+
// args.peerDeviceId 与 ctx.principal.id(=deviceId) 一致, 防 guest 越权操作别的 DM 桶.
|
|
42511
42954
|
"inbox:list": { kind: "public" },
|
|
42512
42955
|
"inbox:markRead": { kind: "public" },
|
|
42513
42956
|
"inbox:postMessage": { kind: "public" },
|
|
42514
|
-
// ----
|
|
42515
|
-
//
|
|
42516
|
-
|
|
42517
|
-
"
|
|
42518
|
-
"
|
|
42519
|
-
"received-capability:autoAttach": { kind: "public" },
|
|
42957
|
+
// ---- contact:* (联系人列表) ----
|
|
42958
|
+
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名 contact:*。
|
|
42959
|
+
// 写入路径在 device:connect / 自动反向;list / remove 作联系人读 / 删,owner-only。
|
|
42960
|
+
"contact:list": ADMIN_ANY,
|
|
42961
|
+
"contact:remove": ADMIN_ANY,
|
|
42520
42962
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
42521
42963
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
42522
42964
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -42557,7 +42999,7 @@ var METHOD_GRANT_MAP = {
|
|
|
42557
42999
|
"history:list": CAPABILITY_SCOPED,
|
|
42558
43000
|
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
42559
43001
|
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
42560
|
-
// handler 内按 ctx.
|
|
43002
|
+
// handler 内按 ctx.principal.id === file.creatorPrincipalId 拦越权。
|
|
42561
43003
|
"history:read": CAPABILITY_SCOPED,
|
|
42562
43004
|
"history:subagents": CAPABILITY_SCOPED,
|
|
42563
43005
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
@@ -42614,6 +43056,15 @@ var METHOD_GRANT_MAP = {
|
|
|
42614
43056
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
42615
43057
|
"extension.install-from-channel": ADMIN_ANY,
|
|
42616
43058
|
"extension.update-from-channel": ADMIN_ANY,
|
|
43059
|
+
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
43060
|
+
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
43061
|
+
"auth:login:start": ADMIN_ANY,
|
|
43062
|
+
"auth:getIdentity": ADMIN_ANY,
|
|
43063
|
+
"auth:logout": ADMIN_ANY,
|
|
43064
|
+
// ---- device:* 设备目录 + 连接(决策 #15,owner-only) ----
|
|
43065
|
+
// 设备目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
43066
|
+
"device:list": ADMIN_ANY,
|
|
43067
|
+
"device:connect": ADMIN_ANY,
|
|
42617
43068
|
// ---- app-builder Project picker(spec 2026-06-01-app-builder-project-picker-design) ----
|
|
42618
43069
|
// owner-only:picker UI 给 owner 管理本机 build 中的 project(fs + 端口分配)。
|
|
42619
43070
|
// 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
|
|
@@ -43020,19 +43471,23 @@ async function startDaemon(config) {
|
|
|
43020
43471
|
} else if (config.tunnel) {
|
|
43021
43472
|
resolvedAuthToken = authFile.token;
|
|
43022
43473
|
}
|
|
43023
|
-
const
|
|
43024
|
-
const
|
|
43474
|
+
const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
|
|
43475
|
+
const feishuIdentity = ownerIdentityStore.read();
|
|
43476
|
+
let feishuActive = feishuIdentity !== null;
|
|
43477
|
+
const ownerPrincipalId = authFile.deviceId;
|
|
43478
|
+
let ownerId = feishuIdentity?.identity.ownerId ?? "";
|
|
43479
|
+
let ownerProvider = feishuIdentity?.identity.provider ?? "";
|
|
43480
|
+
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
43025
43481
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
43026
43482
|
const inboxStore = new InboxStore(config.dataDir);
|
|
43027
|
-
const inboxManager = new InboxManager(inboxStore, (
|
|
43483
|
+
const inboxManager = new InboxManager(inboxStore, (_peerDeviceId, frame) => {
|
|
43028
43484
|
wsServer?.broadcastToOwners(frame);
|
|
43029
43485
|
});
|
|
43030
|
-
const
|
|
43031
|
-
|
|
43486
|
+
const contactStore = new ContactStore(config.dataDir);
|
|
43487
|
+
contactStore.load();
|
|
43488
|
+
const serverKeyStore = new ServerKeyStore(config.dataDir);
|
|
43032
43489
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
43033
43490
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
43034
|
-
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
43035
|
-
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
43036
43491
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
43037
43492
|
onDeleted: (cap) => {
|
|
43038
43493
|
const deletedAt = Date.now();
|
|
@@ -43041,7 +43496,7 @@ async function startDaemon(config) {
|
|
|
43041
43496
|
capabilityId: cap.id,
|
|
43042
43497
|
deletedAt
|
|
43043
43498
|
});
|
|
43044
|
-
wsServer?.
|
|
43499
|
+
wsServer?.closeConnectionsByGuestId(cap.id);
|
|
43045
43500
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
43046
43501
|
if (cleanup.removed.length > 0) {
|
|
43047
43502
|
logger.info("capability delete cascade: guest sessions removed", {
|
|
@@ -43058,15 +43513,60 @@ async function startDaemon(config) {
|
|
|
43058
43513
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
43059
43514
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
43060
43515
|
expectedToken: resolvedAuthToken,
|
|
43061
|
-
authenticate: (t,
|
|
43062
|
-
|
|
43516
|
+
authenticate: async (t, selfUrl) => {
|
|
43517
|
+
const result = await authenticate(t, {
|
|
43063
43518
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
43064
43519
|
ownerPrincipalId,
|
|
43065
43520
|
ownerDisplayName,
|
|
43066
|
-
|
|
43067
|
-
|
|
43068
|
-
|
|
43521
|
+
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入验签函数
|
|
43522
|
+
// → 一律 BAD_TOKEN)。已激活时用缓存的 server 公钥本地验签;
|
|
43523
|
+
// 公钥缓存缺失(登录后拉取失败)→ fail-closed 拒绝 guest。
|
|
43524
|
+
...feishuActive ? {
|
|
43525
|
+
verifyConnectToken: (token) => {
|
|
43526
|
+
const publicKeyPem = serverKeyStore.read();
|
|
43527
|
+
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
43528
|
+
return verifyConnectToken({
|
|
43529
|
+
token,
|
|
43530
|
+
publicKeyPem,
|
|
43531
|
+
expectedDeviceId: authFile.deviceId
|
|
43532
|
+
});
|
|
43533
|
+
}
|
|
43534
|
+
} : {}
|
|
43069
43535
|
});
|
|
43536
|
+
if (result.ok && result.context.principal.kind === "guest") {
|
|
43537
|
+
void autoReverseContact({
|
|
43538
|
+
store: contactStore,
|
|
43539
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43540
|
+
deviceId: result.context.principal.id,
|
|
43541
|
+
ownerId: result.context.ownerId ?? "",
|
|
43542
|
+
provider: result.context.provider ?? "",
|
|
43543
|
+
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
43544
|
+
selfUrl,
|
|
43545
|
+
// 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
|
|
43546
|
+
// 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
|
|
43547
|
+
exchangeToken: async (targetDeviceId) => {
|
|
43548
|
+
const record = ownerIdentityStore.read();
|
|
43549
|
+
if (!record) return null;
|
|
43550
|
+
try {
|
|
43551
|
+
const r = await centralExchange({
|
|
43552
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43553
|
+
jwt: record.ttcToken,
|
|
43554
|
+
provider: record.identity.provider,
|
|
43555
|
+
deviceId: targetDeviceId,
|
|
43556
|
+
selfDeviceId: authFile.deviceId
|
|
43557
|
+
});
|
|
43558
|
+
return r.token;
|
|
43559
|
+
} catch (e) {
|
|
43560
|
+
logger.warn("auto-reverse exchange token failed", {
|
|
43561
|
+
targetDeviceId,
|
|
43562
|
+
err: e.message
|
|
43563
|
+
});
|
|
43564
|
+
return null;
|
|
43565
|
+
}
|
|
43566
|
+
}
|
|
43567
|
+
});
|
|
43568
|
+
}
|
|
43569
|
+
return result;
|
|
43070
43570
|
},
|
|
43071
43571
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
43072
43572
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -43221,10 +43721,52 @@ async function startDaemon(config) {
|
|
|
43221
43721
|
}
|
|
43222
43722
|
return `http://${config.host}:${config.port}`;
|
|
43223
43723
|
};
|
|
43724
|
+
const reportDevice = async () => {
|
|
43725
|
+
if (!feishuActive) return;
|
|
43726
|
+
const record = ownerIdentityStore.read();
|
|
43727
|
+
if (!record) return;
|
|
43728
|
+
try {
|
|
43729
|
+
await centralUpsertDevice({
|
|
43730
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43731
|
+
ttcJwt: record.ttcToken,
|
|
43732
|
+
provider: record.identity.provider,
|
|
43733
|
+
deviceId: authFile.deviceId,
|
|
43734
|
+
url: currentTunnelUrl ?? void 0,
|
|
43735
|
+
name: ownerDisplayName
|
|
43736
|
+
});
|
|
43737
|
+
logger.info("device registered to central server", {
|
|
43738
|
+
deviceId: authFile.deviceId,
|
|
43739
|
+
url: currentTunnelUrl ?? "(none)"
|
|
43740
|
+
});
|
|
43741
|
+
} catch (err) {
|
|
43742
|
+
logger.warn("device register failed (best-effort)", { err: err.message });
|
|
43743
|
+
}
|
|
43744
|
+
};
|
|
43745
|
+
const fetchServerKey = async () => {
|
|
43746
|
+
if (!feishuActive) return;
|
|
43747
|
+
if (serverKeyStore.read()) return;
|
|
43748
|
+
try {
|
|
43749
|
+
const { publicKeyPem } = await centralGetPublicKey({
|
|
43750
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API
|
|
43751
|
+
});
|
|
43752
|
+
serverKeyStore.write(publicKeyPem);
|
|
43753
|
+
logger.info("server signing key cached");
|
|
43754
|
+
} catch (err) {
|
|
43755
|
+
logger.warn("server signing key fetch failed (guest \u5165\u7AD9\u5C06\u88AB\u62D2\u7EDD)", {
|
|
43756
|
+
err: err.message
|
|
43757
|
+
});
|
|
43758
|
+
}
|
|
43759
|
+
};
|
|
43224
43760
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
43225
43761
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
43226
43762
|
await publishedChannelStore.load();
|
|
43227
43763
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
43764
|
+
const loginFlow = new LoginFlow({
|
|
43765
|
+
store: ownerIdentityStore,
|
|
43766
|
+
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
43767
|
+
ttcAuthBase: TTC_HOSTS.auth,
|
|
43768
|
+
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
43769
|
+
});
|
|
43228
43770
|
const appBuilderStore = new ProjectStore(
|
|
43229
43771
|
import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
|
|
43230
43772
|
);
|
|
@@ -43235,7 +43777,7 @@ async function startDaemon(config) {
|
|
|
43235
43777
|
const effectivePreviewPorts = Array.from(
|
|
43236
43778
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
43237
43779
|
);
|
|
43238
|
-
const
|
|
43780
|
+
const makeHandlers = () => buildMethodHandlers({
|
|
43239
43781
|
manager,
|
|
43240
43782
|
workspace,
|
|
43241
43783
|
skills,
|
|
@@ -43280,15 +43822,12 @@ async function startDaemon(config) {
|
|
|
43280
43822
|
personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
|
|
43281
43823
|
// capability:list / delete handler 依赖
|
|
43282
43824
|
capabilityManager,
|
|
43283
|
-
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
43284
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
43285
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
43286
|
-
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
43287
|
-
getPersonalCapability: () => personalCapability,
|
|
43288
43825
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
43289
43826
|
ownerDisplayName,
|
|
43290
|
-
//
|
|
43827
|
+
// 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
|
|
43291
43828
|
ownerPrincipalId,
|
|
43829
|
+
ownerId,
|
|
43830
|
+
ownerProvider,
|
|
43292
43831
|
personaStore,
|
|
43293
43832
|
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
43294
43833
|
capabilityRegistry,
|
|
@@ -43296,18 +43835,56 @@ async function startDaemon(config) {
|
|
|
43296
43835
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
43297
43836
|
inboxManager,
|
|
43298
43837
|
inboxStore,
|
|
43299
|
-
//
|
|
43300
|
-
|
|
43301
|
-
//
|
|
43838
|
+
// 联系人列表 store(device:connect / 自动反向落同一 store)
|
|
43839
|
+
contactStore,
|
|
43840
|
+
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
43302
43841
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43303
|
-
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
43304
|
-
connectRemote,
|
|
43305
43842
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
43306
43843
|
// level so stopAll() runs in the shutdown hook below.
|
|
43307
43844
|
extensionRuntime,
|
|
43308
43845
|
// extension sharing v1 — owner-side published-channels store.
|
|
43309
43846
|
publishedChannelStore,
|
|
43310
43847
|
bundleCache,
|
|
43848
|
+
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
43849
|
+
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
43850
|
+
feishuAuth: { loginFlow, ownerIdentityStore, deviceId: authFile.deviceId },
|
|
43851
|
+
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
43852
|
+
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
43853
|
+
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
43854
|
+
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
43855
|
+
feishuDevice: {
|
|
43856
|
+
store: contactStore,
|
|
43857
|
+
exchange: async (deviceId) => {
|
|
43858
|
+
const record = ownerIdentityStore.read();
|
|
43859
|
+
if (!record) {
|
|
43860
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
43861
|
+
}
|
|
43862
|
+
return centralExchange({
|
|
43863
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43864
|
+
jwt: record.ttcToken,
|
|
43865
|
+
provider: record.identity.provider,
|
|
43866
|
+
deviceId,
|
|
43867
|
+
selfDeviceId: authFile.deviceId
|
|
43868
|
+
});
|
|
43869
|
+
},
|
|
43870
|
+
listDevices: async () => {
|
|
43871
|
+
const record = ownerIdentityStore.read();
|
|
43872
|
+
if (!record) {
|
|
43873
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
43874
|
+
}
|
|
43875
|
+
return centralListDevices({
|
|
43876
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43877
|
+
ttcJwt: record.ttcToken,
|
|
43878
|
+
provider: record.identity.provider
|
|
43879
|
+
});
|
|
43880
|
+
},
|
|
43881
|
+
connectRemote,
|
|
43882
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43883
|
+
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
43884
|
+
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
43885
|
+
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
43886
|
+
selfUrl: () => currentTunnelUrl
|
|
43887
|
+
},
|
|
43311
43888
|
// app-builder
|
|
43312
43889
|
appBuilderStore,
|
|
43313
43890
|
devServerLifecycle: devServerSupervisor,
|
|
@@ -43316,6 +43893,7 @@ async function startDaemon(config) {
|
|
|
43316
43893
|
broadcastProjectUpdated: (event) => wsServer?.broadcastToOwners(event),
|
|
43317
43894
|
logger
|
|
43318
43895
|
});
|
|
43896
|
+
let handlers = makeHandlers();
|
|
43319
43897
|
const authResolver = new AuthContextResolver({
|
|
43320
43898
|
ownerToken: resolvedAuthToken
|
|
43321
43899
|
});
|
|
@@ -43353,6 +43931,31 @@ async function startDaemon(config) {
|
|
|
43353
43931
|
},
|
|
43354
43932
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
43355
43933
|
extensionsRoot: () => extensionsRoot(),
|
|
43934
|
+
// 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
|
|
43935
|
+
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
43936
|
+
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
43937
|
+
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
43938
|
+
// People 立即解锁,不需要重启 daemon
|
|
43939
|
+
// 失败则广播 auth:login:failed。
|
|
43940
|
+
feishuLogin: {
|
|
43941
|
+
handleLoginCallback: async (params) => {
|
|
43942
|
+
const result = await loginFlow.handleCallback(params);
|
|
43943
|
+
if (result.ok) {
|
|
43944
|
+
feishuActive = true;
|
|
43945
|
+
ownerId = result.identity.ownerId;
|
|
43946
|
+
ownerProvider = result.identity.provider;
|
|
43947
|
+
ownerDisplayName = result.identity.displayName;
|
|
43948
|
+
handlers = makeHandlers();
|
|
43949
|
+
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
43950
|
+
setImmediate(() => wsServer?.closeAllClients());
|
|
43951
|
+
void reportDevice();
|
|
43952
|
+
void fetchServerKey();
|
|
43953
|
+
} else {
|
|
43954
|
+
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
43955
|
+
}
|
|
43956
|
+
return result;
|
|
43957
|
+
}
|
|
43958
|
+
},
|
|
43356
43959
|
// app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
|
|
43357
43960
|
previewPorts: effectivePreviewPorts
|
|
43358
43961
|
});
|
|
@@ -43360,8 +43963,6 @@ async function startDaemon(config) {
|
|
|
43360
43963
|
host: config.host,
|
|
43361
43964
|
port: config.port,
|
|
43362
43965
|
logger,
|
|
43363
|
-
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
43364
|
-
ownerPrincipalId,
|
|
43365
43966
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
43366
43967
|
{
|
|
43367
43968
|
manager,
|
|
@@ -43381,14 +43982,12 @@ async function startDaemon(config) {
|
|
|
43381
43982
|
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
43382
43983
|
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
43383
43984
|
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
43384
|
-
tryVerifyCapabilityToken: (token
|
|
43985
|
+
tryVerifyCapabilityToken: (token) => {
|
|
43385
43986
|
const v2 = capabilityRegistry.verifyToken(token);
|
|
43386
43987
|
if (!v2.ok) return null;
|
|
43387
43988
|
return {
|
|
43388
43989
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
43389
|
-
grants: v2.capability.grants
|
|
43390
|
-
capabilityId: v2.capability.id,
|
|
43391
|
-
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
43990
|
+
grants: v2.capability.grants
|
|
43392
43991
|
};
|
|
43393
43992
|
},
|
|
43394
43993
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
@@ -43439,6 +44038,12 @@ async function startDaemon(config) {
|
|
|
43439
44038
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
43440
44039
|
const handler = handlers[type];
|
|
43441
44040
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
44041
|
+
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
44042
|
+
throw new ClawdError(
|
|
44043
|
+
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
44044
|
+
`${type} requires feishu login (People/remote identity)`
|
|
44045
|
+
);
|
|
44046
|
+
}
|
|
43442
44047
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
43443
44048
|
const verdict = computeGrantForFrame(type, frame);
|
|
43444
44049
|
if (verdict.kind === "check") {
|
|
@@ -43556,6 +44161,8 @@ ${bar}
|
|
|
43556
44161
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
43557
44162
|
}
|
|
43558
44163
|
}
|
|
44164
|
+
void reportDevice();
|
|
44165
|
+
void fetchServerKey();
|
|
43559
44166
|
const tickAttachmentGc = () => {
|
|
43560
44167
|
try {
|
|
43561
44168
|
runAttachmentGc({
|