@clawos-dev/clawd 0.2.67-beta.115.8d05743 → 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 +175 -776
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -86,20 +86,16 @@ var init_methods = __esm({
|
|
|
86
86
|
"git:branch",
|
|
87
87
|
"git:branches",
|
|
88
88
|
"capabilities:get",
|
|
89
|
-
// ---- persona:* (
|
|
89
|
+
// ---- persona:* (persona 管理) ----
|
|
90
90
|
"persona:create",
|
|
91
91
|
"persona:list",
|
|
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 管理。
|
|
95
97
|
"persona:issueToken",
|
|
96
98
|
"persona:revokeToken",
|
|
97
|
-
"persona:listSubSessions",
|
|
98
|
-
// owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session
|
|
99
|
-
// 元数据。后续增量靠 persona:listenerChat:* push 维护,本 RPC 仅在 owner ws connect /
|
|
100
|
-
// reconnect 时一次性补齐 cache。
|
|
101
|
-
"persona:listListenerChatsForToken",
|
|
102
|
-
"persona:appendOwnerMessage",
|
|
103
99
|
// ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
|
|
104
100
|
// session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
|
|
105
101
|
// 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
|
|
@@ -609,8 +605,8 @@ var init_parseUtil = __esm({
|
|
|
609
605
|
init_errors2();
|
|
610
606
|
init_en();
|
|
611
607
|
makeIssue = (params) => {
|
|
612
|
-
const { data, path:
|
|
613
|
-
const fullPath = [...
|
|
608
|
+
const { data, path: path32, errorMaps, issueData } = params;
|
|
609
|
+
const fullPath = [...path32, ...issueData.path || []];
|
|
614
610
|
const fullIssue = {
|
|
615
611
|
...issueData,
|
|
616
612
|
path: fullPath
|
|
@@ -921,11 +917,11 @@ var init_types = __esm({
|
|
|
921
917
|
init_parseUtil();
|
|
922
918
|
init_util();
|
|
923
919
|
ParseInputLazyPath = class {
|
|
924
|
-
constructor(parent, value,
|
|
920
|
+
constructor(parent, value, path32, key) {
|
|
925
921
|
this._cachedPath = [];
|
|
926
922
|
this.parent = parent;
|
|
927
923
|
this.data = value;
|
|
928
|
-
this._path =
|
|
924
|
+
this._path = path32;
|
|
929
925
|
this._key = key;
|
|
930
926
|
}
|
|
931
927
|
get path() {
|
|
@@ -4335,9 +4331,11 @@ var init_attachment_schemas = __esm({
|
|
|
4335
4331
|
stale: external_exports.boolean().optional()
|
|
4336
4332
|
});
|
|
4337
4333
|
AttachmentSignUrlArgs = external_exports.object({
|
|
4338
|
-
/**
|
|
4339
|
-
|
|
4340
|
-
/**
|
|
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 字段) */
|
|
4341
4339
|
ttlSeconds: external_exports.number().int().positive().nullable().optional()
|
|
4342
4340
|
});
|
|
4343
4341
|
AttachmentSignUrlResponseSchema = external_exports.object({
|
|
@@ -4383,7 +4381,7 @@ var init_attachment_schemas = __esm({
|
|
|
4383
4381
|
});
|
|
4384
4382
|
|
|
4385
4383
|
// ../protocol/src/persona-schemas.ts
|
|
4386
|
-
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema
|
|
4384
|
+
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema;
|
|
4387
4385
|
var init_persona_schemas = __esm({
|
|
4388
4386
|
"../protocol/src/persona-schemas.ts"() {
|
|
4389
4387
|
"use strict";
|
|
@@ -4401,7 +4399,13 @@ var init_persona_schemas = __esm({
|
|
|
4401
4399
|
// 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
|
|
4402
4400
|
// for forward compatibility, consumer fallbacks to default when key not found.
|
|
4403
4401
|
iconKey: external_exports.string().optional(),
|
|
4404
|
-
|
|
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(),
|
|
4405
4409
|
createdAt: external_exports.number(),
|
|
4406
4410
|
updatedAt: external_exports.number()
|
|
4407
4411
|
}).strict();
|
|
@@ -4462,121 +4466,6 @@ var init_persona_schemas = __esm({
|
|
|
4462
4466
|
personaId: external_exports.string().min(1),
|
|
4463
4467
|
token: external_exports.string().min(1)
|
|
4464
4468
|
});
|
|
4465
|
-
PersonaAppendOwnerMessageArgsSchema = external_exports.object({
|
|
4466
|
-
personaId: external_exports.string().min(1),
|
|
4467
|
-
subSessionId: external_exports.string().min(1),
|
|
4468
|
-
text: external_exports.string().min(1)
|
|
4469
|
-
});
|
|
4470
|
-
FRAME_TYPE_CHAT_OPEN = "chat:open";
|
|
4471
|
-
FRAME_TYPE_CHAT_RENAME = "chat:rename";
|
|
4472
|
-
FRAME_TYPE_CHAT_DELETE = "chat:delete";
|
|
4473
|
-
FRAME_TYPE_CHAT_LIST = "chat:list";
|
|
4474
|
-
FRAME_TYPE_CHAT_CREATED = "chat:created";
|
|
4475
|
-
FRAME_TYPE_CHAT_RENAMED = "chat:renamed";
|
|
4476
|
-
FRAME_TYPE_CHAT_DELETED = "chat:deleted";
|
|
4477
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED = "persona:listenerChat:created";
|
|
4478
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED = "persona:listenerChat:renamed";
|
|
4479
|
-
FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED = "persona:listenerChat:deleted";
|
|
4480
|
-
FRAME_TYPE_PERSONA_LISTENER_STATUS = "persona:listenerStatus";
|
|
4481
|
-
ChatSummarySchema = external_exports.object({
|
|
4482
|
-
sessionId: external_exports.string().min(1),
|
|
4483
|
-
/** 派生 sessionId 时使用的原始 chatId(永远三元组 hash 派生,无 default 特例) */
|
|
4484
|
-
chatId: external_exports.string().min(1),
|
|
4485
|
-
/** sub-session label,listener 视为 chat 名 */
|
|
4486
|
-
label: external_exports.string(),
|
|
4487
|
-
/** SessionFile.createdAt(ISO string) */
|
|
4488
|
-
createdAt: external_exports.string()
|
|
4489
|
-
});
|
|
4490
|
-
ChatOpenRequestSchema = external_exports.object({
|
|
4491
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_OPEN),
|
|
4492
|
-
/** 客户端生成的 chatId(uuid 推荐);daemon 用三元组派生 sub-session sessionId */
|
|
4493
|
-
chatId: external_exports.string().min(1),
|
|
4494
|
-
/** 不存在时是否自动创建。true = 创建(典型:新建对话);false/缺省 = 命中已有,否则 NOT_FOUND */
|
|
4495
|
-
autoCreate: external_exports.boolean().optional(),
|
|
4496
|
-
requestId: external_exports.string().min(1).optional()
|
|
4497
|
-
});
|
|
4498
|
-
ChatRenameRequestSchema = external_exports.object({
|
|
4499
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
4500
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4501
|
-
sessionId: external_exports.string().min(1),
|
|
4502
|
-
label: external_exports.string().min(1)
|
|
4503
|
-
});
|
|
4504
|
-
ChatDeleteRequestSchema = external_exports.object({
|
|
4505
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
4506
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4507
|
-
sessionId: external_exports.string().min(1)
|
|
4508
|
-
});
|
|
4509
|
-
ChatRenameResponseSchema = external_exports.object({
|
|
4510
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
4511
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4512
|
-
ok: external_exports.literal(true)
|
|
4513
|
-
});
|
|
4514
|
-
ChatDeleteResponseSchema = external_exports.object({
|
|
4515
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
4516
|
-
requestId: external_exports.string().min(1).optional(),
|
|
4517
|
-
ok: external_exports.literal(true)
|
|
4518
|
-
});
|
|
4519
|
-
ChatListPushSchema = external_exports.object({
|
|
4520
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_LIST),
|
|
4521
|
-
chats: external_exports.array(ChatSummarySchema)
|
|
4522
|
-
});
|
|
4523
|
-
ChatCreatedFrameSchema = external_exports.object({
|
|
4524
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_CREATED),
|
|
4525
|
-
sessionId: external_exports.string().min(1),
|
|
4526
|
-
chatId: external_exports.string().min(1),
|
|
4527
|
-
label: external_exports.string().optional(),
|
|
4528
|
-
/** ISO timestamp */
|
|
4529
|
-
createdAt: external_exports.string().min(1)
|
|
4530
|
-
});
|
|
4531
|
-
ChatRenamedFrameSchema = external_exports.object({
|
|
4532
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_RENAMED),
|
|
4533
|
-
sessionId: external_exports.string().min(1),
|
|
4534
|
-
label: external_exports.string().min(1)
|
|
4535
|
-
});
|
|
4536
|
-
ChatDeletedFrameSchema = external_exports.object({
|
|
4537
|
-
type: external_exports.literal(FRAME_TYPE_CHAT_DELETED),
|
|
4538
|
-
sessionId: external_exports.string().min(1)
|
|
4539
|
-
});
|
|
4540
|
-
PersonaListenerChatCreatedFrameSchema = external_exports.object({
|
|
4541
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED),
|
|
4542
|
-
personaId: external_exports.string().min(1),
|
|
4543
|
-
/** owner 同时持有 token 字符串:sidebar 已经按 token 维度渲染 listener leaf,
|
|
4544
|
-
* push 帧用 token 直接定位归属 leaf。token 不是密钥,owner 本来就持有。 */
|
|
4545
|
-
token: external_exports.string().min(1),
|
|
4546
|
-
sessionId: external_exports.string().min(1),
|
|
4547
|
-
chatId: external_exports.string().min(1),
|
|
4548
|
-
label: external_exports.string().optional(),
|
|
4549
|
-
createdAt: external_exports.string().min(1)
|
|
4550
|
-
});
|
|
4551
|
-
PersonaListenerChatRenamedFrameSchema = external_exports.object({
|
|
4552
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED),
|
|
4553
|
-
personaId: external_exports.string().min(1),
|
|
4554
|
-
token: external_exports.string().min(1),
|
|
4555
|
-
sessionId: external_exports.string().min(1),
|
|
4556
|
-
label: external_exports.string().min(1)
|
|
4557
|
-
});
|
|
4558
|
-
PersonaListenerChatDeletedFrameSchema = external_exports.object({
|
|
4559
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED),
|
|
4560
|
-
personaId: external_exports.string().min(1),
|
|
4561
|
-
token: external_exports.string().min(1),
|
|
4562
|
-
sessionId: external_exports.string().min(1),
|
|
4563
|
-
/** 原始 chatId(用于 owner UI 比对 personaView.chatId 清理 stale selection) */
|
|
4564
|
-
chatId: external_exports.string().min(1)
|
|
4565
|
-
});
|
|
4566
|
-
PersonaListenerStatusFrameSchema = external_exports.object({
|
|
4567
|
-
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_STATUS),
|
|
4568
|
-
personaId: external_exports.string().min(1),
|
|
4569
|
-
token: external_exports.string().min(1),
|
|
4570
|
-
status: external_exports.enum(["online", "offline"]),
|
|
4571
|
-
activeWsCount: external_exports.number().int().nonnegative()
|
|
4572
|
-
});
|
|
4573
|
-
PersonaListListenerChatsForTokenArgsSchema = external_exports.object({
|
|
4574
|
-
personaId: external_exports.string().min(1),
|
|
4575
|
-
token: external_exports.string().min(1)
|
|
4576
|
-
});
|
|
4577
|
-
PersonaListListenerChatsForTokenResponseSchema = external_exports.object({
|
|
4578
|
-
chats: external_exports.array(ChatSummarySchema)
|
|
4579
|
-
});
|
|
4580
4469
|
}
|
|
4581
4470
|
});
|
|
4582
4471
|
|
|
@@ -5441,8 +5330,8 @@ var require_req = __commonJS({
|
|
|
5441
5330
|
if (req.originalUrl) {
|
|
5442
5331
|
_req.url = req.originalUrl;
|
|
5443
5332
|
} else {
|
|
5444
|
-
const
|
|
5445
|
-
_req.url = typeof
|
|
5333
|
+
const path32 = req.path;
|
|
5334
|
+
_req.url = typeof path32 === "string" ? path32 : req.url ? req.url.path || req.url : void 0;
|
|
5446
5335
|
}
|
|
5447
5336
|
if (req.query) {
|
|
5448
5337
|
_req.query = req.query;
|
|
@@ -5607,14 +5496,14 @@ var require_redact = __commonJS({
|
|
|
5607
5496
|
}
|
|
5608
5497
|
return obj;
|
|
5609
5498
|
}
|
|
5610
|
-
function parsePath(
|
|
5499
|
+
function parsePath(path32) {
|
|
5611
5500
|
const parts = [];
|
|
5612
5501
|
let current = "";
|
|
5613
5502
|
let inBrackets = false;
|
|
5614
5503
|
let inQuotes = false;
|
|
5615
5504
|
let quoteChar = "";
|
|
5616
|
-
for (let i = 0; i <
|
|
5617
|
-
const char =
|
|
5505
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5506
|
+
const char = path32[i];
|
|
5618
5507
|
if (!inBrackets && char === ".") {
|
|
5619
5508
|
if (current) {
|
|
5620
5509
|
parts.push(current);
|
|
@@ -5745,10 +5634,10 @@ var require_redact = __commonJS({
|
|
|
5745
5634
|
return current;
|
|
5746
5635
|
}
|
|
5747
5636
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5748
|
-
for (const
|
|
5749
|
-
const parts = parsePath(
|
|
5637
|
+
for (const path32 of paths) {
|
|
5638
|
+
const parts = parsePath(path32);
|
|
5750
5639
|
if (parts.includes("*")) {
|
|
5751
|
-
redactWildcardPath(obj, parts, censor,
|
|
5640
|
+
redactWildcardPath(obj, parts, censor, path32, remove);
|
|
5752
5641
|
} else {
|
|
5753
5642
|
if (remove) {
|
|
5754
5643
|
removeKey(obj, parts);
|
|
@@ -5833,8 +5722,8 @@ var require_redact = __commonJS({
|
|
|
5833
5722
|
}
|
|
5834
5723
|
} else {
|
|
5835
5724
|
if (afterWildcard.includes("*")) {
|
|
5836
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5837
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5725
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
5726
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path32];
|
|
5838
5727
|
return censor(value, fullPath);
|
|
5839
5728
|
} : censor;
|
|
5840
5729
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5869,8 +5758,8 @@ var require_redact = __commonJS({
|
|
|
5869
5758
|
return null;
|
|
5870
5759
|
}
|
|
5871
5760
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5872
|
-
for (const
|
|
5873
|
-
const parts = parsePath(
|
|
5761
|
+
for (const path32 of pathsToClone) {
|
|
5762
|
+
const parts = parsePath(path32);
|
|
5874
5763
|
let current = pathStructure;
|
|
5875
5764
|
for (let i = 0; i < parts.length; i++) {
|
|
5876
5765
|
const part = parts[i];
|
|
@@ -5922,24 +5811,24 @@ var require_redact = __commonJS({
|
|
|
5922
5811
|
}
|
|
5923
5812
|
return cloneSelectively(obj, pathStructure);
|
|
5924
5813
|
}
|
|
5925
|
-
function validatePath(
|
|
5926
|
-
if (typeof
|
|
5814
|
+
function validatePath(path32) {
|
|
5815
|
+
if (typeof path32 !== "string") {
|
|
5927
5816
|
throw new Error("Paths must be (non-empty) strings");
|
|
5928
5817
|
}
|
|
5929
|
-
if (
|
|
5818
|
+
if (path32 === "") {
|
|
5930
5819
|
throw new Error("Invalid redaction path ()");
|
|
5931
5820
|
}
|
|
5932
|
-
if (
|
|
5933
|
-
throw new Error(`Invalid redaction path (${
|
|
5821
|
+
if (path32.includes("..")) {
|
|
5822
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5934
5823
|
}
|
|
5935
|
-
if (
|
|
5936
|
-
throw new Error(`Invalid redaction path (${
|
|
5824
|
+
if (path32.includes(",")) {
|
|
5825
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5937
5826
|
}
|
|
5938
5827
|
let bracketCount = 0;
|
|
5939
5828
|
let inQuotes = false;
|
|
5940
5829
|
let quoteChar = "";
|
|
5941
|
-
for (let i = 0; i <
|
|
5942
|
-
const char =
|
|
5830
|
+
for (let i = 0; i < path32.length; i++) {
|
|
5831
|
+
const char = path32[i];
|
|
5943
5832
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
5944
5833
|
if (!inQuotes) {
|
|
5945
5834
|
inQuotes = true;
|
|
@@ -5953,20 +5842,20 @@ var require_redact = __commonJS({
|
|
|
5953
5842
|
} else if (char === "]" && !inQuotes) {
|
|
5954
5843
|
bracketCount--;
|
|
5955
5844
|
if (bracketCount < 0) {
|
|
5956
|
-
throw new Error(`Invalid redaction path (${
|
|
5845
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5957
5846
|
}
|
|
5958
5847
|
}
|
|
5959
5848
|
}
|
|
5960
5849
|
if (bracketCount !== 0) {
|
|
5961
|
-
throw new Error(`Invalid redaction path (${
|
|
5850
|
+
throw new Error(`Invalid redaction path (${path32})`);
|
|
5962
5851
|
}
|
|
5963
5852
|
}
|
|
5964
5853
|
function validatePaths(paths) {
|
|
5965
5854
|
if (!Array.isArray(paths)) {
|
|
5966
5855
|
throw new TypeError("paths must be an array");
|
|
5967
5856
|
}
|
|
5968
|
-
for (const
|
|
5969
|
-
validatePath(
|
|
5857
|
+
for (const path32 of paths) {
|
|
5858
|
+
validatePath(path32);
|
|
5970
5859
|
}
|
|
5971
5860
|
}
|
|
5972
5861
|
function slowRedact(options = {}) {
|
|
@@ -6134,8 +6023,8 @@ var require_redaction = __commonJS({
|
|
|
6134
6023
|
if (shape[k2] === null) {
|
|
6135
6024
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6136
6025
|
} else {
|
|
6137
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6138
|
-
return censor(value, [k2, ...
|
|
6026
|
+
const wrappedCensor = typeof censor === "function" ? (value, path32) => {
|
|
6027
|
+
return censor(value, [k2, ...path32]);
|
|
6139
6028
|
} : censor;
|
|
6140
6029
|
o[k2] = Redact({
|
|
6141
6030
|
paths: shape[k2],
|
|
@@ -6356,7 +6245,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6356
6245
|
var fs28 = require("fs");
|
|
6357
6246
|
var EventEmitter2 = require("events");
|
|
6358
6247
|
var inherits = require("util").inherits;
|
|
6359
|
-
var
|
|
6248
|
+
var path32 = require("path");
|
|
6360
6249
|
var sleep = require_atomic_sleep();
|
|
6361
6250
|
var assert = require("assert");
|
|
6362
6251
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6410,7 +6299,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6410
6299
|
const mode = sonic.mode;
|
|
6411
6300
|
if (sonic.sync) {
|
|
6412
6301
|
try {
|
|
6413
|
-
if (sonic.mkdir) fs28.mkdirSync(
|
|
6302
|
+
if (sonic.mkdir) fs28.mkdirSync(path32.dirname(file), { recursive: true });
|
|
6414
6303
|
const fd = fs28.openSync(file, flags, mode);
|
|
6415
6304
|
fileOpened(null, fd);
|
|
6416
6305
|
} catch (err) {
|
|
@@ -6418,7 +6307,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6418
6307
|
throw err;
|
|
6419
6308
|
}
|
|
6420
6309
|
} else if (sonic.mkdir) {
|
|
6421
|
-
fs28.mkdir(
|
|
6310
|
+
fs28.mkdir(path32.dirname(file), { recursive: true }, (err) => {
|
|
6422
6311
|
if (err) return fileOpened(err);
|
|
6423
6312
|
fs28.open(file, flags, mode, fileOpened);
|
|
6424
6313
|
});
|
|
@@ -10049,11 +9938,11 @@ var init_lib = __esm({
|
|
|
10049
9938
|
}
|
|
10050
9939
|
}
|
|
10051
9940
|
},
|
|
10052
|
-
addToPath: function addToPath(
|
|
10053
|
-
var last =
|
|
9941
|
+
addToPath: function addToPath(path32, added, removed, oldPosInc, options) {
|
|
9942
|
+
var last = path32.lastComponent;
|
|
10054
9943
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10055
9944
|
return {
|
|
10056
|
-
oldPos:
|
|
9945
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10057
9946
|
lastComponent: {
|
|
10058
9947
|
count: last.count + 1,
|
|
10059
9948
|
added,
|
|
@@ -10063,7 +9952,7 @@ var init_lib = __esm({
|
|
|
10063
9952
|
};
|
|
10064
9953
|
} else {
|
|
10065
9954
|
return {
|
|
10066
|
-
oldPos:
|
|
9955
|
+
oldPos: path32.oldPos + oldPosInc,
|
|
10067
9956
|
lastComponent: {
|
|
10068
9957
|
count: 1,
|
|
10069
9958
|
added,
|
|
@@ -10494,10 +10383,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10494
10383
|
const memories = raw.map((m2) => {
|
|
10495
10384
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10496
10385
|
const rec = m2;
|
|
10497
|
-
const
|
|
10386
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
10498
10387
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10499
|
-
if (!
|
|
10500
|
-
const entry = { path:
|
|
10388
|
+
if (!path32 || content == null) return null;
|
|
10389
|
+
const entry = { path: path32, content };
|
|
10501
10390
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10502
10391
|
return entry;
|
|
10503
10392
|
}).filter((m2) => m2 !== null);
|
|
@@ -11301,10 +11190,10 @@ function parseAttachment(obj) {
|
|
|
11301
11190
|
const memories = raw.map((m2) => {
|
|
11302
11191
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11303
11192
|
const rec = m2;
|
|
11304
|
-
const
|
|
11193
|
+
const path32 = typeof rec.path === "string" ? rec.path : null;
|
|
11305
11194
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11306
|
-
if (!
|
|
11307
|
-
const out = { path:
|
|
11195
|
+
if (!path32 || content == null) return null;
|
|
11196
|
+
const out = { path: path32, content };
|
|
11308
11197
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11309
11198
|
return out;
|
|
11310
11199
|
}).filter((m2) => m2 !== null);
|
|
@@ -20218,7 +20107,7 @@ var require_websocket_server = __commonJS({
|
|
|
20218
20107
|
// src/run-case/recorder.ts
|
|
20219
20108
|
function startRunCaseRecorder(opts) {
|
|
20220
20109
|
const now = opts.now ?? Date.now;
|
|
20221
|
-
const dir =
|
|
20110
|
+
const dir = import_node_path28.default.dirname(opts.recordPath);
|
|
20222
20111
|
let stream = null;
|
|
20223
20112
|
let closing = false;
|
|
20224
20113
|
let closedSettled = false;
|
|
@@ -20258,12 +20147,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20258
20147
|
};
|
|
20259
20148
|
return { tap, close, closed };
|
|
20260
20149
|
}
|
|
20261
|
-
var import_node_fs25,
|
|
20150
|
+
var import_node_fs25, import_node_path28;
|
|
20262
20151
|
var init_recorder = __esm({
|
|
20263
20152
|
"src/run-case/recorder.ts"() {
|
|
20264
20153
|
"use strict";
|
|
20265
20154
|
import_node_fs25 = __toESM(require("fs"), 1);
|
|
20266
|
-
|
|
20155
|
+
import_node_path28 = __toESM(require("path"), 1);
|
|
20267
20156
|
}
|
|
20268
20157
|
});
|
|
20269
20158
|
|
|
@@ -20306,7 +20195,7 @@ var init_wire = __esm({
|
|
|
20306
20195
|
// src/run-case/controller.ts
|
|
20307
20196
|
async function runController(opts) {
|
|
20308
20197
|
const now = opts.now ?? Date.now;
|
|
20309
|
-
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-"));
|
|
20310
20199
|
const ownsCwd = opts.cwd === void 0;
|
|
20311
20200
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20312
20201
|
const spawnCtx = { cwd };
|
|
@@ -20473,13 +20362,13 @@ async function runController(opts) {
|
|
|
20473
20362
|
}
|
|
20474
20363
|
return exitCode ?? 0;
|
|
20475
20364
|
}
|
|
20476
|
-
var import_node_fs26, import_node_os14,
|
|
20365
|
+
var import_node_fs26, import_node_os14, import_node_path29;
|
|
20477
20366
|
var init_controller = __esm({
|
|
20478
20367
|
"src/run-case/controller.ts"() {
|
|
20479
20368
|
"use strict";
|
|
20480
20369
|
import_node_fs26 = require("fs");
|
|
20481
20370
|
import_node_os14 = __toESM(require("os"), 1);
|
|
20482
|
-
|
|
20371
|
+
import_node_path29 = __toESM(require("path"), 1);
|
|
20483
20372
|
init_claude();
|
|
20484
20373
|
init_stdout_splitter();
|
|
20485
20374
|
init_permission_stdio();
|
|
@@ -20711,7 +20600,7 @@ Env (advanced):
|
|
|
20711
20600
|
`;
|
|
20712
20601
|
|
|
20713
20602
|
// src/index.ts
|
|
20714
|
-
var
|
|
20603
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
20715
20604
|
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
20716
20605
|
|
|
20717
20606
|
// src/logger.ts
|
|
@@ -23430,15 +23319,6 @@ var PersonaRegistry = class {
|
|
|
23430
23319
|
list() {
|
|
23431
23320
|
return Array.from(this.cache.values());
|
|
23432
23321
|
}
|
|
23433
|
-
verifyToken(personaId, token) {
|
|
23434
|
-
const persona = this.cache.get(personaId);
|
|
23435
|
-
if (!persona) return { ok: false, code: "PERSONA_DELETED" };
|
|
23436
|
-
if (!persona.public) return { ok: false, code: "PERSONA_NOT_PUBLIC" };
|
|
23437
|
-
const entry = persona.tokenMap[token];
|
|
23438
|
-
if (!entry) return { ok: false, code: "TOKEN_INVALID" };
|
|
23439
|
-
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
23440
|
-
return { ok: true, label: entry.label };
|
|
23441
|
-
}
|
|
23442
23322
|
/**
|
|
23443
23323
|
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23444
23324
|
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
@@ -23448,7 +23328,7 @@ var PersonaRegistry = class {
|
|
|
23448
23328
|
if (!token) return null;
|
|
23449
23329
|
for (const persona of this.cache.values()) {
|
|
23450
23330
|
if (!persona.public) continue;
|
|
23451
|
-
const entry = persona.tokenMap[token];
|
|
23331
|
+
const entry = persona.tokenMap?.[token];
|
|
23452
23332
|
if (!entry || entry.revoked) continue;
|
|
23453
23333
|
return { personaId: persona.personaId, label: entry.label };
|
|
23454
23334
|
}
|
|
@@ -23747,6 +23627,8 @@ var PersonaManager = class {
|
|
|
23747
23627
|
model: args.model,
|
|
23748
23628
|
public: args.public ?? false,
|
|
23749
23629
|
iconKey: args.iconKey,
|
|
23630
|
+
// 初始化空 token 集合;后续由 issueToken / revokeToken 维护,
|
|
23631
|
+
// file-sharing HTTP Bearer 鉴权用 PersonaRegistry.findByToken 反查
|
|
23750
23632
|
tokenMap: {},
|
|
23751
23633
|
createdAt: now,
|
|
23752
23634
|
updatedAt: now
|
|
@@ -23790,15 +23672,16 @@ var PersonaManager = class {
|
|
|
23790
23672
|
}
|
|
23791
23673
|
/**
|
|
23792
23674
|
* 删除 persona。
|
|
23793
|
-
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/
|
|
23794
|
-
* 其中包含 .clawd/sub-sessions/——无需额外清理。
|
|
23795
|
-
* 旧的 <dataDir>/sessions/<personaId>/ 路径已废弃,不再维护。
|
|
23675
|
+
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录。
|
|
23796
23676
|
*/
|
|
23797
23677
|
delete(personaId) {
|
|
23798
23678
|
this.deps.store.remove(personaId);
|
|
23799
23679
|
this.deps.registry.remove(personaId);
|
|
23800
23680
|
}
|
|
23801
|
-
/**
|
|
23681
|
+
/**
|
|
23682
|
+
* 生成 32-char base64url token 并写入 tokenMap,给 file-sharing HTTP Bearer
|
|
23683
|
+
* 鉴权用(PersonaRegistry.findByToken 反查 personaId/label)。
|
|
23684
|
+
*/
|
|
23802
23685
|
issueToken(personaId, label) {
|
|
23803
23686
|
const existing = this.deps.registry.get(personaId);
|
|
23804
23687
|
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
@@ -23810,22 +23693,23 @@ var PersonaManager = class {
|
|
|
23810
23693
|
};
|
|
23811
23694
|
const updated = {
|
|
23812
23695
|
...existing,
|
|
23813
|
-
tokenMap: { ...existing.tokenMap, [token]: entry },
|
|
23696
|
+
tokenMap: { ...existing.tokenMap ?? {}, [token]: entry },
|
|
23814
23697
|
updatedAt: Date.now()
|
|
23815
23698
|
};
|
|
23816
23699
|
this.deps.store.writeMeta(updated);
|
|
23817
23700
|
this.deps.registry.set(updated);
|
|
23818
23701
|
return { token, persona: updated };
|
|
23819
23702
|
}
|
|
23703
|
+
/** 标记 token 为 revoked(保留条目用于审计);不存在的 token 抛错。 */
|
|
23820
23704
|
revokeToken(personaId, token) {
|
|
23821
23705
|
const existing = this.deps.registry.get(personaId);
|
|
23822
23706
|
if (!existing) throw new Error(`persona not found: ${personaId}`);
|
|
23823
|
-
const tokenEntry = existing.tokenMap[token];
|
|
23707
|
+
const tokenEntry = existing.tokenMap?.[token];
|
|
23824
23708
|
if (!tokenEntry) throw new Error(`token not found in persona ${personaId}`);
|
|
23825
23709
|
const updated = {
|
|
23826
23710
|
...existing,
|
|
23827
23711
|
tokenMap: {
|
|
23828
|
-
...existing.tokenMap,
|
|
23712
|
+
...existing.tokenMap ?? {},
|
|
23829
23713
|
[token]: { ...tokenEntry, revoked: true }
|
|
23830
23714
|
},
|
|
23831
23715
|
updatedAt: Date.now()
|
|
@@ -23834,70 +23718,6 @@ var PersonaManager = class {
|
|
|
23834
23718
|
this.deps.registry.set(updated);
|
|
23835
23719
|
return updated;
|
|
23836
23720
|
}
|
|
23837
|
-
/**
|
|
23838
|
-
* 拿到(或按需创建)该 (token, chatId) 对应的 sub-session。subSessionId 由 personaId + token +
|
|
23839
|
-
* chatId 三元组 hash 派生,保证 idempotent:同一 tuple 永远复用同一个 sub-session。
|
|
23840
|
-
*
|
|
23841
|
-
* chatId 必填 —— listener 协议从 v2 起拆 phase:客户端 auth 后通过 chat:open(chatId, autoCreate?)
|
|
23842
|
-
* 显式进入某个 chat;不再有 'default' 默认值。chatId 通常是客户端生成的 uuid,落 listener 端
|
|
23843
|
-
* 持久化由客户端负责(daemon 不替客户端记忆"最近哪个 chat")。
|
|
23844
|
-
*
|
|
23845
|
-
* 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
|
|
23846
|
-
* (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
|
|
23847
|
-
*/
|
|
23848
|
-
getOrCreateSubSession(personaId, token, chatId) {
|
|
23849
|
-
const persona = this.deps.registry.get(personaId);
|
|
23850
|
-
if (!persona) throw new Error(`persona not found: ${personaId}`);
|
|
23851
|
-
if (!chatId) throw new Error("chatId is required");
|
|
23852
|
-
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
23853
|
-
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
23854
|
-
const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
23855
|
-
if (existing) {
|
|
23856
|
-
return { sessionFile: existing, isNew: false };
|
|
23857
|
-
}
|
|
23858
|
-
const tokenEntry = persona.tokenMap[token];
|
|
23859
|
-
const subLabel = tokenEntry?.label ?? "unknown";
|
|
23860
|
-
const sessionFile = this.deps.sessionManager.createForScope({
|
|
23861
|
-
sessionId: subSessionId,
|
|
23862
|
-
scope,
|
|
23863
|
-
cwd: this.deps.store.personaDirPath(personaId),
|
|
23864
|
-
tool: "claude",
|
|
23865
|
-
label: subLabel,
|
|
23866
|
-
model: persona.model,
|
|
23867
|
-
chatId
|
|
23868
|
-
});
|
|
23869
|
-
return { sessionFile, isNew: true };
|
|
23870
|
-
}
|
|
23871
|
-
/**
|
|
23872
|
-
* 检查 (token, chatId) 对应的 sub-session 是否存在;不存在返回 null。
|
|
23873
|
-
* chat:open(autoCreate=false) 路径用:listener 想"只切已有 chat"时不能撞 auto-create。
|
|
23874
|
-
*/
|
|
23875
|
-
getSubSession(personaId, token, chatId) {
|
|
23876
|
-
if (!chatId) return null;
|
|
23877
|
-
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
23878
|
-
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
23879
|
-
return this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
23880
|
-
}
|
|
23881
|
-
/**
|
|
23882
|
-
* 校验 sessionId 是否属于 (personaId, token) tuple(前 12 字符 tokenHash 一致)。
|
|
23883
|
-
* chat:rename / chat:delete 用:listener 只能操作自己 tuple 内的 chat。
|
|
23884
|
-
*/
|
|
23885
|
-
tokenPrefixFor(personaId, token) {
|
|
23886
|
-
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23887
|
-
return `${personaId}-${tokenHash}`;
|
|
23888
|
-
}
|
|
23889
|
-
/**
|
|
23890
|
-
* 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
|
|
23891
|
-
* 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
|
|
23892
|
-
*/
|
|
23893
|
-
appendOwnerMessage(personaId, subSessionId, text) {
|
|
23894
|
-
this.deps.sessionManager.injectOwnerMessage({
|
|
23895
|
-
sessionId: subSessionId,
|
|
23896
|
-
scope: { kind: "persona", personaId, mode: "listener" },
|
|
23897
|
-
text
|
|
23898
|
-
});
|
|
23899
|
-
}
|
|
23900
|
-
// ---------------- 内部 ----------------
|
|
23901
23721
|
/**
|
|
23902
23722
|
* label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
|
|
23903
23723
|
* base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
|
|
@@ -23913,15 +23733,6 @@ var PersonaManager = class {
|
|
|
23913
23733
|
}
|
|
23914
23734
|
throw new Error(`failed to generate unique personaId for label=${label}`);
|
|
23915
23735
|
}
|
|
23916
|
-
/**
|
|
23917
|
-
* subSessionId 派生:永远三元组 `${personaId}-${tokenHash12}-${chatHash8}`,无 default 特例。
|
|
23918
|
-
* 旧 v1 实现保留 default 二元组兼容已废除。
|
|
23919
|
-
*/
|
|
23920
|
-
deriveSubSessionId(personaId, token, chatId) {
|
|
23921
|
-
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23922
|
-
const chatHash = import_node_crypto3.default.createHash("sha256").update(chatId).digest("hex").slice(0, 8);
|
|
23923
|
-
return `${personaId}-${tokenHash}-${chatHash}`;
|
|
23924
|
-
}
|
|
23925
23736
|
};
|
|
23926
23737
|
|
|
23927
23738
|
// src/persona/seed.ts
|
|
@@ -25396,7 +25207,6 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
25396
25207
|
|
|
25397
25208
|
// src/transport/local-ws-server.ts
|
|
25398
25209
|
var import_node_http = __toESM(require("http"), 1);
|
|
25399
|
-
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
25400
25210
|
var LocalWsServer = class {
|
|
25401
25211
|
constructor(opts) {
|
|
25402
25212
|
this.opts = opts;
|
|
@@ -25519,8 +25329,7 @@ var LocalWsServer = class {
|
|
|
25519
25329
|
if (!c) return;
|
|
25520
25330
|
this.safeSend(c.ws, frame);
|
|
25521
25331
|
}
|
|
25522
|
-
// URL path 路由:'/' →
|
|
25523
|
-
// '/personas/<id>' → personaBoundHandler,其它 → close 4404
|
|
25332
|
+
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25524
25333
|
routeConnection(socket, req) {
|
|
25525
25334
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
25526
25335
|
const urlPath = (() => {
|
|
@@ -25534,15 +25343,6 @@ var LocalWsServer = class {
|
|
|
25534
25343
|
this.onConnection(socket, remoteAddress);
|
|
25535
25344
|
return;
|
|
25536
25345
|
}
|
|
25537
|
-
const personaMatch = urlPath.match(PERSONA_PATH_RE);
|
|
25538
|
-
if (personaMatch && this.opts.personaBoundHandler) {
|
|
25539
|
-
this.logger?.info("persona ws connected", {
|
|
25540
|
-
personaId: personaMatch[1],
|
|
25541
|
-
remoteAddress
|
|
25542
|
-
});
|
|
25543
|
-
this.opts.personaBoundHandler.handle(socket, personaMatch[1]);
|
|
25544
|
-
return;
|
|
25545
|
-
}
|
|
25546
25346
|
try {
|
|
25547
25347
|
socket.close(4404, "unknown path");
|
|
25548
25348
|
} catch {
|
|
@@ -25685,434 +25485,6 @@ function removeSubscription(client, sessionId) {
|
|
|
25685
25485
|
else client.subscribedSessions.set(sessionId, next);
|
|
25686
25486
|
}
|
|
25687
25487
|
|
|
25688
|
-
// src/transport/persona-bound-handler.ts
|
|
25689
|
-
init_protocol();
|
|
25690
|
-
var PersonaBoundHandler = class {
|
|
25691
|
-
constructor(deps) {
|
|
25692
|
-
this.deps = deps;
|
|
25693
|
-
}
|
|
25694
|
-
deps;
|
|
25695
|
-
// tuple fan-out 表:tokenPrefix → 同 tuple 所有 listener ws 的 send fn 集合
|
|
25696
|
-
tupleSubscribers = /* @__PURE__ */ new Map();
|
|
25697
|
-
// tuple → 当前活跃 ws 数;从 0→1 emit online;从 1→0 emit offline
|
|
25698
|
-
listenerWsCount = /* @__PURE__ */ new Map();
|
|
25699
|
-
handle(ws, personaId) {
|
|
25700
|
-
let phase = "pre-auth";
|
|
25701
|
-
let scope = null;
|
|
25702
|
-
let unsubscribeSession = null;
|
|
25703
|
-
const send = (frame) => {
|
|
25704
|
-
try {
|
|
25705
|
-
ws.send(JSON.stringify(frame));
|
|
25706
|
-
} catch (err) {
|
|
25707
|
-
this.deps.logger.warn(`persona ws send failed: ${err.message}`);
|
|
25708
|
-
}
|
|
25709
|
-
};
|
|
25710
|
-
const sendError = (requestId, code, message) => {
|
|
25711
|
-
const errFrame = { type: "error", code, message };
|
|
25712
|
-
if (requestId) errFrame.requestId = requestId;
|
|
25713
|
-
send(errFrame);
|
|
25714
|
-
};
|
|
25715
|
-
const cleanupTupleRegistration = () => {
|
|
25716
|
-
if (!scope) return;
|
|
25717
|
-
const subs = this.tupleSubscribers.get(scope.tokenPrefix);
|
|
25718
|
-
if (subs) {
|
|
25719
|
-
subs.delete(send);
|
|
25720
|
-
if (subs.size === 0) this.tupleSubscribers.delete(scope.tokenPrefix);
|
|
25721
|
-
}
|
|
25722
|
-
const before = this.listenerWsCount.get(scope.tokenPrefix) ?? 0;
|
|
25723
|
-
const after = before - 1;
|
|
25724
|
-
if (after <= 0) {
|
|
25725
|
-
this.listenerWsCount.delete(scope.tokenPrefix);
|
|
25726
|
-
this.deps.ownerBroadcast(
|
|
25727
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25728
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25729
|
-
personaId: scope.personaId,
|
|
25730
|
-
token: scope.token,
|
|
25731
|
-
status: "offline",
|
|
25732
|
-
activeWsCount: 0
|
|
25733
|
-
})
|
|
25734
|
-
);
|
|
25735
|
-
} else {
|
|
25736
|
-
this.listenerWsCount.set(scope.tokenPrefix, after);
|
|
25737
|
-
this.deps.ownerBroadcast(
|
|
25738
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25739
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25740
|
-
personaId: scope.personaId,
|
|
25741
|
-
token: scope.token,
|
|
25742
|
-
status: "online",
|
|
25743
|
-
activeWsCount: after
|
|
25744
|
-
})
|
|
25745
|
-
);
|
|
25746
|
-
}
|
|
25747
|
-
};
|
|
25748
|
-
ws.on("message", (raw) => {
|
|
25749
|
-
let parsedRaw;
|
|
25750
|
-
try {
|
|
25751
|
-
parsedRaw = JSON.parse(raw.toString());
|
|
25752
|
-
} catch {
|
|
25753
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25754
|
-
return;
|
|
25755
|
-
}
|
|
25756
|
-
if (typeof parsedRaw !== "object" || parsedRaw === null) {
|
|
25757
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25758
|
-
return;
|
|
25759
|
-
}
|
|
25760
|
-
const frame = parsedRaw;
|
|
25761
|
-
if (phase === "pre-auth") {
|
|
25762
|
-
const authParse = AuthRequestFrameSchema.safeParse(frame);
|
|
25763
|
-
if (!authParse.success) {
|
|
25764
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25765
|
-
return;
|
|
25766
|
-
}
|
|
25767
|
-
const { token } = authParse.data;
|
|
25768
|
-
const verify = this.deps.registry.verifyToken(personaId, token);
|
|
25769
|
-
if (!verify.ok) {
|
|
25770
|
-
const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
|
|
25771
|
-
ws.close(code, verify.code);
|
|
25772
|
-
return;
|
|
25773
|
-
}
|
|
25774
|
-
const persona = this.deps.registry.get(personaId);
|
|
25775
|
-
if (!persona) {
|
|
25776
|
-
ws.close(4404, "PERSONA_DELETED");
|
|
25777
|
-
return;
|
|
25778
|
-
}
|
|
25779
|
-
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
25780
|
-
scope = { personaId, token, tokenPrefix };
|
|
25781
|
-
phase = "tuple-watch";
|
|
25782
|
-
let subs = this.tupleSubscribers.get(tokenPrefix);
|
|
25783
|
-
if (!subs) {
|
|
25784
|
-
subs = /* @__PURE__ */ new Set();
|
|
25785
|
-
this.tupleSubscribers.set(tokenPrefix, subs);
|
|
25786
|
-
}
|
|
25787
|
-
subs.add(send);
|
|
25788
|
-
const nextCount = (this.listenerWsCount.get(tokenPrefix) ?? 0) + 1;
|
|
25789
|
-
this.listenerWsCount.set(tokenPrefix, nextCount);
|
|
25790
|
-
this.deps.ownerBroadcast(
|
|
25791
|
-
PersonaListenerStatusFrameSchema.parse({
|
|
25792
|
-
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
25793
|
-
personaId,
|
|
25794
|
-
token,
|
|
25795
|
-
status: "online",
|
|
25796
|
-
activeWsCount: nextCount
|
|
25797
|
-
})
|
|
25798
|
-
);
|
|
25799
|
-
send({ type: "auth:ok" });
|
|
25800
|
-
send(
|
|
25801
|
-
ChatListPushSchema.parse({
|
|
25802
|
-
type: FRAME_TYPE_CHAT_LIST,
|
|
25803
|
-
chats: this.listChatsForTuple(personaId, tokenPrefix)
|
|
25804
|
-
})
|
|
25805
|
-
);
|
|
25806
|
-
return;
|
|
25807
|
-
}
|
|
25808
|
-
const type = frame.type;
|
|
25809
|
-
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
25810
|
-
if (type === "auth") {
|
|
25811
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25812
|
-
return;
|
|
25813
|
-
}
|
|
25814
|
-
if (type === "ping") {
|
|
25815
|
-
send({ type: "pong", at: Date.now() });
|
|
25816
|
-
return;
|
|
25817
|
-
}
|
|
25818
|
-
if (!scope) {
|
|
25819
|
-
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
25820
|
-
return;
|
|
25821
|
-
}
|
|
25822
|
-
if (type === "chat:open") {
|
|
25823
|
-
const parsed = ChatOpenRequestSchema.safeParse(frame);
|
|
25824
|
-
if (!parsed.success) {
|
|
25825
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25826
|
-
return;
|
|
25827
|
-
}
|
|
25828
|
-
const { chatId, autoCreate } = parsed.data;
|
|
25829
|
-
let sessionFile;
|
|
25830
|
-
let isNew = false;
|
|
25831
|
-
try {
|
|
25832
|
-
if (autoCreate) {
|
|
25833
|
-
const r = this.deps.personaManager.getOrCreateSubSession(scope.personaId, scope.token, chatId);
|
|
25834
|
-
sessionFile = r.sessionFile;
|
|
25835
|
-
isNew = r.isNew;
|
|
25836
|
-
} else {
|
|
25837
|
-
sessionFile = this.deps.personaManager.getSubSession(scope.personaId, scope.token, chatId);
|
|
25838
|
-
if (!sessionFile) {
|
|
25839
|
-
sendError(requestId, "SESSION_NOT_FOUND", `chatId not found: ${chatId}`);
|
|
25840
|
-
return;
|
|
25841
|
-
}
|
|
25842
|
-
}
|
|
25843
|
-
} catch (err) {
|
|
25844
|
-
sendError(requestId, "INTERNAL", err.message);
|
|
25845
|
-
return;
|
|
25846
|
-
}
|
|
25847
|
-
if (unsubscribeSession && scope.activeSubSessionId !== sessionFile.sessionId) {
|
|
25848
|
-
unsubscribeSession();
|
|
25849
|
-
unsubscribeSession = null;
|
|
25850
|
-
}
|
|
25851
|
-
if (!unsubscribeSession) {
|
|
25852
|
-
unsubscribeSession = this.deps.sessionManager.subscribe(
|
|
25853
|
-
sessionFile.sessionId,
|
|
25854
|
-
{ kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
25855
|
-
(eventFrame) => send(eventFrame)
|
|
25856
|
-
);
|
|
25857
|
-
}
|
|
25858
|
-
scope.activeSubSessionId = sessionFile.sessionId;
|
|
25859
|
-
phase = "chat-active";
|
|
25860
|
-
const persona = this.deps.registry.get(scope.personaId);
|
|
25861
|
-
const infoFrame = {
|
|
25862
|
-
type: "session:info",
|
|
25863
|
-
sessionId: sessionFile.sessionId,
|
|
25864
|
-
chatId,
|
|
25865
|
-
label: persona?.label ?? "",
|
|
25866
|
-
cwd: sessionFile.cwd
|
|
25867
|
-
};
|
|
25868
|
-
if (persona?.model) infoFrame.model = persona.model;
|
|
25869
|
-
if (requestId) infoFrame.requestId = requestId;
|
|
25870
|
-
send(infoFrame);
|
|
25871
|
-
send({
|
|
25872
|
-
type: "session:status",
|
|
25873
|
-
sessionId: sessionFile.sessionId,
|
|
25874
|
-
status: this.deps.sessionManager.getCurrentStatus(sessionFile.sessionId)
|
|
25875
|
-
});
|
|
25876
|
-
if (isNew) {
|
|
25877
|
-
const createdBase = {
|
|
25878
|
-
sessionId: sessionFile.sessionId,
|
|
25879
|
-
chatId,
|
|
25880
|
-
label: sessionFile.label,
|
|
25881
|
-
createdAt: sessionFile.createdAt
|
|
25882
|
-
};
|
|
25883
|
-
this.fanoutTuple(
|
|
25884
|
-
scope.tokenPrefix,
|
|
25885
|
-
ChatCreatedFrameSchema.parse({ type: FRAME_TYPE_CHAT_CREATED, ...createdBase })
|
|
25886
|
-
);
|
|
25887
|
-
this.deps.ownerBroadcast(
|
|
25888
|
-
PersonaListenerChatCreatedFrameSchema.parse({
|
|
25889
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED,
|
|
25890
|
-
personaId: scope.personaId,
|
|
25891
|
-
token: scope.token,
|
|
25892
|
-
...createdBase
|
|
25893
|
-
})
|
|
25894
|
-
);
|
|
25895
|
-
}
|
|
25896
|
-
return;
|
|
25897
|
-
}
|
|
25898
|
-
if (type === "chat:rename") {
|
|
25899
|
-
const parsed = ChatRenameRequestSchema.safeParse(frame);
|
|
25900
|
-
if (!parsed.success) {
|
|
25901
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25902
|
-
return;
|
|
25903
|
-
}
|
|
25904
|
-
const { sessionId: targetId, label } = parsed.data;
|
|
25905
|
-
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
25906
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
25907
|
-
return;
|
|
25908
|
-
}
|
|
25909
|
-
try {
|
|
25910
|
-
this.deps.sessionManager.renameForScope({
|
|
25911
|
-
sessionId: targetId,
|
|
25912
|
-
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
25913
|
-
label
|
|
25914
|
-
});
|
|
25915
|
-
} catch (err) {
|
|
25916
|
-
const e = err;
|
|
25917
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
25918
|
-
return;
|
|
25919
|
-
}
|
|
25920
|
-
if (requestId) send({ type: FRAME_TYPE_CHAT_RENAME, requestId, ok: true });
|
|
25921
|
-
this.fanoutTuple(
|
|
25922
|
-
scope.tokenPrefix,
|
|
25923
|
-
ChatRenamedFrameSchema.parse({ type: FRAME_TYPE_CHAT_RENAMED, sessionId: targetId, label })
|
|
25924
|
-
);
|
|
25925
|
-
this.deps.ownerBroadcast(
|
|
25926
|
-
PersonaListenerChatRenamedFrameSchema.parse({
|
|
25927
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED,
|
|
25928
|
-
personaId: scope.personaId,
|
|
25929
|
-
token: scope.token,
|
|
25930
|
-
sessionId: targetId,
|
|
25931
|
-
label
|
|
25932
|
-
})
|
|
25933
|
-
);
|
|
25934
|
-
return;
|
|
25935
|
-
}
|
|
25936
|
-
if (type === "chat:delete") {
|
|
25937
|
-
const parsed = ChatDeleteRequestSchema.safeParse(frame);
|
|
25938
|
-
if (!parsed.success) {
|
|
25939
|
-
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
25940
|
-
return;
|
|
25941
|
-
}
|
|
25942
|
-
const { sessionId: targetId } = parsed.data;
|
|
25943
|
-
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
25944
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
25945
|
-
return;
|
|
25946
|
-
}
|
|
25947
|
-
const before = this.deps.sessionManager.readForScope(targetId, {
|
|
25948
|
-
kind: "persona",
|
|
25949
|
-
personaId: scope.personaId,
|
|
25950
|
-
mode: "listener"
|
|
25951
|
-
});
|
|
25952
|
-
const chatIdForFrame = before?.chatId ?? "unknown";
|
|
25953
|
-
try {
|
|
25954
|
-
this.deps.sessionManager.deleteForScope({
|
|
25955
|
-
sessionId: targetId,
|
|
25956
|
-
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" }
|
|
25957
|
-
});
|
|
25958
|
-
} catch (err) {
|
|
25959
|
-
const e = err;
|
|
25960
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
25961
|
-
return;
|
|
25962
|
-
}
|
|
25963
|
-
if (requestId) send({ type: FRAME_TYPE_CHAT_DELETE, requestId, ok: true });
|
|
25964
|
-
this.fanoutTuple(
|
|
25965
|
-
scope.tokenPrefix,
|
|
25966
|
-
ChatDeletedFrameSchema.parse({ type: FRAME_TYPE_CHAT_DELETED, sessionId: targetId })
|
|
25967
|
-
);
|
|
25968
|
-
this.deps.ownerBroadcast(
|
|
25969
|
-
PersonaListenerChatDeletedFrameSchema.parse({
|
|
25970
|
-
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED,
|
|
25971
|
-
personaId: scope.personaId,
|
|
25972
|
-
token: scope.token,
|
|
25973
|
-
sessionId: targetId,
|
|
25974
|
-
chatId: chatIdForFrame
|
|
25975
|
-
})
|
|
25976
|
-
);
|
|
25977
|
-
if (scope.activeSubSessionId === targetId) {
|
|
25978
|
-
unsubscribeSession?.();
|
|
25979
|
-
unsubscribeSession = null;
|
|
25980
|
-
scope.activeSubSessionId = void 0;
|
|
25981
|
-
phase = "tuple-watch";
|
|
25982
|
-
}
|
|
25983
|
-
return;
|
|
25984
|
-
}
|
|
25985
|
-
if (phase !== "chat-active" || !scope.activeSubSessionId) {
|
|
25986
|
-
sendError(
|
|
25987
|
-
requestId,
|
|
25988
|
-
"METHOD_NOT_ALLOWED",
|
|
25989
|
-
`${typeof type === "string" ? type : "unknown"} requires chat:open first`
|
|
25990
|
-
);
|
|
25991
|
-
return;
|
|
25992
|
-
}
|
|
25993
|
-
const requireScopedSession = () => {
|
|
25994
|
-
if (frame.sessionId !== scope.activeSubSessionId) {
|
|
25995
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of scope");
|
|
25996
|
-
return false;
|
|
25997
|
-
}
|
|
25998
|
-
return true;
|
|
25999
|
-
};
|
|
26000
|
-
const listenerScope = () => ({
|
|
26001
|
-
kind: "persona",
|
|
26002
|
-
personaId: scope.personaId,
|
|
26003
|
-
mode: "listener"
|
|
26004
|
-
});
|
|
26005
|
-
switch (type) {
|
|
26006
|
-
case "session:send": {
|
|
26007
|
-
if (!requireScopedSession()) return;
|
|
26008
|
-
const text = frame.text;
|
|
26009
|
-
if (typeof text !== "string") {
|
|
26010
|
-
sendError(requestId, "VALIDATION_ERROR", "text must be string");
|
|
26011
|
-
return;
|
|
26012
|
-
}
|
|
26013
|
-
try {
|
|
26014
|
-
this.deps.sessionManager.sendForScope({
|
|
26015
|
-
sessionId: scope.activeSubSessionId,
|
|
26016
|
-
scope: listenerScope(),
|
|
26017
|
-
text
|
|
26018
|
-
});
|
|
26019
|
-
if (requestId) send({ type: "session:send", ok: true, requestId });
|
|
26020
|
-
} catch (err) {
|
|
26021
|
-
const e = err;
|
|
26022
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26023
|
-
}
|
|
26024
|
-
return;
|
|
26025
|
-
}
|
|
26026
|
-
case "session:new": {
|
|
26027
|
-
if (!requireScopedSession()) return;
|
|
26028
|
-
try {
|
|
26029
|
-
this.deps.sessionManager.resetForScope({
|
|
26030
|
-
sessionId: scope.activeSubSessionId,
|
|
26031
|
-
scope: listenerScope()
|
|
26032
|
-
});
|
|
26033
|
-
if (requestId)
|
|
26034
|
-
send({ type: "session:info", sessionId: scope.activeSubSessionId, requestId });
|
|
26035
|
-
} catch (err) {
|
|
26036
|
-
const e = err;
|
|
26037
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26038
|
-
}
|
|
26039
|
-
return;
|
|
26040
|
-
}
|
|
26041
|
-
case "history:read": {
|
|
26042
|
-
if (!requireScopedSession()) return;
|
|
26043
|
-
const limit = typeof frame.limit === "number" && frame.limit > 0 ? frame.limit : 50;
|
|
26044
|
-
const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
|
|
26045
|
-
try {
|
|
26046
|
-
const page = this.deps.sessionManager.readHistoryPageForScope(
|
|
26047
|
-
scope.activeSubSessionId,
|
|
26048
|
-
listenerScope(),
|
|
26049
|
-
limit,
|
|
26050
|
-
offset
|
|
26051
|
-
);
|
|
26052
|
-
if (requestId) {
|
|
26053
|
-
send({
|
|
26054
|
-
type: "history:read",
|
|
26055
|
-
requestId,
|
|
26056
|
-
events: page.events,
|
|
26057
|
-
offset,
|
|
26058
|
-
total: page.total
|
|
26059
|
-
});
|
|
26060
|
-
}
|
|
26061
|
-
} catch (err) {
|
|
26062
|
-
const e = err;
|
|
26063
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
26064
|
-
}
|
|
26065
|
-
return;
|
|
26066
|
-
}
|
|
26067
|
-
default:
|
|
26068
|
-
sendError(
|
|
26069
|
-
requestId,
|
|
26070
|
-
"METHOD_NOT_ALLOWED",
|
|
26071
|
-
`${typeof type === "string" ? type : "unknown"} not allowed for listener`
|
|
26072
|
-
);
|
|
26073
|
-
return;
|
|
26074
|
-
}
|
|
26075
|
-
});
|
|
26076
|
-
ws.on("close", () => {
|
|
26077
|
-
unsubscribeSession?.();
|
|
26078
|
-
unsubscribeSession = null;
|
|
26079
|
-
cleanupTupleRegistration();
|
|
26080
|
-
});
|
|
26081
|
-
}
|
|
26082
|
-
// ---- helpers ----
|
|
26083
|
-
fanoutTuple(tokenPrefix, frame) {
|
|
26084
|
-
const subs = this.tupleSubscribers.get(tokenPrefix);
|
|
26085
|
-
if (!subs) return;
|
|
26086
|
-
for (const fn of subs) {
|
|
26087
|
-
try {
|
|
26088
|
-
fn(frame);
|
|
26089
|
-
} catch {
|
|
26090
|
-
}
|
|
26091
|
-
}
|
|
26092
|
-
}
|
|
26093
|
-
isInTuple(sessionId, tokenPrefix) {
|
|
26094
|
-
return sessionId === tokenPrefix || sessionId.startsWith(`${tokenPrefix}-`);
|
|
26095
|
-
}
|
|
26096
|
-
listChatsForTuple(personaId, tokenPrefix) {
|
|
26097
|
-
const all = this.deps.sessionManager.listPersonaSessions(personaId, "listener");
|
|
26098
|
-
return all.filter((s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)).map((s) => ({
|
|
26099
|
-
sessionId: s.sessionId,
|
|
26100
|
-
chatId: s.chatId ?? "unknown",
|
|
26101
|
-
label: s.label ?? "",
|
|
26102
|
-
createdAt: s.createdAt
|
|
26103
|
-
}));
|
|
26104
|
-
}
|
|
26105
|
-
// ---------------- owner-side helpers ----------------
|
|
26106
|
-
/**
|
|
26107
|
-
* owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session 元数据。
|
|
26108
|
-
* 由 handlers/persona.ts persona:listListenerChatsForToken 调用。
|
|
26109
|
-
*/
|
|
26110
|
-
listChatsForOwner(personaId, token) {
|
|
26111
|
-
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
26112
|
-
return this.listChatsForTuple(personaId, tokenPrefix);
|
|
26113
|
-
}
|
|
26114
|
-
};
|
|
26115
|
-
|
|
26116
25488
|
// src/transport/auth.ts
|
|
26117
25489
|
init_protocol();
|
|
26118
25490
|
var AuthGate = class {
|
|
@@ -27475,14 +26847,24 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
27475
26847
|
function authFilePath(dataDir) {
|
|
27476
26848
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
27477
26849
|
}
|
|
27478
|
-
function
|
|
26850
|
+
function loadOrCreateAuthFile(opts) {
|
|
27479
26851
|
const file = authFilePath(opts.dataDir);
|
|
26852
|
+
const generate = opts.generate ?? defaultGenerate;
|
|
26853
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
27480
26854
|
const existing = readAuthFile(file);
|
|
27481
|
-
if (existing && existing.token
|
|
27482
|
-
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
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;
|
|
27486
26868
|
}
|
|
27487
26869
|
function defaultGenerate() {
|
|
27488
26870
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
@@ -27491,13 +26873,14 @@ function readAuthFile(file) {
|
|
|
27491
26873
|
try {
|
|
27492
26874
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27493
26875
|
const parsed = JSON.parse(raw);
|
|
27494
|
-
if (typeof parsed?.token
|
|
27495
|
-
return
|
|
27496
|
-
token: parsed.token,
|
|
27497
|
-
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
27498
|
-
};
|
|
26876
|
+
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
26877
|
+
return null;
|
|
27499
26878
|
}
|
|
27500
|
-
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
|
+
};
|
|
27501
26884
|
} catch (err) {
|
|
27502
26885
|
const code = err?.code;
|
|
27503
26886
|
if (code === "ENOENT") return null;
|
|
@@ -28097,7 +27480,7 @@ function buildMetaHandlers(deps) {
|
|
|
28097
27480
|
// src/handlers/persona.ts
|
|
28098
27481
|
init_protocol();
|
|
28099
27482
|
function buildPersonaHandlers(deps) {
|
|
28100
|
-
const { personaManager, personaRegistry
|
|
27483
|
+
const { personaManager, personaRegistry } = deps;
|
|
28101
27484
|
const create = async (frame) => {
|
|
28102
27485
|
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28103
27486
|
const args = PersonaCreateArgsSchema.parse(rest);
|
|
@@ -28154,27 +27537,6 @@ function buildPersonaHandlers(deps) {
|
|
|
28154
27537
|
const persona = personaManager.revokeToken(args.personaId, args.token);
|
|
28155
27538
|
return { response: { type: "persona:info", ...persona } };
|
|
28156
27539
|
};
|
|
28157
|
-
const listSubSessions = async (frame) => {
|
|
28158
|
-
const args = PersonaIdArgsSchema.parse(frame);
|
|
28159
|
-
const sessions = sessionManager.listPersonaSessions(args.personaId, "listener");
|
|
28160
|
-
return {
|
|
28161
|
-
response: { type: "persona:subSessions", sessions }
|
|
28162
|
-
};
|
|
28163
|
-
};
|
|
28164
|
-
const appendOwnerMessage = async (frame) => {
|
|
28165
|
-
const args = PersonaAppendOwnerMessageArgsSchema.parse(frame);
|
|
28166
|
-
personaManager.appendOwnerMessage(args.personaId, args.subSessionId, args.text);
|
|
28167
|
-
return {
|
|
28168
|
-
response: { type: "persona:appendOwnerMessage", ok: true }
|
|
28169
|
-
};
|
|
28170
|
-
};
|
|
28171
|
-
const listListenerChatsForToken = async (frame) => {
|
|
28172
|
-
const args = PersonaListListenerChatsForTokenArgsSchema.parse(frame);
|
|
28173
|
-
const chats = personaBoundHandler.listChatsForOwner(args.personaId, args.token);
|
|
28174
|
-
return {
|
|
28175
|
-
response: { type: "persona:listListenerChatsForToken", chats }
|
|
28176
|
-
};
|
|
28177
|
-
};
|
|
28178
27540
|
return {
|
|
28179
27541
|
"persona:create": create,
|
|
28180
27542
|
"persona:list": list,
|
|
@@ -28182,14 +27544,12 @@ function buildPersonaHandlers(deps) {
|
|
|
28182
27544
|
"persona:update": update,
|
|
28183
27545
|
"persona:delete": del,
|
|
28184
27546
|
"persona:issueToken": issueToken,
|
|
28185
|
-
"persona:revokeToken": revokeToken
|
|
28186
|
-
"persona:listSubSessions": listSubSessions,
|
|
28187
|
-
"persona:listListenerChatsForToken": listListenerChatsForToken,
|
|
28188
|
-
"persona:appendOwnerMessage": appendOwnerMessage
|
|
27547
|
+
"persona:revokeToken": revokeToken
|
|
28189
27548
|
};
|
|
28190
27549
|
}
|
|
28191
27550
|
|
|
28192
27551
|
// src/handlers/attachment.ts
|
|
27552
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
28193
27553
|
init_protocol();
|
|
28194
27554
|
init_protocol();
|
|
28195
27555
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -28214,8 +27574,51 @@ function buildAttachmentHandlers(deps) {
|
|
|
28214
27574
|
"httpBaseUrl unavailable (daemon HTTP not ready)"
|
|
28215
27575
|
);
|
|
28216
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
|
+
}
|
|
28217
27620
|
const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
28218
|
-
const parts = signUrlParts(secret,
|
|
27621
|
+
const parts = signUrlParts(secret, candidateAbs, ttl);
|
|
28219
27622
|
const url = buildSignedFileUrl(httpBaseUrl, parts);
|
|
28220
27623
|
return {
|
|
28221
27624
|
response: {
|
|
@@ -28301,6 +27704,12 @@ function buildAttachmentHandlers(deps) {
|
|
|
28301
27704
|
"attachment.groupListPersona": groupListPersona
|
|
28302
27705
|
};
|
|
28303
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
|
+
}
|
|
28304
27713
|
|
|
28305
27714
|
// src/handlers/index.ts
|
|
28306
27715
|
function buildMethodHandlers(deps) {
|
|
@@ -28314,9 +27723,7 @@ function buildMethodHandlers(deps) {
|
|
|
28314
27723
|
...buildMetaHandlers(deps),
|
|
28315
27724
|
...buildPersonaHandlers({
|
|
28316
27725
|
personaManager: deps.personaManager,
|
|
28317
|
-
personaRegistry: deps.personaRegistry
|
|
28318
|
-
sessionManager: deps.manager,
|
|
28319
|
-
personaBoundHandler: deps.personaBoundHandler
|
|
27726
|
+
personaRegistry: deps.personaRegistry
|
|
28320
27727
|
}),
|
|
28321
27728
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
28322
27729
|
};
|
|
@@ -28326,7 +27733,7 @@ function buildMethodHandlers(deps) {
|
|
|
28326
27733
|
async function startDaemon(config) {
|
|
28327
27734
|
const logger = createLogger({
|
|
28328
27735
|
level: config.logLevel,
|
|
28329
|
-
file:
|
|
27736
|
+
file: import_node_path27.default.join(config.dataDir, "clawd.log")
|
|
28330
27737
|
});
|
|
28331
27738
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
28332
27739
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -28338,10 +27745,12 @@ async function startDaemon(config) {
|
|
|
28338
27745
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
28339
27746
|
}
|
|
28340
27747
|
let resolvedAuthToken = null;
|
|
27748
|
+
let authFile = null;
|
|
28341
27749
|
if (config.authToken && config.authToken.trim()) {
|
|
28342
27750
|
resolvedAuthToken = config.authToken.trim();
|
|
28343
27751
|
} else if (config.tunnel) {
|
|
28344
|
-
|
|
27752
|
+
authFile = loadOrCreateAuthFile({ dataDir: config.dataDir });
|
|
27753
|
+
resolvedAuthToken = authFile.token;
|
|
28345
27754
|
}
|
|
28346
27755
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
28347
27756
|
let wsServer = null;
|
|
@@ -28358,7 +27767,7 @@ async function startDaemon(config) {
|
|
|
28358
27767
|
const agents = new AgentsScanner();
|
|
28359
27768
|
const history = new ClaudeHistoryReader();
|
|
28360
27769
|
let transport = null;
|
|
28361
|
-
const personaStore = new PersonaStore(
|
|
27770
|
+
const personaStore = new PersonaStore(import_node_path27.default.join(config.dataDir, "personas"));
|
|
28362
27771
|
const defaultsRoot = findDefaultsRoot();
|
|
28363
27772
|
if (defaultsRoot) {
|
|
28364
27773
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -28373,7 +27782,7 @@ async function startDaemon(config) {
|
|
|
28373
27782
|
getAdapter,
|
|
28374
27783
|
historyReader: history,
|
|
28375
27784
|
dataDir: config.dataDir,
|
|
28376
|
-
personaRoot:
|
|
27785
|
+
personaRoot: import_node_path27.default.join(config.dataDir, "personas"),
|
|
28377
27786
|
personaStore,
|
|
28378
27787
|
ownerDisplayName,
|
|
28379
27788
|
mode: config.mode,
|
|
@@ -28396,7 +27805,7 @@ async function startDaemon(config) {
|
|
|
28396
27805
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
28397
27806
|
attachmentGroup: {
|
|
28398
27807
|
onFileEdit: (input) => {
|
|
28399
|
-
const absPath =
|
|
27808
|
+
const absPath = import_node_path27.default.isAbsolute(input.relPath) ? input.relPath : import_node_path27.default.join(input.cwd, input.relPath);
|
|
28400
27809
|
let size = 0;
|
|
28401
27810
|
try {
|
|
28402
27811
|
size = import_node_fs24.default.statSync(absPath).size;
|
|
@@ -28473,16 +27882,6 @@ async function startDaemon(config) {
|
|
|
28473
27882
|
}
|
|
28474
27883
|
return `http://${config.host}:${config.port}`;
|
|
28475
27884
|
};
|
|
28476
|
-
const personaBoundHandler = new PersonaBoundHandler({
|
|
28477
|
-
registry: personaRegistry,
|
|
28478
|
-
personaManager,
|
|
28479
|
-
personaStore,
|
|
28480
|
-
sessionManager: manager,
|
|
28481
|
-
logger,
|
|
28482
|
-
ownerBroadcast: (frame) => {
|
|
28483
|
-
transport?.broadcastAll(frame);
|
|
28484
|
-
}
|
|
28485
|
-
});
|
|
28486
27885
|
const handlers = buildMethodHandlers({
|
|
28487
27886
|
manager,
|
|
28488
27887
|
workspace,
|
|
@@ -28494,7 +27893,6 @@ async function startDaemon(config) {
|
|
|
28494
27893
|
store,
|
|
28495
27894
|
personaManager,
|
|
28496
27895
|
personaRegistry,
|
|
28497
|
-
personaBoundHandler,
|
|
28498
27896
|
getTunnelUrl: () => currentTunnelUrl,
|
|
28499
27897
|
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
28500
27898
|
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
@@ -28508,11 +27906,12 @@ async function startDaemon(config) {
|
|
|
28508
27906
|
// 根据 sessionId 反查 scope 写盘。
|
|
28509
27907
|
attachment: {
|
|
28510
27908
|
groupFileStore,
|
|
27909
|
+
sessionStore: store,
|
|
28511
27910
|
getHttpBaseUrl,
|
|
28512
|
-
// HMAC sign secret
|
|
28513
|
-
// noAuth 模式
|
|
28514
|
-
getSignSecret: () =>
|
|
28515
|
-
// 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 走
|
|
28516
27915
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
28517
27916
|
getSessionScope: (sessionId) => {
|
|
28518
27917
|
const file = store.read(sessionId);
|
|
@@ -28535,8 +27934,9 @@ async function startDaemon(config) {
|
|
|
28535
27934
|
personaStore,
|
|
28536
27935
|
groupFileStore,
|
|
28537
27936
|
sessionStore: store,
|
|
28538
|
-
// /files HMAC verify
|
|
28539
|
-
|
|
27937
|
+
// /files HMAC verify 用 auth.json 的 signSecret 字段(与 attachment.signUrl 同源)。
|
|
27938
|
+
// --auth-token CLI 模式没 signSecret → 路由返 501,sign URL 功能整体禁用。
|
|
27939
|
+
getSignSecret: () => authFile?.signSecret ?? null
|
|
28540
27940
|
});
|
|
28541
27941
|
wsServer = new LocalWsServer({
|
|
28542
27942
|
host: config.host,
|
|
@@ -28557,7 +27957,6 @@ async function startDaemon(config) {
|
|
|
28557
27957
|
),
|
|
28558
27958
|
protocolVersion: PROTOCOL_VERSION,
|
|
28559
27959
|
authGate: authGate ?? void 0,
|
|
28560
|
-
personaBoundHandler,
|
|
28561
27960
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
28562
27961
|
httpRequestHandler: httpRouter,
|
|
28563
27962
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
@@ -28675,8 +28074,8 @@ async function startDaemon(config) {
|
|
|
28675
28074
|
const lines = [
|
|
28676
28075
|
`Tunnel: ${r.url}`,
|
|
28677
28076
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
28678
|
-
`Frpc config: ${
|
|
28679
|
-
`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")}`
|
|
28680
28079
|
];
|
|
28681
28080
|
const width = Math.max(...lines.map((l) => l.length));
|
|
28682
28081
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -28689,7 +28088,7 @@ ${bar}
|
|
|
28689
28088
|
|
|
28690
28089
|
`);
|
|
28691
28090
|
try {
|
|
28692
|
-
const connectPath =
|
|
28091
|
+
const connectPath = import_node_path27.default.join(config.dataDir, "connect.txt");
|
|
28693
28092
|
import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
28694
28093
|
} catch {
|
|
28695
28094
|
}
|