@clawos-dev/clawd 0.2.123-beta.244.5e5107f → 0.2.123-beta.245.cb0366f
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
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
|
|
@@ -279,10 +274,6 @@ var init_errors = __esm({
|
|
|
279
274
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
280
275
|
FORBIDDEN: "FORBIDDEN",
|
|
281
276
|
INVALID_PARAM: "INVALID_PARAM",
|
|
282
|
-
// 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
|
|
283
|
-
// daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
|
|
284
|
-
// UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
|
|
285
|
-
FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED",
|
|
286
277
|
// app-builder 单栏默认 refactor (spec 2026-06-02-app-builder-single-pane-default-design):
|
|
287
278
|
// appBuilder:createProject 加 personaId + session 未绑校验后抛的两个错码。
|
|
288
279
|
WRONG_PERSONA: "WRONG_PERSONA",
|
|
@@ -716,8 +707,8 @@ var init_parseUtil = __esm({
|
|
|
716
707
|
init_errors2();
|
|
717
708
|
init_en();
|
|
718
709
|
makeIssue = (params) => {
|
|
719
|
-
const { data, path:
|
|
720
|
-
const fullPath = [...
|
|
710
|
+
const { data, path: path59, errorMaps, issueData } = params;
|
|
711
|
+
const fullPath = [...path59, ...issueData.path || []];
|
|
721
712
|
const fullIssue = {
|
|
722
713
|
...issueData,
|
|
723
714
|
path: fullPath
|
|
@@ -1028,11 +1019,11 @@ var init_types = __esm({
|
|
|
1028
1019
|
init_parseUtil();
|
|
1029
1020
|
init_util();
|
|
1030
1021
|
ParseInputLazyPath = class {
|
|
1031
|
-
constructor(parent, value,
|
|
1022
|
+
constructor(parent, value, path59, key) {
|
|
1032
1023
|
this._cachedPath = [];
|
|
1033
1024
|
this.parent = parent;
|
|
1034
1025
|
this.data = value;
|
|
1035
|
-
this._path =
|
|
1026
|
+
this._path = path59;
|
|
1036
1027
|
this._key = key;
|
|
1037
1028
|
}
|
|
1038
1029
|
get path() {
|
|
@@ -4673,41 +4664,53 @@ var init_schemas = __esm({
|
|
|
4673
4664
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4674
4665
|
chatId: external_exports.string().min(1).optional(),
|
|
4675
4666
|
/**
|
|
4676
|
-
* 创建该 session
|
|
4677
|
-
*
|
|
4678
|
-
*
|
|
4679
|
-
*
|
|
4667
|
+
* 创建该 session 的 principal id(owner uuid 或 guest cap.id),从 session:create handler
|
|
4668
|
+
* 的 ctx.principal.id 派生。Person identity Q2「按对端人聚合 sessions」需要它:
|
|
4669
|
+
* UI 端按 `creatorPrincipalId === ownerPrincipalId` 区分「我自己」vs「别人接入」,
|
|
4670
|
+
* 后者再通过 PersonAlias 表反查 personId 二级分组。
|
|
4680
4671
|
*
|
|
4681
|
-
*
|
|
4682
|
-
* subDevice 背书(guest ctx)或本机 deviceId(owner ctx)。optional 兼容老 session。
|
|
4683
|
-
*
|
|
4684
|
-
* 决策 #16 删除了 creatorOwnerPrincipalId(personal token #735 遗留)——当年 guest 的
|
|
4685
|
-
* principal.id 都是同张 cap.id 区分不了人才加它;现在 principal.id = 设备 deviceId、
|
|
4686
|
-
* 归属人由 contact(deviceId→ownerId) 反查,该字段理由消失。
|
|
4672
|
+
* optional:兼容 2026-05-21 之前的老 session 数据,新建一定有值。
|
|
4687
4673
|
*/
|
|
4688
4674
|
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4689
4675
|
/**
|
|
4690
|
-
*
|
|
4691
|
-
*
|
|
4692
|
-
*
|
|
4676
|
+
* 创建该 session 的接入者 owner identity(auth 帧 selfPrincipalId)。global personal token
|
|
4677
|
+
* (#735) 后 creatorPrincipalId 在 guest ctx 永远是同一张 personal cap.id,UI 端无法区分
|
|
4678
|
+
* 接入者;这个字段把"是哪个人"的信息固定下来。
|
|
4679
|
+
*
|
|
4680
|
+
* 派生规则:
|
|
4681
|
+
* - owner ctx 创建:= 本机 ownerPrincipalId
|
|
4682
|
+
* - guest ctx + auth 帧带 selfPrincipalId:= ctx.peerOwnerPrincipalId
|
|
4683
|
+
* - 旧 per-peer cap 路径 / 老 session:undefined(UI 视作 self 兼容)
|
|
4684
|
+
*
|
|
4685
|
+
* UI 用法:
|
|
4686
|
+
* - Recents/Active/Pinned 过滤:`!creatorOwnerPrincipalId || === ownerPrincipalId` 才显示
|
|
4687
|
+
* - MyPersonas 二级分组:non-self → 按 contacts 反查 displayName 分桶
|
|
4688
|
+
*
|
|
4689
|
+
* 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
|
|
4690
|
+
*/
|
|
4691
|
+
creatorOwnerPrincipalId: external_exports.string().min(1).optional(),
|
|
4692
|
+
/**
|
|
4693
|
+
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上以 guest ctx
|
|
4694
|
+
* 创建的 session**,远端是 source of truth,本机是该 session 的持久化副本,方便
|
|
4695
|
+
* sidebar 跨设备聚合。字段值是远端 daemon 的 ownerPrincipalId(即我接入的那位朋友)。
|
|
4693
4696
|
*
|
|
4694
4697
|
* 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
|
|
4695
4698
|
* 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
|
|
4696
4699
|
*
|
|
4697
4700
|
* - undefined / 缺省 → 本机 native session(在我自己机器上跑)
|
|
4698
|
-
* - 非空 → 我的会话在该
|
|
4701
|
+
* - 非空 → 我的会话在该 ownerPrincipalId 朋友的 daemon 上跑,本机为持久化副本
|
|
4699
4702
|
*
|
|
4700
|
-
*
|
|
4701
|
-
* | 形态 | creatorPrincipalId
|
|
4702
|
-
*
|
|
4703
|
-
* | 本机 owner 自创 | 本机
|
|
4704
|
-
* |
|
|
4705
|
-
* | 我在朋友 daemon
|
|
4703
|
+
* 与 `creatorPrincipalId` / `creatorOwnerPrincipalId` 在四类 session 下取值矩阵:
|
|
4704
|
+
* | 形态 | creatorPrincipalId | creatorOwnerPrincipalId | originOwnerPrincipalId |
|
|
4705
|
+
* |-------------------------------------|--------------------------|-------------------------|------------------------|
|
|
4706
|
+
* | 本机 owner 自创 | 本机 ownerPrincipalId | 本机 ownerPrincipalId | undefined |
|
|
4707
|
+
* | 本机被别人通过 personal token 接入创建 | 远端接入者 cap.id | 远端接入者 ownerPrincipalId | undefined |
|
|
4708
|
+
* | 我在朋友 daemon 上以 guest ctx 创建(mirror 回本机) | 朋友 daemon personal cap.id | 我自己 ownerPrincipalId | 朋友 ownerPrincipalId |
|
|
4706
4709
|
*
|
|
4707
4710
|
* UI 用法:
|
|
4708
4711
|
* - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
|
|
4709
4712
|
* - 行内显示 `@<朋友>` chip 暗示位置
|
|
4710
|
-
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)`
|
|
4713
|
+
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端
|
|
4711
4714
|
*
|
|
4712
4715
|
* daemon 用法:
|
|
4713
4716
|
* - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
|
|
@@ -5141,14 +5144,18 @@ var init_schemas = __esm({
|
|
|
5141
5144
|
token: external_exports.string().min(1),
|
|
5142
5145
|
scheme: external_exports.literal("bearer").optional(),
|
|
5143
5146
|
/**
|
|
5144
|
-
*
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5147
|
-
*
|
|
5148
|
-
* selfUrl 保留:它只是"可达地址"(auto-reverse 反向连接用),不是身份——填错只影响
|
|
5149
|
-
* 反向连通性,不构成伪造风险,且 url 不在 token 里(token 只签身份不签地址)。
|
|
5147
|
+
* guest 端 ws 接入时自报对端稳定 ownerPrincipalId。AuthGate.authenticate 收到
|
|
5148
|
+
* 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
|
|
5149
|
+
* 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
|
|
5150
|
+
* tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
|
|
5150
5151
|
*/
|
|
5151
|
-
|
|
5152
|
+
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5153
|
+
/**
|
|
5154
|
+
* 同 selfPrincipalId:自报 owner displayName(人类可读),让对端 daemon 把
|
|
5155
|
+
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5156
|
+
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5157
|
+
*/
|
|
5158
|
+
selfDisplayName: external_exports.string().optional()
|
|
5152
5159
|
});
|
|
5153
5160
|
AuthOkFrameSchema = external_exports.object({
|
|
5154
5161
|
type: external_exports.literal("auth:ok"),
|
|
@@ -5417,7 +5424,7 @@ function stripSecretHash(cap) {
|
|
|
5417
5424
|
const { secretHash: _hash, ...wire } = cap;
|
|
5418
5425
|
return wire;
|
|
5419
5426
|
}
|
|
5420
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5427
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS, PersonalCapGetResponseSchema;
|
|
5421
5428
|
var init_capability = __esm({
|
|
5422
5429
|
"../protocol/src/capability.ts"() {
|
|
5423
5430
|
"use strict";
|
|
@@ -5469,11 +5476,7 @@ var init_capability = __esm({
|
|
|
5469
5476
|
owner: external_exports.object({
|
|
5470
5477
|
id: external_exports.string(),
|
|
5471
5478
|
kind: external_exports.enum(["owner", "guest"]),
|
|
5472
|
-
displayName: external_exports.string()
|
|
5473
|
-
/** 归属人 id(决策 #16:TTC unique_id 等,落 contact.ownerId) */
|
|
5474
|
-
ownerId: external_exports.string(),
|
|
5475
|
-
/** 身份来源(决策 #16:'ttc'/'google',落 contact.provider) */
|
|
5476
|
-
provider: external_exports.string()
|
|
5479
|
+
displayName: external_exports.string()
|
|
5477
5480
|
}).strict(),
|
|
5478
5481
|
capability: CapabilityWireSchema,
|
|
5479
5482
|
grantedPersonas: external_exports.array(
|
|
@@ -5489,6 +5492,13 @@ var init_capability = __esm({
|
|
|
5489
5492
|
actions: Object.freeze(["read", "send"])
|
|
5490
5493
|
})
|
|
5491
5494
|
]);
|
|
5495
|
+
PersonalCapGetResponseSchema = external_exports.object({
|
|
5496
|
+
type: external_exports.literal("personal-cap:get:ok"),
|
|
5497
|
+
token: external_exports.string().min(1),
|
|
5498
|
+
capability: CapabilityWireSchema,
|
|
5499
|
+
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5500
|
+
shareBaseUrl: external_exports.string().min(1)
|
|
5501
|
+
}).strict();
|
|
5492
5502
|
}
|
|
5493
5503
|
});
|
|
5494
5504
|
|
|
@@ -5500,80 +5510,83 @@ var init_inbox = __esm({
|
|
|
5500
5510
|
init_zod();
|
|
5501
5511
|
InboxMessageSchema = external_exports.object({
|
|
5502
5512
|
id: external_exports.string().min(1),
|
|
5503
|
-
|
|
5504
|
-
|
|
5513
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5514
|
+
senderPrincipalId: external_exports.string().min(1),
|
|
5505
5515
|
text: external_exports.string().min(1),
|
|
5506
5516
|
createdAt: external_exports.number().int().nonnegative(),
|
|
5507
5517
|
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5508
5518
|
}).strict();
|
|
5509
5519
|
InboxPostMessageArgsSchema = external_exports.object({
|
|
5510
|
-
|
|
5520
|
+
peerOwnerId: external_exports.string().min(1).optional(),
|
|
5511
5521
|
id: external_exports.string().min(1),
|
|
5512
5522
|
text: external_exports.string().min(1),
|
|
5513
5523
|
createdAt: external_exports.number().int().nonnegative()
|
|
5514
5524
|
}).strict();
|
|
5515
5525
|
InboxListArgsSchema = external_exports.object({
|
|
5516
|
-
|
|
5526
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5517
5527
|
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5518
5528
|
}).strict();
|
|
5519
5529
|
InboxMarkReadArgsSchema = external_exports.object({
|
|
5520
|
-
|
|
5530
|
+
peerOwnerId: external_exports.string().min(1),
|
|
5521
5531
|
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5522
5532
|
}).strict();
|
|
5523
5533
|
}
|
|
5524
5534
|
});
|
|
5525
5535
|
|
|
5526
|
-
// ../protocol/src/
|
|
5527
|
-
var
|
|
5528
|
-
var
|
|
5529
|
-
"../protocol/src/
|
|
5536
|
+
// ../protocol/src/received-capability.ts
|
|
5537
|
+
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityAddArgsSchema, ReceivedCapabilityAddOkSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAutoAttachArgsSchema, ReceivedCapabilityAutoAttachOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
|
|
5538
|
+
var init_received_capability = __esm({
|
|
5539
|
+
"../protocol/src/received-capability.ts"() {
|
|
5530
5540
|
"use strict";
|
|
5531
5541
|
init_zod();
|
|
5532
5542
|
init_capability();
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
* 如 dev-<uuid>),provider 无关。同 deviceId 重复 upsert 覆盖。
|
|
5537
|
-
*/
|
|
5538
|
-
deviceId: external_exports.string().min(1),
|
|
5539
|
-
/**
|
|
5540
|
-
* 归属人 id(决策 #16):TTC unique_id 等,由 connect token subOwner 签名背书。
|
|
5541
|
-
* 与 provider 一起唯一标识一个人。DM 不再按它路由(路由用 deviceId),仅显示/分组用。
|
|
5542
|
-
*/
|
|
5543
|
-
ownerId: external_exports.string().min(1),
|
|
5544
|
-
/** 身份来源(决策 #16):'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...,token 背书 */
|
|
5545
|
-
provider: external_exports.string().min(1),
|
|
5546
|
-
/** 联系人显示名 */
|
|
5547
|
-
displayName: external_exports.string(),
|
|
5548
|
-
/** 该设备可达的 ws/tunnel 地址 */
|
|
5543
|
+
ReceivedCapabilitySchema = external_exports.object({
|
|
5544
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5545
|
+
ownerDisplayName: external_exports.string(),
|
|
5549
5546
|
remoteUrl: external_exports.string().min(1),
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
// device:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5553
|
-
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5554
|
-
connectToken: external_exports.string(),
|
|
5547
|
+
capabilityId: external_exports.string().min(1),
|
|
5548
|
+
capabilityToken: external_exports.string().min(1),
|
|
5555
5549
|
grants: external_exports.array(GrantSchema),
|
|
5556
|
-
|
|
5550
|
+
receivedAt: external_exports.number().int().nonnegative()
|
|
5551
|
+
}).strict();
|
|
5552
|
+
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5553
|
+
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5554
|
+
remoteUrl: external_exports.string().min(1),
|
|
5555
|
+
token: external_exports.string().min(1)
|
|
5556
|
+
}).strict();
|
|
5557
|
+
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5558
|
+
type: external_exports.literal("received-capability:add:ok"),
|
|
5559
|
+
receivedCap: ReceivedCapabilityWireSchema
|
|
5557
5560
|
}).strict();
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
deviceId: external_exports.string().min(1)
|
|
5561
|
+
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5562
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5561
5563
|
}).strict();
|
|
5562
|
-
|
|
5563
|
-
type: external_exports.literal("
|
|
5564
|
-
|
|
5564
|
+
ReceivedCapabilityRemoveOkSchema = external_exports.object({
|
|
5565
|
+
type: external_exports.literal("received-capability:remove:ok"),
|
|
5566
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5567
|
+
}).strict();
|
|
5568
|
+
ReceivedCapabilityListOkSchema = external_exports.object({
|
|
5569
|
+
type: external_exports.literal("received-capability:list:ok"),
|
|
5570
|
+
receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
|
|
5571
|
+
}).strict();
|
|
5572
|
+
ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
|
|
5573
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5574
|
+
ownerDisplayName: external_exports.string(),
|
|
5575
|
+
remoteUrl: external_exports.string().min(1),
|
|
5576
|
+
capabilityId: external_exports.string().min(1),
|
|
5577
|
+
token: external_exports.string().min(1),
|
|
5578
|
+
grants: external_exports.array(GrantSchema)
|
|
5565
5579
|
}).strict();
|
|
5566
|
-
|
|
5567
|
-
type: external_exports.literal("
|
|
5568
|
-
contacts: external_exports.array(ContactWireSchema)
|
|
5580
|
+
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5581
|
+
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5569
5582
|
}).strict();
|
|
5570
|
-
|
|
5571
|
-
type: external_exports.literal("
|
|
5572
|
-
|
|
5583
|
+
ReceivedCapabilityAddedFrameSchema = external_exports.object({
|
|
5584
|
+
type: external_exports.literal("received-capability:added"),
|
|
5585
|
+
receivedCap: ReceivedCapabilityWireSchema
|
|
5573
5586
|
}).strict();
|
|
5574
|
-
|
|
5575
|
-
type: external_exports.literal("
|
|
5576
|
-
|
|
5587
|
+
ReceivedCapabilityRemovedFrameSchema = external_exports.object({
|
|
5588
|
+
type: external_exports.literal("received-capability:removed"),
|
|
5589
|
+
ownerPrincipalId: external_exports.string().min(1)
|
|
5577
5590
|
}).strict();
|
|
5578
5591
|
}
|
|
5579
5592
|
});
|
|
@@ -5777,107 +5790,6 @@ var init_extension = __esm({
|
|
|
5777
5790
|
}
|
|
5778
5791
|
});
|
|
5779
5792
|
|
|
5780
|
-
// ../protocol/src/feishu-auth.ts
|
|
5781
|
-
var FEISHU_GATED_METHODS, DeviceEntrySchema, DeviceListResponseSchema, DeviceConnectArgsSchema, DeviceConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
|
|
5782
|
-
var init_feishu_auth = __esm({
|
|
5783
|
-
"../protocol/src/feishu-auth.ts"() {
|
|
5784
|
-
"use strict";
|
|
5785
|
-
init_zod();
|
|
5786
|
-
FEISHU_GATED_METHODS = [
|
|
5787
|
-
// capability(调试 / 撤销,仍属远程身份体系)
|
|
5788
|
-
"capability:list",
|
|
5789
|
-
"capability:delete",
|
|
5790
|
-
// 联系人列表(device:connect 落同一 store;list 读 / remove 删)
|
|
5791
|
-
"contact:list",
|
|
5792
|
-
"contact:remove",
|
|
5793
|
-
// DM / 跨用户通知
|
|
5794
|
-
"inbox:list",
|
|
5795
|
-
"inbox:markRead",
|
|
5796
|
-
"inbox:postMessage",
|
|
5797
|
-
// mirror peer sessions(远端会话镜像)
|
|
5798
|
-
"peerSession:upsert",
|
|
5799
|
-
"peerSession:remove",
|
|
5800
|
-
// 设备目录 + 连接(决策 #15:daemon 代理中心 server,需要 owner 的 ttcJwt)
|
|
5801
|
-
"device:list",
|
|
5802
|
-
"device:connect"
|
|
5803
|
-
];
|
|
5804
|
-
DeviceEntrySchema = external_exports.object({
|
|
5805
|
-
/**
|
|
5806
|
-
* 设备标识(决策 #15:daemon 本地生成的 dev-<uuid>,与人的身份 unionId 无关)。
|
|
5807
|
-
* 中心 server clawd_devices 注册表仅作服务发现(url 下发 + 目录),不在安全路径上。
|
|
5808
|
-
*/
|
|
5809
|
-
deviceId: external_exports.string().min(1),
|
|
5810
|
-
/** 显示名 */
|
|
5811
|
-
name: external_exports.string().min(1),
|
|
5812
|
-
/** ws/tunnel 地址 */
|
|
5813
|
-
url: external_exports.string().min(1)
|
|
5814
|
-
});
|
|
5815
|
-
DeviceListResponseSchema = external_exports.object({
|
|
5816
|
-
type: external_exports.literal("device:list:ok"),
|
|
5817
|
-
devices: external_exports.array(DeviceEntrySchema)
|
|
5818
|
-
});
|
|
5819
|
-
DeviceConnectArgsSchema = external_exports.object({
|
|
5820
|
-
/** 目标设备的 deviceId(对方 daemon 生成的 dev-<uuid>) */
|
|
5821
|
-
deviceId: external_exports.string().min(1),
|
|
5822
|
-
/**
|
|
5823
|
-
* 目标设备的 ws/tunnel 地址(可选):
|
|
5824
|
-
* - 公开目录点击:带 url(device:list 已下发,省一次 server 查询)
|
|
5825
|
-
* - 凭 deviceId 连接:缺省 url → daemon exchange 时由中心 server 下发该设备上报的 url
|
|
5826
|
-
* - 注册表不在安全路径上:知道对方 url 时显式传入即可连接(不依赖中心 server 服务发现)
|
|
5827
|
-
*/
|
|
5828
|
-
url: external_exports.string().min(1).optional(),
|
|
5829
|
-
/** 显示名(公共设备来自目录;缺省时连上后用 whoami 回填) */
|
|
5830
|
-
name: external_exports.string().optional()
|
|
5831
|
-
});
|
|
5832
|
-
DeviceConnectResponseSchema = external_exports.object({
|
|
5833
|
-
type: external_exports.literal("device:connect:ok"),
|
|
5834
|
-
/** 连接的目标设备标识(= 请求 args.deviceId) */
|
|
5835
|
-
deviceId: external_exports.string(),
|
|
5836
|
-
name: external_exports.string(),
|
|
5837
|
-
url: external_exports.string()
|
|
5838
|
-
});
|
|
5839
|
-
FeishuIdentitySchema = external_exports.object({
|
|
5840
|
-
/** 归属人 id(决策 #16:TTC unique_id,取代飞书 union_id;跨登录方式稳定) */
|
|
5841
|
-
ownerId: external_exports.string().min(1),
|
|
5842
|
-
/** 身份来源:'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...(决策 #16,拓展用) */
|
|
5843
|
-
provider: external_exports.string().min(1),
|
|
5844
|
-
/** 显示名。TTC user_info 接口 data.name */
|
|
5845
|
-
displayName: external_exports.string().min(1),
|
|
5846
|
-
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
5847
|
-
avatarUrl: external_exports.string().optional()
|
|
5848
|
-
});
|
|
5849
|
-
AuthLoginStartResponseSchema = external_exports.object({
|
|
5850
|
-
type: external_exports.literal("auth:login:start:ok"),
|
|
5851
|
-
/** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
|
|
5852
|
-
authUrl: external_exports.string().min(1),
|
|
5853
|
-
/** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
|
|
5854
|
-
state: external_exports.string().min(1)
|
|
5855
|
-
});
|
|
5856
|
-
AuthGetIdentityResponseSchema = external_exports.object({
|
|
5857
|
-
type: external_exports.literal("auth:getIdentity:ok"),
|
|
5858
|
-
/** null = 未登录 */
|
|
5859
|
-
identity: FeishuIdentitySchema.nullable(),
|
|
5860
|
-
/**
|
|
5861
|
-
* 本机设备标识(决策 #15:daemon 生成并持久化在 auth.json,如 dev-<uuid>)。
|
|
5862
|
-
* 设备属性,与登录态无关、登录登出不变。UI"我的设备标识"用它拼
|
|
5863
|
-
* clawos://device/<deviceId>(替代旧的 unionId——不再向外暴露人的身份标识)。
|
|
5864
|
-
*/
|
|
5865
|
-
deviceId: external_exports.string().min(1)
|
|
5866
|
-
});
|
|
5867
|
-
AuthLogoutResponseSchema = external_exports.object({
|
|
5868
|
-
type: external_exports.literal("auth:logout:ok")
|
|
5869
|
-
});
|
|
5870
|
-
AuthLoginDoneEventSchema = external_exports.object({
|
|
5871
|
-
type: external_exports.literal("auth:login:done"),
|
|
5872
|
-
identity: FeishuIdentitySchema
|
|
5873
|
-
});
|
|
5874
|
-
AuthLoginFailedEventSchema = external_exports.object({
|
|
5875
|
-
type: external_exports.literal("auth:login:failed"),
|
|
5876
|
-
reason: external_exports.string()
|
|
5877
|
-
});
|
|
5878
|
-
}
|
|
5879
|
-
});
|
|
5880
|
-
|
|
5881
5793
|
// ../protocol/src/runtime.ts
|
|
5882
5794
|
var init_runtime = __esm({
|
|
5883
5795
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5893,9 +5805,8 @@ var init_runtime = __esm({
|
|
|
5893
5805
|
init_principal();
|
|
5894
5806
|
init_capability();
|
|
5895
5807
|
init_inbox();
|
|
5896
|
-
|
|
5808
|
+
init_received_capability();
|
|
5897
5809
|
init_extension();
|
|
5898
|
-
init_feishu_auth();
|
|
5899
5810
|
}
|
|
5900
5811
|
});
|
|
5901
5812
|
|
|
@@ -6170,8 +6081,8 @@ var require_req = __commonJS({
|
|
|
6170
6081
|
if (req.originalUrl) {
|
|
6171
6082
|
_req.url = req.originalUrl;
|
|
6172
6083
|
} else {
|
|
6173
|
-
const
|
|
6174
|
-
_req.url = typeof
|
|
6084
|
+
const path59 = req.path;
|
|
6085
|
+
_req.url = typeof path59 === "string" ? path59 : req.url ? req.url.path || req.url : void 0;
|
|
6175
6086
|
}
|
|
6176
6087
|
if (req.query) {
|
|
6177
6088
|
_req.query = req.query;
|
|
@@ -6336,14 +6247,14 @@ var require_redact = __commonJS({
|
|
|
6336
6247
|
}
|
|
6337
6248
|
return obj;
|
|
6338
6249
|
}
|
|
6339
|
-
function parsePath(
|
|
6250
|
+
function parsePath(path59) {
|
|
6340
6251
|
const parts = [];
|
|
6341
6252
|
let current = "";
|
|
6342
6253
|
let inBrackets = false;
|
|
6343
6254
|
let inQuotes = false;
|
|
6344
6255
|
let quoteChar = "";
|
|
6345
|
-
for (let i = 0; i <
|
|
6346
|
-
const char =
|
|
6256
|
+
for (let i = 0; i < path59.length; i++) {
|
|
6257
|
+
const char = path59[i];
|
|
6347
6258
|
if (!inBrackets && char === ".") {
|
|
6348
6259
|
if (current) {
|
|
6349
6260
|
parts.push(current);
|
|
@@ -6474,10 +6385,10 @@ var require_redact = __commonJS({
|
|
|
6474
6385
|
return current;
|
|
6475
6386
|
}
|
|
6476
6387
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
6477
|
-
for (const
|
|
6478
|
-
const parts = parsePath(
|
|
6388
|
+
for (const path59 of paths) {
|
|
6389
|
+
const parts = parsePath(path59);
|
|
6479
6390
|
if (parts.includes("*")) {
|
|
6480
|
-
redactWildcardPath(obj, parts, censor,
|
|
6391
|
+
redactWildcardPath(obj, parts, censor, path59, remove);
|
|
6481
6392
|
} else {
|
|
6482
6393
|
if (remove) {
|
|
6483
6394
|
removeKey(obj, parts);
|
|
@@ -6562,8 +6473,8 @@ var require_redact = __commonJS({
|
|
|
6562
6473
|
}
|
|
6563
6474
|
} else {
|
|
6564
6475
|
if (afterWildcard.includes("*")) {
|
|
6565
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6566
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
6476
|
+
const wrappedCensor = typeof censor === "function" ? (value, path59) => {
|
|
6477
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path59];
|
|
6567
6478
|
return censor(value, fullPath);
|
|
6568
6479
|
} : censor;
|
|
6569
6480
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6598,8 +6509,8 @@ var require_redact = __commonJS({
|
|
|
6598
6509
|
return null;
|
|
6599
6510
|
}
|
|
6600
6511
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6601
|
-
for (const
|
|
6602
|
-
const parts = parsePath(
|
|
6512
|
+
for (const path59 of pathsToClone) {
|
|
6513
|
+
const parts = parsePath(path59);
|
|
6603
6514
|
let current = pathStructure;
|
|
6604
6515
|
for (let i = 0; i < parts.length; i++) {
|
|
6605
6516
|
const part = parts[i];
|
|
@@ -6651,24 +6562,24 @@ var require_redact = __commonJS({
|
|
|
6651
6562
|
}
|
|
6652
6563
|
return cloneSelectively(obj, pathStructure);
|
|
6653
6564
|
}
|
|
6654
|
-
function validatePath(
|
|
6655
|
-
if (typeof
|
|
6565
|
+
function validatePath(path59) {
|
|
6566
|
+
if (typeof path59 !== "string") {
|
|
6656
6567
|
throw new Error("Paths must be (non-empty) strings");
|
|
6657
6568
|
}
|
|
6658
|
-
if (
|
|
6569
|
+
if (path59 === "") {
|
|
6659
6570
|
throw new Error("Invalid redaction path ()");
|
|
6660
6571
|
}
|
|
6661
|
-
if (
|
|
6662
|
-
throw new Error(`Invalid redaction path (${
|
|
6572
|
+
if (path59.includes("..")) {
|
|
6573
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6663
6574
|
}
|
|
6664
|
-
if (
|
|
6665
|
-
throw new Error(`Invalid redaction path (${
|
|
6575
|
+
if (path59.includes(",")) {
|
|
6576
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6666
6577
|
}
|
|
6667
6578
|
let bracketCount = 0;
|
|
6668
6579
|
let inQuotes = false;
|
|
6669
6580
|
let quoteChar = "";
|
|
6670
|
-
for (let i = 0; i <
|
|
6671
|
-
const char =
|
|
6581
|
+
for (let i = 0; i < path59.length; i++) {
|
|
6582
|
+
const char = path59[i];
|
|
6672
6583
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6673
6584
|
if (!inQuotes) {
|
|
6674
6585
|
inQuotes = true;
|
|
@@ -6682,20 +6593,20 @@ var require_redact = __commonJS({
|
|
|
6682
6593
|
} else if (char === "]" && !inQuotes) {
|
|
6683
6594
|
bracketCount--;
|
|
6684
6595
|
if (bracketCount < 0) {
|
|
6685
|
-
throw new Error(`Invalid redaction path (${
|
|
6596
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6686
6597
|
}
|
|
6687
6598
|
}
|
|
6688
6599
|
}
|
|
6689
6600
|
if (bracketCount !== 0) {
|
|
6690
|
-
throw new Error(`Invalid redaction path (${
|
|
6601
|
+
throw new Error(`Invalid redaction path (${path59})`);
|
|
6691
6602
|
}
|
|
6692
6603
|
}
|
|
6693
6604
|
function validatePaths(paths) {
|
|
6694
6605
|
if (!Array.isArray(paths)) {
|
|
6695
6606
|
throw new TypeError("paths must be an array");
|
|
6696
6607
|
}
|
|
6697
|
-
for (const
|
|
6698
|
-
validatePath(
|
|
6608
|
+
for (const path59 of paths) {
|
|
6609
|
+
validatePath(path59);
|
|
6699
6610
|
}
|
|
6700
6611
|
}
|
|
6701
6612
|
function slowRedact(options = {}) {
|
|
@@ -6863,8 +6774,8 @@ var require_redaction = __commonJS({
|
|
|
6863
6774
|
if (shape[k2] === null) {
|
|
6864
6775
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6865
6776
|
} else {
|
|
6866
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6867
|
-
return censor(value, [k2, ...
|
|
6777
|
+
const wrappedCensor = typeof censor === "function" ? (value, path59) => {
|
|
6778
|
+
return censor(value, [k2, ...path59]);
|
|
6868
6779
|
} : censor;
|
|
6869
6780
|
o[k2] = Redact({
|
|
6870
6781
|
paths: shape[k2],
|
|
@@ -7082,10 +6993,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
7082
6993
|
var require_sonic_boom = __commonJS({
|
|
7083
6994
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
7084
6995
|
"use strict";
|
|
7085
|
-
var
|
|
6996
|
+
var fs48 = require("fs");
|
|
7086
6997
|
var EventEmitter3 = require("events");
|
|
7087
6998
|
var inherits = require("util").inherits;
|
|
7088
|
-
var
|
|
6999
|
+
var path59 = require("path");
|
|
7089
7000
|
var sleep = require_atomic_sleep();
|
|
7090
7001
|
var assert = require("assert");
|
|
7091
7002
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -7139,20 +7050,20 @@ var require_sonic_boom = __commonJS({
|
|
|
7139
7050
|
const mode = sonic.mode;
|
|
7140
7051
|
if (sonic.sync) {
|
|
7141
7052
|
try {
|
|
7142
|
-
if (sonic.mkdir)
|
|
7143
|
-
const fd =
|
|
7053
|
+
if (sonic.mkdir) fs48.mkdirSync(path59.dirname(file), { recursive: true });
|
|
7054
|
+
const fd = fs48.openSync(file, flags, mode);
|
|
7144
7055
|
fileOpened(null, fd);
|
|
7145
7056
|
} catch (err) {
|
|
7146
7057
|
fileOpened(err);
|
|
7147
7058
|
throw err;
|
|
7148
7059
|
}
|
|
7149
7060
|
} else if (sonic.mkdir) {
|
|
7150
|
-
|
|
7061
|
+
fs48.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
7151
7062
|
if (err) return fileOpened(err);
|
|
7152
|
-
|
|
7063
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
7153
7064
|
});
|
|
7154
7065
|
} else {
|
|
7155
|
-
|
|
7066
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
7156
7067
|
}
|
|
7157
7068
|
}
|
|
7158
7069
|
function SonicBoom(opts) {
|
|
@@ -7193,8 +7104,8 @@ var require_sonic_boom = __commonJS({
|
|
|
7193
7104
|
this.flush = flushBuffer;
|
|
7194
7105
|
this.flushSync = flushBufferSync;
|
|
7195
7106
|
this._actualWrite = actualWriteBuffer;
|
|
7196
|
-
fsWriteSync = () =>
|
|
7197
|
-
fsWrite = () =>
|
|
7107
|
+
fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
|
|
7108
|
+
fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
|
|
7198
7109
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
7199
7110
|
this._writingBuf = "";
|
|
7200
7111
|
this.write = write;
|
|
@@ -7203,15 +7114,15 @@ var require_sonic_boom = __commonJS({
|
|
|
7203
7114
|
this._actualWrite = actualWrite;
|
|
7204
7115
|
fsWriteSync = () => {
|
|
7205
7116
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7206
|
-
return
|
|
7117
|
+
return fs48.writeSync(this.fd, this._writingBuf);
|
|
7207
7118
|
}
|
|
7208
|
-
return
|
|
7119
|
+
return fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7209
7120
|
};
|
|
7210
7121
|
fsWrite = () => {
|
|
7211
7122
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7212
|
-
return
|
|
7123
|
+
return fs48.write(this.fd, this._writingBuf, this.release);
|
|
7213
7124
|
}
|
|
7214
|
-
return
|
|
7125
|
+
return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
7215
7126
|
};
|
|
7216
7127
|
} else {
|
|
7217
7128
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7268,7 +7179,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7268
7179
|
}
|
|
7269
7180
|
}
|
|
7270
7181
|
if (this._fsync) {
|
|
7271
|
-
|
|
7182
|
+
fs48.fsyncSync(this.fd);
|
|
7272
7183
|
}
|
|
7273
7184
|
const len = this._len;
|
|
7274
7185
|
if (this._reopening) {
|
|
@@ -7382,7 +7293,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7382
7293
|
const onDrain = () => {
|
|
7383
7294
|
if (!this._fsync) {
|
|
7384
7295
|
try {
|
|
7385
|
-
|
|
7296
|
+
fs48.fsync(this.fd, (err) => {
|
|
7386
7297
|
this._flushPending = false;
|
|
7387
7298
|
cb(err);
|
|
7388
7299
|
});
|
|
@@ -7484,7 +7395,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7484
7395
|
const fd = this.fd;
|
|
7485
7396
|
this.once("ready", () => {
|
|
7486
7397
|
if (fd !== this.fd) {
|
|
7487
|
-
|
|
7398
|
+
fs48.close(fd, (err) => {
|
|
7488
7399
|
if (err) {
|
|
7489
7400
|
return this.emit("error", err);
|
|
7490
7401
|
}
|
|
@@ -7533,7 +7444,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7533
7444
|
buf = this._bufs[0];
|
|
7534
7445
|
}
|
|
7535
7446
|
try {
|
|
7536
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7447
|
+
const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
|
|
7537
7448
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7538
7449
|
buf = releasedBufObj.writingBuf;
|
|
7539
7450
|
this._len = releasedBufObj.len;
|
|
@@ -7549,7 +7460,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7549
7460
|
}
|
|
7550
7461
|
}
|
|
7551
7462
|
try {
|
|
7552
|
-
|
|
7463
|
+
fs48.fsyncSync(this.fd);
|
|
7553
7464
|
} catch {
|
|
7554
7465
|
}
|
|
7555
7466
|
}
|
|
@@ -7570,7 +7481,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7570
7481
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7571
7482
|
}
|
|
7572
7483
|
try {
|
|
7573
|
-
const n =
|
|
7484
|
+
const n = fs48.writeSync(this.fd, buf);
|
|
7574
7485
|
buf = buf.subarray(n);
|
|
7575
7486
|
this._len = Math.max(this._len - n, 0);
|
|
7576
7487
|
if (buf.length <= 0) {
|
|
@@ -7598,13 +7509,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7598
7509
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7599
7510
|
if (this.sync) {
|
|
7600
7511
|
try {
|
|
7601
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7512
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7602
7513
|
release(null, written);
|
|
7603
7514
|
} catch (err) {
|
|
7604
7515
|
release(err);
|
|
7605
7516
|
}
|
|
7606
7517
|
} else {
|
|
7607
|
-
|
|
7518
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7608
7519
|
}
|
|
7609
7520
|
}
|
|
7610
7521
|
function actualWriteBuffer() {
|
|
@@ -7613,7 +7524,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7613
7524
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7614
7525
|
if (this.sync) {
|
|
7615
7526
|
try {
|
|
7616
|
-
const written =
|
|
7527
|
+
const written = fs48.writeSync(this.fd, this._writingBuf);
|
|
7617
7528
|
release(null, written);
|
|
7618
7529
|
} catch (err) {
|
|
7619
7530
|
release(err);
|
|
@@ -7622,7 +7533,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7622
7533
|
if (kCopyBuffer) {
|
|
7623
7534
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7624
7535
|
}
|
|
7625
|
-
|
|
7536
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7626
7537
|
}
|
|
7627
7538
|
}
|
|
7628
7539
|
function actualClose(sonic) {
|
|
@@ -7638,12 +7549,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7638
7549
|
sonic._lens = [];
|
|
7639
7550
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7640
7551
|
try {
|
|
7641
|
-
|
|
7552
|
+
fs48.fsync(sonic.fd, closeWrapped);
|
|
7642
7553
|
} catch {
|
|
7643
7554
|
}
|
|
7644
7555
|
function closeWrapped() {
|
|
7645
7556
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7646
|
-
|
|
7557
|
+
fs48.close(sonic.fd, done);
|
|
7647
7558
|
} else {
|
|
7648
7559
|
done();
|
|
7649
7560
|
}
|
|
@@ -7900,7 +7811,7 @@ var require_thread_stream = __commonJS({
|
|
|
7900
7811
|
var { version: version2 } = require_package();
|
|
7901
7812
|
var { EventEmitter: EventEmitter3 } = require("events");
|
|
7902
7813
|
var { Worker } = require("worker_threads");
|
|
7903
|
-
var { join:
|
|
7814
|
+
var { join: join14 } = require("path");
|
|
7904
7815
|
var { pathToFileURL } = require("url");
|
|
7905
7816
|
var { wait } = require_wait();
|
|
7906
7817
|
var {
|
|
@@ -7936,7 +7847,7 @@ var require_thread_stream = __commonJS({
|
|
|
7936
7847
|
function createWorker(stream, opts) {
|
|
7937
7848
|
const { filename, workerData } = opts;
|
|
7938
7849
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7939
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7850
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join14(__dirname, "lib", "worker.js");
|
|
7940
7851
|
const worker = new Worker(toExecute, {
|
|
7941
7852
|
...opts.workerOpts,
|
|
7942
7853
|
trackUnmanagedFds: false,
|
|
@@ -8322,7 +8233,7 @@ var require_transport = __commonJS({
|
|
|
8322
8233
|
"use strict";
|
|
8323
8234
|
var { createRequire } = require("module");
|
|
8324
8235
|
var getCallers = require_caller();
|
|
8325
|
-
var { join:
|
|
8236
|
+
var { join: join14, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8326
8237
|
var sleep = require_atomic_sleep();
|
|
8327
8238
|
var onExit = require_on_exit_leak_free();
|
|
8328
8239
|
var ThreadStream = require_thread_stream();
|
|
@@ -8385,7 +8296,7 @@ var require_transport = __commonJS({
|
|
|
8385
8296
|
throw new Error("only one of target or targets can be specified");
|
|
8386
8297
|
}
|
|
8387
8298
|
if (targets) {
|
|
8388
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8299
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
8389
8300
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8390
8301
|
return {
|
|
8391
8302
|
...dest,
|
|
@@ -8403,7 +8314,7 @@ var require_transport = __commonJS({
|
|
|
8403
8314
|
});
|
|
8404
8315
|
});
|
|
8405
8316
|
} else if (pipeline3) {
|
|
8406
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8317
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
8407
8318
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8408
8319
|
return {
|
|
8409
8320
|
...dest,
|
|
@@ -8425,7 +8336,7 @@ var require_transport = __commonJS({
|
|
|
8425
8336
|
return origin;
|
|
8426
8337
|
}
|
|
8427
8338
|
if (origin === "pino/file") {
|
|
8428
|
-
return
|
|
8339
|
+
return join14(__dirname, "..", "file.js");
|
|
8429
8340
|
}
|
|
8430
8341
|
let fixTarget2;
|
|
8431
8342
|
for (const filePath of callers) {
|
|
@@ -9415,7 +9326,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9415
9326
|
return circularValue;
|
|
9416
9327
|
}
|
|
9417
9328
|
let res = "";
|
|
9418
|
-
let
|
|
9329
|
+
let join14 = ",";
|
|
9419
9330
|
const originalIndentation = indentation;
|
|
9420
9331
|
if (Array.isArray(value)) {
|
|
9421
9332
|
if (value.length === 0) {
|
|
@@ -9429,7 +9340,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9429
9340
|
indentation += spacer;
|
|
9430
9341
|
res += `
|
|
9431
9342
|
${indentation}`;
|
|
9432
|
-
|
|
9343
|
+
join14 = `,
|
|
9433
9344
|
${indentation}`;
|
|
9434
9345
|
}
|
|
9435
9346
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9437,13 +9348,13 @@ ${indentation}`;
|
|
|
9437
9348
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9438
9349
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9439
9350
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9440
|
-
res +=
|
|
9351
|
+
res += join14;
|
|
9441
9352
|
}
|
|
9442
9353
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9443
9354
|
res += tmp !== void 0 ? tmp : "null";
|
|
9444
9355
|
if (value.length - 1 > maximumBreadth) {
|
|
9445
9356
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9446
|
-
res += `${
|
|
9357
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9447
9358
|
}
|
|
9448
9359
|
if (spacer !== "") {
|
|
9449
9360
|
res += `
|
|
@@ -9464,7 +9375,7 @@ ${originalIndentation}`;
|
|
|
9464
9375
|
let separator = "";
|
|
9465
9376
|
if (spacer !== "") {
|
|
9466
9377
|
indentation += spacer;
|
|
9467
|
-
|
|
9378
|
+
join14 = `,
|
|
9468
9379
|
${indentation}`;
|
|
9469
9380
|
whitespace = " ";
|
|
9470
9381
|
}
|
|
@@ -9478,13 +9389,13 @@ ${indentation}`;
|
|
|
9478
9389
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9479
9390
|
if (tmp !== void 0) {
|
|
9480
9391
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9481
|
-
separator =
|
|
9392
|
+
separator = join14;
|
|
9482
9393
|
}
|
|
9483
9394
|
}
|
|
9484
9395
|
if (keyLength > maximumBreadth) {
|
|
9485
9396
|
const removedKeys = keyLength - maximumBreadth;
|
|
9486
9397
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9487
|
-
separator =
|
|
9398
|
+
separator = join14;
|
|
9488
9399
|
}
|
|
9489
9400
|
if (spacer !== "" && separator.length > 1) {
|
|
9490
9401
|
res = `
|
|
@@ -9525,7 +9436,7 @@ ${originalIndentation}`;
|
|
|
9525
9436
|
}
|
|
9526
9437
|
const originalIndentation = indentation;
|
|
9527
9438
|
let res = "";
|
|
9528
|
-
let
|
|
9439
|
+
let join14 = ",";
|
|
9529
9440
|
if (Array.isArray(value)) {
|
|
9530
9441
|
if (value.length === 0) {
|
|
9531
9442
|
return "[]";
|
|
@@ -9538,7 +9449,7 @@ ${originalIndentation}`;
|
|
|
9538
9449
|
indentation += spacer;
|
|
9539
9450
|
res += `
|
|
9540
9451
|
${indentation}`;
|
|
9541
|
-
|
|
9452
|
+
join14 = `,
|
|
9542
9453
|
${indentation}`;
|
|
9543
9454
|
}
|
|
9544
9455
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9546,13 +9457,13 @@ ${indentation}`;
|
|
|
9546
9457
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9547
9458
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9548
9459
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9549
|
-
res +=
|
|
9460
|
+
res += join14;
|
|
9550
9461
|
}
|
|
9551
9462
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9552
9463
|
res += tmp !== void 0 ? tmp : "null";
|
|
9553
9464
|
if (value.length - 1 > maximumBreadth) {
|
|
9554
9465
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9555
|
-
res += `${
|
|
9466
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9556
9467
|
}
|
|
9557
9468
|
if (spacer !== "") {
|
|
9558
9469
|
res += `
|
|
@@ -9565,7 +9476,7 @@ ${originalIndentation}`;
|
|
|
9565
9476
|
let whitespace = "";
|
|
9566
9477
|
if (spacer !== "") {
|
|
9567
9478
|
indentation += spacer;
|
|
9568
|
-
|
|
9479
|
+
join14 = `,
|
|
9569
9480
|
${indentation}`;
|
|
9570
9481
|
whitespace = " ";
|
|
9571
9482
|
}
|
|
@@ -9574,7 +9485,7 @@ ${indentation}`;
|
|
|
9574
9485
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9575
9486
|
if (tmp !== void 0) {
|
|
9576
9487
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9577
|
-
separator =
|
|
9488
|
+
separator = join14;
|
|
9578
9489
|
}
|
|
9579
9490
|
}
|
|
9580
9491
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9632,20 +9543,20 @@ ${originalIndentation}`;
|
|
|
9632
9543
|
indentation += spacer;
|
|
9633
9544
|
let res2 = `
|
|
9634
9545
|
${indentation}`;
|
|
9635
|
-
const
|
|
9546
|
+
const join15 = `,
|
|
9636
9547
|
${indentation}`;
|
|
9637
9548
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9638
9549
|
let i = 0;
|
|
9639
9550
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9640
9551
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9641
9552
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9642
|
-
res2 +=
|
|
9553
|
+
res2 += join15;
|
|
9643
9554
|
}
|
|
9644
9555
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9645
9556
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9646
9557
|
if (value.length - 1 > maximumBreadth) {
|
|
9647
9558
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9648
|
-
res2 += `${
|
|
9559
|
+
res2 += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9649
9560
|
}
|
|
9650
9561
|
res2 += `
|
|
9651
9562
|
${originalIndentation}`;
|
|
@@ -9661,16 +9572,16 @@ ${originalIndentation}`;
|
|
|
9661
9572
|
return '"[Object]"';
|
|
9662
9573
|
}
|
|
9663
9574
|
indentation += spacer;
|
|
9664
|
-
const
|
|
9575
|
+
const join14 = `,
|
|
9665
9576
|
${indentation}`;
|
|
9666
9577
|
let res = "";
|
|
9667
9578
|
let separator = "";
|
|
9668
9579
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9669
9580
|
if (isTypedArrayWithEntries(value)) {
|
|
9670
|
-
res += stringifyTypedArray(value,
|
|
9581
|
+
res += stringifyTypedArray(value, join14, maximumBreadth);
|
|
9671
9582
|
keys = keys.slice(value.length);
|
|
9672
9583
|
maximumPropertiesToStringify -= value.length;
|
|
9673
|
-
separator =
|
|
9584
|
+
separator = join14;
|
|
9674
9585
|
}
|
|
9675
9586
|
if (deterministic) {
|
|
9676
9587
|
keys = sort(keys, comparator);
|
|
@@ -9681,13 +9592,13 @@ ${indentation}`;
|
|
|
9681
9592
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9682
9593
|
if (tmp !== void 0) {
|
|
9683
9594
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9684
|
-
separator =
|
|
9595
|
+
separator = join14;
|
|
9685
9596
|
}
|
|
9686
9597
|
}
|
|
9687
9598
|
if (keyLength > maximumBreadth) {
|
|
9688
9599
|
const removedKeys = keyLength - maximumBreadth;
|
|
9689
9600
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9690
|
-
separator =
|
|
9601
|
+
separator = join14;
|
|
9691
9602
|
}
|
|
9692
9603
|
if (separator !== "") {
|
|
9693
9604
|
res = `
|
|
@@ -10778,11 +10689,11 @@ var init_lib = __esm({
|
|
|
10778
10689
|
}
|
|
10779
10690
|
}
|
|
10780
10691
|
},
|
|
10781
|
-
addToPath: function addToPath(
|
|
10782
|
-
var last =
|
|
10692
|
+
addToPath: function addToPath(path59, added, removed, oldPosInc, options) {
|
|
10693
|
+
var last = path59.lastComponent;
|
|
10783
10694
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10784
10695
|
return {
|
|
10785
|
-
oldPos:
|
|
10696
|
+
oldPos: path59.oldPos + oldPosInc,
|
|
10786
10697
|
lastComponent: {
|
|
10787
10698
|
count: last.count + 1,
|
|
10788
10699
|
added,
|
|
@@ -10792,7 +10703,7 @@ var init_lib = __esm({
|
|
|
10792
10703
|
};
|
|
10793
10704
|
} else {
|
|
10794
10705
|
return {
|
|
10795
|
-
oldPos:
|
|
10706
|
+
oldPos: path59.oldPos + oldPosInc,
|
|
10796
10707
|
lastComponent: {
|
|
10797
10708
|
count: 1,
|
|
10798
10709
|
added,
|
|
@@ -11238,10 +11149,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
11238
11149
|
const memories = raw.map((m2) => {
|
|
11239
11150
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11240
11151
|
const rec = m2;
|
|
11241
|
-
const
|
|
11152
|
+
const path59 = typeof rec.path === "string" ? rec.path : null;
|
|
11242
11153
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11243
|
-
if (!
|
|
11244
|
-
const entry = { path:
|
|
11154
|
+
if (!path59 || content == null) return null;
|
|
11155
|
+
const entry = { path: path59, content };
|
|
11245
11156
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
11246
11157
|
return entry;
|
|
11247
11158
|
}).filter((m2) => m2 !== null);
|
|
@@ -12072,10 +11983,10 @@ function parseAttachment(obj) {
|
|
|
12072
11983
|
const memories = raw.map((m2) => {
|
|
12073
11984
|
if (!m2 || typeof m2 !== "object") return null;
|
|
12074
11985
|
const rec = m2;
|
|
12075
|
-
const
|
|
11986
|
+
const path59 = typeof rec.path === "string" ? rec.path : null;
|
|
12076
11987
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
12077
|
-
if (!
|
|
12078
|
-
const out = { path:
|
|
11988
|
+
if (!path59 || content == null) return null;
|
|
11989
|
+
const out = { path: path59, content };
|
|
12079
11990
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
12080
11991
|
return out;
|
|
12081
11992
|
}).filter((m2) => m2 !== null);
|
|
@@ -21274,7 +21185,7 @@ var require_BufferList = __commonJS({
|
|
|
21274
21185
|
this.head = this.tail = null;
|
|
21275
21186
|
this.length = 0;
|
|
21276
21187
|
};
|
|
21277
|
-
BufferList.prototype.join = function
|
|
21188
|
+
BufferList.prototype.join = function join14(s) {
|
|
21278
21189
|
if (this.length === 0) return "";
|
|
21279
21190
|
var p2 = this.head;
|
|
21280
21191
|
var ret = "" + p2.data;
|
|
@@ -23864,8 +23775,8 @@ var require_utils = __commonJS({
|
|
|
23864
23775
|
var result = transform[inputType][outputType](input);
|
|
23865
23776
|
return result;
|
|
23866
23777
|
};
|
|
23867
|
-
exports2.resolve = function(
|
|
23868
|
-
var parts =
|
|
23778
|
+
exports2.resolve = function(path59) {
|
|
23779
|
+
var parts = path59.split("/");
|
|
23869
23780
|
var result = [];
|
|
23870
23781
|
for (var index = 0; index < parts.length; index++) {
|
|
23871
23782
|
var part = parts[index];
|
|
@@ -29718,18 +29629,18 @@ var require_object = __commonJS({
|
|
|
29718
29629
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
29719
29630
|
this.files[name] = object;
|
|
29720
29631
|
};
|
|
29721
|
-
var parentFolder = function(
|
|
29722
|
-
if (
|
|
29723
|
-
|
|
29632
|
+
var parentFolder = function(path59) {
|
|
29633
|
+
if (path59.slice(-1) === "/") {
|
|
29634
|
+
path59 = path59.substring(0, path59.length - 1);
|
|
29724
29635
|
}
|
|
29725
|
-
var lastSlash =
|
|
29726
|
-
return lastSlash > 0 ?
|
|
29636
|
+
var lastSlash = path59.lastIndexOf("/");
|
|
29637
|
+
return lastSlash > 0 ? path59.substring(0, lastSlash) : "";
|
|
29727
29638
|
};
|
|
29728
|
-
var forceTrailingSlash = function(
|
|
29729
|
-
if (
|
|
29730
|
-
|
|
29639
|
+
var forceTrailingSlash = function(path59) {
|
|
29640
|
+
if (path59.slice(-1) !== "/") {
|
|
29641
|
+
path59 += "/";
|
|
29731
29642
|
}
|
|
29732
|
-
return
|
|
29643
|
+
return path59;
|
|
29733
29644
|
};
|
|
29734
29645
|
var folderAdd = function(name, createFolders) {
|
|
29735
29646
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
|
|
@@ -33215,7 +33126,7 @@ var SessionManager = class {
|
|
|
33215
33126
|
}
|
|
33216
33127
|
}
|
|
33217
33128
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
33218
|
-
create(args, creatorPrincipalId) {
|
|
33129
|
+
create(args, creatorPrincipalId, creatorOwnerPrincipalId) {
|
|
33219
33130
|
let cwd;
|
|
33220
33131
|
if (args.ownerPersonaId) {
|
|
33221
33132
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -33258,6 +33169,7 @@ var SessionManager = class {
|
|
|
33258
33169
|
ownerPersonaId: args.ownerPersonaId,
|
|
33259
33170
|
appBuilderProject: args.appBuilderProject,
|
|
33260
33171
|
creatorPrincipalId,
|
|
33172
|
+
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
33261
33173
|
createdAt: iso,
|
|
33262
33174
|
updatedAt: iso
|
|
33263
33175
|
};
|
|
@@ -36726,10 +36638,9 @@ var LocalWsServer = class {
|
|
|
36726
36638
|
httpServer = null;
|
|
36727
36639
|
frameHandler = null;
|
|
36728
36640
|
clients = /* @__PURE__ */ new Map();
|
|
36729
|
-
//
|
|
36730
|
-
//
|
|
36731
|
-
|
|
36732
|
-
guestIdToClients = /* @__PURE__ */ new Map();
|
|
36641
|
+
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
36642
|
+
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
36643
|
+
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
36733
36644
|
logger;
|
|
36734
36645
|
pingIntervalMs;
|
|
36735
36646
|
async start() {
|
|
@@ -36847,6 +36758,38 @@ var LocalWsServer = class {
|
|
|
36847
36758
|
this.safeSend(c.ws, frame);
|
|
36848
36759
|
}
|
|
36849
36760
|
}
|
|
36761
|
+
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
36762
|
+
// - 所有 owner ws (owner 看自家所有 channel)
|
|
36763
|
+
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
36764
|
+
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
36765
|
+
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
36766
|
+
this.broadcastToOwners(frame);
|
|
36767
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36768
|
+
if (!set) return;
|
|
36769
|
+
for (const id of set) {
|
|
36770
|
+
const c = this.clients.get(id);
|
|
36771
|
+
if (!c) continue;
|
|
36772
|
+
this.safeSend(c.ws, frame);
|
|
36773
|
+
}
|
|
36774
|
+
}
|
|
36775
|
+
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
36776
|
+
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
36777
|
+
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
36778
|
+
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36779
|
+
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36780
|
+
broadcastToPrincipal(principalId, frame) {
|
|
36781
|
+
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
36782
|
+
this.broadcastToOwners(frame);
|
|
36783
|
+
return;
|
|
36784
|
+
}
|
|
36785
|
+
const set = this.capabilityIdToClients.get(principalId);
|
|
36786
|
+
if (!set) return;
|
|
36787
|
+
for (const id of set) {
|
|
36788
|
+
const c = this.clients.get(id);
|
|
36789
|
+
if (!c) continue;
|
|
36790
|
+
this.safeSend(c.ws, frame);
|
|
36791
|
+
}
|
|
36792
|
+
}
|
|
36850
36793
|
firstSubscriber(sessionId) {
|
|
36851
36794
|
for (const c of this.clients.values()) {
|
|
36852
36795
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -36869,16 +36812,16 @@ var LocalWsServer = class {
|
|
|
36869
36812
|
this.safeSend(c.ws, frame);
|
|
36870
36813
|
}
|
|
36871
36814
|
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
36872
|
-
// 绑到 client;guest 连接同时进
|
|
36815
|
+
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
36873
36816
|
attachClientContext(clientId, ctx) {
|
|
36874
36817
|
const c = this.clients.get(clientId);
|
|
36875
36818
|
if (!c) return;
|
|
36876
36819
|
c.ctx = ctx;
|
|
36877
|
-
if (ctx.
|
|
36878
|
-
let set = this.
|
|
36820
|
+
if (ctx.capabilityId) {
|
|
36821
|
+
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
36879
36822
|
if (!set) {
|
|
36880
36823
|
set = /* @__PURE__ */ new Set();
|
|
36881
|
-
this.
|
|
36824
|
+
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
36882
36825
|
}
|
|
36883
36826
|
set.add(clientId);
|
|
36884
36827
|
}
|
|
@@ -36887,27 +36830,10 @@ var LocalWsServer = class {
|
|
|
36887
36830
|
getClientContext(clientId) {
|
|
36888
36831
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36889
36832
|
}
|
|
36890
|
-
//
|
|
36891
|
-
// (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
|
|
36892
|
-
// close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
|
|
36893
|
-
// 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
|
|
36894
|
-
closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
|
|
36895
|
-
const ids = [...this.clients.keys()];
|
|
36896
|
-
for (const id of ids) {
|
|
36897
|
-
const c = this.clients.get(id);
|
|
36898
|
-
if (!c) continue;
|
|
36899
|
-
try {
|
|
36900
|
-
c.ws.close(code, reason);
|
|
36901
|
-
} catch {
|
|
36902
|
-
}
|
|
36903
|
-
}
|
|
36904
|
-
}
|
|
36905
|
-
// Task 1.10 撤销级联:关闭某 guest principal 的所有活跃 ws 连接。close code 4401
|
|
36833
|
+
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
36906
36834
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36910
|
-
const set = this.guestIdToClients.get(guestPrincipalId);
|
|
36835
|
+
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36836
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36911
36837
|
if (!set) return;
|
|
36912
36838
|
const ids = [...set];
|
|
36913
36839
|
for (const id of ids) {
|
|
@@ -36972,18 +36898,16 @@ var LocalWsServer = class {
|
|
|
36972
36898
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36973
36899
|
}
|
|
36974
36900
|
socket.on("message", (data) => {
|
|
36975
|
-
|
|
36976
|
-
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36977
|
-
});
|
|
36901
|
+
this.onMessage(handle, data, remoteAddress);
|
|
36978
36902
|
});
|
|
36979
36903
|
socket.on("close", () => {
|
|
36980
36904
|
const c = this.clients.get(id);
|
|
36981
36905
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
36982
|
-
const
|
|
36983
|
-
if (
|
|
36984
|
-
const set = this.
|
|
36906
|
+
const capId = c?.ctx?.capabilityId;
|
|
36907
|
+
if (capId) {
|
|
36908
|
+
const set = this.capabilityIdToClients.get(capId);
|
|
36985
36909
|
set?.delete(id);
|
|
36986
|
-
if (set && set.size === 0) this.
|
|
36910
|
+
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
36987
36911
|
}
|
|
36988
36912
|
this.clients.delete(id);
|
|
36989
36913
|
authGate?.unregister(id);
|
|
@@ -36993,7 +36917,7 @@ var LocalWsServer = class {
|
|
|
36993
36917
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36994
36918
|
});
|
|
36995
36919
|
}
|
|
36996
|
-
|
|
36920
|
+
onMessage(client, data, remoteAddress) {
|
|
36997
36921
|
let frame;
|
|
36998
36922
|
try {
|
|
36999
36923
|
frame = JSON.parse(data.toString());
|
|
@@ -37004,7 +36928,7 @@ var LocalWsServer = class {
|
|
|
37004
36928
|
const authGate = this.opts.authGate;
|
|
37005
36929
|
if (authGate) {
|
|
37006
36930
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
37007
|
-
const verdict =
|
|
36931
|
+
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
37008
36932
|
if (verdict !== "pass") {
|
|
37009
36933
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
37010
36934
|
try {
|
|
@@ -37019,8 +36943,14 @@ var LocalWsServer = class {
|
|
|
37019
36943
|
}
|
|
37020
36944
|
} else if (frame.type === "auth") {
|
|
37021
36945
|
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
36946
|
+
const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
|
|
36947
|
+
const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
|
|
37022
36948
|
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
37023
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36949
|
+
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
36950
|
+
token,
|
|
36951
|
+
selfPrincipalId || void 0,
|
|
36952
|
+
selfDisplayName || void 0
|
|
36953
|
+
);
|
|
37024
36954
|
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
37025
36955
|
}
|
|
37026
36956
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
@@ -37101,7 +37031,7 @@ var AuthGate = class {
|
|
|
37101
37031
|
registerConnection(handle) {
|
|
37102
37032
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
37103
37033
|
if (!enforce) {
|
|
37104
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null
|
|
37034
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
37105
37035
|
return true;
|
|
37106
37036
|
}
|
|
37107
37037
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -37110,7 +37040,7 @@ var AuthGate = class {
|
|
|
37110
37040
|
if (!st || st.authed) return;
|
|
37111
37041
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
37112
37042
|
}, this.opts.timeoutMs);
|
|
37113
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer
|
|
37043
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
37114
37044
|
return false;
|
|
37115
37045
|
}
|
|
37116
37046
|
unregister(handleId) {
|
|
@@ -37126,15 +37056,12 @@ var AuthGate = class {
|
|
|
37126
37056
|
}
|
|
37127
37057
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
37128
37058
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
37129
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
37059
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接)
|
|
37130
37060
|
// - pass:已 authed,调用方继续走业务路由
|
|
37131
|
-
|
|
37132
|
-
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
37133
|
-
async handleFrame(handle, frame) {
|
|
37061
|
+
handleFrame(handle, frame) {
|
|
37134
37062
|
const st = this.states.get(handle.id);
|
|
37135
37063
|
if (!st) return "reject";
|
|
37136
37064
|
if (st.authed) return "pass";
|
|
37137
|
-
if (st.pending) return "reject";
|
|
37138
37065
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
37139
37066
|
if (!parsed.success) {
|
|
37140
37067
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -37144,14 +37071,11 @@ var AuthGate = class {
|
|
|
37144
37071
|
}
|
|
37145
37072
|
let ctx = null;
|
|
37146
37073
|
if (this.opts.authenticate) {
|
|
37147
|
-
|
|
37148
|
-
|
|
37149
|
-
|
|
37150
|
-
|
|
37151
|
-
|
|
37152
|
-
st.pending = false;
|
|
37153
|
-
}
|
|
37154
|
-
if (!this.states.has(handle.id)) return "reject";
|
|
37074
|
+
const r = this.opts.authenticate(
|
|
37075
|
+
parsed.data.token,
|
|
37076
|
+
parsed.data.selfPrincipalId,
|
|
37077
|
+
parsed.data.selfDisplayName
|
|
37078
|
+
);
|
|
37155
37079
|
if (!r.ok) {
|
|
37156
37080
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
37157
37081
|
this.markFailed(handle.id);
|
|
@@ -37254,33 +37178,26 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
37254
37178
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
37255
37179
|
};
|
|
37256
37180
|
}
|
|
37257
|
-
function
|
|
37181
|
+
function guestContext(cap, peerOwnerPrincipalId) {
|
|
37258
37182
|
return {
|
|
37259
|
-
principal: { id:
|
|
37260
|
-
grants:
|
|
37261
|
-
|
|
37262
|
-
|
|
37263
|
-
})),
|
|
37264
|
-
ownerId: subOwner,
|
|
37265
|
-
provider
|
|
37183
|
+
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
37184
|
+
grants: cap.grants,
|
|
37185
|
+
capabilityId: cap.id,
|
|
37186
|
+
...peerOwnerPrincipalId ? { peerOwnerPrincipalId } : {}
|
|
37266
37187
|
};
|
|
37267
37188
|
}
|
|
37268
|
-
|
|
37189
|
+
function authenticate(token, deps) {
|
|
37269
37190
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
37270
37191
|
if (deps.isOwnerToken(token)) {
|
|
37271
37192
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
37272
37193
|
}
|
|
37273
|
-
if (!deps.
|
|
37274
|
-
const
|
|
37275
|
-
if (!
|
|
37276
|
-
if (
|
|
37277
|
-
|
|
37278
|
-
return { ok: false, code: "BAD_TOKEN" };
|
|
37194
|
+
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
37195
|
+
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
37196
|
+
if (!v2.ok) {
|
|
37197
|
+
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
37198
|
+
return { ok: false, code: v2.code };
|
|
37279
37199
|
}
|
|
37280
|
-
return {
|
|
37281
|
-
ok: true,
|
|
37282
|
-
context: connectGuestContext(result.subDevice, result.subOwner, result.provider, result.displayName)
|
|
37283
|
-
};
|
|
37200
|
+
return { ok: true, context: guestContext(v2.capability, deps.selfPrincipalId) };
|
|
37284
37201
|
}
|
|
37285
37202
|
|
|
37286
37203
|
// src/permission/capability-store.ts
|
|
@@ -37426,8 +37343,8 @@ var CapabilityManager = class {
|
|
|
37426
37343
|
list() {
|
|
37427
37344
|
return this.registry.list();
|
|
37428
37345
|
}
|
|
37429
|
-
//
|
|
37430
|
-
// 返回 Capability
|
|
37346
|
+
// whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
|
|
37347
|
+
// 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
|
|
37431
37348
|
findById(id) {
|
|
37432
37349
|
return this.registry.findById(id);
|
|
37433
37350
|
}
|
|
@@ -37460,14 +37377,89 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37460
37377
|
return { removed };
|
|
37461
37378
|
}
|
|
37462
37379
|
|
|
37380
|
+
// src/permission/personal-capability.ts
|
|
37381
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37382
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37383
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37384
|
+
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37385
|
+
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37386
|
+
function personalCapFilePath(dataDir) {
|
|
37387
|
+
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37388
|
+
}
|
|
37389
|
+
function loadOrCreatePersonalCapability(opts) {
|
|
37390
|
+
const file = personalCapFilePath(opts.dataDir);
|
|
37391
|
+
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37392
|
+
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37393
|
+
const now = opts.now ?? Date.now;
|
|
37394
|
+
const existing = readFile(file);
|
|
37395
|
+
if (existing) return existing;
|
|
37396
|
+
const token = generateToken();
|
|
37397
|
+
const id = generateId();
|
|
37398
|
+
const capability = {
|
|
37399
|
+
id,
|
|
37400
|
+
secretHash: sha256Hex2(token),
|
|
37401
|
+
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37402
|
+
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37403
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37404
|
+
resource: { ...g2.resource },
|
|
37405
|
+
actions: [...g2.actions]
|
|
37406
|
+
})),
|
|
37407
|
+
issuedAt: now(),
|
|
37408
|
+
usedCount: 0
|
|
37409
|
+
};
|
|
37410
|
+
const content = { token, capability };
|
|
37411
|
+
writeFile(file, content);
|
|
37412
|
+
return content;
|
|
37413
|
+
}
|
|
37414
|
+
function readFile(file) {
|
|
37415
|
+
let raw;
|
|
37416
|
+
try {
|
|
37417
|
+
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37418
|
+
} catch (err) {
|
|
37419
|
+
const code = err?.code;
|
|
37420
|
+
if (code === "ENOENT") return null;
|
|
37421
|
+
return null;
|
|
37422
|
+
}
|
|
37423
|
+
let parsed;
|
|
37424
|
+
try {
|
|
37425
|
+
parsed = JSON.parse(raw);
|
|
37426
|
+
} catch {
|
|
37427
|
+
return null;
|
|
37428
|
+
}
|
|
37429
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
37430
|
+
const obj = parsed;
|
|
37431
|
+
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37432
|
+
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37433
|
+
if (!capParsed.success) return null;
|
|
37434
|
+
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37435
|
+
return { token: obj.token, capability: capParsed.data };
|
|
37436
|
+
}
|
|
37437
|
+
function writeFile(file, content) {
|
|
37438
|
+
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37439
|
+
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37440
|
+
try {
|
|
37441
|
+
import_node_fs15.default.chmodSync(file, 384);
|
|
37442
|
+
} catch {
|
|
37443
|
+
}
|
|
37444
|
+
}
|
|
37445
|
+
function defaultGenerateToken() {
|
|
37446
|
+
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37447
|
+
}
|
|
37448
|
+
function defaultGenerateId() {
|
|
37449
|
+
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37450
|
+
}
|
|
37451
|
+
function sha256Hex2(s) {
|
|
37452
|
+
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37453
|
+
}
|
|
37454
|
+
|
|
37463
37455
|
// src/inbox/inbox-store.ts
|
|
37464
|
-
var
|
|
37465
|
-
var
|
|
37456
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37457
|
+
var path22 = __toESM(require("path"), 1);
|
|
37466
37458
|
var INBOX_SUBDIR = "inbox";
|
|
37467
37459
|
var InboxStore = class {
|
|
37468
37460
|
constructor(dataDir) {
|
|
37469
37461
|
this.dataDir = dataDir;
|
|
37470
|
-
|
|
37462
|
+
fs20.mkdirSync(this.dirPath(), { recursive: true });
|
|
37471
37463
|
}
|
|
37472
37464
|
dataDir;
|
|
37473
37465
|
/**
|
|
@@ -37475,11 +37467,11 @@ var InboxStore = class {
|
|
|
37475
37467
|
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
37476
37468
|
* 文件不存在时返回 [] (不抛, 不创建).
|
|
37477
37469
|
*/
|
|
37478
|
-
list(
|
|
37479
|
-
const file = this.filePath(
|
|
37470
|
+
list(peerOwnerId, sinceCreatedAt) {
|
|
37471
|
+
const file = this.filePath(peerOwnerId);
|
|
37480
37472
|
let raw;
|
|
37481
37473
|
try {
|
|
37482
|
-
raw =
|
|
37474
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37483
37475
|
} catch (err) {
|
|
37484
37476
|
if (err?.code === "ENOENT") return [];
|
|
37485
37477
|
return [];
|
|
@@ -37489,13 +37481,13 @@ var InboxStore = class {
|
|
|
37489
37481
|
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
37490
37482
|
}
|
|
37491
37483
|
/**
|
|
37492
|
-
* 列出所有 peer 的
|
|
37484
|
+
* 列出所有 peer 的 peerOwnerId (扫目录所有 *.jsonl 文件名).
|
|
37493
37485
|
*/
|
|
37494
|
-
|
|
37486
|
+
listAllPeerOwnerIds() {
|
|
37495
37487
|
const dir = this.dirPath();
|
|
37496
37488
|
let entries;
|
|
37497
37489
|
try {
|
|
37498
|
-
entries =
|
|
37490
|
+
entries = fs20.readdirSync(dir);
|
|
37499
37491
|
} catch (err) {
|
|
37500
37492
|
if (err?.code === "ENOENT") return [];
|
|
37501
37493
|
return [];
|
|
@@ -37507,63 +37499,63 @@ var InboxStore = class {
|
|
|
37507
37499
|
* 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
|
|
37508
37500
|
*/
|
|
37509
37501
|
append(message) {
|
|
37510
|
-
const existing = this.list(message.
|
|
37502
|
+
const existing = this.list(message.peerOwnerId);
|
|
37511
37503
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37512
|
-
const file = this.filePath(message.
|
|
37504
|
+
const file = this.filePath(message.peerOwnerId);
|
|
37513
37505
|
const line = JSON.stringify(message) + "\n";
|
|
37514
|
-
|
|
37506
|
+
fs20.appendFileSync(file, line, { mode: 384 });
|
|
37515
37507
|
try {
|
|
37516
|
-
|
|
37508
|
+
fs20.chmodSync(file, 384);
|
|
37517
37509
|
} catch {
|
|
37518
37510
|
}
|
|
37519
37511
|
}
|
|
37520
37512
|
/**
|
|
37521
|
-
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=
|
|
37522
|
-
* readBy[
|
|
37513
|
+
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=principalId 的消息
|
|
37514
|
+
* readBy[principalId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
|
|
37523
37515
|
* 返写入了几条.
|
|
37524
37516
|
*/
|
|
37525
|
-
markRead(
|
|
37526
|
-
const messages = this.list(
|
|
37517
|
+
markRead(peerOwnerId, principalId, upToCreatedAt) {
|
|
37518
|
+
const messages = this.list(peerOwnerId);
|
|
37527
37519
|
let changed = 0;
|
|
37528
37520
|
const next = messages.map((m2) => {
|
|
37529
|
-
if (m2.
|
|
37521
|
+
if (m2.senderPrincipalId === principalId) return m2;
|
|
37530
37522
|
if (m2.createdAt > upToCreatedAt) return m2;
|
|
37531
|
-
const cur = m2.readBy[
|
|
37523
|
+
const cur = m2.readBy[principalId];
|
|
37532
37524
|
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
37533
37525
|
changed++;
|
|
37534
|
-
return { ...m2, readBy: { ...m2.readBy, [
|
|
37526
|
+
return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
|
|
37535
37527
|
});
|
|
37536
37528
|
if (changed === 0) return 0;
|
|
37537
|
-
this.rewriteFile(
|
|
37529
|
+
this.rewriteFile(peerOwnerId, next);
|
|
37538
37530
|
return changed;
|
|
37539
37531
|
}
|
|
37540
37532
|
/**
|
|
37541
37533
|
* 删整个 peer 的 jsonl (peer:remove cascade). 不存在 no-op.
|
|
37542
37534
|
*/
|
|
37543
|
-
|
|
37544
|
-
const file = this.filePath(
|
|
37535
|
+
removeByPeerOwnerId(peerOwnerId) {
|
|
37536
|
+
const file = this.filePath(peerOwnerId);
|
|
37545
37537
|
try {
|
|
37546
|
-
|
|
37538
|
+
fs20.unlinkSync(file);
|
|
37547
37539
|
} catch (err) {
|
|
37548
37540
|
if (err?.code === "ENOENT") return;
|
|
37549
37541
|
}
|
|
37550
37542
|
}
|
|
37551
|
-
rewriteFile(
|
|
37552
|
-
const file = this.filePath(
|
|
37543
|
+
rewriteFile(peerOwnerId, messages) {
|
|
37544
|
+
const file = this.filePath(peerOwnerId);
|
|
37553
37545
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37554
37546
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37555
|
-
|
|
37556
|
-
|
|
37547
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37548
|
+
fs20.renameSync(tmp, file);
|
|
37557
37549
|
try {
|
|
37558
|
-
|
|
37550
|
+
fs20.chmodSync(file, 384);
|
|
37559
37551
|
} catch {
|
|
37560
37552
|
}
|
|
37561
37553
|
}
|
|
37562
37554
|
dirPath() {
|
|
37563
|
-
return
|
|
37555
|
+
return path22.join(this.dataDir, INBOX_SUBDIR);
|
|
37564
37556
|
}
|
|
37565
|
-
filePath(
|
|
37566
|
-
return
|
|
37557
|
+
filePath(peerOwnerId) {
|
|
37558
|
+
return path22.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37567
37559
|
}
|
|
37568
37560
|
};
|
|
37569
37561
|
function parseAllLines(raw) {
|
|
@@ -37599,72 +37591,72 @@ var InboxManager = class {
|
|
|
37599
37591
|
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
37600
37592
|
*/
|
|
37601
37593
|
postMessage(args) {
|
|
37602
|
-
const beforeLen = this.store.list(args.
|
|
37594
|
+
const beforeLen = this.store.list(args.peerOwnerId).length;
|
|
37603
37595
|
const msg = {
|
|
37604
37596
|
id: args.id,
|
|
37605
|
-
|
|
37606
|
-
|
|
37597
|
+
peerOwnerId: args.peerOwnerId,
|
|
37598
|
+
senderPrincipalId: args.senderPrincipalId,
|
|
37607
37599
|
text: args.text,
|
|
37608
37600
|
createdAt: args.createdAt,
|
|
37609
37601
|
readBy: {}
|
|
37610
37602
|
};
|
|
37611
37603
|
this.store.append(msg);
|
|
37612
|
-
const afterLen = this.store.list(args.
|
|
37604
|
+
const afterLen = this.store.list(args.peerOwnerId).length;
|
|
37613
37605
|
if (afterLen === beforeLen) return msg;
|
|
37614
|
-
this.broadcast(args.
|
|
37606
|
+
this.broadcast(args.peerOwnerId, {
|
|
37615
37607
|
type: "inbox:event",
|
|
37616
|
-
|
|
37608
|
+
peerOwnerId: args.peerOwnerId,
|
|
37617
37609
|
message: msg
|
|
37618
37610
|
});
|
|
37619
37611
|
return msg;
|
|
37620
37612
|
}
|
|
37621
|
-
list(
|
|
37622
|
-
return this.store.list(
|
|
37613
|
+
list(peerOwnerId, sinceCreatedAt) {
|
|
37614
|
+
return this.store.list(peerOwnerId, sinceCreatedAt);
|
|
37623
37615
|
}
|
|
37624
37616
|
/**
|
|
37625
37617
|
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
37626
37618
|
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
|
|
37627
37619
|
*/
|
|
37628
37620
|
markRead(args) {
|
|
37629
|
-
const before = this.store.list(args.
|
|
37630
|
-
const changed = this.store.markRead(args.
|
|
37621
|
+
const before = this.store.list(args.peerOwnerId);
|
|
37622
|
+
const changed = this.store.markRead(args.peerOwnerId, args.principalId, args.upToCreatedAt);
|
|
37631
37623
|
if (changed === 0) return 0;
|
|
37632
|
-
const after = this.store.list(args.
|
|
37624
|
+
const after = this.store.list(args.peerOwnerId);
|
|
37633
37625
|
const beforeById = new Map(before.map((m2) => [m2.id, m2]));
|
|
37634
37626
|
for (const m2 of after) {
|
|
37635
37627
|
const prev = beforeById.get(m2.id);
|
|
37636
37628
|
if (!prev) continue;
|
|
37637
|
-
if (prev.readBy[args.
|
|
37638
|
-
this.broadcast(args.
|
|
37629
|
+
if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
|
|
37630
|
+
this.broadcast(args.peerOwnerId, {
|
|
37639
37631
|
type: "inbox:event",
|
|
37640
|
-
|
|
37632
|
+
peerOwnerId: args.peerOwnerId,
|
|
37641
37633
|
message: m2
|
|
37642
37634
|
});
|
|
37643
37635
|
}
|
|
37644
37636
|
return changed;
|
|
37645
37637
|
}
|
|
37646
37638
|
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
37647
|
-
removeChannel(
|
|
37648
|
-
this.store.
|
|
37639
|
+
removeChannel(peerOwnerId) {
|
|
37640
|
+
this.store.removeByPeerOwnerId(peerOwnerId);
|
|
37649
37641
|
}
|
|
37650
37642
|
};
|
|
37651
37643
|
|
|
37652
|
-
// src/state/
|
|
37653
|
-
var
|
|
37654
|
-
var
|
|
37655
|
-
var FILE_NAME = "
|
|
37656
|
-
var
|
|
37644
|
+
// src/state/received-capability-store.ts
|
|
37645
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37646
|
+
var path23 = __toESM(require("path"), 1);
|
|
37647
|
+
var FILE_NAME = "received-capabilities.json";
|
|
37648
|
+
var ReceivedCapabilityStore = class {
|
|
37657
37649
|
constructor(dataDir) {
|
|
37658
37650
|
this.dataDir = dataDir;
|
|
37659
37651
|
}
|
|
37660
37652
|
dataDir;
|
|
37661
|
-
|
|
37653
|
+
caps = /* @__PURE__ */ new Map();
|
|
37662
37654
|
load() {
|
|
37663
|
-
this.
|
|
37664
|
-
const file =
|
|
37655
|
+
this.caps.clear();
|
|
37656
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37665
37657
|
let raw;
|
|
37666
37658
|
try {
|
|
37667
|
-
raw =
|
|
37659
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
37668
37660
|
} catch (err) {
|
|
37669
37661
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37670
37662
|
return;
|
|
@@ -37676,58 +37668,52 @@ var ContactStore = class {
|
|
|
37676
37668
|
this.renameBak(file);
|
|
37677
37669
|
return;
|
|
37678
37670
|
}
|
|
37679
|
-
const arr = parsed?.
|
|
37671
|
+
const arr = parsed?.receivedCaps;
|
|
37680
37672
|
if (!Array.isArray(arr)) return;
|
|
37681
37673
|
for (const entry of arr) {
|
|
37682
|
-
const r =
|
|
37683
|
-
if (r.success) this.
|
|
37674
|
+
const r = ReceivedCapabilitySchema.safeParse(entry);
|
|
37675
|
+
if (r.success) this.caps.set(r.data.ownerPrincipalId, r.data);
|
|
37684
37676
|
}
|
|
37685
37677
|
}
|
|
37686
37678
|
list() {
|
|
37687
|
-
return Array.from(this.
|
|
37688
|
-
}
|
|
37689
|
-
get(deviceId) {
|
|
37690
|
-
return this.contacts.get(deviceId) ?? null;
|
|
37679
|
+
return Array.from(this.caps.values());
|
|
37691
37680
|
}
|
|
37692
|
-
|
|
37693
|
-
return this.
|
|
37681
|
+
get(ownerPrincipalId) {
|
|
37682
|
+
return this.caps.get(ownerPrincipalId) ?? null;
|
|
37694
37683
|
}
|
|
37695
|
-
|
|
37696
|
-
|
|
37697
|
-
return this.list().filter((c) => c.ownerId === ownerId && c.provider === provider);
|
|
37684
|
+
hasOwner(ownerPrincipalId) {
|
|
37685
|
+
return this.caps.has(ownerPrincipalId);
|
|
37698
37686
|
}
|
|
37699
|
-
upsert(
|
|
37700
|
-
this.
|
|
37687
|
+
upsert(cap) {
|
|
37688
|
+
this.caps.set(cap.ownerPrincipalId, cap);
|
|
37701
37689
|
this.flush();
|
|
37702
37690
|
}
|
|
37703
|
-
|
|
37704
|
-
|
|
37705
|
-
|
|
37706
|
-
if (existed) this.flush();
|
|
37707
|
-
return existed;
|
|
37691
|
+
remove(ownerPrincipalId) {
|
|
37692
|
+
if (!this.caps.delete(ownerPrincipalId)) return;
|
|
37693
|
+
this.flush();
|
|
37708
37694
|
}
|
|
37709
37695
|
flush() {
|
|
37710
|
-
const file =
|
|
37696
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37711
37697
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37712
37698
|
const content = JSON.stringify(
|
|
37713
|
-
{
|
|
37699
|
+
{ receivedCaps: Array.from(this.caps.values()) },
|
|
37714
37700
|
null,
|
|
37715
37701
|
2
|
|
37716
37702
|
);
|
|
37717
|
-
|
|
37718
|
-
|
|
37719
|
-
|
|
37703
|
+
fs21.mkdirSync(this.dataDir, { recursive: true });
|
|
37704
|
+
fs21.writeFileSync(tmp, content, { mode: 384 });
|
|
37705
|
+
fs21.renameSync(tmp, file);
|
|
37720
37706
|
}
|
|
37721
37707
|
renameBak(file) {
|
|
37722
37708
|
try {
|
|
37723
|
-
|
|
37709
|
+
fs21.renameSync(file, `${file}.bak`);
|
|
37724
37710
|
} catch {
|
|
37725
37711
|
}
|
|
37726
37712
|
}
|
|
37727
37713
|
};
|
|
37728
37714
|
|
|
37729
|
-
// src/
|
|
37730
|
-
var
|
|
37715
|
+
// src/received-capability/connect-remote.ts
|
|
37716
|
+
var crypto6 = __toESM(require("crypto"), 1);
|
|
37731
37717
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37732
37718
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37733
37719
|
async function connectRemote(args) {
|
|
@@ -37744,11 +37730,8 @@ async function connectRemote(args) {
|
|
|
37744
37730
|
JSON.stringify({
|
|
37745
37731
|
type: "auth",
|
|
37746
37732
|
token: args.token,
|
|
37747
|
-
|
|
37748
|
-
|
|
37749
|
-
// 决策 #16:身份字段(selfPrincipalId/selfDisplayName/selfDeviceId)已删——
|
|
37750
|
-
// 对方从 connect token 签名背书取 deviceId/ownerId/provider,不信自报。
|
|
37751
|
-
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37733
|
+
selfPrincipalId: args.selfPrincipalId,
|
|
37734
|
+
selfDisplayName: args.selfDisplayName
|
|
37752
37735
|
})
|
|
37753
37736
|
);
|
|
37754
37737
|
await new Promise((resolve6, reject) => {
|
|
@@ -37759,7 +37742,7 @@ async function connectRemote(args) {
|
|
|
37759
37742
|
} catch {
|
|
37760
37743
|
return;
|
|
37761
37744
|
}
|
|
37762
|
-
if (f.type === "auth:ok") {
|
|
37745
|
+
if (f.type === "auth:ok" || f.type === "ready") {
|
|
37763
37746
|
ws.off("message", onMessage);
|
|
37764
37747
|
resolve6();
|
|
37765
37748
|
return;
|
|
@@ -37776,7 +37759,7 @@ async function connectRemote(args) {
|
|
|
37776
37759
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37777
37760
|
});
|
|
37778
37761
|
function call(method, payload) {
|
|
37779
|
-
const requestId =
|
|
37762
|
+
const requestId = crypto6.randomUUID();
|
|
37780
37763
|
return new Promise((resolve6, reject) => {
|
|
37781
37764
|
const onMessage = (raw) => {
|
|
37782
37765
|
let f;
|
|
@@ -37805,9 +37788,9 @@ async function connectRemote(args) {
|
|
|
37805
37788
|
whoami: async () => {
|
|
37806
37789
|
const f = await call("whoami", {});
|
|
37807
37790
|
return {
|
|
37808
|
-
ownerId: f.owner.
|
|
37809
|
-
provider: f.owner.provider,
|
|
37791
|
+
ownerId: f.owner.id,
|
|
37810
37792
|
displayName: f.owner.displayName,
|
|
37793
|
+
capabilityId: f.capability.id,
|
|
37811
37794
|
grants: f.capability.grants
|
|
37812
37795
|
};
|
|
37813
37796
|
},
|
|
@@ -37820,96 +37803,62 @@ async function connectRemote(args) {
|
|
|
37820
37803
|
};
|
|
37821
37804
|
}
|
|
37822
37805
|
|
|
37823
|
-
// src/contact/auto-reverse.ts
|
|
37824
|
-
init_protocol();
|
|
37825
|
-
async function autoReverseContact(args) {
|
|
37826
|
-
if (!args.selfUrl) return;
|
|
37827
|
-
const now = args.now ?? Date.now;
|
|
37828
|
-
const grants = PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37829
|
-
resource: { ...g2.resource },
|
|
37830
|
-
actions: [...g2.actions]
|
|
37831
|
-
}));
|
|
37832
|
-
const base = {
|
|
37833
|
-
deviceId: args.deviceId,
|
|
37834
|
-
ownerId: args.ownerId,
|
|
37835
|
-
provider: args.provider,
|
|
37836
|
-
displayName: args.displayName,
|
|
37837
|
-
remoteUrl: args.selfUrl,
|
|
37838
|
-
connectToken: "",
|
|
37839
|
-
grants,
|
|
37840
|
-
addedAt: now()
|
|
37841
|
-
};
|
|
37842
|
-
args.store.upsert(base);
|
|
37843
|
-
args.broadcast({ type: "contact:added", contact: base });
|
|
37844
|
-
if (!args.exchangeToken) return;
|
|
37845
|
-
let token;
|
|
37846
|
-
try {
|
|
37847
|
-
token = await args.exchangeToken(args.deviceId);
|
|
37848
|
-
} catch {
|
|
37849
|
-
return;
|
|
37850
|
-
}
|
|
37851
|
-
if (!token) return;
|
|
37852
|
-
const upgraded = { ...base, connectToken: token };
|
|
37853
|
-
args.store.upsert(upgraded);
|
|
37854
|
-
args.broadcast({ type: "contact:added", contact: upgraded });
|
|
37855
|
-
}
|
|
37856
|
-
|
|
37857
37806
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37858
|
-
var
|
|
37859
|
-
var
|
|
37807
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
37808
|
+
var path24 = __toESM(require("path"), 1);
|
|
37860
37809
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37861
37810
|
function migrateFlattenSessions(opts) {
|
|
37862
37811
|
const dataDir = opts.dataDir;
|
|
37863
37812
|
const now = opts.now ?? Date.now;
|
|
37864
|
-
const sessionsDir =
|
|
37865
|
-
const flagPath =
|
|
37813
|
+
const sessionsDir = path24.join(dataDir, "sessions");
|
|
37814
|
+
const flagPath = path24.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37866
37815
|
if (existsSync3(flagPath)) {
|
|
37867
37816
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37868
37817
|
}
|
|
37869
37818
|
let movedBare = 0;
|
|
37870
37819
|
let movedVmOwner = 0;
|
|
37871
37820
|
let archivedListener = 0;
|
|
37872
|
-
const defaultDir =
|
|
37821
|
+
const defaultDir = path24.join(sessionsDir, "default");
|
|
37873
37822
|
if (existsSync3(defaultDir)) {
|
|
37874
37823
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37875
37824
|
if (!entry.endsWith(".json")) continue;
|
|
37876
|
-
const src =
|
|
37877
|
-
const dst =
|
|
37878
|
-
|
|
37825
|
+
const src = path24.join(defaultDir, entry);
|
|
37826
|
+
const dst = path24.join(sessionsDir, entry);
|
|
37827
|
+
fs22.renameSync(src, dst);
|
|
37879
37828
|
movedBare += 1;
|
|
37880
37829
|
}
|
|
37881
37830
|
rmdirIfEmpty(defaultDir);
|
|
37882
37831
|
}
|
|
37883
37832
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37884
|
-
const personaDir =
|
|
37833
|
+
const personaDir = path24.join(sessionsDir, pid);
|
|
37885
37834
|
if (!isDir(personaDir)) continue;
|
|
37886
37835
|
if (pid === "default") continue;
|
|
37887
|
-
const ownerSrc =
|
|
37836
|
+
const ownerSrc = path24.join(personaDir, "owner");
|
|
37888
37837
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37889
|
-
const ownerDst =
|
|
37890
|
-
|
|
37838
|
+
const ownerDst = path24.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37839
|
+
fs22.mkdirSync(ownerDst, { recursive: true });
|
|
37891
37840
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37892
37841
|
if (!file.endsWith(".json")) continue;
|
|
37893
|
-
|
|
37842
|
+
fs22.renameSync(path24.join(ownerSrc, file), path24.join(ownerDst, file));
|
|
37894
37843
|
movedVmOwner += 1;
|
|
37895
37844
|
}
|
|
37896
37845
|
rmdirIfEmpty(ownerSrc);
|
|
37897
37846
|
}
|
|
37898
|
-
const listenerSrc =
|
|
37847
|
+
const listenerSrc = path24.join(personaDir, "listener");
|
|
37899
37848
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37900
|
-
const archiveDst =
|
|
37901
|
-
|
|
37849
|
+
const archiveDst = path24.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37850
|
+
fs22.mkdirSync(archiveDst, { recursive: true });
|
|
37902
37851
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37903
37852
|
if (!file.endsWith(".json")) continue;
|
|
37904
|
-
|
|
37853
|
+
fs22.renameSync(path24.join(listenerSrc, file), path24.join(archiveDst, file));
|
|
37905
37854
|
archivedListener += 1;
|
|
37906
37855
|
}
|
|
37907
37856
|
rmdirIfEmpty(listenerSrc);
|
|
37908
37857
|
}
|
|
37909
37858
|
rmdirIfEmpty(personaDir);
|
|
37910
37859
|
}
|
|
37911
|
-
|
|
37912
|
-
|
|
37860
|
+
fs22.mkdirSync(sessionsDir, { recursive: true });
|
|
37861
|
+
fs22.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37913
37862
|
return {
|
|
37914
37863
|
skipped: false,
|
|
37915
37864
|
flagWritten: true,
|
|
@@ -37920,7 +37869,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37920
37869
|
}
|
|
37921
37870
|
function existsSync3(p2) {
|
|
37922
37871
|
try {
|
|
37923
|
-
|
|
37872
|
+
fs22.statSync(p2);
|
|
37924
37873
|
return true;
|
|
37925
37874
|
} catch {
|
|
37926
37875
|
return false;
|
|
@@ -37928,31 +37877,31 @@ function existsSync3(p2) {
|
|
|
37928
37877
|
}
|
|
37929
37878
|
function isDir(p2) {
|
|
37930
37879
|
try {
|
|
37931
|
-
return
|
|
37880
|
+
return fs22.statSync(p2).isDirectory();
|
|
37932
37881
|
} catch {
|
|
37933
37882
|
return false;
|
|
37934
37883
|
}
|
|
37935
37884
|
}
|
|
37936
37885
|
function readdirSafe(p2) {
|
|
37937
37886
|
try {
|
|
37938
|
-
return
|
|
37887
|
+
return fs22.readdirSync(p2);
|
|
37939
37888
|
} catch {
|
|
37940
37889
|
return [];
|
|
37941
37890
|
}
|
|
37942
37891
|
}
|
|
37943
37892
|
function rmdirIfEmpty(p2) {
|
|
37944
37893
|
try {
|
|
37945
|
-
|
|
37894
|
+
fs22.rmdirSync(p2);
|
|
37946
37895
|
} catch {
|
|
37947
37896
|
}
|
|
37948
37897
|
}
|
|
37949
37898
|
|
|
37950
37899
|
// src/transport/http-router.ts
|
|
37951
|
-
var
|
|
37952
|
-
var
|
|
37900
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
37901
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
37953
37902
|
|
|
37954
37903
|
// src/attachment/mime.ts
|
|
37955
|
-
var
|
|
37904
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
37956
37905
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37957
37906
|
var EXT_TO_NATIVE_MIME = {
|
|
37958
37907
|
// 图片
|
|
@@ -38059,14 +38008,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
38059
38008
|
".mk"
|
|
38060
38009
|
]);
|
|
38061
38010
|
function lookupMime(filePathOrName) {
|
|
38062
|
-
const ext =
|
|
38011
|
+
const ext = import_node_path17.default.extname(filePathOrName).toLowerCase();
|
|
38063
38012
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
38064
38013
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
38065
38014
|
return "application/octet-stream";
|
|
38066
38015
|
}
|
|
38067
38016
|
|
|
38068
38017
|
// src/attachment/sign-url.ts
|
|
38069
|
-
var
|
|
38018
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
38070
38019
|
var HMAC_ALGO = "sha256";
|
|
38071
38020
|
function base64urlEncode(buf) {
|
|
38072
38021
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -38083,7 +38032,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
38083
38032
|
}
|
|
38084
38033
|
function computeSig(secret, absPath, e) {
|
|
38085
38034
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
38086
|
-
return
|
|
38035
|
+
return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
38087
38036
|
}
|
|
38088
38037
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
38089
38038
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -38118,7 +38067,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
38118
38067
|
if (provided.length !== expected.length) {
|
|
38119
38068
|
return { ok: false, code: "BAD_SIG" };
|
|
38120
38069
|
}
|
|
38121
|
-
if (!
|
|
38070
|
+
if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
|
|
38122
38071
|
return { ok: false, code: "BAD_SIG" };
|
|
38123
38072
|
}
|
|
38124
38073
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -38128,9 +38077,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
38128
38077
|
}
|
|
38129
38078
|
|
|
38130
38079
|
// src/attachment/upload.ts
|
|
38131
|
-
var
|
|
38132
|
-
var
|
|
38133
|
-
var
|
|
38080
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
38081
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
38082
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
38134
38083
|
var import_promises = require("stream/promises");
|
|
38135
38084
|
var UploadError = class extends Error {
|
|
38136
38085
|
constructor(code, message) {
|
|
@@ -38141,24 +38090,24 @@ var UploadError = class extends Error {
|
|
|
38141
38090
|
code;
|
|
38142
38091
|
};
|
|
38143
38092
|
function assertValidFileName(fileName) {
|
|
38144
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
38093
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path18.default.basename(fileName)) {
|
|
38145
38094
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
38146
38095
|
}
|
|
38147
38096
|
}
|
|
38148
38097
|
var HASH_PREFIX_LEN = 16;
|
|
38149
38098
|
async function writeUploadedAttachment(args) {
|
|
38150
38099
|
assertValidFileName(args.fileName);
|
|
38151
|
-
const attachmentsRoot =
|
|
38100
|
+
const attachmentsRoot = import_node_path18.default.join(args.sessionDir, ".attachments");
|
|
38152
38101
|
try {
|
|
38153
|
-
|
|
38102
|
+
import_node_fs16.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
38154
38103
|
} catch (err) {
|
|
38155
38104
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
38156
38105
|
}
|
|
38157
|
-
const hasher =
|
|
38106
|
+
const hasher = import_node_crypto6.default.createHash("sha256");
|
|
38158
38107
|
let actualSize = 0;
|
|
38159
|
-
const tmpPath =
|
|
38108
|
+
const tmpPath = import_node_path18.default.join(
|
|
38160
38109
|
attachmentsRoot,
|
|
38161
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
38110
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
|
|
38162
38111
|
);
|
|
38163
38112
|
try {
|
|
38164
38113
|
await (0, import_promises.pipeline)(
|
|
@@ -38171,18 +38120,18 @@ async function writeUploadedAttachment(args) {
|
|
|
38171
38120
|
yield buf;
|
|
38172
38121
|
}
|
|
38173
38122
|
},
|
|
38174
|
-
|
|
38123
|
+
import_node_fs16.default.createWriteStream(tmpPath, { mode: 384 })
|
|
38175
38124
|
);
|
|
38176
38125
|
} catch (err) {
|
|
38177
38126
|
try {
|
|
38178
|
-
|
|
38127
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38179
38128
|
} catch {
|
|
38180
38129
|
}
|
|
38181
38130
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
38182
38131
|
}
|
|
38183
38132
|
if (actualSize !== args.contentLength) {
|
|
38184
38133
|
try {
|
|
38185
|
-
|
|
38134
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38186
38135
|
} catch {
|
|
38187
38136
|
}
|
|
38188
38137
|
throw new UploadError(
|
|
@@ -38191,35 +38140,35 @@ async function writeUploadedAttachment(args) {
|
|
|
38191
38140
|
);
|
|
38192
38141
|
}
|
|
38193
38142
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
38194
|
-
const hashDir =
|
|
38143
|
+
const hashDir = import_node_path18.default.join(attachmentsRoot, attachmentId);
|
|
38195
38144
|
let finalFileName;
|
|
38196
38145
|
let hashDirExists = false;
|
|
38197
38146
|
try {
|
|
38198
|
-
hashDirExists =
|
|
38147
|
+
hashDirExists = import_node_fs16.default.statSync(hashDir).isDirectory();
|
|
38199
38148
|
} catch {
|
|
38200
38149
|
}
|
|
38201
38150
|
if (hashDirExists) {
|
|
38202
|
-
const existing =
|
|
38151
|
+
const existing = import_node_fs16.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
38203
38152
|
finalFileName = existing[0] ?? args.fileName;
|
|
38204
38153
|
try {
|
|
38205
|
-
|
|
38154
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38206
38155
|
} catch {
|
|
38207
38156
|
}
|
|
38208
38157
|
} else {
|
|
38209
38158
|
try {
|
|
38210
|
-
|
|
38159
|
+
import_node_fs16.default.mkdirSync(hashDir, { recursive: true });
|
|
38211
38160
|
finalFileName = args.fileName;
|
|
38212
|
-
|
|
38161
|
+
import_node_fs16.default.renameSync(tmpPath, import_node_path18.default.join(hashDir, finalFileName));
|
|
38213
38162
|
} catch (err) {
|
|
38214
38163
|
try {
|
|
38215
|
-
|
|
38164
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
38216
38165
|
} catch {
|
|
38217
38166
|
}
|
|
38218
38167
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
38219
38168
|
}
|
|
38220
38169
|
}
|
|
38221
|
-
const absPath =
|
|
38222
|
-
const relPath =
|
|
38170
|
+
const absPath = import_node_path18.default.join(hashDir, finalFileName);
|
|
38171
|
+
const relPath = import_node_path18.default.relative(args.sessionDir, absPath);
|
|
38223
38172
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
38224
38173
|
relPath: absPath,
|
|
38225
38174
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -38233,7 +38182,7 @@ async function writeUploadedAttachment(args) {
|
|
|
38233
38182
|
|
|
38234
38183
|
// src/extension/import.ts
|
|
38235
38184
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
38236
|
-
var
|
|
38185
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
38237
38186
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
38238
38187
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
38239
38188
|
var ImportError = class extends Error {
|
|
@@ -38251,7 +38200,7 @@ async function importZip(buf, root) {
|
|
|
38251
38200
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
38252
38201
|
}
|
|
38253
38202
|
for (const name of Object.keys(zip.files)) {
|
|
38254
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
38203
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path19.default.isAbsolute(name)) {
|
|
38255
38204
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
38256
38205
|
}
|
|
38257
38206
|
}
|
|
@@ -38284,7 +38233,7 @@ async function importZip(buf, root) {
|
|
|
38284
38233
|
);
|
|
38285
38234
|
}
|
|
38286
38235
|
}
|
|
38287
|
-
const destDir =
|
|
38236
|
+
const destDir = import_node_path19.default.join(root, manifest.id);
|
|
38288
38237
|
let destExists = false;
|
|
38289
38238
|
try {
|
|
38290
38239
|
await import_promises2.default.access(destDir);
|
|
@@ -38294,15 +38243,15 @@ async function importZip(buf, root) {
|
|
|
38294
38243
|
if (destExists) {
|
|
38295
38244
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
38296
38245
|
}
|
|
38297
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
38246
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path19.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
38298
38247
|
try {
|
|
38299
38248
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38300
|
-
const dest =
|
|
38249
|
+
const dest = import_node_path19.default.join(stage, name);
|
|
38301
38250
|
if (entry.dir) {
|
|
38302
38251
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38303
38252
|
continue;
|
|
38304
38253
|
}
|
|
38305
|
-
await import_promises2.default.mkdir(
|
|
38254
|
+
await import_promises2.default.mkdir(import_node_path19.default.dirname(dest), { recursive: true });
|
|
38306
38255
|
const content = await entry.async("nodebuffer");
|
|
38307
38256
|
await import_promises2.default.writeFile(dest, content);
|
|
38308
38257
|
}
|
|
@@ -38416,7 +38365,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38416
38365
|
if (fileName === "." || fileName === "..") return false;
|
|
38417
38366
|
if (fileName.startsWith(".")) return false;
|
|
38418
38367
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38419
|
-
return fileName ===
|
|
38368
|
+
return fileName === import_node_path20.default.basename(fileName);
|
|
38420
38369
|
}
|
|
38421
38370
|
function createHttpRouter(deps) {
|
|
38422
38371
|
return async (req, res) => {
|
|
@@ -38431,25 +38380,6 @@ function createHttpRouter(deps) {
|
|
|
38431
38380
|
});
|
|
38432
38381
|
return true;
|
|
38433
38382
|
}
|
|
38434
|
-
if (url.pathname === "/auth/callback") {
|
|
38435
|
-
if (req.method !== "GET") {
|
|
38436
|
-
sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
38437
|
-
return true;
|
|
38438
|
-
}
|
|
38439
|
-
if (!deps.feishuLogin) {
|
|
38440
|
-
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
38441
|
-
return true;
|
|
38442
|
-
}
|
|
38443
|
-
const result = await deps.feishuLogin.handleLoginCallback({
|
|
38444
|
-
token: url.searchParams.get("token") ?? "",
|
|
38445
|
-
state: url.searchParams.get("state") ?? "",
|
|
38446
|
-
expiresAt: url.searchParams.get("expires_at")
|
|
38447
|
-
});
|
|
38448
|
-
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>`;
|
|
38449
|
-
res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
38450
|
-
res.end(html);
|
|
38451
|
-
return true;
|
|
38452
|
-
}
|
|
38453
38383
|
if (url.pathname.startsWith(RPC_ROUTE_PREFIX)) {
|
|
38454
38384
|
if (!deps.rpcDispatcher) {
|
|
38455
38385
|
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "HTTP RPC adapter not wired" });
|
|
@@ -38645,7 +38575,7 @@ function createHttpRouter(deps) {
|
|
|
38645
38575
|
return true;
|
|
38646
38576
|
}
|
|
38647
38577
|
let absPath;
|
|
38648
|
-
if (
|
|
38578
|
+
if (import_node_path20.default.isAbsolute(pathParam)) {
|
|
38649
38579
|
absPath = pathParam;
|
|
38650
38580
|
} else if (deps.sessionStore) {
|
|
38651
38581
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38653,7 +38583,7 @@ function createHttpRouter(deps) {
|
|
|
38653
38583
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38654
38584
|
return true;
|
|
38655
38585
|
}
|
|
38656
|
-
absPath =
|
|
38586
|
+
absPath = import_node_path20.default.join(file.cwd, pathParam);
|
|
38657
38587
|
} else {
|
|
38658
38588
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38659
38589
|
return true;
|
|
@@ -38720,9 +38650,6 @@ function parseUrl(rawUrl) {
|
|
|
38720
38650
|
return null;
|
|
38721
38651
|
}
|
|
38722
38652
|
}
|
|
38723
|
-
function escapeHtml(s) {
|
|
38724
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38725
|
-
}
|
|
38726
38653
|
function sendJson(res, status, body, extraHeaders) {
|
|
38727
38654
|
res.writeHead(status, {
|
|
38728
38655
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38736,7 +38663,7 @@ function withCtx(ctx, body) {
|
|
|
38736
38663
|
function streamFile(res, absPath, logger) {
|
|
38737
38664
|
let stat;
|
|
38738
38665
|
try {
|
|
38739
|
-
stat =
|
|
38666
|
+
stat = import_node_fs17.default.statSync(absPath);
|
|
38740
38667
|
} catch (err) {
|
|
38741
38668
|
const code = err?.code;
|
|
38742
38669
|
if (code === "ENOENT") {
|
|
@@ -38751,7 +38678,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38751
38678
|
return;
|
|
38752
38679
|
}
|
|
38753
38680
|
const mime = lookupMime(absPath);
|
|
38754
|
-
const basename =
|
|
38681
|
+
const basename = import_node_path20.default.basename(absPath);
|
|
38755
38682
|
res.writeHead(200, {
|
|
38756
38683
|
"Content-Type": mime,
|
|
38757
38684
|
"Content-Length": String(stat.size),
|
|
@@ -38759,7 +38686,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38759
38686
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38760
38687
|
"X-Content-Type-Options": "nosniff"
|
|
38761
38688
|
});
|
|
38762
|
-
const stream =
|
|
38689
|
+
const stream = import_node_fs17.default.createReadStream(absPath);
|
|
38763
38690
|
stream.on("error", (err) => {
|
|
38764
38691
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38765
38692
|
res.destroy();
|
|
@@ -38768,8 +38695,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38768
38695
|
}
|
|
38769
38696
|
|
|
38770
38697
|
// src/attachment/gc.ts
|
|
38771
|
-
var
|
|
38772
|
-
var
|
|
38698
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38699
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38773
38700
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38774
38701
|
function runAttachmentGc(args) {
|
|
38775
38702
|
const now = (args.now ?? Date.now)();
|
|
@@ -38778,38 +38705,38 @@ function runAttachmentGc(args) {
|
|
|
38778
38705
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38779
38706
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38780
38707
|
if (entry.stale) continue;
|
|
38781
|
-
if (
|
|
38708
|
+
if (import_node_path21.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38782
38709
|
}
|
|
38783
38710
|
}
|
|
38784
38711
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38785
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38712
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path21.default.join(
|
|
38786
38713
|
args.dataDir,
|
|
38787
38714
|
"sessions",
|
|
38788
38715
|
...scopeSubPath(scope).map(safeFileName),
|
|
38789
38716
|
safeFileName(sessionId)
|
|
38790
38717
|
);
|
|
38791
|
-
const attRoot =
|
|
38718
|
+
const attRoot = import_node_path21.default.join(sessionDir, ".attachments");
|
|
38792
38719
|
let hashDirs;
|
|
38793
38720
|
try {
|
|
38794
|
-
hashDirs =
|
|
38721
|
+
hashDirs = import_node_fs18.default.readdirSync(attRoot);
|
|
38795
38722
|
} catch (err) {
|
|
38796
38723
|
if (err.code === "ENOENT") continue;
|
|
38797
38724
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38798
38725
|
continue;
|
|
38799
38726
|
}
|
|
38800
38727
|
for (const hashDir of hashDirs) {
|
|
38801
|
-
const hashDirAbs =
|
|
38728
|
+
const hashDirAbs = import_node_path21.default.join(attRoot, hashDir);
|
|
38802
38729
|
let files;
|
|
38803
38730
|
try {
|
|
38804
|
-
files =
|
|
38731
|
+
files = import_node_fs18.default.readdirSync(hashDirAbs);
|
|
38805
38732
|
} catch {
|
|
38806
38733
|
continue;
|
|
38807
38734
|
}
|
|
38808
38735
|
for (const name of files) {
|
|
38809
|
-
const file =
|
|
38736
|
+
const file = import_node_path21.default.join(hashDirAbs, name);
|
|
38810
38737
|
let stat;
|
|
38811
38738
|
try {
|
|
38812
|
-
stat =
|
|
38739
|
+
stat = import_node_fs18.default.statSync(file);
|
|
38813
38740
|
} catch {
|
|
38814
38741
|
continue;
|
|
38815
38742
|
}
|
|
@@ -38818,27 +38745,27 @@ function runAttachmentGc(args) {
|
|
|
38818
38745
|
if (age < ttlMs) continue;
|
|
38819
38746
|
if (liveAbs.has(file)) continue;
|
|
38820
38747
|
try {
|
|
38821
|
-
|
|
38748
|
+
import_node_fs18.default.unlinkSync(file);
|
|
38822
38749
|
} catch (err) {
|
|
38823
38750
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38824
38751
|
}
|
|
38825
38752
|
}
|
|
38826
38753
|
try {
|
|
38827
|
-
if (
|
|
38754
|
+
if (import_node_fs18.default.readdirSync(hashDirAbs).length === 0) import_node_fs18.default.rmdirSync(hashDirAbs);
|
|
38828
38755
|
} catch {
|
|
38829
38756
|
}
|
|
38830
38757
|
}
|
|
38831
38758
|
try {
|
|
38832
|
-
if (
|
|
38759
|
+
if (import_node_fs18.default.readdirSync(attRoot).length === 0) import_node_fs18.default.rmdirSync(attRoot);
|
|
38833
38760
|
} catch {
|
|
38834
38761
|
}
|
|
38835
38762
|
}
|
|
38836
38763
|
}
|
|
38837
38764
|
|
|
38838
38765
|
// src/attachment/group.ts
|
|
38839
|
-
var
|
|
38840
|
-
var
|
|
38841
|
-
var
|
|
38766
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38767
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38768
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38842
38769
|
init_protocol();
|
|
38843
38770
|
var GroupFileStore = class {
|
|
38844
38771
|
dataDir;
|
|
@@ -38849,11 +38776,11 @@ var GroupFileStore = class {
|
|
|
38849
38776
|
this.logger = opts.logger;
|
|
38850
38777
|
}
|
|
38851
38778
|
rootForScope(scope) {
|
|
38852
|
-
return
|
|
38779
|
+
return import_node_path22.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38853
38780
|
}
|
|
38854
38781
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38855
38782
|
filePath(scope, sessionId) {
|
|
38856
|
-
return
|
|
38783
|
+
return import_node_path22.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38857
38784
|
}
|
|
38858
38785
|
cacheKey(scope, sessionId) {
|
|
38859
38786
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38862,7 +38789,7 @@ var GroupFileStore = class {
|
|
|
38862
38789
|
readFile(scope, sessionId) {
|
|
38863
38790
|
const file = this.filePath(scope, sessionId);
|
|
38864
38791
|
try {
|
|
38865
|
-
const raw =
|
|
38792
|
+
const raw = import_node_fs19.default.readFileSync(file, "utf8");
|
|
38866
38793
|
const parsed = JSON.parse(raw);
|
|
38867
38794
|
if (!Array.isArray(parsed)) {
|
|
38868
38795
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38888,10 +38815,10 @@ var GroupFileStore = class {
|
|
|
38888
38815
|
}
|
|
38889
38816
|
writeFile(scope, sessionId, entries) {
|
|
38890
38817
|
const file = this.filePath(scope, sessionId);
|
|
38891
|
-
|
|
38818
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true });
|
|
38892
38819
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38893
|
-
|
|
38894
|
-
|
|
38820
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38821
|
+
import_node_fs19.default.renameSync(tmp, file);
|
|
38895
38822
|
}
|
|
38896
38823
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38897
38824
|
list(scope, sessionId) {
|
|
@@ -38927,7 +38854,7 @@ var GroupFileStore = class {
|
|
|
38927
38854
|
entries[idx] = next;
|
|
38928
38855
|
} else {
|
|
38929
38856
|
next = {
|
|
38930
|
-
id: `gf-${
|
|
38857
|
+
id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
|
|
38931
38858
|
relPath: input.relPath,
|
|
38932
38859
|
from: input.from,
|
|
38933
38860
|
label: input.label,
|
|
@@ -38977,10 +38904,10 @@ var GroupFileStore = class {
|
|
|
38977
38904
|
};
|
|
38978
38905
|
|
|
38979
38906
|
// src/discovery/state-file.ts
|
|
38980
|
-
var
|
|
38981
|
-
var
|
|
38907
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38908
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38982
38909
|
function defaultStateFilePath(dataDir) {
|
|
38983
|
-
return
|
|
38910
|
+
return import_node_path23.default.join(dataDir, "state.json");
|
|
38984
38911
|
}
|
|
38985
38912
|
function isPidAlive(pid) {
|
|
38986
38913
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -39002,7 +38929,7 @@ var StateFileManager = class {
|
|
|
39002
38929
|
}
|
|
39003
38930
|
read() {
|
|
39004
38931
|
try {
|
|
39005
|
-
const raw =
|
|
38932
|
+
const raw = import_node_fs20.default.readFileSync(this.file, "utf8");
|
|
39006
38933
|
const parsed = JSON.parse(raw);
|
|
39007
38934
|
return parsed;
|
|
39008
38935
|
} catch {
|
|
@@ -39016,34 +38943,34 @@ var StateFileManager = class {
|
|
|
39016
38943
|
return { status: "stale", existing };
|
|
39017
38944
|
}
|
|
39018
38945
|
write(state) {
|
|
39019
|
-
|
|
38946
|
+
import_node_fs20.default.mkdirSync(import_node_path23.default.dirname(this.file), { recursive: true });
|
|
39020
38947
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
39021
|
-
|
|
39022
|
-
|
|
38948
|
+
import_node_fs20.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38949
|
+
import_node_fs20.default.renameSync(tmp, this.file);
|
|
39023
38950
|
if (process.platform !== "win32") {
|
|
39024
38951
|
try {
|
|
39025
|
-
|
|
38952
|
+
import_node_fs20.default.chmodSync(this.file, 384);
|
|
39026
38953
|
} catch {
|
|
39027
38954
|
}
|
|
39028
38955
|
}
|
|
39029
38956
|
}
|
|
39030
38957
|
delete() {
|
|
39031
38958
|
try {
|
|
39032
|
-
|
|
38959
|
+
import_node_fs20.default.unlinkSync(this.file);
|
|
39033
38960
|
} catch {
|
|
39034
38961
|
}
|
|
39035
38962
|
}
|
|
39036
38963
|
};
|
|
39037
38964
|
|
|
39038
38965
|
// src/tunnel/tunnel-manager.ts
|
|
39039
|
-
var
|
|
39040
|
-
var
|
|
39041
|
-
var
|
|
38966
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
38967
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
38968
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
39042
38969
|
var import_node_child_process5 = require("child_process");
|
|
39043
38970
|
|
|
39044
38971
|
// src/tunnel/tunnel-store.ts
|
|
39045
|
-
var
|
|
39046
|
-
var
|
|
38972
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38973
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
39047
38974
|
var TunnelStore = class {
|
|
39048
38975
|
constructor(filePath) {
|
|
39049
38976
|
this.filePath = filePath;
|
|
@@ -39051,7 +38978,7 @@ var TunnelStore = class {
|
|
|
39051
38978
|
filePath;
|
|
39052
38979
|
async get() {
|
|
39053
38980
|
try {
|
|
39054
|
-
const raw = await
|
|
38981
|
+
const raw = await import_node_fs21.default.promises.readFile(this.filePath, "utf8");
|
|
39055
38982
|
const obj = JSON.parse(raw);
|
|
39056
38983
|
if (!isPersistedTunnel(obj)) return null;
|
|
39057
38984
|
return obj;
|
|
@@ -39062,22 +38989,22 @@ var TunnelStore = class {
|
|
|
39062
38989
|
}
|
|
39063
38990
|
}
|
|
39064
38991
|
async set(v2) {
|
|
39065
|
-
const dir =
|
|
39066
|
-
await
|
|
38992
|
+
const dir = import_node_path24.default.dirname(this.filePath);
|
|
38993
|
+
await import_node_fs21.default.promises.mkdir(dir, { recursive: true });
|
|
39067
38994
|
const data = JSON.stringify(v2, null, 2);
|
|
39068
38995
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
39069
|
-
await
|
|
38996
|
+
await import_node_fs21.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
39070
38997
|
if (process.platform !== "win32") {
|
|
39071
38998
|
try {
|
|
39072
|
-
await
|
|
38999
|
+
await import_node_fs21.default.promises.chmod(tmp, 384);
|
|
39073
39000
|
} catch {
|
|
39074
39001
|
}
|
|
39075
39002
|
}
|
|
39076
|
-
await
|
|
39003
|
+
await import_node_fs21.default.promises.rename(tmp, this.filePath);
|
|
39077
39004
|
}
|
|
39078
39005
|
async clear() {
|
|
39079
39006
|
try {
|
|
39080
|
-
await
|
|
39007
|
+
await import_node_fs21.default.promises.unlink(this.filePath);
|
|
39081
39008
|
} catch (err) {
|
|
39082
39009
|
const code = err?.code;
|
|
39083
39010
|
if (code !== "ENOENT") throw err;
|
|
@@ -39172,9 +39099,9 @@ function escape(v2) {
|
|
|
39172
39099
|
}
|
|
39173
39100
|
|
|
39174
39101
|
// src/tunnel/frpc-binary.ts
|
|
39175
|
-
var
|
|
39102
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
39176
39103
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
39177
|
-
var
|
|
39104
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
39178
39105
|
var import_node_child_process3 = require("child_process");
|
|
39179
39106
|
var import_node_stream2 = require("stream");
|
|
39180
39107
|
var import_promises3 = require("stream/promises");
|
|
@@ -39206,20 +39133,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
39206
39133
|
}
|
|
39207
39134
|
async function ensureFrpcBinary(opts) {
|
|
39208
39135
|
if (opts.override) {
|
|
39209
|
-
if (!
|
|
39136
|
+
if (!import_node_fs22.default.existsSync(opts.override)) {
|
|
39210
39137
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
39211
39138
|
}
|
|
39212
39139
|
return opts.override;
|
|
39213
39140
|
}
|
|
39214
39141
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
39215
39142
|
const platform = opts.platform ?? detectPlatform();
|
|
39216
|
-
const binDir =
|
|
39217
|
-
|
|
39143
|
+
const binDir = import_node_path25.default.join(opts.dataDir, "bin");
|
|
39144
|
+
import_node_fs22.default.mkdirSync(binDir, { recursive: true });
|
|
39218
39145
|
cleanupStaleArtifacts(binDir);
|
|
39219
|
-
const stableBin =
|
|
39220
|
-
if (
|
|
39146
|
+
const stableBin = import_node_path25.default.join(binDir, "frpc");
|
|
39147
|
+
if (import_node_fs22.default.existsSync(stableBin)) return stableBin;
|
|
39221
39148
|
const partialBin = `${stableBin}.partial`;
|
|
39222
|
-
const tarballPath =
|
|
39149
|
+
const tarballPath = import_node_path25.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
39223
39150
|
try {
|
|
39224
39151
|
const url = frpcDownloadUrl(version2, platform);
|
|
39225
39152
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -39228,8 +39155,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
39228
39155
|
} else {
|
|
39229
39156
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
39230
39157
|
}
|
|
39231
|
-
|
|
39232
|
-
|
|
39158
|
+
import_node_fs22.default.chmodSync(partialBin, 493);
|
|
39159
|
+
import_node_fs22.default.renameSync(partialBin, stableBin);
|
|
39233
39160
|
} finally {
|
|
39234
39161
|
safeUnlink(tarballPath);
|
|
39235
39162
|
safeUnlink(partialBin);
|
|
@@ -39239,15 +39166,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
39239
39166
|
function cleanupStaleArtifacts(binDir) {
|
|
39240
39167
|
let entries;
|
|
39241
39168
|
try {
|
|
39242
|
-
entries =
|
|
39169
|
+
entries = import_node_fs22.default.readdirSync(binDir);
|
|
39243
39170
|
} catch {
|
|
39244
39171
|
return;
|
|
39245
39172
|
}
|
|
39246
39173
|
for (const name of entries) {
|
|
39247
39174
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
39248
|
-
const full =
|
|
39175
|
+
const full = import_node_path25.default.join(binDir, name);
|
|
39249
39176
|
try {
|
|
39250
|
-
|
|
39177
|
+
import_node_fs22.default.rmSync(full, { recursive: true, force: true });
|
|
39251
39178
|
} catch {
|
|
39252
39179
|
}
|
|
39253
39180
|
}
|
|
@@ -39255,7 +39182,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
39255
39182
|
}
|
|
39256
39183
|
function safeUnlink(p2) {
|
|
39257
39184
|
try {
|
|
39258
|
-
|
|
39185
|
+
import_node_fs22.default.unlinkSync(p2);
|
|
39259
39186
|
} catch {
|
|
39260
39187
|
}
|
|
39261
39188
|
}
|
|
@@ -39266,13 +39193,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
39266
39193
|
if (!res.ok || !res.body) {
|
|
39267
39194
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
39268
39195
|
}
|
|
39269
|
-
const out =
|
|
39196
|
+
const out = import_node_fs22.default.createWriteStream(dest);
|
|
39270
39197
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
39271
39198
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
39272
39199
|
}
|
|
39273
39200
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
39274
|
-
const work =
|
|
39275
|
-
|
|
39201
|
+
const work = import_node_path25.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
39202
|
+
import_node_fs22.default.mkdirSync(work, { recursive: true });
|
|
39276
39203
|
try {
|
|
39277
39204
|
await new Promise((resolve6, reject) => {
|
|
39278
39205
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -39280,32 +39207,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
39280
39207
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
39281
39208
|
});
|
|
39282
39209
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
39283
|
-
const src =
|
|
39284
|
-
if (!
|
|
39210
|
+
const src = import_node_path25.default.join(work, dirName, "frpc");
|
|
39211
|
+
if (!import_node_fs22.default.existsSync(src)) {
|
|
39285
39212
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
39286
39213
|
}
|
|
39287
|
-
|
|
39214
|
+
import_node_fs22.default.copyFileSync(src, destBin);
|
|
39288
39215
|
} finally {
|
|
39289
|
-
|
|
39216
|
+
import_node_fs22.default.rmSync(work, { recursive: true, force: true });
|
|
39290
39217
|
}
|
|
39291
39218
|
}
|
|
39292
39219
|
|
|
39293
39220
|
// src/tunnel/frpc-process.ts
|
|
39294
|
-
var
|
|
39295
|
-
var
|
|
39221
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
39222
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
39296
39223
|
var import_node_child_process4 = require("child_process");
|
|
39297
39224
|
function frpcPidFilePath(dataDir) {
|
|
39298
|
-
return
|
|
39225
|
+
return import_node_path26.default.join(dataDir, "frpc.pid");
|
|
39299
39226
|
}
|
|
39300
39227
|
function writeFrpcPid(dataDir, pid) {
|
|
39301
39228
|
try {
|
|
39302
|
-
|
|
39229
|
+
import_node_fs23.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
39303
39230
|
} catch {
|
|
39304
39231
|
}
|
|
39305
39232
|
}
|
|
39306
39233
|
function clearFrpcPid(dataDir) {
|
|
39307
39234
|
try {
|
|
39308
|
-
|
|
39235
|
+
import_node_fs23.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
39309
39236
|
} catch {
|
|
39310
39237
|
}
|
|
39311
39238
|
}
|
|
@@ -39321,7 +39248,7 @@ function defaultIsPidAlive(pid) {
|
|
|
39321
39248
|
}
|
|
39322
39249
|
function defaultReadPidFile(file) {
|
|
39323
39250
|
try {
|
|
39324
|
-
return
|
|
39251
|
+
return import_node_fs23.default.readFileSync(file, "utf8");
|
|
39325
39252
|
} catch {
|
|
39326
39253
|
return null;
|
|
39327
39254
|
}
|
|
@@ -39337,7 +39264,7 @@ function defaultSleep(ms) {
|
|
|
39337
39264
|
}
|
|
39338
39265
|
async function killStaleFrpc(deps) {
|
|
39339
39266
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
39340
|
-
const tomlPath =
|
|
39267
|
+
const tomlPath = import_node_path26.default.join(deps.dataDir, "frpc.toml");
|
|
39341
39268
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
39342
39269
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
39343
39270
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -39361,7 +39288,7 @@ async function killStaleFrpc(deps) {
|
|
|
39361
39288
|
}
|
|
39362
39289
|
if (victims.size === 0) {
|
|
39363
39290
|
try {
|
|
39364
|
-
|
|
39291
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
39365
39292
|
} catch {
|
|
39366
39293
|
}
|
|
39367
39294
|
return;
|
|
@@ -39372,7 +39299,7 @@ async function killStaleFrpc(deps) {
|
|
|
39372
39299
|
}
|
|
39373
39300
|
await sleep(deps.reapWaitMs ?? 300);
|
|
39374
39301
|
try {
|
|
39375
|
-
|
|
39302
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
39376
39303
|
} catch {
|
|
39377
39304
|
}
|
|
39378
39305
|
}
|
|
@@ -39409,7 +39336,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
39409
39336
|
var TunnelManager = class {
|
|
39410
39337
|
constructor(deps) {
|
|
39411
39338
|
this.deps = deps;
|
|
39412
|
-
this.store = deps.store ?? new TunnelStore(
|
|
39339
|
+
this.store = deps.store ?? new TunnelStore(import_node_path27.default.join(deps.dataDir, "tunnel.json"));
|
|
39413
39340
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39414
39341
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39415
39342
|
}
|
|
@@ -39536,8 +39463,8 @@ var TunnelManager = class {
|
|
|
39536
39463
|
dataDir: this.deps.dataDir,
|
|
39537
39464
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39538
39465
|
});
|
|
39539
|
-
const tomlPath =
|
|
39540
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39466
|
+
const tomlPath = import_node_path27.default.join(this.deps.dataDir, "frpc.toml");
|
|
39467
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
|
|
39541
39468
|
const toml = buildFrpcToml({
|
|
39542
39469
|
serverAddr: t.frpsHost,
|
|
39543
39470
|
serverPort: t.frpsPort,
|
|
@@ -39547,12 +39474,12 @@ var TunnelManager = class {
|
|
|
39547
39474
|
localPort,
|
|
39548
39475
|
logLevel: "info"
|
|
39549
39476
|
});
|
|
39550
|
-
await
|
|
39477
|
+
await import_node_fs24.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39551
39478
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39552
39479
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39553
39480
|
});
|
|
39554
|
-
const logFilePath =
|
|
39555
|
-
const logStream =
|
|
39481
|
+
const logFilePath = import_node_path27.default.join(this.deps.dataDir, "frpc.log");
|
|
39482
|
+
const logStream = import_node_fs24.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39556
39483
|
logStream.on("error", () => {
|
|
39557
39484
|
});
|
|
39558
39485
|
const tee = (chunk) => {
|
|
@@ -39635,61 +39562,59 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39635
39562
|
|
|
39636
39563
|
// src/tunnel/device-key.ts
|
|
39637
39564
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39638
|
-
var
|
|
39639
|
-
var
|
|
39565
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39566
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39640
39567
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39641
39568
|
function deriveStableDeviceKey(opts = {}) {
|
|
39642
39569
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39643
39570
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39644
39571
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39645
|
-
const defaultDataDir =
|
|
39646
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39572
|
+
const defaultDataDir = import_node_path28.default.resolve(import_node_path28.default.join(home, ".clawd"));
|
|
39573
|
+
const normalizedDataDir = opts.dataDir ? import_node_path28.default.resolve(opts.dataDir) : null;
|
|
39647
39574
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39648
39575
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39649
|
-
return
|
|
39576
|
+
return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39650
39577
|
}
|
|
39651
39578
|
|
|
39652
39579
|
// src/auth-store.ts
|
|
39653
|
-
var
|
|
39654
|
-
var
|
|
39655
|
-
var
|
|
39580
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39581
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39582
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39656
39583
|
var AUTH_FILE_NAME = "auth.json";
|
|
39657
39584
|
function authFilePath(dataDir) {
|
|
39658
|
-
return
|
|
39585
|
+
return import_node_path29.default.join(dataDir, AUTH_FILE_NAME);
|
|
39659
39586
|
}
|
|
39660
39587
|
function loadOrCreateAuthFile(opts) {
|
|
39661
39588
|
const file = authFilePath(opts.dataDir);
|
|
39662
|
-
const generate = opts.generate ??
|
|
39589
|
+
const generate = opts.generate ?? defaultGenerateToken2;
|
|
39663
39590
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39664
39591
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39665
39592
|
const existing = readAuthFile(file);
|
|
39666
|
-
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId
|
|
39593
|
+
if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
|
|
39667
39594
|
return {
|
|
39668
39595
|
token: existing.token,
|
|
39669
39596
|
signSecret: existing.signSecret,
|
|
39670
39597
|
ownerPrincipalId: existing.ownerPrincipalId,
|
|
39671
|
-
deviceId: existing.deviceId,
|
|
39672
39598
|
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39673
39599
|
};
|
|
39674
39600
|
}
|
|
39675
39601
|
const token = existing?.token || generate();
|
|
39676
39602
|
const signSecret = existing?.signSecret || generate();
|
|
39677
39603
|
const ownerPrincipalId = existing?.ownerPrincipalId || genId();
|
|
39678
|
-
const deviceId = ownerPrincipalId;
|
|
39679
39604
|
const createdAt = existing?.createdAt || now().toISOString();
|
|
39680
|
-
const next = { token, signSecret, ownerPrincipalId,
|
|
39605
|
+
const next = { token, signSecret, ownerPrincipalId, createdAt };
|
|
39681
39606
|
writeAuthFile(file, next);
|
|
39682
39607
|
return next;
|
|
39683
39608
|
}
|
|
39684
|
-
function
|
|
39685
|
-
return
|
|
39609
|
+
function defaultGenerateToken2() {
|
|
39610
|
+
return import_node_crypto10.default.randomBytes(32).toString("base64url");
|
|
39686
39611
|
}
|
|
39687
39612
|
function defaultGenerateOwnerPrincipalId() {
|
|
39688
|
-
return `owner-${
|
|
39613
|
+
return `owner-${import_node_crypto10.default.randomUUID()}`;
|
|
39689
39614
|
}
|
|
39690
39615
|
function readAuthFile(file) {
|
|
39691
39616
|
try {
|
|
39692
|
-
const raw =
|
|
39617
|
+
const raw = import_node_fs25.default.readFileSync(file, "utf8");
|
|
39693
39618
|
const parsed = JSON.parse(raw);
|
|
39694
39619
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39695
39620
|
return null;
|
|
@@ -39698,7 +39623,6 @@ function readAuthFile(file) {
|
|
|
39698
39623
|
token: parsed.token,
|
|
39699
39624
|
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
39700
39625
|
ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
|
|
39701
|
-
deviceId: typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? parsed.deviceId : void 0,
|
|
39702
39626
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
39703
39627
|
};
|
|
39704
39628
|
} catch (err) {
|
|
@@ -39708,25 +39632,25 @@ function readAuthFile(file) {
|
|
|
39708
39632
|
}
|
|
39709
39633
|
}
|
|
39710
39634
|
function writeAuthFile(file, content) {
|
|
39711
|
-
|
|
39712
|
-
|
|
39635
|
+
import_node_fs25.default.mkdirSync(import_node_path29.default.dirname(file), { recursive: true });
|
|
39636
|
+
import_node_fs25.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39713
39637
|
try {
|
|
39714
|
-
|
|
39638
|
+
import_node_fs25.default.chmodSync(file, 384);
|
|
39715
39639
|
} catch {
|
|
39716
39640
|
}
|
|
39717
39641
|
}
|
|
39718
39642
|
|
|
39719
39643
|
// src/owner-profile.ts
|
|
39720
|
-
var
|
|
39644
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39721
39645
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39722
|
-
var
|
|
39646
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
39723
39647
|
var PROFILE_FILENAME = "profile.json";
|
|
39724
39648
|
function loadOwnerDisplayName(dataDir) {
|
|
39725
39649
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39726
|
-
const profilePath =
|
|
39650
|
+
const profilePath = import_node_path30.default.join(dataDir, PROFILE_FILENAME);
|
|
39727
39651
|
let raw;
|
|
39728
39652
|
try {
|
|
39729
|
-
raw =
|
|
39653
|
+
raw = import_node_fs26.default.readFileSync(profilePath, "utf8");
|
|
39730
39654
|
} catch {
|
|
39731
39655
|
return fallback;
|
|
39732
39656
|
}
|
|
@@ -39748,357 +39672,6 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39748
39672
|
return displayName;
|
|
39749
39673
|
}
|
|
39750
39674
|
|
|
39751
|
-
// src/feishu-auth/owner-identity-store.ts
|
|
39752
|
-
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39753
|
-
var import_node_path30 = __toESM(require("path"), 1);
|
|
39754
|
-
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
39755
|
-
var OwnerIdentityStore = class {
|
|
39756
|
-
file;
|
|
39757
|
-
constructor(dataDir) {
|
|
39758
|
-
this.file = import_node_path30.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
39759
|
-
}
|
|
39760
|
-
read() {
|
|
39761
|
-
let raw;
|
|
39762
|
-
try {
|
|
39763
|
-
raw = import_node_fs26.default.readFileSync(this.file, "utf8");
|
|
39764
|
-
} catch {
|
|
39765
|
-
return null;
|
|
39766
|
-
}
|
|
39767
|
-
let parsed;
|
|
39768
|
-
try {
|
|
39769
|
-
parsed = JSON.parse(raw);
|
|
39770
|
-
} catch {
|
|
39771
|
-
return null;
|
|
39772
|
-
}
|
|
39773
|
-
const r = parsed;
|
|
39774
|
-
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) {
|
|
39775
|
-
return null;
|
|
39776
|
-
}
|
|
39777
|
-
return {
|
|
39778
|
-
identity: {
|
|
39779
|
-
ownerId: r.identity.ownerId,
|
|
39780
|
-
provider: r.identity.provider,
|
|
39781
|
-
displayName: r.identity.displayName,
|
|
39782
|
-
...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
|
|
39783
|
-
},
|
|
39784
|
-
ttcToken: r.ttcToken,
|
|
39785
|
-
ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
|
|
39786
|
-
loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39787
|
-
};
|
|
39788
|
-
}
|
|
39789
|
-
write(record) {
|
|
39790
|
-
import_node_fs26.default.mkdirSync(import_node_path30.default.dirname(this.file), { recursive: true });
|
|
39791
|
-
import_node_fs26.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
|
|
39792
|
-
try {
|
|
39793
|
-
import_node_fs26.default.chmodSync(this.file, 384);
|
|
39794
|
-
} catch {
|
|
39795
|
-
}
|
|
39796
|
-
}
|
|
39797
|
-
clear() {
|
|
39798
|
-
try {
|
|
39799
|
-
import_node_fs26.default.unlinkSync(this.file);
|
|
39800
|
-
} catch (err) {
|
|
39801
|
-
const code = err?.code;
|
|
39802
|
-
if (code !== "ENOENT") throw err;
|
|
39803
|
-
}
|
|
39804
|
-
}
|
|
39805
|
-
};
|
|
39806
|
-
|
|
39807
|
-
// src/feishu-auth/login-flow.ts
|
|
39808
|
-
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39809
|
-
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
39810
|
-
var LoginFlow = class {
|
|
39811
|
-
constructor(deps) {
|
|
39812
|
-
this.deps = deps;
|
|
39813
|
-
}
|
|
39814
|
-
deps;
|
|
39815
|
-
pendingStates = /* @__PURE__ */ new Map();
|
|
39816
|
-
start() {
|
|
39817
|
-
const state = import_node_crypto10.default.randomBytes(16).toString("base64url");
|
|
39818
|
-
const now = (this.deps.now ?? Date.now)();
|
|
39819
|
-
this.pendingStates.set(state, now);
|
|
39820
|
-
this.gcExpired(now);
|
|
39821
|
-
const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
|
|
39822
|
-
url.searchParams.set("callback_url", this.deps.getCallbackUrl());
|
|
39823
|
-
url.searchParams.set("state", state);
|
|
39824
|
-
url.searchParams.set("auto", "1");
|
|
39825
|
-
return { authUrl: url.toString(), state };
|
|
39826
|
-
}
|
|
39827
|
-
async handleCallback(params) {
|
|
39828
|
-
const now = (this.deps.now ?? Date.now)();
|
|
39829
|
-
const issuedAt = this.pendingStates.get(params.state);
|
|
39830
|
-
if (issuedAt === void 0) {
|
|
39831
|
-
return { ok: false, reason: "state mismatch" };
|
|
39832
|
-
}
|
|
39833
|
-
this.pendingStates.delete(params.state);
|
|
39834
|
-
if (now - issuedAt > STATE_TTL_MS) {
|
|
39835
|
-
return { ok: false, reason: "state expired" };
|
|
39836
|
-
}
|
|
39837
|
-
if (!params.token) {
|
|
39838
|
-
return { ok: false, reason: "missing token" };
|
|
39839
|
-
}
|
|
39840
|
-
let identity;
|
|
39841
|
-
try {
|
|
39842
|
-
identity = await this.deps.fetchUserInfo(params.token);
|
|
39843
|
-
} catch (err) {
|
|
39844
|
-
return { ok: false, reason: `user_info failed: ${err.message}` };
|
|
39845
|
-
}
|
|
39846
|
-
const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
|
|
39847
|
-
this.deps.store.write({
|
|
39848
|
-
identity,
|
|
39849
|
-
ttcToken: params.token,
|
|
39850
|
-
ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
|
|
39851
|
-
loginAt: new Date(now).toISOString()
|
|
39852
|
-
});
|
|
39853
|
-
return { ok: true, identity };
|
|
39854
|
-
}
|
|
39855
|
-
gcExpired(now) {
|
|
39856
|
-
for (const [state, issuedAt] of this.pendingStates) {
|
|
39857
|
-
if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
|
|
39858
|
-
}
|
|
39859
|
-
}
|
|
39860
|
-
};
|
|
39861
|
-
|
|
39862
|
-
// src/feishu-auth/ttc-client.ts
|
|
39863
|
-
var TTC_HOSTS = {
|
|
39864
|
-
auth: "https://app.ttcadvisory.com",
|
|
39865
|
-
api: "https://api.ttcadvisory.com"
|
|
39866
|
-
};
|
|
39867
|
-
var TtcUserInfoError = class extends Error {
|
|
39868
|
-
constructor(code, message) {
|
|
39869
|
-
super(message);
|
|
39870
|
-
this.code = code;
|
|
39871
|
-
this.name = "TtcUserInfoError";
|
|
39872
|
-
}
|
|
39873
|
-
code;
|
|
39874
|
-
};
|
|
39875
|
-
async function fetchTtcUserInfo(opts) {
|
|
39876
|
-
const doFetch = opts.fetchImpl ?? fetch;
|
|
39877
|
-
let res;
|
|
39878
|
-
try {
|
|
39879
|
-
res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
|
|
39880
|
-
headers: { Authorization: `Bearer ${opts.token}` }
|
|
39881
|
-
});
|
|
39882
|
-
} catch (err) {
|
|
39883
|
-
throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
|
|
39884
|
-
}
|
|
39885
|
-
if (!res.ok) {
|
|
39886
|
-
if (res.status === 401) {
|
|
39887
|
-
throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
|
|
39888
|
-
}
|
|
39889
|
-
throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
|
|
39890
|
-
}
|
|
39891
|
-
const body = await res.json();
|
|
39892
|
-
if (body.code !== 0) {
|
|
39893
|
-
throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
|
|
39894
|
-
}
|
|
39895
|
-
const d = body.data;
|
|
39896
|
-
if (!d || typeof d.unique_id !== "string" || d.unique_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
|
|
39897
|
-
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing unique_id or name");
|
|
39898
|
-
}
|
|
39899
|
-
return {
|
|
39900
|
-
ownerId: d.unique_id,
|
|
39901
|
-
provider: "ttc",
|
|
39902
|
-
displayName: d.name,
|
|
39903
|
-
...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
|
|
39904
|
-
};
|
|
39905
|
-
}
|
|
39906
|
-
|
|
39907
|
-
// src/feishu-auth/central-client.ts
|
|
39908
|
-
var CentralClientError = class extends Error {
|
|
39909
|
-
constructor(code, message, cause) {
|
|
39910
|
-
super(message);
|
|
39911
|
-
this.code = code;
|
|
39912
|
-
this.cause = cause;
|
|
39913
|
-
this.name = "CentralClientError";
|
|
39914
|
-
}
|
|
39915
|
-
code;
|
|
39916
|
-
cause;
|
|
39917
|
-
};
|
|
39918
|
-
async function centralRequest(opts, path60, init) {
|
|
39919
|
-
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
39920
|
-
const url = `${opts.api.replace(/\/+$/, "")}${path60}`;
|
|
39921
|
-
const ctrl = new AbortController();
|
|
39922
|
-
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
39923
|
-
let res;
|
|
39924
|
-
try {
|
|
39925
|
-
res = await f(url, {
|
|
39926
|
-
method: init.method,
|
|
39927
|
-
headers: {
|
|
39928
|
-
"content-type": "application/json",
|
|
39929
|
-
...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
|
|
39930
|
-
},
|
|
39931
|
-
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
39932
|
-
signal: ctrl.signal
|
|
39933
|
-
});
|
|
39934
|
-
} catch (err) {
|
|
39935
|
-
clearTimeout(timer);
|
|
39936
|
-
throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
|
|
39937
|
-
}
|
|
39938
|
-
clearTimeout(timer);
|
|
39939
|
-
if (res.status === 401) {
|
|
39940
|
-
throw new CentralClientError("UNAUTHORIZED", "TTC JWT invalid or expired");
|
|
39941
|
-
}
|
|
39942
|
-
if (!res.ok) {
|
|
39943
|
-
throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}`);
|
|
39944
|
-
}
|
|
39945
|
-
try {
|
|
39946
|
-
return await res.json();
|
|
39947
|
-
} catch (err) {
|
|
39948
|
-
throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
|
|
39949
|
-
}
|
|
39950
|
-
}
|
|
39951
|
-
async function centralExchange(opts) {
|
|
39952
|
-
const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
|
|
39953
|
-
method: "POST",
|
|
39954
|
-
body: {
|
|
39955
|
-
jwt: opts.jwt,
|
|
39956
|
-
provider: opts.provider,
|
|
39957
|
-
deviceId: opts.deviceId,
|
|
39958
|
-
selfDeviceId: opts.selfDeviceId
|
|
39959
|
-
}
|
|
39960
|
-
});
|
|
39961
|
-
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
|
|
39962
|
-
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
|
|
39963
|
-
}
|
|
39964
|
-
const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
|
|
39965
|
-
return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
|
|
39966
|
-
}
|
|
39967
|
-
async function centralGetPublicKey(opts) {
|
|
39968
|
-
const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
|
|
39969
|
-
method: "GET"
|
|
39970
|
-
});
|
|
39971
|
-
if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
|
|
39972
|
-
throw new CentralClientError("BAD_RESPONSE", "public-key: missing or malformed publicKeyPem");
|
|
39973
|
-
}
|
|
39974
|
-
return { publicKeyPem: body.publicKeyPem };
|
|
39975
|
-
}
|
|
39976
|
-
async function centralUpsertDevice(opts) {
|
|
39977
|
-
const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
|
|
39978
|
-
method: "PUT",
|
|
39979
|
-
bearer: opts.ttcJwt,
|
|
39980
|
-
body: {
|
|
39981
|
-
deviceId: opts.deviceId,
|
|
39982
|
-
provider: opts.provider,
|
|
39983
|
-
...opts.url ? { url: opts.url } : {},
|
|
39984
|
-
...opts.name ? { name: opts.name } : {}
|
|
39985
|
-
}
|
|
39986
|
-
});
|
|
39987
|
-
if (body.ok !== true) {
|
|
39988
|
-
throw new CentralClientError("BAD_RESPONSE", "upsertDevice: server did not ack ok");
|
|
39989
|
-
}
|
|
39990
|
-
return { ok: true };
|
|
39991
|
-
}
|
|
39992
|
-
async function centralListDevices(opts) {
|
|
39993
|
-
const body = await centralRequest(
|
|
39994
|
-
opts,
|
|
39995
|
-
`/api/clawd-identity/devices?provider=${encodeURIComponent(opts.provider)}`,
|
|
39996
|
-
{ method: "GET", bearer: opts.ttcJwt }
|
|
39997
|
-
);
|
|
39998
|
-
if (!Array.isArray(body.devices)) {
|
|
39999
|
-
throw new CentralClientError("BAD_RESPONSE", "devices: missing devices array");
|
|
40000
|
-
}
|
|
40001
|
-
const out = [];
|
|
40002
|
-
for (const raw of body.devices) {
|
|
40003
|
-
const d = raw;
|
|
40004
|
-
if (typeof d.deviceId !== "string" || typeof d.name !== "string" || typeof d.url !== "string") {
|
|
40005
|
-
throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
|
|
40006
|
-
}
|
|
40007
|
-
out.push({ deviceId: d.deviceId, name: d.name, url: d.url });
|
|
40008
|
-
}
|
|
40009
|
-
return out;
|
|
40010
|
-
}
|
|
40011
|
-
|
|
40012
|
-
// src/feishu-auth/verify-token.ts
|
|
40013
|
-
var crypto13 = __toESM(require("crypto"), 1);
|
|
40014
|
-
var CONNECT_TOKEN_PREFIX = "clawdtk1";
|
|
40015
|
-
function verifyConnectToken(args) {
|
|
40016
|
-
const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
40017
|
-
const parts = args.token.split(".");
|
|
40018
|
-
if (parts.length !== 3 || parts[0] !== CONNECT_TOKEN_PREFIX) {
|
|
40019
|
-
return { ok: false, reason: "BAD_FORMAT" };
|
|
40020
|
-
}
|
|
40021
|
-
const [prefix, payloadB64, sigB64] = parts;
|
|
40022
|
-
const data = `${prefix}.${payloadB64}`;
|
|
40023
|
-
let signatureValid = false;
|
|
40024
|
-
try {
|
|
40025
|
-
signatureValid = crypto13.verify(
|
|
40026
|
-
null,
|
|
40027
|
-
Buffer.from(data, "utf8"),
|
|
40028
|
-
args.publicKeyPem,
|
|
40029
|
-
Buffer.from(sigB64, "base64url")
|
|
40030
|
-
);
|
|
40031
|
-
} catch {
|
|
40032
|
-
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
40033
|
-
}
|
|
40034
|
-
if (!signatureValid) {
|
|
40035
|
-
return { ok: false, reason: "BAD_SIGNATURE" };
|
|
40036
|
-
}
|
|
40037
|
-
let payload;
|
|
40038
|
-
try {
|
|
40039
|
-
payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
40040
|
-
} catch {
|
|
40041
|
-
return { ok: false, reason: "BAD_FORMAT" };
|
|
40042
|
-
}
|
|
40043
|
-
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") {
|
|
40044
|
-
return { ok: false, reason: "BAD_FORMAT" };
|
|
40045
|
-
}
|
|
40046
|
-
if (payload.aud !== args.expectedDeviceId) {
|
|
40047
|
-
return { ok: false, reason: "WRONG_AUDIENCE" };
|
|
40048
|
-
}
|
|
40049
|
-
if (payload.exp <= now) {
|
|
40050
|
-
return { ok: false, reason: "EXPIRED" };
|
|
40051
|
-
}
|
|
40052
|
-
return {
|
|
40053
|
-
ok: true,
|
|
40054
|
-
subDevice: payload.subDevice,
|
|
40055
|
-
subOwner: payload.subOwner,
|
|
40056
|
-
provider: payload.provider,
|
|
40057
|
-
displayName: payload.name
|
|
40058
|
-
};
|
|
40059
|
-
}
|
|
40060
|
-
|
|
40061
|
-
// src/feishu-auth/server-key.ts
|
|
40062
|
-
var fs35 = __toESM(require("fs"), 1);
|
|
40063
|
-
var path39 = __toESM(require("path"), 1);
|
|
40064
|
-
var FILE_NAME2 = "server-signing-key.json";
|
|
40065
|
-
var ServerKeyStore = class {
|
|
40066
|
-
constructor(dataDir) {
|
|
40067
|
-
this.dataDir = dataDir;
|
|
40068
|
-
}
|
|
40069
|
-
dataDir;
|
|
40070
|
-
filePath() {
|
|
40071
|
-
return path39.join(this.dataDir, FILE_NAME2);
|
|
40072
|
-
}
|
|
40073
|
-
/** 读缓存的公钥;无缓存 / 损坏 → null(调用方决定是否触发拉取) */
|
|
40074
|
-
read() {
|
|
40075
|
-
try {
|
|
40076
|
-
const raw = fs35.readFileSync(this.filePath(), "utf8");
|
|
40077
|
-
const parsed = JSON.parse(raw);
|
|
40078
|
-
if (typeof parsed.publicKeyPem === "string" && parsed.publicKeyPem.includes("PUBLIC KEY")) {
|
|
40079
|
-
return parsed.publicKeyPem;
|
|
40080
|
-
}
|
|
40081
|
-
return null;
|
|
40082
|
-
} catch {
|
|
40083
|
-
return null;
|
|
40084
|
-
}
|
|
40085
|
-
}
|
|
40086
|
-
write(publicKeyPem) {
|
|
40087
|
-
const content = {
|
|
40088
|
-
publicKeyPem,
|
|
40089
|
-
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40090
|
-
};
|
|
40091
|
-
fs35.mkdirSync(this.dataDir, { recursive: true });
|
|
40092
|
-
fs35.writeFileSync(this.filePath(), JSON.stringify(content, null, 2), { mode: 384 });
|
|
40093
|
-
}
|
|
40094
|
-
clear() {
|
|
40095
|
-
try {
|
|
40096
|
-
fs35.unlinkSync(this.filePath());
|
|
40097
|
-
} catch {
|
|
40098
|
-
}
|
|
40099
|
-
}
|
|
40100
|
-
};
|
|
40101
|
-
|
|
40102
39675
|
// src/index.ts
|
|
40103
39676
|
init_protocol();
|
|
40104
39677
|
|
|
@@ -40229,15 +39802,23 @@ function buildSessionHandlers(deps) {
|
|
|
40229
39802
|
}
|
|
40230
39803
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
40231
39804
|
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
40232
|
-
const
|
|
39805
|
+
const creatorOwnerPrincipalId = ctx?.principal.kind === "guest" ? ctx.peerOwnerPrincipalId : ownerPrincipalId;
|
|
39806
|
+
const { response, broadcast } = manager.create(
|
|
39807
|
+
args,
|
|
39808
|
+
creatorPrincipalId,
|
|
39809
|
+
creatorOwnerPrincipalId
|
|
39810
|
+
);
|
|
40233
39811
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
40234
39812
|
};
|
|
40235
39813
|
const list = async (_frame, _client, ctx) => {
|
|
40236
39814
|
const { response } = manager.list();
|
|
40237
39815
|
if (ctx && ctx.principal.kind === "guest") {
|
|
40238
39816
|
const sessions = response.sessions ?? [];
|
|
40239
|
-
const
|
|
40240
|
-
const
|
|
39817
|
+
const peerOwnerId = ctx.peerOwnerPrincipalId;
|
|
39818
|
+
const capId = ctx.capabilityId;
|
|
39819
|
+
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39820
|
+
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === capId
|
|
39821
|
+
);
|
|
40241
39822
|
return { response: { type: "session:list", sessions: filtered } };
|
|
40242
39823
|
}
|
|
40243
39824
|
return { response: { type: "session:list", ...response } };
|
|
@@ -40525,7 +40106,7 @@ function buildPermissionHandlers(deps) {
|
|
|
40525
40106
|
}
|
|
40526
40107
|
|
|
40527
40108
|
// src/handlers/history.ts
|
|
40528
|
-
var
|
|
40109
|
+
var path41 = __toESM(require("path"), 1);
|
|
40529
40110
|
init_protocol();
|
|
40530
40111
|
|
|
40531
40112
|
// src/session/recent-dirs.ts
|
|
@@ -40543,7 +40124,7 @@ function listRecentDirs(store, limit = 50) {
|
|
|
40543
40124
|
}
|
|
40544
40125
|
|
|
40545
40126
|
// src/permission/persona-paths.ts
|
|
40546
|
-
var
|
|
40127
|
+
var path40 = __toESM(require("path"), 1);
|
|
40547
40128
|
function getAllowedPersonaIds(grants, action) {
|
|
40548
40129
|
const ids = /* @__PURE__ */ new Set();
|
|
40549
40130
|
for (const g2 of grants) {
|
|
@@ -40556,26 +40137,26 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
40556
40137
|
return ids;
|
|
40557
40138
|
}
|
|
40558
40139
|
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
40559
|
-
const root =
|
|
40560
|
-
const target =
|
|
40561
|
-
const sep3 = root.endsWith(
|
|
40140
|
+
const root = path40.resolve(personaRoot);
|
|
40141
|
+
const target = path40.resolve(absPath);
|
|
40142
|
+
const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
|
|
40562
40143
|
if (!target.startsWith(root + sep3)) return false;
|
|
40563
|
-
const rel =
|
|
40144
|
+
const rel = path40.relative(root, target);
|
|
40564
40145
|
if (!rel || rel.startsWith("..")) return false;
|
|
40565
|
-
const personaId = rel.split(
|
|
40146
|
+
const personaId = rel.split(path40.sep)[0];
|
|
40566
40147
|
if (!personaId) return false;
|
|
40567
40148
|
const allowed = getAllowedPersonaIds(grants, action);
|
|
40568
40149
|
if (allowed === "*") return true;
|
|
40569
40150
|
return allowed.has(personaId);
|
|
40570
40151
|
}
|
|
40571
40152
|
function personaIdFromPath(absPath, personaRoot) {
|
|
40572
|
-
const root =
|
|
40573
|
-
const target =
|
|
40574
|
-
const sep3 = root.endsWith(
|
|
40153
|
+
const root = path40.resolve(personaRoot);
|
|
40154
|
+
const target = path40.resolve(absPath);
|
|
40155
|
+
const sep3 = root.endsWith(path40.sep) ? "" : path40.sep;
|
|
40575
40156
|
if (!target.startsWith(root + sep3)) return null;
|
|
40576
|
-
const rel =
|
|
40157
|
+
const rel = path40.relative(root, target);
|
|
40577
40158
|
if (!rel || rel.startsWith("..")) return null;
|
|
40578
|
-
const id = rel.split(
|
|
40159
|
+
const id = rel.split(path40.sep)[0];
|
|
40579
40160
|
return id || null;
|
|
40580
40161
|
}
|
|
40581
40162
|
|
|
@@ -40600,7 +40181,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40600
40181
|
if (!pid) return false;
|
|
40601
40182
|
return isGuestPathAllowed(
|
|
40602
40183
|
ctx.grants,
|
|
40603
|
-
|
|
40184
|
+
path41.join(personaRoot, pid),
|
|
40604
40185
|
personaRoot,
|
|
40605
40186
|
"read"
|
|
40606
40187
|
);
|
|
@@ -40611,7 +40192,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40611
40192
|
};
|
|
40612
40193
|
const list = async (frame, _client, ctx) => {
|
|
40613
40194
|
const args = HistoryListArgs.parse(frame);
|
|
40614
|
-
assertGuestPath(ctx,
|
|
40195
|
+
assertGuestPath(ctx, path41.resolve(args.projectPath), personaRoot, "history:list");
|
|
40615
40196
|
const sessions = await history.listSessions(args);
|
|
40616
40197
|
return { response: { type: "history:list", sessions } };
|
|
40617
40198
|
};
|
|
@@ -40620,7 +40201,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40620
40201
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
40621
40202
|
const f = file;
|
|
40622
40203
|
if (ctx && ctx.principal.kind === "guest") {
|
|
40623
|
-
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.
|
|
40204
|
+
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.capabilityId;
|
|
40624
40205
|
if (!ok) {
|
|
40625
40206
|
throw new ClawdError(
|
|
40626
40207
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -40643,13 +40224,13 @@ function buildHistoryHandlers(deps) {
|
|
|
40643
40224
|
};
|
|
40644
40225
|
const subagents = async (frame, _client, ctx) => {
|
|
40645
40226
|
const args = HistorySubagentsArgs.parse(frame);
|
|
40646
|
-
assertGuestPath(ctx,
|
|
40227
|
+
assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagents");
|
|
40647
40228
|
const subs = await history.listSubagents(args);
|
|
40648
40229
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
40649
40230
|
};
|
|
40650
40231
|
const subagentRead = async (frame, _client, ctx) => {
|
|
40651
40232
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
40652
|
-
assertGuestPath(ctx,
|
|
40233
|
+
assertGuestPath(ctx, path41.resolve(args.cwd), personaRoot, "history:subagent-read");
|
|
40653
40234
|
const res = await history.readSubagent(args);
|
|
40654
40235
|
return { response: { type: "history:subagent-read", ...res } };
|
|
40655
40236
|
};
|
|
@@ -40657,7 +40238,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40657
40238
|
const dirs = listRecentDirs(store);
|
|
40658
40239
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40659
40240
|
const filtered = dirs.filter(
|
|
40660
|
-
(d) => isGuestPathAllowed(ctx.grants,
|
|
40241
|
+
(d) => isGuestPathAllowed(ctx.grants, path41.resolve(d.cwd), personaRoot, "read")
|
|
40661
40242
|
);
|
|
40662
40243
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
40663
40244
|
}
|
|
@@ -40674,7 +40255,7 @@ function buildHistoryHandlers(deps) {
|
|
|
40674
40255
|
}
|
|
40675
40256
|
|
|
40676
40257
|
// src/handlers/workspace.ts
|
|
40677
|
-
var
|
|
40258
|
+
var path42 = __toESM(require("path"), 1);
|
|
40678
40259
|
var os15 = __toESM(require("os"), 1);
|
|
40679
40260
|
init_protocol();
|
|
40680
40261
|
init_protocol();
|
|
@@ -40715,22 +40296,22 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40715
40296
|
const args = WorkspaceListArgs.parse(frame);
|
|
40716
40297
|
const isGuest = ctx?.principal.kind === "guest";
|
|
40717
40298
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
|
|
40718
|
-
const resolvedCwd =
|
|
40719
|
-
const target = args.path ?
|
|
40299
|
+
const resolvedCwd = path42.resolve(args.cwd ?? fallbackCwd);
|
|
40300
|
+
const target = args.path ? path42.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
40720
40301
|
assertGuestPath2(ctx, target, personaRoot, "workspace:list");
|
|
40721
40302
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
40722
40303
|
return { response: { type: "workspace:list", ...res } };
|
|
40723
40304
|
};
|
|
40724
40305
|
const read = async (frame, _client, ctx) => {
|
|
40725
40306
|
const args = WorkspaceReadArgs.parse(frame);
|
|
40726
|
-
const target =
|
|
40307
|
+
const target = path42.isAbsolute(args.path) ? path42.resolve(args.path) : path42.resolve(args.cwd, args.path);
|
|
40727
40308
|
assertGuestPath2(ctx, target, personaRoot, "workspace:read");
|
|
40728
40309
|
const res = workspace.read(args);
|
|
40729
40310
|
return { response: { type: "workspace:read", ...res } };
|
|
40730
40311
|
};
|
|
40731
40312
|
const skillsList = async (frame, _client, ctx) => {
|
|
40732
40313
|
const args = SkillsListArgs.parse(frame);
|
|
40733
|
-
const cwdAbs =
|
|
40314
|
+
const cwdAbs = path42.resolve(args.cwd);
|
|
40734
40315
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
|
|
40735
40316
|
const list2 = skills.list(args);
|
|
40736
40317
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40742,7 +40323,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40742
40323
|
};
|
|
40743
40324
|
const agentsList = async (frame, _client, ctx) => {
|
|
40744
40325
|
const args = AgentsListArgs.parse(frame);
|
|
40745
|
-
const cwdAbs =
|
|
40326
|
+
const cwdAbs = path42.resolve(args.cwd);
|
|
40746
40327
|
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
|
|
40747
40328
|
const list2 = agents.list(args);
|
|
40748
40329
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
@@ -40761,7 +40342,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40761
40342
|
}
|
|
40762
40343
|
|
|
40763
40344
|
// src/handlers/git.ts
|
|
40764
|
-
var
|
|
40345
|
+
var path44 = __toESM(require("path"), 1);
|
|
40765
40346
|
init_protocol();
|
|
40766
40347
|
init_protocol();
|
|
40767
40348
|
|
|
@@ -40847,7 +40428,7 @@ async function listGitBranches(cwd) {
|
|
|
40847
40428
|
// src/handlers/git.ts
|
|
40848
40429
|
function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
40849
40430
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40850
|
-
if (!isGuestPathAllowed(ctx.grants,
|
|
40431
|
+
if (!isGuestPathAllowed(ctx.grants, path44.resolve(cwd), personaRoot, "read")) {
|
|
40851
40432
|
throw new ClawdError(
|
|
40852
40433
|
ERROR_CODES.UNAUTHORIZED,
|
|
40853
40434
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -40902,7 +40483,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40902
40483
|
capabilityId: external_exports.string().min(1)
|
|
40903
40484
|
}).strict();
|
|
40904
40485
|
function buildCapabilityHandlers(deps) {
|
|
40905
|
-
const { manager } = deps;
|
|
40486
|
+
const { manager, getShareBaseUrl, getPersonalCapability } = deps;
|
|
40906
40487
|
const list = async () => {
|
|
40907
40488
|
return {
|
|
40908
40489
|
response: {
|
|
@@ -40928,45 +40509,70 @@ function buildCapabilityHandlers(deps) {
|
|
|
40928
40509
|
}
|
|
40929
40510
|
};
|
|
40930
40511
|
};
|
|
40512
|
+
const personalCapGet = async () => {
|
|
40513
|
+
const { token, capability } = getPersonalCapability();
|
|
40514
|
+
return {
|
|
40515
|
+
response: {
|
|
40516
|
+
type: "personal-cap:get:ok",
|
|
40517
|
+
token,
|
|
40518
|
+
capability: stripSecretHash(capability),
|
|
40519
|
+
shareBaseUrl: getShareBaseUrl()
|
|
40520
|
+
}
|
|
40521
|
+
};
|
|
40522
|
+
};
|
|
40931
40523
|
return {
|
|
40932
40524
|
"capability:list": list,
|
|
40933
|
-
"capability:delete": del
|
|
40525
|
+
"capability:delete": del,
|
|
40526
|
+
"personal-cap:get": personalCapGet
|
|
40934
40527
|
};
|
|
40935
40528
|
}
|
|
40936
40529
|
|
|
40937
40530
|
// src/handlers/inbox.ts
|
|
40938
40531
|
init_protocol();
|
|
40939
|
-
function
|
|
40532
|
+
function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
40940
40533
|
if (ctx.principal.kind === "owner") {
|
|
40941
|
-
if (!
|
|
40534
|
+
if (!argsPeerOwnerId) {
|
|
40942
40535
|
throw new ClawdError(
|
|
40943
40536
|
ERROR_CODES.VALIDATION_ERROR,
|
|
40944
|
-
"inbox: owner ctx requires
|
|
40537
|
+
"inbox: owner ctx requires peerOwnerId"
|
|
40945
40538
|
);
|
|
40946
40539
|
}
|
|
40947
|
-
return
|
|
40540
|
+
return argsPeerOwnerId;
|
|
40541
|
+
}
|
|
40542
|
+
if (!ctx.capabilityId) {
|
|
40543
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
40544
|
+
}
|
|
40545
|
+
let resolved = ctx.peerOwnerPrincipalId;
|
|
40546
|
+
if (!resolved) {
|
|
40547
|
+
const cap = capManager.findById(ctx.capabilityId);
|
|
40548
|
+
resolved = cap?.peerOwnerId;
|
|
40549
|
+
}
|
|
40550
|
+
if (!resolved) {
|
|
40551
|
+
throw new ClawdError(
|
|
40552
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
40553
|
+
`inbox: guest cap ${ctx.capabilityId} has no peerOwnerPrincipalId (auth frame missing selfPrincipalId)`
|
|
40554
|
+
);
|
|
40948
40555
|
}
|
|
40949
|
-
|
|
40950
|
-
if (argsPeerDeviceId && argsPeerDeviceId !== resolved) {
|
|
40556
|
+
if (argsPeerOwnerId && argsPeerOwnerId !== resolved) {
|
|
40951
40557
|
throw new ClawdError(
|
|
40952
40558
|
ERROR_CODES.UNAUTHORIZED,
|
|
40953
|
-
`inbox: guest args.
|
|
40559
|
+
`inbox: guest args.peerOwnerId mismatch (resolved=${resolved}, args=${argsPeerOwnerId})`
|
|
40954
40560
|
);
|
|
40955
40561
|
}
|
|
40956
40562
|
return resolved;
|
|
40957
40563
|
}
|
|
40958
40564
|
function buildInboxHandlers(deps) {
|
|
40959
|
-
const { manager } = deps;
|
|
40565
|
+
const { manager, capManager } = deps;
|
|
40960
40566
|
const postMessage = async (frame, _client, ctx) => {
|
|
40961
40567
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40962
40568
|
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
40963
40569
|
if (!ctx) {
|
|
40964
40570
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40965
40571
|
}
|
|
40966
|
-
const
|
|
40572
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40967
40573
|
const message = manager.postMessage({
|
|
40968
|
-
|
|
40969
|
-
|
|
40574
|
+
peerOwnerId,
|
|
40575
|
+
senderPrincipalId: ctx.principal.id,
|
|
40970
40576
|
text: args.text,
|
|
40971
40577
|
id: args.id,
|
|
40972
40578
|
createdAt: args.createdAt
|
|
@@ -40981,10 +40587,10 @@ function buildInboxHandlers(deps) {
|
|
|
40981
40587
|
if (!ctx) {
|
|
40982
40588
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40983
40589
|
}
|
|
40984
|
-
const
|
|
40985
|
-
const messages = manager.list(
|
|
40590
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40591
|
+
const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
|
|
40986
40592
|
return {
|
|
40987
|
-
response: { type: "inbox:list:ok",
|
|
40593
|
+
response: { type: "inbox:list:ok", peerOwnerId, messages }
|
|
40988
40594
|
};
|
|
40989
40595
|
};
|
|
40990
40596
|
const markRead = async (frame, _client, ctx) => {
|
|
@@ -40993,16 +40599,16 @@ function buildInboxHandlers(deps) {
|
|
|
40993
40599
|
if (!ctx) {
|
|
40994
40600
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40995
40601
|
}
|
|
40996
|
-
const
|
|
40602
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
40997
40603
|
const updated = manager.markRead({
|
|
40998
|
-
|
|
40999
|
-
|
|
40604
|
+
peerOwnerId,
|
|
40605
|
+
principalId: ctx.principal.id,
|
|
41000
40606
|
upToCreatedAt: args.upToCreatedAt
|
|
41001
40607
|
});
|
|
41002
40608
|
return {
|
|
41003
40609
|
response: {
|
|
41004
40610
|
type: "inbox:markRead:ok",
|
|
41005
|
-
|
|
40611
|
+
peerOwnerId,
|
|
41006
40612
|
upToCreatedAt: args.upToCreatedAt,
|
|
41007
40613
|
updated
|
|
41008
40614
|
}
|
|
@@ -41015,45 +40621,116 @@ function buildInboxHandlers(deps) {
|
|
|
41015
40621
|
};
|
|
41016
40622
|
}
|
|
41017
40623
|
|
|
41018
|
-
// src/handlers/
|
|
40624
|
+
// src/handlers/received-capability.ts
|
|
41019
40625
|
init_protocol();
|
|
41020
40626
|
function ensureOwner(ctx) {
|
|
41021
40627
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
41022
40628
|
throw new ClawdError(
|
|
41023
40629
|
ERROR_CODES.UNAUTHORIZED,
|
|
41024
|
-
"UNAUTHORIZED:
|
|
40630
|
+
"UNAUTHORIZED: received-capability:* requires owner ctx"
|
|
40631
|
+
);
|
|
40632
|
+
}
|
|
40633
|
+
}
|
|
40634
|
+
function ensureGuestCtx(ctx) {
|
|
40635
|
+
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40636
|
+
throw new ClawdError(
|
|
40637
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
40638
|
+
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
41025
40639
|
);
|
|
41026
40640
|
}
|
|
41027
40641
|
}
|
|
41028
|
-
function
|
|
40642
|
+
function buildReceivedCapabilityHandlers(deps) {
|
|
40643
|
+
const now = deps.now ?? Date.now;
|
|
41029
40644
|
const list = async (_frame, _client, ctx) => {
|
|
41030
40645
|
ensureOwner(ctx);
|
|
41031
40646
|
return {
|
|
41032
40647
|
response: {
|
|
41033
|
-
type: "
|
|
41034
|
-
|
|
40648
|
+
type: "received-capability:list:ok",
|
|
40649
|
+
receivedCaps: deps.store.list()
|
|
41035
40650
|
}
|
|
41036
40651
|
};
|
|
41037
40652
|
};
|
|
40653
|
+
const add = async (frame, _client, ctx) => {
|
|
40654
|
+
ensureOwner(ctx);
|
|
40655
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40656
|
+
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40657
|
+
let conn;
|
|
40658
|
+
try {
|
|
40659
|
+
conn = await deps.connectRemote({
|
|
40660
|
+
url: args.remoteUrl,
|
|
40661
|
+
token: args.token,
|
|
40662
|
+
selfPrincipalId: deps.selfPrincipalId(),
|
|
40663
|
+
selfDisplayName: deps.selfDisplayName()
|
|
40664
|
+
});
|
|
40665
|
+
} catch (e) {
|
|
40666
|
+
throw new ClawdError(
|
|
40667
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
40668
|
+
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40669
|
+
);
|
|
40670
|
+
}
|
|
40671
|
+
try {
|
|
40672
|
+
const wh = await conn.whoami();
|
|
40673
|
+
const rc = {
|
|
40674
|
+
ownerPrincipalId: wh.ownerId,
|
|
40675
|
+
ownerDisplayName: wh.displayName,
|
|
40676
|
+
remoteUrl: args.remoteUrl,
|
|
40677
|
+
capabilityId: wh.capabilityId,
|
|
40678
|
+
capabilityToken: args.token,
|
|
40679
|
+
grants: wh.grants,
|
|
40680
|
+
receivedAt: now()
|
|
40681
|
+
};
|
|
40682
|
+
deps.store.upsert(rc);
|
|
40683
|
+
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40684
|
+
return {
|
|
40685
|
+
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40686
|
+
};
|
|
40687
|
+
} finally {
|
|
40688
|
+
try {
|
|
40689
|
+
conn.close();
|
|
40690
|
+
} catch {
|
|
40691
|
+
}
|
|
40692
|
+
}
|
|
40693
|
+
};
|
|
41038
40694
|
const remove = async (frame, _client, ctx) => {
|
|
41039
40695
|
ensureOwner(ctx);
|
|
41040
40696
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
41041
|
-
const args =
|
|
41042
|
-
deps.store.
|
|
40697
|
+
const args = ReceivedCapabilityRemoveArgsSchema.parse(rest);
|
|
40698
|
+
deps.store.remove(args.ownerPrincipalId);
|
|
41043
40699
|
deps.broadcast({
|
|
41044
|
-
type: "
|
|
41045
|
-
|
|
40700
|
+
type: "received-capability:removed",
|
|
40701
|
+
ownerPrincipalId: args.ownerPrincipalId
|
|
41046
40702
|
});
|
|
41047
40703
|
return {
|
|
41048
40704
|
response: {
|
|
41049
|
-
type: "
|
|
41050
|
-
|
|
40705
|
+
type: "received-capability:remove:ok",
|
|
40706
|
+
ownerPrincipalId: args.ownerPrincipalId
|
|
41051
40707
|
}
|
|
41052
40708
|
};
|
|
41053
40709
|
};
|
|
40710
|
+
const autoAttach = async (frame, _client, ctx) => {
|
|
40711
|
+
ensureGuestCtx(ctx);
|
|
40712
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40713
|
+
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40714
|
+
const rc = {
|
|
40715
|
+
ownerPrincipalId: args.ownerPrincipalId,
|
|
40716
|
+
ownerDisplayName: args.ownerDisplayName,
|
|
40717
|
+
remoteUrl: args.remoteUrl,
|
|
40718
|
+
capabilityId: args.capabilityId,
|
|
40719
|
+
capabilityToken: args.token,
|
|
40720
|
+
grants: args.grants,
|
|
40721
|
+
receivedAt: now()
|
|
40722
|
+
};
|
|
40723
|
+
deps.store.upsert(rc);
|
|
40724
|
+
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40725
|
+
return {
|
|
40726
|
+
response: { type: "received-capability:autoAttach:ok" }
|
|
40727
|
+
};
|
|
40728
|
+
};
|
|
41054
40729
|
return {
|
|
41055
|
-
"
|
|
41056
|
-
"
|
|
40730
|
+
"received-capability:list": list,
|
|
40731
|
+
"received-capability:add": add,
|
|
40732
|
+
"received-capability:remove": remove,
|
|
40733
|
+
"received-capability:autoAttach": autoAttach
|
|
41057
40734
|
};
|
|
41058
40735
|
}
|
|
41059
40736
|
|
|
@@ -41064,11 +40741,7 @@ function buildWhoamiHandler(deps) {
|
|
|
41064
40741
|
if (!ctx) {
|
|
41065
40742
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
41066
40743
|
}
|
|
41067
|
-
const owner =
|
|
41068
|
-
...makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName),
|
|
41069
|
-
ownerId: deps.ownerId,
|
|
41070
|
-
provider: deps.ownerProvider
|
|
41071
|
-
};
|
|
40744
|
+
const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
|
|
41072
40745
|
let capability;
|
|
41073
40746
|
if (ctx.principal.kind === "owner") {
|
|
41074
40747
|
capability = {
|
|
@@ -41079,13 +40752,14 @@ function buildWhoamiHandler(deps) {
|
|
|
41079
40752
|
usedCount: 0
|
|
41080
40753
|
};
|
|
41081
40754
|
} else {
|
|
41082
|
-
|
|
41083
|
-
|
|
41084
|
-
|
|
41085
|
-
|
|
41086
|
-
|
|
41087
|
-
|
|
41088
|
-
}
|
|
40755
|
+
if (!ctx.capabilityId) {
|
|
40756
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
|
|
40757
|
+
}
|
|
40758
|
+
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
40759
|
+
if (!cap) {
|
|
40760
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
|
|
40761
|
+
}
|
|
40762
|
+
capability = stripSecretHash(cap);
|
|
41089
40763
|
}
|
|
41090
40764
|
const grantedPersonas = [];
|
|
41091
40765
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -41116,123 +40790,6 @@ function buildWhoamiHandler(deps) {
|
|
|
41116
40790
|
};
|
|
41117
40791
|
}
|
|
41118
40792
|
|
|
41119
|
-
// src/handlers/feishu-auth.ts
|
|
41120
|
-
function buildFeishuAuthHandlers(deps) {
|
|
41121
|
-
const loginStart = async () => {
|
|
41122
|
-
const { authUrl, state } = deps.loginFlow.start();
|
|
41123
|
-
return {
|
|
41124
|
-
response: { type: "auth:login:start:ok", authUrl, state }
|
|
41125
|
-
};
|
|
41126
|
-
};
|
|
41127
|
-
const getIdentity = async () => {
|
|
41128
|
-
const record = deps.ownerIdentityStore.read();
|
|
41129
|
-
return {
|
|
41130
|
-
response: {
|
|
41131
|
-
type: "auth:getIdentity:ok",
|
|
41132
|
-
identity: record ? record.identity : null,
|
|
41133
|
-
// 决策 #15:设备属性,与登录态无关——UI"分享我的 Hub 标识"用
|
|
41134
|
-
deviceId: deps.deviceId
|
|
41135
|
-
}
|
|
41136
|
-
};
|
|
41137
|
-
};
|
|
41138
|
-
const logout = async () => {
|
|
41139
|
-
deps.ownerIdentityStore.clear();
|
|
41140
|
-
return {
|
|
41141
|
-
response: { type: "auth:logout:ok" }
|
|
41142
|
-
};
|
|
41143
|
-
};
|
|
41144
|
-
return {
|
|
41145
|
-
"auth:login:start": loginStart,
|
|
41146
|
-
"auth:getIdentity": getIdentity,
|
|
41147
|
-
"auth:logout": logout
|
|
41148
|
-
};
|
|
41149
|
-
}
|
|
41150
|
-
|
|
41151
|
-
// src/handlers/device.ts
|
|
41152
|
-
init_protocol();
|
|
41153
|
-
function ensureOwner2(ctx) {
|
|
41154
|
-
if (!ctx || ctx.principal.kind !== "owner") {
|
|
41155
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: device:* requires owner ctx");
|
|
41156
|
-
}
|
|
41157
|
-
}
|
|
41158
|
-
function buildDeviceHandlers(deps) {
|
|
41159
|
-
const now = deps.now ?? Date.now;
|
|
41160
|
-
const list = async (_frame, _client, ctx) => {
|
|
41161
|
-
ensureOwner2(ctx);
|
|
41162
|
-
const devices = await deps.listDevices();
|
|
41163
|
-
return {
|
|
41164
|
-
response: { type: "device:list:ok", devices }
|
|
41165
|
-
};
|
|
41166
|
-
};
|
|
41167
|
-
const connect = async (frame, _client, ctx) => {
|
|
41168
|
-
ensureOwner2(ctx);
|
|
41169
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
41170
|
-
const args = DeviceConnectArgsSchema.parse(rest);
|
|
41171
|
-
const exchanged = await deps.exchange(args.deviceId);
|
|
41172
|
-
const url = args.url ?? exchanged.deviceUrl;
|
|
41173
|
-
if (!url) {
|
|
41174
|
-
throw new ClawdError(
|
|
41175
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
41176
|
-
"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"
|
|
41177
|
-
);
|
|
41178
|
-
}
|
|
41179
|
-
let conn;
|
|
41180
|
-
try {
|
|
41181
|
-
conn = await deps.connectRemote({
|
|
41182
|
-
url,
|
|
41183
|
-
token: exchanged.token,
|
|
41184
|
-
// 自动反向(spec 决策 #11):自报本机可达地址,让对方反向加我为联系人;
|
|
41185
|
-
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)。
|
|
41186
|
-
// 身份不自报:对方从 token 签名背书取我的 deviceId/ownerId/provider(决策 #16)。
|
|
41187
|
-
selfUrl: deps.selfUrl() ?? void 0
|
|
41188
|
-
});
|
|
41189
|
-
} catch (e) {
|
|
41190
|
-
throw new ClawdError(
|
|
41191
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
41192
|
-
`DEVICE_UNREACHABLE: ${e.message}`
|
|
41193
|
-
);
|
|
41194
|
-
}
|
|
41195
|
-
try {
|
|
41196
|
-
const wh = await conn.whoami();
|
|
41197
|
-
const contact = {
|
|
41198
|
-
deviceId: args.deviceId,
|
|
41199
|
-
ownerId: wh.ownerId,
|
|
41200
|
-
provider: wh.provider,
|
|
41201
|
-
// 显示名优先用调用方传的 name:公共目录点击连接带 clawd_devices.name(对外展示名,
|
|
41202
|
-
// 如"公共1");"输入标识"连接不带 name → 回落 whoami 报的对方 owner 真名。
|
|
41203
|
-
// 归属人身份不丢(ownerId/provider 仍来自 whoami 背书)。
|
|
41204
|
-
displayName: args.name || wh.displayName || args.deviceId,
|
|
41205
|
-
remoteUrl: url,
|
|
41206
|
-
// connectToken 存自包含签名 token(断线重连复用,30 天有效;
|
|
41207
|
-
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
41208
|
-
connectToken: exchanged.token,
|
|
41209
|
-
grants: wh.grants,
|
|
41210
|
-
addedAt: now()
|
|
41211
|
-
};
|
|
41212
|
-
deps.store.upsert(contact);
|
|
41213
|
-
deps.broadcast({ type: "contact:added", contact });
|
|
41214
|
-
return {
|
|
41215
|
-
response: {
|
|
41216
|
-
type: "device:connect:ok",
|
|
41217
|
-
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
41218
|
-
deviceId: args.deviceId,
|
|
41219
|
-
name: contact.displayName,
|
|
41220
|
-
url
|
|
41221
|
-
}
|
|
41222
|
-
};
|
|
41223
|
-
} finally {
|
|
41224
|
-
try {
|
|
41225
|
-
conn.close();
|
|
41226
|
-
} catch {
|
|
41227
|
-
}
|
|
41228
|
-
}
|
|
41229
|
-
};
|
|
41230
|
-
return {
|
|
41231
|
-
"device:list": list,
|
|
41232
|
-
"device:connect": connect
|
|
41233
|
-
};
|
|
41234
|
-
}
|
|
41235
|
-
|
|
41236
40793
|
// src/handlers/meta.ts
|
|
41237
40794
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
41238
40795
|
init_protocol();
|
|
@@ -43108,25 +42665,27 @@ function buildMethodHandlers(deps) {
|
|
|
43108
42665
|
personaRegistry: deps.personaRegistry
|
|
43109
42666
|
}),
|
|
43110
42667
|
...buildCapabilityHandlers({
|
|
43111
|
-
manager: deps.capabilityManager
|
|
42668
|
+
manager: deps.capabilityManager,
|
|
42669
|
+
getShareBaseUrl: deps.getShareBaseUrl,
|
|
42670
|
+
getPersonalCapability: deps.getPersonalCapability
|
|
43112
42671
|
}),
|
|
43113
42672
|
...buildInboxHandlers({
|
|
43114
|
-
manager: deps.inboxManager
|
|
42673
|
+
manager: deps.inboxManager,
|
|
42674
|
+
capManager: deps.capabilityManager
|
|
43115
42675
|
}),
|
|
43116
|
-
...
|
|
43117
|
-
store: deps.
|
|
43118
|
-
|
|
42676
|
+
...buildReceivedCapabilityHandlers({
|
|
42677
|
+
store: deps.receivedCapabilityStore,
|
|
42678
|
+
connectRemote: deps.connectRemote,
|
|
42679
|
+
broadcast: deps.broadcastToOwners,
|
|
42680
|
+
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
42681
|
+
selfDisplayName: () => deps.ownerDisplayName
|
|
43119
42682
|
}),
|
|
43120
42683
|
whoami: buildWhoamiHandler({
|
|
43121
42684
|
ownerDisplayName: deps.ownerDisplayName,
|
|
43122
42685
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
43123
|
-
ownerId: deps.ownerId,
|
|
43124
|
-
ownerProvider: deps.ownerProvider,
|
|
43125
42686
|
personaStore: deps.personaStore,
|
|
43126
42687
|
capabilityManager: deps.capabilityManager
|
|
43127
42688
|
}),
|
|
43128
|
-
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
43129
|
-
...buildDeviceHandlers(deps.feishuDevice),
|
|
43130
42689
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
43131
42690
|
...buildExtensionHandlers({
|
|
43132
42691
|
loadAll,
|
|
@@ -43628,22 +43187,23 @@ var METHOD_GRANT_MAP = {
|
|
|
43628
43187
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
43629
43188
|
"whoami": { kind: "public" },
|
|
43630
43189
|
// ---- capability platform(admin-only) ----
|
|
43631
|
-
//
|
|
43632
|
-
//
|
|
43633
|
-
// 撤销级联 + 清旧 per-peer cap。
|
|
43190
|
+
// global personal token (2026-05-25): capability:issue / bindPeer 已下线;
|
|
43191
|
+
// personal capability 由 daemon 启动时自动创建,personal-cap:get 是 owner-only 拿 token + shareBaseUrl
|
|
43634
43192
|
"capability:list": ADMIN_ANY,
|
|
43635
43193
|
"capability:delete": ADMIN_ANY,
|
|
43636
|
-
|
|
43637
|
-
//
|
|
43638
|
-
//
|
|
43194
|
+
"personal-cap:get": ADMIN_ANY,
|
|
43195
|
+
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
43196
|
+
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
43197
|
+
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
43639
43198
|
"inbox:list": { kind: "public" },
|
|
43640
43199
|
"inbox:markRead": { kind: "public" },
|
|
43641
43200
|
"inbox:postMessage": { kind: "public" },
|
|
43642
|
-
// ----
|
|
43643
|
-
//
|
|
43644
|
-
|
|
43645
|
-
"
|
|
43646
|
-
"
|
|
43201
|
+
// ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
|
|
43202
|
+
// owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
|
|
43203
|
+
"received-capability:list": ADMIN_ANY,
|
|
43204
|
+
"received-capability:add": ADMIN_ANY,
|
|
43205
|
+
"received-capability:remove": ADMIN_ANY,
|
|
43206
|
+
"received-capability:autoAttach": { kind: "public" },
|
|
43647
43207
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
43648
43208
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
43649
43209
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -43684,7 +43244,7 @@ var METHOD_GRANT_MAP = {
|
|
|
43684
43244
|
"history:list": CAPABILITY_SCOPED,
|
|
43685
43245
|
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
43686
43246
|
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
43687
|
-
// handler 内按 ctx.
|
|
43247
|
+
// handler 内按 ctx.capabilityId === file.creatorPrincipalId 拦越权。
|
|
43688
43248
|
"history:read": CAPABILITY_SCOPED,
|
|
43689
43249
|
"history:subagents": CAPABILITY_SCOPED,
|
|
43690
43250
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
@@ -43741,15 +43301,6 @@ var METHOD_GRANT_MAP = {
|
|
|
43741
43301
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
43742
43302
|
"extension.install-from-channel": ADMIN_ANY,
|
|
43743
43303
|
"extension.update-from-channel": ADMIN_ANY,
|
|
43744
|
-
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
43745
|
-
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
43746
|
-
"auth:login:start": ADMIN_ANY,
|
|
43747
|
-
"auth:getIdentity": ADMIN_ANY,
|
|
43748
|
-
"auth:logout": ADMIN_ANY,
|
|
43749
|
-
// ---- device:* 设备目录 + 连接(决策 #15,owner-only) ----
|
|
43750
|
-
// 设备目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
43751
|
-
"device:list": ADMIN_ANY,
|
|
43752
|
-
"device:connect": ADMIN_ANY,
|
|
43753
43304
|
// ---- app-builder Project picker(spec 2026-06-01-app-builder-project-picker-design) ----
|
|
43754
43305
|
// owner-only:picker UI 给 owner 管理本机 build 中的 project(fs + 端口分配)。
|
|
43755
43306
|
// 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
|
|
@@ -44160,23 +43711,19 @@ async function startDaemon(config) {
|
|
|
44160
43711
|
} else if (config.tunnel) {
|
|
44161
43712
|
resolvedAuthToken = authFile.token;
|
|
44162
43713
|
}
|
|
44163
|
-
const
|
|
44164
|
-
const
|
|
44165
|
-
let feishuActive = feishuIdentity !== null;
|
|
44166
|
-
const ownerPrincipalId = authFile.deviceId;
|
|
44167
|
-
let ownerId = feishuIdentity?.identity.ownerId ?? "";
|
|
44168
|
-
let ownerProvider = feishuIdentity?.identity.provider ?? "";
|
|
44169
|
-
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
43714
|
+
const ownerPrincipalId = authFile.ownerPrincipalId;
|
|
43715
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
44170
43716
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
44171
43717
|
const inboxStore = new InboxStore(config.dataDir);
|
|
44172
|
-
const inboxManager = new InboxManager(inboxStore, (
|
|
43718
|
+
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
44173
43719
|
wsServer?.broadcastToOwners(frame);
|
|
44174
43720
|
});
|
|
44175
|
-
const
|
|
44176
|
-
|
|
44177
|
-
const serverKeyStore = new ServerKeyStore(config.dataDir);
|
|
43721
|
+
const receivedCapabilityStore = new ReceivedCapabilityStore(config.dataDir);
|
|
43722
|
+
receivedCapabilityStore.load();
|
|
44178
43723
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
44179
43724
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
43725
|
+
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
43726
|
+
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
44180
43727
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
44181
43728
|
onDeleted: (cap) => {
|
|
44182
43729
|
const deletedAt = Date.now();
|
|
@@ -44185,7 +43732,7 @@ async function startDaemon(config) {
|
|
|
44185
43732
|
capabilityId: cap.id,
|
|
44186
43733
|
deletedAt
|
|
44187
43734
|
});
|
|
44188
|
-
wsServer?.
|
|
43735
|
+
wsServer?.closeConnectionsByCapability(cap.id);
|
|
44189
43736
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
44190
43737
|
if (cleanup.removed.length > 0) {
|
|
44191
43738
|
logger.info("capability delete cascade: guest sessions removed", {
|
|
@@ -44202,60 +43749,15 @@ async function startDaemon(config) {
|
|
|
44202
43749
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
44203
43750
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
44204
43751
|
expectedToken: resolvedAuthToken,
|
|
44205
|
-
authenticate:
|
|
44206
|
-
|
|
43752
|
+
authenticate: (t, selfPrincipalId, selfDisplayName) => {
|
|
43753
|
+
return authenticate(t, {
|
|
44207
43754
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
44208
43755
|
ownerPrincipalId,
|
|
44209
43756
|
ownerDisplayName,
|
|
44210
|
-
|
|
44211
|
-
|
|
44212
|
-
|
|
44213
|
-
...feishuActive ? {
|
|
44214
|
-
verifyConnectToken: (token) => {
|
|
44215
|
-
const publicKeyPem = serverKeyStore.read();
|
|
44216
|
-
if (!publicKeyPem) return { ok: false, reason: "BAD_SIGNATURE" };
|
|
44217
|
-
return verifyConnectToken({
|
|
44218
|
-
token,
|
|
44219
|
-
publicKeyPem,
|
|
44220
|
-
expectedDeviceId: authFile.deviceId
|
|
44221
|
-
});
|
|
44222
|
-
}
|
|
44223
|
-
} : {}
|
|
43757
|
+
capabilityRegistry,
|
|
43758
|
+
selfPrincipalId,
|
|
43759
|
+
selfDisplayName
|
|
44224
43760
|
});
|
|
44225
|
-
if (result.ok && result.context.principal.kind === "guest") {
|
|
44226
|
-
void autoReverseContact({
|
|
44227
|
-
store: contactStore,
|
|
44228
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
44229
|
-
deviceId: result.context.principal.id,
|
|
44230
|
-
ownerId: result.context.ownerId ?? "",
|
|
44231
|
-
provider: result.context.provider ?? "",
|
|
44232
|
-
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
44233
|
-
selfUrl,
|
|
44234
|
-
// 决策 #16 换票补全:用 owner 当前 jwt 换 aud=对方设备 的票(本机主动连对方用)。
|
|
44235
|
-
// 现读 ownerIdentityStore(热切换后用新身份);失败 → null → 保持空票。
|
|
44236
|
-
exchangeToken: async (targetDeviceId) => {
|
|
44237
|
-
const record = ownerIdentityStore.read();
|
|
44238
|
-
if (!record) return null;
|
|
44239
|
-
try {
|
|
44240
|
-
const r = await centralExchange({
|
|
44241
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
44242
|
-
jwt: record.ttcToken,
|
|
44243
|
-
provider: record.identity.provider,
|
|
44244
|
-
deviceId: targetDeviceId,
|
|
44245
|
-
selfDeviceId: authFile.deviceId
|
|
44246
|
-
});
|
|
44247
|
-
return r.token;
|
|
44248
|
-
} catch (e) {
|
|
44249
|
-
logger.warn("auto-reverse exchange token failed", {
|
|
44250
|
-
targetDeviceId,
|
|
44251
|
-
err: e.message
|
|
44252
|
-
});
|
|
44253
|
-
return null;
|
|
44254
|
-
}
|
|
44255
|
-
}
|
|
44256
|
-
});
|
|
44257
|
-
}
|
|
44258
|
-
return result;
|
|
44259
43761
|
},
|
|
44260
43762
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
44261
43763
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -44411,52 +43913,10 @@ async function startDaemon(config) {
|
|
|
44411
43913
|
}
|
|
44412
43914
|
return `http://${config.host}:${config.port}`;
|
|
44413
43915
|
};
|
|
44414
|
-
const reportDevice = async () => {
|
|
44415
|
-
if (!feishuActive) return;
|
|
44416
|
-
const record = ownerIdentityStore.read();
|
|
44417
|
-
if (!record) return;
|
|
44418
|
-
try {
|
|
44419
|
-
await centralUpsertDevice({
|
|
44420
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
44421
|
-
ttcJwt: record.ttcToken,
|
|
44422
|
-
provider: record.identity.provider,
|
|
44423
|
-
deviceId: authFile.deviceId,
|
|
44424
|
-
url: currentTunnelUrl ?? void 0,
|
|
44425
|
-
name: ownerDisplayName
|
|
44426
|
-
});
|
|
44427
|
-
logger.info("device registered to central server", {
|
|
44428
|
-
deviceId: authFile.deviceId,
|
|
44429
|
-
url: currentTunnelUrl ?? "(none)"
|
|
44430
|
-
});
|
|
44431
|
-
} catch (err) {
|
|
44432
|
-
logger.warn("device register failed (best-effort)", { err: err.message });
|
|
44433
|
-
}
|
|
44434
|
-
};
|
|
44435
|
-
const fetchServerKey = async () => {
|
|
44436
|
-
if (!feishuActive) return;
|
|
44437
|
-
if (serverKeyStore.read()) return;
|
|
44438
|
-
try {
|
|
44439
|
-
const { publicKeyPem } = await centralGetPublicKey({
|
|
44440
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API
|
|
44441
|
-
});
|
|
44442
|
-
serverKeyStore.write(publicKeyPem);
|
|
44443
|
-
logger.info("server signing key cached");
|
|
44444
|
-
} catch (err) {
|
|
44445
|
-
logger.warn("server signing key fetch failed (guest \u5165\u7AD9\u5C06\u88AB\u62D2\u7EDD)", {
|
|
44446
|
-
err: err.message
|
|
44447
|
-
});
|
|
44448
|
-
}
|
|
44449
|
-
};
|
|
44450
43916
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
44451
43917
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
44452
43918
|
await publishedChannelStore.load();
|
|
44453
43919
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
44454
|
-
const loginFlow = new LoginFlow({
|
|
44455
|
-
store: ownerIdentityStore,
|
|
44456
|
-
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
44457
|
-
ttcAuthBase: TTC_HOSTS.auth,
|
|
44458
|
-
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
44459
|
-
});
|
|
44460
43920
|
const appBuilderStore = new ProjectStore(
|
|
44461
43921
|
import_node_path47.default.join(config.dataDir, "personas/persona-app-builder")
|
|
44462
43922
|
);
|
|
@@ -44472,7 +43932,7 @@ async function startDaemon(config) {
|
|
|
44472
43932
|
const effectivePreviewPorts = Array.from(
|
|
44473
43933
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
44474
43934
|
);
|
|
44475
|
-
const
|
|
43935
|
+
const handlers = buildMethodHandlers({
|
|
44476
43936
|
manager,
|
|
44477
43937
|
workspace,
|
|
44478
43938
|
skills,
|
|
@@ -44517,12 +43977,15 @@ async function startDaemon(config) {
|
|
|
44517
43977
|
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
44518
43978
|
// capability:list / delete handler 依赖
|
|
44519
43979
|
capabilityManager,
|
|
43980
|
+
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
43981
|
+
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
43982
|
+
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
43983
|
+
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
43984
|
+
getPersonalCapability: () => personalCapability,
|
|
44520
43985
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
44521
43986
|
ownerDisplayName,
|
|
44522
|
-
//
|
|
43987
|
+
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
44523
43988
|
ownerPrincipalId,
|
|
44524
|
-
ownerId,
|
|
44525
|
-
ownerProvider,
|
|
44526
43989
|
personaStore,
|
|
44527
43990
|
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
44528
43991
|
capabilityRegistry,
|
|
@@ -44530,56 +43993,18 @@ async function startDaemon(config) {
|
|
|
44530
43993
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
44531
43994
|
inboxManager,
|
|
44532
43995
|
inboxStore,
|
|
44533
|
-
//
|
|
44534
|
-
|
|
44535
|
-
//
|
|
43996
|
+
// bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
|
|
43997
|
+
receivedCapabilityStore,
|
|
43998
|
+
// received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
|
|
44536
43999
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
44000
|
+
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
44001
|
+
connectRemote,
|
|
44537
44002
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
44538
44003
|
// level so stopAll() runs in the shutdown hook below.
|
|
44539
44004
|
extensionRuntime,
|
|
44540
44005
|
// extension sharing v1 — owner-side published-channels store.
|
|
44541
44006
|
publishedChannelStore,
|
|
44542
44007
|
bundleCache,
|
|
44543
|
-
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
44544
|
-
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
44545
|
-
feishuAuth: { loginFlow, ownerIdentityStore, deviceId: authFile.deviceId },
|
|
44546
|
-
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
44547
|
-
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
44548
|
-
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
44549
|
-
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
44550
|
-
feishuDevice: {
|
|
44551
|
-
store: contactStore,
|
|
44552
|
-
exchange: async (deviceId) => {
|
|
44553
|
-
const record = ownerIdentityStore.read();
|
|
44554
|
-
if (!record) {
|
|
44555
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
44556
|
-
}
|
|
44557
|
-
return centralExchange({
|
|
44558
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
44559
|
-
jwt: record.ttcToken,
|
|
44560
|
-
provider: record.identity.provider,
|
|
44561
|
-
deviceId,
|
|
44562
|
-
selfDeviceId: authFile.deviceId
|
|
44563
|
-
});
|
|
44564
|
-
},
|
|
44565
|
-
listDevices: async () => {
|
|
44566
|
-
const record = ownerIdentityStore.read();
|
|
44567
|
-
if (!record) {
|
|
44568
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
44569
|
-
}
|
|
44570
|
-
return centralListDevices({
|
|
44571
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
44572
|
-
ttcJwt: record.ttcToken,
|
|
44573
|
-
provider: record.identity.provider
|
|
44574
|
-
});
|
|
44575
|
-
},
|
|
44576
|
-
connectRemote,
|
|
44577
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
44578
|
-
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
44579
|
-
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
44580
|
-
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
44581
|
-
selfUrl: () => currentTunnelUrl
|
|
44582
|
-
},
|
|
44583
44008
|
// app-builder
|
|
44584
44009
|
appBuilderStore,
|
|
44585
44010
|
devServerLifecycle: devServerSupervisor,
|
|
@@ -44609,7 +44034,6 @@ async function startDaemon(config) {
|
|
|
44609
44034
|
},
|
|
44610
44035
|
logger
|
|
44611
44036
|
});
|
|
44612
|
-
let handlers = makeHandlers();
|
|
44613
44037
|
const authResolver = new AuthContextResolver({
|
|
44614
44038
|
ownerToken: resolvedAuthToken
|
|
44615
44039
|
});
|
|
@@ -44647,31 +44071,6 @@ async function startDaemon(config) {
|
|
|
44647
44071
|
},
|
|
44648
44072
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
44649
44073
|
extensionsRoot: () => extensionsRoot(),
|
|
44650
|
-
// 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
|
|
44651
|
-
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
44652
|
-
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
44653
|
-
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
44654
|
-
// People 立即解锁,不需要重启 daemon
|
|
44655
|
-
// 失败则广播 auth:login:failed。
|
|
44656
|
-
feishuLogin: {
|
|
44657
|
-
handleLoginCallback: async (params) => {
|
|
44658
|
-
const result = await loginFlow.handleCallback(params);
|
|
44659
|
-
if (result.ok) {
|
|
44660
|
-
feishuActive = true;
|
|
44661
|
-
ownerId = result.identity.ownerId;
|
|
44662
|
-
ownerProvider = result.identity.provider;
|
|
44663
|
-
ownerDisplayName = result.identity.displayName;
|
|
44664
|
-
handlers = makeHandlers();
|
|
44665
|
-
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
44666
|
-
setImmediate(() => wsServer?.closeAllClients());
|
|
44667
|
-
void reportDevice();
|
|
44668
|
-
void fetchServerKey();
|
|
44669
|
-
} else {
|
|
44670
|
-
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
44671
|
-
}
|
|
44672
|
-
return result;
|
|
44673
|
-
}
|
|
44674
|
-
},
|
|
44675
44074
|
// app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
|
|
44676
44075
|
previewPorts: effectivePreviewPorts
|
|
44677
44076
|
});
|
|
@@ -44679,6 +44078,8 @@ async function startDaemon(config) {
|
|
|
44679
44078
|
host: config.host,
|
|
44680
44079
|
port: config.port,
|
|
44681
44080
|
logger,
|
|
44081
|
+
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
44082
|
+
ownerPrincipalId,
|
|
44682
44083
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
44683
44084
|
{
|
|
44684
44085
|
manager,
|
|
@@ -44698,12 +44099,14 @@ async function startDaemon(config) {
|
|
|
44698
44099
|
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
44699
44100
|
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
44700
44101
|
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
44701
|
-
tryVerifyCapabilityToken: (token) => {
|
|
44102
|
+
tryVerifyCapabilityToken: (token, selfPrincipalId, _selfDisplayName) => {
|
|
44702
44103
|
const v2 = capabilityRegistry.verifyToken(token);
|
|
44703
44104
|
if (!v2.ok) return null;
|
|
44704
44105
|
return {
|
|
44705
44106
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
44706
|
-
grants: v2.capability.grants
|
|
44107
|
+
grants: v2.capability.grants,
|
|
44108
|
+
capabilityId: v2.capability.id,
|
|
44109
|
+
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
44707
44110
|
};
|
|
44708
44111
|
},
|
|
44709
44112
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
@@ -44754,12 +44157,6 @@ async function startDaemon(config) {
|
|
|
44754
44157
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
44755
44158
|
const handler = handlers[type];
|
|
44756
44159
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
44757
|
-
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
44758
|
-
throw new ClawdError(
|
|
44759
|
-
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
44760
|
-
`${type} requires feishu login (People/remote identity)`
|
|
44761
|
-
);
|
|
44762
|
-
}
|
|
44763
44160
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
44764
44161
|
const verdict = computeGrantForFrame(type, frame);
|
|
44765
44162
|
if (verdict.kind === "check") {
|
|
@@ -44877,8 +44274,6 @@ ${bar}
|
|
|
44877
44274
|
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
44878
44275
|
}
|
|
44879
44276
|
}
|
|
44880
|
-
void reportDevice();
|
|
44881
|
-
void fetchServerKey();
|
|
44882
44277
|
const tickAttachmentGc = () => {
|
|
44883
44278
|
try {
|
|
44884
44279
|
runAttachmentGc({
|