@clawos-dev/clawd 0.2.117 → 0.2.119-beta.236.8805685
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 +860 -1469
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -132,11 +132,15 @@ var init_methods = __esm({
|
|
|
132
132
|
"attachment.groupRemove",
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
135
|
+
// global personal token 模型 (2026-05-25): UI 不再调 capability:issue —— daemon 启动时
|
|
136
|
+
// loadOrCreatePersonalCapability 自动创建 1 张 fixed-grants 的 personal capability,
|
|
137
|
+
// 所有联系人共用 personal token。capability:list / capability:delete 保留 (供调试 /
|
|
138
|
+
// 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
|
|
138
139
|
"capability:list",
|
|
139
140
|
"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",
|
|
140
144
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
141
145
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
142
146
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -144,12 +148,14 @@ var init_methods = __esm({
|
|
|
144
148
|
"inbox:list",
|
|
145
149
|
"inbox:markRead",
|
|
146
150
|
"inbox:postMessage",
|
|
147
|
-
// ----
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
"
|
|
152
|
-
"
|
|
151
|
+
// ---- received-capability:* (对方颁给我的 cap, 视角 B 双向授权模型, 2026-05-23) ----
|
|
152
|
+
// owner 入口: list / add / remove
|
|
153
|
+
// guest 端: autoAttach (B 接 A 的 token 后通过 cap channel ws 推 cap 给 A)
|
|
154
|
+
// 详见 received-capability.ts JSDoc + spec 2026-05-23-bidirectional-cap-design.md
|
|
155
|
+
"received-capability:list",
|
|
156
|
+
"received-capability:add",
|
|
157
|
+
"received-capability:remove",
|
|
158
|
+
"received-capability:autoAttach",
|
|
153
159
|
// ---- whoami (v2 capability platform) ----
|
|
154
160
|
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
155
161
|
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
@@ -175,17 +181,6 @@ var init_methods = __esm({
|
|
|
175
181
|
// 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
|
|
176
182
|
"extension.install-from-channel",
|
|
177
183
|
"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",
|
|
189
184
|
// ---- app-builder Project (spec 2026-06-01 picker → 2026-06-02 单栏默认 refactor) ----
|
|
190
185
|
// persona-app-builder Project 管理;schemas: ProjectMetadataSchema + 5 个 args/result 对。
|
|
191
186
|
// listProjects: readdir projects/,按目录读 .clawd-project.json
|
|
@@ -270,10 +265,6 @@ var init_errors = __esm({
|
|
|
270
265
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
271
266
|
FORBIDDEN: "FORBIDDEN",
|
|
272
267
|
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",
|
|
277
268
|
// app-builder 单栏默认 refactor (spec 2026-06-02-app-builder-single-pane-default-design):
|
|
278
269
|
// appBuilder:createProject 加 personaId + session 未绑校验后抛的两个错码。
|
|
279
270
|
WRONG_PERSONA: "WRONG_PERSONA",
|
|
@@ -707,8 +698,8 @@ var init_parseUtil = __esm({
|
|
|
707
698
|
init_errors2();
|
|
708
699
|
init_en();
|
|
709
700
|
makeIssue = (params) => {
|
|
710
|
-
const { data, path:
|
|
711
|
-
const fullPath = [...
|
|
701
|
+
const { data, path: path59, errorMaps, issueData } = params;
|
|
702
|
+
const fullPath = [...path59, ...issueData.path || []];
|
|
712
703
|
const fullIssue = {
|
|
713
704
|
...issueData,
|
|
714
705
|
path: fullPath
|
|
@@ -1019,11 +1010,11 @@ var init_types = __esm({
|
|
|
1019
1010
|
init_parseUtil();
|
|
1020
1011
|
init_util();
|
|
1021
1012
|
ParseInputLazyPath = class {
|
|
1022
|
-
constructor(parent, value,
|
|
1013
|
+
constructor(parent, value, path59, key) {
|
|
1023
1014
|
this._cachedPath = [];
|
|
1024
1015
|
this.parent = parent;
|
|
1025
1016
|
this.data = value;
|
|
1026
|
-
this._path =
|
|
1017
|
+
this._path = path59;
|
|
1027
1018
|
this._key = key;
|
|
1028
1019
|
}
|
|
1029
1020
|
get path() {
|
|
@@ -4664,41 +4655,53 @@ var init_schemas = __esm({
|
|
|
4664
4655
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4665
4656
|
chatId: external_exports.string().min(1).optional(),
|
|
4666
4657
|
/**
|
|
4667
|
-
* 创建该 session
|
|
4668
|
-
*
|
|
4669
|
-
*
|
|
4670
|
-
*
|
|
4658
|
+
* 创建该 session 的 principal id(owner uuid 或 guest cap.id),从 session:create handler
|
|
4659
|
+
* 的 ctx.principal.id 派生。Person identity Q2「按对端人聚合 sessions」需要它:
|
|
4660
|
+
* UI 端按 `creatorPrincipalId === ownerPrincipalId` 区分「我自己」vs「别人接入」,
|
|
4661
|
+
* 后者再通过 PersonAlias 表反查 personId 二级分组。
|
|
4671
4662
|
*
|
|
4672
|
-
*
|
|
4673
|
-
* subDevice 背书(guest ctx)或本机 deviceId(owner ctx)。optional 兼容老 session。
|
|
4674
|
-
*
|
|
4675
|
-
* 决策 #16 删除了 creatorOwnerPrincipalId(personal token #735 遗留)——当年 guest 的
|
|
4676
|
-
* principal.id 都是同张 cap.id 区分不了人才加它;现在 principal.id = 设备 deviceId、
|
|
4677
|
-
* 归属人由 contact(deviceId→ownerId) 反查,该字段理由消失。
|
|
4663
|
+
* optional:兼容 2026-05-21 之前的老 session 数据,新建一定有值。
|
|
4678
4664
|
*/
|
|
4679
4665
|
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4680
4666
|
/**
|
|
4681
|
-
*
|
|
4682
|
-
*
|
|
4683
|
-
*
|
|
4667
|
+
* 创建该 session 的接入者 owner identity(auth 帧 selfPrincipalId)。global personal token
|
|
4668
|
+
* (#735) 后 creatorPrincipalId 在 guest ctx 永远是同一张 personal cap.id,UI 端无法区分
|
|
4669
|
+
* 接入者;这个字段把"是哪个人"的信息固定下来。
|
|
4670
|
+
*
|
|
4671
|
+
* 派生规则:
|
|
4672
|
+
* - owner ctx 创建:= 本机 ownerPrincipalId
|
|
4673
|
+
* - guest ctx + auth 帧带 selfPrincipalId:= ctx.peerOwnerPrincipalId
|
|
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 方案)。
|
|
4681
|
+
*/
|
|
4682
|
+
creatorOwnerPrincipalId: external_exports.string().min(1).optional(),
|
|
4683
|
+
/**
|
|
4684
|
+
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上以 guest ctx
|
|
4685
|
+
* 创建的 session**,远端是 source of truth,本机是该 session 的持久化副本,方便
|
|
4686
|
+
* sidebar 跨设备聚合。字段值是远端 daemon 的 ownerPrincipalId(即我接入的那位朋友)。
|
|
4684
4687
|
*
|
|
4685
4688
|
* 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
|
|
4686
4689
|
* 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
|
|
4687
4690
|
*
|
|
4688
4691
|
* - undefined / 缺省 → 本机 native session(在我自己机器上跑)
|
|
4689
|
-
* - 非空 → 我的会话在该
|
|
4692
|
+
* - 非空 → 我的会话在该 ownerPrincipalId 朋友的 daemon 上跑,本机为持久化副本
|
|
4690
4693
|
*
|
|
4691
|
-
*
|
|
4692
|
-
* | 形态 | creatorPrincipalId
|
|
4693
|
-
*
|
|
4694
|
-
* | 本机 owner 自创 | 本机
|
|
4695
|
-
* |
|
|
4696
|
-
* | 我在朋友 daemon
|
|
4694
|
+
* 与 `creatorPrincipalId` / `creatorOwnerPrincipalId` 在四类 session 下取值矩阵:
|
|
4695
|
+
* | 形态 | creatorPrincipalId | creatorOwnerPrincipalId | originOwnerPrincipalId |
|
|
4696
|
+
* |-------------------------------------|--------------------------|-------------------------|------------------------|
|
|
4697
|
+
* | 本机 owner 自创 | 本机 ownerPrincipalId | 本机 ownerPrincipalId | undefined |
|
|
4698
|
+
* | 本机被别人通过 personal token 接入创建 | 远端接入者 cap.id | 远端接入者 ownerPrincipalId | undefined |
|
|
4699
|
+
* | 我在朋友 daemon 上以 guest ctx 创建(mirror 回本机) | 朋友 daemon personal cap.id | 我自己 ownerPrincipalId | 朋友 ownerPrincipalId |
|
|
4697
4700
|
*
|
|
4698
4701
|
* UI 用法:
|
|
4699
4702
|
* - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
|
|
4700
4703
|
* - 行内显示 `@<朋友>` chip 暗示位置
|
|
4701
|
-
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)`
|
|
4704
|
+
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端
|
|
4702
4705
|
*
|
|
4703
4706
|
* daemon 用法:
|
|
4704
4707
|
* - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
|
|
@@ -5132,14 +5135,18 @@ var init_schemas = __esm({
|
|
|
5132
5135
|
token: external_exports.string().min(1),
|
|
5133
5136
|
scheme: external_exports.literal("bearer").optional(),
|
|
5134
5137
|
/**
|
|
5135
|
-
*
|
|
5136
|
-
*
|
|
5137
|
-
*
|
|
5138
|
-
*
|
|
5139
|
-
* selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
|
|
5140
|
-
* 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
|
|
5138
|
+
* guest 端 ws 接入时自报对端稳定 ownerPrincipalId。AuthGate.authenticate 收到
|
|
5139
|
+
* 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
|
|
5140
|
+
* 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
|
|
5141
|
+
* tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
|
|
5141
5142
|
*/
|
|
5142
|
-
|
|
5143
|
+
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5144
|
+
/**
|
|
5145
|
+
* 同 selfPrincipalId:自报 owner displayName(人类可读),让对端 daemon 把
|
|
5146
|
+
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5147
|
+
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5148
|
+
*/
|
|
5149
|
+
selfDisplayName: external_exports.string().optional()
|
|
5143
5150
|
});
|
|
5144
5151
|
AuthOkFrameSchema = external_exports.object({
|
|
5145
5152
|
type: external_exports.literal("auth:ok"),
|
|
@@ -5352,7 +5359,7 @@ function stripSecretHash(cap) {
|
|
|
5352
5359
|
const { secretHash: _hash, ...wire } = cap;
|
|
5353
5360
|
return wire;
|
|
5354
5361
|
}
|
|
5355
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5362
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS, PersonalCapGetResponseSchema;
|
|
5356
5363
|
var init_capability = __esm({
|
|
5357
5364
|
"../protocol/src/capability.ts"() {
|
|
5358
5365
|
"use strict";
|
|
@@ -5404,11 +5411,7 @@ var init_capability = __esm({
|
|
|
5404
5411
|
owner: external_exports.object({
|
|
5405
5412
|
id: external_exports.string(),
|
|
5406
5413
|
kind: external_exports.enum(["owner", "guest"]),
|
|
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()
|
|
5414
|
+
displayName: external_exports.string()
|
|
5412
5415
|
}).strict(),
|
|
5413
5416
|
capability: CapabilityWireSchema,
|
|
5414
5417
|
grantedPersonas: external_exports.array(
|
|
@@ -5424,6 +5427,13 @@ var init_capability = __esm({
|
|
|
5424
5427
|
actions: Object.freeze(["read", "send"])
|
|
5425
5428
|
})
|
|
5426
5429
|
]);
|
|
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();
|
|
5427
5437
|
}
|
|
5428
5438
|
});
|
|
5429
5439
|
|
|
@@ -5435,80 +5445,83 @@ var init_inbox = __esm({
|
|
|
5435
5445
|
init_zod();
|
|
5436
5446
|
InboxMessageSchema = external_exports.object({
|
|
5437
5447
|
id: external_exports.string().min(1),
|
|
5438
|
-
|
|
5439
|
-
|
|
5448
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5449
|
+
senderPrincipalId: external_exports.string().min(1),
|
|
5440
5450
|
text: external_exports.string().min(1),
|
|
5441
5451
|
createdAt: external_exports.number().int().nonnegative(),
|
|
5442
5452
|
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5443
5453
|
}).strict();
|
|
5444
5454
|
InboxPostMessageArgsSchema = external_exports.object({
|
|
5445
|
-
|
|
5455
|
+
peerOwnerId: external_exports.string().min(1).optional(),
|
|
5446
5456
|
id: external_exports.string().min(1),
|
|
5447
5457
|
text: external_exports.string().min(1),
|
|
5448
5458
|
createdAt: external_exports.number().int().nonnegative()
|
|
5449
5459
|
}).strict();
|
|
5450
5460
|
InboxListArgsSchema = external_exports.object({
|
|
5451
|
-
|
|
5461
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5452
5462
|
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5453
5463
|
}).strict();
|
|
5454
5464
|
InboxMarkReadArgsSchema = external_exports.object({
|
|
5455
|
-
|
|
5465
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5456
5466
|
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5457
5467
|
}).strict();
|
|
5458
5468
|
}
|
|
5459
5469
|
});
|
|
5460
5470
|
|
|
5461
|
-
// ../protocol/src/
|
|
5462
|
-
var
|
|
5463
|
-
var
|
|
5464
|
-
"../protocol/src/
|
|
5471
|
+
// ../protocol/src/received-capability.ts
|
|
5472
|
+
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityAddArgsSchema, ReceivedCapabilityAddOkSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAutoAttachArgsSchema, ReceivedCapabilityAutoAttachOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
|
|
5473
|
+
var init_received_capability = __esm({
|
|
5474
|
+
"../protocol/src/received-capability.ts"() {
|
|
5465
5475
|
"use strict";
|
|
5466
5476
|
init_zod();
|
|
5467
5477
|
init_capability();
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
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 地址 */
|
|
5478
|
+
ReceivedCapabilitySchema = external_exports.object({
|
|
5479
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5480
|
+
ownerDisplayName: external_exports.string(),
|
|
5484
5481
|
remoteUrl: external_exports.string().min(1),
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
// device:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5488
|
-
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5489
|
-
connectToken: external_exports.string(),
|
|
5482
|
+
capabilityId: external_exports.string().min(1),
|
|
5483
|
+
capabilityToken: external_exports.string().min(1),
|
|
5490
5484
|
grants: external_exports.array(GrantSchema),
|
|
5491
|
-
|
|
5485
|
+
receivedAt: external_exports.number().int().nonnegative()
|
|
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
|
+
}).strict();
|
|
5492
|
+
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5493
|
+
type: external_exports.literal("received-capability:add:ok"),
|
|
5494
|
+
receivedCap: ReceivedCapabilityWireSchema
|
|
5492
5495
|
}).strict();
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
deviceId: external_exports.string().min(1)
|
|
5496
|
+
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5497
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5496
5498
|
}).strict();
|
|
5497
|
-
|
|
5498
|
-
type: external_exports.literal("
|
|
5499
|
-
|
|
5499
|
+
ReceivedCapabilityRemoveOkSchema = external_exports.object({
|
|
5500
|
+
type: external_exports.literal("received-capability:remove:ok"),
|
|
5501
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5502
|
+
}).strict();
|
|
5503
|
+
ReceivedCapabilityListOkSchema = external_exports.object({
|
|
5504
|
+
type: external_exports.literal("received-capability:list:ok"),
|
|
5505
|
+
receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
|
|
5506
|
+
}).strict();
|
|
5507
|
+
ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
|
|
5508
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5509
|
+
ownerDisplayName: external_exports.string(),
|
|
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)
|
|
5500
5514
|
}).strict();
|
|
5501
|
-
|
|
5502
|
-
type: external_exports.literal("
|
|
5503
|
-
contacts: external_exports.array(ContactWireSchema)
|
|
5515
|
+
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5516
|
+
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5504
5517
|
}).strict();
|
|
5505
|
-
|
|
5506
|
-
type: external_exports.literal("
|
|
5507
|
-
|
|
5518
|
+
ReceivedCapabilityAddedFrameSchema = external_exports.object({
|
|
5519
|
+
type: external_exports.literal("received-capability:added"),
|
|
5520
|
+
receivedCap: ReceivedCapabilityWireSchema
|
|
5508
5521
|
}).strict();
|
|
5509
|
-
|
|
5510
|
-
type: external_exports.literal("
|
|
5511
|
-
|
|
5522
|
+
ReceivedCapabilityRemovedFrameSchema = external_exports.object({
|
|
5523
|
+
type: external_exports.literal("received-capability:removed"),
|
|
5524
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5512
5525
|
}).strict();
|
|
5513
5526
|
}
|
|
5514
5527
|
});
|
|
@@ -5712,107 +5725,6 @@ var init_extension = __esm({
|
|
|
5712
5725
|
}
|
|
5713
5726
|
});
|
|
5714
5727
|
|
|
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
|
-
|
|
5816
5728
|
// ../protocol/src/runtime.ts
|
|
5817
5729
|
var init_runtime = __esm({
|
|
5818
5730
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5828,9 +5740,8 @@ var init_runtime = __esm({
|
|
|
5828
5740
|
init_principal();
|
|
5829
5741
|
init_capability();
|
|
5830
5742
|
init_inbox();
|
|
5831
|
-
|
|
5743
|
+
init_received_capability();
|
|
5832
5744
|
init_extension();
|
|
5833
|
-
init_feishu_auth();
|
|
5834
5745
|
}
|
|
5835
5746
|
});
|
|
5836
5747
|
|
|
@@ -6105,8 +6016,8 @@ var require_req = __commonJS({
|
|
|
6105
6016
|
if (req.originalUrl) {
|
|
6106
6017
|
_req.url = req.originalUrl;
|
|
6107
6018
|
} else {
|
|
6108
|
-
const
|
|
6109
|
-
_req.url = typeof
|
|
6019
|
+
const path59 = req.path;
|
|
6020
|
+
_req.url = typeof path59 === "string" ? path59 : req.url ? req.url.path || req.url : void 0;
|
|
6110
6021
|
}
|
|
6111
6022
|
if (req.query) {
|
|
6112
6023
|
_req.query = req.query;
|
|
@@ -6271,14 +6182,14 @@ var require_redact = __commonJS({
|
|
|
6271
6182
|
}
|
|
6272
6183
|
return obj;
|
|
6273
6184
|
}
|
|
6274
|
-
function parsePath(
|
|
6185
|
+
function parsePath(path59) {
|
|
6275
6186
|
const parts = [];
|
|
6276
6187
|
let current = "";
|
|
6277
6188
|
let inBrackets = false;
|
|
6278
6189
|
let inQuotes = false;
|
|
6279
6190
|
let quoteChar = "";
|
|
6280
|
-
for (let i = 0; i <
|
|
6281
|
-
const char =
|
|
6191
|
+
for (let i = 0; i < path59.length; i++) {
|
|
6192
|
+
const char = path59[i];
|
|
6282
6193
|
if (!inBrackets && char === ".") {
|
|
6283
6194
|
if (current) {
|
|
6284
6195
|
parts.push(current);
|
|
@@ -6409,10 +6320,10 @@ var require_redact = __commonJS({
|
|
|
6409
6320
|
return current;
|
|
6410
6321
|
}
|
|
6411
6322
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6412
|
-
for (const
|
|
6413
|
-
const parts = parsePath(
|
|
6323
|
+
for (const path59 of paths) {
|
|
6324
|
+
const parts = parsePath(path59);
|
|
6414
6325
|
if (parts.includes("*")) {
|
|
6415
|
-
redactWildcardPath(obj, parts, censor,
|
|
6326
|
+
redactWildcardPath(obj, parts, censor, path59, remove);
|
|
6416
6327
|
} else {
|
|
6417
6328
|
if (remove) {
|
|
6418
6329
|
removeKey(obj, parts);
|
|
@@ -6497,8 +6408,8 @@ var require_redact = __commonJS({
|
|
|
6497
6408
|
}
|
|
6498
6409
|
} else {
|
|
6499
6410
|
if (afterWildcard.includes("*")) {
|
|
6500
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6501
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6411
|
+
const wrappedCensor = typeof censor === "function" ? (value, path59) => {
|
|
6412
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path59];
|
|
6502
6413
|
return censor(value, fullPath);
|
|
6503
6414
|
} : censor;
|
|
6504
6415
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6533,8 +6444,8 @@ var require_redact = __commonJS({
|
|
|
6533
6444
|
return null;
|
|
6534
6445
|
}
|
|
6535
6446
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6536
|
-
for (const
|
|
6537
|
-
const parts = parsePath(
|
|
6447
|
+
for (const path59 of pathsToClone) {
|
|
6448
|
+
const parts = parsePath(path59);
|
|
6538
6449
|
let current = pathStructure;
|
|
6539
6450
|
for (let i = 0; i < parts.length; i++) {
|
|
6540
6451
|
const part = parts[i];
|
|
@@ -6586,24 +6497,24 @@ var require_redact = __commonJS({
|
|
|
6586
6497
|
}
|
|
6587
6498
|
return cloneSelectively(obj, pathStructure);
|
|
6588
6499
|
}
|
|
6589
|
-
function validatePath(
|
|
6590
|
-
if (typeof
|
|
6500
|
+
function validatePath(path59) {
|
|
6501
|
+
if (typeof path59 !== "string") {
|
|
6591
6502
|
throw new Error("Paths must be (non-empty) strings");
|
|
6592
6503
|
}
|
|
6593
|
-
if (
|
|
6504
|
+
if (path59 === "") {
|
|
6594
6505
|
throw new Error("Invalid redaction path ()");
|
|
6595
6506
|
}
|
|
6596
|
-
if (
|
|
6597
|
-
throw new Error(`Invalid redaction path (${
|
|
6507
|
+
if (path59.includes("..")) {
|
|
6508
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6598
6509
|
}
|
|
6599
|
-
if (
|
|
6600
|
-
throw new Error(`Invalid redaction path (${
|
|
6510
|
+
if (path59.includes(",")) {
|
|
6511
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6601
6512
|
}
|
|
6602
6513
|
let bracketCount = 0;
|
|
6603
6514
|
let inQuotes = false;
|
|
6604
6515
|
let quoteChar = "";
|
|
6605
|
-
for (let i = 0; i <
|
|
6606
|
-
const char =
|
|
6516
|
+
for (let i = 0; i < path59.length; i++) {
|
|
6517
|
+
const char = path59[i];
|
|
6607
6518
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6608
6519
|
if (!inQuotes) {
|
|
6609
6520
|
inQuotes = true;
|
|
@@ -6617,20 +6528,20 @@ var require_redact = __commonJS({
|
|
|
6617
6528
|
} else if (char === "]" && !inQuotes) {
|
|
6618
6529
|
bracketCount--;
|
|
6619
6530
|
if (bracketCount < 0) {
|
|
6620
|
-
throw new Error(`Invalid redaction path (${
|
|
6531
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6621
6532
|
}
|
|
6622
6533
|
}
|
|
6623
6534
|
}
|
|
6624
6535
|
if (bracketCount !== 0) {
|
|
6625
|
-
throw new Error(`Invalid redaction path (${
|
|
6536
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6626
6537
|
}
|
|
6627
6538
|
}
|
|
6628
6539
|
function validatePaths(paths) {
|
|
6629
6540
|
if (!Array.isArray(paths)) {
|
|
6630
6541
|
throw new TypeError("paths must be an array");
|
|
6631
6542
|
}
|
|
6632
|
-
for (const
|
|
6633
|
-
validatePath(
|
|
6543
|
+
for (const path59 of paths) {
|
|
6544
|
+
validatePath(path59);
|
|
6634
6545
|
}
|
|
6635
6546
|
}
|
|
6636
6547
|
function slowRedact(options = {}) {
|
|
@@ -6798,8 +6709,8 @@ var require_redaction = __commonJS({
|
|
|
6798
6709
|
if (shape[k2] === null) {
|
|
6799
6710
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6800
6711
|
} else {
|
|
6801
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6802
|
-
return censor(value, [k2, ...
|
|
6712
|
+
const wrappedCensor = typeof censor === "function" ? (value, path59) => {
|
|
6713
|
+
return censor(value, [k2, ...path59]);
|
|
6803
6714
|
} : censor;
|
|
6804
6715
|
o[k2] = Redact({
|
|
6805
6716
|
paths: shape[k2],
|
|
@@ -7017,10 +6928,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
7017
6928
|
var require_sonic_boom = __commonJS({
|
|
7018
6929
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
7019
6930
|
"use strict";
|
|
7020
|
-
var
|
|
6931
|
+
var fs48 = require("fs");
|
|
7021
6932
|
var EventEmitter3 = require("events");
|
|
7022
6933
|
var inherits = require("util").inherits;
|
|
7023
|
-
var
|
|
6934
|
+
var path59 = require("path");
|
|
7024
6935
|
var sleep = require_atomic_sleep();
|
|
7025
6936
|
var assert = require("assert");
|
|
7026
6937
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -7074,20 +6985,20 @@ var require_sonic_boom = __commonJS({
|
|
|
7074
6985
|
const mode = sonic.mode;
|
|
7075
6986
|
if (sonic.sync) {
|
|
7076
6987
|
try {
|
|
7077
|
-
if (sonic.mkdir)
|
|
7078
|
-
const fd =
|
|
6988
|
+
if (sonic.mkdir) fs48.mkdirSync(path59.dirname(file), { recursive: true });
|
|
6989
|
+
const fd = fs48.openSync(file, flags, mode);
|
|
7079
6990
|
fileOpened(null, fd);
|
|
7080
6991
|
} catch (err) {
|
|
7081
6992
|
fileOpened(err);
|
|
7082
6993
|
throw err;
|
|
7083
6994
|
}
|
|
7084
6995
|
} else if (sonic.mkdir) {
|
|
7085
|
-
|
|
6996
|
+
fs48.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
7086
6997
|
if (err) return fileOpened(err);
|
|
7087
|
-
|
|
6998
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
7088
6999
|
});
|
|
7089
7000
|
} else {
|
|
7090
|
-
|
|
7001
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
7091
7002
|
}
|
|
7092
7003
|
}
|
|
7093
7004
|
function SonicBoom(opts) {
|
|
@@ -7128,8 +7039,8 @@ var require_sonic_boom = __commonJS({
|
|
|
7128
7039
|
this.flush = flushBuffer;
|
|
7129
7040
|
this.flushSync = flushBufferSync;
|
|
7130
7041
|
this._actualWrite = actualWriteBuffer;
|
|
7131
|
-
fsWriteSync = () =>
|
|
7132
|
-
fsWrite = () =>
|
|
7042
|
+
fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
|
|
7043
|
+
fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
|
|
7133
7044
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
7134
7045
|
this._writingBuf = "";
|
|
7135
7046
|
this.write = write;
|
|
@@ -7138,15 +7049,15 @@ var require_sonic_boom = __commonJS({
|
|
|
7138
7049
|
this._actualWrite = actualWrite;
|
|
7139
7050
|
fsWriteSync = () => {
|
|
7140
7051
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7141
|
-
return
|
|
7052
|
+
return fs48.writeSync(this.fd, this._writingBuf);
|
|
7142
7053
|
}
|
|
7143
|
-
return
|
|
7054
|
+
return fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7144
7055
|
};
|
|
7145
7056
|
fsWrite = () => {
|
|
7146
7057
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7147
|
-
return
|
|
7058
|
+
return fs48.write(this.fd, this._writingBuf, this.release);
|
|
7148
7059
|
}
|
|
7149
|
-
return
|
|
7060
|
+
return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
7150
7061
|
};
|
|
7151
7062
|
} else {
|
|
7152
7063
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7203,7 +7114,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7203
7114
|
}
|
|
7204
7115
|
}
|
|
7205
7116
|
if (this._fsync) {
|
|
7206
|
-
|
|
7117
|
+
fs48.fsyncSync(this.fd);
|
|
7207
7118
|
}
|
|
7208
7119
|
const len = this._len;
|
|
7209
7120
|
if (this._reopening) {
|
|
@@ -7317,7 +7228,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7317
7228
|
const onDrain = () => {
|
|
7318
7229
|
if (!this._fsync) {
|
|
7319
7230
|
try {
|
|
7320
|
-
|
|
7231
|
+
fs48.fsync(this.fd, (err) => {
|
|
7321
7232
|
this._flushPending = false;
|
|
7322
7233
|
cb(err);
|
|
7323
7234
|
});
|
|
@@ -7419,7 +7330,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7419
7330
|
const fd = this.fd;
|
|
7420
7331
|
this.once("ready", () => {
|
|
7421
7332
|
if (fd !== this.fd) {
|
|
7422
|
-
|
|
7333
|
+
fs48.close(fd, (err) => {
|
|
7423
7334
|
if (err) {
|
|
7424
7335
|
return this.emit("error", err);
|
|
7425
7336
|
}
|
|
@@ -7468,7 +7379,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7468
7379
|
buf = this._bufs[0];
|
|
7469
7380
|
}
|
|
7470
7381
|
try {
|
|
7471
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7382
|
+
const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
|
|
7472
7383
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7473
7384
|
buf = releasedBufObj.writingBuf;
|
|
7474
7385
|
this._len = releasedBufObj.len;
|
|
@@ -7484,7 +7395,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7484
7395
|
}
|
|
7485
7396
|
}
|
|
7486
7397
|
try {
|
|
7487
|
-
|
|
7398
|
+
fs48.fsyncSync(this.fd);
|
|
7488
7399
|
} catch {
|
|
7489
7400
|
}
|
|
7490
7401
|
}
|
|
@@ -7505,7 +7416,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7505
7416
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7506
7417
|
}
|
|
7507
7418
|
try {
|
|
7508
|
-
const n =
|
|
7419
|
+
const n = fs48.writeSync(this.fd, buf);
|
|
7509
7420
|
buf = buf.subarray(n);
|
|
7510
7421
|
this._len = Math.max(this._len - n, 0);
|
|
7511
7422
|
if (buf.length <= 0) {
|
|
@@ -7533,13 +7444,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7533
7444
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7534
7445
|
if (this.sync) {
|
|
7535
7446
|
try {
|
|
7536
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7447
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7537
7448
|
release(null, written);
|
|
7538
7449
|
} catch (err) {
|
|
7539
7450
|
release(err);
|
|
7540
7451
|
}
|
|
7541
7452
|
} else {
|
|
7542
|
-
|
|
7453
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7543
7454
|
}
|
|
7544
7455
|
}
|
|
7545
7456
|
function actualWriteBuffer() {
|
|
@@ -7548,7 +7459,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7548
7459
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7549
7460
|
if (this.sync) {
|
|
7550
7461
|
try {
|
|
7551
|
-
const written =
|
|
7462
|
+
const written = fs48.writeSync(this.fd, this._writingBuf);
|
|
7552
7463
|
release(null, written);
|
|
7553
7464
|
} catch (err) {
|
|
7554
7465
|
release(err);
|
|
@@ -7557,7 +7468,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7557
7468
|
if (kCopyBuffer) {
|
|
7558
7469
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7559
7470
|
}
|
|
7560
|
-
|
|
7471
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7561
7472
|
}
|
|
7562
7473
|
}
|
|
7563
7474
|
function actualClose(sonic) {
|
|
@@ -7573,12 +7484,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7573
7484
|
sonic._lens = [];
|
|
7574
7485
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7575
7486
|
try {
|
|
7576
|
-
|
|
7487
|
+
fs48.fsync(sonic.fd, closeWrapped);
|
|
7577
7488
|
} catch {
|
|
7578
7489
|
}
|
|
7579
7490
|
function closeWrapped() {
|
|
7580
7491
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7581
|
-
|
|
7492
|
+
fs48.close(sonic.fd, done);
|
|
7582
7493
|
} else {
|
|
7583
7494
|
done();
|
|
7584
7495
|
}
|
|
@@ -7835,7 +7746,7 @@ var require_thread_stream = __commonJS({
|
|
|
7835
7746
|
var { version: version2 } = require_package();
|
|
7836
7747
|
var { EventEmitter: EventEmitter3 } = require("events");
|
|
7837
7748
|
var { Worker } = require("worker_threads");
|
|
7838
|
-
var { join:
|
|
7749
|
+
var { join: join12 } = require("path");
|
|
7839
7750
|
var { pathToFileURL } = require("url");
|
|
7840
7751
|
var { wait } = require_wait();
|
|
7841
7752
|
var {
|
|
@@ -7871,7 +7782,7 @@ var require_thread_stream = __commonJS({
|
|
|
7871
7782
|
function createWorker(stream, opts) {
|
|
7872
7783
|
const { filename, workerData } = opts;
|
|
7873
7784
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7874
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7785
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join12(__dirname, "lib", "worker.js");
|
|
7875
7786
|
const worker = new Worker(toExecute, {
|
|
7876
7787
|
...opts.workerOpts,
|
|
7877
7788
|
trackUnmanagedFds: false,
|
|
@@ -8257,7 +8168,7 @@ var require_transport = __commonJS({
|
|
|
8257
8168
|
"use strict";
|
|
8258
8169
|
var { createRequire } = require("module");
|
|
8259
8170
|
var getCallers = require_caller();
|
|
8260
|
-
var { join:
|
|
8171
|
+
var { join: join12, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8261
8172
|
var sleep = require_atomic_sleep();
|
|
8262
8173
|
var onExit = require_on_exit_leak_free();
|
|
8263
8174
|
var ThreadStream = require_thread_stream();
|
|
@@ -8320,7 +8231,7 @@ var require_transport = __commonJS({
|
|
|
8320
8231
|
throw new Error("only one of target or targets can be specified");
|
|
8321
8232
|
}
|
|
8322
8233
|
if (targets) {
|
|
8323
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8234
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8324
8235
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8325
8236
|
return {
|
|
8326
8237
|
...dest,
|
|
@@ -8338,7 +8249,7 @@ var require_transport = __commonJS({
|
|
|
8338
8249
|
});
|
|
8339
8250
|
});
|
|
8340
8251
|
} else if (pipeline3) {
|
|
8341
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8252
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8342
8253
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8343
8254
|
return {
|
|
8344
8255
|
...dest,
|
|
@@ -8360,7 +8271,7 @@ var require_transport = __commonJS({
|
|
|
8360
8271
|
return origin;
|
|
8361
8272
|
}
|
|
8362
8273
|
if (origin === "pino/file") {
|
|
8363
|
-
return
|
|
8274
|
+
return join12(__dirname, "..", "file.js");
|
|
8364
8275
|
}
|
|
8365
8276
|
let fixTarget2;
|
|
8366
8277
|
for (const filePath of callers) {
|
|
@@ -9350,7 +9261,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9350
9261
|
return circularValue;
|
|
9351
9262
|
}
|
|
9352
9263
|
let res = "";
|
|
9353
|
-
let
|
|
9264
|
+
let join12 = ",";
|
|
9354
9265
|
const originalIndentation = indentation;
|
|
9355
9266
|
if (Array.isArray(value)) {
|
|
9356
9267
|
if (value.length === 0) {
|
|
@@ -9364,7 +9275,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9364
9275
|
indentation += spacer;
|
|
9365
9276
|
res += `
|
|
9366
9277
|
${indentation}`;
|
|
9367
|
-
|
|
9278
|
+
join12 = `,
|
|
9368
9279
|
${indentation}`;
|
|
9369
9280
|
}
|
|
9370
9281
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9372,13 +9283,13 @@ ${indentation}`;
|
|
|
9372
9283
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9373
9284
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9374
9285
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9375
|
-
res +=
|
|
9286
|
+
res += join12;
|
|
9376
9287
|
}
|
|
9377
9288
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9378
9289
|
res += tmp !== void 0 ? tmp : "null";
|
|
9379
9290
|
if (value.length - 1 > maximumBreadth) {
|
|
9380
9291
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9381
|
-
res += `${
|
|
9292
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9382
9293
|
}
|
|
9383
9294
|
if (spacer !== "") {
|
|
9384
9295
|
res += `
|
|
@@ -9399,7 +9310,7 @@ ${originalIndentation}`;
|
|
|
9399
9310
|
let separator = "";
|
|
9400
9311
|
if (spacer !== "") {
|
|
9401
9312
|
indentation += spacer;
|
|
9402
|
-
|
|
9313
|
+
join12 = `,
|
|
9403
9314
|
${indentation}`;
|
|
9404
9315
|
whitespace = " ";
|
|
9405
9316
|
}
|
|
@@ -9413,13 +9324,13 @@ ${indentation}`;
|
|
|
9413
9324
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9414
9325
|
if (tmp !== void 0) {
|
|
9415
9326
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9416
|
-
separator =
|
|
9327
|
+
separator = join12;
|
|
9417
9328
|
}
|
|
9418
9329
|
}
|
|
9419
9330
|
if (keyLength > maximumBreadth) {
|
|
9420
9331
|
const removedKeys = keyLength - maximumBreadth;
|
|
9421
9332
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9422
|
-
separator =
|
|
9333
|
+
separator = join12;
|
|
9423
9334
|
}
|
|
9424
9335
|
if (spacer !== "" && separator.length > 1) {
|
|
9425
9336
|
res = `
|
|
@@ -9460,7 +9371,7 @@ ${originalIndentation}`;
|
|
|
9460
9371
|
}
|
|
9461
9372
|
const originalIndentation = indentation;
|
|
9462
9373
|
let res = "";
|
|
9463
|
-
let
|
|
9374
|
+
let join12 = ",";
|
|
9464
9375
|
if (Array.isArray(value)) {
|
|
9465
9376
|
if (value.length === 0) {
|
|
9466
9377
|
return "[]";
|
|
@@ -9473,7 +9384,7 @@ ${originalIndentation}`;
|
|
|
9473
9384
|
indentation += spacer;
|
|
9474
9385
|
res += `
|
|
9475
9386
|
${indentation}`;
|
|
9476
|
-
|
|
9387
|
+
join12 = `,
|
|
9477
9388
|
${indentation}`;
|
|
9478
9389
|
}
|
|
9479
9390
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9481,13 +9392,13 @@ ${indentation}`;
|
|
|
9481
9392
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9482
9393
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9483
9394
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9484
|
-
res +=
|
|
9395
|
+
res += join12;
|
|
9485
9396
|
}
|
|
9486
9397
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9487
9398
|
res += tmp !== void 0 ? tmp : "null";
|
|
9488
9399
|
if (value.length - 1 > maximumBreadth) {
|
|
9489
9400
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9490
|
-
res += `${
|
|
9401
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9491
9402
|
}
|
|
9492
9403
|
if (spacer !== "") {
|
|
9493
9404
|
res += `
|
|
@@ -9500,7 +9411,7 @@ ${originalIndentation}`;
|
|
|
9500
9411
|
let whitespace = "";
|
|
9501
9412
|
if (spacer !== "") {
|
|
9502
9413
|
indentation += spacer;
|
|
9503
|
-
|
|
9414
|
+
join12 = `,
|
|
9504
9415
|
${indentation}`;
|
|
9505
9416
|
whitespace = " ";
|
|
9506
9417
|
}
|
|
@@ -9509,7 +9420,7 @@ ${indentation}`;
|
|
|
9509
9420
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9510
9421
|
if (tmp !== void 0) {
|
|
9511
9422
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9512
|
-
separator =
|
|
9423
|
+
separator = join12;
|
|
9513
9424
|
}
|
|
9514
9425
|
}
|
|
9515
9426
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9567,20 +9478,20 @@ ${originalIndentation}`;
|
|
|
9567
9478
|
indentation += spacer;
|
|
9568
9479
|
let res2 = `
|
|
9569
9480
|
${indentation}`;
|
|
9570
|
-
const
|
|
9481
|
+
const join13 = `,
|
|
9571
9482
|
${indentation}`;
|
|
9572
9483
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9573
9484
|
let i = 0;
|
|
9574
9485
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9575
9486
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9576
9487
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9577
|
-
res2 +=
|
|
9488
|
+
res2 += join13;
|
|
9578
9489
|
}
|
|
9579
9490
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9580
9491
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9581
9492
|
if (value.length - 1 > maximumBreadth) {
|
|
9582
9493
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9583
|
-
res2 += `${
|
|
9494
|
+
res2 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9584
9495
|
}
|
|
9585
9496
|
res2 += `
|
|
9586
9497
|
${originalIndentation}`;
|
|
@@ -9596,16 +9507,16 @@ ${originalIndentation}`;
|
|
|
9596
9507
|
return '"[Object]"';
|
|
9597
9508
|
}
|
|
9598
9509
|
indentation += spacer;
|
|
9599
|
-
const
|
|
9510
|
+
const join12 = `,
|
|
9600
9511
|
${indentation}`;
|
|
9601
9512
|
let res = "";
|
|
9602
9513
|
let separator = "";
|
|
9603
9514
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9604
9515
|
if (isTypedArrayWithEntries(value)) {
|
|
9605
|
-
res += stringifyTypedArray(value,
|
|
9516
|
+
res += stringifyTypedArray(value, join12, maximumBreadth);
|
|
9606
9517
|
keys = keys.slice(value.length);
|
|
9607
9518
|
maximumPropertiesToStringify -= value.length;
|
|
9608
|
-
separator =
|
|
9519
|
+
separator = join12;
|
|
9609
9520
|
}
|
|
9610
9521
|
if (deterministic) {
|
|
9611
9522
|
keys = sort(keys, comparator);
|
|
@@ -9616,13 +9527,13 @@ ${indentation}`;
|
|
|
9616
9527
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9617
9528
|
if (tmp !== void 0) {
|
|
9618
9529
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9619
|
-
separator =
|
|
9530
|
+
separator = join12;
|
|
9620
9531
|
}
|
|
9621
9532
|
}
|
|
9622
9533
|
if (keyLength > maximumBreadth) {
|
|
9623
9534
|
const removedKeys = keyLength - maximumBreadth;
|
|
9624
9535
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9625
|
-
separator =
|
|
9536
|
+
separator = join12;
|
|
9626
9537
|
}
|
|
9627
9538
|
if (separator !== "") {
|
|
9628
9539
|
res = `
|
|
@@ -10713,11 +10624,11 @@ var init_lib = __esm({
|
|
|
10713
10624
|
}
|
|
10714
10625
|
}
|
|
10715
10626
|
},
|
|
10716
|
-
addToPath: function addToPath(
|
|
10717
|
-
var last =
|
|
10627
|
+
addToPath: function addToPath(path59, added, removed, oldPosInc, options) {
|
|
10628
|
+
var last = path59.lastComponent;
|
|
10718
10629
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10719
10630
|
return {
|
|
10720
|
-
oldPos:
|
|
10631
|
+
oldPos: path59.oldPos + oldPosInc,
|
|
10721
10632
|
lastComponent: {
|
|
10722
10633
|
count: last.count + 1,
|
|
10723
10634
|
added,
|
|
@@ -10727,7 +10638,7 @@ var init_lib = __esm({
|
|
|
10727
10638
|
};
|
|
10728
10639
|
} else {
|
|
10729
10640
|
return {
|
|
10730
|
-
oldPos:
|
|
10641
|
+
oldPos: path59.oldPos + oldPosInc,
|
|
10731
10642
|
lastComponent: {
|
|
10732
10643
|
count: 1,
|
|
10733
10644
|
added,
|
|
@@ -11173,10 +11084,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11173
11084
|
const memories = raw.map((m2) => {
|
|
11174
11085
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11175
11086
|
const rec = m2;
|
|
11176
|
-
const
|
|
11087
|
+
const path59 = typeof rec.path === "string" ? rec.path : null;
|
|
11177
11088
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11178
|
-
if (!
|
|
11179
|
-
const entry = { path:
|
|
11089
|
+
if (!path59 || content == null) return null;
|
|
11090
|
+
const entry = { path: path59, content };
|
|
11180
11091
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
11181
11092
|
return entry;
|
|
11182
11093
|
}).filter((m2) => m2 !== null);
|
|
@@ -12007,10 +11918,10 @@ function parseAttachment(obj) {
|
|
|
12007
11918
|
const memories = raw.map((m2) => {
|
|
12008
11919
|
if (!m2 || typeof m2 !== "object") return null;
|
|
12009
11920
|
const rec = m2;
|
|
12010
|
-
const
|
|
11921
|
+
const path59 = typeof rec.path === "string" ? rec.path : null;
|
|
12011
11922
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
12012
|
-
if (!
|
|
12013
|
-
const out = { path:
|
|
11923
|
+
if (!path59 || content == null) return null;
|
|
11924
|
+
const out = { path: path59, content };
|
|
12014
11925
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
12015
11926
|
return out;
|
|
12016
11927
|
}).filter((m2) => m2 !== null);
|
|
@@ -21209,7 +21120,7 @@ var require_BufferList = __commonJS({
|
|
|
21209
21120
|
this.head = this.tail = null;
|
|
21210
21121
|
this.length = 0;
|
|
21211
21122
|
};
|
|
21212
|
-
BufferList.prototype.join = function
|
|
21123
|
+
BufferList.prototype.join = function join12(s) {
|
|
21213
21124
|
if (this.length === 0) return "";
|
|
21214
21125
|
var p2 = this.head;
|
|
21215
21126
|
var ret = "" + p2.data;
|
|
@@ -23799,8 +23710,8 @@ var require_utils = __commonJS({
|
|
|
23799
23710
|
var result = transform[inputType][outputType](input);
|
|
23800
23711
|
return result;
|
|
23801
23712
|
};
|
|
23802
|
-
exports2.resolve = function(
|
|
23803
|
-
var parts =
|
|
23713
|
+
exports2.resolve = function(path59) {
|
|
23714
|
+
var parts = path59.split("/");
|
|
23804
23715
|
var result = [];
|
|
23805
23716
|
for (var index = 0; index < parts.length; index++) {
|
|
23806
23717
|
var part = parts[index];
|
|
@@ -29653,18 +29564,18 @@ var require_object = __commonJS({
|
|
|
29653
29564
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
29654
29565
|
this.files[name] = object;
|
|
29655
29566
|
};
|
|
29656
|
-
var parentFolder = function(
|
|
29657
|
-
if (
|
|
29658
|
-
|
|
29567
|
+
var parentFolder = function(path59) {
|
|
29568
|
+
if (path59.slice(-1) === "/") {
|
|
29569
|
+
path59 = path59.substring(0, path59.length - 1);
|
|
29659
29570
|
}
|
|
29660
|
-
var lastSlash =
|
|
29661
|
-
return lastSlash > 0 ?
|
|
29571
|
+
var lastSlash = path59.lastIndexOf("/");
|
|
29572
|
+
return lastSlash > 0 ? path59.substring(0, lastSlash) : "";
|
|
29662
29573
|
};
|
|
29663
|
-
var forceTrailingSlash = function(
|
|
29664
|
-
if (
|
|
29665
|
-
|
|
29574
|
+
var forceTrailingSlash = function(path59) {
|
|
29575
|
+
if (path59.slice(-1) !== "/") {
|
|
29576
|
+
path59 += "/";
|
|
29666
29577
|
}
|
|
29667
|
-
return
|
|
29578
|
+
return path59;
|
|
29668
29579
|
};
|
|
29669
29580
|
var folderAdd = function(name, createFolders) {
|
|
29670
29581
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -33142,7 +33053,7 @@ var SessionManager = class {
|
|
|
33142
33053
|
}
|
|
33143
33054
|
}
|
|
33144
33055
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
33145
|
-
create(args, creatorPrincipalId) {
|
|
33056
|
+
create(args, creatorPrincipalId, creatorOwnerPrincipalId) {
|
|
33146
33057
|
let cwd;
|
|
33147
33058
|
if (args.ownerPersonaId) {
|
|
33148
33059
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -33185,6 +33096,7 @@ var SessionManager = class {
|
|
|
33185
33096
|
ownerPersonaId: args.ownerPersonaId,
|
|
33186
33097
|
appBuilderProject: args.appBuilderProject,
|
|
33187
33098
|
creatorPrincipalId,
|
|
33099
|
+
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
33188
33100
|
createdAt: iso,
|
|
33189
33101
|
updatedAt: iso
|
|
33190
33102
|
};
|
|
@@ -36625,10 +36537,9 @@ var LocalWsServer = class {
|
|
|
36625
36537
|
httpServer = null;
|
|
36626
36538
|
frameHandler = null;
|
|
36627
36539
|
clients = /* @__PURE__ */ new Map();
|
|
36628
|
-
//
|
|
36629
|
-
//
|
|
36630
|
-
|
|
36631
|
-
guestIdToClients = /* @__PURE__ */ new Map();
|
|
36540
|
+
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
36541
|
+
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
36542
|
+
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
36632
36543
|
logger;
|
|
36633
36544
|
pingIntervalMs;
|
|
36634
36545
|
async start() {
|
|
@@ -36746,6 +36657,38 @@ var LocalWsServer = class {
|
|
|
36746
36657
|
this.safeSend(c.ws, frame);
|
|
36747
36658
|
}
|
|
36748
36659
|
}
|
|
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
|
+
}
|
|
36749
36692
|
firstSubscriber(sessionId) {
|
|
36750
36693
|
for (const c of this.clients.values()) {
|
|
36751
36694
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -36768,16 +36711,16 @@ var LocalWsServer = class {
|
|
|
36768
36711
|
this.safeSend(c.ws, frame);
|
|
36769
36712
|
}
|
|
36770
36713
|
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
36771
|
-
// 绑到 client;guest 连接同时进
|
|
36714
|
+
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
36772
36715
|
attachClientContext(clientId, ctx) {
|
|
36773
36716
|
const c = this.clients.get(clientId);
|
|
36774
36717
|
if (!c) return;
|
|
36775
36718
|
c.ctx = ctx;
|
|
36776
|
-
if (ctx.
|
|
36777
|
-
let set = this.
|
|
36719
|
+
if (ctx.capabilityId) {
|
|
36720
|
+
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
36778
36721
|
if (!set) {
|
|
36779
36722
|
set = /* @__PURE__ */ new Set();
|
|
36780
|
-
this.
|
|
36723
|
+
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
36781
36724
|
}
|
|
36782
36725
|
set.add(clientId);
|
|
36783
36726
|
}
|
|
@@ -36786,27 +36729,10 @@ var LocalWsServer = class {
|
|
|
36786
36729
|
getClientContext(clientId) {
|
|
36787
36730
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36788
36731
|
}
|
|
36789
|
-
//
|
|
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
|
|
36732
|
+
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
36805
36733
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36806
|
-
|
|
36807
|
-
|
|
36808
|
-
closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36809
|
-
const set = this.guestIdToClients.get(guestPrincipalId);
|
|
36734
|
+
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36735
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36810
36736
|
if (!set) return;
|
|
36811
36737
|
const ids = [...set];
|
|
36812
36738
|
for (const id of ids) {
|
|
@@ -36871,18 +36797,16 @@ var LocalWsServer = class {
|
|
|
36871
36797
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36872
36798
|
}
|
|
36873
36799
|
socket.on("message", (data) => {
|
|
36874
|
-
|
|
36875
|
-
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36876
|
-
});
|
|
36800
|
+
this.onMessage(handle, data, remoteAddress);
|
|
36877
36801
|
});
|
|
36878
36802
|
socket.on("close", () => {
|
|
36879
36803
|
const c = this.clients.get(id);
|
|
36880
36804
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
36881
|
-
const
|
|
36882
|
-
if (
|
|
36883
|
-
const set = this.
|
|
36805
|
+
const capId = c?.ctx?.capabilityId;
|
|
36806
|
+
if (capId) {
|
|
36807
|
+
const set = this.capabilityIdToClients.get(capId);
|
|
36884
36808
|
set?.delete(id);
|
|
36885
|
-
if (set && set.size === 0) this.
|
|
36809
|
+
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
36886
36810
|
}
|
|
36887
36811
|
this.clients.delete(id);
|
|
36888
36812
|
authGate?.unregister(id);
|
|
@@ -36892,7 +36816,7 @@ var LocalWsServer = class {
|
|
|
36892
36816
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36893
36817
|
});
|
|
36894
36818
|
}
|
|
36895
|
-
|
|
36819
|
+
onMessage(client, data, remoteAddress) {
|
|
36896
36820
|
let frame;
|
|
36897
36821
|
try {
|
|
36898
36822
|
frame = JSON.parse(data.toString());
|
|
@@ -36903,7 +36827,7 @@ var LocalWsServer = class {
|
|
|
36903
36827
|
const authGate = this.opts.authGate;
|
|
36904
36828
|
if (authGate) {
|
|
36905
36829
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36906
|
-
const verdict =
|
|
36830
|
+
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36907
36831
|
if (verdict !== "pass") {
|
|
36908
36832
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36909
36833
|
try {
|
|
@@ -36918,8 +36842,14 @@ var LocalWsServer = class {
|
|
|
36918
36842
|
}
|
|
36919
36843
|
} else if (frame.type === "auth") {
|
|
36920
36844
|
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 ?? "" : "";
|
|
36921
36847
|
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
36922
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36848
|
+
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36849
|
+
token,
|
|
36850
|
+
selfPrincipalId || void 0,
|
|
36851
|
+
selfDisplayName || void 0
|
|
36852
|
+
);
|
|
36923
36853
|
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
36924
36854
|
}
|
|
36925
36855
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
@@ -37000,7 +36930,7 @@ var AuthGate = class {
|
|
|
37000
36930
|
registerConnection(handle) {
|
|
37001
36931
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
37002
36932
|
if (!enforce) {
|
|
37003
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null
|
|
36933
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
37004
36934
|
return true;
|
|
37005
36935
|
}
|
|
37006
36936
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -37009,7 +36939,7 @@ var AuthGate = class {
|
|
|
37009
36939
|
if (!st || st.authed) return;
|
|
37010
36940
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
37011
36941
|
}, this.opts.timeoutMs);
|
|
37012
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer
|
|
36942
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
37013
36943
|
return false;
|
|
37014
36944
|
}
|
|
37015
36945
|
unregister(handleId) {
|
|
@@ -37025,15 +36955,12 @@ var AuthGate = class {
|
|
|
37025
36955
|
}
|
|
37026
36956
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
37027
36957
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
37028
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
36958
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接)
|
|
37029
36959
|
// - pass:已 authed,调用方继续走业务路由
|
|
37030
|
-
|
|
37031
|
-
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
37032
|
-
async handleFrame(handle, frame) {
|
|
36960
|
+
handleFrame(handle, frame) {
|
|
37033
36961
|
const st = this.states.get(handle.id);
|
|
37034
36962
|
if (!st) return "reject";
|
|
37035
36963
|
if (st.authed) return "pass";
|
|
37036
|
-
if (st.pending) return "reject";
|
|
37037
36964
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
37038
36965
|
if (!parsed.success) {
|
|
37039
36966
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -37043,14 +36970,11 @@ var AuthGate = class {
|
|
|
37043
36970
|
}
|
|
37044
36971
|
let ctx = null;
|
|
37045
36972
|
if (this.opts.authenticate) {
|
|
37046
|
-
|
|
37047
|
-
|
|
37048
|
-
|
|
37049
|
-
|
|
37050
|
-
|
|
37051
|
-
st.pending = false;
|
|
37052
|
-
}
|
|
37053
|
-
if (!this.states.has(handle.id)) return "reject";
|
|
36973
|
+
const r = this.opts.authenticate(
|
|
36974
|
+
parsed.data.token,
|
|
36975
|
+
parsed.data.selfPrincipalId,
|
|
36976
|
+
parsed.data.selfDisplayName
|
|
36977
|
+
);
|
|
37054
36978
|
if (!r.ok) {
|
|
37055
36979
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
37056
36980
|
this.markFailed(handle.id);
|
|
@@ -37153,33 +37077,26 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
37153
37077
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
37154
37078
|
};
|
|
37155
37079
|
}
|
|
37156
|
-
function
|
|
37080
|
+
function guestContext(cap, peerOwnerPrincipalId) {
|
|
37157
37081
|
return {
|
|
37158
|
-
principal: { id:
|
|
37159
|
-
grants:
|
|
37160
|
-
|
|
37161
|
-
|
|
37162
|
-
})),
|
|
37163
|
-
ownerId: subOwner,
|
|
37164
|
-
provider
|
|
37082
|
+
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
37083
|
+
grants: cap.grants,
|
|
37084
|
+
capabilityId: cap.id,
|
|
37085
|
+
...peerOwnerPrincipalId ? { peerOwnerPrincipalId } : {}
|
|
37165
37086
|
};
|
|
37166
37087
|
}
|
|
37167
|
-
|
|
37088
|
+
function authenticate(token, deps) {
|
|
37168
37089
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
37169
37090
|
if (deps.isOwnerToken(token)) {
|
|
37170
37091
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
37171
37092
|
}
|
|
37172
|
-
if (!deps.
|
|
37173
|
-
const
|
|
37174
|
-
if (!
|
|
37175
|
-
if (
|
|
37176
|
-
|
|
37177
|
-
return { ok: false, code: "BAD_TOKEN" };
|
|
37093
|
+
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
37094
|
+
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
37095
|
+
if (!v2.ok) {
|
|
37096
|
+
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
37097
|
+
return { ok: false, code: v2.code };
|
|
37178
37098
|
}
|
|
37179
|
-
return {
|
|
37180
|
-
ok: true,
|
|
37181
|
-
context: connectGuestContext(result.subDevice, result.subOwner, result.provider, result.displayName)
|
|
37182
|
-
};
|
|
37099
|
+
return { ok: true, context: guestContext(v2.capability, deps.selfPrincipalId) };
|
|
37183
37100
|
}
|
|
37184
37101
|
|
|
37185
37102
|
// src/permission/capability-store.ts
|
|
@@ -37325,8 +37242,8 @@ var CapabilityManager = class {
|
|
|
37325
37242
|
list() {
|
|
37326
37243
|
return this.registry.list();
|
|
37327
37244
|
}
|
|
37328
|
-
//
|
|
37329
|
-
// 返回 Capability
|
|
37245
|
+
// whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
|
|
37246
|
+
// 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
|
|
37330
37247
|
findById(id) {
|
|
37331
37248
|
return this.registry.findById(id);
|
|
37332
37249
|
}
|
|
@@ -37359,14 +37276,89 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37359
37276
|
return { removed };
|
|
37360
37277
|
}
|
|
37361
37278
|
|
|
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
|
+
|
|
37362
37354
|
// src/inbox/inbox-store.ts
|
|
37363
|
-
var
|
|
37364
|
-
var
|
|
37355
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37356
|
+
var path22 = __toESM(require("path"), 1);
|
|
37365
37357
|
var INBOX_SUBDIR = "inbox";
|
|
37366
37358
|
var InboxStore = class {
|
|
37367
37359
|
constructor(dataDir) {
|
|
37368
37360
|
this.dataDir = dataDir;
|
|
37369
|
-
|
|
37361
|
+
fs20.mkdirSync(this.dirPath(), { recursive: true });
|
|
37370
37362
|
}
|
|
37371
37363
|
dataDir;
|
|
37372
37364
|
/**
|
|
@@ -37374,11 +37366,11 @@ var InboxStore = class {
|
|
|
37374
37366
|
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
37375
37367
|
* 文件不存在时返回 [] (不抛, 不创建).
|
|
37376
37368
|
*/
|
|
37377
|
-
list(
|
|
37378
|
-
const file = this.filePath(
|
|
37369
|
+
list(peerOwnerId, sinceCreatedAt) {
|
|
37370
|
+
const file = this.filePath(peerOwnerId);
|
|
37379
37371
|
let raw;
|
|
37380
37372
|
try {
|
|
37381
|
-
raw =
|
|
37373
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37382
37374
|
} catch (err) {
|
|
37383
37375
|
if (err?.code === "ENOENT") return [];
|
|
37384
37376
|
return [];
|
|
@@ -37388,13 +37380,13 @@ var InboxStore = class {
|
|
|
37388
37380
|
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
37389
37381
|
}
|
|
37390
37382
|
/**
|
|
37391
|
-
* 列出所有 peer 的
|
|
37383
|
+
* 列出所有 peer 的 peerOwnerId (扫目录所有 *.jsonl 文件名).
|
|
37392
37384
|
*/
|
|
37393
|
-
|
|
37385
|
+
listAllPeerOwnerIds() {
|
|
37394
37386
|
const dir = this.dirPath();
|
|
37395
37387
|
let entries;
|
|
37396
37388
|
try {
|
|
37397
|
-
entries =
|
|
37389
|
+
entries = fs20.readdirSync(dir);
|
|
37398
37390
|
} catch (err) {
|
|
37399
37391
|
if (err?.code === "ENOENT") return [];
|
|
37400
37392
|
return [];
|
|
@@ -37406,63 +37398,63 @@ var InboxStore = class {
|
|
|
37406
37398
|
* 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
|
|
37407
37399
|
*/
|
|
37408
37400
|
append(message) {
|
|
37409
|
-
const existing = this.list(message.
|
|
37401
|
+
const existing = this.list(message.peerOwnerId);
|
|
37410
37402
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37411
|
-
const file = this.filePath(message.
|
|
37403
|
+
const file = this.filePath(message.peerOwnerId);
|
|
37412
37404
|
const line = JSON.stringify(message) + "\n";
|
|
37413
|
-
|
|
37405
|
+
fs20.appendFileSync(file, line, { mode: 384 });
|
|
37414
37406
|
try {
|
|
37415
|
-
|
|
37407
|
+
fs20.chmodSync(file, 384);
|
|
37416
37408
|
} catch {
|
|
37417
37409
|
}
|
|
37418
37410
|
}
|
|
37419
37411
|
/**
|
|
37420
|
-
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=
|
|
37421
|
-
* readBy[
|
|
37412
|
+
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=principalId 的消息
|
|
37413
|
+
* readBy[principalId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
|
|
37422
37414
|
* 返写入了几条.
|
|
37423
37415
|
*/
|
|
37424
|
-
markRead(
|
|
37425
|
-
const messages = this.list(
|
|
37416
|
+
markRead(peerOwnerId, principalId, upToCreatedAt) {
|
|
37417
|
+
const messages = this.list(peerOwnerId);
|
|
37426
37418
|
let changed = 0;
|
|
37427
37419
|
const next = messages.map((m2) => {
|
|
37428
|
-
if (m2.
|
|
37420
|
+
if (m2.senderPrincipalId === principalId) return m2;
|
|
37429
37421
|
if (m2.createdAt > upToCreatedAt) return m2;
|
|
37430
|
-
const cur = m2.readBy[
|
|
37422
|
+
const cur = m2.readBy[principalId];
|
|
37431
37423
|
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
37432
37424
|
changed++;
|
|
37433
|
-
return { ...m2, readBy: { ...m2.readBy, [
|
|
37425
|
+
return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
|
|
37434
37426
|
});
|
|
37435
37427
|
if (changed === 0) return 0;
|
|
37436
|
-
this.rewriteFile(
|
|
37428
|
+
this.rewriteFile(peerOwnerId, next);
|
|
37437
37429
|
return changed;
|
|
37438
37430
|
}
|
|
37439
37431
|
/**
|
|
37440
37432
|
* 删整个 peer 的 jsonl (peer:remove cascade). 不存在 no-op.
|
|
37441
37433
|
*/
|
|
37442
|
-
|
|
37443
|
-
const file = this.filePath(
|
|
37434
|
+
removeByPeerOwnerId(peerOwnerId) {
|
|
37435
|
+
const file = this.filePath(peerOwnerId);
|
|
37444
37436
|
try {
|
|
37445
|
-
|
|
37437
|
+
fs20.unlinkSync(file);
|
|
37446
37438
|
} catch (err) {
|
|
37447
37439
|
if (err?.code === "ENOENT") return;
|
|
37448
37440
|
}
|
|
37449
37441
|
}
|
|
37450
|
-
rewriteFile(
|
|
37451
|
-
const file = this.filePath(
|
|
37442
|
+
rewriteFile(peerOwnerId, messages) {
|
|
37443
|
+
const file = this.filePath(peerOwnerId);
|
|
37452
37444
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37453
37445
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37454
|
-
|
|
37455
|
-
|
|
37446
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37447
|
+
fs20.renameSync(tmp, file);
|
|
37456
37448
|
try {
|
|
37457
|
-
|
|
37449
|
+
fs20.chmodSync(file, 384);
|
|
37458
37450
|
} catch {
|
|
37459
37451
|
}
|
|
37460
37452
|
}
|
|
37461
37453
|
dirPath() {
|
|
37462
|
-
return
|
|
37454
|
+
return path22.join(this.dataDir, INBOX_SUBDIR);
|
|
37463
37455
|
}
|
|
37464
|
-
filePath(
|
|
37465
|
-
return
|
|
37456
|
+
filePath(peerOwnerId) {
|
|
37457
|
+
return path22.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37466
37458
|
}
|
|
37467
37459
|
};
|
|
37468
37460
|
function parseAllLines(raw) {
|
|
@@ -37498,72 +37490,72 @@ var InboxManager = class {
|
|
|
37498
37490
|
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
37499
37491
|
*/
|
|
37500
37492
|
postMessage(args) {
|
|
37501
|
-
const beforeLen = this.store.list(args.
|
|
37493
|
+
const beforeLen = this.store.list(args.peerOwnerId).length;
|
|
37502
37494
|
const msg = {
|
|
37503
37495
|
id: args.id,
|
|
37504
|
-
|
|
37505
|
-
|
|
37496
|
+
peerOwnerId: args.peerOwnerId,
|
|
37497
|
+
senderPrincipalId: args.senderPrincipalId,
|
|
37506
37498
|
text: args.text,
|
|
37507
37499
|
createdAt: args.createdAt,
|
|
37508
37500
|
readBy: {}
|
|
37509
37501
|
};
|
|
37510
37502
|
this.store.append(msg);
|
|
37511
|
-
const afterLen = this.store.list(args.
|
|
37503
|
+
const afterLen = this.store.list(args.peerOwnerId).length;
|
|
37512
37504
|
if (afterLen === beforeLen) return msg;
|
|
37513
|
-
this.broadcast(args.
|
|
37505
|
+
this.broadcast(args.peerOwnerId, {
|
|
37514
37506
|
type: "inbox:event",
|
|
37515
|
-
|
|
37507
|
+
peerOwnerId: args.peerOwnerId,
|
|
37516
37508
|
message: msg
|
|
37517
37509
|
});
|
|
37518
37510
|
return msg;
|
|
37519
37511
|
}
|
|
37520
|
-
list(
|
|
37521
|
-
return this.store.list(
|
|
37512
|
+
list(peerOwnerId, sinceCreatedAt) {
|
|
37513
|
+
return this.store.list(peerOwnerId, sinceCreatedAt);
|
|
37522
37514
|
}
|
|
37523
37515
|
/**
|
|
37524
37516
|
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
37525
37517
|
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
|
|
37526
37518
|
*/
|
|
37527
37519
|
markRead(args) {
|
|
37528
|
-
const before = this.store.list(args.
|
|
37529
|
-
const changed = this.store.markRead(args.
|
|
37520
|
+
const before = this.store.list(args.peerOwnerId);
|
|
37521
|
+
const changed = this.store.markRead(args.peerOwnerId, args.principalId, args.upToCreatedAt);
|
|
37530
37522
|
if (changed === 0) return 0;
|
|
37531
|
-
const after = this.store.list(args.
|
|
37523
|
+
const after = this.store.list(args.peerOwnerId);
|
|
37532
37524
|
const beforeById = new Map(before.map((m2) => [m2.id, m2]));
|
|
37533
37525
|
for (const m2 of after) {
|
|
37534
37526
|
const prev = beforeById.get(m2.id);
|
|
37535
37527
|
if (!prev) continue;
|
|
37536
|
-
if (prev.readBy[args.
|
|
37537
|
-
this.broadcast(args.
|
|
37528
|
+
if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
|
|
37529
|
+
this.broadcast(args.peerOwnerId, {
|
|
37538
37530
|
type: "inbox:event",
|
|
37539
|
-
|
|
37531
|
+
peerOwnerId: args.peerOwnerId,
|
|
37540
37532
|
message: m2
|
|
37541
37533
|
});
|
|
37542
37534
|
}
|
|
37543
37535
|
return changed;
|
|
37544
37536
|
}
|
|
37545
37537
|
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
37546
|
-
removeChannel(
|
|
37547
|
-
this.store.
|
|
37538
|
+
removeChannel(peerOwnerId) {
|
|
37539
|
+
this.store.removeByPeerOwnerId(peerOwnerId);
|
|
37548
37540
|
}
|
|
37549
37541
|
};
|
|
37550
37542
|
|
|
37551
|
-
// src/state/
|
|
37552
|
-
var
|
|
37553
|
-
var
|
|
37554
|
-
var FILE_NAME = "
|
|
37555
|
-
var
|
|
37543
|
+
// src/state/received-capability-store.ts
|
|
37544
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37545
|
+
var path23 = __toESM(require("path"), 1);
|
|
37546
|
+
var FILE_NAME = "received-capabilities.json";
|
|
37547
|
+
var ReceivedCapabilityStore = class {
|
|
37556
37548
|
constructor(dataDir) {
|
|
37557
37549
|
this.dataDir = dataDir;
|
|
37558
37550
|
}
|
|
37559
37551
|
dataDir;
|
|
37560
|
-
|
|
37552
|
+
caps = /* @__PURE__ */ new Map();
|
|
37561
37553
|
load() {
|
|
37562
|
-
this.
|
|
37563
|
-
const file =
|
|
37554
|
+
this.caps.clear();
|
|
37555
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37564
37556
|
let raw;
|
|
37565
37557
|
try {
|
|
37566
|
-
raw =
|
|
37558
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
37567
37559
|
} catch (err) {
|
|
37568
37560
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37569
37561
|
return;
|
|
@@ -37575,58 +37567,52 @@ var ContactStore = class {
|
|
|
37575
37567
|
this.renameBak(file);
|
|
37576
37568
|
return;
|
|
37577
37569
|
}
|
|
37578
|
-
const arr = parsed?.
|
|
37570
|
+
const arr = parsed?.receivedCaps;
|
|
37579
37571
|
if (!Array.isArray(arr)) return;
|
|
37580
37572
|
for (const entry of arr) {
|
|
37581
|
-
const r =
|
|
37582
|
-
if (r.success) this.
|
|
37573
|
+
const r = ReceivedCapabilitySchema.safeParse(entry);
|
|
37574
|
+
if (r.success) this.caps.set(r.data.ownerPrincipalId, r.data);
|
|
37583
37575
|
}
|
|
37584
37576
|
}
|
|
37585
37577
|
list() {
|
|
37586
|
-
return Array.from(this.
|
|
37587
|
-
}
|
|
37588
|
-
get(deviceId) {
|
|
37589
|
-
return this.contacts.get(deviceId) ?? null;
|
|
37578
|
+
return Array.from(this.caps.values());
|
|
37590
37579
|
}
|
|
37591
|
-
|
|
37592
|
-
return this.
|
|
37580
|
+
get(ownerPrincipalId) {
|
|
37581
|
+
return this.caps.get(ownerPrincipalId) ?? null;
|
|
37593
37582
|
}
|
|
37594
|
-
|
|
37595
|
-
|
|
37596
|
-
return this.list().filter((c) => c.ownerId === ownerId && c.provider === provider);
|
|
37583
|
+
hasOwner(ownerPrincipalId) {
|
|
37584
|
+
return this.caps.has(ownerPrincipalId);
|
|
37597
37585
|
}
|
|
37598
|
-
upsert(
|
|
37599
|
-
this.
|
|
37586
|
+
upsert(cap) {
|
|
37587
|
+
this.caps.set(cap.ownerPrincipalId, cap);
|
|
37600
37588
|
this.flush();
|
|
37601
37589
|
}
|
|
37602
|
-
|
|
37603
|
-
|
|
37604
|
-
|
|
37605
|
-
if (existed) this.flush();
|
|
37606
|
-
return existed;
|
|
37590
|
+
remove(ownerPrincipalId) {
|
|
37591
|
+
if (!this.caps.delete(ownerPrincipalId)) return;
|
|
37592
|
+
this.flush();
|
|
37607
37593
|
}
|
|
37608
37594
|
flush() {
|
|
37609
|
-
const file =
|
|
37595
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37610
37596
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37611
37597
|
const content = JSON.stringify(
|
|
37612
|
-
{
|
|
37598
|
+
{ receivedCaps: Array.from(this.caps.values()) },
|
|
37613
37599
|
null,
|
|
37614
37600
|
2
|
|
37615
37601
|
);
|
|
37616
|
-
|
|
37617
|
-
|
|
37618
|
-
|
|
37602
|
+
fs21.mkdirSync(this.dataDir, { recursive: true });
|
|
37603
|
+
fs21.writeFileSync(tmp, content, { mode: 384 });
|
|
37604
|
+
fs21.renameSync(tmp, file);
|
|
37619
37605
|
}
|
|
37620
37606
|
renameBak(file) {
|
|
37621
37607
|
try {
|
|
37622
|
-
|
|
37608
|
+
fs21.renameSync(file, `${file}.bak`);
|
|
37623
37609
|
} catch {
|
|
37624
37610
|
}
|
|
37625
37611
|
}
|
|
37626
37612
|
};
|
|
37627
37613
|
|
|
37628
|
-
// src/
|
|
37629
|
-
var
|
|
37614
|
+
// src/received-capability/connect-remote.ts
|
|
37615
|
+
var crypto6 = __toESM(require("crypto"), 1);
|
|
37630
37616
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37631
37617
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37632
37618
|
async function connectRemote(args) {
|
|
@@ -37643,11 +37629,8 @@ async function connectRemote(args) {
|
|
|
37643
37629
|
JSON.stringify({
|
|
37644
37630
|
type: "auth",
|
|
37645
37631
|
token: args.token,
|
|
37646
|
-
|
|
37647
|
-
|
|
37648
|
-
// 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
|
|
37649
|
-
// 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
|
|
37650
|
-
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37632
|
+
selfPrincipalId: args.selfPrincipalId,
|
|
37633
|
+
selfDisplayName: args.selfDisplayName
|
|
37651
37634
|
})
|
|
37652
37635
|
);
|
|
37653
37636
|
await new Promise((resolve6, reject) => {
|
|
@@ -37658,7 +37641,7 @@ async function connectRemote(args) {
|
|
|
37658
37641
|
} catch {
|
|
37659
37642
|
return;
|
|
37660
37643
|
}
|
|
37661
|
-
if (f.type === "auth:ok") {
|
|
37644
|
+
if (f.type === "auth:ok" || f.type === "ready") {
|
|
37662
37645
|
ws.off("message", onMessage);
|
|
37663
37646
|
resolve6();
|
|
37664
37647
|
return;
|
|
@@ -37675,7 +37658,7 @@ async function connectRemote(args) {
|
|
|
37675
37658
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37676
37659
|
});
|
|
37677
37660
|
function call(method, payload) {
|
|
37678
|
-
const requestId =
|
|
37661
|
+
const requestId = crypto6.randomUUID();
|
|
37679
37662
|
return new Promise((resolve6, reject) => {
|
|
37680
37663
|
const onMessage = (raw) => {
|
|
37681
37664
|
let f;
|
|
@@ -37704,9 +37687,9 @@ async function connectRemote(args) {
|
|
|
37704
37687
|
whoami: async () => {
|
|
37705
37688
|
const f = await call("whoami", {});
|
|
37706
37689
|
return {
|
|
37707
|
-
ownerId: f.owner.
|
|
37708
|
-
provider: f.owner.provider,
|
|
37690
|
+
ownerId: f.owner.id,
|
|
37709
37691
|
displayName: f.owner.displayName,
|
|
37692
|
+
capabilityId: f.capability.id,
|
|
37710
37693
|
grants: f.capability.grants
|
|
37711
37694
|
};
|
|
37712
37695
|
},
|
|
@@ -37719,96 +37702,62 @@ async function connectRemote(args) {
|
|
|
37719
37702
|
};
|
|
37720
37703
|
}
|
|
37721
37704
|
|
|
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
|
-
|
|
37756
37705
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37757
|
-
var
|
|
37758
|
-
var
|
|
37706
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
37707
|
+
var path24 = __toESM(require("path"), 1);
|
|
37759
37708
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37760
37709
|
function migrateFlattenSessions(opts) {
|
|
37761
37710
|
const dataDir = opts.dataDir;
|
|
37762
37711
|
const now = opts.now ?? Date.now;
|
|
37763
|
-
const sessionsDir =
|
|
37764
|
-
const flagPath =
|
|
37712
|
+
const sessionsDir = path24.join(dataDir, "sessions");
|
|
37713
|
+
const flagPath = path24.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37765
37714
|
if (existsSync3(flagPath)) {
|
|
37766
37715
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37767
37716
|
}
|
|
37768
37717
|
let movedBare = 0;
|
|
37769
37718
|
let movedVmOwner = 0;
|
|
37770
37719
|
let archivedListener = 0;
|
|
37771
|
-
const defaultDir =
|
|
37720
|
+
const defaultDir = path24.join(sessionsDir, "default");
|
|
37772
37721
|
if (existsSync3(defaultDir)) {
|
|
37773
37722
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37774
37723
|
if (!entry.endsWith(".json")) continue;
|
|
37775
|
-
const src =
|
|
37776
|
-
const dst =
|
|
37777
|
-
|
|
37724
|
+
const src = path24.join(defaultDir, entry);
|
|
37725
|
+
const dst = path24.join(sessionsDir, entry);
|
|
37726
|
+
fs22.renameSync(src, dst);
|
|
37778
37727
|
movedBare += 1;
|
|
37779
37728
|
}
|
|
37780
37729
|
rmdirIfEmpty(defaultDir);
|
|
37781
37730
|
}
|
|
37782
37731
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37783
|
-
const personaDir =
|
|
37732
|
+
const personaDir = path24.join(sessionsDir, pid);
|
|
37784
37733
|
if (!isDir(personaDir)) continue;
|
|
37785
37734
|
if (pid === "default") continue;
|
|
37786
|
-
const ownerSrc =
|
|
37735
|
+
const ownerSrc = path24.join(personaDir, "owner");
|
|
37787
37736
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37788
|
-
const ownerDst =
|
|
37789
|
-
|
|
37737
|
+
const ownerDst = path24.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37738
|
+
fs22.mkdirSync(ownerDst, { recursive: true });
|
|
37790
37739
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37791
37740
|
if (!file.endsWith(".json")) continue;
|
|
37792
|
-
|
|
37741
|
+
fs22.renameSync(path24.join(ownerSrc, file), path24.join(ownerDst, file));
|
|
37793
37742
|
movedVmOwner += 1;
|
|
37794
37743
|
}
|
|
37795
37744
|
rmdirIfEmpty(ownerSrc);
|
|
37796
37745
|
}
|
|
37797
|
-
const listenerSrc =
|
|
37746
|
+
const listenerSrc = path24.join(personaDir, "listener");
|
|
37798
37747
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37799
|
-
const archiveDst =
|
|
37800
|
-
|
|
37748
|
+
const archiveDst = path24.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37749
|
+
fs22.mkdirSync(archiveDst, { recursive: true });
|
|
37801
37750
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37802
37751
|
if (!file.endsWith(".json")) continue;
|
|
37803
|
-
|
|
37752
|
+
fs22.renameSync(path24.join(listenerSrc, file), path24.join(archiveDst, file));
|
|
37804
37753
|
archivedListener += 1;
|
|
37805
37754
|
}
|
|
37806
37755
|
rmdirIfEmpty(listenerSrc);
|
|
37807
37756
|
}
|
|
37808
37757
|
rmdirIfEmpty(personaDir);
|
|
37809
37758
|
}
|
|
37810
|
-
|
|
37811
|
-
|
|
37759
|
+
fs22.mkdirSync(sessionsDir, { recursive: true });
|
|
37760
|
+
fs22.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37812
37761
|
return {
|
|
37813
37762
|
skipped: false,
|
|
37814
37763
|
flagWritten: true,
|
|
@@ -37819,7 +37768,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37819
37768
|
}
|
|
37820
37769
|
function existsSync3(p2) {
|
|
37821
37770
|
try {
|
|
37822
|
-
|
|
37771
|
+
fs22.statSync(p2);
|
|
37823
37772
|
return true;
|
|
37824
37773
|
} catch {
|
|
37825
37774
|
return false;
|
|
@@ -37827,31 +37776,31 @@ function existsSync3(p2) {
|
|
|
37827
37776
|
}
|
|
37828
37777
|
function isDir(p2) {
|
|
37829
37778
|
try {
|
|
37830
|
-
return
|
|
37779
|
+
return fs22.statSync(p2).isDirectory();
|
|
37831
37780
|
} catch {
|
|
37832
37781
|
return false;
|
|
37833
37782
|
}
|
|
37834
37783
|
}
|
|
37835
37784
|
function readdirSafe(p2) {
|
|
37836
37785
|
try {
|
|
37837
|
-
return
|
|
37786
|
+
return fs22.readdirSync(p2);
|
|
37838
37787
|
} catch {
|
|
37839
37788
|
return [];
|
|
37840
37789
|
}
|
|
37841
37790
|
}
|
|
37842
37791
|
function rmdirIfEmpty(p2) {
|
|
37843
37792
|
try {
|
|
37844
|
-
|
|
37793
|
+
fs22.rmdirSync(p2);
|
|
37845
37794
|
} catch {
|
|
37846
37795
|
}
|
|
37847
37796
|
}
|
|
37848
37797
|
|
|
37849
37798
|
// src/transport/http-router.ts
|
|
37850
|
-
var
|
|
37851
|
-
var
|
|
37799
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
37800
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
37852
37801
|
|
|
37853
37802
|
// src/attachment/mime.ts
|
|
37854
|
-
var
|
|
37803
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
37855
37804
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37856
37805
|
var EXT_TO_NATIVE_MIME = {
|
|
37857
37806
|
// 图片
|
|
@@ -37958,14 +37907,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37958
37907
|
".mk"
|
|
37959
37908
|
]);
|
|
37960
37909
|
function lookupMime(filePathOrName) {
|
|
37961
|
-
const ext =
|
|
37910
|
+
const ext = import_node_path17.default.extname(filePathOrName).toLowerCase();
|
|
37962
37911
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37963
37912
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37964
37913
|
return "application/octet-stream";
|
|
37965
37914
|
}
|
|
37966
37915
|
|
|
37967
37916
|
// src/attachment/sign-url.ts
|
|
37968
|
-
var
|
|
37917
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
37969
37918
|
var HMAC_ALGO = "sha256";
|
|
37970
37919
|
function base64urlEncode(buf) {
|
|
37971
37920
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37982,7 +37931,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37982
37931
|
}
|
|
37983
37932
|
function computeSig(secret, absPath, e) {
|
|
37984
37933
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37985
|
-
return
|
|
37934
|
+
return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37986
37935
|
}
|
|
37987
37936
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37988
37937
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -38017,7 +37966,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
38017
37966
|
if (provided.length !== expected.length) {
|
|
38018
37967
|
return { ok: false, code: "BAD_SIG" };
|
|
38019
37968
|
}
|
|
38020
|
-
if (!
|
|
37969
|
+
if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
|
|
38021
37970
|
return { ok: false, code: "BAD_SIG" };
|
|
38022
37971
|
}
|
|
38023
37972
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -38027,9 +37976,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
38027
37976
|
}
|
|
38028
37977
|
|
|
38029
37978
|
// src/attachment/upload.ts
|
|
38030
|
-
var
|
|
38031
|
-
var
|
|
38032
|
-
var
|
|
37979
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37980
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
37981
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
38033
37982
|
var import_promises = require("stream/promises");
|
|
38034
37983
|
var UploadError = class extends Error {
|
|
38035
37984
|
constructor(code, message) {
|
|
@@ -38040,24 +37989,24 @@ var UploadError = class extends Error {
|
|
|
38040
37989
|
code;
|
|
38041
37990
|
};
|
|
38042
37991
|
function assertValidFileName(fileName) {
|
|
38043
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
37992
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path18.default.basename(fileName)) {
|
|
38044
37993
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
38045
37994
|
}
|
|
38046
37995
|
}
|
|
38047
37996
|
var HASH_PREFIX_LEN = 16;
|
|
38048
37997
|
async function writeUploadedAttachment(args) {
|
|
38049
37998
|
assertValidFileName(args.fileName);
|
|
38050
|
-
const attachmentsRoot =
|
|
37999
|
+
const attachmentsRoot = import_node_path18.default.join(args.sessionDir, ".attachments");
|
|
38051
38000
|
try {
|
|
38052
|
-
|
|
38001
|
+
import_node_fs16.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
38053
38002
|
} catch (err) {
|
|
38054
38003
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
38055
38004
|
}
|
|
38056
|
-
const hasher =
|
|
38005
|
+
const hasher = import_node_crypto6.default.createHash("sha256");
|
|
38057
38006
|
let actualSize = 0;
|
|
38058
|
-
const tmpPath =
|
|
38007
|
+
const tmpPath = import_node_path18.default.join(
|
|
38059
38008
|
attachmentsRoot,
|
|
38060
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
38009
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
|
|
38061
38010
|
);
|
|
38062
38011
|
try {
|
|
38063
38012
|
await (0, import_promises.pipeline)(
|
|
@@ -38070,18 +38019,18 @@ async function writeUploadedAttachment(args) {
|
|
|
38070
38019
|
yield buf;
|
|
38071
38020
|
}
|
|
38072
38021
|
},
|
|
38073
|
-
|
|
38022
|
+
import_node_fs16.default.createWriteStream(tmpPath, { mode: 384 })
|
|
38074
38023
|
);
|
|
38075
38024
|
} catch (err) {
|
|
38076
38025
|
try {
|
|
38077
|
-
|
|
38026
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38078
38027
|
} catch {
|
|
38079
38028
|
}
|
|
38080
38029
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
38081
38030
|
}
|
|
38082
38031
|
if (actualSize !== args.contentLength) {
|
|
38083
38032
|
try {
|
|
38084
|
-
|
|
38033
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38085
38034
|
} catch {
|
|
38086
38035
|
}
|
|
38087
38036
|
throw new UploadError(
|
|
@@ -38090,35 +38039,35 @@ async function writeUploadedAttachment(args) {
|
|
|
38090
38039
|
);
|
|
38091
38040
|
}
|
|
38092
38041
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
38093
|
-
const hashDir =
|
|
38042
|
+
const hashDir = import_node_path18.default.join(attachmentsRoot, attachmentId);
|
|
38094
38043
|
let finalFileName;
|
|
38095
38044
|
let hashDirExists = false;
|
|
38096
38045
|
try {
|
|
38097
|
-
hashDirExists =
|
|
38046
|
+
hashDirExists = import_node_fs16.default.statSync(hashDir).isDirectory();
|
|
38098
38047
|
} catch {
|
|
38099
38048
|
}
|
|
38100
38049
|
if (hashDirExists) {
|
|
38101
|
-
const existing =
|
|
38050
|
+
const existing = import_node_fs16.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
38102
38051
|
finalFileName = existing[0] ?? args.fileName;
|
|
38103
38052
|
try {
|
|
38104
|
-
|
|
38053
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38105
38054
|
} catch {
|
|
38106
38055
|
}
|
|
38107
38056
|
} else {
|
|
38108
38057
|
try {
|
|
38109
|
-
|
|
38058
|
+
import_node_fs16.default.mkdirSync(hashDir, { recursive: true });
|
|
38110
38059
|
finalFileName = args.fileName;
|
|
38111
|
-
|
|
38060
|
+
import_node_fs16.default.renameSync(tmpPath, import_node_path18.default.join(hashDir, finalFileName));
|
|
38112
38061
|
} catch (err) {
|
|
38113
38062
|
try {
|
|
38114
|
-
|
|
38063
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38115
38064
|
} catch {
|
|
38116
38065
|
}
|
|
38117
38066
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
38118
38067
|
}
|
|
38119
38068
|
}
|
|
38120
|
-
const absPath =
|
|
38121
|
-
const relPath =
|
|
38069
|
+
const absPath = import_node_path18.default.join(hashDir, finalFileName);
|
|
38070
|
+
const relPath = import_node_path18.default.relative(args.sessionDir, absPath);
|
|
38122
38071
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
38123
38072
|
relPath: absPath,
|
|
38124
38073
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -38132,7 +38081,7 @@ async function writeUploadedAttachment(args) {
|
|
|
38132
38081
|
|
|
38133
38082
|
// src/extension/import.ts
|
|
38134
38083
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
38135
|
-
var
|
|
38084
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
38136
38085
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
38137
38086
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
38138
38087
|
var ImportError = class extends Error {
|
|
@@ -38150,7 +38099,7 @@ async function importZip(buf, root) {
|
|
|
38150
38099
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
38151
38100
|
}
|
|
38152
38101
|
for (const name of Object.keys(zip.files)) {
|
|
38153
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
38102
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path19.default.isAbsolute(name)) {
|
|
38154
38103
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
38155
38104
|
}
|
|
38156
38105
|
}
|
|
@@ -38183,7 +38132,7 @@ async function importZip(buf, root) {
|
|
|
38183
38132
|
);
|
|
38184
38133
|
}
|
|
38185
38134
|
}
|
|
38186
|
-
const destDir =
|
|
38135
|
+
const destDir = import_node_path19.default.join(root, manifest.id);
|
|
38187
38136
|
let destExists = false;
|
|
38188
38137
|
try {
|
|
38189
38138
|
await import_promises2.default.access(destDir);
|
|
@@ -38193,15 +38142,15 @@ async function importZip(buf, root) {
|
|
|
38193
38142
|
if (destExists) {
|
|
38194
38143
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
38195
38144
|
}
|
|
38196
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
38145
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path19.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
38197
38146
|
try {
|
|
38198
38147
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38199
|
-
const dest =
|
|
38148
|
+
const dest = import_node_path19.default.join(stage, name);
|
|
38200
38149
|
if (entry.dir) {
|
|
38201
38150
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38202
38151
|
continue;
|
|
38203
38152
|
}
|
|
38204
|
-
await import_promises2.default.mkdir(
|
|
38153
|
+
await import_promises2.default.mkdir(import_node_path19.default.dirname(dest), { recursive: true });
|
|
38205
38154
|
const content = await entry.async("nodebuffer");
|
|
38206
38155
|
await import_promises2.default.writeFile(dest, content);
|
|
38207
38156
|
}
|
|
@@ -38315,7 +38264,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38315
38264
|
if (fileName === "." || fileName === "..") return false;
|
|
38316
38265
|
if (fileName.startsWith(".")) return false;
|
|
38317
38266
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38318
|
-
return fileName ===
|
|
38267
|
+
return fileName === import_node_path20.default.basename(fileName);
|
|
38319
38268
|
}
|
|
38320
38269
|
function createHttpRouter(deps) {
|
|
38321
38270
|
return async (req, res) => {
|
|
@@ -38330,25 +38279,6 @@ function createHttpRouter(deps) {
|
|
|
38330
38279
|
});
|
|
38331
38280
|
return true;
|
|
38332
38281
|
}
|
|
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
|
-
}
|
|
38352
38282
|
if (url.pathname.startsWith(RPC_ROUTE_PREFIX)) {
|
|
38353
38283
|
if (!deps.rpcDispatcher) {
|
|
38354
38284
|
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "HTTP RPC adapter not wired" });
|
|
@@ -38544,7 +38474,7 @@ function createHttpRouter(deps) {
|
|
|
38544
38474
|
return true;
|
|
38545
38475
|
}
|
|
38546
38476
|
let absPath;
|
|
38547
|
-
if (
|
|
38477
|
+
if (import_node_path20.default.isAbsolute(pathParam)) {
|
|
38548
38478
|
absPath = pathParam;
|
|
38549
38479
|
} else if (deps.sessionStore) {
|
|
38550
38480
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38552,7 +38482,7 @@ function createHttpRouter(deps) {
|
|
|
38552
38482
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38553
38483
|
return true;
|
|
38554
38484
|
}
|
|
38555
|
-
absPath =
|
|
38485
|
+
absPath = import_node_path20.default.join(file.cwd, pathParam);
|
|
38556
38486
|
} else {
|
|
38557
38487
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38558
38488
|
return true;
|
|
@@ -38619,9 +38549,6 @@ function parseUrl(rawUrl) {
|
|
|
38619
38549
|
return null;
|
|
38620
38550
|
}
|
|
38621
38551
|
}
|
|
38622
|
-
function escapeHtml(s) {
|
|
38623
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38624
|
-
}
|
|
38625
38552
|
function sendJson(res, status, body, extraHeaders) {
|
|
38626
38553
|
res.writeHead(status, {
|
|
38627
38554
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38635,7 +38562,7 @@ function withCtx(ctx, body) {
|
|
|
38635
38562
|
function streamFile(res, absPath, logger) {
|
|
38636
38563
|
let stat;
|
|
38637
38564
|
try {
|
|
38638
|
-
stat =
|
|
38565
|
+
stat = import_node_fs17.default.statSync(absPath);
|
|
38639
38566
|
} catch (err) {
|
|
38640
38567
|
const code = err?.code;
|
|
38641
38568
|
if (code === "ENOENT") {
|
|
@@ -38650,7 +38577,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38650
38577
|
return;
|
|
38651
38578
|
}
|
|
38652
38579
|
const mime = lookupMime(absPath);
|
|
38653
|
-
const basename =
|
|
38580
|
+
const basename = import_node_path20.default.basename(absPath);
|
|
38654
38581
|
res.writeHead(200, {
|
|
38655
38582
|
"Content-Type": mime,
|
|
38656
38583
|
"Content-Length": String(stat.size),
|
|
@@ -38658,7 +38585,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38658
38585
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38659
38586
|
"X-Content-Type-Options": "nosniff"
|
|
38660
38587
|
});
|
|
38661
|
-
const stream =
|
|
38588
|
+
const stream = import_node_fs17.default.createReadStream(absPath);
|
|
38662
38589
|
stream.on("error", (err) => {
|
|
38663
38590
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38664
38591
|
res.destroy();
|
|
@@ -38667,8 +38594,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38667
38594
|
}
|
|
38668
38595
|
|
|
38669
38596
|
// src/attachment/gc.ts
|
|
38670
|
-
var
|
|
38671
|
-
var
|
|
38597
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38598
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38672
38599
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38673
38600
|
function runAttachmentGc(args) {
|
|
38674
38601
|
const now = (args.now ?? Date.now)();
|
|
@@ -38677,38 +38604,38 @@ function runAttachmentGc(args) {
|
|
|
38677
38604
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38678
38605
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38679
38606
|
if (entry.stale) continue;
|
|
38680
|
-
if (
|
|
38607
|
+
if (import_node_path21.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38681
38608
|
}
|
|
38682
38609
|
}
|
|
38683
38610
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38684
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38611
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path21.default.join(
|
|
38685
38612
|
args.dataDir,
|
|
38686
38613
|
"sessions",
|
|
38687
38614
|
...scopeSubPath(scope).map(safeFileName),
|
|
38688
38615
|
safeFileName(sessionId)
|
|
38689
38616
|
);
|
|
38690
|
-
const attRoot =
|
|
38617
|
+
const attRoot = import_node_path21.default.join(sessionDir, ".attachments");
|
|
38691
38618
|
let hashDirs;
|
|
38692
38619
|
try {
|
|
38693
|
-
hashDirs =
|
|
38620
|
+
hashDirs = import_node_fs18.default.readdirSync(attRoot);
|
|
38694
38621
|
} catch (err) {
|
|
38695
38622
|
if (err.code === "ENOENT") continue;
|
|
38696
38623
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38697
38624
|
continue;
|
|
38698
38625
|
}
|
|
38699
38626
|
for (const hashDir of hashDirs) {
|
|
38700
|
-
const hashDirAbs =
|
|
38627
|
+
const hashDirAbs = import_node_path21.default.join(attRoot, hashDir);
|
|
38701
38628
|
let files;
|
|
38702
38629
|
try {
|
|
38703
|
-
files =
|
|
38630
|
+
files = import_node_fs18.default.readdirSync(hashDirAbs);
|
|
38704
38631
|
} catch {
|
|
38705
38632
|
continue;
|
|
38706
38633
|
}
|
|
38707
38634
|
for (const name of files) {
|
|
38708
|
-
const file =
|
|
38635
|
+
const file = import_node_path21.default.join(hashDirAbs, name);
|
|
38709
38636
|
let stat;
|
|
38710
38637
|
try {
|
|
38711
|
-
stat =
|
|
38638
|
+
stat = import_node_fs18.default.statSync(file);
|
|
38712
38639
|
} catch {
|
|
38713
38640
|
continue;
|
|
38714
38641
|
}
|
|
@@ -38717,27 +38644,27 @@ function runAttachmentGc(args) {
|
|
|
38717
38644
|
if (age < ttlMs) continue;
|
|
38718
38645
|
if (liveAbs.has(file)) continue;
|
|
38719
38646
|
try {
|
|
38720
|
-
|
|
38647
|
+
import_node_fs18.default.unlinkSync(file);
|
|
38721
38648
|
} catch (err) {
|
|
38722
38649
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38723
38650
|
}
|
|
38724
38651
|
}
|
|
38725
38652
|
try {
|
|
38726
|
-
if (
|
|
38653
|
+
if (import_node_fs18.default.readdirSync(hashDirAbs).length === 0) import_node_fs18.default.rmdirSync(hashDirAbs);
|
|
38727
38654
|
} catch {
|
|
38728
38655
|
}
|
|
38729
38656
|
}
|
|
38730
38657
|
try {
|
|
38731
|
-
if (
|
|
38658
|
+
if (import_node_fs18.default.readdirSync(attRoot).length === 0) import_node_fs18.default.rmdirSync(attRoot);
|
|
38732
38659
|
} catch {
|
|
38733
38660
|
}
|
|
38734
38661
|
}
|
|
38735
38662
|
}
|
|
38736
38663
|
|
|
38737
38664
|
// src/attachment/group.ts
|
|
38738
|
-
var
|
|
38739
|
-
var
|
|
38740
|
-
var
|
|
38665
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38666
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38667
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38741
38668
|
init_protocol();
|
|
38742
38669
|
var GroupFileStore = class {
|
|
38743
38670
|
dataDir;
|
|
@@ -38748,11 +38675,11 @@ var GroupFileStore = class {
|
|
|
38748
38675
|
this.logger = opts.logger;
|
|
38749
38676
|
}
|
|
38750
38677
|
rootForScope(scope) {
|
|
38751
|
-
return
|
|
38678
|
+
return import_node_path22.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38752
38679
|
}
|
|
38753
38680
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38754
38681
|
filePath(scope, sessionId) {
|
|
38755
|
-
return
|
|
38682
|
+
return import_node_path22.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38756
38683
|
}
|
|
38757
38684
|
cacheKey(scope, sessionId) {
|
|
38758
38685
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38761,7 +38688,7 @@ var GroupFileStore = class {
|
|
|
38761
38688
|
readFile(scope, sessionId) {
|
|
38762
38689
|
const file = this.filePath(scope, sessionId);
|
|
38763
38690
|
try {
|
|
38764
|
-
const raw =
|
|
38691
|
+
const raw = import_node_fs19.default.readFileSync(file, "utf8");
|
|
38765
38692
|
const parsed = JSON.parse(raw);
|
|
38766
38693
|
if (!Array.isArray(parsed)) {
|
|
38767
38694
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38787,10 +38714,10 @@ var GroupFileStore = class {
|
|
|
38787
38714
|
}
|
|
38788
38715
|
writeFile(scope, sessionId, entries) {
|
|
38789
38716
|
const file = this.filePath(scope, sessionId);
|
|
38790
|
-
|
|
38717
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true });
|
|
38791
38718
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38792
|
-
|
|
38793
|
-
|
|
38719
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38720
|
+
import_node_fs19.default.renameSync(tmp, file);
|
|
38794
38721
|
}
|
|
38795
38722
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38796
38723
|
list(scope, sessionId) {
|
|
@@ -38826,7 +38753,7 @@ var GroupFileStore = class {
|
|
|
38826
38753
|
entries[idx] = next;
|
|
38827
38754
|
} else {
|
|
38828
38755
|
next = {
|
|
38829
|
-
id: `gf-${
|
|
38756
|
+
id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
|
|
38830
38757
|
relPath: input.relPath,
|
|
38831
38758
|
from: input.from,
|
|
38832
38759
|
label: input.label,
|
|
@@ -38876,10 +38803,10 @@ var GroupFileStore = class {
|
|
|
38876
38803
|
};
|
|
38877
38804
|
|
|
38878
38805
|
// src/discovery/state-file.ts
|
|
38879
|
-
var
|
|
38880
|
-
var
|
|
38806
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38807
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38881
38808
|
function defaultStateFilePath(dataDir) {
|
|
38882
|
-
return
|
|
38809
|
+
return import_node_path23.default.join(dataDir, "state.json");
|
|
38883
38810
|
}
|
|
38884
38811
|
function isPidAlive(pid) {
|
|
38885
38812
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38901,7 +38828,7 @@ var StateFileManager = class {
|
|
|
38901
38828
|
}
|
|
38902
38829
|
read() {
|
|
38903
38830
|
try {
|
|
38904
|
-
const raw =
|
|
38831
|
+
const raw = import_node_fs20.default.readFileSync(this.file, "utf8");
|
|
38905
38832
|
const parsed = JSON.parse(raw);
|
|
38906
38833
|
return parsed;
|
|
38907
38834
|
} catch {
|
|
@@ -38915,34 +38842,34 @@ var StateFileManager = class {
|
|
|
38915
38842
|
return { status: "stale", existing };
|
|
38916
38843
|
}
|
|
38917
38844
|
write(state) {
|
|
38918
|
-
|
|
38845
|
+
import_node_fs20.default.mkdirSync(import_node_path23.default.dirname(this.file), { recursive: true });
|
|
38919
38846
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38920
|
-
|
|
38921
|
-
|
|
38847
|
+
import_node_fs20.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38848
|
+
import_node_fs20.default.renameSync(tmp, this.file);
|
|
38922
38849
|
if (process.platform !== "win32") {
|
|
38923
38850
|
try {
|
|
38924
|
-
|
|
38851
|
+
import_node_fs20.default.chmodSync(this.file, 384);
|
|
38925
38852
|
} catch {
|
|
38926
38853
|
}
|
|
38927
38854
|
}
|
|
38928
38855
|
}
|
|
38929
38856
|
delete() {
|
|
38930
38857
|
try {
|
|
38931
|
-
|
|
38858
|
+
import_node_fs20.default.unlinkSync(this.file);
|
|
38932
38859
|
} catch {
|
|
38933
38860
|
}
|
|
38934
38861
|
}
|
|
38935
38862
|
};
|
|
38936
38863
|
|
|
38937
38864
|
// src/tunnel/tunnel-manager.ts
|
|
38938
|
-
var
|
|
38939
|
-
var
|
|
38940
|
-
var
|
|
38865
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
38866
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
38867
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
38941
38868
|
var import_node_child_process5 = require("child_process");
|
|
38942
38869
|
|
|
38943
38870
|
// src/tunnel/tunnel-store.ts
|
|
38944
|
-
var
|
|
38945
|
-
var
|
|
38871
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38872
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
38946
38873
|
var TunnelStore = class {
|
|
38947
38874
|
constructor(filePath) {
|
|
38948
38875
|
this.filePath = filePath;
|
|
@@ -38950,7 +38877,7 @@ var TunnelStore = class {
|
|
|
38950
38877
|
filePath;
|
|
38951
38878
|
async get() {
|
|
38952
38879
|
try {
|
|
38953
|
-
const raw = await
|
|
38880
|
+
const raw = await import_node_fs21.default.promises.readFile(this.filePath, "utf8");
|
|
38954
38881
|
const obj = JSON.parse(raw);
|
|
38955
38882
|
if (!isPersistedTunnel(obj)) return null;
|
|
38956
38883
|
return obj;
|
|
@@ -38961,22 +38888,22 @@ var TunnelStore = class {
|
|
|
38961
38888
|
}
|
|
38962
38889
|
}
|
|
38963
38890
|
async set(v2) {
|
|
38964
|
-
const dir =
|
|
38965
|
-
await
|
|
38891
|
+
const dir = import_node_path24.default.dirname(this.filePath);
|
|
38892
|
+
await import_node_fs21.default.promises.mkdir(dir, { recursive: true });
|
|
38966
38893
|
const data = JSON.stringify(v2, null, 2);
|
|
38967
38894
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38968
|
-
await
|
|
38895
|
+
await import_node_fs21.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38969
38896
|
if (process.platform !== "win32") {
|
|
38970
38897
|
try {
|
|
38971
|
-
await
|
|
38898
|
+
await import_node_fs21.default.promises.chmod(tmp, 384);
|
|
38972
38899
|
} catch {
|
|
38973
38900
|
}
|
|
38974
38901
|
}
|
|
38975
|
-
await
|
|
38902
|
+
await import_node_fs21.default.promises.rename(tmp, this.filePath);
|
|
38976
38903
|
}
|
|
38977
38904
|
async clear() {
|
|
38978
38905
|
try {
|
|
38979
|
-
await
|
|
38906
|
+
await import_node_fs21.default.promises.unlink(this.filePath);
|
|
38980
38907
|
} catch (err) {
|
|
38981
38908
|
const code = err?.code;
|
|
38982
38909
|
if (code !== "ENOENT") throw err;
|
|
@@ -39071,9 +38998,9 @@ function escape(v2) {
|
|
|
39071
38998
|
}
|
|
39072
38999
|
|
|
39073
39000
|
// src/tunnel/frpc-binary.ts
|
|
39074
|
-
var
|
|
39001
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
39075
39002
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
39076
|
-
var
|
|
39003
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
39077
39004
|
var import_node_child_process3 = require("child_process");
|
|
39078
39005
|
var import_node_stream2 = require("stream");
|
|
39079
39006
|
var import_promises3 = require("stream/promises");
|
|
@@ -39105,20 +39032,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
39105
39032
|
}
|
|
39106
39033
|
async function ensureFrpcBinary(opts) {
|
|
39107
39034
|
if (opts.override) {
|
|
39108
|
-
if (!
|
|
39035
|
+
if (!import_node_fs22.default.existsSync(opts.override)) {
|
|
39109
39036
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
39110
39037
|
}
|
|
39111
39038
|
return opts.override;
|
|
39112
39039
|
}
|
|
39113
39040
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
39114
39041
|
const platform = opts.platform ?? detectPlatform();
|
|
39115
|
-
const binDir =
|
|
39116
|
-
|
|
39042
|
+
const binDir = import_node_path25.default.join(opts.dataDir, "bin");
|
|
39043
|
+
import_node_fs22.default.mkdirSync(binDir, { recursive: true });
|
|
39117
39044
|
cleanupStaleArtifacts(binDir);
|
|
39118
|
-
const stableBin =
|
|
39119
|
-
if (
|
|
39045
|
+
const stableBin = import_node_path25.default.join(binDir, "frpc");
|
|
39046
|
+
if (import_node_fs22.default.existsSync(stableBin)) return stableBin;
|
|
39120
39047
|
const partialBin = `${stableBin}.partial`;
|
|
39121
|
-
const tarballPath =
|
|
39048
|
+
const tarballPath = import_node_path25.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
39122
39049
|
try {
|
|
39123
39050
|
const url = frpcDownloadUrl(version2, platform);
|
|
39124
39051
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -39127,8 +39054,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
39127
39054
|
} else {
|
|
39128
39055
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
39129
39056
|
}
|
|
39130
|
-
|
|
39131
|
-
|
|
39057
|
+
import_node_fs22.default.chmodSync(partialBin, 493);
|
|
39058
|
+
import_node_fs22.default.renameSync(partialBin, stableBin);
|
|
39132
39059
|
} finally {
|
|
39133
39060
|
safeUnlink(tarballPath);
|
|
39134
39061
|
safeUnlink(partialBin);
|
|
@@ -39138,15 +39065,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
39138
39065
|
function cleanupStaleArtifacts(binDir) {
|
|
39139
39066
|
let entries;
|
|
39140
39067
|
try {
|
|
39141
|
-
entries =
|
|
39068
|
+
entries = import_node_fs22.default.readdirSync(binDir);
|
|
39142
39069
|
} catch {
|
|
39143
39070
|
return;
|
|
39144
39071
|
}
|
|
39145
39072
|
for (const name of entries) {
|
|
39146
39073
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
39147
|
-
const full =
|
|
39074
|
+
const full = import_node_path25.default.join(binDir, name);
|
|
39148
39075
|
try {
|
|
39149
|
-
|
|
39076
|
+
import_node_fs22.default.rmSync(full, { recursive: true, force: true });
|
|
39150
39077
|
} catch {
|
|
39151
39078
|
}
|
|
39152
39079
|
}
|
|
@@ -39154,7 +39081,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
39154
39081
|
}
|
|
39155
39082
|
function safeUnlink(p2) {
|
|
39156
39083
|
try {
|
|
39157
|
-
|
|
39084
|
+
import_node_fs22.default.unlinkSync(p2);
|
|
39158
39085
|
} catch {
|
|
39159
39086
|
}
|
|
39160
39087
|
}
|
|
@@ -39165,13 +39092,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
39165
39092
|
if (!res.ok || !res.body) {
|
|
39166
39093
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
39167
39094
|
}
|
|
39168
|
-
const out =
|
|
39095
|
+
const out = import_node_fs22.default.createWriteStream(dest);
|
|
39169
39096
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
39170
39097
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
39171
39098
|
}
|
|
39172
39099
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
39173
|
-
const work =
|
|
39174
|
-
|
|
39100
|
+
const work = import_node_path25.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
39101
|
+
import_node_fs22.default.mkdirSync(work, { recursive: true });
|
|
39175
39102
|
try {
|
|
39176
39103
|
await new Promise((resolve6, reject) => {
|
|
39177
39104
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -39179,32 +39106,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
39179
39106
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
39180
39107
|
});
|
|
39181
39108
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
39182
|
-
const src =
|
|
39183
|
-
if (!
|
|
39109
|
+
const src = import_node_path25.default.join(work, dirName, "frpc");
|
|
39110
|
+
if (!import_node_fs22.default.existsSync(src)) {
|
|
39184
39111
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
39185
39112
|
}
|
|
39186
|
-
|
|
39113
|
+
import_node_fs22.default.copyFileSync(src, destBin);
|
|
39187
39114
|
} finally {
|
|
39188
|
-
|
|
39115
|
+
import_node_fs22.default.rmSync(work, { recursive: true, force: true });
|
|
39189
39116
|
}
|
|
39190
39117
|
}
|
|
39191
39118
|
|
|
39192
39119
|
// src/tunnel/frpc-process.ts
|
|
39193
|
-
var
|
|
39194
|
-
var
|
|
39120
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
39121
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
39195
39122
|
var import_node_child_process4 = require("child_process");
|
|
39196
39123
|
function frpcPidFilePath(dataDir) {
|
|
39197
|
-
return
|
|
39124
|
+
return import_node_path26.default.join(dataDir, "frpc.pid");
|
|
39198
39125
|
}
|
|
39199
39126
|
function writeFrpcPid(dataDir, pid) {
|
|
39200
39127
|
try {
|
|
39201
|
-
|
|
39128
|
+
import_node_fs23.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
39202
39129
|
} catch {
|
|
39203
39130
|
}
|
|
39204
39131
|
}
|
|
39205
39132
|
function clearFrpcPid(dataDir) {
|
|
39206
39133
|
try {
|
|
39207
|
-
|
|
39134
|
+
import_node_fs23.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
39208
39135
|
} catch {
|
|
39209
39136
|
}
|
|
39210
39137
|
}
|
|
@@ -39220,7 +39147,7 @@ function defaultIsPidAlive(pid) {
|
|
|
39220
39147
|
}
|
|
39221
39148
|
function defaultReadPidFile(file) {
|
|
39222
39149
|
try {
|
|
39223
|
-
return
|
|
39150
|
+
return import_node_fs23.default.readFileSync(file, "utf8");
|
|
39224
39151
|
} catch {
|
|
39225
39152
|
return null;
|
|
39226
39153
|
}
|
|
@@ -39236,7 +39163,7 @@ function defaultSleep(ms) {
|
|
|
39236
39163
|
}
|
|
39237
39164
|
async function killStaleFrpc(deps) {
|
|
39238
39165
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
39239
|
-
const tomlPath =
|
|
39166
|
+
const tomlPath = import_node_path26.default.join(deps.dataDir, "frpc.toml");
|
|
39240
39167
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
39241
39168
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
39242
39169
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -39260,7 +39187,7 @@ async function killStaleFrpc(deps) {
|
|
|
39260
39187
|
}
|
|
39261
39188
|
if (victims.size === 0) {
|
|
39262
39189
|
try {
|
|
39263
|
-
|
|
39190
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
39264
39191
|
} catch {
|
|
39265
39192
|
}
|
|
39266
39193
|
return;
|
|
@@ -39271,7 +39198,7 @@ async function killStaleFrpc(deps) {
|
|
|
39271
39198
|
}
|
|
39272
39199
|
await sleep(deps.reapWaitMs ?? 300);
|
|
39273
39200
|
try {
|
|
39274
|
-
|
|
39201
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
39275
39202
|
} catch {
|
|
39276
39203
|
}
|
|
39277
39204
|
}
|
|
@@ -39308,7 +39235,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
39308
39235
|
var TunnelManager = class {
|
|
39309
39236
|
constructor(deps) {
|
|
39310
39237
|
this.deps = deps;
|
|
39311
|
-
this.store = deps.store ?? new TunnelStore(
|
|
39238
|
+
this.store = deps.store ?? new TunnelStore(import_node_path27.default.join(deps.dataDir, "tunnel.json"));
|
|
39312
39239
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39313
39240
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39314
39241
|
}
|
|
@@ -39435,8 +39362,8 @@ var TunnelManager = class {
|
|
|
39435
39362
|
dataDir: this.deps.dataDir,
|
|
39436
39363
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39437
39364
|
});
|
|
39438
|
-
const tomlPath =
|
|
39439
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39365
|
+
const tomlPath = import_node_path27.default.join(this.deps.dataDir, "frpc.toml");
|
|
39366
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
|
|
39440
39367
|
const toml = buildFrpcToml({
|
|
39441
39368
|
serverAddr: t.frpsHost,
|
|
39442
39369
|
serverPort: t.frpsPort,
|
|
@@ -39446,12 +39373,12 @@ var TunnelManager = class {
|
|
|
39446
39373
|
localPort,
|
|
39447
39374
|
logLevel: "info"
|
|
39448
39375
|
});
|
|
39449
|
-
await
|
|
39376
|
+
await import_node_fs24.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39450
39377
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39451
39378
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39452
39379
|
});
|
|
39453
|
-
const logFilePath =
|
|
39454
|
-
const logStream =
|
|
39380
|
+
const logFilePath = import_node_path27.default.join(this.deps.dataDir, "frpc.log");
|
|
39381
|
+
const logStream = import_node_fs24.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39455
39382
|
logStream.on("error", () => {
|
|
39456
39383
|
});
|
|
39457
39384
|
const tee = (chunk) => {
|
|
@@ -39534,65 +39461,59 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39534
39461
|
|
|
39535
39462
|
// src/tunnel/device-key.ts
|
|
39536
39463
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39537
|
-
var
|
|
39538
|
-
var
|
|
39464
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39465
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39539
39466
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39540
39467
|
function deriveStableDeviceKey(opts = {}) {
|
|
39541
39468
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39542
39469
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39543
39470
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39544
|
-
const defaultDataDir =
|
|
39545
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39471
|
+
const defaultDataDir = import_node_path28.default.resolve(import_node_path28.default.join(home, ".clawd"));
|
|
39472
|
+
const normalizedDataDir = opts.dataDir ? import_node_path28.default.resolve(opts.dataDir) : null;
|
|
39546
39473
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39547
39474
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39548
|
-
return
|
|
39475
|
+
return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39549
39476
|
}
|
|
39550
39477
|
|
|
39551
39478
|
// src/auth-store.ts
|
|
39552
|
-
var
|
|
39553
|
-
var
|
|
39554
|
-
var
|
|
39479
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39480
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39481
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39555
39482
|
var AUTH_FILE_NAME = "auth.json";
|
|
39556
39483
|
function authFilePath(dataDir) {
|
|
39557
|
-
return
|
|
39484
|
+
return import_node_path29.default.join(dataDir, AUTH_FILE_NAME);
|
|
39558
39485
|
}
|
|
39559
39486
|
function loadOrCreateAuthFile(opts) {
|
|
39560
39487
|
const file = authFilePath(opts.dataDir);
|
|
39561
|
-
const generate = opts.generate ??
|
|
39488
|
+
const generate = opts.generate ?? defaultGenerateToken2;
|
|
39562
39489
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39563
|
-
const genDevice = opts.genDeviceId ?? defaultGenerateDeviceId;
|
|
39564
39490
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39565
39491
|
const existing = readAuthFile(file);
|
|
39566
|
-
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId
|
|
39492
|
+
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
|
|
39567
39493
|
return {
|
|
39568
39494
|
token: existing.token,
|
|
39569
39495
|
signSecret: existing.signSecret,
|
|
39570
39496
|
ownerPrincipalId: existing.ownerPrincipalId,
|
|
39571
|
-
deviceId: existing.deviceId,
|
|
39572
39497
|
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39573
39498
|
};
|
|
39574
39499
|
}
|
|
39575
39500
|
const token = existing?.token || generate();
|
|
39576
39501
|
const signSecret = existing?.signSecret || generate();
|
|
39577
39502
|
const ownerPrincipalId = existing?.ownerPrincipalId || genId();
|
|
39578
|
-
const deviceId = existing?.deviceId || genDevice();
|
|
39579
39503
|
const createdAt = existing?.createdAt || now().toISOString();
|
|
39580
|
-
const next = { token, signSecret, ownerPrincipalId,
|
|
39504
|
+
const next = { token, signSecret, ownerPrincipalId, createdAt };
|
|
39581
39505
|
writeAuthFile(file, next);
|
|
39582
39506
|
return next;
|
|
39583
39507
|
}
|
|
39584
|
-
function
|
|
39585
|
-
return
|
|
39508
|
+
function defaultGenerateToken2() {
|
|
39509
|
+
return import_node_crypto10.default.randomBytes(32).toString("base64url");
|
|
39586
39510
|
}
|
|
39587
39511
|
function defaultGenerateOwnerPrincipalId() {
|
|
39588
|
-
return `owner-${
|
|
39589
|
-
}
|
|
39590
|
-
function defaultGenerateDeviceId() {
|
|
39591
|
-
return `dev-${import_node_crypto9.default.randomUUID()}`;
|
|
39512
|
+
return `owner-${import_node_crypto10.default.randomUUID()}`;
|
|
39592
39513
|
}
|
|
39593
39514
|
function readAuthFile(file) {
|
|
39594
39515
|
try {
|
|
39595
|
-
const raw =
|
|
39516
|
+
const raw = import_node_fs25.default.readFileSync(file, "utf8");
|
|
39596
39517
|
const parsed = JSON.parse(raw);
|
|
39597
39518
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39598
39519
|
return null;
|
|
@@ -39601,7 +39522,6 @@ function readAuthFile(file) {
|
|
|
39601
39522
|
token: parsed.token,
|
|
39602
39523
|
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
39603
39524
|
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,
|
|
39605
39525
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
39606
39526
|
};
|
|
39607
39527
|
} catch (err) {
|
|
@@ -39611,25 +39531,25 @@ function readAuthFile(file) {
|
|
|
39611
39531
|
}
|
|
39612
39532
|
}
|
|
39613
39533
|
function writeAuthFile(file, content) {
|
|
39614
|
-
|
|
39615
|
-
|
|
39534
|
+
import_node_fs25.default.mkdirSync(import_node_path29.default.dirname(file), { recursive: true });
|
|
39535
|
+
import_node_fs25.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39616
39536
|
try {
|
|
39617
|
-
|
|
39537
|
+
import_node_fs25.default.chmodSync(file, 384);
|
|
39618
39538
|
} catch {
|
|
39619
39539
|
}
|
|
39620
39540
|
}
|
|
39621
39541
|
|
|
39622
39542
|
// src/owner-profile.ts
|
|
39623
|
-
var
|
|
39543
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39624
39544
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39625
|
-
var
|
|
39545
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
39626
39546
|
var PROFILE_FILENAME = "profile.json";
|
|
39627
39547
|
function loadOwnerDisplayName(dataDir) {
|
|
39628
39548
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39629
|
-
const profilePath =
|
|
39549
|
+
const profilePath = import_node_path30.default.join(dataDir, PROFILE_FILENAME);
|
|
39630
39550
|
let raw;
|
|
39631
39551
|
try {
|
|
39632
|
-
raw =
|
|
39552
|
+
raw = import_node_fs26.default.readFileSync(profilePath, "utf8");
|
|
39633
39553
|
} catch {
|
|
39634
39554
|
return fallback;
|
|
39635
39555
|
}
|
|
@@ -39651,357 +39571,6 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39651
39571
|
return displayName;
|
|
39652
39572
|
}
|
|
39653
39573
|
|
|
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
|
-
|
|
40005
39574
|
// src/index.ts
|
|
40006
39575
|
init_protocol();
|
|
40007
39576
|
|
|
@@ -40132,15 +39701,23 @@ function buildSessionHandlers(deps) {
|
|
|
40132
39701
|
}
|
|
40133
39702
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
40134
39703
|
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
40135
|
-
const
|
|
39704
|
+
const creatorOwnerPrincipalId = ctx?.principal.kind === "guest" ? ctx.peerOwnerPrincipalId : ownerPrincipalId;
|
|
39705
|
+
const { response, broadcast } = manager.create(
|
|
39706
|
+
args,
|
|
39707
|
+
creatorPrincipalId,
|
|
39708
|
+
creatorOwnerPrincipalId
|
|
39709
|
+
);
|
|
40136
39710
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
40137
39711
|
};
|
|
40138
39712
|
const list = async (_frame, _client, ctx) => {
|
|
40139
39713
|
const { response } = manager.list();
|
|
40140
39714
|
if (ctx && ctx.principal.kind === "guest") {
|
|
40141
39715
|
const sessions = response.sessions ?? [];
|
|
40142
|
-
const
|
|
40143
|
-
const
|
|
39716
|
+
const peerOwnerId = ctx.peerOwnerPrincipalId;
|
|
39717
|
+
const capId = ctx.capabilityId;
|
|
39718
|
+
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39719
|
+
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === capId
|
|
39720
|
+
);
|
|
40144
39721
|
return { response: { type: "session:list", sessions: filtered } };
|
|
40145
39722
|
}
|
|
40146
39723
|
return { response: { type: "session:list", ...response } };
|
|
@@ -40428,7 +40005,7 @@ function buildPermissionHandlers(deps) {
|
|
|
40428
40005
|
}
|
|
40429
40006
|
|
|
40430
40007
|
// src/handlers/history.ts
|
|
40431
|
-
var
|
|
40008
|
+
var path41 = __toESM(require("path"), 1);
|
|
40432
40009
|
init_protocol();
|
|
40433
40010
|
|
|
40434
40011
|
// src/session/recent-dirs.ts
|
|
@@ -40446,7 +40023,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
40446
40023
|
}
|
|
40447
40024
|
|
|
40448
40025
|
// src/permission/persona-paths.ts
|
|
40449
|
-
var
|
|
40026
|
+
var path40 = __toESM(require("path"), 1);
|
|
40450
40027
|
function getAllowedPersonaIds(grants, action) {
|
|
40451
40028
|
const ids = /* @__PURE__ */ new Set();
|
|
40452
40029
|
for (const g2 of grants) {
|
|
@@ -40459,26 +40036,26 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
40459
40036
|
return ids;
|
|
40460
40037
|
}
|
|
40461
40038
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
40462
|
-
const root =
|
|
40463
|
-
const target =
|
|
40464
|
-
const sep3 = root.endsWith(
|
|
40039
|
+
const root = path40.resolve(personaRoot);
|
|
40040
|
+
const target = path40.resolve(absPath);
|
|
40041
|
+
const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
|
|
40465
40042
|
if (!target.startsWith(root + sep3)) return false;
|
|
40466
|
-
const rel =
|
|
40043
|
+
const rel = path40.relative(root, target);
|
|
40467
40044
|
if (!rel || rel.startsWith("..")) return false;
|
|
40468
|
-
const personaId = rel.split(
|
|
40045
|
+
const personaId = rel.split(path40.sep)[0];
|
|
40469
40046
|
if (!personaId) return false;
|
|
40470
40047
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
40471
40048
|
if (allowed === "*") return true;
|
|
40472
40049
|
return allowed.has(personaId);
|
|
40473
40050
|
}
|
|
40474
40051
|
function personaIdFromPath(absPath, personaRoot) {
|
|
40475
|
-
const root =
|
|
40476
|
-
const target =
|
|
40477
|
-
const sep3 = root.endsWith(
|
|
40052
|
+
const root = path40.resolve(personaRoot);
|
|
40053
|
+
const target = path40.resolve(absPath);
|
|
40054
|
+
const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
|
|
40478
40055
|
if (!target.startsWith(root + sep3)) return null;
|
|
40479
|
-
const rel =
|
|
40056
|
+
const rel = path40.relative(root, target);
|
|
40480
40057
|
if (!rel || rel.startsWith("..")) return null;
|
|
40481
|
-
const id = rel.split(
|
|
40058
|
+
const id = rel.split(path40.sep)[0];
|
|
40482
40059
|
return id || null;
|
|
40483
40060
|
}
|
|
40484
40061
|
|
|
@@ -40503,7 +40080,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40503
40080
|
if (!pid) return false;
|
|
40504
40081
|
return isGuestPathAllowed(
|
|
40505
40082
|
ctx.grants,
|
|
40506
|
-
|
|
40083
|
+
path41.join(personaRoot, pid),
|
|
40507
40084
|
personaRoot,
|
|
40508
40085
|
"read"
|
|
40509
40086
|
);
|
|
@@ -40514,7 +40091,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40514
40091
|
};
|
|
40515
40092
|
const list = async (frame, _client, ctx) => {
|
|
40516
40093
|
const args = HistoryListArgs.parse(frame);
|
|
40517
|
-
assertGuestPath(ctx,
|
|
40094
|
+
assertGuestPath(ctx, path41.resolve(args.projectPath), personaRoot, "history:list");
|
|
40518
40095
|
const sessions = await history.listSessions(args);
|
|
40519
40096
|
return { response: { type: "history:list", sessions } };
|
|
40520
40097
|
};
|
|
@@ -40523,7 +40100,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40523
40100
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
40524
40101
|
const f = file;
|
|
40525
40102
|
if (ctx && ctx.principal.kind === "guest") {
|
|
40526
|
-
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.
|
|
40103
|
+
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.capabilityId;
|
|
40527
40104
|
if (!ok) {
|
|
40528
40105
|
throw new ClawdError(
|
|
40529
40106
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -40546,13 +40123,13 @@ function buildHistoryHandlers(deps) {
|
|
|
40546
40123
|
};
|
|
40547
40124
|
const subagents = async (frame, _client, ctx) => {
|
|
40548
40125
|
const args = HistorySubagentsArgs.parse(frame);
|
|
40549
|
-
assertGuestPath(ctx,
|
|
40126
|
+
assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagents");
|
|
40550
40127
|
const subs = await history.listSubagents(args);
|
|
40551
40128
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
40552
40129
|
};
|
|
40553
40130
|
const subagentRead = async (frame, _client, ctx) => {
|
|
40554
40131
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
40555
|
-
assertGuestPath(ctx,
|
|
40132
|
+
assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagent-read");
|
|
40556
40133
|
const res = await history.readSubagent(args);
|
|
40557
40134
|
return { response: { type: "history:subagent-read", ...res } };
|
|
40558
40135
|
};
|
|
@@ -40560,7 +40137,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40560
40137
|
const dirs = listRecentDirs(store);
|
|
40561
40138
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40562
40139
|
const filtered = dirs.filter(
|
|
40563
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
40140
|
+
(d) => isGuestPathAllowed(ctx.grants, path41.resolve(d.cwd), personaRoot, "read")
|
|
40564
40141
|
);
|
|
40565
40142
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
40566
40143
|
}
|
|
@@ -40577,7 +40154,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40577
40154
|
}
|
|
40578
40155
|
|
|
40579
40156
|
// src/handlers/workspace.ts
|
|
40580
|
-
var
|
|
40157
|
+
var path42 = __toESM(require("path"), 1);
|
|
40581
40158
|
var os15 = __toESM(require("os"), 1);
|
|
40582
40159
|
init_protocol();
|
|
40583
40160
|
init_protocol();
|
|
@@ -40618,22 +40195,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40618
40195
|
const args = WorkspaceListArgs.parse(frame);
|
|
40619
40196
|
const isGuest = ctx?.principal.kind === "guest";
|
|
40620
40197
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
|
|
40621
|
-
const resolvedCwd =
|
|
40622
|
-
const target = args.path ?
|
|
40198
|
+
const resolvedCwd = path42.resolve(args.cwd ?? fallbackCwd);
|
|
40199
|
+
const target = args.path ? path42.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
40623
40200
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list");
|
|
40624
40201
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
40625
40202
|
return { response: { type: "workspace:list", ...res } };
|
|
40626
40203
|
};
|
|
40627
40204
|
const read = async (frame, _client, ctx) => {
|
|
40628
40205
|
const args = WorkspaceReadArgs.parse(frame);
|
|
40629
|
-
const target =
|
|
40206
|
+
const target = path42.isAbsolute(args.path) ? path42.resolve(args.path) : path42.resolve(args.cwd, args.path);
|
|
40630
40207
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read");
|
|
40631
40208
|
const res = workspace.read(args);
|
|
40632
40209
|
return { response: { type: "workspace:read", ...res } };
|
|
40633
40210
|
};
|
|
40634
40211
|
const skillsList = async (frame, _client, ctx) => {
|
|
40635
40212
|
const args = SkillsListArgs.parse(frame);
|
|
40636
|
-
const cwdAbs =
|
|
40213
|
+
const cwdAbs = path42.resolve(args.cwd);
|
|
40637
40214
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
|
|
40638
40215
|
const list2 = skills.list(args);
|
|
40639
40216
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40645,7 +40222,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40645
40222
|
};
|
|
40646
40223
|
const agentsList = async (frame, _client, ctx) => {
|
|
40647
40224
|
const args = AgentsListArgs.parse(frame);
|
|
40648
|
-
const cwdAbs =
|
|
40225
|
+
const cwdAbs = path42.resolve(args.cwd);
|
|
40649
40226
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
|
|
40650
40227
|
const list2 = agents.list(args);
|
|
40651
40228
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40664,7 +40241,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40664
40241
|
}
|
|
40665
40242
|
|
|
40666
40243
|
// src/handlers/git.ts
|
|
40667
|
-
var
|
|
40244
|
+
var path44 = __toESM(require("path"), 1);
|
|
40668
40245
|
init_protocol();
|
|
40669
40246
|
init_protocol();
|
|
40670
40247
|
|
|
@@ -40750,7 +40327,7 @@ async function listGitBranches(cwd) {
|
|
|
40750
40327
|
// src/handlers/git.ts
|
|
40751
40328
|
function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
40752
40329
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40753
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
40330
|
+
if (!isGuestPathAllowed(ctx.grants, path44.resolve(cwd), personaRoot, "read")) {
|
|
40754
40331
|
throw new ClawdError(
|
|
40755
40332
|
ERROR_CODES.UNAUTHORIZED,
|
|
40756
40333
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -40805,7 +40382,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40805
40382
|
capabilityId: external_exports.string().min(1)
|
|
40806
40383
|
}).strict();
|
|
40807
40384
|
function buildCapabilityHandlers(deps) {
|
|
40808
|
-
const { manager } = deps;
|
|
40385
|
+
const { manager, getShareBaseUrl, getPersonalCapability } = deps;
|
|
40809
40386
|
const list = async () => {
|
|
40810
40387
|
return {
|
|
40811
40388
|
response: {
|
|
@@ -40831,45 +40408,70 @@ function buildCapabilityHandlers(deps) {
|
|
|
40831
40408
|
}
|
|
40832
40409
|
};
|
|
40833
40410
|
};
|
|
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
|
+
};
|
|
40834
40422
|
return {
|
|
40835
40423
|
"capability:list": list,
|
|
40836
|
-
"capability:delete": del
|
|
40424
|
+
"capability:delete": del,
|
|
40425
|
+
"personal-cap:get": personalCapGet
|
|
40837
40426
|
};
|
|
40838
40427
|
}
|
|
40839
40428
|
|
|
40840
40429
|
// src/handlers/inbox.ts
|
|
40841
40430
|
init_protocol();
|
|
40842
|
-
function
|
|
40431
|
+
function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
40843
40432
|
if (ctx.principal.kind === "owner") {
|
|
40844
|
-
if (!
|
|
40433
|
+
if (!argsPeerOwnerId) {
|
|
40845
40434
|
throw new ClawdError(
|
|
40846
40435
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40847
|
-
"inbox: owner ctx requires
|
|
40436
|
+
"inbox: owner ctx requires peerOwnerId"
|
|
40848
40437
|
);
|
|
40849
40438
|
}
|
|
40850
|
-
return
|
|
40439
|
+
return argsPeerOwnerId;
|
|
40440
|
+
}
|
|
40441
|
+
if (!ctx.capabilityId) {
|
|
40442
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
40851
40443
|
}
|
|
40852
|
-
|
|
40853
|
-
if (
|
|
40444
|
+
let resolved = ctx.peerOwnerPrincipalId;
|
|
40445
|
+
if (!resolved) {
|
|
40446
|
+
const cap = capManager.findById(ctx.capabilityId);
|
|
40447
|
+
resolved = cap?.peerOwnerId;
|
|
40448
|
+
}
|
|
40449
|
+
if (!resolved) {
|
|
40854
40450
|
throw new ClawdError(
|
|
40855
40451
|
ERROR_CODES.UNAUTHORIZED,
|
|
40856
|
-
`inbox: guest
|
|
40452
|
+
`inbox: guest cap ${ctx.capabilityId} has no peerOwnerPrincipalId (auth frame missing selfPrincipalId)`
|
|
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})`
|
|
40857
40459
|
);
|
|
40858
40460
|
}
|
|
40859
40461
|
return resolved;
|
|
40860
40462
|
}
|
|
40861
40463
|
function buildInboxHandlers(deps) {
|
|
40862
|
-
const { manager } = deps;
|
|
40464
|
+
const { manager, capManager } = deps;
|
|
40863
40465
|
const postMessage = async (frame, _client, ctx) => {
|
|
40864
40466
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40865
40467
|
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
40866
40468
|
if (!ctx) {
|
|
40867
40469
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40868
40470
|
}
|
|
40869
|
-
const
|
|
40471
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40870
40472
|
const message = manager.postMessage({
|
|
40871
|
-
|
|
40872
|
-
|
|
40473
|
+
peerOwnerId,
|
|
40474
|
+
senderPrincipalId: ctx.principal.id,
|
|
40873
40475
|
text: args.text,
|
|
40874
40476
|
id: args.id,
|
|
40875
40477
|
createdAt: args.createdAt
|
|
@@ -40884,10 +40486,10 @@ function buildInboxHandlers(deps) {
|
|
|
40884
40486
|
if (!ctx) {
|
|
40885
40487
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40886
40488
|
}
|
|
40887
|
-
const
|
|
40888
|
-
const messages = manager.list(
|
|
40489
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40490
|
+
const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
|
|
40889
40491
|
return {
|
|
40890
|
-
response: { type: "inbox:list:ok",
|
|
40492
|
+
response: { type: "inbox:list:ok", peerOwnerId, messages }
|
|
40891
40493
|
};
|
|
40892
40494
|
};
|
|
40893
40495
|
const markRead = async (frame, _client, ctx) => {
|
|
@@ -40896,16 +40498,16 @@ function buildInboxHandlers(deps) {
|
|
|
40896
40498
|
if (!ctx) {
|
|
40897
40499
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40898
40500
|
}
|
|
40899
|
-
const
|
|
40501
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40900
40502
|
const updated = manager.markRead({
|
|
40901
|
-
|
|
40902
|
-
|
|
40503
|
+
peerOwnerId,
|
|
40504
|
+
principalId: ctx.principal.id,
|
|
40903
40505
|
upToCreatedAt: args.upToCreatedAt
|
|
40904
40506
|
});
|
|
40905
40507
|
return {
|
|
40906
40508
|
response: {
|
|
40907
40509
|
type: "inbox:markRead:ok",
|
|
40908
|
-
|
|
40510
|
+
peerOwnerId,
|
|
40909
40511
|
upToCreatedAt: args.upToCreatedAt,
|
|
40910
40512
|
updated
|
|
40911
40513
|
}
|
|
@@ -40918,45 +40520,116 @@ function buildInboxHandlers(deps) {
|
|
|
40918
40520
|
};
|
|
40919
40521
|
}
|
|
40920
40522
|
|
|
40921
|
-
// src/handlers/
|
|
40523
|
+
// src/handlers/received-capability.ts
|
|
40922
40524
|
init_protocol();
|
|
40923
40525
|
function ensureOwner(ctx) {
|
|
40924
40526
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40925
40527
|
throw new ClawdError(
|
|
40926
40528
|
ERROR_CODES.UNAUTHORIZED,
|
|
40927
|
-
"UNAUTHORIZED:
|
|
40529
|
+
"UNAUTHORIZED: received-capability:* requires owner ctx"
|
|
40530
|
+
);
|
|
40531
|
+
}
|
|
40532
|
+
}
|
|
40533
|
+
function ensureGuestCtx(ctx) {
|
|
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"
|
|
40928
40538
|
);
|
|
40929
40539
|
}
|
|
40930
40540
|
}
|
|
40931
|
-
function
|
|
40541
|
+
function buildReceivedCapabilityHandlers(deps) {
|
|
40542
|
+
const now = deps.now ?? Date.now;
|
|
40932
40543
|
const list = async (_frame, _client, ctx) => {
|
|
40933
40544
|
ensureOwner(ctx);
|
|
40934
40545
|
return {
|
|
40935
40546
|
response: {
|
|
40936
|
-
type: "
|
|
40937
|
-
|
|
40547
|
+
type: "received-capability:list:ok",
|
|
40548
|
+
receivedCaps: deps.store.list()
|
|
40938
40549
|
}
|
|
40939
40550
|
};
|
|
40940
40551
|
};
|
|
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
|
+
};
|
|
40941
40593
|
const remove = async (frame, _client, ctx) => {
|
|
40942
40594
|
ensureOwner(ctx);
|
|
40943
40595
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40944
|
-
const args =
|
|
40945
|
-
deps.store.
|
|
40596
|
+
const args = ReceivedCapabilityRemoveArgsSchema.parse(rest);
|
|
40597
|
+
deps.store.remove(args.ownerPrincipalId);
|
|
40946
40598
|
deps.broadcast({
|
|
40947
|
-
type: "
|
|
40948
|
-
|
|
40599
|
+
type: "received-capability:removed",
|
|
40600
|
+
ownerPrincipalId: args.ownerPrincipalId
|
|
40949
40601
|
});
|
|
40950
40602
|
return {
|
|
40951
40603
|
response: {
|
|
40952
|
-
type: "
|
|
40953
|
-
|
|
40604
|
+
type: "received-capability:remove:ok",
|
|
40605
|
+
ownerPrincipalId: args.ownerPrincipalId
|
|
40954
40606
|
}
|
|
40955
40607
|
};
|
|
40956
40608
|
};
|
|
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
|
+
};
|
|
40957
40628
|
return {
|
|
40958
|
-
"
|
|
40959
|
-
"
|
|
40629
|
+
"received-capability:list": list,
|
|
40630
|
+
"received-capability:add": add,
|
|
40631
|
+
"received-capability:remove": remove,
|
|
40632
|
+
"received-capability:autoAttach": autoAttach
|
|
40960
40633
|
};
|
|
40961
40634
|
}
|
|
40962
40635
|
|
|
@@ -40967,11 +40640,7 @@ function buildWhoamiHandler(deps) {
|
|
|
40967
40640
|
if (!ctx) {
|
|
40968
40641
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
40969
40642
|
}
|
|
40970
|
-
const owner =
|
|
40971
|
-
...makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName),
|
|
40972
|
-
ownerId: deps.ownerId,
|
|
40973
|
-
provider: deps.ownerProvider
|
|
40974
|
-
};
|
|
40643
|
+
const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
|
|
40975
40644
|
let capability;
|
|
40976
40645
|
if (ctx.principal.kind === "owner") {
|
|
40977
40646
|
capability = {
|
|
@@ -40982,13 +40651,14 @@ function buildWhoamiHandler(deps) {
|
|
|
40982
40651
|
usedCount: 0
|
|
40983
40652
|
};
|
|
40984
40653
|
} else {
|
|
40985
|
-
|
|
40986
|
-
|
|
40987
|
-
|
|
40988
|
-
|
|
40989
|
-
|
|
40990
|
-
|
|
40991
|
-
}
|
|
40654
|
+
if (!ctx.capabilityId) {
|
|
40655
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
|
|
40656
|
+
}
|
|
40657
|
+
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
40658
|
+
if (!cap) {
|
|
40659
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
|
|
40660
|
+
}
|
|
40661
|
+
capability = stripSecretHash(cap);
|
|
40992
40662
|
}
|
|
40993
40663
|
const grantedPersonas = [];
|
|
40994
40664
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -41019,123 +40689,6 @@ function buildWhoamiHandler(deps) {
|
|
|
41019
40689
|
};
|
|
41020
40690
|
}
|
|
41021
40691
|
|
|
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
|
-
|
|
41139
40692
|
// src/handlers/meta.ts
|
|
41140
40693
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
41141
40694
|
init_protocol();
|
|
@@ -42548,25 +42101,27 @@ function buildMethodHandlers(deps) {
|
|
|
42548
42101
|
personaRegistry: deps.personaRegistry
|
|
42549
42102
|
}),
|
|
42550
42103
|
...buildCapabilityHandlers({
|
|
42551
|
-
manager: deps.capabilityManager
|
|
42104
|
+
manager: deps.capabilityManager,
|
|
42105
|
+
getShareBaseUrl: deps.getShareBaseUrl,
|
|
42106
|
+
getPersonalCapability: deps.getPersonalCapability
|
|
42552
42107
|
}),
|
|
42553
42108
|
...buildInboxHandlers({
|
|
42554
|
-
manager: deps.inboxManager
|
|
42109
|
+
manager: deps.inboxManager,
|
|
42110
|
+
capManager: deps.capabilityManager
|
|
42555
42111
|
}),
|
|
42556
|
-
...
|
|
42557
|
-
store: deps.
|
|
42558
|
-
|
|
42112
|
+
...buildReceivedCapabilityHandlers({
|
|
42113
|
+
store: deps.receivedCapabilityStore,
|
|
42114
|
+
connectRemote: deps.connectRemote,
|
|
42115
|
+
broadcast: deps.broadcastToOwners,
|
|
42116
|
+
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
42117
|
+
selfDisplayName: () => deps.ownerDisplayName
|
|
42559
42118
|
}),
|
|
42560
42119
|
whoami: buildWhoamiHandler({
|
|
42561
42120
|
ownerDisplayName: deps.ownerDisplayName,
|
|
42562
42121
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
42563
|
-
ownerId: deps.ownerId,
|
|
42564
|
-
ownerProvider: deps.ownerProvider,
|
|
42565
42122
|
personaStore: deps.personaStore,
|
|
42566
42123
|
capabilityManager: deps.capabilityManager
|
|
42567
42124
|
}),
|
|
42568
|
-
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
42569
|
-
...buildDeviceHandlers(deps.feishuDevice),
|
|
42570
42125
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
42571
42126
|
...buildExtensionHandlers({
|
|
42572
42127
|
loadAll,
|
|
@@ -42760,7 +42315,7 @@ var ProjectStore = class {
|
|
|
42760
42315
|
// src/app-builder/dev-server-supervisor.ts
|
|
42761
42316
|
var import_node_child_process9 = require("child_process");
|
|
42762
42317
|
var import_node_events2 = require("events");
|
|
42763
|
-
var DEFAULT_READY_PATTERN = /Local:\s+https
|
|
42318
|
+
var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
|
|
42764
42319
|
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
42765
42320
|
running = /* @__PURE__ */ new Map();
|
|
42766
42321
|
// key: sessionId
|
|
@@ -43013,22 +42568,23 @@ var METHOD_GRANT_MAP = {
|
|
|
43013
42568
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
43014
42569
|
"whoami": { kind: "public" },
|
|
43015
42570
|
// ---- capability platform(admin-only) ----
|
|
43016
|
-
//
|
|
43017
|
-
//
|
|
43018
|
-
// 撤销级联 + 清旧 per-peer cap。
|
|
42571
|
+
// global personal token (2026-05-25): capability:issue / bindPeer 已下线;
|
|
42572
|
+
// personal capability 由 daemon 启动时自动创建,personal-cap:get 是 owner-only 拿 token + shareBaseUrl
|
|
43019
42573
|
"capability:list": ADMIN_ANY,
|
|
43020
42574
|
"capability:delete": ADMIN_ANY,
|
|
43021
|
-
|
|
43022
|
-
//
|
|
43023
|
-
//
|
|
42575
|
+
"personal-cap:get": ADMIN_ANY,
|
|
42576
|
+
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
42577
|
+
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
42578
|
+
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
43024
42579
|
"inbox:list": { kind: "public" },
|
|
43025
42580
|
"inbox:markRead": { kind: "public" },
|
|
43026
42581
|
"inbox:postMessage": { kind: "public" },
|
|
43027
|
-
// ----
|
|
43028
|
-
//
|
|
43029
|
-
|
|
43030
|
-
"
|
|
43031
|
-
"
|
|
42582
|
+
// ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
|
|
42583
|
+
// owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
|
|
42584
|
+
"received-capability:list": ADMIN_ANY,
|
|
42585
|
+
"received-capability:add": ADMIN_ANY,
|
|
42586
|
+
"received-capability:remove": ADMIN_ANY,
|
|
42587
|
+
"received-capability:autoAttach": { kind: "public" },
|
|
43032
42588
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
43033
42589
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
43034
42590
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -43069,7 +42625,7 @@ var METHOD_GRANT_MAP = {
|
|
|
43069
42625
|
"history:list": CAPABILITY_SCOPED,
|
|
43070
42626
|
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
43071
42627
|
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
43072
|
-
// handler 内按 ctx.
|
|
42628
|
+
// handler 内按 ctx.capabilityId === file.creatorPrincipalId 拦越权。
|
|
43073
42629
|
"history:read": CAPABILITY_SCOPED,
|
|
43074
42630
|
"history:subagents": CAPABILITY_SCOPED,
|
|
43075
42631
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
@@ -43126,15 +42682,6 @@ var METHOD_GRANT_MAP = {
|
|
|
43126
42682
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
43127
42683
|
"extension.install-from-channel": ADMIN_ANY,
|
|
43128
42684
|
"extension.update-from-channel": ADMIN_ANY,
|
|
43129
|
-
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
43130
|
-
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
43131
|
-
"auth:login:start": ADMIN_ANY,
|
|
43132
|
-
"auth:getIdentity": ADMIN_ANY,
|
|
43133
|
-
"auth:logout": ADMIN_ANY,
|
|
43134
|
-
// ---- device:* 设备目录 + 连接(决策 #15,owner-only) ----
|
|
43135
|
-
// 设备目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
43136
|
-
"device:list": ADMIN_ANY,
|
|
43137
|
-
"device:connect": ADMIN_ANY,
|
|
43138
42685
|
// ---- app-builder Project picker(spec 2026-06-01-app-builder-project-picker-design) ----
|
|
43139
42686
|
// owner-only:picker UI 给 owner 管理本机 build 中的 project(fs + 端口分配)。
|
|
43140
42687
|
// 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
|
|
@@ -43541,23 +43088,19 @@ async function startDaemon(config) {
|
|
|
43541
43088
|
} else if (config.tunnel) {
|
|
43542
43089
|
resolvedAuthToken = authFile.token;
|
|
43543
43090
|
}
|
|
43544
|
-
const
|
|
43545
|
-
const
|
|
43546
|
-
let feishuActive = feishuIdentity !== null;
|
|
43547
|
-
const ownerPrincipalId = authFile.deviceId;
|
|
43548
|
-
let ownerId = feishuIdentity?.identity.ownerId ?? "";
|
|
43549
|
-
let ownerProvider = feishuIdentity?.identity.provider ?? "";
|
|
43550
|
-
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
43091
|
+
const ownerPrincipalId = authFile.ownerPrincipalId;
|
|
43092
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
43551
43093
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
43552
43094
|
const inboxStore = new InboxStore(config.dataDir);
|
|
43553
|
-
const inboxManager = new InboxManager(inboxStore, (
|
|
43095
|
+
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
43554
43096
|
wsServer?.broadcastToOwners(frame);
|
|
43555
43097
|
});
|
|
43556
|
-
const
|
|
43557
|
-
|
|
43558
|
-
const serverKeyStore = new ServerKeyStore(config.dataDir);
|
|
43098
|
+
const receivedCapabilityStore = new ReceivedCapabilityStore(config.dataDir);
|
|
43099
|
+
receivedCapabilityStore.load();
|
|
43559
43100
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
43560
43101
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
43102
|
+
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
43103
|
+
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
43561
43104
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
43562
43105
|
onDeleted: (cap) => {
|
|
43563
43106
|
const deletedAt = Date.now();
|
|
@@ -43566,7 +43109,7 @@ async function startDaemon(config) {
|
|
|
43566
43109
|
capabilityId: cap.id,
|
|
43567
43110
|
deletedAt
|
|
43568
43111
|
});
|
|
43569
|
-
wsServer?.
|
|
43112
|
+
wsServer?.closeConnectionsByCapability(cap.id);
|
|
43570
43113
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
43571
43114
|
if (cleanup.removed.length > 0) {
|
|
43572
43115
|
logger.info("capability delete cascade: guest sessions removed", {
|
|
@@ -43583,60 +43126,15 @@ async function startDaemon(config) {
|
|
|
43583
43126
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
43584
43127
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
43585
43128
|
expectedToken: resolvedAuthToken,
|
|
43586
|
-
authenticate:
|
|
43587
|
-
|
|
43129
|
+
authenticate: (t, selfPrincipalId, selfDisplayName) => {
|
|
43130
|
+
return authenticate(t, {
|
|
43588
43131
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
43589
43132
|
ownerPrincipalId,
|
|
43590
43133
|
ownerDisplayName,
|
|
43591
|
-
|
|
43592
|
-
|
|
43593
|
-
|
|
43594
|
-
...feishuActive ? {
|
|
43595
|
-
verifyConnectToken: (token) => {
|
|
43596
|
-
const publicKeyPem = serverKeyStore.read();
|
|
43597
|
-
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
43598
|
-
return verifyConnectToken({
|
|
43599
|
-
token,
|
|
43600
|
-
publicKeyPem,
|
|
43601
|
-
expectedDeviceId: authFile.deviceId
|
|
43602
|
-
});
|
|
43603
|
-
}
|
|
43604
|
-
} : {}
|
|
43134
|
+
capabilityRegistry,
|
|
43135
|
+
selfPrincipalId,
|
|
43136
|
+
selfDisplayName
|
|
43605
43137
|
});
|
|
43606
|
-
if (result.ok && result.context.principal.kind === "guest") {
|
|
43607
|
-
void autoReverseContact({
|
|
43608
|
-
store: contactStore,
|
|
43609
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43610
|
-
deviceId: result.context.principal.id,
|
|
43611
|
-
ownerId: result.context.ownerId ?? "",
|
|
43612
|
-
provider: result.context.provider ?? "",
|
|
43613
|
-
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
43614
|
-
selfUrl,
|
|
43615
|
-
// 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
|
|
43616
|
-
// 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
|
|
43617
|
-
exchangeToken: async (targetDeviceId) => {
|
|
43618
|
-
const record = ownerIdentityStore.read();
|
|
43619
|
-
if (!record) return null;
|
|
43620
|
-
try {
|
|
43621
|
-
const r = await centralExchange({
|
|
43622
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43623
|
-
jwt: record.ttcToken,
|
|
43624
|
-
provider: record.identity.provider,
|
|
43625
|
-
deviceId: targetDeviceId,
|
|
43626
|
-
selfDeviceId: authFile.deviceId
|
|
43627
|
-
});
|
|
43628
|
-
return r.token;
|
|
43629
|
-
} catch (e) {
|
|
43630
|
-
logger.warn("auto-reverse exchange token failed", {
|
|
43631
|
-
targetDeviceId,
|
|
43632
|
-
err: e.message
|
|
43633
|
-
});
|
|
43634
|
-
return null;
|
|
43635
|
-
}
|
|
43636
|
-
}
|
|
43637
|
-
});
|
|
43638
|
-
}
|
|
43639
|
-
return result;
|
|
43640
43138
|
},
|
|
43641
43139
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
43642
43140
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -43791,52 +43289,10 @@ async function startDaemon(config) {
|
|
|
43791
43289
|
}
|
|
43792
43290
|
return `http://${config.host}:${config.port}`;
|
|
43793
43291
|
};
|
|
43794
|
-
const reportDevice = async () => {
|
|
43795
|
-
if (!feishuActive) return;
|
|
43796
|
-
const record = ownerIdentityStore.read();
|
|
43797
|
-
if (!record) return;
|
|
43798
|
-
try {
|
|
43799
|
-
await centralUpsertDevice({
|
|
43800
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43801
|
-
ttcJwt: record.ttcToken,
|
|
43802
|
-
provider: record.identity.provider,
|
|
43803
|
-
deviceId: authFile.deviceId,
|
|
43804
|
-
url: currentTunnelUrl ?? void 0,
|
|
43805
|
-
name: ownerDisplayName
|
|
43806
|
-
});
|
|
43807
|
-
logger.info("device registered to central server", {
|
|
43808
|
-
deviceId: authFile.deviceId,
|
|
43809
|
-
url: currentTunnelUrl ?? "(none)"
|
|
43810
|
-
});
|
|
43811
|
-
} catch (err) {
|
|
43812
|
-
logger.warn("device register failed (best-effort)", { err: err.message });
|
|
43813
|
-
}
|
|
43814
|
-
};
|
|
43815
|
-
const fetchServerKey = async () => {
|
|
43816
|
-
if (!feishuActive) return;
|
|
43817
|
-
if (serverKeyStore.read()) return;
|
|
43818
|
-
try {
|
|
43819
|
-
const { publicKeyPem } = await centralGetPublicKey({
|
|
43820
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API
|
|
43821
|
-
});
|
|
43822
|
-
serverKeyStore.write(publicKeyPem);
|
|
43823
|
-
logger.info("server signing key cached");
|
|
43824
|
-
} catch (err) {
|
|
43825
|
-
logger.warn("server signing key fetch failed (guest \u5165\u7AD9\u5C06\u88AB\u62D2\u7EDD)", {
|
|
43826
|
-
err: err.message
|
|
43827
|
-
});
|
|
43828
|
-
}
|
|
43829
|
-
};
|
|
43830
43292
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
43831
43293
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
43832
43294
|
await publishedChannelStore.load();
|
|
43833
43295
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
43834
|
-
const loginFlow = new LoginFlow({
|
|
43835
|
-
store: ownerIdentityStore,
|
|
43836
|
-
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
43837
|
-
ttcAuthBase: TTC_HOSTS.auth,
|
|
43838
|
-
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
43839
|
-
});
|
|
43840
43296
|
const appBuilderStore = new ProjectStore(
|
|
43841
43297
|
import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
|
|
43842
43298
|
);
|
|
@@ -43847,7 +43303,7 @@ async function startDaemon(config) {
|
|
|
43847
43303
|
const effectivePreviewPorts = Array.from(
|
|
43848
43304
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
43849
43305
|
);
|
|
43850
|
-
const
|
|
43306
|
+
const handlers = buildMethodHandlers({
|
|
43851
43307
|
manager,
|
|
43852
43308
|
workspace,
|
|
43853
43309
|
skills,
|
|
@@ -43892,12 +43348,15 @@ async function startDaemon(config) {
|
|
|
43892
43348
|
personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
|
|
43893
43349
|
// capability:list / delete handler 依赖
|
|
43894
43350
|
capabilityManager,
|
|
43351
|
+
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
43352
|
+
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
43353
|
+
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
43354
|
+
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
43355
|
+
getPersonalCapability: () => personalCapability,
|
|
43895
43356
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
43896
43357
|
ownerDisplayName,
|
|
43897
|
-
//
|
|
43358
|
+
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
43898
43359
|
ownerPrincipalId,
|
|
43899
|
-
ownerId,
|
|
43900
|
-
ownerProvider,
|
|
43901
43360
|
personaStore,
|
|
43902
43361
|
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
43903
43362
|
capabilityRegistry,
|
|
@@ -43905,56 +43364,18 @@ async function startDaemon(config) {
|
|
|
43905
43364
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
43906
43365
|
inboxManager,
|
|
43907
43366
|
inboxStore,
|
|
43908
|
-
//
|
|
43909
|
-
|
|
43910
|
-
//
|
|
43367
|
+
// bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
|
|
43368
|
+
receivedCapabilityStore,
|
|
43369
|
+
// received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
|
|
43911
43370
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43371
|
+
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
43372
|
+
connectRemote,
|
|
43912
43373
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
43913
43374
|
// level so stopAll() runs in the shutdown hook below.
|
|
43914
43375
|
extensionRuntime,
|
|
43915
43376
|
// extension sharing v1 — owner-side published-channels store.
|
|
43916
43377
|
publishedChannelStore,
|
|
43917
43378
|
bundleCache,
|
|
43918
|
-
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
43919
|
-
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
43920
|
-
feishuAuth: { loginFlow, ownerIdentityStore, deviceId: authFile.deviceId },
|
|
43921
|
-
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
43922
|
-
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
43923
|
-
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
43924
|
-
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
43925
|
-
feishuDevice: {
|
|
43926
|
-
store: contactStore,
|
|
43927
|
-
exchange: async (deviceId) => {
|
|
43928
|
-
const record = ownerIdentityStore.read();
|
|
43929
|
-
if (!record) {
|
|
43930
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
43931
|
-
}
|
|
43932
|
-
return centralExchange({
|
|
43933
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43934
|
-
jwt: record.ttcToken,
|
|
43935
|
-
provider: record.identity.provider,
|
|
43936
|
-
deviceId,
|
|
43937
|
-
selfDeviceId: authFile.deviceId
|
|
43938
|
-
});
|
|
43939
|
-
},
|
|
43940
|
-
listDevices: async () => {
|
|
43941
|
-
const record = ownerIdentityStore.read();
|
|
43942
|
-
if (!record) {
|
|
43943
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
43944
|
-
}
|
|
43945
|
-
return centralListDevices({
|
|
43946
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
43947
|
-
ttcJwt: record.ttcToken,
|
|
43948
|
-
provider: record.identity.provider
|
|
43949
|
-
});
|
|
43950
|
-
},
|
|
43951
|
-
connectRemote,
|
|
43952
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
43953
|
-
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
43954
|
-
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
43955
|
-
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
43956
|
-
selfUrl: () => currentTunnelUrl
|
|
43957
|
-
},
|
|
43958
43379
|
// app-builder
|
|
43959
43380
|
appBuilderStore,
|
|
43960
43381
|
devServerLifecycle: devServerSupervisor,
|
|
@@ -43963,7 +43384,6 @@ async function startDaemon(config) {
|
|
|
43963
43384
|
broadcastProjectUpdated: (event) => wsServer?.broadcastToOwners(event),
|
|
43964
43385
|
logger
|
|
43965
43386
|
});
|
|
43966
|
-
let handlers = makeHandlers();
|
|
43967
43387
|
const authResolver = new AuthContextResolver({
|
|
43968
43388
|
ownerToken: resolvedAuthToken
|
|
43969
43389
|
});
|
|
@@ -44001,31 +43421,6 @@ async function startDaemon(config) {
|
|
|
44001
43421
|
},
|
|
44002
43422
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
44003
43423
|
extensionsRoot: () => extensionsRoot(),
|
|
44004
|
-
// 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
|
|
44005
|
-
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
44006
|
-
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
44007
|
-
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
44008
|
-
// People 立即解锁,不需要重启 daemon
|
|
44009
|
-
// 失败则广播 auth:login:failed。
|
|
44010
|
-
feishuLogin: {
|
|
44011
|
-
handleLoginCallback: async (params) => {
|
|
44012
|
-
const result = await loginFlow.handleCallback(params);
|
|
44013
|
-
if (result.ok) {
|
|
44014
|
-
feishuActive = true;
|
|
44015
|
-
ownerId = result.identity.ownerId;
|
|
44016
|
-
ownerProvider = result.identity.provider;
|
|
44017
|
-
ownerDisplayName = result.identity.displayName;
|
|
44018
|
-
handlers = makeHandlers();
|
|
44019
|
-
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
44020
|
-
setImmediate(() => wsServer?.closeAllClients());
|
|
44021
|
-
void reportDevice();
|
|
44022
|
-
void fetchServerKey();
|
|
44023
|
-
} else {
|
|
44024
|
-
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
44025
|
-
}
|
|
44026
|
-
return result;
|
|
44027
|
-
}
|
|
44028
|
-
},
|
|
44029
43424
|
// app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
|
|
44030
43425
|
previewPorts: effectivePreviewPorts
|
|
44031
43426
|
});
|
|
@@ -44033,6 +43428,8 @@ async function startDaemon(config) {
|
|
|
44033
43428
|
host: config.host,
|
|
44034
43429
|
port: config.port,
|
|
44035
43430
|
logger,
|
|
43431
|
+
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
43432
|
+
ownerPrincipalId,
|
|
44036
43433
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
44037
43434
|
{
|
|
44038
43435
|
manager,
|
|
@@ -44052,12 +43449,14 @@ async function startDaemon(config) {
|
|
|
44052
43449
|
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
44053
43450
|
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
44054
43451
|
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
44055
|
-
tryVerifyCapabilityToken: (token) => {
|
|
43452
|
+
tryVerifyCapabilityToken: (token, selfPrincipalId, _selfDisplayName) => {
|
|
44056
43453
|
const v2 = capabilityRegistry.verifyToken(token);
|
|
44057
43454
|
if (!v2.ok) return null;
|
|
44058
43455
|
return {
|
|
44059
43456
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
44060
|
-
grants: v2.capability.grants
|
|
43457
|
+
grants: v2.capability.grants,
|
|
43458
|
+
capabilityId: v2.capability.id,
|
|
43459
|
+
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
44061
43460
|
};
|
|
44062
43461
|
},
|
|
44063
43462
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
@@ -44108,12 +43507,6 @@ async function startDaemon(config) {
|
|
|
44108
43507
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
44109
43508
|
const handler = handlers[type];
|
|
44110
43509
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
44111
|
-
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
44112
|
-
throw new ClawdError(
|
|
44113
|
-
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
44114
|
-
`${type} requires feishu login (People/remote identity)`
|
|
44115
|
-
);
|
|
44116
|
-
}
|
|
44117
43510
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
44118
43511
|
const verdict = computeGrantForFrame(type, frame);
|
|
44119
43512
|
if (verdict.kind === "check") {
|
|
@@ -44231,8 +43624,6 @@ ${bar}
|
|
|
44231
43624
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
44232
43625
|
}
|
|
44233
43626
|
}
|
|
44234
|
-
void reportDevice();
|
|
44235
|
-
void fetchServerKey();
|
|
44236
43627
|
const tickAttachmentGc = () => {
|
|
44237
43628
|
try {
|
|
44238
43629
|
runAttachmentGc({
|