@clawos-dev/clawd 0.2.76 → 0.2.77-beta.145.522509b

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +604 -2351
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -87,16 +87,15 @@ var init_methods = __esm({
87
87
  "git:branches",
88
88
  "capabilities:get",
89
89
  // ---- persona:* (persona 管理) ----
90
- // persona:list 响应 shape 不分流(统一返 PersonaFile[]),但 daemon handler 按
91
- // ConnectionContext 投影不同子集:
92
- // owner ctx → 返全量 personas(含 private)
93
- // guest ctx → persona.public === true ∧ assertGrant(ctx.grants, {persona,p.id}, 'read')
94
- // method-grants.ts: persona:list 是 CAPABILITY_SCOPED;其它 persona:* 仍 ADMIN_ANY。
95
90
  "persona:create",
96
91
  "persona:list",
97
92
  "persona:get",
98
93
  "persona:update",
99
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",
100
99
  // ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
101
100
  // session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
102
101
  // 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
@@ -107,45 +106,14 @@ var init_methods = __esm({
107
106
  // ---- attachment.* file-sharing(详见 attachment-schemas.ts) ----
108
107
  // 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
109
108
  // (attachment-skills / attachment-deferred-tools / attachment_memories)是不同概念。
110
- // 全部管理类 RPC dispatcher grant 限 ownercapability admin-only);
111
- // 实际文件传输走 HTTP 路由(GET /files?p=&e=&s=,HMAC 签名自包含,不在白名单内)。
109
+ // 全部管理类 RPC handler 入口 requireOwnerpersonal token 调任意一个都 403);
110
+ // 实际文件传输走 HTTP 路由(GET /files?p=&e=&s=,签名验证),不在白名单内。
112
111
  "attachment.signUrl",
113
112
  "attachment.groupAdd",
114
113
  "attachment.groupRemove",
115
114
  "attachment.groupList",
116
- // ---- capability:* (capability platform 鉴权底座) ----
117
- // owner 颁发 / 列出 / 删除给 guest 的 capability。三者均需 admin 权限(METHOD_GRANT_MAP
118
- // 在 daemon 端固定为 `{ resource: '*', action: 'admin' }`,owner 自动满足)。
119
- // 颁发后 daemon 推 'capability:tokenIssued' 帧;删除推 'capability:tokenDeleted' 帧。
120
- // 删除是 hard delete:CapabilityStore 物理移除 + 关该 cap 的所有活跃 ws + rm
121
- // personas/<pid>/.clawd/sessions/guests/<capId>/ guest sessions 目录。
122
- "capability:issue",
123
- "capability:list",
124
- "capability:delete",
125
- // bidirectional cap 视角 B 2026-05-23: owner 主动告诉 daemon "我颁的某 cap 给了谁"
126
- // (UI 在 accept dialog 4 步流程里调; 等价于 recordPeerHello 但不经 ws auth path).
127
- // 否则 B 端颁回的 cap 因为 A 不连 B → peerOwnerId 永不回写 → useContacts 派生 pending.
128
- "capability:bindPeer",
129
- // ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
130
- // owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
131
- // inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
132
- // 任何 principal (owner/guest) 可调 (Phase 4).
133
- "inbox:list",
134
- "inbox:markRead",
135
- "inbox:postMessage",
136
- // ---- received-capability:* (对方颁给我的 cap, 视角 B 双向授权模型, 2026-05-23) ----
137
- // owner 入口: list / add / remove
138
- // guest 端: autoAttach (B 接 A 的 token 后通过 cap channel ws 推 cap 给 A)
139
- // 详见 received-capability.ts JSDoc + spec 2026-05-23-bidirectional-cap-design.md
140
- "received-capability:list",
141
- "received-capability:add",
142
- "received-capability:remove",
143
- "received-capability:autoAttach",
144
- // ---- whoami (v2 capability platform) ----
145
- // 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
146
- // grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
147
- // 的 preview 一次性临时 client 用它判定身份和可用 persona。
148
- "whoami",
115
+ // v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
116
+ "attachment.groupListPersona",
149
117
  "info",
150
118
  "ping"
151
119
  ];
@@ -641,8 +609,8 @@ var init_parseUtil = __esm({
641
609
  init_errors2();
642
610
  init_en();
643
611
  makeIssue = (params) => {
644
- const { data, path: path39, errorMaps, issueData } = params;
645
- const fullPath = [...path39, ...issueData.path || []];
612
+ const { data, path: path32, errorMaps, issueData } = params;
613
+ const fullPath = [...path32, ...issueData.path || []];
646
614
  const fullIssue = {
647
615
  ...issueData,
648
616
  path: fullPath
@@ -953,11 +921,11 @@ var init_types = __esm({
953
921
  init_parseUtil();
954
922
  init_util();
955
923
  ParseInputLazyPath = class {
956
- constructor(parent, value, path39, key) {
924
+ constructor(parent, value, path32, key) {
957
925
  this._cachedPath = [];
958
926
  this.parent = parent;
959
927
  this.data = value;
960
- this._path = path39;
928
+ this._path = path32;
961
929
  this._key = key;
962
930
  }
963
931
  get path() {
@@ -4341,12 +4309,12 @@ var init_zod = __esm({
4341
4309
  });
4342
4310
 
4343
4311
  // ../protocol/src/attachment-schemas.ts
4344
- var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
4312
+ var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentGroupListPersonaArgs, AttachmentGroupListPersonaResponseSchema;
4345
4313
  var init_attachment_schemas = __esm({
4346
4314
  "../protocol/src/attachment-schemas.ts"() {
4347
4315
  "use strict";
4348
4316
  init_zod();
4349
- TOKEN_ROLES = ["owner"];
4317
+ TOKEN_ROLES = ["owner", "personal"];
4350
4318
  GROUP_FILE_SOURCES = ["agent", "owner"];
4351
4319
  GroupFileEntrySchema = external_exports.object({
4352
4320
  /** daemon 派发的稳定 id(用于 RPC remove / UI key) */
@@ -4402,15 +4370,31 @@ var init_attachment_schemas = __esm({
4402
4370
  AttachmentGroupListResponseSchema = external_exports.object({
4403
4371
  entries: external_exports.array(GroupFileEntrySchema)
4404
4372
  });
4373
+ AttachmentGroupListPersonaArgs = external_exports.object({
4374
+ personaId: external_exports.string().min(1)
4375
+ });
4376
+ AttachmentGroupListPersonaResponseSchema = external_exports.object({
4377
+ perSession: external_exports.array(
4378
+ external_exports.object({
4379
+ sessionId: external_exports.string().min(1),
4380
+ entries: external_exports.array(GroupFileEntrySchema)
4381
+ })
4382
+ )
4383
+ });
4405
4384
  }
4406
4385
  });
4407
4386
 
4408
4387
  // ../protocol/src/persona-schemas.ts
4409
- var PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
4388
+ var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema;
4410
4389
  var init_persona_schemas = __esm({
4411
4390
  "../protocol/src/persona-schemas.ts"() {
4412
4391
  "use strict";
4413
4392
  init_zod();
4393
+ PersonaTokenEntrySchema = external_exports.object({
4394
+ label: external_exports.string(),
4395
+ issuedAt: external_exports.number(),
4396
+ revoked: external_exports.boolean()
4397
+ });
4414
4398
  PersonaFileSchema = external_exports.object({
4415
4399
  personaId: external_exports.string(),
4416
4400
  label: external_exports.string(),
@@ -4419,6 +4403,13 @@ var init_persona_schemas = __esm({
4419
4403
  // 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
4420
4404
  // for forward compatibility, consumer fallbacks to default when key not found.
4421
4405
  iconKey: external_exports.string().optional(),
4406
+ /**
4407
+ * Persona token 列表:file-sharing HTTP Bearer 鉴权用。
4408
+ * - 写:`PersonaManager.issueToken` / `revokeToken`
4409
+ * - 读:`PersonaRegistry.findByToken` 反向查表(需 persona.public === true)
4410
+ * - optional:兼容历史 persona.json 不含此字段的情形(旧 daemon 写文件时省略)
4411
+ */
4412
+ tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema).optional(),
4422
4413
  createdAt: external_exports.number(),
4423
4414
  updatedAt: external_exports.number()
4424
4415
  }).strict();
@@ -4477,6 +4468,14 @@ var init_persona_schemas = __esm({
4477
4468
  iconKey: external_exports.string().nullable().optional()
4478
4469
  }).strict()
4479
4470
  }).strict();
4471
+ PersonaIssueTokenArgsSchema = external_exports.object({
4472
+ personaId: external_exports.string().min(1),
4473
+ label: external_exports.string().min(1)
4474
+ });
4475
+ PersonaRevokeTokenArgsSchema = external_exports.object({
4476
+ personaId: external_exports.string().min(1),
4477
+ token: external_exports.string().min(1)
4478
+ });
4480
4479
  }
4481
4480
  });
4482
4481
 
@@ -4583,15 +4582,6 @@ var init_schemas = __esm({
4583
4582
  // 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
4584
4583
  // optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
4585
4584
  chatId: external_exports.string().min(1).optional(),
4586
- /**
4587
- * 创建该 session 的 principal id(owner uuid 或 guest cap.id),从 session:create handler
4588
- * 的 ctx.principal.id 派生。Person identity Q2「按对端人聚合 sessions」需要它:
4589
- * UI 端按 `creatorPrincipalId === ownerPrincipalId` 区分「我自己」vs「别人接入」,
4590
- * 后者再通过 PersonAlias 表反查 personId 二级分组。
4591
- *
4592
- * optional:兼容 2026-05-21 之前的老 session 数据,新建一定有值。
4593
- */
4594
- creatorPrincipalId: external_exports.string().min(1).optional(),
4595
4585
  createdAt: external_exports.string().min(1),
4596
4586
  updatedAt: external_exports.string().min(1)
4597
4587
  });
@@ -5001,20 +4991,7 @@ var init_schemas = __esm({
5001
4991
  AuthRequestFrameSchema = external_exports.object({
5002
4992
  type: external_exports.literal("auth"),
5003
4993
  token: external_exports.string().min(1),
5004
- scheme: external_exports.literal("bearer").optional(),
5005
- /**
5006
- * guest 端 ws 接入时自报对端稳定 ownerPrincipalId。AuthGate.authenticate 收到
5007
- * 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
5008
- * 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
5009
- * tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
5010
- */
5011
- selfPrincipalId: external_exports.string().min(1).optional(),
5012
- /**
5013
- * 同 selfPrincipalId:自报 owner displayName(人类可读),让对端 daemon 把
5014
- * 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
5015
- * 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
5016
- */
5017
- selfDisplayName: external_exports.string().optional()
4994
+ scheme: external_exports.literal("bearer").optional()
5018
4995
  });
5019
4996
  AuthOkFrameSchema = external_exports.object({
5020
4997
  type: external_exports.literal("auth:ok"),
@@ -5052,9 +5029,10 @@ var init_schemas = __esm({
5052
5029
  // PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
5053
5030
  // 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
5054
5031
  // 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
5055
- /** 'owner' = 拿到 owner-token 的连接(不限来源 IP)。来源 TOKEN_ROLES(spec §11 #1 中央真理源)。
5056
- * 历史 'personal' role(persona file-sharing token)在 2026-05-21 删除;tokenPersonaId 一同移除。 */
5032
+ /** 'owner' = 拿到 owner-token 的连接(不限来源 IP);'personal' = persona 派发的访客 token。来源 TOKEN_ROLES(spec §11 #1 中央真理源) */
5057
5033
  tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
5034
+ /** tokenRole='personal' 时携带(绑定到具体 persona);owner 不携带 */
5035
+ tokenPersonaId: external_exports.string().min(1).optional(),
5058
5036
  /** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
5059
5037
  isLoopback: external_exports.boolean().optional(),
5060
5038
  /** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
@@ -5081,194 +5059,6 @@ var init_persona_mode = __esm({
5081
5059
  }
5082
5060
  });
5083
5061
 
5084
- // ../protocol/src/principal.ts
5085
- function makeOwnerPrincipal(id, displayName) {
5086
- return PrincipalSchema.parse({ id, kind: "owner", displayName });
5087
- }
5088
- var PrincipalKindSchema, PrincipalSchema;
5089
- var init_principal = __esm({
5090
- "../protocol/src/principal.ts"() {
5091
- "use strict";
5092
- init_zod();
5093
- PrincipalKindSchema = external_exports.enum(["owner", "guest"]);
5094
- PrincipalSchema = external_exports.object({
5095
- id: external_exports.string().min(1),
5096
- kind: PrincipalKindSchema,
5097
- displayName: external_exports.string()
5098
- }).strict();
5099
- }
5100
- });
5101
-
5102
- // ../protocol/src/capability.ts
5103
- function stripSecretHash(cap) {
5104
- const { secretHash: _hash, ...wire } = cap;
5105
- return wire;
5106
- }
5107
- var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, CapabilityIssueArgsSchema, CapabilityBindPeerArgsSchema;
5108
- var init_capability = __esm({
5109
- "../protocol/src/capability.ts"() {
5110
- "use strict";
5111
- init_zod();
5112
- ResourceSchema = external_exports.discriminatedUnion("type", [
5113
- external_exports.object({ type: external_exports.literal("persona"), id: external_exports.string().min(1) }).strict(),
5114
- external_exports.object({ type: external_exports.literal("chat"), id: external_exports.string().min(1) }).strict(),
5115
- external_exports.object({ type: external_exports.literal("*") }).strict()
5116
- ]);
5117
- ActionSchema = external_exports.enum(["read", "send", "admin"]);
5118
- GrantSchema = external_exports.object({
5119
- resource: ResourceSchema,
5120
- actions: external_exports.array(ActionSchema).min(1)
5121
- }).strict();
5122
- CapabilitySchema = external_exports.object({
5123
- id: external_exports.string().min(1),
5124
- // sha256(token) hex 64
5125
- secretHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
5126
- displayName: external_exports.string(),
5127
- // 空数组合法 = 纯访客(只能 DM)
5128
- grants: external_exports.array(GrantSchema),
5129
- issuedAt: external_exports.number().int().nonnegative(),
5130
- expiresAt: external_exports.number().int().positive().optional(),
5131
- maxUses: external_exports.number().int().positive().optional(),
5132
- usedCount: external_exports.number().int().nonnegative(),
5133
- /**
5134
- * guest 在 auth 帧捎带 selfPrincipalId 时由 AuthGate.authenticate 回写;
5135
- * 未被消费的 cap 此字段缺失。
5136
- */
5137
- peerOwnerId: external_exports.string().min(1).optional(),
5138
- /** 首次 client hello 时间戳(与 peerOwnerId 同步写入) */
5139
- firstUsedByPeerAt: external_exports.number().int().positive().optional(),
5140
- /**
5141
- * 对方 daemon owner 的 displayName,guest auth 帧 selfDisplayName 触发
5142
- * recordPeerHello 时同步回写。视角 B (2026-05-23) UI 派生联系人列表用此字段
5143
- * 显示对方名字(避免 fallback 到我颁 cap 时填的 displayName)。
5144
- */
5145
- peerDisplayName: external_exports.string().optional()
5146
- }).strict();
5147
- CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
5148
- CapabilityErrorCodeSchema = external_exports.enum([
5149
- "TOKEN_INVALID",
5150
- "TOKEN_REVOKED",
5151
- "TOKEN_EXPIRED",
5152
- "TOKEN_EXHAUSTED"
5153
- ]);
5154
- WhoamiResponseSchema = external_exports.object({
5155
- type: external_exports.literal("whoami:ok"),
5156
- owner: external_exports.object({
5157
- id: external_exports.string(),
5158
- kind: external_exports.enum(["owner", "guest"]),
5159
- displayName: external_exports.string()
5160
- }).strict(),
5161
- capability: CapabilityWireSchema,
5162
- grantedPersonas: external_exports.array(
5163
- external_exports.object({
5164
- id: external_exports.string().min(1),
5165
- displayName: external_exports.string()
5166
- }).strict()
5167
- )
5168
- }).strict();
5169
- CapabilityIssueArgsSchema = external_exports.object({
5170
- displayName: external_exports.string().min(1),
5171
- grants: external_exports.array(GrantSchema),
5172
- expiresAt: external_exports.number().int().positive().optional(),
5173
- maxUses: external_exports.number().int().positive().optional()
5174
- }).strict();
5175
- CapabilityBindPeerArgsSchema = external_exports.object({
5176
- capabilityId: external_exports.string().min(1),
5177
- peerOwnerId: external_exports.string().min(1),
5178
- peerDisplayName: external_exports.string()
5179
- }).strict();
5180
- }
5181
- });
5182
-
5183
- // ../protocol/src/inbox.ts
5184
- var InboxMessageSchema, InboxPostMessageArgsSchema, InboxListArgsSchema, InboxMarkReadArgsSchema;
5185
- var init_inbox = __esm({
5186
- "../protocol/src/inbox.ts"() {
5187
- "use strict";
5188
- init_zod();
5189
- InboxMessageSchema = external_exports.object({
5190
- id: external_exports.string().min(1),
5191
- peerOwnerId: external_exports.string().min(1),
5192
- senderPrincipalId: external_exports.string().min(1),
5193
- text: external_exports.string().min(1),
5194
- createdAt: external_exports.number().int().nonnegative(),
5195
- readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
5196
- }).strict();
5197
- InboxPostMessageArgsSchema = external_exports.object({
5198
- peerOwnerId: external_exports.string().min(1).optional(),
5199
- id: external_exports.string().min(1),
5200
- text: external_exports.string().min(1),
5201
- createdAt: external_exports.number().int().nonnegative()
5202
- }).strict();
5203
- InboxListArgsSchema = external_exports.object({
5204
- peerOwnerId: external_exports.string().min(1),
5205
- sinceCreatedAt: external_exports.number().int().nonnegative().optional()
5206
- }).strict();
5207
- InboxMarkReadArgsSchema = external_exports.object({
5208
- peerOwnerId: external_exports.string().min(1),
5209
- upToCreatedAt: external_exports.number().int().nonnegative()
5210
- }).strict();
5211
- }
5212
- });
5213
-
5214
- // ../protocol/src/received-capability.ts
5215
- var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityAddArgsSchema, ReceivedCapabilityAddOkSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAutoAttachArgsSchema, ReceivedCapabilityAutoAttachOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
5216
- var init_received_capability = __esm({
5217
- "../protocol/src/received-capability.ts"() {
5218
- "use strict";
5219
- init_zod();
5220
- init_capability();
5221
- ReceivedCapabilitySchema = external_exports.object({
5222
- ownerPrincipalId: external_exports.string().min(1),
5223
- ownerDisplayName: external_exports.string(),
5224
- remoteUrl: external_exports.string().min(1),
5225
- capabilityId: external_exports.string().min(1),
5226
- capabilityToken: external_exports.string().min(1),
5227
- grants: external_exports.array(GrantSchema),
5228
- receivedAt: external_exports.number().int().nonnegative()
5229
- }).strict();
5230
- ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
5231
- ReceivedCapabilityAddArgsSchema = external_exports.object({
5232
- remoteUrl: external_exports.string().min(1),
5233
- token: external_exports.string().min(1)
5234
- }).strict();
5235
- ReceivedCapabilityAddOkSchema = external_exports.object({
5236
- type: external_exports.literal("received-capability:add:ok"),
5237
- receivedCap: ReceivedCapabilityWireSchema
5238
- }).strict();
5239
- ReceivedCapabilityRemoveArgsSchema = external_exports.object({
5240
- ownerPrincipalId: external_exports.string().min(1)
5241
- }).strict();
5242
- ReceivedCapabilityRemoveOkSchema = external_exports.object({
5243
- type: external_exports.literal("received-capability:remove:ok"),
5244
- ownerPrincipalId: external_exports.string().min(1)
5245
- }).strict();
5246
- ReceivedCapabilityListOkSchema = external_exports.object({
5247
- type: external_exports.literal("received-capability:list:ok"),
5248
- receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
5249
- }).strict();
5250
- ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
5251
- ownerPrincipalId: external_exports.string().min(1),
5252
- ownerDisplayName: external_exports.string(),
5253
- remoteUrl: external_exports.string().min(1),
5254
- capabilityId: external_exports.string().min(1),
5255
- token: external_exports.string().min(1),
5256
- grants: external_exports.array(GrantSchema)
5257
- }).strict();
5258
- ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
5259
- type: external_exports.literal("received-capability:autoAttach:ok")
5260
- }).strict();
5261
- ReceivedCapabilityAddedFrameSchema = external_exports.object({
5262
- type: external_exports.literal("received-capability:added"),
5263
- receivedCap: ReceivedCapabilityWireSchema
5264
- }).strict();
5265
- ReceivedCapabilityRemovedFrameSchema = external_exports.object({
5266
- type: external_exports.literal("received-capability:removed"),
5267
- ownerPrincipalId: external_exports.string().min(1)
5268
- }).strict();
5269
- }
5270
- });
5271
-
5272
5062
  // ../protocol/src/runtime.ts
5273
5063
  var init_runtime = __esm({
5274
5064
  "../protocol/src/runtime.ts"() {
@@ -5281,10 +5071,6 @@ var init_runtime = __esm({
5281
5071
  init_persona_schemas();
5282
5072
  init_persona_mode();
5283
5073
  init_attachment_schemas();
5284
- init_principal();
5285
- init_capability();
5286
- init_inbox();
5287
- init_received_capability();
5288
5074
  }
5289
5075
  });
5290
5076
 
@@ -5559,8 +5345,8 @@ var require_req = __commonJS({
5559
5345
  if (req.originalUrl) {
5560
5346
  _req.url = req.originalUrl;
5561
5347
  } else {
5562
- const path39 = req.path;
5563
- _req.url = typeof path39 === "string" ? path39 : req.url ? req.url.path || req.url : void 0;
5348
+ const path32 = req.path;
5349
+ _req.url = typeof path32 === "string" ? path32 : req.url ? req.url.path || req.url : void 0;
5564
5350
  }
5565
5351
  if (req.query) {
5566
5352
  _req.query = req.query;
@@ -5725,14 +5511,14 @@ var require_redact = __commonJS({
5725
5511
  }
5726
5512
  return obj;
5727
5513
  }
5728
- function parsePath(path39) {
5514
+ function parsePath(path32) {
5729
5515
  const parts = [];
5730
5516
  let current = "";
5731
5517
  let inBrackets = false;
5732
5518
  let inQuotes = false;
5733
5519
  let quoteChar = "";
5734
- for (let i = 0; i < path39.length; i++) {
5735
- const char = path39[i];
5520
+ for (let i = 0; i < path32.length; i++) {
5521
+ const char = path32[i];
5736
5522
  if (!inBrackets && char === ".") {
5737
5523
  if (current) {
5738
5524
  parts.push(current);
@@ -5863,10 +5649,10 @@ var require_redact = __commonJS({
5863
5649
  return current;
5864
5650
  }
5865
5651
  function redactPaths(obj, paths, censor, remove = false) {
5866
- for (const path39 of paths) {
5867
- const parts = parsePath(path39);
5652
+ for (const path32 of paths) {
5653
+ const parts = parsePath(path32);
5868
5654
  if (parts.includes("*")) {
5869
- redactWildcardPath(obj, parts, censor, path39, remove);
5655
+ redactWildcardPath(obj, parts, censor, path32, remove);
5870
5656
  } else {
5871
5657
  if (remove) {
5872
5658
  removeKey(obj, parts);
@@ -5951,8 +5737,8 @@ var require_redact = __commonJS({
5951
5737
  }
5952
5738
  } else {
5953
5739
  if (afterWildcard.includes("*")) {
5954
- const wrappedCensor = typeof censor === "function" ? (value, path39) => {
5955
- const fullPath = [...pathArray.slice(0, pathLength), ...path39];
5740
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
5741
+ const fullPath = [...pathArray.slice(0, pathLength), ...path32];
5956
5742
  return censor(value, fullPath);
5957
5743
  } : censor;
5958
5744
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -5987,8 +5773,8 @@ var require_redact = __commonJS({
5987
5773
  return null;
5988
5774
  }
5989
5775
  const pathStructure = /* @__PURE__ */ new Map();
5990
- for (const path39 of pathsToClone) {
5991
- const parts = parsePath(path39);
5776
+ for (const path32 of pathsToClone) {
5777
+ const parts = parsePath(path32);
5992
5778
  let current = pathStructure;
5993
5779
  for (let i = 0; i < parts.length; i++) {
5994
5780
  const part = parts[i];
@@ -6040,24 +5826,24 @@ var require_redact = __commonJS({
6040
5826
  }
6041
5827
  return cloneSelectively(obj, pathStructure);
6042
5828
  }
6043
- function validatePath(path39) {
6044
- if (typeof path39 !== "string") {
5829
+ function validatePath(path32) {
5830
+ if (typeof path32 !== "string") {
6045
5831
  throw new Error("Paths must be (non-empty) strings");
6046
5832
  }
6047
- if (path39 === "") {
5833
+ if (path32 === "") {
6048
5834
  throw new Error("Invalid redaction path ()");
6049
5835
  }
6050
- if (path39.includes("..")) {
6051
- throw new Error(`Invalid redaction path (${path39})`);
5836
+ if (path32.includes("..")) {
5837
+ throw new Error(`Invalid redaction path (${path32})`);
6052
5838
  }
6053
- if (path39.includes(",")) {
6054
- throw new Error(`Invalid redaction path (${path39})`);
5839
+ if (path32.includes(",")) {
5840
+ throw new Error(`Invalid redaction path (${path32})`);
6055
5841
  }
6056
5842
  let bracketCount = 0;
6057
5843
  let inQuotes = false;
6058
5844
  let quoteChar = "";
6059
- for (let i = 0; i < path39.length; i++) {
6060
- const char = path39[i];
5845
+ for (let i = 0; i < path32.length; i++) {
5846
+ const char = path32[i];
6061
5847
  if ((char === '"' || char === "'") && bracketCount > 0) {
6062
5848
  if (!inQuotes) {
6063
5849
  inQuotes = true;
@@ -6071,20 +5857,20 @@ var require_redact = __commonJS({
6071
5857
  } else if (char === "]" && !inQuotes) {
6072
5858
  bracketCount--;
6073
5859
  if (bracketCount < 0) {
6074
- throw new Error(`Invalid redaction path (${path39})`);
5860
+ throw new Error(`Invalid redaction path (${path32})`);
6075
5861
  }
6076
5862
  }
6077
5863
  }
6078
5864
  if (bracketCount !== 0) {
6079
- throw new Error(`Invalid redaction path (${path39})`);
5865
+ throw new Error(`Invalid redaction path (${path32})`);
6080
5866
  }
6081
5867
  }
6082
5868
  function validatePaths(paths) {
6083
5869
  if (!Array.isArray(paths)) {
6084
5870
  throw new TypeError("paths must be an array");
6085
5871
  }
6086
- for (const path39 of paths) {
6087
- validatePath(path39);
5872
+ for (const path32 of paths) {
5873
+ validatePath(path32);
6088
5874
  }
6089
5875
  }
6090
5876
  function slowRedact(options = {}) {
@@ -6252,8 +6038,8 @@ var require_redaction = __commonJS({
6252
6038
  if (shape[k2] === null) {
6253
6039
  o[k2] = (value) => topCensor(value, [k2]);
6254
6040
  } else {
6255
- const wrappedCensor = typeof censor === "function" ? (value, path39) => {
6256
- return censor(value, [k2, ...path39]);
6041
+ const wrappedCensor = typeof censor === "function" ? (value, path32) => {
6042
+ return censor(value, [k2, ...path32]);
6257
6043
  } : censor;
6258
6044
  o[k2] = Redact({
6259
6045
  paths: shape[k2],
@@ -6471,10 +6257,10 @@ var require_atomic_sleep = __commonJS({
6471
6257
  var require_sonic_boom = __commonJS({
6472
6258
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
6473
6259
  "use strict";
6474
- var fs34 = require("fs");
6260
+ var fs28 = require("fs");
6475
6261
  var EventEmitter2 = require("events");
6476
6262
  var inherits = require("util").inherits;
6477
- var path39 = require("path");
6263
+ var path32 = require("path");
6478
6264
  var sleep = require_atomic_sleep();
6479
6265
  var assert = require("assert");
6480
6266
  var BUSY_WRITE_TIMEOUT = 100;
@@ -6528,20 +6314,20 @@ var require_sonic_boom = __commonJS({
6528
6314
  const mode = sonic.mode;
6529
6315
  if (sonic.sync) {
6530
6316
  try {
6531
- if (sonic.mkdir) fs34.mkdirSync(path39.dirname(file), { recursive: true });
6532
- const fd = fs34.openSync(file, flags, mode);
6317
+ if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
6318
+ const fd = fs28.openSync(file, flags, mode);
6533
6319
  fileOpened(null, fd);
6534
6320
  } catch (err) {
6535
6321
  fileOpened(err);
6536
6322
  throw err;
6537
6323
  }
6538
6324
  } else if (sonic.mkdir) {
6539
- fs34.mkdir(path39.dirname(file), { recursive: true }, (err) => {
6325
+ fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
6540
6326
  if (err) return fileOpened(err);
6541
- fs34.open(file, flags, mode, fileOpened);
6327
+ fs28.open(file, flags, mode, fileOpened);
6542
6328
  });
6543
6329
  } else {
6544
- fs34.open(file, flags, mode, fileOpened);
6330
+ fs28.open(file, flags, mode, fileOpened);
6545
6331
  }
6546
6332
  }
6547
6333
  function SonicBoom(opts) {
@@ -6582,8 +6368,8 @@ var require_sonic_boom = __commonJS({
6582
6368
  this.flush = flushBuffer;
6583
6369
  this.flushSync = flushBufferSync;
6584
6370
  this._actualWrite = actualWriteBuffer;
6585
- fsWriteSync = () => fs34.writeSync(this.fd, this._writingBuf);
6586
- fsWrite = () => fs34.write(this.fd, this._writingBuf, this.release);
6371
+ fsWriteSync = () => fs28.writeSync(this.fd, this._writingBuf);
6372
+ fsWrite = () => fs28.write(this.fd, this._writingBuf, this.release);
6587
6373
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
6588
6374
  this._writingBuf = "";
6589
6375
  this.write = write;
@@ -6592,15 +6378,15 @@ var require_sonic_boom = __commonJS({
6592
6378
  this._actualWrite = actualWrite;
6593
6379
  fsWriteSync = () => {
6594
6380
  if (Buffer.isBuffer(this._writingBuf)) {
6595
- return fs34.writeSync(this.fd, this._writingBuf);
6381
+ return fs28.writeSync(this.fd, this._writingBuf);
6596
6382
  }
6597
- return fs34.writeSync(this.fd, this._writingBuf, "utf8");
6383
+ return fs28.writeSync(this.fd, this._writingBuf, "utf8");
6598
6384
  };
6599
6385
  fsWrite = () => {
6600
6386
  if (Buffer.isBuffer(this._writingBuf)) {
6601
- return fs34.write(this.fd, this._writingBuf, this.release);
6387
+ return fs28.write(this.fd, this._writingBuf, this.release);
6602
6388
  }
6603
- return fs34.write(this.fd, this._writingBuf, "utf8", this.release);
6389
+ return fs28.write(this.fd, this._writingBuf, "utf8", this.release);
6604
6390
  };
6605
6391
  } else {
6606
6392
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -6657,7 +6443,7 @@ var require_sonic_boom = __commonJS({
6657
6443
  }
6658
6444
  }
6659
6445
  if (this._fsync) {
6660
- fs34.fsyncSync(this.fd);
6446
+ fs28.fsyncSync(this.fd);
6661
6447
  }
6662
6448
  const len = this._len;
6663
6449
  if (this._reopening) {
@@ -6771,7 +6557,7 @@ var require_sonic_boom = __commonJS({
6771
6557
  const onDrain = () => {
6772
6558
  if (!this._fsync) {
6773
6559
  try {
6774
- fs34.fsync(this.fd, (err) => {
6560
+ fs28.fsync(this.fd, (err) => {
6775
6561
  this._flushPending = false;
6776
6562
  cb(err);
6777
6563
  });
@@ -6873,7 +6659,7 @@ var require_sonic_boom = __commonJS({
6873
6659
  const fd = this.fd;
6874
6660
  this.once("ready", () => {
6875
6661
  if (fd !== this.fd) {
6876
- fs34.close(fd, (err) => {
6662
+ fs28.close(fd, (err) => {
6877
6663
  if (err) {
6878
6664
  return this.emit("error", err);
6879
6665
  }
@@ -6922,7 +6708,7 @@ var require_sonic_boom = __commonJS({
6922
6708
  buf = this._bufs[0];
6923
6709
  }
6924
6710
  try {
6925
- const n = Buffer.isBuffer(buf) ? fs34.writeSync(this.fd, buf) : fs34.writeSync(this.fd, buf, "utf8");
6711
+ const n = Buffer.isBuffer(buf) ? fs28.writeSync(this.fd, buf) : fs28.writeSync(this.fd, buf, "utf8");
6926
6712
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
6927
6713
  buf = releasedBufObj.writingBuf;
6928
6714
  this._len = releasedBufObj.len;
@@ -6938,7 +6724,7 @@ var require_sonic_boom = __commonJS({
6938
6724
  }
6939
6725
  }
6940
6726
  try {
6941
- fs34.fsyncSync(this.fd);
6727
+ fs28.fsyncSync(this.fd);
6942
6728
  } catch {
6943
6729
  }
6944
6730
  }
@@ -6959,7 +6745,7 @@ var require_sonic_boom = __commonJS({
6959
6745
  buf = mergeBuf(this._bufs[0], this._lens[0]);
6960
6746
  }
6961
6747
  try {
6962
- const n = fs34.writeSync(this.fd, buf);
6748
+ const n = fs28.writeSync(this.fd, buf);
6963
6749
  buf = buf.subarray(n);
6964
6750
  this._len = Math.max(this._len - n, 0);
6965
6751
  if (buf.length <= 0) {
@@ -6987,13 +6773,13 @@ var require_sonic_boom = __commonJS({
6987
6773
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
6988
6774
  if (this.sync) {
6989
6775
  try {
6990
- const written = Buffer.isBuffer(this._writingBuf) ? fs34.writeSync(this.fd, this._writingBuf) : fs34.writeSync(this.fd, this._writingBuf, "utf8");
6776
+ const written = Buffer.isBuffer(this._writingBuf) ? fs28.writeSync(this.fd, this._writingBuf) : fs28.writeSync(this.fd, this._writingBuf, "utf8");
6991
6777
  release(null, written);
6992
6778
  } catch (err) {
6993
6779
  release(err);
6994
6780
  }
6995
6781
  } else {
6996
- fs34.write(this.fd, this._writingBuf, release);
6782
+ fs28.write(this.fd, this._writingBuf, release);
6997
6783
  }
6998
6784
  }
6999
6785
  function actualWriteBuffer() {
@@ -7002,7 +6788,7 @@ var require_sonic_boom = __commonJS({
7002
6788
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7003
6789
  if (this.sync) {
7004
6790
  try {
7005
- const written = fs34.writeSync(this.fd, this._writingBuf);
6791
+ const written = fs28.writeSync(this.fd, this._writingBuf);
7006
6792
  release(null, written);
7007
6793
  } catch (err) {
7008
6794
  release(err);
@@ -7011,7 +6797,7 @@ var require_sonic_boom = __commonJS({
7011
6797
  if (kCopyBuffer) {
7012
6798
  this._writingBuf = Buffer.from(this._writingBuf);
7013
6799
  }
7014
- fs34.write(this.fd, this._writingBuf, release);
6800
+ fs28.write(this.fd, this._writingBuf, release);
7015
6801
  }
7016
6802
  }
7017
6803
  function actualClose(sonic) {
@@ -7027,12 +6813,12 @@ var require_sonic_boom = __commonJS({
7027
6813
  sonic._lens = [];
7028
6814
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7029
6815
  try {
7030
- fs34.fsync(sonic.fd, closeWrapped);
6816
+ fs28.fsync(sonic.fd, closeWrapped);
7031
6817
  } catch {
7032
6818
  }
7033
6819
  function closeWrapped() {
7034
6820
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7035
- fs34.close(sonic.fd, done);
6821
+ fs28.close(sonic.fd, done);
7036
6822
  } else {
7037
6823
  done();
7038
6824
  }
@@ -7289,7 +7075,7 @@ var require_thread_stream = __commonJS({
7289
7075
  var { version: version2 } = require_package();
7290
7076
  var { EventEmitter: EventEmitter2 } = require("events");
7291
7077
  var { Worker } = require("worker_threads");
7292
- var { join: join11 } = require("path");
7078
+ var { join: join4 } = require("path");
7293
7079
  var { pathToFileURL } = require("url");
7294
7080
  var { wait } = require_wait();
7295
7081
  var {
@@ -7325,7 +7111,7 @@ var require_thread_stream = __commonJS({
7325
7111
  function createWorker(stream, opts) {
7326
7112
  const { filename, workerData } = opts;
7327
7113
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
7328
- const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
7114
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join4(__dirname, "lib", "worker.js");
7329
7115
  const worker = new Worker(toExecute, {
7330
7116
  ...opts.workerOpts,
7331
7117
  trackUnmanagedFds: false,
@@ -7711,7 +7497,7 @@ var require_transport = __commonJS({
7711
7497
  "use strict";
7712
7498
  var { createRequire } = require("module");
7713
7499
  var getCallers = require_caller();
7714
- var { join: join11, isAbsolute, sep: sep2 } = require("path");
7500
+ var { join: join4, isAbsolute, sep } = require("path");
7715
7501
  var sleep = require_atomic_sleep();
7716
7502
  var onExit = require_on_exit_leak_free();
7717
7503
  var ThreadStream = require_thread_stream();
@@ -7774,7 +7560,7 @@ var require_transport = __commonJS({
7774
7560
  throw new Error("only one of target or targets can be specified");
7775
7561
  }
7776
7562
  if (targets) {
7777
- target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7563
+ target = bundlerOverrides["pino-worker"] || join4(__dirname, "worker.js");
7778
7564
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
7779
7565
  return {
7780
7566
  ...dest,
@@ -7792,7 +7578,7 @@ var require_transport = __commonJS({
7792
7578
  });
7793
7579
  });
7794
7580
  } else if (pipeline2) {
7795
- target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
7581
+ target = bundlerOverrides["pino-worker"] || join4(__dirname, "worker.js");
7796
7582
  options.pipelines = [pipeline2.map((dest) => {
7797
7583
  return {
7798
7584
  ...dest,
@@ -7814,12 +7600,12 @@ var require_transport = __commonJS({
7814
7600
  return origin;
7815
7601
  }
7816
7602
  if (origin === "pino/file") {
7817
- return join11(__dirname, "..", "file.js");
7603
+ return join4(__dirname, "..", "file.js");
7818
7604
  }
7819
7605
  let fixTarget2;
7820
7606
  for (const filePath of callers) {
7821
7607
  try {
7822
- const context = filePath === "node:repl" ? process.cwd() + sep2 : filePath;
7608
+ const context = filePath === "node:repl" ? process.cwd() + sep : filePath;
7823
7609
  fixTarget2 = createRequire(context).resolve(origin);
7824
7610
  break;
7825
7611
  } catch (err) {
@@ -8804,7 +8590,7 @@ var require_safe_stable_stringify = __commonJS({
8804
8590
  return circularValue;
8805
8591
  }
8806
8592
  let res = "";
8807
- let join11 = ",";
8593
+ let join4 = ",";
8808
8594
  const originalIndentation = indentation;
8809
8595
  if (Array.isArray(value)) {
8810
8596
  if (value.length === 0) {
@@ -8818,7 +8604,7 @@ var require_safe_stable_stringify = __commonJS({
8818
8604
  indentation += spacer;
8819
8605
  res += `
8820
8606
  ${indentation}`;
8821
- join11 = `,
8607
+ join4 = `,
8822
8608
  ${indentation}`;
8823
8609
  }
8824
8610
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -8826,13 +8612,13 @@ ${indentation}`;
8826
8612
  for (; i < maximumValuesToStringify - 1; i++) {
8827
8613
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
8828
8614
  res += tmp2 !== void 0 ? tmp2 : "null";
8829
- res += join11;
8615
+ res += join4;
8830
8616
  }
8831
8617
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
8832
8618
  res += tmp !== void 0 ? tmp : "null";
8833
8619
  if (value.length - 1 > maximumBreadth) {
8834
8620
  const removedKeys = value.length - maximumBreadth - 1;
8835
- res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
8621
+ res += `${join4}"... ${getItemCount(removedKeys)} not stringified"`;
8836
8622
  }
8837
8623
  if (spacer !== "") {
8838
8624
  res += `
@@ -8853,7 +8639,7 @@ ${originalIndentation}`;
8853
8639
  let separator = "";
8854
8640
  if (spacer !== "") {
8855
8641
  indentation += spacer;
8856
- join11 = `,
8642
+ join4 = `,
8857
8643
  ${indentation}`;
8858
8644
  whitespace = " ";
8859
8645
  }
@@ -8867,13 +8653,13 @@ ${indentation}`;
8867
8653
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
8868
8654
  if (tmp !== void 0) {
8869
8655
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
8870
- separator = join11;
8656
+ separator = join4;
8871
8657
  }
8872
8658
  }
8873
8659
  if (keyLength > maximumBreadth) {
8874
8660
  const removedKeys = keyLength - maximumBreadth;
8875
8661
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
8876
- separator = join11;
8662
+ separator = join4;
8877
8663
  }
8878
8664
  if (spacer !== "" && separator.length > 1) {
8879
8665
  res = `
@@ -8914,7 +8700,7 @@ ${originalIndentation}`;
8914
8700
  }
8915
8701
  const originalIndentation = indentation;
8916
8702
  let res = "";
8917
- let join11 = ",";
8703
+ let join4 = ",";
8918
8704
  if (Array.isArray(value)) {
8919
8705
  if (value.length === 0) {
8920
8706
  return "[]";
@@ -8927,7 +8713,7 @@ ${originalIndentation}`;
8927
8713
  indentation += spacer;
8928
8714
  res += `
8929
8715
  ${indentation}`;
8930
- join11 = `,
8716
+ join4 = `,
8931
8717
  ${indentation}`;
8932
8718
  }
8933
8719
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -8935,13 +8721,13 @@ ${indentation}`;
8935
8721
  for (; i < maximumValuesToStringify - 1; i++) {
8936
8722
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
8937
8723
  res += tmp2 !== void 0 ? tmp2 : "null";
8938
- res += join11;
8724
+ res += join4;
8939
8725
  }
8940
8726
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
8941
8727
  res += tmp !== void 0 ? tmp : "null";
8942
8728
  if (value.length - 1 > maximumBreadth) {
8943
8729
  const removedKeys = value.length - maximumBreadth - 1;
8944
- res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
8730
+ res += `${join4}"... ${getItemCount(removedKeys)} not stringified"`;
8945
8731
  }
8946
8732
  if (spacer !== "") {
8947
8733
  res += `
@@ -8954,7 +8740,7 @@ ${originalIndentation}`;
8954
8740
  let whitespace = "";
8955
8741
  if (spacer !== "") {
8956
8742
  indentation += spacer;
8957
- join11 = `,
8743
+ join4 = `,
8958
8744
  ${indentation}`;
8959
8745
  whitespace = " ";
8960
8746
  }
@@ -8963,7 +8749,7 @@ ${indentation}`;
8963
8749
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
8964
8750
  if (tmp !== void 0) {
8965
8751
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
8966
- separator = join11;
8752
+ separator = join4;
8967
8753
  }
8968
8754
  }
8969
8755
  if (spacer !== "" && separator.length > 1) {
@@ -9021,20 +8807,20 @@ ${originalIndentation}`;
9021
8807
  indentation += spacer;
9022
8808
  let res2 = `
9023
8809
  ${indentation}`;
9024
- const join12 = `,
8810
+ const join5 = `,
9025
8811
  ${indentation}`;
9026
8812
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
9027
8813
  let i = 0;
9028
8814
  for (; i < maximumValuesToStringify - 1; i++) {
9029
8815
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9030
8816
  res2 += tmp2 !== void 0 ? tmp2 : "null";
9031
- res2 += join12;
8817
+ res2 += join5;
9032
8818
  }
9033
8819
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9034
8820
  res2 += tmp !== void 0 ? tmp : "null";
9035
8821
  if (value.length - 1 > maximumBreadth) {
9036
8822
  const removedKeys = value.length - maximumBreadth - 1;
9037
- res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
8823
+ res2 += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
9038
8824
  }
9039
8825
  res2 += `
9040
8826
  ${originalIndentation}`;
@@ -9050,16 +8836,16 @@ ${originalIndentation}`;
9050
8836
  return '"[Object]"';
9051
8837
  }
9052
8838
  indentation += spacer;
9053
- const join11 = `,
8839
+ const join4 = `,
9054
8840
  ${indentation}`;
9055
8841
  let res = "";
9056
8842
  let separator = "";
9057
8843
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
9058
8844
  if (isTypedArrayWithEntries(value)) {
9059
- res += stringifyTypedArray(value, join11, maximumBreadth);
8845
+ res += stringifyTypedArray(value, join4, maximumBreadth);
9060
8846
  keys = keys.slice(value.length);
9061
8847
  maximumPropertiesToStringify -= value.length;
9062
- separator = join11;
8848
+ separator = join4;
9063
8849
  }
9064
8850
  if (deterministic) {
9065
8851
  keys = sort(keys, comparator);
@@ -9070,13 +8856,13 @@ ${indentation}`;
9070
8856
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
9071
8857
  if (tmp !== void 0) {
9072
8858
  res += `${separator}${strEscape(key2)}: ${tmp}`;
9073
- separator = join11;
8859
+ separator = join4;
9074
8860
  }
9075
8861
  }
9076
8862
  if (keyLength > maximumBreadth) {
9077
8863
  const removedKeys = keyLength - maximumBreadth;
9078
8864
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
9079
- separator = join11;
8865
+ separator = join4;
9080
8866
  }
9081
8867
  if (separator !== "") {
9082
8868
  res = `
@@ -10167,11 +9953,11 @@ var init_lib = __esm({
10167
9953
  }
10168
9954
  }
10169
9955
  },
10170
- addToPath: function addToPath(path39, added, removed, oldPosInc, options) {
10171
- var last = path39.lastComponent;
9956
+ addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
9957
+ var last = path32.lastComponent;
10172
9958
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
10173
9959
  return {
10174
- oldPos: path39.oldPos + oldPosInc,
9960
+ oldPos: path32.oldPos + oldPosInc,
10175
9961
  lastComponent: {
10176
9962
  count: last.count + 1,
10177
9963
  added,
@@ -10181,7 +9967,7 @@ var init_lib = __esm({
10181
9967
  };
10182
9968
  } else {
10183
9969
  return {
10184
- oldPos: path39.oldPos + oldPosInc,
9970
+ oldPos: path32.oldPos + oldPosInc,
10185
9971
  lastComponent: {
10186
9972
  count: 1,
10187
9973
  added,
@@ -10239,7 +10025,7 @@ var init_lib = __esm({
10239
10025
  tokenize: function tokenize(value) {
10240
10026
  return Array.from(value);
10241
10027
  },
10242
- join: function join4(chars) {
10028
+ join: function join3(chars) {
10243
10029
  return chars.join("");
10244
10030
  },
10245
10031
  postProcess: function postProcess(changeObjects) {
@@ -10612,10 +10398,10 @@ function attachmentToHistoryMessage(o, ts) {
10612
10398
  const memories = raw.map((m2) => {
10613
10399
  if (!m2 || typeof m2 !== "object") return null;
10614
10400
  const rec = m2;
10615
- const path39 = typeof rec.path === "string" ? rec.path : null;
10401
+ const path32 = typeof rec.path === "string" ? rec.path : null;
10616
10402
  const content = typeof rec.content === "string" ? rec.content : null;
10617
- if (!path39 || content == null) return null;
10618
- const entry = { path: path39, content };
10403
+ if (!path32 || content == null) return null;
10404
+ const entry = { path: path32, content };
10619
10405
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
10620
10406
  return entry;
10621
10407
  }).filter((m2) => m2 !== null);
@@ -11075,27 +10861,6 @@ var init_claude_history = __esm({
11075
10861
  }
11076
10862
  });
11077
10863
 
11078
- // src/tools/sandbox.ts
11079
- function shouldSandbox(cwd, personaRoot) {
11080
- if (!personaRoot) return false;
11081
- const sep2 = personaRoot.endsWith(path12.sep) ? "" : path12.sep;
11082
- return cwd.startsWith(personaRoot + sep2) && cwd !== personaRoot;
11083
- }
11084
- function inferSandboxSettingsPath(cwd, personaRoot) {
11085
- if (!shouldSandbox(cwd, personaRoot)) return null;
11086
- const rel = path12.relative(personaRoot, cwd);
11087
- const personaId = rel.split(path12.sep)[0];
11088
- if (!personaId) return null;
11089
- return path12.join(personaRoot, personaId, ".clawd", "sandbox-settings.json");
11090
- }
11091
- var path12;
11092
- var init_sandbox = __esm({
11093
- "src/tools/sandbox.ts"() {
11094
- "use strict";
11095
- path12 = __toESM(require("path"), 1);
11096
- }
11097
- });
11098
-
11099
10864
  // src/tools/claude.ts
11100
10865
  function macOSDesktopCandidates(home) {
11101
10866
  return [
@@ -11145,7 +10910,7 @@ function buildSpawnArgs(ctx) {
11145
10910
  ];
11146
10911
  if (ctx.model) args.push("--model", ctx.model);
11147
10912
  switch (ctx.personaMode) {
11148
- case "guest":
10913
+ case "listener":
11149
10914
  args.push("--setting-sources", "project,local");
11150
10915
  break;
11151
10916
  case "owner":
@@ -11160,8 +10925,7 @@ function buildSpawnArgs(ctx) {
11160
10925
  throw new Error(`unexpected personaMode: ${String(_exhaustive)}`);
11161
10926
  }
11162
10927
  }
11163
- const sandboxSettings = ctx.extraSettings ?? inferSandboxSettingsPath(ctx.cwd, ctx.personaRoot);
11164
- if (sandboxSettings) args.push("--settings", sandboxSettings);
10928
+ if (ctx.extraSettings) args.push("--settings", ctx.extraSettings);
11165
10929
  if (ctx.extraSystemPrompt) args.push("--append-system-prompt", ctx.extraSystemPrompt);
11166
10930
  if (ctx.effort) args.push("--effort", ctx.effort);
11167
10931
  if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
@@ -11441,10 +11205,10 @@ function parseAttachment(obj) {
11441
11205
  const memories = raw.map((m2) => {
11442
11206
  if (!m2 || typeof m2 !== "object") return null;
11443
11207
  const rec = m2;
11444
- const path39 = typeof rec.path === "string" ? rec.path : null;
11208
+ const path32 = typeof rec.path === "string" ? rec.path : null;
11445
11209
  const content = typeof rec.content === "string" ? rec.content : null;
11446
- if (!path39 || content == null) return null;
11447
- const out = { path: path39, content };
11210
+ if (!path32 || content == null) return null;
11211
+ const out = { path: path32, content };
11448
11212
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
11449
11213
  return out;
11450
11214
  }).filter((m2) => m2 !== null);
@@ -11560,7 +11324,6 @@ var init_claude = __esm({
11560
11324
  init_protocol();
11561
11325
  init_claude_history();
11562
11326
  init_tool_result_extra();
11563
- init_sandbox();
11564
11327
  ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
11565
11328
  "hook_additional_context",
11566
11329
  "hook_success",
@@ -18943,7 +18706,7 @@ var require_websocket = __commonJS({
18943
18706
  var http2 = require("http");
18944
18707
  var net = require("net");
18945
18708
  var tls = require("tls");
18946
- var { randomBytes: randomBytes2, createHash: createHash3 } = require("crypto");
18709
+ var { randomBytes, createHash } = require("crypto");
18947
18710
  var { Duplex, Readable: Readable3 } = require("stream");
18948
18711
  var { URL: URL2 } = require("url");
18949
18712
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -19473,7 +19236,7 @@ var require_websocket = __commonJS({
19473
19236
  }
19474
19237
  }
19475
19238
  const defaultPort = isSecure ? 443 : 80;
19476
- const key = randomBytes2(16).toString("base64");
19239
+ const key = randomBytes(16).toString("base64");
19477
19240
  const request = isSecure ? https.request : http2.request;
19478
19241
  const protocolSet = /* @__PURE__ */ new Set();
19479
19242
  let perMessageDeflate;
@@ -19603,7 +19366,7 @@ var require_websocket = __commonJS({
19603
19366
  abortHandshake(websocket, socket, "Invalid Upgrade header");
19604
19367
  return;
19605
19368
  }
19606
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
19369
+ const digest = createHash("sha1").update(key + GUID).digest("base64");
19607
19370
  if (res.headers["sec-websocket-accept"] !== digest) {
19608
19371
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
19609
19372
  return;
@@ -19970,7 +19733,7 @@ var require_websocket_server = __commonJS({
19970
19733
  var EventEmitter2 = require("events");
19971
19734
  var http2 = require("http");
19972
19735
  var { Duplex } = require("stream");
19973
- var { createHash: createHash3 } = require("crypto");
19736
+ var { createHash } = require("crypto");
19974
19737
  var extension2 = require_extension();
19975
19738
  var PerMessageDeflate2 = require_permessage_deflate();
19976
19739
  var subprotocol2 = require_subprotocol();
@@ -20271,7 +20034,7 @@ var require_websocket_server = __commonJS({
20271
20034
  );
20272
20035
  }
20273
20036
  if (this._state > RUNNING) return abortHandshake(socket, 503);
20274
- const digest = createHash3("sha1").update(key + GUID).digest("base64");
20037
+ const digest = createHash("sha1").update(key + GUID).digest("base64");
20275
20038
  const headers = [
20276
20039
  "HTTP/1.1 101 Switching Protocols",
20277
20040
  "Upgrade: websocket",
@@ -20890,9 +20653,6 @@ function createLogger(opts = {}) {
20890
20653
  return wrap(base);
20891
20654
  }
20892
20655
 
20893
- // src/session/store-factory.ts
20894
- var path5 = __toESM(require("path"), 1);
20895
-
20896
20656
  // src/session/store.ts
20897
20657
  var import_node_fs3 = __toESM(require("fs"), 1);
20898
20658
  var import_node_path4 = __toESM(require("path"), 1);
@@ -20901,14 +20661,10 @@ init_protocol();
20901
20661
  // src/session/scope.ts
20902
20662
  var import_node_path3 = __toESM(require("path"), 1);
20903
20663
  function scopeKey(scope) {
20904
- if (scope.kind === "default") return "default";
20905
- if (scope.mode === "guest") return `persona:${scope.personaId}:guest:${scope.capId}`;
20906
- return `persona:${scope.personaId}:${scope.mode}`;
20664
+ return scope.kind === "default" ? "default" : `persona:${scope.personaId}:${scope.mode}`;
20907
20665
  }
20908
20666
  function scopeSubPath(scope) {
20909
- if (scope.kind === "default") return ["default"];
20910
- if (scope.mode === "guest") return [scope.personaId, "guests", scope.capId];
20911
- return [scope.personaId, scope.mode];
20667
+ return scope.kind === "default" ? ["default"] : [scope.personaId, scope.mode];
20912
20668
  }
20913
20669
  function metaFromScope(scope, personaRoot) {
20914
20670
  if (scope.kind === "default") return void 0;
@@ -20917,18 +20673,10 @@ function metaFromScope(scope, personaRoot) {
20917
20673
  }
20918
20674
  return {
20919
20675
  idleKillEnabled: true,
20920
- personaMode: "guest",
20676
+ personaMode: "listener",
20921
20677
  extraSettings: import_node_path3.default.join(personaRoot, scope.personaId, ".clawd", "sandbox-settings.json")
20922
20678
  };
20923
20679
  }
20924
- function scopeForFile(file, ownerPrincipalId) {
20925
- if (!file.ownerPersonaId) return { kind: "default" };
20926
- const creator = file.creatorPrincipalId;
20927
- if (ownerPrincipalId && creator && creator !== ownerPrincipalId) {
20928
- return { kind: "persona", personaId: file.ownerPersonaId, mode: "guest", capId: creator };
20929
- }
20930
- return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
20931
- }
20932
20680
 
20933
20681
  // src/session/store.ts
20934
20682
  function safeFileName(sessionId) {
@@ -20942,16 +20690,12 @@ function safeFileName(sessionId) {
20942
20690
  var SessionStore = class {
20943
20691
  root;
20944
20692
  constructor(opts) {
20945
- if ("root" in opts) {
20946
- this.root = opts.root;
20947
- } else {
20948
- const scope = opts.scope ?? { kind: "default" };
20949
- this.root = import_node_path4.default.join(
20950
- opts.dataDir,
20951
- "sessions",
20952
- ...scopeSubPath(scope).map(safeFileName)
20953
- );
20954
- }
20693
+ const scope = opts.scope ?? { kind: "default" };
20694
+ this.root = import_node_path4.default.join(
20695
+ opts.dataDir,
20696
+ "sessions",
20697
+ ...scopeSubPath(scope).map(safeFileName)
20698
+ );
20955
20699
  }
20956
20700
  filePath(sessionId) {
20957
20701
  return import_node_path4.default.join(this.root, `${safeFileName(sessionId)}.json`);
@@ -21016,66 +20760,6 @@ var SessionStore = class {
21016
20760
  }
21017
20761
  };
21018
20762
 
21019
- // src/session/store-factory.ts
21020
- var SessionStoreFactory = class {
21021
- dataDir;
21022
- bareStore = null;
21023
- vmOwnerStores = /* @__PURE__ */ new Map();
21024
- // vmGuest 索引 key = `${pid}::${capId}`(按 cap.id 隔离,一张 cap 一个目录)
21025
- vmGuestStores = /* @__PURE__ */ new Map();
21026
- constructor(opts) {
21027
- this.dataDir = opts.dataDir;
21028
- }
21029
- // ---- root path 派生(暴露给 migration / revoke cascade 等外部消费方) ----
21030
- bareRoot() {
21031
- return path5.join(this.dataDir, "sessions");
21032
- }
21033
- vmOwnerRoot(personaId) {
21034
- return path5.join(
21035
- this.dataDir,
21036
- "personas",
21037
- safeFileName(personaId),
21038
- ".clawd",
21039
- "sessions",
21040
- "owner"
21041
- );
21042
- }
21043
- vmGuestRoot(personaId, capId) {
21044
- return path5.join(
21045
- this.dataDir,
21046
- "personas",
21047
- safeFileName(personaId),
21048
- ".clawd",
21049
- "sessions",
21050
- "guests",
21051
- safeFileName(capId)
21052
- );
21053
- }
21054
- // ---- SessionStore 工厂(缓存) ----
21055
- forBare() {
21056
- if (!this.bareStore) {
21057
- this.bareStore = new SessionStore({ root: this.bareRoot() });
21058
- }
21059
- return this.bareStore;
21060
- }
21061
- forVmOwner(personaId) {
21062
- const key = personaId;
21063
- const cached = this.vmOwnerStores.get(key);
21064
- if (cached) return cached;
21065
- const st = new SessionStore({ root: this.vmOwnerRoot(personaId) });
21066
- this.vmOwnerStores.set(key, st);
21067
- return st;
21068
- }
21069
- forVmGuest(personaId, capId) {
21070
- const key = `${personaId}::${capId}`;
21071
- const cached = this.vmGuestStores.get(key);
21072
- if (cached) return cached;
21073
- const st = new SessionStore({ root: this.vmGuestRoot(personaId, capId) });
21074
- this.vmGuestStores.set(key, st);
21075
- return st;
21076
- }
21077
- };
21078
-
21079
20763
  // src/session/manager.ts
21080
20764
  var import_node_fs5 = __toESM(require("fs"), 1);
21081
20765
  var import_node_path6 = __toESM(require("path"), 1);
@@ -21197,7 +20881,7 @@ function cloneState(s) {
21197
20881
  pendingSend: s.pendingSend
21198
20882
  };
21199
20883
  }
21200
- var IDLE_KILL_DELAY_MS = 6e4;
20884
+ var IDLE_KILL_DELAY_MS = 5e3;
21201
20885
  var SEEN_UUID_CAP = 2e3;
21202
20886
  function applyEventBatch(state, events, deps) {
21203
20887
  if (events.length === 0) return { state, effects: [] };
@@ -21237,10 +20921,7 @@ function buildSpawnContext(state, deps) {
21237
20921
  toolSessionId: file.toolSessionId,
21238
20922
  model: file.model,
21239
20923
  permissionMode: file.permissionMode,
21240
- effort: file.effort,
21241
- // Phase 2 capability platform (plan §3): personaRoot 透传给 buildSpawnArgs;
21242
- // 内部 shouldSandbox(cwd, personaRoot) 决定是否注入 --settings sandbox-settings.
21243
- personaRoot: deps.personaRoot
20924
+ effort: file.effort
21244
20925
  };
21245
20926
  const meta = state.subSessionMeta;
21246
20927
  if (meta?.personaMode) {
@@ -22025,7 +21706,6 @@ var SessionRunner = class {
22025
21706
  resolveContextWindow: this.hooks.resolveContextWindow,
22026
21707
  genUuid: this.hooks.genUuid ?? v4_default,
22027
21708
  ownerDisplayName: this.hooks.ownerDisplayName,
22028
- personaRoot: this.hooks.personaRoot,
22029
21709
  mode: this.hooks.mode ?? "sdk"
22030
21710
  };
22031
21711
  const { state, effects } = reduceSession(this.state, inputMsg, deps);
@@ -22457,18 +22137,9 @@ var SessionManager = class {
22457
22137
  attachObserver(observer) {
22458
22138
  this.attachedObserver = observer;
22459
22139
  }
22460
- // 按 scope 拿对应的 SessionStore.
22461
- // Phase 2 (capability platform plan §1) 路由切到 SessionStoreFactory:
22462
- // default → factory.forBare() <dataDir>/sessions/
22463
- // persona/<pid>/owner → factory.forVmOwner(pid) <dataDir>/personas/<pid>/.clawd/sessions/owner/
22464
- // persona/<pid>/guest → factory.forVmGuest(pid, capId) <dataDir>/personas/<pid>/.clawd/sessions/guests/<capId>/
22465
- // 缺省 (无 factory 注入): 回退 dataDir+scope 老路径 (向后兼容旧 spec).
22140
+ // 按 scope 拿对应的 SessionStore。default scope 复用 deps.store(保证与
22141
+ // bootstrap 注入的 store 一致);persona scope 第一次访问时按需创建并缓存。
22466
22142
  storeFor(scope) {
22467
- if (this.deps.storeFactory) {
22468
- if (scope.kind === "default") return this.deps.storeFactory.forBare();
22469
- if (scope.mode === "owner") return this.deps.storeFactory.forVmOwner(scope.personaId);
22470
- return this.deps.storeFactory.forVmGuest(scope.personaId, scope.capId);
22471
- }
22472
22143
  if (scope.kind === "default") return this.deps.store;
22473
22144
  const key = scopeKey(scope);
22474
22145
  const cached = this.storesByScope.get(key);
@@ -22488,18 +22159,19 @@ var SessionManager = class {
22488
22159
  this.capabilitiesCache.set(tool, caps);
22489
22160
  return caps;
22490
22161
  }
22491
- // 由 SessionFile 反推 scope 的 thin wrapper —— 决策矩阵收敛到 scope.scopeForFile
22492
- // free function(scope.spec.ts transition 表驱动测试),manager 只负责注入 deps.
22162
+ // 由 SessionFile.ownerPersonaId schema 字段推回写入 scope
22163
+ // ownerPersonaId create({ownerPersonaId}) schema 落盘时记入,永远只反映
22164
+ // session 出身(不可变);此函数只用于 SessionStore 的写入路由,不参与运行时行为
22165
+ // 决策(personaMode='owner' 等由路径 → scope → metaFromScope 派生)。
22166
+ // 仅覆盖 owner + default 两类——listener sub-session 不会从 default RPC 路径进来。
22493
22167
  scopeForFile(file) {
22494
- return scopeForFile(file, this.deps.ownerPrincipalId);
22168
+ return file.ownerPersonaId ? { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" } : { kind: "default" };
22495
22169
  }
22496
- // 扫所有 persona 命名空间. 用于 findOwnedSession / listAllOwned scope 查询.
22497
- // Phase 2 capability platform: 新布局下 persona 资产在 <dataDir>/personas/<pid>/,
22498
- // 直接 readdir 这个目录拿所有 personaId. 老布局 (无 storeFactory) fallback 扫
22499
- // <dataDir>/sessions/ 列子目录 (排除 'default').
22170
+ // <dataDir>/sessions/ 列出所有 persona 命名空间(不含 'default')。
22171
+ // 用于 findOwnedSession / listAllOwned scope 查询。
22500
22172
  listPersonaIdsOnDisk() {
22501
22173
  if (!this.deps.dataDir) return [];
22502
- const root = this.deps.storeFactory ? import_node_path6.default.join(this.deps.dataDir, "personas") : import_node_path6.default.join(this.deps.dataDir, "sessions");
22174
+ const root = import_node_path6.default.join(this.deps.dataDir, "sessions");
22503
22175
  let entries;
22504
22176
  try {
22505
22177
  entries = import_node_fs5.default.readdirSync(root, { withFileTypes: true });
@@ -22510,37 +22182,13 @@ var SessionManager = class {
22510
22182
  }
22511
22183
  return entries.filter((e) => e.isDirectory() && e.name !== "default").map((e) => e.name);
22512
22184
  }
22513
- // 扫某 persona guests/ 子目录, 拿所有 capId.
22514
- // 路径: <dataDir>/personas/<pid>/.clawd/sessions/guests/<capId>/
22515
- // 只在 storeFactory 注入 (新布局) 下生效, 老布局无 guest 目录.
22516
- listGuestCapIdsForPersona(personaId) {
22517
- if (!this.deps.dataDir || !this.deps.storeFactory) return [];
22518
- const root = import_node_path6.default.join(
22519
- this.deps.dataDir,
22520
- "personas",
22521
- personaId,
22522
- ".clawd",
22523
- "sessions",
22524
- "guests"
22525
- );
22526
- let entries;
22527
- try {
22528
- entries = import_node_fs5.default.readdirSync(root, { withFileTypes: true });
22529
- } catch (err) {
22530
- const code = err?.code;
22531
- if (code === "ENOENT") return [];
22532
- throw err;
22533
- }
22534
- return entries.filter((e) => e.isDirectory()).map((e) => e.name);
22535
- }
22536
- // owner / default + persona guests 三大分类下按 sessionId 找文件——前端只传 sessionId 不带 scope,
22537
- // SessionManager 在这里定位。四层快慢路径:
22185
+ // owner / default 两个分类下按 sessionId 找文件——前端只传 sessionId 不带 scope,
22186
+ // SessionManager 在这里定位。三层快慢路径:
22538
22187
  // 1) active runner(用户当前在用的 session):runner.state.file 是 SessionFile 内存权威副本,
22539
22188
  // 直接返回,零磁盘 I/O。覆盖 attachment / file-sharing 等"用户操作 → 紧接着 RPC"的
22540
22189
  // 绝大多数场景
22541
22190
  // 2) default scope 磁盘:inactive default session 命中
22542
22191
  // 3) 所有 persona owner 目录扫盘:inactive persona owner session 命中
22543
- // 4) 每个 persona 的 guests/<capId>/ 扫盘:guest 通过我颁发 token 接进来创建的 session
22544
22192
  // listener sub-session 不走这条——transport 始终明确传 (personaId, sessionId)。
22545
22193
  //
22546
22194
  // 公开方法:attachment / file-sharing handler 通过 deps 闭包消费,不应直接持 SessionStore
@@ -22552,13 +22200,8 @@ var SessionManager = class {
22552
22200
  if (dflt) return dflt;
22553
22201
  for (const personaId of this.listPersonaIdsOnDisk()) {
22554
22202
  const ownerStore = this.storeFor({ kind: "persona", personaId, mode: "owner" });
22555
- const found = ownerStore.read(sessionId);
22556
- if (found) return found;
22557
- for (const capId of this.listGuestCapIdsForPersona(personaId)) {
22558
- const guestStore = this.storeFor({ kind: "persona", personaId, mode: "guest", capId });
22559
- const g2 = guestStore.read(sessionId);
22560
- if (g2) return g2;
22561
- }
22203
+ const file = ownerStore.read(sessionId);
22204
+ if (file) return file;
22562
22205
  }
22563
22206
  return null;
22564
22207
  }
@@ -22569,40 +22212,30 @@ var SessionManager = class {
22569
22212
  const file = this.findOwnedSession(sessionId);
22570
22213
  return file ? this.scopeForFile(file) : null;
22571
22214
  }
22572
- // 合并 default + 每个 persona owner/ + guests/<capId>/ —— 桌面 App 主 session 列表入口.
22573
- // guest sessions 由 handlers/session.ts list 处按 ctx.principal.kind === 'guest' 做权限过滤
22574
- // (creatorPrincipalId === capId), 这里仅负责数据源汇总.
22575
- // listener sub-session 不走这条(listener 走 persona:listSubSessions RPC 单独入口)。
22215
+ // 合并 default + 所有 persona owner SessionFile —— 桌面 App 主 session 列表入口。
22216
+ // 同样不含 listener sub-session(listener persona:listSubSessions RPC 单独入口)。
22576
22217
  listAllOwned() {
22577
22218
  const out = [];
22578
22219
  out.push(...this.deps.store.list());
22579
22220
  for (const personaId of this.listPersonaIdsOnDisk()) {
22580
22221
  const ownerStore = this.storeFor({ kind: "persona", personaId, mode: "owner" });
22581
22222
  out.push(...ownerStore.list());
22582
- for (const capId of this.listGuestCapIdsForPersona(personaId)) {
22583
- const guestStore = this.storeFor({ kind: "persona", personaId, mode: "guest", capId });
22584
- out.push(...guestStore.list());
22585
- }
22586
22223
  }
22587
22224
  return out.sort(
22588
22225
  (a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0
22589
22226
  );
22590
22227
  }
22591
- // 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
22592
- // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
22228
+ // 写回 SessionFile,自动根据 ownerPersonaId 派生写入 scope
22229
+ // 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由。
22593
22230
  writeOwned(file) {
22594
22231
  return this.storeFor(this.scopeForFile(file)).write(file);
22595
22232
  }
22596
- // 删除 owned SessionFile(default / persona owner / persona guest),与 findOwnedSession 对称扫盘.
22233
+ // 删除 owned SessionFile(default persona/owner),与 findOwnedSession 对称扫盘。
22597
22234
  deleteOwned(sessionId) {
22598
22235
  if (this.deps.store.delete(sessionId)) return true;
22599
22236
  for (const personaId of this.listPersonaIdsOnDisk()) {
22600
22237
  const ownerStore = this.storeFor({ kind: "persona", personaId, mode: "owner" });
22601
22238
  if (ownerStore.delete(sessionId)) return true;
22602
- for (const capId of this.listGuestCapIdsForPersona(personaId)) {
22603
- const guestStore = this.storeFor({ kind: "persona", personaId, mode: "guest", capId });
22604
- if (guestStore.delete(sessionId)) return true;
22605
- }
22606
22239
  }
22607
22240
  return false;
22608
22241
  }
@@ -22610,8 +22243,8 @@ var SessionManager = class {
22610
22243
  // 经过 compressFrameForWire 后决定是 push collector 还是走 deps.broadcastFrame。
22611
22244
  // scope 决定两件事:SessionStore 路由 + SubSessionMeta 派生(metaFromScope)。
22612
22245
  // - default → 复用 deps.store,无 subSessionMeta(普通 session)
22613
- // - persona/owner → personas/<pid>/.clawd/sessions/owner/,meta={idleKillEnabled:false, personaMode:'owner'}
22614
- // - persona/guestpersonas/<pid>/.clawd/sessions/guests/<capId>/,meta={idleKillEnabled:true, personaMode:'guest', extraSettings:<sandbox>}
22246
+ // - persona/owner → <dataDir>/sessions/<personaId>/owner/,meta={idleKillEnabled:false, personaMode:'owner'}
22247
+ // - persona/listener<dataDir>/sessions/<personaId>/listener/,meta={idleKillEnabled:true, personaMode:'listener', extraSettings:<sandbox>}
22615
22248
  newRunner(file, scope) {
22616
22249
  const adapter = this.deps.getAdapter(file.tool ?? "claude");
22617
22250
  const store = this.storeFor(scope);
@@ -22630,9 +22263,6 @@ var SessionManager = class {
22630
22263
  dataDir: this.deps.dataDir,
22631
22264
  personaStore: this.deps.personaStore,
22632
22265
  ownerDisplayName: this.deps.ownerDisplayName,
22633
- // Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
22634
- // cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
22635
- personaRoot: this.deps.personaRoot,
22636
22266
  // 透传给 ReducerDeps.mode:reducer send !procAlive spawn 路径据此判断要不要把
22637
22267
  // readyForSend 翻 false(TUI 翻 false 让 user_text 走 pendingSend 暂存;SDK 不动保持直写)
22638
22268
  mode: this.deps.mode,
@@ -22730,7 +22360,7 @@ var SessionManager = class {
22730
22360
  }
22731
22361
  }
22732
22362
  // ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
22733
- create(args, creatorPrincipalId) {
22363
+ create(args) {
22734
22364
  let cwd;
22735
22365
  if (args.ownerPersonaId) {
22736
22366
  cwd = derivePersonaSpawnCwd(
@@ -22771,11 +22401,10 @@ var SessionManager = class {
22771
22401
  iconKey: args.iconKey,
22772
22402
  forkedFromSessionId: args.forkedFromSessionId,
22773
22403
  ownerPersonaId: args.ownerPersonaId,
22774
- creatorPrincipalId,
22775
22404
  createdAt: iso,
22776
22405
  updatedAt: iso
22777
22406
  };
22778
- const scope = this.scopeForFile(file);
22407
+ const scope = args.ownerPersonaId ? { kind: "persona", personaId: args.ownerPersonaId, mode: "owner" } : { kind: "default" };
22779
22408
  const written = this.storeFor(scope).write(file);
22780
22409
  return { response: written, broadcast: [] };
22781
22410
  }
@@ -22983,6 +22612,7 @@ var SessionManager = class {
22983
22612
  const pty = tsid ? this.ptyTransports.get(tsid) : void 0;
22984
22613
  if (pty) {
22985
22614
  pty.write("\x1B");
22615
+ runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
22986
22616
  return;
22987
22617
  }
22988
22618
  await runner.sendControlRequest("interrupt");
@@ -23215,18 +22845,28 @@ var SessionManager = class {
23215
22845
  this.runners.set(file.sessionId, r);
23216
22846
  return r;
23217
22847
  }
23218
- // ---------------- scope-explicit sub-session API ----------------
22848
+ // ---------------- persona / listener sub-session 专用 API ----------------
23219
22849
  //
23220
- // 区别于 owner / default sessionId 入口,这些 API 接 SessionScope 参数 ——
23221
- // 调用方(PersonaManager / observer 等)明确传 scope,由 storeFor 路由到正确目录:
23222
- // - {kind:'persona', personaId, mode:'guest', capId} → guests/<capId>/,meta={idleKill:true, sandbox}
23223
- // - {kind:'persona', personaId, mode:'owner'} → owner/,meta={idleKill:false}
22850
+ // PersonaManager SessionManager 之上的薄编排层,listener sub-session 的持久化
22851
+ // (SessionFile)仍由 SessionManager 持有。区别于普通 / owner session:
22852
+ // - SessionFile 落到 <dataDir>/sessions/<personaId>/listener/(拓扑分类)
22853
+ // - reducer state.subSessionMeta.idleKillEnabled = true(turn_end 自动 idle-kill)
22854
+ // - sessionId 由 PersonaManager 派生(personaId-<tokenHash>),不走自动 newSessionId
23224
22855
  //
23225
- // SubSessionMeta scope metaFromScope 派生,不进 SessionFile schema。
22856
+ // 这些 API SessionScope 参数(不再是单参 agentId)—— transport 始终明确传
22857
+ // {kind:'persona', personaId, mode:'listener'},与 owner / default 分类隔离。
23226
22858
  /** 按 scope 读取 SessionFile;不存在返回 null */
23227
22859
  readForScope(sessionId, scope) {
23228
22860
  return this.storeFor(scope).read(sessionId);
23229
22861
  }
22862
+ /**
22863
+ * 列出指定 persona 在指定 mode 下的所有 SessionFile。
22864
+ * persona:listSubSessions RPC 入口(mode='listener');
22865
+ * 路径下还未写过任何 session 时 list() 返回空数组(SessionStore.list 内部 ENOENT 处理)
22866
+ */
22867
+ listPersonaSessions(personaId, mode) {
22868
+ return this.storeFor({ kind: "persona", personaId, mode }).list();
22869
+ }
23230
22870
  /**
23231
22871
  * 创建 sub-session 的 SessionFile + 准备 runner(不 spawn)。
23232
22872
  * SubSessionMeta 不进 SessionFile schema,运行时由 scope → metaFromScope 派生。
@@ -23371,7 +23011,7 @@ var SessionManager = class {
23371
23011
  return { response: { ok: true }, broadcast };
23372
23012
  }
23373
23013
  /**
23374
- * 改 SessionFile.label(scope-explicit sub-session 重命名入口)。
23014
+ * 改 SessionFile.label(用于 listener-bound chat:rename RPC)。
23375
23015
  * 不存在抛 SESSION_NOT_FOUND。
23376
23016
  */
23377
23017
  renameForScope(args) {
@@ -23386,7 +23026,7 @@ var SessionManager = class {
23386
23026
  store.write({ ...file, label: args.label });
23387
23027
  }
23388
23028
  /**
23389
- * 删除 scope-explicit sub-session
23029
+ * 删除 listener-scope sub-session(用于 chat:delete RPC)。
23390
23030
  * - 若 runner 存在:走 reducer 'delete' command 收尾(SIGKILL + 文件 unlink + broadcast)
23391
23031
  * - 若 runner 不存在:直接删 scope store 文件 + 广播 session:deleted
23392
23032
  * 不存在抛 SESSION_NOT_FOUND。
@@ -23657,7 +23297,7 @@ var SessionManager = class {
23657
23297
 
23658
23298
  // src/persona/store.ts
23659
23299
  var fs6 = __toESM(require("fs"), 1);
23660
- var path8 = __toESM(require("path"), 1);
23300
+ var path7 = __toESM(require("path"), 1);
23661
23301
  init_protocol();
23662
23302
  var DEFAULT_SETTINGS = {
23663
23303
  permissions: {
@@ -23686,13 +23326,13 @@ var PersonaStore = class {
23686
23326
  }
23687
23327
  root;
23688
23328
  personaDir(personaId) {
23689
- return path8.join(this.root, safeFileName(personaId));
23329
+ return path7.join(this.root, safeFileName(personaId));
23690
23330
  }
23691
23331
  metaPath(personaId) {
23692
- return path8.join(this.personaDir(personaId), ".clawd", "persona.json");
23332
+ return path7.join(this.personaDir(personaId), ".clawd", "persona.json");
23693
23333
  }
23694
23334
  claudeMdPath(personaId) {
23695
- return path8.join(this.personaDir(personaId), "CLAUDE.md");
23335
+ return path7.join(this.personaDir(personaId), "CLAUDE.md");
23696
23336
  }
23697
23337
  /**
23698
23338
  * Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
@@ -23702,11 +23342,11 @@ var PersonaStore = class {
23702
23342
  * 加载 persona 人格,只有 listener 多一层 OS sandbox。
23703
23343
  */
23704
23344
  sandboxSettingsPath(personaId) {
23705
- return path8.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
23345
+ return path7.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
23706
23346
  }
23707
23347
  write(persona, personality) {
23708
23348
  const dir = this.personaDir(persona.personaId);
23709
- fs6.mkdirSync(path8.join(dir, ".clawd"), { recursive: true });
23349
+ fs6.mkdirSync(path7.join(dir, ".clawd"), { recursive: true });
23710
23350
  this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
23711
23351
  this.atomicWrite(
23712
23352
  this.sandboxSettingsPath(persona.personaId),
@@ -23724,10 +23364,6 @@ var PersonaStore = class {
23724
23364
  const p2 = this.metaPath(personaId);
23725
23365
  if (!fs6.existsSync(p2)) return null;
23726
23366
  const raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
23727
- if (raw && typeof raw === "object" && "tokenMap" in raw) {
23728
- delete raw.tokenMap;
23729
- this.atomicWrite(p2, JSON.stringify(raw, null, 2));
23730
- }
23731
23367
  return PersonaFileSchema.parse(raw);
23732
23368
  }
23733
23369
  has(personaId) {
@@ -23753,14 +23389,14 @@ var PersonaStore = class {
23753
23389
  }
23754
23390
  /** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
23755
23391
  skillsDir(personaId) {
23756
- return path8.join(this.personaDir(personaId), ".claude", "skills");
23392
+ return path7.join(this.personaDir(personaId), ".claude", "skills");
23757
23393
  }
23758
23394
  /**
23759
23395
  * Claude Code 项目级 settings 路径:`<personaDir>/.claude/settings.json`。
23760
23396
  * 这里只读 `enabledPlugins` 字段,由 owner 通过 CC `/plugin` 之类命令维护,daemon 不写。
23761
23397
  */
23762
23398
  claudeSettingsPath(personaId) {
23763
- return path8.join(this.personaDir(personaId), ".claude", "settings.json");
23399
+ return path7.join(this.personaDir(personaId), ".claude", "settings.json");
23764
23400
  }
23765
23401
  /**
23766
23402
  * 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
@@ -23794,7 +23430,7 @@ var PersonaStore = class {
23794
23430
  list() {
23795
23431
  if (!fs6.existsSync(this.root)) return [];
23796
23432
  return fs6.readdirSync(this.root).filter((name) => {
23797
- return fs6.existsSync(path8.join(this.root, name, ".clawd", "persona.json"));
23433
+ return fs6.existsSync(path7.join(this.root, name, ".clawd", "persona.json"));
23798
23434
  });
23799
23435
  }
23800
23436
  remove(personaId) {
@@ -23819,19 +23455,12 @@ var PersonaRegistry = class {
23819
23455
  }
23820
23456
  store;
23821
23457
  cache = /* @__PURE__ */ new Map();
23822
- /**
23823
- * 从 store 全量重建缓存(boot 时 + 测试 setup 时)。
23824
- * 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
23825
- * try/catch 隔离,让其它 persona 仍能加载。
23826
- */
23458
+ /** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
23827
23459
  reload() {
23828
23460
  this.cache.clear();
23829
23461
  for (const id of this.store.list()) {
23830
- try {
23831
- const p2 = this.store.read(id);
23832
- if (p2) this.cache.set(p2.personaId, p2);
23833
- } catch {
23834
- }
23462
+ const p2 = this.store.read(id);
23463
+ if (p2) this.cache.set(p2.personaId, p2);
23835
23464
  }
23836
23465
  }
23837
23466
  get(personaId) {
@@ -23847,6 +23476,21 @@ var PersonaRegistry = class {
23847
23476
  list() {
23848
23477
  return Array.from(this.cache.values());
23849
23478
  }
23479
+ /**
23480
+ * file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
23481
+ * 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
23482
+ * persona 数量通常 < 100,性能可忽略。
23483
+ */
23484
+ findByToken(token) {
23485
+ if (!token) return null;
23486
+ for (const persona of this.cache.values()) {
23487
+ if (!persona.public) continue;
23488
+ const entry = persona.tokenMap?.[token];
23489
+ if (!entry || entry.revoked) continue;
23490
+ return { personaId: persona.personaId, label: entry.label };
23491
+ }
23492
+ return null;
23493
+ }
23850
23494
  };
23851
23495
 
23852
23496
  // src/persona/manager.ts
@@ -24140,6 +23784,9 @@ var PersonaManager = class {
24140
23784
  model: args.model,
24141
23785
  public: args.public ?? false,
24142
23786
  iconKey: args.iconKey,
23787
+ // 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
23788
+ // file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
23789
+ tokenMap: {},
24143
23790
  createdAt: now,
24144
23791
  updatedAt: now
24145
23792
  };
@@ -24189,22 +23836,52 @@ var PersonaManager = class {
24189
23836
  }
24190
23837
  /**
24191
23838
  * 删除 persona。
24192
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
24193
- * Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
24194
- * .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
24195
- *
24196
- * ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
24197
- * ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
24198
- * 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
24199
- * 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
24200
- * 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
24201
- * force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
24202
- * "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
23839
+ * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
24203
23840
  */
24204
23841
  delete(personaId) {
24205
23842
  this.deps.store.remove(personaId);
24206
23843
  this.deps.registry.remove(personaId);
24207
23844
  }
23845
+ /**
23846
+ * 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
23847
+ * 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
23848
+ */
23849
+ issueToken(personaId, label) {
23850
+ const existing = this.deps.registry.get(personaId);
23851
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23852
+ const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
23853
+ const entry = {
23854
+ label,
23855
+ issuedAt: Date.now(),
23856
+ revoked: false
23857
+ };
23858
+ const updated = {
23859
+ ...existing,
23860
+ tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
23861
+ updatedAt: Date.now()
23862
+ };
23863
+ this.deps.store.writeMeta(updated);
23864
+ this.deps.registry.set(updated);
23865
+ return { token, persona: updated };
23866
+ }
23867
+ /** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
23868
+ revokeToken(personaId, token) {
23869
+ const existing = this.deps.registry.get(personaId);
23870
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23871
+ const tokenEntry = existing.tokenMap?.[token];
23872
+ if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23873
+ const updated = {
23874
+ ...existing,
23875
+ tokenMap: {
23876
+ ...existing.tokenMap ?? {},
23877
+ [token]: { ...tokenEntry, revoked: true }
23878
+ },
23879
+ updatedAt: Date.now()
23880
+ };
23881
+ this.deps.store.writeMeta(updated);
23882
+ this.deps.registry.set(updated);
23883
+ return updated;
23884
+ }
24208
23885
  /**
24209
23886
  * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
24210
23887
  * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
@@ -24224,7 +23901,7 @@ var PersonaManager = class {
24224
23901
 
24225
23902
  // src/persona/seed.ts
24226
23903
  var fs8 = __toESM(require("fs"), 1);
24227
- var path10 = __toESM(require("path"), 1);
23904
+ var path9 = __toESM(require("path"), 1);
24228
23905
  var import_node_url = require("url");
24229
23906
  var import_meta = {};
24230
23907
  var DEFAULT_PERSONAS = [
@@ -24260,14 +23937,14 @@ var DEFAULT_PERSONAS = [
24260
23937
  function findDefaultsRoot() {
24261
23938
  const candidates = [];
24262
23939
  try {
24263
- const here = path10.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
24264
- candidates.push(path10.resolve(here, "defaults"));
24265
- candidates.push(path10.resolve(here, "persona-defaults"));
23940
+ const here = path9.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
23941
+ candidates.push(path9.resolve(here, "defaults"));
23942
+ candidates.push(path9.resolve(here, "persona-defaults"));
24266
23943
  } catch {
24267
23944
  }
24268
23945
  if (process.argv[1]) {
24269
- const argvDir = path10.dirname(process.argv[1]);
24270
- candidates.push(path10.resolve(argvDir, "persona-defaults"));
23946
+ const argvDir = path9.dirname(process.argv[1]);
23947
+ candidates.push(path9.resolve(argvDir, "persona-defaults"));
24271
23948
  }
24272
23949
  for (const c of candidates) {
24273
23950
  try {
@@ -24284,7 +23961,7 @@ function seedDefaultPersonas(args) {
24284
23961
  args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
24285
23962
  continue;
24286
23963
  }
24287
- const bundleDir = path10.join(args.defaultsRoot, entry.personaId);
23964
+ const bundleDir = path9.join(args.defaultsRoot, entry.personaId);
24288
23965
  if (!fs8.existsSync(bundleDir)) {
24289
23966
  args.logger.warn("persona.seed.skip", {
24290
23967
  personaId: entry.personaId,
@@ -24293,7 +23970,7 @@ function seedDefaultPersonas(args) {
24293
23970
  });
24294
23971
  continue;
24295
23972
  }
24296
- const claudeMdPath = path10.join(bundleDir, "CLAUDE.md");
23973
+ const claudeMdPath = path9.join(bundleDir, "CLAUDE.md");
24297
23974
  if (!fs8.existsSync(claudeMdPath)) {
24298
23975
  args.logger.warn("persona.seed.skip", {
24299
23976
  personaId: entry.personaId,
@@ -24310,6 +23987,7 @@ function seedDefaultPersonas(args) {
24310
23987
  model: entry.model,
24311
23988
  public: entry.public,
24312
23989
  iconKey: entry.iconKey,
23990
+ tokenMap: {},
24313
23991
  createdAt: now,
24314
23992
  updatedAt: now
24315
23993
  };
@@ -24321,8 +23999,8 @@ function seedDefaultPersonas(args) {
24321
23999
  function copyBundleExtras(srcDir, dstDir) {
24322
24000
  for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
24323
24001
  if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
24324
- const srcPath = path10.join(srcDir, entry.name);
24325
- const dstPath = path10.join(dstDir, entry.name);
24002
+ const srcPath = path9.join(srcDir, entry.name);
24003
+ const dstPath = path9.join(dstDir, entry.name);
24326
24004
  if (entry.isDirectory()) {
24327
24005
  fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
24328
24006
  } else if (entry.isFile()) {
@@ -25663,7 +25341,6 @@ var import_sender = __toESM(require_sender(), 1);
25663
25341
  var import_subprotocol = __toESM(require_subprotocol(), 1);
25664
25342
  var import_websocket = __toESM(require_websocket(), 1);
25665
25343
  var import_websocket_server = __toESM(require_websocket_server(), 1);
25666
- var wrapper_default = import_websocket.default;
25667
25344
 
25668
25345
  // src/transport/local-ws-server.ts
25669
25346
  var import_node_http = __toESM(require("http"), 1);
@@ -25678,9 +25355,6 @@ var LocalWsServer = class {
25678
25355
  httpServer = null;
25679
25356
  frameHandler = null;
25680
25357
  clients = /* @__PURE__ */ new Map();
25681
- // Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
25682
- // 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
25683
- capabilityIdToClients = /* @__PURE__ */ new Map();
25684
25358
  logger;
25685
25359
  pingIntervalMs;
25686
25360
  async start() {
@@ -25771,49 +25445,6 @@ var LocalWsServer = class {
25771
25445
  this.safeSend(c.ws, frame);
25772
25446
  }
25773
25447
  }
25774
- // Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
25775
- // 用于 capability:tokenIssued / capability:tokenDeleted 等 owner-only push frame——
25776
- // guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
25777
- broadcastToOwners(frame) {
25778
- const gate = this.opts.authGate;
25779
- for (const c of this.clients.values()) {
25780
- if (gate && !gate.isAuthed(c.handle.id)) continue;
25781
- if (c.ctx && c.ctx.principal.kind !== "owner") continue;
25782
- this.safeSend(c.ws, frame);
25783
- }
25784
- }
25785
- // capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
25786
- // - 所有 owner ws (owner 看自家所有 channel)
25787
- // - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
25788
- // 一次调用同时推两端, inbox:event push 帧由此走.
25789
- broadcastToCapabilityChannel(capabilityId, frame) {
25790
- this.broadcastToOwners(frame);
25791
- const set = this.capabilityIdToClients.get(capabilityId);
25792
- if (!set) return;
25793
- for (const id of set) {
25794
- const c = this.clients.get(id);
25795
- if (!c) continue;
25796
- this.safeSend(c.ws, frame);
25797
- }
25798
- }
25799
- // Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
25800
- // principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
25801
- // principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
25802
- // principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
25803
- // 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
25804
- broadcastToPrincipal(principalId, frame) {
25805
- if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
25806
- this.broadcastToOwners(frame);
25807
- return;
25808
- }
25809
- const set = this.capabilityIdToClients.get(principalId);
25810
- if (!set) return;
25811
- for (const id of set) {
25812
- const c = this.clients.get(id);
25813
- if (!c) continue;
25814
- this.safeSend(c.ws, frame);
25815
- }
25816
- }
25817
25448
  firstSubscriber(sessionId) {
25818
25449
  for (const c of this.clients.values()) {
25819
25450
  if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
@@ -25835,40 +25466,6 @@ var LocalWsServer = class {
25835
25466
  if (!c) return;
25836
25467
  this.safeSend(c.ws, frame);
25837
25468
  }
25838
- // Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
25839
- // 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
25840
- attachClientContext(clientId, ctx) {
25841
- const c = this.clients.get(clientId);
25842
- if (!c) return;
25843
- c.ctx = ctx;
25844
- if (ctx.capabilityId) {
25845
- let set = this.capabilityIdToClients.get(ctx.capabilityId);
25846
- if (!set) {
25847
- set = /* @__PURE__ */ new Set();
25848
- this.capabilityIdToClients.set(ctx.capabilityId, set);
25849
- }
25850
- set.add(clientId);
25851
- }
25852
- }
25853
- // 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
25854
- getClientContext(clientId) {
25855
- return this.clients.get(clientId)?.ctx ?? null;
25856
- }
25857
- // Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
25858
- // 让客户端识别 'capability revoked' 而非普通断连。
25859
- closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
25860
- const set = this.capabilityIdToClients.get(capabilityId);
25861
- if (!set) return;
25862
- const ids = [...set];
25863
- for (const id of ids) {
25864
- const c = this.clients.get(id);
25865
- if (!c) continue;
25866
- try {
25867
- c.ws.close(code, reason);
25868
- } catch {
25869
- }
25870
- }
25871
- }
25872
25469
  // URL path 路由:'/' → 主连接路径;其他 → close 4404
25873
25470
  routeConnection(socket, req) {
25874
25471
  const remoteAddress = req?.socket?.remoteAddress;
@@ -25903,7 +25500,7 @@ var LocalWsServer = class {
25903
25500
  } catch {
25904
25501
  }
25905
25502
  }, this.pingIntervalMs);
25906
- this.clients.set(id, { handle, ws: socket, pingTimer, ctx: null });
25503
+ this.clients.set(id, { handle, ws: socket, pingTimer });
25907
25504
  this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
25908
25505
  const authGate = this.opts.authGate;
25909
25506
  let authed = true;
@@ -25927,12 +25524,6 @@ var LocalWsServer = class {
25927
25524
  socket.on("close", () => {
25928
25525
  const c = this.clients.get(id);
25929
25526
  if (c?.pingTimer) clearInterval(c.pingTimer);
25930
- const capId = c?.ctx?.capabilityId;
25931
- if (capId) {
25932
- const set = this.capabilityIdToClients.get(capId);
25933
- set?.delete(id);
25934
- if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
25935
- }
25936
25527
  this.clients.delete(id);
25937
25528
  authGate?.unregister(id);
25938
25529
  this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
@@ -25965,17 +25556,6 @@ var LocalWsServer = class {
25965
25556
  return;
25966
25557
  }
25967
25558
  } else if (frame.type === "auth") {
25968
- const token = typeof frame.token === "string" ? frame.token ?? "" : "";
25969
- const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
25970
- const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
25971
- if (token && this.opts.tryVerifyCapabilityToken) {
25972
- const guestCtx = this.opts.tryVerifyCapabilityToken(
25973
- token,
25974
- selfPrincipalId || void 0,
25975
- selfDisplayName || void 0
25976
- );
25977
- if (guestCtx) this.attachClientContext(client.id, guestCtx);
25978
- }
25979
25559
  this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
25980
25560
  return;
25981
25561
  }
@@ -26092,31 +25672,15 @@ var AuthGate = class {
26092
25672
  this.markFailed(handle.id);
26093
25673
  return frame.type === "auth" ? "consumed" : "reject";
26094
25674
  }
26095
- let ctx = null;
26096
- if (this.opts.authenticate) {
26097
- const r = this.opts.authenticate(
26098
- parsed.data.token,
26099
- parsed.data.selfPrincipalId,
26100
- parsed.data.selfDisplayName
26101
- );
26102
- if (!r.ok) {
26103
- this.opts.closeConnection(handle, 4401, r.code);
26104
- this.markFailed(handle.id);
26105
- return "consumed";
26106
- }
26107
- ctx = r.context;
26108
- } else {
26109
- if (!this.opts.expectedToken) {
26110
- this.opts.closeConnection(handle, 1008, "auth not configured");
26111
- this.markFailed(handle.id);
26112
- return "consumed";
26113
- }
26114
- if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
26115
- this.opts.closeConnection(handle, 1008, "auth failed");
26116
- this.markFailed(handle.id);
26117
- return "consumed";
26118
- }
26119
- ctx = this.opts.buildOwnerContext?.() ?? null;
25675
+ if (!this.opts.expectedToken) {
25676
+ this.opts.closeConnection(handle, 1008, "auth not configured");
25677
+ this.markFailed(handle.id);
25678
+ return "consumed";
25679
+ }
25680
+ if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
25681
+ this.opts.closeConnection(handle, 1008, "auth failed");
25682
+ this.markFailed(handle.id);
25683
+ return "consumed";
26120
25684
  }
26121
25685
  if (st.timer != null) {
26122
25686
  const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
@@ -26124,7 +25688,6 @@ var AuthGate = class {
26124
25688
  }
26125
25689
  st.authed = true;
26126
25690
  st.timer = null;
26127
- if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
26128
25691
  this.opts.sendOk(handle, { type: "auth:ok" });
26129
25692
  return "consumed";
26130
25693
  }
@@ -26160,7 +25723,7 @@ var AuthContextResolver = class {
26160
25723
  }
26161
25724
  opts;
26162
25725
  /**
26163
- * 从 `Authorization` 头解析。null = 无凭证 / 不识别,调用方一律 401。
25726
+ * 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
26164
25727
  * remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
26165
25728
  */
26166
25729
  resolveFromHeader(authHeader, remoteAddress) {
@@ -26170,6 +25733,15 @@ var AuthContextResolver = class {
26170
25733
  if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
26171
25734
  return { role: "owner", isLoopback };
26172
25735
  }
25736
+ const personalHit = this.opts.personaRegistry.findByToken(token);
25737
+ if (personalHit) {
25738
+ return {
25739
+ role: "personal",
25740
+ personaId: personalHit.personaId,
25741
+ label: personalHit.label,
25742
+ isLoopback
25743
+ };
25744
+ }
26173
25745
  return null;
26174
25746
  }
26175
25747
  };
@@ -26191,753 +25763,206 @@ function constantTimeEqual2(a, b2) {
26191
25763
  return diff2 === 0;
26192
25764
  }
26193
25765
 
26194
- // ../protocol/src/index.ts
26195
- init_runtime();
26196
-
26197
- // src/transport/connection-context.ts
26198
- function ownerContext(ownerPrincipalId, displayName) {
26199
- return {
26200
- principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
26201
- grants: [{ resource: { type: "*" }, actions: ["admin"] }]
26202
- };
26203
- }
26204
- function guestContext(cap) {
26205
- return {
26206
- principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
26207
- grants: cap.grants,
26208
- capabilityId: cap.id
26209
- };
26210
- }
26211
- function authenticate(token, deps) {
26212
- if (!token) return { ok: false, code: "NO_TOKEN" };
26213
- if (deps.isOwnerToken(token)) {
26214
- return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
26215
- }
26216
- if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
26217
- const v2 = deps.capabilityRegistry.verifyToken(token);
26218
- if (!v2.ok) {
26219
- if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
26220
- return { ok: false, code: v2.code };
26221
- }
26222
- if (deps.selfPrincipalId && deps.selfDisplayName && deps.onGuestHello) {
26223
- try {
26224
- deps.onGuestHello(v2.capability.id, deps.selfPrincipalId, deps.selfDisplayName);
26225
- } catch (e) {
26226
- if (e.message === "TOKEN_SHARED_LEAK") {
26227
- return { ok: false, code: "TOKEN_REVOKED" };
26228
- }
26229
- throw e;
26230
- }
26231
- }
26232
- return { ok: true, context: guestContext(v2.capability) };
26233
- }
25766
+ // src/transport/http-router.ts
25767
+ var import_node_fs14 = __toESM(require("fs"), 1);
25768
+ var import_node_path16 = __toESM(require("path"), 1);
26234
25769
 
26235
- // src/permission/capability-store.ts
26236
- var fs15 = __toESM(require("fs"), 1);
26237
- var path18 = __toESM(require("path"), 1);
26238
- var CAPABILITIES_FILE_NAME = "capabilities.json";
26239
- var FILE_VERSION = 1;
26240
- var CapabilityStore = class {
26241
- constructor(dataDir) {
26242
- this.dataDir = dataDir;
26243
- fs15.mkdirSync(dataDir, { recursive: true });
26244
- this.cache = this.readFromDisk();
26245
- }
25770
+ // src/attachment/group.ts
25771
+ var import_node_fs13 = __toESM(require("fs"), 1);
25772
+ var import_node_path14 = __toESM(require("path"), 1);
25773
+ var import_node_crypto4 = __toESM(require("crypto"), 1);
25774
+ init_protocol();
25775
+ var GroupFileStore = class {
26246
25776
  dataDir;
26247
- cache;
26248
- list() {
26249
- return [...this.cache];
25777
+ logger;
25778
+ cache = /* @__PURE__ */ new Map();
25779
+ constructor(opts) {
25780
+ this.dataDir = opts.dataDir;
25781
+ this.logger = opts.logger;
26250
25782
  }
26251
- upsert(cap) {
26252
- const idx = this.cache.findIndex((c) => c.id === cap.id);
26253
- if (idx >= 0) {
26254
- this.cache[idx] = cap;
26255
- } else {
26256
- this.cache.push(cap);
26257
- }
26258
- this.flush();
25783
+ rootForScope(scope) {
25784
+ return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
26259
25785
  }
26260
- remove(id) {
26261
- const next = this.cache.filter((c) => c.id !== id);
26262
- if (next.length === this.cache.length) return;
26263
- this.cache = next;
26264
- this.flush();
25786
+ /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
25787
+ filePath(scope, sessionId) {
25788
+ return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
26265
25789
  }
26266
- filePath() {
26267
- return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
25790
+ cacheKey(scope, sessionId) {
25791
+ return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
26268
25792
  }
26269
- readFromDisk() {
26270
- const file = this.filePath();
26271
- let raw;
25793
+ /** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
25794
+ readFile(scope, sessionId) {
25795
+ const file = this.filePath(scope, sessionId);
26272
25796
  try {
26273
- raw = fs15.readFileSync(file, "utf8");
26274
- } catch (err) {
26275
- if (err?.code === "ENOENT") return [];
26276
- return [];
26277
- }
26278
- if (!raw.trim()) return [];
26279
- let parsed;
26280
- try {
26281
- parsed = JSON.parse(raw);
26282
- } catch {
26283
- return [];
26284
- }
26285
- if (!parsed || typeof parsed !== "object") return [];
26286
- const arr = parsed.capabilities;
26287
- if (!Array.isArray(arr)) return [];
26288
- const out = [];
26289
- for (const item of arr) {
26290
- const r = CapabilitySchema.safeParse(item);
26291
- if (r.success) out.push(r.data);
26292
- }
26293
- return out;
26294
- }
26295
- flush() {
26296
- const content = { version: FILE_VERSION, capabilities: this.cache };
26297
- this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
26298
- }
26299
- atomicWrite(file, content) {
26300
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26301
- fs15.writeFileSync(tmp, content, { mode: 384 });
26302
- fs15.renameSync(tmp, file);
26303
- try {
26304
- fs15.chmodSync(file, 384);
26305
- } catch {
26306
- }
26307
- }
26308
- };
26309
-
26310
- // src/permission/capability-registry.ts
26311
- var crypto4 = __toESM(require("crypto"), 1);
26312
- var CapabilityRegistry = class {
26313
- constructor(store, now = () => Date.now()) {
26314
- this.store = store;
26315
- this.now = now;
26316
- for (const cap of store.list()) {
26317
- this.bySecretHash.set(cap.secretHash, cap);
26318
- }
26319
- }
26320
- store;
26321
- now;
26322
- // sha256(token) → Capability
26323
- bySecretHash = /* @__PURE__ */ new Map();
26324
- list() {
26325
- return this.store.list();
26326
- }
26327
- verifyToken(token) {
26328
- if (!token) return { ok: false, code: "TOKEN_INVALID" };
26329
- const hash = sha256Hex(token);
26330
- const cap = this.bySecretHash.get(hash);
26331
- if (!cap) return { ok: false, code: "TOKEN_INVALID" };
26332
- if (cap.expiresAt && this.now() >= cap.expiresAt) {
26333
- return { ok: false, code: "TOKEN_EXPIRED" };
26334
- }
26335
- if (cap.maxUses && cap.usedCount >= cap.maxUses) {
26336
- return { ok: false, code: "TOKEN_EXHAUSTED" };
26337
- }
26338
- const updated = { ...cap, usedCount: cap.usedCount + 1 };
26339
- this.bySecretHash.set(hash, updated);
26340
- this.store.upsert(updated);
26341
- return { ok: true, capability: updated };
26342
- }
26343
- upsertCapability(cap) {
26344
- this.bySecretHash.set(cap.secretHash, cap);
26345
- this.store.upsert(cap);
26346
- }
26347
- /**
26348
- * Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
26349
- * 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
26350
- * idempotent:不存在 → null。
26351
- */
26352
- delete(id) {
26353
- const current = this.store.list().find((c) => c.id === id);
26354
- if (!current) return null;
26355
- this.bySecretHash.delete(current.secretHash);
26356
- this.store.remove(id);
26357
- return current;
26358
- }
26359
- findById(id) {
26360
- return this.store.list().find((c) => c.id === id) ?? null;
26361
- }
26362
- };
26363
- function sha256Hex(s) {
26364
- return crypto4.createHash("sha256").update(s).digest("hex");
26365
- }
26366
-
26367
- // src/permission/capability-manager.ts
26368
- var crypto5 = __toESM(require("crypto"), 1);
26369
- var CapabilityManager = class {
26370
- constructor(registry2, hooks = {}) {
26371
- this.registry = registry2;
26372
- this.hooks = hooks;
26373
- }
26374
- registry;
26375
- hooks;
26376
- list() {
26377
- return this.registry.list();
26378
- }
26379
- // whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
26380
- // 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
26381
- findById(id) {
26382
- return this.registry.findById(id);
26383
- }
26384
- issue(args) {
26385
- if (!args.displayName.trim()) {
26386
- throw new Error("CapabilityManager.issue: displayName must be non-empty");
26387
- }
26388
- const token = (this.hooks.generateToken ?? defaultGenerateToken)();
26389
- const id = (this.hooks.generateId ?? defaultGenerateId)();
26390
- const now = (this.hooks.now ?? Date.now)();
26391
- const cap = {
26392
- id,
26393
- secretHash: sha256Hex2(token),
26394
- displayName: args.displayName,
26395
- grants: args.grants,
26396
- issuedAt: now,
26397
- usedCount: 0,
26398
- ...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
26399
- ...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
26400
- };
26401
- this.registry.upsertCapability(cap);
26402
- this.hooks.onIssued?.(cap, token);
26403
- return { token, capability: cap };
26404
- }
26405
- /**
26406
- * guest 端 auth 帧捎带 selfPrincipalId/selfDisplayName 时,AuthGate.authenticate
26407
- * 校验 cap token 通过后调本方法把 peer 字段写回 cap:
26408
- * - 首次(cap.firstUsedByPeerAt 未设)→ 写盘 + 触发 onIssued 复用 push 频道
26409
- * 让 owner UI 通过 capability:updated 实时拿到 peer 字段
26410
- * - 重复(同 peerOwnerId)→ noop
26411
- * - 冲突(不同 peerOwnerId)→ throw TOKEN_SHARED_LEAK,AuthGate 转 close 4401
26412
- */
26413
- recordPeerHello(capId, args) {
26414
- const cap = this.registry.findById(capId);
26415
- if (!cap) return { changed: false };
26416
- if (cap.firstUsedByPeerAt != null) {
26417
- if (cap.peerOwnerId !== args.ownerPrincipalId) {
26418
- throw new Error("TOKEN_SHARED_LEAK");
25797
+ const raw = import_node_fs13.default.readFileSync(file, "utf8");
25798
+ const parsed = JSON.parse(raw);
25799
+ if (!Array.isArray(parsed)) {
25800
+ this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
25801
+ file
25802
+ });
25803
+ return [];
26419
25804
  }
26420
- return { changed: false };
26421
- }
26422
- const updated = {
26423
- ...cap,
26424
- peerOwnerId: args.ownerPrincipalId,
26425
- firstUsedByPeerAt: args.now,
26426
- peerDisplayName: args.displayName
26427
- };
26428
- this.registry.upsertCapability(updated);
26429
- this.hooks.onIssued?.(updated, "__peer_hello__");
26430
- return { changed: true };
26431
- }
26432
- /**
26433
- * Hard delete:从 registry / store 物理移除 capability。
26434
- * idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
26435
- * onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
26436
- */
26437
- delete(id) {
26438
- const removed = this.registry.delete(id);
26439
- if (!removed) return null;
26440
- this.hooks.onDeleted?.(removed);
26441
- return { capability: removed };
26442
- }
26443
- };
26444
- function defaultGenerateToken() {
26445
- return crypto5.randomBytes(24).toString("base64url");
26446
- }
26447
- function defaultGenerateId() {
26448
- return "cap_" + crypto5.randomBytes(6).toString("base64url");
26449
- }
26450
- function sha256Hex2(s) {
26451
- return crypto5.createHash("sha256").update(s).digest("hex");
26452
- }
26453
-
26454
- // src/permission/cleanup.ts
26455
- var fs16 = __toESM(require("fs"), 1);
26456
- function cleanupGuestSessionsForCapability(cap, factory) {
26457
- const removed = [];
26458
- for (const g2 of cap.grants) {
26459
- if (g2.resource.type !== "persona") continue;
26460
- const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
26461
- try {
26462
- fs16.rmSync(dir, { recursive: true, force: true });
26463
- removed.push(dir);
26464
- } catch {
26465
- }
26466
- }
26467
- return { removed };
26468
- }
26469
-
26470
- // src/inbox/inbox-store.ts
26471
- var fs17 = __toESM(require("fs"), 1);
26472
- var path19 = __toESM(require("path"), 1);
26473
- var INBOX_SUBDIR = "inbox";
26474
- var InboxStore = class {
26475
- constructor(dataDir) {
26476
- this.dataDir = dataDir;
26477
- fs17.mkdirSync(this.dirPath(), { recursive: true });
26478
- }
26479
- dataDir;
26480
- /**
26481
- * 列出某 peer 的消息, 按 createdAt 升序.
26482
- * sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
26483
- * 文件不存在时返回 [] (不抛, 不创建).
26484
- */
26485
- list(peerOwnerId, sinceCreatedAt) {
26486
- const file = this.filePath(peerOwnerId);
26487
- let raw;
26488
- try {
26489
- raw = fs17.readFileSync(file, "utf8");
25805
+ const out = [];
25806
+ for (const entry of parsed) {
25807
+ const r = GroupFileEntrySchema.safeParse(entry);
25808
+ if (r.success) out.push(r.data);
25809
+ }
25810
+ return out;
26490
25811
  } catch (err) {
26491
- if (err?.code === "ENOENT") return [];
25812
+ const code = err?.code;
25813
+ if (code === "ENOENT") return [];
25814
+ this.logger?.warn("GroupFileStore.readFile failed", {
25815
+ file,
25816
+ err: err.message
25817
+ });
26492
25818
  return [];
26493
25819
  }
26494
- const all = parseAllLines(raw);
26495
- if (sinceCreatedAt === void 0) return all;
26496
- return all.filter((m2) => m2.createdAt > sinceCreatedAt);
26497
25820
  }
26498
- /**
26499
- * 列出所有 peer 的 peerOwnerId (扫目录所有 *.jsonl 文件名).
26500
- */
26501
- listAllPeerOwnerIds() {
26502
- const dir = this.dirPath();
26503
- let entries;
26504
- try {
26505
- entries = fs17.readdirSync(dir);
26506
- } catch (err) {
26507
- if (err?.code === "ENOENT") return [];
26508
- return [];
26509
- }
26510
- return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
25821
+ writeFile(scope, sessionId, entries) {
25822
+ const file = this.filePath(scope, sessionId);
25823
+ import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
25824
+ const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
25825
+ import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
25826
+ import_node_fs13.default.renameSync(tmp, file);
26511
25827
  }
26512
- /**
26513
- * 追加一条消息,按 message.id 幂等去重。
26514
- * 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
26515
- */
26516
- append(message) {
26517
- const existing = this.list(message.peerOwnerId);
26518
- if (existing.some((m2) => m2.id === message.id)) return;
26519
- const file = this.filePath(message.peerOwnerId);
26520
- const line = JSON.stringify(message) + "\n";
26521
- fs17.appendFileSync(file, line, { mode: 384 });
26522
- try {
26523
- fs17.chmodSync(file, 384);
26524
- } catch {
26525
- }
25828
+ /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
25829
+ list(scope, sessionId) {
25830
+ const key = this.cacheKey(scope, sessionId);
25831
+ const cached = this.cache.get(key);
25832
+ if (cached) return cached.entries;
25833
+ const entries = this.readFile(scope, sessionId);
25834
+ this.cache.set(key, { entries, scope });
25835
+ return entries;
26526
25836
  }
26527
25837
  /**
26528
- * 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=principalId 的消息
26529
- * readBy[principalId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
26530
- * 返写入了几条.
25838
+ * upsert:
25839
+ * - relPath 已存在 更新 lastEditedAt + clear stale;不动 from / addedAt / id
25840
+ * (保留首次入群人 / 入群时刻)
25841
+ * - 不存在 → append,id 派生稳定 uuid,addedAt = now
25842
+ *
25843
+ * 返回最新 entry(caller 可用来 broadcast 通知)。
26531
25844
  */
26532
- markRead(peerOwnerId, principalId, upToCreatedAt) {
26533
- const messages = this.list(peerOwnerId);
26534
- let changed = 0;
26535
- const next = messages.map((m2) => {
26536
- if (m2.senderPrincipalId === principalId) return m2;
26537
- if (m2.createdAt > upToCreatedAt) return m2;
26538
- const cur = m2.readBy[principalId];
26539
- if (cur !== void 0 && cur >= upToCreatedAt) return m2;
26540
- changed++;
26541
- return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
26542
- });
26543
- if (changed === 0) return 0;
26544
- this.rewriteFile(peerOwnerId, next);
26545
- return changed;
25845
+ upsert(scope, sessionId, input, now = Date.now()) {
25846
+ const entries = this.list(scope, sessionId).slice();
25847
+ const idx = entries.findIndex((e) => e.relPath === input.relPath);
25848
+ let next;
25849
+ if (idx >= 0) {
25850
+ const prev = entries[idx];
25851
+ next = {
25852
+ ...prev,
25853
+ size: input.size,
25854
+ mime: input.mime,
25855
+ lastEditedAt: now,
25856
+ stale: false
25857
+ // label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
25858
+ };
25859
+ entries[idx] = next;
25860
+ } else {
25861
+ next = {
25862
+ id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
25863
+ relPath: input.relPath,
25864
+ from: input.from,
25865
+ label: input.label,
25866
+ size: input.size,
25867
+ mime: input.mime,
25868
+ addedAt: now
25869
+ // agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
25870
+ };
25871
+ entries.push(next);
25872
+ }
25873
+ this.writeFile(scope, sessionId, entries);
25874
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
25875
+ return next;
26546
25876
  }
26547
25877
  /**
26548
- * 删整个 peer jsonl (peer:remove cascade). 不存在 no-op.
25878
+ * 标记一个 relPath stale(agent rm / mv 后文件不在)。
25879
+ * - 命中 → stale=true,UI 灰显
25880
+ * - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
25881
+ *
25882
+ * 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
26549
25883
  */
26550
- removeByPeerOwnerId(peerOwnerId) {
26551
- const file = this.filePath(peerOwnerId);
26552
- try {
26553
- fs17.unlinkSync(file);
26554
- } catch (err) {
26555
- if (err?.code === "ENOENT") return;
26556
- }
26557
- }
26558
- rewriteFile(peerOwnerId, messages) {
26559
- const file = this.filePath(peerOwnerId);
26560
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26561
- const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
26562
- fs17.writeFileSync(tmp, content, { mode: 384 });
26563
- fs17.renameSync(tmp, file);
26564
- try {
26565
- fs17.chmodSync(file, 384);
26566
- } catch {
26567
- }
26568
- }
26569
- dirPath() {
26570
- return path19.join(this.dataDir, INBOX_SUBDIR);
26571
- }
26572
- filePath(peerOwnerId) {
26573
- return path19.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26574
- }
26575
- };
26576
- function parseAllLines(raw) {
26577
- const out = [];
26578
- for (const line of raw.split("\n")) {
26579
- const trimmed = line.trim();
26580
- if (!trimmed) continue;
26581
- let parsed;
26582
- try {
26583
- parsed = JSON.parse(trimmed);
26584
- } catch {
26585
- continue;
26586
- }
26587
- const r = InboxMessageSchema.safeParse(parsed);
26588
- if (r.success) out.push(r.data);
26589
- }
26590
- return out;
26591
- }
26592
-
26593
- // src/inbox/inbox-manager.ts
26594
- var InboxManager = class {
26595
- constructor(store, broadcast) {
26596
- this.store = store;
26597
- this.broadcast = broadcast;
25884
+ markStale(scope, sessionId, relPath) {
25885
+ const entries = this.list(scope, sessionId).slice();
25886
+ const idx = entries.findIndex((e) => e.relPath === relPath);
25887
+ if (idx < 0) return;
25888
+ if (entries[idx].stale) return;
25889
+ entries[idx] = { ...entries[idx], stale: true };
25890
+ this.writeFile(scope, sessionId, entries);
25891
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
26598
25892
  }
26599
- store;
26600
- broadcast;
26601
25893
  /**
26602
- * 写一条消息到 peer.jsonl + broadcast 给本机所有 owner ws。
26603
- * id 幂等:同 id 已落盘则 no-op(不广播、不重写)。
25894
+ * 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
25895
+ * agent 自动入群的不应走这条 —— markStale 表达"文件不在了"语义;
25896
+ * owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
26604
25897
  *
26605
- * caller 必须已通过 handler 鉴权
26606
- * (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
25898
+ * 返回值:true=命中并删除;false=relPath 不在群里。
26607
25899
  */
26608
- postMessage(args) {
26609
- const beforeLen = this.store.list(args.peerOwnerId).length;
26610
- const msg = {
26611
- id: args.id,
26612
- peerOwnerId: args.peerOwnerId,
26613
- senderPrincipalId: args.senderPrincipalId,
26614
- text: args.text,
26615
- createdAt: args.createdAt,
26616
- readBy: {}
26617
- };
26618
- this.store.append(msg);
26619
- const afterLen = this.store.list(args.peerOwnerId).length;
26620
- if (afterLen === beforeLen) return msg;
26621
- this.broadcast(args.peerOwnerId, {
26622
- type: "inbox:event",
26623
- peerOwnerId: args.peerOwnerId,
26624
- message: msg
26625
- });
26626
- return msg;
26627
- }
26628
- list(peerOwnerId, sinceCreatedAt) {
26629
- return this.store.list(peerOwnerId, sinceCreatedAt);
25900
+ remove(scope, sessionId, relPath) {
25901
+ const entries = this.list(scope, sessionId).slice();
25902
+ const idx = entries.findIndex((e) => e.relPath === relPath);
25903
+ if (idx < 0) return false;
25904
+ entries.splice(idx, 1);
25905
+ this.writeFile(scope, sessionId, entries);
25906
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
25907
+ return true;
26630
25908
  }
26631
25909
  /**
26632
- * 标已读. 返写入了几条 (sender !== self createdAt <= upToCreatedAt 的消息).
26633
- * broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
25910
+ * session 聚合查询(spec §4 HTTP ACL:personal 视野并集)。
25911
+ *
25912
+ * 扫 <dataDir>/sessions/<personaId>/owner/*.group-files.json 和
25913
+ * <dataDir>/sessions/<personaId>/listener/*.group-files.json,每文件读一份。
25914
+ *
25915
+ * 复杂度 O(N sessions × N entries),N 通常 < 100,可接受。
26634
25916
  */
26635
- markRead(args) {
26636
- const before = this.store.list(args.peerOwnerId);
26637
- const changed = this.store.markRead(args.peerOwnerId, args.principalId, args.upToCreatedAt);
26638
- if (changed === 0) return 0;
26639
- const after = this.store.list(args.peerOwnerId);
26640
- const beforeById = new Map(before.map((m2) => [m2.id, m2]));
26641
- for (const m2 of after) {
26642
- const prev = beforeById.get(m2.id);
26643
- if (!prev) continue;
26644
- if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
26645
- this.broadcast(args.peerOwnerId, {
26646
- type: "inbox:event",
26647
- peerOwnerId: args.peerOwnerId,
26648
- message: m2
26649
- });
26650
- }
26651
- return changed;
26652
- }
26653
- /** peer:remove cascade. 删整个 peer 的 jsonl. */
26654
- removeChannel(peerOwnerId) {
26655
- this.store.removeByPeerOwnerId(peerOwnerId);
26656
- }
26657
- };
26658
-
26659
- // src/state/received-capability-store.ts
26660
- var fs18 = __toESM(require("fs"), 1);
26661
- var path20 = __toESM(require("path"), 1);
26662
- var FILE_NAME = "received-capabilities.json";
26663
- var ReceivedCapabilityStore = class {
26664
- constructor(dataDir) {
26665
- this.dataDir = dataDir;
26666
- }
26667
- dataDir;
26668
- caps = /* @__PURE__ */ new Map();
26669
- load() {
26670
- this.caps.clear();
26671
- const file = path20.join(this.dataDir, FILE_NAME);
26672
- let raw;
26673
- try {
26674
- raw = fs18.readFileSync(file, "utf8");
26675
- } catch (err) {
26676
- if (err?.code !== "ENOENT") this.renameBak(file);
26677
- return;
26678
- }
26679
- let parsed;
26680
- try {
26681
- parsed = JSON.parse(raw);
26682
- } catch {
26683
- this.renameBak(file);
26684
- return;
26685
- }
26686
- const arr = parsed?.receivedCaps;
26687
- if (!Array.isArray(arr)) return;
26688
- for (const entry of arr) {
26689
- const r = ReceivedCapabilitySchema.safeParse(entry);
26690
- if (r.success) this.caps.set(r.data.ownerPrincipalId, r.data);
26691
- }
26692
- }
26693
- list() {
26694
- return Array.from(this.caps.values());
26695
- }
26696
- get(ownerPrincipalId) {
26697
- return this.caps.get(ownerPrincipalId) ?? null;
26698
- }
26699
- hasOwner(ownerPrincipalId) {
26700
- return this.caps.has(ownerPrincipalId);
26701
- }
26702
- upsert(cap) {
26703
- this.caps.set(cap.ownerPrincipalId, cap);
26704
- this.flush();
26705
- }
26706
- remove(ownerPrincipalId) {
26707
- if (!this.caps.delete(ownerPrincipalId)) return;
26708
- this.flush();
26709
- }
26710
- flush() {
26711
- const file = path20.join(this.dataDir, FILE_NAME);
26712
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
26713
- const content = JSON.stringify(
26714
- { receivedCaps: Array.from(this.caps.values()) },
26715
- null,
26716
- 2
26717
- );
26718
- fs18.mkdirSync(this.dataDir, { recursive: true });
26719
- fs18.writeFileSync(tmp, content, { mode: 384 });
26720
- fs18.renameSync(tmp, file);
26721
- }
26722
- renameBak(file) {
26723
- try {
26724
- fs18.renameSync(file, `${file}.bak`);
26725
- } catch {
26726
- }
26727
- }
26728
- };
26729
-
26730
- // src/state/legacy-migration.ts
26731
- var fs19 = __toESM(require("fs"), 1);
26732
- var path21 = __toESM(require("path"), 1);
26733
- var TARGETS = [
26734
- { src: "capabilities.json", bak: "capabilities.legacy.bak" },
26735
- { src: "remote-personas", bak: "remote-personas.legacy.bak" },
26736
- { src: "peers.json", bak: "peers.legacy.bak" }
26737
- ];
26738
- function migrateLegacyFiles(dataDir) {
26739
- for (const t of TARGETS) {
26740
- const srcPath = path21.join(dataDir, t.src);
26741
- const bakPath = path21.join(dataDir, t.bak);
26742
- if (!fs19.existsSync(srcPath)) continue;
26743
- if (fs19.existsSync(bakPath)) continue;
26744
- try {
26745
- fs19.renameSync(srcPath, bakPath);
26746
- } catch {
26747
- }
26748
- }
26749
- }
26750
-
26751
- // src/received-capability/connect-remote.ts
26752
- var crypto6 = __toESM(require("crypto"), 1);
26753
- var HANDSHAKE_TIMEOUT_MS = 5e3;
26754
- var RPC_TIMEOUT_MS = 5e3;
26755
- async function connectRemote(args) {
26756
- const ws = new wrapper_default(args.url);
26757
- await new Promise((resolve2, reject) => {
26758
- const onError = (e) => reject(e);
26759
- ws.once("open", () => {
26760
- ws.off("error", onError);
26761
- resolve2();
26762
- });
26763
- ws.once("error", onError);
26764
- });
26765
- ws.send(
26766
- JSON.stringify({
26767
- type: "auth",
26768
- token: args.token,
26769
- selfPrincipalId: args.selfPrincipalId,
26770
- selfDisplayName: args.selfDisplayName
26771
- })
26772
- );
26773
- await new Promise((resolve2, reject) => {
26774
- const onMessage = (raw) => {
26775
- let f;
25917
+ listByPersona(personaId) {
25918
+ const out = [];
25919
+ for (const mode of ["owner", "listener"]) {
25920
+ const scope = { kind: "persona", personaId, mode };
25921
+ const root = this.rootForScope(scope);
25922
+ let names;
26776
25923
  try {
26777
- f = JSON.parse(raw.toString());
26778
- } catch {
26779
- return;
26780
- }
26781
- if (f.type === "auth:ok" || f.type === "ready") {
26782
- ws.off("message", onMessage);
26783
- resolve2();
26784
- return;
26785
- }
26786
- if (f.type === "auth:error" || f.code === 4401) {
26787
- ws.off("message", onMessage);
26788
- reject(new Error(`auth failed: ${f.message ?? f.type ?? "unknown"}`));
25924
+ names = import_node_fs13.default.readdirSync(root);
25925
+ } catch (err) {
25926
+ const code = err?.code;
25927
+ if (code === "ENOENT") continue;
25928
+ continue;
26789
25929
  }
26790
- };
26791
- ws.on("message", onMessage);
26792
- setTimeout(() => {
26793
- ws.off("message", onMessage);
26794
- reject(new Error("auth handshake timeout"));
26795
- }, HANDSHAKE_TIMEOUT_MS);
26796
- });
26797
- function call(method, payload) {
26798
- const requestId = crypto6.randomUUID();
26799
- return new Promise((resolve2, reject) => {
26800
- const onMessage = (raw) => {
26801
- let f;
26802
- try {
26803
- f = JSON.parse(raw.toString());
26804
- } catch {
26805
- return;
26806
- }
26807
- if (f.requestId !== requestId) return;
26808
- ws.off("message", onMessage);
26809
- if (f.error || f.type?.endsWith(":error")) {
26810
- reject(new Error(f.message ?? f.error ?? "rpc error"));
26811
- } else {
26812
- resolve2(f);
26813
- }
26814
- };
26815
- ws.on("message", onMessage);
26816
- ws.send(JSON.stringify({ type: method, requestId, ...payload }));
26817
- setTimeout(() => {
26818
- ws.off("message", onMessage);
26819
- reject(new Error(`${method} timeout`));
26820
- }, RPC_TIMEOUT_MS);
26821
- });
26822
- }
26823
- return {
26824
- whoami: async () => {
26825
- const f = await call("whoami", {});
26826
- return {
26827
- ownerId: f.owner.id,
26828
- displayName: f.owner.displayName,
26829
- capabilityId: f.capability.id,
26830
- grants: f.capability.grants
26831
- };
26832
- },
26833
- close: () => {
26834
- try {
26835
- ws.close();
26836
- } catch {
25930
+ for (const name of names) {
25931
+ if (!name.endsWith(".group-files.json")) continue;
25932
+ const sessionId = name.slice(0, -".group-files.json".length);
25933
+ if (!sessionId) continue;
25934
+ const entries = this.list(scope, sessionId);
25935
+ out.push({ sessionId, entries });
26837
25936
  }
26838
25937
  }
26839
- };
26840
- }
26841
-
26842
- // src/migrations/2026-05-20-flatten-sessions.ts
26843
- var fs20 = __toESM(require("fs"), 1);
26844
- var path22 = __toESM(require("path"), 1);
26845
- var MIGRATION_FLAG_NAME = ".migration.v1.done";
26846
- function migrateFlattenSessions(opts) {
26847
- const dataDir = opts.dataDir;
26848
- const now = opts.now ?? Date.now;
26849
- const sessionsDir = path22.join(dataDir, "sessions");
26850
- const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
26851
- if (existsSync4(flagPath)) {
26852
- return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
26853
- }
26854
- let movedBare = 0;
26855
- let movedVmOwner = 0;
26856
- let archivedListener = 0;
26857
- const defaultDir = path22.join(sessionsDir, "default");
26858
- if (existsSync4(defaultDir)) {
26859
- for (const entry of readdirSafe(defaultDir)) {
26860
- if (!entry.endsWith(".json")) continue;
26861
- const src = path22.join(defaultDir, entry);
26862
- const dst = path22.join(sessionsDir, entry);
26863
- fs20.renameSync(src, dst);
26864
- movedBare += 1;
26865
- }
26866
- rmdirIfEmpty(defaultDir);
26867
- }
26868
- for (const pid of readdirSafe(sessionsDir)) {
26869
- const personaDir = path22.join(sessionsDir, pid);
26870
- if (!isDir(personaDir)) continue;
26871
- if (pid === "default") continue;
26872
- const ownerSrc = path22.join(personaDir, "owner");
26873
- if (existsSync4(ownerSrc) && isDir(ownerSrc)) {
26874
- const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
26875
- fs20.mkdirSync(ownerDst, { recursive: true });
26876
- for (const file of readdirSafe(ownerSrc)) {
26877
- if (!file.endsWith(".json")) continue;
26878
- fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
26879
- movedVmOwner += 1;
26880
- }
26881
- rmdirIfEmpty(ownerSrc);
26882
- }
26883
- const listenerSrc = path22.join(personaDir, "listener");
26884
- if (existsSync4(listenerSrc) && isDir(listenerSrc)) {
26885
- const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
26886
- fs20.mkdirSync(archiveDst, { recursive: true });
26887
- for (const file of readdirSafe(listenerSrc)) {
26888
- if (!file.endsWith(".json")) continue;
26889
- fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
26890
- archivedListener += 1;
26891
- }
26892
- rmdirIfEmpty(listenerSrc);
26893
- }
26894
- rmdirIfEmpty(personaDir);
26895
- }
26896
- fs20.mkdirSync(sessionsDir, { recursive: true });
26897
- fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
26898
- return {
26899
- skipped: false,
26900
- flagWritten: true,
26901
- movedBare,
26902
- movedVmOwner,
26903
- archivedListener
26904
- };
26905
- }
26906
- function existsSync4(p2) {
26907
- try {
26908
- fs20.statSync(p2);
26909
- return true;
26910
- } catch {
26911
- return false;
25938
+ return out;
26912
25939
  }
26913
- }
26914
- function isDir(p2) {
26915
- try {
26916
- return fs20.statSync(p2).isDirectory();
26917
- } catch {
25940
+ };
25941
+ function personalViewable(groupStore, personaDir, personaId, absPath) {
25942
+ const realTarget = safeRealpath(absPath);
25943
+ if (!realTarget) {
26918
25944
  return false;
26919
25945
  }
26920
- }
26921
- function readdirSafe(p2) {
26922
- try {
26923
- return fs20.readdirSync(p2);
26924
- } catch {
26925
- return [];
25946
+ const personasUnion = groupStore.listByPersona(personaId);
25947
+ for (const { entries } of personasUnion) {
25948
+ for (const e of entries) {
25949
+ if (e.stale) continue;
25950
+ const realEntry = safeRealpath(import_node_path14.default.join(personaDir, e.relPath));
25951
+ if (realEntry && realEntry === realTarget) return true;
25952
+ }
26926
25953
  }
25954
+ return false;
26927
25955
  }
26928
- function rmdirIfEmpty(p2) {
25956
+ function safeRealpath(p2) {
26929
25957
  try {
26930
- fs20.rmdirSync(p2);
25958
+ return import_node_fs13.default.realpathSync(p2);
26931
25959
  } catch {
25960
+ return null;
26932
25961
  }
26933
25962
  }
26934
25963
 
26935
- // src/transport/http-router.ts
26936
- var import_node_fs13 = __toESM(require("fs"), 1);
26937
- var import_node_path15 = __toESM(require("path"), 1);
26938
-
26939
25964
  // src/attachment/mime.ts
26940
- var import_node_path14 = __toESM(require("path"), 1);
25965
+ var import_node_path15 = __toESM(require("path"), 1);
26941
25966
  var TEXT_PLAIN = "text/plain; charset=utf-8";
26942
25967
  var EXT_TO_NATIVE_MIME = {
26943
25968
  // 图片
@@ -27044,14 +26069,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
27044
26069
  ".mk"
27045
26070
  ]);
27046
26071
  function lookupMime(filePathOrName) {
27047
- const ext = import_node_path14.default.extname(filePathOrName).toLowerCase();
26072
+ const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
27048
26073
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
27049
26074
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
27050
26075
  return "application/octet-stream";
27051
26076
  }
27052
26077
 
27053
26078
  // src/attachment/sign-url.ts
27054
- var import_node_crypto4 = __toESM(require("crypto"), 1);
26079
+ var import_node_crypto5 = __toESM(require("crypto"), 1);
27055
26080
  var HMAC_ALGO = "sha256";
27056
26081
  function base64urlEncode(buf) {
27057
26082
  const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
@@ -27068,7 +26093,7 @@ function decodeAbsPathFromUrl(encoded) {
27068
26093
  }
27069
26094
  function computeSig(secret, absPath, e) {
27070
26095
  const msg = e === null ? absPath : `${absPath}|${e}`;
27071
- return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
26096
+ return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
27072
26097
  }
27073
26098
  function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
27074
26099
  const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
@@ -27103,7 +26128,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27103
26128
  if (provided.length !== expected.length) {
27104
26129
  return { ok: false, code: "BAD_SIG" };
27105
26130
  }
27106
- if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
26131
+ if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
27107
26132
  return { ok: false, code: "BAD_SIG" };
27108
26133
  }
27109
26134
  if (e !== null && now() / 1e3 > e) {
@@ -27124,7 +26149,7 @@ function createHttpRouter(deps) {
27124
26149
  sendJson(res, 200, { ok: true, version: deps.daemonVersion });
27125
26150
  return true;
27126
26151
  }
27127
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
26152
+ if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27128
26153
  return false;
27129
26154
  }
27130
26155
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
@@ -27164,8 +26189,43 @@ function createHttpRouter(deps) {
27164
26189
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27165
26190
  return true;
27166
26191
  }
26192
+ const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
26193
+ if (personaFilesMatch && req.method === "GET") {
26194
+ const pid = personaFilesMatch[1];
26195
+ const pathParam = url.searchParams.get("path");
26196
+ if (!pathParam) {
26197
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
26198
+ return true;
26199
+ }
26200
+ if (!deps.personaStore || !deps.groupFileStore) {
26201
+ sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
26202
+ return true;
26203
+ }
26204
+ const personaDir = deps.personaStore.personaDirPath(pid);
26205
+ const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
26206
+ if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
26207
+ sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
26208
+ return true;
26209
+ }
26210
+ if (ctx.role === "personal") {
26211
+ if (ctx.personaId !== pid) {
26212
+ sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
26213
+ return true;
26214
+ }
26215
+ if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
26216
+ sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
26217
+ return true;
26218
+ }
26219
+ }
26220
+ streamFile(res, absPath, deps.logger);
26221
+ return true;
26222
+ }
27167
26223
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27168
26224
  if (sessionFilesMatch && req.method === "GET") {
26225
+ if (ctx.role !== "owner") {
26226
+ sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
26227
+ return true;
26228
+ }
27169
26229
  const sid = sessionFilesMatch[1];
27170
26230
  const pathParam = url.searchParams.get("path");
27171
26231
  if (!pathParam) {
@@ -27173,7 +26233,7 @@ function createHttpRouter(deps) {
27173
26233
  return true;
27174
26234
  }
27175
26235
  let absPath;
27176
- if (import_node_path15.default.isAbsolute(pathParam)) {
26236
+ if (import_node_path16.default.isAbsolute(pathParam)) {
27177
26237
  absPath = pathParam;
27178
26238
  } else if (deps.sessionStore) {
27179
26239
  const file = deps.sessionStore.read(sid);
@@ -27181,7 +26241,7 @@ function createHttpRouter(deps) {
27181
26241
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27182
26242
  return true;
27183
26243
  }
27184
- absPath = import_node_path15.default.join(file.cwd, pathParam);
26244
+ absPath = import_node_path16.default.join(file.cwd, pathParam);
27185
26245
  } else {
27186
26246
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27187
26247
  return true;
@@ -27206,182 +26266,47 @@ function sendJson(res, status, body) {
27206
26266
  res.end(JSON.stringify(body));
27207
26267
  }
27208
26268
  function withCtx(ctx, body) {
27209
- return { ...body, role: ctx.role };
26269
+ return { ...body, role: ctx.role, personaId: ctx.personaId };
26270
+ }
26271
+ function isContainedIn(abs, root) {
26272
+ const normalized = import_node_path16.default.resolve(abs);
26273
+ const normalizedRoot = import_node_path16.default.resolve(root);
26274
+ if (normalized === normalizedRoot) return true;
26275
+ return normalized.startsWith(normalizedRoot + import_node_path16.default.sep);
27210
26276
  }
27211
26277
  function streamFile(res, absPath, logger) {
27212
26278
  let stat;
27213
26279
  try {
27214
- stat = import_node_fs13.default.statSync(absPath);
26280
+ stat = import_node_fs14.default.statSync(absPath);
27215
26281
  } catch (err) {
27216
26282
  const code = err?.code;
27217
26283
  if (code === "ENOENT") {
27218
26284
  sendJson(res, 404, { code: "NOT_FOUND", message: "file not found" });
27219
26285
  } else {
27220
26286
  sendJson(res, 500, { code: "STAT_FAILED", message: err.message });
27221
- }
27222
- return;
27223
- }
27224
- if (!stat.isFile()) {
27225
- sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
27226
- return;
27227
- }
27228
- const mime = lookupMime(absPath);
27229
- const basename = import_node_path15.default.basename(absPath);
27230
- res.writeHead(200, {
27231
- "Content-Type": mime,
27232
- "Content-Length": String(stat.size),
27233
- "Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
27234
- // 防止浏览器把任意 mime 当 html 渲染
27235
- "X-Content-Type-Options": "nosniff"
27236
- });
27237
- const stream = import_node_fs13.default.createReadStream(absPath);
27238
- stream.on("error", (err) => {
27239
- logger?.warn("streamFile read error", { absPath, err: err.message });
27240
- res.destroy();
27241
- });
27242
- stream.pipe(res);
27243
- }
27244
-
27245
- // src/attachment/group.ts
27246
- var import_node_fs14 = __toESM(require("fs"), 1);
27247
- var import_node_path16 = __toESM(require("path"), 1);
27248
- var import_node_crypto5 = __toESM(require("crypto"), 1);
27249
- init_protocol();
27250
- var GroupFileStore = class {
27251
- dataDir;
27252
- logger;
27253
- cache = /* @__PURE__ */ new Map();
27254
- constructor(opts) {
27255
- this.dataDir = opts.dataDir;
27256
- this.logger = opts.logger;
27257
- }
27258
- rootForScope(scope) {
27259
- return import_node_path16.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27260
- }
27261
- /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27262
- filePath(scope, sessionId) {
27263
- return import_node_path16.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27264
- }
27265
- cacheKey(scope, sessionId) {
27266
- return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
27267
- }
27268
- /** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
27269
- readFile(scope, sessionId) {
27270
- const file = this.filePath(scope, sessionId);
27271
- try {
27272
- const raw = import_node_fs14.default.readFileSync(file, "utf8");
27273
- const parsed = JSON.parse(raw);
27274
- if (!Array.isArray(parsed)) {
27275
- this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
27276
- file
27277
- });
27278
- return [];
27279
- }
27280
- const out = [];
27281
- for (const entry of parsed) {
27282
- const r = GroupFileEntrySchema.safeParse(entry);
27283
- if (r.success) out.push(r.data);
27284
- }
27285
- return out;
27286
- } catch (err) {
27287
- const code = err?.code;
27288
- if (code === "ENOENT") return [];
27289
- this.logger?.warn("GroupFileStore.readFile failed", {
27290
- file,
27291
- err: err.message
27292
- });
27293
- return [];
27294
- }
27295
- }
27296
- writeFile(scope, sessionId, entries) {
27297
- const file = this.filePath(scope, sessionId);
27298
- import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
27299
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27300
- import_node_fs14.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27301
- import_node_fs14.default.renameSync(tmp, file);
27302
- }
27303
- /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27304
- list(scope, sessionId) {
27305
- const key = this.cacheKey(scope, sessionId);
27306
- const cached = this.cache.get(key);
27307
- if (cached) return cached.entries;
27308
- const entries = this.readFile(scope, sessionId);
27309
- this.cache.set(key, { entries });
27310
- return entries;
27311
- }
27312
- /**
27313
- * upsert:
27314
- * - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
27315
- * (保留首次入群人 / 入群时刻)
27316
- * - 不存在 → append,id 派生稳定 uuid,addedAt = now
27317
- *
27318
- * 返回最新 entry(caller 可用来 broadcast 通知)。
27319
- */
27320
- upsert(scope, sessionId, input, now = Date.now()) {
27321
- const entries = this.list(scope, sessionId).slice();
27322
- const idx = entries.findIndex((e) => e.relPath === input.relPath);
27323
- let next;
27324
- if (idx >= 0) {
27325
- const prev = entries[idx];
27326
- next = {
27327
- ...prev,
27328
- size: input.size,
27329
- mime: input.mime,
27330
- lastEditedAt: now,
27331
- stale: false
27332
- // label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
27333
- };
27334
- entries[idx] = next;
27335
- } else {
27336
- next = {
27337
- id: `gf-${import_node_crypto5.default.randomBytes(6).toString("base64url")}`,
27338
- relPath: input.relPath,
27339
- from: input.from,
27340
- label: input.label,
27341
- size: input.size,
27342
- mime: input.mime,
27343
- addedAt: now
27344
- // agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
27345
- };
27346
- entries.push(next);
27347
- }
27348
- this.writeFile(scope, sessionId, entries);
27349
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
27350
- return next;
27351
- }
27352
- /**
27353
- * 标记一个 relPath stale(agent rm / mv 后文件不在)。
27354
- * - 命中 → stale=true,UI 灰显
27355
- * - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
27356
- *
27357
- * 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
27358
- */
27359
- markStale(scope, sessionId, relPath) {
27360
- const entries = this.list(scope, sessionId).slice();
27361
- const idx = entries.findIndex((e) => e.relPath === relPath);
27362
- if (idx < 0) return;
27363
- if (entries[idx].stale) return;
27364
- entries[idx] = { ...entries[idx], stale: true };
27365
- this.writeFile(scope, sessionId, entries);
27366
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
26287
+ }
26288
+ return;
27367
26289
  }
27368
- /**
27369
- * 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
27370
- * agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
27371
- * owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
27372
- *
27373
- * 返回值:true=命中并删除;false=relPath 不在群里。
27374
- */
27375
- remove(scope, sessionId, relPath) {
27376
- const entries = this.list(scope, sessionId).slice();
27377
- const idx = entries.findIndex((e) => e.relPath === relPath);
27378
- if (idx < 0) return false;
27379
- entries.splice(idx, 1);
27380
- this.writeFile(scope, sessionId, entries);
27381
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
27382
- return true;
26290
+ if (!stat.isFile()) {
26291
+ sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
26292
+ return;
27383
26293
  }
27384
- };
26294
+ const mime = lookupMime(absPath);
26295
+ const basename = import_node_path16.default.basename(absPath);
26296
+ res.writeHead(200, {
26297
+ "Content-Type": mime,
26298
+ "Content-Length": String(stat.size),
26299
+ "Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
26300
+ // 防止浏览器把任意 mime 当 html 渲染
26301
+ "X-Content-Type-Options": "nosniff"
26302
+ });
26303
+ const stream = import_node_fs14.default.createReadStream(absPath);
26304
+ stream.on("error", (err) => {
26305
+ logger?.warn("streamFile read error", { absPath, err: err.message });
26306
+ res.destroy();
26307
+ });
26308
+ stream.pipe(res);
26309
+ }
27385
26310
 
27386
26311
  // src/discovery/state-file.ts
27387
26312
  var import_node_fs15 = __toESM(require("fs"), 1);
@@ -28061,32 +26986,26 @@ function authFilePath(dataDir) {
28061
26986
  }
28062
26987
  function loadOrCreateAuthFile(opts) {
28063
26988
  const file = authFilePath(opts.dataDir);
28064
- const generate = opts.generate ?? defaultGenerateToken2;
28065
- const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
26989
+ const generate = opts.generate ?? defaultGenerate;
28066
26990
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
28067
26991
  const existing = readAuthFile(file);
28068
- if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
26992
+ if (existing && existing.token && existing.signSecret) {
28069
26993
  return {
28070
26994
  token: existing.token,
28071
26995
  signSecret: existing.signSecret,
28072
- ownerPrincipalId: existing.ownerPrincipalId,
28073
26996
  createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
28074
26997
  };
28075
26998
  }
28076
26999
  const token = existing?.token || generate();
28077
27000
  const signSecret = existing?.signSecret || generate();
28078
- const ownerPrincipalId = existing?.ownerPrincipalId || genId();
28079
27001
  const createdAt = existing?.createdAt || now().toISOString();
28080
- const next = { token, signSecret, ownerPrincipalId, createdAt };
27002
+ const next = { token, signSecret, createdAt };
28081
27003
  writeAuthFile(file, next);
28082
27004
  return next;
28083
27005
  }
28084
- function defaultGenerateToken2() {
27006
+ function defaultGenerate() {
28085
27007
  return import_node_crypto8.default.randomBytes(32).toString("base64url");
28086
27008
  }
28087
- function defaultGenerateOwnerPrincipalId() {
28088
- return `owner-${import_node_crypto8.default.randomUUID()}`;
28089
- }
28090
27009
  function readAuthFile(file) {
28091
27010
  try {
28092
27011
  const raw = import_node_fs20.default.readFileSync(file, "utf8");
@@ -28097,7 +27016,6 @@ function readAuthFile(file) {
28097
27016
  return {
28098
27017
  token: parsed.token,
28099
27018
  signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
28100
- ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
28101
27019
  createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
28102
27020
  };
28103
27021
  } catch (err) {
@@ -28211,63 +27129,10 @@ function forkSession(input) {
28211
27129
  return { forkedToolSessionId, forkedFilePath };
28212
27130
  }
28213
27131
 
28214
- // src/permission/capability.ts
28215
- function matchResource(grant, target) {
28216
- if (grant.type === "*") return true;
28217
- if (grant.type !== target.type) return false;
28218
- if (grant.id === "*") return true;
28219
- return grant.id === target.id;
28220
- }
28221
- function assertGrant(grants, resource, action) {
28222
- for (const g2 of grants) {
28223
- if (!matchResource(g2.resource, resource)) continue;
28224
- if (g2.actions.includes(action)) return true;
28225
- if (g2.actions.includes("admin")) return true;
28226
- }
28227
- return false;
28228
- }
28229
-
28230
- // src/permission/session-access.ts
28231
- function canAccessSession(ctx, sessionId, action, deps) {
28232
- if (ctx.principal.kind === "owner") return true;
28233
- const file = deps.readSession(sessionId);
28234
- if (!file) return true;
28235
- return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
28236
- }
28237
- function canAccessPersona(grants, personaId, action) {
28238
- if (!personaId) return false;
28239
- const resource = { type: "persona", id: personaId };
28240
- return assertGrant(grants, resource, action);
28241
- }
28242
-
28243
27132
  // src/handlers/session.ts
28244
- init_protocol();
28245
27133
  function buildSessionHandlers(deps) {
28246
- const { manager, observer, getAdapter: getAdapter2, store, ownerPrincipalId } = deps;
28247
- const ensureSessionAccess = (ctx, sessionId, action) => {
28248
- if (!ctx) return;
28249
- const ok = canAccessSession(ctx, sessionId, action, {
28250
- readSession: (sid) => store.read(sid)
28251
- });
28252
- if (!ok) {
28253
- throw new ClawdError(
28254
- ERROR_CODES.UNAUTHORIZED,
28255
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} session ${sessionId}`
28256
- );
28257
- }
28258
- };
28259
- const ensurePersonaAccess = (ctx, personaId, action) => {
28260
- if (!ctx) return;
28261
- if (ctx.principal.kind === "owner") return;
28262
- const ok = canAccessPersona(ctx.grants, personaId, action);
28263
- if (!ok) {
28264
- throw new ClawdError(
28265
- ERROR_CODES.UNAUTHORIZED,
28266
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} on persona:${personaId ?? "<none>"}`
28267
- );
28268
- }
28269
- };
28270
- const create = async (frame, _client, ctx) => {
27134
+ const { manager, observer, getAdapter: getAdapter2 } = deps;
27135
+ const create = async (frame) => {
28271
27136
  const args = SessionCreateArgs.parse(frame);
28272
27137
  if (args.ownerPersonaId) {
28273
27138
  const persona = deps.personaRegistry.get(args.ownerPersonaId);
@@ -28275,75 +27140,58 @@ function buildSessionHandlers(deps) {
28275
27140
  throw new Error(`persona not found: ${args.ownerPersonaId}`);
28276
27141
  }
28277
27142
  }
28278
- ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
28279
- const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
28280
- const { response, broadcast } = manager.create(args, creatorPrincipalId);
27143
+ const { response, broadcast } = manager.create(args);
28281
27144
  return { response: { type: "session:info", ...response }, broadcast };
28282
27145
  };
28283
- const list = async (_frame, _client, ctx) => {
27146
+ const list = async () => {
28284
27147
  const { response } = manager.list();
28285
- if (ctx && ctx.principal.kind === "guest") {
28286
- const sessions = response.sessions ?? [];
28287
- const capId = ctx.capabilityId;
28288
- const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter((s) => s.creatorPrincipalId === capId);
28289
- return { response: { type: "session:list", sessions: filtered } };
28290
- }
28291
27148
  return { response: { type: "session:list", ...response } };
28292
27149
  };
28293
- const get = async (frame, _client, ctx) => {
27150
+ const get = async (frame) => {
28294
27151
  const args = SessionIdArgs.parse(frame);
28295
- ensureSessionAccess(ctx, args.sessionId, "read");
28296
27152
  const { response } = manager.get(args);
28297
27153
  return { response: { type: "session:info", ...response } };
28298
27154
  };
28299
- const update = async (frame, _client, ctx) => {
27155
+ const update = async (frame) => {
28300
27156
  const args = SessionUpdateArgs.parse(frame);
28301
- ensureSessionAccess(ctx, args.sessionId, "send");
28302
27157
  const { response, broadcast } = manager.update(args);
28303
27158
  return { response: { type: "session:info", ...response }, broadcast };
28304
27159
  };
28305
- const del = async (frame, _client, ctx) => {
27160
+ const del = async (frame) => {
28306
27161
  const args = SessionIdArgs.parse(frame);
28307
- ensureSessionAccess(ctx, args.sessionId, "send");
28308
27162
  const { broadcast } = manager.delete(args);
28309
27163
  return {
28310
27164
  response: { type: "session:deleted", sessionId: args.sessionId },
28311
27165
  broadcast
28312
27166
  };
28313
27167
  };
28314
- const send = async (frame, _client, ctx) => {
27168
+ const send = async (frame) => {
28315
27169
  const args = SessionSendArgs.parse(frame);
28316
- ensureSessionAccess(ctx, args.sessionId, "send");
28317
27170
  const { broadcast } = manager.send(args);
28318
27171
  return { response: { type: "session:send", ok: true }, broadcast };
28319
27172
  };
28320
- const stop = async (frame, _client, ctx) => {
27173
+ const stop = async (frame) => {
28321
27174
  const args = SessionIdArgs.parse(frame);
28322
- ensureSessionAccess(ctx, args.sessionId, "send");
28323
27175
  const { broadcast } = await manager.stop(args);
28324
27176
  return { response: { type: "session:stop", ok: true }, broadcast };
28325
27177
  };
28326
- const interrupt = async (frame, _client, ctx) => {
27178
+ const interrupt = async (frame) => {
28327
27179
  const args = SessionIdArgs.parse(frame);
28328
- ensureSessionAccess(ctx, args.sessionId, "send");
28329
27180
  const { broadcast } = await manager.interrupt(args);
28330
27181
  return { response: { type: "session:interrupt", ok: true }, broadcast };
28331
27182
  };
28332
- const rewind = async (frame, _client, ctx) => {
27183
+ const rewind = async (frame) => {
28333
27184
  const args = SessionRewindArgs.parse(frame);
28334
- ensureSessionAccess(ctx, args.sessionId, "send");
28335
27185
  const { response, broadcast } = await manager.rewind(args);
28336
27186
  return { response: { type: "session:rewind", ...response }, broadcast };
28337
27187
  };
28338
- const rewindDiff = async (frame, _client, ctx) => {
27188
+ const rewindDiff = async (frame) => {
28339
27189
  const args = SessionRewindDiffArgs.parse(frame);
28340
- ensureSessionAccess(ctx, args.sessionId, "read");
28341
27190
  const { response } = await manager.rewindDiff(args);
28342
27191
  return { response: { type: "session:rewind-diff", ...response } };
28343
27192
  };
28344
- const rewindableMessageIds = async (frame, _client, ctx) => {
27193
+ const rewindableMessageIds = async (frame) => {
28345
27194
  const args = SessionRewindableMessageIdsArgs.parse(frame);
28346
- ensureSessionAccess(ctx, args.sessionId, "read");
28347
27195
  const { response } = manager.rewindableMessageIds(args);
28348
27196
  return {
28349
27197
  response: { type: "session:rewindable-message-ids", ...response }
@@ -28354,22 +27202,19 @@ function buildSessionHandlers(deps) {
28354
27202
  const result = forkSession(args);
28355
27203
  return { response: { type: "session:fork", ...result } };
28356
27204
  };
28357
- const newSession = async (frame, _client, ctx) => {
27205
+ const newSession = async (frame) => {
28358
27206
  const args = SessionIdArgs.parse(frame);
28359
- ensureSessionAccess(ctx, args.sessionId, "send");
28360
27207
  observer.stop(args.sessionId);
28361
27208
  const { response, broadcast } = manager.newSession(args);
28362
27209
  return { response: { type: "session:info", ...response }, broadcast };
28363
27210
  };
28364
- const resume = async (frame, _client, ctx) => {
27211
+ const resume = async (frame) => {
28365
27212
  const args = SessionResumeArgs.parse(frame);
28366
- ensureSessionAccess(ctx, args.sessionId, "send");
28367
27213
  const { response, broadcast } = manager.resume(args);
28368
27214
  return { response: { type: "session:info", ...response }, broadcast };
28369
27215
  };
28370
- const observe = async (frame, _client, ctx) => {
27216
+ const observe = async (frame) => {
28371
27217
  const args = SessionObserveArgs.parse(frame);
28372
- ensureSessionAccess(ctx, args.sessionId, "read");
28373
27218
  const { response: file } = manager.get({ sessionId: args.sessionId });
28374
27219
  const sessionFile = file;
28375
27220
  manager.ensureSession(sessionFile);
@@ -28383,17 +27228,14 @@ function buildSessionHandlers(deps) {
28383
27228
  });
28384
27229
  return { response: { type: "session:observe", ok: true } };
28385
27230
  };
28386
- const events = async (frame, _client, ctx) => {
27231
+ const events = async (frame) => {
28387
27232
  const args = SessionEventsArgs.parse(frame);
28388
- ensureSessionAccess(ctx, args.sessionId, "read");
28389
27233
  const { response } = manager.getEvents(args);
28390
27234
  return { response: { type: "session:events", ...response } };
28391
27235
  };
28392
- const subscribe = async (frame, client, ctx) => {
28393
- if (typeof frame.sessionId === "string") {
28394
- ensureSessionAccess(ctx, frame.sessionId, "read");
27236
+ const subscribe = async (frame, client) => {
27237
+ if (typeof frame.sessionId === "string")
28395
27238
  addSubscription(client, frame.sessionId);
28396
- }
28397
27239
  return {
28398
27240
  response: { type: "subscribed", sessionId: frame.sessionId }
28399
27241
  };
@@ -28405,9 +27247,8 @@ function buildSessionHandlers(deps) {
28405
27247
  response: { type: "unsubscribed", sessionId: frame.sessionId }
28406
27248
  };
28407
27249
  };
28408
- const pin = async (frame, _client, ctx) => {
27250
+ const pin = async (frame) => {
28409
27251
  const args = SessionPinArgs.parse(frame);
28410
- ensureSessionAccess(ctx, args.sessionId, "send");
28411
27252
  const { response, broadcast } = manager.pin(args);
28412
27253
  return { response: { type: "session:info", ...response }, broadcast };
28413
27254
  };
@@ -28416,15 +27257,13 @@ function buildSessionHandlers(deps) {
28416
27257
  const { response, broadcast } = manager.reorderPins(args);
28417
27258
  return { response: { type: "session:reorderPins", ...response }, broadcast };
28418
27259
  };
28419
- const answerQuestion = async (frame, _client, ctx) => {
27260
+ const answerQuestion = async (frame) => {
28420
27261
  const args = AnswerQuestionArgs.parse(frame);
28421
- ensureSessionAccess(ctx, args.sessionId, "send");
28422
27262
  const { response, broadcast } = manager.answerQuestion(args);
28423
27263
  return { response: { type: "session:answerQuestion", ...response }, broadcast };
28424
27264
  };
28425
- const cancelQuestion = async (frame, _client, ctx) => {
27265
+ const cancelQuestion = async (frame) => {
28426
27266
  const args = CancelQuestionArgs.parse(frame);
28427
- ensureSessionAccess(ctx, args.sessionId, "send");
28428
27267
  const { response, broadcast } = await manager.cancelQuestion(args);
28429
27268
  return { response: { type: "session:cancelQuestion", ...response }, broadcast };
28430
27269
  };
@@ -28519,7 +27358,6 @@ function listRecentDirs(store, limit = 50) {
28519
27358
  }
28520
27359
 
28521
27360
  // src/handlers/history.ts
28522
- init_protocol();
28523
27361
  function buildHistoryHandlers(deps) {
28524
27362
  const { manager, history, store } = deps;
28525
27363
  const projects = async () => {
@@ -28531,27 +27369,17 @@ function buildHistoryHandlers(deps) {
28531
27369
  const sessions = await history.listSessions(args);
28532
27370
  return { response: { type: "history:list", sessions } };
28533
27371
  };
28534
- const read = async (frame, _client, ctx) => {
27372
+ const read = async (frame) => {
28535
27373
  const args = HistoryReadArgs.parse(frame);
28536
27374
  const { response: file } = manager.get({ sessionId: args.sessionId });
28537
- const f = file;
28538
- if (ctx && ctx.principal.kind === "guest") {
28539
- const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.capabilityId;
28540
- if (!ok) {
28541
- throw new ClawdError(
28542
- ERROR_CODES.UNAUTHORIZED,
28543
- `principal guest:${ctx.principal.id} cannot read history of session ${args.sessionId}`
28544
- );
28545
- }
28546
- }
28547
- if (!f.toolSessionId) {
27375
+ if (!file.toolSessionId) {
28548
27376
  return {
28549
27377
  response: { type: "history:read", messages: [], total: 0, offset: 0 }
28550
27378
  };
28551
27379
  }
28552
27380
  const res = await history.read({
28553
- cwd: f.cwd,
28554
- toolSessionId: f.toolSessionId,
27381
+ cwd: file.cwd,
27382
+ toolSessionId: file.toolSessionId,
28555
27383
  limit: args.limit,
28556
27384
  offset: args.offset
28557
27385
  });
@@ -28733,345 +27561,6 @@ function buildCapabilitiesHandlers(deps) {
28733
27561
  };
28734
27562
  }
28735
27563
 
28736
- // src/handlers/capability.ts
28737
- init_zod();
28738
- init_protocol();
28739
- var DeleteArgsSchema = external_exports.object({
28740
- capabilityId: external_exports.string().min(1)
28741
- }).strict();
28742
- function buildCapabilityHandlers(deps) {
28743
- const { manager, getShareBaseUrl } = deps;
28744
- const issue = async (frame) => {
28745
- const { type: _type, requestId: _requestId, ...rest } = frame;
28746
- const args = CapabilityIssueArgsSchema.parse(rest);
28747
- const { token, capability } = manager.issue({
28748
- displayName: args.displayName,
28749
- grants: args.grants,
28750
- ...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
28751
- ...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
28752
- });
28753
- const base = getShareBaseUrl().replace(/\/$/, "");
28754
- const shareUrl = `${base}/?token=${encodeURIComponent(token)}`;
28755
- return {
28756
- response: {
28757
- type: "capability:issued",
28758
- token,
28759
- shareUrl,
28760
- capability: stripSecretHash(capability)
28761
- }
28762
- };
28763
- };
28764
- const list = async () => {
28765
- return {
28766
- response: {
28767
- type: "capability:list",
28768
- capabilities: manager.list().map(stripSecretHash)
28769
- }
28770
- };
28771
- };
28772
- const del = async (frame) => {
28773
- const { type: _type, requestId: _requestId, ...rest } = frame;
28774
- const args = DeleteArgsSchema.parse(rest);
28775
- const result = manager.delete(args.capabilityId);
28776
- if (!result) {
28777
- throw new ClawdError(
28778
- ERROR_CODES.VALIDATION_ERROR,
28779
- `capability not found: ${args.capabilityId}`
28780
- );
28781
- }
28782
- return {
28783
- response: {
28784
- type: "capability:deleted",
28785
- capabilityId: args.capabilityId
28786
- }
28787
- };
28788
- };
28789
- const bindPeer = async (frame) => {
28790
- const { type: _type, requestId: _requestId, ...rest } = frame;
28791
- const args = CapabilityBindPeerArgsSchema.parse(rest);
28792
- try {
28793
- manager.recordPeerHello(args.capabilityId, {
28794
- ownerPrincipalId: args.peerOwnerId,
28795
- displayName: args.peerDisplayName,
28796
- now: Date.now()
28797
- });
28798
- } catch (e) {
28799
- throw new ClawdError(
28800
- ERROR_CODES.VALIDATION_ERROR,
28801
- `capability:bindPeer: ${e.message}`
28802
- );
28803
- }
28804
- return {
28805
- response: { type: "capability:bindPeer:ok", capabilityId: args.capabilityId }
28806
- };
28807
- };
28808
- return {
28809
- "capability:issue": issue,
28810
- "capability:list": list,
28811
- "capability:delete": del,
28812
- "capability:bindPeer": bindPeer
28813
- };
28814
- }
28815
-
28816
- // src/handlers/inbox.ts
28817
- init_protocol();
28818
- function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
28819
- if (ctx.principal.kind === "owner") {
28820
- if (!argsPeerOwnerId) {
28821
- throw new ClawdError(
28822
- ERROR_CODES.VALIDATION_ERROR,
28823
- "inbox: owner ctx requires peerOwnerId"
28824
- );
28825
- }
28826
- return argsPeerOwnerId;
28827
- }
28828
- if (!ctx.capabilityId) {
28829
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
28830
- }
28831
- const cap = capManager.findById(ctx.capabilityId);
28832
- if (!cap?.peerOwnerId) {
28833
- throw new ClawdError(
28834
- ERROR_CODES.UNAUTHORIZED,
28835
- `inbox: guest cap ${ctx.capabilityId} has no peerOwnerId (recordPeerHello not yet)`
28836
- );
28837
- }
28838
- if (argsPeerOwnerId && argsPeerOwnerId !== cap.peerOwnerId) {
28839
- throw new ClawdError(
28840
- ERROR_CODES.UNAUTHORIZED,
28841
- `inbox: guest args.peerOwnerId mismatch (cap.peerOwnerId=${cap.peerOwnerId}, args=${argsPeerOwnerId})`
28842
- );
28843
- }
28844
- return cap.peerOwnerId;
28845
- }
28846
- function buildInboxHandlers(deps) {
28847
- const { manager, capManager } = deps;
28848
- const postMessage = async (frame, _client, ctx) => {
28849
- const { type: _t, requestId: _r, ...rest } = frame;
28850
- const args = InboxPostMessageArgsSchema.parse(rest);
28851
- if (!ctx) {
28852
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
28853
- }
28854
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28855
- const message = manager.postMessage({
28856
- peerOwnerId,
28857
- senderPrincipalId: ctx.principal.id,
28858
- text: args.text,
28859
- id: args.id,
28860
- createdAt: args.createdAt
28861
- });
28862
- return {
28863
- response: { type: "inbox:postMessage:ok", message }
28864
- };
28865
- };
28866
- const list = async (frame, _client, ctx) => {
28867
- const { type: _t, requestId: _r, ...rest } = frame;
28868
- const args = InboxListArgsSchema.parse(rest);
28869
- if (!ctx) {
28870
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
28871
- }
28872
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28873
- const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
28874
- return {
28875
- response: { type: "inbox:list:ok", peerOwnerId, messages }
28876
- };
28877
- };
28878
- const markRead = async (frame, _client, ctx) => {
28879
- const { type: _t, requestId: _r, ...rest } = frame;
28880
- const args = InboxMarkReadArgsSchema.parse(rest);
28881
- if (!ctx) {
28882
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
28883
- }
28884
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28885
- const updated = manager.markRead({
28886
- peerOwnerId,
28887
- principalId: ctx.principal.id,
28888
- upToCreatedAt: args.upToCreatedAt
28889
- });
28890
- return {
28891
- response: {
28892
- type: "inbox:markRead:ok",
28893
- peerOwnerId,
28894
- upToCreatedAt: args.upToCreatedAt,
28895
- updated
28896
- }
28897
- };
28898
- };
28899
- return {
28900
- "inbox:postMessage": postMessage,
28901
- "inbox:list": list,
28902
- "inbox:markRead": markRead
28903
- };
28904
- }
28905
-
28906
- // src/handlers/received-capability.ts
28907
- init_protocol();
28908
- function ensureOwner(ctx) {
28909
- if (!ctx || ctx.principal.kind !== "owner") {
28910
- throw new ClawdError(
28911
- ERROR_CODES.UNAUTHORIZED,
28912
- "UNAUTHORIZED: received-capability:* requires owner ctx"
28913
- );
28914
- }
28915
- }
28916
- function ensureGuestCtx(ctx) {
28917
- if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
28918
- throw new ClawdError(
28919
- ERROR_CODES.UNAUTHORIZED,
28920
- "UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
28921
- );
28922
- }
28923
- }
28924
- function buildReceivedCapabilityHandlers(deps) {
28925
- const now = deps.now ?? Date.now;
28926
- const list = async (_frame, _client, ctx) => {
28927
- ensureOwner(ctx);
28928
- return {
28929
- response: {
28930
- type: "received-capability:list:ok",
28931
- receivedCaps: deps.store.list()
28932
- }
28933
- };
28934
- };
28935
- const add = async (frame, _client, ctx) => {
28936
- ensureOwner(ctx);
28937
- const { type: _t, requestId: _r, ...rest } = frame;
28938
- const args = ReceivedCapabilityAddArgsSchema.parse(rest);
28939
- let conn;
28940
- try {
28941
- conn = await deps.connectRemote({
28942
- url: args.remoteUrl,
28943
- token: args.token,
28944
- selfPrincipalId: deps.selfPrincipalId(),
28945
- selfDisplayName: deps.selfDisplayName()
28946
- });
28947
- } catch (e) {
28948
- throw new ClawdError(
28949
- ERROR_CODES.VALIDATION_ERROR,
28950
- `INVITE_REMOTE_UNREACHABLE: ${e.message}`
28951
- );
28952
- }
28953
- try {
28954
- const wh = await conn.whoami();
28955
- const rc = {
28956
- ownerPrincipalId: wh.ownerId,
28957
- ownerDisplayName: wh.displayName,
28958
- remoteUrl: args.remoteUrl,
28959
- capabilityId: wh.capabilityId,
28960
- capabilityToken: args.token,
28961
- grants: wh.grants,
28962
- receivedAt: now()
28963
- };
28964
- deps.store.upsert(rc);
28965
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
28966
- return {
28967
- response: { type: "received-capability:add:ok", receivedCap: rc }
28968
- };
28969
- } finally {
28970
- try {
28971
- conn.close();
28972
- } catch {
28973
- }
28974
- }
28975
- };
28976
- const remove = async (frame, _client, ctx) => {
28977
- ensureOwner(ctx);
28978
- const { type: _t, requestId: _r, ...rest } = frame;
28979
- const args = ReceivedCapabilityRemoveArgsSchema.parse(rest);
28980
- deps.store.remove(args.ownerPrincipalId);
28981
- deps.broadcast({
28982
- type: "received-capability:removed",
28983
- ownerPrincipalId: args.ownerPrincipalId
28984
- });
28985
- return {
28986
- response: {
28987
- type: "received-capability:remove:ok",
28988
- ownerPrincipalId: args.ownerPrincipalId
28989
- }
28990
- };
28991
- };
28992
- const autoAttach = async (frame, _client, ctx) => {
28993
- ensureGuestCtx(ctx);
28994
- const { type: _t, requestId: _r, ...rest } = frame;
28995
- const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
28996
- const rc = {
28997
- ownerPrincipalId: args.ownerPrincipalId,
28998
- ownerDisplayName: args.ownerDisplayName,
28999
- remoteUrl: args.remoteUrl,
29000
- capabilityId: args.capabilityId,
29001
- capabilityToken: args.token,
29002
- grants: args.grants,
29003
- receivedAt: now()
29004
- };
29005
- deps.store.upsert(rc);
29006
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
29007
- return {
29008
- response: { type: "received-capability:autoAttach:ok" }
29009
- };
29010
- };
29011
- return {
29012
- "received-capability:list": list,
29013
- "received-capability:add": add,
29014
- "received-capability:remove": remove,
29015
- "received-capability:autoAttach": autoAttach
29016
- };
29017
- }
29018
-
29019
- // src/handlers/whoami.ts
29020
- init_protocol();
29021
- function buildWhoamiHandler(deps) {
29022
- return async (_frame, _client, ctx) => {
29023
- if (!ctx) {
29024
- throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
29025
- }
29026
- const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
29027
- let capability;
29028
- if (ctx.principal.kind === "owner") {
29029
- capability = {
29030
- id: deps.ownerPrincipalId,
29031
- displayName: deps.ownerDisplayName,
29032
- grants: ctx.grants,
29033
- issuedAt: 0,
29034
- usedCount: 0
29035
- };
29036
- } else {
29037
- if (!ctx.capabilityId) {
29038
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
29039
- }
29040
- const cap = deps.capabilityManager.findById(ctx.capabilityId);
29041
- if (!cap) {
29042
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
29043
- }
29044
- capability = stripSecretHash(cap);
29045
- }
29046
- const grantedPersonas = [];
29047
- const isGuest = ctx.principal.kind === "guest";
29048
- const hasWildcard = capability.grants.some((g2) => g2.resource.type === "*");
29049
- if (hasWildcard) {
29050
- for (const id of deps.personaStore.list()) {
29051
- const file = deps.personaStore.read(id);
29052
- if (!file) continue;
29053
- if (isGuest && !file.public) continue;
29054
- grantedPersonas.push({ id: file.personaId, displayName: file.label });
29055
- }
29056
- } else {
29057
- for (const g2 of capability.grants) {
29058
- if (g2.resource.type !== "persona") continue;
29059
- const file = deps.personaStore.read(g2.resource.id);
29060
- if (!file) continue;
29061
- grantedPersonas.push({ id: file.personaId, displayName: file.label });
29062
- }
29063
- }
29064
- return {
29065
- response: {
29066
- type: "whoami:ok",
29067
- owner,
29068
- capability,
29069
- grantedPersonas
29070
- }
29071
- };
29072
- };
29073
- }
29074
-
29075
27564
  // src/handlers/meta.ts
29076
27565
  var import_node_os13 = __toESM(require("os"), 1);
29077
27566
  init_protocol();
@@ -29135,14 +27624,9 @@ function buildPersonaHandlers(deps) {
29135
27624
  const persona = personaManager.create(args);
29136
27625
  return { response: { type: "persona:info", ...persona } };
29137
27626
  };
29138
- const list = async (_frame, _client, ctx) => {
29139
- const all = personaRegistry.list();
29140
- const isGuest = ctx?.principal.kind === "guest";
29141
- const personas = isGuest ? all.filter(
29142
- (p2) => p2.public && assertGrant(ctx.grants, { type: "persona", id: p2.personaId }, "read")
29143
- ) : all;
27627
+ const list = async () => {
29144
27628
  return {
29145
- response: { type: "persona:list", personas }
27629
+ response: { type: "persona:list", personas: personaRegistry.list() }
29146
27630
  };
29147
27631
  };
29148
27632
  const get = async (frame) => {
@@ -29180,12 +27664,26 @@ function buildPersonaHandlers(deps) {
29180
27664
  response: { type: "persona:deleted", personaId: args.personaId }
29181
27665
  };
29182
27666
  };
27667
+ const issueToken = async (frame) => {
27668
+ const args = PersonaIssueTokenArgsSchema.parse(frame);
27669
+ const { token, persona } = personaManager.issueToken(args.personaId, args.label);
27670
+ return {
27671
+ response: { type: "persona:tokenIssued", token, persona }
27672
+ };
27673
+ };
27674
+ const revokeToken = async (frame) => {
27675
+ const args = PersonaRevokeTokenArgsSchema.parse(frame);
27676
+ const persona = personaManager.revokeToken(args.personaId, args.token);
27677
+ return { response: { type: "persona:info", ...persona } };
27678
+ };
29183
27679
  return {
29184
27680
  "persona:create": create,
29185
27681
  "persona:list": list,
29186
27682
  "persona:get": get,
29187
27683
  "persona:update": update,
29188
- "persona:delete": del
27684
+ "persona:delete": del,
27685
+ "persona:issueToken": issueToken,
27686
+ "persona:revokeToken": revokeToken
29189
27687
  };
29190
27688
  }
29191
27689
 
@@ -29316,11 +27814,23 @@ function buildAttachmentHandlers(deps) {
29316
27814
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29317
27815
  return { response: { type: "attachment.groupList", entries } };
29318
27816
  };
27817
+ const groupListPersona = async (frame) => {
27818
+ if (!deps.groupFileStore) {
27819
+ throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
27820
+ }
27821
+ const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
27822
+ if (!parsed.success) {
27823
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
27824
+ }
27825
+ const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
27826
+ return { response: { type: "attachment.groupListPersona", perSession } };
27827
+ };
29319
27828
  return {
29320
27829
  "attachment.signUrl": signUrl,
29321
27830
  "attachment.groupAdd": groupAdd,
29322
27831
  "attachment.groupRemove": groupRemove,
29323
- "attachment.groupList": groupList
27832
+ "attachment.groupList": groupList,
27833
+ "attachment.groupListPersona": groupListPersona
29324
27834
  };
29325
27835
  }
29326
27836
 
@@ -29338,136 +27848,10 @@ function buildMethodHandlers(deps) {
29338
27848
  personaManager: deps.personaManager,
29339
27849
  personaRegistry: deps.personaRegistry
29340
27850
  }),
29341
- ...buildCapabilityHandlers({
29342
- manager: deps.capabilityManager,
29343
- getShareBaseUrl: deps.getShareBaseUrl
29344
- }),
29345
- ...buildInboxHandlers({
29346
- manager: deps.inboxManager,
29347
- capManager: deps.capabilityManager
29348
- }),
29349
- ...buildReceivedCapabilityHandlers({
29350
- store: deps.receivedCapabilityStore,
29351
- connectRemote: deps.connectRemote,
29352
- broadcast: deps.broadcastToOwners,
29353
- selfPrincipalId: () => deps.ownerPrincipalId,
29354
- selfDisplayName: () => deps.ownerDisplayName
29355
- }),
29356
- whoami: buildWhoamiHandler({
29357
- ownerDisplayName: deps.ownerDisplayName,
29358
- ownerPrincipalId: deps.ownerPrincipalId,
29359
- personaStore: deps.personaStore,
29360
- capabilityManager: deps.capabilityManager
29361
- }),
29362
27851
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
29363
27852
  };
29364
27853
  }
29365
27854
 
29366
- // src/handlers/method-grants.ts
29367
- var ADMIN_ANY = {
29368
- kind: "fixed",
29369
- resource: { type: "*" },
29370
- action: "admin"
29371
- };
29372
- var CAPABILITY_SCOPED = { kind: "capability-scoped" };
29373
- var METHOD_GRANT_MAP = {
29374
- // ---- public(meta-only,guest 也能调) ----
29375
- "info": { kind: "public" },
29376
- "ping": { kind: "public" },
29377
- // v2 Phase 6: whoami 是身份反查,任何 authed connection (owner / guest) 都能调;
29378
- // handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
29379
- // "查 capabilityManager 拿 caller 的 guest capability"。
29380
- "whoami": { kind: "public" },
29381
- // ---- capability platform(admin-only,本 PR 新增) ----
29382
- "capability:issue": ADMIN_ANY,
29383
- "capability:list": ADMIN_ANY,
29384
- "capability:delete": ADMIN_ANY,
29385
- "capability:bindPeer": 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" },
29391
- "inbox:postMessage": { kind: "public" },
29392
- // ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
29393
- // owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
29394
- "received-capability:list": ADMIN_ANY,
29395
- "received-capability:add": ADMIN_ANY,
29396
- "received-capability:remove": ADMIN_ANY,
29397
- "received-capability:autoAttach": { kind: "public" },
29398
- // ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
29399
- // dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
29400
- // owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
29401
- "session:create": CAPABILITY_SCOPED,
29402
- "session:list": CAPABILITY_SCOPED,
29403
- "session:get": CAPABILITY_SCOPED,
29404
- "session:update": CAPABILITY_SCOPED,
29405
- "session:delete": CAPABILITY_SCOPED,
29406
- "session:send": CAPABILITY_SCOPED,
29407
- "session:stop": CAPABILITY_SCOPED,
29408
- "session:interrupt": CAPABILITY_SCOPED,
29409
- "session:rewind": CAPABILITY_SCOPED,
29410
- "session:rewind-diff": CAPABILITY_SCOPED,
29411
- "session:rewindable-message-ids": CAPABILITY_SCOPED,
29412
- "session:fork": CAPABILITY_SCOPED,
29413
- "session:new": CAPABILITY_SCOPED,
29414
- "session:resume": CAPABILITY_SCOPED,
29415
- "session:observe": CAPABILITY_SCOPED,
29416
- "session:events": CAPABILITY_SCOPED,
29417
- "session:subscribe": CAPABILITY_SCOPED,
29418
- "session:unsubscribe": CAPABILITY_SCOPED,
29419
- "session:pin": CAPABILITY_SCOPED,
29420
- "session:reorderPins": ADMIN_ANY,
29421
- // owner 全局操作,无 personaId 维度
29422
- "permission:respond": CAPABILITY_SCOPED,
29423
- "session:answerQuestion": CAPABILITY_SCOPED,
29424
- "session:cancelQuestion": CAPABILITY_SCOPED,
29425
- "history:projects": ADMIN_ANY,
29426
- "history:list": ADMIN_ANY,
29427
- // history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
29428
- // 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
29429
- // handler 内按 ctx.capabilityId === file.creatorPrincipalId 拦越权。
29430
- "history:read": CAPABILITY_SCOPED,
29431
- "history:subagents": ADMIN_ANY,
29432
- "history:subagent-read": ADMIN_ANY,
29433
- "history:recentDirs": ADMIN_ANY,
29434
- "workspace:list": ADMIN_ANY,
29435
- "workspace:read": ADMIN_ANY,
29436
- "skills:list": ADMIN_ANY,
29437
- "agents:list": ADMIN_ANY,
29438
- "git:root": ADMIN_ANY,
29439
- "git:branch": ADMIN_ANY,
29440
- "git:branches": ADMIN_ANY,
29441
- "capabilities:get": ADMIN_ANY,
29442
- "persona:create": ADMIN_ANY,
29443
- // persona:list 下沉到 CAPABILITY_SCOPED:guest 调用合法(用于 People 面板展开
29444
- // 对方分享的 personas),handler 端按 ctx + persona.public + assertGrant 过滤。
29445
- "persona:list": CAPABILITY_SCOPED,
29446
- "persona:get": ADMIN_ANY,
29447
- "persona:update": ADMIN_ANY,
29448
- "persona:delete": ADMIN_ANY,
29449
- "session:pty:input": ADMIN_ANY,
29450
- "session:pty:resize": ADMIN_ANY,
29451
- // file-sharing attachment.* RPC:dispatcher 用 admin-only 兜底(wire-level 拦),
29452
- // 实际只有 owner 能调(personal token 链路 2026-05-21 删除,HTTP Bearer 只识别 owner)
29453
- "attachment.signUrl": ADMIN_ANY,
29454
- "attachment.groupAdd": ADMIN_ANY,
29455
- "attachment.groupRemove": ADMIN_ANY,
29456
- "attachment.groupList": ADMIN_ANY
29457
- };
29458
- function computeGrantForFrame(method, frame) {
29459
- const rule = METHOD_GRANT_MAP[method];
29460
- if (!rule) return { kind: "public" };
29461
- if (rule.kind === "public") return { kind: "public" };
29462
- if (rule.kind === "capability-scoped") return { kind: "public" };
29463
- if (rule.kind === "fixed") {
29464
- return { kind: "check", resource: rule.resource, action: rule.action };
29465
- }
29466
- const picked = rule.pick(frame);
29467
- if (!picked) return { kind: "public" };
29468
- return { kind: "check", resource: picked.resource, action: picked.action };
29469
- }
29470
-
29471
27855
  // src/index.ts
29472
27856
  async function startDaemon(config) {
29473
27857
  const logger = createLogger({
@@ -29483,90 +27867,24 @@ async function startDaemon(config) {
29483
27867
  if (pre.status === "stale") {
29484
27868
  logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
29485
27869
  }
29486
- const authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
29487
27870
  let resolvedAuthToken = null;
27871
+ let authFile = null;
29488
27872
  if (config.authToken && config.authToken.trim()) {
29489
27873
  resolvedAuthToken = config.authToken.trim();
29490
27874
  } else if (config.tunnel) {
27875
+ authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
29491
27876
  resolvedAuthToken = authFile.token;
29492
27877
  }
29493
- const ownerPrincipalId = authFile.ownerPrincipalId;
29494
- const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
29495
27878
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
29496
- migrateLegacyFiles(config.dataDir);
29497
- const inboxStore = new InboxStore(config.dataDir);
29498
- const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
29499
- wsServer?.broadcastToOwners(frame);
29500
- });
29501
- const receivedCapabilityStore = new ReceivedCapabilityStore(config.dataDir);
29502
- receivedCapabilityStore.load();
29503
- const capabilityStore = new CapabilityStore(config.dataDir);
29504
- const capabilityRegistry = new CapabilityRegistry(capabilityStore);
29505
- const capabilityManager = new CapabilityManager(capabilityRegistry, {
29506
- onIssued: (cap, token) => {
29507
- wsServer?.broadcastToOwners({
29508
- type: "capability:tokenIssued",
29509
- capability: stripSecretHash(cap),
29510
- token
29511
- });
29512
- },
29513
- onDeleted: (cap) => {
29514
- const deletedAt = Date.now();
29515
- wsServer?.broadcastToOwners({
29516
- type: "capability:tokenDeleted",
29517
- capabilityId: cap.id,
29518
- deletedAt
29519
- });
29520
- wsServer?.closeConnectionsByCapability(cap.id);
29521
- const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
29522
- if (cleanup.removed.length > 0) {
29523
- logger.info("capability delete cascade: guest sessions removed", {
29524
- capabilityId: cap.id,
29525
- removedDirs: cleanup.removed
29526
- });
29527
- }
29528
- }
29529
- });
29530
- migrateDropPersonsDir(config.dataDir);
29531
27879
  let wsServer = null;
29532
27880
  const authGate = authMode === "first-message" ? new AuthGate({
29533
27881
  shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
29534
- // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
29535
- // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
29536
27882
  expectedToken: resolvedAuthToken,
29537
- authenticate: (t, selfPrincipalId, selfDisplayName) => {
29538
- return authenticate(t, {
29539
- isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
29540
- ownerPrincipalId,
29541
- ownerDisplayName,
29542
- capabilityRegistry,
29543
- selfPrincipalId,
29544
- selfDisplayName,
29545
- onGuestHello: (capId, ownerId, name) => {
29546
- capabilityManager.recordPeerHello(capId, {
29547
- ownerPrincipalId: ownerId,
29548
- displayName: name,
29549
- now: Date.now()
29550
- });
29551
- }
29552
- });
29553
- },
29554
- onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
29555
- buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
29556
27883
  closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
29557
27884
  sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
29558
27885
  }) : null;
29559
27886
  resetRegistry();
29560
- const migrateResult = migrateFlattenSessions({ dataDir: config.dataDir });
29561
- if (!migrateResult.skipped && (migrateResult.movedBare || migrateResult.movedVmOwner || migrateResult.archivedListener)) {
29562
- logger.info("sessions migration applied", {
29563
- movedBare: migrateResult.movedBare,
29564
- movedVmOwner: migrateResult.movedVmOwner,
29565
- archivedListener: migrateResult.archivedListener
29566
- });
29567
- }
29568
- const sessionStoreFactory = new SessionStoreFactory({ dataDir: config.dataDir });
29569
- const store = sessionStoreFactory.forBare();
27887
+ const store = new SessionStore({ dataDir: config.dataDir });
29570
27888
  const workspace = new WorkspaceBrowser();
29571
27889
  const skills = new SkillsScanner();
29572
27890
  const agents = new AgentsScanner();
@@ -29579,12 +27897,10 @@ async function startDaemon(config) {
29579
27897
  } else {
29580
27898
  logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
29581
27899
  }
27900
+ const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
29582
27901
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
29583
27902
  const manager = new SessionManager({
29584
27903
  store,
29585
- // Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
29586
- // 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
29587
- storeFactory: sessionStoreFactory,
29588
27904
  logger,
29589
27905
  getAdapter,
29590
27906
  historyReader: history,
@@ -29592,7 +27908,6 @@ async function startDaemon(config) {
29592
27908
  personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
29593
27909
  personaStore,
29594
27910
  ownerDisplayName,
29595
- ownerPrincipalId,
29596
27911
  mode: config.mode,
29597
27912
  broadcastFrame: (frame, target) => {
29598
27913
  if (target === "all") {
@@ -29728,37 +28043,18 @@ async function startDaemon(config) {
29728
28043
  // group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
29729
28044
  // 'persona/<pid>/owner',default 走 'default'。
29730
28045
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid)
29731
- },
29732
- // Task 1.9: capability:issue/list/revoke handler 依赖
29733
- capabilityManager,
29734
- // v2 Phase 5: capability:issue 返回的 shareUrl 用此 base URL 拼接。
29735
- // tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
29736
- getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
29737
- // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
29738
- ownerDisplayName,
29739
- // owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
29740
- ownerPrincipalId,
29741
- personaStore,
29742
- // capability handler 也用 (capability:issue 走 registry / capability:list)
29743
- capabilityRegistry,
29744
- // capability platform v3 inbox: handler 接 manager (post/list/markRead),
29745
- // cascade 接 store (list 统计 deletedInboxEvents).
29746
- inboxManager,
29747
- inboxStore,
29748
- // bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
29749
- receivedCapabilityStore,
29750
- // received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
29751
- broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
29752
- // received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
29753
- connectRemote
28046
+ }
29754
28047
  });
29755
28048
  const authResolver = new AuthContextResolver({
29756
- ownerToken: resolvedAuthToken
28049
+ ownerToken: resolvedAuthToken,
28050
+ personaRegistry
29757
28051
  });
29758
28052
  const httpRouter = createHttpRouter({
29759
28053
  authResolver,
29760
28054
  daemonVersion: version,
29761
28055
  logger,
28056
+ personaStore,
28057
+ groupFileStore,
29762
28058
  sessionStore: store,
29763
28059
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
29764
28060
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
@@ -29768,8 +28064,6 @@ async function startDaemon(config) {
29768
28064
  host: config.host,
29769
28065
  port: config.port,
29770
28066
  logger,
29771
- // broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
29772
- ownerPrincipalId,
29773
28067
  readyFrameBuilder: (ctx) => buildReadyFrame(
29774
28068
  {
29775
28069
  manager,
@@ -29785,29 +28079,6 @@ async function startDaemon(config) {
29785
28079
  ),
29786
28080
  protocolVersion: PROTOCOL_VERSION,
29787
28081
  authGate: authGate ?? void 0,
29788
- // noAuth 模式下仍验 capability token: 修 capability platform 漏洞 — 远端 client
29789
- // 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
29790
- // capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
29791
- // 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
29792
- tryVerifyCapabilityToken: (token, selfPrincipalId, selfDisplayName) => {
29793
- const v2 = capabilityRegistry.verifyToken(token);
29794
- if (!v2.ok) return null;
29795
- if (selfPrincipalId && selfDisplayName) {
29796
- try {
29797
- capabilityManager.recordPeerHello(v2.capability.id, {
29798
- ownerPrincipalId: selfPrincipalId,
29799
- displayName: selfDisplayName,
29800
- now: Date.now()
29801
- });
29802
- } catch {
29803
- }
29804
- }
29805
- return {
29806
- principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
29807
- grants: v2.capability.grants,
29808
- capabilityId: v2.capability.id
29809
- };
29810
- },
29811
28082
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
29812
28083
  httpRequestHandler: httpRouter,
29813
28084
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
@@ -29860,18 +28131,7 @@ async function startDaemon(config) {
29860
28131
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
29861
28132
  const handler = handlers[type];
29862
28133
  if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
29863
- const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
29864
- const verdict = computeGrantForFrame(type, frame);
29865
- if (verdict.kind === "check") {
29866
- const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
29867
- if (!ok) {
29868
- throw new ClawdError(
29869
- ERROR_CODES.UNAUTHORIZED,
29870
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
29871
- );
29872
- }
29873
- }
29874
- const result = await handler(frame, client, ctx);
28134
+ const result = await handler(frame, client);
29875
28135
  if (requestId && result.response) {
29876
28136
  client.send({ ...result.response, requestId });
29877
28137
  }
@@ -29994,13 +28254,6 @@ ${bar}
29994
28254
  manager
29995
28255
  };
29996
28256
  }
29997
- function migrateDropPersonsDir(dataDir) {
29998
- const dir = import_node_path27.default.join(dataDir, "persons");
29999
- try {
30000
- import_node_fs24.default.rmSync(dir, { recursive: true, force: true });
30001
- } catch {
30002
- }
30003
- }
30004
28257
 
30005
28258
  // src/cli.ts
30006
28259
  async function main() {