@clawos-dev/clawd 0.2.66 → 0.2.67-beta.119.a74a0cd
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 +182 -1005
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -85,24 +85,17 @@ var init_methods = __esm({
|
|
|
85
85
|
"git:root",
|
|
86
86
|
"git:branch",
|
|
87
87
|
"git:branches",
|
|
88
|
-
"git:worktree:prefix",
|
|
89
|
-
"git:worktree:create",
|
|
90
|
-
"git:worktree:remove",
|
|
91
88
|
"capabilities:get",
|
|
92
|
-
// ---- persona:* (
|
|
89
|
+
// ---- persona:* (persona 管理) ----
|
|
93
90
|
"persona:create",
|
|
94
91
|
"persona:list",
|
|
95
92
|
"persona:get",
|
|
96
93
|
"persona:update",
|
|
97
94
|
"persona:delete",
|
|
95
|
+
// token 颁发 / 吊销:file-sharing HTTP Bearer 鉴权用(PersonaRegistry.findByToken
|
|
96
|
+
// 反查 personaId / label)。owner UI 在 PersonaSettingsDrawer "分享 Token" section 管理。
|
|
98
97
|
"persona:issueToken",
|
|
99
98
|
"persona:revokeToken",
|
|
100
|
-
"persona:listSubSessions",
|
|
101
|
-
// owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session
|
|
102
|
-
// 元数据。后续增量靠 persona:listenerChat:* push 维护,本 RPC 仅在 owner ws connect /
|
|
103
|
-
// reconnect 时一次性补齐 cache。
|
|
104
|
-
"persona:listListenerChatsForToken",
|
|
105
|
-
"persona:appendOwnerMessage",
|
|
106
99
|
// ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
|
|
107
100
|
// session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
|
|
108
101
|
// 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
|
|
@@ -612,8 +605,8 @@ var init_parseUtil = __esm({
|
|
|
612
605
|
init_errors2();
|
|
613
606
|
init_en();
|
|
614
607
|
makeIssue = (params) => {
|
|
615
|
-
const { data, path:
|
|
616
|
-
const fullPath = [...
|
|
608
|
+
const { data, path: path32, errorMaps, issueData } = params;
|
|
609
|
+
const fullPath = [...path32, ...issueData.path || []];
|
|
617
610
|
const fullIssue = {
|
|
618
611
|
...issueData,
|
|
619
612
|
path: fullPath
|
|
@@ -924,11 +917,11 @@ var init_types = __esm({
|
|
|
924
917
|
init_parseUtil();
|
|
925
918
|
init_util();
|
|
926
919
|
ParseInputLazyPath = class {
|
|
927
|
-
constructor(parent, value,
|
|
920
|
+
constructor(parent, value, path32, key) {
|
|
928
921
|
this._cachedPath = [];
|
|
929
922
|
this.parent = parent;
|
|
930
923
|
this.data = value;
|
|
931
|
-
this._path =
|
|
924
|
+
this._path = path32;
|
|
932
925
|
this._key = key;
|
|
933
926
|
}
|
|
934
927
|
get path() {
|
|
@@ -4338,9 +4331,11 @@ var init_attachment_schemas = __esm({
|
|
|
4338
4331
|
stale: external_exports.boolean().optional()
|
|
4339
4332
|
});
|
|
4340
4333
|
AttachmentSignUrlArgs = external_exports.object({
|
|
4341
|
-
/**
|
|
4342
|
-
|
|
4343
|
-
/**
|
|
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 字段) */
|
|
4344
4339
|
ttlSeconds: external_exports.number().int().positive().nullable().optional()
|
|
4345
4340
|
});
|
|
4346
4341
|
AttachmentSignUrlResponseSchema = external_exports.object({
|
|
@@ -4386,7 +4381,7 @@ var init_attachment_schemas = __esm({
|
|
|
4386
4381
|
});
|
|
4387
4382
|
|
|
4388
4383
|
// ../protocol/src/persona-schemas.ts
|
|
4389
|
-
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema
|
|
4384
|
+
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema;
|
|
4390
4385
|
var init_persona_schemas = __esm({
|
|
4391
4386
|
"../protocol/src/persona-schemas.ts"() {
|
|
4392
4387
|
"use strict";
|
|
@@ -4404,7 +4399,13 @@ var init_persona_schemas = __esm({
|
|
|
4404
4399
|
// 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
|
|
4405
4400
|
// for forward compatibility, consumer fallbacks to default when key not found.
|
|
4406
4401
|
iconKey: external_exports.string().optional(),
|
|
4407
|
-
|
|
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(),
|
|
4408
4409
|
createdAt: external_exports.number(),
|
|
4409
4410
|
updatedAt: external_exports.number()
|
|
4410
4411
|
}).strict();
|
|
@@ -4465,126 +4466,11 @@ var init_persona_schemas = __esm({
|
|
|
4465
4466
|
personaId: external_exports.string().min(1),
|
|
4466
4467
|
token: external_exports.string().min(1)
|
|
4467
4468
|
});
|
|
4468
|
-
PersonaAppendOwnerMessageArgsSchema = external_exports.object({
|
|
4469
|
-
personaId: external_exports.string().min(1),
|
|
4470
|
-
subSessionId: external_exports.string().min(1),
|
|
4471
|
-
text: external_exports.string().min(1)
|
|
4472
|
-
});
|
|
4473
|
-
FRAME_TYPE_CHAT_OPEN = "chat:open";
|
|
4474
|
-
FRAME_TYPE_CHAT_RENAME = "chat:rename";
|
|
4475
|
-
FRAME_TYPE_CHAT_DELETE = "chat:delete";
|
|
4476
|
-
FRAME_TYPE_CHAT_LIST = "chat:list";
|
|
4477
|
-
FRAME_TYPE_CHAT_CREATED = "chat:created";
|
|
4478
|
-
FRAME_TYPE_CHAT_RENAMED = "chat:renamed";
|
|
4479
|
-
FRAME_TYPE_CHAT_DELETED = "chat:deleted";
|
|
4480
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED = "persona:listenerChat:created";
|
|
4481
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED = "persona:listenerChat:renamed";
|
|
4482
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED = "persona:listenerChat:deleted";
|
|
4483
|
-
FRAME_TYPE_PERSONA_LISTENER_STATUS = "persona:listenerStatus";
|
|
4484
|
-
ChatSummarySchema = external_exports.object({
|
|
4485
|
-
sessionId: external_exports.string().min(1),
|
|
4486
|
-
/** 派生 sessionId 时使用的原始 chatId(永远三元组 hash 派生,无 default 特例) */
|
|
4487
|
-
chatId: external_exports.string().min(1),
|
|
4488
|
-
/** sub-session label,listener 视为 chat 名 */
|
|
4489
|
-
label: external_exports.string(),
|
|
4490
|
-
/** SessionFile.createdAt(ISO string) */
|
|
4491
|
-
createdAt: external_exports.string()
|
|
4492
|
-
});
|
|
4493
|
-
ChatOpenRequestSchema = external_exports.object({
|
|
4494
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_OPEN),
|
|
4495
|
-
/** 客户端生成的 chatId(uuid 推荐);daemon 用三元组派生 sub-session sessionId */
|
|
4496
|
-
chatId: external_exports.string().min(1),
|
|
4497
|
-
/** 不存在时是否自动创建。true = 创建(典型:新建对话);false/缺省 = 命中已有,否则 NOT_FOUND */
|
|
4498
|
-
autoCreate: external_exports.boolean().optional(),
|
|
4499
|
-
requestId: external_exports.string().min(1).optional()
|
|
4500
|
-
});
|
|
4501
|
-
ChatRenameRequestSchema = external_exports.object({
|
|
4502
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
4503
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4504
|
-
sessionId: external_exports.string().min(1),
|
|
4505
|
-
label: external_exports.string().min(1)
|
|
4506
|
-
});
|
|
4507
|
-
ChatDeleteRequestSchema = external_exports.object({
|
|
4508
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
4509
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4510
|
-
sessionId: external_exports.string().min(1)
|
|
4511
|
-
});
|
|
4512
|
-
ChatRenameResponseSchema = external_exports.object({
|
|
4513
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
4514
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4515
|
-
ok: external_exports.literal(true)
|
|
4516
|
-
});
|
|
4517
|
-
ChatDeleteResponseSchema = external_exports.object({
|
|
4518
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
4519
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4520
|
-
ok: external_exports.literal(true)
|
|
4521
|
-
});
|
|
4522
|
-
ChatListPushSchema = external_exports.object({
|
|
4523
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_LIST),
|
|
4524
|
-
chats: external_exports.array(ChatSummarySchema)
|
|
4525
|
-
});
|
|
4526
|
-
ChatCreatedFrameSchema = external_exports.object({
|
|
4527
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_CREATED),
|
|
4528
|
-
sessionId: external_exports.string().min(1),
|
|
4529
|
-
chatId: external_exports.string().min(1),
|
|
4530
|
-
label: external_exports.string().optional(),
|
|
4531
|
-
/** ISO timestamp */
|
|
4532
|
-
createdAt: external_exports.string().min(1)
|
|
4533
|
-
});
|
|
4534
|
-
ChatRenamedFrameSchema = external_exports.object({
|
|
4535
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAMED),
|
|
4536
|
-
sessionId: external_exports.string().min(1),
|
|
4537
|
-
label: external_exports.string().min(1)
|
|
4538
|
-
});
|
|
4539
|
-
ChatDeletedFrameSchema = external_exports.object({
|
|
4540
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETED),
|
|
4541
|
-
sessionId: external_exports.string().min(1)
|
|
4542
|
-
});
|
|
4543
|
-
PersonaListenerChatCreatedFrameSchema = external_exports.object({
|
|
4544
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED),
|
|
4545
|
-
personaId: external_exports.string().min(1),
|
|
4546
|
-
/** owner 同时持有 token 字符串:sidebar 已经按 token 维度渲染 listener leaf,
|
|
4547
|
-
* push 帧用 token 直接定位归属 leaf。token 不是密钥,owner 本来就持有。 */
|
|
4548
|
-
token: external_exports.string().min(1),
|
|
4549
|
-
sessionId: external_exports.string().min(1),
|
|
4550
|
-
chatId: external_exports.string().min(1),
|
|
4551
|
-
label: external_exports.string().optional(),
|
|
4552
|
-
createdAt: external_exports.string().min(1)
|
|
4553
|
-
});
|
|
4554
|
-
PersonaListenerChatRenamedFrameSchema = external_exports.object({
|
|
4555
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED),
|
|
4556
|
-
personaId: external_exports.string().min(1),
|
|
4557
|
-
token: external_exports.string().min(1),
|
|
4558
|
-
sessionId: external_exports.string().min(1),
|
|
4559
|
-
label: external_exports.string().min(1)
|
|
4560
|
-
});
|
|
4561
|
-
PersonaListenerChatDeletedFrameSchema = external_exports.object({
|
|
4562
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED),
|
|
4563
|
-
personaId: external_exports.string().min(1),
|
|
4564
|
-
token: external_exports.string().min(1),
|
|
4565
|
-
sessionId: external_exports.string().min(1),
|
|
4566
|
-
/** 原始 chatId(用于 owner UI 比对 personaView.chatId 清理 stale selection) */
|
|
4567
|
-
chatId: external_exports.string().min(1)
|
|
4568
|
-
});
|
|
4569
|
-
PersonaListenerStatusFrameSchema = external_exports.object({
|
|
4570
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_STATUS),
|
|
4571
|
-
personaId: external_exports.string().min(1),
|
|
4572
|
-
token: external_exports.string().min(1),
|
|
4573
|
-
status: external_exports.enum(["online", "offline"]),
|
|
4574
|
-
activeWsCount: external_exports.number().int().nonnegative()
|
|
4575
|
-
});
|
|
4576
|
-
PersonaListListenerChatsForTokenArgsSchema = external_exports.object({
|
|
4577
|
-
personaId: external_exports.string().min(1),
|
|
4578
|
-
token: external_exports.string().min(1)
|
|
4579
|
-
});
|
|
4580
|
-
PersonaListListenerChatsForTokenResponseSchema = external_exports.object({
|
|
4581
|
-
chats: external_exports.array(ChatSummarySchema)
|
|
4582
|
-
});
|
|
4583
4469
|
}
|
|
4584
4470
|
});
|
|
4585
4471
|
|
|
4586
4472
|
// ../protocol/src/schemas.ts
|
|
4587
|
-
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,
|
|
4473
|
+
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;
|
|
4588
4474
|
var init_schemas = __esm({
|
|
4589
4475
|
"../protocol/src/schemas.ts"() {
|
|
4590
4476
|
"use strict";
|
|
@@ -4674,9 +4560,6 @@ var init_schemas = __esm({
|
|
|
4674
4560
|
pinSortOrder: external_exports.number().int().nullable().optional(),
|
|
4675
4561
|
// 用户在 NewSessionDialog 或 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
4676
4562
|
iconKey: external_exports.string().optional(),
|
|
4677
|
-
// NewSessionDialog 勾选 worktree 创建的 session 才会持久化这两个字段
|
|
4678
|
-
worktreeRoot: external_exports.string().min(1).optional(),
|
|
4679
|
-
worktreeBranch: external_exports.string().min(1).optional(),
|
|
4680
4563
|
// 由 session:fork 派生的 session 在 create 时记录源 session 的 sessionId(稳定 key,
|
|
4681
4564
|
// 不受 parent clear / re-spawn 影响);sidebar 据此挂 fork 图标。非 fork session 该字段缺省。
|
|
4682
4565
|
// 旧字段 forkedFromToolSessionId 已弃用,daemon 启动时一次性迁移老数据
|
|
@@ -4897,9 +4780,6 @@ var init_schemas = __esm({
|
|
|
4897
4780
|
effort: external_exports.string().optional(),
|
|
4898
4781
|
// 用户在 NewSessionDialog 选择的 icon 标识;UI 端做 enum 兜底
|
|
4899
4782
|
iconKey: external_exports.string().optional(),
|
|
4900
|
-
// NewSessionDialog 勾选 worktree 时由 UI 传入,daemon 原样持久化,不做额外副作用
|
|
4901
|
-
worktreeRoot: external_exports.string().min(1).optional(),
|
|
4902
|
-
worktreeBranch: external_exports.string().min(1).optional(),
|
|
4903
4783
|
// session:fork 派生 session 时由 UI 传入源 sessionId,daemon 写到 SessionFile.forkedFromSessionId
|
|
4904
4784
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4905
4785
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
@@ -5064,25 +4944,6 @@ var init_schemas = __esm({
|
|
|
5064
4944
|
branches: external_exports.array(external_exports.string()),
|
|
5065
4945
|
head: external_exports.string().nullable()
|
|
5066
4946
|
});
|
|
5067
|
-
GitWorktreePrefixArgs = external_exports.object({}).optional();
|
|
5068
|
-
GitWorktreePrefixResponseSchema = external_exports.object({
|
|
5069
|
-
prefix: external_exports.string().min(1)
|
|
5070
|
-
});
|
|
5071
|
-
GitWorktreeCreateArgs = external_exports.object({
|
|
5072
|
-
cwd: external_exports.string().min(1),
|
|
5073
|
-
baseBranch: external_exports.string().min(1),
|
|
5074
|
-
label: external_exports.string().min(1)
|
|
5075
|
-
});
|
|
5076
|
-
GitWorktreeCreateResponseSchema = external_exports.object({
|
|
5077
|
-
worktreeRoot: external_exports.string().min(1),
|
|
5078
|
-
branch: external_exports.string().min(1),
|
|
5079
|
-
baseBranch: external_exports.string().min(1)
|
|
5080
|
-
});
|
|
5081
|
-
GitWorktreeRemoveArgs = external_exports.object({
|
|
5082
|
-
worktreeRoot: external_exports.string().min(1),
|
|
5083
|
-
worktreeBranch: external_exports.string().min(1)
|
|
5084
|
-
});
|
|
5085
|
-
GitWorktreeRemoveResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
|
|
5086
4947
|
HistoryRecentDirsArgs = external_exports.object({}).optional();
|
|
5087
4948
|
RecentDirEntrySchema = external_exports.object({
|
|
5088
4949
|
cwd: external_exports.string().min(1),
|
|
@@ -5469,8 +5330,8 @@ var require_req = __commonJS({
|
|
|
5469
5330
|
if (req.originalUrl) {
|
|
5470
5331
|
_req.url = req.originalUrl;
|
|
5471
5332
|
} else {
|
|
5472
|
-
const
|
|
5473
|
-
_req.url = typeof
|
|
5333
|
+
const path32 = req.path;
|
|
5334
|
+
_req.url = typeof path32 === "string" ? path32 : req.url ? req.url.path || req.url : void 0;
|
|
5474
5335
|
}
|
|
5475
5336
|
if (req.query) {
|
|
5476
5337
|
_req.query = req.query;
|
|
@@ -5635,14 +5496,14 @@ var require_redact = __commonJS({
|
|
|
5635
5496
|
}
|
|
5636
5497
|
return obj;
|
|
5637
5498
|
}
|
|
5638
|
-
function parsePath(
|
|
5499
|
+
function parsePath(path32) {
|
|
5639
5500
|
const parts = [];
|
|
5640
5501
|
let current = "";
|
|
5641
5502
|
let inBrackets = false;
|
|
5642
5503
|
let inQuotes = false;
|
|
5643
5504
|
let quoteChar = "";
|
|
5644
|
-
for (let i = 0; i <
|
|
5645
|
-
const char =
|
|
5505
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5506
|
+
const char = path32[i];
|
|
5646
5507
|
if (!inBrackets && char === ".") {
|
|
5647
5508
|
if (current) {
|
|
5648
5509
|
parts.push(current);
|
|
@@ -5773,10 +5634,10 @@ var require_redact = __commonJS({
|
|
|
5773
5634
|
return current;
|
|
5774
5635
|
}
|
|
5775
5636
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5776
|
-
for (const
|
|
5777
|
-
const parts = parsePath(
|
|
5637
|
+
for (const path32 of paths) {
|
|
5638
|
+
const parts = parsePath(path32);
|
|
5778
5639
|
if (parts.includes("*")) {
|
|
5779
|
-
redactWildcardPath(obj, parts, censor,
|
|
5640
|
+
redactWildcardPath(obj, parts, censor, path32, remove);
|
|
5780
5641
|
} else {
|
|
5781
5642
|
if (remove) {
|
|
5782
5643
|
removeKey(obj, parts);
|
|
@@ -5861,8 +5722,8 @@ var require_redact = __commonJS({
|
|
|
5861
5722
|
}
|
|
5862
5723
|
} else {
|
|
5863
5724
|
if (afterWildcard.includes("*")) {
|
|
5864
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5865
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5725
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
5726
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path32];
|
|
5866
5727
|
return censor(value, fullPath);
|
|
5867
5728
|
} : censor;
|
|
5868
5729
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5897,8 +5758,8 @@ var require_redact = __commonJS({
|
|
|
5897
5758
|
return null;
|
|
5898
5759
|
}
|
|
5899
5760
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5900
|
-
for (const
|
|
5901
|
-
const parts = parsePath(
|
|
5761
|
+
for (const path32 of pathsToClone) {
|
|
5762
|
+
const parts = parsePath(path32);
|
|
5902
5763
|
let current = pathStructure;
|
|
5903
5764
|
for (let i = 0; i < parts.length; i++) {
|
|
5904
5765
|
const part = parts[i];
|
|
@@ -5950,24 +5811,24 @@ var require_redact = __commonJS({
|
|
|
5950
5811
|
}
|
|
5951
5812
|
return cloneSelectively(obj, pathStructure);
|
|
5952
5813
|
}
|
|
5953
|
-
function validatePath(
|
|
5954
|
-
if (typeof
|
|
5814
|
+
function validatePath(path32) {
|
|
5815
|
+
if (typeof path32 !== "string") {
|
|
5955
5816
|
throw new Error("Paths must be (non-empty) strings");
|
|
5956
5817
|
}
|
|
5957
|
-
if (
|
|
5818
|
+
if (path32 === "") {
|
|
5958
5819
|
throw new Error("Invalid redaction path ()");
|
|
5959
5820
|
}
|
|
5960
|
-
if (
|
|
5961
|
-
throw new Error(`Invalid redaction path (${
|
|
5821
|
+
if (path32.includes("..")) {
|
|
5822
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5962
5823
|
}
|
|
5963
|
-
if (
|
|
5964
|
-
throw new Error(`Invalid redaction path (${
|
|
5824
|
+
if (path32.includes(",")) {
|
|
5825
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5965
5826
|
}
|
|
5966
5827
|
let bracketCount = 0;
|
|
5967
5828
|
let inQuotes = false;
|
|
5968
5829
|
let quoteChar = "";
|
|
5969
|
-
for (let i = 0; i <
|
|
5970
|
-
const char =
|
|
5830
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5831
|
+
const char = path32[i];
|
|
5971
5832
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
5972
5833
|
if (!inQuotes) {
|
|
5973
5834
|
inQuotes = true;
|
|
@@ -5981,20 +5842,20 @@ var require_redact = __commonJS({
|
|
|
5981
5842
|
} else if (char === "]" && !inQuotes) {
|
|
5982
5843
|
bracketCount--;
|
|
5983
5844
|
if (bracketCount < 0) {
|
|
5984
|
-
throw new Error(`Invalid redaction path (${
|
|
5845
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5985
5846
|
}
|
|
5986
5847
|
}
|
|
5987
5848
|
}
|
|
5988
5849
|
if (bracketCount !== 0) {
|
|
5989
|
-
throw new Error(`Invalid redaction path (${
|
|
5850
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5990
5851
|
}
|
|
5991
5852
|
}
|
|
5992
5853
|
function validatePaths(paths) {
|
|
5993
5854
|
if (!Array.isArray(paths)) {
|
|
5994
5855
|
throw new TypeError("paths must be an array");
|
|
5995
5856
|
}
|
|
5996
|
-
for (const
|
|
5997
|
-
validatePath(
|
|
5857
|
+
for (const path32 of paths) {
|
|
5858
|
+
validatePath(path32);
|
|
5998
5859
|
}
|
|
5999
5860
|
}
|
|
6000
5861
|
function slowRedact(options = {}) {
|
|
@@ -6162,8 +6023,8 @@ var require_redaction = __commonJS({
|
|
|
6162
6023
|
if (shape[k2] === null) {
|
|
6163
6024
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6164
6025
|
} else {
|
|
6165
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6166
|
-
return censor(value, [k2, ...
|
|
6026
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
6027
|
+
return censor(value, [k2, ...path32]);
|
|
6167
6028
|
} : censor;
|
|
6168
6029
|
o[k2] = Redact({
|
|
6169
6030
|
paths: shape[k2],
|
|
@@ -6384,7 +6245,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6384
6245
|
var fs28 = require("fs");
|
|
6385
6246
|
var EventEmitter2 = require("events");
|
|
6386
6247
|
var inherits = require("util").inherits;
|
|
6387
|
-
var
|
|
6248
|
+
var path32 = require("path");
|
|
6388
6249
|
var sleep = require_atomic_sleep();
|
|
6389
6250
|
var assert = require("assert");
|
|
6390
6251
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6438,7 +6299,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6438
6299
|
const mode = sonic.mode;
|
|
6439
6300
|
if (sonic.sync) {
|
|
6440
6301
|
try {
|
|
6441
|
-
if (sonic.mkdir) fs28.mkdirSync(
|
|
6302
|
+
if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
|
|
6442
6303
|
const fd = fs28.openSync(file, flags, mode);
|
|
6443
6304
|
fileOpened(null, fd);
|
|
6444
6305
|
} catch (err) {
|
|
@@ -6446,7 +6307,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6446
6307
|
throw err;
|
|
6447
6308
|
}
|
|
6448
6309
|
} else if (sonic.mkdir) {
|
|
6449
|
-
fs28.mkdir(
|
|
6310
|
+
fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
|
|
6450
6311
|
if (err) return fileOpened(err);
|
|
6451
6312
|
fs28.open(file, flags, mode, fileOpened);
|
|
6452
6313
|
});
|
|
@@ -9306,7 +9167,7 @@ var require_multistream = __commonJS({
|
|
|
9306
9167
|
var require_pino = __commonJS({
|
|
9307
9168
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
|
|
9308
9169
|
"use strict";
|
|
9309
|
-
var
|
|
9170
|
+
var os15 = require("os");
|
|
9310
9171
|
var stdSerializers = require_pino_std_serializers();
|
|
9311
9172
|
var caller = require_caller();
|
|
9312
9173
|
var redaction = require_redaction();
|
|
@@ -9353,7 +9214,7 @@ var require_pino = __commonJS({
|
|
|
9353
9214
|
} = symbols;
|
|
9354
9215
|
var { epochTime, nullTime } = time;
|
|
9355
9216
|
var { pid } = process;
|
|
9356
|
-
var hostname =
|
|
9217
|
+
var hostname = os15.hostname();
|
|
9357
9218
|
var defaultErrorSerializer = stdSerializers.err;
|
|
9358
9219
|
var defaultOptions = {
|
|
9359
9220
|
level: "info",
|
|
@@ -10077,11 +9938,11 @@ var init_lib = __esm({
|
|
|
10077
9938
|
}
|
|
10078
9939
|
}
|
|
10079
9940
|
},
|
|
10080
|
-
addToPath: function addToPath(
|
|
10081
|
-
var last =
|
|
9941
|
+
addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
|
|
9942
|
+
var last = path32.lastComponent;
|
|
10082
9943
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10083
9944
|
return {
|
|
10084
|
-
oldPos:
|
|
9945
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10085
9946
|
lastComponent: {
|
|
10086
9947
|
count: last.count + 1,
|
|
10087
9948
|
added,
|
|
@@ -10091,7 +9952,7 @@ var init_lib = __esm({
|
|
|
10091
9952
|
};
|
|
10092
9953
|
} else {
|
|
10093
9954
|
return {
|
|
10094
|
-
oldPos:
|
|
9955
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10095
9956
|
lastComponent: {
|
|
10096
9957
|
count: 1,
|
|
10097
9958
|
added,
|
|
@@ -10522,10 +10383,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10522
10383
|
const memories = raw.map((m2) => {
|
|
10523
10384
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10524
10385
|
const rec = m2;
|
|
10525
|
-
const
|
|
10386
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
10526
10387
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10527
|
-
if (!
|
|
10528
|
-
const entry = { path:
|
|
10388
|
+
if (!path32 || content == null) return null;
|
|
10389
|
+
const entry = { path: path32, content };
|
|
10529
10390
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10530
10391
|
return entry;
|
|
10531
10392
|
}).filter((m2) => m2 !== null);
|
|
@@ -11329,10 +11190,10 @@ function parseAttachment(obj) {
|
|
|
11329
11190
|
const memories = raw.map((m2) => {
|
|
11330
11191
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11331
11192
|
const rec = m2;
|
|
11332
|
-
const
|
|
11193
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
11333
11194
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11334
|
-
if (!
|
|
11335
|
-
const out = { path:
|
|
11195
|
+
if (!path32 || content == null) return null;
|
|
11196
|
+
const out = { path: path32, content };
|
|
11336
11197
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11337
11198
|
return out;
|
|
11338
11199
|
}).filter((m2) => m2 !== null);
|
|
@@ -20246,7 +20107,7 @@ var require_websocket_server = __commonJS({
|
|
|
20246
20107
|
// src/run-case/recorder.ts
|
|
20247
20108
|
function startRunCaseRecorder(opts) {
|
|
20248
20109
|
const now = opts.now ?? Date.now;
|
|
20249
|
-
const dir =
|
|
20110
|
+
const dir = import_node_path28.default.dirname(opts.recordPath);
|
|
20250
20111
|
let stream = null;
|
|
20251
20112
|
let closing = false;
|
|
20252
20113
|
let closedSettled = false;
|
|
@@ -20286,12 +20147,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20286
20147
|
};
|
|
20287
20148
|
return { tap, close, closed };
|
|
20288
20149
|
}
|
|
20289
|
-
var import_node_fs25,
|
|
20150
|
+
var import_node_fs25, import_node_path28;
|
|
20290
20151
|
var init_recorder = __esm({
|
|
20291
20152
|
"src/run-case/recorder.ts"() {
|
|
20292
20153
|
"use strict";
|
|
20293
20154
|
import_node_fs25 = __toESM(require("fs"), 1);
|
|
20294
|
-
|
|
20155
|
+
import_node_path28 = __toESM(require("path"), 1);
|
|
20295
20156
|
}
|
|
20296
20157
|
});
|
|
20297
20158
|
|
|
@@ -20334,7 +20195,7 @@ var init_wire = __esm({
|
|
|
20334
20195
|
// src/run-case/controller.ts
|
|
20335
20196
|
async function runController(opts) {
|
|
20336
20197
|
const now = opts.now ?? Date.now;
|
|
20337
|
-
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(
|
|
20198
|
+
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path29.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
|
|
20338
20199
|
const ownsCwd = opts.cwd === void 0;
|
|
20339
20200
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20340
20201
|
const spawnCtx = { cwd };
|
|
@@ -20501,13 +20362,13 @@ async function runController(opts) {
|
|
|
20501
20362
|
}
|
|
20502
20363
|
return exitCode ?? 0;
|
|
20503
20364
|
}
|
|
20504
|
-
var import_node_fs26,
|
|
20365
|
+
var import_node_fs26, import_node_os14, import_node_path29;
|
|
20505
20366
|
var init_controller = __esm({
|
|
20506
20367
|
"src/run-case/controller.ts"() {
|
|
20507
20368
|
"use strict";
|
|
20508
20369
|
import_node_fs26 = require("fs");
|
|
20509
|
-
|
|
20510
|
-
|
|
20370
|
+
import_node_os14 = __toESM(require("os"), 1);
|
|
20371
|
+
import_node_path29 = __toESM(require("path"), 1);
|
|
20511
20372
|
init_claude();
|
|
20512
20373
|
init_stdout_splitter();
|
|
20513
20374
|
init_permission_stdio();
|
|
@@ -20739,7 +20600,7 @@ Env (advanced):
|
|
|
20739
20600
|
`;
|
|
20740
20601
|
|
|
20741
20602
|
// src/index.ts
|
|
20742
|
-
var
|
|
20603
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
20743
20604
|
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
20744
20605
|
|
|
20745
20606
|
// src/logger.ts
|
|
@@ -22436,8 +22297,6 @@ var SessionManager = class {
|
|
|
22436
22297
|
permissionMode: args.permissionMode,
|
|
22437
22298
|
effort: args.effort,
|
|
22438
22299
|
iconKey: args.iconKey,
|
|
22439
|
-
worktreeRoot: args.worktreeRoot,
|
|
22440
|
-
worktreeBranch: args.worktreeBranch,
|
|
22441
22300
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
22442
22301
|
ownerPersonaId: args.ownerPersonaId,
|
|
22443
22302
|
createdAt: iso,
|
|
@@ -23460,15 +23319,6 @@ var PersonaRegistry = class {
|
|
|
23460
23319
|
list() {
|
|
23461
23320
|
return Array.from(this.cache.values());
|
|
23462
23321
|
}
|
|
23463
|
-
verifyToken(personaId, token) {
|
|
23464
|
-
const persona = this.cache.get(personaId);
|
|
23465
|
-
if (!persona) return { ok: false, code: "PERSONA_DELETED" };
|
|
23466
|
-
if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
|
|
23467
|
-
const entry = persona.tokenMap[token];
|
|
23468
|
-
if (!entry) return { ok: false, code: "TOKEN_INVALID" };
|
|
23469
|
-
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
23470
|
-
return { ok: true, label: entry.label };
|
|
23471
|
-
}
|
|
23472
23322
|
/**
|
|
23473
23323
|
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23474
23324
|
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
@@ -23478,7 +23328,7 @@ var PersonaRegistry = class {
|
|
|
23478
23328
|
if (!token) return null;
|
|
23479
23329
|
for (const persona of this.cache.values()) {
|
|
23480
23330
|
if (!persona.public) continue;
|
|
23481
|
-
const entry = persona.tokenMap[token];
|
|
23331
|
+
const entry = persona.tokenMap?.[token];
|
|
23482
23332
|
if (!entry || entry.revoked) continue;
|
|
23483
23333
|
return { personaId: persona.personaId, label: entry.label };
|
|
23484
23334
|
}
|
|
@@ -23777,6 +23627,8 @@ var PersonaManager = class {
|
|
|
23777
23627
|
model: args.model,
|
|
23778
23628
|
public: args.public ?? false,
|
|
23779
23629
|
iconKey: args.iconKey,
|
|
23630
|
+
// 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
|
|
23631
|
+
// file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
|
|
23780
23632
|
tokenMap: {},
|
|
23781
23633
|
createdAt: now,
|
|
23782
23634
|
updatedAt: now
|
|
@@ -23820,15 +23672,16 @@ var PersonaManager = class {
|
|
|
23820
23672
|
}
|
|
23821
23673
|
/**
|
|
23822
23674
|
* 删除 persona。
|
|
23823
|
-
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/
|
|
23824
|
-
* 其中包含 .clawd/sub-sessions/——无需额外清理。
|
|
23825
|
-
* 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
|
|
23675
|
+
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
|
|
23826
23676
|
*/
|
|
23827
23677
|
delete(personaId) {
|
|
23828
23678
|
this.deps.store.remove(personaId);
|
|
23829
23679
|
this.deps.registry.remove(personaId);
|
|
23830
23680
|
}
|
|
23831
|
-
/**
|
|
23681
|
+
/**
|
|
23682
|
+
* 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
|
|
23683
|
+
* 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
|
|
23684
|
+
*/
|
|
23832
23685
|
issueToken(personaId, label) {
|
|
23833
23686
|
const existing = this.deps.registry.get(personaId);
|
|
23834
23687
|
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
@@ -23840,22 +23693,23 @@ var PersonaManager = class {
|
|
|
23840
23693
|
};
|
|
23841
23694
|
const updated = {
|
|
23842
23695
|
...existing,
|
|
23843
|
-
tokenMap: { ...existing.tokenMap, [token]: entry },
|
|
23696
|
+
tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
|
|
23844
23697
|
updatedAt: Date.now()
|
|
23845
23698
|
};
|
|
23846
23699
|
this.deps.store.writeMeta(updated);
|
|
23847
23700
|
this.deps.registry.set(updated);
|
|
23848
23701
|
return { token, persona: updated };
|
|
23849
23702
|
}
|
|
23703
|
+
/** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
|
|
23850
23704
|
revokeToken(personaId, token) {
|
|
23851
23705
|
const existing = this.deps.registry.get(personaId);
|
|
23852
23706
|
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23853
|
-
const tokenEntry = existing.tokenMap[token];
|
|
23707
|
+
const tokenEntry = existing.tokenMap?.[token];
|
|
23854
23708
|
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
23855
23709
|
const updated = {
|
|
23856
23710
|
...existing,
|
|
23857
23711
|
tokenMap: {
|
|
23858
|
-
...existing.tokenMap,
|
|
23712
|
+
...existing.tokenMap ?? {},
|
|
23859
23713
|
[token]: { ...tokenEntry, revoked: true }
|
|
23860
23714
|
},
|
|
23861
23715
|
updatedAt: Date.now()
|
|
@@ -23864,70 +23718,6 @@ var PersonaManager = class {
|
|
|
23864
23718
|
this.deps.registry.set(updated);
|
|
23865
23719
|
return updated;
|
|
23866
23720
|
}
|
|
23867
|
-
/**
|
|
23868
|
-
* 拿到(或按需创建)该 (token, chatId) 对应的 sub-session。subSessionId 由 personaId + token +
|
|
23869
|
-
* chatId 三元组 hash 派生,保证 idempotent:同一 tuple 永远复用同一个 sub-session。
|
|
23870
|
-
*
|
|
23871
|
-
* chatId 必填 —— listener 协议从 v2 起拆 phase:客户端 auth 后通过 chat:open(chatId, autoCreate?)
|
|
23872
|
-
* 显式进入某个 chat;不再有 'default' 默认值。chatId 通常是客户端生成的 uuid,落 listener 端
|
|
23873
|
-
* 持久化由客户端负责(daemon 不替客户端记忆"最近哪个 chat")。
|
|
23874
|
-
*
|
|
23875
|
-
* 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
|
|
23876
|
-
* (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
|
|
23877
|
-
*/
|
|
23878
|
-
getOrCreateSubSession(personaId, token, chatId) {
|
|
23879
|
-
const persona = this.deps.registry.get(personaId);
|
|
23880
|
-
if (!persona) throw new Error(`persona not found: ${personaId}`);
|
|
23881
|
-
if (!chatId) throw new Error("chatId is required");
|
|
23882
|
-
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
23883
|
-
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
23884
|
-
const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
23885
|
-
if (existing) {
|
|
23886
|
-
return { sessionFile: existing, isNew: false };
|
|
23887
|
-
}
|
|
23888
|
-
const tokenEntry = persona.tokenMap[token];
|
|
23889
|
-
const subLabel = tokenEntry?.label ?? "unknown";
|
|
23890
|
-
const sessionFile = this.deps.sessionManager.createForScope({
|
|
23891
|
-
sessionId: subSessionId,
|
|
23892
|
-
scope,
|
|
23893
|
-
cwd: this.deps.store.personaDirPath(personaId),
|
|
23894
|
-
tool: "claude",
|
|
23895
|
-
label: subLabel,
|
|
23896
|
-
model: persona.model,
|
|
23897
|
-
chatId
|
|
23898
|
-
});
|
|
23899
|
-
return { sessionFile, isNew: true };
|
|
23900
|
-
}
|
|
23901
|
-
/**
|
|
23902
|
-
* 检查 (token, chatId) 对应的 sub-session 是否存在;不存在返回 null。
|
|
23903
|
-
* chat:open(autoCreate=false) 路径用:listener 想"只切已有 chat"时不能撞 auto-create。
|
|
23904
|
-
*/
|
|
23905
|
-
getSubSession(personaId, token, chatId) {
|
|
23906
|
-
if (!chatId) return null;
|
|
23907
|
-
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
23908
|
-
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
23909
|
-
return this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
23910
|
-
}
|
|
23911
|
-
/**
|
|
23912
|
-
* 校验 sessionId 是否属于 (personaId, token) tuple(前 12 字符 tokenHash 一致)。
|
|
23913
|
-
* chat:rename / chat:delete 用:listener 只能操作自己 tuple 内的 chat。
|
|
23914
|
-
*/
|
|
23915
|
-
tokenPrefixFor(personaId, token) {
|
|
23916
|
-
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23917
|
-
return `${personaId}-${tokenHash}`;
|
|
23918
|
-
}
|
|
23919
|
-
/**
|
|
23920
|
-
* 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
|
|
23921
|
-
* 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
|
|
23922
|
-
*/
|
|
23923
|
-
appendOwnerMessage(personaId, subSessionId, text) {
|
|
23924
|
-
this.deps.sessionManager.injectOwnerMessage({
|
|
23925
|
-
sessionId: subSessionId,
|
|
23926
|
-
scope: { kind: "persona", personaId, mode: "listener" },
|
|
23927
|
-
text
|
|
23928
|
-
});
|
|
23929
|
-
}
|
|
23930
|
-
// ---------------- 内部 ----------------
|
|
23931
23721
|
/**
|
|
23932
23722
|
* label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
|
|
23933
23723
|
* base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
|
|
@@ -23943,15 +23733,6 @@ var PersonaManager = class {
|
|
|
23943
23733
|
}
|
|
23944
23734
|
throw new Error(`failed to generate unique personaId for label=${label}`);
|
|
23945
23735
|
}
|
|
23946
|
-
/**
|
|
23947
|
-
* subSessionId 派生:永远三元组 `${personaId}-${tokenHash12}-${chatHash8}`,无 default 特例。
|
|
23948
|
-
* 旧 v1 实现保留 default 二元组兼容已废除。
|
|
23949
|
-
*/
|
|
23950
|
-
deriveSubSessionId(personaId, token, chatId) {
|
|
23951
|
-
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23952
|
-
const chatHash = import_node_crypto3.default.createHash("sha256").update(chatId).digest("hex").slice(0, 8);
|
|
23953
|
-
return `${personaId}-${tokenHash}-${chatHash}`;
|
|
23954
|
-
}
|
|
23955
23736
|
};
|
|
23956
23737
|
|
|
23957
23738
|
// src/persona/seed.ts
|
|
@@ -25426,7 +25207,6 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
25426
25207
|
|
|
25427
25208
|
// src/transport/local-ws-server.ts
|
|
25428
25209
|
var import_node_http = __toESM(require("http"), 1);
|
|
25429
|
-
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
25430
25210
|
var LocalWsServer = class {
|
|
25431
25211
|
constructor(opts) {
|
|
25432
25212
|
this.opts = opts;
|
|
@@ -25549,8 +25329,7 @@ var LocalWsServer = class {
|
|
|
25549
25329
|
if (!c) return;
|
|
25550
25330
|
this.safeSend(c.ws, frame);
|
|
25551
25331
|
}
|
|
25552
|
-
// URL path 路由:'/' →
|
|
25553
|
-
// '/personas/<id>' → personaBoundHandler,其它 → close 4404
|
|
25332
|
+
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25554
25333
|
routeConnection(socket, req) {
|
|
25555
25334
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
25556
25335
|
const urlPath = (() => {
|
|
@@ -25564,15 +25343,6 @@ var LocalWsServer = class {
|
|
|
25564
25343
|
this.onConnection(socket, remoteAddress);
|
|
25565
25344
|
return;
|
|
25566
25345
|
}
|
|
25567
|
-
const personaMatch = urlPath.match(PERSONA_PATH_RE);
|
|
25568
|
-
if (personaMatch && this.opts.personaBoundHandler) {
|
|
25569
|
-
this.logger?.info("persona ws connected", {
|
|
25570
|
-
personaId: personaMatch[1],
|
|
25571
|
-
remoteAddress
|
|
25572
|
-
});
|
|
25573
|
-
this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
|
|
25574
|
-
return;
|
|
25575
|
-
}
|
|
25576
25346
|
try {
|
|
25577
25347
|
socket.close(4404, "unknown path");
|
|
25578
25348
|
} catch {
|
|
@@ -25715,434 +25485,6 @@ function removeSubscription(client, sessionId) {
|
|
|
25715
25485
|
else client.subscribedSessions.set(sessionId, next);
|
|
25716
25486
|
}
|
|
25717
25487
|
|
|
25718
|
-
// src/transport/persona-bound-handler.ts
|
|
25719
|
-
init_protocol();
|
|
25720
|
-
var PersonaBoundHandler = class {
|
|
25721
|
-
constructor(deps) {
|
|
25722
|
-
this.deps = deps;
|
|
25723
|
-
}
|
|
25724
|
-
deps;
|
|
25725
|
-
// tuple fan-out 表:tokenPrefix → 同 tuple 所有 listener ws 的 send fn 集合
|
|
25726
|
-
tupleSubscribers = /* @__PURE__ */ new Map();
|
|
25727
|
-
// tuple → 当前活跃 ws 数;从 0→1 emit online;从 1→0 emit offline
|
|
25728
|
-
listenerWsCount = /* @__PURE__ */ new Map();
|
|
25729
|
-
handle(ws, personaId) {
|
|
25730
|
-
let phase = "pre-auth";
|
|
25731
|
-
let scope = null;
|
|
25732
|
-
let unsubscribeSession = null;
|
|
25733
|
-
const send = (frame) => {
|
|
25734
|
-
try {
|
|
25735
|
-
ws.send(JSON.stringify(frame));
|
|
25736
|
-
} catch (err) {
|
|
25737
|
-
this.deps.logger.warn(`persona ws send failed: ${err.message}`);
|
|
25738
|
-
}
|
|
25739
|
-
};
|
|
25740
|
-
const sendError = (requestId, code, message) => {
|
|
25741
|
-
const errFrame = { type: "error", code, message };
|
|
25742
|
-
if (requestId) errFrame.requestId = requestId;
|
|
25743
|
-
send(errFrame);
|
|
25744
|
-
};
|
|
25745
|
-
const cleanupTupleRegistration = () => {
|
|
25746
|
-
if (!scope) return;
|
|
25747
|
-
const subs = this.tupleSubscribers.get(scope.tokenPrefix);
|
|
25748
|
-
if (subs) {
|
|
25749
|
-
subs.delete(send);
|
|
25750
|
-
if (subs.size === 0) this.tupleSubscribers.delete(scope.tokenPrefix);
|
|
25751
|
-
}
|
|
25752
|
-
const before = this.listenerWsCount.get(scope.tokenPrefix) ?? 0;
|
|
25753
|
-
const after = before - 1;
|
|
25754
|
-
if (after <= 0) {
|
|
25755
|
-
this.listenerWsCount.delete(scope.tokenPrefix);
|
|
25756
|
-
this.deps.ownerBroadcast(
|
|
25757
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25758
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25759
|
-
personaId: scope.personaId,
|
|
25760
|
-
token: scope.token,
|
|
25761
|
-
status: "offline",
|
|
25762
|
-
activeWsCount: 0
|
|
25763
|
-
})
|
|
25764
|
-
);
|
|
25765
|
-
} else {
|
|
25766
|
-
this.listenerWsCount.set(scope.tokenPrefix, after);
|
|
25767
|
-
this.deps.ownerBroadcast(
|
|
25768
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25769
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25770
|
-
personaId: scope.personaId,
|
|
25771
|
-
token: scope.token,
|
|
25772
|
-
status: "online",
|
|
25773
|
-
activeWsCount: after
|
|
25774
|
-
})
|
|
25775
|
-
);
|
|
25776
|
-
}
|
|
25777
|
-
};
|
|
25778
|
-
ws.on("message", (raw) => {
|
|
25779
|
-
let parsedRaw;
|
|
25780
|
-
try {
|
|
25781
|
-
parsedRaw = JSON.parse(raw.toString());
|
|
25782
|
-
} catch {
|
|
25783
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25784
|
-
return;
|
|
25785
|
-
}
|
|
25786
|
-
if (typeof parsedRaw !== "object" || parsedRaw === null) {
|
|
25787
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25788
|
-
return;
|
|
25789
|
-
}
|
|
25790
|
-
const frame = parsedRaw;
|
|
25791
|
-
if (phase === "pre-auth") {
|
|
25792
|
-
const authParse = AuthRequestFrameSchema.safeParse(frame);
|
|
25793
|
-
if (!authParse.success) {
|
|
25794
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25795
|
-
return;
|
|
25796
|
-
}
|
|
25797
|
-
const { token } = authParse.data;
|
|
25798
|
-
const verify = this.deps.registry.verifyToken(personaId, token);
|
|
25799
|
-
if (!verify.ok) {
|
|
25800
|
-
const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
|
|
25801
|
-
ws.close(code, verify.code);
|
|
25802
|
-
return;
|
|
25803
|
-
}
|
|
25804
|
-
const persona = this.deps.registry.get(personaId);
|
|
25805
|
-
if (!persona) {
|
|
25806
|
-
ws.close(4404, "PERSONA_DELETED");
|
|
25807
|
-
return;
|
|
25808
|
-
}
|
|
25809
|
-
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
25810
|
-
scope = { personaId, token, tokenPrefix };
|
|
25811
|
-
phase = "tuple-watch";
|
|
25812
|
-
let subs = this.tupleSubscribers.get(tokenPrefix);
|
|
25813
|
-
if (!subs) {
|
|
25814
|
-
subs = /* @__PURE__ */ new Set();
|
|
25815
|
-
this.tupleSubscribers.set(tokenPrefix, subs);
|
|
25816
|
-
}
|
|
25817
|
-
subs.add(send);
|
|
25818
|
-
const nextCount = (this.listenerWsCount.get(tokenPrefix) ?? 0) + 1;
|
|
25819
|
-
this.listenerWsCount.set(tokenPrefix, nextCount);
|
|
25820
|
-
this.deps.ownerBroadcast(
|
|
25821
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25822
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25823
|
-
personaId,
|
|
25824
|
-
token,
|
|
25825
|
-
status: "online",
|
|
25826
|
-
activeWsCount: nextCount
|
|
25827
|
-
})
|
|
25828
|
-
);
|
|
25829
|
-
send({ type: "auth:ok" });
|
|
25830
|
-
send(
|
|
25831
|
-
ChatListPushSchema.parse({
|
|
25832
|
-
type: FRAME_TYPE_CHAT_LIST,
|
|
25833
|
-
chats: this.listChatsForTuple(personaId, tokenPrefix)
|
|
25834
|
-
})
|
|
25835
|
-
);
|
|
25836
|
-
return;
|
|
25837
|
-
}
|
|
25838
|
-
const type = frame.type;
|
|
25839
|
-
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
25840
|
-
if (type === "auth") {
|
|
25841
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25842
|
-
return;
|
|
25843
|
-
}
|
|
25844
|
-
if (type === "ping") {
|
|
25845
|
-
send({ type: "pong", at: Date.now() });
|
|
25846
|
-
return;
|
|
25847
|
-
}
|
|
25848
|
-
if (!scope) {
|
|
25849
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25850
|
-
return;
|
|
25851
|
-
}
|
|
25852
|
-
if (type === "chat:open") {
|
|
25853
|
-
const parsed = ChatOpenRequestSchema.safeParse(frame);
|
|
25854
|
-
if (!parsed.success) {
|
|
25855
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25856
|
-
return;
|
|
25857
|
-
}
|
|
25858
|
-
const { chatId, autoCreate } = parsed.data;
|
|
25859
|
-
let sessionFile;
|
|
25860
|
-
let isNew = false;
|
|
25861
|
-
try {
|
|
25862
|
-
if (autoCreate) {
|
|
25863
|
-
const r = this.deps.personaManager.getOrCreateSubSession(scope.personaId, scope.token, chatId);
|
|
25864
|
-
sessionFile = r.sessionFile;
|
|
25865
|
-
isNew = r.isNew;
|
|
25866
|
-
} else {
|
|
25867
|
-
sessionFile = this.deps.personaManager.getSubSession(scope.personaId, scope.token, chatId);
|
|
25868
|
-
if (!sessionFile) {
|
|
25869
|
-
sendError(requestId, "SESSION_NOT_FOUND", `chatId not found: ${chatId}`);
|
|
25870
|
-
return;
|
|
25871
|
-
}
|
|
25872
|
-
}
|
|
25873
|
-
} catch (err) {
|
|
25874
|
-
sendError(requestId, "INTERNAL", err.message);
|
|
25875
|
-
return;
|
|
25876
|
-
}
|
|
25877
|
-
if (unsubscribeSession && scope.activeSubSessionId !== sessionFile.sessionId) {
|
|
25878
|
-
unsubscribeSession();
|
|
25879
|
-
unsubscribeSession = null;
|
|
25880
|
-
}
|
|
25881
|
-
if (!unsubscribeSession) {
|
|
25882
|
-
unsubscribeSession = this.deps.sessionManager.subscribe(
|
|
25883
|
-
sessionFile.sessionId,
|
|
25884
|
-
{ kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
25885
|
-
(eventFrame) => send(eventFrame)
|
|
25886
|
-
);
|
|
25887
|
-
}
|
|
25888
|
-
scope.activeSubSessionId = sessionFile.sessionId;
|
|
25889
|
-
phase = "chat-active";
|
|
25890
|
-
const persona = this.deps.registry.get(scope.personaId);
|
|
25891
|
-
const infoFrame = {
|
|
25892
|
-
type: "session:info",
|
|
25893
|
-
sessionId: sessionFile.sessionId,
|
|
25894
|
-
chatId,
|
|
25895
|
-
label: persona?.label ?? "",
|
|
25896
|
-
cwd: sessionFile.cwd
|
|
25897
|
-
};
|
|
25898
|
-
if (persona?.model) infoFrame.model = persona.model;
|
|
25899
|
-
if (requestId) infoFrame.requestId = requestId;
|
|
25900
|
-
send(infoFrame);
|
|
25901
|
-
send({
|
|
25902
|
-
type: "session:status",
|
|
25903
|
-
sessionId: sessionFile.sessionId,
|
|
25904
|
-
status: this.deps.sessionManager.getCurrentStatus(sessionFile.sessionId)
|
|
25905
|
-
});
|
|
25906
|
-
if (isNew) {
|
|
25907
|
-
const createdBase = {
|
|
25908
|
-
sessionId: sessionFile.sessionId,
|
|
25909
|
-
chatId,
|
|
25910
|
-
label: sessionFile.label,
|
|
25911
|
-
createdAt: sessionFile.createdAt
|
|
25912
|
-
};
|
|
25913
|
-
this.fanoutTuple(
|
|
25914
|
-
scope.tokenPrefix,
|
|
25915
|
-
ChatCreatedFrameSchema.parse({ type: FRAME_TYPE_CHAT_CREATED, ...createdBase })
|
|
25916
|
-
);
|
|
25917
|
-
this.deps.ownerBroadcast(
|
|
25918
|
-
PersonaListenerChatCreatedFrameSchema.parse({
|
|
25919
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED,
|
|
25920
|
-
personaId: scope.personaId,
|
|
25921
|
-
token: scope.token,
|
|
25922
|
-
...createdBase
|
|
25923
|
-
})
|
|
25924
|
-
);
|
|
25925
|
-
}
|
|
25926
|
-
return;
|
|
25927
|
-
}
|
|
25928
|
-
if (type === "chat:rename") {
|
|
25929
|
-
const parsed = ChatRenameRequestSchema.safeParse(frame);
|
|
25930
|
-
if (!parsed.success) {
|
|
25931
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25932
|
-
return;
|
|
25933
|
-
}
|
|
25934
|
-
const { sessionId: targetId, label } = parsed.data;
|
|
25935
|
-
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
25936
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
25937
|
-
return;
|
|
25938
|
-
}
|
|
25939
|
-
try {
|
|
25940
|
-
this.deps.sessionManager.renameForScope({
|
|
25941
|
-
sessionId: targetId,
|
|
25942
|
-
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
25943
|
-
label
|
|
25944
|
-
});
|
|
25945
|
-
} catch (err) {
|
|
25946
|
-
const e = err;
|
|
25947
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
25948
|
-
return;
|
|
25949
|
-
}
|
|
25950
|
-
if (requestId) send({ type: FRAME_TYPE_CHAT_RENAME, requestId, ok: true });
|
|
25951
|
-
this.fanoutTuple(
|
|
25952
|
-
scope.tokenPrefix,
|
|
25953
|
-
ChatRenamedFrameSchema.parse({ type: FRAME_TYPE_CHAT_RENAMED, sessionId: targetId, label })
|
|
25954
|
-
);
|
|
25955
|
-
this.deps.ownerBroadcast(
|
|
25956
|
-
PersonaListenerChatRenamedFrameSchema.parse({
|
|
25957
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED,
|
|
25958
|
-
personaId: scope.personaId,
|
|
25959
|
-
token: scope.token,
|
|
25960
|
-
sessionId: targetId,
|
|
25961
|
-
label
|
|
25962
|
-
})
|
|
25963
|
-
);
|
|
25964
|
-
return;
|
|
25965
|
-
}
|
|
25966
|
-
if (type === "chat:delete") {
|
|
25967
|
-
const parsed = ChatDeleteRequestSchema.safeParse(frame);
|
|
25968
|
-
if (!parsed.success) {
|
|
25969
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25970
|
-
return;
|
|
25971
|
-
}
|
|
25972
|
-
const { sessionId: targetId } = parsed.data;
|
|
25973
|
-
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
25974
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
25975
|
-
return;
|
|
25976
|
-
}
|
|
25977
|
-
const before = this.deps.sessionManager.readForScope(targetId, {
|
|
25978
|
-
kind: "persona",
|
|
25979
|
-
personaId: scope.personaId,
|
|
25980
|
-
mode: "listener"
|
|
25981
|
-
});
|
|
25982
|
-
const chatIdForFrame = before?.chatId ?? "unknown";
|
|
25983
|
-
try {
|
|
25984
|
-
this.deps.sessionManager.deleteForScope({
|
|
25985
|
-
sessionId: targetId,
|
|
25986
|
-
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" }
|
|
25987
|
-
});
|
|
25988
|
-
} catch (err) {
|
|
25989
|
-
const e = err;
|
|
25990
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
25991
|
-
return;
|
|
25992
|
-
}
|
|
25993
|
-
if (requestId) send({ type: FRAME_TYPE_CHAT_DELETE, requestId, ok: true });
|
|
25994
|
-
this.fanoutTuple(
|
|
25995
|
-
scope.tokenPrefix,
|
|
25996
|
-
ChatDeletedFrameSchema.parse({ type: FRAME_TYPE_CHAT_DELETED, sessionId: targetId })
|
|
25997
|
-
);
|
|
25998
|
-
this.deps.ownerBroadcast(
|
|
25999
|
-
PersonaListenerChatDeletedFrameSchema.parse({
|
|
26000
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED,
|
|
26001
|
-
personaId: scope.personaId,
|
|
26002
|
-
token: scope.token,
|
|
26003
|
-
sessionId: targetId,
|
|
26004
|
-
chatId: chatIdForFrame
|
|
26005
|
-
})
|
|
26006
|
-
);
|
|
26007
|
-
if (scope.activeSubSessionId === targetId) {
|
|
26008
|
-
unsubscribeSession?.();
|
|
26009
|
-
unsubscribeSession = null;
|
|
26010
|
-
scope.activeSubSessionId = void 0;
|
|
26011
|
-
phase = "tuple-watch";
|
|
26012
|
-
}
|
|
26013
|
-
return;
|
|
26014
|
-
}
|
|
26015
|
-
if (phase !== "chat-active" || !scope.activeSubSessionId) {
|
|
26016
|
-
sendError(
|
|
26017
|
-
requestId,
|
|
26018
|
-
"METHOD_NOT_ALLOWED",
|
|
26019
|
-
`${typeof type === "string" ? type : "unknown"} requires chat:open first`
|
|
26020
|
-
);
|
|
26021
|
-
return;
|
|
26022
|
-
}
|
|
26023
|
-
const requireScopedSession = () => {
|
|
26024
|
-
if (frame.sessionId !== scope.activeSubSessionId) {
|
|
26025
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of scope");
|
|
26026
|
-
return false;
|
|
26027
|
-
}
|
|
26028
|
-
return true;
|
|
26029
|
-
};
|
|
26030
|
-
const listenerScope = () => ({
|
|
26031
|
-
kind: "persona",
|
|
26032
|
-
personaId: scope.personaId,
|
|
26033
|
-
mode: "listener"
|
|
26034
|
-
});
|
|
26035
|
-
switch (type) {
|
|
26036
|
-
case "session:send": {
|
|
26037
|
-
if (!requireScopedSession()) return;
|
|
26038
|
-
const text = frame.text;
|
|
26039
|
-
if (typeof text !== "string") {
|
|
26040
|
-
sendError(requestId, "VALIDATION_ERROR", "text must be string");
|
|
26041
|
-
return;
|
|
26042
|
-
}
|
|
26043
|
-
try {
|
|
26044
|
-
this.deps.sessionManager.sendForScope({
|
|
26045
|
-
sessionId: scope.activeSubSessionId,
|
|
26046
|
-
scope: listenerScope(),
|
|
26047
|
-
text
|
|
26048
|
-
});
|
|
26049
|
-
if (requestId) send({ type: "session:send", ok: true, requestId });
|
|
26050
|
-
} catch (err) {
|
|
26051
|
-
const e = err;
|
|
26052
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26053
|
-
}
|
|
26054
|
-
return;
|
|
26055
|
-
}
|
|
26056
|
-
case "session:new": {
|
|
26057
|
-
if (!requireScopedSession()) return;
|
|
26058
|
-
try {
|
|
26059
|
-
this.deps.sessionManager.resetForScope({
|
|
26060
|
-
sessionId: scope.activeSubSessionId,
|
|
26061
|
-
scope: listenerScope()
|
|
26062
|
-
});
|
|
26063
|
-
if (requestId)
|
|
26064
|
-
send({ type: "session:info", sessionId: scope.activeSubSessionId, requestId });
|
|
26065
|
-
} catch (err) {
|
|
26066
|
-
const e = err;
|
|
26067
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26068
|
-
}
|
|
26069
|
-
return;
|
|
26070
|
-
}
|
|
26071
|
-
case "history:read": {
|
|
26072
|
-
if (!requireScopedSession()) return;
|
|
26073
|
-
const limit = typeof frame.limit === "number" && frame.limit > 0 ? frame.limit : 50;
|
|
26074
|
-
const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
|
|
26075
|
-
try {
|
|
26076
|
-
const page = this.deps.sessionManager.readHistoryPageForScope(
|
|
26077
|
-
scope.activeSubSessionId,
|
|
26078
|
-
listenerScope(),
|
|
26079
|
-
limit,
|
|
26080
|
-
offset
|
|
26081
|
-
);
|
|
26082
|
-
if (requestId) {
|
|
26083
|
-
send({
|
|
26084
|
-
type: "history:read",
|
|
26085
|
-
requestId,
|
|
26086
|
-
events: page.events,
|
|
26087
|
-
offset,
|
|
26088
|
-
total: page.total
|
|
26089
|
-
});
|
|
26090
|
-
}
|
|
26091
|
-
} catch (err) {
|
|
26092
|
-
const e = err;
|
|
26093
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26094
|
-
}
|
|
26095
|
-
return;
|
|
26096
|
-
}
|
|
26097
|
-
default:
|
|
26098
|
-
sendError(
|
|
26099
|
-
requestId,
|
|
26100
|
-
"METHOD_NOT_ALLOWED",
|
|
26101
|
-
`${typeof type === "string" ? type : "unknown"} not allowed for listener`
|
|
26102
|
-
);
|
|
26103
|
-
return;
|
|
26104
|
-
}
|
|
26105
|
-
});
|
|
26106
|
-
ws.on("close", () => {
|
|
26107
|
-
unsubscribeSession?.();
|
|
26108
|
-
unsubscribeSession = null;
|
|
26109
|
-
cleanupTupleRegistration();
|
|
26110
|
-
});
|
|
26111
|
-
}
|
|
26112
|
-
// ---- helpers ----
|
|
26113
|
-
fanoutTuple(tokenPrefix, frame) {
|
|
26114
|
-
const subs = this.tupleSubscribers.get(tokenPrefix);
|
|
26115
|
-
if (!subs) return;
|
|
26116
|
-
for (const fn of subs) {
|
|
26117
|
-
try {
|
|
26118
|
-
fn(frame);
|
|
26119
|
-
} catch {
|
|
26120
|
-
}
|
|
26121
|
-
}
|
|
26122
|
-
}
|
|
26123
|
-
isInTuple(sessionId, tokenPrefix) {
|
|
26124
|
-
return sessionId === tokenPrefix || sessionId.startsWith(`${tokenPrefix}-`);
|
|
26125
|
-
}
|
|
26126
|
-
listChatsForTuple(personaId, tokenPrefix) {
|
|
26127
|
-
const all = this.deps.sessionManager.listPersonaSessions(personaId, "listener");
|
|
26128
|
-
return all.filter((s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)).map((s) => ({
|
|
26129
|
-
sessionId: s.sessionId,
|
|
26130
|
-
chatId: s.chatId ?? "unknown",
|
|
26131
|
-
label: s.label ?? "",
|
|
26132
|
-
createdAt: s.createdAt
|
|
26133
|
-
}));
|
|
26134
|
-
}
|
|
26135
|
-
// ---------------- owner-side helpers ----------------
|
|
26136
|
-
/**
|
|
26137
|
-
* owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session 元数据。
|
|
26138
|
-
* 由 handlers/persona.ts persona:listListenerChatsForToken 调用。
|
|
26139
|
-
*/
|
|
26140
|
-
listChatsForOwner(personaId, token) {
|
|
26141
|
-
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
26142
|
-
return this.listChatsForTuple(personaId, tokenPrefix);
|
|
26143
|
-
}
|
|
26144
|
-
};
|
|
26145
|
-
|
|
26146
25488
|
// src/transport/auth.ts
|
|
26147
25489
|
init_protocol();
|
|
26148
25490
|
var AuthGate = class {
|
|
@@ -27505,14 +26847,24 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
27505
26847
|
function authFilePath(dataDir) {
|
|
27506
26848
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
27507
26849
|
}
|
|
27508
|
-
function
|
|
26850
|
+
function loadOrCreateAuthFile(opts) {
|
|
27509
26851
|
const file = authFilePath(opts.dataDir);
|
|
26852
|
+
const generate = opts.generate ?? defaultGenerate;
|
|
26853
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
27510
26854
|
const existing = readAuthFile(file);
|
|
27511
|
-
if (existing && existing.token
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27515
|
-
|
|
26855
|
+
if (existing && existing.token && existing.signSecret) {
|
|
26856
|
+
return {
|
|
26857
|
+
token: existing.token,
|
|
26858
|
+
signSecret: existing.signSecret,
|
|
26859
|
+
createdAt: existing.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
26860
|
+
};
|
|
26861
|
+
}
|
|
26862
|
+
const token = existing?.token || generate();
|
|
26863
|
+
const signSecret = existing?.signSecret || generate();
|
|
26864
|
+
const createdAt = existing?.createdAt || now().toISOString();
|
|
26865
|
+
const next = { token, signSecret, createdAt };
|
|
26866
|
+
writeAuthFile(file, next);
|
|
26867
|
+
return next;
|
|
27516
26868
|
}
|
|
27517
26869
|
function defaultGenerate() {
|
|
27518
26870
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
@@ -27521,13 +26873,14 @@ function readAuthFile(file) {
|
|
|
27521
26873
|
try {
|
|
27522
26874
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27523
26875
|
const parsed = JSON.parse(raw);
|
|
27524
|
-
if (typeof parsed?.token
|
|
27525
|
-
return
|
|
27526
|
-
token: parsed.token,
|
|
27527
|
-
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
27528
|
-
};
|
|
26876
|
+
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
26877
|
+
return null;
|
|
27529
26878
|
}
|
|
27530
|
-
return
|
|
26879
|
+
return {
|
|
26880
|
+
token: parsed.token,
|
|
26881
|
+
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
26882
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
26883
|
+
};
|
|
27531
26884
|
} catch (err) {
|
|
27532
26885
|
const code = err?.code;
|
|
27533
26886
|
if (code === "ENOENT") return null;
|
|
@@ -27957,21 +27310,9 @@ init_protocol();
|
|
|
27957
27310
|
// src/workspace/git.ts
|
|
27958
27311
|
var import_node_child_process6 = require("child_process");
|
|
27959
27312
|
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
27960
|
-
var import_node_os13 = __toESM(require("os"), 1);
|
|
27961
27313
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
27962
27314
|
var import_node_util = require("util");
|
|
27963
27315
|
var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
|
|
27964
|
-
function formatChildProcessError(err) {
|
|
27965
|
-
const e = err;
|
|
27966
|
-
const stderrLine = (e.stderr ?? "").split("\n").map((s) => s.trim()).find((s) => s.length > 0);
|
|
27967
|
-
if (stderrLine) return stderrLine;
|
|
27968
|
-
const parts = [];
|
|
27969
|
-
if (e.killed) parts.push("killed");
|
|
27970
|
-
if (e.signal) parts.push(`signal=${e.signal}`);
|
|
27971
|
-
if (e.code !== void 0 && e.code !== null) parts.push(`code=${e.code}`);
|
|
27972
|
-
if (parts.length > 0) return parts.join(" ");
|
|
27973
|
-
return e.message ?? "unknown error";
|
|
27974
|
-
}
|
|
27975
27316
|
function normalizePath(p2) {
|
|
27976
27317
|
const resolved = import_node_path25.default.resolve(p2);
|
|
27977
27318
|
try {
|
|
@@ -28044,160 +27385,6 @@ async function listGitBranches(cwd) {
|
|
|
28044
27385
|
return { branches: [], head: null };
|
|
28045
27386
|
}
|
|
28046
27387
|
}
|
|
28047
|
-
var LABEL_WHITELIST = /^[a-zA-Z0-9 _-]+$/;
|
|
28048
|
-
var MAX_SANITIZED_LABEL_LEN = 40;
|
|
28049
|
-
function sanitizeLabel(raw) {
|
|
28050
|
-
if (typeof raw !== "string") return { sanitized: null, reason: "empty" };
|
|
28051
|
-
if (raw.length === 0) return { sanitized: null, reason: "empty" };
|
|
28052
|
-
if (!LABEL_WHITELIST.test(raw)) return { sanitized: null, reason: "invalid-chars" };
|
|
28053
|
-
let s = raw.toLowerCase();
|
|
28054
|
-
s = s.replace(/\s+/g, "-");
|
|
28055
|
-
s = s.replace(/-+/g, "-");
|
|
28056
|
-
s = s.replace(/^[-_]+|[-_]+$/g, "");
|
|
28057
|
-
s = s.slice(0, MAX_SANITIZED_LABEL_LEN);
|
|
28058
|
-
s = s.replace(/^[-_]+|[-_]+$/g, "");
|
|
28059
|
-
if (!s) return { sanitized: null, reason: "empty" };
|
|
28060
|
-
return { sanitized: s, reason: null };
|
|
28061
|
-
}
|
|
28062
|
-
function computePrefix() {
|
|
28063
|
-
let username;
|
|
28064
|
-
try {
|
|
28065
|
-
username = import_node_os13.default.userInfo().username;
|
|
28066
|
-
} catch {
|
|
28067
|
-
username = void 0;
|
|
28068
|
-
}
|
|
28069
|
-
const { sanitized } = sanitizeLabel(username);
|
|
28070
|
-
if (sanitized) return sanitized;
|
|
28071
|
-
const rand = Math.floor(1e3 + Math.random() * 9e3).toString();
|
|
28072
|
-
return `user-${rand}`;
|
|
28073
|
-
}
|
|
28074
|
-
function flattenToDirName(branch) {
|
|
28075
|
-
return branch.replace(/\//g, "-");
|
|
28076
|
-
}
|
|
28077
|
-
function encodeClaudeProjectDir(absPath) {
|
|
28078
|
-
if (!absPath || typeof absPath !== "string") return "";
|
|
28079
|
-
let canonical = import_node_path25.default.resolve(absPath);
|
|
28080
|
-
try {
|
|
28081
|
-
canonical = import_node_fs23.default.realpathSync(canonical);
|
|
28082
|
-
} catch {
|
|
28083
|
-
try {
|
|
28084
|
-
const parent = import_node_fs23.default.realpathSync(import_node_path25.default.dirname(canonical));
|
|
28085
|
-
canonical = import_node_path25.default.join(parent, import_node_path25.default.basename(canonical));
|
|
28086
|
-
} catch {
|
|
28087
|
-
}
|
|
28088
|
-
}
|
|
28089
|
-
return canonical.replace(/[/.]/g, "-");
|
|
28090
|
-
}
|
|
28091
|
-
async function createWorktree(input) {
|
|
28092
|
-
const { cwd, baseBranch, label } = input;
|
|
28093
|
-
if (!cwd || typeof cwd !== "string") throw new Error("cwd \u4E0D\u80FD\u4E3A\u7A7A");
|
|
28094
|
-
if (!baseBranch || typeof baseBranch !== "string") throw new Error("baseBranch \u4E0D\u80FD\u4E3A\u7A7A");
|
|
28095
|
-
const labelResult = sanitizeLabel(label);
|
|
28096
|
-
if (labelResult.reason === "invalid-chars") {
|
|
28097
|
-
throw new Error("label \u4E0D\u80FD\u542B\u4E2D\u6587\u6216\u7279\u6B8A\u5B57\u7B26\uFF0C\u53EA\u5141\u8BB8\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u7A7A\u683C\u3001\u8FDE\u5B57\u7B26\u3001\u4E0B\u5212\u7EBF");
|
|
28098
|
-
}
|
|
28099
|
-
if (labelResult.reason === "empty" || !labelResult.sanitized) {
|
|
28100
|
-
throw new Error("label \u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u82F1\u6587\u5B57\u6BCD\u6216\u6570\u5B57");
|
|
28101
|
-
}
|
|
28102
|
-
const prefix = computePrefix();
|
|
28103
|
-
const branch = `${prefix}/${labelResult.sanitized}`;
|
|
28104
|
-
const dirName = flattenToDirName(branch);
|
|
28105
|
-
const { isGitRoot } = await getGitRoot(cwd);
|
|
28106
|
-
if (!isGitRoot) {
|
|
28107
|
-
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
28108
|
-
}
|
|
28109
|
-
const parent = import_node_path25.default.dirname(import_node_path25.default.resolve(cwd));
|
|
28110
|
-
if (parent === "/" || parent === import_node_path25.default.resolve(cwd)) {
|
|
28111
|
-
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
28112
|
-
}
|
|
28113
|
-
const worktreeRoot = import_node_path25.default.join(parent, dirName);
|
|
28114
|
-
try {
|
|
28115
|
-
await pexec("git", ["-C", cwd, "fetch", "origin", baseBranch, "--no-tags"], {
|
|
28116
|
-
timeout: 3e4
|
|
28117
|
-
});
|
|
28118
|
-
} catch (err) {
|
|
28119
|
-
throw new Error(
|
|
28120
|
-
`\u57FA\u51C6\u5206\u652F ${baseBranch} \u4E0D\u5B58\u5728\u6216 fetch \u8FDC\u7AEF\u5931\u8D25\uFF1A${formatChildProcessError(err)}`
|
|
28121
|
-
);
|
|
28122
|
-
}
|
|
28123
|
-
try {
|
|
28124
|
-
await pexec("git", ["-C", cwd, "rev-parse", "--verify", `refs/heads/${branch}`], {
|
|
28125
|
-
timeout: 3e3
|
|
28126
|
-
});
|
|
28127
|
-
throw new Error(`\u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label`);
|
|
28128
|
-
} catch (err) {
|
|
28129
|
-
const msg = err.message;
|
|
28130
|
-
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
28131
|
-
}
|
|
28132
|
-
if (import_node_fs23.default.existsSync(worktreeRoot)) {
|
|
28133
|
-
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
28134
|
-
}
|
|
28135
|
-
try {
|
|
28136
|
-
await pexec(
|
|
28137
|
-
"git",
|
|
28138
|
-
["-C", cwd, "worktree", "add", worktreeRoot, "-b", branch, `origin/${baseBranch}`],
|
|
28139
|
-
{ timeout: 15e3 }
|
|
28140
|
-
);
|
|
28141
|
-
} catch (err) {
|
|
28142
|
-
throw new Error(`\u521B\u5EFA worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
28143
|
-
}
|
|
28144
|
-
return { worktreeRoot, branch, baseBranch };
|
|
28145
|
-
}
|
|
28146
|
-
async function removeWorktree(input) {
|
|
28147
|
-
const { worktreeRoot, worktreeBranch } = input;
|
|
28148
|
-
if (!worktreeRoot || !worktreeBranch) {
|
|
28149
|
-
throw new Error("worktreeRoot \u548C worktreeBranch \u90FD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
28150
|
-
}
|
|
28151
|
-
let repoRoot = null;
|
|
28152
|
-
try {
|
|
28153
|
-
const { stdout } = await pexec(
|
|
28154
|
-
"git",
|
|
28155
|
-
["-C", worktreeRoot, "rev-parse", "--git-common-dir"],
|
|
28156
|
-
{ timeout: 3e3 }
|
|
28157
|
-
);
|
|
28158
|
-
const gitCommonDir = stdout.trim();
|
|
28159
|
-
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
28160
|
-
const absGitCommon = import_node_path25.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path25.default.resolve(worktreeRoot, gitCommonDir);
|
|
28161
|
-
repoRoot = import_node_path25.default.dirname(absGitCommon);
|
|
28162
|
-
} catch {
|
|
28163
|
-
repoRoot = null;
|
|
28164
|
-
}
|
|
28165
|
-
if (repoRoot) {
|
|
28166
|
-
try {
|
|
28167
|
-
await pexec("git", ["-C", repoRoot, "worktree", "remove", worktreeRoot, "--force"], {
|
|
28168
|
-
timeout: 1e4
|
|
28169
|
-
});
|
|
28170
|
-
} catch (err) {
|
|
28171
|
-
const stderr = err.stderr ?? "";
|
|
28172
|
-
const lower = stderr.toLowerCase();
|
|
28173
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs23.default.existsSync(worktreeRoot);
|
|
28174
|
-
if (!vanished) {
|
|
28175
|
-
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
28176
|
-
}
|
|
28177
|
-
}
|
|
28178
|
-
try {
|
|
28179
|
-
await pexec("git", ["-C", repoRoot, "branch", "-D", worktreeBranch], { timeout: 5e3 });
|
|
28180
|
-
} catch (err) {
|
|
28181
|
-
const stderr = err.stderr ?? "";
|
|
28182
|
-
const lower = stderr.toLowerCase();
|
|
28183
|
-
const vanished = lower.includes("not found") || lower.includes("no such");
|
|
28184
|
-
if (!vanished) {
|
|
28185
|
-
throw new Error(`\u6E05\u7406\u5206\u652F\u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
28186
|
-
}
|
|
28187
|
-
}
|
|
28188
|
-
}
|
|
28189
|
-
try {
|
|
28190
|
-
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
28191
|
-
if (encoded) {
|
|
28192
|
-
const projectsRoot = import_node_path25.default.join(import_node_os13.default.homedir(), ".claude", "projects");
|
|
28193
|
-
const target = import_node_path25.default.resolve(projectsRoot, encoded);
|
|
28194
|
-
if (target.startsWith(projectsRoot + import_node_path25.default.sep) && target !== projectsRoot) {
|
|
28195
|
-
import_node_fs23.default.rmSync(target, { recursive: true, force: true });
|
|
28196
|
-
}
|
|
28197
|
-
}
|
|
28198
|
-
} catch {
|
|
28199
|
-
}
|
|
28200
|
-
}
|
|
28201
27388
|
|
|
28202
27389
|
// src/handlers/git.ts
|
|
28203
27390
|
function buildGitHandlers() {
|
|
@@ -28216,36 +27403,10 @@ function buildGitHandlers() {
|
|
|
28216
27403
|
const res = await listGitBranches(args.cwd);
|
|
28217
27404
|
return { response: { type: "git:branches", ...res } };
|
|
28218
27405
|
};
|
|
28219
|
-
const worktreePrefix = async () => {
|
|
28220
|
-
return {
|
|
28221
|
-
response: { type: "git:worktree:prefix", prefix: computePrefix() }
|
|
28222
|
-
};
|
|
28223
|
-
};
|
|
28224
|
-
const worktreeCreate = async (frame) => {
|
|
28225
|
-
const args = GitWorktreeCreateArgs.parse(frame);
|
|
28226
|
-
try {
|
|
28227
|
-
const res = await createWorktree(args);
|
|
28228
|
-
return { response: { type: "git:worktree:create", ...res } };
|
|
28229
|
-
} catch (err) {
|
|
28230
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, err.message);
|
|
28231
|
-
}
|
|
28232
|
-
};
|
|
28233
|
-
const worktreeRemove = async (frame) => {
|
|
28234
|
-
const args = GitWorktreeRemoveArgs.parse(frame);
|
|
28235
|
-
try {
|
|
28236
|
-
await removeWorktree(args);
|
|
28237
|
-
return { response: { type: "git:worktree:remove", ok: true } };
|
|
28238
|
-
} catch (err) {
|
|
28239
|
-
throw new ClawdError(ERROR_CODES.INTERNAL, err.message);
|
|
28240
|
-
}
|
|
28241
|
-
};
|
|
28242
27406
|
return {
|
|
28243
27407
|
"git:root": root,
|
|
28244
27408
|
"git:branch": branch,
|
|
28245
|
-
"git:branches": branches
|
|
28246
|
-
"git:worktree:prefix": worktreePrefix,
|
|
28247
|
-
"git:worktree:create": worktreeCreate,
|
|
28248
|
-
"git:worktree:remove": worktreeRemove
|
|
27409
|
+
"git:branches": branches
|
|
28249
27410
|
};
|
|
28250
27411
|
}
|
|
28251
27412
|
|
|
@@ -28264,7 +27425,7 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
28264
27425
|
}
|
|
28265
27426
|
|
|
28266
27427
|
// src/handlers/meta.ts
|
|
28267
|
-
var
|
|
27428
|
+
var import_node_os13 = __toESM(require("os"), 1);
|
|
28268
27429
|
init_protocol();
|
|
28269
27430
|
|
|
28270
27431
|
// src/version.ts
|
|
@@ -28294,7 +27455,7 @@ function buildReadyFrame(deps, client) {
|
|
|
28294
27455
|
return {
|
|
28295
27456
|
version,
|
|
28296
27457
|
protocolVersion: PROTOCOL_VERSION,
|
|
28297
|
-
hostname:
|
|
27458
|
+
hostname: import_node_os13.default.hostname(),
|
|
28298
27459
|
os: process.platform,
|
|
28299
27460
|
tools,
|
|
28300
27461
|
runningSessions: info.runningSessions,
|
|
@@ -28319,7 +27480,7 @@ function buildMetaHandlers(deps) {
|
|
|
28319
27480
|
// src/handlers/persona.ts
|
|
28320
27481
|
init_protocol();
|
|
28321
27482
|
function buildPersonaHandlers(deps) {
|
|
28322
|
-
const { personaManager, personaRegistry
|
|
27483
|
+
const { personaManager, personaRegistry } = deps;
|
|
28323
27484
|
const create = async (frame) => {
|
|
28324
27485
|
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28325
27486
|
const args = PersonaCreateArgsSchema.parse(rest);
|
|
@@ -28376,27 +27537,6 @@ function buildPersonaHandlers(deps) {
|
|
|
28376
27537
|
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
28377
27538
|
return { response: { type: "persona:info", ...persona } };
|
|
28378
27539
|
};
|
|
28379
|
-
const listSubSessions = async (frame) => {
|
|
28380
|
-
const args = PersonaIdArgsSchema.parse(frame);
|
|
28381
|
-
const sessions = sessionManager.listPersonaSessions(args.personaId, "listener");
|
|
28382
|
-
return {
|
|
28383
|
-
response: { type: "persona:subSessions", sessions }
|
|
28384
|
-
};
|
|
28385
|
-
};
|
|
28386
|
-
const appendOwnerMessage = async (frame) => {
|
|
28387
|
-
const args = PersonaAppendOwnerMessageArgsSchema.parse(frame);
|
|
28388
|
-
personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
|
|
28389
|
-
return {
|
|
28390
|
-
response: { type: "persona:appendOwnerMessage", ok: true }
|
|
28391
|
-
};
|
|
28392
|
-
};
|
|
28393
|
-
const listListenerChatsForToken = async (frame) => {
|
|
28394
|
-
const args = PersonaListListenerChatsForTokenArgsSchema.parse(frame);
|
|
28395
|
-
const chats = personaBoundHandler.listChatsForOwner(args.personaId, args.token);
|
|
28396
|
-
return {
|
|
28397
|
-
response: { type: "persona:listListenerChatsForToken", chats }
|
|
28398
|
-
};
|
|
28399
|
-
};
|
|
28400
27540
|
return {
|
|
28401
27541
|
"persona:create": create,
|
|
28402
27542
|
"persona:list": list,
|
|
@@ -28404,14 +27544,12 @@ function buildPersonaHandlers(deps) {
|
|
|
28404
27544
|
"persona:update": update,
|
|
28405
27545
|
"persona:delete": del,
|
|
28406
27546
|
"persona:issueToken": issueToken,
|
|
28407
|
-
"persona:revokeToken": revokeToken
|
|
28408
|
-
"persona:listSubSessions": listSubSessions,
|
|
28409
|
-
"persona:listListenerChatsForToken": listListenerChatsForToken,
|
|
28410
|
-
"persona:appendOwnerMessage": appendOwnerMessage
|
|
27547
|
+
"persona:revokeToken": revokeToken
|
|
28411
27548
|
};
|
|
28412
27549
|
}
|
|
28413
27550
|
|
|
28414
27551
|
// src/handlers/attachment.ts
|
|
27552
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
28415
27553
|
init_protocol();
|
|
28416
27554
|
init_protocol();
|
|
28417
27555
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -28436,8 +27574,51 @@ function buildAttachmentHandlers(deps) {
|
|
|
28436
27574
|
"httpBaseUrl unavailable (daemon HTTP not ready)"
|
|
28437
27575
|
);
|
|
28438
27576
|
}
|
|
27577
|
+
if (!deps.sessionStore || !deps.getSessionScope || !deps.groupFileStore) {
|
|
27578
|
+
throw new ClawdError(
|
|
27579
|
+
ERROR_CODES.METHOD_NOT_IMPLEMENTED,
|
|
27580
|
+
"signUrl requires session/group stores"
|
|
27581
|
+
);
|
|
27582
|
+
}
|
|
27583
|
+
const sessionFile = deps.sessionStore.read(args.sessionId);
|
|
27584
|
+
if (!sessionFile) {
|
|
27585
|
+
throw new ClawdError(
|
|
27586
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27587
|
+
`session ${args.sessionId} not found`
|
|
27588
|
+
);
|
|
27589
|
+
}
|
|
27590
|
+
const scope = deps.getSessionScope(args.sessionId);
|
|
27591
|
+
if (!scope) {
|
|
27592
|
+
throw new ClawdError(
|
|
27593
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27594
|
+
`session ${args.sessionId} scope unresolved`
|
|
27595
|
+
);
|
|
27596
|
+
}
|
|
27597
|
+
const cwdAbs = import_node_path26.default.resolve(sessionFile.cwd);
|
|
27598
|
+
const candidateAbs = import_node_path26.default.isAbsolute(args.relPath) ? import_node_path26.default.resolve(args.relPath) : import_node_path26.default.resolve(cwdAbs, args.relPath);
|
|
27599
|
+
if (!isContainedIn2(candidateAbs, cwdAbs)) {
|
|
27600
|
+
throw new ClawdError(
|
|
27601
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27602
|
+
"relPath escapes session cwd"
|
|
27603
|
+
);
|
|
27604
|
+
}
|
|
27605
|
+
const relPath = import_node_path26.default.relative(cwdAbs, candidateAbs);
|
|
27606
|
+
if (relPath === "" || relPath.startsWith("..")) {
|
|
27607
|
+
throw new ClawdError(
|
|
27608
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27609
|
+
"relPath escapes session cwd"
|
|
27610
|
+
);
|
|
27611
|
+
}
|
|
27612
|
+
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
27613
|
+
const entry = entries.find((e) => e.relPath === relPath && !e.stale);
|
|
27614
|
+
if (!entry) {
|
|
27615
|
+
throw new ClawdError(
|
|
27616
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27617
|
+
`relPath not in session group files or stale: ${relPath}`
|
|
27618
|
+
);
|
|
27619
|
+
}
|
|
28439
27620
|
const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
28440
|
-
const parts = signUrlParts(secret,
|
|
27621
|
+
const parts = signUrlParts(secret, candidateAbs, ttl);
|
|
28441
27622
|
const url = buildSignedFileUrl(httpBaseUrl, parts);
|
|
28442
27623
|
return {
|
|
28443
27624
|
response: {
|
|
@@ -28523,6 +27704,12 @@ function buildAttachmentHandlers(deps) {
|
|
|
28523
27704
|
"attachment.groupListPersona": groupListPersona
|
|
28524
27705
|
};
|
|
28525
27706
|
}
|
|
27707
|
+
function isContainedIn2(abs, root) {
|
|
27708
|
+
const normalized = import_node_path26.default.resolve(abs);
|
|
27709
|
+
const normalizedRoot = import_node_path26.default.resolve(root);
|
|
27710
|
+
if (normalized === normalizedRoot) return true;
|
|
27711
|
+
return normalized.startsWith(normalizedRoot + import_node_path26.default.sep);
|
|
27712
|
+
}
|
|
28526
27713
|
|
|
28527
27714
|
// src/handlers/index.ts
|
|
28528
27715
|
function buildMethodHandlers(deps) {
|
|
@@ -28536,9 +27723,7 @@ function buildMethodHandlers(deps) {
|
|
|
28536
27723
|
...buildMetaHandlers(deps),
|
|
28537
27724
|
...buildPersonaHandlers({
|
|
28538
27725
|
personaManager: deps.personaManager,
|
|
28539
|
-
personaRegistry: deps.personaRegistry
|
|
28540
|
-
sessionManager: deps.manager,
|
|
28541
|
-
personaBoundHandler: deps.personaBoundHandler
|
|
27726
|
+
personaRegistry: deps.personaRegistry
|
|
28542
27727
|
}),
|
|
28543
27728
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
28544
27729
|
};
|
|
@@ -28548,7 +27733,7 @@ function buildMethodHandlers(deps) {
|
|
|
28548
27733
|
async function startDaemon(config) {
|
|
28549
27734
|
const logger = createLogger({
|
|
28550
27735
|
level: config.logLevel,
|
|
28551
|
-
file:
|
|
27736
|
+
file: import_node_path27.default.join(config.dataDir, "clawd.log")
|
|
28552
27737
|
});
|
|
28553
27738
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
28554
27739
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -28560,10 +27745,12 @@ async function startDaemon(config) {
|
|
|
28560
27745
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
28561
27746
|
}
|
|
28562
27747
|
let resolvedAuthToken = null;
|
|
27748
|
+
let authFile = null;
|
|
28563
27749
|
if (config.authToken && config.authToken.trim()) {
|
|
28564
27750
|
resolvedAuthToken = config.authToken.trim();
|
|
28565
27751
|
} else if (config.tunnel) {
|
|
28566
|
-
|
|
27752
|
+
authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
|
|
27753
|
+
resolvedAuthToken = authFile.token;
|
|
28567
27754
|
}
|
|
28568
27755
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
28569
27756
|
let wsServer = null;
|
|
@@ -28580,7 +27767,7 @@ async function startDaemon(config) {
|
|
|
28580
27767
|
const agents = new AgentsScanner();
|
|
28581
27768
|
const history = new ClaudeHistoryReader();
|
|
28582
27769
|
let transport = null;
|
|
28583
|
-
const personaStore = new PersonaStore(
|
|
27770
|
+
const personaStore = new PersonaStore(import_node_path27.default.join(config.dataDir, "personas"));
|
|
28584
27771
|
const defaultsRoot = findDefaultsRoot();
|
|
28585
27772
|
if (defaultsRoot) {
|
|
28586
27773
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -28595,7 +27782,7 @@ async function startDaemon(config) {
|
|
|
28595
27782
|
getAdapter,
|
|
28596
27783
|
historyReader: history,
|
|
28597
27784
|
dataDir: config.dataDir,
|
|
28598
|
-
personaRoot:
|
|
27785
|
+
personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
|
|
28599
27786
|
personaStore,
|
|
28600
27787
|
ownerDisplayName,
|
|
28601
27788
|
mode: config.mode,
|
|
@@ -28618,7 +27805,7 @@ async function startDaemon(config) {
|
|
|
28618
27805
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
28619
27806
|
attachmentGroup: {
|
|
28620
27807
|
onFileEdit: (input) => {
|
|
28621
|
-
const absPath =
|
|
27808
|
+
const absPath = import_node_path27.default.isAbsolute(input.relPath) ? input.relPath : import_node_path27.default.join(input.cwd, input.relPath);
|
|
28622
27809
|
let size = 0;
|
|
28623
27810
|
try {
|
|
28624
27811
|
size = import_node_fs24.default.statSync(absPath).size;
|
|
@@ -28695,16 +27882,6 @@ async function startDaemon(config) {
|
|
|
28695
27882
|
}
|
|
28696
27883
|
return `http://${config.host}:${config.port}`;
|
|
28697
27884
|
};
|
|
28698
|
-
const personaBoundHandler = new PersonaBoundHandler({
|
|
28699
|
-
registry: personaRegistry,
|
|
28700
|
-
personaManager,
|
|
28701
|
-
personaStore,
|
|
28702
|
-
sessionManager: manager,
|
|
28703
|
-
logger,
|
|
28704
|
-
ownerBroadcast: (frame) => {
|
|
28705
|
-
transport?.broadcastAll(frame);
|
|
28706
|
-
}
|
|
28707
|
-
});
|
|
28708
27885
|
const handlers = buildMethodHandlers({
|
|
28709
27886
|
manager,
|
|
28710
27887
|
workspace,
|
|
@@ -28716,7 +27893,6 @@ async function startDaemon(config) {
|
|
|
28716
27893
|
store,
|
|
28717
27894
|
personaManager,
|
|
28718
27895
|
personaRegistry,
|
|
28719
|
-
personaBoundHandler,
|
|
28720
27896
|
getTunnelUrl: () => currentTunnelUrl,
|
|
28721
27897
|
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
28722
27898
|
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
@@ -28730,11 +27906,12 @@ async function startDaemon(config) {
|
|
|
28730
27906
|
// 根据 sessionId 反查 scope 写盘。
|
|
28731
27907
|
attachment: {
|
|
28732
27908
|
groupFileStore,
|
|
27909
|
+
sessionStore: store,
|
|
28733
27910
|
getHttpBaseUrl,
|
|
28734
|
-
// HMAC sign secret
|
|
28735
|
-
// noAuth 模式
|
|
28736
|
-
getSignSecret: () =>
|
|
28737
|
-
// group RPC
|
|
27911
|
+
// HMAC sign secret:~/.clawd/auth.json signSecret 字段(与 WS Bearer token 独立)。
|
|
27912
|
+
// --auth-token CLI 模式 / noAuth 模式 authFile 为 null → handler 自己返 NOT_IMPLEMENTED。
|
|
27913
|
+
getSignSecret: () => authFile?.signSecret ?? "",
|
|
27914
|
+
// group RPC + sign 都用:根据 sessionId 反查 scope;owner-mode persona session 走
|
|
28738
27915
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
28739
27916
|
getSessionScope: (sessionId) => {
|
|
28740
27917
|
const file = store.read(sessionId);
|
|
@@ -28757,8 +27934,9 @@ async function startDaemon(config) {
|
|
|
28757
27934
|
personaStore,
|
|
28758
27935
|
groupFileStore,
|
|
28759
27936
|
sessionStore: store,
|
|
28760
|
-
// /files HMAC verify
|
|
28761
|
-
|
|
27937
|
+
// /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
|
|
27938
|
+
// --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
|
|
27939
|
+
getSignSecret: () => authFile?.signSecret ?? null
|
|
28762
27940
|
});
|
|
28763
27941
|
wsServer = new LocalWsServer({
|
|
28764
27942
|
host: config.host,
|
|
@@ -28779,7 +27957,6 @@ async function startDaemon(config) {
|
|
|
28779
27957
|
),
|
|
28780
27958
|
protocolVersion: PROTOCOL_VERSION,
|
|
28781
27959
|
authGate: authGate ?? void 0,
|
|
28782
|
-
personaBoundHandler,
|
|
28783
27960
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
28784
27961
|
httpRequestHandler: httpRouter,
|
|
28785
27962
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
@@ -28897,8 +28074,8 @@ async function startDaemon(config) {
|
|
|
28897
28074
|
const lines = [
|
|
28898
28075
|
`Tunnel: ${r.url}`,
|
|
28899
28076
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
28900
|
-
`Frpc config: ${
|
|
28901
|
-
`Frpc log: ${
|
|
28077
|
+
`Frpc config: ${import_node_path27.default.join(config.dataDir, "frpc.toml")}`,
|
|
28078
|
+
`Frpc log: ${import_node_path27.default.join(config.dataDir, "frpc.log")}`
|
|
28902
28079
|
];
|
|
28903
28080
|
const width = Math.max(...lines.map((l) => l.length));
|
|
28904
28081
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -28911,7 +28088,7 @@ ${bar}
|
|
|
28911
28088
|
|
|
28912
28089
|
`);
|
|
28913
28090
|
try {
|
|
28914
|
-
const connectPath =
|
|
28091
|
+
const connectPath = import_node_path27.default.join(config.dataDir, "connect.txt");
|
|
28915
28092
|
import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
28916
28093
|
} catch {
|
|
28917
28094
|
}
|