@clawos-dev/clawd 0.2.76 → 0.2.77-beta.145.522509b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +604 -2351
- 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
|
|
111
|
-
// 实际文件传输走 HTTP 路由(GET /files?p=&e=&s
|
|
109
|
+
// 全部管理类 RPC handler 入口 requireOwner(personal 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
|
-
//
|
|
117
|
-
|
|
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:
|
|
645
|
-
const fullPath = [...
|
|
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,
|
|
924
|
+
constructor(parent, value, path32, key) {
|
|
957
925
|
this._cachedPath = [];
|
|
958
926
|
this.parent = parent;
|
|
959
927
|
this.data = value;
|
|
960
|
-
this._path =
|
|
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
|
|
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
|
|
5563
|
-
_req.url = typeof
|
|
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(
|
|
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 <
|
|
5735
|
-
const char =
|
|
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
|
|
5867
|
-
const parts = parsePath(
|
|
5652
|
+
for (const path32 of paths) {
|
|
5653
|
+
const parts = parsePath(path32);
|
|
5868
5654
|
if (parts.includes("*")) {
|
|
5869
|
-
redactWildcardPath(obj, parts, censor,
|
|
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,
|
|
5955
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
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
|
|
5991
|
-
const parts = parsePath(
|
|
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(
|
|
6044
|
-
if (typeof
|
|
5829
|
+
function validatePath(path32) {
|
|
5830
|
+
if (typeof path32 !== "string") {
|
|
6045
5831
|
throw new Error("Paths must be (non-empty) strings");
|
|
6046
5832
|
}
|
|
6047
|
-
if (
|
|
5833
|
+
if (path32 === "") {
|
|
6048
5834
|
throw new Error("Invalid redaction path ()");
|
|
6049
5835
|
}
|
|
6050
|
-
if (
|
|
6051
|
-
throw new Error(`Invalid redaction path (${
|
|
5836
|
+
if (path32.includes("..")) {
|
|
5837
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
6052
5838
|
}
|
|
6053
|
-
if (
|
|
6054
|
-
throw new Error(`Invalid redaction path (${
|
|
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 <
|
|
6060
|
-
const char =
|
|
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 (${
|
|
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 (${
|
|
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
|
|
6087
|
-
validatePath(
|
|
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,
|
|
6256
|
-
return censor(value, [k2, ...
|
|
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
|
|
6260
|
+
var fs28 = require("fs");
|
|
6475
6261
|
var EventEmitter2 = require("events");
|
|
6476
6262
|
var inherits = require("util").inherits;
|
|
6477
|
-
var
|
|
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)
|
|
6532
|
-
const fd =
|
|
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
|
-
|
|
6325
|
+
fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
|
|
6540
6326
|
if (err) return fileOpened(err);
|
|
6541
|
-
|
|
6327
|
+
fs28.open(file, flags, mode, fileOpened);
|
|
6542
6328
|
});
|
|
6543
6329
|
} else {
|
|
6544
|
-
|
|
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 = () =>
|
|
6586
|
-
fsWrite = () =>
|
|
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
|
|
6381
|
+
return fs28.writeSync(this.fd, this._writingBuf);
|
|
6596
6382
|
}
|
|
6597
|
-
return
|
|
6383
|
+
return fs28.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6598
6384
|
};
|
|
6599
6385
|
fsWrite = () => {
|
|
6600
6386
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6601
|
-
return
|
|
6387
|
+
return fs28.write(this.fd, this._writingBuf, this.release);
|
|
6602
6388
|
}
|
|
6603
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ?
|
|
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
|
-
|
|
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 =
|
|
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) ?
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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"] ||
|
|
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:
|
|
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"] ||
|
|
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"] ||
|
|
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
|
|
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() +
|
|
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
|
|
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
|
-
|
|
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 +=
|
|
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 += `${
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 +=
|
|
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 += `${
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 +=
|
|
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 += `${
|
|
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
|
|
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,
|
|
8845
|
+
res += stringifyTypedArray(value, join4, maximumBreadth);
|
|
9060
8846
|
keys = keys.slice(value.length);
|
|
9061
8847
|
maximumPropertiesToStringify -= value.length;
|
|
9062
|
-
separator =
|
|
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 =
|
|
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 =
|
|
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(
|
|
10171
|
-
var last =
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
10401
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
10616
10402
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10617
|
-
if (!
|
|
10618
|
-
const entry = { path:
|
|
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 "
|
|
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
|
-
|
|
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
|
|
11208
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
11445
11209
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11446
|
-
if (!
|
|
11447
|
-
const out = { path:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
20946
|
-
|
|
20947
|
-
|
|
20948
|
-
|
|
20949
|
-
|
|
20950
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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
|
|
22492
|
-
//
|
|
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
|
|
22168
|
+
return file.ownerPersonaId ? { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" } : { kind: "default" };
|
|
22495
22169
|
}
|
|
22496
|
-
//
|
|
22497
|
-
//
|
|
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 =
|
|
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
|
-
//
|
|
22514
|
-
//
|
|
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
|
|
22556
|
-
if (
|
|
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 +
|
|
22573
|
-
//
|
|
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
|
|
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
|
|
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 →
|
|
22614
|
-
// - persona/
|
|
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
|
|
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 =
|
|
22407
|
+
const scope = args.ownerPersonaId ? { kind: "persona", personaId: args.ownerPersonaId, mode: "owner" } : { kind: "default" };
|
|
22779
22408
|
const written = this.storeFor(scope).write(file);
|
|
22780
22409
|
return { response: written, broadcast: [] };
|
|
22781
22410
|
}
|
|
@@ -22983,6 +22612,7 @@ var SessionManager = class {
|
|
|
22983
22612
|
const pty = tsid ? this.ptyTransports.get(tsid) : void 0;
|
|
22984
22613
|
if (pty) {
|
|
22985
22614
|
pty.write("\x1B");
|
|
22615
|
+
runner.input({ kind: "inject-events", events: [{ kind: "turn_end" }] });
|
|
22986
22616
|
return;
|
|
22987
22617
|
}
|
|
22988
22618
|
await runner.sendControlRequest("interrupt");
|
|
@@ -23215,18 +22845,28 @@ var SessionManager = class {
|
|
|
23215
22845
|
this.runners.set(file.sessionId, r);
|
|
23216
22846
|
return r;
|
|
23217
22847
|
}
|
|
23218
|
-
// ----------------
|
|
22848
|
+
// ---------------- persona / listener sub-session 专用 API ----------------
|
|
23219
22849
|
//
|
|
23220
|
-
//
|
|
23221
|
-
//
|
|
23222
|
-
// -
|
|
23223
|
-
// -
|
|
22850
|
+
// PersonaManager 是 SessionManager 之上的薄编排层,listener sub-session 的持久化
|
|
22851
|
+
// (SessionFile)仍由 SessionManager 持有。区别于普通 / owner session:
|
|
22852
|
+
// - SessionFile 落到 <dataDir>/sessions/<personaId>/listener/(拓扑分类)
|
|
22853
|
+
// - reducer state.subSessionMeta.idleKillEnabled = true(turn_end 自动 idle-kill)
|
|
22854
|
+
// - sessionId 由 PersonaManager 派生(personaId-<tokenHash>),不走自动 newSessionId
|
|
23224
22855
|
//
|
|
23225
|
-
//
|
|
22856
|
+
// 这些 API 接 SessionScope 参数(不再是单参 agentId)—— transport 始终明确传
|
|
22857
|
+
// {kind:'persona', personaId, mode:'listener'},与 owner / default 分类隔离。
|
|
23226
22858
|
/** 按 scope 读取 SessionFile;不存在返回 null */
|
|
23227
22859
|
readForScope(sessionId, scope) {
|
|
23228
22860
|
return this.storeFor(scope).read(sessionId);
|
|
23229
22861
|
}
|
|
22862
|
+
/**
|
|
22863
|
+
* 列出指定 persona 在指定 mode 下的所有 SessionFile。
|
|
22864
|
+
* persona:listSubSessions RPC 入口(mode='listener');
|
|
22865
|
+
* 路径下还未写过任何 session 时 list() 返回空数组(SessionStore.list 内部 ENOENT 处理)
|
|
22866
|
+
*/
|
|
22867
|
+
listPersonaSessions(personaId, mode) {
|
|
22868
|
+
return this.storeFor({ kind: "persona", personaId, mode }).list();
|
|
22869
|
+
}
|
|
23230
22870
|
/**
|
|
23231
22871
|
* 创建 sub-session 的 SessionFile + 准备 runner(不 spawn)。
|
|
23232
22872
|
* SubSessionMeta 不进 SessionFile schema,运行时由 scope → metaFromScope 派生。
|
|
@@ -23371,7 +23011,7 @@ var SessionManager = class {
|
|
|
23371
23011
|
return { response: { ok: true }, broadcast };
|
|
23372
23012
|
}
|
|
23373
23013
|
/**
|
|
23374
|
-
* 改 SessionFile.label
|
|
23014
|
+
* 改 SessionFile.label(用于 listener-bound chat:rename RPC)。
|
|
23375
23015
|
* 不存在抛 SESSION_NOT_FOUND。
|
|
23376
23016
|
*/
|
|
23377
23017
|
renameForScope(args) {
|
|
@@ -23386,7 +23026,7 @@ var SessionManager = class {
|
|
|
23386
23026
|
store.write({ ...file, label: args.label });
|
|
23387
23027
|
}
|
|
23388
23028
|
/**
|
|
23389
|
-
* 删除 scope
|
|
23029
|
+
* 删除 listener-scope sub-session(用于 chat:delete RPC)。
|
|
23390
23030
|
* - 若 runner 存在:走 reducer 'delete' command 收尾(SIGKILL + 文件 unlink + broadcast)
|
|
23391
23031
|
* - 若 runner 不存在:直接删 scope store 文件 + 广播 session:deleted
|
|
23392
23032
|
* 不存在抛 SESSION_NOT_FOUND。
|
|
@@ -23657,7 +23297,7 @@ var SessionManager = class {
|
|
|
23657
23297
|
|
|
23658
23298
|
// src/persona/store.ts
|
|
23659
23299
|
var fs6 = __toESM(require("fs"), 1);
|
|
23660
|
-
var
|
|
23300
|
+
var path7 = __toESM(require("path"), 1);
|
|
23661
23301
|
init_protocol();
|
|
23662
23302
|
var DEFAULT_SETTINGS = {
|
|
23663
23303
|
permissions: {
|
|
@@ -23686,13 +23326,13 @@ var PersonaStore = class {
|
|
|
23686
23326
|
}
|
|
23687
23327
|
root;
|
|
23688
23328
|
personaDir(personaId) {
|
|
23689
|
-
return
|
|
23329
|
+
return path7.join(this.root, safeFileName(personaId));
|
|
23690
23330
|
}
|
|
23691
23331
|
metaPath(personaId) {
|
|
23692
|
-
return
|
|
23332
|
+
return path7.join(this.personaDir(personaId), ".clawd", "persona.json");
|
|
23693
23333
|
}
|
|
23694
23334
|
claudeMdPath(personaId) {
|
|
23695
|
-
return
|
|
23335
|
+
return path7.join(this.personaDir(personaId), "CLAUDE.md");
|
|
23696
23336
|
}
|
|
23697
23337
|
/**
|
|
23698
23338
|
* Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
|
|
@@ -23702,11 +23342,11 @@ var PersonaStore = class {
|
|
|
23702
23342
|
* 加载 persona 人格,只有 listener 多一层 OS sandbox。
|
|
23703
23343
|
*/
|
|
23704
23344
|
sandboxSettingsPath(personaId) {
|
|
23705
|
-
return
|
|
23345
|
+
return path7.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
|
|
23706
23346
|
}
|
|
23707
23347
|
write(persona, personality) {
|
|
23708
23348
|
const dir = this.personaDir(persona.personaId);
|
|
23709
|
-
fs6.mkdirSync(
|
|
23349
|
+
fs6.mkdirSync(path7.join(dir, ".clawd"), { recursive: true });
|
|
23710
23350
|
this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
|
|
23711
23351
|
this.atomicWrite(
|
|
23712
23352
|
this.sandboxSettingsPath(persona.personaId),
|
|
@@ -23724,10 +23364,6 @@ var PersonaStore = class {
|
|
|
23724
23364
|
const p2 = this.metaPath(personaId);
|
|
23725
23365
|
if (!fs6.existsSync(p2)) return null;
|
|
23726
23366
|
const raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
|
|
23727
|
-
if (raw && typeof raw === "object" && "tokenMap" in raw) {
|
|
23728
|
-
delete raw.tokenMap;
|
|
23729
|
-
this.atomicWrite(p2, JSON.stringify(raw, null, 2));
|
|
23730
|
-
}
|
|
23731
23367
|
return PersonaFileSchema.parse(raw);
|
|
23732
23368
|
}
|
|
23733
23369
|
has(personaId) {
|
|
@@ -23753,14 +23389,14 @@ var PersonaStore = class {
|
|
|
23753
23389
|
}
|
|
23754
23390
|
/** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
|
|
23755
23391
|
skillsDir(personaId) {
|
|
23756
|
-
return
|
|
23392
|
+
return path7.join(this.personaDir(personaId), ".claude", "skills");
|
|
23757
23393
|
}
|
|
23758
23394
|
/**
|
|
23759
23395
|
* Claude Code 项目级 settings 路径:`<personaDir>/.claude/settings.json`。
|
|
23760
23396
|
* 这里只读 `enabledPlugins` 字段,由 owner 通过 CC `/plugin` 之类命令维护,daemon 不写。
|
|
23761
23397
|
*/
|
|
23762
23398
|
claudeSettingsPath(personaId) {
|
|
23763
|
-
return
|
|
23399
|
+
return path7.join(this.personaDir(personaId), ".claude", "settings.json");
|
|
23764
23400
|
}
|
|
23765
23401
|
/**
|
|
23766
23402
|
* 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
|
|
@@ -23794,7 +23430,7 @@ var PersonaStore = class {
|
|
|
23794
23430
|
list() {
|
|
23795
23431
|
if (!fs6.existsSync(this.root)) return [];
|
|
23796
23432
|
return fs6.readdirSync(this.root).filter((name) => {
|
|
23797
|
-
return fs6.existsSync(
|
|
23433
|
+
return fs6.existsSync(path7.join(this.root, name, ".clawd", "persona.json"));
|
|
23798
23434
|
});
|
|
23799
23435
|
}
|
|
23800
23436
|
remove(personaId) {
|
|
@@ -23819,19 +23455,12 @@ var PersonaRegistry = class {
|
|
|
23819
23455
|
}
|
|
23820
23456
|
store;
|
|
23821
23457
|
cache = /* @__PURE__ */ new Map();
|
|
23822
|
-
/**
|
|
23823
|
-
* 从 store 全量重建缓存(boot 时 + 测试 setup 时)。
|
|
23824
|
-
* 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
|
|
23825
|
-
* try/catch 隔离,让其它 persona 仍能加载。
|
|
23826
|
-
*/
|
|
23458
|
+
/** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
|
|
23827
23459
|
reload() {
|
|
23828
23460
|
this.cache.clear();
|
|
23829
23461
|
for (const id of this.store.list()) {
|
|
23830
|
-
|
|
23831
|
-
|
|
23832
|
-
if (p2) this.cache.set(p2.personaId, p2);
|
|
23833
|
-
} catch {
|
|
23834
|
-
}
|
|
23462
|
+
const p2 = this.store.read(id);
|
|
23463
|
+
if (p2) this.cache.set(p2.personaId, p2);
|
|
23835
23464
|
}
|
|
23836
23465
|
}
|
|
23837
23466
|
get(personaId) {
|
|
@@ -23847,6 +23476,21 @@ var PersonaRegistry = class {
|
|
|
23847
23476
|
list() {
|
|
23848
23477
|
return Array.from(this.cache.values());
|
|
23849
23478
|
}
|
|
23479
|
+
/**
|
|
23480
|
+
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23481
|
+
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
23482
|
+
* persona 数量通常 < 100,性能可忽略。
|
|
23483
|
+
*/
|
|
23484
|
+
findByToken(token) {
|
|
23485
|
+
if (!token) return null;
|
|
23486
|
+
for (const persona of this.cache.values()) {
|
|
23487
|
+
if (!persona.public) continue;
|
|
23488
|
+
const entry = persona.tokenMap?.[token];
|
|
23489
|
+
if (!entry || entry.revoked) continue;
|
|
23490
|
+
return { personaId: persona.personaId, label: entry.label };
|
|
23491
|
+
}
|
|
23492
|
+
return null;
|
|
23493
|
+
}
|
|
23850
23494
|
};
|
|
23851
23495
|
|
|
23852
23496
|
// src/persona/manager.ts
|
|
@@ -24140,6 +23784,9 @@ var PersonaManager = class {
|
|
|
24140
23784
|
model: args.model,
|
|
24141
23785
|
public: args.public ?? false,
|
|
24142
23786
|
iconKey: args.iconKey,
|
|
23787
|
+
// 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
|
|
23788
|
+
// file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
|
|
23789
|
+
tokenMap: {},
|
|
24143
23790
|
createdAt: now,
|
|
24144
23791
|
updatedAt: now
|
|
24145
23792
|
};
|
|
@@ -24189,22 +23836,52 @@ var PersonaManager = class {
|
|
|
24189
23836
|
}
|
|
24190
23837
|
/**
|
|
24191
23838
|
* 删除 persona。
|
|
24192
|
-
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/
|
|
24193
|
-
* Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
|
|
24194
|
-
* .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
|
|
24195
|
-
*
|
|
24196
|
-
* ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
|
|
24197
|
-
* ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
|
|
24198
|
-
* 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
|
|
24199
|
-
* 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
|
|
24200
|
-
* 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
|
|
24201
|
-
* force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
|
|
24202
|
-
* "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
|
|
23839
|
+
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
|
|
24203
23840
|
*/
|
|
24204
23841
|
delete(personaId) {
|
|
24205
23842
|
this.deps.store.remove(personaId);
|
|
24206
23843
|
this.deps.registry.remove(personaId);
|
|
24207
23844
|
}
|
|
23845
|
+
/**
|
|
23846
|
+
* 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
|
|
23847
|
+
* 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
|
|
23848
|
+
*/
|
|
23849
|
+
issueToken(personaId, label) {
|
|
23850
|
+
const existing = this.deps.registry.get(personaId);
|
|
23851
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23852
|
+
const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
23853
|
+
const entry = {
|
|
23854
|
+
label,
|
|
23855
|
+
issuedAt: Date.now(),
|
|
23856
|
+
revoked: false
|
|
23857
|
+
};
|
|
23858
|
+
const updated = {
|
|
23859
|
+
...existing,
|
|
23860
|
+
tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
|
|
23861
|
+
updatedAt: Date.now()
|
|
23862
|
+
};
|
|
23863
|
+
this.deps.store.writeMeta(updated);
|
|
23864
|
+
this.deps.registry.set(updated);
|
|
23865
|
+
return { token, persona: updated };
|
|
23866
|
+
}
|
|
23867
|
+
/** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
|
|
23868
|
+
revokeToken(personaId, token) {
|
|
23869
|
+
const existing = this.deps.registry.get(personaId);
|
|
23870
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23871
|
+
const tokenEntry = existing.tokenMap?.[token];
|
|
23872
|
+
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
23873
|
+
const updated = {
|
|
23874
|
+
...existing,
|
|
23875
|
+
tokenMap: {
|
|
23876
|
+
...existing.tokenMap ?? {},
|
|
23877
|
+
[token]: { ...tokenEntry, revoked: true }
|
|
23878
|
+
},
|
|
23879
|
+
updatedAt: Date.now()
|
|
23880
|
+
};
|
|
23881
|
+
this.deps.store.writeMeta(updated);
|
|
23882
|
+
this.deps.registry.set(updated);
|
|
23883
|
+
return updated;
|
|
23884
|
+
}
|
|
24208
23885
|
/**
|
|
24209
23886
|
* label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
|
|
24210
23887
|
* base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
|
|
@@ -24224,7 +23901,7 @@ var PersonaManager = class {
|
|
|
24224
23901
|
|
|
24225
23902
|
// src/persona/seed.ts
|
|
24226
23903
|
var fs8 = __toESM(require("fs"), 1);
|
|
24227
|
-
var
|
|
23904
|
+
var path9 = __toESM(require("path"), 1);
|
|
24228
23905
|
var import_node_url = require("url");
|
|
24229
23906
|
var import_meta = {};
|
|
24230
23907
|
var DEFAULT_PERSONAS = [
|
|
@@ -24260,14 +23937,14 @@ var DEFAULT_PERSONAS = [
|
|
|
24260
23937
|
function findDefaultsRoot() {
|
|
24261
23938
|
const candidates = [];
|
|
24262
23939
|
try {
|
|
24263
|
-
const here =
|
|
24264
|
-
candidates.push(
|
|
24265
|
-
candidates.push(
|
|
23940
|
+
const here = path9.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
23941
|
+
candidates.push(path9.resolve(here, "defaults"));
|
|
23942
|
+
candidates.push(path9.resolve(here, "persona-defaults"));
|
|
24266
23943
|
} catch {
|
|
24267
23944
|
}
|
|
24268
23945
|
if (process.argv[1]) {
|
|
24269
|
-
const argvDir =
|
|
24270
|
-
candidates.push(
|
|
23946
|
+
const argvDir = path9.dirname(process.argv[1]);
|
|
23947
|
+
candidates.push(path9.resolve(argvDir, "persona-defaults"));
|
|
24271
23948
|
}
|
|
24272
23949
|
for (const c of candidates) {
|
|
24273
23950
|
try {
|
|
@@ -24284,7 +23961,7 @@ function seedDefaultPersonas(args) {
|
|
|
24284
23961
|
args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
|
|
24285
23962
|
continue;
|
|
24286
23963
|
}
|
|
24287
|
-
const bundleDir =
|
|
23964
|
+
const bundleDir = path9.join(args.defaultsRoot, entry.personaId);
|
|
24288
23965
|
if (!fs8.existsSync(bundleDir)) {
|
|
24289
23966
|
args.logger.warn("persona.seed.skip", {
|
|
24290
23967
|
personaId: entry.personaId,
|
|
@@ -24293,7 +23970,7 @@ function seedDefaultPersonas(args) {
|
|
|
24293
23970
|
});
|
|
24294
23971
|
continue;
|
|
24295
23972
|
}
|
|
24296
|
-
const claudeMdPath =
|
|
23973
|
+
const claudeMdPath = path9.join(bundleDir, "CLAUDE.md");
|
|
24297
23974
|
if (!fs8.existsSync(claudeMdPath)) {
|
|
24298
23975
|
args.logger.warn("persona.seed.skip", {
|
|
24299
23976
|
personaId: entry.personaId,
|
|
@@ -24310,6 +23987,7 @@ function seedDefaultPersonas(args) {
|
|
|
24310
23987
|
model: entry.model,
|
|
24311
23988
|
public: entry.public,
|
|
24312
23989
|
iconKey: entry.iconKey,
|
|
23990
|
+
tokenMap: {},
|
|
24313
23991
|
createdAt: now,
|
|
24314
23992
|
updatedAt: now
|
|
24315
23993
|
};
|
|
@@ -24321,8 +23999,8 @@ function seedDefaultPersonas(args) {
|
|
|
24321
23999
|
function copyBundleExtras(srcDir, dstDir) {
|
|
24322
24000
|
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
24323
24001
|
if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
|
|
24324
|
-
const srcPath =
|
|
24325
|
-
const dstPath =
|
|
24002
|
+
const srcPath = path9.join(srcDir, entry.name);
|
|
24003
|
+
const dstPath = path9.join(dstDir, entry.name);
|
|
24326
24004
|
if (entry.isDirectory()) {
|
|
24327
24005
|
fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
|
|
24328
24006
|
} else if (entry.isFile()) {
|
|
@@ -25663,7 +25341,6 @@ var import_sender = __toESM(require_sender(), 1);
|
|
|
25663
25341
|
var import_subprotocol = __toESM(require_subprotocol(), 1);
|
|
25664
25342
|
var import_websocket = __toESM(require_websocket(), 1);
|
|
25665
25343
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
25666
|
-
var wrapper_default = import_websocket.default;
|
|
25667
25344
|
|
|
25668
25345
|
// src/transport/local-ws-server.ts
|
|
25669
25346
|
var import_node_http = __toESM(require("http"), 1);
|
|
@@ -25678,9 +25355,6 @@ var LocalWsServer = class {
|
|
|
25678
25355
|
httpServer = null;
|
|
25679
25356
|
frameHandler = null;
|
|
25680
25357
|
clients = /* @__PURE__ */ new Map();
|
|
25681
|
-
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
25682
|
-
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
25683
|
-
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
25684
25358
|
logger;
|
|
25685
25359
|
pingIntervalMs;
|
|
25686
25360
|
async start() {
|
|
@@ -25771,49 +25445,6 @@ var LocalWsServer = class {
|
|
|
25771
25445
|
this.safeSend(c.ws, frame);
|
|
25772
25446
|
}
|
|
25773
25447
|
}
|
|
25774
|
-
// Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
|
|
25775
|
-
// 用于 capability:tokenIssued / capability:tokenDeleted 等 owner-only push frame——
|
|
25776
|
-
// guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
|
|
25777
|
-
broadcastToOwners(frame) {
|
|
25778
|
-
const gate = this.opts.authGate;
|
|
25779
|
-
for (const c of this.clients.values()) {
|
|
25780
|
-
if (gate && !gate.isAuthed(c.handle.id)) continue;
|
|
25781
|
-
if (c.ctx && c.ctx.principal.kind !== "owner") continue;
|
|
25782
|
-
this.safeSend(c.ws, frame);
|
|
25783
|
-
}
|
|
25784
|
-
}
|
|
25785
|
-
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
25786
|
-
// - 所有 owner ws (owner 看自家所有 channel)
|
|
25787
|
-
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
25788
|
-
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
25789
|
-
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
25790
|
-
this.broadcastToOwners(frame);
|
|
25791
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25792
|
-
if (!set) return;
|
|
25793
|
-
for (const id of set) {
|
|
25794
|
-
const c = this.clients.get(id);
|
|
25795
|
-
if (!c) continue;
|
|
25796
|
-
this.safeSend(c.ws, frame);
|
|
25797
|
-
}
|
|
25798
|
-
}
|
|
25799
|
-
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
25800
|
-
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
25801
|
-
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
25802
|
-
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
25803
|
-
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
25804
|
-
broadcastToPrincipal(principalId, frame) {
|
|
25805
|
-
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
25806
|
-
this.broadcastToOwners(frame);
|
|
25807
|
-
return;
|
|
25808
|
-
}
|
|
25809
|
-
const set = this.capabilityIdToClients.get(principalId);
|
|
25810
|
-
if (!set) return;
|
|
25811
|
-
for (const id of set) {
|
|
25812
|
-
const c = this.clients.get(id);
|
|
25813
|
-
if (!c) continue;
|
|
25814
|
-
this.safeSend(c.ws, frame);
|
|
25815
|
-
}
|
|
25816
|
-
}
|
|
25817
25448
|
firstSubscriber(sessionId) {
|
|
25818
25449
|
for (const c of this.clients.values()) {
|
|
25819
25450
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -25835,40 +25466,6 @@ var LocalWsServer = class {
|
|
|
25835
25466
|
if (!c) return;
|
|
25836
25467
|
this.safeSend(c.ws, frame);
|
|
25837
25468
|
}
|
|
25838
|
-
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
25839
|
-
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
25840
|
-
attachClientContext(clientId, ctx) {
|
|
25841
|
-
const c = this.clients.get(clientId);
|
|
25842
|
-
if (!c) return;
|
|
25843
|
-
c.ctx = ctx;
|
|
25844
|
-
if (ctx.capabilityId) {
|
|
25845
|
-
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
25846
|
-
if (!set) {
|
|
25847
|
-
set = /* @__PURE__ */ new Set();
|
|
25848
|
-
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
25849
|
-
}
|
|
25850
|
-
set.add(clientId);
|
|
25851
|
-
}
|
|
25852
|
-
}
|
|
25853
|
-
// 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
|
|
25854
|
-
getClientContext(clientId) {
|
|
25855
|
-
return this.clients.get(clientId)?.ctx ?? null;
|
|
25856
|
-
}
|
|
25857
|
-
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
25858
|
-
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
25859
|
-
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
25860
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25861
|
-
if (!set) return;
|
|
25862
|
-
const ids = [...set];
|
|
25863
|
-
for (const id of ids) {
|
|
25864
|
-
const c = this.clients.get(id);
|
|
25865
|
-
if (!c) continue;
|
|
25866
|
-
try {
|
|
25867
|
-
c.ws.close(code, reason);
|
|
25868
|
-
} catch {
|
|
25869
|
-
}
|
|
25870
|
-
}
|
|
25871
|
-
}
|
|
25872
25469
|
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25873
25470
|
routeConnection(socket, req) {
|
|
25874
25471
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
@@ -25903,7 +25500,7 @@ var LocalWsServer = class {
|
|
|
25903
25500
|
} catch {
|
|
25904
25501
|
}
|
|
25905
25502
|
}, this.pingIntervalMs);
|
|
25906
|
-
this.clients.set(id, { handle, ws: socket, pingTimer
|
|
25503
|
+
this.clients.set(id, { handle, ws: socket, pingTimer });
|
|
25907
25504
|
this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
|
|
25908
25505
|
const authGate = this.opts.authGate;
|
|
25909
25506
|
let authed = true;
|
|
@@ -25927,12 +25524,6 @@ var LocalWsServer = class {
|
|
|
25927
25524
|
socket.on("close", () => {
|
|
25928
25525
|
const c = this.clients.get(id);
|
|
25929
25526
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
25930
|
-
const capId = c?.ctx?.capabilityId;
|
|
25931
|
-
if (capId) {
|
|
25932
|
-
const set = this.capabilityIdToClients.get(capId);
|
|
25933
|
-
set?.delete(id);
|
|
25934
|
-
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
25935
|
-
}
|
|
25936
25527
|
this.clients.delete(id);
|
|
25937
25528
|
authGate?.unregister(id);
|
|
25938
25529
|
this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
|
|
@@ -25965,17 +25556,6 @@ var LocalWsServer = class {
|
|
|
25965
25556
|
return;
|
|
25966
25557
|
}
|
|
25967
25558
|
} else if (frame.type === "auth") {
|
|
25968
|
-
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
25969
|
-
const selfPrincipalId = typeof frame.selfPrincipalId === "string" ? frame.selfPrincipalId ?? "" : "";
|
|
25970
|
-
const selfDisplayName = typeof frame.selfDisplayName === "string" ? frame.selfDisplayName ?? "" : "";
|
|
25971
|
-
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
25972
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(
|
|
25973
|
-
token,
|
|
25974
|
-
selfPrincipalId || void 0,
|
|
25975
|
-
selfDisplayName || void 0
|
|
25976
|
-
);
|
|
25977
|
-
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
25978
|
-
}
|
|
25979
25559
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
25980
25560
|
return;
|
|
25981
25561
|
}
|
|
@@ -26092,31 +25672,15 @@ var AuthGate = class {
|
|
|
26092
25672
|
this.markFailed(handle.id);
|
|
26093
25673
|
return frame.type === "auth" ? "consumed" : "reject";
|
|
26094
25674
|
}
|
|
26095
|
-
|
|
26096
|
-
|
|
26097
|
-
|
|
26098
|
-
|
|
26099
|
-
|
|
26100
|
-
|
|
26101
|
-
);
|
|
26102
|
-
|
|
26103
|
-
|
|
26104
|
-
this.markFailed(handle.id);
|
|
26105
|
-
return "consumed";
|
|
26106
|
-
}
|
|
26107
|
-
ctx = r.context;
|
|
26108
|
-
} else {
|
|
26109
|
-
if (!this.opts.expectedToken) {
|
|
26110
|
-
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
26111
|
-
this.markFailed(handle.id);
|
|
26112
|
-
return "consumed";
|
|
26113
|
-
}
|
|
26114
|
-
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
26115
|
-
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
26116
|
-
this.markFailed(handle.id);
|
|
26117
|
-
return "consumed";
|
|
26118
|
-
}
|
|
26119
|
-
ctx = this.opts.buildOwnerContext?.() ?? null;
|
|
25675
|
+
if (!this.opts.expectedToken) {
|
|
25676
|
+
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
25677
|
+
this.markFailed(handle.id);
|
|
25678
|
+
return "consumed";
|
|
25679
|
+
}
|
|
25680
|
+
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
25681
|
+
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
25682
|
+
this.markFailed(handle.id);
|
|
25683
|
+
return "consumed";
|
|
26120
25684
|
}
|
|
26121
25685
|
if (st.timer != null) {
|
|
26122
25686
|
const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -26124,7 +25688,6 @@ var AuthGate = class {
|
|
|
26124
25688
|
}
|
|
26125
25689
|
st.authed = true;
|
|
26126
25690
|
st.timer = null;
|
|
26127
|
-
if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
|
|
26128
25691
|
this.opts.sendOk(handle, { type: "auth:ok" });
|
|
26129
25692
|
return "consumed";
|
|
26130
25693
|
}
|
|
@@ -26160,7 +25723,7 @@ var AuthContextResolver = class {
|
|
|
26160
25723
|
}
|
|
26161
25724
|
opts;
|
|
26162
25725
|
/**
|
|
26163
|
-
* 从 `Authorization` 头解析。null = 无凭证 /
|
|
25726
|
+
* 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
|
|
26164
25727
|
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
26165
25728
|
*/
|
|
26166
25729
|
resolveFromHeader(authHeader, remoteAddress) {
|
|
@@ -26170,6 +25733,15 @@ var AuthContextResolver = class {
|
|
|
26170
25733
|
if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
|
|
26171
25734
|
return { role: "owner", isLoopback };
|
|
26172
25735
|
}
|
|
25736
|
+
const personalHit = this.opts.personaRegistry.findByToken(token);
|
|
25737
|
+
if (personalHit) {
|
|
25738
|
+
return {
|
|
25739
|
+
role: "personal",
|
|
25740
|
+
personaId: personalHit.personaId,
|
|
25741
|
+
label: personalHit.label,
|
|
25742
|
+
isLoopback
|
|
25743
|
+
};
|
|
25744
|
+
}
|
|
26173
25745
|
return null;
|
|
26174
25746
|
}
|
|
26175
25747
|
};
|
|
@@ -26191,753 +25763,206 @@ function constantTimeEqual2(a, b2) {
|
|
|
26191
25763
|
return diff2 === 0;
|
|
26192
25764
|
}
|
|
26193
25765
|
|
|
26194
|
-
//
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
// src/transport/connection-context.ts
|
|
26198
|
-
function ownerContext(ownerPrincipalId, displayName) {
|
|
26199
|
-
return {
|
|
26200
|
-
principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
|
|
26201
|
-
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
26202
|
-
};
|
|
26203
|
-
}
|
|
26204
|
-
function guestContext(cap) {
|
|
26205
|
-
return {
|
|
26206
|
-
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
26207
|
-
grants: cap.grants,
|
|
26208
|
-
capabilityId: cap.id
|
|
26209
|
-
};
|
|
26210
|
-
}
|
|
26211
|
-
function authenticate(token, deps) {
|
|
26212
|
-
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
26213
|
-
if (deps.isOwnerToken(token)) {
|
|
26214
|
-
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
26215
|
-
}
|
|
26216
|
-
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
26217
|
-
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
26218
|
-
if (!v2.ok) {
|
|
26219
|
-
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
26220
|
-
return { ok: false, code: v2.code };
|
|
26221
|
-
}
|
|
26222
|
-
if (deps.selfPrincipalId && deps.selfDisplayName && deps.onGuestHello) {
|
|
26223
|
-
try {
|
|
26224
|
-
deps.onGuestHello(v2.capability.id, deps.selfPrincipalId, deps.selfDisplayName);
|
|
26225
|
-
} catch (e) {
|
|
26226
|
-
if (e.message === "TOKEN_SHARED_LEAK") {
|
|
26227
|
-
return { ok: false, code: "TOKEN_REVOKED" };
|
|
26228
|
-
}
|
|
26229
|
-
throw e;
|
|
26230
|
-
}
|
|
26231
|
-
}
|
|
26232
|
-
return { ok: true, context: guestContext(v2.capability) };
|
|
26233
|
-
}
|
|
25766
|
+
// src/transport/http-router.ts
|
|
25767
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
25768
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
26234
25769
|
|
|
26235
|
-
// src/
|
|
26236
|
-
var
|
|
26237
|
-
var
|
|
26238
|
-
var
|
|
26239
|
-
|
|
26240
|
-
var
|
|
26241
|
-
constructor(dataDir) {
|
|
26242
|
-
this.dataDir = dataDir;
|
|
26243
|
-
fs15.mkdirSync(dataDir, { recursive: true });
|
|
26244
|
-
this.cache = this.readFromDisk();
|
|
26245
|
-
}
|
|
25770
|
+
// src/attachment/group.ts
|
|
25771
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
25772
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
25773
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
25774
|
+
init_protocol();
|
|
25775
|
+
var GroupFileStore = class {
|
|
26246
25776
|
dataDir;
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
25777
|
+
logger;
|
|
25778
|
+
cache = /* @__PURE__ */ new Map();
|
|
25779
|
+
constructor(opts) {
|
|
25780
|
+
this.dataDir = opts.dataDir;
|
|
25781
|
+
this.logger = opts.logger;
|
|
26250
25782
|
}
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
if (idx >= 0) {
|
|
26254
|
-
this.cache[idx] = cap;
|
|
26255
|
-
} else {
|
|
26256
|
-
this.cache.push(cap);
|
|
26257
|
-
}
|
|
26258
|
-
this.flush();
|
|
25783
|
+
rootForScope(scope) {
|
|
25784
|
+
return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
26259
25785
|
}
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
this.cache = next;
|
|
26264
|
-
this.flush();
|
|
25786
|
+
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
25787
|
+
filePath(scope, sessionId) {
|
|
25788
|
+
return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
26265
25789
|
}
|
|
26266
|
-
|
|
26267
|
-
return
|
|
25790
|
+
cacheKey(scope, sessionId) {
|
|
25791
|
+
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
26268
25792
|
}
|
|
26269
|
-
|
|
26270
|
-
|
|
26271
|
-
|
|
25793
|
+
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
25794
|
+
readFile(scope, sessionId) {
|
|
25795
|
+
const file = this.filePath(scope, sessionId);
|
|
26272
25796
|
try {
|
|
26273
|
-
raw =
|
|
26274
|
-
|
|
26275
|
-
if (
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
try {
|
|
26281
|
-
parsed = JSON.parse(raw);
|
|
26282
|
-
} catch {
|
|
26283
|
-
return [];
|
|
26284
|
-
}
|
|
26285
|
-
if (!parsed || typeof parsed !== "object") return [];
|
|
26286
|
-
const arr = parsed.capabilities;
|
|
26287
|
-
if (!Array.isArray(arr)) return [];
|
|
26288
|
-
const out = [];
|
|
26289
|
-
for (const item of arr) {
|
|
26290
|
-
const r = CapabilitySchema.safeParse(item);
|
|
26291
|
-
if (r.success) out.push(r.data);
|
|
26292
|
-
}
|
|
26293
|
-
return out;
|
|
26294
|
-
}
|
|
26295
|
-
flush() {
|
|
26296
|
-
const content = { version: FILE_VERSION, capabilities: this.cache };
|
|
26297
|
-
this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
|
|
26298
|
-
}
|
|
26299
|
-
atomicWrite(file, content) {
|
|
26300
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26301
|
-
fs15.writeFileSync(tmp, content, { mode: 384 });
|
|
26302
|
-
fs15.renameSync(tmp, file);
|
|
26303
|
-
try {
|
|
26304
|
-
fs15.chmodSync(file, 384);
|
|
26305
|
-
} catch {
|
|
26306
|
-
}
|
|
26307
|
-
}
|
|
26308
|
-
};
|
|
26309
|
-
|
|
26310
|
-
// src/permission/capability-registry.ts
|
|
26311
|
-
var crypto4 = __toESM(require("crypto"), 1);
|
|
26312
|
-
var CapabilityRegistry = class {
|
|
26313
|
-
constructor(store, now = () => Date.now()) {
|
|
26314
|
-
this.store = store;
|
|
26315
|
-
this.now = now;
|
|
26316
|
-
for (const cap of store.list()) {
|
|
26317
|
-
this.bySecretHash.set(cap.secretHash, cap);
|
|
26318
|
-
}
|
|
26319
|
-
}
|
|
26320
|
-
store;
|
|
26321
|
-
now;
|
|
26322
|
-
// sha256(token) → Capability
|
|
26323
|
-
bySecretHash = /* @__PURE__ */ new Map();
|
|
26324
|
-
list() {
|
|
26325
|
-
return this.store.list();
|
|
26326
|
-
}
|
|
26327
|
-
verifyToken(token) {
|
|
26328
|
-
if (!token) return { ok: false, code: "TOKEN_INVALID" };
|
|
26329
|
-
const hash = sha256Hex(token);
|
|
26330
|
-
const cap = this.bySecretHash.get(hash);
|
|
26331
|
-
if (!cap) return { ok: false, code: "TOKEN_INVALID" };
|
|
26332
|
-
if (cap.expiresAt && this.now() >= cap.expiresAt) {
|
|
26333
|
-
return { ok: false, code: "TOKEN_EXPIRED" };
|
|
26334
|
-
}
|
|
26335
|
-
if (cap.maxUses && cap.usedCount >= cap.maxUses) {
|
|
26336
|
-
return { ok: false, code: "TOKEN_EXHAUSTED" };
|
|
26337
|
-
}
|
|
26338
|
-
const updated = { ...cap, usedCount: cap.usedCount + 1 };
|
|
26339
|
-
this.bySecretHash.set(hash, updated);
|
|
26340
|
-
this.store.upsert(updated);
|
|
26341
|
-
return { ok: true, capability: updated };
|
|
26342
|
-
}
|
|
26343
|
-
upsertCapability(cap) {
|
|
26344
|
-
this.bySecretHash.set(cap.secretHash, cap);
|
|
26345
|
-
this.store.upsert(cap);
|
|
26346
|
-
}
|
|
26347
|
-
/**
|
|
26348
|
-
* Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
|
|
26349
|
-
* 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
|
|
26350
|
-
* idempotent:不存在 → null。
|
|
26351
|
-
*/
|
|
26352
|
-
delete(id) {
|
|
26353
|
-
const current = this.store.list().find((c) => c.id === id);
|
|
26354
|
-
if (!current) return null;
|
|
26355
|
-
this.bySecretHash.delete(current.secretHash);
|
|
26356
|
-
this.store.remove(id);
|
|
26357
|
-
return current;
|
|
26358
|
-
}
|
|
26359
|
-
findById(id) {
|
|
26360
|
-
return this.store.list().find((c) => c.id === id) ?? null;
|
|
26361
|
-
}
|
|
26362
|
-
};
|
|
26363
|
-
function sha256Hex(s) {
|
|
26364
|
-
return crypto4.createHash("sha256").update(s).digest("hex");
|
|
26365
|
-
}
|
|
26366
|
-
|
|
26367
|
-
// src/permission/capability-manager.ts
|
|
26368
|
-
var crypto5 = __toESM(require("crypto"), 1);
|
|
26369
|
-
var CapabilityManager = class {
|
|
26370
|
-
constructor(registry2, hooks = {}) {
|
|
26371
|
-
this.registry = registry2;
|
|
26372
|
-
this.hooks = hooks;
|
|
26373
|
-
}
|
|
26374
|
-
registry;
|
|
26375
|
-
hooks;
|
|
26376
|
-
list() {
|
|
26377
|
-
return this.registry.list();
|
|
26378
|
-
}
|
|
26379
|
-
// whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
|
|
26380
|
-
// 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
|
|
26381
|
-
findById(id) {
|
|
26382
|
-
return this.registry.findById(id);
|
|
26383
|
-
}
|
|
26384
|
-
issue(args) {
|
|
26385
|
-
if (!args.displayName.trim()) {
|
|
26386
|
-
throw new Error("CapabilityManager.issue: displayName must be non-empty");
|
|
26387
|
-
}
|
|
26388
|
-
const token = (this.hooks.generateToken ?? defaultGenerateToken)();
|
|
26389
|
-
const id = (this.hooks.generateId ?? defaultGenerateId)();
|
|
26390
|
-
const now = (this.hooks.now ?? Date.now)();
|
|
26391
|
-
const cap = {
|
|
26392
|
-
id,
|
|
26393
|
-
secretHash: sha256Hex2(token),
|
|
26394
|
-
displayName: args.displayName,
|
|
26395
|
-
grants: args.grants,
|
|
26396
|
-
issuedAt: now,
|
|
26397
|
-
usedCount: 0,
|
|
26398
|
-
...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
|
|
26399
|
-
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
26400
|
-
};
|
|
26401
|
-
this.registry.upsertCapability(cap);
|
|
26402
|
-
this.hooks.onIssued?.(cap, token);
|
|
26403
|
-
return { token, capability: cap };
|
|
26404
|
-
}
|
|
26405
|
-
/**
|
|
26406
|
-
* guest 端 auth 帧捎带 selfPrincipalId/selfDisplayName 时,AuthGate.authenticate
|
|
26407
|
-
* 校验 cap token 通过后调本方法把 peer 字段写回 cap:
|
|
26408
|
-
* - 首次(cap.firstUsedByPeerAt 未设)→ 写盘 + 触发 onIssued 复用 push 频道
|
|
26409
|
-
* 让 owner UI 通过 capability:updated 实时拿到 peer 字段
|
|
26410
|
-
* - 重复(同 peerOwnerId)→ noop
|
|
26411
|
-
* - 冲突(不同 peerOwnerId)→ throw TOKEN_SHARED_LEAK,AuthGate 转 close 4401
|
|
26412
|
-
*/
|
|
26413
|
-
recordPeerHello(capId, args) {
|
|
26414
|
-
const cap = this.registry.findById(capId);
|
|
26415
|
-
if (!cap) return { changed: false };
|
|
26416
|
-
if (cap.firstUsedByPeerAt != null) {
|
|
26417
|
-
if (cap.peerOwnerId !== args.ownerPrincipalId) {
|
|
26418
|
-
throw new Error("TOKEN_SHARED_LEAK");
|
|
25797
|
+
const raw = import_node_fs13.default.readFileSync(file, "utf8");
|
|
25798
|
+
const parsed = JSON.parse(raw);
|
|
25799
|
+
if (!Array.isArray(parsed)) {
|
|
25800
|
+
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
25801
|
+
file
|
|
25802
|
+
});
|
|
25803
|
+
return [];
|
|
26419
25804
|
}
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
26426
|
-
peerDisplayName: args.displayName
|
|
26427
|
-
};
|
|
26428
|
-
this.registry.upsertCapability(updated);
|
|
26429
|
-
this.hooks.onIssued?.(updated, "__peer_hello__");
|
|
26430
|
-
return { changed: true };
|
|
26431
|
-
}
|
|
26432
|
-
/**
|
|
26433
|
-
* Hard delete:从 registry / store 物理移除 capability。
|
|
26434
|
-
* idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
|
|
26435
|
-
* onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
|
|
26436
|
-
*/
|
|
26437
|
-
delete(id) {
|
|
26438
|
-
const removed = this.registry.delete(id);
|
|
26439
|
-
if (!removed) return null;
|
|
26440
|
-
this.hooks.onDeleted?.(removed);
|
|
26441
|
-
return { capability: removed };
|
|
26442
|
-
}
|
|
26443
|
-
};
|
|
26444
|
-
function defaultGenerateToken() {
|
|
26445
|
-
return crypto5.randomBytes(24).toString("base64url");
|
|
26446
|
-
}
|
|
26447
|
-
function defaultGenerateId() {
|
|
26448
|
-
return "cap_" + crypto5.randomBytes(6).toString("base64url");
|
|
26449
|
-
}
|
|
26450
|
-
function sha256Hex2(s) {
|
|
26451
|
-
return crypto5.createHash("sha256").update(s).digest("hex");
|
|
26452
|
-
}
|
|
26453
|
-
|
|
26454
|
-
// src/permission/cleanup.ts
|
|
26455
|
-
var fs16 = __toESM(require("fs"), 1);
|
|
26456
|
-
function cleanupGuestSessionsForCapability(cap, factory) {
|
|
26457
|
-
const removed = [];
|
|
26458
|
-
for (const g2 of cap.grants) {
|
|
26459
|
-
if (g2.resource.type !== "persona") continue;
|
|
26460
|
-
const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
|
|
26461
|
-
try {
|
|
26462
|
-
fs16.rmSync(dir, { recursive: true, force: true });
|
|
26463
|
-
removed.push(dir);
|
|
26464
|
-
} catch {
|
|
26465
|
-
}
|
|
26466
|
-
}
|
|
26467
|
-
return { removed };
|
|
26468
|
-
}
|
|
26469
|
-
|
|
26470
|
-
// src/inbox/inbox-store.ts
|
|
26471
|
-
var fs17 = __toESM(require("fs"), 1);
|
|
26472
|
-
var path19 = __toESM(require("path"), 1);
|
|
26473
|
-
var INBOX_SUBDIR = "inbox";
|
|
26474
|
-
var InboxStore = class {
|
|
26475
|
-
constructor(dataDir) {
|
|
26476
|
-
this.dataDir = dataDir;
|
|
26477
|
-
fs17.mkdirSync(this.dirPath(), { recursive: true });
|
|
26478
|
-
}
|
|
26479
|
-
dataDir;
|
|
26480
|
-
/**
|
|
26481
|
-
* 列出某 peer 的消息, 按 createdAt 升序.
|
|
26482
|
-
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
26483
|
-
* 文件不存在时返回 [] (不抛, 不创建).
|
|
26484
|
-
*/
|
|
26485
|
-
list(peerOwnerId, sinceCreatedAt) {
|
|
26486
|
-
const file = this.filePath(peerOwnerId);
|
|
26487
|
-
let raw;
|
|
26488
|
-
try {
|
|
26489
|
-
raw = fs17.readFileSync(file, "utf8");
|
|
25805
|
+
const out = [];
|
|
25806
|
+
for (const entry of parsed) {
|
|
25807
|
+
const r = GroupFileEntrySchema.safeParse(entry);
|
|
25808
|
+
if (r.success) out.push(r.data);
|
|
25809
|
+
}
|
|
25810
|
+
return out;
|
|
26490
25811
|
} catch (err) {
|
|
26491
|
-
|
|
25812
|
+
const code = err?.code;
|
|
25813
|
+
if (code === "ENOENT") return [];
|
|
25814
|
+
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
25815
|
+
file,
|
|
25816
|
+
err: err.message
|
|
25817
|
+
});
|
|
26492
25818
|
return [];
|
|
26493
25819
|
}
|
|
26494
|
-
const all = parseAllLines(raw);
|
|
26495
|
-
if (sinceCreatedAt === void 0) return all;
|
|
26496
|
-
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
26497
25820
|
}
|
|
26498
|
-
|
|
26499
|
-
|
|
26500
|
-
|
|
26501
|
-
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
try {
|
|
26505
|
-
entries = fs17.readdirSync(dir);
|
|
26506
|
-
} catch (err) {
|
|
26507
|
-
if (err?.code === "ENOENT") return [];
|
|
26508
|
-
return [];
|
|
26509
|
-
}
|
|
26510
|
-
return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
|
|
25821
|
+
writeFile(scope, sessionId, entries) {
|
|
25822
|
+
const file = this.filePath(scope, sessionId);
|
|
25823
|
+
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
|
|
25824
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
25825
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
25826
|
+
import_node_fs13.default.renameSync(tmp, file);
|
|
26511
25827
|
}
|
|
26512
|
-
/**
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
const
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
const line = JSON.stringify(message) + "\n";
|
|
26521
|
-
fs17.appendFileSync(file, line, { mode: 384 });
|
|
26522
|
-
try {
|
|
26523
|
-
fs17.chmodSync(file, 384);
|
|
26524
|
-
} catch {
|
|
26525
|
-
}
|
|
25828
|
+
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
25829
|
+
list(scope, sessionId) {
|
|
25830
|
+
const key = this.cacheKey(scope, sessionId);
|
|
25831
|
+
const cached = this.cache.get(key);
|
|
25832
|
+
if (cached) return cached.entries;
|
|
25833
|
+
const entries = this.readFile(scope, sessionId);
|
|
25834
|
+
this.cache.set(key, { entries, scope });
|
|
25835
|
+
return entries;
|
|
26526
25836
|
}
|
|
26527
25837
|
/**
|
|
26528
|
-
*
|
|
26529
|
-
*
|
|
26530
|
-
*
|
|
25838
|
+
* upsert:
|
|
25839
|
+
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
25840
|
+
* (保留首次入群人 / 入群时刻)
|
|
25841
|
+
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
25842
|
+
*
|
|
25843
|
+
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
26531
25844
|
*/
|
|
26532
|
-
|
|
26533
|
-
const
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
|
|
25845
|
+
upsert(scope, sessionId, input, now = Date.now()) {
|
|
25846
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25847
|
+
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
25848
|
+
let next;
|
|
25849
|
+
if (idx >= 0) {
|
|
25850
|
+
const prev = entries[idx];
|
|
25851
|
+
next = {
|
|
25852
|
+
...prev,
|
|
25853
|
+
size: input.size,
|
|
25854
|
+
mime: input.mime,
|
|
25855
|
+
lastEditedAt: now,
|
|
25856
|
+
stale: false
|
|
25857
|
+
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
25858
|
+
};
|
|
25859
|
+
entries[idx] = next;
|
|
25860
|
+
} else {
|
|
25861
|
+
next = {
|
|
25862
|
+
id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
|
|
25863
|
+
relPath: input.relPath,
|
|
25864
|
+
from: input.from,
|
|
25865
|
+
label: input.label,
|
|
25866
|
+
size: input.size,
|
|
25867
|
+
mime: input.mime,
|
|
25868
|
+
addedAt: now
|
|
25869
|
+
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
25870
|
+
};
|
|
25871
|
+
entries.push(next);
|
|
25872
|
+
}
|
|
25873
|
+
this.writeFile(scope, sessionId, entries);
|
|
25874
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
25875
|
+
return next;
|
|
26546
25876
|
}
|
|
26547
25877
|
/**
|
|
26548
|
-
*
|
|
25878
|
+
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
25879
|
+
* - 命中 → stale=true,UI 灰显
|
|
25880
|
+
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
25881
|
+
*
|
|
25882
|
+
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
26549
25883
|
*/
|
|
26550
|
-
|
|
26551
|
-
const
|
|
26552
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26555
|
-
|
|
26556
|
-
|
|
26557
|
-
|
|
26558
|
-
rewriteFile(peerOwnerId, messages) {
|
|
26559
|
-
const file = this.filePath(peerOwnerId);
|
|
26560
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26561
|
-
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
26562
|
-
fs17.writeFileSync(tmp, content, { mode: 384 });
|
|
26563
|
-
fs17.renameSync(tmp, file);
|
|
26564
|
-
try {
|
|
26565
|
-
fs17.chmodSync(file, 384);
|
|
26566
|
-
} catch {
|
|
26567
|
-
}
|
|
26568
|
-
}
|
|
26569
|
-
dirPath() {
|
|
26570
|
-
return path19.join(this.dataDir, INBOX_SUBDIR);
|
|
26571
|
-
}
|
|
26572
|
-
filePath(peerOwnerId) {
|
|
26573
|
-
return path19.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
26574
|
-
}
|
|
26575
|
-
};
|
|
26576
|
-
function parseAllLines(raw) {
|
|
26577
|
-
const out = [];
|
|
26578
|
-
for (const line of raw.split("\n")) {
|
|
26579
|
-
const trimmed = line.trim();
|
|
26580
|
-
if (!trimmed) continue;
|
|
26581
|
-
let parsed;
|
|
26582
|
-
try {
|
|
26583
|
-
parsed = JSON.parse(trimmed);
|
|
26584
|
-
} catch {
|
|
26585
|
-
continue;
|
|
26586
|
-
}
|
|
26587
|
-
const r = InboxMessageSchema.safeParse(parsed);
|
|
26588
|
-
if (r.success) out.push(r.data);
|
|
26589
|
-
}
|
|
26590
|
-
return out;
|
|
26591
|
-
}
|
|
26592
|
-
|
|
26593
|
-
// src/inbox/inbox-manager.ts
|
|
26594
|
-
var InboxManager = class {
|
|
26595
|
-
constructor(store, broadcast) {
|
|
26596
|
-
this.store = store;
|
|
26597
|
-
this.broadcast = broadcast;
|
|
25884
|
+
markStale(scope, sessionId, relPath) {
|
|
25885
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25886
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
25887
|
+
if (idx < 0) return;
|
|
25888
|
+
if (entries[idx].stale) return;
|
|
25889
|
+
entries[idx] = { ...entries[idx], stale: true };
|
|
25890
|
+
this.writeFile(scope, sessionId, entries);
|
|
25891
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26598
25892
|
}
|
|
26599
|
-
store;
|
|
26600
|
-
broadcast;
|
|
26601
25893
|
/**
|
|
26602
|
-
*
|
|
26603
|
-
*
|
|
25894
|
+
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
25895
|
+
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
25896
|
+
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
26604
25897
|
*
|
|
26605
|
-
*
|
|
26606
|
-
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
25898
|
+
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
26607
25899
|
*/
|
|
26608
|
-
|
|
26609
|
-
const
|
|
26610
|
-
const
|
|
26611
|
-
|
|
26612
|
-
|
|
26613
|
-
|
|
26614
|
-
|
|
26615
|
-
|
|
26616
|
-
readBy: {}
|
|
26617
|
-
};
|
|
26618
|
-
this.store.append(msg);
|
|
26619
|
-
const afterLen = this.store.list(args.peerOwnerId).length;
|
|
26620
|
-
if (afterLen === beforeLen) return msg;
|
|
26621
|
-
this.broadcast(args.peerOwnerId, {
|
|
26622
|
-
type: "inbox:event",
|
|
26623
|
-
peerOwnerId: args.peerOwnerId,
|
|
26624
|
-
message: msg
|
|
26625
|
-
});
|
|
26626
|
-
return msg;
|
|
26627
|
-
}
|
|
26628
|
-
list(peerOwnerId, sinceCreatedAt) {
|
|
26629
|
-
return this.store.list(peerOwnerId, sinceCreatedAt);
|
|
25900
|
+
remove(scope, sessionId, relPath) {
|
|
25901
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25902
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
25903
|
+
if (idx < 0) return false;
|
|
25904
|
+
entries.splice(idx, 1);
|
|
25905
|
+
this.writeFile(scope, sessionId, entries);
|
|
25906
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
25907
|
+
return true;
|
|
26630
25908
|
}
|
|
26631
25909
|
/**
|
|
26632
|
-
*
|
|
26633
|
-
*
|
|
25910
|
+
* 跨 session 聚合查询(spec §4 HTTP ACL:personal 视野并集)。
|
|
25911
|
+
*
|
|
25912
|
+
* 扫 <dataDir>/sessions/<personaId>/owner/*.group-files.json 和
|
|
25913
|
+
* <dataDir>/sessions/<personaId>/listener/*.group-files.json,每文件读一份。
|
|
25914
|
+
*
|
|
25915
|
+
* 复杂度 O(N sessions × N entries),N 通常 < 100,可接受。
|
|
26634
25916
|
*/
|
|
26635
|
-
|
|
26636
|
-
const
|
|
26637
|
-
const
|
|
26638
|
-
|
|
26639
|
-
|
|
26640
|
-
|
|
26641
|
-
for (const m2 of after) {
|
|
26642
|
-
const prev = beforeById.get(m2.id);
|
|
26643
|
-
if (!prev) continue;
|
|
26644
|
-
if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
|
|
26645
|
-
this.broadcast(args.peerOwnerId, {
|
|
26646
|
-
type: "inbox:event",
|
|
26647
|
-
peerOwnerId: args.peerOwnerId,
|
|
26648
|
-
message: m2
|
|
26649
|
-
});
|
|
26650
|
-
}
|
|
26651
|
-
return changed;
|
|
26652
|
-
}
|
|
26653
|
-
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
26654
|
-
removeChannel(peerOwnerId) {
|
|
26655
|
-
this.store.removeByPeerOwnerId(peerOwnerId);
|
|
26656
|
-
}
|
|
26657
|
-
};
|
|
26658
|
-
|
|
26659
|
-
// src/state/received-capability-store.ts
|
|
26660
|
-
var fs18 = __toESM(require("fs"), 1);
|
|
26661
|
-
var path20 = __toESM(require("path"), 1);
|
|
26662
|
-
var FILE_NAME = "received-capabilities.json";
|
|
26663
|
-
var ReceivedCapabilityStore = class {
|
|
26664
|
-
constructor(dataDir) {
|
|
26665
|
-
this.dataDir = dataDir;
|
|
26666
|
-
}
|
|
26667
|
-
dataDir;
|
|
26668
|
-
caps = /* @__PURE__ */ new Map();
|
|
26669
|
-
load() {
|
|
26670
|
-
this.caps.clear();
|
|
26671
|
-
const file = path20.join(this.dataDir, FILE_NAME);
|
|
26672
|
-
let raw;
|
|
26673
|
-
try {
|
|
26674
|
-
raw = fs18.readFileSync(file, "utf8");
|
|
26675
|
-
} catch (err) {
|
|
26676
|
-
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
26677
|
-
return;
|
|
26678
|
-
}
|
|
26679
|
-
let parsed;
|
|
26680
|
-
try {
|
|
26681
|
-
parsed = JSON.parse(raw);
|
|
26682
|
-
} catch {
|
|
26683
|
-
this.renameBak(file);
|
|
26684
|
-
return;
|
|
26685
|
-
}
|
|
26686
|
-
const arr = parsed?.receivedCaps;
|
|
26687
|
-
if (!Array.isArray(arr)) return;
|
|
26688
|
-
for (const entry of arr) {
|
|
26689
|
-
const r = ReceivedCapabilitySchema.safeParse(entry);
|
|
26690
|
-
if (r.success) this.caps.set(r.data.ownerPrincipalId, r.data);
|
|
26691
|
-
}
|
|
26692
|
-
}
|
|
26693
|
-
list() {
|
|
26694
|
-
return Array.from(this.caps.values());
|
|
26695
|
-
}
|
|
26696
|
-
get(ownerPrincipalId) {
|
|
26697
|
-
return this.caps.get(ownerPrincipalId) ?? null;
|
|
26698
|
-
}
|
|
26699
|
-
hasOwner(ownerPrincipalId) {
|
|
26700
|
-
return this.caps.has(ownerPrincipalId);
|
|
26701
|
-
}
|
|
26702
|
-
upsert(cap) {
|
|
26703
|
-
this.caps.set(cap.ownerPrincipalId, cap);
|
|
26704
|
-
this.flush();
|
|
26705
|
-
}
|
|
26706
|
-
remove(ownerPrincipalId) {
|
|
26707
|
-
if (!this.caps.delete(ownerPrincipalId)) return;
|
|
26708
|
-
this.flush();
|
|
26709
|
-
}
|
|
26710
|
-
flush() {
|
|
26711
|
-
const file = path20.join(this.dataDir, FILE_NAME);
|
|
26712
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
26713
|
-
const content = JSON.stringify(
|
|
26714
|
-
{ receivedCaps: Array.from(this.caps.values()) },
|
|
26715
|
-
null,
|
|
26716
|
-
2
|
|
26717
|
-
);
|
|
26718
|
-
fs18.mkdirSync(this.dataDir, { recursive: true });
|
|
26719
|
-
fs18.writeFileSync(tmp, content, { mode: 384 });
|
|
26720
|
-
fs18.renameSync(tmp, file);
|
|
26721
|
-
}
|
|
26722
|
-
renameBak(file) {
|
|
26723
|
-
try {
|
|
26724
|
-
fs18.renameSync(file, `${file}.bak`);
|
|
26725
|
-
} catch {
|
|
26726
|
-
}
|
|
26727
|
-
}
|
|
26728
|
-
};
|
|
26729
|
-
|
|
26730
|
-
// src/state/legacy-migration.ts
|
|
26731
|
-
var fs19 = __toESM(require("fs"), 1);
|
|
26732
|
-
var path21 = __toESM(require("path"), 1);
|
|
26733
|
-
var TARGETS = [
|
|
26734
|
-
{ src: "capabilities.json", bak: "capabilities.legacy.bak" },
|
|
26735
|
-
{ src: "remote-personas", bak: "remote-personas.legacy.bak" },
|
|
26736
|
-
{ src: "peers.json", bak: "peers.legacy.bak" }
|
|
26737
|
-
];
|
|
26738
|
-
function migrateLegacyFiles(dataDir) {
|
|
26739
|
-
for (const t of TARGETS) {
|
|
26740
|
-
const srcPath = path21.join(dataDir, t.src);
|
|
26741
|
-
const bakPath = path21.join(dataDir, t.bak);
|
|
26742
|
-
if (!fs19.existsSync(srcPath)) continue;
|
|
26743
|
-
if (fs19.existsSync(bakPath)) continue;
|
|
26744
|
-
try {
|
|
26745
|
-
fs19.renameSync(srcPath, bakPath);
|
|
26746
|
-
} catch {
|
|
26747
|
-
}
|
|
26748
|
-
}
|
|
26749
|
-
}
|
|
26750
|
-
|
|
26751
|
-
// src/received-capability/connect-remote.ts
|
|
26752
|
-
var crypto6 = __toESM(require("crypto"), 1);
|
|
26753
|
-
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
26754
|
-
var RPC_TIMEOUT_MS = 5e3;
|
|
26755
|
-
async function connectRemote(args) {
|
|
26756
|
-
const ws = new wrapper_default(args.url);
|
|
26757
|
-
await new Promise((resolve2, reject) => {
|
|
26758
|
-
const onError = (e) => reject(e);
|
|
26759
|
-
ws.once("open", () => {
|
|
26760
|
-
ws.off("error", onError);
|
|
26761
|
-
resolve2();
|
|
26762
|
-
});
|
|
26763
|
-
ws.once("error", onError);
|
|
26764
|
-
});
|
|
26765
|
-
ws.send(
|
|
26766
|
-
JSON.stringify({
|
|
26767
|
-
type: "auth",
|
|
26768
|
-
token: args.token,
|
|
26769
|
-
selfPrincipalId: args.selfPrincipalId,
|
|
26770
|
-
selfDisplayName: args.selfDisplayName
|
|
26771
|
-
})
|
|
26772
|
-
);
|
|
26773
|
-
await new Promise((resolve2, reject) => {
|
|
26774
|
-
const onMessage = (raw) => {
|
|
26775
|
-
let f;
|
|
25917
|
+
listByPersona(personaId) {
|
|
25918
|
+
const out = [];
|
|
25919
|
+
for (const mode of ["owner", "listener"]) {
|
|
25920
|
+
const scope = { kind: "persona", personaId, mode };
|
|
25921
|
+
const root = this.rootForScope(scope);
|
|
25922
|
+
let names;
|
|
26776
25923
|
try {
|
|
26777
|
-
|
|
26778
|
-
} catch {
|
|
26779
|
-
|
|
26780
|
-
|
|
26781
|
-
|
|
26782
|
-
ws.off("message", onMessage);
|
|
26783
|
-
resolve2();
|
|
26784
|
-
return;
|
|
26785
|
-
}
|
|
26786
|
-
if (f.type === "auth:error" || f.code === 4401) {
|
|
26787
|
-
ws.off("message", onMessage);
|
|
26788
|
-
reject(new Error(`auth failed: ${f.message ?? f.type ?? "unknown"}`));
|
|
25924
|
+
names = import_node_fs13.default.readdirSync(root);
|
|
25925
|
+
} catch (err) {
|
|
25926
|
+
const code = err?.code;
|
|
25927
|
+
if (code === "ENOENT") continue;
|
|
25928
|
+
continue;
|
|
26789
25929
|
}
|
|
26790
|
-
|
|
26791
|
-
|
|
26792
|
-
|
|
26793
|
-
|
|
26794
|
-
|
|
26795
|
-
|
|
26796
|
-
});
|
|
26797
|
-
function call(method, payload) {
|
|
26798
|
-
const requestId = crypto6.randomUUID();
|
|
26799
|
-
return new Promise((resolve2, reject) => {
|
|
26800
|
-
const onMessage = (raw) => {
|
|
26801
|
-
let f;
|
|
26802
|
-
try {
|
|
26803
|
-
f = JSON.parse(raw.toString());
|
|
26804
|
-
} catch {
|
|
26805
|
-
return;
|
|
26806
|
-
}
|
|
26807
|
-
if (f.requestId !== requestId) return;
|
|
26808
|
-
ws.off("message", onMessage);
|
|
26809
|
-
if (f.error || f.type?.endsWith(":error")) {
|
|
26810
|
-
reject(new Error(f.message ?? f.error ?? "rpc error"));
|
|
26811
|
-
} else {
|
|
26812
|
-
resolve2(f);
|
|
26813
|
-
}
|
|
26814
|
-
};
|
|
26815
|
-
ws.on("message", onMessage);
|
|
26816
|
-
ws.send(JSON.stringify({ type: method, requestId, ...payload }));
|
|
26817
|
-
setTimeout(() => {
|
|
26818
|
-
ws.off("message", onMessage);
|
|
26819
|
-
reject(new Error(`${method} timeout`));
|
|
26820
|
-
}, RPC_TIMEOUT_MS);
|
|
26821
|
-
});
|
|
26822
|
-
}
|
|
26823
|
-
return {
|
|
26824
|
-
whoami: async () => {
|
|
26825
|
-
const f = await call("whoami", {});
|
|
26826
|
-
return {
|
|
26827
|
-
ownerId: f.owner.id,
|
|
26828
|
-
displayName: f.owner.displayName,
|
|
26829
|
-
capabilityId: f.capability.id,
|
|
26830
|
-
grants: f.capability.grants
|
|
26831
|
-
};
|
|
26832
|
-
},
|
|
26833
|
-
close: () => {
|
|
26834
|
-
try {
|
|
26835
|
-
ws.close();
|
|
26836
|
-
} catch {
|
|
25930
|
+
for (const name of names) {
|
|
25931
|
+
if (!name.endsWith(".group-files.json")) continue;
|
|
25932
|
+
const sessionId = name.slice(0, -".group-files.json".length);
|
|
25933
|
+
if (!sessionId) continue;
|
|
25934
|
+
const entries = this.list(scope, sessionId);
|
|
25935
|
+
out.push({ sessionId, entries });
|
|
26837
25936
|
}
|
|
26838
25937
|
}
|
|
26839
|
-
|
|
26840
|
-
}
|
|
26841
|
-
|
|
26842
|
-
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
26843
|
-
var fs20 = __toESM(require("fs"), 1);
|
|
26844
|
-
var path22 = __toESM(require("path"), 1);
|
|
26845
|
-
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
26846
|
-
function migrateFlattenSessions(opts) {
|
|
26847
|
-
const dataDir = opts.dataDir;
|
|
26848
|
-
const now = opts.now ?? Date.now;
|
|
26849
|
-
const sessionsDir = path22.join(dataDir, "sessions");
|
|
26850
|
-
const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
26851
|
-
if (existsSync4(flagPath)) {
|
|
26852
|
-
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
26853
|
-
}
|
|
26854
|
-
let movedBare = 0;
|
|
26855
|
-
let movedVmOwner = 0;
|
|
26856
|
-
let archivedListener = 0;
|
|
26857
|
-
const defaultDir = path22.join(sessionsDir, "default");
|
|
26858
|
-
if (existsSync4(defaultDir)) {
|
|
26859
|
-
for (const entry of readdirSafe(defaultDir)) {
|
|
26860
|
-
if (!entry.endsWith(".json")) continue;
|
|
26861
|
-
const src = path22.join(defaultDir, entry);
|
|
26862
|
-
const dst = path22.join(sessionsDir, entry);
|
|
26863
|
-
fs20.renameSync(src, dst);
|
|
26864
|
-
movedBare += 1;
|
|
26865
|
-
}
|
|
26866
|
-
rmdirIfEmpty(defaultDir);
|
|
26867
|
-
}
|
|
26868
|
-
for (const pid of readdirSafe(sessionsDir)) {
|
|
26869
|
-
const personaDir = path22.join(sessionsDir, pid);
|
|
26870
|
-
if (!isDir(personaDir)) continue;
|
|
26871
|
-
if (pid === "default") continue;
|
|
26872
|
-
const ownerSrc = path22.join(personaDir, "owner");
|
|
26873
|
-
if (existsSync4(ownerSrc) && isDir(ownerSrc)) {
|
|
26874
|
-
const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
26875
|
-
fs20.mkdirSync(ownerDst, { recursive: true });
|
|
26876
|
-
for (const file of readdirSafe(ownerSrc)) {
|
|
26877
|
-
if (!file.endsWith(".json")) continue;
|
|
26878
|
-
fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
|
|
26879
|
-
movedVmOwner += 1;
|
|
26880
|
-
}
|
|
26881
|
-
rmdirIfEmpty(ownerSrc);
|
|
26882
|
-
}
|
|
26883
|
-
const listenerSrc = path22.join(personaDir, "listener");
|
|
26884
|
-
if (existsSync4(listenerSrc) && isDir(listenerSrc)) {
|
|
26885
|
-
const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
|
|
26886
|
-
fs20.mkdirSync(archiveDst, { recursive: true });
|
|
26887
|
-
for (const file of readdirSafe(listenerSrc)) {
|
|
26888
|
-
if (!file.endsWith(".json")) continue;
|
|
26889
|
-
fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
|
|
26890
|
-
archivedListener += 1;
|
|
26891
|
-
}
|
|
26892
|
-
rmdirIfEmpty(listenerSrc);
|
|
26893
|
-
}
|
|
26894
|
-
rmdirIfEmpty(personaDir);
|
|
26895
|
-
}
|
|
26896
|
-
fs20.mkdirSync(sessionsDir, { recursive: true });
|
|
26897
|
-
fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
26898
|
-
return {
|
|
26899
|
-
skipped: false,
|
|
26900
|
-
flagWritten: true,
|
|
26901
|
-
movedBare,
|
|
26902
|
-
movedVmOwner,
|
|
26903
|
-
archivedListener
|
|
26904
|
-
};
|
|
26905
|
-
}
|
|
26906
|
-
function existsSync4(p2) {
|
|
26907
|
-
try {
|
|
26908
|
-
fs20.statSync(p2);
|
|
26909
|
-
return true;
|
|
26910
|
-
} catch {
|
|
26911
|
-
return false;
|
|
25938
|
+
return out;
|
|
26912
25939
|
}
|
|
26913
|
-
}
|
|
26914
|
-
function
|
|
26915
|
-
|
|
26916
|
-
|
|
26917
|
-
} catch {
|
|
25940
|
+
};
|
|
25941
|
+
function personalViewable(groupStore, personaDir, personaId, absPath) {
|
|
25942
|
+
const realTarget = safeRealpath(absPath);
|
|
25943
|
+
if (!realTarget) {
|
|
26918
25944
|
return false;
|
|
26919
25945
|
}
|
|
26920
|
-
|
|
26921
|
-
|
|
26922
|
-
|
|
26923
|
-
|
|
26924
|
-
|
|
26925
|
-
|
|
25946
|
+
const personasUnion = groupStore.listByPersona(personaId);
|
|
25947
|
+
for (const { entries } of personasUnion) {
|
|
25948
|
+
for (const e of entries) {
|
|
25949
|
+
if (e.stale) continue;
|
|
25950
|
+
const realEntry = safeRealpath(import_node_path14.default.join(personaDir, e.relPath));
|
|
25951
|
+
if (realEntry && realEntry === realTarget) return true;
|
|
25952
|
+
}
|
|
26926
25953
|
}
|
|
25954
|
+
return false;
|
|
26927
25955
|
}
|
|
26928
|
-
function
|
|
25956
|
+
function safeRealpath(p2) {
|
|
26929
25957
|
try {
|
|
26930
|
-
|
|
25958
|
+
return import_node_fs13.default.realpathSync(p2);
|
|
26931
25959
|
} catch {
|
|
25960
|
+
return null;
|
|
26932
25961
|
}
|
|
26933
25962
|
}
|
|
26934
25963
|
|
|
26935
|
-
// src/transport/http-router.ts
|
|
26936
|
-
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26937
|
-
var import_node_path15 = __toESM(require("path"), 1);
|
|
26938
|
-
|
|
26939
25964
|
// src/attachment/mime.ts
|
|
26940
|
-
var
|
|
25965
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
26941
25966
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
26942
25967
|
var EXT_TO_NATIVE_MIME = {
|
|
26943
25968
|
// 图片
|
|
@@ -27044,14 +26069,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
27044
26069
|
".mk"
|
|
27045
26070
|
]);
|
|
27046
26071
|
function lookupMime(filePathOrName) {
|
|
27047
|
-
const ext =
|
|
26072
|
+
const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
|
|
27048
26073
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
27049
26074
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
27050
26075
|
return "application/octet-stream";
|
|
27051
26076
|
}
|
|
27052
26077
|
|
|
27053
26078
|
// src/attachment/sign-url.ts
|
|
27054
|
-
var
|
|
26079
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27055
26080
|
var HMAC_ALGO = "sha256";
|
|
27056
26081
|
function base64urlEncode(buf) {
|
|
27057
26082
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -27068,7 +26093,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
27068
26093
|
}
|
|
27069
26094
|
function computeSig(secret, absPath, e) {
|
|
27070
26095
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
27071
|
-
return
|
|
26096
|
+
return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
27072
26097
|
}
|
|
27073
26098
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
27074
26099
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -27103,7 +26128,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
27103
26128
|
if (provided.length !== expected.length) {
|
|
27104
26129
|
return { ok: false, code: "BAD_SIG" };
|
|
27105
26130
|
}
|
|
27106
|
-
if (!
|
|
26131
|
+
if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
|
|
27107
26132
|
return { ok: false, code: "BAD_SIG" };
|
|
27108
26133
|
}
|
|
27109
26134
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -27124,7 +26149,7 @@ function createHttpRouter(deps) {
|
|
|
27124
26149
|
sendJson(res, 200, { ok: true, version: deps.daemonVersion });
|
|
27125
26150
|
return true;
|
|
27126
26151
|
}
|
|
27127
|
-
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
|
|
26152
|
+
if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
|
|
27128
26153
|
return false;
|
|
27129
26154
|
}
|
|
27130
26155
|
if (url.pathname.startsWith("/files/") && req.method === "GET") {
|
|
@@ -27164,8 +26189,43 @@ function createHttpRouter(deps) {
|
|
|
27164
26189
|
sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
|
|
27165
26190
|
return true;
|
|
27166
26191
|
}
|
|
26192
|
+
const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
|
|
26193
|
+
if (personaFilesMatch && req.method === "GET") {
|
|
26194
|
+
const pid = personaFilesMatch[1];
|
|
26195
|
+
const pathParam = url.searchParams.get("path");
|
|
26196
|
+
if (!pathParam) {
|
|
26197
|
+
sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26198
|
+
return true;
|
|
26199
|
+
}
|
|
26200
|
+
if (!deps.personaStore || !deps.groupFileStore) {
|
|
26201
|
+
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
|
|
26202
|
+
return true;
|
|
26203
|
+
}
|
|
26204
|
+
const personaDir = deps.personaStore.personaDirPath(pid);
|
|
26205
|
+
const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
|
|
26206
|
+
if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
|
|
26207
|
+
sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
|
|
26208
|
+
return true;
|
|
26209
|
+
}
|
|
26210
|
+
if (ctx.role === "personal") {
|
|
26211
|
+
if (ctx.personaId !== pid) {
|
|
26212
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
|
|
26213
|
+
return true;
|
|
26214
|
+
}
|
|
26215
|
+
if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
|
|
26216
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
|
|
26217
|
+
return true;
|
|
26218
|
+
}
|
|
26219
|
+
}
|
|
26220
|
+
streamFile(res, absPath, deps.logger);
|
|
26221
|
+
return true;
|
|
26222
|
+
}
|
|
27167
26223
|
const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
|
|
27168
26224
|
if (sessionFilesMatch && req.method === "GET") {
|
|
26225
|
+
if (ctx.role !== "owner") {
|
|
26226
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
|
|
26227
|
+
return true;
|
|
26228
|
+
}
|
|
27169
26229
|
const sid = sessionFilesMatch[1];
|
|
27170
26230
|
const pathParam = url.searchParams.get("path");
|
|
27171
26231
|
if (!pathParam) {
|
|
@@ -27173,7 +26233,7 @@ function createHttpRouter(deps) {
|
|
|
27173
26233
|
return true;
|
|
27174
26234
|
}
|
|
27175
26235
|
let absPath;
|
|
27176
|
-
if (
|
|
26236
|
+
if (import_node_path16.default.isAbsolute(pathParam)) {
|
|
27177
26237
|
absPath = pathParam;
|
|
27178
26238
|
} else if (deps.sessionStore) {
|
|
27179
26239
|
const file = deps.sessionStore.read(sid);
|
|
@@ -27181,7 +26241,7 @@ function createHttpRouter(deps) {
|
|
|
27181
26241
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
27182
26242
|
return true;
|
|
27183
26243
|
}
|
|
27184
|
-
absPath =
|
|
26244
|
+
absPath = import_node_path16.default.join(file.cwd, pathParam);
|
|
27185
26245
|
} else {
|
|
27186
26246
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
27187
26247
|
return true;
|
|
@@ -27206,182 +26266,47 @@ function sendJson(res, status, body) {
|
|
|
27206
26266
|
res.end(JSON.stringify(body));
|
|
27207
26267
|
}
|
|
27208
26268
|
function withCtx(ctx, body) {
|
|
27209
|
-
return { ...body, role: ctx.role };
|
|
26269
|
+
return { ...body, role: ctx.role, personaId: ctx.personaId };
|
|
26270
|
+
}
|
|
26271
|
+
function isContainedIn(abs, root) {
|
|
26272
|
+
const normalized = import_node_path16.default.resolve(abs);
|
|
26273
|
+
const normalizedRoot = import_node_path16.default.resolve(root);
|
|
26274
|
+
if (normalized === normalizedRoot) return true;
|
|
26275
|
+
return normalized.startsWith(normalizedRoot + import_node_path16.default.sep);
|
|
27210
26276
|
}
|
|
27211
26277
|
function streamFile(res, absPath, logger) {
|
|
27212
26278
|
let stat;
|
|
27213
26279
|
try {
|
|
27214
|
-
stat =
|
|
26280
|
+
stat = import_node_fs14.default.statSync(absPath);
|
|
27215
26281
|
} catch (err) {
|
|
27216
26282
|
const code = err?.code;
|
|
27217
26283
|
if (code === "ENOENT") {
|
|
27218
26284
|
sendJson(res, 404, { code: "NOT_FOUND", message: "file not found" });
|
|
27219
26285
|
} else {
|
|
27220
26286
|
sendJson(res, 500, { code: "STAT_FAILED", message: err.message });
|
|
27221
|
-
}
|
|
27222
|
-
return;
|
|
27223
|
-
}
|
|
27224
|
-
if (!stat.isFile()) {
|
|
27225
|
-
sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
|
|
27226
|
-
return;
|
|
27227
|
-
}
|
|
27228
|
-
const mime = lookupMime(absPath);
|
|
27229
|
-
const basename = import_node_path15.default.basename(absPath);
|
|
27230
|
-
res.writeHead(200, {
|
|
27231
|
-
"Content-Type": mime,
|
|
27232
|
-
"Content-Length": String(stat.size),
|
|
27233
|
-
"Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
|
|
27234
|
-
// 防止浏览器把任意 mime 当 html 渲染
|
|
27235
|
-
"X-Content-Type-Options": "nosniff"
|
|
27236
|
-
});
|
|
27237
|
-
const stream = import_node_fs13.default.createReadStream(absPath);
|
|
27238
|
-
stream.on("error", (err) => {
|
|
27239
|
-
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
27240
|
-
res.destroy();
|
|
27241
|
-
});
|
|
27242
|
-
stream.pipe(res);
|
|
27243
|
-
}
|
|
27244
|
-
|
|
27245
|
-
// src/attachment/group.ts
|
|
27246
|
-
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
27247
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
27248
|
-
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27249
|
-
init_protocol();
|
|
27250
|
-
var GroupFileStore = class {
|
|
27251
|
-
dataDir;
|
|
27252
|
-
logger;
|
|
27253
|
-
cache = /* @__PURE__ */ new Map();
|
|
27254
|
-
constructor(opts) {
|
|
27255
|
-
this.dataDir = opts.dataDir;
|
|
27256
|
-
this.logger = opts.logger;
|
|
27257
|
-
}
|
|
27258
|
-
rootForScope(scope) {
|
|
27259
|
-
return import_node_path16.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
27260
|
-
}
|
|
27261
|
-
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
27262
|
-
filePath(scope, sessionId) {
|
|
27263
|
-
return import_node_path16.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
27264
|
-
}
|
|
27265
|
-
cacheKey(scope, sessionId) {
|
|
27266
|
-
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
27267
|
-
}
|
|
27268
|
-
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
27269
|
-
readFile(scope, sessionId) {
|
|
27270
|
-
const file = this.filePath(scope, sessionId);
|
|
27271
|
-
try {
|
|
27272
|
-
const raw = import_node_fs14.default.readFileSync(file, "utf8");
|
|
27273
|
-
const parsed = JSON.parse(raw);
|
|
27274
|
-
if (!Array.isArray(parsed)) {
|
|
27275
|
-
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
27276
|
-
file
|
|
27277
|
-
});
|
|
27278
|
-
return [];
|
|
27279
|
-
}
|
|
27280
|
-
const out = [];
|
|
27281
|
-
for (const entry of parsed) {
|
|
27282
|
-
const r = GroupFileEntrySchema.safeParse(entry);
|
|
27283
|
-
if (r.success) out.push(r.data);
|
|
27284
|
-
}
|
|
27285
|
-
return out;
|
|
27286
|
-
} catch (err) {
|
|
27287
|
-
const code = err?.code;
|
|
27288
|
-
if (code === "ENOENT") return [];
|
|
27289
|
-
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
27290
|
-
file,
|
|
27291
|
-
err: err.message
|
|
27292
|
-
});
|
|
27293
|
-
return [];
|
|
27294
|
-
}
|
|
27295
|
-
}
|
|
27296
|
-
writeFile(scope, sessionId, entries) {
|
|
27297
|
-
const file = this.filePath(scope, sessionId);
|
|
27298
|
-
import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
27299
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
27300
|
-
import_node_fs14.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
27301
|
-
import_node_fs14.default.renameSync(tmp, file);
|
|
27302
|
-
}
|
|
27303
|
-
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
27304
|
-
list(scope, sessionId) {
|
|
27305
|
-
const key = this.cacheKey(scope, sessionId);
|
|
27306
|
-
const cached = this.cache.get(key);
|
|
27307
|
-
if (cached) return cached.entries;
|
|
27308
|
-
const entries = this.readFile(scope, sessionId);
|
|
27309
|
-
this.cache.set(key, { entries });
|
|
27310
|
-
return entries;
|
|
27311
|
-
}
|
|
27312
|
-
/**
|
|
27313
|
-
* upsert:
|
|
27314
|
-
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
27315
|
-
* (保留首次入群人 / 入群时刻)
|
|
27316
|
-
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
27317
|
-
*
|
|
27318
|
-
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
27319
|
-
*/
|
|
27320
|
-
upsert(scope, sessionId, input, now = Date.now()) {
|
|
27321
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27322
|
-
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
27323
|
-
let next;
|
|
27324
|
-
if (idx >= 0) {
|
|
27325
|
-
const prev = entries[idx];
|
|
27326
|
-
next = {
|
|
27327
|
-
...prev,
|
|
27328
|
-
size: input.size,
|
|
27329
|
-
mime: input.mime,
|
|
27330
|
-
lastEditedAt: now,
|
|
27331
|
-
stale: false
|
|
27332
|
-
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
27333
|
-
};
|
|
27334
|
-
entries[idx] = next;
|
|
27335
|
-
} else {
|
|
27336
|
-
next = {
|
|
27337
|
-
id: `gf-${import_node_crypto5.default.randomBytes(6).toString("base64url")}`,
|
|
27338
|
-
relPath: input.relPath,
|
|
27339
|
-
from: input.from,
|
|
27340
|
-
label: input.label,
|
|
27341
|
-
size: input.size,
|
|
27342
|
-
mime: input.mime,
|
|
27343
|
-
addedAt: now
|
|
27344
|
-
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
27345
|
-
};
|
|
27346
|
-
entries.push(next);
|
|
27347
|
-
}
|
|
27348
|
-
this.writeFile(scope, sessionId, entries);
|
|
27349
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27350
|
-
return next;
|
|
27351
|
-
}
|
|
27352
|
-
/**
|
|
27353
|
-
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
27354
|
-
* - 命中 → stale=true,UI 灰显
|
|
27355
|
-
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
27356
|
-
*
|
|
27357
|
-
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
27358
|
-
*/
|
|
27359
|
-
markStale(scope, sessionId, relPath) {
|
|
27360
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27361
|
-
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27362
|
-
if (idx < 0) return;
|
|
27363
|
-
if (entries[idx].stale) return;
|
|
27364
|
-
entries[idx] = { ...entries[idx], stale: true };
|
|
27365
|
-
this.writeFile(scope, sessionId, entries);
|
|
27366
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
26287
|
+
}
|
|
26288
|
+
return;
|
|
27367
26289
|
}
|
|
27368
|
-
|
|
27369
|
-
|
|
27370
|
-
|
|
27371
|
-
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
27372
|
-
*
|
|
27373
|
-
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
27374
|
-
*/
|
|
27375
|
-
remove(scope, sessionId, relPath) {
|
|
27376
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27377
|
-
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27378
|
-
if (idx < 0) return false;
|
|
27379
|
-
entries.splice(idx, 1);
|
|
27380
|
-
this.writeFile(scope, sessionId, entries);
|
|
27381
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27382
|
-
return true;
|
|
26290
|
+
if (!stat.isFile()) {
|
|
26291
|
+
sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
|
|
26292
|
+
return;
|
|
27383
26293
|
}
|
|
27384
|
-
|
|
26294
|
+
const mime = lookupMime(absPath);
|
|
26295
|
+
const basename = import_node_path16.default.basename(absPath);
|
|
26296
|
+
res.writeHead(200, {
|
|
26297
|
+
"Content-Type": mime,
|
|
26298
|
+
"Content-Length": String(stat.size),
|
|
26299
|
+
"Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename)}`,
|
|
26300
|
+
// 防止浏览器把任意 mime 当 html 渲染
|
|
26301
|
+
"X-Content-Type-Options": "nosniff"
|
|
26302
|
+
});
|
|
26303
|
+
const stream = import_node_fs14.default.createReadStream(absPath);
|
|
26304
|
+
stream.on("error", (err) => {
|
|
26305
|
+
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
26306
|
+
res.destroy();
|
|
26307
|
+
});
|
|
26308
|
+
stream.pipe(res);
|
|
26309
|
+
}
|
|
27385
26310
|
|
|
27386
26311
|
// src/discovery/state-file.ts
|
|
27387
26312
|
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
@@ -28061,32 +26986,26 @@ function authFilePath(dataDir) {
|
|
|
28061
26986
|
}
|
|
28062
26987
|
function loadOrCreateAuthFile(opts) {
|
|
28063
26988
|
const file = authFilePath(opts.dataDir);
|
|
28064
|
-
const generate = opts.generate ??
|
|
28065
|
-
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
26989
|
+
const generate = opts.generate ?? defaultGenerate;
|
|
28066
26990
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
28067
26991
|
const existing = readAuthFile(file);
|
|
28068
|
-
if (existing && existing.token && existing.signSecret
|
|
26992
|
+
if (existing && existing.token && existing.signSecret) {
|
|
28069
26993
|
return {
|
|
28070
26994
|
token: existing.token,
|
|
28071
26995
|
signSecret: existing.signSecret,
|
|
28072
|
-
ownerPrincipalId: existing.ownerPrincipalId,
|
|
28073
26996
|
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
28074
26997
|
};
|
|
28075
26998
|
}
|
|
28076
26999
|
const token = existing?.token || generate();
|
|
28077
27000
|
const signSecret = existing?.signSecret || generate();
|
|
28078
|
-
const ownerPrincipalId = existing?.ownerPrincipalId || genId();
|
|
28079
27001
|
const createdAt = existing?.createdAt || now().toISOString();
|
|
28080
|
-
const next = { token, signSecret,
|
|
27002
|
+
const next = { token, signSecret, createdAt };
|
|
28081
27003
|
writeAuthFile(file, next);
|
|
28082
27004
|
return next;
|
|
28083
27005
|
}
|
|
28084
|
-
function
|
|
27006
|
+
function defaultGenerate() {
|
|
28085
27007
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
28086
27008
|
}
|
|
28087
|
-
function defaultGenerateOwnerPrincipalId() {
|
|
28088
|
-
return `owner-${import_node_crypto8.default.randomUUID()}`;
|
|
28089
|
-
}
|
|
28090
27009
|
function readAuthFile(file) {
|
|
28091
27010
|
try {
|
|
28092
27011
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
@@ -28097,7 +27016,6 @@ function readAuthFile(file) {
|
|
|
28097
27016
|
return {
|
|
28098
27017
|
token: parsed.token,
|
|
28099
27018
|
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
28100
|
-
ownerPrincipalId: typeof parsed.ownerPrincipalId === "string" && parsed.ownerPrincipalId.length > 0 ? parsed.ownerPrincipalId : void 0,
|
|
28101
27019
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
28102
27020
|
};
|
|
28103
27021
|
} catch (err) {
|
|
@@ -28211,63 +27129,10 @@ function forkSession(input) {
|
|
|
28211
27129
|
return { forkedToolSessionId, forkedFilePath };
|
|
28212
27130
|
}
|
|
28213
27131
|
|
|
28214
|
-
// src/permission/capability.ts
|
|
28215
|
-
function matchResource(grant, target) {
|
|
28216
|
-
if (grant.type === "*") return true;
|
|
28217
|
-
if (grant.type !== target.type) return false;
|
|
28218
|
-
if (grant.id === "*") return true;
|
|
28219
|
-
return grant.id === target.id;
|
|
28220
|
-
}
|
|
28221
|
-
function assertGrant(grants, resource, action) {
|
|
28222
|
-
for (const g2 of grants) {
|
|
28223
|
-
if (!matchResource(g2.resource, resource)) continue;
|
|
28224
|
-
if (g2.actions.includes(action)) return true;
|
|
28225
|
-
if (g2.actions.includes("admin")) return true;
|
|
28226
|
-
}
|
|
28227
|
-
return false;
|
|
28228
|
-
}
|
|
28229
|
-
|
|
28230
|
-
// src/permission/session-access.ts
|
|
28231
|
-
function canAccessSession(ctx, sessionId, action, deps) {
|
|
28232
|
-
if (ctx.principal.kind === "owner") return true;
|
|
28233
|
-
const file = deps.readSession(sessionId);
|
|
28234
|
-
if (!file) return true;
|
|
28235
|
-
return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
|
|
28236
|
-
}
|
|
28237
|
-
function canAccessPersona(grants, personaId, action) {
|
|
28238
|
-
if (!personaId) return false;
|
|
28239
|
-
const resource = { type: "persona", id: personaId };
|
|
28240
|
-
return assertGrant(grants, resource, action);
|
|
28241
|
-
}
|
|
28242
|
-
|
|
28243
27132
|
// src/handlers/session.ts
|
|
28244
|
-
init_protocol();
|
|
28245
27133
|
function buildSessionHandlers(deps) {
|
|
28246
|
-
const { manager, observer, getAdapter: getAdapter2
|
|
28247
|
-
const
|
|
28248
|
-
if (!ctx) return;
|
|
28249
|
-
const ok = canAccessSession(ctx, sessionId, action, {
|
|
28250
|
-
readSession: (sid) => store.read(sid)
|
|
28251
|
-
});
|
|
28252
|
-
if (!ok) {
|
|
28253
|
-
throw new ClawdError(
|
|
28254
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28255
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} session ${sessionId}`
|
|
28256
|
-
);
|
|
28257
|
-
}
|
|
28258
|
-
};
|
|
28259
|
-
const ensurePersonaAccess = (ctx, personaId, action) => {
|
|
28260
|
-
if (!ctx) return;
|
|
28261
|
-
if (ctx.principal.kind === "owner") return;
|
|
28262
|
-
const ok = canAccessPersona(ctx.grants, personaId, action);
|
|
28263
|
-
if (!ok) {
|
|
28264
|
-
throw new ClawdError(
|
|
28265
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28266
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} on persona:${personaId ?? "<none>"}`
|
|
28267
|
-
);
|
|
28268
|
-
}
|
|
28269
|
-
};
|
|
28270
|
-
const create = async (frame, _client, ctx) => {
|
|
27134
|
+
const { manager, observer, getAdapter: getAdapter2 } = deps;
|
|
27135
|
+
const create = async (frame) => {
|
|
28271
27136
|
const args = SessionCreateArgs.parse(frame);
|
|
28272
27137
|
if (args.ownerPersonaId) {
|
|
28273
27138
|
const persona = deps.personaRegistry.get(args.ownerPersonaId);
|
|
@@ -28275,75 +27140,58 @@ function buildSessionHandlers(deps) {
|
|
|
28275
27140
|
throw new Error(`persona not found: ${args.ownerPersonaId}`);
|
|
28276
27141
|
}
|
|
28277
27142
|
}
|
|
28278
|
-
|
|
28279
|
-
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
28280
|
-
const { response, broadcast } = manager.create(args, creatorPrincipalId);
|
|
27143
|
+
const { response, broadcast } = manager.create(args);
|
|
28281
27144
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28282
27145
|
};
|
|
28283
|
-
const list = async (
|
|
27146
|
+
const list = async () => {
|
|
28284
27147
|
const { response } = manager.list();
|
|
28285
|
-
if (ctx && ctx.principal.kind === "guest") {
|
|
28286
|
-
const sessions = response.sessions ?? [];
|
|
28287
|
-
const capId = ctx.capabilityId;
|
|
28288
|
-
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter((s) => s.creatorPrincipalId === capId);
|
|
28289
|
-
return { response: { type: "session:list", sessions: filtered } };
|
|
28290
|
-
}
|
|
28291
27148
|
return { response: { type: "session:list", ...response } };
|
|
28292
27149
|
};
|
|
28293
|
-
const get = async (frame
|
|
27150
|
+
const get = async (frame) => {
|
|
28294
27151
|
const args = SessionIdArgs.parse(frame);
|
|
28295
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28296
27152
|
const { response } = manager.get(args);
|
|
28297
27153
|
return { response: { type: "session:info", ...response } };
|
|
28298
27154
|
};
|
|
28299
|
-
const update = async (frame
|
|
27155
|
+
const update = async (frame) => {
|
|
28300
27156
|
const args = SessionUpdateArgs.parse(frame);
|
|
28301
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28302
27157
|
const { response, broadcast } = manager.update(args);
|
|
28303
27158
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28304
27159
|
};
|
|
28305
|
-
const del = async (frame
|
|
27160
|
+
const del = async (frame) => {
|
|
28306
27161
|
const args = SessionIdArgs.parse(frame);
|
|
28307
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28308
27162
|
const { broadcast } = manager.delete(args);
|
|
28309
27163
|
return {
|
|
28310
27164
|
response: { type: "session:deleted", sessionId: args.sessionId },
|
|
28311
27165
|
broadcast
|
|
28312
27166
|
};
|
|
28313
27167
|
};
|
|
28314
|
-
const send = async (frame
|
|
27168
|
+
const send = async (frame) => {
|
|
28315
27169
|
const args = SessionSendArgs.parse(frame);
|
|
28316
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28317
27170
|
const { broadcast } = manager.send(args);
|
|
28318
27171
|
return { response: { type: "session:send", ok: true }, broadcast };
|
|
28319
27172
|
};
|
|
28320
|
-
const stop = async (frame
|
|
27173
|
+
const stop = async (frame) => {
|
|
28321
27174
|
const args = SessionIdArgs.parse(frame);
|
|
28322
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28323
27175
|
const { broadcast } = await manager.stop(args);
|
|
28324
27176
|
return { response: { type: "session:stop", ok: true }, broadcast };
|
|
28325
27177
|
};
|
|
28326
|
-
const interrupt = async (frame
|
|
27178
|
+
const interrupt = async (frame) => {
|
|
28327
27179
|
const args = SessionIdArgs.parse(frame);
|
|
28328
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28329
27180
|
const { broadcast } = await manager.interrupt(args);
|
|
28330
27181
|
return { response: { type: "session:interrupt", ok: true }, broadcast };
|
|
28331
27182
|
};
|
|
28332
|
-
const rewind = async (frame
|
|
27183
|
+
const rewind = async (frame) => {
|
|
28333
27184
|
const args = SessionRewindArgs.parse(frame);
|
|
28334
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28335
27185
|
const { response, broadcast } = await manager.rewind(args);
|
|
28336
27186
|
return { response: { type: "session:rewind", ...response }, broadcast };
|
|
28337
27187
|
};
|
|
28338
|
-
const rewindDiff = async (frame
|
|
27188
|
+
const rewindDiff = async (frame) => {
|
|
28339
27189
|
const args = SessionRewindDiffArgs.parse(frame);
|
|
28340
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28341
27190
|
const { response } = await manager.rewindDiff(args);
|
|
28342
27191
|
return { response: { type: "session:rewind-diff", ...response } };
|
|
28343
27192
|
};
|
|
28344
|
-
const rewindableMessageIds = async (frame
|
|
27193
|
+
const rewindableMessageIds = async (frame) => {
|
|
28345
27194
|
const args = SessionRewindableMessageIdsArgs.parse(frame);
|
|
28346
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28347
27195
|
const { response } = manager.rewindableMessageIds(args);
|
|
28348
27196
|
return {
|
|
28349
27197
|
response: { type: "session:rewindable-message-ids", ...response }
|
|
@@ -28354,22 +27202,19 @@ function buildSessionHandlers(deps) {
|
|
|
28354
27202
|
const result = forkSession(args);
|
|
28355
27203
|
return { response: { type: "session:fork", ...result } };
|
|
28356
27204
|
};
|
|
28357
|
-
const newSession = async (frame
|
|
27205
|
+
const newSession = async (frame) => {
|
|
28358
27206
|
const args = SessionIdArgs.parse(frame);
|
|
28359
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28360
27207
|
observer.stop(args.sessionId);
|
|
28361
27208
|
const { response, broadcast } = manager.newSession(args);
|
|
28362
27209
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28363
27210
|
};
|
|
28364
|
-
const resume = async (frame
|
|
27211
|
+
const resume = async (frame) => {
|
|
28365
27212
|
const args = SessionResumeArgs.parse(frame);
|
|
28366
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28367
27213
|
const { response, broadcast } = manager.resume(args);
|
|
28368
27214
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28369
27215
|
};
|
|
28370
|
-
const observe = async (frame
|
|
27216
|
+
const observe = async (frame) => {
|
|
28371
27217
|
const args = SessionObserveArgs.parse(frame);
|
|
28372
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28373
27218
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
28374
27219
|
const sessionFile = file;
|
|
28375
27220
|
manager.ensureSession(sessionFile);
|
|
@@ -28383,17 +27228,14 @@ function buildSessionHandlers(deps) {
|
|
|
28383
27228
|
});
|
|
28384
27229
|
return { response: { type: "session:observe", ok: true } };
|
|
28385
27230
|
};
|
|
28386
|
-
const events = async (frame
|
|
27231
|
+
const events = async (frame) => {
|
|
28387
27232
|
const args = SessionEventsArgs.parse(frame);
|
|
28388
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28389
27233
|
const { response } = manager.getEvents(args);
|
|
28390
27234
|
return { response: { type: "session:events", ...response } };
|
|
28391
27235
|
};
|
|
28392
|
-
const subscribe = async (frame, client
|
|
28393
|
-
if (typeof frame.sessionId === "string")
|
|
28394
|
-
ensureSessionAccess(ctx, frame.sessionId, "read");
|
|
27236
|
+
const subscribe = async (frame, client) => {
|
|
27237
|
+
if (typeof frame.sessionId === "string")
|
|
28395
27238
|
addSubscription(client, frame.sessionId);
|
|
28396
|
-
}
|
|
28397
27239
|
return {
|
|
28398
27240
|
response: { type: "subscribed", sessionId: frame.sessionId }
|
|
28399
27241
|
};
|
|
@@ -28405,9 +27247,8 @@ function buildSessionHandlers(deps) {
|
|
|
28405
27247
|
response: { type: "unsubscribed", sessionId: frame.sessionId }
|
|
28406
27248
|
};
|
|
28407
27249
|
};
|
|
28408
|
-
const pin = async (frame
|
|
27250
|
+
const pin = async (frame) => {
|
|
28409
27251
|
const args = SessionPinArgs.parse(frame);
|
|
28410
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28411
27252
|
const { response, broadcast } = manager.pin(args);
|
|
28412
27253
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28413
27254
|
};
|
|
@@ -28416,15 +27257,13 @@ function buildSessionHandlers(deps) {
|
|
|
28416
27257
|
const { response, broadcast } = manager.reorderPins(args);
|
|
28417
27258
|
return { response: { type: "session:reorderPins", ...response }, broadcast };
|
|
28418
27259
|
};
|
|
28419
|
-
const answerQuestion = async (frame
|
|
27260
|
+
const answerQuestion = async (frame) => {
|
|
28420
27261
|
const args = AnswerQuestionArgs.parse(frame);
|
|
28421
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28422
27262
|
const { response, broadcast } = manager.answerQuestion(args);
|
|
28423
27263
|
return { response: { type: "session:answerQuestion", ...response }, broadcast };
|
|
28424
27264
|
};
|
|
28425
|
-
const cancelQuestion = async (frame
|
|
27265
|
+
const cancelQuestion = async (frame) => {
|
|
28426
27266
|
const args = CancelQuestionArgs.parse(frame);
|
|
28427
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28428
27267
|
const { response, broadcast } = await manager.cancelQuestion(args);
|
|
28429
27268
|
return { response: { type: "session:cancelQuestion", ...response }, broadcast };
|
|
28430
27269
|
};
|
|
@@ -28519,7 +27358,6 @@ function listRecentDirs(store, limit = 50) {
|
|
|
28519
27358
|
}
|
|
28520
27359
|
|
|
28521
27360
|
// src/handlers/history.ts
|
|
28522
|
-
init_protocol();
|
|
28523
27361
|
function buildHistoryHandlers(deps) {
|
|
28524
27362
|
const { manager, history, store } = deps;
|
|
28525
27363
|
const projects = async () => {
|
|
@@ -28531,27 +27369,17 @@ function buildHistoryHandlers(deps) {
|
|
|
28531
27369
|
const sessions = await history.listSessions(args);
|
|
28532
27370
|
return { response: { type: "history:list", sessions } };
|
|
28533
27371
|
};
|
|
28534
|
-
const read = async (frame
|
|
27372
|
+
const read = async (frame) => {
|
|
28535
27373
|
const args = HistoryReadArgs.parse(frame);
|
|
28536
27374
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
28537
|
-
|
|
28538
|
-
if (ctx && ctx.principal.kind === "guest") {
|
|
28539
|
-
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.capabilityId;
|
|
28540
|
-
if (!ok) {
|
|
28541
|
-
throw new ClawdError(
|
|
28542
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28543
|
-
`principal guest:${ctx.principal.id} cannot read history of session ${args.sessionId}`
|
|
28544
|
-
);
|
|
28545
|
-
}
|
|
28546
|
-
}
|
|
28547
|
-
if (!f.toolSessionId) {
|
|
27375
|
+
if (!file.toolSessionId) {
|
|
28548
27376
|
return {
|
|
28549
27377
|
response: { type: "history:read", messages: [], total: 0, offset: 0 }
|
|
28550
27378
|
};
|
|
28551
27379
|
}
|
|
28552
27380
|
const res = await history.read({
|
|
28553
|
-
cwd:
|
|
28554
|
-
toolSessionId:
|
|
27381
|
+
cwd: file.cwd,
|
|
27382
|
+
toolSessionId: file.toolSessionId,
|
|
28555
27383
|
limit: args.limit,
|
|
28556
27384
|
offset: args.offset
|
|
28557
27385
|
});
|
|
@@ -28733,345 +27561,6 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
28733
27561
|
};
|
|
28734
27562
|
}
|
|
28735
27563
|
|
|
28736
|
-
// src/handlers/capability.ts
|
|
28737
|
-
init_zod();
|
|
28738
|
-
init_protocol();
|
|
28739
|
-
var DeleteArgsSchema = external_exports.object({
|
|
28740
|
-
capabilityId: external_exports.string().min(1)
|
|
28741
|
-
}).strict();
|
|
28742
|
-
function buildCapabilityHandlers(deps) {
|
|
28743
|
-
const { manager, getShareBaseUrl } = deps;
|
|
28744
|
-
const issue = async (frame) => {
|
|
28745
|
-
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28746
|
-
const args = CapabilityIssueArgsSchema.parse(rest);
|
|
28747
|
-
const { token, capability } = manager.issue({
|
|
28748
|
-
displayName: args.displayName,
|
|
28749
|
-
grants: args.grants,
|
|
28750
|
-
...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
|
|
28751
|
-
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
28752
|
-
});
|
|
28753
|
-
const base = getShareBaseUrl().replace(/\/$/, "");
|
|
28754
|
-
const shareUrl = `${base}/?token=${encodeURIComponent(token)}`;
|
|
28755
|
-
return {
|
|
28756
|
-
response: {
|
|
28757
|
-
type: "capability:issued",
|
|
28758
|
-
token,
|
|
28759
|
-
shareUrl,
|
|
28760
|
-
capability: stripSecretHash(capability)
|
|
28761
|
-
}
|
|
28762
|
-
};
|
|
28763
|
-
};
|
|
28764
|
-
const list = async () => {
|
|
28765
|
-
return {
|
|
28766
|
-
response: {
|
|
28767
|
-
type: "capability:list",
|
|
28768
|
-
capabilities: manager.list().map(stripSecretHash)
|
|
28769
|
-
}
|
|
28770
|
-
};
|
|
28771
|
-
};
|
|
28772
|
-
const del = async (frame) => {
|
|
28773
|
-
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28774
|
-
const args = DeleteArgsSchema.parse(rest);
|
|
28775
|
-
const result = manager.delete(args.capabilityId);
|
|
28776
|
-
if (!result) {
|
|
28777
|
-
throw new ClawdError(
|
|
28778
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28779
|
-
`capability not found: ${args.capabilityId}`
|
|
28780
|
-
);
|
|
28781
|
-
}
|
|
28782
|
-
return {
|
|
28783
|
-
response: {
|
|
28784
|
-
type: "capability:deleted",
|
|
28785
|
-
capabilityId: args.capabilityId
|
|
28786
|
-
}
|
|
28787
|
-
};
|
|
28788
|
-
};
|
|
28789
|
-
const bindPeer = async (frame) => {
|
|
28790
|
-
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28791
|
-
const args = CapabilityBindPeerArgsSchema.parse(rest);
|
|
28792
|
-
try {
|
|
28793
|
-
manager.recordPeerHello(args.capabilityId, {
|
|
28794
|
-
ownerPrincipalId: args.peerOwnerId,
|
|
28795
|
-
displayName: args.peerDisplayName,
|
|
28796
|
-
now: Date.now()
|
|
28797
|
-
});
|
|
28798
|
-
} catch (e) {
|
|
28799
|
-
throw new ClawdError(
|
|
28800
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28801
|
-
`capability:bindPeer: ${e.message}`
|
|
28802
|
-
);
|
|
28803
|
-
}
|
|
28804
|
-
return {
|
|
28805
|
-
response: { type: "capability:bindPeer:ok", capabilityId: args.capabilityId }
|
|
28806
|
-
};
|
|
28807
|
-
};
|
|
28808
|
-
return {
|
|
28809
|
-
"capability:issue": issue,
|
|
28810
|
-
"capability:list": list,
|
|
28811
|
-
"capability:delete": del,
|
|
28812
|
-
"capability:bindPeer": bindPeer
|
|
28813
|
-
};
|
|
28814
|
-
}
|
|
28815
|
-
|
|
28816
|
-
// src/handlers/inbox.ts
|
|
28817
|
-
init_protocol();
|
|
28818
|
-
function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
28819
|
-
if (ctx.principal.kind === "owner") {
|
|
28820
|
-
if (!argsPeerOwnerId) {
|
|
28821
|
-
throw new ClawdError(
|
|
28822
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28823
|
-
"inbox: owner ctx requires peerOwnerId"
|
|
28824
|
-
);
|
|
28825
|
-
}
|
|
28826
|
-
return argsPeerOwnerId;
|
|
28827
|
-
}
|
|
28828
|
-
if (!ctx.capabilityId) {
|
|
28829
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
28830
|
-
}
|
|
28831
|
-
const cap = capManager.findById(ctx.capabilityId);
|
|
28832
|
-
if (!cap?.peerOwnerId) {
|
|
28833
|
-
throw new ClawdError(
|
|
28834
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28835
|
-
`inbox: guest cap ${ctx.capabilityId} has no peerOwnerId (recordPeerHello not yet)`
|
|
28836
|
-
);
|
|
28837
|
-
}
|
|
28838
|
-
if (argsPeerOwnerId && argsPeerOwnerId !== cap.peerOwnerId) {
|
|
28839
|
-
throw new ClawdError(
|
|
28840
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28841
|
-
`inbox: guest args.peerOwnerId mismatch (cap.peerOwnerId=${cap.peerOwnerId}, args=${argsPeerOwnerId})`
|
|
28842
|
-
);
|
|
28843
|
-
}
|
|
28844
|
-
return cap.peerOwnerId;
|
|
28845
|
-
}
|
|
28846
|
-
function buildInboxHandlers(deps) {
|
|
28847
|
-
const { manager, capManager } = deps;
|
|
28848
|
-
const postMessage = async (frame, _client, ctx) => {
|
|
28849
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28850
|
-
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
28851
|
-
if (!ctx) {
|
|
28852
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
28853
|
-
}
|
|
28854
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
28855
|
-
const message = manager.postMessage({
|
|
28856
|
-
peerOwnerId,
|
|
28857
|
-
senderPrincipalId: ctx.principal.id,
|
|
28858
|
-
text: args.text,
|
|
28859
|
-
id: args.id,
|
|
28860
|
-
createdAt: args.createdAt
|
|
28861
|
-
});
|
|
28862
|
-
return {
|
|
28863
|
-
response: { type: "inbox:postMessage:ok", message }
|
|
28864
|
-
};
|
|
28865
|
-
};
|
|
28866
|
-
const list = async (frame, _client, ctx) => {
|
|
28867
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28868
|
-
const args = InboxListArgsSchema.parse(rest);
|
|
28869
|
-
if (!ctx) {
|
|
28870
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
28871
|
-
}
|
|
28872
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
28873
|
-
const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
|
|
28874
|
-
return {
|
|
28875
|
-
response: { type: "inbox:list:ok", peerOwnerId, messages }
|
|
28876
|
-
};
|
|
28877
|
-
};
|
|
28878
|
-
const markRead = async (frame, _client, ctx) => {
|
|
28879
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28880
|
-
const args = InboxMarkReadArgsSchema.parse(rest);
|
|
28881
|
-
if (!ctx) {
|
|
28882
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
28883
|
-
}
|
|
28884
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId, capManager);
|
|
28885
|
-
const updated = manager.markRead({
|
|
28886
|
-
peerOwnerId,
|
|
28887
|
-
principalId: ctx.principal.id,
|
|
28888
|
-
upToCreatedAt: args.upToCreatedAt
|
|
28889
|
-
});
|
|
28890
|
-
return {
|
|
28891
|
-
response: {
|
|
28892
|
-
type: "inbox:markRead:ok",
|
|
28893
|
-
peerOwnerId,
|
|
28894
|
-
upToCreatedAt: args.upToCreatedAt,
|
|
28895
|
-
updated
|
|
28896
|
-
}
|
|
28897
|
-
};
|
|
28898
|
-
};
|
|
28899
|
-
return {
|
|
28900
|
-
"inbox:postMessage": postMessage,
|
|
28901
|
-
"inbox:list": list,
|
|
28902
|
-
"inbox:markRead": markRead
|
|
28903
|
-
};
|
|
28904
|
-
}
|
|
28905
|
-
|
|
28906
|
-
// src/handlers/received-capability.ts
|
|
28907
|
-
init_protocol();
|
|
28908
|
-
function ensureOwner(ctx) {
|
|
28909
|
-
if (!ctx || ctx.principal.kind !== "owner") {
|
|
28910
|
-
throw new ClawdError(
|
|
28911
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28912
|
-
"UNAUTHORIZED: received-capability:* requires owner ctx"
|
|
28913
|
-
);
|
|
28914
|
-
}
|
|
28915
|
-
}
|
|
28916
|
-
function ensureGuestCtx(ctx) {
|
|
28917
|
-
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
28918
|
-
throw new ClawdError(
|
|
28919
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28920
|
-
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
28921
|
-
);
|
|
28922
|
-
}
|
|
28923
|
-
}
|
|
28924
|
-
function buildReceivedCapabilityHandlers(deps) {
|
|
28925
|
-
const now = deps.now ?? Date.now;
|
|
28926
|
-
const list = async (_frame, _client, ctx) => {
|
|
28927
|
-
ensureOwner(ctx);
|
|
28928
|
-
return {
|
|
28929
|
-
response: {
|
|
28930
|
-
type: "received-capability:list:ok",
|
|
28931
|
-
receivedCaps: deps.store.list()
|
|
28932
|
-
}
|
|
28933
|
-
};
|
|
28934
|
-
};
|
|
28935
|
-
const add = async (frame, _client, ctx) => {
|
|
28936
|
-
ensureOwner(ctx);
|
|
28937
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28938
|
-
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
28939
|
-
let conn;
|
|
28940
|
-
try {
|
|
28941
|
-
conn = await deps.connectRemote({
|
|
28942
|
-
url: args.remoteUrl,
|
|
28943
|
-
token: args.token,
|
|
28944
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
28945
|
-
selfDisplayName: deps.selfDisplayName()
|
|
28946
|
-
});
|
|
28947
|
-
} catch (e) {
|
|
28948
|
-
throw new ClawdError(
|
|
28949
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28950
|
-
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
28951
|
-
);
|
|
28952
|
-
}
|
|
28953
|
-
try {
|
|
28954
|
-
const wh = await conn.whoami();
|
|
28955
|
-
const rc = {
|
|
28956
|
-
ownerPrincipalId: wh.ownerId,
|
|
28957
|
-
ownerDisplayName: wh.displayName,
|
|
28958
|
-
remoteUrl: args.remoteUrl,
|
|
28959
|
-
capabilityId: wh.capabilityId,
|
|
28960
|
-
capabilityToken: args.token,
|
|
28961
|
-
grants: wh.grants,
|
|
28962
|
-
receivedAt: now()
|
|
28963
|
-
};
|
|
28964
|
-
deps.store.upsert(rc);
|
|
28965
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
28966
|
-
return {
|
|
28967
|
-
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
28968
|
-
};
|
|
28969
|
-
} finally {
|
|
28970
|
-
try {
|
|
28971
|
-
conn.close();
|
|
28972
|
-
} catch {
|
|
28973
|
-
}
|
|
28974
|
-
}
|
|
28975
|
-
};
|
|
28976
|
-
const remove = async (frame, _client, ctx) => {
|
|
28977
|
-
ensureOwner(ctx);
|
|
28978
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28979
|
-
const args = ReceivedCapabilityRemoveArgsSchema.parse(rest);
|
|
28980
|
-
deps.store.remove(args.ownerPrincipalId);
|
|
28981
|
-
deps.broadcast({
|
|
28982
|
-
type: "received-capability:removed",
|
|
28983
|
-
ownerPrincipalId: args.ownerPrincipalId
|
|
28984
|
-
});
|
|
28985
|
-
return {
|
|
28986
|
-
response: {
|
|
28987
|
-
type: "received-capability:remove:ok",
|
|
28988
|
-
ownerPrincipalId: args.ownerPrincipalId
|
|
28989
|
-
}
|
|
28990
|
-
};
|
|
28991
|
-
};
|
|
28992
|
-
const autoAttach = async (frame, _client, ctx) => {
|
|
28993
|
-
ensureGuestCtx(ctx);
|
|
28994
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28995
|
-
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
28996
|
-
const rc = {
|
|
28997
|
-
ownerPrincipalId: args.ownerPrincipalId,
|
|
28998
|
-
ownerDisplayName: args.ownerDisplayName,
|
|
28999
|
-
remoteUrl: args.remoteUrl,
|
|
29000
|
-
capabilityId: args.capabilityId,
|
|
29001
|
-
capabilityToken: args.token,
|
|
29002
|
-
grants: args.grants,
|
|
29003
|
-
receivedAt: now()
|
|
29004
|
-
};
|
|
29005
|
-
deps.store.upsert(rc);
|
|
29006
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
29007
|
-
return {
|
|
29008
|
-
response: { type: "received-capability:autoAttach:ok" }
|
|
29009
|
-
};
|
|
29010
|
-
};
|
|
29011
|
-
return {
|
|
29012
|
-
"received-capability:list": list,
|
|
29013
|
-
"received-capability:add": add,
|
|
29014
|
-
"received-capability:remove": remove,
|
|
29015
|
-
"received-capability:autoAttach": autoAttach
|
|
29016
|
-
};
|
|
29017
|
-
}
|
|
29018
|
-
|
|
29019
|
-
// src/handlers/whoami.ts
|
|
29020
|
-
init_protocol();
|
|
29021
|
-
function buildWhoamiHandler(deps) {
|
|
29022
|
-
return async (_frame, _client, ctx) => {
|
|
29023
|
-
if (!ctx) {
|
|
29024
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
29025
|
-
}
|
|
29026
|
-
const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
|
|
29027
|
-
let capability;
|
|
29028
|
-
if (ctx.principal.kind === "owner") {
|
|
29029
|
-
capability = {
|
|
29030
|
-
id: deps.ownerPrincipalId,
|
|
29031
|
-
displayName: deps.ownerDisplayName,
|
|
29032
|
-
grants: ctx.grants,
|
|
29033
|
-
issuedAt: 0,
|
|
29034
|
-
usedCount: 0
|
|
29035
|
-
};
|
|
29036
|
-
} else {
|
|
29037
|
-
if (!ctx.capabilityId) {
|
|
29038
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
|
|
29039
|
-
}
|
|
29040
|
-
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
29041
|
-
if (!cap) {
|
|
29042
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
|
|
29043
|
-
}
|
|
29044
|
-
capability = stripSecretHash(cap);
|
|
29045
|
-
}
|
|
29046
|
-
const grantedPersonas = [];
|
|
29047
|
-
const isGuest = ctx.principal.kind === "guest";
|
|
29048
|
-
const hasWildcard = capability.grants.some((g2) => g2.resource.type === "*");
|
|
29049
|
-
if (hasWildcard) {
|
|
29050
|
-
for (const id of deps.personaStore.list()) {
|
|
29051
|
-
const file = deps.personaStore.read(id);
|
|
29052
|
-
if (!file) continue;
|
|
29053
|
-
if (isGuest && !file.public) continue;
|
|
29054
|
-
grantedPersonas.push({ id: file.personaId, displayName: file.label });
|
|
29055
|
-
}
|
|
29056
|
-
} else {
|
|
29057
|
-
for (const g2 of capability.grants) {
|
|
29058
|
-
if (g2.resource.type !== "persona") continue;
|
|
29059
|
-
const file = deps.personaStore.read(g2.resource.id);
|
|
29060
|
-
if (!file) continue;
|
|
29061
|
-
grantedPersonas.push({ id: file.personaId, displayName: file.label });
|
|
29062
|
-
}
|
|
29063
|
-
}
|
|
29064
|
-
return {
|
|
29065
|
-
response: {
|
|
29066
|
-
type: "whoami:ok",
|
|
29067
|
-
owner,
|
|
29068
|
-
capability,
|
|
29069
|
-
grantedPersonas
|
|
29070
|
-
}
|
|
29071
|
-
};
|
|
29072
|
-
};
|
|
29073
|
-
}
|
|
29074
|
-
|
|
29075
27564
|
// src/handlers/meta.ts
|
|
29076
27565
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
29077
27566
|
init_protocol();
|
|
@@ -29135,14 +27624,9 @@ function buildPersonaHandlers(deps) {
|
|
|
29135
27624
|
const persona = personaManager.create(args);
|
|
29136
27625
|
return { response: { type: "persona:info", ...persona } };
|
|
29137
27626
|
};
|
|
29138
|
-
const list = async (
|
|
29139
|
-
const all = personaRegistry.list();
|
|
29140
|
-
const isGuest = ctx?.principal.kind === "guest";
|
|
29141
|
-
const personas = isGuest ? all.filter(
|
|
29142
|
-
(p2) => p2.public && assertGrant(ctx.grants, { type: "persona", id: p2.personaId }, "read")
|
|
29143
|
-
) : all;
|
|
27627
|
+
const list = async () => {
|
|
29144
27628
|
return {
|
|
29145
|
-
response: { type: "persona:list", personas }
|
|
27629
|
+
response: { type: "persona:list", personas: personaRegistry.list() }
|
|
29146
27630
|
};
|
|
29147
27631
|
};
|
|
29148
27632
|
const get = async (frame) => {
|
|
@@ -29180,12 +27664,26 @@ function buildPersonaHandlers(deps) {
|
|
|
29180
27664
|
response: { type: "persona:deleted", personaId: args.personaId }
|
|
29181
27665
|
};
|
|
29182
27666
|
};
|
|
27667
|
+
const issueToken = async (frame) => {
|
|
27668
|
+
const args = PersonaIssueTokenArgsSchema.parse(frame);
|
|
27669
|
+
const { token, persona } = personaManager.issueToken(args.personaId, args.label);
|
|
27670
|
+
return {
|
|
27671
|
+
response: { type: "persona:tokenIssued", token, persona }
|
|
27672
|
+
};
|
|
27673
|
+
};
|
|
27674
|
+
const revokeToken = async (frame) => {
|
|
27675
|
+
const args = PersonaRevokeTokenArgsSchema.parse(frame);
|
|
27676
|
+
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
27677
|
+
return { response: { type: "persona:info", ...persona } };
|
|
27678
|
+
};
|
|
29183
27679
|
return {
|
|
29184
27680
|
"persona:create": create,
|
|
29185
27681
|
"persona:list": list,
|
|
29186
27682
|
"persona:get": get,
|
|
29187
27683
|
"persona:update": update,
|
|
29188
|
-
"persona:delete": del
|
|
27684
|
+
"persona:delete": del,
|
|
27685
|
+
"persona:issueToken": issueToken,
|
|
27686
|
+
"persona:revokeToken": revokeToken
|
|
29189
27687
|
};
|
|
29190
27688
|
}
|
|
29191
27689
|
|
|
@@ -29316,11 +27814,23 @@ function buildAttachmentHandlers(deps) {
|
|
|
29316
27814
|
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
29317
27815
|
return { response: { type: "attachment.groupList", entries } };
|
|
29318
27816
|
};
|
|
27817
|
+
const groupListPersona = async (frame) => {
|
|
27818
|
+
if (!deps.groupFileStore) {
|
|
27819
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
27820
|
+
}
|
|
27821
|
+
const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
|
|
27822
|
+
if (!parsed.success) {
|
|
27823
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
27824
|
+
}
|
|
27825
|
+
const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
|
|
27826
|
+
return { response: { type: "attachment.groupListPersona", perSession } };
|
|
27827
|
+
};
|
|
29319
27828
|
return {
|
|
29320
27829
|
"attachment.signUrl": signUrl,
|
|
29321
27830
|
"attachment.groupAdd": groupAdd,
|
|
29322
27831
|
"attachment.groupRemove": groupRemove,
|
|
29323
|
-
"attachment.groupList": groupList
|
|
27832
|
+
"attachment.groupList": groupList,
|
|
27833
|
+
"attachment.groupListPersona": groupListPersona
|
|
29324
27834
|
};
|
|
29325
27835
|
}
|
|
29326
27836
|
|
|
@@ -29338,136 +27848,10 @@ function buildMethodHandlers(deps) {
|
|
|
29338
27848
|
personaManager: deps.personaManager,
|
|
29339
27849
|
personaRegistry: deps.personaRegistry
|
|
29340
27850
|
}),
|
|
29341
|
-
...buildCapabilityHandlers({
|
|
29342
|
-
manager: deps.capabilityManager,
|
|
29343
|
-
getShareBaseUrl: deps.getShareBaseUrl
|
|
29344
|
-
}),
|
|
29345
|
-
...buildInboxHandlers({
|
|
29346
|
-
manager: deps.inboxManager,
|
|
29347
|
-
capManager: deps.capabilityManager
|
|
29348
|
-
}),
|
|
29349
|
-
...buildReceivedCapabilityHandlers({
|
|
29350
|
-
store: deps.receivedCapabilityStore,
|
|
29351
|
-
connectRemote: deps.connectRemote,
|
|
29352
|
-
broadcast: deps.broadcastToOwners,
|
|
29353
|
-
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
29354
|
-
selfDisplayName: () => deps.ownerDisplayName
|
|
29355
|
-
}),
|
|
29356
|
-
whoami: buildWhoamiHandler({
|
|
29357
|
-
ownerDisplayName: deps.ownerDisplayName,
|
|
29358
|
-
ownerPrincipalId: deps.ownerPrincipalId,
|
|
29359
|
-
personaStore: deps.personaStore,
|
|
29360
|
-
capabilityManager: deps.capabilityManager
|
|
29361
|
-
}),
|
|
29362
27851
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
29363
27852
|
};
|
|
29364
27853
|
}
|
|
29365
27854
|
|
|
29366
|
-
// src/handlers/method-grants.ts
|
|
29367
|
-
var ADMIN_ANY = {
|
|
29368
|
-
kind: "fixed",
|
|
29369
|
-
resource: { type: "*" },
|
|
29370
|
-
action: "admin"
|
|
29371
|
-
};
|
|
29372
|
-
var CAPABILITY_SCOPED = { kind: "capability-scoped" };
|
|
29373
|
-
var METHOD_GRANT_MAP = {
|
|
29374
|
-
// ---- public(meta-only,guest 也能调) ----
|
|
29375
|
-
"info": { kind: "public" },
|
|
29376
|
-
"ping": { kind: "public" },
|
|
29377
|
-
// v2 Phase 6: whoami 是身份反查,任何 authed connection (owner / guest) 都能调;
|
|
29378
|
-
// handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
|
|
29379
|
-
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
29380
|
-
"whoami": { kind: "public" },
|
|
29381
|
-
// ---- capability platform(admin-only,本 PR 新增) ----
|
|
29382
|
-
"capability:issue": ADMIN_ANY,
|
|
29383
|
-
"capability:list": ADMIN_ANY,
|
|
29384
|
-
"capability:delete": ADMIN_ANY,
|
|
29385
|
-
"capability:bindPeer": ADMIN_ANY,
|
|
29386
|
-
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
29387
|
-
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
29388
|
-
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
29389
|
-
"inbox:list": { kind: "public" },
|
|
29390
|
-
"inbox:markRead": { kind: "public" },
|
|
29391
|
-
"inbox:postMessage": { kind: "public" },
|
|
29392
|
-
// ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
|
|
29393
|
-
// owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
|
|
29394
|
-
"received-capability:list": ADMIN_ANY,
|
|
29395
|
-
"received-capability:add": ADMIN_ANY,
|
|
29396
|
-
"received-capability:remove": ADMIN_ANY,
|
|
29397
|
-
"received-capability:autoAttach": { kind: "public" },
|
|
29398
|
-
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
29399
|
-
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
29400
|
-
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
29401
|
-
"session:create": CAPABILITY_SCOPED,
|
|
29402
|
-
"session:list": CAPABILITY_SCOPED,
|
|
29403
|
-
"session:get": CAPABILITY_SCOPED,
|
|
29404
|
-
"session:update": CAPABILITY_SCOPED,
|
|
29405
|
-
"session:delete": CAPABILITY_SCOPED,
|
|
29406
|
-
"session:send": CAPABILITY_SCOPED,
|
|
29407
|
-
"session:stop": CAPABILITY_SCOPED,
|
|
29408
|
-
"session:interrupt": CAPABILITY_SCOPED,
|
|
29409
|
-
"session:rewind": CAPABILITY_SCOPED,
|
|
29410
|
-
"session:rewind-diff": CAPABILITY_SCOPED,
|
|
29411
|
-
"session:rewindable-message-ids": CAPABILITY_SCOPED,
|
|
29412
|
-
"session:fork": CAPABILITY_SCOPED,
|
|
29413
|
-
"session:new": CAPABILITY_SCOPED,
|
|
29414
|
-
"session:resume": CAPABILITY_SCOPED,
|
|
29415
|
-
"session:observe": CAPABILITY_SCOPED,
|
|
29416
|
-
"session:events": CAPABILITY_SCOPED,
|
|
29417
|
-
"session:subscribe": CAPABILITY_SCOPED,
|
|
29418
|
-
"session:unsubscribe": CAPABILITY_SCOPED,
|
|
29419
|
-
"session:pin": CAPABILITY_SCOPED,
|
|
29420
|
-
"session:reorderPins": ADMIN_ANY,
|
|
29421
|
-
// owner 全局操作,无 personaId 维度
|
|
29422
|
-
"permission:respond": CAPABILITY_SCOPED,
|
|
29423
|
-
"session:answerQuestion": CAPABILITY_SCOPED,
|
|
29424
|
-
"session:cancelQuestion": CAPABILITY_SCOPED,
|
|
29425
|
-
"history:projects": ADMIN_ANY,
|
|
29426
|
-
"history:list": ADMIN_ANY,
|
|
29427
|
-
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
29428
|
-
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
29429
|
-
// handler 内按 ctx.capabilityId === file.creatorPrincipalId 拦越权。
|
|
29430
|
-
"history:read": CAPABILITY_SCOPED,
|
|
29431
|
-
"history:subagents": ADMIN_ANY,
|
|
29432
|
-
"history:subagent-read": ADMIN_ANY,
|
|
29433
|
-
"history:recentDirs": ADMIN_ANY,
|
|
29434
|
-
"workspace:list": ADMIN_ANY,
|
|
29435
|
-
"workspace:read": ADMIN_ANY,
|
|
29436
|
-
"skills:list": ADMIN_ANY,
|
|
29437
|
-
"agents:list": ADMIN_ANY,
|
|
29438
|
-
"git:root": ADMIN_ANY,
|
|
29439
|
-
"git:branch": ADMIN_ANY,
|
|
29440
|
-
"git:branches": ADMIN_ANY,
|
|
29441
|
-
"capabilities:get": ADMIN_ANY,
|
|
29442
|
-
"persona:create": ADMIN_ANY,
|
|
29443
|
-
// persona:list 下沉到 CAPABILITY_SCOPED:guest 调用合法(用于 People 面板展开
|
|
29444
|
-
// 对方分享的 personas),handler 端按 ctx + persona.public + assertGrant 过滤。
|
|
29445
|
-
"persona:list": CAPABILITY_SCOPED,
|
|
29446
|
-
"persona:get": ADMIN_ANY,
|
|
29447
|
-
"persona:update": ADMIN_ANY,
|
|
29448
|
-
"persona:delete": ADMIN_ANY,
|
|
29449
|
-
"session:pty:input": ADMIN_ANY,
|
|
29450
|
-
"session:pty:resize": ADMIN_ANY,
|
|
29451
|
-
// file-sharing attachment.* RPC:dispatcher 用 admin-only 兜底(wire-level 拦),
|
|
29452
|
-
// 实际只有 owner 能调(personal token 链路 2026-05-21 删除,HTTP Bearer 只识别 owner)
|
|
29453
|
-
"attachment.signUrl": ADMIN_ANY,
|
|
29454
|
-
"attachment.groupAdd": ADMIN_ANY,
|
|
29455
|
-
"attachment.groupRemove": ADMIN_ANY,
|
|
29456
|
-
"attachment.groupList": ADMIN_ANY
|
|
29457
|
-
};
|
|
29458
|
-
function computeGrantForFrame(method, frame) {
|
|
29459
|
-
const rule = METHOD_GRANT_MAP[method];
|
|
29460
|
-
if (!rule) return { kind: "public" };
|
|
29461
|
-
if (rule.kind === "public") return { kind: "public" };
|
|
29462
|
-
if (rule.kind === "capability-scoped") return { kind: "public" };
|
|
29463
|
-
if (rule.kind === "fixed") {
|
|
29464
|
-
return { kind: "check", resource: rule.resource, action: rule.action };
|
|
29465
|
-
}
|
|
29466
|
-
const picked = rule.pick(frame);
|
|
29467
|
-
if (!picked) return { kind: "public" };
|
|
29468
|
-
return { kind: "check", resource: picked.resource, action: picked.action };
|
|
29469
|
-
}
|
|
29470
|
-
|
|
29471
27855
|
// src/index.ts
|
|
29472
27856
|
async function startDaemon(config) {
|
|
29473
27857
|
const logger = createLogger({
|
|
@@ -29483,90 +27867,24 @@ async function startDaemon(config) {
|
|
|
29483
27867
|
if (pre.status === "stale") {
|
|
29484
27868
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
29485
27869
|
}
|
|
29486
|
-
const authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
|
|
29487
27870
|
let resolvedAuthToken = null;
|
|
27871
|
+
let authFile = null;
|
|
29488
27872
|
if (config.authToken && config.authToken.trim()) {
|
|
29489
27873
|
resolvedAuthToken = config.authToken.trim();
|
|
29490
27874
|
} else if (config.tunnel) {
|
|
27875
|
+
authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
|
|
29491
27876
|
resolvedAuthToken = authFile.token;
|
|
29492
27877
|
}
|
|
29493
|
-
const ownerPrincipalId = authFile.ownerPrincipalId;
|
|
29494
|
-
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29495
27878
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
29496
|
-
migrateLegacyFiles(config.dataDir);
|
|
29497
|
-
const inboxStore = new InboxStore(config.dataDir);
|
|
29498
|
-
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
29499
|
-
wsServer?.broadcastToOwners(frame);
|
|
29500
|
-
});
|
|
29501
|
-
const receivedCapabilityStore = new ReceivedCapabilityStore(config.dataDir);
|
|
29502
|
-
receivedCapabilityStore.load();
|
|
29503
|
-
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
29504
|
-
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
29505
|
-
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
29506
|
-
onIssued: (cap, token) => {
|
|
29507
|
-
wsServer?.broadcastToOwners({
|
|
29508
|
-
type: "capability:tokenIssued",
|
|
29509
|
-
capability: stripSecretHash(cap),
|
|
29510
|
-
token
|
|
29511
|
-
});
|
|
29512
|
-
},
|
|
29513
|
-
onDeleted: (cap) => {
|
|
29514
|
-
const deletedAt = Date.now();
|
|
29515
|
-
wsServer?.broadcastToOwners({
|
|
29516
|
-
type: "capability:tokenDeleted",
|
|
29517
|
-
capabilityId: cap.id,
|
|
29518
|
-
deletedAt
|
|
29519
|
-
});
|
|
29520
|
-
wsServer?.closeConnectionsByCapability(cap.id);
|
|
29521
|
-
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
29522
|
-
if (cleanup.removed.length > 0) {
|
|
29523
|
-
logger.info("capability delete cascade: guest sessions removed", {
|
|
29524
|
-
capabilityId: cap.id,
|
|
29525
|
-
removedDirs: cleanup.removed
|
|
29526
|
-
});
|
|
29527
|
-
}
|
|
29528
|
-
}
|
|
29529
|
-
});
|
|
29530
|
-
migrateDropPersonsDir(config.dataDir);
|
|
29531
27879
|
let wsServer = null;
|
|
29532
27880
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
29533
27881
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
29534
|
-
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
29535
|
-
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
29536
27882
|
expectedToken: resolvedAuthToken,
|
|
29537
|
-
authenticate: (t, selfPrincipalId, selfDisplayName) => {
|
|
29538
|
-
return authenticate(t, {
|
|
29539
|
-
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
29540
|
-
ownerPrincipalId,
|
|
29541
|
-
ownerDisplayName,
|
|
29542
|
-
capabilityRegistry,
|
|
29543
|
-
selfPrincipalId,
|
|
29544
|
-
selfDisplayName,
|
|
29545
|
-
onGuestHello: (capId, ownerId, name) => {
|
|
29546
|
-
capabilityManager.recordPeerHello(capId, {
|
|
29547
|
-
ownerPrincipalId: ownerId,
|
|
29548
|
-
displayName: name,
|
|
29549
|
-
now: Date.now()
|
|
29550
|
-
});
|
|
29551
|
-
}
|
|
29552
|
-
});
|
|
29553
|
-
},
|
|
29554
|
-
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
29555
|
-
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
29556
27883
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
29557
27884
|
sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
|
|
29558
27885
|
}) : null;
|
|
29559
27886
|
resetRegistry();
|
|
29560
|
-
const
|
|
29561
|
-
if (!migrateResult.skipped && (migrateResult.movedBare || migrateResult.movedVmOwner || migrateResult.archivedListener)) {
|
|
29562
|
-
logger.info("sessions migration applied", {
|
|
29563
|
-
movedBare: migrateResult.movedBare,
|
|
29564
|
-
movedVmOwner: migrateResult.movedVmOwner,
|
|
29565
|
-
archivedListener: migrateResult.archivedListener
|
|
29566
|
-
});
|
|
29567
|
-
}
|
|
29568
|
-
const sessionStoreFactory = new SessionStoreFactory({ dataDir: config.dataDir });
|
|
29569
|
-
const store = sessionStoreFactory.forBare();
|
|
27887
|
+
const store = new SessionStore({ dataDir: config.dataDir });
|
|
29570
27888
|
const workspace = new WorkspaceBrowser();
|
|
29571
27889
|
const skills = new SkillsScanner();
|
|
29572
27890
|
const agents = new AgentsScanner();
|
|
@@ -29579,12 +27897,10 @@ async function startDaemon(config) {
|
|
|
29579
27897
|
} else {
|
|
29580
27898
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
29581
27899
|
}
|
|
27900
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29582
27901
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
29583
27902
|
const manager = new SessionManager({
|
|
29584
27903
|
store,
|
|
29585
|
-
// Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
|
|
29586
|
-
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
29587
|
-
storeFactory: sessionStoreFactory,
|
|
29588
27904
|
logger,
|
|
29589
27905
|
getAdapter,
|
|
29590
27906
|
historyReader: history,
|
|
@@ -29592,7 +27908,6 @@ async function startDaemon(config) {
|
|
|
29592
27908
|
personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
|
|
29593
27909
|
personaStore,
|
|
29594
27910
|
ownerDisplayName,
|
|
29595
|
-
ownerPrincipalId,
|
|
29596
27911
|
mode: config.mode,
|
|
29597
27912
|
broadcastFrame: (frame, target) => {
|
|
29598
27913
|
if (target === "all") {
|
|
@@ -29728,37 +28043,18 @@ async function startDaemon(config) {
|
|
|
29728
28043
|
// group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
|
|
29729
28044
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
29730
28045
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid)
|
|
29731
|
-
}
|
|
29732
|
-
// Task 1.9: capability:issue/list/revoke handler 依赖
|
|
29733
|
-
capabilityManager,
|
|
29734
|
-
// v2 Phase 5: capability:issue 返回的 shareUrl 用此 base URL 拼接。
|
|
29735
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
29736
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
29737
|
-
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
29738
|
-
ownerDisplayName,
|
|
29739
|
-
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
29740
|
-
ownerPrincipalId,
|
|
29741
|
-
personaStore,
|
|
29742
|
-
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
29743
|
-
capabilityRegistry,
|
|
29744
|
-
// capability platform v3 inbox: handler 接 manager (post/list/markRead),
|
|
29745
|
-
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
29746
|
-
inboxManager,
|
|
29747
|
-
inboxStore,
|
|
29748
|
-
// bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
|
|
29749
|
-
receivedCapabilityStore,
|
|
29750
|
-
// received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
|
|
29751
|
-
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
29752
|
-
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
29753
|
-
connectRemote
|
|
28046
|
+
}
|
|
29754
28047
|
});
|
|
29755
28048
|
const authResolver = new AuthContextResolver({
|
|
29756
|
-
ownerToken: resolvedAuthToken
|
|
28049
|
+
ownerToken: resolvedAuthToken,
|
|
28050
|
+
personaRegistry
|
|
29757
28051
|
});
|
|
29758
28052
|
const httpRouter = createHttpRouter({
|
|
29759
28053
|
authResolver,
|
|
29760
28054
|
daemonVersion: version,
|
|
29761
28055
|
logger,
|
|
28056
|
+
personaStore,
|
|
28057
|
+
groupFileStore,
|
|
29762
28058
|
sessionStore: store,
|
|
29763
28059
|
// /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
|
|
29764
28060
|
// --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
|
|
@@ -29768,8 +28064,6 @@ async function startDaemon(config) {
|
|
|
29768
28064
|
host: config.host,
|
|
29769
28065
|
port: config.port,
|
|
29770
28066
|
logger,
|
|
29771
|
-
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
29772
|
-
ownerPrincipalId,
|
|
29773
28067
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
29774
28068
|
{
|
|
29775
28069
|
manager,
|
|
@@ -29785,29 +28079,6 @@ async function startDaemon(config) {
|
|
|
29785
28079
|
),
|
|
29786
28080
|
protocolVersion: PROTOCOL_VERSION,
|
|
29787
28081
|
authGate: authGate ?? void 0,
|
|
29788
|
-
// noAuth 模式下仍验 capability token: 修 capability platform 漏洞 — 远端 client
|
|
29789
|
-
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
29790
|
-
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
29791
|
-
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
29792
|
-
tryVerifyCapabilityToken: (token, selfPrincipalId, selfDisplayName) => {
|
|
29793
|
-
const v2 = capabilityRegistry.verifyToken(token);
|
|
29794
|
-
if (!v2.ok) return null;
|
|
29795
|
-
if (selfPrincipalId && selfDisplayName) {
|
|
29796
|
-
try {
|
|
29797
|
-
capabilityManager.recordPeerHello(v2.capability.id, {
|
|
29798
|
-
ownerPrincipalId: selfPrincipalId,
|
|
29799
|
-
displayName: selfDisplayName,
|
|
29800
|
-
now: Date.now()
|
|
29801
|
-
});
|
|
29802
|
-
} catch {
|
|
29803
|
-
}
|
|
29804
|
-
}
|
|
29805
|
-
return {
|
|
29806
|
-
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
29807
|
-
grants: v2.capability.grants,
|
|
29808
|
-
capabilityId: v2.capability.id
|
|
29809
|
-
};
|
|
29810
|
-
},
|
|
29811
28082
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29812
28083
|
httpRequestHandler: httpRouter,
|
|
29813
28084
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
@@ -29860,18 +28131,7 @@ async function startDaemon(config) {
|
|
|
29860
28131
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
29861
28132
|
const handler = handlers[type];
|
|
29862
28133
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
29863
|
-
const
|
|
29864
|
-
const verdict = computeGrantForFrame(type, frame);
|
|
29865
|
-
if (verdict.kind === "check") {
|
|
29866
|
-
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|
|
29867
|
-
if (!ok) {
|
|
29868
|
-
throw new ClawdError(
|
|
29869
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
29870
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
29871
|
-
);
|
|
29872
|
-
}
|
|
29873
|
-
}
|
|
29874
|
-
const result = await handler(frame, client, ctx);
|
|
28134
|
+
const result = await handler(frame, client);
|
|
29875
28135
|
if (requestId && result.response) {
|
|
29876
28136
|
client.send({ ...result.response, requestId });
|
|
29877
28137
|
}
|
|
@@ -29994,13 +28254,6 @@ ${bar}
|
|
|
29994
28254
|
manager
|
|
29995
28255
|
};
|
|
29996
28256
|
}
|
|
29997
|
-
function migrateDropPersonsDir(dataDir) {
|
|
29998
|
-
const dir = import_node_path27.default.join(dataDir, "persons");
|
|
29999
|
-
try {
|
|
30000
|
-
import_node_fs24.default.rmSync(dir, { recursive: true, force: true });
|
|
30001
|
-
} catch {
|
|
30002
|
-
}
|
|
30003
|
-
}
|
|
30004
28257
|
|
|
30005
28258
|
// src/cli.ts
|
|
30006
28259
|
async function main() {
|