@clawos-dev/clawd 0.2.71-beta.137.a346461 → 0.2.71
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 +718 -2359
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -92,6 +92,10 @@ var init_methods = __esm({
|
|
|
92
92
|
"persona:get",
|
|
93
93
|
"persona:update",
|
|
94
94
|
"persona:delete",
|
|
95
|
+
// token 颁发 / 吊销:file-sharing HTTP Bearer 鉴权用(PersonaRegistry.findByToken
|
|
96
|
+
// 反查 personaId / label)。owner UI 在 PersonaSettingsDrawer "分享 Token" section 管理。
|
|
97
|
+
"persona:issueToken",
|
|
98
|
+
"persona:revokeToken",
|
|
95
99
|
// ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
|
|
96
100
|
// session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
|
|
97
101
|
// 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
|
|
@@ -102,50 +106,16 @@ var init_methods = __esm({
|
|
|
102
106
|
// ---- attachment.* file-sharing(详见 attachment-schemas.ts) ----
|
|
103
107
|
// 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
|
|
104
108
|
// (attachment-skills / attachment-deferred-tools / attachment_memories)是不同概念。
|
|
105
|
-
// 全部管理类 RPC
|
|
106
|
-
// 实际文件传输走 HTTP 路由(GET /files?p=&e=&s
|
|
109
|
+
// 全部管理类 RPC handler 入口 requireOwner(personal token 调任意一个都 403);
|
|
110
|
+
// 实际文件传输走 HTTP 路由(GET /files?p=&e=&s=,签名验证),不在白名单内。
|
|
107
111
|
"attachment.signUrl",
|
|
108
112
|
"attachment.groupAdd",
|
|
109
113
|
"attachment.groupRemove",
|
|
110
114
|
"attachment.groupList",
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
// 在 daemon 端固定为 `{ resource: '*', action: 'admin' }`,owner 自动满足)。
|
|
114
|
-
// 颁发后 daemon 推 'capability:tokenIssued' 帧;删除推 'capability:tokenDeleted' 帧。
|
|
115
|
-
// 删除是 hard delete:CapabilityStore 物理移除 + 关该 cap 的所有活跃 ws + rm
|
|
116
|
-
// personas/<pid>/.clawd/sessions/guests/<capId>/ guest sessions 目录。
|
|
117
|
-
"capability:issue",
|
|
118
|
-
"capability:list",
|
|
119
|
-
"capability:delete",
|
|
120
|
-
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
121
|
-
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
122
|
-
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
123
|
-
// 任何 principal (owner/guest) 可调 (Phase 4).
|
|
124
|
-
"inbox:list",
|
|
125
|
-
"inbox:markRead",
|
|
126
|
-
"inbox:postMessage",
|
|
127
|
-
// ---- remote-persona:* (Phase 4 远程 persona, v1 仅本地存储, 不接 outgoing WS) ----
|
|
128
|
-
// owner 添加 / 列出 / 删除远程 persona 入口. admin-only.
|
|
129
|
-
"remote-persona:add",
|
|
130
|
-
"remote-persona:list",
|
|
131
|
-
"remote-persona:remove",
|
|
132
|
-
// ---- whoami (v2 capability platform) ----
|
|
133
|
-
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
134
|
-
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
135
|
-
// 的 preview 一次性临时 client 用它判定身份和可用 persona。
|
|
136
|
-
"whoami",
|
|
115
|
+
// v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
|
|
116
|
+
"attachment.groupListPersona",
|
|
137
117
|
"info",
|
|
138
|
-
"ping"
|
|
139
|
-
// ---- person:* (Person identity Phase 1, spec v5) ----
|
|
140
|
-
// Person 抽象 = People panel 顶层 entity(一个 Person 聚合 N 个 cap + M 个 remote
|
|
141
|
-
// alias)。owner admin-only。daemon 维护 PersonStore + PersonAliasStore。
|
|
142
|
-
// - list 返回 PersonWithLinks[](含派生 linkedCapabilityIds / linkedRemoteAliases)
|
|
143
|
-
// - create / update / delete 操作 Person 实体(delete 触发 cascade:revoke 全部
|
|
144
|
-
// linked cap + remove subscribed alias + clear linked inbox events)
|
|
145
|
-
"person:list",
|
|
146
|
-
"person:create",
|
|
147
|
-
"person:update",
|
|
148
|
-
"person:delete"
|
|
118
|
+
"ping"
|
|
149
119
|
];
|
|
150
120
|
}
|
|
151
121
|
});
|
|
@@ -635,8 +605,8 @@ var init_parseUtil = __esm({
|
|
|
635
605
|
init_errors2();
|
|
636
606
|
init_en();
|
|
637
607
|
makeIssue = (params) => {
|
|
638
|
-
const { data, path:
|
|
639
|
-
const fullPath = [...
|
|
608
|
+
const { data, path: path32, errorMaps, issueData } = params;
|
|
609
|
+
const fullPath = [...path32, ...issueData.path || []];
|
|
640
610
|
const fullIssue = {
|
|
641
611
|
...issueData,
|
|
642
612
|
path: fullPath
|
|
@@ -947,11 +917,11 @@ var init_types = __esm({
|
|
|
947
917
|
init_parseUtil();
|
|
948
918
|
init_util();
|
|
949
919
|
ParseInputLazyPath = class {
|
|
950
|
-
constructor(parent, value,
|
|
920
|
+
constructor(parent, value, path32, key) {
|
|
951
921
|
this._cachedPath = [];
|
|
952
922
|
this.parent = parent;
|
|
953
923
|
this.data = value;
|
|
954
|
-
this._path =
|
|
924
|
+
this._path = path32;
|
|
955
925
|
this._key = key;
|
|
956
926
|
}
|
|
957
927
|
get path() {
|
|
@@ -4335,12 +4305,12 @@ var init_zod = __esm({
|
|
|
4335
4305
|
});
|
|
4336
4306
|
|
|
4337
4307
|
// ../protocol/src/attachment-schemas.ts
|
|
4338
|
-
var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema;
|
|
4308
|
+
var TOKEN_ROLES, GROUP_FILE_SOURCES, GroupFileEntrySchema, AttachmentSignUrlArgs, AttachmentSignUrlResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentGroupListPersonaArgs, AttachmentGroupListPersonaResponseSchema;
|
|
4339
4309
|
var init_attachment_schemas = __esm({
|
|
4340
4310
|
"../protocol/src/attachment-schemas.ts"() {
|
|
4341
4311
|
"use strict";
|
|
4342
4312
|
init_zod();
|
|
4343
|
-
TOKEN_ROLES = ["owner"];
|
|
4313
|
+
TOKEN_ROLES = ["owner", "personal"];
|
|
4344
4314
|
GROUP_FILE_SOURCES = ["agent", "owner"];
|
|
4345
4315
|
GroupFileEntrySchema = external_exports.object({
|
|
4346
4316
|
/** daemon 派发的稳定 id(用于 RPC remove / UI key) */
|
|
@@ -4361,9 +4331,11 @@ var init_attachment_schemas = __esm({
|
|
|
4361
4331
|
stale: external_exports.boolean().optional()
|
|
4362
4332
|
});
|
|
4363
4333
|
AttachmentSignUrlArgs = external_exports.object({
|
|
4364
|
-
/**
|
|
4365
|
-
|
|
4366
|
-
/**
|
|
4334
|
+
/** 群文件所属的 session */
|
|
4335
|
+
sessionId: external_exports.string().min(1),
|
|
4336
|
+
/** 相对 session.cwd 的路径;允许传绝对路径,daemon 端会归一化(必须在 cwd 内) */
|
|
4337
|
+
relPath: external_exports.string().min(1),
|
|
4338
|
+
/** TTL 秒数;缺省 24h;null 走永久 URL(不带 exp 字段) */
|
|
4367
4339
|
ttlSeconds: external_exports.number().int().positive().nullable().optional()
|
|
4368
4340
|
});
|
|
4369
4341
|
AttachmentSignUrlResponseSchema = external_exports.object({
|
|
@@ -4394,15 +4366,31 @@ var init_attachment_schemas = __esm({
|
|
|
4394
4366
|
AttachmentGroupListResponseSchema = external_exports.object({
|
|
4395
4367
|
entries: external_exports.array(GroupFileEntrySchema)
|
|
4396
4368
|
});
|
|
4369
|
+
AttachmentGroupListPersonaArgs = external_exports.object({
|
|
4370
|
+
personaId: external_exports.string().min(1)
|
|
4371
|
+
});
|
|
4372
|
+
AttachmentGroupListPersonaResponseSchema = external_exports.object({
|
|
4373
|
+
perSession: external_exports.array(
|
|
4374
|
+
external_exports.object({
|
|
4375
|
+
sessionId: external_exports.string().min(1),
|
|
4376
|
+
entries: external_exports.array(GroupFileEntrySchema)
|
|
4377
|
+
})
|
|
4378
|
+
)
|
|
4379
|
+
});
|
|
4397
4380
|
}
|
|
4398
4381
|
});
|
|
4399
4382
|
|
|
4400
4383
|
// ../protocol/src/persona-schemas.ts
|
|
4401
|
-
var PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
|
|
4384
|
+
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema;
|
|
4402
4385
|
var init_persona_schemas = __esm({
|
|
4403
4386
|
"../protocol/src/persona-schemas.ts"() {
|
|
4404
4387
|
"use strict";
|
|
4405
4388
|
init_zod();
|
|
4389
|
+
PersonaTokenEntrySchema = external_exports.object({
|
|
4390
|
+
label: external_exports.string(),
|
|
4391
|
+
issuedAt: external_exports.number(),
|
|
4392
|
+
revoked: external_exports.boolean()
|
|
4393
|
+
});
|
|
4406
4394
|
PersonaFileSchema = external_exports.object({
|
|
4407
4395
|
personaId: external_exports.string(),
|
|
4408
4396
|
label: external_exports.string(),
|
|
@@ -4411,6 +4399,13 @@ var init_persona_schemas = __esm({
|
|
|
4411
4399
|
// 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
|
|
4412
4400
|
// for forward compatibility, consumer fallbacks to default when key not found.
|
|
4413
4401
|
iconKey: external_exports.string().optional(),
|
|
4402
|
+
/**
|
|
4403
|
+
* Persona token 列表:file-sharing HTTP Bearer 鉴权用。
|
|
4404
|
+
* - 写:`PersonaManager.issueToken` / `revokeToken`
|
|
4405
|
+
* - 读:`PersonaRegistry.findByToken` 反向查表(需 persona.public === true)
|
|
4406
|
+
* - optional:兼容历史 persona.json 不含此字段的情形(旧 daemon 写文件时省略)
|
|
4407
|
+
*/
|
|
4408
|
+
tokenMap: external_exports.record(external_exports.string(), PersonaTokenEntrySchema).optional(),
|
|
4414
4409
|
createdAt: external_exports.number(),
|
|
4415
4410
|
updatedAt: external_exports.number()
|
|
4416
4411
|
}).strict();
|
|
@@ -4420,6 +4415,10 @@ var init_persona_schemas = __esm({
|
|
|
4420
4415
|
// SKILL.md frontmatter `description:`;缺省时省略字段
|
|
4421
4416
|
description: external_exports.string().optional()
|
|
4422
4417
|
});
|
|
4418
|
+
PersonaPluginSummarySchema = external_exports.object({
|
|
4419
|
+
// settings.json 中 enabledPlugins 的 key,例如 'superpowers@claude-plugins-official'
|
|
4420
|
+
id: external_exports.string().min(1)
|
|
4421
|
+
});
|
|
4423
4422
|
PersonaSandboxSettingsSchema = external_exports.object({
|
|
4424
4423
|
permissions: external_exports.object({
|
|
4425
4424
|
defaultMode: external_exports.string().optional()
|
|
@@ -4440,6 +4439,8 @@ var init_persona_schemas = __esm({
|
|
|
4440
4439
|
personality: external_exports.string().optional(),
|
|
4441
4440
|
// 仅 'persona:get' 携带;null = 文件不存在 / parse 失败;undefined = 不在该响应类型上
|
|
4442
4441
|
skills: external_exports.array(PersonaSkillSummarySchema).optional(),
|
|
4442
|
+
// 仅 'persona:get' 携带;空数组 = 没启用任何插件 / settings.json 不存在;undefined = 不在该响应类型上
|
|
4443
|
+
plugins: external_exports.array(PersonaPluginSummarySchema).optional(),
|
|
4443
4444
|
sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
|
|
4444
4445
|
});
|
|
4445
4446
|
PersonaCreateArgsSchema = external_exports.object({
|
|
@@ -4463,11 +4464,19 @@ var init_persona_schemas = __esm({
|
|
|
4463
4464
|
iconKey: external_exports.string().nullable().optional()
|
|
4464
4465
|
}).strict()
|
|
4465
4466
|
}).strict();
|
|
4467
|
+
PersonaIssueTokenArgsSchema = external_exports.object({
|
|
4468
|
+
personaId: external_exports.string().min(1),
|
|
4469
|
+
label: external_exports.string().min(1)
|
|
4470
|
+
});
|
|
4471
|
+
PersonaRevokeTokenArgsSchema = external_exports.object({
|
|
4472
|
+
personaId: external_exports.string().min(1),
|
|
4473
|
+
token: external_exports.string().min(1)
|
|
4474
|
+
});
|
|
4466
4475
|
}
|
|
4467
4476
|
});
|
|
4468
4477
|
|
|
4469
4478
|
// ../protocol/src/schemas.ts
|
|
4470
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
|
|
4479
|
+
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
|
|
4471
4480
|
var init_schemas = __esm({
|
|
4472
4481
|
"../protocol/src/schemas.ts"() {
|
|
4473
4482
|
"use strict";
|
|
@@ -4569,15 +4578,6 @@ var init_schemas = __esm({
|
|
|
4569
4578
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
4570
4579
|
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
4571
4580
|
chatId: external_exports.string().min(1).optional(),
|
|
4572
|
-
/**
|
|
4573
|
-
* 创建该 session 的 principal id(owner uuid 或 guest cap.id),从 session:create handler
|
|
4574
|
-
* 的 ctx.principal.id 派生。Person identity Q2「按对端人聚合 sessions」需要它:
|
|
4575
|
-
* UI 端按 `creatorPrincipalId === ownerPrincipalId` 区分「我自己」vs「别人接入」,
|
|
4576
|
-
* 后者再通过 PersonAlias 表反查 personId 二级分组。
|
|
4577
|
-
*
|
|
4578
|
-
* optional:兼容 2026-05-21 之前的老 session 数据,新建一定有值。
|
|
4579
|
-
*/
|
|
4580
|
-
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
4581
4581
|
createdAt: external_exports.string().min(1),
|
|
4582
4582
|
updatedAt: external_exports.string().min(1)
|
|
4583
4583
|
});
|
|
@@ -4987,18 +4987,7 @@ var init_schemas = __esm({
|
|
|
4987
4987
|
AuthRequestFrameSchema = external_exports.object({
|
|
4988
4988
|
type: external_exports.literal("auth"),
|
|
4989
4989
|
token: external_exports.string().min(1),
|
|
4990
|
-
scheme: external_exports.literal("bearer").optional()
|
|
4991
|
-
/**
|
|
4992
|
-
* Person identity Phase 1: 如果 guest 自己也是 daemon owner,把自己的稳定
|
|
4993
|
-
* ownerPrincipalId 主动上报,让对端 daemon 反查 PersonAliasStore 自动 link 到
|
|
4994
|
-
* 已有 Person(避免出现"两个 Bob")。纯访客 / 老 daemon 客户端可省略。
|
|
4995
|
-
*
|
|
4996
|
-
* daemon transport/auth.ts 在 verify token 成功后:
|
|
4997
|
-
* - 若 cap.linkedPrincipalId 未设 → 更新 cap.linkedPrincipalId = selfPrincipalId
|
|
4998
|
-
* + PersonAliasStore.rekey(cap.id, selfPrincipalId)
|
|
4999
|
-
* - 若已设但与本次不一致 → 仅 warn log(潜在协议异常但不阻断)
|
|
5000
|
-
*/
|
|
5001
|
-
selfPrincipalId: external_exports.string().min(1).optional()
|
|
4990
|
+
scheme: external_exports.literal("bearer").optional()
|
|
5002
4991
|
});
|
|
5003
4992
|
AuthOkFrameSchema = external_exports.object({
|
|
5004
4993
|
type: external_exports.literal("auth:ok"),
|
|
@@ -5011,6 +5000,11 @@ var init_schemas = __esm({
|
|
|
5011
5000
|
subdomain: external_exports.string().nullable(),
|
|
5012
5001
|
url: external_exports.string().nullable()
|
|
5013
5002
|
});
|
|
5003
|
+
TunnelUnavailableEventSchema = external_exports.object({
|
|
5004
|
+
type: external_exports.literal("tunnel:unavailable"),
|
|
5005
|
+
reason: external_exports.string().min(1),
|
|
5006
|
+
failedAt: external_exports.string().min(1)
|
|
5007
|
+
});
|
|
5014
5008
|
InfoRunningSessionSchema = external_exports.object({
|
|
5015
5009
|
sessionId: external_exports.string().min(1),
|
|
5016
5010
|
status: SessionStatusSchema,
|
|
@@ -5031,9 +5025,10 @@ var init_schemas = __esm({
|
|
|
5031
5025
|
// PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
|
|
5032
5026
|
// 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
|
|
5033
5027
|
// 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
|
|
5034
|
-
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP
|
|
5035
|
-
* 历史 'personal' role(persona file-sharing token)在 2026-05-21 删除;tokenPersonaId 一同移除。 */
|
|
5028
|
+
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP);'personal' = persona 派发的访客 token。来源 TOKEN_ROLES(spec §11 #1 中央真理源) */
|
|
5036
5029
|
tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
|
|
5030
|
+
/** tokenRole='personal' 时携带(绑定到具体 persona);owner 不携带 */
|
|
5031
|
+
tokenPersonaId: external_exports.string().min(1).optional(),
|
|
5037
5032
|
/** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
|
|
5038
5033
|
isLoopback: external_exports.boolean().optional(),
|
|
5039
5034
|
/** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
|
|
@@ -5060,239 +5055,6 @@ var init_persona_mode = __esm({
|
|
|
5060
5055
|
}
|
|
5061
5056
|
});
|
|
5062
5057
|
|
|
5063
|
-
// ../protocol/src/principal.ts
|
|
5064
|
-
function makeOwnerPrincipal(id, displayName) {
|
|
5065
|
-
return PrincipalSchema.parse({ id, kind: "owner", displayName });
|
|
5066
|
-
}
|
|
5067
|
-
var PrincipalKindSchema, PrincipalSchema;
|
|
5068
|
-
var init_principal = __esm({
|
|
5069
|
-
"../protocol/src/principal.ts"() {
|
|
5070
|
-
"use strict";
|
|
5071
|
-
init_zod();
|
|
5072
|
-
PrincipalKindSchema = external_exports.enum(["owner", "guest"]);
|
|
5073
|
-
PrincipalSchema = external_exports.object({
|
|
5074
|
-
id: external_exports.string().min(1),
|
|
5075
|
-
kind: PrincipalKindSchema,
|
|
5076
|
-
displayName: external_exports.string()
|
|
5077
|
-
}).strict();
|
|
5078
|
-
}
|
|
5079
|
-
});
|
|
5080
|
-
|
|
5081
|
-
// ../protocol/src/capability.ts
|
|
5082
|
-
function stripSecretHash(cap) {
|
|
5083
|
-
const { secretHash: _hash, ...wire } = cap;
|
|
5084
|
-
return wire;
|
|
5085
|
-
}
|
|
5086
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, CapabilityIssueArgsSchema;
|
|
5087
|
-
var init_capability = __esm({
|
|
5088
|
-
"../protocol/src/capability.ts"() {
|
|
5089
|
-
"use strict";
|
|
5090
|
-
init_zod();
|
|
5091
|
-
ResourceSchema = external_exports.discriminatedUnion("type", [
|
|
5092
|
-
external_exports.object({ type: external_exports.literal("persona"), id: external_exports.string().min(1) }).strict(),
|
|
5093
|
-
external_exports.object({ type: external_exports.literal("chat"), id: external_exports.string().min(1) }).strict(),
|
|
5094
|
-
external_exports.object({ type: external_exports.literal("*") }).strict()
|
|
5095
|
-
]);
|
|
5096
|
-
ActionSchema = external_exports.enum(["read", "send", "admin"]);
|
|
5097
|
-
GrantSchema = external_exports.object({
|
|
5098
|
-
resource: ResourceSchema,
|
|
5099
|
-
actions: external_exports.array(ActionSchema).min(1)
|
|
5100
|
-
}).strict();
|
|
5101
|
-
CapabilitySchema = external_exports.object({
|
|
5102
|
-
id: external_exports.string().min(1),
|
|
5103
|
-
// sha256(token) hex 64
|
|
5104
|
-
secretHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
5105
|
-
displayName: external_exports.string(),
|
|
5106
|
-
// 空数组合法 = 纯访客(只能 DM)
|
|
5107
|
-
grants: external_exports.array(GrantSchema),
|
|
5108
|
-
issuedAt: external_exports.number().int().nonnegative(),
|
|
5109
|
-
expiresAt: external_exports.number().int().positive().optional(),
|
|
5110
|
-
maxUses: external_exports.number().int().positive().optional(),
|
|
5111
|
-
usedCount: external_exports.number().int().nonnegative(),
|
|
5112
|
-
/**
|
|
5113
|
-
* Person identity per-People token (2026-05-21): cap 直接绑死 Person 主人。
|
|
5114
|
-
* 一 Person 一 cap:capability:issue 同 person 第二次调用 = 合并 grants,
|
|
5115
|
-
* 不新建 cap。删 PersonAlias 表整套(cap.personId 直接含信息,无需反查)。
|
|
5116
|
-
*/
|
|
5117
|
-
personId: external_exports.string().min(1)
|
|
5118
|
-
}).strict();
|
|
5119
|
-
CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
|
|
5120
|
-
CapabilityErrorCodeSchema = external_exports.enum([
|
|
5121
|
-
"TOKEN_INVALID",
|
|
5122
|
-
"TOKEN_REVOKED",
|
|
5123
|
-
"TOKEN_EXPIRED",
|
|
5124
|
-
"TOKEN_EXHAUSTED"
|
|
5125
|
-
]);
|
|
5126
|
-
WhoamiResponseSchema = external_exports.object({
|
|
5127
|
-
type: external_exports.literal("whoami:ok"),
|
|
5128
|
-
owner: external_exports.object({
|
|
5129
|
-
id: external_exports.string(),
|
|
5130
|
-
kind: external_exports.enum(["owner", "guest"]),
|
|
5131
|
-
displayName: external_exports.string()
|
|
5132
|
-
}).strict(),
|
|
5133
|
-
capability: CapabilityWireSchema,
|
|
5134
|
-
grantedPersonas: external_exports.array(
|
|
5135
|
-
external_exports.object({
|
|
5136
|
-
id: external_exports.string().min(1),
|
|
5137
|
-
displayName: external_exports.string()
|
|
5138
|
-
}).strict()
|
|
5139
|
-
)
|
|
5140
|
-
}).strict();
|
|
5141
|
-
CapabilityIssueArgsSchema = external_exports.object({
|
|
5142
|
-
displayName: external_exports.string().min(1),
|
|
5143
|
-
grants: external_exports.array(GrantSchema),
|
|
5144
|
-
personId: external_exports.string().min(1),
|
|
5145
|
-
expiresAt: external_exports.number().int().positive().optional(),
|
|
5146
|
-
maxUses: external_exports.number().int().positive().optional()
|
|
5147
|
-
}).strict();
|
|
5148
|
-
}
|
|
5149
|
-
});
|
|
5150
|
-
|
|
5151
|
-
// ../protocol/src/inbox.ts
|
|
5152
|
-
var InboxMessageSchema, InboxPostMessageArgsSchema, InboxListArgsSchema, InboxMarkReadArgsSchema;
|
|
5153
|
-
var init_inbox = __esm({
|
|
5154
|
-
"../protocol/src/inbox.ts"() {
|
|
5155
|
-
"use strict";
|
|
5156
|
-
init_zod();
|
|
5157
|
-
InboxMessageSchema = external_exports.object({
|
|
5158
|
-
id: external_exports.string().min(1),
|
|
5159
|
-
capabilityId: external_exports.string().min(1),
|
|
5160
|
-
senderPrincipalId: external_exports.string().min(1),
|
|
5161
|
-
text: external_exports.string().min(1),
|
|
5162
|
-
createdAt: external_exports.number().int().nonnegative(),
|
|
5163
|
-
readBy: external_exports.record(external_exports.string().min(1), external_exports.number().int().positive()).default({})
|
|
5164
|
-
}).strict();
|
|
5165
|
-
InboxPostMessageArgsSchema = external_exports.object({
|
|
5166
|
-
capabilityId: external_exports.string().min(1),
|
|
5167
|
-
text: external_exports.string().min(1)
|
|
5168
|
-
}).strict();
|
|
5169
|
-
InboxListArgsSchema = external_exports.object({
|
|
5170
|
-
capabilityId: external_exports.string().min(1),
|
|
5171
|
-
sinceCreatedAt: external_exports.number().int().nonnegative().optional()
|
|
5172
|
-
}).strict();
|
|
5173
|
-
InboxMarkReadArgsSchema = external_exports.object({
|
|
5174
|
-
capabilityId: external_exports.string().min(1),
|
|
5175
|
-
upToCreatedAt: external_exports.number().int().nonnegative()
|
|
5176
|
-
}).strict();
|
|
5177
|
-
}
|
|
5178
|
-
});
|
|
5179
|
-
|
|
5180
|
-
// ../protocol/src/remote-persona.ts
|
|
5181
|
-
function stripRemotePersonaSecret(rp) {
|
|
5182
|
-
const { capabilityToken: _t, ...wire } = rp;
|
|
5183
|
-
return wire;
|
|
5184
|
-
}
|
|
5185
|
-
var RemotePersonaSchema, RemotePersonaWireSchema, RemotePersonaAddArgsSchema, RemotePersonaRemoveArgsSchema;
|
|
5186
|
-
var init_remote_persona = __esm({
|
|
5187
|
-
"../protocol/src/remote-persona.ts"() {
|
|
5188
|
-
"use strict";
|
|
5189
|
-
init_zod();
|
|
5190
|
-
RemotePersonaSchema = external_exports.object({
|
|
5191
|
-
alias: external_exports.string().min(1),
|
|
5192
|
-
remoteUrl: external_exports.string().min(1),
|
|
5193
|
-
capabilityToken: external_exports.string().min(1),
|
|
5194
|
-
// v2 Phase 7: 远端 daemon 给我颁发的 capability id。preview 时 whoami 返回的
|
|
5195
|
-
// capability.id 直接存进来。旧 v1 持久化文件没此字段 → schema parse fail →
|
|
5196
|
-
// RemotePersonaStore.list 静默跳过 (alpha 阶段可接受, 老板重新 add 即可)。
|
|
5197
|
-
myCapabilityId: external_exports.string().min(1),
|
|
5198
|
-
// owner-id stabilization: 远端 daemon 的稳定 ownerPrincipalId(whoami.owner.id)。
|
|
5199
|
-
// UI DM 时作为 peerPrincipalId 传给远端 daemon(取代字面量 'owner' sentinel),
|
|
5200
|
-
// 让 inbox event from/to 路由 + UI useDm fromOwner 判定都用真实 id。
|
|
5201
|
-
// 旧持久化文件缺该字段 → schema parse fail → store.list 静默跳过(同 myCapabilityId)。
|
|
5202
|
-
ownerPrincipalId: external_exports.string().min(1),
|
|
5203
|
-
remotePersonaId: external_exports.string().min(1),
|
|
5204
|
-
remoteDisplayName: external_exports.string(),
|
|
5205
|
-
ownerDisplayName: external_exports.string().optional(),
|
|
5206
|
-
addedAt: external_exports.number().int().nonnegative(),
|
|
5207
|
-
lastConnectedAt: external_exports.number().int().positive().optional(),
|
|
5208
|
-
/**
|
|
5209
|
-
* Person identity per-People token (2026-05-21): RemotePersona 直接绑死本地 Person。
|
|
5210
|
-
* 删 PersonAlias 表后由该字段提供"远端 owner principal 对应本地哪个 person"。
|
|
5211
|
-
*/
|
|
5212
|
-
personId: external_exports.string().min(1)
|
|
5213
|
-
}).strict();
|
|
5214
|
-
RemotePersonaWireSchema = RemotePersonaSchema.omit({ capabilityToken: true });
|
|
5215
|
-
RemotePersonaAddArgsSchema = external_exports.object({
|
|
5216
|
-
alias: external_exports.string().min(1),
|
|
5217
|
-
remoteUrl: external_exports.string().min(1),
|
|
5218
|
-
capabilityToken: external_exports.string().min(1),
|
|
5219
|
-
// v2 Phase 7: UI 调 preview 后从 whoami response 取 capability.id 传入
|
|
5220
|
-
myCapabilityId: external_exports.string().min(1),
|
|
5221
|
-
// owner-id stabilization: UI 从 whoami.owner.id 取,远端 daemon 稳定身份
|
|
5222
|
-
ownerPrincipalId: external_exports.string().min(1),
|
|
5223
|
-
remotePersonaId: external_exports.string().min(1),
|
|
5224
|
-
remoteDisplayName: external_exports.string(),
|
|
5225
|
-
ownerDisplayName: external_exports.string().optional(),
|
|
5226
|
-
/**
|
|
5227
|
-
* Person identity per-People token: add 时必须关联到一个 Person(owner 视角的"对方是谁")。
|
|
5228
|
-
* daemon handler 内 atomic 写 RemotePersona.personId(PersonAlias 表 2026-05-21 删除)。
|
|
5229
|
-
*/
|
|
5230
|
-
personId: external_exports.string().min(1)
|
|
5231
|
-
}).strict();
|
|
5232
|
-
RemotePersonaRemoveArgsSchema = external_exports.object({
|
|
5233
|
-
alias: external_exports.string().min(1)
|
|
5234
|
-
}).strict();
|
|
5235
|
-
}
|
|
5236
|
-
});
|
|
5237
|
-
|
|
5238
|
-
// ../protocol/src/person.ts
|
|
5239
|
-
var PersonSchema, PersonWithLinksSchema, PersonCreateArgsSchema, PersonUpdateArgsSchema, PersonDeleteArgsSchema, PersonListResponseSchema, PersonCreateResponseSchema, PersonUpdateResponseSchema, PersonDeleteResponseSchema;
|
|
5240
|
-
var init_person = __esm({
|
|
5241
|
-
"../protocol/src/person.ts"() {
|
|
5242
|
-
"use strict";
|
|
5243
|
-
init_zod();
|
|
5244
|
-
PersonSchema = external_exports.object({
|
|
5245
|
-
id: external_exports.string().min(1),
|
|
5246
|
-
displayName: external_exports.string().min(1),
|
|
5247
|
-
notes: external_exports.string().optional(),
|
|
5248
|
-
/** 默认 true(owner 可关闭以阻断该 Person 的入站 DM) */
|
|
5249
|
-
dmEnabled: external_exports.boolean(),
|
|
5250
|
-
createdAt: external_exports.number().int().nonnegative(),
|
|
5251
|
-
updatedAt: external_exports.number().int().nonnegative()
|
|
5252
|
-
}).strict();
|
|
5253
|
-
PersonWithLinksSchema = PersonSchema.extend({
|
|
5254
|
-
linkedCapabilityIds: external_exports.array(external_exports.string()),
|
|
5255
|
-
linkedRemoteAliases: external_exports.array(external_exports.string())
|
|
5256
|
-
}).strict();
|
|
5257
|
-
PersonCreateArgsSchema = external_exports.object({
|
|
5258
|
-
displayName: external_exports.string().min(1),
|
|
5259
|
-
notes: external_exports.string().optional(),
|
|
5260
|
-
/** 缺省 true(在 daemon handler 内 default) */
|
|
5261
|
-
dmEnabled: external_exports.boolean().optional()
|
|
5262
|
-
}).strict();
|
|
5263
|
-
PersonUpdateArgsSchema = external_exports.object({
|
|
5264
|
-
id: external_exports.string().min(1),
|
|
5265
|
-
displayName: external_exports.string().min(1).optional(),
|
|
5266
|
-
notes: external_exports.string().optional(),
|
|
5267
|
-
dmEnabled: external_exports.boolean().optional()
|
|
5268
|
-
}).strict();
|
|
5269
|
-
PersonDeleteArgsSchema = external_exports.object({
|
|
5270
|
-
id: external_exports.string().min(1)
|
|
5271
|
-
}).strict();
|
|
5272
|
-
PersonListResponseSchema = external_exports.object({
|
|
5273
|
-
type: external_exports.literal("person:list:ok"),
|
|
5274
|
-
persons: external_exports.array(PersonWithLinksSchema)
|
|
5275
|
-
}).strict();
|
|
5276
|
-
PersonCreateResponseSchema = external_exports.object({
|
|
5277
|
-
type: external_exports.literal("person:create:ok"),
|
|
5278
|
-
person: PersonSchema
|
|
5279
|
-
}).strict();
|
|
5280
|
-
PersonUpdateResponseSchema = external_exports.object({
|
|
5281
|
-
type: external_exports.literal("person:update:ok"),
|
|
5282
|
-
person: PersonSchema
|
|
5283
|
-
}).strict();
|
|
5284
|
-
PersonDeleteResponseSchema = external_exports.object({
|
|
5285
|
-
type: external_exports.literal("person:delete:ok"),
|
|
5286
|
-
/** cascade 删了哪些 capability id */
|
|
5287
|
-
deletedCapabilityIds: external_exports.array(external_exports.string()),
|
|
5288
|
-
/** cascade 移除了哪些 remote alias */
|
|
5289
|
-
removedRemoteAliases: external_exports.array(external_exports.string()),
|
|
5290
|
-
/** cascade 清掉了多少条 inbox 事件 */
|
|
5291
|
-
deletedInboxEvents: external_exports.number().int().nonnegative()
|
|
5292
|
-
}).strict();
|
|
5293
|
-
}
|
|
5294
|
-
});
|
|
5295
|
-
|
|
5296
5058
|
// ../protocol/src/runtime.ts
|
|
5297
5059
|
var init_runtime = __esm({
|
|
5298
5060
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5305,11 +5067,6 @@ var init_runtime = __esm({
|
|
|
5305
5067
|
init_persona_schemas();
|
|
5306
5068
|
init_persona_mode();
|
|
5307
5069
|
init_attachment_schemas();
|
|
5308
|
-
init_principal();
|
|
5309
|
-
init_capability();
|
|
5310
|
-
init_inbox();
|
|
5311
|
-
init_remote_persona();
|
|
5312
|
-
init_person();
|
|
5313
5070
|
}
|
|
5314
5071
|
});
|
|
5315
5072
|
|
|
@@ -5584,8 +5341,8 @@ var require_req = __commonJS({
|
|
|
5584
5341
|
if (req.originalUrl) {
|
|
5585
5342
|
_req.url = req.originalUrl;
|
|
5586
5343
|
} else {
|
|
5587
|
-
const
|
|
5588
|
-
_req.url = typeof
|
|
5344
|
+
const path32 = req.path;
|
|
5345
|
+
_req.url = typeof path32 === "string" ? path32 : req.url ? req.url.path || req.url : void 0;
|
|
5589
5346
|
}
|
|
5590
5347
|
if (req.query) {
|
|
5591
5348
|
_req.query = req.query;
|
|
@@ -5750,14 +5507,14 @@ var require_redact = __commonJS({
|
|
|
5750
5507
|
}
|
|
5751
5508
|
return obj;
|
|
5752
5509
|
}
|
|
5753
|
-
function parsePath(
|
|
5510
|
+
function parsePath(path32) {
|
|
5754
5511
|
const parts = [];
|
|
5755
5512
|
let current = "";
|
|
5756
5513
|
let inBrackets = false;
|
|
5757
5514
|
let inQuotes = false;
|
|
5758
5515
|
let quoteChar = "";
|
|
5759
|
-
for (let i = 0; i <
|
|
5760
|
-
const char =
|
|
5516
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5517
|
+
const char = path32[i];
|
|
5761
5518
|
if (!inBrackets && char === ".") {
|
|
5762
5519
|
if (current) {
|
|
5763
5520
|
parts.push(current);
|
|
@@ -5888,10 +5645,10 @@ var require_redact = __commonJS({
|
|
|
5888
5645
|
return current;
|
|
5889
5646
|
}
|
|
5890
5647
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5891
|
-
for (const
|
|
5892
|
-
const parts = parsePath(
|
|
5648
|
+
for (const path32 of paths) {
|
|
5649
|
+
const parts = parsePath(path32);
|
|
5893
5650
|
if (parts.includes("*")) {
|
|
5894
|
-
redactWildcardPath(obj, parts, censor,
|
|
5651
|
+
redactWildcardPath(obj, parts, censor, path32, remove);
|
|
5895
5652
|
} else {
|
|
5896
5653
|
if (remove) {
|
|
5897
5654
|
removeKey(obj, parts);
|
|
@@ -5976,8 +5733,8 @@ var require_redact = __commonJS({
|
|
|
5976
5733
|
}
|
|
5977
5734
|
} else {
|
|
5978
5735
|
if (afterWildcard.includes("*")) {
|
|
5979
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5980
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5736
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
5737
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path32];
|
|
5981
5738
|
return censor(value, fullPath);
|
|
5982
5739
|
} : censor;
|
|
5983
5740
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -6012,8 +5769,8 @@ var require_redact = __commonJS({
|
|
|
6012
5769
|
return null;
|
|
6013
5770
|
}
|
|
6014
5771
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
6015
|
-
for (const
|
|
6016
|
-
const parts = parsePath(
|
|
5772
|
+
for (const path32 of pathsToClone) {
|
|
5773
|
+
const parts = parsePath(path32);
|
|
6017
5774
|
let current = pathStructure;
|
|
6018
5775
|
for (let i = 0; i < parts.length; i++) {
|
|
6019
5776
|
const part = parts[i];
|
|
@@ -6065,24 +5822,24 @@ var require_redact = __commonJS({
|
|
|
6065
5822
|
}
|
|
6066
5823
|
return cloneSelectively(obj, pathStructure);
|
|
6067
5824
|
}
|
|
6068
|
-
function validatePath(
|
|
6069
|
-
if (typeof
|
|
5825
|
+
function validatePath(path32) {
|
|
5826
|
+
if (typeof path32 !== "string") {
|
|
6070
5827
|
throw new Error("Paths must be (non-empty) strings");
|
|
6071
5828
|
}
|
|
6072
|
-
if (
|
|
5829
|
+
if (path32 === "") {
|
|
6073
5830
|
throw new Error("Invalid redaction path ()");
|
|
6074
5831
|
}
|
|
6075
|
-
if (
|
|
6076
|
-
throw new Error(`Invalid redaction path (${
|
|
5832
|
+
if (path32.includes("..")) {
|
|
5833
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
6077
5834
|
}
|
|
6078
|
-
if (
|
|
6079
|
-
throw new Error(`Invalid redaction path (${
|
|
5835
|
+
if (path32.includes(",")) {
|
|
5836
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
6080
5837
|
}
|
|
6081
5838
|
let bracketCount = 0;
|
|
6082
5839
|
let inQuotes = false;
|
|
6083
5840
|
let quoteChar = "";
|
|
6084
|
-
for (let i = 0; i <
|
|
6085
|
-
const char =
|
|
5841
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5842
|
+
const char = path32[i];
|
|
6086
5843
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6087
5844
|
if (!inQuotes) {
|
|
6088
5845
|
inQuotes = true;
|
|
@@ -6096,20 +5853,20 @@ var require_redact = __commonJS({
|
|
|
6096
5853
|
} else if (char === "]" && !inQuotes) {
|
|
6097
5854
|
bracketCount--;
|
|
6098
5855
|
if (bracketCount < 0) {
|
|
6099
|
-
throw new Error(`Invalid redaction path (${
|
|
5856
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
6100
5857
|
}
|
|
6101
5858
|
}
|
|
6102
5859
|
}
|
|
6103
5860
|
if (bracketCount !== 0) {
|
|
6104
|
-
throw new Error(`Invalid redaction path (${
|
|
5861
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
6105
5862
|
}
|
|
6106
5863
|
}
|
|
6107
5864
|
function validatePaths(paths) {
|
|
6108
5865
|
if (!Array.isArray(paths)) {
|
|
6109
5866
|
throw new TypeError("paths must be an array");
|
|
6110
5867
|
}
|
|
6111
|
-
for (const
|
|
6112
|
-
validatePath(
|
|
5868
|
+
for (const path32 of paths) {
|
|
5869
|
+
validatePath(path32);
|
|
6113
5870
|
}
|
|
6114
5871
|
}
|
|
6115
5872
|
function slowRedact(options = {}) {
|
|
@@ -6277,8 +6034,8 @@ var require_redaction = __commonJS({
|
|
|
6277
6034
|
if (shape[k2] === null) {
|
|
6278
6035
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6279
6036
|
} else {
|
|
6280
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6281
|
-
return censor(value, [k2, ...
|
|
6037
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
6038
|
+
return censor(value, [k2, ...path32]);
|
|
6282
6039
|
} : censor;
|
|
6283
6040
|
o[k2] = Redact({
|
|
6284
6041
|
paths: shape[k2],
|
|
@@ -6496,10 +6253,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6496
6253
|
var require_sonic_boom = __commonJS({
|
|
6497
6254
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6498
6255
|
"use strict";
|
|
6499
|
-
var
|
|
6256
|
+
var fs28 = require("fs");
|
|
6500
6257
|
var EventEmitter2 = require("events");
|
|
6501
6258
|
var inherits = require("util").inherits;
|
|
6502
|
-
var
|
|
6259
|
+
var path32 = require("path");
|
|
6503
6260
|
var sleep = require_atomic_sleep();
|
|
6504
6261
|
var assert = require("assert");
|
|
6505
6262
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6553,20 +6310,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6553
6310
|
const mode = sonic.mode;
|
|
6554
6311
|
if (sonic.sync) {
|
|
6555
6312
|
try {
|
|
6556
|
-
if (sonic.mkdir)
|
|
6557
|
-
const fd =
|
|
6313
|
+
if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
|
|
6314
|
+
const fd = fs28.openSync(file, flags, mode);
|
|
6558
6315
|
fileOpened(null, fd);
|
|
6559
6316
|
} catch (err) {
|
|
6560
6317
|
fileOpened(err);
|
|
6561
6318
|
throw err;
|
|
6562
6319
|
}
|
|
6563
6320
|
} else if (sonic.mkdir) {
|
|
6564
|
-
|
|
6321
|
+
fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
|
|
6565
6322
|
if (err) return fileOpened(err);
|
|
6566
|
-
|
|
6323
|
+
fs28.open(file, flags, mode, fileOpened);
|
|
6567
6324
|
});
|
|
6568
6325
|
} else {
|
|
6569
|
-
|
|
6326
|
+
fs28.open(file, flags, mode, fileOpened);
|
|
6570
6327
|
}
|
|
6571
6328
|
}
|
|
6572
6329
|
function SonicBoom(opts) {
|
|
@@ -6607,8 +6364,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6607
6364
|
this.flush = flushBuffer;
|
|
6608
6365
|
this.flushSync = flushBufferSync;
|
|
6609
6366
|
this._actualWrite = actualWriteBuffer;
|
|
6610
|
-
fsWriteSync = () =>
|
|
6611
|
-
fsWrite = () =>
|
|
6367
|
+
fsWriteSync = () => fs28.writeSync(this.fd, this._writingBuf);
|
|
6368
|
+
fsWrite = () => fs28.write(this.fd, this._writingBuf, this.release);
|
|
6612
6369
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6613
6370
|
this._writingBuf = "";
|
|
6614
6371
|
this.write = write;
|
|
@@ -6617,15 +6374,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6617
6374
|
this._actualWrite = actualWrite;
|
|
6618
6375
|
fsWriteSync = () => {
|
|
6619
6376
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6620
|
-
return
|
|
6377
|
+
return fs28.writeSync(this.fd, this._writingBuf);
|
|
6621
6378
|
}
|
|
6622
|
-
return
|
|
6379
|
+
return fs28.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6623
6380
|
};
|
|
6624
6381
|
fsWrite = () => {
|
|
6625
6382
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6626
|
-
return
|
|
6383
|
+
return fs28.write(this.fd, this._writingBuf, this.release);
|
|
6627
6384
|
}
|
|
6628
|
-
return
|
|
6385
|
+
return fs28.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6629
6386
|
};
|
|
6630
6387
|
} else {
|
|
6631
6388
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6682,7 +6439,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6682
6439
|
}
|
|
6683
6440
|
}
|
|
6684
6441
|
if (this._fsync) {
|
|
6685
|
-
|
|
6442
|
+
fs28.fsyncSync(this.fd);
|
|
6686
6443
|
}
|
|
6687
6444
|
const len = this._len;
|
|
6688
6445
|
if (this._reopening) {
|
|
@@ -6796,7 +6553,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6796
6553
|
const onDrain = () => {
|
|
6797
6554
|
if (!this._fsync) {
|
|
6798
6555
|
try {
|
|
6799
|
-
|
|
6556
|
+
fs28.fsync(this.fd, (err) => {
|
|
6800
6557
|
this._flushPending = false;
|
|
6801
6558
|
cb(err);
|
|
6802
6559
|
});
|
|
@@ -6898,7 +6655,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6898
6655
|
const fd = this.fd;
|
|
6899
6656
|
this.once("ready", () => {
|
|
6900
6657
|
if (fd !== this.fd) {
|
|
6901
|
-
|
|
6658
|
+
fs28.close(fd, (err) => {
|
|
6902
6659
|
if (err) {
|
|
6903
6660
|
return this.emit("error", err);
|
|
6904
6661
|
}
|
|
@@ -6947,7 +6704,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6947
6704
|
buf = this._bufs[0];
|
|
6948
6705
|
}
|
|
6949
6706
|
try {
|
|
6950
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6707
|
+
const n = Buffer.isBuffer(buf) ? fs28.writeSync(this.fd, buf) : fs28.writeSync(this.fd, buf, "utf8");
|
|
6951
6708
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6952
6709
|
buf = releasedBufObj.writingBuf;
|
|
6953
6710
|
this._len = releasedBufObj.len;
|
|
@@ -6963,7 +6720,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6963
6720
|
}
|
|
6964
6721
|
}
|
|
6965
6722
|
try {
|
|
6966
|
-
|
|
6723
|
+
fs28.fsyncSync(this.fd);
|
|
6967
6724
|
} catch {
|
|
6968
6725
|
}
|
|
6969
6726
|
}
|
|
@@ -6984,7 +6741,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6984
6741
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6985
6742
|
}
|
|
6986
6743
|
try {
|
|
6987
|
-
const n =
|
|
6744
|
+
const n = fs28.writeSync(this.fd, buf);
|
|
6988
6745
|
buf = buf.subarray(n);
|
|
6989
6746
|
this._len = Math.max(this._len - n, 0);
|
|
6990
6747
|
if (buf.length <= 0) {
|
|
@@ -7012,13 +6769,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7012
6769
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7013
6770
|
if (this.sync) {
|
|
7014
6771
|
try {
|
|
7015
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
6772
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs28.writeSync(this.fd, this._writingBuf) : fs28.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7016
6773
|
release(null, written);
|
|
7017
6774
|
} catch (err) {
|
|
7018
6775
|
release(err);
|
|
7019
6776
|
}
|
|
7020
6777
|
} else {
|
|
7021
|
-
|
|
6778
|
+
fs28.write(this.fd, this._writingBuf, release);
|
|
7022
6779
|
}
|
|
7023
6780
|
}
|
|
7024
6781
|
function actualWriteBuffer() {
|
|
@@ -7027,7 +6784,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7027
6784
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7028
6785
|
if (this.sync) {
|
|
7029
6786
|
try {
|
|
7030
|
-
const written =
|
|
6787
|
+
const written = fs28.writeSync(this.fd, this._writingBuf);
|
|
7031
6788
|
release(null, written);
|
|
7032
6789
|
} catch (err) {
|
|
7033
6790
|
release(err);
|
|
@@ -7036,7 +6793,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7036
6793
|
if (kCopyBuffer) {
|
|
7037
6794
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7038
6795
|
}
|
|
7039
|
-
|
|
6796
|
+
fs28.write(this.fd, this._writingBuf, release);
|
|
7040
6797
|
}
|
|
7041
6798
|
}
|
|
7042
6799
|
function actualClose(sonic) {
|
|
@@ -7052,12 +6809,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7052
6809
|
sonic._lens = [];
|
|
7053
6810
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7054
6811
|
try {
|
|
7055
|
-
|
|
6812
|
+
fs28.fsync(sonic.fd, closeWrapped);
|
|
7056
6813
|
} catch {
|
|
7057
6814
|
}
|
|
7058
6815
|
function closeWrapped() {
|
|
7059
6816
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7060
|
-
|
|
6817
|
+
fs28.close(sonic.fd, done);
|
|
7061
6818
|
} else {
|
|
7062
6819
|
done();
|
|
7063
6820
|
}
|
|
@@ -7314,7 +7071,7 @@ var require_thread_stream = __commonJS({
|
|
|
7314
7071
|
var { version: version2 } = require_package();
|
|
7315
7072
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7316
7073
|
var { Worker } = require("worker_threads");
|
|
7317
|
-
var { join:
|
|
7074
|
+
var { join: join4 } = require("path");
|
|
7318
7075
|
var { pathToFileURL } = require("url");
|
|
7319
7076
|
var { wait } = require_wait();
|
|
7320
7077
|
var {
|
|
@@ -7350,7 +7107,7 @@ var require_thread_stream = __commonJS({
|
|
|
7350
7107
|
function createWorker(stream, opts) {
|
|
7351
7108
|
const { filename, workerData } = opts;
|
|
7352
7109
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7353
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7110
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join4(__dirname, "lib", "worker.js");
|
|
7354
7111
|
const worker = new Worker(toExecute, {
|
|
7355
7112
|
...opts.workerOpts,
|
|
7356
7113
|
trackUnmanagedFds: false,
|
|
@@ -7736,7 +7493,7 @@ var require_transport = __commonJS({
|
|
|
7736
7493
|
"use strict";
|
|
7737
7494
|
var { createRequire } = require("module");
|
|
7738
7495
|
var getCallers = require_caller();
|
|
7739
|
-
var { join:
|
|
7496
|
+
var { join: join4, isAbsolute, sep } = require("path");
|
|
7740
7497
|
var sleep = require_atomic_sleep();
|
|
7741
7498
|
var onExit = require_on_exit_leak_free();
|
|
7742
7499
|
var ThreadStream = require_thread_stream();
|
|
@@ -7799,7 +7556,7 @@ var require_transport = __commonJS({
|
|
|
7799
7556
|
throw new Error("only one of target or targets can be specified");
|
|
7800
7557
|
}
|
|
7801
7558
|
if (targets) {
|
|
7802
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7559
|
+
target = bundlerOverrides["pino-worker"] || join4(__dirname, "worker.js");
|
|
7803
7560
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
7804
7561
|
return {
|
|
7805
7562
|
...dest,
|
|
@@ -7817,7 +7574,7 @@ var require_transport = __commonJS({
|
|
|
7817
7574
|
});
|
|
7818
7575
|
});
|
|
7819
7576
|
} else if (pipeline2) {
|
|
7820
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7577
|
+
target = bundlerOverrides["pino-worker"] || join4(__dirname, "worker.js");
|
|
7821
7578
|
options.pipelines = [pipeline2.map((dest) => {
|
|
7822
7579
|
return {
|
|
7823
7580
|
...dest,
|
|
@@ -7839,12 +7596,12 @@ var require_transport = __commonJS({
|
|
|
7839
7596
|
return origin;
|
|
7840
7597
|
}
|
|
7841
7598
|
if (origin === "pino/file") {
|
|
7842
|
-
return
|
|
7599
|
+
return join4(__dirname, "..", "file.js");
|
|
7843
7600
|
}
|
|
7844
7601
|
let fixTarget2;
|
|
7845
7602
|
for (const filePath of callers) {
|
|
7846
7603
|
try {
|
|
7847
|
-
const context = filePath === "node:repl" ? process.cwd() +
|
|
7604
|
+
const context = filePath === "node:repl" ? process.cwd() + sep : filePath;
|
|
7848
7605
|
fixTarget2 = createRequire(context).resolve(origin);
|
|
7849
7606
|
break;
|
|
7850
7607
|
} catch (err) {
|
|
@@ -8829,7 +8586,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8829
8586
|
return circularValue;
|
|
8830
8587
|
}
|
|
8831
8588
|
let res = "";
|
|
8832
|
-
let
|
|
8589
|
+
let join4 = ",";
|
|
8833
8590
|
const originalIndentation = indentation;
|
|
8834
8591
|
if (Array.isArray(value)) {
|
|
8835
8592
|
if (value.length === 0) {
|
|
@@ -8843,7 +8600,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8843
8600
|
indentation += spacer;
|
|
8844
8601
|
res += `
|
|
8845
8602
|
${indentation}`;
|
|
8846
|
-
|
|
8603
|
+
join4 = `,
|
|
8847
8604
|
${indentation}`;
|
|
8848
8605
|
}
|
|
8849
8606
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8851,13 +8608,13 @@ ${indentation}`;
|
|
|
8851
8608
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8852
8609
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8853
8610
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8854
|
-
res +=
|
|
8611
|
+
res += join4;
|
|
8855
8612
|
}
|
|
8856
8613
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8857
8614
|
res += tmp !== void 0 ? tmp : "null";
|
|
8858
8615
|
if (value.length - 1 > maximumBreadth) {
|
|
8859
8616
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8860
|
-
res += `${
|
|
8617
|
+
res += `${join4}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8861
8618
|
}
|
|
8862
8619
|
if (spacer !== "") {
|
|
8863
8620
|
res += `
|
|
@@ -8878,7 +8635,7 @@ ${originalIndentation}`;
|
|
|
8878
8635
|
let separator = "";
|
|
8879
8636
|
if (spacer !== "") {
|
|
8880
8637
|
indentation += spacer;
|
|
8881
|
-
|
|
8638
|
+
join4 = `,
|
|
8882
8639
|
${indentation}`;
|
|
8883
8640
|
whitespace = " ";
|
|
8884
8641
|
}
|
|
@@ -8892,13 +8649,13 @@ ${indentation}`;
|
|
|
8892
8649
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
8893
8650
|
if (tmp !== void 0) {
|
|
8894
8651
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8895
|
-
separator =
|
|
8652
|
+
separator = join4;
|
|
8896
8653
|
}
|
|
8897
8654
|
}
|
|
8898
8655
|
if (keyLength > maximumBreadth) {
|
|
8899
8656
|
const removedKeys = keyLength - maximumBreadth;
|
|
8900
8657
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
8901
|
-
separator =
|
|
8658
|
+
separator = join4;
|
|
8902
8659
|
}
|
|
8903
8660
|
if (spacer !== "" && separator.length > 1) {
|
|
8904
8661
|
res = `
|
|
@@ -8939,7 +8696,7 @@ ${originalIndentation}`;
|
|
|
8939
8696
|
}
|
|
8940
8697
|
const originalIndentation = indentation;
|
|
8941
8698
|
let res = "";
|
|
8942
|
-
let
|
|
8699
|
+
let join4 = ",";
|
|
8943
8700
|
if (Array.isArray(value)) {
|
|
8944
8701
|
if (value.length === 0) {
|
|
8945
8702
|
return "[]";
|
|
@@ -8952,7 +8709,7 @@ ${originalIndentation}`;
|
|
|
8952
8709
|
indentation += spacer;
|
|
8953
8710
|
res += `
|
|
8954
8711
|
${indentation}`;
|
|
8955
|
-
|
|
8712
|
+
join4 = `,
|
|
8956
8713
|
${indentation}`;
|
|
8957
8714
|
}
|
|
8958
8715
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8960,13 +8717,13 @@ ${indentation}`;
|
|
|
8960
8717
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8961
8718
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8962
8719
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8963
|
-
res +=
|
|
8720
|
+
res += join4;
|
|
8964
8721
|
}
|
|
8965
8722
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8966
8723
|
res += tmp !== void 0 ? tmp : "null";
|
|
8967
8724
|
if (value.length - 1 > maximumBreadth) {
|
|
8968
8725
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8969
|
-
res += `${
|
|
8726
|
+
res += `${join4}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8970
8727
|
}
|
|
8971
8728
|
if (spacer !== "") {
|
|
8972
8729
|
res += `
|
|
@@ -8979,7 +8736,7 @@ ${originalIndentation}`;
|
|
|
8979
8736
|
let whitespace = "";
|
|
8980
8737
|
if (spacer !== "") {
|
|
8981
8738
|
indentation += spacer;
|
|
8982
|
-
|
|
8739
|
+
join4 = `,
|
|
8983
8740
|
${indentation}`;
|
|
8984
8741
|
whitespace = " ";
|
|
8985
8742
|
}
|
|
@@ -8988,7 +8745,7 @@ ${indentation}`;
|
|
|
8988
8745
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
8989
8746
|
if (tmp !== void 0) {
|
|
8990
8747
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8991
|
-
separator =
|
|
8748
|
+
separator = join4;
|
|
8992
8749
|
}
|
|
8993
8750
|
}
|
|
8994
8751
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9046,20 +8803,20 @@ ${originalIndentation}`;
|
|
|
9046
8803
|
indentation += spacer;
|
|
9047
8804
|
let res2 = `
|
|
9048
8805
|
${indentation}`;
|
|
9049
|
-
const
|
|
8806
|
+
const join5 = `,
|
|
9050
8807
|
${indentation}`;
|
|
9051
8808
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9052
8809
|
let i = 0;
|
|
9053
8810
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9054
8811
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9055
8812
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9056
|
-
res2 +=
|
|
8813
|
+
res2 += join5;
|
|
9057
8814
|
}
|
|
9058
8815
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9059
8816
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9060
8817
|
if (value.length - 1 > maximumBreadth) {
|
|
9061
8818
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9062
|
-
res2 += `${
|
|
8819
|
+
res2 += `${join5}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9063
8820
|
}
|
|
9064
8821
|
res2 += `
|
|
9065
8822
|
${originalIndentation}`;
|
|
@@ -9075,16 +8832,16 @@ ${originalIndentation}`;
|
|
|
9075
8832
|
return '"[Object]"';
|
|
9076
8833
|
}
|
|
9077
8834
|
indentation += spacer;
|
|
9078
|
-
const
|
|
8835
|
+
const join4 = `,
|
|
9079
8836
|
${indentation}`;
|
|
9080
8837
|
let res = "";
|
|
9081
8838
|
let separator = "";
|
|
9082
8839
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9083
8840
|
if (isTypedArrayWithEntries(value)) {
|
|
9084
|
-
res += stringifyTypedArray(value,
|
|
8841
|
+
res += stringifyTypedArray(value, join4, maximumBreadth);
|
|
9085
8842
|
keys = keys.slice(value.length);
|
|
9086
8843
|
maximumPropertiesToStringify -= value.length;
|
|
9087
|
-
separator =
|
|
8844
|
+
separator = join4;
|
|
9088
8845
|
}
|
|
9089
8846
|
if (deterministic) {
|
|
9090
8847
|
keys = sort(keys, comparator);
|
|
@@ -9095,13 +8852,13 @@ ${indentation}`;
|
|
|
9095
8852
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9096
8853
|
if (tmp !== void 0) {
|
|
9097
8854
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9098
|
-
separator =
|
|
8855
|
+
separator = join4;
|
|
9099
8856
|
}
|
|
9100
8857
|
}
|
|
9101
8858
|
if (keyLength > maximumBreadth) {
|
|
9102
8859
|
const removedKeys = keyLength - maximumBreadth;
|
|
9103
8860
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9104
|
-
separator =
|
|
8861
|
+
separator = join4;
|
|
9105
8862
|
}
|
|
9106
8863
|
if (separator !== "") {
|
|
9107
8864
|
res = `
|
|
@@ -10192,11 +9949,11 @@ var init_lib = __esm({
|
|
|
10192
9949
|
}
|
|
10193
9950
|
}
|
|
10194
9951
|
},
|
|
10195
|
-
addToPath: function addToPath(
|
|
10196
|
-
var last =
|
|
9952
|
+
addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
|
|
9953
|
+
var last = path32.lastComponent;
|
|
10197
9954
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10198
9955
|
return {
|
|
10199
|
-
oldPos:
|
|
9956
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10200
9957
|
lastComponent: {
|
|
10201
9958
|
count: last.count + 1,
|
|
10202
9959
|
added,
|
|
@@ -10206,7 +9963,7 @@ var init_lib = __esm({
|
|
|
10206
9963
|
};
|
|
10207
9964
|
} else {
|
|
10208
9965
|
return {
|
|
10209
|
-
oldPos:
|
|
9966
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10210
9967
|
lastComponent: {
|
|
10211
9968
|
count: 1,
|
|
10212
9969
|
added,
|
|
@@ -10264,7 +10021,7 @@ var init_lib = __esm({
|
|
|
10264
10021
|
tokenize: function tokenize(value) {
|
|
10265
10022
|
return Array.from(value);
|
|
10266
10023
|
},
|
|
10267
|
-
join: function
|
|
10024
|
+
join: function join3(chars) {
|
|
10268
10025
|
return chars.join("");
|
|
10269
10026
|
},
|
|
10270
10027
|
postProcess: function postProcess(changeObjects) {
|
|
@@ -10637,10 +10394,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10637
10394
|
const memories = raw.map((m2) => {
|
|
10638
10395
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10639
10396
|
const rec = m2;
|
|
10640
|
-
const
|
|
10397
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
10641
10398
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10642
|
-
if (!
|
|
10643
|
-
const entry = { path:
|
|
10399
|
+
if (!path32 || content == null) return null;
|
|
10400
|
+
const entry = { path: path32, content };
|
|
10644
10401
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10645
10402
|
return entry;
|
|
10646
10403
|
}).filter((m2) => m2 !== null);
|
|
@@ -11100,27 +10857,6 @@ var init_claude_history = __esm({
|
|
|
11100
10857
|
}
|
|
11101
10858
|
});
|
|
11102
10859
|
|
|
11103
|
-
// src/tools/sandbox.ts
|
|
11104
|
-
function shouldSandbox(cwd, personaRoot) {
|
|
11105
|
-
if (!personaRoot) return false;
|
|
11106
|
-
const sep2 = personaRoot.endsWith(path12.sep) ? "" : path12.sep;
|
|
11107
|
-
return cwd.startsWith(personaRoot + sep2) && cwd !== personaRoot;
|
|
11108
|
-
}
|
|
11109
|
-
function inferSandboxSettingsPath(cwd, personaRoot) {
|
|
11110
|
-
if (!shouldSandbox(cwd, personaRoot)) return null;
|
|
11111
|
-
const rel = path12.relative(personaRoot, cwd);
|
|
11112
|
-
const personaId = rel.split(path12.sep)[0];
|
|
11113
|
-
if (!personaId) return null;
|
|
11114
|
-
return path12.join(personaRoot, personaId, ".clawd", "sandbox-settings.json");
|
|
11115
|
-
}
|
|
11116
|
-
var path12;
|
|
11117
|
-
var init_sandbox = __esm({
|
|
11118
|
-
"src/tools/sandbox.ts"() {
|
|
11119
|
-
"use strict";
|
|
11120
|
-
path12 = __toESM(require("path"), 1);
|
|
11121
|
-
}
|
|
11122
|
-
});
|
|
11123
|
-
|
|
11124
10860
|
// src/tools/claude.ts
|
|
11125
10861
|
function macOSDesktopCandidates(home) {
|
|
11126
10862
|
return [
|
|
@@ -11185,8 +10921,7 @@ function buildSpawnArgs(ctx) {
|
|
|
11185
10921
|
throw new Error(`unexpected personaMode: ${String(_exhaustive)}`);
|
|
11186
10922
|
}
|
|
11187
10923
|
}
|
|
11188
|
-
|
|
11189
|
-
if (sandboxSettings) args.push("--settings", sandboxSettings);
|
|
10924
|
+
if (ctx.extraSettings) args.push("--settings", ctx.extraSettings);
|
|
11190
10925
|
if (ctx.extraSystemPrompt) args.push("--append-system-prompt", ctx.extraSystemPrompt);
|
|
11191
10926
|
if (ctx.effort) args.push("--effort", ctx.effort);
|
|
11192
10927
|
if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
|
|
@@ -11466,10 +11201,10 @@ function parseAttachment(obj) {
|
|
|
11466
11201
|
const memories = raw.map((m2) => {
|
|
11467
11202
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11468
11203
|
const rec = m2;
|
|
11469
|
-
const
|
|
11204
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
11470
11205
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11471
|
-
if (!
|
|
11472
|
-
const out = { path:
|
|
11206
|
+
if (!path32 || content == null) return null;
|
|
11207
|
+
const out = { path: path32, content };
|
|
11473
11208
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11474
11209
|
return out;
|
|
11475
11210
|
}).filter((m2) => m2 !== null);
|
|
@@ -11585,7 +11320,6 @@ var init_claude = __esm({
|
|
|
11585
11320
|
init_protocol();
|
|
11586
11321
|
init_claude_history();
|
|
11587
11322
|
init_tool_result_extra();
|
|
11588
|
-
init_sandbox();
|
|
11589
11323
|
ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
|
|
11590
11324
|
"hook_additional_context",
|
|
11591
11325
|
"hook_success",
|
|
@@ -18968,7 +18702,7 @@ var require_websocket = __commonJS({
|
|
|
18968
18702
|
var http2 = require("http");
|
|
18969
18703
|
var net = require("net");
|
|
18970
18704
|
var tls = require("tls");
|
|
18971
|
-
var { randomBytes
|
|
18705
|
+
var { randomBytes, createHash } = require("crypto");
|
|
18972
18706
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
18973
18707
|
var { URL: URL2 } = require("url");
|
|
18974
18708
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -19498,7 +19232,7 @@ var require_websocket = __commonJS({
|
|
|
19498
19232
|
}
|
|
19499
19233
|
}
|
|
19500
19234
|
const defaultPort = isSecure ? 443 : 80;
|
|
19501
|
-
const key =
|
|
19235
|
+
const key = randomBytes(16).toString("base64");
|
|
19502
19236
|
const request = isSecure ? https.request : http2.request;
|
|
19503
19237
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19504
19238
|
let perMessageDeflate;
|
|
@@ -19628,7 +19362,7 @@ var require_websocket = __commonJS({
|
|
|
19628
19362
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
19629
19363
|
return;
|
|
19630
19364
|
}
|
|
19631
|
-
const digest =
|
|
19365
|
+
const digest = createHash("sha1").update(key + GUID).digest("base64");
|
|
19632
19366
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
19633
19367
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
19634
19368
|
return;
|
|
@@ -19995,7 +19729,7 @@ var require_websocket_server = __commonJS({
|
|
|
19995
19729
|
var EventEmitter2 = require("events");
|
|
19996
19730
|
var http2 = require("http");
|
|
19997
19731
|
var { Duplex } = require("stream");
|
|
19998
|
-
var { createHash
|
|
19732
|
+
var { createHash } = require("crypto");
|
|
19999
19733
|
var extension2 = require_extension();
|
|
20000
19734
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
20001
19735
|
var subprotocol2 = require_subprotocol();
|
|
@@ -20296,7 +20030,7 @@ var require_websocket_server = __commonJS({
|
|
|
20296
20030
|
);
|
|
20297
20031
|
}
|
|
20298
20032
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
20299
|
-
const digest =
|
|
20033
|
+
const digest = createHash("sha1").update(key + GUID).digest("base64");
|
|
20300
20034
|
const headers = [
|
|
20301
20035
|
"HTTP/1.1 101 Switching Protocols",
|
|
20302
20036
|
"Upgrade: websocket",
|
|
@@ -20384,7 +20118,7 @@ var require_websocket_server = __commonJS({
|
|
|
20384
20118
|
// src/run-case/recorder.ts
|
|
20385
20119
|
function startRunCaseRecorder(opts) {
|
|
20386
20120
|
const now = opts.now ?? Date.now;
|
|
20387
|
-
const dir =
|
|
20121
|
+
const dir = import_node_path28.default.dirname(opts.recordPath);
|
|
20388
20122
|
let stream = null;
|
|
20389
20123
|
let closing = false;
|
|
20390
20124
|
let closedSettled = false;
|
|
@@ -20424,12 +20158,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20424
20158
|
};
|
|
20425
20159
|
return { tap, close, closed };
|
|
20426
20160
|
}
|
|
20427
|
-
var import_node_fs25,
|
|
20161
|
+
var import_node_fs25, import_node_path28;
|
|
20428
20162
|
var init_recorder = __esm({
|
|
20429
20163
|
"src/run-case/recorder.ts"() {
|
|
20430
20164
|
"use strict";
|
|
20431
20165
|
import_node_fs25 = __toESM(require("fs"), 1);
|
|
20432
|
-
|
|
20166
|
+
import_node_path28 = __toESM(require("path"), 1);
|
|
20433
20167
|
}
|
|
20434
20168
|
});
|
|
20435
20169
|
|
|
@@ -20472,7 +20206,7 @@ var init_wire = __esm({
|
|
|
20472
20206
|
// src/run-case/controller.ts
|
|
20473
20207
|
async function runController(opts) {
|
|
20474
20208
|
const now = opts.now ?? Date.now;
|
|
20475
|
-
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(
|
|
20209
|
+
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path29.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
|
|
20476
20210
|
const ownsCwd = opts.cwd === void 0;
|
|
20477
20211
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20478
20212
|
const spawnCtx = { cwd };
|
|
@@ -20639,13 +20373,13 @@ async function runController(opts) {
|
|
|
20639
20373
|
}
|
|
20640
20374
|
return exitCode ?? 0;
|
|
20641
20375
|
}
|
|
20642
|
-
var import_node_fs26, import_node_os14,
|
|
20376
|
+
var import_node_fs26, import_node_os14, import_node_path29;
|
|
20643
20377
|
var init_controller = __esm({
|
|
20644
20378
|
"src/run-case/controller.ts"() {
|
|
20645
20379
|
"use strict";
|
|
20646
20380
|
import_node_fs26 = require("fs");
|
|
20647
20381
|
import_node_os14 = __toESM(require("os"), 1);
|
|
20648
|
-
|
|
20382
|
+
import_node_path29 = __toESM(require("path"), 1);
|
|
20649
20383
|
init_claude();
|
|
20650
20384
|
init_stdout_splitter();
|
|
20651
20385
|
init_permission_stdio();
|
|
@@ -20877,7 +20611,7 @@ Env (advanced):
|
|
|
20877
20611
|
`;
|
|
20878
20612
|
|
|
20879
20613
|
// src/index.ts
|
|
20880
|
-
var
|
|
20614
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
20881
20615
|
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
20882
20616
|
|
|
20883
20617
|
// src/logger.ts
|
|
@@ -20915,9 +20649,6 @@ function createLogger(opts = {}) {
|
|
|
20915
20649
|
return wrap(base);
|
|
20916
20650
|
}
|
|
20917
20651
|
|
|
20918
|
-
// src/session/store-factory.ts
|
|
20919
|
-
var path5 = __toESM(require("path"), 1);
|
|
20920
|
-
|
|
20921
20652
|
// src/session/store.ts
|
|
20922
20653
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
20923
20654
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -20955,16 +20686,12 @@ function safeFileName(sessionId) {
|
|
|
20955
20686
|
var SessionStore = class {
|
|
20956
20687
|
root;
|
|
20957
20688
|
constructor(opts) {
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
"sessions",
|
|
20965
|
-
...scopeSubPath(scope).map(safeFileName)
|
|
20966
|
-
);
|
|
20967
|
-
}
|
|
20689
|
+
const scope = opts.scope ?? { kind: "default" };
|
|
20690
|
+
this.root = import_node_path4.default.join(
|
|
20691
|
+
opts.dataDir,
|
|
20692
|
+
"sessions",
|
|
20693
|
+
...scopeSubPath(scope).map(safeFileName)
|
|
20694
|
+
);
|
|
20968
20695
|
}
|
|
20969
20696
|
filePath(sessionId) {
|
|
20970
20697
|
return import_node_path4.default.join(this.root, `${safeFileName(sessionId)}.json`);
|
|
@@ -21029,66 +20756,6 @@ var SessionStore = class {
|
|
|
21029
20756
|
}
|
|
21030
20757
|
};
|
|
21031
20758
|
|
|
21032
|
-
// src/session/store-factory.ts
|
|
21033
|
-
var SessionStoreFactory = class {
|
|
21034
|
-
dataDir;
|
|
21035
|
-
bareStore = null;
|
|
21036
|
-
vmOwnerStores = /* @__PURE__ */ new Map();
|
|
21037
|
-
// vmGuest 索引 key = `${pid}::${personId}`(per-People token: 一 person 一目录)
|
|
21038
|
-
vmGuestStores = /* @__PURE__ */ new Map();
|
|
21039
|
-
constructor(opts) {
|
|
21040
|
-
this.dataDir = opts.dataDir;
|
|
21041
|
-
}
|
|
21042
|
-
// ---- root path 派生(暴露给 migration / revoke cascade 等外部消费方) ----
|
|
21043
|
-
bareRoot() {
|
|
21044
|
-
return path5.join(this.dataDir, "sessions");
|
|
21045
|
-
}
|
|
21046
|
-
vmOwnerRoot(personaId) {
|
|
21047
|
-
return path5.join(
|
|
21048
|
-
this.dataDir,
|
|
21049
|
-
"personas",
|
|
21050
|
-
safeFileName(personaId),
|
|
21051
|
-
".clawd",
|
|
21052
|
-
"sessions",
|
|
21053
|
-
"owner"
|
|
21054
|
-
);
|
|
21055
|
-
}
|
|
21056
|
-
vmGuestRoot(personaId, personId) {
|
|
21057
|
-
return path5.join(
|
|
21058
|
-
this.dataDir,
|
|
21059
|
-
"personas",
|
|
21060
|
-
safeFileName(personaId),
|
|
21061
|
-
".clawd",
|
|
21062
|
-
"sessions",
|
|
21063
|
-
"guests",
|
|
21064
|
-
safeFileName(personId)
|
|
21065
|
-
);
|
|
21066
|
-
}
|
|
21067
|
-
// ---- SessionStore 工厂(缓存) ----
|
|
21068
|
-
forBare() {
|
|
21069
|
-
if (!this.bareStore) {
|
|
21070
|
-
this.bareStore = new SessionStore({ root: this.bareRoot() });
|
|
21071
|
-
}
|
|
21072
|
-
return this.bareStore;
|
|
21073
|
-
}
|
|
21074
|
-
forVmOwner(personaId) {
|
|
21075
|
-
const key = personaId;
|
|
21076
|
-
const cached = this.vmOwnerStores.get(key);
|
|
21077
|
-
if (cached) return cached;
|
|
21078
|
-
const st = new SessionStore({ root: this.vmOwnerRoot(personaId) });
|
|
21079
|
-
this.vmOwnerStores.set(key, st);
|
|
21080
|
-
return st;
|
|
21081
|
-
}
|
|
21082
|
-
forVmGuest(personaId, personId) {
|
|
21083
|
-
const key = `${personaId}::${personId}`;
|
|
21084
|
-
const cached = this.vmGuestStores.get(key);
|
|
21085
|
-
if (cached) return cached;
|
|
21086
|
-
const st = new SessionStore({ root: this.vmGuestRoot(personaId, personId) });
|
|
21087
|
-
this.vmGuestStores.set(key, st);
|
|
21088
|
-
return st;
|
|
21089
|
-
}
|
|
21090
|
-
};
|
|
21091
|
-
|
|
21092
20759
|
// src/session/manager.ts
|
|
21093
20760
|
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
21094
20761
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
@@ -21248,10 +20915,7 @@ function buildSpawnContext(state, deps) {
|
|
|
21248
20915
|
toolSessionId: file.toolSessionId,
|
|
21249
20916
|
model: file.model,
|
|
21250
20917
|
permissionMode: file.permissionMode,
|
|
21251
|
-
effort: file.effort
|
|
21252
|
-
// Phase 2 capability platform (plan §3): personaRoot 透传给 buildSpawnArgs;
|
|
21253
|
-
// 内部 shouldSandbox(cwd, personaRoot) 决定是否注入 --settings sandbox-settings.
|
|
21254
|
-
personaRoot: deps.personaRoot
|
|
20918
|
+
effort: file.effort
|
|
21255
20919
|
};
|
|
21256
20920
|
const meta = state.subSessionMeta;
|
|
21257
20921
|
if (meta?.personaMode) {
|
|
@@ -21978,8 +21642,7 @@ var SessionRunner = class {
|
|
|
21978
21642
|
now: this.hooks.now ?? Date.now,
|
|
21979
21643
|
resolveContextWindow: this.hooks.resolveContextWindow,
|
|
21980
21644
|
genUuid: this.hooks.genUuid ?? v4_default,
|
|
21981
|
-
ownerDisplayName: this.hooks.ownerDisplayName
|
|
21982
|
-
personaRoot: this.hooks.personaRoot
|
|
21645
|
+
ownerDisplayName: this.hooks.ownerDisplayName
|
|
21983
21646
|
};
|
|
21984
21647
|
const { state, effects } = reduceSession(this.state, inputMsg, deps);
|
|
21985
21648
|
this.state = state;
|
|
@@ -22403,20 +22066,9 @@ var SessionManager = class {
|
|
|
22403
22066
|
attachObserver(observer) {
|
|
22404
22067
|
this.attachedObserver = observer;
|
|
22405
22068
|
}
|
|
22406
|
-
// 按 scope 拿对应的 SessionStore.
|
|
22407
|
-
//
|
|
22408
|
-
// default → factory.forBare() <dataDir>/sessions/
|
|
22409
|
-
// persona/<pid>/owner → factory.forVmOwner(pid) <dataDir>/personas/<pid>/.clawd/sessions/owner/
|
|
22410
|
-
// persona/<pid>/listener → throw (listener 角色 #698 已下线, schema 字段保留但运行时不该出现)
|
|
22411
|
-
// 缺省 (无 factory 注入): 回退 dataDir+scope 老路径 (向后兼容旧 spec).
|
|
22069
|
+
// 按 scope 拿对应的 SessionStore。default scope 复用 deps.store(保证与
|
|
22070
|
+
// bootstrap 注入的 store 一致);persona scope 第一次访问时按需创建并缓存。
|
|
22412
22071
|
storeFor(scope) {
|
|
22413
|
-
if (this.deps.storeFactory) {
|
|
22414
|
-
if (scope.kind === "default") return this.deps.storeFactory.forBare();
|
|
22415
|
-
if (scope.mode === "owner") return this.deps.storeFactory.forVmOwner(scope.personaId);
|
|
22416
|
-
throw new Error(
|
|
22417
|
-
`SessionManager: listener scope is deprecated (#698); use forVmGuest in Phase 3+ instead`
|
|
22418
|
-
);
|
|
22419
|
-
}
|
|
22420
22072
|
if (scope.kind === "default") return this.deps.store;
|
|
22421
22073
|
const key = scopeKey(scope);
|
|
22422
22074
|
const cached = this.storesByScope.get(key);
|
|
@@ -22444,13 +22096,11 @@ var SessionManager = class {
|
|
|
22444
22096
|
scopeForFile(file) {
|
|
22445
22097
|
return file.ownerPersonaId ? { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" } : { kind: "default" };
|
|
22446
22098
|
}
|
|
22447
|
-
//
|
|
22448
|
-
//
|
|
22449
|
-
// 直接 readdir 这个目录拿所有 personaId. 老布局 (无 storeFactory) fallback 扫
|
|
22450
|
-
// <dataDir>/sessions/ 列子目录 (排除 'default').
|
|
22099
|
+
// 扫 <dataDir>/sessions/ 列出所有 persona 命名空间(不含 'default')。
|
|
22100
|
+
// 用于 findOwnedSession / listAllOwned 跨 scope 查询。
|
|
22451
22101
|
listPersonaIdsOnDisk() {
|
|
22452
22102
|
if (!this.deps.dataDir) return [];
|
|
22453
|
-
const root =
|
|
22103
|
+
const root = import_node_path6.default.join(this.deps.dataDir, "sessions");
|
|
22454
22104
|
let entries;
|
|
22455
22105
|
try {
|
|
22456
22106
|
entries = import_node_fs5.default.readdirSync(root, { withFileTypes: true });
|
|
@@ -22462,9 +22112,19 @@ var SessionManager = class {
|
|
|
22462
22112
|
return entries.filter((e) => e.isDirectory() && e.name !== "default").map((e) => e.name);
|
|
22463
22113
|
}
|
|
22464
22114
|
// owner / default 两个分类下按 sessionId 找文件——前端只传 sessionId 不带 scope,
|
|
22465
|
-
// SessionManager
|
|
22115
|
+
// SessionManager 在这里定位。三层快慢路径:
|
|
22116
|
+
// 1) active runner(用户当前在用的 session):runner.state.file 是 SessionFile 内存权威副本,
|
|
22117
|
+
// 直接返回,零磁盘 I/O。覆盖 attachment / file-sharing 等"用户操作 → 紧接着 RPC"的
|
|
22118
|
+
// 绝大多数场景
|
|
22119
|
+
// 2) default scope 磁盘:inactive default session 命中
|
|
22120
|
+
// 3) 所有 persona owner 目录扫盘:inactive persona owner session 命中
|
|
22466
22121
|
// listener sub-session 不走这条——transport 始终明确传 (personaId, sessionId)。
|
|
22122
|
+
//
|
|
22123
|
+
// 公开方法:attachment / file-sharing handler 通过 deps 闭包消费,不应直接持 SessionStore
|
|
22124
|
+
// (持 default-only store 是 file-sharing v1 的预存 bug 根因——见 fix #703)
|
|
22467
22125
|
findOwnedSession(sessionId) {
|
|
22126
|
+
const runner = this.runners.get(sessionId);
|
|
22127
|
+
if (runner) return runner.getState().file;
|
|
22468
22128
|
const dflt = this.deps.store.read(sessionId);
|
|
22469
22129
|
if (dflt) return dflt;
|
|
22470
22130
|
for (const personaId of this.listPersonaIdsOnDisk()) {
|
|
@@ -22474,6 +22134,13 @@ var SessionManager = class {
|
|
|
22474
22134
|
}
|
|
22475
22135
|
return null;
|
|
22476
22136
|
}
|
|
22137
|
+
// findOwnedSession + scopeForFile 收口。把"sessionId → SessionScope"的派生
|
|
22138
|
+
// 集中到 manager(scope 单一来源),调用方拿 scope 不再自己拼 object 也不依赖
|
|
22139
|
+
// ownerPersonaId 字段语义。给 attachment handler 通过 deps.getSessionScope 闭包消费。
|
|
22140
|
+
findOwnedSessionScope(sessionId) {
|
|
22141
|
+
const file = this.findOwnedSession(sessionId);
|
|
22142
|
+
return file ? this.scopeForFile(file) : null;
|
|
22143
|
+
}
|
|
22477
22144
|
// 合并 default + 所有 persona owner 的 SessionFile —— 桌面 App 主 session 列表入口。
|
|
22478
22145
|
// 同样不含 listener sub-session(listener 走 persona:listSubSessions RPC 单独入口)。
|
|
22479
22146
|
listAllOwned() {
|
|
@@ -22525,9 +22192,6 @@ var SessionManager = class {
|
|
|
22525
22192
|
dataDir: this.deps.dataDir,
|
|
22526
22193
|
personaStore: this.deps.personaStore,
|
|
22527
22194
|
ownerDisplayName: this.deps.ownerDisplayName,
|
|
22528
|
-
// Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
|
|
22529
|
-
// cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
|
|
22530
|
-
personaRoot: this.deps.personaRoot,
|
|
22531
22195
|
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
22532
22196
|
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
22533
22197
|
scope,
|
|
@@ -22622,7 +22286,7 @@ var SessionManager = class {
|
|
|
22622
22286
|
}
|
|
22623
22287
|
}
|
|
22624
22288
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
22625
|
-
create(args
|
|
22289
|
+
create(args) {
|
|
22626
22290
|
let cwd;
|
|
22627
22291
|
if (args.ownerPersonaId) {
|
|
22628
22292
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -22663,7 +22327,6 @@ var SessionManager = class {
|
|
|
22663
22327
|
iconKey: args.iconKey,
|
|
22664
22328
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
22665
22329
|
ownerPersonaId: args.ownerPersonaId,
|
|
22666
|
-
creatorPrincipalId,
|
|
22667
22330
|
createdAt: iso,
|
|
22668
22331
|
updatedAt: iso
|
|
22669
22332
|
};
|
|
@@ -23541,7 +23204,7 @@ var SessionManager = class {
|
|
|
23541
23204
|
|
|
23542
23205
|
// src/persona/store.ts
|
|
23543
23206
|
var fs6 = __toESM(require("fs"), 1);
|
|
23544
|
-
var
|
|
23207
|
+
var path7 = __toESM(require("path"), 1);
|
|
23545
23208
|
init_protocol();
|
|
23546
23209
|
var DEFAULT_SETTINGS = {
|
|
23547
23210
|
permissions: {
|
|
@@ -23570,13 +23233,13 @@ var PersonaStore = class {
|
|
|
23570
23233
|
}
|
|
23571
23234
|
root;
|
|
23572
23235
|
personaDir(personaId) {
|
|
23573
|
-
return
|
|
23236
|
+
return path7.join(this.root, safeFileName(personaId));
|
|
23574
23237
|
}
|
|
23575
23238
|
metaPath(personaId) {
|
|
23576
|
-
return
|
|
23239
|
+
return path7.join(this.personaDir(personaId), ".clawd", "persona.json");
|
|
23577
23240
|
}
|
|
23578
23241
|
claudeMdPath(personaId) {
|
|
23579
|
-
return
|
|
23242
|
+
return path7.join(this.personaDir(personaId), "CLAUDE.md");
|
|
23580
23243
|
}
|
|
23581
23244
|
/**
|
|
23582
23245
|
* Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
|
|
@@ -23586,11 +23249,11 @@ var PersonaStore = class {
|
|
|
23586
23249
|
* 加载 persona 人格,只有 listener 多一层 OS sandbox。
|
|
23587
23250
|
*/
|
|
23588
23251
|
sandboxSettingsPath(personaId) {
|
|
23589
|
-
return
|
|
23252
|
+
return path7.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
|
|
23590
23253
|
}
|
|
23591
23254
|
write(persona, personality) {
|
|
23592
23255
|
const dir = this.personaDir(persona.personaId);
|
|
23593
|
-
fs6.mkdirSync(
|
|
23256
|
+
fs6.mkdirSync(path7.join(dir, ".clawd"), { recursive: true });
|
|
23594
23257
|
this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
|
|
23595
23258
|
this.atomicWrite(
|
|
23596
23259
|
this.sandboxSettingsPath(persona.personaId),
|
|
@@ -23608,10 +23271,6 @@ var PersonaStore = class {
|
|
|
23608
23271
|
const p2 = this.metaPath(personaId);
|
|
23609
23272
|
if (!fs6.existsSync(p2)) return null;
|
|
23610
23273
|
const raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
|
|
23611
|
-
if (raw && typeof raw === "object" && "tokenMap" in raw) {
|
|
23612
|
-
delete raw.tokenMap;
|
|
23613
|
-
this.atomicWrite(p2, JSON.stringify(raw, null, 2));
|
|
23614
|
-
}
|
|
23615
23274
|
return PersonaFileSchema.parse(raw);
|
|
23616
23275
|
}
|
|
23617
23276
|
has(personaId) {
|
|
@@ -23637,12 +23296,48 @@ var PersonaStore = class {
|
|
|
23637
23296
|
}
|
|
23638
23297
|
/** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
|
|
23639
23298
|
skillsDir(personaId) {
|
|
23640
|
-
return
|
|
23299
|
+
return path7.join(this.personaDir(personaId), ".claude", "skills");
|
|
23300
|
+
}
|
|
23301
|
+
/**
|
|
23302
|
+
* Claude Code 项目级 settings 路径:`<personaDir>/.claude/settings.json`。
|
|
23303
|
+
* 这里只读 `enabledPlugins` 字段,由 owner 通过 CC `/plugin` 之类命令维护,daemon 不写。
|
|
23304
|
+
*/
|
|
23305
|
+
claudeSettingsPath(personaId) {
|
|
23306
|
+
return path7.join(this.personaDir(personaId), ".claude", "settings.json");
|
|
23307
|
+
}
|
|
23308
|
+
/**
|
|
23309
|
+
* 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
|
|
23310
|
+
* 的 key 转成 `{ id }[]` 给 UI 做只读展示。
|
|
23311
|
+
*
|
|
23312
|
+
* 兜底范围(来自老板规则:不要无事实依据的兜底):
|
|
23313
|
+
* - 文件不存在 → [](plugin 未被启用是合法初始态)
|
|
23314
|
+
* - JSON 解析失败 → [](owner 手改文件,schema 漂移交给 UI 兜底)
|
|
23315
|
+
* - `enabledPlugins` 字段缺失 / 非 object → []
|
|
23316
|
+
* - 单 value 非 boolean → 跳过该 entry
|
|
23317
|
+
* 其他错误(fs 权限等)原样抛出,由上层 handler 转 ws error 帧。
|
|
23318
|
+
*/
|
|
23319
|
+
readEnabledPlugins(personaId) {
|
|
23320
|
+
const p2 = this.claudeSettingsPath(personaId);
|
|
23321
|
+
if (!fs6.existsSync(p2)) return [];
|
|
23322
|
+
let raw;
|
|
23323
|
+
try {
|
|
23324
|
+
raw = JSON.parse(fs6.readFileSync(p2, "utf8"));
|
|
23325
|
+
} catch {
|
|
23326
|
+
return [];
|
|
23327
|
+
}
|
|
23328
|
+
if (!raw || typeof raw !== "object") return [];
|
|
23329
|
+
const enabled = raw.enabledPlugins;
|
|
23330
|
+
if (!enabled || typeof enabled !== "object" || Array.isArray(enabled)) return [];
|
|
23331
|
+
const out = [];
|
|
23332
|
+
for (const [id, value] of Object.entries(enabled)) {
|
|
23333
|
+
if (value === true && id.length > 0) out.push({ id });
|
|
23334
|
+
}
|
|
23335
|
+
return out;
|
|
23641
23336
|
}
|
|
23642
23337
|
list() {
|
|
23643
23338
|
if (!fs6.existsSync(this.root)) return [];
|
|
23644
23339
|
return fs6.readdirSync(this.root).filter((name) => {
|
|
23645
|
-
return fs6.existsSync(
|
|
23340
|
+
return fs6.existsSync(path7.join(this.root, name, ".clawd", "persona.json"));
|
|
23646
23341
|
});
|
|
23647
23342
|
}
|
|
23648
23343
|
remove(personaId) {
|
|
@@ -23667,19 +23362,12 @@ var PersonaRegistry = class {
|
|
|
23667
23362
|
}
|
|
23668
23363
|
store;
|
|
23669
23364
|
cache = /* @__PURE__ */ new Map();
|
|
23670
|
-
/**
|
|
23671
|
-
* 从 store 全量重建缓存(boot 时 + 测试 setup 时)。
|
|
23672
|
-
* 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
|
|
23673
|
-
* try/catch 隔离,让其它 persona 仍能加载。
|
|
23674
|
-
*/
|
|
23365
|
+
/** 从 store 全量重建缓存(boot 时 + 测试 setup 时) */
|
|
23675
23366
|
reload() {
|
|
23676
23367
|
this.cache.clear();
|
|
23677
23368
|
for (const id of this.store.list()) {
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
if (p2) this.cache.set(p2.personaId, p2);
|
|
23681
|
-
} catch {
|
|
23682
|
-
}
|
|
23369
|
+
const p2 = this.store.read(id);
|
|
23370
|
+
if (p2) this.cache.set(p2.personaId, p2);
|
|
23683
23371
|
}
|
|
23684
23372
|
}
|
|
23685
23373
|
get(personaId) {
|
|
@@ -23695,6 +23383,21 @@ var PersonaRegistry = class {
|
|
|
23695
23383
|
list() {
|
|
23696
23384
|
return Array.from(this.cache.values());
|
|
23697
23385
|
}
|
|
23386
|
+
/**
|
|
23387
|
+
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23388
|
+
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
23389
|
+
* persona 数量通常 < 100,性能可忽略。
|
|
23390
|
+
*/
|
|
23391
|
+
findByToken(token) {
|
|
23392
|
+
if (!token) return null;
|
|
23393
|
+
for (const persona of this.cache.values()) {
|
|
23394
|
+
if (!persona.public) continue;
|
|
23395
|
+
const entry = persona.tokenMap?.[token];
|
|
23396
|
+
if (!entry || entry.revoked) continue;
|
|
23397
|
+
return { personaId: persona.personaId, label: entry.label };
|
|
23398
|
+
}
|
|
23399
|
+
return null;
|
|
23400
|
+
}
|
|
23698
23401
|
};
|
|
23699
23402
|
|
|
23700
23403
|
// src/persona/manager.ts
|
|
@@ -23988,6 +23691,9 @@ var PersonaManager = class {
|
|
|
23988
23691
|
model: args.model,
|
|
23989
23692
|
public: args.public ?? false,
|
|
23990
23693
|
iconKey: args.iconKey,
|
|
23694
|
+
// 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
|
|
23695
|
+
// file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
|
|
23696
|
+
tokenMap: {},
|
|
23991
23697
|
createdAt: now,
|
|
23992
23698
|
updatedAt: now
|
|
23993
23699
|
};
|
|
@@ -24024,28 +23730,65 @@ var PersonaManager = class {
|
|
|
24024
23730
|
listSkills(personaId) {
|
|
24025
23731
|
return listSkillsForDir(this.deps.store.skillsDir(personaId), "project");
|
|
24026
23732
|
}
|
|
23733
|
+
/**
|
|
23734
|
+
* 读取 persona `.claude/settings.json` 中已启用的插件列表(value === true 的 key)。
|
|
23735
|
+
* 文件不存在 / 解析失败 / 字段缺失 → 空数组。供 persona:get handler 拼响应。
|
|
23736
|
+
*/
|
|
23737
|
+
listEnabledPlugins(personaId) {
|
|
23738
|
+
return this.deps.store.readEnabledPlugins(personaId);
|
|
23739
|
+
}
|
|
24027
23740
|
/** 读 sandbox-settings.json;不存在 / 损坏返回 null。供 persona:get handler 拼响应。 */
|
|
24028
23741
|
readSandboxSettings(personaId) {
|
|
24029
23742
|
return this.deps.store.readSandboxSettings(personaId);
|
|
24030
23743
|
}
|
|
24031
23744
|
/**
|
|
24032
23745
|
* 删除 persona。
|
|
24033
|
-
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/
|
|
24034
|
-
* Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
|
|
24035
|
-
* .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
|
|
24036
|
-
*
|
|
24037
|
-
* ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
|
|
24038
|
-
* ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
|
|
24039
|
-
* 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
|
|
24040
|
-
* 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
|
|
24041
|
-
* 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
|
|
24042
|
-
* force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
|
|
24043
|
-
* "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
|
|
23746
|
+
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
|
|
24044
23747
|
*/
|
|
24045
23748
|
delete(personaId) {
|
|
24046
23749
|
this.deps.store.remove(personaId);
|
|
24047
23750
|
this.deps.registry.remove(personaId);
|
|
24048
23751
|
}
|
|
23752
|
+
/**
|
|
23753
|
+
* 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
|
|
23754
|
+
* 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
|
|
23755
|
+
*/
|
|
23756
|
+
issueToken(personaId, label) {
|
|
23757
|
+
const existing = this.deps.registry.get(personaId);
|
|
23758
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23759
|
+
const token = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
23760
|
+
const entry = {
|
|
23761
|
+
label,
|
|
23762
|
+
issuedAt: Date.now(),
|
|
23763
|
+
revoked: false
|
|
23764
|
+
};
|
|
23765
|
+
const updated = {
|
|
23766
|
+
...existing,
|
|
23767
|
+
tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
|
|
23768
|
+
updatedAt: Date.now()
|
|
23769
|
+
};
|
|
23770
|
+
this.deps.store.writeMeta(updated);
|
|
23771
|
+
this.deps.registry.set(updated);
|
|
23772
|
+
return { token, persona: updated };
|
|
23773
|
+
}
|
|
23774
|
+
/** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
|
|
23775
|
+
revokeToken(personaId, token) {
|
|
23776
|
+
const existing = this.deps.registry.get(personaId);
|
|
23777
|
+
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23778
|
+
const tokenEntry = existing.tokenMap?.[token];
|
|
23779
|
+
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
23780
|
+
const updated = {
|
|
23781
|
+
...existing,
|
|
23782
|
+
tokenMap: {
|
|
23783
|
+
...existing.tokenMap ?? {},
|
|
23784
|
+
[token]: { ...tokenEntry, revoked: true }
|
|
23785
|
+
},
|
|
23786
|
+
updatedAt: Date.now()
|
|
23787
|
+
};
|
|
23788
|
+
this.deps.store.writeMeta(updated);
|
|
23789
|
+
this.deps.registry.set(updated);
|
|
23790
|
+
return updated;
|
|
23791
|
+
}
|
|
24049
23792
|
/**
|
|
24050
23793
|
* label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
|
|
24051
23794
|
* base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
|
|
@@ -24065,7 +23808,7 @@ var PersonaManager = class {
|
|
|
24065
23808
|
|
|
24066
23809
|
// src/persona/seed.ts
|
|
24067
23810
|
var fs8 = __toESM(require("fs"), 1);
|
|
24068
|
-
var
|
|
23811
|
+
var path9 = __toESM(require("path"), 1);
|
|
24069
23812
|
var import_node_url = require("url");
|
|
24070
23813
|
var import_meta = {};
|
|
24071
23814
|
var DEFAULT_PERSONAS = [
|
|
@@ -24101,14 +23844,14 @@ var DEFAULT_PERSONAS = [
|
|
|
24101
23844
|
function findDefaultsRoot() {
|
|
24102
23845
|
const candidates = [];
|
|
24103
23846
|
try {
|
|
24104
|
-
const here =
|
|
24105
|
-
candidates.push(
|
|
24106
|
-
candidates.push(
|
|
23847
|
+
const here = path9.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
23848
|
+
candidates.push(path9.resolve(here, "defaults"));
|
|
23849
|
+
candidates.push(path9.resolve(here, "persona-defaults"));
|
|
24107
23850
|
} catch {
|
|
24108
23851
|
}
|
|
24109
23852
|
if (process.argv[1]) {
|
|
24110
|
-
const argvDir =
|
|
24111
|
-
candidates.push(
|
|
23853
|
+
const argvDir = path9.dirname(process.argv[1]);
|
|
23854
|
+
candidates.push(path9.resolve(argvDir, "persona-defaults"));
|
|
24112
23855
|
}
|
|
24113
23856
|
for (const c of candidates) {
|
|
24114
23857
|
try {
|
|
@@ -24125,7 +23868,7 @@ function seedDefaultPersonas(args) {
|
|
|
24125
23868
|
args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
|
|
24126
23869
|
continue;
|
|
24127
23870
|
}
|
|
24128
|
-
const bundleDir =
|
|
23871
|
+
const bundleDir = path9.join(args.defaultsRoot, entry.personaId);
|
|
24129
23872
|
if (!fs8.existsSync(bundleDir)) {
|
|
24130
23873
|
args.logger.warn("persona.seed.skip", {
|
|
24131
23874
|
personaId: entry.personaId,
|
|
@@ -24134,7 +23877,7 @@ function seedDefaultPersonas(args) {
|
|
|
24134
23877
|
});
|
|
24135
23878
|
continue;
|
|
24136
23879
|
}
|
|
24137
|
-
const claudeMdPath =
|
|
23880
|
+
const claudeMdPath = path9.join(bundleDir, "CLAUDE.md");
|
|
24138
23881
|
if (!fs8.existsSync(claudeMdPath)) {
|
|
24139
23882
|
args.logger.warn("persona.seed.skip", {
|
|
24140
23883
|
personaId: entry.personaId,
|
|
@@ -24151,6 +23894,7 @@ function seedDefaultPersonas(args) {
|
|
|
24151
23894
|
model: entry.model,
|
|
24152
23895
|
public: entry.public,
|
|
24153
23896
|
iconKey: entry.iconKey,
|
|
23897
|
+
tokenMap: {},
|
|
24154
23898
|
createdAt: now,
|
|
24155
23899
|
updatedAt: now
|
|
24156
23900
|
};
|
|
@@ -24162,8 +23906,8 @@ function seedDefaultPersonas(args) {
|
|
|
24162
23906
|
function copyBundleExtras(srcDir, dstDir) {
|
|
24163
23907
|
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
24164
23908
|
if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
|
|
24165
|
-
const srcPath =
|
|
24166
|
-
const dstPath =
|
|
23909
|
+
const srcPath = path9.join(srcDir, entry.name);
|
|
23910
|
+
const dstPath = path9.join(dstDir, entry.name);
|
|
24167
23911
|
if (entry.isDirectory()) {
|
|
24168
23912
|
fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
|
|
24169
23913
|
} else if (entry.isFile()) {
|
|
@@ -25545,9 +25289,6 @@ var LocalWsServer = class {
|
|
|
25545
25289
|
httpServer = null;
|
|
25546
25290
|
frameHandler = null;
|
|
25547
25291
|
clients = /* @__PURE__ */ new Map();
|
|
25548
|
-
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
25549
|
-
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
25550
|
-
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
25551
25292
|
logger;
|
|
25552
25293
|
pingIntervalMs;
|
|
25553
25294
|
async start() {
|
|
@@ -25638,49 +25379,6 @@ var LocalWsServer = class {
|
|
|
25638
25379
|
this.safeSend(c.ws, frame);
|
|
25639
25380
|
}
|
|
25640
25381
|
}
|
|
25641
|
-
// Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
|
|
25642
|
-
// 用于 capability:tokenIssued / capability:tokenDeleted 等 owner-only push frame——
|
|
25643
|
-
// guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
|
|
25644
|
-
broadcastToOwners(frame) {
|
|
25645
|
-
const gate = this.opts.authGate;
|
|
25646
|
-
for (const c of this.clients.values()) {
|
|
25647
|
-
if (gate && !gate.isAuthed(c.handle.id)) continue;
|
|
25648
|
-
if (c.ctx && c.ctx.principal.kind !== "owner") continue;
|
|
25649
|
-
this.safeSend(c.ws, frame);
|
|
25650
|
-
}
|
|
25651
|
-
}
|
|
25652
|
-
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
25653
|
-
// - 所有 owner ws (owner 看自家所有 channel)
|
|
25654
|
-
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
25655
|
-
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
25656
|
-
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
25657
|
-
this.broadcastToOwners(frame);
|
|
25658
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25659
|
-
if (!set) return;
|
|
25660
|
-
for (const id of set) {
|
|
25661
|
-
const c = this.clients.get(id);
|
|
25662
|
-
if (!c) continue;
|
|
25663
|
-
this.safeSend(c.ws, frame);
|
|
25664
|
-
}
|
|
25665
|
-
}
|
|
25666
|
-
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
25667
|
-
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
25668
|
-
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
25669
|
-
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
25670
|
-
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
25671
|
-
broadcastToPrincipal(principalId, frame) {
|
|
25672
|
-
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
25673
|
-
this.broadcastToOwners(frame);
|
|
25674
|
-
return;
|
|
25675
|
-
}
|
|
25676
|
-
const set = this.capabilityIdToClients.get(principalId);
|
|
25677
|
-
if (!set) return;
|
|
25678
|
-
for (const id of set) {
|
|
25679
|
-
const c = this.clients.get(id);
|
|
25680
|
-
if (!c) continue;
|
|
25681
|
-
this.safeSend(c.ws, frame);
|
|
25682
|
-
}
|
|
25683
|
-
}
|
|
25684
25382
|
firstSubscriber(sessionId) {
|
|
25685
25383
|
for (const c of this.clients.values()) {
|
|
25686
25384
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -25702,40 +25400,6 @@ var LocalWsServer = class {
|
|
|
25702
25400
|
if (!c) return;
|
|
25703
25401
|
this.safeSend(c.ws, frame);
|
|
25704
25402
|
}
|
|
25705
|
-
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
25706
|
-
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
25707
|
-
attachClientContext(clientId, ctx) {
|
|
25708
|
-
const c = this.clients.get(clientId);
|
|
25709
|
-
if (!c) return;
|
|
25710
|
-
c.ctx = ctx;
|
|
25711
|
-
if (ctx.capabilityId) {
|
|
25712
|
-
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
25713
|
-
if (!set) {
|
|
25714
|
-
set = /* @__PURE__ */ new Set();
|
|
25715
|
-
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
25716
|
-
}
|
|
25717
|
-
set.add(clientId);
|
|
25718
|
-
}
|
|
25719
|
-
}
|
|
25720
|
-
// 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
|
|
25721
|
-
getClientContext(clientId) {
|
|
25722
|
-
return this.clients.get(clientId)?.ctx ?? null;
|
|
25723
|
-
}
|
|
25724
|
-
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
25725
|
-
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
25726
|
-
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
25727
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25728
|
-
if (!set) return;
|
|
25729
|
-
const ids = [...set];
|
|
25730
|
-
for (const id of ids) {
|
|
25731
|
-
const c = this.clients.get(id);
|
|
25732
|
-
if (!c) continue;
|
|
25733
|
-
try {
|
|
25734
|
-
c.ws.close(code, reason);
|
|
25735
|
-
} catch {
|
|
25736
|
-
}
|
|
25737
|
-
}
|
|
25738
|
-
}
|
|
25739
25403
|
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25740
25404
|
routeConnection(socket, req) {
|
|
25741
25405
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
@@ -25770,7 +25434,7 @@ var LocalWsServer = class {
|
|
|
25770
25434
|
} catch {
|
|
25771
25435
|
}
|
|
25772
25436
|
}, this.pingIntervalMs);
|
|
25773
|
-
this.clients.set(id, { handle, ws: socket, pingTimer
|
|
25437
|
+
this.clients.set(id, { handle, ws: socket, pingTimer });
|
|
25774
25438
|
this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
|
|
25775
25439
|
const authGate = this.opts.authGate;
|
|
25776
25440
|
let authed = true;
|
|
@@ -25794,12 +25458,6 @@ var LocalWsServer = class {
|
|
|
25794
25458
|
socket.on("close", () => {
|
|
25795
25459
|
const c = this.clients.get(id);
|
|
25796
25460
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
25797
|
-
const capId = c?.ctx?.capabilityId;
|
|
25798
|
-
if (capId) {
|
|
25799
|
-
const set = this.capabilityIdToClients.get(capId);
|
|
25800
|
-
set?.delete(id);
|
|
25801
|
-
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
25802
|
-
}
|
|
25803
25461
|
this.clients.delete(id);
|
|
25804
25462
|
authGate?.unregister(id);
|
|
25805
25463
|
this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
|
|
@@ -25832,11 +25490,6 @@ var LocalWsServer = class {
|
|
|
25832
25490
|
return;
|
|
25833
25491
|
}
|
|
25834
25492
|
} else if (frame.type === "auth") {
|
|
25835
|
-
const token = typeof frame.token === "string" ? frame.token ?? "" : "";
|
|
25836
|
-
if (token && this.opts.tryVerifyCapabilityToken) {
|
|
25837
|
-
const guestCtx = this.opts.tryVerifyCapabilityToken(token);
|
|
25838
|
-
if (guestCtx) this.attachClientContext(client.id, guestCtx);
|
|
25839
|
-
}
|
|
25840
25493
|
this.safeSend(this.clients.get(client.id).ws, { type: "auth:ok" });
|
|
25841
25494
|
return;
|
|
25842
25495
|
}
|
|
@@ -25953,27 +25606,15 @@ var AuthGate = class {
|
|
|
25953
25606
|
this.markFailed(handle.id);
|
|
25954
25607
|
return frame.type === "auth" ? "consumed" : "reject";
|
|
25955
25608
|
}
|
|
25956
|
-
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
|
|
25960
|
-
|
|
25961
|
-
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
} else {
|
|
25966
|
-
if (!this.opts.expectedToken) {
|
|
25967
|
-
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
25968
|
-
this.markFailed(handle.id);
|
|
25969
|
-
return "consumed";
|
|
25970
|
-
}
|
|
25971
|
-
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
25972
|
-
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
25973
|
-
this.markFailed(handle.id);
|
|
25974
|
-
return "consumed";
|
|
25975
|
-
}
|
|
25976
|
-
ctx = this.opts.buildOwnerContext?.() ?? null;
|
|
25609
|
+
if (!this.opts.expectedToken) {
|
|
25610
|
+
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
25611
|
+
this.markFailed(handle.id);
|
|
25612
|
+
return "consumed";
|
|
25613
|
+
}
|
|
25614
|
+
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
25615
|
+
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
25616
|
+
this.markFailed(handle.id);
|
|
25617
|
+
return "consumed";
|
|
25977
25618
|
}
|
|
25978
25619
|
if (st.timer != null) {
|
|
25979
25620
|
const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -25981,7 +25622,6 @@ var AuthGate = class {
|
|
|
25981
25622
|
}
|
|
25982
25623
|
st.authed = true;
|
|
25983
25624
|
st.timer = null;
|
|
25984
|
-
if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
|
|
25985
25625
|
this.opts.sendOk(handle, { type: "auth:ok" });
|
|
25986
25626
|
return "consumed";
|
|
25987
25627
|
}
|
|
@@ -26017,7 +25657,7 @@ var AuthContextResolver = class {
|
|
|
26017
25657
|
}
|
|
26018
25658
|
opts;
|
|
26019
25659
|
/**
|
|
26020
|
-
* 从 `Authorization` 头解析。null = 无凭证 /
|
|
25660
|
+
* 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
|
|
26021
25661
|
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
26022
25662
|
*/
|
|
26023
25663
|
resolveFromHeader(authHeader, remoteAddress) {
|
|
@@ -26027,6 +25667,15 @@ var AuthContextResolver = class {
|
|
|
26027
25667
|
if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
|
|
26028
25668
|
return { role: "owner", isLoopback };
|
|
26029
25669
|
}
|
|
25670
|
+
const personalHit = this.opts.personaRegistry.findByToken(token);
|
|
25671
|
+
if (personalHit) {
|
|
25672
|
+
return {
|
|
25673
|
+
role: "personal",
|
|
25674
|
+
personaId: personalHit.personaId,
|
|
25675
|
+
label: personalHit.label,
|
|
25676
|
+
isLoopback
|
|
25677
|
+
};
|
|
25678
|
+
}
|
|
26030
25679
|
return null;
|
|
26031
25680
|
}
|
|
26032
25681
|
};
|
|
@@ -26048,759 +25697,206 @@ function constantTimeEqual2(a, b2) {
|
|
|
26048
25697
|
return diff2 === 0;
|
|
26049
25698
|
}
|
|
26050
25699
|
|
|
26051
|
-
//
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
// src/transport/connection-context.ts
|
|
26055
|
-
function ownerContext(ownerPrincipalId, displayName) {
|
|
26056
|
-
return {
|
|
26057
|
-
principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
|
|
26058
|
-
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
26059
|
-
};
|
|
26060
|
-
}
|
|
26061
|
-
function guestContext(cap) {
|
|
26062
|
-
return {
|
|
26063
|
-
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
26064
|
-
grants: cap.grants,
|
|
26065
|
-
capabilityId: cap.id
|
|
26066
|
-
};
|
|
26067
|
-
}
|
|
26068
|
-
function authenticate(token, deps) {
|
|
26069
|
-
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
26070
|
-
if (deps.isOwnerToken(token)) {
|
|
26071
|
-
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
26072
|
-
}
|
|
26073
|
-
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
26074
|
-
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
26075
|
-
if (v2.ok) return { ok: true, context: guestContext(v2.capability) };
|
|
26076
|
-
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
26077
|
-
return { ok: false, code: v2.code };
|
|
26078
|
-
}
|
|
25700
|
+
// src/transport/http-router.ts
|
|
25701
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
25702
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
26079
25703
|
|
|
26080
|
-
// src/
|
|
26081
|
-
var
|
|
26082
|
-
var
|
|
26083
|
-
var
|
|
26084
|
-
|
|
26085
|
-
var
|
|
26086
|
-
constructor(dataDir) {
|
|
26087
|
-
this.dataDir = dataDir;
|
|
26088
|
-
fs15.mkdirSync(dataDir, { recursive: true });
|
|
26089
|
-
this.cache = this.readFromDisk();
|
|
26090
|
-
}
|
|
25704
|
+
// src/attachment/group.ts
|
|
25705
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
25706
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
25707
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
25708
|
+
init_protocol();
|
|
25709
|
+
var GroupFileStore = class {
|
|
26091
25710
|
dataDir;
|
|
26092
|
-
|
|
26093
|
-
|
|
26094
|
-
|
|
25711
|
+
logger;
|
|
25712
|
+
cache = /* @__PURE__ */ new Map();
|
|
25713
|
+
constructor(opts) {
|
|
25714
|
+
this.dataDir = opts.dataDir;
|
|
25715
|
+
this.logger = opts.logger;
|
|
26095
25716
|
}
|
|
26096
|
-
|
|
26097
|
-
|
|
26098
|
-
if (idx >= 0) {
|
|
26099
|
-
this.cache[idx] = cap;
|
|
26100
|
-
} else {
|
|
26101
|
-
this.cache.push(cap);
|
|
26102
|
-
}
|
|
26103
|
-
this.flush();
|
|
25717
|
+
rootForScope(scope) {
|
|
25718
|
+
return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
26104
25719
|
}
|
|
26105
|
-
|
|
26106
|
-
|
|
26107
|
-
|
|
26108
|
-
this.cache = next;
|
|
26109
|
-
this.flush();
|
|
26110
|
-
}
|
|
26111
|
-
filePath() {
|
|
26112
|
-
return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
|
|
26113
|
-
}
|
|
26114
|
-
readFromDisk() {
|
|
26115
|
-
const file = this.filePath();
|
|
26116
|
-
let raw;
|
|
26117
|
-
try {
|
|
26118
|
-
raw = fs15.readFileSync(file, "utf8");
|
|
26119
|
-
} catch (err) {
|
|
26120
|
-
if (err?.code === "ENOENT") return [];
|
|
26121
|
-
return [];
|
|
26122
|
-
}
|
|
26123
|
-
if (!raw.trim()) return [];
|
|
26124
|
-
let parsed;
|
|
26125
|
-
try {
|
|
26126
|
-
parsed = JSON.parse(raw);
|
|
26127
|
-
} catch {
|
|
26128
|
-
return [];
|
|
26129
|
-
}
|
|
26130
|
-
if (!parsed || typeof parsed !== "object") return [];
|
|
26131
|
-
const arr = parsed.capabilities;
|
|
26132
|
-
if (!Array.isArray(arr)) return [];
|
|
26133
|
-
const out = [];
|
|
26134
|
-
for (const item of arr) {
|
|
26135
|
-
const r = CapabilitySchema.safeParse(item);
|
|
26136
|
-
if (r.success) out.push(r.data);
|
|
26137
|
-
}
|
|
26138
|
-
return out;
|
|
26139
|
-
}
|
|
26140
|
-
flush() {
|
|
26141
|
-
const content = { version: FILE_VERSION, capabilities: this.cache };
|
|
26142
|
-
this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
|
|
26143
|
-
}
|
|
26144
|
-
atomicWrite(file, content) {
|
|
26145
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26146
|
-
fs15.writeFileSync(tmp, content, { mode: 384 });
|
|
26147
|
-
fs15.renameSync(tmp, file);
|
|
26148
|
-
try {
|
|
26149
|
-
fs15.chmodSync(file, 384);
|
|
26150
|
-
} catch {
|
|
26151
|
-
}
|
|
26152
|
-
}
|
|
26153
|
-
};
|
|
26154
|
-
|
|
26155
|
-
// src/permission/capability-registry.ts
|
|
26156
|
-
var crypto4 = __toESM(require("crypto"), 1);
|
|
26157
|
-
var CapabilityRegistry = class {
|
|
26158
|
-
constructor(store, now = () => Date.now()) {
|
|
26159
|
-
this.store = store;
|
|
26160
|
-
this.now = now;
|
|
26161
|
-
for (const cap of store.list()) {
|
|
26162
|
-
this.bySecretHash.set(cap.secretHash, cap);
|
|
26163
|
-
}
|
|
26164
|
-
}
|
|
26165
|
-
store;
|
|
26166
|
-
now;
|
|
26167
|
-
// sha256(token) → Capability
|
|
26168
|
-
bySecretHash = /* @__PURE__ */ new Map();
|
|
26169
|
-
list() {
|
|
26170
|
-
return this.store.list();
|
|
26171
|
-
}
|
|
26172
|
-
verifyToken(token) {
|
|
26173
|
-
if (!token) return { ok: false, code: "TOKEN_INVALID" };
|
|
26174
|
-
const hash = sha256Hex(token);
|
|
26175
|
-
const cap = this.bySecretHash.get(hash);
|
|
26176
|
-
if (!cap) return { ok: false, code: "TOKEN_INVALID" };
|
|
26177
|
-
if (cap.expiresAt && this.now() >= cap.expiresAt) {
|
|
26178
|
-
return { ok: false, code: "TOKEN_EXPIRED" };
|
|
26179
|
-
}
|
|
26180
|
-
if (cap.maxUses && cap.usedCount >= cap.maxUses) {
|
|
26181
|
-
return { ok: false, code: "TOKEN_EXHAUSTED" };
|
|
26182
|
-
}
|
|
26183
|
-
const updated = { ...cap, usedCount: cap.usedCount + 1 };
|
|
26184
|
-
this.bySecretHash.set(hash, updated);
|
|
26185
|
-
this.store.upsert(updated);
|
|
26186
|
-
return { ok: true, capability: updated };
|
|
26187
|
-
}
|
|
26188
|
-
upsertCapability(cap) {
|
|
26189
|
-
this.bySecretHash.set(cap.secretHash, cap);
|
|
26190
|
-
this.store.upsert(cap);
|
|
26191
|
-
}
|
|
26192
|
-
/**
|
|
26193
|
-
* Hard delete:从 store 物理移除 + 从 Map 清掉 secretHash 索引。
|
|
26194
|
-
* 后续 verifyToken 该 token 走 'TOKEN_INVALID' 分支(secretHash 已不在 Map)。
|
|
26195
|
-
* idempotent:不存在 → null。
|
|
26196
|
-
*/
|
|
26197
|
-
delete(id) {
|
|
26198
|
-
const current = this.store.list().find((c) => c.id === id);
|
|
26199
|
-
if (!current) return null;
|
|
26200
|
-
this.bySecretHash.delete(current.secretHash);
|
|
26201
|
-
this.store.remove(id);
|
|
26202
|
-
return current;
|
|
26203
|
-
}
|
|
26204
|
-
findById(id) {
|
|
26205
|
-
return this.store.list().find((c) => c.id === id) ?? null;
|
|
26206
|
-
}
|
|
26207
|
-
};
|
|
26208
|
-
function sha256Hex(s) {
|
|
26209
|
-
return crypto4.createHash("sha256").update(s).digest("hex");
|
|
26210
|
-
}
|
|
26211
|
-
|
|
26212
|
-
// src/permission/capability-manager.ts
|
|
26213
|
-
var crypto5 = __toESM(require("crypto"), 1);
|
|
26214
|
-
var CapabilityManager = class {
|
|
26215
|
-
constructor(registry2, hooks = {}) {
|
|
26216
|
-
this.registry = registry2;
|
|
26217
|
-
this.hooks = hooks;
|
|
26218
|
-
}
|
|
26219
|
-
registry;
|
|
26220
|
-
hooks;
|
|
26221
|
-
list() {
|
|
26222
|
-
return this.registry.list();
|
|
26223
|
-
}
|
|
26224
|
-
// whoami handler 用:根据 capabilityId 反查 caller 的 capability 实体。
|
|
26225
|
-
// 返回 Capability 持久化形态;handler 调 stripSecretHash 后再上 wire。
|
|
26226
|
-
findById(id) {
|
|
26227
|
-
return this.registry.findById(id);
|
|
26228
|
-
}
|
|
26229
|
-
issue(args) {
|
|
26230
|
-
if (!args.displayName.trim()) {
|
|
26231
|
-
throw new Error("CapabilityManager.issue: displayName must be non-empty");
|
|
26232
|
-
}
|
|
26233
|
-
const token = (this.hooks.generateToken ?? defaultGenerateToken)();
|
|
26234
|
-
const id = (this.hooks.generateId ?? defaultGenerateId)();
|
|
26235
|
-
const now = (this.hooks.now ?? Date.now)();
|
|
26236
|
-
const cap = {
|
|
26237
|
-
id,
|
|
26238
|
-
secretHash: sha256Hex2(token),
|
|
26239
|
-
displayName: args.displayName,
|
|
26240
|
-
grants: args.grants,
|
|
26241
|
-
issuedAt: now,
|
|
26242
|
-
usedCount: 0,
|
|
26243
|
-
personId: args.personId,
|
|
26244
|
-
...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
|
|
26245
|
-
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
26246
|
-
};
|
|
26247
|
-
this.registry.upsertCapability(cap);
|
|
26248
|
-
this.hooks.onIssued?.(cap, token);
|
|
26249
|
-
return { token, capability: cap };
|
|
26250
|
-
}
|
|
26251
|
-
/**
|
|
26252
|
-
* Per-People token: 同 personId 已有 cap 时 add grants(merge unique by resource+action)。
|
|
26253
|
-
* 返回 updated cap。如果 personId 没有 cap 返 null(caller 应该调 issue 新建)。
|
|
26254
|
-
*/
|
|
26255
|
-
addGrantsToPerson(personId, newGrants) {
|
|
26256
|
-
const existing = this.registry.list().find((c) => c.personId === personId);
|
|
26257
|
-
if (!existing) return null;
|
|
26258
|
-
const merged = mergeGrants(existing.grants, newGrants);
|
|
26259
|
-
const updated = { ...existing, grants: merged };
|
|
26260
|
-
this.registry.upsertCapability(updated);
|
|
26261
|
-
this.hooks.onIssued?.(updated, "__merged__");
|
|
26262
|
-
return updated;
|
|
26263
|
-
}
|
|
26264
|
-
/** 按 personId 反查现有 cap(per-People token dedupe)。 */
|
|
26265
|
-
findByPerson(personId) {
|
|
26266
|
-
return this.registry.list().find((c) => c.personId === personId) ?? null;
|
|
26267
|
-
}
|
|
26268
|
-
/**
|
|
26269
|
-
* Hard delete:从 registry / store 物理移除 capability。
|
|
26270
|
-
* idempotent:不存在 → null(handler 翻译成 VALIDATION_ERROR)
|
|
26271
|
-
* onDeleted hook 在 daemon/index.ts wire 负责 close 连接 + cleanup 文件 + 广播
|
|
26272
|
-
*/
|
|
26273
|
-
delete(id) {
|
|
26274
|
-
const removed = this.registry.delete(id);
|
|
26275
|
-
if (!removed) return null;
|
|
26276
|
-
this.hooks.onDeleted?.(removed);
|
|
26277
|
-
return { capability: removed };
|
|
26278
|
-
}
|
|
26279
|
-
};
|
|
26280
|
-
function mergeGrants(existing, add) {
|
|
26281
|
-
const key = (g2) => JSON.stringify({ r: g2.resource, a: [...g2.actions].sort() });
|
|
26282
|
-
const seen = new Set(existing.map(key));
|
|
26283
|
-
const out = [...existing];
|
|
26284
|
-
for (const g2 of add) {
|
|
26285
|
-
if (!seen.has(key(g2))) {
|
|
26286
|
-
out.push(g2);
|
|
26287
|
-
seen.add(key(g2));
|
|
26288
|
-
}
|
|
26289
|
-
}
|
|
26290
|
-
return out;
|
|
26291
|
-
}
|
|
26292
|
-
function defaultGenerateToken() {
|
|
26293
|
-
return crypto5.randomBytes(24).toString("base64url");
|
|
26294
|
-
}
|
|
26295
|
-
function defaultGenerateId() {
|
|
26296
|
-
return "cap_" + crypto5.randomBytes(6).toString("base64url");
|
|
26297
|
-
}
|
|
26298
|
-
function sha256Hex2(s) {
|
|
26299
|
-
return crypto5.createHash("sha256").update(s).digest("hex");
|
|
26300
|
-
}
|
|
26301
|
-
|
|
26302
|
-
// src/permission/cleanup.ts
|
|
26303
|
-
var fs16 = __toESM(require("fs"), 1);
|
|
26304
|
-
function cleanupGuestSessionsForCapability(cap, factory) {
|
|
26305
|
-
const removed = [];
|
|
26306
|
-
for (const g2 of cap.grants) {
|
|
26307
|
-
if (g2.resource.type !== "persona") continue;
|
|
26308
|
-
const dir = factory.vmGuestRoot(g2.resource.id, cap.personId);
|
|
26309
|
-
try {
|
|
26310
|
-
fs16.rmSync(dir, { recursive: true, force: true });
|
|
26311
|
-
removed.push(dir);
|
|
26312
|
-
} catch {
|
|
26313
|
-
}
|
|
26314
|
-
}
|
|
26315
|
-
return { removed };
|
|
26316
|
-
}
|
|
26317
|
-
|
|
26318
|
-
// src/inbox/inbox-store.ts
|
|
26319
|
-
var fs17 = __toESM(require("fs"), 1);
|
|
26320
|
-
var path19 = __toESM(require("path"), 1);
|
|
26321
|
-
var INBOX_SUBDIR = "inbox";
|
|
26322
|
-
var InboxStore = class {
|
|
26323
|
-
constructor(dataDir) {
|
|
26324
|
-
this.dataDir = dataDir;
|
|
26325
|
-
fs17.mkdirSync(this.dirPath(), { recursive: true });
|
|
25720
|
+
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
25721
|
+
filePath(scope, sessionId) {
|
|
25722
|
+
return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
26326
25723
|
}
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
* 列出某条 channel 的消息, 按 createdAt 升序.
|
|
26330
|
-
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
26331
|
-
* channel 文件不存在时返回 [] (不抛, 不创建).
|
|
26332
|
-
*/
|
|
26333
|
-
list(capabilityId, sinceCreatedAt) {
|
|
26334
|
-
const file = this.filePath(capabilityId);
|
|
26335
|
-
let raw;
|
|
26336
|
-
try {
|
|
26337
|
-
raw = fs17.readFileSync(file, "utf8");
|
|
26338
|
-
} catch (err) {
|
|
26339
|
-
if (err?.code === "ENOENT") return [];
|
|
26340
|
-
return [];
|
|
26341
|
-
}
|
|
26342
|
-
const all = parseAllLines(raw);
|
|
26343
|
-
if (sinceCreatedAt === void 0) return all;
|
|
26344
|
-
return all.filter((m2) => m2.createdAt > sinceCreatedAt);
|
|
25724
|
+
cacheKey(scope, sessionId) {
|
|
25725
|
+
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
26345
25726
|
}
|
|
26346
|
-
/**
|
|
26347
|
-
|
|
26348
|
-
|
|
26349
|
-
*/
|
|
26350
|
-
listAllCapabilityIds() {
|
|
26351
|
-
const dir = this.dirPath();
|
|
26352
|
-
let entries;
|
|
25727
|
+
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
25728
|
+
readFile(scope, sessionId) {
|
|
25729
|
+
const file = this.filePath(scope, sessionId);
|
|
26353
25730
|
try {
|
|
26354
|
-
|
|
25731
|
+
const raw = import_node_fs13.default.readFileSync(file, "utf8");
|
|
25732
|
+
const parsed = JSON.parse(raw);
|
|
25733
|
+
if (!Array.isArray(parsed)) {
|
|
25734
|
+
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
25735
|
+
file
|
|
25736
|
+
});
|
|
25737
|
+
return [];
|
|
25738
|
+
}
|
|
25739
|
+
const out = [];
|
|
25740
|
+
for (const entry of parsed) {
|
|
25741
|
+
const r = GroupFileEntrySchema.safeParse(entry);
|
|
25742
|
+
if (r.success) out.push(r.data);
|
|
25743
|
+
}
|
|
25744
|
+
return out;
|
|
26355
25745
|
} catch (err) {
|
|
26356
|
-
|
|
25746
|
+
const code = err?.code;
|
|
25747
|
+
if (code === "ENOENT") return [];
|
|
25748
|
+
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
25749
|
+
file,
|
|
25750
|
+
err: err.message
|
|
25751
|
+
});
|
|
26357
25752
|
return [];
|
|
26358
25753
|
}
|
|
26359
|
-
return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
|
|
26360
25754
|
}
|
|
26361
|
-
|
|
26362
|
-
const file = this.filePath(
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
|
|
26366
|
-
|
|
26367
|
-
} catch {
|
|
26368
|
-
}
|
|
25755
|
+
writeFile(scope, sessionId, entries) {
|
|
25756
|
+
const file = this.filePath(scope, sessionId);
|
|
25757
|
+
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
|
|
25758
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
25759
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
25760
|
+
import_node_fs13.default.renameSync(tmp, file);
|
|
26369
25761
|
}
|
|
26370
|
-
/**
|
|
26371
|
-
|
|
26372
|
-
|
|
26373
|
-
|
|
26374
|
-
|
|
26375
|
-
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
const next = messages.map((m2) => {
|
|
26379
|
-
if (m2.senderPrincipalId === principalId) return m2;
|
|
26380
|
-
if (m2.createdAt > upToCreatedAt) return m2;
|
|
26381
|
-
const cur = m2.readBy[principalId];
|
|
26382
|
-
if (cur !== void 0 && cur >= upToCreatedAt) return m2;
|
|
26383
|
-
changed++;
|
|
26384
|
-
return { ...m2, readBy: { ...m2.readBy, [principalId]: upToCreatedAt } };
|
|
26385
|
-
});
|
|
26386
|
-
if (changed === 0) return 0;
|
|
26387
|
-
this.rewriteChannel(capabilityId, next);
|
|
26388
|
-
return changed;
|
|
25762
|
+
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
25763
|
+
list(scope, sessionId) {
|
|
25764
|
+
const key = this.cacheKey(scope, sessionId);
|
|
25765
|
+
const cached = this.cache.get(key);
|
|
25766
|
+
if (cached) return cached.entries;
|
|
25767
|
+
const entries = this.readFile(scope, sessionId);
|
|
25768
|
+
this.cache.set(key, { entries, scope });
|
|
25769
|
+
return entries;
|
|
26389
25770
|
}
|
|
26390
25771
|
/**
|
|
26391
|
-
*
|
|
25772
|
+
* upsert:
|
|
25773
|
+
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
25774
|
+
* (保留首次入群人 / 入群时刻)
|
|
25775
|
+
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
25776
|
+
*
|
|
25777
|
+
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
26392
25778
|
*/
|
|
26393
|
-
|
|
26394
|
-
const
|
|
26395
|
-
|
|
26396
|
-
|
|
26397
|
-
|
|
26398
|
-
|
|
26399
|
-
|
|
26400
|
-
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
};
|
|
26419
|
-
|
|
26420
|
-
const out = [];
|
|
26421
|
-
for (const line of raw.split("\n")) {
|
|
26422
|
-
const trimmed = line.trim();
|
|
26423
|
-
if (!trimmed) continue;
|
|
26424
|
-
let parsed;
|
|
26425
|
-
try {
|
|
26426
|
-
parsed = JSON.parse(trimmed);
|
|
26427
|
-
} catch {
|
|
26428
|
-
continue;
|
|
25779
|
+
upsert(scope, sessionId, input, now = Date.now()) {
|
|
25780
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25781
|
+
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
25782
|
+
let next;
|
|
25783
|
+
if (idx >= 0) {
|
|
25784
|
+
const prev = entries[idx];
|
|
25785
|
+
next = {
|
|
25786
|
+
...prev,
|
|
25787
|
+
size: input.size,
|
|
25788
|
+
mime: input.mime,
|
|
25789
|
+
lastEditedAt: now,
|
|
25790
|
+
stale: false
|
|
25791
|
+
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
25792
|
+
};
|
|
25793
|
+
entries[idx] = next;
|
|
25794
|
+
} else {
|
|
25795
|
+
next = {
|
|
25796
|
+
id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
|
|
25797
|
+
relPath: input.relPath,
|
|
25798
|
+
from: input.from,
|
|
25799
|
+
label: input.label,
|
|
25800
|
+
size: input.size,
|
|
25801
|
+
mime: input.mime,
|
|
25802
|
+
addedAt: now
|
|
25803
|
+
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
25804
|
+
};
|
|
25805
|
+
entries.push(next);
|
|
26429
25806
|
}
|
|
26430
|
-
|
|
26431
|
-
|
|
26432
|
-
|
|
26433
|
-
return out;
|
|
26434
|
-
}
|
|
26435
|
-
|
|
26436
|
-
// src/inbox/inbox-manager.ts
|
|
26437
|
-
var crypto6 = __toESM(require("crypto"), 1);
|
|
26438
|
-
var InboxManager = class {
|
|
26439
|
-
constructor(store, broadcast, opts = {}) {
|
|
26440
|
-
this.store = store;
|
|
26441
|
-
this.broadcast = broadcast;
|
|
26442
|
-
this.now = opts.now ?? Date.now;
|
|
26443
|
-
this.genId = opts.genId ?? defaultGenId;
|
|
25807
|
+
this.writeFile(scope, sessionId, entries);
|
|
25808
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
25809
|
+
return next;
|
|
26444
25810
|
}
|
|
26445
|
-
store;
|
|
26446
|
-
broadcast;
|
|
26447
|
-
now;
|
|
26448
|
-
genId;
|
|
26449
25811
|
/**
|
|
26450
|
-
*
|
|
26451
|
-
*
|
|
25812
|
+
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
25813
|
+
* - 命中 → stale=true,UI 灰显
|
|
25814
|
+
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
25815
|
+
*
|
|
25816
|
+
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
26452
25817
|
*/
|
|
26453
|
-
|
|
26454
|
-
const
|
|
26455
|
-
|
|
26456
|
-
|
|
26457
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
};
|
|
26462
|
-
this.store.append(msg);
|
|
26463
|
-
this.broadcast(args.capabilityId, { type: "inbox:event", message: msg });
|
|
26464
|
-
return msg;
|
|
26465
|
-
}
|
|
26466
|
-
list(capabilityId, sinceCreatedAt) {
|
|
26467
|
-
return this.store.list(capabilityId, sinceCreatedAt);
|
|
25818
|
+
markStale(scope, sessionId, relPath) {
|
|
25819
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25820
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
25821
|
+
if (idx < 0) return;
|
|
25822
|
+
if (entries[idx].stale) return;
|
|
25823
|
+
entries[idx] = { ...entries[idx], stale: true };
|
|
25824
|
+
this.writeFile(scope, sessionId, entries);
|
|
25825
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26468
25826
|
}
|
|
26469
25827
|
/**
|
|
26470
|
-
*
|
|
26471
|
-
*
|
|
26472
|
-
*
|
|
25828
|
+
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
25829
|
+
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
25830
|
+
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
25831
|
+
*
|
|
25832
|
+
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
26473
25833
|
*/
|
|
26474
|
-
|
|
26475
|
-
const
|
|
26476
|
-
const
|
|
26477
|
-
if (
|
|
26478
|
-
|
|
26479
|
-
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
if (!prev) continue;
|
|
26483
|
-
if (prev.readBy[args.principalId] === m2.readBy[args.principalId]) continue;
|
|
26484
|
-
this.broadcast(args.capabilityId, { type: "inbox:event", message: m2 });
|
|
26485
|
-
}
|
|
26486
|
-
return changed;
|
|
26487
|
-
}
|
|
26488
|
-
/** capability:delete cascade. 删整个 channel 文件. */
|
|
26489
|
-
removeChannel(capabilityId) {
|
|
26490
|
-
this.store.removeByCapabilityId(capabilityId);
|
|
26491
|
-
}
|
|
26492
|
-
};
|
|
26493
|
-
function defaultGenId() {
|
|
26494
|
-
return "m_" + crypto6.randomBytes(6).toString("base64url");
|
|
26495
|
-
}
|
|
26496
|
-
|
|
26497
|
-
// src/remote-persona/store.ts
|
|
26498
|
-
var fs18 = __toESM(require("fs"), 1);
|
|
26499
|
-
var path20 = __toESM(require("path"), 1);
|
|
26500
|
-
var REMOTE_PERSONAS_DIR = "remote-personas";
|
|
26501
|
-
var RemotePersonaStore = class {
|
|
26502
|
-
constructor(dataDir) {
|
|
26503
|
-
this.dataDir = dataDir;
|
|
26504
|
-
fs18.mkdirSync(this.rootDir(), { recursive: true });
|
|
26505
|
-
}
|
|
26506
|
-
dataDir;
|
|
26507
|
-
list() {
|
|
26508
|
-
let entries;
|
|
26509
|
-
try {
|
|
26510
|
-
entries = fs18.readdirSync(this.rootDir());
|
|
26511
|
-
} catch (err) {
|
|
26512
|
-
if (err?.code === "ENOENT") return [];
|
|
26513
|
-
return [];
|
|
26514
|
-
}
|
|
26515
|
-
const out = [];
|
|
26516
|
-
for (const name of entries) {
|
|
26517
|
-
if (!name.endsWith(".json")) continue;
|
|
26518
|
-
if (name.includes(".tmp-")) continue;
|
|
26519
|
-
const file = path20.join(this.rootDir(), name);
|
|
26520
|
-
let raw;
|
|
26521
|
-
try {
|
|
26522
|
-
raw = fs18.readFileSync(file, "utf8");
|
|
26523
|
-
} catch {
|
|
26524
|
-
continue;
|
|
26525
|
-
}
|
|
26526
|
-
let parsed;
|
|
26527
|
-
try {
|
|
26528
|
-
parsed = JSON.parse(raw);
|
|
26529
|
-
} catch {
|
|
26530
|
-
continue;
|
|
26531
|
-
}
|
|
26532
|
-
const r = RemotePersonaSchema.safeParse(parsed);
|
|
26533
|
-
if (r.success) out.push(r.data);
|
|
26534
|
-
}
|
|
26535
|
-
return out.sort((a, b2) => a.addedAt > b2.addedAt ? -1 : a.addedAt < b2.addedAt ? 1 : 0);
|
|
26536
|
-
}
|
|
26537
|
-
get(alias) {
|
|
26538
|
-
const file = this.filePath(alias);
|
|
26539
|
-
let raw;
|
|
26540
|
-
try {
|
|
26541
|
-
raw = fs18.readFileSync(file, "utf8");
|
|
26542
|
-
} catch {
|
|
26543
|
-
return null;
|
|
26544
|
-
}
|
|
26545
|
-
try {
|
|
26546
|
-
const parsed = JSON.parse(raw);
|
|
26547
|
-
const r = RemotePersonaSchema.safeParse(parsed);
|
|
26548
|
-
return r.success ? r.data : null;
|
|
26549
|
-
} catch {
|
|
26550
|
-
return null;
|
|
26551
|
-
}
|
|
26552
|
-
}
|
|
26553
|
-
add(rp) {
|
|
26554
|
-
const file = this.filePath(rp.alias);
|
|
26555
|
-
if (fs18.existsSync(file)) {
|
|
26556
|
-
throw new Error(`RemotePersonaStore.add: alias already exists: ${rp.alias}`);
|
|
26557
|
-
}
|
|
26558
|
-
this.atomicWrite(file, rp);
|
|
26559
|
-
}
|
|
26560
|
-
remove(alias) {
|
|
26561
|
-
const file = this.filePath(alias);
|
|
26562
|
-
try {
|
|
26563
|
-
fs18.unlinkSync(file);
|
|
26564
|
-
return true;
|
|
26565
|
-
} catch (err) {
|
|
26566
|
-
if (err?.code === "ENOENT") return false;
|
|
26567
|
-
throw err;
|
|
26568
|
-
}
|
|
25834
|
+
remove(scope, sessionId, relPath) {
|
|
25835
|
+
const entries = this.list(scope, sessionId).slice();
|
|
25836
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
25837
|
+
if (idx < 0) return false;
|
|
25838
|
+
entries.splice(idx, 1);
|
|
25839
|
+
this.writeFile(scope, sessionId, entries);
|
|
25840
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
25841
|
+
return true;
|
|
26569
25842
|
}
|
|
26570
25843
|
/**
|
|
26571
|
-
*
|
|
26572
|
-
*
|
|
25844
|
+
* 跨 session 聚合查询(spec §4 HTTP ACL:personal 视野并集)。
|
|
25845
|
+
*
|
|
25846
|
+
* 扫 <dataDir>/sessions/<personaId>/owner/*.group-files.json 和
|
|
25847
|
+
* <dataDir>/sessions/<personaId>/listener/*.group-files.json,每文件读一份。
|
|
25848
|
+
*
|
|
25849
|
+
* 复杂度 O(N sessions × N entries),N 通常 < 100,可接受。
|
|
26573
25850
|
*/
|
|
26574
|
-
|
|
26575
|
-
const cur = this.get(alias);
|
|
26576
|
-
if (!cur) return;
|
|
26577
|
-
this.atomicWrite(this.filePath(alias), { ...cur, lastConnectedAt: at });
|
|
26578
|
-
}
|
|
26579
|
-
rootDir() {
|
|
26580
|
-
return path20.join(this.dataDir, REMOTE_PERSONAS_DIR);
|
|
26581
|
-
}
|
|
26582
|
-
filePath(alias) {
|
|
26583
|
-
return path20.join(this.rootDir(), `${safeFileName(alias)}.json`);
|
|
26584
|
-
}
|
|
26585
|
-
atomicWrite(file, content) {
|
|
26586
|
-
fs18.mkdirSync(this.rootDir(), { recursive: true });
|
|
26587
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26588
|
-
fs18.writeFileSync(tmp, JSON.stringify(content, null, 2), { mode: 384 });
|
|
26589
|
-
fs18.renameSync(tmp, file);
|
|
26590
|
-
try {
|
|
26591
|
-
fs18.chmodSync(file, 384);
|
|
26592
|
-
} catch {
|
|
26593
|
-
}
|
|
26594
|
-
}
|
|
26595
|
-
};
|
|
26596
|
-
|
|
26597
|
-
// src/person/store.ts
|
|
26598
|
-
var fs19 = __toESM(require("fs"), 1);
|
|
26599
|
-
var path21 = __toESM(require("path"), 1);
|
|
26600
|
-
var PERSONS_DIR = "persons";
|
|
26601
|
-
var PersonStore = class {
|
|
26602
|
-
constructor(dataDir) {
|
|
26603
|
-
this.dataDir = dataDir;
|
|
26604
|
-
fs19.mkdirSync(this.rootDir(), { recursive: true });
|
|
26605
|
-
}
|
|
26606
|
-
dataDir;
|
|
26607
|
-
list() {
|
|
26608
|
-
let entries;
|
|
26609
|
-
try {
|
|
26610
|
-
entries = fs19.readdirSync(this.rootDir());
|
|
26611
|
-
} catch (err) {
|
|
26612
|
-
if (err?.code === "ENOENT") return [];
|
|
26613
|
-
return [];
|
|
26614
|
-
}
|
|
25851
|
+
listByPersona(personaId) {
|
|
26615
25852
|
const out = [];
|
|
26616
|
-
for (const
|
|
26617
|
-
|
|
26618
|
-
|
|
26619
|
-
|
|
26620
|
-
let raw;
|
|
25853
|
+
for (const mode of ["owner", "listener"]) {
|
|
25854
|
+
const scope = { kind: "persona", personaId, mode };
|
|
25855
|
+
const root = this.rootForScope(scope);
|
|
25856
|
+
let names;
|
|
26621
25857
|
try {
|
|
26622
|
-
|
|
26623
|
-
} catch {
|
|
25858
|
+
names = import_node_fs13.default.readdirSync(root);
|
|
25859
|
+
} catch (err) {
|
|
25860
|
+
const code = err?.code;
|
|
25861
|
+
if (code === "ENOENT") continue;
|
|
26624
25862
|
continue;
|
|
26625
25863
|
}
|
|
26626
|
-
|
|
26627
|
-
|
|
26628
|
-
|
|
26629
|
-
|
|
26630
|
-
|
|
25864
|
+
for (const name of names) {
|
|
25865
|
+
if (!name.endsWith(".group-files.json")) continue;
|
|
25866
|
+
const sessionId = name.slice(0, -".group-files.json".length);
|
|
25867
|
+
if (!sessionId) continue;
|
|
25868
|
+
const entries = this.list(scope, sessionId);
|
|
25869
|
+
out.push({ sessionId, entries });
|
|
26631
25870
|
}
|
|
26632
|
-
const r = PersonSchema.safeParse(parsed);
|
|
26633
|
-
if (r.success) out.push(r.data);
|
|
26634
|
-
}
|
|
26635
|
-
return out.sort((a, b2) => a.updatedAt > b2.updatedAt ? -1 : a.updatedAt < b2.updatedAt ? 1 : 0);
|
|
26636
|
-
}
|
|
26637
|
-
get(id) {
|
|
26638
|
-
const file = this.filePath(id);
|
|
26639
|
-
let raw;
|
|
26640
|
-
try {
|
|
26641
|
-
raw = fs19.readFileSync(file, "utf8");
|
|
26642
|
-
} catch {
|
|
26643
|
-
return null;
|
|
26644
|
-
}
|
|
26645
|
-
try {
|
|
26646
|
-
const parsed = JSON.parse(raw);
|
|
26647
|
-
const r = PersonSchema.safeParse(parsed);
|
|
26648
|
-
return r.success ? r.data : null;
|
|
26649
|
-
} catch {
|
|
26650
|
-
return null;
|
|
26651
|
-
}
|
|
26652
|
-
}
|
|
26653
|
-
add(person) {
|
|
26654
|
-
const file = this.filePath(person.id);
|
|
26655
|
-
if (fs19.existsSync(file)) {
|
|
26656
|
-
throw new Error(`PersonStore.add: person id already exists: ${person.id}`);
|
|
26657
|
-
}
|
|
26658
|
-
this.atomicWrite(file, person);
|
|
26659
|
-
}
|
|
26660
|
-
/**
|
|
26661
|
-
* Merge patch into existing person + bump updatedAt to `now`.
|
|
26662
|
-
* 不存在 id → no-op(caller 应预先 get 校验)。
|
|
26663
|
-
* 只允许 patch displayName / notes / dmEnabled(id / createdAt 不可改)。
|
|
26664
|
-
*/
|
|
26665
|
-
update(id, patch, now) {
|
|
26666
|
-
const cur = this.get(id);
|
|
26667
|
-
if (!cur) return;
|
|
26668
|
-
const next = {
|
|
26669
|
-
...cur,
|
|
26670
|
-
...patch.displayName !== void 0 ? { displayName: patch.displayName } : {},
|
|
26671
|
-
...patch.notes !== void 0 ? { notes: patch.notes } : {},
|
|
26672
|
-
...patch.dmEnabled !== void 0 ? { dmEnabled: patch.dmEnabled } : {},
|
|
26673
|
-
updatedAt: now
|
|
26674
|
-
};
|
|
26675
|
-
this.atomicWrite(this.filePath(id), next);
|
|
26676
|
-
}
|
|
26677
|
-
remove(id) {
|
|
26678
|
-
const file = this.filePath(id);
|
|
26679
|
-
try {
|
|
26680
|
-
fs19.unlinkSync(file);
|
|
26681
|
-
return true;
|
|
26682
|
-
} catch (err) {
|
|
26683
|
-
if (err?.code === "ENOENT") return false;
|
|
26684
|
-
throw err;
|
|
26685
|
-
}
|
|
26686
|
-
}
|
|
26687
|
-
rootDir() {
|
|
26688
|
-
return path21.join(this.dataDir, PERSONS_DIR);
|
|
26689
|
-
}
|
|
26690
|
-
filePath(id) {
|
|
26691
|
-
return path21.join(this.rootDir(), `${safeFileName(id)}.json`);
|
|
26692
|
-
}
|
|
26693
|
-
atomicWrite(file, content) {
|
|
26694
|
-
fs19.mkdirSync(this.rootDir(), { recursive: true });
|
|
26695
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26696
|
-
fs19.writeFileSync(tmp, JSON.stringify(content, null, 2), { mode: 384 });
|
|
26697
|
-
fs19.renameSync(tmp, file);
|
|
26698
|
-
try {
|
|
26699
|
-
fs19.chmodSync(file, 384);
|
|
26700
|
-
} catch {
|
|
26701
25871
|
}
|
|
25872
|
+
return out;
|
|
26702
25873
|
}
|
|
26703
25874
|
};
|
|
26704
|
-
|
|
26705
|
-
|
|
26706
|
-
|
|
26707
|
-
var path22 = __toESM(require("path"), 1);
|
|
26708
|
-
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
26709
|
-
function migrateFlattenSessions(opts) {
|
|
26710
|
-
const dataDir = opts.dataDir;
|
|
26711
|
-
const now = opts.now ?? Date.now;
|
|
26712
|
-
const sessionsDir = path22.join(dataDir, "sessions");
|
|
26713
|
-
const flagPath = path22.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
26714
|
-
if (existsSync5(flagPath)) {
|
|
26715
|
-
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
26716
|
-
}
|
|
26717
|
-
let movedBare = 0;
|
|
26718
|
-
let movedVmOwner = 0;
|
|
26719
|
-
let archivedListener = 0;
|
|
26720
|
-
const defaultDir = path22.join(sessionsDir, "default");
|
|
26721
|
-
if (existsSync5(defaultDir)) {
|
|
26722
|
-
for (const entry of readdirSafe(defaultDir)) {
|
|
26723
|
-
if (!entry.endsWith(".json")) continue;
|
|
26724
|
-
const src = path22.join(defaultDir, entry);
|
|
26725
|
-
const dst = path22.join(sessionsDir, entry);
|
|
26726
|
-
fs20.renameSync(src, dst);
|
|
26727
|
-
movedBare += 1;
|
|
26728
|
-
}
|
|
26729
|
-
rmdirIfEmpty(defaultDir);
|
|
26730
|
-
}
|
|
26731
|
-
for (const pid of readdirSafe(sessionsDir)) {
|
|
26732
|
-
const personaDir = path22.join(sessionsDir, pid);
|
|
26733
|
-
if (!isDir(personaDir)) continue;
|
|
26734
|
-
if (pid === "default") continue;
|
|
26735
|
-
const ownerSrc = path22.join(personaDir, "owner");
|
|
26736
|
-
if (existsSync5(ownerSrc) && isDir(ownerSrc)) {
|
|
26737
|
-
const ownerDst = path22.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
26738
|
-
fs20.mkdirSync(ownerDst, { recursive: true });
|
|
26739
|
-
for (const file of readdirSafe(ownerSrc)) {
|
|
26740
|
-
if (!file.endsWith(".json")) continue;
|
|
26741
|
-
fs20.renameSync(path22.join(ownerSrc, file), path22.join(ownerDst, file));
|
|
26742
|
-
movedVmOwner += 1;
|
|
26743
|
-
}
|
|
26744
|
-
rmdirIfEmpty(ownerSrc);
|
|
26745
|
-
}
|
|
26746
|
-
const listenerSrc = path22.join(personaDir, "listener");
|
|
26747
|
-
if (existsSync5(listenerSrc) && isDir(listenerSrc)) {
|
|
26748
|
-
const archiveDst = path22.join(dataDir, ".legacy", `listener-${pid}`);
|
|
26749
|
-
fs20.mkdirSync(archiveDst, { recursive: true });
|
|
26750
|
-
for (const file of readdirSafe(listenerSrc)) {
|
|
26751
|
-
if (!file.endsWith(".json")) continue;
|
|
26752
|
-
fs20.renameSync(path22.join(listenerSrc, file), path22.join(archiveDst, file));
|
|
26753
|
-
archivedListener += 1;
|
|
26754
|
-
}
|
|
26755
|
-
rmdirIfEmpty(listenerSrc);
|
|
26756
|
-
}
|
|
26757
|
-
rmdirIfEmpty(personaDir);
|
|
26758
|
-
}
|
|
26759
|
-
fs20.mkdirSync(sessionsDir, { recursive: true });
|
|
26760
|
-
fs20.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
26761
|
-
return {
|
|
26762
|
-
skipped: false,
|
|
26763
|
-
flagWritten: true,
|
|
26764
|
-
movedBare,
|
|
26765
|
-
movedVmOwner,
|
|
26766
|
-
archivedListener
|
|
26767
|
-
};
|
|
26768
|
-
}
|
|
26769
|
-
function existsSync5(p2) {
|
|
26770
|
-
try {
|
|
26771
|
-
fs20.statSync(p2);
|
|
26772
|
-
return true;
|
|
26773
|
-
} catch {
|
|
26774
|
-
return false;
|
|
26775
|
-
}
|
|
26776
|
-
}
|
|
26777
|
-
function isDir(p2) {
|
|
26778
|
-
try {
|
|
26779
|
-
return fs20.statSync(p2).isDirectory();
|
|
26780
|
-
} catch {
|
|
25875
|
+
function personalViewable(groupStore, personaDir, personaId, absPath) {
|
|
25876
|
+
const realTarget = safeRealpath(absPath);
|
|
25877
|
+
if (!realTarget) {
|
|
26781
25878
|
return false;
|
|
26782
25879
|
}
|
|
26783
|
-
|
|
26784
|
-
|
|
26785
|
-
|
|
26786
|
-
|
|
26787
|
-
|
|
26788
|
-
|
|
25880
|
+
const personasUnion = groupStore.listByPersona(personaId);
|
|
25881
|
+
for (const { entries } of personasUnion) {
|
|
25882
|
+
for (const e of entries) {
|
|
25883
|
+
if (e.stale) continue;
|
|
25884
|
+
const realEntry = safeRealpath(import_node_path14.default.join(personaDir, e.relPath));
|
|
25885
|
+
if (realEntry && realEntry === realTarget) return true;
|
|
25886
|
+
}
|
|
26789
25887
|
}
|
|
25888
|
+
return false;
|
|
26790
25889
|
}
|
|
26791
|
-
function
|
|
25890
|
+
function safeRealpath(p2) {
|
|
26792
25891
|
try {
|
|
26793
|
-
|
|
25892
|
+
return import_node_fs13.default.realpathSync(p2);
|
|
26794
25893
|
} catch {
|
|
25894
|
+
return null;
|
|
26795
25895
|
}
|
|
26796
25896
|
}
|
|
26797
25897
|
|
|
26798
|
-
// src/transport/http-router.ts
|
|
26799
|
-
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26800
|
-
var import_node_path15 = __toESM(require("path"), 1);
|
|
26801
|
-
|
|
26802
25898
|
// src/attachment/mime.ts
|
|
26803
|
-
var
|
|
25899
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
26804
25900
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
26805
25901
|
var EXT_TO_NATIVE_MIME = {
|
|
26806
25902
|
// 图片
|
|
@@ -26907,14 +26003,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
26907
26003
|
".mk"
|
|
26908
26004
|
]);
|
|
26909
26005
|
function lookupMime(filePathOrName) {
|
|
26910
|
-
const ext =
|
|
26006
|
+
const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
|
|
26911
26007
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
26912
26008
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
26913
26009
|
return "application/octet-stream";
|
|
26914
26010
|
}
|
|
26915
26011
|
|
|
26916
26012
|
// src/attachment/sign-url.ts
|
|
26917
|
-
var
|
|
26013
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
26918
26014
|
var HMAC_ALGO = "sha256";
|
|
26919
26015
|
function base64urlEncode(buf) {
|
|
26920
26016
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -26931,7 +26027,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
26931
26027
|
}
|
|
26932
26028
|
function computeSig(secret, absPath, e) {
|
|
26933
26029
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
26934
|
-
return
|
|
26030
|
+
return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
26935
26031
|
}
|
|
26936
26032
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
26937
26033
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -26966,7 +26062,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
26966
26062
|
if (provided.length !== expected.length) {
|
|
26967
26063
|
return { ok: false, code: "BAD_SIG" };
|
|
26968
26064
|
}
|
|
26969
|
-
if (!
|
|
26065
|
+
if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
|
|
26970
26066
|
return { ok: false, code: "BAD_SIG" };
|
|
26971
26067
|
}
|
|
26972
26068
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -26987,7 +26083,7 @@ function createHttpRouter(deps) {
|
|
|
26987
26083
|
sendJson(res, 200, { ok: true, version: deps.daemonVersion });
|
|
26988
26084
|
return true;
|
|
26989
26085
|
}
|
|
26990
|
-
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
|
|
26086
|
+
if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/")) {
|
|
26991
26087
|
return false;
|
|
26992
26088
|
}
|
|
26993
26089
|
if (url.pathname.startsWith("/files/") && req.method === "GET") {
|
|
@@ -27027,8 +26123,43 @@ function createHttpRouter(deps) {
|
|
|
27027
26123
|
sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
|
|
27028
26124
|
return true;
|
|
27029
26125
|
}
|
|
26126
|
+
const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
|
|
26127
|
+
if (personaFilesMatch && req.method === "GET") {
|
|
26128
|
+
const pid = personaFilesMatch[1];
|
|
26129
|
+
const pathParam = url.searchParams.get("path");
|
|
26130
|
+
if (!pathParam) {
|
|
26131
|
+
sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26132
|
+
return true;
|
|
26133
|
+
}
|
|
26134
|
+
if (!deps.personaStore || !deps.groupFileStore) {
|
|
26135
|
+
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
|
|
26136
|
+
return true;
|
|
26137
|
+
}
|
|
26138
|
+
const personaDir = deps.personaStore.personaDirPath(pid);
|
|
26139
|
+
const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
|
|
26140
|
+
if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
|
|
26141
|
+
sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
|
|
26142
|
+
return true;
|
|
26143
|
+
}
|
|
26144
|
+
if (ctx.role === "personal") {
|
|
26145
|
+
if (ctx.personaId !== pid) {
|
|
26146
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
|
|
26147
|
+
return true;
|
|
26148
|
+
}
|
|
26149
|
+
if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
|
|
26150
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
|
|
26151
|
+
return true;
|
|
26152
|
+
}
|
|
26153
|
+
}
|
|
26154
|
+
streamFile(res, absPath, deps.logger);
|
|
26155
|
+
return true;
|
|
26156
|
+
}
|
|
27030
26157
|
const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
|
|
27031
26158
|
if (sessionFilesMatch && req.method === "GET") {
|
|
26159
|
+
if (ctx.role !== "owner") {
|
|
26160
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
|
|
26161
|
+
return true;
|
|
26162
|
+
}
|
|
27032
26163
|
const sid = sessionFilesMatch[1];
|
|
27033
26164
|
const pathParam = url.searchParams.get("path");
|
|
27034
26165
|
if (!pathParam) {
|
|
@@ -27036,7 +26167,7 @@ function createHttpRouter(deps) {
|
|
|
27036
26167
|
return true;
|
|
27037
26168
|
}
|
|
27038
26169
|
let absPath;
|
|
27039
|
-
if (
|
|
26170
|
+
if (import_node_path16.default.isAbsolute(pathParam)) {
|
|
27040
26171
|
absPath = pathParam;
|
|
27041
26172
|
} else if (deps.sessionStore) {
|
|
27042
26173
|
const file = deps.sessionStore.read(sid);
|
|
@@ -27044,7 +26175,7 @@ function createHttpRouter(deps) {
|
|
|
27044
26175
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
27045
26176
|
return true;
|
|
27046
26177
|
}
|
|
27047
|
-
absPath =
|
|
26178
|
+
absPath = import_node_path16.default.join(file.cwd, pathParam);
|
|
27048
26179
|
} else {
|
|
27049
26180
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
27050
26181
|
return true;
|
|
@@ -27069,12 +26200,18 @@ function sendJson(res, status, body) {
|
|
|
27069
26200
|
res.end(JSON.stringify(body));
|
|
27070
26201
|
}
|
|
27071
26202
|
function withCtx(ctx, body) {
|
|
27072
|
-
return { ...body, role: ctx.role };
|
|
26203
|
+
return { ...body, role: ctx.role, personaId: ctx.personaId };
|
|
26204
|
+
}
|
|
26205
|
+
function isContainedIn(abs, root) {
|
|
26206
|
+
const normalized = import_node_path16.default.resolve(abs);
|
|
26207
|
+
const normalizedRoot = import_node_path16.default.resolve(root);
|
|
26208
|
+
if (normalized === normalizedRoot) return true;
|
|
26209
|
+
return normalized.startsWith(normalizedRoot + import_node_path16.default.sep);
|
|
27073
26210
|
}
|
|
27074
26211
|
function streamFile(res, absPath, logger) {
|
|
27075
26212
|
let stat;
|
|
27076
26213
|
try {
|
|
27077
|
-
stat =
|
|
26214
|
+
stat = import_node_fs14.default.statSync(absPath);
|
|
27078
26215
|
} catch (err) {
|
|
27079
26216
|
const code = err?.code;
|
|
27080
26217
|
if (code === "ENOENT") {
|
|
@@ -27089,7 +26226,7 @@ function streamFile(res, absPath, logger) {
|
|
|
27089
26226
|
return;
|
|
27090
26227
|
}
|
|
27091
26228
|
const mime = lookupMime(absPath);
|
|
27092
|
-
const basename =
|
|
26229
|
+
const basename = import_node_path16.default.basename(absPath);
|
|
27093
26230
|
res.writeHead(200, {
|
|
27094
26231
|
"Content-Type": mime,
|
|
27095
26232
|
"Content-Length": String(stat.size),
|
|
@@ -27097,154 +26234,13 @@ function streamFile(res, absPath, logger) {
|
|
|
27097
26234
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
27098
26235
|
"X-Content-Type-Options": "nosniff"
|
|
27099
26236
|
});
|
|
27100
|
-
const stream =
|
|
26237
|
+
const stream = import_node_fs14.default.createReadStream(absPath);
|
|
27101
26238
|
stream.on("error", (err) => {
|
|
27102
|
-
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
27103
|
-
res.destroy();
|
|
27104
|
-
});
|
|
27105
|
-
stream.pipe(res);
|
|
27106
|
-
}
|
|
27107
|
-
|
|
27108
|
-
// src/attachment/group.ts
|
|
27109
|
-
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
27110
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
27111
|
-
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27112
|
-
init_protocol();
|
|
27113
|
-
var GroupFileStore = class {
|
|
27114
|
-
dataDir;
|
|
27115
|
-
logger;
|
|
27116
|
-
cache = /* @__PURE__ */ new Map();
|
|
27117
|
-
constructor(opts) {
|
|
27118
|
-
this.dataDir = opts.dataDir;
|
|
27119
|
-
this.logger = opts.logger;
|
|
27120
|
-
}
|
|
27121
|
-
rootForScope(scope) {
|
|
27122
|
-
return import_node_path16.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
27123
|
-
}
|
|
27124
|
-
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
27125
|
-
filePath(scope, sessionId) {
|
|
27126
|
-
return import_node_path16.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
27127
|
-
}
|
|
27128
|
-
cacheKey(scope, sessionId) {
|
|
27129
|
-
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
27130
|
-
}
|
|
27131
|
-
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
27132
|
-
readFile(scope, sessionId) {
|
|
27133
|
-
const file = this.filePath(scope, sessionId);
|
|
27134
|
-
try {
|
|
27135
|
-
const raw = import_node_fs14.default.readFileSync(file, "utf8");
|
|
27136
|
-
const parsed = JSON.parse(raw);
|
|
27137
|
-
if (!Array.isArray(parsed)) {
|
|
27138
|
-
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
27139
|
-
file
|
|
27140
|
-
});
|
|
27141
|
-
return [];
|
|
27142
|
-
}
|
|
27143
|
-
const out = [];
|
|
27144
|
-
for (const entry of parsed) {
|
|
27145
|
-
const r = GroupFileEntrySchema.safeParse(entry);
|
|
27146
|
-
if (r.success) out.push(r.data);
|
|
27147
|
-
}
|
|
27148
|
-
return out;
|
|
27149
|
-
} catch (err) {
|
|
27150
|
-
const code = err?.code;
|
|
27151
|
-
if (code === "ENOENT") return [];
|
|
27152
|
-
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
27153
|
-
file,
|
|
27154
|
-
err: err.message
|
|
27155
|
-
});
|
|
27156
|
-
return [];
|
|
27157
|
-
}
|
|
27158
|
-
}
|
|
27159
|
-
writeFile(scope, sessionId, entries) {
|
|
27160
|
-
const file = this.filePath(scope, sessionId);
|
|
27161
|
-
import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
27162
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
27163
|
-
import_node_fs14.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
27164
|
-
import_node_fs14.default.renameSync(tmp, file);
|
|
27165
|
-
}
|
|
27166
|
-
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
27167
|
-
list(scope, sessionId) {
|
|
27168
|
-
const key = this.cacheKey(scope, sessionId);
|
|
27169
|
-
const cached = this.cache.get(key);
|
|
27170
|
-
if (cached) return cached.entries;
|
|
27171
|
-
const entries = this.readFile(scope, sessionId);
|
|
27172
|
-
this.cache.set(key, { entries });
|
|
27173
|
-
return entries;
|
|
27174
|
-
}
|
|
27175
|
-
/**
|
|
27176
|
-
* upsert:
|
|
27177
|
-
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
27178
|
-
* (保留首次入群人 / 入群时刻)
|
|
27179
|
-
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
27180
|
-
*
|
|
27181
|
-
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
27182
|
-
*/
|
|
27183
|
-
upsert(scope, sessionId, input, now = Date.now()) {
|
|
27184
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27185
|
-
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
27186
|
-
let next;
|
|
27187
|
-
if (idx >= 0) {
|
|
27188
|
-
const prev = entries[idx];
|
|
27189
|
-
next = {
|
|
27190
|
-
...prev,
|
|
27191
|
-
size: input.size,
|
|
27192
|
-
mime: input.mime,
|
|
27193
|
-
lastEditedAt: now,
|
|
27194
|
-
stale: false
|
|
27195
|
-
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
27196
|
-
};
|
|
27197
|
-
entries[idx] = next;
|
|
27198
|
-
} else {
|
|
27199
|
-
next = {
|
|
27200
|
-
id: `gf-${import_node_crypto5.default.randomBytes(6).toString("base64url")}`,
|
|
27201
|
-
relPath: input.relPath,
|
|
27202
|
-
from: input.from,
|
|
27203
|
-
label: input.label,
|
|
27204
|
-
size: input.size,
|
|
27205
|
-
mime: input.mime,
|
|
27206
|
-
addedAt: now
|
|
27207
|
-
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
27208
|
-
};
|
|
27209
|
-
entries.push(next);
|
|
27210
|
-
}
|
|
27211
|
-
this.writeFile(scope, sessionId, entries);
|
|
27212
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27213
|
-
return next;
|
|
27214
|
-
}
|
|
27215
|
-
/**
|
|
27216
|
-
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
27217
|
-
* - 命中 → stale=true,UI 灰显
|
|
27218
|
-
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
27219
|
-
*
|
|
27220
|
-
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
27221
|
-
*/
|
|
27222
|
-
markStale(scope, sessionId, relPath) {
|
|
27223
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27224
|
-
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27225
|
-
if (idx < 0) return;
|
|
27226
|
-
if (entries[idx].stale) return;
|
|
27227
|
-
entries[idx] = { ...entries[idx], stale: true };
|
|
27228
|
-
this.writeFile(scope, sessionId, entries);
|
|
27229
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27230
|
-
}
|
|
27231
|
-
/**
|
|
27232
|
-
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
27233
|
-
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
27234
|
-
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
27235
|
-
*
|
|
27236
|
-
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
27237
|
-
*/
|
|
27238
|
-
remove(scope, sessionId, relPath) {
|
|
27239
|
-
const entries = this.list(scope, sessionId).slice();
|
|
27240
|
-
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
27241
|
-
if (idx < 0) return false;
|
|
27242
|
-
entries.splice(idx, 1);
|
|
27243
|
-
this.writeFile(scope, sessionId, entries);
|
|
27244
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
27245
|
-
return true;
|
|
27246
|
-
}
|
|
27247
|
-
};
|
|
26239
|
+
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
26240
|
+
res.destroy();
|
|
26241
|
+
});
|
|
26242
|
+
stream.pipe(res);
|
|
26243
|
+
}
|
|
27248
26244
|
|
|
27249
26245
|
// src/discovery/state-file.ts
|
|
27250
26246
|
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
@@ -27922,44 +26918,39 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
27922
26918
|
function authFilePath(dataDir) {
|
|
27923
26919
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
27924
26920
|
}
|
|
27925
|
-
function
|
|
26921
|
+
function loadOrCreateAuthFile(opts) {
|
|
27926
26922
|
const file = authFilePath(opts.dataDir);
|
|
26923
|
+
const generate = opts.generate ?? defaultGenerate;
|
|
26924
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
27927
26925
|
const existing = readAuthFile(file);
|
|
27928
|
-
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
|
|
27933
|
-
|
|
27934
|
-
writeAuthFile(file, {
|
|
27935
|
-
token: existing.token,
|
|
27936
|
-
ownerPrincipalId: ownerPrincipalId2,
|
|
27937
|
-
createdAt: existing.createdAt
|
|
27938
|
-
});
|
|
27939
|
-
return { token: existing.token, ownerPrincipalId: ownerPrincipalId2 };
|
|
27940
|
-
}
|
|
27941
|
-
return { token: existing.token, ownerPrincipalId: existing.ownerPrincipalId };
|
|
26926
|
+
if (existing && existing.token && existing.signSecret) {
|
|
26927
|
+
return {
|
|
26928
|
+
token: existing.token,
|
|
26929
|
+
signSecret: existing.signSecret,
|
|
26930
|
+
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
26931
|
+
};
|
|
27942
26932
|
}
|
|
27943
|
-
const token =
|
|
27944
|
-
const
|
|
27945
|
-
|
|
27946
|
-
|
|
26933
|
+
const token = existing?.token || generate();
|
|
26934
|
+
const signSecret = existing?.signSecret || generate();
|
|
26935
|
+
const createdAt = existing?.createdAt || now().toISOString();
|
|
26936
|
+
const next = { token, signSecret, createdAt };
|
|
26937
|
+
writeAuthFile(file, next);
|
|
26938
|
+
return next;
|
|
27947
26939
|
}
|
|
27948
|
-
function
|
|
26940
|
+
function defaultGenerate() {
|
|
27949
26941
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
27950
26942
|
}
|
|
27951
|
-
function defaultGenerateOwnerPrincipalId() {
|
|
27952
|
-
return `owner-${import_node_crypto8.default.randomUUID()}`;
|
|
27953
|
-
}
|
|
27954
26943
|
function readAuthFile(file) {
|
|
27955
26944
|
try {
|
|
27956
26945
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27957
26946
|
const parsed = JSON.parse(raw);
|
|
27958
|
-
if (typeof parsed?.token !== "string" || parsed.token.length === 0)
|
|
26947
|
+
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
26948
|
+
return null;
|
|
26949
|
+
}
|
|
27959
26950
|
return {
|
|
27960
26951
|
token: parsed.token,
|
|
27961
|
-
|
|
27962
|
-
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt :
|
|
26952
|
+
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
26953
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
27963
26954
|
};
|
|
27964
26955
|
} catch (err) {
|
|
27965
26956
|
const code = err?.code;
|
|
@@ -28072,62 +27063,10 @@ function forkSession(input) {
|
|
|
28072
27063
|
return { forkedToolSessionId, forkedFilePath };
|
|
28073
27064
|
}
|
|
28074
27065
|
|
|
28075
|
-
// src/permission/capability.ts
|
|
28076
|
-
function matchResource(grant, target) {
|
|
28077
|
-
if (grant.type === "*") return true;
|
|
28078
|
-
if (grant.type !== target.type) return false;
|
|
28079
|
-
return grant.id === target.id;
|
|
28080
|
-
}
|
|
28081
|
-
function assertGrant(grants, resource, action) {
|
|
28082
|
-
for (const g2 of grants) {
|
|
28083
|
-
if (!matchResource(g2.resource, resource)) continue;
|
|
28084
|
-
if (g2.actions.includes(action)) return true;
|
|
28085
|
-
if (g2.actions.includes("admin")) return true;
|
|
28086
|
-
}
|
|
28087
|
-
return false;
|
|
28088
|
-
}
|
|
28089
|
-
|
|
28090
|
-
// src/permission/session-access.ts
|
|
28091
|
-
function canAccessSession(ctx, sessionId, action, deps) {
|
|
28092
|
-
if (ctx.principal.kind === "owner") return true;
|
|
28093
|
-
const file = deps.readSession(sessionId);
|
|
28094
|
-
if (!file) return true;
|
|
28095
|
-
return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
|
|
28096
|
-
}
|
|
28097
|
-
function canAccessPersona(grants, personaId, action) {
|
|
28098
|
-
if (!personaId) return false;
|
|
28099
|
-
const resource = { type: "persona", id: personaId };
|
|
28100
|
-
return assertGrant(grants, resource, action);
|
|
28101
|
-
}
|
|
28102
|
-
|
|
28103
27066
|
// src/handlers/session.ts
|
|
28104
|
-
init_protocol();
|
|
28105
27067
|
function buildSessionHandlers(deps) {
|
|
28106
|
-
const { manager, observer, getAdapter: getAdapter2
|
|
28107
|
-
const
|
|
28108
|
-
if (!ctx) return;
|
|
28109
|
-
const ok = canAccessSession(ctx, sessionId, action, {
|
|
28110
|
-
readSession: (sid) => store.read(sid)
|
|
28111
|
-
});
|
|
28112
|
-
if (!ok) {
|
|
28113
|
-
throw new ClawdError(
|
|
28114
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28115
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} session ${sessionId}`
|
|
28116
|
-
);
|
|
28117
|
-
}
|
|
28118
|
-
};
|
|
28119
|
-
const ensurePersonaAccess = (ctx, personaId, action) => {
|
|
28120
|
-
if (!ctx) return;
|
|
28121
|
-
if (ctx.principal.kind === "owner") return;
|
|
28122
|
-
const ok = canAccessPersona(ctx.grants, personaId, action);
|
|
28123
|
-
if (!ok) {
|
|
28124
|
-
throw new ClawdError(
|
|
28125
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28126
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} on persona:${personaId ?? "<none>"}`
|
|
28127
|
-
);
|
|
28128
|
-
}
|
|
28129
|
-
};
|
|
28130
|
-
const create = async (frame, _client, ctx) => {
|
|
27068
|
+
const { manager, observer, getAdapter: getAdapter2 } = deps;
|
|
27069
|
+
const create = async (frame) => {
|
|
28131
27070
|
const args = SessionCreateArgs.parse(frame);
|
|
28132
27071
|
if (args.ownerPersonaId) {
|
|
28133
27072
|
const persona = deps.personaRegistry.get(args.ownerPersonaId);
|
|
@@ -28135,84 +27074,58 @@ function buildSessionHandlers(deps) {
|
|
|
28135
27074
|
throw new Error(`persona not found: ${args.ownerPersonaId}`);
|
|
28136
27075
|
}
|
|
28137
27076
|
}
|
|
28138
|
-
|
|
28139
|
-
let creatorPrincipalId = "";
|
|
28140
|
-
if (ctx) {
|
|
28141
|
-
if (ctx.principal.kind === "guest" && ctx.capabilityId && deps.capabilityManager) {
|
|
28142
|
-
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
28143
|
-
creatorPrincipalId = cap?.personId ?? ctx.principal.id;
|
|
28144
|
-
} else {
|
|
28145
|
-
creatorPrincipalId = ctx.principal.id;
|
|
28146
|
-
}
|
|
28147
|
-
}
|
|
28148
|
-
const { response, broadcast } = manager.create(args, creatorPrincipalId);
|
|
27077
|
+
const { response, broadcast } = manager.create(args);
|
|
28149
27078
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28150
27079
|
};
|
|
28151
|
-
const list = async (
|
|
27080
|
+
const list = async () => {
|
|
28152
27081
|
const { response } = manager.list();
|
|
28153
|
-
if (ctx && ctx.principal.kind === "guest") {
|
|
28154
|
-
const sessions = response.sessions ?? [];
|
|
28155
|
-
const filtered = sessions.filter(
|
|
28156
|
-
(s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")
|
|
28157
|
-
);
|
|
28158
|
-
return { response: { type: "session:list", sessions: filtered } };
|
|
28159
|
-
}
|
|
28160
27082
|
return { response: { type: "session:list", ...response } };
|
|
28161
27083
|
};
|
|
28162
|
-
const get = async (frame
|
|
27084
|
+
const get = async (frame) => {
|
|
28163
27085
|
const args = SessionIdArgs.parse(frame);
|
|
28164
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28165
27086
|
const { response } = manager.get(args);
|
|
28166
27087
|
return { response: { type: "session:info", ...response } };
|
|
28167
27088
|
};
|
|
28168
|
-
const update = async (frame
|
|
27089
|
+
const update = async (frame) => {
|
|
28169
27090
|
const args = SessionUpdateArgs.parse(frame);
|
|
28170
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28171
27091
|
const { response, broadcast } = manager.update(args);
|
|
28172
27092
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28173
27093
|
};
|
|
28174
|
-
const del = async (frame
|
|
27094
|
+
const del = async (frame) => {
|
|
28175
27095
|
const args = SessionIdArgs.parse(frame);
|
|
28176
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28177
27096
|
const { broadcast } = manager.delete(args);
|
|
28178
27097
|
return {
|
|
28179
27098
|
response: { type: "session:deleted", sessionId: args.sessionId },
|
|
28180
27099
|
broadcast
|
|
28181
27100
|
};
|
|
28182
27101
|
};
|
|
28183
|
-
const send = async (frame
|
|
27102
|
+
const send = async (frame) => {
|
|
28184
27103
|
const args = SessionSendArgs.parse(frame);
|
|
28185
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28186
27104
|
const { broadcast } = manager.send(args);
|
|
28187
27105
|
return { response: { type: "session:send", ok: true }, broadcast };
|
|
28188
27106
|
};
|
|
28189
|
-
const stop = async (frame
|
|
27107
|
+
const stop = async (frame) => {
|
|
28190
27108
|
const args = SessionIdArgs.parse(frame);
|
|
28191
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28192
27109
|
const { broadcast } = await manager.stop(args);
|
|
28193
27110
|
return { response: { type: "session:stop", ok: true }, broadcast };
|
|
28194
27111
|
};
|
|
28195
|
-
const interrupt = async (frame
|
|
27112
|
+
const interrupt = async (frame) => {
|
|
28196
27113
|
const args = SessionIdArgs.parse(frame);
|
|
28197
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28198
27114
|
const { broadcast } = await manager.interrupt(args);
|
|
28199
27115
|
return { response: { type: "session:interrupt", ok: true }, broadcast };
|
|
28200
27116
|
};
|
|
28201
|
-
const rewind = async (frame
|
|
27117
|
+
const rewind = async (frame) => {
|
|
28202
27118
|
const args = SessionRewindArgs.parse(frame);
|
|
28203
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28204
27119
|
const { response, broadcast } = await manager.rewind(args);
|
|
28205
27120
|
return { response: { type: "session:rewind", ...response }, broadcast };
|
|
28206
27121
|
};
|
|
28207
|
-
const rewindDiff = async (frame
|
|
27122
|
+
const rewindDiff = async (frame) => {
|
|
28208
27123
|
const args = SessionRewindDiffArgs.parse(frame);
|
|
28209
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28210
27124
|
const { response } = await manager.rewindDiff(args);
|
|
28211
27125
|
return { response: { type: "session:rewind-diff", ...response } };
|
|
28212
27126
|
};
|
|
28213
|
-
const rewindableMessageIds = async (frame
|
|
27127
|
+
const rewindableMessageIds = async (frame) => {
|
|
28214
27128
|
const args = SessionRewindableMessageIdsArgs.parse(frame);
|
|
28215
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28216
27129
|
const { response } = manager.rewindableMessageIds(args);
|
|
28217
27130
|
return {
|
|
28218
27131
|
response: { type: "session:rewindable-message-ids", ...response }
|
|
@@ -28223,22 +27136,19 @@ function buildSessionHandlers(deps) {
|
|
|
28223
27136
|
const result = forkSession(args);
|
|
28224
27137
|
return { response: { type: "session:fork", ...result } };
|
|
28225
27138
|
};
|
|
28226
|
-
const newSession = async (frame
|
|
27139
|
+
const newSession = async (frame) => {
|
|
28227
27140
|
const args = SessionIdArgs.parse(frame);
|
|
28228
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28229
27141
|
observer.stop(args.sessionId);
|
|
28230
27142
|
const { response, broadcast } = manager.newSession(args);
|
|
28231
27143
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28232
27144
|
};
|
|
28233
|
-
const resume = async (frame
|
|
27145
|
+
const resume = async (frame) => {
|
|
28234
27146
|
const args = SessionResumeArgs.parse(frame);
|
|
28235
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28236
27147
|
const { response, broadcast } = manager.resume(args);
|
|
28237
27148
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28238
27149
|
};
|
|
28239
|
-
const observe = async (frame
|
|
27150
|
+
const observe = async (frame) => {
|
|
28240
27151
|
const args = SessionObserveArgs.parse(frame);
|
|
28241
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28242
27152
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
28243
27153
|
const sessionFile = file;
|
|
28244
27154
|
manager.ensureSession(sessionFile);
|
|
@@ -28252,17 +27162,14 @@ function buildSessionHandlers(deps) {
|
|
|
28252
27162
|
});
|
|
28253
27163
|
return { response: { type: "session:observe", ok: true } };
|
|
28254
27164
|
};
|
|
28255
|
-
const events = async (frame
|
|
27165
|
+
const events = async (frame) => {
|
|
28256
27166
|
const args = SessionEventsArgs.parse(frame);
|
|
28257
|
-
ensureSessionAccess(ctx, args.sessionId, "read");
|
|
28258
27167
|
const { response } = manager.getEvents(args);
|
|
28259
27168
|
return { response: { type: "session:events", ...response } };
|
|
28260
27169
|
};
|
|
28261
|
-
const subscribe = async (frame, client
|
|
28262
|
-
if (typeof frame.sessionId === "string")
|
|
28263
|
-
ensureSessionAccess(ctx, frame.sessionId, "read");
|
|
27170
|
+
const subscribe = async (frame, client) => {
|
|
27171
|
+
if (typeof frame.sessionId === "string")
|
|
28264
27172
|
addSubscription(client, frame.sessionId);
|
|
28265
|
-
}
|
|
28266
27173
|
return {
|
|
28267
27174
|
response: { type: "subscribed", sessionId: frame.sessionId }
|
|
28268
27175
|
};
|
|
@@ -28274,9 +27181,8 @@ function buildSessionHandlers(deps) {
|
|
|
28274
27181
|
response: { type: "unsubscribed", sessionId: frame.sessionId }
|
|
28275
27182
|
};
|
|
28276
27183
|
};
|
|
28277
|
-
const pin = async (frame
|
|
27184
|
+
const pin = async (frame) => {
|
|
28278
27185
|
const args = SessionPinArgs.parse(frame);
|
|
28279
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28280
27186
|
const { response, broadcast } = manager.pin(args);
|
|
28281
27187
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
28282
27188
|
};
|
|
@@ -28285,15 +27191,13 @@ function buildSessionHandlers(deps) {
|
|
|
28285
27191
|
const { response, broadcast } = manager.reorderPins(args);
|
|
28286
27192
|
return { response: { type: "session:reorderPins", ...response }, broadcast };
|
|
28287
27193
|
};
|
|
28288
|
-
const answerQuestion = async (frame
|
|
27194
|
+
const answerQuestion = async (frame) => {
|
|
28289
27195
|
const args = AnswerQuestionArgs.parse(frame);
|
|
28290
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28291
27196
|
const { response, broadcast } = manager.answerQuestion(args);
|
|
28292
27197
|
return { response: { type: "session:answerQuestion", ...response }, broadcast };
|
|
28293
27198
|
};
|
|
28294
|
-
const cancelQuestion = async (frame
|
|
27199
|
+
const cancelQuestion = async (frame) => {
|
|
28295
27200
|
const args = CancelQuestionArgs.parse(frame);
|
|
28296
|
-
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
28297
27201
|
const { response, broadcast } = await manager.cancelQuestion(args);
|
|
28298
27202
|
return { response: { type: "session:cancelQuestion", ...response }, broadcast };
|
|
28299
27203
|
};
|
|
@@ -28591,282 +27495,6 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
28591
27495
|
};
|
|
28592
27496
|
}
|
|
28593
27497
|
|
|
28594
|
-
// src/handlers/capability.ts
|
|
28595
|
-
init_zod();
|
|
28596
|
-
init_protocol();
|
|
28597
|
-
var DeleteArgsSchema = external_exports.object({
|
|
28598
|
-
capabilityId: external_exports.string().min(1)
|
|
28599
|
-
}).strict();
|
|
28600
|
-
function buildCapabilityHandlers(deps) {
|
|
28601
|
-
const { manager, getShareBaseUrl, personStore } = deps;
|
|
28602
|
-
const issue = async (frame) => {
|
|
28603
|
-
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28604
|
-
const args = CapabilityIssueArgsSchema.parse(rest);
|
|
28605
|
-
if (!personStore.get(args.personId)) {
|
|
28606
|
-
throw new ClawdError(
|
|
28607
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28608
|
-
`Person not found: ${args.personId}`
|
|
28609
|
-
);
|
|
28610
|
-
}
|
|
28611
|
-
const existing = manager.findByPerson(args.personId);
|
|
28612
|
-
if (existing) {
|
|
28613
|
-
const updated = manager.addGrantsToPerson(args.personId, args.grants);
|
|
28614
|
-
if (!updated) {
|
|
28615
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "addGrantsToPerson returned null");
|
|
28616
|
-
}
|
|
28617
|
-
return {
|
|
28618
|
-
response: {
|
|
28619
|
-
type: "capability:issued",
|
|
28620
|
-
token: "__merged__",
|
|
28621
|
-
shareUrl: "",
|
|
28622
|
-
capability: stripSecretHash(updated)
|
|
28623
|
-
}
|
|
28624
|
-
};
|
|
28625
|
-
}
|
|
28626
|
-
const { personId, ...managerArgs } = args;
|
|
28627
|
-
const { token, capability } = manager.issue({
|
|
28628
|
-
...managerArgs,
|
|
28629
|
-
personId
|
|
28630
|
-
});
|
|
28631
|
-
const base = getShareBaseUrl().replace(/\/$/, "");
|
|
28632
|
-
const shareUrl = `${base}/?token=${encodeURIComponent(token)}`;
|
|
28633
|
-
return {
|
|
28634
|
-
response: {
|
|
28635
|
-
type: "capability:issued",
|
|
28636
|
-
token,
|
|
28637
|
-
shareUrl,
|
|
28638
|
-
capability: stripSecretHash(capability)
|
|
28639
|
-
}
|
|
28640
|
-
};
|
|
28641
|
-
};
|
|
28642
|
-
const list = async () => {
|
|
28643
|
-
return {
|
|
28644
|
-
response: {
|
|
28645
|
-
type: "capability:list",
|
|
28646
|
-
capabilities: manager.list().map(stripSecretHash)
|
|
28647
|
-
}
|
|
28648
|
-
};
|
|
28649
|
-
};
|
|
28650
|
-
const del = async (frame) => {
|
|
28651
|
-
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28652
|
-
const args = DeleteArgsSchema.parse(rest);
|
|
28653
|
-
const result = manager.delete(args.capabilityId);
|
|
28654
|
-
if (!result) {
|
|
28655
|
-
throw new ClawdError(
|
|
28656
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28657
|
-
`capability not found: ${args.capabilityId}`
|
|
28658
|
-
);
|
|
28659
|
-
}
|
|
28660
|
-
return {
|
|
28661
|
-
response: {
|
|
28662
|
-
type: "capability:deleted",
|
|
28663
|
-
capabilityId: args.capabilityId
|
|
28664
|
-
}
|
|
28665
|
-
};
|
|
28666
|
-
};
|
|
28667
|
-
return {
|
|
28668
|
-
"capability:issue": issue,
|
|
28669
|
-
"capability:list": list,
|
|
28670
|
-
"capability:delete": del
|
|
28671
|
-
};
|
|
28672
|
-
}
|
|
28673
|
-
|
|
28674
|
-
// src/handlers/inbox.ts
|
|
28675
|
-
init_protocol();
|
|
28676
|
-
function assertChannelAccess(ctx, capabilityId) {
|
|
28677
|
-
if (ctx.principal.kind === "owner") return;
|
|
28678
|
-
if (ctx.capabilityId === capabilityId) return;
|
|
28679
|
-
throw new ClawdError(
|
|
28680
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
28681
|
-
`inbox: guest can only access own channel (capabilityId=${capabilityId}, ctx.capabilityId=${ctx.capabilityId ?? "none"})`
|
|
28682
|
-
);
|
|
28683
|
-
}
|
|
28684
|
-
function buildInboxHandlers(deps) {
|
|
28685
|
-
const { manager, personStore, capabilityManager } = deps;
|
|
28686
|
-
const postMessage = async (frame, _client, ctx) => {
|
|
28687
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28688
|
-
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
28689
|
-
if (!ctx) {
|
|
28690
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
28691
|
-
}
|
|
28692
|
-
assertChannelAccess(ctx, args.capabilityId);
|
|
28693
|
-
if (ctx.principal.kind === "guest" && ctx.capabilityId) {
|
|
28694
|
-
const cap = capabilityManager.findById(ctx.capabilityId);
|
|
28695
|
-
if (cap) {
|
|
28696
|
-
const person = personStore.get(cap.personId);
|
|
28697
|
-
if (person && !person.dmEnabled) {
|
|
28698
|
-
throw new ClawdError(
|
|
28699
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28700
|
-
`DM_BLOCKED: Person ${person.displayName} has DM disabled`
|
|
28701
|
-
);
|
|
28702
|
-
}
|
|
28703
|
-
}
|
|
28704
|
-
}
|
|
28705
|
-
const message = manager.postMessage({
|
|
28706
|
-
capabilityId: args.capabilityId,
|
|
28707
|
-
senderPrincipalId: ctx.principal.id,
|
|
28708
|
-
text: args.text
|
|
28709
|
-
});
|
|
28710
|
-
return {
|
|
28711
|
-
response: { type: "inbox:postMessage:ok", message }
|
|
28712
|
-
};
|
|
28713
|
-
};
|
|
28714
|
-
const list = async (frame, _client, ctx) => {
|
|
28715
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28716
|
-
const args = InboxListArgsSchema.parse(rest);
|
|
28717
|
-
if (!ctx) {
|
|
28718
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
28719
|
-
}
|
|
28720
|
-
assertChannelAccess(ctx, args.capabilityId);
|
|
28721
|
-
const messages = manager.list(args.capabilityId, args.sinceCreatedAt);
|
|
28722
|
-
return {
|
|
28723
|
-
response: { type: "inbox:list:ok", capabilityId: args.capabilityId, messages }
|
|
28724
|
-
};
|
|
28725
|
-
};
|
|
28726
|
-
const markRead = async (frame, _client, ctx) => {
|
|
28727
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28728
|
-
const args = InboxMarkReadArgsSchema.parse(rest);
|
|
28729
|
-
if (!ctx) {
|
|
28730
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
28731
|
-
}
|
|
28732
|
-
assertChannelAccess(ctx, args.capabilityId);
|
|
28733
|
-
const updated = manager.markRead({
|
|
28734
|
-
capabilityId: args.capabilityId,
|
|
28735
|
-
principalId: ctx.principal.id,
|
|
28736
|
-
upToCreatedAt: args.upToCreatedAt
|
|
28737
|
-
});
|
|
28738
|
-
return {
|
|
28739
|
-
response: {
|
|
28740
|
-
type: "inbox:markRead:ok",
|
|
28741
|
-
capabilityId: args.capabilityId,
|
|
28742
|
-
upToCreatedAt: args.upToCreatedAt,
|
|
28743
|
-
updated
|
|
28744
|
-
}
|
|
28745
|
-
};
|
|
28746
|
-
};
|
|
28747
|
-
return {
|
|
28748
|
-
"inbox:postMessage": postMessage,
|
|
28749
|
-
"inbox:list": list,
|
|
28750
|
-
"inbox:markRead": markRead
|
|
28751
|
-
};
|
|
28752
|
-
}
|
|
28753
|
-
|
|
28754
|
-
// src/handlers/remote-persona.ts
|
|
28755
|
-
init_protocol();
|
|
28756
|
-
function buildRemotePersonaHandlers(deps) {
|
|
28757
|
-
const { store, personStore } = deps;
|
|
28758
|
-
const now = deps.now ?? Date.now;
|
|
28759
|
-
const add = async (frame) => {
|
|
28760
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28761
|
-
const args = RemotePersonaAddArgsSchema.parse(rest);
|
|
28762
|
-
if (!personStore.get(args.personId)) {
|
|
28763
|
-
throw new ClawdError(
|
|
28764
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28765
|
-
`Person not found: ${args.personId}`
|
|
28766
|
-
);
|
|
28767
|
-
}
|
|
28768
|
-
const rp = {
|
|
28769
|
-
...args,
|
|
28770
|
-
addedAt: now()
|
|
28771
|
-
};
|
|
28772
|
-
try {
|
|
28773
|
-
store.add(rp);
|
|
28774
|
-
} catch (err) {
|
|
28775
|
-
if (err.message?.includes("alias already exists")) {
|
|
28776
|
-
throw new ClawdError(
|
|
28777
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28778
|
-
`remote-persona alias already exists: ${args.alias}`
|
|
28779
|
-
);
|
|
28780
|
-
}
|
|
28781
|
-
throw err;
|
|
28782
|
-
}
|
|
28783
|
-
return {
|
|
28784
|
-
response: { type: "remote-persona:add:ok", remotePersona: stripRemotePersonaSecret(rp) }
|
|
28785
|
-
};
|
|
28786
|
-
};
|
|
28787
|
-
const list = async (_frame, _client, ctx) => {
|
|
28788
|
-
const all = store.list();
|
|
28789
|
-
const projected = ctx?.principal.kind === "owner" ? all : all.map(stripRemotePersonaSecret);
|
|
28790
|
-
return {
|
|
28791
|
-
response: {
|
|
28792
|
-
type: "remote-persona:list",
|
|
28793
|
-
remotePersonas: projected
|
|
28794
|
-
}
|
|
28795
|
-
};
|
|
28796
|
-
};
|
|
28797
|
-
const remove = async (frame) => {
|
|
28798
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
28799
|
-
const args = RemotePersonaRemoveArgsSchema.parse(rest);
|
|
28800
|
-
const removed = store.remove(args.alias);
|
|
28801
|
-
return {
|
|
28802
|
-
response: { type: "remote-persona:remove:ok", alias: args.alias, removed }
|
|
28803
|
-
};
|
|
28804
|
-
};
|
|
28805
|
-
return {
|
|
28806
|
-
"remote-persona:add": add,
|
|
28807
|
-
"remote-persona:list": list,
|
|
28808
|
-
"remote-persona:remove": remove
|
|
28809
|
-
};
|
|
28810
|
-
}
|
|
28811
|
-
|
|
28812
|
-
// src/handlers/whoami.ts
|
|
28813
|
-
init_protocol();
|
|
28814
|
-
function buildWhoamiHandler(deps) {
|
|
28815
|
-
return async (_frame, _client, ctx) => {
|
|
28816
|
-
if (!ctx) {
|
|
28817
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
28818
|
-
}
|
|
28819
|
-
const owner = makeOwnerPrincipal(deps.ownerPrincipalId, deps.ownerDisplayName);
|
|
28820
|
-
let capability;
|
|
28821
|
-
if (ctx.principal.kind === "owner") {
|
|
28822
|
-
capability = {
|
|
28823
|
-
id: deps.ownerPrincipalId,
|
|
28824
|
-
displayName: deps.ownerDisplayName,
|
|
28825
|
-
grants: ctx.grants,
|
|
28826
|
-
issuedAt: 0,
|
|
28827
|
-
usedCount: 0,
|
|
28828
|
-
// owner self cap 没有真实 Person 主人;用 ownerPrincipalId 自指,UI 不会反查这条
|
|
28829
|
-
personId: deps.ownerPrincipalId
|
|
28830
|
-
};
|
|
28831
|
-
} else {
|
|
28832
|
-
if (!ctx.capabilityId) {
|
|
28833
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
|
|
28834
|
-
}
|
|
28835
|
-
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
28836
|
-
if (!cap) {
|
|
28837
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
|
|
28838
|
-
}
|
|
28839
|
-
capability = stripSecretHash(cap);
|
|
28840
|
-
}
|
|
28841
|
-
const grantedPersonas = [];
|
|
28842
|
-
const isGuest = ctx.principal.kind === "guest";
|
|
28843
|
-
const hasWildcard = capability.grants.some((g2) => g2.resource.type === "*");
|
|
28844
|
-
if (hasWildcard) {
|
|
28845
|
-
for (const id of deps.personaStore.list()) {
|
|
28846
|
-
const file = deps.personaStore.read(id);
|
|
28847
|
-
if (!file) continue;
|
|
28848
|
-
if (isGuest && !file.public) continue;
|
|
28849
|
-
grantedPersonas.push({ id: file.personaId, displayName: file.label });
|
|
28850
|
-
}
|
|
28851
|
-
} else {
|
|
28852
|
-
for (const g2 of capability.grants) {
|
|
28853
|
-
if (g2.resource.type !== "persona") continue;
|
|
28854
|
-
const file = deps.personaStore.read(g2.resource.id);
|
|
28855
|
-
if (!file) continue;
|
|
28856
|
-
grantedPersonas.push({ id: file.personaId, displayName: file.label });
|
|
28857
|
-
}
|
|
28858
|
-
}
|
|
28859
|
-
return {
|
|
28860
|
-
response: {
|
|
28861
|
-
type: "whoami:ok",
|
|
28862
|
-
owner,
|
|
28863
|
-
capability,
|
|
28864
|
-
grantedPersonas
|
|
28865
|
-
}
|
|
28866
|
-
};
|
|
28867
|
-
};
|
|
28868
|
-
}
|
|
28869
|
-
|
|
28870
27498
|
// src/handlers/meta.ts
|
|
28871
27499
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
28872
27500
|
init_protocol();
|
|
@@ -28943,6 +27571,7 @@ function buildPersonaHandlers(deps) {
|
|
|
28943
27571
|
}
|
|
28944
27572
|
const personality = personaManager.readPersonality(args.personaId) ?? "";
|
|
28945
27573
|
const skills = personaManager.listSkills(args.personaId);
|
|
27574
|
+
const plugins = personaManager.listEnabledPlugins(args.personaId);
|
|
28946
27575
|
const sandboxSettings = personaManager.readSandboxSettings(args.personaId);
|
|
28947
27576
|
return {
|
|
28948
27577
|
response: {
|
|
@@ -28950,6 +27579,7 @@ function buildPersonaHandlers(deps) {
|
|
|
28950
27579
|
...persona,
|
|
28951
27580
|
personality,
|
|
28952
27581
|
skills,
|
|
27582
|
+
plugins,
|
|
28953
27583
|
sandboxSettings
|
|
28954
27584
|
}
|
|
28955
27585
|
};
|
|
@@ -28968,129 +27598,31 @@ function buildPersonaHandlers(deps) {
|
|
|
28968
27598
|
response: { type: "persona:deleted", personaId: args.personaId }
|
|
28969
27599
|
};
|
|
28970
27600
|
};
|
|
27601
|
+
const issueToken = async (frame) => {
|
|
27602
|
+
const args = PersonaIssueTokenArgsSchema.parse(frame);
|
|
27603
|
+
const { token, persona } = personaManager.issueToken(args.personaId, args.label);
|
|
27604
|
+
return {
|
|
27605
|
+
response: { type: "persona:tokenIssued", token, persona }
|
|
27606
|
+
};
|
|
27607
|
+
};
|
|
27608
|
+
const revokeToken = async (frame) => {
|
|
27609
|
+
const args = PersonaRevokeTokenArgsSchema.parse(frame);
|
|
27610
|
+
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
27611
|
+
return { response: { type: "persona:info", ...persona } };
|
|
27612
|
+
};
|
|
28971
27613
|
return {
|
|
28972
27614
|
"persona:create": create,
|
|
28973
27615
|
"persona:list": list,
|
|
28974
27616
|
"persona:get": get,
|
|
28975
27617
|
"persona:update": update,
|
|
28976
|
-
"persona:delete": del
|
|
27618
|
+
"persona:delete": del,
|
|
27619
|
+
"persona:issueToken": issueToken,
|
|
27620
|
+
"persona:revokeToken": revokeToken
|
|
28977
27621
|
};
|
|
28978
27622
|
}
|
|
28979
27623
|
|
|
28980
|
-
// src/handlers/person.ts
|
|
28981
|
-
var import_node_crypto9 = require("crypto");
|
|
28982
|
-
init_protocol();
|
|
28983
|
-
|
|
28984
|
-
// src/person/cascade.ts
|
|
28985
|
-
var EMPTY_RESULT = {
|
|
28986
|
-
deletedCapabilityIds: [],
|
|
28987
|
-
removedRemoteAliases: [],
|
|
28988
|
-
deletedInboxEvents: 0
|
|
28989
|
-
};
|
|
28990
|
-
function cascadeDeletePerson(personId, deps) {
|
|
28991
|
-
if (!deps.personStore.get(personId)) return { ...EMPTY_RESULT };
|
|
28992
|
-
const allCaps = deps.capabilityManager.list();
|
|
28993
|
-
const capsToDelete = allCaps.filter((c) => c.personId === personId);
|
|
28994
|
-
let deletedInboxEvents = 0;
|
|
28995
|
-
for (const c of capsToDelete) {
|
|
28996
|
-
deletedInboxEvents += deps.inboxStore.list(c.id).length;
|
|
28997
|
-
}
|
|
28998
|
-
const deletedCapabilityIds = [];
|
|
28999
|
-
for (const c of capsToDelete) {
|
|
29000
|
-
if (deps.capabilityManager.delete(c.id) !== null) {
|
|
29001
|
-
deletedCapabilityIds.push(c.id);
|
|
29002
|
-
}
|
|
29003
|
-
}
|
|
29004
|
-
const allRemotes = deps.remotePersonaStore.list();
|
|
29005
|
-
const remotesToRemove = allRemotes.filter((r) => r.personId === personId);
|
|
29006
|
-
const removedRemoteAliases = [];
|
|
29007
|
-
for (const r of remotesToRemove) {
|
|
29008
|
-
if (deps.remotePersonaStore.remove(r.alias)) {
|
|
29009
|
-
removedRemoteAliases.push(r.alias);
|
|
29010
|
-
}
|
|
29011
|
-
}
|
|
29012
|
-
deps.personStore.remove(personId);
|
|
29013
|
-
return { deletedCapabilityIds, removedRemoteAliases, deletedInboxEvents };
|
|
29014
|
-
}
|
|
29015
|
-
|
|
29016
|
-
// src/handlers/person.ts
|
|
29017
|
-
function buildPersonHandlers(deps) {
|
|
29018
|
-
const now = deps.now ?? Date.now;
|
|
29019
|
-
const genId = deps.genId ?? defaultGenId2;
|
|
29020
|
-
const list = async () => {
|
|
29021
|
-
const persons = deps.personStore.list();
|
|
29022
|
-
const allCaps = deps.capabilityManager.list();
|
|
29023
|
-
const allRemotes = deps.remotePersonaStore.list();
|
|
29024
|
-
const out = persons.map((p2) => ({
|
|
29025
|
-
...p2,
|
|
29026
|
-
linkedCapabilityIds: allCaps.filter((c) => c.personId === p2.id).map((c) => c.id),
|
|
29027
|
-
linkedRemoteAliases: allRemotes.filter((r) => r.personId === p2.id).map((r) => r.alias)
|
|
29028
|
-
}));
|
|
29029
|
-
return { response: { type: "person:list:ok", persons: out } };
|
|
29030
|
-
};
|
|
29031
|
-
const create = async (frame) => {
|
|
29032
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29033
|
-
const args = PersonCreateArgsSchema.parse(rest);
|
|
29034
|
-
const t = now();
|
|
29035
|
-
const person = {
|
|
29036
|
-
id: genId(),
|
|
29037
|
-
displayName: args.displayName,
|
|
29038
|
-
...args.notes !== void 0 ? { notes: args.notes } : {},
|
|
29039
|
-
dmEnabled: args.dmEnabled ?? true,
|
|
29040
|
-
createdAt: t,
|
|
29041
|
-
updatedAt: t
|
|
29042
|
-
};
|
|
29043
|
-
deps.personStore.add(person);
|
|
29044
|
-
return { response: { type: "person:create:ok", person } };
|
|
29045
|
-
};
|
|
29046
|
-
const update = async (frame) => {
|
|
29047
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29048
|
-
const args = PersonUpdateArgsSchema.parse(rest);
|
|
29049
|
-
const existing = deps.personStore.get(args.id);
|
|
29050
|
-
if (!existing) {
|
|
29051
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `Person not found: ${args.id}`);
|
|
29052
|
-
}
|
|
29053
|
-
const t = now();
|
|
29054
|
-
deps.personStore.update(
|
|
29055
|
-
args.id,
|
|
29056
|
-
{
|
|
29057
|
-
...args.displayName !== void 0 ? { displayName: args.displayName } : {},
|
|
29058
|
-
...args.notes !== void 0 ? { notes: args.notes } : {},
|
|
29059
|
-
...args.dmEnabled !== void 0 ? { dmEnabled: args.dmEnabled } : {}
|
|
29060
|
-
},
|
|
29061
|
-
t
|
|
29062
|
-
);
|
|
29063
|
-
const next = deps.personStore.get(args.id);
|
|
29064
|
-
return { response: { type: "person:update:ok", person: next } };
|
|
29065
|
-
};
|
|
29066
|
-
const del = async (frame) => {
|
|
29067
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
29068
|
-
const args = PersonDeleteArgsSchema.parse(rest);
|
|
29069
|
-
if (!deps.personStore.get(args.id)) {
|
|
29070
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `Person not found: ${args.id}`);
|
|
29071
|
-
}
|
|
29072
|
-
const result = cascadeDeletePerson(args.id, deps);
|
|
29073
|
-
return {
|
|
29074
|
-
response: {
|
|
29075
|
-
type: "person:delete:ok",
|
|
29076
|
-
deletedCapabilityIds: result.deletedCapabilityIds,
|
|
29077
|
-
removedRemoteAliases: result.removedRemoteAliases,
|
|
29078
|
-
deletedInboxEvents: result.deletedInboxEvents
|
|
29079
|
-
}
|
|
29080
|
-
};
|
|
29081
|
-
};
|
|
29082
|
-
return {
|
|
29083
|
-
"person:list": list,
|
|
29084
|
-
"person:create": create,
|
|
29085
|
-
"person:update": update,
|
|
29086
|
-
"person:delete": del
|
|
29087
|
-
};
|
|
29088
|
-
}
|
|
29089
|
-
function defaultGenId2() {
|
|
29090
|
-
return `p_${(0, import_node_crypto9.randomUUID)()}`;
|
|
29091
|
-
}
|
|
29092
|
-
|
|
29093
27624
|
// src/handlers/attachment.ts
|
|
27625
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
29094
27626
|
init_protocol();
|
|
29095
27627
|
init_protocol();
|
|
29096
27628
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -29115,8 +27647,41 @@ function buildAttachmentHandlers(deps) {
|
|
|
29115
27647
|
"httpBaseUrl unavailable (daemon HTTP not ready)"
|
|
29116
27648
|
);
|
|
29117
27649
|
}
|
|
27650
|
+
if (!deps.sessionStore || !deps.getSessionScope || !deps.groupFileStore) {
|
|
27651
|
+
throw new ClawdError(
|
|
27652
|
+
ERROR_CODES.METHOD_NOT_IMPLEMENTED,
|
|
27653
|
+
"signUrl requires session/group stores"
|
|
27654
|
+
);
|
|
27655
|
+
}
|
|
27656
|
+
const sessionFile = deps.sessionStore.read(args.sessionId);
|
|
27657
|
+
if (!sessionFile) {
|
|
27658
|
+
throw new ClawdError(
|
|
27659
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27660
|
+
`session ${args.sessionId} not found`
|
|
27661
|
+
);
|
|
27662
|
+
}
|
|
27663
|
+
const scope = deps.getSessionScope(args.sessionId);
|
|
27664
|
+
if (!scope) {
|
|
27665
|
+
throw new ClawdError(
|
|
27666
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27667
|
+
`session ${args.sessionId} scope unresolved`
|
|
27668
|
+
);
|
|
27669
|
+
}
|
|
27670
|
+
const cwdAbs = import_node_path26.default.resolve(sessionFile.cwd);
|
|
27671
|
+
const candidateAbs = import_node_path26.default.isAbsolute(args.relPath) ? import_node_path26.default.resolve(args.relPath) : import_node_path26.default.resolve(cwdAbs, args.relPath);
|
|
27672
|
+
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
27673
|
+
const entry = entries.find((e) => {
|
|
27674
|
+
const storedAbs = import_node_path26.default.isAbsolute(e.relPath) ? import_node_path26.default.resolve(e.relPath) : import_node_path26.default.resolve(cwdAbs, e.relPath);
|
|
27675
|
+
return storedAbs === candidateAbs && !e.stale;
|
|
27676
|
+
});
|
|
27677
|
+
if (!entry) {
|
|
27678
|
+
throw new ClawdError(
|
|
27679
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27680
|
+
`relPath not in session group files or stale: ${args.relPath}`
|
|
27681
|
+
);
|
|
27682
|
+
}
|
|
29118
27683
|
const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
29119
|
-
const parts = signUrlParts(secret,
|
|
27684
|
+
const parts = signUrlParts(secret, candidateAbs, ttl);
|
|
29120
27685
|
const url = buildSignedFileUrl(httpBaseUrl, parts);
|
|
29121
27686
|
return {
|
|
29122
27687
|
response: {
|
|
@@ -29183,11 +27748,23 @@ function buildAttachmentHandlers(deps) {
|
|
|
29183
27748
|
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
29184
27749
|
return { response: { type: "attachment.groupList", entries } };
|
|
29185
27750
|
};
|
|
27751
|
+
const groupListPersona = async (frame) => {
|
|
27752
|
+
if (!deps.groupFileStore) {
|
|
27753
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
27754
|
+
}
|
|
27755
|
+
const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
|
|
27756
|
+
if (!parsed.success) {
|
|
27757
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
27758
|
+
}
|
|
27759
|
+
const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
|
|
27760
|
+
return { response: { type: "attachment.groupListPersona", perSession } };
|
|
27761
|
+
};
|
|
29186
27762
|
return {
|
|
29187
27763
|
"attachment.signUrl": signUrl,
|
|
29188
27764
|
"attachment.groupAdd": groupAdd,
|
|
29189
27765
|
"attachment.groupRemove": groupRemove,
|
|
29190
|
-
"attachment.groupList": groupList
|
|
27766
|
+
"attachment.groupList": groupList,
|
|
27767
|
+
"attachment.groupListPersona": groupListPersona
|
|
29191
27768
|
};
|
|
29192
27769
|
}
|
|
29193
27770
|
|
|
@@ -29205,143 +27782,15 @@ function buildMethodHandlers(deps) {
|
|
|
29205
27782
|
personaManager: deps.personaManager,
|
|
29206
27783
|
personaRegistry: deps.personaRegistry
|
|
29207
27784
|
}),
|
|
29208
|
-
...buildCapabilityHandlers({
|
|
29209
|
-
manager: deps.capabilityManager,
|
|
29210
|
-
getShareBaseUrl: deps.getShareBaseUrl,
|
|
29211
|
-
personStore: deps.personStore
|
|
29212
|
-
}),
|
|
29213
|
-
...buildInboxHandlers({
|
|
29214
|
-
manager: deps.inboxManager,
|
|
29215
|
-
personStore: deps.personStore,
|
|
29216
|
-
capabilityManager: deps.capabilityManager
|
|
29217
|
-
}),
|
|
29218
|
-
...buildRemotePersonaHandlers({
|
|
29219
|
-
store: deps.remotePersonaStore,
|
|
29220
|
-
personStore: deps.personStore
|
|
29221
|
-
}),
|
|
29222
|
-
...buildPersonHandlers({
|
|
29223
|
-
personStore: deps.personStore,
|
|
29224
|
-
capabilityManager: deps.capabilityManager,
|
|
29225
|
-
remotePersonaStore: deps.remotePersonaStore,
|
|
29226
|
-
inboxStore: deps.inboxStore
|
|
29227
|
-
}),
|
|
29228
|
-
whoami: buildWhoamiHandler({
|
|
29229
|
-
ownerDisplayName: deps.ownerDisplayName,
|
|
29230
|
-
ownerPrincipalId: deps.ownerPrincipalId,
|
|
29231
|
-
personaStore: deps.personaStore,
|
|
29232
|
-
capabilityManager: deps.capabilityManager
|
|
29233
|
-
}),
|
|
29234
27785
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
29235
27786
|
};
|
|
29236
27787
|
}
|
|
29237
27788
|
|
|
29238
|
-
// src/handlers/method-grants.ts
|
|
29239
|
-
var ADMIN_ANY = {
|
|
29240
|
-
kind: "fixed",
|
|
29241
|
-
resource: { type: "*" },
|
|
29242
|
-
action: "admin"
|
|
29243
|
-
};
|
|
29244
|
-
var CAPABILITY_SCOPED = { kind: "capability-scoped" };
|
|
29245
|
-
var METHOD_GRANT_MAP = {
|
|
29246
|
-
// ---- public(meta-only,guest 也能调) ----
|
|
29247
|
-
"info": { kind: "public" },
|
|
29248
|
-
"ping": { kind: "public" },
|
|
29249
|
-
// v2 Phase 6: whoami 是身份反查,任何 authed connection (owner / guest) 都能调;
|
|
29250
|
-
// handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
|
|
29251
|
-
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
29252
|
-
"whoami": { kind: "public" },
|
|
29253
|
-
// ---- capability platform(admin-only,本 PR 新增) ----
|
|
29254
|
-
"capability:issue": ADMIN_ANY,
|
|
29255
|
-
"capability:list": ADMIN_ANY,
|
|
29256
|
-
"capability:delete": ADMIN_ANY,
|
|
29257
|
-
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
29258
|
-
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
29259
|
-
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
29260
|
-
"inbox:list": { kind: "public" },
|
|
29261
|
-
"inbox:markRead": { kind: "public" },
|
|
29262
|
-
"inbox:postMessage": { kind: "public" },
|
|
29263
|
-
// Phase 4 Task 4.3: 远程 persona 仅 owner 管理 (admin-only)
|
|
29264
|
-
"remote-persona:add": ADMIN_ANY,
|
|
29265
|
-
"remote-persona:list": ADMIN_ANY,
|
|
29266
|
-
"remote-persona:remove": ADMIN_ANY,
|
|
29267
|
-
// Person identity Phase 1: 联系人本质是 owner 视角的 People 列表,仅 owner 管理。
|
|
29268
|
-
"person:list": ADMIN_ANY,
|
|
29269
|
-
"person:create": ADMIN_ANY,
|
|
29270
|
-
"person:update": ADMIN_ANY,
|
|
29271
|
-
"person:delete": ADMIN_ANY,
|
|
29272
|
-
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
29273
|
-
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
29274
|
-
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
29275
|
-
"session:create": CAPABILITY_SCOPED,
|
|
29276
|
-
"session:list": CAPABILITY_SCOPED,
|
|
29277
|
-
"session:get": CAPABILITY_SCOPED,
|
|
29278
|
-
"session:update": CAPABILITY_SCOPED,
|
|
29279
|
-
"session:delete": CAPABILITY_SCOPED,
|
|
29280
|
-
"session:send": CAPABILITY_SCOPED,
|
|
29281
|
-
"session:stop": CAPABILITY_SCOPED,
|
|
29282
|
-
"session:interrupt": CAPABILITY_SCOPED,
|
|
29283
|
-
"session:rewind": CAPABILITY_SCOPED,
|
|
29284
|
-
"session:rewind-diff": CAPABILITY_SCOPED,
|
|
29285
|
-
"session:rewindable-message-ids": CAPABILITY_SCOPED,
|
|
29286
|
-
"session:fork": CAPABILITY_SCOPED,
|
|
29287
|
-
"session:new": CAPABILITY_SCOPED,
|
|
29288
|
-
"session:resume": CAPABILITY_SCOPED,
|
|
29289
|
-
"session:observe": CAPABILITY_SCOPED,
|
|
29290
|
-
"session:events": CAPABILITY_SCOPED,
|
|
29291
|
-
"session:subscribe": CAPABILITY_SCOPED,
|
|
29292
|
-
"session:unsubscribe": CAPABILITY_SCOPED,
|
|
29293
|
-
"session:pin": CAPABILITY_SCOPED,
|
|
29294
|
-
"session:reorderPins": ADMIN_ANY,
|
|
29295
|
-
// owner 全局操作,无 personaId 维度
|
|
29296
|
-
"permission:respond": CAPABILITY_SCOPED,
|
|
29297
|
-
"session:answerQuestion": CAPABILITY_SCOPED,
|
|
29298
|
-
"session:cancelQuestion": CAPABILITY_SCOPED,
|
|
29299
|
-
"history:projects": ADMIN_ANY,
|
|
29300
|
-
"history:list": ADMIN_ANY,
|
|
29301
|
-
"history:read": ADMIN_ANY,
|
|
29302
|
-
"history:subagents": ADMIN_ANY,
|
|
29303
|
-
"history:subagent-read": ADMIN_ANY,
|
|
29304
|
-
"history:recentDirs": ADMIN_ANY,
|
|
29305
|
-
"workspace:list": ADMIN_ANY,
|
|
29306
|
-
"workspace:read": ADMIN_ANY,
|
|
29307
|
-
"skills:list": ADMIN_ANY,
|
|
29308
|
-
"agents:list": ADMIN_ANY,
|
|
29309
|
-
"git:root": ADMIN_ANY,
|
|
29310
|
-
"git:branch": ADMIN_ANY,
|
|
29311
|
-
"git:branches": ADMIN_ANY,
|
|
29312
|
-
"capabilities:get": ADMIN_ANY,
|
|
29313
|
-
"persona:create": ADMIN_ANY,
|
|
29314
|
-
"persona:list": ADMIN_ANY,
|
|
29315
|
-
"persona:get": ADMIN_ANY,
|
|
29316
|
-
"persona:update": ADMIN_ANY,
|
|
29317
|
-
"persona:delete": ADMIN_ANY,
|
|
29318
|
-
"session:pty:input": ADMIN_ANY,
|
|
29319
|
-
"session:pty:resize": ADMIN_ANY,
|
|
29320
|
-
// file-sharing attachment.* RPC:dispatcher 用 admin-only 兜底(wire-level 拦),
|
|
29321
|
-
// 实际只有 owner 能调(personal token 链路 2026-05-21 删除,HTTP Bearer 只识别 owner)
|
|
29322
|
-
"attachment.signUrl": ADMIN_ANY,
|
|
29323
|
-
"attachment.groupAdd": ADMIN_ANY,
|
|
29324
|
-
"attachment.groupRemove": ADMIN_ANY,
|
|
29325
|
-
"attachment.groupList": ADMIN_ANY
|
|
29326
|
-
};
|
|
29327
|
-
function computeGrantForFrame(method, frame) {
|
|
29328
|
-
const rule = METHOD_GRANT_MAP[method];
|
|
29329
|
-
if (!rule) return { kind: "public" };
|
|
29330
|
-
if (rule.kind === "public") return { kind: "public" };
|
|
29331
|
-
if (rule.kind === "capability-scoped") return { kind: "public" };
|
|
29332
|
-
if (rule.kind === "fixed") {
|
|
29333
|
-
return { kind: "check", resource: rule.resource, action: rule.action };
|
|
29334
|
-
}
|
|
29335
|
-
const picked = rule.pick(frame);
|
|
29336
|
-
if (!picked) return { kind: "public" };
|
|
29337
|
-
return { kind: "check", resource: picked.resource, action: picked.action };
|
|
29338
|
-
}
|
|
29339
|
-
|
|
29340
27789
|
// src/index.ts
|
|
29341
27790
|
async function startDaemon(config) {
|
|
29342
27791
|
const logger = createLogger({
|
|
29343
27792
|
level: config.logLevel,
|
|
29344
|
-
file:
|
|
27793
|
+
file: import_node_path27.default.join(config.dataDir, "clawd.log")
|
|
29345
27794
|
});
|
|
29346
27795
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
29347
27796
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -29352,103 +27801,45 @@ async function startDaemon(config) {
|
|
|
29352
27801
|
if (pre.status === "stale") {
|
|
29353
27802
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
29354
27803
|
}
|
|
29355
|
-
const auth = loadOrCreateAuth({ dataDir: config.dataDir });
|
|
29356
27804
|
let resolvedAuthToken = null;
|
|
27805
|
+
let authFile = null;
|
|
29357
27806
|
if (config.authToken && config.authToken.trim()) {
|
|
29358
27807
|
resolvedAuthToken = config.authToken.trim();
|
|
29359
27808
|
} else if (config.tunnel) {
|
|
29360
|
-
|
|
27809
|
+
authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
|
|
27810
|
+
resolvedAuthToken = authFile.token;
|
|
29361
27811
|
}
|
|
29362
|
-
const ownerPrincipalId = auth.ownerPrincipalId;
|
|
29363
|
-
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29364
27812
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
29365
|
-
const inboxStore = new InboxStore(config.dataDir);
|
|
29366
|
-
const inboxManager = new InboxManager(inboxStore, (capabilityId, frame) => {
|
|
29367
|
-
wsServer?.broadcastToCapabilityChannel(capabilityId, frame);
|
|
29368
|
-
});
|
|
29369
|
-
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
29370
|
-
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
29371
|
-
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
29372
|
-
onIssued: (cap, token) => {
|
|
29373
|
-
wsServer?.broadcastToOwners({
|
|
29374
|
-
type: "capability:tokenIssued",
|
|
29375
|
-
capability: stripSecretHash(cap),
|
|
29376
|
-
token
|
|
29377
|
-
});
|
|
29378
|
-
},
|
|
29379
|
-
onDeleted: (cap) => {
|
|
29380
|
-
const deletedAt = Date.now();
|
|
29381
|
-
wsServer?.broadcastToOwners({
|
|
29382
|
-
type: "capability:tokenDeleted",
|
|
29383
|
-
capabilityId: cap.id,
|
|
29384
|
-
deletedAt
|
|
29385
|
-
});
|
|
29386
|
-
wsServer?.closeConnectionsByCapability(cap.id);
|
|
29387
|
-
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
29388
|
-
if (cleanup.removed.length > 0) {
|
|
29389
|
-
logger.info("capability delete cascade: guest sessions removed", {
|
|
29390
|
-
capabilityId: cap.id,
|
|
29391
|
-
removedDirs: cleanup.removed
|
|
29392
|
-
});
|
|
29393
|
-
}
|
|
29394
|
-
inboxManager.removeChannel(cap.id);
|
|
29395
|
-
}
|
|
29396
|
-
});
|
|
29397
|
-
const remotePersonaStore = new RemotePersonaStore(config.dataDir);
|
|
29398
|
-
const personStore = new PersonStore(config.dataDir);
|
|
29399
27813
|
let wsServer = null;
|
|
29400
27814
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
29401
27815
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
29402
|
-
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
29403
|
-
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
29404
27816
|
expectedToken: resolvedAuthToken,
|
|
29405
|
-
authenticate: (t, _selfPrincipalId) => {
|
|
29406
|
-
return authenticate(t, {
|
|
29407
|
-
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
29408
|
-
ownerPrincipalId,
|
|
29409
|
-
ownerDisplayName,
|
|
29410
|
-
capabilityRegistry
|
|
29411
|
-
});
|
|
29412
|
-
},
|
|
29413
|
-
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
29414
|
-
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
29415
27817
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
29416
27818
|
sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
|
|
29417
27819
|
}) : null;
|
|
29418
27820
|
resetRegistry();
|
|
29419
|
-
const
|
|
29420
|
-
if (!migrateResult.skipped && (migrateResult.movedBare || migrateResult.movedVmOwner || migrateResult.archivedListener)) {
|
|
29421
|
-
logger.info("sessions migration applied", {
|
|
29422
|
-
movedBare: migrateResult.movedBare,
|
|
29423
|
-
movedVmOwner: migrateResult.movedVmOwner,
|
|
29424
|
-
archivedListener: migrateResult.archivedListener
|
|
29425
|
-
});
|
|
29426
|
-
}
|
|
29427
|
-
const sessionStoreFactory = new SessionStoreFactory({ dataDir: config.dataDir });
|
|
29428
|
-
const store = sessionStoreFactory.forBare();
|
|
27821
|
+
const store = new SessionStore({ dataDir: config.dataDir });
|
|
29429
27822
|
const workspace = new WorkspaceBrowser();
|
|
29430
27823
|
const skills = new SkillsScanner();
|
|
29431
27824
|
const agents = new AgentsScanner();
|
|
29432
27825
|
const history = new ClaudeHistoryReader();
|
|
29433
27826
|
let transport = null;
|
|
29434
|
-
const personaStore = new PersonaStore(
|
|
27827
|
+
const personaStore = new PersonaStore(import_node_path27.default.join(config.dataDir, "personas"));
|
|
29435
27828
|
const defaultsRoot = findDefaultsRoot();
|
|
29436
27829
|
if (defaultsRoot) {
|
|
29437
27830
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
29438
27831
|
} else {
|
|
29439
27832
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
29440
27833
|
}
|
|
27834
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29441
27835
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
29442
27836
|
const manager = new SessionManager({
|
|
29443
27837
|
store,
|
|
29444
|
-
// Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
|
|
29445
|
-
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
29446
|
-
storeFactory: sessionStoreFactory,
|
|
29447
27838
|
logger,
|
|
29448
27839
|
getAdapter,
|
|
29449
27840
|
historyReader: history,
|
|
29450
27841
|
dataDir: config.dataDir,
|
|
29451
|
-
personaRoot:
|
|
27842
|
+
personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
|
|
29452
27843
|
personaStore,
|
|
29453
27844
|
ownerDisplayName,
|
|
29454
27845
|
mode: config.mode,
|
|
@@ -29471,7 +27862,7 @@ async function startDaemon(config) {
|
|
|
29471
27862
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
29472
27863
|
attachmentGroup: {
|
|
29473
27864
|
onFileEdit: (input) => {
|
|
29474
|
-
const absPath =
|
|
27865
|
+
const absPath = import_node_path27.default.isAbsolute(input.relPath) ? input.relPath : import_node_path27.default.join(input.cwd, input.relPath);
|
|
29475
27866
|
let size = 0;
|
|
29476
27867
|
try {
|
|
29477
27868
|
size = import_node_fs24.default.statSync(absPath).size;
|
|
@@ -29570,62 +27961,43 @@ async function startDaemon(config) {
|
|
|
29570
27961
|
httpToken: resolvedAuthToken,
|
|
29571
27962
|
// file-sharing attachment.* RPC。signUrl 用 owner token 做 HMAC secret;group RPC
|
|
29572
27963
|
// 根据 sessionId 反查 scope 写盘。
|
|
27964
|
+
//
|
|
27965
|
+
// sessionStore / getSessionScope 都走 manager 的跨 scope 公开 API(findOwnedSession /
|
|
27966
|
+
// findOwnedSessionScope)—— 否则 default-only SessionStore 找不到 persona owner-mode
|
|
27967
|
+
// session,attachment 整套 RPC 对 persona session 都会报 "session not found"。
|
|
27968
|
+
// 详见 fix(daemon) #703:file-sharing v1 预存 wiring bug,#701 把 desktop 默认 --tunnel
|
|
27969
|
+
// 后首次让 desktop 用户用上 file-sharing 才被暴露。
|
|
29573
27970
|
attachment: {
|
|
29574
27971
|
groupFileStore,
|
|
27972
|
+
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
29575
27973
|
getHttpBaseUrl,
|
|
29576
|
-
// HMAC sign secret
|
|
29577
|
-
// noAuth 模式
|
|
29578
|
-
getSignSecret: () =>
|
|
29579
|
-
// group RPC
|
|
27974
|
+
// HMAC sign secret:~/.clawd/auth.json signSecret 字段(与 WS Bearer token 独立)。
|
|
27975
|
+
// --auth-token CLI 模式 / noAuth 模式 authFile 为 null → handler 自己返 NOT_IMPLEMENTED。
|
|
27976
|
+
getSignSecret: () => authFile?.signSecret ?? "",
|
|
27977
|
+
// group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
|
|
29580
27978
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
29581
|
-
getSessionScope: (
|
|
29582
|
-
|
|
29583
|
-
if (!file) return null;
|
|
29584
|
-
if (file.ownerPersonaId) {
|
|
29585
|
-
return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
|
|
29586
|
-
}
|
|
29587
|
-
return { kind: "default" };
|
|
29588
|
-
}
|
|
29589
|
-
},
|
|
29590
|
-
// Task 1.9: capability:issue/list/revoke handler 依赖
|
|
29591
|
-
capabilityManager,
|
|
29592
|
-
// v2 Phase 5: capability:issue 返回的 shareUrl 用此 base URL 拼接。
|
|
29593
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
29594
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
29595
|
-
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
29596
|
-
ownerDisplayName,
|
|
29597
|
-
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
29598
|
-
ownerPrincipalId,
|
|
29599
|
-
personaStore,
|
|
29600
|
-
// capability handler 也用 (capability:issue 走 registry / capability:list)
|
|
29601
|
-
capabilityRegistry,
|
|
29602
|
-
// capability platform v3 inbox: handler 接 manager (post/list/markRead),
|
|
29603
|
-
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
29604
|
-
inboxManager,
|
|
29605
|
-
inboxStore,
|
|
29606
|
-
// Phase 4 Task 4.3: remote-persona:* handler 依赖 (本地存储, v1 不接 outgoing WS)
|
|
29607
|
-
remotePersonaStore,
|
|
29608
|
-
// Per-People token (2026-05-21): person:* RPC handlers + cap/remote 自带
|
|
29609
|
-
// personId 字段(PersonAlias 表已删)
|
|
29610
|
-
personStore
|
|
27979
|
+
getSessionScope: (sid) => manager.findOwnedSessionScope(sid)
|
|
27980
|
+
}
|
|
29611
27981
|
});
|
|
29612
27982
|
const authResolver = new AuthContextResolver({
|
|
29613
|
-
ownerToken: resolvedAuthToken
|
|
27983
|
+
ownerToken: resolvedAuthToken,
|
|
27984
|
+
personaRegistry
|
|
29614
27985
|
});
|
|
29615
27986
|
const httpRouter = createHttpRouter({
|
|
29616
27987
|
authResolver,
|
|
29617
27988
|
daemonVersion: version,
|
|
29618
27989
|
logger,
|
|
27990
|
+
personaStore,
|
|
27991
|
+
groupFileStore,
|
|
29619
27992
|
sessionStore: store,
|
|
29620
|
-
// /files HMAC verify
|
|
29621
|
-
|
|
27993
|
+
// /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
|
|
27994
|
+
// --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
|
|
27995
|
+
getSignSecret: () => authFile?.signSecret ?? null
|
|
29622
27996
|
});
|
|
29623
27997
|
wsServer = new LocalWsServer({
|
|
29624
27998
|
host: config.host,
|
|
29625
27999
|
port: config.port,
|
|
29626
28000
|
logger,
|
|
29627
|
-
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
29628
|
-
ownerPrincipalId,
|
|
29629
28001
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
29630
28002
|
{
|
|
29631
28003
|
manager,
|
|
@@ -29641,19 +28013,6 @@ async function startDaemon(config) {
|
|
|
29641
28013
|
),
|
|
29642
28014
|
protocolVersion: PROTOCOL_VERSION,
|
|
29643
28015
|
authGate: authGate ?? void 0,
|
|
29644
|
-
// noAuth 模式下仍验 capability token: 修 capability platform 漏洞 — 远端 client
|
|
29645
|
-
// 用 cap token 连 noAuth daemon 时若无此 hook 会 fallback owner ctx, whoami 错返
|
|
29646
|
-
// capability.id = ownerPrincipalId, 导致 myCapabilityId 写错 → inbox channel 写错.
|
|
29647
|
-
// 命中 cap → attach guest ctx; 空 token / 未命中 → 保持 noAuth 行为 (handler fallback owner).
|
|
29648
|
-
tryVerifyCapabilityToken: (token) => {
|
|
29649
|
-
const v2 = capabilityRegistry.verifyToken(token);
|
|
29650
|
-
if (!v2.ok) return null;
|
|
29651
|
-
return {
|
|
29652
|
-
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
29653
|
-
grants: v2.capability.grants,
|
|
29654
|
-
capabilityId: v2.capability.id
|
|
29655
|
-
};
|
|
29656
|
-
},
|
|
29657
28016
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29658
28017
|
httpRequestHandler: httpRouter,
|
|
29659
28018
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
@@ -29706,18 +28065,7 @@ async function startDaemon(config) {
|
|
|
29706
28065
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
29707
28066
|
const handler = handlers[type];
|
|
29708
28067
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
29709
|
-
const
|
|
29710
|
-
const verdict = computeGrantForFrame(type, frame);
|
|
29711
|
-
if (verdict.kind === "check") {
|
|
29712
|
-
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|
|
29713
|
-
if (!ok) {
|
|
29714
|
-
throw new ClawdError(
|
|
29715
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
29716
|
-
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
29717
|
-
);
|
|
29718
|
-
}
|
|
29719
|
-
}
|
|
29720
|
-
const result = await handler(frame, client, ctx);
|
|
28068
|
+
const result = await handler(frame, client);
|
|
29721
28069
|
if (requestId && result.response) {
|
|
29722
28070
|
client.send({ ...result.response, requestId });
|
|
29723
28071
|
}
|
|
@@ -29775,15 +28123,15 @@ async function startDaemon(config) {
|
|
|
29775
28123
|
});
|
|
29776
28124
|
try {
|
|
29777
28125
|
const r = await tunnelMgr.start({ localPort: config.port });
|
|
29778
|
-
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
|
|
28126
|
+
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
|
|
29779
28127
|
stateMgr.write(stateSnapshot);
|
|
29780
28128
|
currentTunnelUrl = r.url;
|
|
29781
28129
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
29782
28130
|
const lines = [
|
|
29783
28131
|
`Tunnel: ${r.url}`,
|
|
29784
28132
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
29785
|
-
`Frpc config: ${
|
|
29786
|
-
`Frpc log: ${
|
|
28133
|
+
`Frpc config: ${import_node_path27.default.join(config.dataDir, "frpc.toml")}`,
|
|
28134
|
+
`Frpc log: ${import_node_path27.default.join(config.dataDir, "frpc.log")}`
|
|
29787
28135
|
];
|
|
29788
28136
|
const width = Math.max(...lines.map((l) => l.length));
|
|
29789
28137
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -29796,19 +28144,30 @@ ${bar}
|
|
|
29796
28144
|
|
|
29797
28145
|
`);
|
|
29798
28146
|
try {
|
|
29799
|
-
const connectPath =
|
|
28147
|
+
const connectPath = import_node_path27.default.join(config.dataDir, "connect.txt");
|
|
29800
28148
|
import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
29801
28149
|
} catch {
|
|
29802
28150
|
}
|
|
29803
28151
|
} catch (err) {
|
|
28152
|
+
const tunnelError = err?.message ?? String(err);
|
|
29804
28153
|
try {
|
|
29805
28154
|
await tunnelMgr.stop();
|
|
29806
28155
|
} catch {
|
|
29807
28156
|
}
|
|
29808
28157
|
tunnelMgr = null;
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
28158
|
+
stateSnapshot = { ...stateSnapshot, tunnelUrl: void 0, tunnelError };
|
|
28159
|
+
try {
|
|
28160
|
+
stateMgr.write(stateSnapshot);
|
|
28161
|
+
} catch {
|
|
28162
|
+
}
|
|
28163
|
+
wss.broadcastAll({
|
|
28164
|
+
type: "tunnel:unavailable",
|
|
28165
|
+
reason: tunnelError,
|
|
28166
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28167
|
+
});
|
|
28168
|
+
process.stdout.write(`Tunnel: unavailable (local mode) \u2014 ${tunnelError}
|
|
28169
|
+
`);
|
|
28170
|
+
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
29812
28171
|
}
|
|
29813
28172
|
}
|
|
29814
28173
|
const shutdown = async () => {
|