@clawos-dev/clawd 0.2.71-beta.132.6fed21c → 0.2.71-beta.135.eac0f51
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 +1291 -837
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -92,10 +92,6 @@ var init_methods = __esm({
|
|
|
92
92
|
"persona:get",
|
|
93
93
|
"persona:update",
|
|
94
94
|
"persona:delete",
|
|
95
|
-
// token 颁发 / 吊销:file-sharing HTTP Bearer 鉴权用(PersonaRegistry.findByToken
|
|
96
|
-
// 反查 personaId / label)。owner UI 在 PersonaSettingsDrawer "分享 Token" section 管理。
|
|
97
|
-
"persona:issueToken",
|
|
98
|
-
"persona:revokeToken",
|
|
99
95
|
// ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
|
|
100
96
|
// session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
|
|
101
97
|
// 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
|
|
@@ -106,21 +102,21 @@ var init_methods = __esm({
|
|
|
106
102
|
// ---- attachment.* file-sharing(详见 attachment-schemas.ts) ----
|
|
107
103
|
// 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
|
|
108
104
|
// (attachment-skills / attachment-deferred-tools / attachment_memories)是不同概念。
|
|
109
|
-
// 全部管理类 RPC
|
|
110
|
-
// 实际文件传输走 HTTP 路由(GET /files?p=&e=&s
|
|
105
|
+
// 全部管理类 RPC dispatcher grant 限 owner(capability admin-only);
|
|
106
|
+
// 实际文件传输走 HTTP 路由(GET /files?p=&e=&s=,HMAC 签名自包含,不在白名单内)。
|
|
111
107
|
"attachment.signUrl",
|
|
112
108
|
"attachment.groupAdd",
|
|
113
109
|
"attachment.groupRemove",
|
|
114
110
|
"attachment.groupList",
|
|
115
|
-
// v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
|
|
116
|
-
"attachment.groupListPersona",
|
|
117
111
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
118
|
-
// owner 颁发 / 列出 /
|
|
112
|
+
// owner 颁发 / 列出 / 删除给 guest 的 capability。三者均需 admin 权限(METHOD_GRANT_MAP
|
|
119
113
|
// 在 daemon 端固定为 `{ resource: '*', action: 'admin' }`,owner 自动满足)。
|
|
120
|
-
// 颁发后 daemon 推 'capability:tokenIssued'
|
|
114
|
+
// 颁发后 daemon 推 'capability:tokenIssued' 帧;删除推 'capability:tokenDeleted' 帧。
|
|
115
|
+
// 删除是 hard delete:CapabilityStore 物理移除 + 关该 cap 的所有活跃 ws + rm
|
|
116
|
+
// personas/<pid>/.clawd/sessions/guests/<capId>/ guest sessions 目录。
|
|
121
117
|
"capability:issue",
|
|
122
118
|
"capability:list",
|
|
123
|
-
"capability:
|
|
119
|
+
"capability:delete",
|
|
124
120
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
125
121
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
126
122
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -139,7 +135,20 @@ var init_methods = __esm({
|
|
|
139
135
|
// 的 preview 一次性临时 client 用它判定身份和可用 persona。
|
|
140
136
|
"whoami",
|
|
141
137
|
"info",
|
|
142
|
-
"ping"
|
|
138
|
+
"ping",
|
|
139
|
+
// ---- person:* (Person identity Phase 1, spec v5) ----
|
|
140
|
+
// Person 抽象 = People panel 顶层 entity(一个 Person 聚合 N 个 cap + M 个 remote
|
|
141
|
+
// alias)。owner admin-only。daemon 维护 PersonStore + PersonAliasStore。
|
|
142
|
+
// - list 返回 PersonWithLinks[](含派生 linkedCapabilityIds / linkedRemoteAliases)
|
|
143
|
+
// - create / update / delete 操作 Person 实体(delete 触发 cascade:revoke 全部
|
|
144
|
+
// linked cap + remove subscribed alias + clear linked inbox events)
|
|
145
|
+
// - link 重新指向某 principal 到目标 personId(手动合并 / 重链入口;自动 link
|
|
146
|
+
// 在 capability:issue / remote-persona:add handler 内部直接写 PersonAliasStore)
|
|
147
|
+
"person:list",
|
|
148
|
+
"person:create",
|
|
149
|
+
"person:update",
|
|
150
|
+
"person:delete",
|
|
151
|
+
"person:link"
|
|
143
152
|
];
|
|
144
153
|
}
|
|
145
154
|
});
|
|
@@ -629,8 +638,8 @@ var init_parseUtil = __esm({
|
|
|
629
638
|
init_errors2();
|
|
630
639
|
init_en();
|
|
631
640
|
makeIssue = (params) => {
|
|
632
|
-
const { data, path:
|
|
633
|
-
const fullPath = [...
|
|
641
|
+
const { data, path: path39, errorMaps, issueData } = params;
|
|
642
|
+
const fullPath = [...path39, ...issueData.path || []];
|
|
634
643
|
const fullIssue = {
|
|
635
644
|
...issueData,
|
|
636
645
|
path: fullPath
|
|
@@ -941,11 +950,11 @@ var init_types = __esm({
|
|
|
941
950
|
init_parseUtil();
|
|
942
951
|
init_util();
|
|
943
952
|
ParseInputLazyPath = class {
|
|
944
|
-
constructor(parent, value,
|
|
953
|
+
constructor(parent, value, path39, key) {
|
|
945
954
|
this._cachedPath = [];
|
|
946
955
|
this.parent = parent;
|
|
947
956
|
this.data = value;
|
|
948
|
-
this._path =
|
|
957
|
+
this._path = path39;
|
|
949
958
|
this._key = key;
|
|
950
959
|
}
|
|
951
960
|
get path() {
|
|
@@ -4329,12 +4338,12 @@ var init_zod = __esm({
|
|
|
4329
4338
|
});
|
|
4330
4339
|
|
|
4331
4340
|
// ../protocol/src/attachment-schemas.ts
|
|
4332
|
-
var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema
|
|
4341
|
+
var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
|
|
4333
4342
|
var init_attachment_schemas = __esm({
|
|
4334
4343
|
"../protocol/src/attachment-schemas.ts"() {
|
|
4335
4344
|
"use strict";
|
|
4336
4345
|
init_zod();
|
|
4337
|
-
TOKEN_ROLES = ["owner"
|
|
4346
|
+
TOKEN_ROLES = ["owner"];
|
|
4338
4347
|
GROUP_FILE_SOURCES = ["agent", "owner"];
|
|
4339
4348
|
GroupFileEntrySchema = external_exports.object({
|
|
4340
4349
|
/** daemon 派发的稳定 id(用于 RPC remove / UI key) */
|
|
@@ -4388,31 +4397,15 @@ var init_attachment_schemas = __esm({
|
|
|
4388
4397
|
AttachmentGroupListResponseSchema = external_exports.object({
|
|
4389
4398
|
entries: external_exports.array(GroupFileEntrySchema)
|
|
4390
4399
|
});
|
|
4391
|
-
AttachmentGroupListPersonaArgs = external_exports.object({
|
|
4392
|
-
personaId: external_exports.string().min(1)
|
|
4393
|
-
});
|
|
4394
|
-
AttachmentGroupListPersonaResponseSchema = external_exports.object({
|
|
4395
|
-
perSession: external_exports.array(
|
|
4396
|
-
external_exports.object({
|
|
4397
|
-
sessionId: external_exports.string().min(1),
|
|
4398
|
-
entries: external_exports.array(GroupFileEntrySchema)
|
|
4399
|
-
})
|
|
4400
|
-
)
|
|
4401
|
-
});
|
|
4402
4400
|
}
|
|
4403
4401
|
});
|
|
4404
4402
|
|
|
4405
4403
|
// ../protocol/src/persona-schemas.ts
|
|
4406
|
-
var
|
|
4404
|
+
var PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
|
|
4407
4405
|
var init_persona_schemas = __esm({
|
|
4408
4406
|
"../protocol/src/persona-schemas.ts"() {
|
|
4409
4407
|
"use strict";
|
|
4410
4408
|
init_zod();
|
|
4411
|
-
PersonaTokenEntrySchema = external_exports.object({
|
|
4412
|
-
label: external_exports.string(),
|
|
4413
|
-
issuedAt: external_exports.number(),
|
|
4414
|
-
revoked: external_exports.boolean()
|
|
4415
|
-
});
|
|
4416
4409
|
PersonaFileSchema = external_exports.object({
|
|
4417
4410
|
personaId: external_exports.string(),
|
|
4418
4411
|
label: external_exports.string(),
|
|
@@ -4421,13 +4414,6 @@ var init_persona_schemas = __esm({
|
|
|
4421
4414
|
// 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
|
|
4422
4415
|
// for forward compatibility, consumer fallbacks to default when key not found.
|
|
4423
4416
|
iconKey: external_exports.string().optional(),
|
|
4424
|
-
/**
|
|
4425
|
-
* Persona token 列表:file-sharing HTTP Bearer 鉴权用。
|
|
4426
|
-
* - 写:`PersonaManager.issueToken` / `revokeToken`
|
|
4427
|
-
* - 读:`PersonaRegistry.findByToken` 反向查表(需 persona.public === true)
|
|
4428
|
-
* - optional:兼容历史 persona.json 不含此字段的情形(旧 daemon 写文件时省略)
|
|
4429
|
-
*/
|
|
4430
|
-
tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema).optional(),
|
|
4431
4417
|
createdAt: external_exports.number(),
|
|
4432
4418
|
updatedAt: external_exports.number()
|
|
4433
4419
|
}).strict();
|
|
@@ -4480,14 +4466,6 @@ var init_persona_schemas = __esm({
|
|
|
4480
4466
|
iconKey: external_exports.string().nullable().optional()
|
|
4481
4467
|
}).strict()
|
|
4482
4468
|
}).strict();
|
|
4483
|
-
PersonaIssueTokenArgsSchema = external_exports.object({
|
|
4484
|
-
personaId: external_exports.string().min(1),
|
|
4485
|
-
label: external_exports.string().min(1)
|
|
4486
|
-
});
|
|
4487
|
-
PersonaRevokeTokenArgsSchema = external_exports.object({
|
|
4488
|
-
personaId: external_exports.string().min(1),
|
|
4489
|
-
token: external_exports.string().min(1)
|
|
4490
|
-
});
|
|
4491
4469
|
}
|
|
4492
4470
|
});
|
|
4493
4471
|
|
|
@@ -4594,6 +4572,15 @@ var init_schemas = __esm({
|
|
|
4594
4572
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
4595
4573
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4596
4574
|
chatId: external_exports.string().min(1).optional(),
|
|
4575
|
+
/**
|
|
4576
|
+
* 创建该 session 的 principal id(owner uuid 或 guest cap.id),从 session:create handler
|
|
4577
|
+
* 的 ctx.principal.id 派生。Person identity Q2「按对端人聚合 sessions」需要它:
|
|
4578
|
+
* UI 端按 `creatorPrincipalId === ownerPrincipalId` 区分「我自己」vs「别人接入」,
|
|
4579
|
+
* 后者再通过 PersonAlias 表反查 personId 二级分组。
|
|
4580
|
+
*
|
|
4581
|
+
* optional:兼容 2026-05-21 之前的老 session 数据,新建一定有值。
|
|
4582
|
+
*/
|
|
4583
|
+
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4597
4584
|
createdAt: external_exports.string().min(1),
|
|
4598
4585
|
updatedAt: external_exports.string().min(1)
|
|
4599
4586
|
});
|
|
@@ -5003,7 +4990,18 @@ var init_schemas = __esm({
|
|
|
5003
4990
|
AuthRequestFrameSchema = external_exports.object({
|
|
5004
4991
|
type: external_exports.literal("auth"),
|
|
5005
4992
|
token: external_exports.string().min(1),
|
|
5006
|
-
scheme: external_exports.literal("bearer").optional()
|
|
4993
|
+
scheme: external_exports.literal("bearer").optional(),
|
|
4994
|
+
/**
|
|
4995
|
+
* Person identity Phase 1: 如果 guest 自己也是 daemon owner,把自己的稳定
|
|
4996
|
+
* ownerPrincipalId 主动上报,让对端 daemon 反查 PersonAliasStore 自动 link 到
|
|
4997
|
+
* 已有 Person(避免出现"两个 Bob")。纯访客 / 老 daemon 客户端可省略。
|
|
4998
|
+
*
|
|
4999
|
+
* daemon transport/auth.ts 在 verify token 成功后:
|
|
5000
|
+
* - 若 cap.linkedPrincipalId 未设 → 更新 cap.linkedPrincipalId = selfPrincipalId
|
|
5001
|
+
* + PersonAliasStore.rekey(cap.id, selfPrincipalId)
|
|
5002
|
+
* - 若已设但与本次不一致 → 仅 warn log(潜在协议异常但不阻断)
|
|
5003
|
+
*/
|
|
5004
|
+
selfPrincipalId: external_exports.string().min(1).optional()
|
|
5007
5005
|
});
|
|
5008
5006
|
AuthOkFrameSchema = external_exports.object({
|
|
5009
5007
|
type: external_exports.literal("auth:ok"),
|
|
@@ -5036,10 +5034,9 @@ var init_schemas = __esm({
|
|
|
5036
5034
|
// PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
|
|
5037
5035
|
// 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
|
|
5038
5036
|
// 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
|
|
5039
|
-
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP
|
|
5037
|
+
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP)。来源 TOKEN_ROLES(spec §11 #1 中央真理源)。
|
|
5038
|
+
* 历史 'personal' role(persona file-sharing token)在 2026-05-21 删除;tokenPersonaId 一同移除。 */
|
|
5040
5039
|
tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
|
|
5041
|
-
/** tokenRole='personal' 时携带(绑定到具体 persona);owner 不携带 */
|
|
5042
|
-
tokenPersonaId: external_exports.string().min(1).optional(),
|
|
5043
5040
|
/** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
|
|
5044
5041
|
isLoopback: external_exports.boolean().optional(),
|
|
5045
5042
|
/** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
|
|
@@ -5067,7 +5064,10 @@ var init_persona_mode = __esm({
|
|
|
5067
5064
|
});
|
|
5068
5065
|
|
|
5069
5066
|
// ../protocol/src/principal.ts
|
|
5070
|
-
|
|
5067
|
+
function makeOwnerPrincipal(id, displayName) {
|
|
5068
|
+
return PrincipalSchema.parse({ id, kind: "owner", displayName });
|
|
5069
|
+
}
|
|
5070
|
+
var PrincipalKindSchema, PrincipalSchema;
|
|
5071
5071
|
var init_principal = __esm({
|
|
5072
5072
|
"../protocol/src/principal.ts"() {
|
|
5073
5073
|
"use strict";
|
|
@@ -5078,11 +5078,6 @@ var init_principal = __esm({
|
|
|
5078
5078
|
kind: PrincipalKindSchema,
|
|
5079
5079
|
displayName: external_exports.string()
|
|
5080
5080
|
}).strict();
|
|
5081
|
-
OWNER_PRINCIPAL = {
|
|
5082
|
-
id: "owner",
|
|
5083
|
-
kind: "owner",
|
|
5084
|
-
displayName: "owner"
|
|
5085
|
-
};
|
|
5086
5081
|
}
|
|
5087
5082
|
});
|
|
5088
5083
|
|
|
@@ -5091,7 +5086,7 @@ function stripSecretHash(cap) {
|
|
|
5091
5086
|
const { secretHash: _hash, ...wire } = cap;
|
|
5092
5087
|
return wire;
|
|
5093
5088
|
}
|
|
5094
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema;
|
|
5089
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, CapabilityIssueArgsSchema;
|
|
5095
5090
|
var init_capability = __esm({
|
|
5096
5091
|
"../protocol/src/capability.ts"() {
|
|
5097
5092
|
"use strict";
|
|
@@ -5117,7 +5112,13 @@ var init_capability = __esm({
|
|
|
5117
5112
|
expiresAt: external_exports.number().int().positive().optional(),
|
|
5118
5113
|
maxUses: external_exports.number().int().positive().optional(),
|
|
5119
5114
|
usedCount: external_exports.number().int().nonnegative(),
|
|
5120
|
-
|
|
5115
|
+
/**
|
|
5116
|
+
* Person identity Phase 1: guest 真实身份。
|
|
5117
|
+
* - 纯访客 / 尚未 authenticate 时 undefined
|
|
5118
|
+
* - guest authenticate 上报 selfPrincipalId 后 daemon 落入此字段(immutable,不允许 owner 修改)
|
|
5119
|
+
* - PersonAliasStore 的 alias key 来源优先 linkedPrincipalId,否则 fallback cap.id
|
|
5120
|
+
*/
|
|
5121
|
+
linkedPrincipalId: external_exports.string().min(1).optional()
|
|
5121
5122
|
}).strict();
|
|
5122
5123
|
CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
|
|
5123
5124
|
CapabilityErrorCodeSchema = external_exports.enum([
|
|
@@ -5141,43 +5142,41 @@ var init_capability = __esm({
|
|
|
5141
5142
|
}).strict()
|
|
5142
5143
|
)
|
|
5143
5144
|
}).strict();
|
|
5145
|
+
CapabilityIssueArgsSchema = external_exports.object({
|
|
5146
|
+
displayName: external_exports.string().min(1),
|
|
5147
|
+
grants: external_exports.array(GrantSchema),
|
|
5148
|
+
personId: external_exports.string().min(1),
|
|
5149
|
+
expiresAt: external_exports.number().int().positive().optional(),
|
|
5150
|
+
maxUses: external_exports.number().int().positive().optional()
|
|
5151
|
+
}).strict();
|
|
5144
5152
|
}
|
|
5145
5153
|
});
|
|
5146
5154
|
|
|
5147
5155
|
// ../protocol/src/inbox.ts
|
|
5148
|
-
var
|
|
5156
|
+
var InboxMessageSchema, InboxPostMessageArgsSchema, InboxListArgsSchema, InboxMarkReadArgsSchema;
|
|
5149
5157
|
var init_inbox = __esm({
|
|
5150
5158
|
"../protocol/src/inbox.ts"() {
|
|
5151
5159
|
"use strict";
|
|
5152
5160
|
init_zod();
|
|
5153
|
-
|
|
5154
|
-
init_capability();
|
|
5155
|
-
INBOX_EVENT_KIND_VALUES = ["persona-mention", "direct-message"];
|
|
5156
|
-
InboxEventKindSchema = external_exports.enum(INBOX_EVENT_KIND_VALUES);
|
|
5157
|
-
INBOX_PREVIEW_MAX_LENGTH = 140;
|
|
5158
|
-
InboxEventSchema = external_exports.object({
|
|
5161
|
+
InboxMessageSchema = external_exports.object({
|
|
5159
5162
|
id: external_exports.string().min(1),
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
// persona-mention 才有, direct-message 不带
|
|
5164
|
-
resource: ResourceSchema.optional(),
|
|
5165
|
-
preview: external_exports.string().max(INBOX_PREVIEW_MAX_LENGTH),
|
|
5163
|
+
capabilityId: external_exports.string().min(1),
|
|
5164
|
+
senderPrincipalId: external_exports.string().min(1),
|
|
5165
|
+
text: external_exports.string().min(1),
|
|
5166
5166
|
createdAt: external_exports.number().int().nonnegative(),
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5167
|
+
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5168
|
+
}).strict();
|
|
5169
|
+
InboxPostMessageArgsSchema = external_exports.object({
|
|
5170
|
+
capabilityId: external_exports.string().min(1),
|
|
5171
|
+
text: external_exports.string().min(1)
|
|
5170
5172
|
}).strict();
|
|
5171
5173
|
InboxListArgsSchema = external_exports.object({
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
+
capabilityId: external_exports.string().min(1),
|
|
5175
|
+
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5174
5176
|
}).strict();
|
|
5175
5177
|
InboxMarkReadArgsSchema = external_exports.object({
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
InboxPostMessageArgsSchema = external_exports.object({
|
|
5179
|
-
peerPrincipalId: external_exports.string().min(1),
|
|
5180
|
-
text: external_exports.string().min(1).max(INBOX_PREVIEW_MAX_LENGTH)
|
|
5178
|
+
capabilityId: external_exports.string().min(1),
|
|
5179
|
+
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5181
5180
|
}).strict();
|
|
5182
5181
|
}
|
|
5183
5182
|
});
|
|
@@ -5200,6 +5199,11 @@ var init_remote_persona = __esm({
|
|
|
5200
5199
|
// capability.id 直接存进来。旧 v1 持久化文件没此字段 → schema parse fail →
|
|
5201
5200
|
// RemotePersonaStore.list 静默跳过 (alpha 阶段可接受, 老板重新 add 即可)。
|
|
5202
5201
|
myCapabilityId: external_exports.string().min(1),
|
|
5202
|
+
// owner-id stabilization: 远端 daemon 的稳定 ownerPrincipalId(whoami.owner.id)。
|
|
5203
|
+
// UI DM 时作为 peerPrincipalId 传给远端 daemon(取代字面量 'owner' sentinel),
|
|
5204
|
+
// 让 inbox event from/to 路由 + UI useDm fromOwner 判定都用真实 id。
|
|
5205
|
+
// 旧持久化文件缺该字段 → schema parse fail → store.list 静默跳过(同 myCapabilityId)。
|
|
5206
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5203
5207
|
remotePersonaId: external_exports.string().min(1),
|
|
5204
5208
|
remoteDisplayName: external_exports.string(),
|
|
5205
5209
|
ownerDisplayName: external_exports.string().optional(),
|
|
@@ -5213,9 +5217,17 @@ var init_remote_persona = __esm({
|
|
|
5213
5217
|
capabilityToken: external_exports.string().min(1),
|
|
5214
5218
|
// v2 Phase 7: UI 调 preview 后从 whoami response 取 capability.id 传入
|
|
5215
5219
|
myCapabilityId: external_exports.string().min(1),
|
|
5220
|
+
// owner-id stabilization: UI 从 whoami.owner.id 取,远端 daemon 稳定身份
|
|
5221
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5216
5222
|
remotePersonaId: external_exports.string().min(1),
|
|
5217
5223
|
remoteDisplayName: external_exports.string(),
|
|
5218
|
-
ownerDisplayName: external_exports.string().optional()
|
|
5224
|
+
ownerDisplayName: external_exports.string().optional(),
|
|
5225
|
+
/**
|
|
5226
|
+
* Person identity Phase 1: add 时必须关联到一个 Person(owner 视角的"对方是谁")。
|
|
5227
|
+
* daemon handler 内 atomic 写 RemotePersona + 写 PersonAliasStore
|
|
5228
|
+
* (alias key = ownerPrincipalId)。
|
|
5229
|
+
*/
|
|
5230
|
+
personId: external_exports.string().min(1)
|
|
5219
5231
|
}).strict();
|
|
5220
5232
|
RemotePersonaRemoveArgsSchema = external_exports.object({
|
|
5221
5233
|
alias: external_exports.string().min(1)
|
|
@@ -5223,6 +5235,75 @@ var init_remote_persona = __esm({
|
|
|
5223
5235
|
}
|
|
5224
5236
|
});
|
|
5225
5237
|
|
|
5238
|
+
// ../protocol/src/person.ts
|
|
5239
|
+
var PersonSchema, PersonAliasSchema, PersonWithLinksSchema, PersonCreateArgsSchema, PersonUpdateArgsSchema, PersonDeleteArgsSchema, PersonLinkArgsSchema, PersonListResponseSchema, PersonCreateResponseSchema, PersonUpdateResponseSchema, PersonDeleteResponseSchema, PersonLinkResponseSchema;
|
|
5240
|
+
var init_person = __esm({
|
|
5241
|
+
"../protocol/src/person.ts"() {
|
|
5242
|
+
"use strict";
|
|
5243
|
+
init_zod();
|
|
5244
|
+
PersonSchema = external_exports.object({
|
|
5245
|
+
id: external_exports.string().min(1),
|
|
5246
|
+
displayName: external_exports.string().min(1),
|
|
5247
|
+
notes: external_exports.string().optional(),
|
|
5248
|
+
/** 默认 true(owner 可关闭以阻断该 Person 的入站 DM) */
|
|
5249
|
+
dmEnabled: external_exports.boolean(),
|
|
5250
|
+
createdAt: external_exports.number().int().nonnegative(),
|
|
5251
|
+
updatedAt: external_exports.number().int().nonnegative()
|
|
5252
|
+
}).strict();
|
|
5253
|
+
PersonAliasSchema = external_exports.object({
|
|
5254
|
+
principalId: external_exports.string().min(1),
|
|
5255
|
+
personId: external_exports.string().min(1)
|
|
5256
|
+
}).strict();
|
|
5257
|
+
PersonWithLinksSchema = PersonSchema.extend({
|
|
5258
|
+
linkedCapabilityIds: external_exports.array(external_exports.string()),
|
|
5259
|
+
linkedRemoteAliases: external_exports.array(external_exports.string())
|
|
5260
|
+
}).strict();
|
|
5261
|
+
PersonCreateArgsSchema = external_exports.object({
|
|
5262
|
+
displayName: external_exports.string().min(1),
|
|
5263
|
+
notes: external_exports.string().optional(),
|
|
5264
|
+
/** 缺省 true(在 daemon handler 内 default) */
|
|
5265
|
+
dmEnabled: external_exports.boolean().optional()
|
|
5266
|
+
}).strict();
|
|
5267
|
+
PersonUpdateArgsSchema = external_exports.object({
|
|
5268
|
+
id: external_exports.string().min(1),
|
|
5269
|
+
displayName: external_exports.string().min(1).optional(),
|
|
5270
|
+
notes: external_exports.string().optional(),
|
|
5271
|
+
dmEnabled: external_exports.boolean().optional()
|
|
5272
|
+
}).strict();
|
|
5273
|
+
PersonDeleteArgsSchema = external_exports.object({
|
|
5274
|
+
id: external_exports.string().min(1)
|
|
5275
|
+
}).strict();
|
|
5276
|
+
PersonLinkArgsSchema = external_exports.object({
|
|
5277
|
+
principalId: external_exports.string().min(1),
|
|
5278
|
+
personId: external_exports.string().min(1)
|
|
5279
|
+
}).strict();
|
|
5280
|
+
PersonListResponseSchema = external_exports.object({
|
|
5281
|
+
type: external_exports.literal("person:list:ok"),
|
|
5282
|
+
persons: external_exports.array(PersonWithLinksSchema)
|
|
5283
|
+
}).strict();
|
|
5284
|
+
PersonCreateResponseSchema = external_exports.object({
|
|
5285
|
+
type: external_exports.literal("person:create:ok"),
|
|
5286
|
+
person: PersonSchema
|
|
5287
|
+
}).strict();
|
|
5288
|
+
PersonUpdateResponseSchema = external_exports.object({
|
|
5289
|
+
type: external_exports.literal("person:update:ok"),
|
|
5290
|
+
person: PersonSchema
|
|
5291
|
+
}).strict();
|
|
5292
|
+
PersonDeleteResponseSchema = external_exports.object({
|
|
5293
|
+
type: external_exports.literal("person:delete:ok"),
|
|
5294
|
+
/** cascade 删了哪些 capability id */
|
|
5295
|
+
deletedCapabilityIds: external_exports.array(external_exports.string()),
|
|
5296
|
+
/** cascade 移除了哪些 remote alias */
|
|
5297
|
+
removedRemoteAliases: external_exports.array(external_exports.string()),
|
|
5298
|
+
/** cascade 清掉了多少条 inbox 事件 */
|
|
5299
|
+
deletedInboxEvents: external_exports.number().int().nonnegative()
|
|
5300
|
+
}).strict();
|
|
5301
|
+
PersonLinkResponseSchema = external_exports.object({
|
|
5302
|
+
type: external_exports.literal("person:link:ok")
|
|
5303
|
+
}).strict();
|
|
5304
|
+
}
|
|
5305
|
+
});
|
|
5306
|
+
|
|
5226
5307
|
// ../protocol/src/runtime.ts
|
|
5227
5308
|
var init_runtime = __esm({
|
|
5228
5309
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5239,6 +5320,7 @@ var init_runtime = __esm({
|
|
|
5239
5320
|
init_capability();
|
|
5240
5321
|
init_inbox();
|
|
5241
5322
|
init_remote_persona();
|
|
5323
|
+
init_person();
|
|
5242
5324
|
}
|
|
5243
5325
|
});
|
|
5244
5326
|
|
|
@@ -5513,8 +5595,8 @@ var require_req = __commonJS({
|
|
|
5513
5595
|
if (req.originalUrl) {
|
|
5514
5596
|
_req.url = req.originalUrl;
|
|
5515
5597
|
} else {
|
|
5516
|
-
const
|
|
5517
|
-
_req.url = typeof
|
|
5598
|
+
const path39 = req.path;
|
|
5599
|
+
_req.url = typeof path39 === "string" ? path39 : req.url ? req.url.path || req.url : void 0;
|
|
5518
5600
|
}
|
|
5519
5601
|
if (req.query) {
|
|
5520
5602
|
_req.query = req.query;
|
|
@@ -5679,14 +5761,14 @@ var require_redact = __commonJS({
|
|
|
5679
5761
|
}
|
|
5680
5762
|
return obj;
|
|
5681
5763
|
}
|
|
5682
|
-
function parsePath(
|
|
5764
|
+
function parsePath(path39) {
|
|
5683
5765
|
const parts = [];
|
|
5684
5766
|
let current = "";
|
|
5685
5767
|
let inBrackets = false;
|
|
5686
5768
|
let inQuotes = false;
|
|
5687
5769
|
let quoteChar = "";
|
|
5688
|
-
for (let i = 0; i <
|
|
5689
|
-
const char =
|
|
5770
|
+
for (let i = 0; i < path39.length; i++) {
|
|
5771
|
+
const char = path39[i];
|
|
5690
5772
|
if (!inBrackets && char === ".") {
|
|
5691
5773
|
if (current) {
|
|
5692
5774
|
parts.push(current);
|
|
@@ -5817,10 +5899,10 @@ var require_redact = __commonJS({
|
|
|
5817
5899
|
return current;
|
|
5818
5900
|
}
|
|
5819
5901
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5820
|
-
for (const
|
|
5821
|
-
const parts = parsePath(
|
|
5902
|
+
for (const path39 of paths) {
|
|
5903
|
+
const parts = parsePath(path39);
|
|
5822
5904
|
if (parts.includes("*")) {
|
|
5823
|
-
redactWildcardPath(obj, parts, censor,
|
|
5905
|
+
redactWildcardPath(obj, parts, censor, path39, remove);
|
|
5824
5906
|
} else {
|
|
5825
5907
|
if (remove) {
|
|
5826
5908
|
removeKey(obj, parts);
|
|
@@ -5905,8 +5987,8 @@ var require_redact = __commonJS({
|
|
|
5905
5987
|
}
|
|
5906
5988
|
} else {
|
|
5907
5989
|
if (afterWildcard.includes("*")) {
|
|
5908
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5909
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5990
|
+
const wrappedCensor = typeof censor === "function" ? (value, path39) => {
|
|
5991
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path39];
|
|
5910
5992
|
return censor(value, fullPath);
|
|
5911
5993
|
} : censor;
|
|
5912
5994
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5941,8 +6023,8 @@ var require_redact = __commonJS({
|
|
|
5941
6023
|
return null;
|
|
5942
6024
|
}
|
|
5943
6025
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5944
|
-
for (const
|
|
5945
|
-
const parts = parsePath(
|
|
6026
|
+
for (const path39 of pathsToClone) {
|
|
6027
|
+
const parts = parsePath(path39);
|
|
5946
6028
|
let current = pathStructure;
|
|
5947
6029
|
for (let i = 0; i < parts.length; i++) {
|
|
5948
6030
|
const part = parts[i];
|
|
@@ -5994,24 +6076,24 @@ var require_redact = __commonJS({
|
|
|
5994
6076
|
}
|
|
5995
6077
|
return cloneSelectively(obj, pathStructure);
|
|
5996
6078
|
}
|
|
5997
|
-
function validatePath(
|
|
5998
|
-
if (typeof
|
|
6079
|
+
function validatePath(path39) {
|
|
6080
|
+
if (typeof path39 !== "string") {
|
|
5999
6081
|
throw new Error("Paths must be (non-empty) strings");
|
|
6000
6082
|
}
|
|
6001
|
-
if (
|
|
6083
|
+
if (path39 === "") {
|
|
6002
6084
|
throw new Error("Invalid redaction path ()");
|
|
6003
6085
|
}
|
|
6004
|
-
if (
|
|
6005
|
-
throw new Error(`Invalid redaction path (${
|
|
6086
|
+
if (path39.includes("..")) {
|
|
6087
|
+
throw new Error(`Invalid redaction path (${path39})`);
|
|
6006
6088
|
}
|
|
6007
|
-
if (
|
|
6008
|
-
throw new Error(`Invalid redaction path (${
|
|
6089
|
+
if (path39.includes(",")) {
|
|
6090
|
+
throw new Error(`Invalid redaction path (${path39})`);
|
|
6009
6091
|
}
|
|
6010
6092
|
let bracketCount = 0;
|
|
6011
6093
|
let inQuotes = false;
|
|
6012
6094
|
let quoteChar = "";
|
|
6013
|
-
for (let i = 0; i <
|
|
6014
|
-
const char =
|
|
6095
|
+
for (let i = 0; i < path39.length; i++) {
|
|
6096
|
+
const char = path39[i];
|
|
6015
6097
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6016
6098
|
if (!inQuotes) {
|
|
6017
6099
|
inQuotes = true;
|
|
@@ -6025,20 +6107,20 @@ var require_redact = __commonJS({
|
|
|
6025
6107
|
} else if (char === "]" && !inQuotes) {
|
|
6026
6108
|
bracketCount--;
|
|
6027
6109
|
if (bracketCount < 0) {
|
|
6028
|
-
throw new Error(`Invalid redaction path (${
|
|
6110
|
+
throw new Error(`Invalid redaction path (${path39})`);
|
|
6029
6111
|
}
|
|
6030
6112
|
}
|
|
6031
6113
|
}
|
|
6032
6114
|
if (bracketCount !== 0) {
|
|
6033
|
-
throw new Error(`Invalid redaction path (${
|
|
6115
|
+
throw new Error(`Invalid redaction path (${path39})`);
|
|
6034
6116
|
}
|
|
6035
6117
|
}
|
|
6036
6118
|
function validatePaths(paths) {
|
|
6037
6119
|
if (!Array.isArray(paths)) {
|
|
6038
6120
|
throw new TypeError("paths must be an array");
|
|
6039
6121
|
}
|
|
6040
|
-
for (const
|
|
6041
|
-
validatePath(
|
|
6122
|
+
for (const path39 of paths) {
|
|
6123
|
+
validatePath(path39);
|
|
6042
6124
|
}
|
|
6043
6125
|
}
|
|
6044
6126
|
function slowRedact(options = {}) {
|
|
@@ -6206,8 +6288,8 @@ var require_redaction = __commonJS({
|
|
|
6206
6288
|
if (shape[k2] === null) {
|
|
6207
6289
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6208
6290
|
} else {
|
|
6209
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6210
|
-
return censor(value, [k2, ...
|
|
6291
|
+
const wrappedCensor = typeof censor === "function" ? (value, path39) => {
|
|
6292
|
+
return censor(value, [k2, ...path39]);
|
|
6211
6293
|
} : censor;
|
|
6212
6294
|
o[k2] = Redact({
|
|
6213
6295
|
paths: shape[k2],
|
|
@@ -6425,10 +6507,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6425
6507
|
var require_sonic_boom = __commonJS({
|
|
6426
6508
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6427
6509
|
"use strict";
|
|
6428
|
-
var
|
|
6510
|
+
var fs35 = require("fs");
|
|
6429
6511
|
var EventEmitter2 = require("events");
|
|
6430
6512
|
var inherits = require("util").inherits;
|
|
6431
|
-
var
|
|
6513
|
+
var path39 = require("path");
|
|
6432
6514
|
var sleep = require_atomic_sleep();
|
|
6433
6515
|
var assert = require("assert");
|
|
6434
6516
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6482,20 +6564,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6482
6564
|
const mode = sonic.mode;
|
|
6483
6565
|
if (sonic.sync) {
|
|
6484
6566
|
try {
|
|
6485
|
-
if (sonic.mkdir)
|
|
6486
|
-
const fd =
|
|
6567
|
+
if (sonic.mkdir) fs35.mkdirSync(path39.dirname(file), { recursive: true });
|
|
6568
|
+
const fd = fs35.openSync(file, flags, mode);
|
|
6487
6569
|
fileOpened(null, fd);
|
|
6488
6570
|
} catch (err) {
|
|
6489
6571
|
fileOpened(err);
|
|
6490
6572
|
throw err;
|
|
6491
6573
|
}
|
|
6492
6574
|
} else if (sonic.mkdir) {
|
|
6493
|
-
|
|
6575
|
+
fs35.mkdir(path39.dirname(file), { recursive: true }, (err) => {
|
|
6494
6576
|
if (err) return fileOpened(err);
|
|
6495
|
-
|
|
6577
|
+
fs35.open(file, flags, mode, fileOpened);
|
|
6496
6578
|
});
|
|
6497
6579
|
} else {
|
|
6498
|
-
|
|
6580
|
+
fs35.open(file, flags, mode, fileOpened);
|
|
6499
6581
|
}
|
|
6500
6582
|
}
|
|
6501
6583
|
function SonicBoom(opts) {
|
|
@@ -6536,8 +6618,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6536
6618
|
this.flush = flushBuffer;
|
|
6537
6619
|
this.flushSync = flushBufferSync;
|
|
6538
6620
|
this._actualWrite = actualWriteBuffer;
|
|
6539
|
-
fsWriteSync = () =>
|
|
6540
|
-
fsWrite = () =>
|
|
6621
|
+
fsWriteSync = () => fs35.writeSync(this.fd, this._writingBuf);
|
|
6622
|
+
fsWrite = () => fs35.write(this.fd, this._writingBuf, this.release);
|
|
6541
6623
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6542
6624
|
this._writingBuf = "";
|
|
6543
6625
|
this.write = write;
|
|
@@ -6546,15 +6628,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6546
6628
|
this._actualWrite = actualWrite;
|
|
6547
6629
|
fsWriteSync = () => {
|
|
6548
6630
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6549
|
-
return
|
|
6631
|
+
return fs35.writeSync(this.fd, this._writingBuf);
|
|
6550
6632
|
}
|
|
6551
|
-
return
|
|
6633
|
+
return fs35.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6552
6634
|
};
|
|
6553
6635
|
fsWrite = () => {
|
|
6554
6636
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6555
|
-
return
|
|
6637
|
+
return fs35.write(this.fd, this._writingBuf, this.release);
|
|
6556
6638
|
}
|
|
6557
|
-
return
|
|
6639
|
+
return fs35.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6558
6640
|
};
|
|
6559
6641
|
} else {
|
|
6560
6642
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6611,7 +6693,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6611
6693
|
}
|
|
6612
6694
|
}
|
|
6613
6695
|
if (this._fsync) {
|
|
6614
|
-
|
|
6696
|
+
fs35.fsyncSync(this.fd);
|
|
6615
6697
|
}
|
|
6616
6698
|
const len = this._len;
|
|
6617
6699
|
if (this._reopening) {
|
|
@@ -6725,7 +6807,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6725
6807
|
const onDrain = () => {
|
|
6726
6808
|
if (!this._fsync) {
|
|
6727
6809
|
try {
|
|
6728
|
-
|
|
6810
|
+
fs35.fsync(this.fd, (err) => {
|
|
6729
6811
|
this._flushPending = false;
|
|
6730
6812
|
cb(err);
|
|
6731
6813
|
});
|
|
@@ -6827,7 +6909,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6827
6909
|
const fd = this.fd;
|
|
6828
6910
|
this.once("ready", () => {
|
|
6829
6911
|
if (fd !== this.fd) {
|
|
6830
|
-
|
|
6912
|
+
fs35.close(fd, (err) => {
|
|
6831
6913
|
if (err) {
|
|
6832
6914
|
return this.emit("error", err);
|
|
6833
6915
|
}
|
|
@@ -6876,7 +6958,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6876
6958
|
buf = this._bufs[0];
|
|
6877
6959
|
}
|
|
6878
6960
|
try {
|
|
6879
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6961
|
+
const n = Buffer.isBuffer(buf) ? fs35.writeSync(this.fd, buf) : fs35.writeSync(this.fd, buf, "utf8");
|
|
6880
6962
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6881
6963
|
buf = releasedBufObj.writingBuf;
|
|
6882
6964
|
this._len = releasedBufObj.len;
|
|
@@ -6892,7 +6974,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6892
6974
|
}
|
|
6893
6975
|
}
|
|
6894
6976
|
try {
|
|
6895
|
-
|
|
6977
|
+
fs35.fsyncSync(this.fd);
|
|
6896
6978
|
} catch {
|
|
6897
6979
|
}
|
|
6898
6980
|
}
|
|
@@ -6913,7 +6995,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6913
6995
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6914
6996
|
}
|
|
6915
6997
|
try {
|
|
6916
|
-
const n =
|
|
6998
|
+
const n = fs35.writeSync(this.fd, buf);
|
|
6917
6999
|
buf = buf.subarray(n);
|
|
6918
7000
|
this._len = Math.max(this._len - n, 0);
|
|
6919
7001
|
if (buf.length <= 0) {
|
|
@@ -6941,13 +7023,13 @@ var require_sonic_boom = __commonJS({
|
|
|
6941
7023
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
6942
7024
|
if (this.sync) {
|
|
6943
7025
|
try {
|
|
6944
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7026
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs35.writeSync(this.fd, this._writingBuf) : fs35.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6945
7027
|
release(null, written);
|
|
6946
7028
|
} catch (err) {
|
|
6947
7029
|
release(err);
|
|
6948
7030
|
}
|
|
6949
7031
|
} else {
|
|
6950
|
-
|
|
7032
|
+
fs35.write(this.fd, this._writingBuf, release);
|
|
6951
7033
|
}
|
|
6952
7034
|
}
|
|
6953
7035
|
function actualWriteBuffer() {
|
|
@@ -6956,7 +7038,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6956
7038
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
6957
7039
|
if (this.sync) {
|
|
6958
7040
|
try {
|
|
6959
|
-
const written =
|
|
7041
|
+
const written = fs35.writeSync(this.fd, this._writingBuf);
|
|
6960
7042
|
release(null, written);
|
|
6961
7043
|
} catch (err) {
|
|
6962
7044
|
release(err);
|
|
@@ -6965,7 +7047,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6965
7047
|
if (kCopyBuffer) {
|
|
6966
7048
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
6967
7049
|
}
|
|
6968
|
-
|
|
7050
|
+
fs35.write(this.fd, this._writingBuf, release);
|
|
6969
7051
|
}
|
|
6970
7052
|
}
|
|
6971
7053
|
function actualClose(sonic) {
|
|
@@ -6981,12 +7063,12 @@ var require_sonic_boom = __commonJS({
|
|
|
6981
7063
|
sonic._lens = [];
|
|
6982
7064
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
6983
7065
|
try {
|
|
6984
|
-
|
|
7066
|
+
fs35.fsync(sonic.fd, closeWrapped);
|
|
6985
7067
|
} catch {
|
|
6986
7068
|
}
|
|
6987
7069
|
function closeWrapped() {
|
|
6988
7070
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
6989
|
-
|
|
7071
|
+
fs35.close(sonic.fd, done);
|
|
6990
7072
|
} else {
|
|
6991
7073
|
done();
|
|
6992
7074
|
}
|
|
@@ -7243,7 +7325,7 @@ var require_thread_stream = __commonJS({
|
|
|
7243
7325
|
var { version: version2 } = require_package();
|
|
7244
7326
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7245
7327
|
var { Worker } = require("worker_threads");
|
|
7246
|
-
var { join:
|
|
7328
|
+
var { join: join12 } = require("path");
|
|
7247
7329
|
var { pathToFileURL } = require("url");
|
|
7248
7330
|
var { wait } = require_wait();
|
|
7249
7331
|
var {
|
|
@@ -7279,7 +7361,7 @@ var require_thread_stream = __commonJS({
|
|
|
7279
7361
|
function createWorker(stream, opts) {
|
|
7280
7362
|
const { filename, workerData } = opts;
|
|
7281
7363
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7282
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7364
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join12(__dirname, "lib", "worker.js");
|
|
7283
7365
|
const worker = new Worker(toExecute, {
|
|
7284
7366
|
...opts.workerOpts,
|
|
7285
7367
|
trackUnmanagedFds: false,
|
|
@@ -7665,7 +7747,7 @@ var require_transport = __commonJS({
|
|
|
7665
7747
|
"use strict";
|
|
7666
7748
|
var { createRequire } = require("module");
|
|
7667
7749
|
var getCallers = require_caller();
|
|
7668
|
-
var { join:
|
|
7750
|
+
var { join: join12, isAbsolute, sep: sep2 } = require("path");
|
|
7669
7751
|
var sleep = require_atomic_sleep();
|
|
7670
7752
|
var onExit = require_on_exit_leak_free();
|
|
7671
7753
|
var ThreadStream = require_thread_stream();
|
|
@@ -7728,7 +7810,7 @@ var require_transport = __commonJS({
|
|
|
7728
7810
|
throw new Error("only one of target or targets can be specified");
|
|
7729
7811
|
}
|
|
7730
7812
|
if (targets) {
|
|
7731
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7813
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
7732
7814
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
7733
7815
|
return {
|
|
7734
7816
|
...dest,
|
|
@@ -7746,7 +7828,7 @@ var require_transport = __commonJS({
|
|
|
7746
7828
|
});
|
|
7747
7829
|
});
|
|
7748
7830
|
} else if (pipeline2) {
|
|
7749
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7831
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
7750
7832
|
options.pipelines = [pipeline2.map((dest) => {
|
|
7751
7833
|
return {
|
|
7752
7834
|
...dest,
|
|
@@ -7768,7 +7850,7 @@ var require_transport = __commonJS({
|
|
|
7768
7850
|
return origin;
|
|
7769
7851
|
}
|
|
7770
7852
|
if (origin === "pino/file") {
|
|
7771
|
-
return
|
|
7853
|
+
return join12(__dirname, "..", "file.js");
|
|
7772
7854
|
}
|
|
7773
7855
|
let fixTarget2;
|
|
7774
7856
|
for (const filePath of callers) {
|
|
@@ -8758,7 +8840,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8758
8840
|
return circularValue;
|
|
8759
8841
|
}
|
|
8760
8842
|
let res = "";
|
|
8761
|
-
let
|
|
8843
|
+
let join12 = ",";
|
|
8762
8844
|
const originalIndentation = indentation;
|
|
8763
8845
|
if (Array.isArray(value)) {
|
|
8764
8846
|
if (value.length === 0) {
|
|
@@ -8772,7 +8854,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8772
8854
|
indentation += spacer;
|
|
8773
8855
|
res += `
|
|
8774
8856
|
${indentation}`;
|
|
8775
|
-
|
|
8857
|
+
join12 = `,
|
|
8776
8858
|
${indentation}`;
|
|
8777
8859
|
}
|
|
8778
8860
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8780,13 +8862,13 @@ ${indentation}`;
|
|
|
8780
8862
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8781
8863
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8782
8864
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8783
|
-
res +=
|
|
8865
|
+
res += join12;
|
|
8784
8866
|
}
|
|
8785
8867
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8786
8868
|
res += tmp !== void 0 ? tmp : "null";
|
|
8787
8869
|
if (value.length - 1 > maximumBreadth) {
|
|
8788
8870
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8789
|
-
res += `${
|
|
8871
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8790
8872
|
}
|
|
8791
8873
|
if (spacer !== "") {
|
|
8792
8874
|
res += `
|
|
@@ -8807,7 +8889,7 @@ ${originalIndentation}`;
|
|
|
8807
8889
|
let separator = "";
|
|
8808
8890
|
if (spacer !== "") {
|
|
8809
8891
|
indentation += spacer;
|
|
8810
|
-
|
|
8892
|
+
join12 = `,
|
|
8811
8893
|
${indentation}`;
|
|
8812
8894
|
whitespace = " ";
|
|
8813
8895
|
}
|
|
@@ -8821,13 +8903,13 @@ ${indentation}`;
|
|
|
8821
8903
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
8822
8904
|
if (tmp !== void 0) {
|
|
8823
8905
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8824
|
-
separator =
|
|
8906
|
+
separator = join12;
|
|
8825
8907
|
}
|
|
8826
8908
|
}
|
|
8827
8909
|
if (keyLength > maximumBreadth) {
|
|
8828
8910
|
const removedKeys = keyLength - maximumBreadth;
|
|
8829
8911
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
8830
|
-
separator =
|
|
8912
|
+
separator = join12;
|
|
8831
8913
|
}
|
|
8832
8914
|
if (spacer !== "" && separator.length > 1) {
|
|
8833
8915
|
res = `
|
|
@@ -8868,7 +8950,7 @@ ${originalIndentation}`;
|
|
|
8868
8950
|
}
|
|
8869
8951
|
const originalIndentation = indentation;
|
|
8870
8952
|
let res = "";
|
|
8871
|
-
let
|
|
8953
|
+
let join12 = ",";
|
|
8872
8954
|
if (Array.isArray(value)) {
|
|
8873
8955
|
if (value.length === 0) {
|
|
8874
8956
|
return "[]";
|
|
@@ -8881,7 +8963,7 @@ ${originalIndentation}`;
|
|
|
8881
8963
|
indentation += spacer;
|
|
8882
8964
|
res += `
|
|
8883
8965
|
${indentation}`;
|
|
8884
|
-
|
|
8966
|
+
join12 = `,
|
|
8885
8967
|
${indentation}`;
|
|
8886
8968
|
}
|
|
8887
8969
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8889,13 +8971,13 @@ ${indentation}`;
|
|
|
8889
8971
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8890
8972
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8891
8973
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8892
|
-
res +=
|
|
8974
|
+
res += join12;
|
|
8893
8975
|
}
|
|
8894
8976
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8895
8977
|
res += tmp !== void 0 ? tmp : "null";
|
|
8896
8978
|
if (value.length - 1 > maximumBreadth) {
|
|
8897
8979
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8898
|
-
res += `${
|
|
8980
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8899
8981
|
}
|
|
8900
8982
|
if (spacer !== "") {
|
|
8901
8983
|
res += `
|
|
@@ -8908,7 +8990,7 @@ ${originalIndentation}`;
|
|
|
8908
8990
|
let whitespace = "";
|
|
8909
8991
|
if (spacer !== "") {
|
|
8910
8992
|
indentation += spacer;
|
|
8911
|
-
|
|
8993
|
+
join12 = `,
|
|
8912
8994
|
${indentation}`;
|
|
8913
8995
|
whitespace = " ";
|
|
8914
8996
|
}
|
|
@@ -8917,7 +8999,7 @@ ${indentation}`;
|
|
|
8917
8999
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
8918
9000
|
if (tmp !== void 0) {
|
|
8919
9001
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8920
|
-
separator =
|
|
9002
|
+
separator = join12;
|
|
8921
9003
|
}
|
|
8922
9004
|
}
|
|
8923
9005
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -8975,20 +9057,20 @@ ${originalIndentation}`;
|
|
|
8975
9057
|
indentation += spacer;
|
|
8976
9058
|
let res2 = `
|
|
8977
9059
|
${indentation}`;
|
|
8978
|
-
const
|
|
9060
|
+
const join13 = `,
|
|
8979
9061
|
${indentation}`;
|
|
8980
9062
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
8981
9063
|
let i = 0;
|
|
8982
9064
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8983
9065
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
8984
9066
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
8985
|
-
res2 +=
|
|
9067
|
+
res2 += join13;
|
|
8986
9068
|
}
|
|
8987
9069
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
8988
9070
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
8989
9071
|
if (value.length - 1 > maximumBreadth) {
|
|
8990
9072
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8991
|
-
res2 += `${
|
|
9073
|
+
res2 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8992
9074
|
}
|
|
8993
9075
|
res2 += `
|
|
8994
9076
|
${originalIndentation}`;
|
|
@@ -9004,16 +9086,16 @@ ${originalIndentation}`;
|
|
|
9004
9086
|
return '"[Object]"';
|
|
9005
9087
|
}
|
|
9006
9088
|
indentation += spacer;
|
|
9007
|
-
const
|
|
9089
|
+
const join12 = `,
|
|
9008
9090
|
${indentation}`;
|
|
9009
9091
|
let res = "";
|
|
9010
9092
|
let separator = "";
|
|
9011
9093
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9012
9094
|
if (isTypedArrayWithEntries(value)) {
|
|
9013
|
-
res += stringifyTypedArray(value,
|
|
9095
|
+
res += stringifyTypedArray(value, join12, maximumBreadth);
|
|
9014
9096
|
keys = keys.slice(value.length);
|
|
9015
9097
|
maximumPropertiesToStringify -= value.length;
|
|
9016
|
-
separator =
|
|
9098
|
+
separator = join12;
|
|
9017
9099
|
}
|
|
9018
9100
|
if (deterministic) {
|
|
9019
9101
|
keys = sort(keys, comparator);
|
|
@@ -9024,13 +9106,13 @@ ${indentation}`;
|
|
|
9024
9106
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9025
9107
|
if (tmp !== void 0) {
|
|
9026
9108
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9027
|
-
separator =
|
|
9109
|
+
separator = join12;
|
|
9028
9110
|
}
|
|
9029
9111
|
}
|
|
9030
9112
|
if (keyLength > maximumBreadth) {
|
|
9031
9113
|
const removedKeys = keyLength - maximumBreadth;
|
|
9032
9114
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9033
|
-
separator =
|
|
9115
|
+
separator = join12;
|
|
9034
9116
|
}
|
|
9035
9117
|
if (separator !== "") {
|
|
9036
9118
|
res = `
|
|
@@ -10121,11 +10203,11 @@ var init_lib = __esm({
|
|
|
10121
10203
|
}
|
|
10122
10204
|
}
|
|
10123
10205
|
},
|
|
10124
|
-
addToPath: function addToPath(
|
|
10125
|
-
var last =
|
|
10206
|
+
addToPath: function addToPath(path39, added, removed, oldPosInc, options) {
|
|
10207
|
+
var last = path39.lastComponent;
|
|
10126
10208
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10127
10209
|
return {
|
|
10128
|
-
oldPos:
|
|
10210
|
+
oldPos: path39.oldPos + oldPosInc,
|
|
10129
10211
|
lastComponent: {
|
|
10130
10212
|
count: last.count + 1,
|
|
10131
10213
|
added,
|
|
@@ -10135,7 +10217,7 @@ var init_lib = __esm({
|
|
|
10135
10217
|
};
|
|
10136
10218
|
} else {
|
|
10137
10219
|
return {
|
|
10138
|
-
oldPos:
|
|
10220
|
+
oldPos: path39.oldPos + oldPosInc,
|
|
10139
10221
|
lastComponent: {
|
|
10140
10222
|
count: 1,
|
|
10141
10223
|
added,
|
|
@@ -10566,10 +10648,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10566
10648
|
const memories = raw.map((m2) => {
|
|
10567
10649
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10568
10650
|
const rec = m2;
|
|
10569
|
-
const
|
|
10651
|
+
const path39 = typeof rec.path === "string" ? rec.path : null;
|
|
10570
10652
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10571
|
-
if (!
|
|
10572
|
-
const entry = { path:
|
|
10653
|
+
if (!path39 || content == null) return null;
|
|
10654
|
+
const entry = { path: path39, content };
|
|
10573
10655
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10574
10656
|
return entry;
|
|
10575
10657
|
}).filter((m2) => m2 !== null);
|
|
@@ -11395,10 +11477,10 @@ function parseAttachment(obj) {
|
|
|
11395
11477
|
const memories = raw.map((m2) => {
|
|
11396
11478
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11397
11479
|
const rec = m2;
|
|
11398
|
-
const
|
|
11480
|
+
const path39 = typeof rec.path === "string" ? rec.path : null;
|
|
11399
11481
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11400
|
-
if (!
|
|
11401
|
-
const out = { path:
|
|
11482
|
+
if (!path39 || content == null) return null;
|
|
11483
|
+
const out = { path: path39, content };
|
|
11402
11484
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11403
11485
|
return out;
|
|
11404
11486
|
}).filter((m2) => m2 !== null);
|
|
@@ -18897,7 +18979,7 @@ var require_websocket = __commonJS({
|
|
|
18897
18979
|
var http2 = require("http");
|
|
18898
18980
|
var net = require("net");
|
|
18899
18981
|
var tls = require("tls");
|
|
18900
|
-
var { randomBytes: randomBytes3, createHash:
|
|
18982
|
+
var { randomBytes: randomBytes3, createHash: createHash3 } = require("crypto");
|
|
18901
18983
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
18902
18984
|
var { URL: URL2 } = require("url");
|
|
18903
18985
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -19557,7 +19639,7 @@ var require_websocket = __commonJS({
|
|
|
19557
19639
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
19558
19640
|
return;
|
|
19559
19641
|
}
|
|
19560
|
-
const digest =
|
|
19642
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
19561
19643
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
19562
19644
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
19563
19645
|
return;
|
|
@@ -19924,7 +20006,7 @@ var require_websocket_server = __commonJS({
|
|
|
19924
20006
|
var EventEmitter2 = require("events");
|
|
19925
20007
|
var http2 = require("http");
|
|
19926
20008
|
var { Duplex } = require("stream");
|
|
19927
|
-
var { createHash:
|
|
20009
|
+
var { createHash: createHash3 } = require("crypto");
|
|
19928
20010
|
var extension2 = require_extension();
|
|
19929
20011
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
19930
20012
|
var subprotocol2 = require_subprotocol();
|
|
@@ -20225,7 +20307,7 @@ var require_websocket_server = __commonJS({
|
|
|
20225
20307
|
);
|
|
20226
20308
|
}
|
|
20227
20309
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
20228
|
-
const digest =
|
|
20310
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
20229
20311
|
const headers = [
|
|
20230
20312
|
"HTTP/1.1 101 Switching Protocols",
|
|
20231
20313
|
"Upgrade: websocket",
|
|
@@ -22551,7 +22633,7 @@ var SessionManager = class {
|
|
|
22551
22633
|
}
|
|
22552
22634
|
}
|
|
22553
22635
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
22554
|
-
create(args) {
|
|
22636
|
+
create(args, creatorPrincipalId) {
|
|
22555
22637
|
let cwd;
|
|
22556
22638
|
if (args.ownerPersonaId) {
|
|
22557
22639
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -22592,6 +22674,7 @@ var SessionManager = class {
|
|
|
22592
22674
|
iconKey: args.iconKey,
|
|
22593
22675
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
22594
22676
|
ownerPersonaId: args.ownerPersonaId,
|
|
22677
|
+
creatorPrincipalId,
|
|
22595
22678
|
createdAt: iso,
|
|
22596
22679
|
updatedAt: iso
|
|
22597
22680
|
};
|
|
@@ -23536,6 +23619,10 @@ var PersonaStore = class {
|
|
|
23536
23619
|
const p2 = this.metaPath(personaId);
|
|
23537
23620
|
if (!fs6.existsSync(p2)) return null;
|
|
23538
23621
|
const raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
|
|
23622
|
+
if (raw && typeof raw === "object" && "tokenMap" in raw) {
|
|
23623
|
+
delete raw.tokenMap;
|
|
23624
|
+
this.atomicWrite(p2, JSON.stringify(raw, null, 2));
|
|
23625
|
+
}
|
|
23539
23626
|
return PersonaFileSchema.parse(raw);
|
|
23540
23627
|
}
|
|
23541
23628
|
has(personaId) {
|
|
@@ -23591,12 +23678,19 @@ var PersonaRegistry = class {
|
|
|
23591
23678
|
}
|
|
23592
23679
|
store;
|
|
23593
23680
|
cache = /* @__PURE__ */ new Map();
|
|
23594
|
-
/**
|
|
23681
|
+
/**
|
|
23682
|
+
* 从 store 全量重建缓存(boot 时 + 测试 setup 时)。
|
|
23683
|
+
* 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
|
|
23684
|
+
* try/catch 隔离,让其它 persona 仍能加载。
|
|
23685
|
+
*/
|
|
23595
23686
|
reload() {
|
|
23596
23687
|
this.cache.clear();
|
|
23597
23688
|
for (const id of this.store.list()) {
|
|
23598
|
-
|
|
23599
|
-
|
|
23689
|
+
try {
|
|
23690
|
+
const p2 = this.store.read(id);
|
|
23691
|
+
if (p2) this.cache.set(p2.personaId, p2);
|
|
23692
|
+
} catch {
|
|
23693
|
+
}
|
|
23600
23694
|
}
|
|
23601
23695
|
}
|
|
23602
23696
|
get(personaId) {
|
|
@@ -23612,21 +23706,6 @@ var PersonaRegistry = class {
|
|
|
23612
23706
|
list() {
|
|
23613
23707
|
return Array.from(this.cache.values());
|
|
23614
23708
|
}
|
|
23615
|
-
/**
|
|
23616
|
-
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23617
|
-
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
23618
|
-
* persona 数量通常 < 100,性能可忽略。
|
|
23619
|
-
*/
|
|
23620
|
-
findByToken(token) {
|
|
23621
|
-
if (!token) return null;
|
|
23622
|
-
for (const persona of this.cache.values()) {
|
|
23623
|
-
if (!persona.public) continue;
|
|
23624
|
-
const entry = persona.tokenMap?.[token];
|
|
23625
|
-
if (!entry || entry.revoked) continue;
|
|
23626
|
-
return { personaId: persona.personaId, label: entry.label };
|
|
23627
|
-
}
|
|
23628
|
-
return null;
|
|
23629
|
-
}
|
|
23630
23709
|
};
|
|
23631
23710
|
|
|
23632
23711
|
// src/persona/manager.ts
|
|
@@ -23920,9 +23999,6 @@ var PersonaManager = class {
|
|
|
23920
23999
|
model: args.model,
|
|
23921
24000
|
public: args.public ?? false,
|
|
23922
24001
|
iconKey: args.iconKey,
|
|
23923
|
-
// 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
|
|
23924
|
-
// file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
|
|
23925
|
-
tokenMap: {},
|
|
23926
24002
|
createdAt: now,
|
|
23927
24003
|
updatedAt: now
|
|
23928
24004
|
};
|
|
@@ -23981,46 +24057,6 @@ var PersonaManager = class {
|
|
|
23981
24057
|
this.deps.store.remove(personaId);
|
|
23982
24058
|
this.deps.registry.remove(personaId);
|
|
23983
24059
|
}
|
|
23984
|
-
/**
|
|
23985
|
-
* 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
|
|
23986
|
-
* 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
|
|
23987
|
-
*/
|
|
23988
|
-
issueToken(personaId, label) {
|
|
23989
|
-
const existing = this.deps.registry.get(personaId);
|
|
23990
|
-
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23991
|
-
const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
23992
|
-
const entry = {
|
|
23993
|
-
label,
|
|
23994
|
-
issuedAt: Date.now(),
|
|
23995
|
-
revoked: false
|
|
23996
|
-
};
|
|
23997
|
-
const updated = {
|
|
23998
|
-
...existing,
|
|
23999
|
-
tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
|
|
24000
|
-
updatedAt: Date.now()
|
|
24001
|
-
};
|
|
24002
|
-
this.deps.store.writeMeta(updated);
|
|
24003
|
-
this.deps.registry.set(updated);
|
|
24004
|
-
return { token, persona: updated };
|
|
24005
|
-
}
|
|
24006
|
-
/** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
|
|
24007
|
-
revokeToken(personaId, token) {
|
|
24008
|
-
const existing = this.deps.registry.get(personaId);
|
|
24009
|
-
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
24010
|
-
const tokenEntry = existing.tokenMap?.[token];
|
|
24011
|
-
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
24012
|
-
const updated = {
|
|
24013
|
-
...existing,
|
|
24014
|
-
tokenMap: {
|
|
24015
|
-
...existing.tokenMap ?? {},
|
|
24016
|
-
[token]: { ...tokenEntry, revoked: true }
|
|
24017
|
-
},
|
|
24018
|
-
updatedAt: Date.now()
|
|
24019
|
-
};
|
|
24020
|
-
this.deps.store.writeMeta(updated);
|
|
24021
|
-
this.deps.registry.set(updated);
|
|
24022
|
-
return updated;
|
|
24023
|
-
}
|
|
24024
24060
|
/**
|
|
24025
24061
|
* label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
|
|
24026
24062
|
* base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
|
|
@@ -24126,7 +24162,6 @@ function seedDefaultPersonas(args) {
|
|
|
24126
24162
|
model: entry.model,
|
|
24127
24163
|
public: entry.public,
|
|
24128
24164
|
iconKey: entry.iconKey,
|
|
24129
|
-
tokenMap: {},
|
|
24130
24165
|
createdAt: now,
|
|
24131
24166
|
updatedAt: now
|
|
24132
24167
|
};
|
|
@@ -25615,7 +25650,7 @@ var LocalWsServer = class {
|
|
|
25615
25650
|
}
|
|
25616
25651
|
}
|
|
25617
25652
|
// Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
|
|
25618
|
-
// 用于 capability:tokenIssued / capability:
|
|
25653
|
+
// 用于 capability:tokenIssued / capability:tokenDeleted 等 owner-only push frame——
|
|
25619
25654
|
// guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
|
|
25620
25655
|
broadcastToOwners(frame) {
|
|
25621
25656
|
const gate = this.opts.authGate;
|
|
@@ -25625,12 +25660,27 @@ var LocalWsServer = class {
|
|
|
25625
25660
|
this.safeSend(c.ws, frame);
|
|
25626
25661
|
}
|
|
25627
25662
|
}
|
|
25663
|
+
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
25664
|
+
// - 所有 owner ws (owner 看自家所有 channel)
|
|
25665
|
+
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
25666
|
+
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
25667
|
+
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
25668
|
+
this.broadcastToOwners(frame);
|
|
25669
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25670
|
+
if (!set) return;
|
|
25671
|
+
for (const id of set) {
|
|
25672
|
+
const c = this.clients.get(id);
|
|
25673
|
+
if (!c) continue;
|
|
25674
|
+
this.safeSend(c.ws, frame);
|
|
25675
|
+
}
|
|
25676
|
+
}
|
|
25628
25677
|
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
25629
|
-
// principalId ===
|
|
25630
|
-
// principalId === '
|
|
25678
|
+
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
25679
|
+
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
25680
|
+
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
25631
25681
|
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
25632
25682
|
broadcastToPrincipal(principalId, frame) {
|
|
25633
|
-
if (principalId === "owner") {
|
|
25683
|
+
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
25634
25684
|
this.broadcastToOwners(frame);
|
|
25635
25685
|
return;
|
|
25636
25686
|
}
|
|
@@ -25793,6 +25843,11 @@ var LocalWsServer = class {
|
|
|
25793
25843
|
return;
|
|
25794
25844
|
}
|
|
25795
25845
|
} else if (frame.type === "auth") {
|
|
25846
|
+
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
25847
|
+
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
25848
|
+
const guestCtx = this.opts.tryVerifyCapabilityToken(token);
|
|
25849
|
+
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
25850
|
+
}
|
|
25796
25851
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
25797
25852
|
return;
|
|
25798
25853
|
}
|
|
@@ -25911,7 +25966,7 @@ var AuthGate = class {
|
|
|
25911
25966
|
}
|
|
25912
25967
|
let ctx = null;
|
|
25913
25968
|
if (this.opts.authenticate) {
|
|
25914
|
-
const r = this.opts.authenticate(parsed.data.token);
|
|
25969
|
+
const r = this.opts.authenticate(parsed.data.token, parsed.data.selfPrincipalId);
|
|
25915
25970
|
if (!r.ok) {
|
|
25916
25971
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
25917
25972
|
this.markFailed(handle.id);
|
|
@@ -25973,7 +26028,7 @@ var AuthContextResolver = class {
|
|
|
25973
26028
|
}
|
|
25974
26029
|
opts;
|
|
25975
26030
|
/**
|
|
25976
|
-
* 从 `Authorization` 头解析。null = 无凭证 /
|
|
26031
|
+
* 从 `Authorization` 头解析。null = 无凭证 / 不识别,调用方一律 401。
|
|
25977
26032
|
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
25978
26033
|
*/
|
|
25979
26034
|
resolveFromHeader(authHeader, remoteAddress) {
|
|
@@ -25983,15 +26038,6 @@ var AuthContextResolver = class {
|
|
|
25983
26038
|
if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
|
|
25984
26039
|
return { role: "owner", isLoopback };
|
|
25985
26040
|
}
|
|
25986
|
-
const personalHit = this.opts.personaRegistry.findByToken(token);
|
|
25987
|
-
if (personalHit) {
|
|
25988
|
-
return {
|
|
25989
|
-
role: "personal",
|
|
25990
|
-
personaId: personalHit.personaId,
|
|
25991
|
-
label: personalHit.label,
|
|
25992
|
-
isLoopback
|
|
25993
|
-
};
|
|
25994
|
-
}
|
|
25995
26041
|
return null;
|
|
25996
26042
|
}
|
|
25997
26043
|
};
|
|
@@ -26017,9 +26063,9 @@ function constantTimeEqual2(a, b2) {
|
|
|
26017
26063
|
init_runtime();
|
|
26018
26064
|
|
|
26019
26065
|
// src/transport/connection-context.ts
|
|
26020
|
-
function ownerContext() {
|
|
26066
|
+
function ownerContext(ownerPrincipalId, displayName) {
|
|
26021
26067
|
return {
|
|
26022
|
-
principal:
|
|
26068
|
+
principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
|
|
26023
26069
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
26024
26070
|
};
|
|
26025
26071
|
}
|
|
@@ -26032,7 +26078,9 @@ function guestContext(cap) {
|
|
|
26032
26078
|
}
|
|
26033
26079
|
function authenticate(token, deps) {
|
|
26034
26080
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
26035
|
-
if (deps.isOwnerToken(token))
|
|
26081
|
+
if (deps.isOwnerToken(token)) {
|
|
26082
|
+
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
26083
|
+
}
|
|
26036
26084
|
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
26037
26085
|
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
26038
26086
|
if (v2.ok) return { ok: true, context: guestContext(v2.capability) };
|
|
@@ -26137,7 +26185,6 @@ var CapabilityRegistry = class {
|
|
|
26137
26185
|
const hash = sha256Hex(token);
|
|
26138
26186
|
const cap = this.bySecretHash.get(hash);
|
|
26139
26187
|
if (!cap) return { ok: false, code: "TOKEN_INVALID" };
|
|
26140
|
-
if (cap.revokedAt) return { ok: false, code: "TOKEN_REVOKED" };
|
|
26141
26188
|
if (cap.expiresAt && this.now() >= cap.expiresAt) {
|
|
26142
26189
|
return { ok: false, code: "TOKEN_EXPIRED" };
|
|
26143
26190
|
}
|
|
@@ -26153,18 +26200,37 @@ var CapabilityRegistry = class {
|
|
|
26153
26200
|
this.bySecretHash.set(cap.secretHash, cap);
|
|
26154
26201
|
this.store.upsert(cap);
|
|
26155
26202
|
}
|
|
26156
|
-
|
|
26203
|
+
/**
|
|
26204
|
+
* Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
|
|
26205
|
+
* 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
|
|
26206
|
+
* idempotent:不存在 → null。
|
|
26207
|
+
*/
|
|
26208
|
+
delete(id) {
|
|
26157
26209
|
const current = this.store.list().find((c) => c.id === id);
|
|
26158
26210
|
if (!current) return null;
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
26162
|
-
this.store.upsert(updated);
|
|
26163
|
-
return updated;
|
|
26211
|
+
this.bySecretHash.delete(current.secretHash);
|
|
26212
|
+
this.store.remove(id);
|
|
26213
|
+
return current;
|
|
26164
26214
|
}
|
|
26165
26215
|
findById(id) {
|
|
26166
26216
|
return this.store.list().find((c) => c.id === id) ?? null;
|
|
26167
26217
|
}
|
|
26218
|
+
/**
|
|
26219
|
+
* Person identity Phase 1 (B11): guest authenticate 上报 selfPrincipalId 时
|
|
26220
|
+
* daemon 把它落到 cap.linkedPrincipalId(之前 undefined)。设计上 immutable:
|
|
26221
|
+
* 只允许 undefined → set;已设置后再调本方法 noop(reject 留给 caller 判断)。
|
|
26222
|
+
*
|
|
26223
|
+
* 返回 true = 实际写入;false = 不存在 / 已设置过(caller 用此区分用户行为)。
|
|
26224
|
+
*/
|
|
26225
|
+
updateLinkedPrincipalId(id, principalId) {
|
|
26226
|
+
const current = this.store.list().find((c) => c.id === id);
|
|
26227
|
+
if (!current) return false;
|
|
26228
|
+
if (current.linkedPrincipalId !== void 0) return false;
|
|
26229
|
+
const updated = { ...current, linkedPrincipalId: principalId };
|
|
26230
|
+
this.bySecretHash.set(updated.secretHash, updated);
|
|
26231
|
+
this.store.upsert(updated);
|
|
26232
|
+
return true;
|
|
26233
|
+
}
|
|
26168
26234
|
};
|
|
26169
26235
|
function sha256Hex(s) {
|
|
26170
26236
|
return crypto4.createHash("sha256").update(s).digest("hex");
|
|
@@ -26205,20 +26271,38 @@ var CapabilityManager = class {
|
|
|
26205
26271
|
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
26206
26272
|
};
|
|
26207
26273
|
this.registry.upsertCapability(cap);
|
|
26274
|
+
if (args.atomicAfterPersist) {
|
|
26275
|
+
try {
|
|
26276
|
+
args.atomicAfterPersist(cap);
|
|
26277
|
+
} catch (err) {
|
|
26278
|
+
try {
|
|
26279
|
+
this.registry.delete(cap.id);
|
|
26280
|
+
} catch {
|
|
26281
|
+
}
|
|
26282
|
+
throw err;
|
|
26283
|
+
}
|
|
26284
|
+
}
|
|
26208
26285
|
this.hooks.onIssued?.(cap, token);
|
|
26209
26286
|
return { token, capability: cap };
|
|
26210
26287
|
}
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26214
|
-
|
|
26215
|
-
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26220
|
-
|
|
26221
|
-
|
|
26288
|
+
/**
|
|
26289
|
+
* Hard delete:从 registry / store 物理移除 capability。
|
|
26290
|
+
* idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
|
|
26291
|
+
* onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
|
|
26292
|
+
*/
|
|
26293
|
+
delete(id) {
|
|
26294
|
+
const removed = this.registry.delete(id);
|
|
26295
|
+
if (!removed) return null;
|
|
26296
|
+
this.hooks.onDeleted?.(removed);
|
|
26297
|
+
return { capability: removed };
|
|
26298
|
+
}
|
|
26299
|
+
/**
|
|
26300
|
+
* Person identity Phase 1 (B11) passthrough:guest authenticate 上报
|
|
26301
|
+
* selfPrincipalId 时升级 cap.linkedPrincipalId(一次性,undefined → set)。
|
|
26302
|
+
* 详见 CapabilityRegistry.updateLinkedPrincipalId JSDoc。
|
|
26303
|
+
*/
|
|
26304
|
+
updateLinkedPrincipalId(id, principalId) {
|
|
26305
|
+
return this.registry.updateLinkedPrincipalId(id, principalId);
|
|
26222
26306
|
}
|
|
26223
26307
|
};
|
|
26224
26308
|
function defaultGenerateToken() {
|
|
@@ -26250,15 +26334,20 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
26250
26334
|
// src/inbox/inbox-store.ts
|
|
26251
26335
|
var fs17 = __toESM(require("fs"), 1);
|
|
26252
26336
|
var path19 = __toESM(require("path"), 1);
|
|
26253
|
-
var
|
|
26337
|
+
var INBOX_SUBDIR = "inbox";
|
|
26254
26338
|
var InboxStore = class {
|
|
26255
26339
|
constructor(dataDir) {
|
|
26256
26340
|
this.dataDir = dataDir;
|
|
26257
|
-
fs17.mkdirSync(
|
|
26341
|
+
fs17.mkdirSync(this.dirPath(), { recursive: true });
|
|
26258
26342
|
}
|
|
26259
26343
|
dataDir;
|
|
26260
|
-
|
|
26261
|
-
|
|
26344
|
+
/**
|
|
26345
|
+
* 列出某条 channel 的消息, 按 createdAt 升序.
|
|
26346
|
+
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
26347
|
+
* channel 文件不存在时返回 [] (不抛, 不创建).
|
|
26348
|
+
*/
|
|
26349
|
+
list(capabilityId, sinceCreatedAt) {
|
|
26350
|
+
const file = this.filePath(capabilityId);
|
|
26262
26351
|
let raw;
|
|
26263
26352
|
try {
|
|
26264
26353
|
raw = fs17.readFileSync(file, "utf8");
|
|
@@ -26266,11 +26355,28 @@ var InboxStore = class {
|
|
|
26266
26355
|
if (err?.code === "ENOENT") return [];
|
|
26267
26356
|
return [];
|
|
26268
26357
|
}
|
|
26269
|
-
|
|
26358
|
+
const all = parseAllLines(raw);
|
|
26359
|
+
if (sinceCreatedAt === void 0) return all;
|
|
26360
|
+
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
26270
26361
|
}
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26362
|
+
/**
|
|
26363
|
+
* 列出所有 channel 的 capabilityId (扫目录所有 *.jsonl 文件名).
|
|
26364
|
+
* 用于 daemon 启动时构建 index (跨 cap 聚合 unread 计数等).
|
|
26365
|
+
*/
|
|
26366
|
+
listAllCapabilityIds() {
|
|
26367
|
+
const dir = this.dirPath();
|
|
26368
|
+
let entries;
|
|
26369
|
+
try {
|
|
26370
|
+
entries = fs17.readdirSync(dir);
|
|
26371
|
+
} catch (err) {
|
|
26372
|
+
if (err?.code === "ENOENT") return [];
|
|
26373
|
+
return [];
|
|
26374
|
+
}
|
|
26375
|
+
return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
|
|
26376
|
+
}
|
|
26377
|
+
append(message) {
|
|
26378
|
+
const file = this.filePath(message.capabilityId);
|
|
26379
|
+
const line = JSON.stringify(message) + "\n";
|
|
26274
26380
|
fs17.appendFileSync(file, line, { mode: 384 });
|
|
26275
26381
|
try {
|
|
26276
26382
|
fs17.chmodSync(file, 384);
|
|
@@ -26278,25 +26384,40 @@ var InboxStore = class {
|
|
|
26278
26384
|
}
|
|
26279
26385
|
}
|
|
26280
26386
|
/**
|
|
26281
|
-
*
|
|
26282
|
-
*
|
|
26387
|
+
* 把该 channel 所有 createdAt<=upToCreatedAt 且 sender!=principalId 的消息
|
|
26388
|
+
* readBy[principalId] 写成 upToCreatedAt. O(N) rewrite 整个 channel 文件.
|
|
26389
|
+
* 返写入了几条 (用于测试断言 / index 维护).
|
|
26283
26390
|
*/
|
|
26284
|
-
markRead(
|
|
26285
|
-
const
|
|
26286
|
-
let changed =
|
|
26287
|
-
const next =
|
|
26288
|
-
if (
|
|
26289
|
-
if (
|
|
26290
|
-
|
|
26291
|
-
|
|
26391
|
+
markRead(capabilityId, principalId, upToCreatedAt) {
|
|
26392
|
+
const messages = this.list(capabilityId);
|
|
26393
|
+
let changed = 0;
|
|
26394
|
+
const next = messages.map((m2) => {
|
|
26395
|
+
if (m2.senderPrincipalId === principalId) return m2;
|
|
26396
|
+
if (m2.createdAt > upToCreatedAt) return m2;
|
|
26397
|
+
const cur = m2.readBy[principalId];
|
|
26398
|
+
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
26399
|
+
changed++;
|
|
26400
|
+
return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
|
|
26292
26401
|
});
|
|
26293
|
-
if (
|
|
26294
|
-
this.
|
|
26402
|
+
if (changed === 0) return 0;
|
|
26403
|
+
this.rewriteChannel(capabilityId, next);
|
|
26404
|
+
return changed;
|
|
26295
26405
|
}
|
|
26296
|
-
|
|
26297
|
-
|
|
26406
|
+
/**
|
|
26407
|
+
* 删整条 channel (capability:delete cascade). 文件不存在 no-op.
|
|
26408
|
+
*/
|
|
26409
|
+
removeByCapabilityId(capabilityId) {
|
|
26410
|
+
const file = this.filePath(capabilityId);
|
|
26411
|
+
try {
|
|
26412
|
+
fs17.unlinkSync(file);
|
|
26413
|
+
} catch (err) {
|
|
26414
|
+
if (err?.code === "ENOENT") return;
|
|
26415
|
+
}
|
|
26416
|
+
}
|
|
26417
|
+
rewriteChannel(capabilityId, messages) {
|
|
26418
|
+
const file = this.filePath(capabilityId);
|
|
26298
26419
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26299
|
-
const content =
|
|
26420
|
+
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
26300
26421
|
fs17.writeFileSync(tmp, content, { mode: 384 });
|
|
26301
26422
|
fs17.renameSync(tmp, file);
|
|
26302
26423
|
try {
|
|
@@ -26304,8 +26425,11 @@ var InboxStore = class {
|
|
|
26304
26425
|
} catch {
|
|
26305
26426
|
}
|
|
26306
26427
|
}
|
|
26307
|
-
|
|
26308
|
-
return path19.join(this.dataDir,
|
|
26428
|
+
dirPath() {
|
|
26429
|
+
return path19.join(this.dataDir, INBOX_SUBDIR);
|
|
26430
|
+
}
|
|
26431
|
+
filePath(capabilityId) {
|
|
26432
|
+
return path19.join(this.dirPath(), `${capabilityId}.jsonl`);
|
|
26309
26433
|
}
|
|
26310
26434
|
};
|
|
26311
26435
|
function parseAllLines(raw) {
|
|
@@ -26319,23 +26443,14 @@ function parseAllLines(raw) {
|
|
|
26319
26443
|
} catch {
|
|
26320
26444
|
continue;
|
|
26321
26445
|
}
|
|
26322
|
-
const r =
|
|
26446
|
+
const r = InboxMessageSchema.safeParse(parsed);
|
|
26323
26447
|
if (r.success) out.push(r.data);
|
|
26324
26448
|
}
|
|
26325
26449
|
return out;
|
|
26326
26450
|
}
|
|
26327
26451
|
|
|
26328
26452
|
// src/inbox/inbox-manager.ts
|
|
26329
|
-
var crypto7 = __toESM(require("crypto"), 1);
|
|
26330
|
-
|
|
26331
|
-
// src/inbox/dm.ts
|
|
26332
26453
|
var crypto6 = __toESM(require("crypto"), 1);
|
|
26333
|
-
function deriveDmThreadId(idA, idB) {
|
|
26334
|
-
const [low, high] = idA < idB ? [idA, idB] : [idB, idA];
|
|
26335
|
-
return crypto6.createHash("sha256").update(`${low}/${high}`).digest("hex");
|
|
26336
|
-
}
|
|
26337
|
-
|
|
26338
|
-
// src/inbox/inbox-manager.ts
|
|
26339
26454
|
var InboxManager = class {
|
|
26340
26455
|
constructor(store, broadcast, opts = {}) {
|
|
26341
26456
|
this.store = store;
|
|
@@ -26348,64 +26463,51 @@ var InboxManager = class {
|
|
|
26348
26463
|
now;
|
|
26349
26464
|
genId;
|
|
26350
26465
|
/**
|
|
26351
|
-
*
|
|
26352
|
-
*
|
|
26353
|
-
*/
|
|
26354
|
-
recordPersonaMention(args) {
|
|
26355
|
-
if (args.sender.kind === "owner") return null;
|
|
26356
|
-
const resource = { type: "persona", id: args.personaId };
|
|
26357
|
-
const ev = {
|
|
26358
|
-
id: this.genId(),
|
|
26359
|
-
kind: "persona-mention",
|
|
26360
|
-
fromPrincipal: args.sender,
|
|
26361
|
-
toPrincipal: OWNER_PRINCIPAL,
|
|
26362
|
-
resource,
|
|
26363
|
-
preview: truncatePreview(args.preview),
|
|
26364
|
-
createdAt: this.now()
|
|
26365
|
-
};
|
|
26366
|
-
this.store.append(ev);
|
|
26367
|
-
this.broadcast({ type: "inbox:event", event: ev });
|
|
26368
|
-
return ev;
|
|
26369
|
-
}
|
|
26370
|
-
/**
|
|
26371
|
-
* Phase 4 Task 4.2: DM 私聊事件. 与 recordPersonaMention 区别:
|
|
26372
|
-
* - kind = 'direct-message' (不是 persona-mention)
|
|
26373
|
-
* - 不带 resource (DM 不挂某个 persona)
|
|
26374
|
-
* - threadId 由 from.id + to.id 派生 (sha256), 双向消息共享同一 thread
|
|
26375
|
-
* - 调用方 (handler) 已校验 sender = ctx.principal (防伪造)
|
|
26376
|
-
*
|
|
26377
|
-
* 返回值是 InboxEvent (含派生 threadId), daemon/index.ts 装配 broadcast 路由:
|
|
26378
|
-
* broadcastToPrincipal(from.id) + broadcastToPrincipal(to.id) (双侧 ws 都收).
|
|
26466
|
+
* 写入一条消息到 channel + broadcast 给该 channel 两端 ws.
|
|
26467
|
+
* caller 必须已通过 handler 鉴权 (guest ctx.capabilityId === args.capabilityId, 否则拦截).
|
|
26379
26468
|
*/
|
|
26380
|
-
|
|
26381
|
-
const
|
|
26469
|
+
postMessage(args) {
|
|
26470
|
+
const msg = {
|
|
26382
26471
|
id: this.genId(),
|
|
26383
|
-
|
|
26384
|
-
|
|
26385
|
-
|
|
26386
|
-
preview: truncatePreview(args.text),
|
|
26472
|
+
capabilityId: args.capabilityId,
|
|
26473
|
+
senderPrincipalId: args.senderPrincipalId,
|
|
26474
|
+
text: args.text,
|
|
26387
26475
|
createdAt: this.now(),
|
|
26388
|
-
|
|
26476
|
+
readBy: {}
|
|
26389
26477
|
};
|
|
26390
|
-
this.store.append(
|
|
26391
|
-
this.broadcast({ type: "inbox:event",
|
|
26392
|
-
return
|
|
26478
|
+
this.store.append(msg);
|
|
26479
|
+
this.broadcast(args.capabilityId, { type: "inbox:event", message: msg });
|
|
26480
|
+
return msg;
|
|
26393
26481
|
}
|
|
26394
|
-
list(
|
|
26395
|
-
|
|
26396
|
-
if (opts.includeRead) return all;
|
|
26397
|
-
return all.filter((e) => e.readAt === void 0);
|
|
26482
|
+
list(capabilityId, sinceCreatedAt) {
|
|
26483
|
+
return this.store.list(capabilityId, sinceCreatedAt);
|
|
26398
26484
|
}
|
|
26399
|
-
|
|
26400
|
-
|
|
26485
|
+
/**
|
|
26486
|
+
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
26487
|
+
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道),
|
|
26488
|
+
* 让对端 UI 实时看到 "对方已读到哪了".
|
|
26489
|
+
*/
|
|
26490
|
+
markRead(args) {
|
|
26491
|
+
const before = this.store.list(args.capabilityId);
|
|
26492
|
+
const changed = this.store.markRead(args.capabilityId, args.principalId, args.upToCreatedAt);
|
|
26493
|
+
if (changed === 0) return 0;
|
|
26494
|
+
const after = this.store.list(args.capabilityId);
|
|
26495
|
+
const beforeById = new Map(before.map((m2) => [m2.id, m2]));
|
|
26496
|
+
for (const m2 of after) {
|
|
26497
|
+
const prev = beforeById.get(m2.id);
|
|
26498
|
+
if (!prev) continue;
|
|
26499
|
+
if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
|
|
26500
|
+
this.broadcast(args.capabilityId, { type: "inbox:event", message: m2 });
|
|
26501
|
+
}
|
|
26502
|
+
return changed;
|
|
26503
|
+
}
|
|
26504
|
+
/** capability:delete cascade. 删整个 channel 文件. */
|
|
26505
|
+
removeChannel(capabilityId) {
|
|
26506
|
+
this.store.removeByCapabilityId(capabilityId);
|
|
26401
26507
|
}
|
|
26402
26508
|
};
|
|
26403
|
-
function truncatePreview(s) {
|
|
26404
|
-
if (s.length <= INBOX_PREVIEW_MAX_LENGTH) return s;
|
|
26405
|
-
return s.slice(0, INBOX_PREVIEW_MAX_LENGTH);
|
|
26406
|
-
}
|
|
26407
26509
|
function defaultGenId() {
|
|
26408
|
-
return "
|
|
26510
|
+
return "m_" + crypto6.randomBytes(6).toString("base64url");
|
|
26409
26511
|
}
|
|
26410
26512
|
|
|
26411
26513
|
// src/remote-persona/store.ts
|
|
@@ -26508,299 +26610,300 @@ var RemotePersonaStore = class {
|
|
|
26508
26610
|
}
|
|
26509
26611
|
};
|
|
26510
26612
|
|
|
26511
|
-
// src/
|
|
26613
|
+
// src/person/store.ts
|
|
26512
26614
|
var fs19 = __toESM(require("fs"), 1);
|
|
26513
26615
|
var path21 = __toESM(require("path"), 1);
|
|
26514
|
-
var
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
const flagPath = path21.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
26520
|
-
if (existsSync4(flagPath)) {
|
|
26521
|
-
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
26616
|
+
var PERSONS_DIR = "persons";
|
|
26617
|
+
var PersonStore = class {
|
|
26618
|
+
constructor(dataDir) {
|
|
26619
|
+
this.dataDir = dataDir;
|
|
26620
|
+
fs19.mkdirSync(this.rootDir(), { recursive: true });
|
|
26522
26621
|
}
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
26529
|
-
if (
|
|
26530
|
-
|
|
26531
|
-
const dst = path21.join(sessionsDir, entry);
|
|
26532
|
-
fs19.renameSync(src, dst);
|
|
26533
|
-
movedBare += 1;
|
|
26622
|
+
dataDir;
|
|
26623
|
+
list() {
|
|
26624
|
+
let entries;
|
|
26625
|
+
try {
|
|
26626
|
+
entries = fs19.readdirSync(this.rootDir());
|
|
26627
|
+
} catch (err) {
|
|
26628
|
+
if (err?.code === "ENOENT") return [];
|
|
26629
|
+
return [];
|
|
26534
26630
|
}
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
for (const file of readdirSafe(ownerSrc)) {
|
|
26546
|
-
if (!file.endsWith(".json")) continue;
|
|
26547
|
-
fs19.renameSync(path21.join(ownerSrc, file), path21.join(ownerDst, file));
|
|
26548
|
-
movedVmOwner += 1;
|
|
26631
|
+
const out = [];
|
|
26632
|
+
for (const name of entries) {
|
|
26633
|
+
if (!name.endsWith(".json")) continue;
|
|
26634
|
+
if (name.includes(".tmp-")) continue;
|
|
26635
|
+
const file = path21.join(this.rootDir(), name);
|
|
26636
|
+
let raw;
|
|
26637
|
+
try {
|
|
26638
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
26639
|
+
} catch {
|
|
26640
|
+
continue;
|
|
26549
26641
|
}
|
|
26550
|
-
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26555
|
-
fs19.mkdirSync(archiveDst, { recursive: true });
|
|
26556
|
-
for (const file of readdirSafe(listenerSrc)) {
|
|
26557
|
-
if (!file.endsWith(".json")) continue;
|
|
26558
|
-
fs19.renameSync(path21.join(listenerSrc, file), path21.join(archiveDst, file));
|
|
26559
|
-
archivedListener += 1;
|
|
26642
|
+
let parsed;
|
|
26643
|
+
try {
|
|
26644
|
+
parsed = JSON.parse(raw);
|
|
26645
|
+
} catch {
|
|
26646
|
+
continue;
|
|
26560
26647
|
}
|
|
26561
|
-
|
|
26648
|
+
const r = PersonSchema.safeParse(parsed);
|
|
26649
|
+
if (r.success) out.push(r.data);
|
|
26562
26650
|
}
|
|
26563
|
-
|
|
26564
|
-
}
|
|
26565
|
-
fs19.mkdirSync(sessionsDir, { recursive: true });
|
|
26566
|
-
fs19.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
26567
|
-
return {
|
|
26568
|
-
skipped: false,
|
|
26569
|
-
flagWritten: true,
|
|
26570
|
-
movedBare,
|
|
26571
|
-
movedVmOwner,
|
|
26572
|
-
archivedListener
|
|
26573
|
-
};
|
|
26574
|
-
}
|
|
26575
|
-
function existsSync4(p2) {
|
|
26576
|
-
try {
|
|
26577
|
-
fs19.statSync(p2);
|
|
26578
|
-
return true;
|
|
26579
|
-
} catch {
|
|
26580
|
-
return false;
|
|
26651
|
+
return out.sort((a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0);
|
|
26581
26652
|
}
|
|
26582
|
-
|
|
26583
|
-
|
|
26584
|
-
|
|
26585
|
-
|
|
26586
|
-
|
|
26587
|
-
|
|
26653
|
+
get(id) {
|
|
26654
|
+
const file = this.filePath(id);
|
|
26655
|
+
let raw;
|
|
26656
|
+
try {
|
|
26657
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
26658
|
+
} catch {
|
|
26659
|
+
return null;
|
|
26660
|
+
}
|
|
26661
|
+
try {
|
|
26662
|
+
const parsed = JSON.parse(raw);
|
|
26663
|
+
const r = PersonSchema.safeParse(parsed);
|
|
26664
|
+
return r.success ? r.data : null;
|
|
26665
|
+
} catch {
|
|
26666
|
+
return null;
|
|
26667
|
+
}
|
|
26588
26668
|
}
|
|
26589
|
-
|
|
26590
|
-
|
|
26591
|
-
|
|
26592
|
-
|
|
26593
|
-
|
|
26594
|
-
|
|
26669
|
+
add(person) {
|
|
26670
|
+
const file = this.filePath(person.id);
|
|
26671
|
+
if (fs19.existsSync(file)) {
|
|
26672
|
+
throw new Error(`PersonStore.add: person id already exists: ${person.id}`);
|
|
26673
|
+
}
|
|
26674
|
+
this.atomicWrite(file, person);
|
|
26595
26675
|
}
|
|
26596
|
-
|
|
26597
|
-
|
|
26598
|
-
|
|
26599
|
-
|
|
26600
|
-
|
|
26676
|
+
/**
|
|
26677
|
+
* Merge patch into existing person + bump updatedAt to `now`.
|
|
26678
|
+
* 不存在 id → no-op(caller 应预先 get 校验)。
|
|
26679
|
+
* 只允许 patch displayName / notes / dmEnabled(id / createdAt 不可改)。
|
|
26680
|
+
*/
|
|
26681
|
+
update(id, patch, now) {
|
|
26682
|
+
const cur = this.get(id);
|
|
26683
|
+
if (!cur) return;
|
|
26684
|
+
const next = {
|
|
26685
|
+
...cur,
|
|
26686
|
+
...patch.displayName !== void 0 ? { displayName: patch.displayName } : {},
|
|
26687
|
+
...patch.notes !== void 0 ? { notes: patch.notes } : {},
|
|
26688
|
+
...patch.dmEnabled !== void 0 ? { dmEnabled: patch.dmEnabled } : {},
|
|
26689
|
+
updatedAt: now
|
|
26690
|
+
};
|
|
26691
|
+
this.atomicWrite(this.filePath(id), next);
|
|
26601
26692
|
}
|
|
26602
|
-
|
|
26603
|
-
|
|
26604
|
-
|
|
26605
|
-
|
|
26606
|
-
|
|
26607
|
-
|
|
26608
|
-
|
|
26609
|
-
|
|
26610
|
-
|
|
26611
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
26612
|
-
init_protocol();
|
|
26613
|
-
var GroupFileStore = class {
|
|
26614
|
-
dataDir;
|
|
26615
|
-
logger;
|
|
26616
|
-
cache = /* @__PURE__ */ new Map();
|
|
26617
|
-
constructor(opts) {
|
|
26618
|
-
this.dataDir = opts.dataDir;
|
|
26619
|
-
this.logger = opts.logger;
|
|
26693
|
+
remove(id) {
|
|
26694
|
+
const file = this.filePath(id);
|
|
26695
|
+
try {
|
|
26696
|
+
fs19.unlinkSync(file);
|
|
26697
|
+
return true;
|
|
26698
|
+
} catch (err) {
|
|
26699
|
+
if (err?.code === "ENOENT") return false;
|
|
26700
|
+
throw err;
|
|
26701
|
+
}
|
|
26620
26702
|
}
|
|
26621
|
-
|
|
26622
|
-
return
|
|
26703
|
+
rootDir() {
|
|
26704
|
+
return path21.join(this.dataDir, PERSONS_DIR);
|
|
26623
26705
|
}
|
|
26624
|
-
|
|
26625
|
-
|
|
26626
|
-
return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
26706
|
+
filePath(id) {
|
|
26707
|
+
return path21.join(this.rootDir(), `${safeFileName(id)}.json`);
|
|
26627
26708
|
}
|
|
26628
|
-
|
|
26629
|
-
|
|
26709
|
+
atomicWrite(file, content) {
|
|
26710
|
+
fs19.mkdirSync(this.rootDir(), { recursive: true });
|
|
26711
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26712
|
+
fs19.writeFileSync(tmp, JSON.stringify(content, null, 2), { mode: 384 });
|
|
26713
|
+
fs19.renameSync(tmp, file);
|
|
26714
|
+
try {
|
|
26715
|
+
fs19.chmodSync(file, 384);
|
|
26716
|
+
} catch {
|
|
26717
|
+
}
|
|
26630
26718
|
}
|
|
26631
|
-
|
|
26632
|
-
|
|
26633
|
-
|
|
26719
|
+
};
|
|
26720
|
+
|
|
26721
|
+
// src/person/alias-store.ts
|
|
26722
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
26723
|
+
var path22 = __toESM(require("path"), 1);
|
|
26724
|
+
var PERSON_ALIASES_SUBDIR = "persons-meta";
|
|
26725
|
+
var PERSON_ALIASES_FILE = "aliases.json";
|
|
26726
|
+
var PersonAliasStore = class {
|
|
26727
|
+
constructor(dataDir) {
|
|
26728
|
+
this.dataDir = dataDir;
|
|
26729
|
+
fs20.mkdirSync(path22.join(this.dataDir, PERSON_ALIASES_SUBDIR), { recursive: true });
|
|
26730
|
+
}
|
|
26731
|
+
dataDir;
|
|
26732
|
+
get filePath() {
|
|
26733
|
+
return path22.join(this.dataDir, PERSON_ALIASES_SUBDIR, PERSON_ALIASES_FILE);
|
|
26734
|
+
}
|
|
26735
|
+
list() {
|
|
26736
|
+
let raw;
|
|
26634
26737
|
try {
|
|
26635
|
-
|
|
26636
|
-
const parsed = JSON.parse(raw);
|
|
26637
|
-
if (!Array.isArray(parsed)) {
|
|
26638
|
-
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
26639
|
-
file
|
|
26640
|
-
});
|
|
26641
|
-
return [];
|
|
26642
|
-
}
|
|
26643
|
-
const out = [];
|
|
26644
|
-
for (const entry of parsed) {
|
|
26645
|
-
const r = GroupFileEntrySchema.safeParse(entry);
|
|
26646
|
-
if (r.success) out.push(r.data);
|
|
26647
|
-
}
|
|
26648
|
-
return out;
|
|
26738
|
+
raw = fs20.readFileSync(this.filePath, "utf8");
|
|
26649
26739
|
} catch (err) {
|
|
26650
|
-
|
|
26651
|
-
|
|
26652
|
-
|
|
26653
|
-
|
|
26654
|
-
|
|
26655
|
-
|
|
26740
|
+
if (err?.code === "ENOENT") return [];
|
|
26741
|
+
return [];
|
|
26742
|
+
}
|
|
26743
|
+
let parsed;
|
|
26744
|
+
try {
|
|
26745
|
+
parsed = JSON.parse(raw);
|
|
26746
|
+
} catch {
|
|
26656
26747
|
return [];
|
|
26657
26748
|
}
|
|
26749
|
+
if (!Array.isArray(parsed)) return [];
|
|
26750
|
+
const out = [];
|
|
26751
|
+
for (const item of parsed) {
|
|
26752
|
+
const r = PersonAliasSchema.safeParse(item);
|
|
26753
|
+
if (r.success) out.push(r.data);
|
|
26754
|
+
}
|
|
26755
|
+
return out;
|
|
26658
26756
|
}
|
|
26659
|
-
|
|
26660
|
-
|
|
26661
|
-
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
|
|
26662
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
26663
|
-
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
26664
|
-
import_node_fs13.default.renameSync(tmp, file);
|
|
26757
|
+
get(principalId) {
|
|
26758
|
+
return this.list().find((a) => a.principalId === principalId)?.personId;
|
|
26665
26759
|
}
|
|
26666
|
-
|
|
26667
|
-
|
|
26668
|
-
|
|
26669
|
-
|
|
26670
|
-
|
|
26671
|
-
|
|
26672
|
-
this.
|
|
26673
|
-
|
|
26760
|
+
set(principalId, personId) {
|
|
26761
|
+
const all = this.list().filter((a) => a.principalId !== principalId);
|
|
26762
|
+
all.push({ principalId, personId });
|
|
26763
|
+
this.write(all);
|
|
26764
|
+
}
|
|
26765
|
+
remove(principalId) {
|
|
26766
|
+
const all = this.list();
|
|
26767
|
+
const next = all.filter((a) => a.principalId !== principalId);
|
|
26768
|
+
if (next.length === all.length) return false;
|
|
26769
|
+
this.write(next);
|
|
26770
|
+
return true;
|
|
26771
|
+
}
|
|
26772
|
+
removeAllByPerson(personId) {
|
|
26773
|
+
const all = this.list();
|
|
26774
|
+
const next = all.filter((a) => a.personId !== personId);
|
|
26775
|
+
if (next.length === all.length) return;
|
|
26776
|
+
this.write(next);
|
|
26674
26777
|
}
|
|
26675
26778
|
/**
|
|
26676
|
-
*
|
|
26677
|
-
*
|
|
26678
|
-
*
|
|
26679
|
-
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
26779
|
+
* 把某条 alias 的 principalId 改为新 key,personId 不变。
|
|
26780
|
+
* 用于 guest 第一次 authenticate 上报 selfPrincipalId 时,把 alias key 从
|
|
26781
|
+
* cap.id(fallback)升级到真实 ownerPrincipalId。
|
|
26680
26782
|
*
|
|
26681
|
-
*
|
|
26783
|
+
* 若 oldKey 不存在 → no-op。
|
|
26784
|
+
* 若 newKey 已存在另一条 entry → 用 oldKey 的 personId 覆盖("old wins"),并去重。
|
|
26682
26785
|
*/
|
|
26683
|
-
|
|
26684
|
-
const
|
|
26685
|
-
const
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26689
|
-
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
26694
|
-
|
|
26695
|
-
|
|
26696
|
-
|
|
26697
|
-
|
|
26698
|
-
|
|
26699
|
-
|
|
26700
|
-
|
|
26701
|
-
relPath: input.relPath,
|
|
26702
|
-
from: input.from,
|
|
26703
|
-
label: input.label,
|
|
26704
|
-
size: input.size,
|
|
26705
|
-
mime: input.mime,
|
|
26706
|
-
addedAt: now
|
|
26707
|
-
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
26708
|
-
};
|
|
26709
|
-
entries.push(next);
|
|
26786
|
+
rekey(oldPrincipalId, newPrincipalId) {
|
|
26787
|
+
const all = this.list();
|
|
26788
|
+
const old = all.find((a) => a.principalId === oldPrincipalId);
|
|
26789
|
+
if (!old) return;
|
|
26790
|
+
const filtered = all.filter(
|
|
26791
|
+
(a) => a.principalId !== oldPrincipalId && a.principalId !== newPrincipalId
|
|
26792
|
+
);
|
|
26793
|
+
filtered.push({ principalId: newPrincipalId, personId: old.personId });
|
|
26794
|
+
this.write(filtered);
|
|
26795
|
+
}
|
|
26796
|
+
write(entries) {
|
|
26797
|
+
fs20.mkdirSync(path22.join(this.dataDir, PERSON_ALIASES_SUBDIR), { recursive: true });
|
|
26798
|
+
const tmp = `${this.filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26799
|
+
fs20.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
26800
|
+
fs20.renameSync(tmp, this.filePath);
|
|
26801
|
+
try {
|
|
26802
|
+
fs20.chmodSync(this.filePath, 384);
|
|
26803
|
+
} catch {
|
|
26710
26804
|
}
|
|
26711
|
-
this.writeFile(scope, sessionId, entries);
|
|
26712
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26713
|
-
return next;
|
|
26714
26805
|
}
|
|
26715
|
-
|
|
26716
|
-
|
|
26717
|
-
|
|
26718
|
-
|
|
26719
|
-
|
|
26720
|
-
|
|
26721
|
-
|
|
26722
|
-
|
|
26723
|
-
|
|
26724
|
-
|
|
26725
|
-
|
|
26726
|
-
|
|
26727
|
-
|
|
26728
|
-
this.writeFile(scope, sessionId, entries);
|
|
26729
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26806
|
+
};
|
|
26807
|
+
|
|
26808
|
+
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
26809
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
26810
|
+
var path23 = __toESM(require("path"), 1);
|
|
26811
|
+
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
26812
|
+
function migrateFlattenSessions(opts) {
|
|
26813
|
+
const dataDir = opts.dataDir;
|
|
26814
|
+
const now = opts.now ?? Date.now;
|
|
26815
|
+
const sessionsDir = path23.join(dataDir, "sessions");
|
|
26816
|
+
const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
26817
|
+
if (existsSync5(flagPath)) {
|
|
26818
|
+
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
26730
26819
|
}
|
|
26731
|
-
|
|
26732
|
-
|
|
26733
|
-
|
|
26734
|
-
|
|
26735
|
-
|
|
26736
|
-
|
|
26737
|
-
|
|
26738
|
-
|
|
26739
|
-
|
|
26740
|
-
|
|
26741
|
-
|
|
26742
|
-
|
|
26743
|
-
|
|
26744
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26745
|
-
return true;
|
|
26820
|
+
let movedBare = 0;
|
|
26821
|
+
let movedVmOwner = 0;
|
|
26822
|
+
let archivedListener = 0;
|
|
26823
|
+
const defaultDir = path23.join(sessionsDir, "default");
|
|
26824
|
+
if (existsSync5(defaultDir)) {
|
|
26825
|
+
for (const entry of readdirSafe(defaultDir)) {
|
|
26826
|
+
if (!entry.endsWith(".json")) continue;
|
|
26827
|
+
const src = path23.join(defaultDir, entry);
|
|
26828
|
+
const dst = path23.join(sessionsDir, entry);
|
|
26829
|
+
fs21.renameSync(src, dst);
|
|
26830
|
+
movedBare += 1;
|
|
26831
|
+
}
|
|
26832
|
+
rmdirIfEmpty(defaultDir);
|
|
26746
26833
|
}
|
|
26747
|
-
|
|
26748
|
-
|
|
26749
|
-
|
|
26750
|
-
|
|
26751
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
26754
|
-
|
|
26755
|
-
|
|
26756
|
-
|
|
26757
|
-
|
|
26758
|
-
|
|
26759
|
-
const root = this.rootForScope(scope);
|
|
26760
|
-
let names;
|
|
26761
|
-
try {
|
|
26762
|
-
names = import_node_fs13.default.readdirSync(root);
|
|
26763
|
-
} catch (err) {
|
|
26764
|
-
const code = err?.code;
|
|
26765
|
-
if (code === "ENOENT") continue;
|
|
26766
|
-
continue;
|
|
26834
|
+
for (const pid of readdirSafe(sessionsDir)) {
|
|
26835
|
+
const personaDir = path23.join(sessionsDir, pid);
|
|
26836
|
+
if (!isDir(personaDir)) continue;
|
|
26837
|
+
if (pid === "default") continue;
|
|
26838
|
+
const ownerSrc = path23.join(personaDir, "owner");
|
|
26839
|
+
if (existsSync5(ownerSrc) && isDir(ownerSrc)) {
|
|
26840
|
+
const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
26841
|
+
fs21.mkdirSync(ownerDst, { recursive: true });
|
|
26842
|
+
for (const file of readdirSafe(ownerSrc)) {
|
|
26843
|
+
if (!file.endsWith(".json")) continue;
|
|
26844
|
+
fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
|
|
26845
|
+
movedVmOwner += 1;
|
|
26767
26846
|
}
|
|
26768
|
-
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
|
|
26772
|
-
|
|
26773
|
-
|
|
26847
|
+
rmdirIfEmpty(ownerSrc);
|
|
26848
|
+
}
|
|
26849
|
+
const listenerSrc = path23.join(personaDir, "listener");
|
|
26850
|
+
if (existsSync5(listenerSrc) && isDir(listenerSrc)) {
|
|
26851
|
+
const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
|
|
26852
|
+
fs21.mkdirSync(archiveDst, { recursive: true });
|
|
26853
|
+
for (const file of readdirSafe(listenerSrc)) {
|
|
26854
|
+
if (!file.endsWith(".json")) continue;
|
|
26855
|
+
fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
|
|
26856
|
+
archivedListener += 1;
|
|
26774
26857
|
}
|
|
26858
|
+
rmdirIfEmpty(listenerSrc);
|
|
26775
26859
|
}
|
|
26776
|
-
|
|
26860
|
+
rmdirIfEmpty(personaDir);
|
|
26777
26861
|
}
|
|
26778
|
-
};
|
|
26779
|
-
|
|
26780
|
-
|
|
26781
|
-
|
|
26862
|
+
fs21.mkdirSync(sessionsDir, { recursive: true });
|
|
26863
|
+
fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
26864
|
+
return {
|
|
26865
|
+
skipped: false,
|
|
26866
|
+
flagWritten: true,
|
|
26867
|
+
movedBare,
|
|
26868
|
+
movedVmOwner,
|
|
26869
|
+
archivedListener
|
|
26870
|
+
};
|
|
26871
|
+
}
|
|
26872
|
+
function existsSync5(p2) {
|
|
26873
|
+
try {
|
|
26874
|
+
fs21.statSync(p2);
|
|
26875
|
+
return true;
|
|
26876
|
+
} catch {
|
|
26782
26877
|
return false;
|
|
26783
26878
|
}
|
|
26784
|
-
|
|
26785
|
-
|
|
26786
|
-
|
|
26787
|
-
|
|
26788
|
-
|
|
26789
|
-
|
|
26790
|
-
}
|
|
26879
|
+
}
|
|
26880
|
+
function isDir(p2) {
|
|
26881
|
+
try {
|
|
26882
|
+
return fs21.statSync(p2).isDirectory();
|
|
26883
|
+
} catch {
|
|
26884
|
+
return false;
|
|
26791
26885
|
}
|
|
26792
|
-
return false;
|
|
26793
26886
|
}
|
|
26794
|
-
function
|
|
26887
|
+
function readdirSafe(p2) {
|
|
26795
26888
|
try {
|
|
26796
|
-
return
|
|
26889
|
+
return fs21.readdirSync(p2);
|
|
26890
|
+
} catch {
|
|
26891
|
+
return [];
|
|
26892
|
+
}
|
|
26893
|
+
}
|
|
26894
|
+
function rmdirIfEmpty(p2) {
|
|
26895
|
+
try {
|
|
26896
|
+
fs21.rmdirSync(p2);
|
|
26797
26897
|
} catch {
|
|
26798
|
-
return null;
|
|
26799
26898
|
}
|
|
26800
26899
|
}
|
|
26801
26900
|
|
|
26802
|
-
// src/
|
|
26901
|
+
// src/transport/http-router.ts
|
|
26902
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26803
26903
|
var import_node_path15 = __toESM(require("path"), 1);
|
|
26904
|
+
|
|
26905
|
+
// src/attachment/mime.ts
|
|
26906
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
26804
26907
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
26805
26908
|
var EXT_TO_NATIVE_MIME = {
|
|
26806
26909
|
// 图片
|
|
@@ -26907,14 +27010,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
26907
27010
|
".mk"
|
|
26908
27011
|
]);
|
|
26909
27012
|
function lookupMime(filePathOrName) {
|
|
26910
|
-
const ext =
|
|
27013
|
+
const ext = import_node_path14.default.extname(filePathOrName).toLowerCase();
|
|
26911
27014
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
26912
27015
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
26913
27016
|
return "application/octet-stream";
|
|
26914
27017
|
}
|
|
26915
27018
|
|
|
26916
27019
|
// src/attachment/sign-url.ts
|
|
26917
|
-
var
|
|
27020
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
26918
27021
|
var HMAC_ALGO = "sha256";
|
|
26919
27022
|
function base64urlEncode(buf) {
|
|
26920
27023
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -26931,7 +27034,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
26931
27034
|
}
|
|
26932
27035
|
function computeSig(secret, absPath, e) {
|
|
26933
27036
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
26934
|
-
return
|
|
27037
|
+
return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
26935
27038
|
}
|
|
26936
27039
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
26937
27040
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -26966,7 +27069,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
26966
27069
|
if (provided.length !== expected.length) {
|
|
26967
27070
|
return { ok: false, code: "BAD_SIG" };
|
|
26968
27071
|
}
|
|
26969
|
-
if (!
|
|
27072
|
+
if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
|
|
26970
27073
|
return { ok: false, code: "BAD_SIG" };
|
|
26971
27074
|
}
|
|
26972
27075
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -26987,7 +27090,7 @@ function createHttpRouter(deps) {
|
|
|
26987
27090
|
sendJson(res, 200, { ok: true, version: deps.daemonVersion });
|
|
26988
27091
|
return true;
|
|
26989
27092
|
}
|
|
26990
|
-
if (!url.pathname.startsWith("/
|
|
27093
|
+
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
|
|
26991
27094
|
return false;
|
|
26992
27095
|
}
|
|
26993
27096
|
if (url.pathname.startsWith("/files/") && req.method === "GET") {
|
|
@@ -27027,43 +27130,8 @@ function createHttpRouter(deps) {
|
|
|
27027
27130
|
sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
|
|
27028
27131
|
return true;
|
|
27029
27132
|
}
|
|
27030
|
-
const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
|
|
27031
|
-
if (personaFilesMatch && req.method === "GET") {
|
|
27032
|
-
const pid = personaFilesMatch[1];
|
|
27033
|
-
const pathParam = url.searchParams.get("path");
|
|
27034
|
-
if (!pathParam) {
|
|
27035
|
-
sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
27036
|
-
return true;
|
|
27037
|
-
}
|
|
27038
|
-
if (!deps.personaStore || !deps.groupFileStore) {
|
|
27039
|
-
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
|
|
27040
|
-
return true;
|
|
27041
|
-
}
|
|
27042
|
-
const personaDir = deps.personaStore.personaDirPath(pid);
|
|
27043
|
-
const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
|
|
27044
|
-
if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
|
|
27045
|
-
sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
|
|
27046
|
-
return true;
|
|
27047
|
-
}
|
|
27048
|
-
if (ctx.role === "personal") {
|
|
27049
|
-
if (ctx.personaId !== pid) {
|
|
27050
|
-
sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
|
|
27051
|
-
return true;
|
|
27052
|
-
}
|
|
27053
|
-
if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
|
|
27054
|
-
sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
|
|
27055
|
-
return true;
|
|
27056
|
-
}
|
|
27057
|
-
}
|
|
27058
|
-
streamFile(res, absPath, deps.logger);
|
|
27059
|
-
return true;
|
|
27060
|
-
}
|
|
27061
27133
|
const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
|
|
27062
27134
|
if (sessionFilesMatch && req.method === "GET") {
|
|
27063
|
-
if (ctx.role !== "owner") {
|
|
27064
|
-
sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
|
|
27065
|
-
return true;
|
|
27066
|
-
}
|
|
27067
27135
|
const sid = sessionFilesMatch[1];
|
|
27068
27136
|
const pathParam = url.searchParams.get("path");
|
|
27069
27137
|
if (!pathParam) {
|
|
@@ -27071,7 +27139,7 @@ function createHttpRouter(deps) {
|
|
|
27071
27139
|
return true;
|
|
27072
27140
|
}
|
|
27073
27141
|
let absPath;
|
|
27074
|
-
if (
|
|
27142
|
+
if (import_node_path15.default.isAbsolute(pathParam)) {
|
|
27075
27143
|
absPath = pathParam;
|
|
27076
27144
|
} else if (deps.sessionStore) {
|
|
27077
27145
|
const file = deps.sessionStore.read(sid);
|
|
@@ -27079,7 +27147,7 @@ function createHttpRouter(deps) {
|
|
|
27079
27147
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
27080
27148
|
return true;
|
|
27081
27149
|
}
|
|
27082
|
-
absPath =
|
|
27150
|
+
absPath = import_node_path15.default.join(file.cwd, pathParam);
|
|
27083
27151
|
} else {
|
|
27084
27152
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
27085
27153
|
return true;
|
|
@@ -27098,53 +27166,188 @@ function parseUrl(rawUrl) {
|
|
|
27098
27166
|
} catch {
|
|
27099
27167
|
return null;
|
|
27100
27168
|
}
|
|
27101
|
-
}
|
|
27102
|
-
function sendJson(res, status, body) {
|
|
27103
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
27104
|
-
res.end(JSON.stringify(body));
|
|
27105
|
-
}
|
|
27106
|
-
function withCtx(ctx, body) {
|
|
27107
|
-
return { ...body, role: ctx.role
|
|
27108
|
-
}
|
|
27109
|
-
function
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
|
|
27117
|
-
|
|
27118
|
-
|
|
27119
|
-
|
|
27120
|
-
|
|
27121
|
-
|
|
27122
|
-
|
|
27169
|
+
}
|
|
27170
|
+
function sendJson(res, status, body) {
|
|
27171
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
27172
|
+
res.end(JSON.stringify(body));
|
|
27173
|
+
}
|
|
27174
|
+
function withCtx(ctx, body) {
|
|
27175
|
+
return { ...body, role: ctx.role };
|
|
27176
|
+
}
|
|
27177
|
+
function streamFile(res, absPath, logger) {
|
|
27178
|
+
let stat;
|
|
27179
|
+
try {
|
|
27180
|
+
stat = import_node_fs13.default.statSync(absPath);
|
|
27181
|
+
} catch (err) {
|
|
27182
|
+
const code = err?.code;
|
|
27183
|
+
if (code === "ENOENT") {
|
|
27184
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: "file not found" });
|
|
27185
|
+
} else {
|
|
27186
|
+
sendJson(res, 500, { code: "STAT_FAILED", message: err.message });
|
|
27187
|
+
}
|
|
27188
|
+
return;
|
|
27189
|
+
}
|
|
27190
|
+
if (!stat.isFile()) {
|
|
27191
|
+
sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
|
|
27192
|
+
return;
|
|
27193
|
+
}
|
|
27194
|
+
const mime = lookupMime(absPath);
|
|
27195
|
+
const basename = import_node_path15.default.basename(absPath);
|
|
27196
|
+
res.writeHead(200, {
|
|
27197
|
+
"Content-Type": mime,
|
|
27198
|
+
"Content-Length": String(stat.size),
|
|
27199
|
+
"Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
|
|
27200
|
+
// 防止浏览器把任意 mime 当 html 渲染
|
|
27201
|
+
"X-Content-Type-Options": "nosniff"
|
|
27202
|
+
});
|
|
27203
|
+
const stream = import_node_fs13.default.createReadStream(absPath);
|
|
27204
|
+
stream.on("error", (err) => {
|
|
27205
|
+
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
27206
|
+
res.destroy();
|
|
27207
|
+
});
|
|
27208
|
+
stream.pipe(res);
|
|
27209
|
+
}
|
|
27210
|
+
|
|
27211
|
+
// src/attachment/group.ts
|
|
27212
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
27213
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
27214
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27215
|
+
init_protocol();
|
|
27216
|
+
var GroupFileStore = class {
|
|
27217
|
+
dataDir;
|
|
27218
|
+
logger;
|
|
27219
|
+
cache = /* @__PURE__ */ new Map();
|
|
27220
|
+
constructor(opts) {
|
|
27221
|
+
this.dataDir = opts.dataDir;
|
|
27222
|
+
this.logger = opts.logger;
|
|
27223
|
+
}
|
|
27224
|
+
rootForScope(scope) {
|
|
27225
|
+
return import_node_path16.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
27226
|
+
}
|
|
27227
|
+
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
27228
|
+
filePath(scope, sessionId) {
|
|
27229
|
+
return import_node_path16.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
27230
|
+
}
|
|
27231
|
+
cacheKey(scope, sessionId) {
|
|
27232
|
+
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
27233
|
+
}
|
|
27234
|
+
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
27235
|
+
readFile(scope, sessionId) {
|
|
27236
|
+
const file = this.filePath(scope, sessionId);
|
|
27237
|
+
try {
|
|
27238
|
+
const raw = import_node_fs14.default.readFileSync(file, "utf8");
|
|
27239
|
+
const parsed = JSON.parse(raw);
|
|
27240
|
+
if (!Array.isArray(parsed)) {
|
|
27241
|
+
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
27242
|
+
file
|
|
27243
|
+
});
|
|
27244
|
+
return [];
|
|
27245
|
+
}
|
|
27246
|
+
const out = [];
|
|
27247
|
+
for (const entry of parsed) {
|
|
27248
|
+
const r = GroupFileEntrySchema.safeParse(entry);
|
|
27249
|
+
if (r.success) out.push(r.data);
|
|
27250
|
+
}
|
|
27251
|
+
return out;
|
|
27252
|
+
} catch (err) {
|
|
27253
|
+
const code = err?.code;
|
|
27254
|
+
if (code === "ENOENT") return [];
|
|
27255
|
+
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
27256
|
+
file,
|
|
27257
|
+
err: err.message
|
|
27258
|
+
});
|
|
27259
|
+
return [];
|
|
27260
|
+
}
|
|
27261
|
+
}
|
|
27262
|
+
writeFile(scope, sessionId, entries) {
|
|
27263
|
+
const file = this.filePath(scope, sessionId);
|
|
27264
|
+
import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
27265
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
27266
|
+
import_node_fs14.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
27267
|
+
import_node_fs14.default.renameSync(tmp, file);
|
|
27268
|
+
}
|
|
27269
|
+
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
27270
|
+
list(scope, sessionId) {
|
|
27271
|
+
const key = this.cacheKey(scope, sessionId);
|
|
27272
|
+
const cached = this.cache.get(key);
|
|
27273
|
+
if (cached) return cached.entries;
|
|
27274
|
+
const entries = this.readFile(scope, sessionId);
|
|
27275
|
+
this.cache.set(key, { entries });
|
|
27276
|
+
return entries;
|
|
27277
|
+
}
|
|
27278
|
+
/**
|
|
27279
|
+
* upsert:
|
|
27280
|
+
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
27281
|
+
* (保留首次入群人 / 入群时刻)
|
|
27282
|
+
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
27283
|
+
*
|
|
27284
|
+
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
27285
|
+
*/
|
|
27286
|
+
upsert(scope, sessionId, input, now = Date.now()) {
|
|
27287
|
+
const entries = this.list(scope, sessionId).slice();
|
|
27288
|
+
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
27289
|
+
let next;
|
|
27290
|
+
if (idx >= 0) {
|
|
27291
|
+
const prev = entries[idx];
|
|
27292
|
+
next = {
|
|
27293
|
+
...prev,
|
|
27294
|
+
size: input.size,
|
|
27295
|
+
mime: input.mime,
|
|
27296
|
+
lastEditedAt: now,
|
|
27297
|
+
stale: false
|
|
27298
|
+
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
27299
|
+
};
|
|
27300
|
+
entries[idx] = next;
|
|
27123
27301
|
} else {
|
|
27124
|
-
|
|
27302
|
+
next = {
|
|
27303
|
+
id: `gf-${import_node_crypto5.default.randomBytes(6).toString("base64url")}`,
|
|
27304
|
+
relPath: input.relPath,
|
|
27305
|
+
from: input.from,
|
|
27306
|
+
label: input.label,
|
|
27307
|
+
size: input.size,
|
|
27308
|
+
mime: input.mime,
|
|
27309
|
+
addedAt: now
|
|
27310
|
+
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
27311
|
+
};
|
|
27312
|
+
entries.push(next);
|
|
27125
27313
|
}
|
|
27126
|
-
|
|
27314
|
+
this.writeFile(scope, sessionId, entries);
|
|
27315
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27316
|
+
return next;
|
|
27127
27317
|
}
|
|
27128
|
-
|
|
27129
|
-
|
|
27130
|
-
|
|
27318
|
+
/**
|
|
27319
|
+
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
27320
|
+
* - 命中 → stale=true,UI 灰显
|
|
27321
|
+
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
27322
|
+
*
|
|
27323
|
+
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
27324
|
+
*/
|
|
27325
|
+
markStale(scope, sessionId, relPath) {
|
|
27326
|
+
const entries = this.list(scope, sessionId).slice();
|
|
27327
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27328
|
+
if (idx < 0) return;
|
|
27329
|
+
if (entries[idx].stale) return;
|
|
27330
|
+
entries[idx] = { ...entries[idx], stale: true };
|
|
27331
|
+
this.writeFile(scope, sessionId, entries);
|
|
27332
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27131
27333
|
}
|
|
27132
|
-
|
|
27133
|
-
|
|
27134
|
-
|
|
27135
|
-
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
27142
|
-
|
|
27143
|
-
|
|
27144
|
-
|
|
27145
|
-
|
|
27146
|
-
|
|
27147
|
-
}
|
|
27334
|
+
/**
|
|
27335
|
+
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
27336
|
+
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
27337
|
+
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
27338
|
+
*
|
|
27339
|
+
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
27340
|
+
*/
|
|
27341
|
+
remove(scope, sessionId, relPath) {
|
|
27342
|
+
const entries = this.list(scope, sessionId).slice();
|
|
27343
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27344
|
+
if (idx < 0) return false;
|
|
27345
|
+
entries.splice(idx, 1);
|
|
27346
|
+
this.writeFile(scope, sessionId, entries);
|
|
27347
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27348
|
+
return true;
|
|
27349
|
+
}
|
|
27350
|
+
};
|
|
27148
27351
|
|
|
27149
27352
|
// src/discovery/state-file.ts
|
|
27150
27353
|
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
@@ -27822,29 +28025,45 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
27822
28025
|
function authFilePath(dataDir) {
|
|
27823
28026
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
27824
28027
|
}
|
|
27825
|
-
function
|
|
28028
|
+
function loadOrCreateAuth(opts) {
|
|
27826
28029
|
const file = authFilePath(opts.dataDir);
|
|
27827
28030
|
const existing = readAuthFile(file);
|
|
27828
|
-
|
|
27829
|
-
const
|
|
28031
|
+
const genToken = opts.generate ?? defaultGenerateToken2;
|
|
28032
|
+
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
27830
28033
|
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
27831
|
-
|
|
27832
|
-
|
|
28034
|
+
if (existing) {
|
|
28035
|
+
if (!existing.ownerPrincipalId) {
|
|
28036
|
+
const ownerPrincipalId2 = genId();
|
|
28037
|
+
writeAuthFile(file, {
|
|
28038
|
+
token: existing.token,
|
|
28039
|
+
ownerPrincipalId: ownerPrincipalId2,
|
|
28040
|
+
createdAt: existing.createdAt
|
|
28041
|
+
});
|
|
28042
|
+
return { token: existing.token, ownerPrincipalId: ownerPrincipalId2 };
|
|
28043
|
+
}
|
|
28044
|
+
return { token: existing.token, ownerPrincipalId: existing.ownerPrincipalId };
|
|
28045
|
+
}
|
|
28046
|
+
const token = genToken();
|
|
28047
|
+
const ownerPrincipalId = genId();
|
|
28048
|
+
writeAuthFile(file, { token, ownerPrincipalId, createdAt: now.toISOString() });
|
|
28049
|
+
return { token, ownerPrincipalId };
|
|
27833
28050
|
}
|
|
27834
|
-
function
|
|
28051
|
+
function defaultGenerateToken2() {
|
|
27835
28052
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
27836
28053
|
}
|
|
28054
|
+
function defaultGenerateOwnerPrincipalId() {
|
|
28055
|
+
return `owner-${import_node_crypto8.default.randomUUID()}`;
|
|
28056
|
+
}
|
|
27837
28057
|
function readAuthFile(file) {
|
|
27838
28058
|
try {
|
|
27839
28059
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27840
28060
|
const parsed = JSON.parse(raw);
|
|
27841
|
-
if (typeof parsed?.token
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
}
|
|
27847
|
-
return null;
|
|
28061
|
+
if (typeof parsed?.token !== "string" || parsed.token.length === 0) return null;
|
|
28062
|
+
return {
|
|
28063
|
+
token: parsed.token,
|
|
28064
|
+
ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : "",
|
|
28065
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
28066
|
+
};
|
|
27848
28067
|
} catch (err) {
|
|
27849
28068
|
const code = err?.code;
|
|
27850
28069
|
if (code === "ENOENT") return null;
|
|
@@ -28020,7 +28239,8 @@ function buildSessionHandlers(deps) {
|
|
|
28020
28239
|
}
|
|
28021
28240
|
}
|
|
28022
28241
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
28023
|
-
const
|
|
28242
|
+
const creatorPrincipalId = ctx?.principal.id ?? "";
|
|
28243
|
+
const { response, broadcast } = manager.create(args, creatorPrincipalId);
|
|
28024
28244
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28025
28245
|
};
|
|
28026
28246
|
const list = async (_frame, _client, ctx) => {
|
|
@@ -28469,21 +28689,27 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
28469
28689
|
// src/handlers/capability.ts
|
|
28470
28690
|
init_zod();
|
|
28471
28691
|
init_protocol();
|
|
28472
|
-
var
|
|
28473
|
-
displayName: external_exports.string().min(1),
|
|
28474
|
-
grants: external_exports.array(GrantSchema),
|
|
28475
|
-
expiresAt: external_exports.number().int().positive().optional(),
|
|
28476
|
-
maxUses: external_exports.number().int().positive().optional()
|
|
28477
|
-
}).strict();
|
|
28478
|
-
var RevokeArgsSchema = external_exports.object({
|
|
28692
|
+
var DeleteArgsSchema = external_exports.object({
|
|
28479
28693
|
capabilityId: external_exports.string().min(1)
|
|
28480
28694
|
}).strict();
|
|
28481
28695
|
function buildCapabilityHandlers(deps) {
|
|
28482
|
-
const { manager, getShareBaseUrl } = deps;
|
|
28696
|
+
const { manager, getShareBaseUrl, personStore, aliasStore } = deps;
|
|
28483
28697
|
const issue = async (frame) => {
|
|
28484
28698
|
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28485
|
-
const args =
|
|
28486
|
-
|
|
28699
|
+
const args = CapabilityIssueArgsSchema.parse(rest);
|
|
28700
|
+
if (!personStore.get(args.personId)) {
|
|
28701
|
+
throw new ClawdError(
|
|
28702
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
28703
|
+
`Person not found: ${args.personId}`
|
|
28704
|
+
);
|
|
28705
|
+
}
|
|
28706
|
+
const { personId, ...managerArgs } = args;
|
|
28707
|
+
const { token, capability } = manager.issue({
|
|
28708
|
+
...managerArgs,
|
|
28709
|
+
atomicAfterPersist: (cap) => {
|
|
28710
|
+
aliasStore.set(cap.id, personId);
|
|
28711
|
+
}
|
|
28712
|
+
});
|
|
28487
28713
|
const base = getShareBaseUrl().replace(/\/$/, "");
|
|
28488
28714
|
const shareUrl = `${base}/?token=${encodeURIComponent(token)}`;
|
|
28489
28715
|
return {
|
|
@@ -28503,97 +28729,132 @@ function buildCapabilityHandlers(deps) {
|
|
|
28503
28729
|
}
|
|
28504
28730
|
};
|
|
28505
28731
|
};
|
|
28506
|
-
const
|
|
28732
|
+
const del = async (frame) => {
|
|
28507
28733
|
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28508
|
-
const args =
|
|
28509
|
-
const result = manager.
|
|
28734
|
+
const args = DeleteArgsSchema.parse(rest);
|
|
28735
|
+
const result = manager.delete(args.capabilityId);
|
|
28510
28736
|
if (!result) {
|
|
28511
28737
|
throw new ClawdError(
|
|
28512
28738
|
ERROR_CODES.VALIDATION_ERROR,
|
|
28513
28739
|
`capability not found: ${args.capabilityId}`
|
|
28514
28740
|
);
|
|
28515
28741
|
}
|
|
28742
|
+
aliasStore.remove(args.capabilityId);
|
|
28743
|
+
if (result.capability.linkedPrincipalId !== void 0) {
|
|
28744
|
+
aliasStore.remove(result.capability.linkedPrincipalId);
|
|
28745
|
+
}
|
|
28516
28746
|
return {
|
|
28517
28747
|
response: {
|
|
28518
|
-
type: "capability:
|
|
28519
|
-
capabilityId: args.capabilityId
|
|
28520
|
-
revokedAt: result.revokedAt
|
|
28748
|
+
type: "capability:deleted",
|
|
28749
|
+
capabilityId: args.capabilityId
|
|
28521
28750
|
}
|
|
28522
28751
|
};
|
|
28523
28752
|
};
|
|
28524
28753
|
return {
|
|
28525
28754
|
"capability:issue": issue,
|
|
28526
28755
|
"capability:list": list,
|
|
28527
|
-
"capability:
|
|
28756
|
+
"capability:delete": del
|
|
28528
28757
|
};
|
|
28529
28758
|
}
|
|
28530
28759
|
|
|
28531
28760
|
// src/handlers/inbox.ts
|
|
28532
28761
|
init_protocol();
|
|
28762
|
+
function assertChannelAccess(ctx, capabilityId) {
|
|
28763
|
+
if (ctx.principal.kind === "owner") return;
|
|
28764
|
+
if (ctx.capabilityId === capabilityId) return;
|
|
28765
|
+
throw new ClawdError(
|
|
28766
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
28767
|
+
`inbox: guest can only access own channel (capabilityId=${capabilityId}, ctx.capabilityId=${ctx.capabilityId ?? "none"})`
|
|
28768
|
+
);
|
|
28769
|
+
}
|
|
28533
28770
|
function buildInboxHandlers(deps) {
|
|
28534
|
-
const { manager,
|
|
28535
|
-
|
|
28536
|
-
|
|
28537
|
-
|
|
28771
|
+
const { manager, personStore, aliasStore } = deps;
|
|
28772
|
+
const postMessage = async (frame, _client, ctx) => {
|
|
28773
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28774
|
+
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
28775
|
+
if (!ctx) {
|
|
28776
|
+
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
28538
28777
|
}
|
|
28539
|
-
|
|
28540
|
-
if (
|
|
28541
|
-
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28778
|
+
assertChannelAccess(ctx, args.capabilityId);
|
|
28779
|
+
if (ctx.principal.kind === "guest") {
|
|
28780
|
+
const senderPrincipalId = ctx.principal.id;
|
|
28781
|
+
const personId = aliasStore.get(senderPrincipalId);
|
|
28782
|
+
if (personId !== void 0) {
|
|
28783
|
+
const person = personStore.get(personId);
|
|
28784
|
+
if (person && !person.dmEnabled) {
|
|
28785
|
+
throw new ClawdError(
|
|
28786
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
28787
|
+
`DM_BLOCKED: Person ${person.displayName} has DM disabled`
|
|
28788
|
+
);
|
|
28789
|
+
}
|
|
28790
|
+
}
|
|
28545
28791
|
}
|
|
28546
|
-
|
|
28547
|
-
|
|
28548
|
-
|
|
28549
|
-
|
|
28550
|
-
|
|
28551
|
-
const events = manager.list({ includeRead: args.includeRead ?? false });
|
|
28792
|
+
const message = manager.postMessage({
|
|
28793
|
+
capabilityId: args.capabilityId,
|
|
28794
|
+
senderPrincipalId: ctx.principal.id,
|
|
28795
|
+
text: args.text
|
|
28796
|
+
});
|
|
28552
28797
|
return {
|
|
28553
|
-
response: { type: "inbox:
|
|
28798
|
+
response: { type: "inbox:postMessage:ok", message }
|
|
28554
28799
|
};
|
|
28555
28800
|
};
|
|
28556
|
-
const
|
|
28801
|
+
const list = async (frame, _client, ctx) => {
|
|
28557
28802
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28558
|
-
const args =
|
|
28559
|
-
|
|
28803
|
+
const args = InboxListArgsSchema.parse(rest);
|
|
28804
|
+
if (!ctx) {
|
|
28805
|
+
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
28806
|
+
}
|
|
28807
|
+
assertChannelAccess(ctx, args.capabilityId);
|
|
28808
|
+
const messages = manager.list(args.capabilityId, args.sinceCreatedAt);
|
|
28560
28809
|
return {
|
|
28561
|
-
response: { type: "inbox:
|
|
28810
|
+
response: { type: "inbox:list:ok", capabilityId: args.capabilityId, messages }
|
|
28562
28811
|
};
|
|
28563
28812
|
};
|
|
28564
|
-
const
|
|
28813
|
+
const markRead = async (frame, _client, ctx) => {
|
|
28565
28814
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28566
|
-
const args =
|
|
28815
|
+
const args = InboxMarkReadArgsSchema.parse(rest);
|
|
28567
28816
|
if (!ctx) {
|
|
28568
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:
|
|
28817
|
+
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
28569
28818
|
}
|
|
28570
|
-
|
|
28571
|
-
const
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28819
|
+
assertChannelAccess(ctx, args.capabilityId);
|
|
28820
|
+
const updated = manager.markRead({
|
|
28821
|
+
capabilityId: args.capabilityId,
|
|
28822
|
+
principalId: ctx.principal.id,
|
|
28823
|
+
upToCreatedAt: args.upToCreatedAt
|
|
28575
28824
|
});
|
|
28576
28825
|
return {
|
|
28577
|
-
response: {
|
|
28826
|
+
response: {
|
|
28827
|
+
type: "inbox:markRead:ok",
|
|
28828
|
+
capabilityId: args.capabilityId,
|
|
28829
|
+
upToCreatedAt: args.upToCreatedAt,
|
|
28830
|
+
updated
|
|
28831
|
+
}
|
|
28578
28832
|
};
|
|
28579
28833
|
};
|
|
28580
28834
|
return {
|
|
28835
|
+
"inbox:postMessage": postMessage,
|
|
28581
28836
|
"inbox:list": list,
|
|
28582
|
-
"inbox:markRead": markRead
|
|
28583
|
-
"inbox:postMessage": postMessage
|
|
28837
|
+
"inbox:markRead": markRead
|
|
28584
28838
|
};
|
|
28585
28839
|
}
|
|
28586
28840
|
|
|
28587
28841
|
// src/handlers/remote-persona.ts
|
|
28588
28842
|
init_protocol();
|
|
28589
28843
|
function buildRemotePersonaHandlers(deps) {
|
|
28590
|
-
const { store } = deps;
|
|
28844
|
+
const { store, personStore, aliasStore } = deps;
|
|
28591
28845
|
const now = deps.now ?? Date.now;
|
|
28592
28846
|
const add = async (frame) => {
|
|
28593
28847
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28594
28848
|
const args = RemotePersonaAddArgsSchema.parse(rest);
|
|
28849
|
+
if (!personStore.get(args.personId)) {
|
|
28850
|
+
throw new ClawdError(
|
|
28851
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
28852
|
+
`Person not found: ${args.personId}`
|
|
28853
|
+
);
|
|
28854
|
+
}
|
|
28855
|
+
const { personId, ...persistFields } = args;
|
|
28595
28856
|
const rp = {
|
|
28596
|
-
...
|
|
28857
|
+
...persistFields,
|
|
28597
28858
|
addedAt: now()
|
|
28598
28859
|
};
|
|
28599
28860
|
try {
|
|
@@ -28607,6 +28868,15 @@ function buildRemotePersonaHandlers(deps) {
|
|
|
28607
28868
|
}
|
|
28608
28869
|
throw err;
|
|
28609
28870
|
}
|
|
28871
|
+
try {
|
|
28872
|
+
aliasStore.set(rp.ownerPrincipalId, personId);
|
|
28873
|
+
} catch (err) {
|
|
28874
|
+
try {
|
|
28875
|
+
store.remove(rp.alias);
|
|
28876
|
+
} catch {
|
|
28877
|
+
}
|
|
28878
|
+
throw err;
|
|
28879
|
+
}
|
|
28610
28880
|
return {
|
|
28611
28881
|
response: { type: "remote-persona:add:ok", remotePersona: stripRemotePersonaSecret(rp) }
|
|
28612
28882
|
};
|
|
@@ -28624,7 +28894,11 @@ function buildRemotePersonaHandlers(deps) {
|
|
|
28624
28894
|
const remove = async (frame) => {
|
|
28625
28895
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28626
28896
|
const args = RemotePersonaRemoveArgsSchema.parse(rest);
|
|
28897
|
+
const existing = store.get(args.alias);
|
|
28627
28898
|
const removed = store.remove(args.alias);
|
|
28899
|
+
if (existing) {
|
|
28900
|
+
aliasStore.remove(existing.ownerPrincipalId);
|
|
28901
|
+
}
|
|
28628
28902
|
return {
|
|
28629
28903
|
response: { type: "remote-persona:remove:ok", alias: args.alias, removed }
|
|
28630
28904
|
};
|
|
@@ -28643,11 +28917,11 @@ function buildWhoamiHandler(deps) {
|
|
|
28643
28917
|
if (!ctx) {
|
|
28644
28918
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
28645
28919
|
}
|
|
28646
|
-
const owner =
|
|
28920
|
+
const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
|
|
28647
28921
|
let capability;
|
|
28648
28922
|
if (ctx.principal.kind === "owner") {
|
|
28649
28923
|
capability = {
|
|
28650
|
-
id:
|
|
28924
|
+
id: deps.ownerPrincipalId,
|
|
28651
28925
|
displayName: deps.ownerDisplayName,
|
|
28652
28926
|
grants: ctx.grants,
|
|
28653
28927
|
issuedAt: 0,
|
|
@@ -28664,11 +28938,13 @@ function buildWhoamiHandler(deps) {
|
|
|
28664
28938
|
capability = stripSecretHash(cap);
|
|
28665
28939
|
}
|
|
28666
28940
|
const grantedPersonas = [];
|
|
28941
|
+
const isGuest = ctx.principal.kind === "guest";
|
|
28667
28942
|
const hasWildcard = capability.grants.some((g2) => g2.resource.type === "*");
|
|
28668
28943
|
if (hasWildcard) {
|
|
28669
28944
|
for (const id of deps.personaStore.list()) {
|
|
28670
28945
|
const file = deps.personaStore.read(id);
|
|
28671
28946
|
if (!file) continue;
|
|
28947
|
+
if (isGuest && !file.public) continue;
|
|
28672
28948
|
grantedPersonas.push({ id: file.personaId, displayName: file.label });
|
|
28673
28949
|
}
|
|
28674
28950
|
} else {
|
|
@@ -28791,28 +29067,151 @@ function buildPersonaHandlers(deps) {
|
|
|
28791
29067
|
response: { type: "persona:deleted", personaId: args.personaId }
|
|
28792
29068
|
};
|
|
28793
29069
|
};
|
|
28794
|
-
const issueToken = async (frame) => {
|
|
28795
|
-
const args = PersonaIssueTokenArgsSchema.parse(frame);
|
|
28796
|
-
const { token, persona } = personaManager.issueToken(args.personaId, args.label);
|
|
28797
|
-
return {
|
|
28798
|
-
response: { type: "persona:tokenIssued", token, persona }
|
|
28799
|
-
};
|
|
28800
|
-
};
|
|
28801
|
-
const revokeToken = async (frame) => {
|
|
28802
|
-
const args = PersonaRevokeTokenArgsSchema.parse(frame);
|
|
28803
|
-
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
28804
|
-
return { response: { type: "persona:info", ...persona } };
|
|
28805
|
-
};
|
|
28806
29070
|
return {
|
|
28807
29071
|
"persona:create": create,
|
|
28808
29072
|
"persona:list": list,
|
|
28809
29073
|
"persona:get": get,
|
|
28810
29074
|
"persona:update": update,
|
|
28811
|
-
"persona:delete": del
|
|
28812
|
-
|
|
28813
|
-
|
|
29075
|
+
"persona:delete": del
|
|
29076
|
+
};
|
|
29077
|
+
}
|
|
29078
|
+
|
|
29079
|
+
// src/handlers/person.ts
|
|
29080
|
+
var import_node_crypto9 = require("crypto");
|
|
29081
|
+
init_protocol();
|
|
29082
|
+
|
|
29083
|
+
// src/person/cascade.ts
|
|
29084
|
+
var EMPTY_RESULT = {
|
|
29085
|
+
deletedCapabilityIds: [],
|
|
29086
|
+
removedRemoteAliases: [],
|
|
29087
|
+
deletedInboxEvents: 0
|
|
29088
|
+
};
|
|
29089
|
+
function cascadeDeletePerson(personId, deps) {
|
|
29090
|
+
if (!deps.personStore.get(personId)) return { ...EMPTY_RESULT };
|
|
29091
|
+
const principalIds = deps.aliasStore.list().filter((a) => a.personId === personId).map((a) => a.principalId);
|
|
29092
|
+
const principalSet = new Set(principalIds);
|
|
29093
|
+
const allCaps = deps.capabilityManager.list();
|
|
29094
|
+
const capsToDelete = allCaps.filter(
|
|
29095
|
+
(c) => principalSet.has(c.id) || c.linkedPrincipalId !== void 0 && principalSet.has(c.linkedPrincipalId)
|
|
29096
|
+
);
|
|
29097
|
+
let deletedInboxEvents = 0;
|
|
29098
|
+
for (const c of capsToDelete) {
|
|
29099
|
+
deletedInboxEvents += deps.inboxStore.list(c.id).length;
|
|
29100
|
+
}
|
|
29101
|
+
const deletedCapabilityIds = [];
|
|
29102
|
+
for (const c of capsToDelete) {
|
|
29103
|
+
if (deps.capabilityManager.delete(c.id) !== null) {
|
|
29104
|
+
deletedCapabilityIds.push(c.id);
|
|
29105
|
+
}
|
|
29106
|
+
}
|
|
29107
|
+
const allRemotes = deps.remotePersonaStore.list();
|
|
29108
|
+
const remotesToRemove = allRemotes.filter((r) => principalSet.has(r.ownerPrincipalId));
|
|
29109
|
+
const removedRemoteAliases = [];
|
|
29110
|
+
for (const r of remotesToRemove) {
|
|
29111
|
+
if (deps.remotePersonaStore.remove(r.alias)) {
|
|
29112
|
+
removedRemoteAliases.push(r.alias);
|
|
29113
|
+
}
|
|
29114
|
+
}
|
|
29115
|
+
deps.aliasStore.removeAllByPerson(personId);
|
|
29116
|
+
deps.personStore.remove(personId);
|
|
29117
|
+
return { deletedCapabilityIds, removedRemoteAliases, deletedInboxEvents };
|
|
29118
|
+
}
|
|
29119
|
+
|
|
29120
|
+
// src/handlers/person.ts
|
|
29121
|
+
function buildPersonHandlers(deps) {
|
|
29122
|
+
const now = deps.now ?? Date.now;
|
|
29123
|
+
const genId = deps.genId ?? defaultGenId2;
|
|
29124
|
+
const list = async () => {
|
|
29125
|
+
const persons = deps.personStore.list();
|
|
29126
|
+
const aliases = deps.aliasStore.list();
|
|
29127
|
+
const personIdByPrincipal = new Map(aliases.map((a) => [a.principalId, a.personId]));
|
|
29128
|
+
const allCaps = deps.capabilityManager.list();
|
|
29129
|
+
const allRemotes = deps.remotePersonaStore.list();
|
|
29130
|
+
const out = persons.map((p2) => {
|
|
29131
|
+
const linkedCapabilityIds = [];
|
|
29132
|
+
for (const c of allCaps) {
|
|
29133
|
+
const key = c.linkedPrincipalId ?? c.id;
|
|
29134
|
+
if (personIdByPrincipal.get(key) === p2.id) linkedCapabilityIds.push(c.id);
|
|
29135
|
+
}
|
|
29136
|
+
const linkedRemoteAliases = [];
|
|
29137
|
+
for (const r of allRemotes) {
|
|
29138
|
+
if (personIdByPrincipal.get(r.ownerPrincipalId) === p2.id) linkedRemoteAliases.push(r.alias);
|
|
29139
|
+
}
|
|
29140
|
+
return { ...p2, linkedCapabilityIds, linkedRemoteAliases };
|
|
29141
|
+
});
|
|
29142
|
+
return { response: { type: "person:list:ok", persons: out } };
|
|
29143
|
+
};
|
|
29144
|
+
const create = async (frame) => {
|
|
29145
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29146
|
+
const args = PersonCreateArgsSchema.parse(rest);
|
|
29147
|
+
const t = now();
|
|
29148
|
+
const person = {
|
|
29149
|
+
id: genId(),
|
|
29150
|
+
displayName: args.displayName,
|
|
29151
|
+
...args.notes !== void 0 ? { notes: args.notes } : {},
|
|
29152
|
+
dmEnabled: args.dmEnabled ?? true,
|
|
29153
|
+
createdAt: t,
|
|
29154
|
+
updatedAt: t
|
|
29155
|
+
};
|
|
29156
|
+
deps.personStore.add(person);
|
|
29157
|
+
return { response: { type: "person:create:ok", person } };
|
|
29158
|
+
};
|
|
29159
|
+
const update = async (frame) => {
|
|
29160
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29161
|
+
const args = PersonUpdateArgsSchema.parse(rest);
|
|
29162
|
+
const existing = deps.personStore.get(args.id);
|
|
29163
|
+
if (!existing) {
|
|
29164
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `Person not found: ${args.id}`);
|
|
29165
|
+
}
|
|
29166
|
+
const t = now();
|
|
29167
|
+
deps.personStore.update(
|
|
29168
|
+
args.id,
|
|
29169
|
+
{
|
|
29170
|
+
...args.displayName !== void 0 ? { displayName: args.displayName } : {},
|
|
29171
|
+
...args.notes !== void 0 ? { notes: args.notes } : {},
|
|
29172
|
+
...args.dmEnabled !== void 0 ? { dmEnabled: args.dmEnabled } : {}
|
|
29173
|
+
},
|
|
29174
|
+
t
|
|
29175
|
+
);
|
|
29176
|
+
const next = deps.personStore.get(args.id);
|
|
29177
|
+
return { response: { type: "person:update:ok", person: next } };
|
|
29178
|
+
};
|
|
29179
|
+
const del = async (frame) => {
|
|
29180
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29181
|
+
const args = PersonDeleteArgsSchema.parse(rest);
|
|
29182
|
+
if (!deps.personStore.get(args.id)) {
|
|
29183
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `Person not found: ${args.id}`);
|
|
29184
|
+
}
|
|
29185
|
+
const result = cascadeDeletePerson(args.id, deps);
|
|
29186
|
+
return {
|
|
29187
|
+
response: {
|
|
29188
|
+
type: "person:delete:ok",
|
|
29189
|
+
deletedCapabilityIds: result.deletedCapabilityIds,
|
|
29190
|
+
removedRemoteAliases: result.removedRemoteAliases,
|
|
29191
|
+
deletedInboxEvents: result.deletedInboxEvents
|
|
29192
|
+
}
|
|
29193
|
+
};
|
|
29194
|
+
};
|
|
29195
|
+
const link = async (frame) => {
|
|
29196
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29197
|
+
const args = PersonLinkArgsSchema.parse(rest);
|
|
29198
|
+
if (!deps.personStore.get(args.personId)) {
|
|
29199
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `Person not found: ${args.personId}`);
|
|
29200
|
+
}
|
|
29201
|
+
deps.aliasStore.set(args.principalId, args.personId);
|
|
29202
|
+
return { response: { type: "person:link:ok" } };
|
|
29203
|
+
};
|
|
29204
|
+
return {
|
|
29205
|
+
"person:list": list,
|
|
29206
|
+
"person:create": create,
|
|
29207
|
+
"person:update": update,
|
|
29208
|
+
"person:delete": del,
|
|
29209
|
+
"person:link": link
|
|
28814
29210
|
};
|
|
28815
29211
|
}
|
|
29212
|
+
function defaultGenId2() {
|
|
29213
|
+
return `p_${(0, import_node_crypto9.randomUUID)()}`;
|
|
29214
|
+
}
|
|
28816
29215
|
|
|
28817
29216
|
// src/handlers/attachment.ts
|
|
28818
29217
|
init_protocol();
|
|
@@ -28907,23 +29306,11 @@ function buildAttachmentHandlers(deps) {
|
|
|
28907
29306
|
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
28908
29307
|
return { response: { type: "attachment.groupList", entries } };
|
|
28909
29308
|
};
|
|
28910
|
-
const groupListPersona = async (frame) => {
|
|
28911
|
-
if (!deps.groupFileStore) {
|
|
28912
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28913
|
-
}
|
|
28914
|
-
const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
|
|
28915
|
-
if (!parsed.success) {
|
|
28916
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28917
|
-
}
|
|
28918
|
-
const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
|
|
28919
|
-
return { response: { type: "attachment.groupListPersona", perSession } };
|
|
28920
|
-
};
|
|
28921
29309
|
return {
|
|
28922
29310
|
"attachment.signUrl": signUrl,
|
|
28923
29311
|
"attachment.groupAdd": groupAdd,
|
|
28924
29312
|
"attachment.groupRemove": groupRemove,
|
|
28925
|
-
"attachment.groupList": groupList
|
|
28926
|
-
"attachment.groupListPersona": groupListPersona
|
|
29313
|
+
"attachment.groupList": groupList
|
|
28927
29314
|
};
|
|
28928
29315
|
}
|
|
28929
29316
|
|
|
@@ -28943,15 +29330,33 @@ function buildMethodHandlers(deps) {
|
|
|
28943
29330
|
}),
|
|
28944
29331
|
...buildCapabilityHandlers({
|
|
28945
29332
|
manager: deps.capabilityManager,
|
|
28946
|
-
getShareBaseUrl: deps.getShareBaseUrl
|
|
29333
|
+
getShareBaseUrl: deps.getShareBaseUrl,
|
|
29334
|
+
personStore: deps.personStore,
|
|
29335
|
+
aliasStore: deps.personAliasStore
|
|
28947
29336
|
}),
|
|
28948
29337
|
...buildInboxHandlers({
|
|
28949
29338
|
manager: deps.inboxManager,
|
|
28950
|
-
|
|
29339
|
+
personStore: deps.personStore,
|
|
29340
|
+
aliasStore: deps.personAliasStore
|
|
29341
|
+
}),
|
|
29342
|
+
...buildRemotePersonaHandlers({
|
|
29343
|
+
store: deps.remotePersonaStore,
|
|
29344
|
+
personStore: deps.personStore,
|
|
29345
|
+
aliasStore: deps.personAliasStore
|
|
29346
|
+
}),
|
|
29347
|
+
...buildPersonHandlers({
|
|
29348
|
+
personStore: deps.personStore,
|
|
29349
|
+
aliasStore: deps.personAliasStore,
|
|
29350
|
+
capabilityManager: deps.capabilityManager,
|
|
29351
|
+
remotePersonaStore: deps.remotePersonaStore,
|
|
29352
|
+
// inbox channel cascade: person:delete 时用 inboxStore.list 统计每个 cap channel
|
|
29353
|
+
// 消息数 sum 起来作为 deletedInboxEvents 返回 (实际删除走 capability:delete
|
|
29354
|
+
// onDeleted hook → inboxManager.removeChannel, cascade 不主动 rm 文件).
|
|
29355
|
+
inboxStore: deps.inboxStore
|
|
28951
29356
|
}),
|
|
28952
|
-
...buildRemotePersonaHandlers({ store: deps.remotePersonaStore }),
|
|
28953
29357
|
whoami: buildWhoamiHandler({
|
|
28954
29358
|
ownerDisplayName: deps.ownerDisplayName,
|
|
29359
|
+
ownerPrincipalId: deps.ownerPrincipalId,
|
|
28955
29360
|
personaStore: deps.personaStore,
|
|
28956
29361
|
capabilityManager: deps.capabilityManager
|
|
28957
29362
|
}),
|
|
@@ -28977,17 +29382,23 @@ var METHOD_GRANT_MAP = {
|
|
|
28977
29382
|
// ---- capability platform(admin-only,本 PR 新增) ----
|
|
28978
29383
|
"capability:issue": ADMIN_ANY,
|
|
28979
29384
|
"capability:list": ADMIN_ANY,
|
|
28980
|
-
"capability:
|
|
28981
|
-
// ---- inbox
|
|
28982
|
-
|
|
28983
|
-
|
|
28984
|
-
|
|
28985
|
-
|
|
29385
|
+
"capability:delete": ADMIN_ANY,
|
|
29386
|
+
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
29387
|
+
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
29388
|
+
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
29389
|
+
"inbox:list": { kind: "public" },
|
|
29390
|
+
"inbox:markRead": { kind: "public" },
|
|
28986
29391
|
"inbox:postMessage": { kind: "public" },
|
|
28987
29392
|
// Phase 4 Task 4.3: 远程 persona 仅 owner 管理 (admin-only)
|
|
28988
29393
|
"remote-persona:add": ADMIN_ANY,
|
|
28989
29394
|
"remote-persona:list": ADMIN_ANY,
|
|
28990
29395
|
"remote-persona:remove": ADMIN_ANY,
|
|
29396
|
+
// Person identity Phase 1: 联系人本质是 owner 视角的 People 列表,仅 owner 管理。
|
|
29397
|
+
"person:list": ADMIN_ANY,
|
|
29398
|
+
"person:create": ADMIN_ANY,
|
|
29399
|
+
"person:update": ADMIN_ANY,
|
|
29400
|
+
"person:delete": ADMIN_ANY,
|
|
29401
|
+
"person:link": ADMIN_ANY,
|
|
28991
29402
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
28992
29403
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
28993
29404
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -29034,17 +29445,14 @@ var METHOD_GRANT_MAP = {
|
|
|
29034
29445
|
"persona:get": ADMIN_ANY,
|
|
29035
29446
|
"persona:update": ADMIN_ANY,
|
|
29036
29447
|
"persona:delete": ADMIN_ANY,
|
|
29037
|
-
"persona:issueToken": ADMIN_ANY,
|
|
29038
|
-
"persona:revokeToken": ADMIN_ANY,
|
|
29039
29448
|
"session:pty:input": ADMIN_ANY,
|
|
29040
29449
|
"session:pty:resize": ADMIN_ANY,
|
|
29041
|
-
// file-sharing attachment
|
|
29042
|
-
//
|
|
29450
|
+
// file-sharing attachment.* RPC:dispatcher 用 admin-only 兜底(wire-level 拦),
|
|
29451
|
+
// 实际只有 owner 能调(personal token 链路 2026-05-21 删除,HTTP Bearer 只识别 owner)
|
|
29043
29452
|
"attachment.signUrl": ADMIN_ANY,
|
|
29044
29453
|
"attachment.groupAdd": ADMIN_ANY,
|
|
29045
29454
|
"attachment.groupRemove": ADMIN_ANY,
|
|
29046
|
-
"attachment.groupList": ADMIN_ANY
|
|
29047
|
-
"attachment.groupListPersona": ADMIN_ANY
|
|
29455
|
+
"attachment.groupList": ADMIN_ANY
|
|
29048
29456
|
};
|
|
29049
29457
|
function computeGrantForFrame(method, frame) {
|
|
29050
29458
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -29074,13 +29482,20 @@ async function startDaemon(config) {
|
|
|
29074
29482
|
if (pre.status === "stale") {
|
|
29075
29483
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
29076
29484
|
}
|
|
29485
|
+
const auth = loadOrCreateAuth({ dataDir: config.dataDir });
|
|
29077
29486
|
let resolvedAuthToken = null;
|
|
29078
29487
|
if (config.authToken && config.authToken.trim()) {
|
|
29079
29488
|
resolvedAuthToken = config.authToken.trim();
|
|
29080
29489
|
} else if (config.tunnel) {
|
|
29081
|
-
resolvedAuthToken =
|
|
29490
|
+
resolvedAuthToken = auth.token;
|
|
29082
29491
|
}
|
|
29492
|
+
const ownerPrincipalId = auth.ownerPrincipalId;
|
|
29493
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29083
29494
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
29495
|
+
const inboxStore = new InboxStore(config.dataDir);
|
|
29496
|
+
const inboxManager = new InboxManager(inboxStore, (capabilityId, frame) => {
|
|
29497
|
+
wsServer?.broadcastToCapabilityChannel(capabilityId, frame);
|
|
29498
|
+
});
|
|
29084
29499
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
29085
29500
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
29086
29501
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
@@ -29091,46 +29506,66 @@ async function startDaemon(config) {
|
|
|
29091
29506
|
token
|
|
29092
29507
|
});
|
|
29093
29508
|
},
|
|
29094
|
-
|
|
29509
|
+
onDeleted: (cap) => {
|
|
29510
|
+
const deletedAt = Date.now();
|
|
29095
29511
|
wsServer?.broadcastToOwners({
|
|
29096
|
-
type: "capability:
|
|
29512
|
+
type: "capability:tokenDeleted",
|
|
29097
29513
|
capabilityId: cap.id,
|
|
29098
|
-
|
|
29514
|
+
deletedAt
|
|
29099
29515
|
});
|
|
29100
29516
|
wsServer?.closeConnectionsByCapability(cap.id);
|
|
29101
29517
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
29102
29518
|
if (cleanup.removed.length > 0) {
|
|
29103
|
-
logger.info("capability
|
|
29519
|
+
logger.info("capability delete cascade: guest sessions removed", {
|
|
29104
29520
|
capabilityId: cap.id,
|
|
29105
29521
|
removedDirs: cleanup.removed
|
|
29106
29522
|
});
|
|
29107
29523
|
}
|
|
29524
|
+
inboxManager.removeChannel(cap.id);
|
|
29108
29525
|
}
|
|
29109
29526
|
});
|
|
29110
|
-
const inboxStore = new InboxStore(config.dataDir);
|
|
29111
29527
|
const remotePersonaStore = new RemotePersonaStore(config.dataDir);
|
|
29112
|
-
const
|
|
29113
|
-
|
|
29114
|
-
const fromId = frame.event.fromPrincipal.id;
|
|
29115
|
-
const toId = frame.event.toPrincipal.id;
|
|
29116
|
-
wsServer?.broadcastToPrincipal(fromId, frame);
|
|
29117
|
-
if (toId !== fromId) wsServer?.broadcastToPrincipal(toId, frame);
|
|
29118
|
-
return;
|
|
29119
|
-
}
|
|
29120
|
-
wsServer?.broadcastToOwners(frame);
|
|
29121
|
-
});
|
|
29528
|
+
const personStore = new PersonStore(config.dataDir);
|
|
29529
|
+
const personAliasStore = new PersonAliasStore(config.dataDir);
|
|
29122
29530
|
let wsServer = null;
|
|
29123
29531
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
29124
29532
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
29125
29533
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
29126
29534
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
29127
29535
|
expectedToken: resolvedAuthToken,
|
|
29128
|
-
authenticate: (t) =>
|
|
29129
|
-
|
|
29130
|
-
|
|
29131
|
-
|
|
29536
|
+
authenticate: (t, selfPrincipalId) => {
|
|
29537
|
+
const r = authenticate(t, {
|
|
29538
|
+
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
29539
|
+
ownerPrincipalId,
|
|
29540
|
+
ownerDisplayName,
|
|
29541
|
+
capabilityRegistry
|
|
29542
|
+
});
|
|
29543
|
+
if (r.ok && selfPrincipalId && r.context.principal.kind === "guest" && r.context.capabilityId) {
|
|
29544
|
+
const capId = r.context.capabilityId;
|
|
29545
|
+
const cap = capabilityManager.findById(capId);
|
|
29546
|
+
if (cap && cap.linkedPrincipalId === void 0) {
|
|
29547
|
+
capabilityManager.updateLinkedPrincipalId(capId, selfPrincipalId);
|
|
29548
|
+
try {
|
|
29549
|
+
personAliasStore.rekey(capId, selfPrincipalId);
|
|
29550
|
+
} catch (e) {
|
|
29551
|
+
logger.warn("auth.alias.rekey.failed", {
|
|
29552
|
+
capId,
|
|
29553
|
+
selfPrincipalId,
|
|
29554
|
+
err: e.message
|
|
29555
|
+
});
|
|
29556
|
+
}
|
|
29557
|
+
} else if (cap && cap.linkedPrincipalId !== selfPrincipalId) {
|
|
29558
|
+
logger.warn("auth.selfPrincipalId.mismatch", {
|
|
29559
|
+
capId,
|
|
29560
|
+
expected: cap.linkedPrincipalId,
|
|
29561
|
+
got: selfPrincipalId
|
|
29562
|
+
});
|
|
29563
|
+
}
|
|
29564
|
+
}
|
|
29565
|
+
return r;
|
|
29566
|
+
},
|
|
29132
29567
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
29133
|
-
buildOwnerContext: ownerContext,
|
|
29568
|
+
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
29134
29569
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
29135
29570
|
sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
|
|
29136
29571
|
}) : null;
|
|
@@ -29157,7 +29592,6 @@ async function startDaemon(config) {
|
|
|
29157
29592
|
} else {
|
|
29158
29593
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
29159
29594
|
}
|
|
29160
|
-
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29161
29595
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
29162
29596
|
const manager = new SessionManager({
|
|
29163
29597
|
store,
|
|
@@ -29314,24 +29748,29 @@ async function startDaemon(config) {
|
|
|
29314
29748
|
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
29315
29749
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
29316
29750
|
ownerDisplayName,
|
|
29751
|
+
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
29752
|
+
ownerPrincipalId,
|
|
29317
29753
|
personaStore,
|
|
29318
|
-
//
|
|
29754
|
+
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
29319
29755
|
capabilityRegistry,
|
|
29320
|
-
//
|
|
29756
|
+
// capability platform v3 inbox: handler 接 manager (post/list/markRead),
|
|
29757
|
+
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
29321
29758
|
inboxManager,
|
|
29759
|
+
inboxStore,
|
|
29322
29760
|
// Phase 4 Task 4.3: remote-persona:* handler 依赖 (本地存储, v1 不接 outgoing WS)
|
|
29323
|
-
remotePersonaStore
|
|
29761
|
+
remotePersonaStore,
|
|
29762
|
+
// Person identity Phase 1 (B13): person:* RPC handlers + 给 capability:issue /
|
|
29763
|
+
// remote-persona:add atomic 写 alias / cascade 删 Person 用
|
|
29764
|
+
personStore,
|
|
29765
|
+
personAliasStore
|
|
29324
29766
|
});
|
|
29325
29767
|
const authResolver = new AuthContextResolver({
|
|
29326
|
-
ownerToken: resolvedAuthToken
|
|
29327
|
-
personaRegistry
|
|
29768
|
+
ownerToken: resolvedAuthToken
|
|
29328
29769
|
});
|
|
29329
29770
|
const httpRouter = createHttpRouter({
|
|
29330
29771
|
authResolver,
|
|
29331
29772
|
daemonVersion: version,
|
|
29332
29773
|
logger,
|
|
29333
|
-
personaStore,
|
|
29334
|
-
groupFileStore,
|
|
29335
29774
|
sessionStore: store,
|
|
29336
29775
|
// /files HMAC verify 用同一份 owner token 做 secret(与 attachment.signUrl 同源)
|
|
29337
29776
|
getSignSecret: () => resolvedAuthToken ?? null
|
|
@@ -29340,6 +29779,8 @@ async function startDaemon(config) {
|
|
|
29340
29779
|
host: config.host,
|
|
29341
29780
|
port: config.port,
|
|
29342
29781
|
logger,
|
|
29782
|
+
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
29783
|
+
ownerPrincipalId,
|
|
29343
29784
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
29344
29785
|
{
|
|
29345
29786
|
manager,
|
|
@@ -29355,6 +29796,19 @@ async function startDaemon(config) {
|
|
|
29355
29796
|
),
|
|
29356
29797
|
protocolVersion: PROTOCOL_VERSION,
|
|
29357
29798
|
authGate: authGate ?? void 0,
|
|
29799
|
+
// noAuth 模式下仍验 capability token: 修 capability platform 漏洞 — 远端 client
|
|
29800
|
+
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
29801
|
+
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
29802
|
+
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
29803
|
+
tryVerifyCapabilityToken: (token) => {
|
|
29804
|
+
const v2 = capabilityRegistry.verifyToken(token);
|
|
29805
|
+
if (!v2.ok) return null;
|
|
29806
|
+
return {
|
|
29807
|
+
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
29808
|
+
grants: v2.capability.grants,
|
|
29809
|
+
capabilityId: v2.capability.id
|
|
29810
|
+
};
|
|
29811
|
+
},
|
|
29358
29812
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29359
29813
|
httpRequestHandler: httpRouter,
|
|
29360
29814
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
@@ -29407,7 +29861,7 @@ async function startDaemon(config) {
|
|
|
29407
29861
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
29408
29862
|
const handler = handlers[type];
|
|
29409
29863
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
29410
|
-
const ctx = wsServer.getClientContext(client.id) ?? ownerContext();
|
|
29864
|
+
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
29411
29865
|
const verdict = computeGrantForFrame(type, frame);
|
|
29412
29866
|
if (verdict.kind === "check") {
|
|
29413
29867
|
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|