@clawos-dev/clawd 0.2.73-beta.141.0c5abdc → 0.2.73-beta.142.96910a0

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 +623 -2356
  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
  }
@@ -23200,18 +22829,28 @@ var SessionManager = class {
23200
22829
  this.runners.set(file.sessionId, r);
23201
22830
  return r;
23202
22831
  }
23203
- // ---------------- scope-explicit sub-session API ----------------
22832
+ // ---------------- persona / listener sub-session 专用 API ----------------
23204
22833
  //
23205
- // 区别于 owner / default sessionId 入口,这些 API 接 SessionScope 参数 ——
23206
- // 调用方(PersonaManager / observer 等)明确传 scope,由 storeFor 路由到正确目录:
23207
- // - {kind:'persona', personaId, mode:'guest', capId} → guests/<capId>/,meta={idleKill:true, sandbox}
23208
- // - {kind:'persona', personaId, mode:'owner'} → owner/,meta={idleKill:false}
22834
+ // PersonaManager SessionManager 之上的薄编排层,listener sub-session 的持久化
22835
+ // (SessionFile)仍由 SessionManager 持有。区别于普通 / owner session:
22836
+ // - SessionFile 落到 <dataDir>/sessions/<personaId>/listener/(拓扑分类)
22837
+ // - reducer state.subSessionMeta.idleKillEnabled = true(turn_end 自动 idle-kill)
22838
+ // - sessionId 由 PersonaManager 派生(personaId-<tokenHash>),不走自动 newSessionId
23209
22839
  //
23210
- // SubSessionMeta scope metaFromScope 派生,不进 SessionFile schema。
22840
+ // 这些 API SessionScope 参数(不再是单参 agentId)—— transport 始终明确传
22841
+ // {kind:'persona', personaId, mode:'listener'},与 owner / default 分类隔离。
23211
22842
  /** 按 scope 读取 SessionFile;不存在返回 null */
23212
22843
  readForScope(sessionId, scope) {
23213
22844
  return this.storeFor(scope).read(sessionId);
23214
22845
  }
22846
+ /**
22847
+ * 列出指定 persona 在指定 mode 下的所有 SessionFile。
22848
+ * persona:listSubSessions RPC 入口(mode='listener');
22849
+ * 路径下还未写过任何 session 时 list() 返回空数组(SessionStore.list 内部 ENOENT 处理)
22850
+ */
22851
+ listPersonaSessions(personaId, mode) {
22852
+ return this.storeFor({ kind: "persona", personaId, mode }).list();
22853
+ }
23215
22854
  /**
23216
22855
  * 创建 sub-session 的 SessionFile + 准备 runner(不 spawn)。
23217
22856
  * SubSessionMeta 不进 SessionFile schema,运行时由 scope → metaFromScope 派生。
@@ -23356,7 +22995,7 @@ var SessionManager = class {
23356
22995
  return { response: { ok: true }, broadcast };
23357
22996
  }
23358
22997
  /**
23359
- * 改 SessionFile.label(scope-explicit sub-session 重命名入口)。
22998
+ * 改 SessionFile.label(用于 listener-bound chat:rename RPC)。
23360
22999
  * 不存在抛 SESSION_NOT_FOUND。
23361
23000
  */
23362
23001
  renameForScope(args) {
@@ -23371,7 +23010,7 @@ var SessionManager = class {
23371
23010
  store.write({ ...file, label: args.label });
23372
23011
  }
23373
23012
  /**
23374
- * 删除 scope-explicit sub-session
23013
+ * 删除 listener-scope sub-session(用于 chat:delete RPC)。
23375
23014
  * - 若 runner 存在:走 reducer 'delete' command 收尾(SIGKILL + 文件 unlink + broadcast)
23376
23015
  * - 若 runner 不存在:直接删 scope store 文件 + 广播 session:deleted
23377
23016
  * 不存在抛 SESSION_NOT_FOUND。
@@ -23642,7 +23281,7 @@ var SessionManager = class {
23642
23281
 
23643
23282
  // src/persona/store.ts
23644
23283
  var fs6 = __toESM(require("fs"), 1);
23645
- var path8 = __toESM(require("path"), 1);
23284
+ var path7 = __toESM(require("path"), 1);
23646
23285
  init_protocol();
23647
23286
  var DEFAULT_SETTINGS = {
23648
23287
  permissions: {
@@ -23671,13 +23310,13 @@ var PersonaStore = class {
23671
23310
  }
23672
23311
  root;
23673
23312
  personaDir(personaId) {
23674
- return path8.join(this.root, safeFileName(personaId));
23313
+ return path7.join(this.root, safeFileName(personaId));
23675
23314
  }
23676
23315
  metaPath(personaId) {
23677
- return path8.join(this.personaDir(personaId), ".clawd", "persona.json");
23316
+ return path7.join(this.personaDir(personaId), ".clawd", "persona.json");
23678
23317
  }
23679
23318
  claudeMdPath(personaId) {
23680
- return path8.join(this.personaDir(personaId), "CLAUDE.md");
23319
+ return path7.join(this.personaDir(personaId), "CLAUDE.md");
23681
23320
  }
23682
23321
  /**
23683
23322
  * Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
@@ -23687,11 +23326,11 @@ var PersonaStore = class {
23687
23326
  * 加载 persona 人格,只有 listener 多一层 OS sandbox。
23688
23327
  */
23689
23328
  sandboxSettingsPath(personaId) {
23690
- return path8.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
23329
+ return path7.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
23691
23330
  }
23692
23331
  write(persona, personality) {
23693
23332
  const dir = this.personaDir(persona.personaId);
23694
- fs6.mkdirSync(path8.join(dir, ".clawd"), { recursive: true });
23333
+ fs6.mkdirSync(path7.join(dir, ".clawd"), { recursive: true });
23695
23334
  this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
23696
23335
  this.atomicWrite(
23697
23336
  this.sandboxSettingsPath(persona.personaId),
@@ -23709,10 +23348,6 @@ var PersonaStore = class {
23709
23348
  const p2 = this.metaPath(personaId);
23710
23349
  if (!fs6.existsSync(p2)) return null;
23711
23350
  const raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
23712
- if (raw && typeof raw === "object" && "tokenMap" in raw) {
23713
- delete raw.tokenMap;
23714
- this.atomicWrite(p2, JSON.stringify(raw, null, 2));
23715
- }
23716
23351
  return PersonaFileSchema.parse(raw);
23717
23352
  }
23718
23353
  has(personaId) {
@@ -23738,14 +23373,14 @@ var PersonaStore = class {
23738
23373
  }
23739
23374
  /** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
23740
23375
  skillsDir(personaId) {
23741
- return path8.join(this.personaDir(personaId), ".claude", "skills");
23376
+ return path7.join(this.personaDir(personaId), ".claude", "skills");
23742
23377
  }
23743
23378
  /**
23744
23379
  * Claude Code 项目级 settings 路径:`<personaDir>/.claude/settings.json`。
23745
23380
  * 这里只读 `enabledPlugins` 字段,由 owner 通过 CC `/plugin` 之类命令维护,daemon 不写。
23746
23381
  */
23747
23382
  claudeSettingsPath(personaId) {
23748
- return path8.join(this.personaDir(personaId), ".claude", "settings.json");
23383
+ return path7.join(this.personaDir(personaId), ".claude", "settings.json");
23749
23384
  }
23750
23385
  /**
23751
23386
  * 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
@@ -23779,7 +23414,7 @@ var PersonaStore = class {
23779
23414
  list() {
23780
23415
  if (!fs6.existsSync(this.root)) return [];
23781
23416
  return fs6.readdirSync(this.root).filter((name) => {
23782
- return fs6.existsSync(path8.join(this.root, name, ".clawd", "persona.json"));
23417
+ return fs6.existsSync(path7.join(this.root, name, ".clawd", "persona.json"));
23783
23418
  });
23784
23419
  }
23785
23420
  remove(personaId) {
@@ -23804,19 +23439,12 @@ var PersonaRegistry = class {
23804
23439
  }
23805
23440
  store;
23806
23441
  cache = /* @__PURE__ */ new Map();
23807
- /**
23808
- * 从 store 全量重建缓存(boot 时 + 测试 setup 时)。
23809
- * 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
23810
- * try/catch 隔离,让其它 persona 仍能加载。
23811
- */
23442
+ /** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
23812
23443
  reload() {
23813
23444
  this.cache.clear();
23814
23445
  for (const id of this.store.list()) {
23815
- try {
23816
- const p2 = this.store.read(id);
23817
- if (p2) this.cache.set(p2.personaId, p2);
23818
- } catch {
23819
- }
23446
+ const p2 = this.store.read(id);
23447
+ if (p2) this.cache.set(p2.personaId, p2);
23820
23448
  }
23821
23449
  }
23822
23450
  get(personaId) {
@@ -23832,6 +23460,21 @@ var PersonaRegistry = class {
23832
23460
  list() {
23833
23461
  return Array.from(this.cache.values());
23834
23462
  }
23463
+ /**
23464
+ * file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
23465
+ * 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
23466
+ * persona 数量通常 < 100,性能可忽略。
23467
+ */
23468
+ findByToken(token) {
23469
+ if (!token) return null;
23470
+ for (const persona of this.cache.values()) {
23471
+ if (!persona.public) continue;
23472
+ const entry = persona.tokenMap?.[token];
23473
+ if (!entry || entry.revoked) continue;
23474
+ return { personaId: persona.personaId, label: entry.label };
23475
+ }
23476
+ return null;
23477
+ }
23835
23478
  };
23836
23479
 
23837
23480
  // src/persona/manager.ts
@@ -24125,6 +23768,9 @@ var PersonaManager = class {
24125
23768
  model: args.model,
24126
23769
  public: args.public ?? false,
24127
23770
  iconKey: args.iconKey,
23771
+ // 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
23772
+ // file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
23773
+ tokenMap: {},
24128
23774
  createdAt: now,
24129
23775
  updatedAt: now
24130
23776
  };
@@ -24174,22 +23820,52 @@ var PersonaManager = class {
24174
23820
  }
24175
23821
  /**
24176
23822
  * 删除 persona。
24177
- * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
24178
- * Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
24179
- * .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
24180
- *
24181
- * ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
24182
- * ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
24183
- * 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
24184
- * 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
24185
- * 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
24186
- * force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
24187
- * "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
23823
+ * PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
24188
23824
  */
24189
23825
  delete(personaId) {
24190
23826
  this.deps.store.remove(personaId);
24191
23827
  this.deps.registry.remove(personaId);
24192
23828
  }
23829
+ /**
23830
+ * 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
23831
+ * 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
23832
+ */
23833
+ issueToken(personaId, label) {
23834
+ const existing = this.deps.registry.get(personaId);
23835
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23836
+ const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
23837
+ const entry = {
23838
+ label,
23839
+ issuedAt: Date.now(),
23840
+ revoked: false
23841
+ };
23842
+ const updated = {
23843
+ ...existing,
23844
+ tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
23845
+ updatedAt: Date.now()
23846
+ };
23847
+ this.deps.store.writeMeta(updated);
23848
+ this.deps.registry.set(updated);
23849
+ return { token, persona: updated };
23850
+ }
23851
+ /** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
23852
+ revokeToken(personaId, token) {
23853
+ const existing = this.deps.registry.get(personaId);
23854
+ if (!existing) throw new Error(`persona not found: ${personaId}`);
23855
+ const tokenEntry = existing.tokenMap?.[token];
23856
+ if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
23857
+ const updated = {
23858
+ ...existing,
23859
+ tokenMap: {
23860
+ ...existing.tokenMap ?? {},
23861
+ [token]: { ...tokenEntry, revoked: true }
23862
+ },
23863
+ updatedAt: Date.now()
23864
+ };
23865
+ this.deps.store.writeMeta(updated);
23866
+ this.deps.registry.set(updated);
23867
+ return updated;
23868
+ }
24193
23869
  /**
24194
23870
  * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
24195
23871
  * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
@@ -24209,7 +23885,7 @@ var PersonaManager = class {
24209
23885
 
24210
23886
  // src/persona/seed.ts
24211
23887
  var fs8 = __toESM(require("fs"), 1);
24212
- var path10 = __toESM(require("path"), 1);
23888
+ var path9 = __toESM(require("path"), 1);
24213
23889
  var import_node_url = require("url");
24214
23890
  var import_meta = {};
24215
23891
  var DEFAULT_PERSONAS = [
@@ -24245,14 +23921,14 @@ var DEFAULT_PERSONAS = [
24245
23921
  function findDefaultsRoot() {
24246
23922
  const candidates = [];
24247
23923
  try {
24248
- const here = path10.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
24249
- candidates.push(path10.resolve(here, "defaults"));
24250
- candidates.push(path10.resolve(here, "persona-defaults"));
23924
+ const here = path9.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
23925
+ candidates.push(path9.resolve(here, "defaults"));
23926
+ candidates.push(path9.resolve(here, "persona-defaults"));
24251
23927
  } catch {
24252
23928
  }
24253
23929
  if (process.argv[1]) {
24254
- const argvDir = path10.dirname(process.argv[1]);
24255
- candidates.push(path10.resolve(argvDir, "persona-defaults"));
23930
+ const argvDir = path9.dirname(process.argv[1]);
23931
+ candidates.push(path9.resolve(argvDir, "persona-defaults"));
24256
23932
  }
24257
23933
  for (const c of candidates) {
24258
23934
  try {
@@ -24269,7 +23945,7 @@ function seedDefaultPersonas(args) {
24269
23945
  args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
24270
23946
  continue;
24271
23947
  }
24272
- const bundleDir = path10.join(args.defaultsRoot, entry.personaId);
23948
+ const bundleDir = path9.join(args.defaultsRoot, entry.personaId);
24273
23949
  if (!fs8.existsSync(bundleDir)) {
24274
23950
  args.logger.warn("persona.seed.skip", {
24275
23951
  personaId: entry.personaId,
@@ -24278,7 +23954,7 @@ function seedDefaultPersonas(args) {
24278
23954
  });
24279
23955
  continue;
24280
23956
  }
24281
- const claudeMdPath = path10.join(bundleDir, "CLAUDE.md");
23957
+ const claudeMdPath = path9.join(bundleDir, "CLAUDE.md");
24282
23958
  if (!fs8.existsSync(claudeMdPath)) {
24283
23959
  args.logger.warn("persona.seed.skip", {
24284
23960
  personaId: entry.personaId,
@@ -24295,6 +23971,7 @@ function seedDefaultPersonas(args) {
24295
23971
  model: entry.model,
24296
23972
  public: entry.public,
24297
23973
  iconKey: entry.iconKey,
23974
+ tokenMap: {},
24298
23975
  createdAt: now,
24299
23976
  updatedAt: now
24300
23977
  };
@@ -24306,8 +23983,8 @@ function seedDefaultPersonas(args) {
24306
23983
  function copyBundleExtras(srcDir, dstDir) {
24307
23984
  for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
24308
23985
  if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
24309
- const srcPath = path10.join(srcDir, entry.name);
24310
- const dstPath = path10.join(dstDir, entry.name);
23986
+ const srcPath = path9.join(srcDir, entry.name);
23987
+ const dstPath = path9.join(dstDir, entry.name);
24311
23988
  if (entry.isDirectory()) {
24312
23989
  fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
24313
23990
  } else if (entry.isFile()) {
@@ -24910,6 +24587,16 @@ function spawnPty(opts) {
24910
24587
 
24911
24588
  // src/tools/claude-tui.ts
24912
24589
  var { Terminal } = import_headless.default;
24590
+ var POPUP_FOOTER_PATTERNS = [
24591
+ { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
24592
+ { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
24593
+ ];
24594
+ function matchPopupFooterKind(bufferText) {
24595
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
24596
+ if (p2.regex.test(bufferText)) return p2.kind;
24597
+ }
24598
+ return null;
24599
+ }
24913
24600
  function hasInputFrame(bufferText) {
24914
24601
  const lines = bufferText.split("\n");
24915
24602
  for (let i = 1; i < lines.length; i++) {
@@ -24924,6 +24611,7 @@ var READY_SIGNATURE_PATTERNS = [
24924
24611
  { name: "input-frame", match: hasInputFrame }
24925
24612
  ];
24926
24613
  function isReadySignature(bufferText) {
24614
+ if (matchPopupFooterKind(bufferText) !== null) return false;
24927
24615
  return READY_SIGNATURE_PATTERNS.some((p2) => p2.match(bufferText));
24928
24616
  }
24929
24617
  function createPopupDetector(opts) {
@@ -24939,7 +24627,7 @@ function createPopupDetector(opts) {
24939
24627
  let pendingClear = null;
24940
24628
  let quietTimer = null;
24941
24629
  const QUIET_MS = opts.quietMs ?? 300;
24942
- const scanReady = () => {
24630
+ const scanScreen = () => {
24943
24631
  const buf = term.buffer.active;
24944
24632
  const lines = [];
24945
24633
  for (let i = 0; i < buf.length; i++) {
@@ -24947,16 +24635,20 @@ function createPopupDetector(opts) {
24947
24635
  if (!line) continue;
24948
24636
  lines.push(line.translateToString(true));
24949
24637
  }
24950
- return isReadySignature(lines.join("\n"));
24638
+ const text = lines.join("\n");
24639
+ return { ready: isReadySignature(text), footerKind: matchPopupFooterKind(text) };
24951
24640
  };
24952
24641
  const tick = () => {
24953
24642
  if (quietTimer) clearTimeout(quietTimer);
24954
24643
  quietTimer = setTimeout(() => {
24955
24644
  quietTimer = null;
24956
- const ready = scanReady();
24645
+ const { ready, footerKind } = scanScreen();
24646
+ const popupKind = footerKind ?? "unknown";
24647
+ console.log("[BUGHUNT-READYGATE] quietTick", { ready, prevVisible: visible, popupKind });
24957
24648
  if (!ready && visible === null) {
24958
- visible = "unknown";
24959
- opts.onTransition("unknown", true);
24649
+ visible = popupKind;
24650
+ console.log("[BUGHUNT-READYGATE] emit popup_open", { popupKind });
24651
+ opts.onTransition(popupKind, true);
24960
24652
  } else if (!ready && visible !== null) {
24961
24653
  if (pendingClear) {
24962
24654
  clearTimeout(pendingClear);
@@ -25635,7 +25327,6 @@ var import_sender = __toESM(require_sender(), 1);
25635
25327
  var import_subprotocol = __toESM(require_subprotocol(), 1);
25636
25328
  var import_websocket = __toESM(require_websocket(), 1);
25637
25329
  var import_websocket_server = __toESM(require_websocket_server(), 1);
25638
- var wrapper_default = import_websocket.default;
25639
25330
 
25640
25331
  // src/transport/local-ws-server.ts
25641
25332
  var import_node_http = __toESM(require("http"), 1);
@@ -25650,9 +25341,6 @@ var LocalWsServer = class {
25650
25341
  httpServer = null;
25651
25342
  frameHandler = null;
25652
25343
  clients = /* @__PURE__ */ new Map();
25653
- // Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
25654
- // 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
25655
- capabilityIdToClients = /* @__PURE__ */ new Map();
25656
25344
  logger;
25657
25345
  pingIntervalMs;
25658
25346
  async start() {
@@ -25743,49 +25431,6 @@ var LocalWsServer = class {
25743
25431
  this.safeSend(c.ws, frame);
25744
25432
  }
25745
25433
  }
25746
- // Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
25747
- // 用于 capability:tokenIssued / capability:tokenDeleted 等 owner-only push frame——
25748
- // guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
25749
- broadcastToOwners(frame) {
25750
- const gate = this.opts.authGate;
25751
- for (const c of this.clients.values()) {
25752
- if (gate && !gate.isAuthed(c.handle.id)) continue;
25753
- if (c.ctx && c.ctx.principal.kind !== "owner") continue;
25754
- this.safeSend(c.ws, frame);
25755
- }
25756
- }
25757
- // capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
25758
- // - 所有 owner ws (owner 看自家所有 channel)
25759
- // - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
25760
- // 一次调用同时推两端, inbox:event push 帧由此走.
25761
- broadcastToCapabilityChannel(capabilityId, frame) {
25762
- this.broadcastToOwners(frame);
25763
- const set = this.capabilityIdToClients.get(capabilityId);
25764
- if (!set) return;
25765
- for (const id of set) {
25766
- const c = this.clients.get(id);
25767
- if (!c) continue;
25768
- this.safeSend(c.ws, frame);
25769
- }
25770
- }
25771
- // Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
25772
- // principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
25773
- // principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
25774
- // principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
25775
- // 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
25776
- broadcastToPrincipal(principalId, frame) {
25777
- if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
25778
- this.broadcastToOwners(frame);
25779
- return;
25780
- }
25781
- const set = this.capabilityIdToClients.get(principalId);
25782
- if (!set) return;
25783
- for (const id of set) {
25784
- const c = this.clients.get(id);
25785
- if (!c) continue;
25786
- this.safeSend(c.ws, frame);
25787
- }
25788
- }
25789
25434
  firstSubscriber(sessionId) {
25790
25435
  for (const c of this.clients.values()) {
25791
25436
  if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
@@ -25807,40 +25452,6 @@ var LocalWsServer = class {
25807
25452
  if (!c) return;
25808
25453
  this.safeSend(c.ws, frame);
25809
25454
  }
25810
- // Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
25811
- // 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
25812
- attachClientContext(clientId, ctx) {
25813
- const c = this.clients.get(clientId);
25814
- if (!c) return;
25815
- c.ctx = ctx;
25816
- if (ctx.capabilityId) {
25817
- let set = this.capabilityIdToClients.get(ctx.capabilityId);
25818
- if (!set) {
25819
- set = /* @__PURE__ */ new Set();
25820
- this.capabilityIdToClients.set(ctx.capabilityId, set);
25821
- }
25822
- set.add(clientId);
25823
- }
25824
- }
25825
- // 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
25826
- getClientContext(clientId) {
25827
- return this.clients.get(clientId)?.ctx ?? null;
25828
- }
25829
- // Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
25830
- // 让客户端识别 'capability revoked' 而非普通断连。
25831
- closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
25832
- const set = this.capabilityIdToClients.get(capabilityId);
25833
- if (!set) return;
25834
- const ids = [...set];
25835
- for (const id of ids) {
25836
- const c = this.clients.get(id);
25837
- if (!c) continue;
25838
- try {
25839
- c.ws.close(code, reason);
25840
- } catch {
25841
- }
25842
- }
25843
- }
25844
25455
  // URL path 路由:'/' → 主连接路径;其他 → close 4404
25845
25456
  routeConnection(socket, req) {
25846
25457
  const remoteAddress = req?.socket?.remoteAddress;
@@ -25875,7 +25486,7 @@ var LocalWsServer = class {
25875
25486
  } catch {
25876
25487
  }
25877
25488
  }, this.pingIntervalMs);
25878
- this.clients.set(id, { handle, ws: socket, pingTimer, ctx: null });
25489
+ this.clients.set(id, { handle, ws: socket, pingTimer });
25879
25490
  this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
25880
25491
  const authGate = this.opts.authGate;
25881
25492
  let authed = true;
@@ -25899,12 +25510,6 @@ var LocalWsServer = class {
25899
25510
  socket.on("close", () => {
25900
25511
  const c = this.clients.get(id);
25901
25512
  if (c?.pingTimer) clearInterval(c.pingTimer);
25902
- const capId = c?.ctx?.capabilityId;
25903
- if (capId) {
25904
- const set = this.capabilityIdToClients.get(capId);
25905
- set?.delete(id);
25906
- if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
25907
- }
25908
25513
  this.clients.delete(id);
25909
25514
  authGate?.unregister(id);
25910
25515
  this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
@@ -25937,17 +25542,6 @@ var LocalWsServer = class {
25937
25542
  return;
25938
25543
  }
25939
25544
  } else if (frame.type === "auth") {
25940
- const token = typeof frame.token === "string" ? frame.token ?? "" : "";
25941
- const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
25942
- const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
25943
- if (token && this.opts.tryVerifyCapabilityToken) {
25944
- const guestCtx = this.opts.tryVerifyCapabilityToken(
25945
- token,
25946
- selfPrincipalId || void 0,
25947
- selfDisplayName || void 0
25948
- );
25949
- if (guestCtx) this.attachClientContext(client.id, guestCtx);
25950
- }
25951
25545
  this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
25952
25546
  return;
25953
25547
  }
@@ -26064,31 +25658,15 @@ var AuthGate = class {
26064
25658
  this.markFailed(handle.id);
26065
25659
  return frame.type === "auth" ? "consumed" : "reject";
26066
25660
  }
26067
- let ctx = null;
26068
- if (this.opts.authenticate) {
26069
- const r = this.opts.authenticate(
26070
- parsed.data.token,
26071
- parsed.data.selfPrincipalId,
26072
- parsed.data.selfDisplayName
26073
- );
26074
- if (!r.ok) {
26075
- this.opts.closeConnection(handle, 4401, r.code);
26076
- this.markFailed(handle.id);
26077
- return "consumed";
26078
- }
26079
- ctx = r.context;
26080
- } else {
26081
- if (!this.opts.expectedToken) {
26082
- this.opts.closeConnection(handle, 1008, "auth not configured");
26083
- this.markFailed(handle.id);
26084
- return "consumed";
26085
- }
26086
- if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
26087
- this.opts.closeConnection(handle, 1008, "auth failed");
26088
- this.markFailed(handle.id);
26089
- return "consumed";
26090
- }
26091
- ctx = this.opts.buildOwnerContext?.() ?? null;
25661
+ if (!this.opts.expectedToken) {
25662
+ this.opts.closeConnection(handle, 1008, "auth not configured");
25663
+ this.markFailed(handle.id);
25664
+ return "consumed";
25665
+ }
25666
+ if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
25667
+ this.opts.closeConnection(handle, 1008, "auth failed");
25668
+ this.markFailed(handle.id);
25669
+ return "consumed";
26092
25670
  }
26093
25671
  if (st.timer != null) {
26094
25672
  const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
@@ -26096,7 +25674,6 @@ var AuthGate = class {
26096
25674
  }
26097
25675
  st.authed = true;
26098
25676
  st.timer = null;
26099
- if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
26100
25677
  this.opts.sendOk(handle, { type: "auth:ok" });
26101
25678
  return "consumed";
26102
25679
  }
@@ -26132,7 +25709,7 @@ var AuthContextResolver = class {
26132
25709
  }
26133
25710
  opts;
26134
25711
  /**
26135
- * 从 `Authorization` 头解析。null = 无凭证 / 不识别,调用方一律 401。
25712
+ * 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
26136
25713
  * remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
26137
25714
  */
26138
25715
  resolveFromHeader(authHeader, remoteAddress) {
@@ -26142,6 +25719,15 @@ var AuthContextResolver = class {
26142
25719
  if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
26143
25720
  return { role: "owner", isLoopback };
26144
25721
  }
25722
+ const personalHit = this.opts.personaRegistry.findByToken(token);
25723
+ if (personalHit) {
25724
+ return {
25725
+ role: "personal",
25726
+ personaId: personalHit.personaId,
25727
+ label: personalHit.label,
25728
+ isLoopback
25729
+ };
25730
+ }
26145
25731
  return null;
26146
25732
  }
26147
25733
  };
@@ -26163,753 +25749,206 @@ function constantTimeEqual2(a, b2) {
26163
25749
  return diff2 === 0;
26164
25750
  }
26165
25751
 
26166
- // ../protocol/src/index.ts
26167
- init_runtime();
26168
-
26169
- // src/transport/connection-context.ts
26170
- function ownerContext(ownerPrincipalId, displayName) {
26171
- return {
26172
- principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
26173
- grants: [{ resource: { type: "*" }, actions: ["admin"] }]
26174
- };
26175
- }
26176
- function guestContext(cap) {
26177
- return {
26178
- principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
26179
- grants: cap.grants,
26180
- capabilityId: cap.id
26181
- };
26182
- }
26183
- function authenticate(token, deps) {
26184
- if (!token) return { ok: false, code: "NO_TOKEN" };
26185
- if (deps.isOwnerToken(token)) {
26186
- return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
26187
- }
26188
- if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
26189
- const v2 = deps.capabilityRegistry.verifyToken(token);
26190
- if (!v2.ok) {
26191
- if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
26192
- return { ok: false, code: v2.code };
26193
- }
26194
- if (deps.selfPrincipalId && deps.selfDisplayName && deps.onGuestHello) {
26195
- try {
26196
- deps.onGuestHello(v2.capability.id, deps.selfPrincipalId, deps.selfDisplayName);
26197
- } catch (e) {
26198
- if (e.message === "TOKEN_SHARED_LEAK") {
26199
- return { ok: false, code: "TOKEN_REVOKED" };
26200
- }
26201
- throw e;
26202
- }
26203
- }
26204
- return { ok: true, context: guestContext(v2.capability) };
26205
- }
25752
+ // src/transport/http-router.ts
25753
+ var import_node_fs14 = __toESM(require("fs"), 1);
25754
+ var import_node_path16 = __toESM(require("path"), 1);
26206
25755
 
26207
- // src/permission/capability-store.ts
26208
- var fs15 = __toESM(require("fs"), 1);
26209
- var path18 = __toESM(require("path"), 1);
26210
- var CAPABILITIES_FILE_NAME = "capabilities.json";
26211
- var FILE_VERSION = 1;
26212
- var CapabilityStore = class {
26213
- constructor(dataDir) {
26214
- this.dataDir = dataDir;
26215
- fs15.mkdirSync(dataDir, { recursive: true });
26216
- this.cache = this.readFromDisk();
26217
- }
25756
+ // src/attachment/group.ts
25757
+ var import_node_fs13 = __toESM(require("fs"), 1);
25758
+ var import_node_path14 = __toESM(require("path"), 1);
25759
+ var import_node_crypto4 = __toESM(require("crypto"), 1);
25760
+ init_protocol();
25761
+ var GroupFileStore = class {
26218
25762
  dataDir;
26219
- cache;
26220
- list() {
26221
- return [...this.cache];
25763
+ logger;
25764
+ cache = /* @__PURE__ */ new Map();
25765
+ constructor(opts) {
25766
+ this.dataDir = opts.dataDir;
25767
+ this.logger = opts.logger;
26222
25768
  }
26223
- upsert(cap) {
26224
- const idx = this.cache.findIndex((c) => c.id === cap.id);
26225
- if (idx >= 0) {
26226
- this.cache[idx] = cap;
26227
- } else {
26228
- this.cache.push(cap);
26229
- }
26230
- this.flush();
25769
+ rootForScope(scope) {
25770
+ return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
26231
25771
  }
26232
- remove(id) {
26233
- const next = this.cache.filter((c) => c.id !== id);
26234
- if (next.length === this.cache.length) return;
26235
- this.cache = next;
26236
- this.flush();
25772
+ /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
25773
+ filePath(scope, sessionId) {
25774
+ return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
26237
25775
  }
26238
- filePath() {
26239
- return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
25776
+ cacheKey(scope, sessionId) {
25777
+ return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
26240
25778
  }
26241
- readFromDisk() {
26242
- const file = this.filePath();
26243
- let raw;
25779
+ /** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
25780
+ readFile(scope, sessionId) {
25781
+ const file = this.filePath(scope, sessionId);
26244
25782
  try {
26245
- raw = fs15.readFileSync(file, "utf8");
26246
- } catch (err) {
26247
- if (err?.code === "ENOENT") return [];
26248
- return [];
26249
- }
26250
- if (!raw.trim()) return [];
26251
- let parsed;
26252
- try {
26253
- parsed = JSON.parse(raw);
26254
- } catch {
26255
- return [];
26256
- }
26257
- if (!parsed || typeof parsed !== "object") return [];
26258
- const arr = parsed.capabilities;
26259
- if (!Array.isArray(arr)) return [];
26260
- const out = [];
26261
- for (const item of arr) {
26262
- const r = CapabilitySchema.safeParse(item);
26263
- if (r.success) out.push(r.data);
26264
- }
26265
- return out;
26266
- }
26267
- flush() {
26268
- const content = { version: FILE_VERSION, capabilities: this.cache };
26269
- this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
26270
- }
26271
- atomicWrite(file, content) {
26272
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26273
- fs15.writeFileSync(tmp, content, { mode: 384 });
26274
- fs15.renameSync(tmp, file);
26275
- try {
26276
- fs15.chmodSync(file, 384);
26277
- } catch {
26278
- }
26279
- }
26280
- };
26281
-
26282
- // src/permission/capability-registry.ts
26283
- var crypto4 = __toESM(require("crypto"), 1);
26284
- var CapabilityRegistry = class {
26285
- constructor(store, now = () => Date.now()) {
26286
- this.store = store;
26287
- this.now = now;
26288
- for (const cap of store.list()) {
26289
- this.bySecretHash.set(cap.secretHash, cap);
26290
- }
26291
- }
26292
- store;
26293
- now;
26294
- // sha256(token) → Capability
26295
- bySecretHash = /* @__PURE__ */ new Map();
26296
- list() {
26297
- return this.store.list();
26298
- }
26299
- verifyToken(token) {
26300
- if (!token) return { ok: false, code: "TOKEN_INVALID" };
26301
- const hash = sha256Hex(token);
26302
- const cap = this.bySecretHash.get(hash);
26303
- if (!cap) return { ok: false, code: "TOKEN_INVALID" };
26304
- if (cap.expiresAt && this.now() >= cap.expiresAt) {
26305
- return { ok: false, code: "TOKEN_EXPIRED" };
26306
- }
26307
- if (cap.maxUses && cap.usedCount >= cap.maxUses) {
26308
- return { ok: false, code: "TOKEN_EXHAUSTED" };
26309
- }
26310
- const updated = { ...cap, usedCount: cap.usedCount + 1 };
26311
- this.bySecretHash.set(hash, updated);
26312
- this.store.upsert(updated);
26313
- return { ok: true, capability: updated };
26314
- }
26315
- upsertCapability(cap) {
26316
- this.bySecretHash.set(cap.secretHash, cap);
26317
- this.store.upsert(cap);
26318
- }
26319
- /**
26320
- * Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
26321
- * 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
26322
- * idempotent:不存在 → null。
26323
- */
26324
- delete(id) {
26325
- const current = this.store.list().find((c) => c.id === id);
26326
- if (!current) return null;
26327
- this.bySecretHash.delete(current.secretHash);
26328
- this.store.remove(id);
26329
- return current;
26330
- }
26331
- findById(id) {
26332
- return this.store.list().find((c) => c.id === id) ?? null;
26333
- }
26334
- };
26335
- function sha256Hex(s) {
26336
- return crypto4.createHash("sha256").update(s).digest("hex");
26337
- }
26338
-
26339
- // src/permission/capability-manager.ts
26340
- var crypto5 = __toESM(require("crypto"), 1);
26341
- var CapabilityManager = class {
26342
- constructor(registry2, hooks = {}) {
26343
- this.registry = registry2;
26344
- this.hooks = hooks;
26345
- }
26346
- registry;
26347
- hooks;
26348
- list() {
26349
- return this.registry.list();
26350
- }
26351
- // whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
26352
- // 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
26353
- findById(id) {
26354
- return this.registry.findById(id);
26355
- }
26356
- issue(args) {
26357
- if (!args.displayName.trim()) {
26358
- throw new Error("CapabilityManager.issue: displayName must be non-empty");
26359
- }
26360
- const token = (this.hooks.generateToken ?? defaultGenerateToken)();
26361
- const id = (this.hooks.generateId ?? defaultGenerateId)();
26362
- const now = (this.hooks.now ?? Date.now)();
26363
- const cap = {
26364
- id,
26365
- secretHash: sha256Hex2(token),
26366
- displayName: args.displayName,
26367
- grants: args.grants,
26368
- issuedAt: now,
26369
- usedCount: 0,
26370
- ...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
26371
- ...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
26372
- };
26373
- this.registry.upsertCapability(cap);
26374
- this.hooks.onIssued?.(cap, token);
26375
- return { token, capability: cap };
26376
- }
26377
- /**
26378
- * guest 端 auth 帧捎带 selfPrincipalId/selfDisplayName 时,AuthGate.authenticate
26379
- * 校验 cap token 通过后调本方法把 peer 字段写回 cap:
26380
- * - 首次(cap.firstUsedByPeerAt 未设)→ 写盘 + 触发 onIssued 复用 push 频道
26381
- * 让 owner UI 通过 capability:updated 实时拿到 peer 字段
26382
- * - 重复(同 peerOwnerId)→ noop
26383
- * - 冲突(不同 peerOwnerId)→ throw TOKEN_SHARED_LEAK,AuthGate 转 close 4401
26384
- */
26385
- recordPeerHello(capId, args) {
26386
- const cap = this.registry.findById(capId);
26387
- if (!cap) return { changed: false };
26388
- if (cap.firstUsedByPeerAt != null) {
26389
- if (cap.peerOwnerId !== args.ownerPrincipalId) {
26390
- throw new Error("TOKEN_SHARED_LEAK");
25783
+ const raw = import_node_fs13.default.readFileSync(file, "utf8");
25784
+ const parsed = JSON.parse(raw);
25785
+ if (!Array.isArray(parsed)) {
25786
+ this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
25787
+ file
25788
+ });
25789
+ return [];
26391
25790
  }
26392
- return { changed: false };
26393
- }
26394
- const updated = {
26395
- ...cap,
26396
- peerOwnerId: args.ownerPrincipalId,
26397
- firstUsedByPeerAt: args.now,
26398
- peerDisplayName: args.displayName
26399
- };
26400
- this.registry.upsertCapability(updated);
26401
- this.hooks.onIssued?.(updated, "__peer_hello__");
26402
- return { changed: true };
26403
- }
26404
- /**
26405
- * Hard delete:从 registry / store 物理移除 capability。
26406
- * idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
26407
- * onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
26408
- */
26409
- delete(id) {
26410
- const removed = this.registry.delete(id);
26411
- if (!removed) return null;
26412
- this.hooks.onDeleted?.(removed);
26413
- return { capability: removed };
26414
- }
26415
- };
26416
- function defaultGenerateToken() {
26417
- return crypto5.randomBytes(24).toString("base64url");
26418
- }
26419
- function defaultGenerateId() {
26420
- return "cap_" + crypto5.randomBytes(6).toString("base64url");
26421
- }
26422
- function sha256Hex2(s) {
26423
- return crypto5.createHash("sha256").update(s).digest("hex");
26424
- }
26425
-
26426
- // src/permission/cleanup.ts
26427
- var fs16 = __toESM(require("fs"), 1);
26428
- function cleanupGuestSessionsForCapability(cap, factory) {
26429
- const removed = [];
26430
- for (const g2 of cap.grants) {
26431
- if (g2.resource.type !== "persona") continue;
26432
- const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
26433
- try {
26434
- fs16.rmSync(dir, { recursive: true, force: true });
26435
- removed.push(dir);
26436
- } catch {
26437
- }
26438
- }
26439
- return { removed };
26440
- }
26441
-
26442
- // src/inbox/inbox-store.ts
26443
- var fs17 = __toESM(require("fs"), 1);
26444
- var path19 = __toESM(require("path"), 1);
26445
- var INBOX_SUBDIR = "inbox";
26446
- var InboxStore = class {
26447
- constructor(dataDir) {
26448
- this.dataDir = dataDir;
26449
- fs17.mkdirSync(this.dirPath(), { recursive: true });
26450
- }
26451
- dataDir;
26452
- /**
26453
- * 列出某 peer 的消息, 按 createdAt 升序.
26454
- * sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
26455
- * 文件不存在时返回 [] (不抛, 不创建).
26456
- */
26457
- list(peerOwnerId, sinceCreatedAt) {
26458
- const file = this.filePath(peerOwnerId);
26459
- let raw;
26460
- try {
26461
- raw = fs17.readFileSync(file, "utf8");
25791
+ const out = [];
25792
+ for (const entry of parsed) {
25793
+ const r = GroupFileEntrySchema.safeParse(entry);
25794
+ if (r.success) out.push(r.data);
25795
+ }
25796
+ return out;
26462
25797
  } catch (err) {
26463
- if (err?.code === "ENOENT") return [];
25798
+ const code = err?.code;
25799
+ if (code === "ENOENT") return [];
25800
+ this.logger?.warn("GroupFileStore.readFile failed", {
25801
+ file,
25802
+ err: err.message
25803
+ });
26464
25804
  return [];
26465
25805
  }
26466
- const all = parseAllLines(raw);
26467
- if (sinceCreatedAt === void 0) return all;
26468
- return all.filter((m2) => m2.createdAt > sinceCreatedAt);
26469
25806
  }
26470
- /**
26471
- * 列出所有 peer 的 peerOwnerId (扫目录所有 *.jsonl 文件名).
26472
- */
26473
- listAllPeerOwnerIds() {
26474
- const dir = this.dirPath();
26475
- let entries;
26476
- try {
26477
- entries = fs17.readdirSync(dir);
26478
- } catch (err) {
26479
- if (err?.code === "ENOENT") return [];
26480
- return [];
26481
- }
26482
- return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
25807
+ writeFile(scope, sessionId, entries) {
25808
+ const file = this.filePath(scope, sessionId);
25809
+ import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
25810
+ const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
25811
+ import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
25812
+ import_node_fs13.default.renameSync(tmp, file);
26483
25813
  }
26484
- /**
26485
- * 追加一条消息,按 message.id 幂等去重。
26486
- * 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
26487
- */
26488
- append(message) {
26489
- const existing = this.list(message.peerOwnerId);
26490
- if (existing.some((m2) => m2.id === message.id)) return;
26491
- const file = this.filePath(message.peerOwnerId);
26492
- const line = JSON.stringify(message) + "\n";
26493
- fs17.appendFileSync(file, line, { mode: 384 });
26494
- try {
26495
- fs17.chmodSync(file, 384);
26496
- } catch {
26497
- }
25814
+ /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
25815
+ list(scope, sessionId) {
25816
+ const key = this.cacheKey(scope, sessionId);
25817
+ const cached = this.cache.get(key);
25818
+ if (cached) return cached.entries;
25819
+ const entries = this.readFile(scope, sessionId);
25820
+ this.cache.set(key, { entries, scope });
25821
+ return entries;
26498
25822
  }
26499
25823
  /**
26500
- * 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=principalId 的消息
26501
- * readBy[principalId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
26502
- * 返写入了几条.
25824
+ * upsert:
25825
+ * - relPath 已存在 更新 lastEditedAt + clear stale;不动 from / addedAt / id
25826
+ * (保留首次入群人 / 入群时刻)
25827
+ * - 不存在 → append,id 派生稳定 uuid,addedAt = now
25828
+ *
25829
+ * 返回最新 entry(caller 可用来 broadcast 通知)。
26503
25830
  */
26504
- markRead(peerOwnerId, principalId, upToCreatedAt) {
26505
- const messages = this.list(peerOwnerId);
26506
- let changed = 0;
26507
- const next = messages.map((m2) => {
26508
- if (m2.senderPrincipalId === principalId) return m2;
26509
- if (m2.createdAt > upToCreatedAt) return m2;
26510
- const cur = m2.readBy[principalId];
26511
- if (cur !== void 0 && cur >= upToCreatedAt) return m2;
26512
- changed++;
26513
- return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
26514
- });
26515
- if (changed === 0) return 0;
26516
- this.rewriteFile(peerOwnerId, next);
26517
- return changed;
25831
+ upsert(scope, sessionId, input, now = Date.now()) {
25832
+ const entries = this.list(scope, sessionId).slice();
25833
+ const idx = entries.findIndex((e) => e.relPath === input.relPath);
25834
+ let next;
25835
+ if (idx >= 0) {
25836
+ const prev = entries[idx];
25837
+ next = {
25838
+ ...prev,
25839
+ size: input.size,
25840
+ mime: input.mime,
25841
+ lastEditedAt: now,
25842
+ stale: false
25843
+ // label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
25844
+ };
25845
+ entries[idx] = next;
25846
+ } else {
25847
+ next = {
25848
+ id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
25849
+ relPath: input.relPath,
25850
+ from: input.from,
25851
+ label: input.label,
25852
+ size: input.size,
25853
+ mime: input.mime,
25854
+ addedAt: now
25855
+ // agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
25856
+ };
25857
+ entries.push(next);
25858
+ }
25859
+ this.writeFile(scope, sessionId, entries);
25860
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
25861
+ return next;
26518
25862
  }
26519
25863
  /**
26520
- * 删整个 peer jsonl (peer:remove cascade). 不存在 no-op.
25864
+ * 标记一个 relPath stale(agent rm / mv 后文件不在)。
25865
+ * - 命中 → stale=true,UI 灰显
25866
+ * - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
25867
+ *
25868
+ * 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
26521
25869
  */
26522
- removeByPeerOwnerId(peerOwnerId) {
26523
- const file = this.filePath(peerOwnerId);
26524
- try {
26525
- fs17.unlinkSync(file);
26526
- } catch (err) {
26527
- if (err?.code === "ENOENT") return;
26528
- }
26529
- }
26530
- rewriteFile(peerOwnerId, messages) {
26531
- const file = this.filePath(peerOwnerId);
26532
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
26533
- const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
26534
- fs17.writeFileSync(tmp, content, { mode: 384 });
26535
- fs17.renameSync(tmp, file);
26536
- try {
26537
- fs17.chmodSync(file, 384);
26538
- } catch {
26539
- }
26540
- }
26541
- dirPath() {
26542
- return path19.join(this.dataDir, INBOX_SUBDIR);
26543
- }
26544
- filePath(peerOwnerId) {
26545
- return path19.join(this.dirPath(), `${peerOwnerId}.jsonl`);
26546
- }
26547
- };
26548
- function parseAllLines(raw) {
26549
- const out = [];
26550
- for (const line of raw.split("\n")) {
26551
- const trimmed = line.trim();
26552
- if (!trimmed) continue;
26553
- let parsed;
26554
- try {
26555
- parsed = JSON.parse(trimmed);
26556
- } catch {
26557
- continue;
26558
- }
26559
- const r = InboxMessageSchema.safeParse(parsed);
26560
- if (r.success) out.push(r.data);
26561
- }
26562
- return out;
26563
- }
26564
-
26565
- // src/inbox/inbox-manager.ts
26566
- var InboxManager = class {
26567
- constructor(store, broadcast) {
26568
- this.store = store;
26569
- this.broadcast = broadcast;
25870
+ markStale(scope, sessionId, relPath) {
25871
+ const entries = this.list(scope, sessionId).slice();
25872
+ const idx = entries.findIndex((e) => e.relPath === relPath);
25873
+ if (idx < 0) return;
25874
+ if (entries[idx].stale) return;
25875
+ entries[idx] = { ...entries[idx], stale: true };
25876
+ this.writeFile(scope, sessionId, entries);
25877
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
26570
25878
  }
26571
- store;
26572
- broadcast;
26573
25879
  /**
26574
- * 写一条消息到 peer.jsonl + broadcast 给本机所有 owner ws。
26575
- * id 幂等:同 id 已落盘则 no-op(不广播、不重写)。
25880
+ * 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
25881
+ * agent 自动入群的不应走这条 —— markStale 表达"文件不在了"语义;
25882
+ * owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
26576
25883
  *
26577
- * caller 必须已通过 handler 鉴权
26578
- * (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
25884
+ * 返回值:true=命中并删除;false=relPath 不在群里。
26579
25885
  */
26580
- postMessage(args) {
26581
- const beforeLen = this.store.list(args.peerOwnerId).length;
26582
- const msg = {
26583
- id: args.id,
26584
- peerOwnerId: args.peerOwnerId,
26585
- senderPrincipalId: args.senderPrincipalId,
26586
- text: args.text,
26587
- createdAt: args.createdAt,
26588
- readBy: {}
26589
- };
26590
- this.store.append(msg);
26591
- const afterLen = this.store.list(args.peerOwnerId).length;
26592
- if (afterLen === beforeLen) return msg;
26593
- this.broadcast(args.peerOwnerId, {
26594
- type: "inbox:event",
26595
- peerOwnerId: args.peerOwnerId,
26596
- message: msg
26597
- });
26598
- return msg;
26599
- }
26600
- list(peerOwnerId, sinceCreatedAt) {
26601
- return this.store.list(peerOwnerId, sinceCreatedAt);
25886
+ remove(scope, sessionId, relPath) {
25887
+ const entries = this.list(scope, sessionId).slice();
25888
+ const idx = entries.findIndex((e) => e.relPath === relPath);
25889
+ if (idx < 0) return false;
25890
+ entries.splice(idx, 1);
25891
+ this.writeFile(scope, sessionId, entries);
25892
+ this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
25893
+ return true;
26602
25894
  }
26603
25895
  /**
26604
- * 标已读. 返写入了几条 (sender !== self createdAt <= upToCreatedAt 的消息).
26605
- * broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
25896
+ * session 聚合查询(spec §4 HTTP ACL:personal 视野并集)。
25897
+ *
25898
+ * 扫 <dataDir>/sessions/<personaId>/owner/*.group-files.json 和
25899
+ * <dataDir>/sessions/<personaId>/listener/*.group-files.json,每文件读一份。
25900
+ *
25901
+ * 复杂度 O(N sessions × N entries),N 通常 < 100,可接受。
26606
25902
  */
26607
- markRead(args) {
26608
- const before = this.store.list(args.peerOwnerId);
26609
- const changed = this.store.markRead(args.peerOwnerId, args.principalId, args.upToCreatedAt);
26610
- if (changed === 0) return 0;
26611
- const after = this.store.list(args.peerOwnerId);
26612
- const beforeById = new Map(before.map((m2) => [m2.id, m2]));
26613
- for (const m2 of after) {
26614
- const prev = beforeById.get(m2.id);
26615
- if (!prev) continue;
26616
- if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
26617
- this.broadcast(args.peerOwnerId, {
26618
- type: "inbox:event",
26619
- peerOwnerId: args.peerOwnerId,
26620
- message: m2
26621
- });
26622
- }
26623
- return changed;
26624
- }
26625
- /** peer:remove cascade. 删整个 peer 的 jsonl. */
26626
- removeChannel(peerOwnerId) {
26627
- this.store.removeByPeerOwnerId(peerOwnerId);
26628
- }
26629
- };
26630
-
26631
- // src/state/received-capability-store.ts
26632
- var fs18 = __toESM(require("fs"), 1);
26633
- var path20 = __toESM(require("path"), 1);
26634
- var FILE_NAME = "received-capabilities.json";
26635
- var ReceivedCapabilityStore = class {
26636
- constructor(dataDir) {
26637
- this.dataDir = dataDir;
26638
- }
26639
- dataDir;
26640
- caps = /* @__PURE__ */ new Map();
26641
- load() {
26642
- this.caps.clear();
26643
- const file = path20.join(this.dataDir, FILE_NAME);
26644
- let raw;
26645
- try {
26646
- raw = fs18.readFileSync(file, "utf8");
26647
- } catch (err) {
26648
- if (err?.code !== "ENOENT") this.renameBak(file);
26649
- return;
26650
- }
26651
- let parsed;
26652
- try {
26653
- parsed = JSON.parse(raw);
26654
- } catch {
26655
- this.renameBak(file);
26656
- return;
26657
- }
26658
- const arr = parsed?.receivedCaps;
26659
- if (!Array.isArray(arr)) return;
26660
- for (const entry of arr) {
26661
- const r = ReceivedCapabilitySchema.safeParse(entry);
26662
- if (r.success) this.caps.set(r.data.ownerPrincipalId, r.data);
26663
- }
26664
- }
26665
- list() {
26666
- return Array.from(this.caps.values());
26667
- }
26668
- get(ownerPrincipalId) {
26669
- return this.caps.get(ownerPrincipalId) ?? null;
26670
- }
26671
- hasOwner(ownerPrincipalId) {
26672
- return this.caps.has(ownerPrincipalId);
26673
- }
26674
- upsert(cap) {
26675
- this.caps.set(cap.ownerPrincipalId, cap);
26676
- this.flush();
26677
- }
26678
- remove(ownerPrincipalId) {
26679
- if (!this.caps.delete(ownerPrincipalId)) return;
26680
- this.flush();
26681
- }
26682
- flush() {
26683
- const file = path20.join(this.dataDir, FILE_NAME);
26684
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
26685
- const content = JSON.stringify(
26686
- { receivedCaps: Array.from(this.caps.values()) },
26687
- null,
26688
- 2
26689
- );
26690
- fs18.mkdirSync(this.dataDir, { recursive: true });
26691
- fs18.writeFileSync(tmp, content, { mode: 384 });
26692
- fs18.renameSync(tmp, file);
26693
- }
26694
- renameBak(file) {
26695
- try {
26696
- fs18.renameSync(file, `${file}.bak`);
26697
- } catch {
26698
- }
26699
- }
26700
- };
26701
-
26702
- // src/state/legacy-migration.ts
26703
- var fs19 = __toESM(require("fs"), 1);
26704
- var path21 = __toESM(require("path"), 1);
26705
- var TARGETS = [
26706
- { src: "capabilities.json", bak: "capabilities.legacy.bak" },
26707
- { src: "remote-personas", bak: "remote-personas.legacy.bak" },
26708
- { src: "peers.json", bak: "peers.legacy.bak" }
26709
- ];
26710
- function migrateLegacyFiles(dataDir) {
26711
- for (const t of TARGETS) {
26712
- const srcPath = path21.join(dataDir, t.src);
26713
- const bakPath = path21.join(dataDir, t.bak);
26714
- if (!fs19.existsSync(srcPath)) continue;
26715
- if (fs19.existsSync(bakPath)) continue;
26716
- try {
26717
- fs19.renameSync(srcPath, bakPath);
26718
- } catch {
26719
- }
26720
- }
26721
- }
26722
-
26723
- // src/received-capability/connect-remote.ts
26724
- var crypto6 = __toESM(require("crypto"), 1);
26725
- var HANDSHAKE_TIMEOUT_MS = 5e3;
26726
- var RPC_TIMEOUT_MS = 5e3;
26727
- async function connectRemote(args) {
26728
- const ws = new wrapper_default(args.url);
26729
- await new Promise((resolve2, reject) => {
26730
- const onError = (e) => reject(e);
26731
- ws.once("open", () => {
26732
- ws.off("error", onError);
26733
- resolve2();
26734
- });
26735
- ws.once("error", onError);
26736
- });
26737
- ws.send(
26738
- JSON.stringify({
26739
- type: "auth",
26740
- token: args.token,
26741
- selfPrincipalId: args.selfPrincipalId,
26742
- selfDisplayName: args.selfDisplayName
26743
- })
26744
- );
26745
- await new Promise((resolve2, reject) => {
26746
- const onMessage = (raw) => {
26747
- let f;
25903
+ listByPersona(personaId) {
25904
+ const out = [];
25905
+ for (const mode of ["owner", "listener"]) {
25906
+ const scope = { kind: "persona", personaId, mode };
25907
+ const root = this.rootForScope(scope);
25908
+ let names;
26748
25909
  try {
26749
- f = JSON.parse(raw.toString());
26750
- } catch {
26751
- return;
26752
- }
26753
- if (f.type === "auth:ok" || f.type === "ready") {
26754
- ws.off("message", onMessage);
26755
- resolve2();
26756
- return;
26757
- }
26758
- if (f.type === "auth:error" || f.code === 4401) {
26759
- ws.off("message", onMessage);
26760
- reject(new Error(`auth failed: ${f.message ?? f.type ?? "unknown"}`));
25910
+ names = import_node_fs13.default.readdirSync(root);
25911
+ } catch (err) {
25912
+ const code = err?.code;
25913
+ if (code === "ENOENT") continue;
25914
+ continue;
26761
25915
  }
26762
- };
26763
- ws.on("message", onMessage);
26764
- setTimeout(() => {
26765
- ws.off("message", onMessage);
26766
- reject(new Error("auth handshake timeout"));
26767
- }, HANDSHAKE_TIMEOUT_MS);
26768
- });
26769
- function call(method, payload) {
26770
- const requestId = crypto6.randomUUID();
26771
- return new Promise((resolve2, reject) => {
26772
- const onMessage = (raw) => {
26773
- let f;
26774
- try {
26775
- f = JSON.parse(raw.toString());
26776
- } catch {
26777
- return;
26778
- }
26779
- if (f.requestId !== requestId) return;
26780
- ws.off("message", onMessage);
26781
- if (f.error || f.type?.endsWith(":error")) {
26782
- reject(new Error(f.message ?? f.error ?? "rpc error"));
26783
- } else {
26784
- resolve2(f);
26785
- }
26786
- };
26787
- ws.on("message", onMessage);
26788
- ws.send(JSON.stringify({ type: method, requestId, ...payload }));
26789
- setTimeout(() => {
26790
- ws.off("message", onMessage);
26791
- reject(new Error(`${method} timeout`));
26792
- }, RPC_TIMEOUT_MS);
26793
- });
26794
- }
26795
- return {
26796
- whoami: async () => {
26797
- const f = await call("whoami", {});
26798
- return {
26799
- ownerId: f.owner.id,
26800
- displayName: f.owner.displayName,
26801
- capabilityId: f.capability.id,
26802
- grants: f.capability.grants
26803
- };
26804
- },
26805
- close: () => {
26806
- try {
26807
- ws.close();
26808
- } catch {
25916
+ for (const name of names) {
25917
+ if (!name.endsWith(".group-files.json")) continue;
25918
+ const sessionId = name.slice(0, -".group-files.json".length);
25919
+ if (!sessionId) continue;
25920
+ const entries = this.list(scope, sessionId);
25921
+ out.push({ sessionId, entries });
26809
25922
  }
26810
25923
  }
26811
- };
26812
- }
26813
-
26814
- // src/migrations/2026-05-20-flatten-sessions.ts
26815
- var fs20 = __toESM(require("fs"), 1);
26816
- var path22 = __toESM(require("path"), 1);
26817
- var MIGRATION_FLAG_NAME = ".migration.v1.done";
26818
- function migrateFlattenSessions(opts) {
26819
- const dataDir = opts.dataDir;
26820
- const now = opts.now ?? Date.now;
26821
- const sessionsDir = path22.join(dataDir, "sessions");
26822
- const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
26823
- if (existsSync4(flagPath)) {
26824
- return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
26825
- }
26826
- let movedBare = 0;
26827
- let movedVmOwner = 0;
26828
- let archivedListener = 0;
26829
- const defaultDir = path22.join(sessionsDir, "default");
26830
- if (existsSync4(defaultDir)) {
26831
- for (const entry of readdirSafe(defaultDir)) {
26832
- if (!entry.endsWith(".json")) continue;
26833
- const src = path22.join(defaultDir, entry);
26834
- const dst = path22.join(sessionsDir, entry);
26835
- fs20.renameSync(src, dst);
26836
- movedBare += 1;
26837
- }
26838
- rmdirIfEmpty(defaultDir);
26839
- }
26840
- for (const pid of readdirSafe(sessionsDir)) {
26841
- const personaDir = path22.join(sessionsDir, pid);
26842
- if (!isDir(personaDir)) continue;
26843
- if (pid === "default") continue;
26844
- const ownerSrc = path22.join(personaDir, "owner");
26845
- if (existsSync4(ownerSrc) && isDir(ownerSrc)) {
26846
- const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
26847
- fs20.mkdirSync(ownerDst, { recursive: true });
26848
- for (const file of readdirSafe(ownerSrc)) {
26849
- if (!file.endsWith(".json")) continue;
26850
- fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
26851
- movedVmOwner += 1;
26852
- }
26853
- rmdirIfEmpty(ownerSrc);
26854
- }
26855
- const listenerSrc = path22.join(personaDir, "listener");
26856
- if (existsSync4(listenerSrc) && isDir(listenerSrc)) {
26857
- const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
26858
- fs20.mkdirSync(archiveDst, { recursive: true });
26859
- for (const file of readdirSafe(listenerSrc)) {
26860
- if (!file.endsWith(".json")) continue;
26861
- fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
26862
- archivedListener += 1;
26863
- }
26864
- rmdirIfEmpty(listenerSrc);
26865
- }
26866
- rmdirIfEmpty(personaDir);
26867
- }
26868
- fs20.mkdirSync(sessionsDir, { recursive: true });
26869
- fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
26870
- return {
26871
- skipped: false,
26872
- flagWritten: true,
26873
- movedBare,
26874
- movedVmOwner,
26875
- archivedListener
26876
- };
26877
- }
26878
- function existsSync4(p2) {
26879
- try {
26880
- fs20.statSync(p2);
26881
- return true;
26882
- } catch {
26883
- return false;
25924
+ return out;
26884
25925
  }
26885
- }
26886
- function isDir(p2) {
26887
- try {
26888
- return fs20.statSync(p2).isDirectory();
26889
- } catch {
25926
+ };
25927
+ function personalViewable(groupStore, personaDir, personaId, absPath) {
25928
+ const realTarget = safeRealpath(absPath);
25929
+ if (!realTarget) {
26890
25930
  return false;
26891
25931
  }
26892
- }
26893
- function readdirSafe(p2) {
26894
- try {
26895
- return fs20.readdirSync(p2);
26896
- } catch {
26897
- return [];
25932
+ const personasUnion = groupStore.listByPersona(personaId);
25933
+ for (const { entries } of personasUnion) {
25934
+ for (const e of entries) {
25935
+ if (e.stale) continue;
25936
+ const realEntry = safeRealpath(import_node_path14.default.join(personaDir, e.relPath));
25937
+ if (realEntry && realEntry === realTarget) return true;
25938
+ }
26898
25939
  }
25940
+ return false;
26899
25941
  }
26900
- function rmdirIfEmpty(p2) {
25942
+ function safeRealpath(p2) {
26901
25943
  try {
26902
- fs20.rmdirSync(p2);
25944
+ return import_node_fs13.default.realpathSync(p2);
26903
25945
  } catch {
25946
+ return null;
26904
25947
  }
26905
25948
  }
26906
25949
 
26907
- // src/transport/http-router.ts
26908
- var import_node_fs13 = __toESM(require("fs"), 1);
26909
- var import_node_path15 = __toESM(require("path"), 1);
26910
-
26911
25950
  // src/attachment/mime.ts
26912
- var import_node_path14 = __toESM(require("path"), 1);
25951
+ var import_node_path15 = __toESM(require("path"), 1);
26913
25952
  var TEXT_PLAIN = "text/plain; charset=utf-8";
26914
25953
  var EXT_TO_NATIVE_MIME = {
26915
25954
  // 图片
@@ -27016,14 +26055,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
27016
26055
  ".mk"
27017
26056
  ]);
27018
26057
  function lookupMime(filePathOrName) {
27019
- const ext = import_node_path14.default.extname(filePathOrName).toLowerCase();
26058
+ const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
27020
26059
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
27021
26060
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
27022
26061
  return "application/octet-stream";
27023
26062
  }
27024
26063
 
27025
26064
  // src/attachment/sign-url.ts
27026
- var import_node_crypto4 = __toESM(require("crypto"), 1);
26065
+ var import_node_crypto5 = __toESM(require("crypto"), 1);
27027
26066
  var HMAC_ALGO = "sha256";
27028
26067
  function base64urlEncode(buf) {
27029
26068
  const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
@@ -27040,7 +26079,7 @@ function decodeAbsPathFromUrl(encoded) {
27040
26079
  }
27041
26080
  function computeSig(secret, absPath, e) {
27042
26081
  const msg = e === null ? absPath : `${absPath}|${e}`;
27043
- return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
26082
+ return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
27044
26083
  }
27045
26084
  function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
27046
26085
  const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
@@ -27075,7 +26114,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
27075
26114
  if (provided.length !== expected.length) {
27076
26115
  return { ok: false, code: "BAD_SIG" };
27077
26116
  }
27078
- if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
26117
+ if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
27079
26118
  return { ok: false, code: "BAD_SIG" };
27080
26119
  }
27081
26120
  if (e !== null && now() / 1e3 > e) {
@@ -27096,7 +26135,7 @@ function createHttpRouter(deps) {
27096
26135
  sendJson(res, 200, { ok: true, version: deps.daemonVersion });
27097
26136
  return true;
27098
26137
  }
27099
- if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
26138
+ if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
27100
26139
  return false;
27101
26140
  }
27102
26141
  if (url.pathname.startsWith("/files/") && req.method === "GET") {
@@ -27136,8 +26175,43 @@ function createHttpRouter(deps) {
27136
26175
  sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
27137
26176
  return true;
27138
26177
  }
26178
+ const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
26179
+ if (personaFilesMatch && req.method === "GET") {
26180
+ const pid = personaFilesMatch[1];
26181
+ const pathParam = url.searchParams.get("path");
26182
+ if (!pathParam) {
26183
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
26184
+ return true;
26185
+ }
26186
+ if (!deps.personaStore || !deps.groupFileStore) {
26187
+ sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
26188
+ return true;
26189
+ }
26190
+ const personaDir = deps.personaStore.personaDirPath(pid);
26191
+ const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
26192
+ if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
26193
+ sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
26194
+ return true;
26195
+ }
26196
+ if (ctx.role === "personal") {
26197
+ if (ctx.personaId !== pid) {
26198
+ sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
26199
+ return true;
26200
+ }
26201
+ if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
26202
+ sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
26203
+ return true;
26204
+ }
26205
+ }
26206
+ streamFile(res, absPath, deps.logger);
26207
+ return true;
26208
+ }
27139
26209
  const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
27140
26210
  if (sessionFilesMatch && req.method === "GET") {
26211
+ if (ctx.role !== "owner") {
26212
+ sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
26213
+ return true;
26214
+ }
27141
26215
  const sid = sessionFilesMatch[1];
27142
26216
  const pathParam = url.searchParams.get("path");
27143
26217
  if (!pathParam) {
@@ -27145,7 +26219,7 @@ function createHttpRouter(deps) {
27145
26219
  return true;
27146
26220
  }
27147
26221
  let absPath;
27148
- if (import_node_path15.default.isAbsolute(pathParam)) {
26222
+ if (import_node_path16.default.isAbsolute(pathParam)) {
27149
26223
  absPath = pathParam;
27150
26224
  } else if (deps.sessionStore) {
27151
26225
  const file = deps.sessionStore.read(sid);
@@ -27153,7 +26227,7 @@ function createHttpRouter(deps) {
27153
26227
  sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
27154
26228
  return true;
27155
26229
  }
27156
- absPath = import_node_path15.default.join(file.cwd, pathParam);
26230
+ absPath = import_node_path16.default.join(file.cwd, pathParam);
27157
26231
  } else {
27158
26232
  sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
27159
26233
  return true;
@@ -27178,182 +26252,47 @@ function sendJson(res, status, body) {
27178
26252
  res.end(JSON.stringify(body));
27179
26253
  }
27180
26254
  function withCtx(ctx, body) {
27181
- return { ...body, role: ctx.role };
26255
+ return { ...body, role: ctx.role, personaId: ctx.personaId };
26256
+ }
26257
+ function isContainedIn(abs, root) {
26258
+ const normalized = import_node_path16.default.resolve(abs);
26259
+ const normalizedRoot = import_node_path16.default.resolve(root);
26260
+ if (normalized === normalizedRoot) return true;
26261
+ return normalized.startsWith(normalizedRoot + import_node_path16.default.sep);
27182
26262
  }
27183
26263
  function streamFile(res, absPath, logger) {
27184
26264
  let stat;
27185
26265
  try {
27186
- stat = import_node_fs13.default.statSync(absPath);
26266
+ stat = import_node_fs14.default.statSync(absPath);
27187
26267
  } catch (err) {
27188
26268
  const code = err?.code;
27189
26269
  if (code === "ENOENT") {
27190
26270
  sendJson(res, 404, { code: "NOT_FOUND", message: "file not found" });
27191
26271
  } else {
27192
26272
  sendJson(res, 500, { code: "STAT_FAILED", message: err.message });
27193
- }
27194
- return;
27195
- }
27196
- if (!stat.isFile()) {
27197
- sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
27198
- return;
27199
- }
27200
- const mime = lookupMime(absPath);
27201
- const basename = import_node_path15.default.basename(absPath);
27202
- res.writeHead(200, {
27203
- "Content-Type": mime,
27204
- "Content-Length": String(stat.size),
27205
- "Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
27206
- // 防止浏览器把任意 mime 当 html 渲染
27207
- "X-Content-Type-Options": "nosniff"
27208
- });
27209
- const stream = import_node_fs13.default.createReadStream(absPath);
27210
- stream.on("error", (err) => {
27211
- logger?.warn("streamFile read error", { absPath, err: err.message });
27212
- res.destroy();
27213
- });
27214
- stream.pipe(res);
27215
- }
27216
-
27217
- // src/attachment/group.ts
27218
- var import_node_fs14 = __toESM(require("fs"), 1);
27219
- var import_node_path16 = __toESM(require("path"), 1);
27220
- var import_node_crypto5 = __toESM(require("crypto"), 1);
27221
- init_protocol();
27222
- var GroupFileStore = class {
27223
- dataDir;
27224
- logger;
27225
- cache = /* @__PURE__ */ new Map();
27226
- constructor(opts) {
27227
- this.dataDir = opts.dataDir;
27228
- this.logger = opts.logger;
27229
- }
27230
- rootForScope(scope) {
27231
- return import_node_path16.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
27232
- }
27233
- /** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
27234
- filePath(scope, sessionId) {
27235
- return import_node_path16.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
27236
- }
27237
- cacheKey(scope, sessionId) {
27238
- return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
27239
- }
27240
- /** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
27241
- readFile(scope, sessionId) {
27242
- const file = this.filePath(scope, sessionId);
27243
- try {
27244
- const raw = import_node_fs14.default.readFileSync(file, "utf8");
27245
- const parsed = JSON.parse(raw);
27246
- if (!Array.isArray(parsed)) {
27247
- this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
27248
- file
27249
- });
27250
- return [];
27251
- }
27252
- const out = [];
27253
- for (const entry of parsed) {
27254
- const r = GroupFileEntrySchema.safeParse(entry);
27255
- if (r.success) out.push(r.data);
27256
- }
27257
- return out;
27258
- } catch (err) {
27259
- const code = err?.code;
27260
- if (code === "ENOENT") return [];
27261
- this.logger?.warn("GroupFileStore.readFile failed", {
27262
- file,
27263
- err: err.message
27264
- });
27265
- return [];
27266
- }
27267
- }
27268
- writeFile(scope, sessionId, entries) {
27269
- const file = this.filePath(scope, sessionId);
27270
- import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
27271
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
27272
- import_node_fs14.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
27273
- import_node_fs14.default.renameSync(tmp, file);
27274
- }
27275
- /** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
27276
- list(scope, sessionId) {
27277
- const key = this.cacheKey(scope, sessionId);
27278
- const cached = this.cache.get(key);
27279
- if (cached) return cached.entries;
27280
- const entries = this.readFile(scope, sessionId);
27281
- this.cache.set(key, { entries });
27282
- return entries;
27283
- }
27284
- /**
27285
- * upsert:
27286
- * - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
27287
- * (保留首次入群人 / 入群时刻)
27288
- * - 不存在 → append,id 派生稳定 uuid,addedAt = now
27289
- *
27290
- * 返回最新 entry(caller 可用来 broadcast 通知)。
27291
- */
27292
- upsert(scope, sessionId, input, now = Date.now()) {
27293
- const entries = this.list(scope, sessionId).slice();
27294
- const idx = entries.findIndex((e) => e.relPath === input.relPath);
27295
- let next;
27296
- if (idx >= 0) {
27297
- const prev = entries[idx];
27298
- next = {
27299
- ...prev,
27300
- size: input.size,
27301
- mime: input.mime,
27302
- lastEditedAt: now,
27303
- stale: false
27304
- // label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
27305
- };
27306
- entries[idx] = next;
27307
- } else {
27308
- next = {
27309
- id: `gf-${import_node_crypto5.default.randomBytes(6).toString("base64url")}`,
27310
- relPath: input.relPath,
27311
- from: input.from,
27312
- label: input.label,
27313
- size: input.size,
27314
- mime: input.mime,
27315
- addedAt: now
27316
- // agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
27317
- };
27318
- entries.push(next);
27319
- }
27320
- this.writeFile(scope, sessionId, entries);
27321
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
27322
- return next;
27323
- }
27324
- /**
27325
- * 标记一个 relPath stale(agent rm / mv 后文件不在)。
27326
- * - 命中 → stale=true,UI 灰显
27327
- * - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
27328
- *
27329
- * 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
27330
- */
27331
- markStale(scope, sessionId, relPath) {
27332
- const entries = this.list(scope, sessionId).slice();
27333
- const idx = entries.findIndex((e) => e.relPath === relPath);
27334
- if (idx < 0) return;
27335
- if (entries[idx].stale) return;
27336
- entries[idx] = { ...entries[idx], stale: true };
27337
- this.writeFile(scope, sessionId, entries);
27338
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
26273
+ }
26274
+ return;
27339
26275
  }
27340
- /**
27341
- * 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
27342
- * agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
27343
- * owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
27344
- *
27345
- * 返回值:true=命中并删除;false=relPath 不在群里。
27346
- */
27347
- remove(scope, sessionId, relPath) {
27348
- const entries = this.list(scope, sessionId).slice();
27349
- const idx = entries.findIndex((e) => e.relPath === relPath);
27350
- if (idx < 0) return false;
27351
- entries.splice(idx, 1);
27352
- this.writeFile(scope, sessionId, entries);
27353
- this.cache.set(this.cacheKey(scope, sessionId), { entries });
27354
- return true;
26276
+ if (!stat.isFile()) {
26277
+ sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
26278
+ return;
27355
26279
  }
27356
- };
26280
+ const mime = lookupMime(absPath);
26281
+ const basename = import_node_path16.default.basename(absPath);
26282
+ res.writeHead(200, {
26283
+ "Content-Type": mime,
26284
+ "Content-Length": String(stat.size),
26285
+ "Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
26286
+ // 防止浏览器把任意 mime 当 html 渲染
26287
+ "X-Content-Type-Options": "nosniff"
26288
+ });
26289
+ const stream = import_node_fs14.default.createReadStream(absPath);
26290
+ stream.on("error", (err) => {
26291
+ logger?.warn("streamFile read error", { absPath, err: err.message });
26292
+ res.destroy();
26293
+ });
26294
+ stream.pipe(res);
26295
+ }
27357
26296
 
27358
26297
  // src/discovery/state-file.ts
27359
26298
  var import_node_fs15 = __toESM(require("fs"), 1);
@@ -28033,32 +26972,26 @@ function authFilePath(dataDir) {
28033
26972
  }
28034
26973
  function loadOrCreateAuthFile(opts) {
28035
26974
  const file = authFilePath(opts.dataDir);
28036
- const generate = opts.generate ?? defaultGenerateToken2;
28037
- const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
26975
+ const generate = opts.generate ?? defaultGenerate;
28038
26976
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
28039
26977
  const existing = readAuthFile(file);
28040
- if (existing && existing.token && existing.signSecret && existing.ownerPrincipalId) {
26978
+ if (existing && existing.token && existing.signSecret) {
28041
26979
  return {
28042
26980
  token: existing.token,
28043
26981
  signSecret: existing.signSecret,
28044
- ownerPrincipalId: existing.ownerPrincipalId,
28045
26982
  createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
28046
26983
  };
28047
26984
  }
28048
26985
  const token = existing?.token || generate();
28049
26986
  const signSecret = existing?.signSecret || generate();
28050
- const ownerPrincipalId = existing?.ownerPrincipalId || genId();
28051
26987
  const createdAt = existing?.createdAt || now().toISOString();
28052
- const next = { token, signSecret, ownerPrincipalId, createdAt };
26988
+ const next = { token, signSecret, createdAt };
28053
26989
  writeAuthFile(file, next);
28054
26990
  return next;
28055
26991
  }
28056
- function defaultGenerateToken2() {
26992
+ function defaultGenerate() {
28057
26993
  return import_node_crypto8.default.randomBytes(32).toString("base64url");
28058
26994
  }
28059
- function defaultGenerateOwnerPrincipalId() {
28060
- return `owner-${import_node_crypto8.default.randomUUID()}`;
28061
- }
28062
26995
  function readAuthFile(file) {
28063
26996
  try {
28064
26997
  const raw = import_node_fs20.default.readFileSync(file, "utf8");
@@ -28069,7 +27002,6 @@ function readAuthFile(file) {
28069
27002
  return {
28070
27003
  token: parsed.token,
28071
27004
  signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
28072
- ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
28073
27005
  createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
28074
27006
  };
28075
27007
  } catch (err) {
@@ -28183,63 +27115,10 @@ function forkSession(input) {
28183
27115
  return { forkedToolSessionId, forkedFilePath };
28184
27116
  }
28185
27117
 
28186
- // src/permission/capability.ts
28187
- function matchResource(grant, target) {
28188
- if (grant.type === "*") return true;
28189
- if (grant.type !== target.type) return false;
28190
- if (grant.id === "*") return true;
28191
- return grant.id === target.id;
28192
- }
28193
- function assertGrant(grants, resource, action) {
28194
- for (const g2 of grants) {
28195
- if (!matchResource(g2.resource, resource)) continue;
28196
- if (g2.actions.includes(action)) return true;
28197
- if (g2.actions.includes("admin")) return true;
28198
- }
28199
- return false;
28200
- }
28201
-
28202
- // src/permission/session-access.ts
28203
- function canAccessSession(ctx, sessionId, action, deps) {
28204
- if (ctx.principal.kind === "owner") return true;
28205
- const file = deps.readSession(sessionId);
28206
- if (!file) return true;
28207
- return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
28208
- }
28209
- function canAccessPersona(grants, personaId, action) {
28210
- if (!personaId) return false;
28211
- const resource = { type: "persona", id: personaId };
28212
- return assertGrant(grants, resource, action);
28213
- }
28214
-
28215
27118
  // src/handlers/session.ts
28216
- init_protocol();
28217
27119
  function buildSessionHandlers(deps) {
28218
- const { manager, observer, getAdapter: getAdapter2, store, ownerPrincipalId } = deps;
28219
- const ensureSessionAccess = (ctx, sessionId, action) => {
28220
- if (!ctx) return;
28221
- const ok = canAccessSession(ctx, sessionId, action, {
28222
- readSession: (sid) => store.read(sid)
28223
- });
28224
- if (!ok) {
28225
- throw new ClawdError(
28226
- ERROR_CODES.UNAUTHORIZED,
28227
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} session ${sessionId}`
28228
- );
28229
- }
28230
- };
28231
- const ensurePersonaAccess = (ctx, personaId, action) => {
28232
- if (!ctx) return;
28233
- if (ctx.principal.kind === "owner") return;
28234
- const ok = canAccessPersona(ctx.grants, personaId, action);
28235
- if (!ok) {
28236
- throw new ClawdError(
28237
- ERROR_CODES.UNAUTHORIZED,
28238
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} on persona:${personaId ?? "<none>"}`
28239
- );
28240
- }
28241
- };
28242
- const create = async (frame, _client, ctx) => {
27120
+ const { manager, observer, getAdapter: getAdapter2 } = deps;
27121
+ const create = async (frame) => {
28243
27122
  const args = SessionCreateArgs.parse(frame);
28244
27123
  if (args.ownerPersonaId) {
28245
27124
  const persona = deps.personaRegistry.get(args.ownerPersonaId);
@@ -28247,75 +27126,58 @@ function buildSessionHandlers(deps) {
28247
27126
  throw new Error(`persona not found: ${args.ownerPersonaId}`);
28248
27127
  }
28249
27128
  }
28250
- ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
28251
- const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
28252
- const { response, broadcast } = manager.create(args, creatorPrincipalId);
27129
+ const { response, broadcast } = manager.create(args);
28253
27130
  return { response: { type: "session:info", ...response }, broadcast };
28254
27131
  };
28255
- const list = async (_frame, _client, ctx) => {
27132
+ const list = async () => {
28256
27133
  const { response } = manager.list();
28257
- if (ctx && ctx.principal.kind === "guest") {
28258
- const sessions = response.sessions ?? [];
28259
- const capId = ctx.capabilityId;
28260
- const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter((s) => s.creatorPrincipalId === capId);
28261
- return { response: { type: "session:list", sessions: filtered } };
28262
- }
28263
27134
  return { response: { type: "session:list", ...response } };
28264
27135
  };
28265
- const get = async (frame, _client, ctx) => {
27136
+ const get = async (frame) => {
28266
27137
  const args = SessionIdArgs.parse(frame);
28267
- ensureSessionAccess(ctx, args.sessionId, "read");
28268
27138
  const { response } = manager.get(args);
28269
27139
  return { response: { type: "session:info", ...response } };
28270
27140
  };
28271
- const update = async (frame, _client, ctx) => {
27141
+ const update = async (frame) => {
28272
27142
  const args = SessionUpdateArgs.parse(frame);
28273
- ensureSessionAccess(ctx, args.sessionId, "send");
28274
27143
  const { response, broadcast } = manager.update(args);
28275
27144
  return { response: { type: "session:info", ...response }, broadcast };
28276
27145
  };
28277
- const del = async (frame, _client, ctx) => {
27146
+ const del = async (frame) => {
28278
27147
  const args = SessionIdArgs.parse(frame);
28279
- ensureSessionAccess(ctx, args.sessionId, "send");
28280
27148
  const { broadcast } = manager.delete(args);
28281
27149
  return {
28282
27150
  response: { type: "session:deleted", sessionId: args.sessionId },
28283
27151
  broadcast
28284
27152
  };
28285
27153
  };
28286
- const send = async (frame, _client, ctx) => {
27154
+ const send = async (frame) => {
28287
27155
  const args = SessionSendArgs.parse(frame);
28288
- ensureSessionAccess(ctx, args.sessionId, "send");
28289
27156
  const { broadcast } = manager.send(args);
28290
27157
  return { response: { type: "session:send", ok: true }, broadcast };
28291
27158
  };
28292
- const stop = async (frame, _client, ctx) => {
27159
+ const stop = async (frame) => {
28293
27160
  const args = SessionIdArgs.parse(frame);
28294
- ensureSessionAccess(ctx, args.sessionId, "send");
28295
27161
  const { broadcast } = await manager.stop(args);
28296
27162
  return { response: { type: "session:stop", ok: true }, broadcast };
28297
27163
  };
28298
- const interrupt = async (frame, _client, ctx) => {
27164
+ const interrupt = async (frame) => {
28299
27165
  const args = SessionIdArgs.parse(frame);
28300
- ensureSessionAccess(ctx, args.sessionId, "send");
28301
27166
  const { broadcast } = await manager.interrupt(args);
28302
27167
  return { response: { type: "session:interrupt", ok: true }, broadcast };
28303
27168
  };
28304
- const rewind = async (frame, _client, ctx) => {
27169
+ const rewind = async (frame) => {
28305
27170
  const args = SessionRewindArgs.parse(frame);
28306
- ensureSessionAccess(ctx, args.sessionId, "send");
28307
27171
  const { response, broadcast } = await manager.rewind(args);
28308
27172
  return { response: { type: "session:rewind", ...response }, broadcast };
28309
27173
  };
28310
- const rewindDiff = async (frame, _client, ctx) => {
27174
+ const rewindDiff = async (frame) => {
28311
27175
  const args = SessionRewindDiffArgs.parse(frame);
28312
- ensureSessionAccess(ctx, args.sessionId, "read");
28313
27176
  const { response } = await manager.rewindDiff(args);
28314
27177
  return { response: { type: "session:rewind-diff", ...response } };
28315
27178
  };
28316
- const rewindableMessageIds = async (frame, _client, ctx) => {
27179
+ const rewindableMessageIds = async (frame) => {
28317
27180
  const args = SessionRewindableMessageIdsArgs.parse(frame);
28318
- ensureSessionAccess(ctx, args.sessionId, "read");
28319
27181
  const { response } = manager.rewindableMessageIds(args);
28320
27182
  return {
28321
27183
  response: { type: "session:rewindable-message-ids", ...response }
@@ -28326,22 +27188,19 @@ function buildSessionHandlers(deps) {
28326
27188
  const result = forkSession(args);
28327
27189
  return { response: { type: "session:fork", ...result } };
28328
27190
  };
28329
- const newSession = async (frame, _client, ctx) => {
27191
+ const newSession = async (frame) => {
28330
27192
  const args = SessionIdArgs.parse(frame);
28331
- ensureSessionAccess(ctx, args.sessionId, "send");
28332
27193
  observer.stop(args.sessionId);
28333
27194
  const { response, broadcast } = manager.newSession(args);
28334
27195
  return { response: { type: "session:info", ...response }, broadcast };
28335
27196
  };
28336
- const resume = async (frame, _client, ctx) => {
27197
+ const resume = async (frame) => {
28337
27198
  const args = SessionResumeArgs.parse(frame);
28338
- ensureSessionAccess(ctx, args.sessionId, "send");
28339
27199
  const { response, broadcast } = manager.resume(args);
28340
27200
  return { response: { type: "session:info", ...response }, broadcast };
28341
27201
  };
28342
- const observe = async (frame, _client, ctx) => {
27202
+ const observe = async (frame) => {
28343
27203
  const args = SessionObserveArgs.parse(frame);
28344
- ensureSessionAccess(ctx, args.sessionId, "read");
28345
27204
  const { response: file } = manager.get({ sessionId: args.sessionId });
28346
27205
  const sessionFile = file;
28347
27206
  manager.ensureSession(sessionFile);
@@ -28355,17 +27214,14 @@ function buildSessionHandlers(deps) {
28355
27214
  });
28356
27215
  return { response: { type: "session:observe", ok: true } };
28357
27216
  };
28358
- const events = async (frame, _client, ctx) => {
27217
+ const events = async (frame) => {
28359
27218
  const args = SessionEventsArgs.parse(frame);
28360
- ensureSessionAccess(ctx, args.sessionId, "read");
28361
27219
  const { response } = manager.getEvents(args);
28362
27220
  return { response: { type: "session:events", ...response } };
28363
27221
  };
28364
- const subscribe = async (frame, client, ctx) => {
28365
- if (typeof frame.sessionId === "string") {
28366
- ensureSessionAccess(ctx, frame.sessionId, "read");
27222
+ const subscribe = async (frame, client) => {
27223
+ if (typeof frame.sessionId === "string")
28367
27224
  addSubscription(client, frame.sessionId);
28368
- }
28369
27225
  return {
28370
27226
  response: { type: "subscribed", sessionId: frame.sessionId }
28371
27227
  };
@@ -28377,9 +27233,8 @@ function buildSessionHandlers(deps) {
28377
27233
  response: { type: "unsubscribed", sessionId: frame.sessionId }
28378
27234
  };
28379
27235
  };
28380
- const pin = async (frame, _client, ctx) => {
27236
+ const pin = async (frame) => {
28381
27237
  const args = SessionPinArgs.parse(frame);
28382
- ensureSessionAccess(ctx, args.sessionId, "send");
28383
27238
  const { response, broadcast } = manager.pin(args);
28384
27239
  return { response: { type: "session:info", ...response }, broadcast };
28385
27240
  };
@@ -28388,15 +27243,13 @@ function buildSessionHandlers(deps) {
28388
27243
  const { response, broadcast } = manager.reorderPins(args);
28389
27244
  return { response: { type: "session:reorderPins", ...response }, broadcast };
28390
27245
  };
28391
- const answerQuestion = async (frame, _client, ctx) => {
27246
+ const answerQuestion = async (frame) => {
28392
27247
  const args = AnswerQuestionArgs.parse(frame);
28393
- ensureSessionAccess(ctx, args.sessionId, "send");
28394
27248
  const { response, broadcast } = manager.answerQuestion(args);
28395
27249
  return { response: { type: "session:answerQuestion", ...response }, broadcast };
28396
27250
  };
28397
- const cancelQuestion = async (frame, _client, ctx) => {
27251
+ const cancelQuestion = async (frame) => {
28398
27252
  const args = CancelQuestionArgs.parse(frame);
28399
- ensureSessionAccess(ctx, args.sessionId, "send");
28400
27253
  const { response, broadcast } = await manager.cancelQuestion(args);
28401
27254
  return { response: { type: "session:cancelQuestion", ...response }, broadcast };
28402
27255
  };
@@ -28491,7 +27344,6 @@ function listRecentDirs(store, limit = 50) {
28491
27344
  }
28492
27345
 
28493
27346
  // src/handlers/history.ts
28494
- init_protocol();
28495
27347
  function buildHistoryHandlers(deps) {
28496
27348
  const { manager, history, store } = deps;
28497
27349
  const projects = async () => {
@@ -28503,27 +27355,17 @@ function buildHistoryHandlers(deps) {
28503
27355
  const sessions = await history.listSessions(args);
28504
27356
  return { response: { type: "history:list", sessions } };
28505
27357
  };
28506
- const read = async (frame, _client, ctx) => {
27358
+ const read = async (frame) => {
28507
27359
  const args = HistoryReadArgs.parse(frame);
28508
27360
  const { response: file } = manager.get({ sessionId: args.sessionId });
28509
- const f = file;
28510
- if (ctx && ctx.principal.kind === "guest") {
28511
- const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.capabilityId;
28512
- if (!ok) {
28513
- throw new ClawdError(
28514
- ERROR_CODES.UNAUTHORIZED,
28515
- `principal guest:${ctx.principal.id} cannot read history of session ${args.sessionId}`
28516
- );
28517
- }
28518
- }
28519
- if (!f.toolSessionId) {
27361
+ if (!file.toolSessionId) {
28520
27362
  return {
28521
27363
  response: { type: "history:read", messages: [], total: 0, offset: 0 }
28522
27364
  };
28523
27365
  }
28524
27366
  const res = await history.read({
28525
- cwd: f.cwd,
28526
- toolSessionId: f.toolSessionId,
27367
+ cwd: file.cwd,
27368
+ toolSessionId: file.toolSessionId,
28527
27369
  limit: args.limit,
28528
27370
  offset: args.offset
28529
27371
  });
@@ -28705,345 +27547,6 @@ function buildCapabilitiesHandlers(deps) {
28705
27547
  };
28706
27548
  }
28707
27549
 
28708
- // src/handlers/capability.ts
28709
- init_zod();
28710
- init_protocol();
28711
- var DeleteArgsSchema = external_exports.object({
28712
- capabilityId: external_exports.string().min(1)
28713
- }).strict();
28714
- function buildCapabilityHandlers(deps) {
28715
- const { manager, getShareBaseUrl } = deps;
28716
- const issue = async (frame) => {
28717
- const { type: _type, requestId: _requestId, ...rest } = frame;
28718
- const args = CapabilityIssueArgsSchema.parse(rest);
28719
- const { token, capability } = manager.issue({
28720
- displayName: args.displayName,
28721
- grants: args.grants,
28722
- ...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
28723
- ...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
28724
- });
28725
- const base = getShareBaseUrl().replace(/\/$/, "");
28726
- const shareUrl = `${base}/?token=${encodeURIComponent(token)}`;
28727
- return {
28728
- response: {
28729
- type: "capability:issued",
28730
- token,
28731
- shareUrl,
28732
- capability: stripSecretHash(capability)
28733
- }
28734
- };
28735
- };
28736
- const list = async () => {
28737
- return {
28738
- response: {
28739
- type: "capability:list",
28740
- capabilities: manager.list().map(stripSecretHash)
28741
- }
28742
- };
28743
- };
28744
- const del = async (frame) => {
28745
- const { type: _type, requestId: _requestId, ...rest } = frame;
28746
- const args = DeleteArgsSchema.parse(rest);
28747
- const result = manager.delete(args.capabilityId);
28748
- if (!result) {
28749
- throw new ClawdError(
28750
- ERROR_CODES.VALIDATION_ERROR,
28751
- `capability not found: ${args.capabilityId}`
28752
- );
28753
- }
28754
- return {
28755
- response: {
28756
- type: "capability:deleted",
28757
- capabilityId: args.capabilityId
28758
- }
28759
- };
28760
- };
28761
- const bindPeer = async (frame) => {
28762
- const { type: _type, requestId: _requestId, ...rest } = frame;
28763
- const args = CapabilityBindPeerArgsSchema.parse(rest);
28764
- try {
28765
- manager.recordPeerHello(args.capabilityId, {
28766
- ownerPrincipalId: args.peerOwnerId,
28767
- displayName: args.peerDisplayName,
28768
- now: Date.now()
28769
- });
28770
- } catch (e) {
28771
- throw new ClawdError(
28772
- ERROR_CODES.VALIDATION_ERROR,
28773
- `capability:bindPeer: ${e.message}`
28774
- );
28775
- }
28776
- return {
28777
- response: { type: "capability:bindPeer:ok", capabilityId: args.capabilityId }
28778
- };
28779
- };
28780
- return {
28781
- "capability:issue": issue,
28782
- "capability:list": list,
28783
- "capability:delete": del,
28784
- "capability:bindPeer": bindPeer
28785
- };
28786
- }
28787
-
28788
- // src/handlers/inbox.ts
28789
- init_protocol();
28790
- function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
28791
- if (ctx.principal.kind === "owner") {
28792
- if (!argsPeerOwnerId) {
28793
- throw new ClawdError(
28794
- ERROR_CODES.VALIDATION_ERROR,
28795
- "inbox: owner ctx requires peerOwnerId"
28796
- );
28797
- }
28798
- return argsPeerOwnerId;
28799
- }
28800
- if (!ctx.capabilityId) {
28801
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
28802
- }
28803
- const cap = capManager.findById(ctx.capabilityId);
28804
- if (!cap?.peerOwnerId) {
28805
- throw new ClawdError(
28806
- ERROR_CODES.UNAUTHORIZED,
28807
- `inbox: guest cap ${ctx.capabilityId} has no peerOwnerId (recordPeerHello not yet)`
28808
- );
28809
- }
28810
- if (argsPeerOwnerId && argsPeerOwnerId !== cap.peerOwnerId) {
28811
- throw new ClawdError(
28812
- ERROR_CODES.UNAUTHORIZED,
28813
- `inbox: guest args.peerOwnerId mismatch (cap.peerOwnerId=${cap.peerOwnerId}, args=${argsPeerOwnerId})`
28814
- );
28815
- }
28816
- return cap.peerOwnerId;
28817
- }
28818
- function buildInboxHandlers(deps) {
28819
- const { manager, capManager } = deps;
28820
- const postMessage = async (frame, _client, ctx) => {
28821
- const { type: _t, requestId: _r, ...rest } = frame;
28822
- const args = InboxPostMessageArgsSchema.parse(rest);
28823
- if (!ctx) {
28824
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
28825
- }
28826
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28827
- const message = manager.postMessage({
28828
- peerOwnerId,
28829
- senderPrincipalId: ctx.principal.id,
28830
- text: args.text,
28831
- id: args.id,
28832
- createdAt: args.createdAt
28833
- });
28834
- return {
28835
- response: { type: "inbox:postMessage:ok", message }
28836
- };
28837
- };
28838
- const list = async (frame, _client, ctx) => {
28839
- const { type: _t, requestId: _r, ...rest } = frame;
28840
- const args = InboxListArgsSchema.parse(rest);
28841
- if (!ctx) {
28842
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
28843
- }
28844
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28845
- const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
28846
- return {
28847
- response: { type: "inbox:list:ok", peerOwnerId, messages }
28848
- };
28849
- };
28850
- const markRead = async (frame, _client, ctx) => {
28851
- const { type: _t, requestId: _r, ...rest } = frame;
28852
- const args = InboxMarkReadArgsSchema.parse(rest);
28853
- if (!ctx) {
28854
- throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
28855
- }
28856
- const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
28857
- const updated = manager.markRead({
28858
- peerOwnerId,
28859
- principalId: ctx.principal.id,
28860
- upToCreatedAt: args.upToCreatedAt
28861
- });
28862
- return {
28863
- response: {
28864
- type: "inbox:markRead:ok",
28865
- peerOwnerId,
28866
- upToCreatedAt: args.upToCreatedAt,
28867
- updated
28868
- }
28869
- };
28870
- };
28871
- return {
28872
- "inbox:postMessage": postMessage,
28873
- "inbox:list": list,
28874
- "inbox:markRead": markRead
28875
- };
28876
- }
28877
-
28878
- // src/handlers/received-capability.ts
28879
- init_protocol();
28880
- function ensureOwner(ctx) {
28881
- if (!ctx || ctx.principal.kind !== "owner") {
28882
- throw new ClawdError(
28883
- ERROR_CODES.UNAUTHORIZED,
28884
- "UNAUTHORIZED: received-capability:* requires owner ctx"
28885
- );
28886
- }
28887
- }
28888
- function ensureGuestCtx(ctx) {
28889
- if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
28890
- throw new ClawdError(
28891
- ERROR_CODES.UNAUTHORIZED,
28892
- "UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
28893
- );
28894
- }
28895
- }
28896
- function buildReceivedCapabilityHandlers(deps) {
28897
- const now = deps.now ?? Date.now;
28898
- const list = async (_frame, _client, ctx) => {
28899
- ensureOwner(ctx);
28900
- return {
28901
- response: {
28902
- type: "received-capability:list:ok",
28903
- receivedCaps: deps.store.list()
28904
- }
28905
- };
28906
- };
28907
- const add = async (frame, _client, ctx) => {
28908
- ensureOwner(ctx);
28909
- const { type: _t, requestId: _r, ...rest } = frame;
28910
- const args = ReceivedCapabilityAddArgsSchema.parse(rest);
28911
- let conn;
28912
- try {
28913
- conn = await deps.connectRemote({
28914
- url: args.remoteUrl,
28915
- token: args.token,
28916
- selfPrincipalId: deps.selfPrincipalId(),
28917
- selfDisplayName: deps.selfDisplayName()
28918
- });
28919
- } catch (e) {
28920
- throw new ClawdError(
28921
- ERROR_CODES.VALIDATION_ERROR,
28922
- `INVITE_REMOTE_UNREACHABLE: ${e.message}`
28923
- );
28924
- }
28925
- try {
28926
- const wh = await conn.whoami();
28927
- const rc = {
28928
- ownerPrincipalId: wh.ownerId,
28929
- ownerDisplayName: wh.displayName,
28930
- remoteUrl: args.remoteUrl,
28931
- capabilityId: wh.capabilityId,
28932
- capabilityToken: args.token,
28933
- grants: wh.grants,
28934
- receivedAt: now()
28935
- };
28936
- deps.store.upsert(rc);
28937
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
28938
- return {
28939
- response: { type: "received-capability:add:ok", receivedCap: rc }
28940
- };
28941
- } finally {
28942
- try {
28943
- conn.close();
28944
- } catch {
28945
- }
28946
- }
28947
- };
28948
- const remove = async (frame, _client, ctx) => {
28949
- ensureOwner(ctx);
28950
- const { type: _t, requestId: _r, ...rest } = frame;
28951
- const args = ReceivedCapabilityRemoveArgsSchema.parse(rest);
28952
- deps.store.remove(args.ownerPrincipalId);
28953
- deps.broadcast({
28954
- type: "received-capability:removed",
28955
- ownerPrincipalId: args.ownerPrincipalId
28956
- });
28957
- return {
28958
- response: {
28959
- type: "received-capability:remove:ok",
28960
- ownerPrincipalId: args.ownerPrincipalId
28961
- }
28962
- };
28963
- };
28964
- const autoAttach = async (frame, _client, ctx) => {
28965
- ensureGuestCtx(ctx);
28966
- const { type: _t, requestId: _r, ...rest } = frame;
28967
- const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
28968
- const rc = {
28969
- ownerPrincipalId: args.ownerPrincipalId,
28970
- ownerDisplayName: args.ownerDisplayName,
28971
- remoteUrl: args.remoteUrl,
28972
- capabilityId: args.capabilityId,
28973
- capabilityToken: args.token,
28974
- grants: args.grants,
28975
- receivedAt: now()
28976
- };
28977
- deps.store.upsert(rc);
28978
- deps.broadcast({ type: "received-capability:added", receivedCap: rc });
28979
- return {
28980
- response: { type: "received-capability:autoAttach:ok" }
28981
- };
28982
- };
28983
- return {
28984
- "received-capability:list": list,
28985
- "received-capability:add": add,
28986
- "received-capability:remove": remove,
28987
- "received-capability:autoAttach": autoAttach
28988
- };
28989
- }
28990
-
28991
- // src/handlers/whoami.ts
28992
- init_protocol();
28993
- function buildWhoamiHandler(deps) {
28994
- return async (_frame, _client, ctx) => {
28995
- if (!ctx) {
28996
- throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
28997
- }
28998
- const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
28999
- let capability;
29000
- if (ctx.principal.kind === "owner") {
29001
- capability = {
29002
- id: deps.ownerPrincipalId,
29003
- displayName: deps.ownerDisplayName,
29004
- grants: ctx.grants,
29005
- issuedAt: 0,
29006
- usedCount: 0
29007
- };
29008
- } else {
29009
- if (!ctx.capabilityId) {
29010
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
29011
- }
29012
- const cap = deps.capabilityManager.findById(ctx.capabilityId);
29013
- if (!cap) {
29014
- throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
29015
- }
29016
- capability = stripSecretHash(cap);
29017
- }
29018
- const grantedPersonas = [];
29019
- const isGuest = ctx.principal.kind === "guest";
29020
- const hasWildcard = capability.grants.some((g2) => g2.resource.type === "*");
29021
- if (hasWildcard) {
29022
- for (const id of deps.personaStore.list()) {
29023
- const file = deps.personaStore.read(id);
29024
- if (!file) continue;
29025
- if (isGuest && !file.public) continue;
29026
- grantedPersonas.push({ id: file.personaId, displayName: file.label });
29027
- }
29028
- } else {
29029
- for (const g2 of capability.grants) {
29030
- if (g2.resource.type !== "persona") continue;
29031
- const file = deps.personaStore.read(g2.resource.id);
29032
- if (!file) continue;
29033
- grantedPersonas.push({ id: file.personaId, displayName: file.label });
29034
- }
29035
- }
29036
- return {
29037
- response: {
29038
- type: "whoami:ok",
29039
- owner,
29040
- capability,
29041
- grantedPersonas
29042
- }
29043
- };
29044
- };
29045
- }
29046
-
29047
27550
  // src/handlers/meta.ts
29048
27551
  var import_node_os13 = __toESM(require("os"), 1);
29049
27552
  init_protocol();
@@ -29107,14 +27610,9 @@ function buildPersonaHandlers(deps) {
29107
27610
  const persona = personaManager.create(args);
29108
27611
  return { response: { type: "persona:info", ...persona } };
29109
27612
  };
29110
- const list = async (_frame, _client, ctx) => {
29111
- const all = personaRegistry.list();
29112
- const isGuest = ctx?.principal.kind === "guest";
29113
- const personas = isGuest ? all.filter(
29114
- (p2) => p2.public && assertGrant(ctx.grants, { type: "persona", id: p2.personaId }, "read")
29115
- ) : all;
27613
+ const list = async () => {
29116
27614
  return {
29117
- response: { type: "persona:list", personas }
27615
+ response: { type: "persona:list", personas: personaRegistry.list() }
29118
27616
  };
29119
27617
  };
29120
27618
  const get = async (frame) => {
@@ -29152,12 +27650,26 @@ function buildPersonaHandlers(deps) {
29152
27650
  response: { type: "persona:deleted", personaId: args.personaId }
29153
27651
  };
29154
27652
  };
27653
+ const issueToken = async (frame) => {
27654
+ const args = PersonaIssueTokenArgsSchema.parse(frame);
27655
+ const { token, persona } = personaManager.issueToken(args.personaId, args.label);
27656
+ return {
27657
+ response: { type: "persona:tokenIssued", token, persona }
27658
+ };
27659
+ };
27660
+ const revokeToken = async (frame) => {
27661
+ const args = PersonaRevokeTokenArgsSchema.parse(frame);
27662
+ const persona = personaManager.revokeToken(args.personaId, args.token);
27663
+ return { response: { type: "persona:info", ...persona } };
27664
+ };
29155
27665
  return {
29156
27666
  "persona:create": create,
29157
27667
  "persona:list": list,
29158
27668
  "persona:get": get,
29159
27669
  "persona:update": update,
29160
- "persona:delete": del
27670
+ "persona:delete": del,
27671
+ "persona:issueToken": issueToken,
27672
+ "persona:revokeToken": revokeToken
29161
27673
  };
29162
27674
  }
29163
27675
 
@@ -29288,11 +27800,23 @@ function buildAttachmentHandlers(deps) {
29288
27800
  const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
29289
27801
  return { response: { type: "attachment.groupList", entries } };
29290
27802
  };
27803
+ const groupListPersona = async (frame) => {
27804
+ if (!deps.groupFileStore) {
27805
+ throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
27806
+ }
27807
+ const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
27808
+ if (!parsed.success) {
27809
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
27810
+ }
27811
+ const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
27812
+ return { response: { type: "attachment.groupListPersona", perSession } };
27813
+ };
29291
27814
  return {
29292
27815
  "attachment.signUrl": signUrl,
29293
27816
  "attachment.groupAdd": groupAdd,
29294
27817
  "attachment.groupRemove": groupRemove,
29295
- "attachment.groupList": groupList
27818
+ "attachment.groupList": groupList,
27819
+ "attachment.groupListPersona": groupListPersona
29296
27820
  };
29297
27821
  }
29298
27822
 
@@ -29310,136 +27834,10 @@ function buildMethodHandlers(deps) {
29310
27834
  personaManager: deps.personaManager,
29311
27835
  personaRegistry: deps.personaRegistry
29312
27836
  }),
29313
- ...buildCapabilityHandlers({
29314
- manager: deps.capabilityManager,
29315
- getShareBaseUrl: deps.getShareBaseUrl
29316
- }),
29317
- ...buildInboxHandlers({
29318
- manager: deps.inboxManager,
29319
- capManager: deps.capabilityManager
29320
- }),
29321
- ...buildReceivedCapabilityHandlers({
29322
- store: deps.receivedCapabilityStore,
29323
- connectRemote: deps.connectRemote,
29324
- broadcast: deps.broadcastToOwners,
29325
- selfPrincipalId: () => deps.ownerPrincipalId,
29326
- selfDisplayName: () => deps.ownerDisplayName
29327
- }),
29328
- whoami: buildWhoamiHandler({
29329
- ownerDisplayName: deps.ownerDisplayName,
29330
- ownerPrincipalId: deps.ownerPrincipalId,
29331
- personaStore: deps.personaStore,
29332
- capabilityManager: deps.capabilityManager
29333
- }),
29334
27837
  ...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
29335
27838
  };
29336
27839
  }
29337
27840
 
29338
- // src/handlers/method-grants.ts
29339
- var ADMIN_ANY = {
29340
- kind: "fixed",
29341
- resource: { type: "*" },
29342
- action: "admin"
29343
- };
29344
- var CAPABILITY_SCOPED = { kind: "capability-scoped" };
29345
- var METHOD_GRANT_MAP = {
29346
- // ---- public(meta-only,guest 也能调) ----
29347
- "info": { kind: "public" },
29348
- "ping": { kind: "public" },
29349
- // v2 Phase 6: whoami 是身份反查,任何 authed connection (owner / guest) 都能调;
29350
- // handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
29351
- // "查 capabilityManager 拿 caller 的 guest capability"。
29352
- "whoami": { kind: "public" },
29353
- // ---- capability platform(admin-only,本 PR 新增) ----
29354
- "capability:issue": ADMIN_ANY,
29355
- "capability:list": ADMIN_ANY,
29356
- "capability:delete": ADMIN_ANY,
29357
- "capability:bindPeer": ADMIN_ANY,
29358
- // ---- inbox: channel-based IM (capability platform v3) ----
29359
- // 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
29360
- // guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
29361
- "inbox:list": { kind: "public" },
29362
- "inbox:markRead": { kind: "public" },
29363
- "inbox:postMessage": { kind: "public" },
29364
- // ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
29365
- // owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
29366
- "received-capability:list": ADMIN_ANY,
29367
- "received-capability:add": ADMIN_ANY,
29368
- "received-capability:remove": ADMIN_ANY,
29369
- "received-capability:autoAttach": { kind: "public" },
29370
- // ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
29371
- // dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
29372
- // owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
29373
- "session:create": CAPABILITY_SCOPED,
29374
- "session:list": CAPABILITY_SCOPED,
29375
- "session:get": CAPABILITY_SCOPED,
29376
- "session:update": CAPABILITY_SCOPED,
29377
- "session:delete": CAPABILITY_SCOPED,
29378
- "session:send": CAPABILITY_SCOPED,
29379
- "session:stop": CAPABILITY_SCOPED,
29380
- "session:interrupt": CAPABILITY_SCOPED,
29381
- "session:rewind": CAPABILITY_SCOPED,
29382
- "session:rewind-diff": CAPABILITY_SCOPED,
29383
- "session:rewindable-message-ids": CAPABILITY_SCOPED,
29384
- "session:fork": CAPABILITY_SCOPED,
29385
- "session:new": CAPABILITY_SCOPED,
29386
- "session:resume": CAPABILITY_SCOPED,
29387
- "session:observe": CAPABILITY_SCOPED,
29388
- "session:events": CAPABILITY_SCOPED,
29389
- "session:subscribe": CAPABILITY_SCOPED,
29390
- "session:unsubscribe": CAPABILITY_SCOPED,
29391
- "session:pin": CAPABILITY_SCOPED,
29392
- "session:reorderPins": ADMIN_ANY,
29393
- // owner 全局操作,无 personaId 维度
29394
- "permission:respond": CAPABILITY_SCOPED,
29395
- "session:answerQuestion": CAPABILITY_SCOPED,
29396
- "session:cancelQuestion": CAPABILITY_SCOPED,
29397
- "history:projects": ADMIN_ANY,
29398
- "history:list": ADMIN_ANY,
29399
- // history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
29400
- // 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
29401
- // handler 内按 ctx.capabilityId === file.creatorPrincipalId 拦越权。
29402
- "history:read": CAPABILITY_SCOPED,
29403
- "history:subagents": ADMIN_ANY,
29404
- "history:subagent-read": ADMIN_ANY,
29405
- "history:recentDirs": ADMIN_ANY,
29406
- "workspace:list": ADMIN_ANY,
29407
- "workspace:read": ADMIN_ANY,
29408
- "skills:list": ADMIN_ANY,
29409
- "agents:list": ADMIN_ANY,
29410
- "git:root": ADMIN_ANY,
29411
- "git:branch": ADMIN_ANY,
29412
- "git:branches": ADMIN_ANY,
29413
- "capabilities:get": ADMIN_ANY,
29414
- "persona:create": ADMIN_ANY,
29415
- // persona:list 下沉到 CAPABILITY_SCOPED:guest 调用合法(用于 People 面板展开
29416
- // 对方分享的 personas),handler 端按 ctx + persona.public + assertGrant 过滤。
29417
- "persona:list": CAPABILITY_SCOPED,
29418
- "persona:get": ADMIN_ANY,
29419
- "persona:update": ADMIN_ANY,
29420
- "persona:delete": ADMIN_ANY,
29421
- "session:pty:input": ADMIN_ANY,
29422
- "session:pty:resize": ADMIN_ANY,
29423
- // file-sharing attachment.* RPC:dispatcher 用 admin-only 兜底(wire-level 拦),
29424
- // 实际只有 owner 能调(personal token 链路 2026-05-21 删除,HTTP Bearer 只识别 owner)
29425
- "attachment.signUrl": ADMIN_ANY,
29426
- "attachment.groupAdd": ADMIN_ANY,
29427
- "attachment.groupRemove": ADMIN_ANY,
29428
- "attachment.groupList": ADMIN_ANY
29429
- };
29430
- function computeGrantForFrame(method, frame) {
29431
- const rule = METHOD_GRANT_MAP[method];
29432
- if (!rule) return { kind: "public" };
29433
- if (rule.kind === "public") return { kind: "public" };
29434
- if (rule.kind === "capability-scoped") return { kind: "public" };
29435
- if (rule.kind === "fixed") {
29436
- return { kind: "check", resource: rule.resource, action: rule.action };
29437
- }
29438
- const picked = rule.pick(frame);
29439
- if (!picked) return { kind: "public" };
29440
- return { kind: "check", resource: picked.resource, action: picked.action };
29441
- }
29442
-
29443
27841
  // src/index.ts
29444
27842
  async function startDaemon(config) {
29445
27843
  const logger = createLogger({
@@ -29455,90 +27853,24 @@ async function startDaemon(config) {
29455
27853
  if (pre.status === "stale") {
29456
27854
  logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
29457
27855
  }
29458
- const authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
29459
27856
  let resolvedAuthToken = null;
27857
+ let authFile = null;
29460
27858
  if (config.authToken && config.authToken.trim()) {
29461
27859
  resolvedAuthToken = config.authToken.trim();
29462
27860
  } else if (config.tunnel) {
27861
+ authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
29463
27862
  resolvedAuthToken = authFile.token;
29464
27863
  }
29465
- const ownerPrincipalId = authFile.ownerPrincipalId;
29466
- const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
29467
27864
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
29468
- migrateLegacyFiles(config.dataDir);
29469
- const inboxStore = new InboxStore(config.dataDir);
29470
- const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
29471
- wsServer?.broadcastToOwners(frame);
29472
- });
29473
- const receivedCapabilityStore = new ReceivedCapabilityStore(config.dataDir);
29474
- receivedCapabilityStore.load();
29475
- const capabilityStore = new CapabilityStore(config.dataDir);
29476
- const capabilityRegistry = new CapabilityRegistry(capabilityStore);
29477
- const capabilityManager = new CapabilityManager(capabilityRegistry, {
29478
- onIssued: (cap, token) => {
29479
- wsServer?.broadcastToOwners({
29480
- type: "capability:tokenIssued",
29481
- capability: stripSecretHash(cap),
29482
- token
29483
- });
29484
- },
29485
- onDeleted: (cap) => {
29486
- const deletedAt = Date.now();
29487
- wsServer?.broadcastToOwners({
29488
- type: "capability:tokenDeleted",
29489
- capabilityId: cap.id,
29490
- deletedAt
29491
- });
29492
- wsServer?.closeConnectionsByCapability(cap.id);
29493
- const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
29494
- if (cleanup.removed.length > 0) {
29495
- logger.info("capability delete cascade: guest sessions removed", {
29496
- capabilityId: cap.id,
29497
- removedDirs: cleanup.removed
29498
- });
29499
- }
29500
- }
29501
- });
29502
- migrateDropPersonsDir(config.dataDir);
29503
27865
  let wsServer = null;
29504
27866
  const authGate = authMode === "first-message" ? new AuthGate({
29505
27867
  shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
29506
- // Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
29507
- // owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
29508
27868
  expectedToken: resolvedAuthToken,
29509
- authenticate: (t, selfPrincipalId, selfDisplayName) => {
29510
- return authenticate(t, {
29511
- isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
29512
- ownerPrincipalId,
29513
- ownerDisplayName,
29514
- capabilityRegistry,
29515
- selfPrincipalId,
29516
- selfDisplayName,
29517
- onGuestHello: (capId, ownerId, name) => {
29518
- capabilityManager.recordPeerHello(capId, {
29519
- ownerPrincipalId: ownerId,
29520
- displayName: name,
29521
- now: Date.now()
29522
- });
29523
- }
29524
- });
29525
- },
29526
- onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
29527
- buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
29528
27869
  closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
29529
27870
  sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
29530
27871
  }) : null;
29531
27872
  resetRegistry();
29532
- const migrateResult = migrateFlattenSessions({ dataDir: config.dataDir });
29533
- if (!migrateResult.skipped && (migrateResult.movedBare || migrateResult.movedVmOwner || migrateResult.archivedListener)) {
29534
- logger.info("sessions migration applied", {
29535
- movedBare: migrateResult.movedBare,
29536
- movedVmOwner: migrateResult.movedVmOwner,
29537
- archivedListener: migrateResult.archivedListener
29538
- });
29539
- }
29540
- const sessionStoreFactory = new SessionStoreFactory({ dataDir: config.dataDir });
29541
- const store = sessionStoreFactory.forBare();
27873
+ const store = new SessionStore({ dataDir: config.dataDir });
29542
27874
  const workspace = new WorkspaceBrowser();
29543
27875
  const skills = new SkillsScanner();
29544
27876
  const agents = new AgentsScanner();
@@ -29551,12 +27883,10 @@ async function startDaemon(config) {
29551
27883
  } else {
29552
27884
  logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
29553
27885
  }
27886
+ const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
29554
27887
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
29555
27888
  const manager = new SessionManager({
29556
27889
  store,
29557
- // Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
29558
- // 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
29559
- storeFactory: sessionStoreFactory,
29560
27890
  logger,
29561
27891
  getAdapter,
29562
27892
  historyReader: history,
@@ -29564,7 +27894,6 @@ async function startDaemon(config) {
29564
27894
  personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
29565
27895
  personaStore,
29566
27896
  ownerDisplayName,
29567
- ownerPrincipalId,
29568
27897
  mode: config.mode,
29569
27898
  broadcastFrame: (frame, target) => {
29570
27899
  if (target === "all") {
@@ -29700,37 +28029,18 @@ async function startDaemon(config) {
29700
28029
  // group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
29701
28030
  // 'persona/<pid>/owner',default 走 'default'。
29702
28031
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid)
29703
- },
29704
- // Task 1.9: capability:issue/list/revoke handler 依赖
29705
- capabilityManager,
29706
- // v2 Phase 5: capability:issue 返回的 shareUrl 用此 base URL 拼接。
29707
- // tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
29708
- getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
29709
- // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
29710
- ownerDisplayName,
29711
- // owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
29712
- ownerPrincipalId,
29713
- personaStore,
29714
- // capability handler 也用 (capability:issue 走 registry / capability:list)
29715
- capabilityRegistry,
29716
- // capability platform v3 inbox: handler 接 manager (post/list/markRead),
29717
- // cascade 接 store (list 统计 deletedInboxEvents).
29718
- inboxManager,
29719
- inboxStore,
29720
- // bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
29721
- receivedCapabilityStore,
29722
- // received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
29723
- broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
29724
- // received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
29725
- connectRemote
28032
+ }
29726
28033
  });
29727
28034
  const authResolver = new AuthContextResolver({
29728
- ownerToken: resolvedAuthToken
28035
+ ownerToken: resolvedAuthToken,
28036
+ personaRegistry
29729
28037
  });
29730
28038
  const httpRouter = createHttpRouter({
29731
28039
  authResolver,
29732
28040
  daemonVersion: version,
29733
28041
  logger,
28042
+ personaStore,
28043
+ groupFileStore,
29734
28044
  sessionStore: store,
29735
28045
  // /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
29736
28046
  // --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
@@ -29740,8 +28050,6 @@ async function startDaemon(config) {
29740
28050
  host: config.host,
29741
28051
  port: config.port,
29742
28052
  logger,
29743
- // broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
29744
- ownerPrincipalId,
29745
28053
  readyFrameBuilder: (ctx) => buildReadyFrame(
29746
28054
  {
29747
28055
  manager,
@@ -29757,29 +28065,6 @@ async function startDaemon(config) {
29757
28065
  ),
29758
28066
  protocolVersion: PROTOCOL_VERSION,
29759
28067
  authGate: authGate ?? void 0,
29760
- // noAuth 模式下仍验 capability token: 修 capability platform 漏洞 — 远端 client
29761
- // 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
29762
- // capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
29763
- // 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
29764
- tryVerifyCapabilityToken: (token, selfPrincipalId, selfDisplayName) => {
29765
- const v2 = capabilityRegistry.verifyToken(token);
29766
- if (!v2.ok) return null;
29767
- if (selfPrincipalId && selfDisplayName) {
29768
- try {
29769
- capabilityManager.recordPeerHello(v2.capability.id, {
29770
- ownerPrincipalId: selfPrincipalId,
29771
- displayName: selfDisplayName,
29772
- now: Date.now()
29773
- });
29774
- } catch {
29775
- }
29776
- }
29777
- return {
29778
- principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
29779
- grants: v2.capability.grants,
29780
- capabilityId: v2.capability.id
29781
- };
29782
- },
29783
28068
  // file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
29784
28069
  httpRequestHandler: httpRouter,
29785
28070
  // 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
@@ -29832,18 +28117,7 @@ async function startDaemon(config) {
29832
28117
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
29833
28118
  const handler = handlers[type];
29834
28119
  if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
29835
- const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
29836
- const verdict = computeGrantForFrame(type, frame);
29837
- if (verdict.kind === "check") {
29838
- const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
29839
- if (!ok) {
29840
- throw new ClawdError(
29841
- ERROR_CODES.UNAUTHORIZED,
29842
- `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
29843
- );
29844
- }
29845
- }
29846
- const result = await handler(frame, client, ctx);
28120
+ const result = await handler(frame, client);
29847
28121
  if (requestId && result.response) {
29848
28122
  client.send({ ...result.response, requestId });
29849
28123
  }
@@ -29966,13 +28240,6 @@ ${bar}
29966
28240
  manager
29967
28241
  };
29968
28242
  }
29969
- function migrateDropPersonsDir(dataDir) {
29970
- const dir = import_node_path27.default.join(dataDir, "persons");
29971
- try {
29972
- import_node_fs24.default.rmSync(dir, { recursive: true, force: true });
29973
- } catch {
29974
- }
29975
- }
29976
28243
 
29977
28244
  // src/cli.ts
29978
28245
  async function main() {