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