@clawos-dev/clawd 0.2.64-beta.104.6f4b611 → 0.2.64-beta.105.5ecd0a6
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 +340 -1812
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -110,22 +110,6 @@ var init_methods = __esm({
|
|
|
110
110
|
// 触发频率低(仅 window resize),response 也是 ack。
|
|
111
111
|
"session:pty:input",
|
|
112
112
|
"session:pty:resize",
|
|
113
|
-
// ---- attachment.* file-sharing(spec §5;详见 attachment-schemas.ts) ----
|
|
114
|
-
// 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
|
|
115
|
-
// (attachment-skills / attachment-deferred-tools / attachment_memories)是不同概念。
|
|
116
|
-
// 全部管理类 RPC handler 入口 requireOwner(personal token 调任意一个都 403);
|
|
117
|
-
// 实际文件传输走 HTTP 路由(GET/POST,详见 spec §5),不在白名单内。
|
|
118
|
-
"attachment.outboxCreate",
|
|
119
|
-
"attachment.outboxRevoke",
|
|
120
|
-
"attachment.outboxList",
|
|
121
|
-
"attachment.mountAdd",
|
|
122
|
-
"attachment.mountRemove",
|
|
123
|
-
"attachment.mountList",
|
|
124
|
-
"attachment.groupAdd",
|
|
125
|
-
"attachment.groupRemove",
|
|
126
|
-
"attachment.groupList",
|
|
127
|
-
// v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
|
|
128
|
-
"attachment.groupListPersona",
|
|
129
113
|
"info",
|
|
130
114
|
"ping"
|
|
131
115
|
];
|
|
@@ -617,8 +601,8 @@ var init_parseUtil = __esm({
|
|
|
617
601
|
init_errors2();
|
|
618
602
|
init_en();
|
|
619
603
|
makeIssue = (params) => {
|
|
620
|
-
const { data, path:
|
|
621
|
-
const fullPath = [...
|
|
604
|
+
const { data, path: path28, errorMaps, issueData } = params;
|
|
605
|
+
const fullPath = [...path28, ...issueData.path || []];
|
|
622
606
|
const fullIssue = {
|
|
623
607
|
...issueData,
|
|
624
608
|
path: fullPath
|
|
@@ -929,11 +913,11 @@ var init_types = __esm({
|
|
|
929
913
|
init_parseUtil();
|
|
930
914
|
init_util();
|
|
931
915
|
ParseInputLazyPath = class {
|
|
932
|
-
constructor(parent, value,
|
|
916
|
+
constructor(parent, value, path28, key) {
|
|
933
917
|
this._cachedPath = [];
|
|
934
918
|
this.parent = parent;
|
|
935
919
|
this.data = value;
|
|
936
|
-
this._path =
|
|
920
|
+
this._path = path28;
|
|
937
921
|
this._key = key;
|
|
938
922
|
}
|
|
939
923
|
get path() {
|
|
@@ -4316,151 +4300,6 @@ var init_zod = __esm({
|
|
|
4316
4300
|
}
|
|
4317
4301
|
});
|
|
4318
4302
|
|
|
4319
|
-
// ../protocol/src/attachment-schemas.ts
|
|
4320
|
-
var TOKEN_ROLES, GROUP_FILE_SOURCES, MOUNT_MODES, GroupFileEntrySchema, MountEntrySchema, OutboxScopeSchema, OutboxCapEntrySchema, AttachmentOutboxCreateArgs, AttachmentOutboxCreateResponseSchema, AttachmentOutboxRevokeArgs, AttachmentOutboxRevokeResponseSchema, AttachmentOutboxListArgs, AttachmentOutboxListResponseSchema, AttachmentMountAddArgs, AttachmentMountAddResponseSchema, AttachmentMountRemoveArgs, AttachmentMountRemoveResponseSchema, AttachmentMountListArgs, AttachmentMountListResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentGroupListPersonaArgs, AttachmentGroupListPersonaResponseSchema;
|
|
4321
|
-
var init_attachment_schemas = __esm({
|
|
4322
|
-
"../protocol/src/attachment-schemas.ts"() {
|
|
4323
|
-
"use strict";
|
|
4324
|
-
init_zod();
|
|
4325
|
-
TOKEN_ROLES = ["owner", "personal"];
|
|
4326
|
-
GROUP_FILE_SOURCES = ["agent", "user", "owner"];
|
|
4327
|
-
MOUNT_MODES = ["link", "copy"];
|
|
4328
|
-
GroupFileEntrySchema = external_exports.object({
|
|
4329
|
-
/** daemon 派发的稳定 id(用于 RPC remove / UI key) */
|
|
4330
|
-
id: external_exports.string().min(1),
|
|
4331
|
-
/** 相对 personaDir / sessionCwd 的路径 */
|
|
4332
|
-
relPath: external_exports.string().min(1),
|
|
4333
|
-
from: external_exports.enum(GROUP_FILE_SOURCES),
|
|
4334
|
-
/** user 上传时的客户端标识(personal label / hostname),from='user' 才有 */
|
|
4335
|
-
addedBy: external_exports.string().optional(),
|
|
4336
|
-
/** owner 手动加群时的可选备注 */
|
|
4337
|
-
label: external_exports.string().optional(),
|
|
4338
|
-
/** 文件字节数(stat 时拍) */
|
|
4339
|
-
size: external_exports.number().int().nonnegative(),
|
|
4340
|
-
mime: external_exports.string().min(1),
|
|
4341
|
-
/** 入群时间戳(ms) */
|
|
4342
|
-
addedAt: external_exports.number().int().nonnegative(),
|
|
4343
|
-
/** 最近一次 agent Write/Edit 时间戳(agent 反复改同 path 时更新) */
|
|
4344
|
-
lastEditedAt: external_exports.number().int().nonnegative().optional(),
|
|
4345
|
-
/** agent rm / 文件不见了时打标;UI 灰显,不能再 share */
|
|
4346
|
-
stale: external_exports.boolean().optional()
|
|
4347
|
-
});
|
|
4348
|
-
MountEntrySchema = external_exports.object({
|
|
4349
|
-
/** personaDir 下的 basename(link 目标 / copy 目的名) */
|
|
4350
|
-
basename: external_exports.string().min(1),
|
|
4351
|
-
/** 原始绝对路径(link 模式下 realpath 后用于 sandbox.allowRead 派生) */
|
|
4352
|
-
absPath: external_exports.string().min(1),
|
|
4353
|
-
mode: external_exports.enum(MOUNT_MODES)
|
|
4354
|
-
});
|
|
4355
|
-
OutboxScopeSchema = external_exports.discriminatedUnion("kind", [
|
|
4356
|
-
external_exports.object({ kind: external_exports.literal("public") }),
|
|
4357
|
-
external_exports.object({ kind: external_exports.literal("personal"), personalId: external_exports.string().min(1) })
|
|
4358
|
-
]);
|
|
4359
|
-
OutboxCapEntrySchema = external_exports.object({
|
|
4360
|
-
capToken: external_exports.string().min(1),
|
|
4361
|
-
/** 归属:persona 域 cap 带 personaId;direct 会话 cap 带 sessionId */
|
|
4362
|
-
scopeRef: external_exports.union([
|
|
4363
|
-
external_exports.object({ kind: external_exports.literal("persona"), personaId: external_exports.string().min(1) }),
|
|
4364
|
-
external_exports.object({ kind: external_exports.literal("session"), sessionId: external_exports.string().min(1) })
|
|
4365
|
-
]),
|
|
4366
|
-
absPath: external_exports.string().min(1),
|
|
4367
|
-
/** display name(UI 列表显示 + Share dialog 标题),通常等于 basename */
|
|
4368
|
-
name: external_exports.string().min(1),
|
|
4369
|
-
expiresAt: external_exports.number().int().nonnegative().nullable(),
|
|
4370
|
-
oneShot: external_exports.boolean(),
|
|
4371
|
-
scope: OutboxScopeSchema,
|
|
4372
|
-
hits: external_exports.number().int().nonnegative(),
|
|
4373
|
-
revoked: external_exports.boolean().optional(),
|
|
4374
|
-
createdAt: external_exports.number().int().nonnegative()
|
|
4375
|
-
});
|
|
4376
|
-
AttachmentOutboxCreateArgs = external_exports.object({
|
|
4377
|
-
/** persona 域:传 personaId;direct 会话:传 sessionId(二选一) */
|
|
4378
|
-
personaId: external_exports.string().min(1).optional(),
|
|
4379
|
-
sessionId: external_exports.string().min(1).optional(),
|
|
4380
|
-
absPath: external_exports.string().min(1),
|
|
4381
|
-
/** TTL 秒数;缺省 24h;'never' 走 null */
|
|
4382
|
-
ttlSeconds: external_exports.number().int().positive().nullable().optional(),
|
|
4383
|
-
oneShot: external_exports.boolean().optional(),
|
|
4384
|
-
scope: OutboxScopeSchema.optional()
|
|
4385
|
-
});
|
|
4386
|
-
AttachmentOutboxCreateResponseSchema = external_exports.object({
|
|
4387
|
-
capToken: external_exports.string().min(1),
|
|
4388
|
-
/** 完整 URL(含 httpBaseUrl 前缀),UI 直接复制到剪贴板 */
|
|
4389
|
-
url: external_exports.string().min(1),
|
|
4390
|
-
expiresAt: external_exports.number().int().nonnegative().nullable()
|
|
4391
|
-
});
|
|
4392
|
-
AttachmentOutboxRevokeArgs = external_exports.object({
|
|
4393
|
-
capToken: external_exports.string().min(1)
|
|
4394
|
-
});
|
|
4395
|
-
AttachmentOutboxRevokeResponseSchema = external_exports.object({
|
|
4396
|
-
revoked: external_exports.literal(true)
|
|
4397
|
-
});
|
|
4398
|
-
AttachmentOutboxListArgs = external_exports.object({
|
|
4399
|
-
/** 缺省 = 全部(含 direct 会话);传 personaId 过滤到该 persona */
|
|
4400
|
-
personaId: external_exports.string().min(1).optional()
|
|
4401
|
-
});
|
|
4402
|
-
AttachmentOutboxListResponseSchema = external_exports.object({
|
|
4403
|
-
entries: external_exports.array(OutboxCapEntrySchema)
|
|
4404
|
-
});
|
|
4405
|
-
AttachmentMountAddArgs = external_exports.object({
|
|
4406
|
-
personaId: external_exports.string().min(1),
|
|
4407
|
-
absPath: external_exports.string().min(1),
|
|
4408
|
-
mode: external_exports.enum(MOUNT_MODES)
|
|
4409
|
-
});
|
|
4410
|
-
AttachmentMountAddResponseSchema = external_exports.object({
|
|
4411
|
-
entry: MountEntrySchema,
|
|
4412
|
-
/** sandbox.allowRead 派生后是否需要重启 session 才能让 CC 子进程跟 symlink */
|
|
4413
|
-
sandboxRestartNeeded: external_exports.boolean()
|
|
4414
|
-
});
|
|
4415
|
-
AttachmentMountRemoveArgs = external_exports.object({
|
|
4416
|
-
personaId: external_exports.string().min(1),
|
|
4417
|
-
basename: external_exports.string().min(1)
|
|
4418
|
-
});
|
|
4419
|
-
AttachmentMountRemoveResponseSchema = external_exports.object({
|
|
4420
|
-
removed: external_exports.literal(true)
|
|
4421
|
-
});
|
|
4422
|
-
AttachmentMountListArgs = external_exports.object({
|
|
4423
|
-
personaId: external_exports.string().min(1)
|
|
4424
|
-
});
|
|
4425
|
-
AttachmentMountListResponseSchema = external_exports.object({
|
|
4426
|
-
entries: external_exports.array(MountEntrySchema)
|
|
4427
|
-
});
|
|
4428
|
-
AttachmentGroupAddArgs = external_exports.object({
|
|
4429
|
-
sessionId: external_exports.string().min(1),
|
|
4430
|
-
relPath: external_exports.string().min(1),
|
|
4431
|
-
/** owner 手动加群时可选备注;agent / inbox 自动入清单不走此 RPC */
|
|
4432
|
-
label: external_exports.string().optional()
|
|
4433
|
-
});
|
|
4434
|
-
AttachmentGroupAddResponseSchema = external_exports.object({
|
|
4435
|
-
entry: GroupFileEntrySchema
|
|
4436
|
-
});
|
|
4437
|
-
AttachmentGroupRemoveArgs = external_exports.object({
|
|
4438
|
-
sessionId: external_exports.string().min(1),
|
|
4439
|
-
relPath: external_exports.string().min(1)
|
|
4440
|
-
});
|
|
4441
|
-
AttachmentGroupRemoveResponseSchema = external_exports.object({
|
|
4442
|
-
removed: external_exports.literal(true)
|
|
4443
|
-
});
|
|
4444
|
-
AttachmentGroupListArgs = external_exports.object({
|
|
4445
|
-
sessionId: external_exports.string().min(1)
|
|
4446
|
-
});
|
|
4447
|
-
AttachmentGroupListResponseSchema = external_exports.object({
|
|
4448
|
-
entries: external_exports.array(GroupFileEntrySchema)
|
|
4449
|
-
});
|
|
4450
|
-
AttachmentGroupListPersonaArgs = external_exports.object({
|
|
4451
|
-
personaId: external_exports.string().min(1)
|
|
4452
|
-
});
|
|
4453
|
-
AttachmentGroupListPersonaResponseSchema = external_exports.object({
|
|
4454
|
-
perSession: external_exports.array(
|
|
4455
|
-
external_exports.object({
|
|
4456
|
-
sessionId: external_exports.string().min(1),
|
|
4457
|
-
entries: external_exports.array(GroupFileEntrySchema)
|
|
4458
|
-
})
|
|
4459
|
-
)
|
|
4460
|
-
});
|
|
4461
|
-
}
|
|
4462
|
-
});
|
|
4463
|
-
|
|
4464
4303
|
// ../protocol/src/persona-schemas.ts
|
|
4465
4304
|
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema, FRAME_TYPE_CHAT_OPEN, FRAME_TYPE_CHAT_RENAME, FRAME_TYPE_CHAT_DELETE, FRAME_TYPE_CHAT_LIST, FRAME_TYPE_CHAT_CREATED, FRAME_TYPE_CHAT_RENAMED, FRAME_TYPE_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED, FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED, FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_STATUS, ChatSummarySchema, ChatOpenRequestSchema, ChatRenameRequestSchema, ChatDeleteRequestSchema, ChatRenameResponseSchema, ChatDeleteResponseSchema, ChatListPushSchema, ChatCreatedFrameSchema, ChatRenamedFrameSchema, ChatDeletedFrameSchema, PersonaListenerChatCreatedFrameSchema, PersonaListenerChatRenamedFrameSchema, PersonaListenerChatDeletedFrameSchema, PersonaListenerStatusFrameSchema, PersonaListListenerChatsForTokenArgsSchema, PersonaListListenerChatsForTokenResponseSchema;
|
|
4466
4305
|
var init_persona_schemas = __esm({
|
|
@@ -4666,7 +4505,6 @@ var init_schemas = __esm({
|
|
|
4666
4505
|
"use strict";
|
|
4667
4506
|
init_zod();
|
|
4668
4507
|
init_events();
|
|
4669
|
-
init_attachment_schemas();
|
|
4670
4508
|
init_persona_schemas();
|
|
4671
4509
|
SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
|
|
4672
4510
|
UsageSchema = external_exports.object({
|
|
@@ -5223,22 +5061,7 @@ var init_schemas = __esm({
|
|
|
5223
5061
|
hostname: external_exports.string(),
|
|
5224
5062
|
os: external_exports.string(),
|
|
5225
5063
|
tools: external_exports.array(external_exports.object({ id: external_exports.string(), available: external_exports.boolean() })),
|
|
5226
|
-
runningSessions: external_exports.array(InfoRunningSessionSchema)
|
|
5227
|
-
// ── file-sharing 会话身份回执(spec: superpowers/specs/2026-05-19-clawd-file-sharing-design.md §8) ──
|
|
5228
|
-
// PR 1 阶段标 optional(daemon 还没下发,旧 daemon info 响应不带这五个字段);
|
|
5229
|
-
// PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
|
|
5230
|
-
// 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
|
|
5231
|
-
// 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
|
|
5232
|
-
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP);'personal' = persona 派发的访客 token。来源 TOKEN_ROLES(spec §11 #1 中央真理源) */
|
|
5233
|
-
tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
|
|
5234
|
-
/** tokenRole='personal' 时携带(绑定到具体 persona);owner 不携带 */
|
|
5235
|
-
tokenPersonaId: external_exports.string().min(1).optional(),
|
|
5236
|
-
/** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
|
|
5237
|
-
isLoopback: external_exports.boolean().optional(),
|
|
5238
|
-
/** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
|
|
5239
|
-
httpBaseUrl: external_exports.string().optional(),
|
|
5240
|
-
/** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
|
|
5241
|
-
httpToken: external_exports.string().optional()
|
|
5064
|
+
runningSessions: external_exports.array(InfoRunningSessionSchema)
|
|
5242
5065
|
});
|
|
5243
5066
|
}
|
|
5244
5067
|
});
|
|
@@ -5270,7 +5093,6 @@ var init_runtime = __esm({
|
|
|
5270
5093
|
init_frames();
|
|
5271
5094
|
init_persona_schemas();
|
|
5272
5095
|
init_persona_mode();
|
|
5273
|
-
init_attachment_schemas();
|
|
5274
5096
|
}
|
|
5275
5097
|
});
|
|
5276
5098
|
|
|
@@ -5545,8 +5367,8 @@ var require_req = __commonJS({
|
|
|
5545
5367
|
if (req.originalUrl) {
|
|
5546
5368
|
_req.url = req.originalUrl;
|
|
5547
5369
|
} else {
|
|
5548
|
-
const
|
|
5549
|
-
_req.url = typeof
|
|
5370
|
+
const path28 = req.path;
|
|
5371
|
+
_req.url = typeof path28 === "string" ? path28 : req.url ? req.url.path || req.url : void 0;
|
|
5550
5372
|
}
|
|
5551
5373
|
if (req.query) {
|
|
5552
5374
|
_req.query = req.query;
|
|
@@ -5711,14 +5533,14 @@ var require_redact = __commonJS({
|
|
|
5711
5533
|
}
|
|
5712
5534
|
return obj;
|
|
5713
5535
|
}
|
|
5714
|
-
function parsePath(
|
|
5536
|
+
function parsePath(path28) {
|
|
5715
5537
|
const parts = [];
|
|
5716
5538
|
let current = "";
|
|
5717
5539
|
let inBrackets = false;
|
|
5718
5540
|
let inQuotes = false;
|
|
5719
5541
|
let quoteChar = "";
|
|
5720
|
-
for (let i = 0; i <
|
|
5721
|
-
const char =
|
|
5542
|
+
for (let i = 0; i < path28.length; i++) {
|
|
5543
|
+
const char = path28[i];
|
|
5722
5544
|
if (!inBrackets && char === ".") {
|
|
5723
5545
|
if (current) {
|
|
5724
5546
|
parts.push(current);
|
|
@@ -5849,10 +5671,10 @@ var require_redact = __commonJS({
|
|
|
5849
5671
|
return current;
|
|
5850
5672
|
}
|
|
5851
5673
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5852
|
-
for (const
|
|
5853
|
-
const parts = parsePath(
|
|
5674
|
+
for (const path28 of paths) {
|
|
5675
|
+
const parts = parsePath(path28);
|
|
5854
5676
|
if (parts.includes("*")) {
|
|
5855
|
-
redactWildcardPath(obj, parts, censor,
|
|
5677
|
+
redactWildcardPath(obj, parts, censor, path28, remove);
|
|
5856
5678
|
} else {
|
|
5857
5679
|
if (remove) {
|
|
5858
5680
|
removeKey(obj, parts);
|
|
@@ -5937,8 +5759,8 @@ var require_redact = __commonJS({
|
|
|
5937
5759
|
}
|
|
5938
5760
|
} else {
|
|
5939
5761
|
if (afterWildcard.includes("*")) {
|
|
5940
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5941
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5762
|
+
const wrappedCensor = typeof censor === "function" ? (value, path28) => {
|
|
5763
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path28];
|
|
5942
5764
|
return censor(value, fullPath);
|
|
5943
5765
|
} : censor;
|
|
5944
5766
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5973,8 +5795,8 @@ var require_redact = __commonJS({
|
|
|
5973
5795
|
return null;
|
|
5974
5796
|
}
|
|
5975
5797
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5976
|
-
for (const
|
|
5977
|
-
const parts = parsePath(
|
|
5798
|
+
for (const path28 of pathsToClone) {
|
|
5799
|
+
const parts = parsePath(path28);
|
|
5978
5800
|
let current = pathStructure;
|
|
5979
5801
|
for (let i = 0; i < parts.length; i++) {
|
|
5980
5802
|
const part = parts[i];
|
|
@@ -6026,24 +5848,24 @@ var require_redact = __commonJS({
|
|
|
6026
5848
|
}
|
|
6027
5849
|
return cloneSelectively(obj, pathStructure);
|
|
6028
5850
|
}
|
|
6029
|
-
function validatePath(
|
|
6030
|
-
if (typeof
|
|
5851
|
+
function validatePath(path28) {
|
|
5852
|
+
if (typeof path28 !== "string") {
|
|
6031
5853
|
throw new Error("Paths must be (non-empty) strings");
|
|
6032
5854
|
}
|
|
6033
|
-
if (
|
|
5855
|
+
if (path28 === "") {
|
|
6034
5856
|
throw new Error("Invalid redaction path ()");
|
|
6035
5857
|
}
|
|
6036
|
-
if (
|
|
6037
|
-
throw new Error(`Invalid redaction path (${
|
|
5858
|
+
if (path28.includes("..")) {
|
|
5859
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6038
5860
|
}
|
|
6039
|
-
if (
|
|
6040
|
-
throw new Error(`Invalid redaction path (${
|
|
5861
|
+
if (path28.includes(",")) {
|
|
5862
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6041
5863
|
}
|
|
6042
5864
|
let bracketCount = 0;
|
|
6043
5865
|
let inQuotes = false;
|
|
6044
5866
|
let quoteChar = "";
|
|
6045
|
-
for (let i = 0; i <
|
|
6046
|
-
const char =
|
|
5867
|
+
for (let i = 0; i < path28.length; i++) {
|
|
5868
|
+
const char = path28[i];
|
|
6047
5869
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6048
5870
|
if (!inQuotes) {
|
|
6049
5871
|
inQuotes = true;
|
|
@@ -6057,20 +5879,20 @@ var require_redact = __commonJS({
|
|
|
6057
5879
|
} else if (char === "]" && !inQuotes) {
|
|
6058
5880
|
bracketCount--;
|
|
6059
5881
|
if (bracketCount < 0) {
|
|
6060
|
-
throw new Error(`Invalid redaction path (${
|
|
5882
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6061
5883
|
}
|
|
6062
5884
|
}
|
|
6063
5885
|
}
|
|
6064
5886
|
if (bracketCount !== 0) {
|
|
6065
|
-
throw new Error(`Invalid redaction path (${
|
|
5887
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6066
5888
|
}
|
|
6067
5889
|
}
|
|
6068
5890
|
function validatePaths(paths) {
|
|
6069
5891
|
if (!Array.isArray(paths)) {
|
|
6070
5892
|
throw new TypeError("paths must be an array");
|
|
6071
5893
|
}
|
|
6072
|
-
for (const
|
|
6073
|
-
validatePath(
|
|
5894
|
+
for (const path28 of paths) {
|
|
5895
|
+
validatePath(path28);
|
|
6074
5896
|
}
|
|
6075
5897
|
}
|
|
6076
5898
|
function slowRedact(options = {}) {
|
|
@@ -6238,8 +6060,8 @@ var require_redaction = __commonJS({
|
|
|
6238
6060
|
if (shape[k2] === null) {
|
|
6239
6061
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6240
6062
|
} else {
|
|
6241
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6242
|
-
return censor(value, [k2, ...
|
|
6063
|
+
const wrappedCensor = typeof censor === "function" ? (value, path28) => {
|
|
6064
|
+
return censor(value, [k2, ...path28]);
|
|
6243
6065
|
} : censor;
|
|
6244
6066
|
o[k2] = Redact({
|
|
6245
6067
|
paths: shape[k2],
|
|
@@ -6457,10 +6279,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6457
6279
|
var require_sonic_boom = __commonJS({
|
|
6458
6280
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6459
6281
|
"use strict";
|
|
6460
|
-
var
|
|
6282
|
+
var fs26 = require("fs");
|
|
6461
6283
|
var EventEmitter2 = require("events");
|
|
6462
6284
|
var inherits = require("util").inherits;
|
|
6463
|
-
var
|
|
6285
|
+
var path28 = require("path");
|
|
6464
6286
|
var sleep = require_atomic_sleep();
|
|
6465
6287
|
var assert = require("assert");
|
|
6466
6288
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6514,20 +6336,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6514
6336
|
const mode = sonic.mode;
|
|
6515
6337
|
if (sonic.sync) {
|
|
6516
6338
|
try {
|
|
6517
|
-
if (sonic.mkdir)
|
|
6518
|
-
const fd =
|
|
6339
|
+
if (sonic.mkdir) fs26.mkdirSync(path28.dirname(file), { recursive: true });
|
|
6340
|
+
const fd = fs26.openSync(file, flags, mode);
|
|
6519
6341
|
fileOpened(null, fd);
|
|
6520
6342
|
} catch (err) {
|
|
6521
6343
|
fileOpened(err);
|
|
6522
6344
|
throw err;
|
|
6523
6345
|
}
|
|
6524
6346
|
} else if (sonic.mkdir) {
|
|
6525
|
-
|
|
6347
|
+
fs26.mkdir(path28.dirname(file), { recursive: true }, (err) => {
|
|
6526
6348
|
if (err) return fileOpened(err);
|
|
6527
|
-
|
|
6349
|
+
fs26.open(file, flags, mode, fileOpened);
|
|
6528
6350
|
});
|
|
6529
6351
|
} else {
|
|
6530
|
-
|
|
6352
|
+
fs26.open(file, flags, mode, fileOpened);
|
|
6531
6353
|
}
|
|
6532
6354
|
}
|
|
6533
6355
|
function SonicBoom(opts) {
|
|
@@ -6568,8 +6390,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6568
6390
|
this.flush = flushBuffer;
|
|
6569
6391
|
this.flushSync = flushBufferSync;
|
|
6570
6392
|
this._actualWrite = actualWriteBuffer;
|
|
6571
|
-
fsWriteSync = () =>
|
|
6572
|
-
fsWrite = () =>
|
|
6393
|
+
fsWriteSync = () => fs26.writeSync(this.fd, this._writingBuf);
|
|
6394
|
+
fsWrite = () => fs26.write(this.fd, this._writingBuf, this.release);
|
|
6573
6395
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6574
6396
|
this._writingBuf = "";
|
|
6575
6397
|
this.write = write;
|
|
@@ -6578,15 +6400,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6578
6400
|
this._actualWrite = actualWrite;
|
|
6579
6401
|
fsWriteSync = () => {
|
|
6580
6402
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6581
|
-
return
|
|
6403
|
+
return fs26.writeSync(this.fd, this._writingBuf);
|
|
6582
6404
|
}
|
|
6583
|
-
return
|
|
6405
|
+
return fs26.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6584
6406
|
};
|
|
6585
6407
|
fsWrite = () => {
|
|
6586
6408
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6587
|
-
return
|
|
6409
|
+
return fs26.write(this.fd, this._writingBuf, this.release);
|
|
6588
6410
|
}
|
|
6589
|
-
return
|
|
6411
|
+
return fs26.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6590
6412
|
};
|
|
6591
6413
|
} else {
|
|
6592
6414
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6643,7 +6465,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6643
6465
|
}
|
|
6644
6466
|
}
|
|
6645
6467
|
if (this._fsync) {
|
|
6646
|
-
|
|
6468
|
+
fs26.fsyncSync(this.fd);
|
|
6647
6469
|
}
|
|
6648
6470
|
const len = this._len;
|
|
6649
6471
|
if (this._reopening) {
|
|
@@ -6757,7 +6579,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6757
6579
|
const onDrain = () => {
|
|
6758
6580
|
if (!this._fsync) {
|
|
6759
6581
|
try {
|
|
6760
|
-
|
|
6582
|
+
fs26.fsync(this.fd, (err) => {
|
|
6761
6583
|
this._flushPending = false;
|
|
6762
6584
|
cb(err);
|
|
6763
6585
|
});
|
|
@@ -6859,7 +6681,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6859
6681
|
const fd = this.fd;
|
|
6860
6682
|
this.once("ready", () => {
|
|
6861
6683
|
if (fd !== this.fd) {
|
|
6862
|
-
|
|
6684
|
+
fs26.close(fd, (err) => {
|
|
6863
6685
|
if (err) {
|
|
6864
6686
|
return this.emit("error", err);
|
|
6865
6687
|
}
|
|
@@ -6908,7 +6730,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6908
6730
|
buf = this._bufs[0];
|
|
6909
6731
|
}
|
|
6910
6732
|
try {
|
|
6911
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6733
|
+
const n = Buffer.isBuffer(buf) ? fs26.writeSync(this.fd, buf) : fs26.writeSync(this.fd, buf, "utf8");
|
|
6912
6734
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6913
6735
|
buf = releasedBufObj.writingBuf;
|
|
6914
6736
|
this._len = releasedBufObj.len;
|
|
@@ -6924,7 +6746,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6924
6746
|
}
|
|
6925
6747
|
}
|
|
6926
6748
|
try {
|
|
6927
|
-
|
|
6749
|
+
fs26.fsyncSync(this.fd);
|
|
6928
6750
|
} catch {
|
|
6929
6751
|
}
|
|
6930
6752
|
}
|
|
@@ -6945,7 +6767,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6945
6767
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6946
6768
|
}
|
|
6947
6769
|
try {
|
|
6948
|
-
const n =
|
|
6770
|
+
const n = fs26.writeSync(this.fd, buf);
|
|
6949
6771
|
buf = buf.subarray(n);
|
|
6950
6772
|
this._len = Math.max(this._len - n, 0);
|
|
6951
6773
|
if (buf.length <= 0) {
|
|
@@ -6973,13 +6795,13 @@ var require_sonic_boom = __commonJS({
|
|
|
6973
6795
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
6974
6796
|
if (this.sync) {
|
|
6975
6797
|
try {
|
|
6976
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
6798
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs26.writeSync(this.fd, this._writingBuf) : fs26.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6977
6799
|
release(null, written);
|
|
6978
6800
|
} catch (err) {
|
|
6979
6801
|
release(err);
|
|
6980
6802
|
}
|
|
6981
6803
|
} else {
|
|
6982
|
-
|
|
6804
|
+
fs26.write(this.fd, this._writingBuf, release);
|
|
6983
6805
|
}
|
|
6984
6806
|
}
|
|
6985
6807
|
function actualWriteBuffer() {
|
|
@@ -6988,7 +6810,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6988
6810
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
6989
6811
|
if (this.sync) {
|
|
6990
6812
|
try {
|
|
6991
|
-
const written =
|
|
6813
|
+
const written = fs26.writeSync(this.fd, this._writingBuf);
|
|
6992
6814
|
release(null, written);
|
|
6993
6815
|
} catch (err) {
|
|
6994
6816
|
release(err);
|
|
@@ -6997,7 +6819,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6997
6819
|
if (kCopyBuffer) {
|
|
6998
6820
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
6999
6821
|
}
|
|
7000
|
-
|
|
6822
|
+
fs26.write(this.fd, this._writingBuf, release);
|
|
7001
6823
|
}
|
|
7002
6824
|
}
|
|
7003
6825
|
function actualClose(sonic) {
|
|
@@ -7013,12 +6835,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7013
6835
|
sonic._lens = [];
|
|
7014
6836
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7015
6837
|
try {
|
|
7016
|
-
|
|
6838
|
+
fs26.fsync(sonic.fd, closeWrapped);
|
|
7017
6839
|
} catch {
|
|
7018
6840
|
}
|
|
7019
6841
|
function closeWrapped() {
|
|
7020
6842
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7021
|
-
|
|
6843
|
+
fs26.close(sonic.fd, done);
|
|
7022
6844
|
} else {
|
|
7023
6845
|
done();
|
|
7024
6846
|
}
|
|
@@ -10153,11 +9975,11 @@ var init_lib = __esm({
|
|
|
10153
9975
|
}
|
|
10154
9976
|
}
|
|
10155
9977
|
},
|
|
10156
|
-
addToPath: function addToPath(
|
|
10157
|
-
var last =
|
|
9978
|
+
addToPath: function addToPath(path28, added, removed, oldPosInc, options) {
|
|
9979
|
+
var last = path28.lastComponent;
|
|
10158
9980
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10159
9981
|
return {
|
|
10160
|
-
oldPos:
|
|
9982
|
+
oldPos: path28.oldPos + oldPosInc,
|
|
10161
9983
|
lastComponent: {
|
|
10162
9984
|
count: last.count + 1,
|
|
10163
9985
|
added,
|
|
@@ -10167,7 +9989,7 @@ var init_lib = __esm({
|
|
|
10167
9989
|
};
|
|
10168
9990
|
} else {
|
|
10169
9991
|
return {
|
|
10170
|
-
oldPos:
|
|
9992
|
+
oldPos: path28.oldPos + oldPosInc,
|
|
10171
9993
|
lastComponent: {
|
|
10172
9994
|
count: 1,
|
|
10173
9995
|
added,
|
|
@@ -10598,10 +10420,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10598
10420
|
const memories = raw.map((m2) => {
|
|
10599
10421
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10600
10422
|
const rec = m2;
|
|
10601
|
-
const
|
|
10423
|
+
const path28 = typeof rec.path === "string" ? rec.path : null;
|
|
10602
10424
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10603
|
-
if (!
|
|
10604
|
-
const entry = { path:
|
|
10425
|
+
if (!path28 || content == null) return null;
|
|
10426
|
+
const entry = { path: path28, content };
|
|
10605
10427
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10606
10428
|
return entry;
|
|
10607
10429
|
}).filter((m2) => m2 !== null);
|
|
@@ -11405,10 +11227,10 @@ function parseAttachment(obj) {
|
|
|
11405
11227
|
const memories = raw.map((m2) => {
|
|
11406
11228
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11407
11229
|
const rec = m2;
|
|
11408
|
-
const
|
|
11230
|
+
const path28 = typeof rec.path === "string" ? rec.path : null;
|
|
11409
11231
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11410
|
-
if (!
|
|
11411
|
-
const out = { path:
|
|
11232
|
+
if (!path28 || content == null) return null;
|
|
11233
|
+
const out = { path: path28, content };
|
|
11412
11234
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11413
11235
|
return out;
|
|
11414
11236
|
}).filter((m2) => m2 !== null);
|
|
@@ -18903,7 +18725,7 @@ var require_websocket = __commonJS({
|
|
|
18903
18725
|
"use strict";
|
|
18904
18726
|
var EventEmitter2 = require("events");
|
|
18905
18727
|
var https = require("https");
|
|
18906
|
-
var
|
|
18728
|
+
var http = require("http");
|
|
18907
18729
|
var net = require("net");
|
|
18908
18730
|
var tls = require("tls");
|
|
18909
18731
|
var { randomBytes, createHash } = require("crypto");
|
|
@@ -19437,7 +19259,7 @@ var require_websocket = __commonJS({
|
|
|
19437
19259
|
}
|
|
19438
19260
|
const defaultPort = isSecure ? 443 : 80;
|
|
19439
19261
|
const key = randomBytes(16).toString("base64");
|
|
19440
|
-
const request = isSecure ? https.request :
|
|
19262
|
+
const request = isSecure ? https.request : http.request;
|
|
19441
19263
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19442
19264
|
let perMessageDeflate;
|
|
19443
19265
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -19931,7 +19753,7 @@ var require_websocket_server = __commonJS({
|
|
|
19931
19753
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
19932
19754
|
"use strict";
|
|
19933
19755
|
var EventEmitter2 = require("events");
|
|
19934
|
-
var
|
|
19756
|
+
var http = require("http");
|
|
19935
19757
|
var { Duplex } = require("stream");
|
|
19936
19758
|
var { createHash } = require("crypto");
|
|
19937
19759
|
var extension2 = require_extension();
|
|
@@ -20006,8 +19828,8 @@ var require_websocket_server = __commonJS({
|
|
|
20006
19828
|
);
|
|
20007
19829
|
}
|
|
20008
19830
|
if (options.port != null) {
|
|
20009
|
-
this._server =
|
|
20010
|
-
const body =
|
|
19831
|
+
this._server = http.createServer((req, res) => {
|
|
19832
|
+
const body = http.STATUS_CODES[426];
|
|
20011
19833
|
res.writeHead(426, {
|
|
20012
19834
|
"Content-Length": body.length,
|
|
20013
19835
|
"Content-Type": "text/plain"
|
|
@@ -20294,7 +20116,7 @@ var require_websocket_server = __commonJS({
|
|
|
20294
20116
|
this.destroy();
|
|
20295
20117
|
}
|
|
20296
20118
|
function abortHandshake(socket, code, message, headers) {
|
|
20297
|
-
message = message ||
|
|
20119
|
+
message = message || http.STATUS_CODES[code];
|
|
20298
20120
|
headers = {
|
|
20299
20121
|
Connection: "close",
|
|
20300
20122
|
"Content-Type": "text/html",
|
|
@@ -20303,7 +20125,7 @@ var require_websocket_server = __commonJS({
|
|
|
20303
20125
|
};
|
|
20304
20126
|
socket.once("finish", socket.destroy);
|
|
20305
20127
|
socket.end(
|
|
20306
|
-
`HTTP/1.1 ${code} ${
|
|
20128
|
+
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
|
|
20307
20129
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20308
20130
|
);
|
|
20309
20131
|
}
|
|
@@ -20322,7 +20144,7 @@ var require_websocket_server = __commonJS({
|
|
|
20322
20144
|
// src/run-case/recorder.ts
|
|
20323
20145
|
function startRunCaseRecorder(opts) {
|
|
20324
20146
|
const now = opts.now ?? Date.now;
|
|
20325
|
-
const dir =
|
|
20147
|
+
const dir = import_node_path24.default.dirname(opts.recordPath);
|
|
20326
20148
|
let stream = null;
|
|
20327
20149
|
let closing = false;
|
|
20328
20150
|
let closedSettled = false;
|
|
@@ -20336,8 +20158,8 @@ function startRunCaseRecorder(opts) {
|
|
|
20336
20158
|
});
|
|
20337
20159
|
const ensureStream = () => {
|
|
20338
20160
|
if (stream) return stream;
|
|
20339
|
-
|
|
20340
|
-
stream =
|
|
20161
|
+
import_node_fs23.default.mkdirSync(dir, { recursive: true });
|
|
20162
|
+
stream = import_node_fs23.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
20341
20163
|
stream.on("close", () => closedResolve());
|
|
20342
20164
|
return stream;
|
|
20343
20165
|
};
|
|
@@ -20362,12 +20184,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20362
20184
|
};
|
|
20363
20185
|
return { tap, close, closed };
|
|
20364
20186
|
}
|
|
20365
|
-
var
|
|
20187
|
+
var import_node_fs23, import_node_path24;
|
|
20366
20188
|
var init_recorder = __esm({
|
|
20367
20189
|
"src/run-case/recorder.ts"() {
|
|
20368
20190
|
"use strict";
|
|
20369
|
-
|
|
20370
|
-
|
|
20191
|
+
import_node_fs23 = __toESM(require("fs"), 1);
|
|
20192
|
+
import_node_path24 = __toESM(require("path"), 1);
|
|
20371
20193
|
}
|
|
20372
20194
|
});
|
|
20373
20195
|
|
|
@@ -20410,7 +20232,7 @@ var init_wire = __esm({
|
|
|
20410
20232
|
// src/run-case/controller.ts
|
|
20411
20233
|
async function runController(opts) {
|
|
20412
20234
|
const now = opts.now ?? Date.now;
|
|
20413
|
-
const cwd = opts.cwd ?? (0,
|
|
20235
|
+
const cwd = opts.cwd ?? (0, import_node_fs24.mkdtempSync)(import_node_path25.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
|
|
20414
20236
|
const ownsCwd = opts.cwd === void 0;
|
|
20415
20237
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20416
20238
|
const spawnCtx = { cwd };
|
|
@@ -20571,19 +20393,19 @@ async function runController(opts) {
|
|
|
20571
20393
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
20572
20394
|
if (ownsCwd) {
|
|
20573
20395
|
try {
|
|
20574
|
-
(0,
|
|
20396
|
+
(0, import_node_fs24.rmSync)(cwd, { recursive: true, force: true });
|
|
20575
20397
|
} catch {
|
|
20576
20398
|
}
|
|
20577
20399
|
}
|
|
20578
20400
|
return exitCode ?? 0;
|
|
20579
20401
|
}
|
|
20580
|
-
var
|
|
20402
|
+
var import_node_fs24, import_node_os15, import_node_path25;
|
|
20581
20403
|
var init_controller = __esm({
|
|
20582
20404
|
"src/run-case/controller.ts"() {
|
|
20583
20405
|
"use strict";
|
|
20584
|
-
|
|
20406
|
+
import_node_fs24 = require("fs");
|
|
20585
20407
|
import_node_os15 = __toESM(require("os"), 1);
|
|
20586
|
-
|
|
20408
|
+
import_node_path25 = __toESM(require("path"), 1);
|
|
20587
20409
|
init_claude();
|
|
20588
20410
|
init_stdout_splitter();
|
|
20589
20411
|
init_permission_stdio();
|
|
@@ -20815,8 +20637,8 @@ Env (advanced):
|
|
|
20815
20637
|
`;
|
|
20816
20638
|
|
|
20817
20639
|
// src/index.ts
|
|
20818
|
-
var
|
|
20819
|
-
var
|
|
20640
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
20641
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
20820
20642
|
|
|
20821
20643
|
// src/logger.ts
|
|
20822
20644
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -21808,11 +21630,6 @@ var SessionRunner = class {
|
|
|
21808
21630
|
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
21809
21631
|
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
21810
21632
|
idleKillTimers = /* @__PURE__ */ new Map();
|
|
21811
|
-
// file-sharing 待配对的 file-edit tool_use(spec §6 PR 3):在同一 session 流里
|
|
21812
|
-
// tool_use(kind='tool_call')与 tool_result 通过 toolUseId 配对。tool_use 提前到,
|
|
21813
|
-
// 等 tool_result 来时反查 path 调 onFileEdit。条目 in-flight 期间很少(个位数),
|
|
21814
|
-
// 不需要 LRU;session 退出时整个 runner 销毁自然清理。
|
|
21815
|
-
pendingFileEdits = /* @__PURE__ */ new Map();
|
|
21816
21633
|
getState() {
|
|
21817
21634
|
return this.state;
|
|
21818
21635
|
}
|
|
@@ -21821,13 +21638,7 @@ var SessionRunner = class {
|
|
|
21821
21638
|
const personaStore = this.hooks.personaStore;
|
|
21822
21639
|
const adapter = this.hooks.adapter;
|
|
21823
21640
|
const deps = {
|
|
21824
|
-
|
|
21825
|
-
// 配对 tool_use ↔ tool_result 调 onFileEdit。原 reducer 路径不变。
|
|
21826
|
-
parseLine: (l) => {
|
|
21827
|
-
const events = adapter.parseLine(l);
|
|
21828
|
-
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
21829
|
-
return events;
|
|
21830
|
-
},
|
|
21641
|
+
parseLine: (l) => adapter.parseLine(l),
|
|
21831
21642
|
// persona mention injection 在 deps 装配处包一层,对 reducer 完全透明。
|
|
21832
21643
|
// 命中 mention 时拆成两条 stdin 帧(先 system-reminder 块、后 user 原文):
|
|
21833
21644
|
// - CC stream-json 按行处理,落盘是两条独立 user message;
|
|
@@ -21895,54 +21706,8 @@ var SessionRunner = class {
|
|
|
21895
21706
|
// reducer 的 batch dedup 按 events[0].uuid 整组处理,避免跨路径重复。
|
|
21896
21707
|
feedObserverEvents(events) {
|
|
21897
21708
|
if (events.length === 0) return;
|
|
21898
|
-
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
21899
21709
|
this.input({ kind: "inject-events", events });
|
|
21900
21710
|
}
|
|
21901
|
-
/**
|
|
21902
|
-
* file-sharing tool_use ↔ tool_result 配对(spec §6 PR 3)。
|
|
21903
|
-
*
|
|
21904
|
-
* 1) tool_call kind + tool ∈ FILE_EDIT_TOOLS → cache toolUseId → { tool, relPath }
|
|
21905
|
-
* relPath 从 input 里取(Write/Edit/MultiEdit 是 file_path,NotebookEdit 是 notebook_path)。
|
|
21906
|
-
* 2) tool_result kind + cache 命中 + 非 error → 调 onFileEdit + 删 cache
|
|
21907
|
-
* 3) tool_result kind + cache 命中 + 是 error → 删 cache,不调(spec 只要"成功"才入清单)
|
|
21908
|
-
* 4) tool_call 但 input 没有 path → 不 cache(防御)
|
|
21909
|
-
*
|
|
21910
|
-
* 不破坏 reducer 路径——纯只读扫描 events 数组。
|
|
21911
|
-
*/
|
|
21912
|
-
observeForFileEdit(events) {
|
|
21913
|
-
const cb = this.hooks.onFileEdit;
|
|
21914
|
-
if (!cb) return;
|
|
21915
|
-
for (const ev of events) {
|
|
21916
|
-
if (ev.kind === "tool_call") {
|
|
21917
|
-
const tool = ev.tool;
|
|
21918
|
-
if (!isFileEditTool(tool)) continue;
|
|
21919
|
-
const relPath = extractEditPath(ev.input);
|
|
21920
|
-
if (!relPath) continue;
|
|
21921
|
-
this.pendingFileEdits.set(ev.toolUseId, {
|
|
21922
|
-
tool,
|
|
21923
|
-
relPath
|
|
21924
|
-
});
|
|
21925
|
-
} else if (ev.kind === "tool_result") {
|
|
21926
|
-
const pending = this.pendingFileEdits.get(ev.toolUseId);
|
|
21927
|
-
if (!pending) continue;
|
|
21928
|
-
this.pendingFileEdits.delete(ev.toolUseId);
|
|
21929
|
-
if (ev.error != null) continue;
|
|
21930
|
-
try {
|
|
21931
|
-
cb({
|
|
21932
|
-
toolUseId: ev.toolUseId,
|
|
21933
|
-
tool: pending.tool,
|
|
21934
|
-
relPath: pending.relPath,
|
|
21935
|
-
cwd: this.state.file.cwd
|
|
21936
|
-
});
|
|
21937
|
-
} catch (err) {
|
|
21938
|
-
this.hooks.logger?.warn("onFileEdit hook threw", {
|
|
21939
|
-
err: err.message,
|
|
21940
|
-
sessionId: this.state.file.sessionId
|
|
21941
|
-
});
|
|
21942
|
-
}
|
|
21943
|
-
}
|
|
21944
|
-
}
|
|
21945
|
-
}
|
|
21946
21711
|
// 向子进程 stdin 写一条 CC IPC `control_request` 并返回 Promise<response>
|
|
21947
21712
|
// —— CC 侧会异步回写匹配 request_id 的 `control_response` 帧,
|
|
21948
21713
|
// 在 stdout 行处理入口被 tryHandleControlResponse 拦截并 resolve pending
|
|
@@ -22132,15 +21897,6 @@ var SessionRunner = class {
|
|
|
22132
21897
|
}
|
|
22133
21898
|
}
|
|
22134
21899
|
};
|
|
22135
|
-
function isFileEditTool(name) {
|
|
22136
|
-
return name === "Write" || name === "Edit" || name === "MultiEdit" || name === "NotebookEdit";
|
|
22137
|
-
}
|
|
22138
|
-
function extractEditPath(input) {
|
|
22139
|
-
if (!input || typeof input !== "object") return null;
|
|
22140
|
-
const o = input;
|
|
22141
|
-
const candidate = typeof o.file_path === "string" && o.file_path || typeof o.notebook_path === "string" && o.notebook_path || typeof o.path === "string" && o.path || null;
|
|
22142
|
-
return candidate || null;
|
|
22143
|
-
}
|
|
22144
21900
|
|
|
22145
21901
|
// src/session/manager.ts
|
|
22146
21902
|
function compressFrameForWire(frame) {
|
|
@@ -22197,6 +21953,16 @@ function nowIso2(deps) {
|
|
|
22197
21953
|
function newSessionId() {
|
|
22198
21954
|
return v4_default().replace(/-/g, "").slice(0, 16);
|
|
22199
21955
|
}
|
|
21956
|
+
function derivePersonaSpawnCwd(file, personaRoot) {
|
|
21957
|
+
const personaId = file.ownerPersonaId;
|
|
21958
|
+
if (!personaId) return file.cwd;
|
|
21959
|
+
if (!personaRoot) {
|
|
21960
|
+
throw new Error(
|
|
21961
|
+
`derivePersonaSpawnCwd: personaRoot missing for owner session ${file.sessionId} (ownerPersonaId=${personaId})`
|
|
21962
|
+
);
|
|
21963
|
+
}
|
|
21964
|
+
return import_node_path6.default.join(personaRoot, safeFileName(personaId));
|
|
21965
|
+
}
|
|
22200
21966
|
function makeInitialState(file, subSessionMeta) {
|
|
22201
21967
|
return {
|
|
22202
21968
|
file,
|
|
@@ -22355,7 +22121,24 @@ var SessionManager = class {
|
|
|
22355
22121
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
22356
22122
|
const store = this.storeFor(scope);
|
|
22357
22123
|
const subSessionMeta = metaFromScope(scope, this.deps.personaRoot ?? "");
|
|
22358
|
-
const
|
|
22124
|
+
const correctedCwd = derivePersonaSpawnCwd(file, this.deps.personaRoot);
|
|
22125
|
+
if (correctedCwd !== file.cwd) {
|
|
22126
|
+
this.deps.logger?.warn("owner session cwd drifted from persona dir; auto-correcting", {
|
|
22127
|
+
sessionId: file.sessionId,
|
|
22128
|
+
ownerPersonaId: file.ownerPersonaId,
|
|
22129
|
+
stale: file.cwd,
|
|
22130
|
+
corrected: correctedCwd
|
|
22131
|
+
});
|
|
22132
|
+
file = { ...file, cwd: correctedCwd, updatedAt: nowIso2(this.deps) };
|
|
22133
|
+
try {
|
|
22134
|
+
store.write(file);
|
|
22135
|
+
} catch (err) {
|
|
22136
|
+
this.deps.logger?.warn("failed to persist auto-corrected owner cwd", {
|
|
22137
|
+
sessionId: file.sessionId,
|
|
22138
|
+
error: err.message
|
|
22139
|
+
});
|
|
22140
|
+
}
|
|
22141
|
+
}
|
|
22359
22142
|
const runner = new SessionRunner(makeInitialState(file, subSessionMeta), {
|
|
22360
22143
|
broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
|
|
22361
22144
|
store,
|
|
@@ -22368,15 +22151,7 @@ var SessionManager = class {
|
|
|
22368
22151
|
resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId),
|
|
22369
22152
|
dataDir: this.deps.dataDir,
|
|
22370
22153
|
personaStore: this.deps.personaStore,
|
|
22371
|
-
ownerDisplayName: this.deps.ownerDisplayName
|
|
22372
|
-
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
22373
|
-
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
22374
|
-
scope,
|
|
22375
|
-
sessionId: file.sessionId,
|
|
22376
|
-
tool: input.tool,
|
|
22377
|
-
relPath: input.relPath,
|
|
22378
|
-
cwd: input.cwd
|
|
22379
|
-
}) : void 0
|
|
22154
|
+
ownerDisplayName: this.deps.ownerDisplayName
|
|
22380
22155
|
});
|
|
22381
22156
|
if (this.deps.mode === "tui" && !file.toolSessionId) {
|
|
22382
22157
|
const newTsid = v4_default();
|
|
@@ -22464,14 +22239,31 @@ var SessionManager = class {
|
|
|
22464
22239
|
}
|
|
22465
22240
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
22466
22241
|
create(args) {
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22242
|
+
this.deps.logger?.warn("[spawn-cwd-debug] manager.create entry", {
|
|
22243
|
+
argsOwnerPersonaId: args.ownerPersonaId,
|
|
22244
|
+
argsCwd: args.cwd,
|
|
22245
|
+
personaRoot: this.deps.personaRoot
|
|
22246
|
+
});
|
|
22247
|
+
let cwd;
|
|
22248
|
+
if (args.ownerPersonaId) {
|
|
22249
|
+
cwd = derivePersonaSpawnCwd(
|
|
22250
|
+
{
|
|
22251
|
+
sessionId: "",
|
|
22252
|
+
cwd: args.cwd ?? "",
|
|
22253
|
+
tool: "claude",
|
|
22254
|
+
ownerPersonaId: args.ownerPersonaId,
|
|
22255
|
+
createdAt: "",
|
|
22256
|
+
updatedAt: ""
|
|
22257
|
+
},
|
|
22258
|
+
this.deps.personaRoot
|
|
22259
|
+
);
|
|
22260
|
+
this.deps.logger?.warn("[spawn-cwd-debug] derived persona cwd", { derivedCwd: cwd });
|
|
22261
|
+
} else if (args.cwd) {
|
|
22262
|
+
cwd = args.cwd;
|
|
22263
|
+
this.deps.logger?.warn("[spawn-cwd-debug] using args.cwd (no ownerPersonaId)", {
|
|
22264
|
+
cwd
|
|
22265
|
+
});
|
|
22266
|
+
} else {
|
|
22475
22267
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, "cwd required when ownerPersonaId is absent");
|
|
22476
22268
|
}
|
|
22477
22269
|
try {
|
|
@@ -23527,21 +23319,6 @@ var PersonaRegistry = class {
|
|
|
23527
23319
|
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
23528
23320
|
return { ok: true, label: entry.label };
|
|
23529
23321
|
}
|
|
23530
|
-
/**
|
|
23531
|
-
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23532
|
-
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
23533
|
-
* persona 数量通常 < 100,性能可忽略。
|
|
23534
|
-
*/
|
|
23535
|
-
findByToken(token) {
|
|
23536
|
-
if (!token) return null;
|
|
23537
|
-
for (const persona of this.cache.values()) {
|
|
23538
|
-
if (!persona.public) continue;
|
|
23539
|
-
const entry = persona.tokenMap[token];
|
|
23540
|
-
if (!entry || entry.revoked) continue;
|
|
23541
|
-
return { personaId: persona.personaId, label: entry.label };
|
|
23542
|
-
}
|
|
23543
|
-
return null;
|
|
23544
|
-
}
|
|
23545
23322
|
};
|
|
23546
23323
|
|
|
23547
23324
|
// src/persona/manager.ts
|
|
@@ -25483,7 +25260,6 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
25483
25260
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
25484
25261
|
|
|
25485
25262
|
// src/transport/local-ws-server.ts
|
|
25486
|
-
var import_node_http = __toESM(require("http"), 1);
|
|
25487
25263
|
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
25488
25264
|
var LocalWsServer = class {
|
|
25489
25265
|
constructor(opts) {
|
|
@@ -25493,7 +25269,6 @@ var LocalWsServer = class {
|
|
|
25493
25269
|
}
|
|
25494
25270
|
opts;
|
|
25495
25271
|
wss = null;
|
|
25496
|
-
httpServer = null;
|
|
25497
25272
|
frameHandler = null;
|
|
25498
25273
|
clients = /* @__PURE__ */ new Map();
|
|
25499
25274
|
logger;
|
|
@@ -25501,28 +25276,25 @@ var LocalWsServer = class {
|
|
|
25501
25276
|
async start() {
|
|
25502
25277
|
const host = this.opts.host ?? "127.0.0.1";
|
|
25503
25278
|
await new Promise((resolve2, reject) => {
|
|
25504
|
-
const
|
|
25505
|
-
|
|
25506
|
-
|
|
25507
|
-
|
|
25508
|
-
this.routeConnection(ws, req);
|
|
25509
|
-
});
|
|
25279
|
+
const wss = new import_websocket_server.default({
|
|
25280
|
+
host,
|
|
25281
|
+
port: this.opts.port,
|
|
25282
|
+
clientTracking: true
|
|
25510
25283
|
});
|
|
25511
|
-
|
|
25284
|
+
wss.on("listening", () => {
|
|
25512
25285
|
this.logger?.info("ws listening", { host, port: this.opts.port });
|
|
25513
25286
|
resolve2();
|
|
25514
25287
|
});
|
|
25515
|
-
|
|
25288
|
+
wss.on("error", (err) => {
|
|
25516
25289
|
this.logger?.error("ws server error", { err: err.message });
|
|
25517
25290
|
reject(err);
|
|
25518
25291
|
});
|
|
25519
|
-
|
|
25520
|
-
this.httpServer = httpServer;
|
|
25292
|
+
wss.on("connection", (socket, req) => this.routeConnection(socket, req));
|
|
25521
25293
|
this.wss = wss;
|
|
25522
25294
|
});
|
|
25523
25295
|
}
|
|
25524
25296
|
async stop() {
|
|
25525
|
-
if (!this.
|
|
25297
|
+
if (!this.wss) return;
|
|
25526
25298
|
for (const c of this.clients.values()) {
|
|
25527
25299
|
try {
|
|
25528
25300
|
c.ws.close(1001, "shutdown");
|
|
@@ -25534,32 +25306,7 @@ var LocalWsServer = class {
|
|
|
25534
25306
|
await new Promise((resolve2) => {
|
|
25535
25307
|
this.wss?.close(() => resolve2());
|
|
25536
25308
|
});
|
|
25537
|
-
await new Promise((resolve2) => {
|
|
25538
|
-
this.httpServer?.close(() => resolve2());
|
|
25539
|
-
});
|
|
25540
25309
|
this.wss = null;
|
|
25541
|
-
this.httpServer = null;
|
|
25542
|
-
}
|
|
25543
|
-
/** http.createServer 'request' 入口:file-sharing 路由优先,未命中走 404 */
|
|
25544
|
-
async handleHttpRequest(req, res) {
|
|
25545
|
-
const handler = this.opts.httpRequestHandler;
|
|
25546
|
-
if (handler) {
|
|
25547
|
-
try {
|
|
25548
|
-
const handled = await handler(req, res);
|
|
25549
|
-
if (handled) return;
|
|
25550
|
-
} catch (err) {
|
|
25551
|
-
this.logger?.warn("http handler threw", { err: err.message });
|
|
25552
|
-
if (!res.headersSent) {
|
|
25553
|
-
res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
|
|
25554
|
-
res.end(JSON.stringify({ code: "INTERNAL", message: "http handler error" }));
|
|
25555
|
-
}
|
|
25556
|
-
return;
|
|
25557
|
-
}
|
|
25558
|
-
}
|
|
25559
|
-
if (!res.headersSent) {
|
|
25560
|
-
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
25561
|
-
res.end(JSON.stringify({ code: "NOT_FOUND", message: "no route" }));
|
|
25562
|
-
}
|
|
25563
25310
|
}
|
|
25564
25311
|
onFrame(handler) {
|
|
25565
25312
|
this.frameHandler = handler;
|
|
@@ -25661,7 +25408,7 @@ var LocalWsServer = class {
|
|
|
25661
25408
|
}
|
|
25662
25409
|
try {
|
|
25663
25410
|
if (authed) {
|
|
25664
|
-
const readyFrame = this.opts.readyFrameBuilder(
|
|
25411
|
+
const readyFrame = this.opts.readyFrameBuilder();
|
|
25665
25412
|
this.safeSend(socket, { type: "ready", ...readyFrame });
|
|
25666
25413
|
} else {
|
|
25667
25414
|
this.safeSend(socket, { type: "ready", protocolVersion: this.opts.protocolVersion });
|
|
@@ -25698,7 +25445,7 @@ var LocalWsServer = class {
|
|
|
25698
25445
|
if (verdict !== "pass") {
|
|
25699
25446
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
25700
25447
|
try {
|
|
25701
|
-
const full = this.opts.readyFrameBuilder(
|
|
25448
|
+
const full = this.opts.readyFrameBuilder();
|
|
25702
25449
|
this.safeSend(this.clients.get(client.id).ws, { type: "ready", ...full });
|
|
25703
25450
|
} catch (err) {
|
|
25704
25451
|
this.logger?.warn("post-auth ready frame build failed", { err: err.message });
|
|
@@ -26295,1039 +26042,118 @@ function isLocalhost(addr) {
|
|
|
26295
26042
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
26296
26043
|
}
|
|
26297
26044
|
|
|
26298
|
-
// src/
|
|
26299
|
-
var AuthContextResolver = class {
|
|
26300
|
-
constructor(opts) {
|
|
26301
|
-
this.opts = opts;
|
|
26302
|
-
}
|
|
26303
|
-
opts;
|
|
26304
|
-
/**
|
|
26305
|
-
* 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
|
|
26306
|
-
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
26307
|
-
*/
|
|
26308
|
-
resolveFromHeader(authHeader, remoteAddress) {
|
|
26309
|
-
const token = parseBearer(authHeader);
|
|
26310
|
-
if (!token) return null;
|
|
26311
|
-
const isLoopback = isLoopbackAddr(remoteAddress);
|
|
26312
|
-
if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
|
|
26313
|
-
return { role: "owner", isLoopback };
|
|
26314
|
-
}
|
|
26315
|
-
const personalHit = this.opts.personaRegistry.findByToken(token);
|
|
26316
|
-
if (personalHit) {
|
|
26317
|
-
return {
|
|
26318
|
-
role: "personal",
|
|
26319
|
-
personaId: personalHit.personaId,
|
|
26320
|
-
label: personalHit.label,
|
|
26321
|
-
isLoopback
|
|
26322
|
-
};
|
|
26323
|
-
}
|
|
26324
|
-
return null;
|
|
26325
|
-
}
|
|
26326
|
-
};
|
|
26327
|
-
function parseBearer(header) {
|
|
26328
|
-
if (!header) return null;
|
|
26329
|
-
const raw = Array.isArray(header) ? header[0] : header;
|
|
26330
|
-
if (typeof raw !== "string") return null;
|
|
26331
|
-
const m2 = /^Bearer\s+(\S+)$/i.exec(raw.trim());
|
|
26332
|
-
return m2 ? m2[1] : null;
|
|
26333
|
-
}
|
|
26334
|
-
function isLoopbackAddr(addr) {
|
|
26335
|
-
if (!addr) return false;
|
|
26336
|
-
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
26337
|
-
}
|
|
26338
|
-
function constantTimeEqual2(a, b2) {
|
|
26339
|
-
if (a.length !== b2.length) return false;
|
|
26340
|
-
let diff2 = 0;
|
|
26341
|
-
for (let i = 0; i < a.length; i++) diff2 |= a.charCodeAt(i) ^ b2.charCodeAt(i);
|
|
26342
|
-
return diff2 === 0;
|
|
26343
|
-
}
|
|
26344
|
-
|
|
26345
|
-
// src/transport/http-router.ts
|
|
26346
|
-
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
26347
|
-
var import_node_path17 = __toESM(require("path"), 1);
|
|
26348
|
-
|
|
26349
|
-
// src/attachment/group.ts
|
|
26045
|
+
// src/discovery/state-file.ts
|
|
26350
26046
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26351
26047
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
26352
|
-
|
|
26353
|
-
|
|
26354
|
-
|
|
26355
|
-
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
26048
|
+
function defaultStateFilePath(dataDir) {
|
|
26049
|
+
return import_node_path14.default.join(dataDir, "state.json");
|
|
26050
|
+
}
|
|
26051
|
+
function isPidAlive(pid) {
|
|
26052
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
26053
|
+
try {
|
|
26054
|
+
process.kill(pid, 0);
|
|
26055
|
+
return true;
|
|
26056
|
+
} catch (err) {
|
|
26057
|
+
const code = err.code;
|
|
26058
|
+
return code === "EPERM";
|
|
26364
26059
|
}
|
|
26365
|
-
|
|
26366
|
-
|
|
26367
|
-
|
|
26060
|
+
}
|
|
26061
|
+
var StateFileManager = class {
|
|
26062
|
+
file;
|
|
26063
|
+
constructor(opts) {
|
|
26064
|
+
this.file = defaultStateFilePath(opts.dataDir);
|
|
26368
26065
|
}
|
|
26369
|
-
|
|
26370
|
-
return
|
|
26066
|
+
filePath() {
|
|
26067
|
+
return this.file;
|
|
26371
26068
|
}
|
|
26372
|
-
|
|
26373
|
-
readFile(scope, sessionId) {
|
|
26374
|
-
const file = this.filePath(scope, sessionId);
|
|
26069
|
+
read() {
|
|
26375
26070
|
try {
|
|
26376
|
-
const raw = import_node_fs13.default.readFileSync(file, "utf8");
|
|
26071
|
+
const raw = import_node_fs13.default.readFileSync(this.file, "utf8");
|
|
26377
26072
|
const parsed = JSON.parse(raw);
|
|
26378
|
-
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
});
|
|
26382
|
-
return [];
|
|
26383
|
-
}
|
|
26384
|
-
const out = [];
|
|
26385
|
-
for (const entry of parsed) {
|
|
26386
|
-
const r = GroupFileEntrySchema.safeParse(entry);
|
|
26387
|
-
if (r.success) out.push(r.data);
|
|
26388
|
-
}
|
|
26389
|
-
return out;
|
|
26390
|
-
} catch (err) {
|
|
26391
|
-
const code = err?.code;
|
|
26392
|
-
if (code === "ENOENT") return [];
|
|
26393
|
-
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
26394
|
-
file,
|
|
26395
|
-
err: err.message
|
|
26396
|
-
});
|
|
26397
|
-
return [];
|
|
26073
|
+
return parsed;
|
|
26074
|
+
} catch {
|
|
26075
|
+
return null;
|
|
26398
26076
|
}
|
|
26399
26077
|
}
|
|
26400
|
-
|
|
26401
|
-
const
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
import_node_fs13.default.renameSync(tmp, file);
|
|
26078
|
+
preflight() {
|
|
26079
|
+
const existing = this.read();
|
|
26080
|
+
if (!existing) return { status: "ok" };
|
|
26081
|
+
if (isPidAlive(existing.pid)) return { status: "active", existing };
|
|
26082
|
+
return { status: "stale", existing };
|
|
26406
26083
|
}
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
const
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26084
|
+
write(state) {
|
|
26085
|
+
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(this.file), { recursive: true });
|
|
26086
|
+
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
26087
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
26088
|
+
import_node_fs13.default.renameSync(tmp, this.file);
|
|
26089
|
+
if (process.platform !== "win32") {
|
|
26090
|
+
try {
|
|
26091
|
+
import_node_fs13.default.chmodSync(this.file, 384);
|
|
26092
|
+
} catch {
|
|
26093
|
+
}
|
|
26094
|
+
}
|
|
26415
26095
|
}
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26419
|
-
|
|
26420
|
-
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
26421
|
-
*
|
|
26422
|
-
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
26423
|
-
*/
|
|
26424
|
-
upsert(scope, sessionId, input, now = Date.now()) {
|
|
26425
|
-
const entries = this.list(scope, sessionId).slice();
|
|
26426
|
-
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
26427
|
-
let next;
|
|
26428
|
-
if (idx >= 0) {
|
|
26429
|
-
const prev = entries[idx];
|
|
26430
|
-
next = {
|
|
26431
|
-
...prev,
|
|
26432
|
-
size: input.size,
|
|
26433
|
-
mime: input.mime,
|
|
26434
|
-
lastEditedAt: now,
|
|
26435
|
-
stale: false
|
|
26436
|
-
// addedBy / label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
26437
|
-
};
|
|
26438
|
-
entries[idx] = next;
|
|
26439
|
-
} else {
|
|
26440
|
-
next = {
|
|
26441
|
-
id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
|
|
26442
|
-
relPath: input.relPath,
|
|
26443
|
-
from: input.from,
|
|
26444
|
-
addedBy: input.addedBy,
|
|
26445
|
-
label: input.label,
|
|
26446
|
-
size: input.size,
|
|
26447
|
-
mime: input.mime,
|
|
26448
|
-
addedAt: now
|
|
26449
|
-
// agent / inbox 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
26450
|
-
};
|
|
26451
|
-
entries.push(next);
|
|
26096
|
+
delete() {
|
|
26097
|
+
try {
|
|
26098
|
+
import_node_fs13.default.unlinkSync(this.file);
|
|
26099
|
+
} catch {
|
|
26452
26100
|
}
|
|
26453
|
-
this.writeFile(scope, sessionId, entries);
|
|
26454
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26455
|
-
return next;
|
|
26456
26101
|
}
|
|
26457
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
this.
|
|
26471
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26102
|
+
};
|
|
26103
|
+
|
|
26104
|
+
// src/tunnel/tunnel-manager.ts
|
|
26105
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
26106
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
26107
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
26108
|
+
var import_node_child_process5 = require("child_process");
|
|
26109
|
+
|
|
26110
|
+
// src/tunnel/tunnel-store.ts
|
|
26111
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
26112
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
26113
|
+
var TunnelStore = class {
|
|
26114
|
+
constructor(filePath) {
|
|
26115
|
+
this.filePath = filePath;
|
|
26472
26116
|
}
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
|
|
26479
|
-
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
|
|
26483
|
-
|
|
26484
|
-
|
|
26485
|
-
this.writeFile(scope, sessionId, entries);
|
|
26486
|
-
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26487
|
-
return true;
|
|
26117
|
+
filePath;
|
|
26118
|
+
async get() {
|
|
26119
|
+
try {
|
|
26120
|
+
const raw = await import_node_fs14.default.promises.readFile(this.filePath, "utf8");
|
|
26121
|
+
const obj = JSON.parse(raw);
|
|
26122
|
+
if (!isPersistedTunnel(obj)) return null;
|
|
26123
|
+
return obj;
|
|
26124
|
+
} catch (err) {
|
|
26125
|
+
const code = err?.code;
|
|
26126
|
+
if (code === "ENOENT") return null;
|
|
26127
|
+
return null;
|
|
26128
|
+
}
|
|
26488
26129
|
}
|
|
26489
|
-
|
|
26490
|
-
|
|
26491
|
-
|
|
26492
|
-
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
|
|
26496
|
-
*/
|
|
26497
|
-
listByPersona(personaId) {
|
|
26498
|
-
const out = [];
|
|
26499
|
-
for (const mode of ["owner", "listener"]) {
|
|
26500
|
-
const scope = { kind: "persona", personaId, mode };
|
|
26501
|
-
const root = this.rootForScope(scope);
|
|
26502
|
-
let names;
|
|
26130
|
+
async set(v2) {
|
|
26131
|
+
const dir = import_node_path15.default.dirname(this.filePath);
|
|
26132
|
+
await import_node_fs14.default.promises.mkdir(dir, { recursive: true });
|
|
26133
|
+
const data = JSON.stringify(v2, null, 2);
|
|
26134
|
+
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
26135
|
+
await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
26136
|
+
if (process.platform !== "win32") {
|
|
26503
26137
|
try {
|
|
26504
|
-
|
|
26505
|
-
} catch
|
|
26506
|
-
const code = err?.code;
|
|
26507
|
-
if (code === "ENOENT") continue;
|
|
26508
|
-
continue;
|
|
26509
|
-
}
|
|
26510
|
-
for (const name of names) {
|
|
26511
|
-
if (!name.endsWith(".group-files.json")) continue;
|
|
26512
|
-
const sessionId = name.slice(0, -".group-files.json".length);
|
|
26513
|
-
if (!sessionId) continue;
|
|
26514
|
-
const entries = this.list(scope, sessionId);
|
|
26515
|
-
out.push({ sessionId, entries });
|
|
26138
|
+
await import_node_fs14.default.promises.chmod(tmp, 384);
|
|
26139
|
+
} catch {
|
|
26516
26140
|
}
|
|
26517
26141
|
}
|
|
26518
|
-
|
|
26519
|
-
}
|
|
26520
|
-
};
|
|
26521
|
-
function personalViewable(groupStore, personaDir, personaId, absPath) {
|
|
26522
|
-
const realTarget = safeRealpath(absPath);
|
|
26523
|
-
if (!realTarget) {
|
|
26524
|
-
return false;
|
|
26142
|
+
await import_node_fs14.default.promises.rename(tmp, this.filePath);
|
|
26525
26143
|
}
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
26529
|
-
|
|
26530
|
-
const
|
|
26531
|
-
if (
|
|
26144
|
+
async clear() {
|
|
26145
|
+
try {
|
|
26146
|
+
await import_node_fs14.default.promises.unlink(this.filePath);
|
|
26147
|
+
} catch (err) {
|
|
26148
|
+
const code = err?.code;
|
|
26149
|
+
if (code !== "ENOENT") throw err;
|
|
26532
26150
|
}
|
|
26533
26151
|
}
|
|
26534
|
-
return false;
|
|
26535
|
-
}
|
|
26536
|
-
function safeRealpath(p2) {
|
|
26537
|
-
try {
|
|
26538
|
-
return import_node_fs13.default.realpathSync(p2);
|
|
26539
|
-
} catch {
|
|
26540
|
-
return null;
|
|
26541
|
-
}
|
|
26542
|
-
}
|
|
26543
|
-
|
|
26544
|
-
// src/attachment/mime.ts
|
|
26545
|
-
var import_node_path15 = __toESM(require("path"), 1);
|
|
26546
|
-
var EXT_TO_MIME = {
|
|
26547
|
-
".md": "text/markdown",
|
|
26548
|
-
".markdown": "text/markdown",
|
|
26549
|
-
".txt": "text/plain",
|
|
26550
|
-
".log": "text/plain",
|
|
26551
|
-
".json": "application/json",
|
|
26552
|
-
".html": "text/html",
|
|
26553
|
-
".htm": "text/html",
|
|
26554
|
-
".css": "text/css",
|
|
26555
|
-
".js": "application/javascript",
|
|
26556
|
-
".ts": "application/typescript",
|
|
26557
|
-
".tsx": "application/typescript",
|
|
26558
|
-
".png": "image/png",
|
|
26559
|
-
".jpg": "image/jpeg",
|
|
26560
|
-
".jpeg": "image/jpeg",
|
|
26561
|
-
".gif": "image/gif",
|
|
26562
|
-
".webp": "image/webp",
|
|
26563
|
-
".svg": "image/svg+xml",
|
|
26564
|
-
".pdf": "application/pdf",
|
|
26565
|
-
".mp4": "video/mp4",
|
|
26566
|
-
".webm": "video/webm",
|
|
26567
|
-
".mp3": "audio/mpeg",
|
|
26568
|
-
".wav": "audio/wav",
|
|
26569
|
-
".zip": "application/zip",
|
|
26570
|
-
".gz": "application/gzip",
|
|
26571
|
-
".tar": "application/x-tar",
|
|
26572
|
-
".csv": "text/csv",
|
|
26573
|
-
".xml": "application/xml",
|
|
26574
|
-
".yaml": "application/yaml",
|
|
26575
|
-
".yml": "application/yaml"
|
|
26576
26152
|
};
|
|
26577
|
-
function
|
|
26578
|
-
|
|
26579
|
-
|
|
26580
|
-
|
|
26581
|
-
|
|
26582
|
-
// src/attachment/inbox.ts
|
|
26583
|
-
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
26584
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
26585
|
-
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
26586
|
-
var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
26587
|
-
async function handlePersonaInbox(req, args, deps) {
|
|
26588
|
-
const filename = readFilenameFromUrl(req.url);
|
|
26589
|
-
if (!filename) throw httpErr(400, "INVALID_PARAM", "missing ?name= query");
|
|
26590
|
-
const safeName = sanitizeFilename(filename);
|
|
26591
|
-
const mime = lookupMime(safeName);
|
|
26592
|
-
if (deps.isAllowedMime && !deps.isAllowedMime(mime)) {
|
|
26593
|
-
throw httpErr(415, "UNSUPPORTED_MEDIA", `mime ${mime} not allowed`);
|
|
26594
|
-
}
|
|
26595
|
-
const inboxDir = import_node_path16.default.join(args.personaDir, "_inbox");
|
|
26596
|
-
import_node_fs14.default.mkdirSync(inboxDir, { recursive: true });
|
|
26597
|
-
const sha = import_node_crypto5.default.randomBytes(6).toString("hex");
|
|
26598
|
-
const fileName = `${sha}-${safeName}`;
|
|
26599
|
-
const absPath = import_node_path16.default.join(inboxDir, fileName);
|
|
26600
|
-
const size = await streamBodyToFile(req, absPath, deps.maxBytes ?? DEFAULT_MAX_BYTES);
|
|
26601
|
-
const relPath = `_inbox/${fileName}`;
|
|
26602
|
-
deps.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
26603
|
-
relPath,
|
|
26604
|
-
from: args.from,
|
|
26605
|
-
addedBy: args.addedBy,
|
|
26606
|
-
size,
|
|
26607
|
-
mime
|
|
26608
|
-
});
|
|
26609
|
-
return { relPath, size, mime };
|
|
26610
|
-
}
|
|
26611
|
-
async function handleSessionInbox(req, args, deps) {
|
|
26612
|
-
const filename = readFilenameFromUrl(req.url);
|
|
26613
|
-
if (!filename) throw httpErr(400, "INVALID_PARAM", "missing ?name= query");
|
|
26614
|
-
const safeName = sanitizeFilename(filename);
|
|
26615
|
-
const mime = lookupMime(safeName);
|
|
26616
|
-
if (deps.isAllowedMime && !deps.isAllowedMime(mime)) {
|
|
26617
|
-
throw httpErr(415, "UNSUPPORTED_MEDIA", `mime ${mime} not allowed`);
|
|
26618
|
-
}
|
|
26619
|
-
const inboxDir = import_node_path16.default.join(args.sessionCwd, "_inbox");
|
|
26620
|
-
import_node_fs14.default.mkdirSync(inboxDir, { recursive: true });
|
|
26621
|
-
const sha = import_node_crypto5.default.randomBytes(6).toString("hex");
|
|
26622
|
-
const fileName = `${sha}-${safeName}`;
|
|
26623
|
-
const absPath = import_node_path16.default.join(inboxDir, fileName);
|
|
26624
|
-
const size = await streamBodyToFile(req, absPath, deps.maxBytes ?? DEFAULT_MAX_BYTES);
|
|
26625
|
-
const relPath = `_inbox/${fileName}`;
|
|
26626
|
-
deps.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
26627
|
-
relPath,
|
|
26628
|
-
from: "owner",
|
|
26629
|
-
size,
|
|
26630
|
-
mime
|
|
26631
|
-
});
|
|
26632
|
-
return { relPath, size, mime };
|
|
26633
|
-
}
|
|
26634
|
-
function streamBodyToFile(req, absPath, maxBytes) {
|
|
26635
|
-
return new Promise((resolve2, reject) => {
|
|
26636
|
-
let received = 0;
|
|
26637
|
-
const tmp = `${absPath}.tmp-${process.pid}-${Date.now()}`;
|
|
26638
|
-
const stream = import_node_fs14.default.createWriteStream(tmp, { mode: 384 });
|
|
26639
|
-
req.on("data", (chunk) => {
|
|
26640
|
-
received += chunk.length;
|
|
26641
|
-
if (received > maxBytes) {
|
|
26642
|
-
stream.destroy();
|
|
26643
|
-
try {
|
|
26644
|
-
import_node_fs14.default.unlinkSync(tmp);
|
|
26645
|
-
} catch {
|
|
26646
|
-
}
|
|
26647
|
-
reject(httpErr(413, "TOO_LARGE", `upload exceeds ${maxBytes} bytes`));
|
|
26648
|
-
req.destroy();
|
|
26649
|
-
return;
|
|
26650
|
-
}
|
|
26651
|
-
stream.write(chunk);
|
|
26652
|
-
});
|
|
26653
|
-
req.on("end", () => {
|
|
26654
|
-
stream.end();
|
|
26655
|
-
stream.on("finish", () => {
|
|
26656
|
-
try {
|
|
26657
|
-
import_node_fs14.default.renameSync(tmp, absPath);
|
|
26658
|
-
resolve2(received);
|
|
26659
|
-
} catch (err) {
|
|
26660
|
-
reject(err);
|
|
26661
|
-
}
|
|
26662
|
-
});
|
|
26663
|
-
});
|
|
26664
|
-
req.on("error", reject);
|
|
26665
|
-
stream.on("error", reject);
|
|
26666
|
-
});
|
|
26667
|
-
}
|
|
26668
|
-
function readFilenameFromUrl(rawUrl) {
|
|
26669
|
-
if (!rawUrl) return null;
|
|
26670
|
-
try {
|
|
26671
|
-
const u = new URL(rawUrl, "http://placeholder");
|
|
26672
|
-
return u.searchParams.get("name");
|
|
26673
|
-
} catch {
|
|
26674
|
-
return null;
|
|
26675
|
-
}
|
|
26676
|
-
}
|
|
26677
|
-
function sanitizeFilename(name) {
|
|
26678
|
-
const trimmed = name.replace(/[\x00-\x1f]/g, "").replace(/\//g, "_").replace(/\\/g, "_");
|
|
26679
|
-
const dotsOnly = trimmed.replace(/^\.+/, (m2) => "_".repeat(m2.length));
|
|
26680
|
-
if (!dotsOnly) return "unnamed";
|
|
26681
|
-
return dotsOnly.slice(0, 200);
|
|
26682
|
-
}
|
|
26683
|
-
function httpErr(status, code, message) {
|
|
26684
|
-
const err = new Error(message);
|
|
26685
|
-
err.httpStatus = status;
|
|
26686
|
-
err.code = code;
|
|
26687
|
-
return err;
|
|
26688
|
-
}
|
|
26689
|
-
function isHttpError(err) {
|
|
26690
|
-
return err instanceof Error && typeof err.httpStatus === "number" && typeof err.code === "string";
|
|
26691
|
-
}
|
|
26692
|
-
async function handleInboxRequest(req, res, ctxRouter, deps) {
|
|
26693
|
-
const personaMatch = ctxRouter.pathname.match(/^\/persona\/([^/]+)\/inbox$/);
|
|
26694
|
-
if (personaMatch && req.method === "POST") {
|
|
26695
|
-
if (!deps.getPersonaDir || !deps.getPersonaScope) {
|
|
26696
|
-
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "inbox not wired" });
|
|
26697
|
-
return true;
|
|
26698
|
-
}
|
|
26699
|
-
const pid = personaMatch[1];
|
|
26700
|
-
const personaDir = deps.getPersonaDir(pid);
|
|
26701
|
-
if (!personaDir) {
|
|
26702
|
-
sendJson(res, 404, { code: "NOT_FOUND", message: `persona ${pid} not found` });
|
|
26703
|
-
return true;
|
|
26704
|
-
}
|
|
26705
|
-
const sessionId = new URL(req.url ?? "", "http://placeholder").searchParams.get("session") || pid;
|
|
26706
|
-
try {
|
|
26707
|
-
const result = await handlePersonaInbox(
|
|
26708
|
-
req,
|
|
26709
|
-
{
|
|
26710
|
-
personaId: pid,
|
|
26711
|
-
personaDir,
|
|
26712
|
-
sessionId,
|
|
26713
|
-
scope: deps.getPersonaScope(pid),
|
|
26714
|
-
addedBy: deps.addedBy,
|
|
26715
|
-
from: ctxRouter.isOwner ? "owner" : "user"
|
|
26716
|
-
},
|
|
26717
|
-
deps.inboxOpts ?? { groupFileStore: deps.groupFileStore }
|
|
26718
|
-
);
|
|
26719
|
-
sendJson(res, 200, result);
|
|
26720
|
-
} catch (err) {
|
|
26721
|
-
handleInboxError(err, res);
|
|
26722
|
-
}
|
|
26723
|
-
return true;
|
|
26724
|
-
}
|
|
26725
|
-
const sessionMatch = ctxRouter.pathname.match(/^\/session\/([^/]+)\/inbox$/);
|
|
26726
|
-
if (sessionMatch && req.method === "POST") {
|
|
26727
|
-
if (!ctxRouter.isOwner) {
|
|
26728
|
-
sendJson(res, 403, { code: "FORBIDDEN", message: "direct session inbox is owner-only" });
|
|
26729
|
-
return true;
|
|
26730
|
-
}
|
|
26731
|
-
if (!deps.getSessionCwd) {
|
|
26732
|
-
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "inbox not wired" });
|
|
26733
|
-
return true;
|
|
26734
|
-
}
|
|
26735
|
-
const sid = sessionMatch[1];
|
|
26736
|
-
const sessionCwd = deps.getSessionCwd(sid);
|
|
26737
|
-
if (!sessionCwd) {
|
|
26738
|
-
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
26739
|
-
return true;
|
|
26740
|
-
}
|
|
26741
|
-
try {
|
|
26742
|
-
const result = await handleSessionInbox(
|
|
26743
|
-
req,
|
|
26744
|
-
{
|
|
26745
|
-
sessionId: sid,
|
|
26746
|
-
sessionCwd: sessionCwd.cwd,
|
|
26747
|
-
scope: sessionCwd.scope,
|
|
26748
|
-
from: "owner"
|
|
26749
|
-
},
|
|
26750
|
-
deps.inboxOpts ?? { groupFileStore: deps.groupFileStore }
|
|
26751
|
-
);
|
|
26752
|
-
sendJson(res, 200, result);
|
|
26753
|
-
} catch (err) {
|
|
26754
|
-
handleInboxError(err, res);
|
|
26755
|
-
}
|
|
26756
|
-
return true;
|
|
26757
|
-
}
|
|
26758
|
-
return false;
|
|
26759
|
-
}
|
|
26760
|
-
function handleInboxError(err, res) {
|
|
26761
|
-
if (isHttpError(err)) {
|
|
26762
|
-
sendJson(res, err.httpStatus, { code: err.code, message: err.message });
|
|
26763
|
-
return;
|
|
26764
|
-
}
|
|
26765
|
-
sendJson(res, 500, { code: "INTERNAL", message: err.message });
|
|
26766
|
-
}
|
|
26767
|
-
function sendJson(res, status, body) {
|
|
26768
|
-
if (res.headersSent) return;
|
|
26769
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
26770
|
-
res.end(JSON.stringify(body));
|
|
26771
|
-
}
|
|
26772
|
-
|
|
26773
|
-
// src/transport/http-router.ts
|
|
26774
|
-
function createHttpRouter(deps) {
|
|
26775
|
-
return async (req, res) => {
|
|
26776
|
-
const url = parseUrl(req.url);
|
|
26777
|
-
if (!url) {
|
|
26778
|
-
sendJson2(res, 400, { code: "INVALID_URL", message: "malformed request URL" });
|
|
26779
|
-
return true;
|
|
26780
|
-
}
|
|
26781
|
-
if (url.pathname === "/healthz" && req.method === "GET") {
|
|
26782
|
-
sendJson2(res, 200, { ok: true, version: deps.daemonVersion });
|
|
26783
|
-
return true;
|
|
26784
|
-
}
|
|
26785
|
-
if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && url.pathname !== "/outbox" && !url.pathname.startsWith("/outbox/")) {
|
|
26786
|
-
return false;
|
|
26787
|
-
}
|
|
26788
|
-
{
|
|
26789
|
-
const directMatch = url.pathname.match(/^\/outbox\/([^/]+)$/);
|
|
26790
|
-
const personaMatch = url.pathname.match(/^\/persona\/([^/]+)\/outbox\/([^/]+)$/);
|
|
26791
|
-
if (directMatch || personaMatch) {
|
|
26792
|
-
if (!deps.outboxStore) {
|
|
26793
|
-
sendJson2(res, 501, { code: "NOT_IMPLEMENTED", message: "outbox store not wired" });
|
|
26794
|
-
return true;
|
|
26795
|
-
}
|
|
26796
|
-
const capToken = directMatch ? directMatch[1] : personaMatch[2];
|
|
26797
|
-
const lookup = deps.outboxStore.lookup(capToken);
|
|
26798
|
-
if (!lookup.ok) {
|
|
26799
|
-
const statusByCode = {
|
|
26800
|
-
NOT_FOUND: 404,
|
|
26801
|
-
EXPIRED: 410,
|
|
26802
|
-
REVOKED: 410
|
|
26803
|
-
};
|
|
26804
|
-
sendJson2(res, statusByCode[lookup.code], { code: lookup.code, message: "outbox cap not usable" });
|
|
26805
|
-
return true;
|
|
26806
|
-
}
|
|
26807
|
-
const { entry } = lookup;
|
|
26808
|
-
if (entry.scope.kind === "personal") {
|
|
26809
|
-
const ctx2 = deps.authResolver.resolveFromHeader(
|
|
26810
|
-
req.headers.authorization,
|
|
26811
|
-
req.socket.remoteAddress ?? void 0
|
|
26812
|
-
);
|
|
26813
|
-
if (!ctx2 || ctx2.role !== "personal" || ctx2.personaId !== entry.scope.personalId) {
|
|
26814
|
-
sendJson2(res, 403, { code: "FORBIDDEN", message: "personal-scoped cap requires matching token" });
|
|
26815
|
-
return true;
|
|
26816
|
-
}
|
|
26817
|
-
}
|
|
26818
|
-
const outboxStore = deps.outboxStore;
|
|
26819
|
-
streamFile(res, entry.absPath, deps.logger, () => outboxStore.consume(capToken));
|
|
26820
|
-
return true;
|
|
26821
|
-
}
|
|
26822
|
-
}
|
|
26823
|
-
const ctx = deps.authResolver.resolveFromHeader(
|
|
26824
|
-
req.headers.authorization,
|
|
26825
|
-
req.socket.remoteAddress ?? void 0
|
|
26826
|
-
);
|
|
26827
|
-
if (!ctx) {
|
|
26828
|
-
sendJson2(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
|
|
26829
|
-
return true;
|
|
26830
|
-
}
|
|
26831
|
-
const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
|
|
26832
|
-
if (personaFilesMatch && req.method === "GET") {
|
|
26833
|
-
const pid = personaFilesMatch[1];
|
|
26834
|
-
const pathParam = url.searchParams.get("path");
|
|
26835
|
-
if (!pathParam) {
|
|
26836
|
-
sendJson2(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26837
|
-
return true;
|
|
26838
|
-
}
|
|
26839
|
-
if (!deps.personaStore || !deps.groupFileStore) {
|
|
26840
|
-
sendJson2(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
|
|
26841
|
-
return true;
|
|
26842
|
-
}
|
|
26843
|
-
const personaDir = deps.personaStore.personaDirPath(pid);
|
|
26844
|
-
const absPath = import_node_path17.default.isAbsolute(pathParam) ? pathParam : import_node_path17.default.join(personaDir, pathParam);
|
|
26845
|
-
if (!import_node_path17.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
|
|
26846
|
-
sendJson2(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
|
|
26847
|
-
return true;
|
|
26848
|
-
}
|
|
26849
|
-
if (ctx.role === "personal") {
|
|
26850
|
-
if (ctx.personaId !== pid) {
|
|
26851
|
-
sendJson2(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
|
|
26852
|
-
return true;
|
|
26853
|
-
}
|
|
26854
|
-
if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
|
|
26855
|
-
sendJson2(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
|
|
26856
|
-
return true;
|
|
26857
|
-
}
|
|
26858
|
-
}
|
|
26859
|
-
streamFile(res, absPath, deps.logger);
|
|
26860
|
-
return true;
|
|
26861
|
-
}
|
|
26862
|
-
const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
|
|
26863
|
-
if (sessionFilesMatch && req.method === "GET") {
|
|
26864
|
-
if (ctx.role !== "owner") {
|
|
26865
|
-
sendJson2(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
|
|
26866
|
-
return true;
|
|
26867
|
-
}
|
|
26868
|
-
const sid = sessionFilesMatch[1];
|
|
26869
|
-
const pathParam = url.searchParams.get("path");
|
|
26870
|
-
if (!pathParam) {
|
|
26871
|
-
sendJson2(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26872
|
-
return true;
|
|
26873
|
-
}
|
|
26874
|
-
let absPath;
|
|
26875
|
-
if (import_node_path17.default.isAbsolute(pathParam)) {
|
|
26876
|
-
absPath = pathParam;
|
|
26877
|
-
} else if (deps.sessionStore) {
|
|
26878
|
-
const file = deps.sessionStore.read(sid);
|
|
26879
|
-
if (!file) {
|
|
26880
|
-
sendJson2(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
26881
|
-
return true;
|
|
26882
|
-
}
|
|
26883
|
-
absPath = import_node_path17.default.join(file.cwd, pathParam);
|
|
26884
|
-
} else {
|
|
26885
|
-
sendJson2(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
26886
|
-
return true;
|
|
26887
|
-
}
|
|
26888
|
-
streamFile(res, absPath, deps.logger);
|
|
26889
|
-
return true;
|
|
26890
|
-
}
|
|
26891
|
-
if (/^\/persona\/[^/]+\/inbox$/.test(url.pathname) && req.method === "POST") {
|
|
26892
|
-
if (!deps.groupFileStore || !deps.personaStore) {
|
|
26893
|
-
sendJson2(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "inbox not wired" }));
|
|
26894
|
-
return true;
|
|
26895
|
-
}
|
|
26896
|
-
const handled = await handleInboxRequest(
|
|
26897
|
-
req,
|
|
26898
|
-
res,
|
|
26899
|
-
{
|
|
26900
|
-
pathname: url.pathname,
|
|
26901
|
-
isOwner: ctx.role === "owner",
|
|
26902
|
-
personaId: ctx.personaId
|
|
26903
|
-
},
|
|
26904
|
-
{
|
|
26905
|
-
groupFileStore: deps.groupFileStore,
|
|
26906
|
-
getPersonaDir: (pid) => deps.personaStore.personaDirPath(pid),
|
|
26907
|
-
// PR 7:persona inbox 默认走 owner-mode scope;UI 端如果用 personal listener
|
|
26908
|
-
// 也归到 owner scope(session 维度)—— PR 9 完善时再走 listener scope。
|
|
26909
|
-
getPersonaScope: (pid) => ({
|
|
26910
|
-
kind: "persona",
|
|
26911
|
-
personaId: pid,
|
|
26912
|
-
mode: "owner"
|
|
26913
|
-
}),
|
|
26914
|
-
addedBy: ctx.label ?? (ctx.role === "owner" ? "owner" : "unknown")
|
|
26915
|
-
}
|
|
26916
|
-
);
|
|
26917
|
-
if (handled) return true;
|
|
26918
|
-
}
|
|
26919
|
-
if (/^\/session\/[^/]+\/inbox$/.test(url.pathname) && req.method === "POST") {
|
|
26920
|
-
if (!deps.groupFileStore || !deps.sessionStore) {
|
|
26921
|
-
sendJson2(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "inbox not wired" }));
|
|
26922
|
-
return true;
|
|
26923
|
-
}
|
|
26924
|
-
const handled = await handleInboxRequest(
|
|
26925
|
-
req,
|
|
26926
|
-
res,
|
|
26927
|
-
{
|
|
26928
|
-
pathname: url.pathname,
|
|
26929
|
-
isOwner: ctx.role === "owner"
|
|
26930
|
-
},
|
|
26931
|
-
{
|
|
26932
|
-
groupFileStore: deps.groupFileStore,
|
|
26933
|
-
getSessionCwd: (sid) => {
|
|
26934
|
-
const f = deps.sessionStore.read(sid);
|
|
26935
|
-
if (!f) return null;
|
|
26936
|
-
const scope = f.ownerPersonaId ? { kind: "persona", personaId: f.ownerPersonaId, mode: "owner" } : { kind: "default" };
|
|
26937
|
-
return { cwd: f.cwd, scope };
|
|
26938
|
-
},
|
|
26939
|
-
addedBy: ctx.role === "owner" ? "owner" : "unknown"
|
|
26940
|
-
}
|
|
26941
|
-
);
|
|
26942
|
-
if (handled) return true;
|
|
26943
|
-
}
|
|
26944
|
-
if (/^\/persona\/[^/]+\/attachment-meta$/.test(url.pathname) && req.method === "GET") {
|
|
26945
|
-
sendJson2(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "attachment-meta \u2014 PR 6" }));
|
|
26946
|
-
return true;
|
|
26947
|
-
}
|
|
26948
|
-
sendJson2(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
26949
|
-
return true;
|
|
26950
|
-
};
|
|
26951
|
-
}
|
|
26952
|
-
function parseUrl(rawUrl) {
|
|
26953
|
-
if (!rawUrl) return null;
|
|
26954
|
-
try {
|
|
26955
|
-
return new URL(rawUrl, "http://placeholder");
|
|
26956
|
-
} catch {
|
|
26957
|
-
return null;
|
|
26958
|
-
}
|
|
26959
|
-
}
|
|
26960
|
-
function sendJson2(res, status, body) {
|
|
26961
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
26962
|
-
res.end(JSON.stringify(body));
|
|
26963
|
-
}
|
|
26964
|
-
function withCtx(ctx, body) {
|
|
26965
|
-
return { ...body, role: ctx.role, personaId: ctx.personaId };
|
|
26966
|
-
}
|
|
26967
|
-
function isContainedIn(abs, root) {
|
|
26968
|
-
const normalized = import_node_path17.default.resolve(abs);
|
|
26969
|
-
const normalizedRoot = import_node_path17.default.resolve(root);
|
|
26970
|
-
if (normalized === normalizedRoot) return true;
|
|
26971
|
-
return normalized.startsWith(normalizedRoot + import_node_path17.default.sep);
|
|
26972
|
-
}
|
|
26973
|
-
function streamFile(res, absPath, logger, onComplete) {
|
|
26974
|
-
let stat;
|
|
26975
|
-
try {
|
|
26976
|
-
stat = import_node_fs15.default.statSync(absPath);
|
|
26977
|
-
} catch (err) {
|
|
26978
|
-
const code = err?.code;
|
|
26979
|
-
if (code === "ENOENT") {
|
|
26980
|
-
sendJson2(res, 404, { code: "NOT_FOUND", message: "file not found" });
|
|
26981
|
-
} else {
|
|
26982
|
-
sendJson2(res, 500, { code: "STAT_FAILED", message: err.message });
|
|
26983
|
-
}
|
|
26984
|
-
return;
|
|
26985
|
-
}
|
|
26986
|
-
if (!stat.isFile()) {
|
|
26987
|
-
sendJson2(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
|
|
26988
|
-
return;
|
|
26989
|
-
}
|
|
26990
|
-
const mime = lookupMime(absPath);
|
|
26991
|
-
res.writeHead(200, {
|
|
26992
|
-
"Content-Type": mime,
|
|
26993
|
-
"Content-Length": String(stat.size),
|
|
26994
|
-
// 防止浏览器把任意 mime 当 html 渲染(spec §11 #8 安全心智)
|
|
26995
|
-
"X-Content-Type-Options": "nosniff"
|
|
26996
|
-
});
|
|
26997
|
-
const stream = import_node_fs15.default.createReadStream(absPath);
|
|
26998
|
-
stream.on("error", (err) => {
|
|
26999
|
-
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
27000
|
-
res.destroy();
|
|
27001
|
-
});
|
|
27002
|
-
if (onComplete) {
|
|
27003
|
-
res.once("finish", onComplete);
|
|
27004
|
-
}
|
|
27005
|
-
stream.pipe(res);
|
|
27006
|
-
}
|
|
27007
|
-
|
|
27008
|
-
// src/attachment/outbox.ts
|
|
27009
|
-
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
27010
|
-
var import_node_path18 = __toESM(require("path"), 1);
|
|
27011
|
-
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
27012
|
-
init_protocol();
|
|
27013
|
-
var FILE_NAME = "outbox-caps.json";
|
|
27014
|
-
var OutboxStore = class {
|
|
27015
|
-
file;
|
|
27016
|
-
now;
|
|
27017
|
-
logger;
|
|
27018
|
-
cache = [];
|
|
27019
|
-
constructor(opts) {
|
|
27020
|
-
this.file = import_node_path18.default.join(opts.dataDir, FILE_NAME);
|
|
27021
|
-
this.now = opts.now ?? Date.now;
|
|
27022
|
-
this.logger = opts.logger;
|
|
27023
|
-
this.reload();
|
|
27024
|
-
}
|
|
27025
|
-
reload() {
|
|
27026
|
-
try {
|
|
27027
|
-
const raw = import_node_fs16.default.readFileSync(this.file, "utf8");
|
|
27028
|
-
const parsed = JSON.parse(raw);
|
|
27029
|
-
if (!Array.isArray(parsed)) {
|
|
27030
|
-
this.logger?.warn("OutboxStore.reload: outbox-caps.json is not an array; resetting", {
|
|
27031
|
-
file: this.file
|
|
27032
|
-
});
|
|
27033
|
-
this.cache = [];
|
|
27034
|
-
return;
|
|
27035
|
-
}
|
|
27036
|
-
const out = [];
|
|
27037
|
-
for (const item of parsed) {
|
|
27038
|
-
const r = OutboxCapEntrySchema.safeParse(item);
|
|
27039
|
-
if (r.success) out.push(r.data);
|
|
27040
|
-
}
|
|
27041
|
-
this.cache = out;
|
|
27042
|
-
} catch (err) {
|
|
27043
|
-
const code = err?.code;
|
|
27044
|
-
if (code !== "ENOENT") {
|
|
27045
|
-
this.logger?.warn("OutboxStore.reload failed; outbox-caps.json may be corrupt", {
|
|
27046
|
-
file: this.file,
|
|
27047
|
-
err: err.message
|
|
27048
|
-
});
|
|
27049
|
-
}
|
|
27050
|
-
this.cache = [];
|
|
27051
|
-
}
|
|
27052
|
-
}
|
|
27053
|
-
persist() {
|
|
27054
|
-
import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(this.file), { recursive: true });
|
|
27055
|
-
const tmp = `${this.file}.tmp-${process.pid}-${Date.now()}`;
|
|
27056
|
-
import_node_fs16.default.writeFileSync(tmp, JSON.stringify(this.cache, null, 2), { mode: 384 });
|
|
27057
|
-
import_node_fs16.default.renameSync(tmp, this.file);
|
|
27058
|
-
}
|
|
27059
|
-
/** 列出当前缓存(含 revoked / 过期,UI Drawer 显灰;过滤靠调用方) */
|
|
27060
|
-
list() {
|
|
27061
|
-
return this.cache.slice();
|
|
27062
|
-
}
|
|
27063
|
-
/** 按 personaId / sessionId 过滤;为 outbox Drawer 服务 */
|
|
27064
|
-
listByPersona(personaId) {
|
|
27065
|
-
return this.cache.filter(
|
|
27066
|
-
(c) => c.scopeRef.kind === "persona" && c.scopeRef.personaId === personaId
|
|
27067
|
-
);
|
|
27068
|
-
}
|
|
27069
|
-
/**
|
|
27070
|
-
* Mint a new cap. capToken = 32B 随机 base64url(256-bit entropy),spec §11 #8 防剪贴板/微信
|
|
27071
|
-
* 截图泄露通过短码的暴力面。返回完整 entry 让 caller 拼 URL。
|
|
27072
|
-
*
|
|
27073
|
-
* @param ttlSeconds null = 永久;undefined = 默认 24h(spec §12)
|
|
27074
|
-
*/
|
|
27075
|
-
mint(input) {
|
|
27076
|
-
const ts = this.now();
|
|
27077
|
-
const ttl = input.ttlSeconds === null ? null : input.ttlSeconds ?? 24 * 3600;
|
|
27078
|
-
const entry = {
|
|
27079
|
-
capToken: import_node_crypto6.default.randomBytes(32).toString("base64url"),
|
|
27080
|
-
scopeRef: input.scopeRef,
|
|
27081
|
-
absPath: input.absPath,
|
|
27082
|
-
name: input.name,
|
|
27083
|
-
expiresAt: ttl === null ? null : ts + ttl * 1e3,
|
|
27084
|
-
oneShot: input.oneShot ?? false,
|
|
27085
|
-
scope: input.scope ?? { kind: "public" },
|
|
27086
|
-
hits: 0,
|
|
27087
|
-
createdAt: ts
|
|
27088
|
-
};
|
|
27089
|
-
this.cache.push(entry);
|
|
27090
|
-
this.persist();
|
|
27091
|
-
return entry;
|
|
27092
|
-
}
|
|
27093
|
-
/** Mark revoked;不删除条目(便于审计),上层 lookup 后返回 410 */
|
|
27094
|
-
revoke(capToken) {
|
|
27095
|
-
const idx = this.cache.findIndex((c) => c.capToken === capToken);
|
|
27096
|
-
if (idx < 0) return false;
|
|
27097
|
-
if (this.cache[idx].revoked) return false;
|
|
27098
|
-
this.cache[idx] = { ...this.cache[idx], revoked: true };
|
|
27099
|
-
this.persist();
|
|
27100
|
-
return true;
|
|
27101
|
-
}
|
|
27102
|
-
/**
|
|
27103
|
-
* HTTP 层用:根据 capToken 找到 entry 并判定能不能用。
|
|
27104
|
-
* 三种结果:
|
|
27105
|
-
* - { ok: true, entry } 可用,调用方流文件
|
|
27106
|
-
* - { ok: false, code: 'EXPIRED' | 'REVOKED' | 'NOT_FOUND' }
|
|
27107
|
-
*
|
|
27108
|
-
* 命中 + oneShot=true 时由调用方调 consume() 把 cache 中 hits 自增并立即 revoke
|
|
27109
|
-
* (consume 应当在 response 'finish' 后才触发,避免中途断流误算消费)。
|
|
27110
|
-
*/
|
|
27111
|
-
lookup(capToken) {
|
|
27112
|
-
const entry = this.cache.find((c) => c.capToken === capToken);
|
|
27113
|
-
if (!entry) return { ok: false, code: "NOT_FOUND" };
|
|
27114
|
-
if (entry.revoked) return { ok: false, code: "REVOKED" };
|
|
27115
|
-
if (entry.expiresAt !== null && this.now() > entry.expiresAt) {
|
|
27116
|
-
return { ok: false, code: "EXPIRED" };
|
|
27117
|
-
}
|
|
27118
|
-
return { ok: true, entry };
|
|
27119
|
-
}
|
|
27120
|
-
/** 命中后真正"消费"一次:hits++ + oneShot 时 revoke。daemon HTTP 层 stream 成功后再调 */
|
|
27121
|
-
consume(capToken) {
|
|
27122
|
-
const idx = this.cache.findIndex((c) => c.capToken === capToken);
|
|
27123
|
-
if (idx < 0) return;
|
|
27124
|
-
const prev = this.cache[idx];
|
|
27125
|
-
this.cache[idx] = {
|
|
27126
|
-
...prev,
|
|
27127
|
-
hits: prev.hits + 1,
|
|
27128
|
-
revoked: prev.oneShot ? true : prev.revoked
|
|
27129
|
-
};
|
|
27130
|
-
this.persist();
|
|
27131
|
-
}
|
|
27132
|
-
};
|
|
27133
|
-
|
|
27134
|
-
// src/attachment/mount.ts
|
|
27135
|
-
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
27136
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
27137
|
-
init_protocol();
|
|
27138
|
-
var MountStore = class {
|
|
27139
|
-
constructor(opts) {
|
|
27140
|
-
this.opts = opts;
|
|
27141
|
-
}
|
|
27142
|
-
opts;
|
|
27143
|
-
filePath(personaId) {
|
|
27144
|
-
return import_node_path19.default.join(this.opts.personaDirPath(personaId), ".clawd", "shared-files.json");
|
|
27145
|
-
}
|
|
27146
|
-
list(personaId) {
|
|
27147
|
-
try {
|
|
27148
|
-
const raw = import_node_fs17.default.readFileSync(this.filePath(personaId), "utf8");
|
|
27149
|
-
const parsed = JSON.parse(raw);
|
|
27150
|
-
if (!Array.isArray(parsed)) return [];
|
|
27151
|
-
const out = [];
|
|
27152
|
-
for (const item of parsed) {
|
|
27153
|
-
const r = MountEntrySchema.safeParse(item);
|
|
27154
|
-
if (r.success) out.push(r.data);
|
|
27155
|
-
}
|
|
27156
|
-
return out;
|
|
27157
|
-
} catch {
|
|
27158
|
-
return [];
|
|
27159
|
-
}
|
|
27160
|
-
}
|
|
27161
|
-
/**
|
|
27162
|
-
* Add a mount。在 personaDir 创建 link/copy + 写 manifest。冲突 basename 抛错(caller
|
|
27163
|
-
* 决定是覆盖还是改名)。
|
|
27164
|
-
*/
|
|
27165
|
-
add(personaId, input) {
|
|
27166
|
-
const personaDir = this.opts.personaDirPath(personaId);
|
|
27167
|
-
if (!import_node_fs17.default.existsSync(personaDir)) {
|
|
27168
|
-
throw new Error(`personaDir not found: ${personaDir}`);
|
|
27169
|
-
}
|
|
27170
|
-
const source = import_node_path19.default.resolve(input.absPath);
|
|
27171
|
-
if (!import_node_fs17.default.existsSync(source)) {
|
|
27172
|
-
throw new Error(`source path not found: ${source}`);
|
|
27173
|
-
}
|
|
27174
|
-
const basename = import_node_path19.default.basename(source);
|
|
27175
|
-
const dest = import_node_path19.default.join(personaDir, basename);
|
|
27176
|
-
if (import_node_fs17.default.existsSync(dest)) {
|
|
27177
|
-
throw new Error(`destination already exists: ${dest}`);
|
|
27178
|
-
}
|
|
27179
|
-
if (input.mode === "link") {
|
|
27180
|
-
import_node_fs17.default.symlinkSync(source, dest);
|
|
27181
|
-
} else {
|
|
27182
|
-
import_node_fs17.default.copyFileSync(source, dest);
|
|
27183
|
-
}
|
|
27184
|
-
const entry = {
|
|
27185
|
-
basename,
|
|
27186
|
-
absPath: source,
|
|
27187
|
-
mode: input.mode
|
|
27188
|
-
};
|
|
27189
|
-
const next = this.list(personaId).concat([entry]);
|
|
27190
|
-
this.writeManifest(personaId, next);
|
|
27191
|
-
return entry;
|
|
27192
|
-
}
|
|
27193
|
-
remove(personaId, basename) {
|
|
27194
|
-
const entries = this.list(personaId);
|
|
27195
|
-
const idx = entries.findIndex((e) => e.basename === basename);
|
|
27196
|
-
if (idx < 0) return false;
|
|
27197
|
-
const entry = entries[idx];
|
|
27198
|
-
const personaDir = this.opts.personaDirPath(personaId);
|
|
27199
|
-
const dest = import_node_path19.default.join(personaDir, basename);
|
|
27200
|
-
try {
|
|
27201
|
-
import_node_fs17.default.unlinkSync(dest);
|
|
27202
|
-
} catch {
|
|
27203
|
-
}
|
|
27204
|
-
const next = entries.slice();
|
|
27205
|
-
next.splice(idx, 1);
|
|
27206
|
-
this.writeManifest(personaId, next);
|
|
27207
|
-
void entry;
|
|
27208
|
-
return true;
|
|
27209
|
-
}
|
|
27210
|
-
writeManifest(personaId, entries) {
|
|
27211
|
-
const file = this.filePath(personaId);
|
|
27212
|
-
import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
|
|
27213
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
27214
|
-
import_node_fs17.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
27215
|
-
import_node_fs17.default.renameSync(tmp, file);
|
|
27216
|
-
}
|
|
27217
|
-
};
|
|
27218
|
-
|
|
27219
|
-
// src/discovery/state-file.ts
|
|
27220
|
-
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
27221
|
-
var import_node_path20 = __toESM(require("path"), 1);
|
|
27222
|
-
function defaultStateFilePath(dataDir) {
|
|
27223
|
-
return import_node_path20.default.join(dataDir, "state.json");
|
|
27224
|
-
}
|
|
27225
|
-
function isPidAlive(pid) {
|
|
27226
|
-
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
27227
|
-
try {
|
|
27228
|
-
process.kill(pid, 0);
|
|
27229
|
-
return true;
|
|
27230
|
-
} catch (err) {
|
|
27231
|
-
const code = err.code;
|
|
27232
|
-
return code === "EPERM";
|
|
27233
|
-
}
|
|
27234
|
-
}
|
|
27235
|
-
var StateFileManager = class {
|
|
27236
|
-
file;
|
|
27237
|
-
constructor(opts) {
|
|
27238
|
-
this.file = defaultStateFilePath(opts.dataDir);
|
|
27239
|
-
}
|
|
27240
|
-
filePath() {
|
|
27241
|
-
return this.file;
|
|
27242
|
-
}
|
|
27243
|
-
read() {
|
|
27244
|
-
try {
|
|
27245
|
-
const raw = import_node_fs18.default.readFileSync(this.file, "utf8");
|
|
27246
|
-
const parsed = JSON.parse(raw);
|
|
27247
|
-
return parsed;
|
|
27248
|
-
} catch {
|
|
27249
|
-
return null;
|
|
27250
|
-
}
|
|
27251
|
-
}
|
|
27252
|
-
preflight() {
|
|
27253
|
-
const existing = this.read();
|
|
27254
|
-
if (!existing) return { status: "ok" };
|
|
27255
|
-
if (isPidAlive(existing.pid)) return { status: "active", existing };
|
|
27256
|
-
return { status: "stale", existing };
|
|
27257
|
-
}
|
|
27258
|
-
write(state) {
|
|
27259
|
-
import_node_fs18.default.mkdirSync(import_node_path20.default.dirname(this.file), { recursive: true });
|
|
27260
|
-
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
27261
|
-
import_node_fs18.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
27262
|
-
import_node_fs18.default.renameSync(tmp, this.file);
|
|
27263
|
-
if (process.platform !== "win32") {
|
|
27264
|
-
try {
|
|
27265
|
-
import_node_fs18.default.chmodSync(this.file, 384);
|
|
27266
|
-
} catch {
|
|
27267
|
-
}
|
|
27268
|
-
}
|
|
27269
|
-
}
|
|
27270
|
-
delete() {
|
|
27271
|
-
try {
|
|
27272
|
-
import_node_fs18.default.unlinkSync(this.file);
|
|
27273
|
-
} catch {
|
|
27274
|
-
}
|
|
27275
|
-
}
|
|
27276
|
-
};
|
|
27277
|
-
|
|
27278
|
-
// src/tunnel/tunnel-manager.ts
|
|
27279
|
-
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
27280
|
-
var import_node_path24 = __toESM(require("path"), 1);
|
|
27281
|
-
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
27282
|
-
var import_node_child_process5 = require("child_process");
|
|
27283
|
-
|
|
27284
|
-
// src/tunnel/tunnel-store.ts
|
|
27285
|
-
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
27286
|
-
var import_node_path21 = __toESM(require("path"), 1);
|
|
27287
|
-
var TunnelStore = class {
|
|
27288
|
-
constructor(filePath) {
|
|
27289
|
-
this.filePath = filePath;
|
|
27290
|
-
}
|
|
27291
|
-
filePath;
|
|
27292
|
-
async get() {
|
|
27293
|
-
try {
|
|
27294
|
-
const raw = await import_node_fs19.default.promises.readFile(this.filePath, "utf8");
|
|
27295
|
-
const obj = JSON.parse(raw);
|
|
27296
|
-
if (!isPersistedTunnel(obj)) return null;
|
|
27297
|
-
return obj;
|
|
27298
|
-
} catch (err) {
|
|
27299
|
-
const code = err?.code;
|
|
27300
|
-
if (code === "ENOENT") return null;
|
|
27301
|
-
return null;
|
|
27302
|
-
}
|
|
27303
|
-
}
|
|
27304
|
-
async set(v2) {
|
|
27305
|
-
const dir = import_node_path21.default.dirname(this.filePath);
|
|
27306
|
-
await import_node_fs19.default.promises.mkdir(dir, { recursive: true });
|
|
27307
|
-
const data = JSON.stringify(v2, null, 2);
|
|
27308
|
-
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
27309
|
-
await import_node_fs19.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
27310
|
-
if (process.platform !== "win32") {
|
|
27311
|
-
try {
|
|
27312
|
-
await import_node_fs19.default.promises.chmod(tmp, 384);
|
|
27313
|
-
} catch {
|
|
27314
|
-
}
|
|
27315
|
-
}
|
|
27316
|
-
await import_node_fs19.default.promises.rename(tmp, this.filePath);
|
|
27317
|
-
}
|
|
27318
|
-
async clear() {
|
|
27319
|
-
try {
|
|
27320
|
-
await import_node_fs19.default.promises.unlink(this.filePath);
|
|
27321
|
-
} catch (err) {
|
|
27322
|
-
const code = err?.code;
|
|
27323
|
-
if (code !== "ENOENT") throw err;
|
|
27324
|
-
}
|
|
27325
|
-
}
|
|
27326
|
-
};
|
|
27327
|
-
function isPersistedTunnel(o) {
|
|
27328
|
-
if (!o || typeof o !== "object") return false;
|
|
27329
|
-
const r = o;
|
|
27330
|
-
return typeof r.subdomain === "string" && typeof r.frpsHost === "string" && typeof r.frpsPort === "number" && typeof r.frpsToken === "string" && typeof r.url === "string" && typeof r.registeredAt === "string";
|
|
26153
|
+
function isPersistedTunnel(o) {
|
|
26154
|
+
if (!o || typeof o !== "object") return false;
|
|
26155
|
+
const r = o;
|
|
26156
|
+
return typeof r.subdomain === "string" && typeof r.frpsHost === "string" && typeof r.frpsPort === "number" && typeof r.frpsToken === "string" && typeof r.url === "string" && typeof r.registeredAt === "string";
|
|
27331
26157
|
}
|
|
27332
26158
|
|
|
27333
26159
|
// src/tunnel/register-client.ts
|
|
@@ -27412,9 +26238,9 @@ function escape(v2) {
|
|
|
27412
26238
|
}
|
|
27413
26239
|
|
|
27414
26240
|
// src/tunnel/frpc-binary.ts
|
|
27415
|
-
var
|
|
26241
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
27416
26242
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
27417
|
-
var
|
|
26243
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
27418
26244
|
var import_node_child_process3 = require("child_process");
|
|
27419
26245
|
var import_node_stream2 = require("stream");
|
|
27420
26246
|
var import_promises = require("stream/promises");
|
|
@@ -27446,20 +26272,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
27446
26272
|
}
|
|
27447
26273
|
async function ensureFrpcBinary(opts) {
|
|
27448
26274
|
if (opts.override) {
|
|
27449
|
-
if (!
|
|
26275
|
+
if (!import_node_fs15.default.existsSync(opts.override)) {
|
|
27450
26276
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
27451
26277
|
}
|
|
27452
26278
|
return opts.override;
|
|
27453
26279
|
}
|
|
27454
26280
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
27455
26281
|
const platform = opts.platform ?? detectPlatform();
|
|
27456
|
-
const binDir =
|
|
27457
|
-
|
|
26282
|
+
const binDir = import_node_path16.default.join(opts.dataDir, "bin");
|
|
26283
|
+
import_node_fs15.default.mkdirSync(binDir, { recursive: true });
|
|
27458
26284
|
cleanupStaleArtifacts(binDir);
|
|
27459
|
-
const stableBin =
|
|
27460
|
-
if (
|
|
26285
|
+
const stableBin = import_node_path16.default.join(binDir, "frpc");
|
|
26286
|
+
if (import_node_fs15.default.existsSync(stableBin)) return stableBin;
|
|
27461
26287
|
const partialBin = `${stableBin}.partial`;
|
|
27462
|
-
const tarballPath =
|
|
26288
|
+
const tarballPath = import_node_path16.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
27463
26289
|
try {
|
|
27464
26290
|
const url = frpcDownloadUrl(version2, platform);
|
|
27465
26291
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -27468,8 +26294,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
27468
26294
|
} else {
|
|
27469
26295
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
27470
26296
|
}
|
|
27471
|
-
|
|
27472
|
-
|
|
26297
|
+
import_node_fs15.default.chmodSync(partialBin, 493);
|
|
26298
|
+
import_node_fs15.default.renameSync(partialBin, stableBin);
|
|
27473
26299
|
} finally {
|
|
27474
26300
|
safeUnlink(tarballPath);
|
|
27475
26301
|
safeUnlink(partialBin);
|
|
@@ -27479,15 +26305,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
27479
26305
|
function cleanupStaleArtifacts(binDir) {
|
|
27480
26306
|
let entries;
|
|
27481
26307
|
try {
|
|
27482
|
-
entries =
|
|
26308
|
+
entries = import_node_fs15.default.readdirSync(binDir);
|
|
27483
26309
|
} catch {
|
|
27484
26310
|
return;
|
|
27485
26311
|
}
|
|
27486
26312
|
for (const name of entries) {
|
|
27487
26313
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
27488
|
-
const full =
|
|
26314
|
+
const full = import_node_path16.default.join(binDir, name);
|
|
27489
26315
|
try {
|
|
27490
|
-
|
|
26316
|
+
import_node_fs15.default.rmSync(full, { recursive: true, force: true });
|
|
27491
26317
|
} catch {
|
|
27492
26318
|
}
|
|
27493
26319
|
}
|
|
@@ -27495,7 +26321,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
27495
26321
|
}
|
|
27496
26322
|
function safeUnlink(p2) {
|
|
27497
26323
|
try {
|
|
27498
|
-
|
|
26324
|
+
import_node_fs15.default.unlinkSync(p2);
|
|
27499
26325
|
} catch {
|
|
27500
26326
|
}
|
|
27501
26327
|
}
|
|
@@ -27506,13 +26332,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
27506
26332
|
if (!res.ok || !res.body) {
|
|
27507
26333
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
27508
26334
|
}
|
|
27509
|
-
const out =
|
|
26335
|
+
const out = import_node_fs15.default.createWriteStream(dest);
|
|
27510
26336
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
27511
26337
|
await (0, import_promises.pipeline)(nodeStream, out);
|
|
27512
26338
|
}
|
|
27513
26339
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
27514
|
-
const work =
|
|
27515
|
-
|
|
26340
|
+
const work = import_node_path16.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
26341
|
+
import_node_fs15.default.mkdirSync(work, { recursive: true });
|
|
27516
26342
|
try {
|
|
27517
26343
|
await new Promise((resolve2, reject) => {
|
|
27518
26344
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -27520,32 +26346,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
27520
26346
|
proc.on("exit", (code) => code === 0 ? resolve2() : reject(new Error(`tar exited ${code}`)));
|
|
27521
26347
|
});
|
|
27522
26348
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
27523
|
-
const src =
|
|
27524
|
-
if (!
|
|
26349
|
+
const src = import_node_path16.default.join(work, dirName, "frpc");
|
|
26350
|
+
if (!import_node_fs15.default.existsSync(src)) {
|
|
27525
26351
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
27526
26352
|
}
|
|
27527
|
-
|
|
26353
|
+
import_node_fs15.default.copyFileSync(src, destBin);
|
|
27528
26354
|
} finally {
|
|
27529
|
-
|
|
26355
|
+
import_node_fs15.default.rmSync(work, { recursive: true, force: true });
|
|
27530
26356
|
}
|
|
27531
26357
|
}
|
|
27532
26358
|
|
|
27533
26359
|
// src/tunnel/frpc-process.ts
|
|
27534
|
-
var
|
|
27535
|
-
var
|
|
26360
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
26361
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
27536
26362
|
var import_node_child_process4 = require("child_process");
|
|
27537
26363
|
function frpcPidFilePath(dataDir) {
|
|
27538
|
-
return
|
|
26364
|
+
return import_node_path17.default.join(dataDir, "frpc.pid");
|
|
27539
26365
|
}
|
|
27540
26366
|
function writeFrpcPid(dataDir, pid) {
|
|
27541
26367
|
try {
|
|
27542
|
-
|
|
26368
|
+
import_node_fs16.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
27543
26369
|
} catch {
|
|
27544
26370
|
}
|
|
27545
26371
|
}
|
|
27546
26372
|
function clearFrpcPid(dataDir) {
|
|
27547
26373
|
try {
|
|
27548
|
-
|
|
26374
|
+
import_node_fs16.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
27549
26375
|
} catch {
|
|
27550
26376
|
}
|
|
27551
26377
|
}
|
|
@@ -27561,7 +26387,7 @@ function defaultIsPidAlive(pid) {
|
|
|
27561
26387
|
}
|
|
27562
26388
|
function defaultReadPidFile(file) {
|
|
27563
26389
|
try {
|
|
27564
|
-
return
|
|
26390
|
+
return import_node_fs16.default.readFileSync(file, "utf8");
|
|
27565
26391
|
} catch {
|
|
27566
26392
|
return null;
|
|
27567
26393
|
}
|
|
@@ -27577,7 +26403,7 @@ function defaultSleep(ms) {
|
|
|
27577
26403
|
}
|
|
27578
26404
|
async function killStaleFrpc(deps) {
|
|
27579
26405
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
27580
|
-
const tomlPath =
|
|
26406
|
+
const tomlPath = import_node_path17.default.join(deps.dataDir, "frpc.toml");
|
|
27581
26407
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
27582
26408
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
27583
26409
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -27601,7 +26427,7 @@ async function killStaleFrpc(deps) {
|
|
|
27601
26427
|
}
|
|
27602
26428
|
if (victims.size === 0) {
|
|
27603
26429
|
try {
|
|
27604
|
-
|
|
26430
|
+
import_node_fs16.default.unlinkSync(pidFile);
|
|
27605
26431
|
} catch {
|
|
27606
26432
|
}
|
|
27607
26433
|
return;
|
|
@@ -27612,7 +26438,7 @@ async function killStaleFrpc(deps) {
|
|
|
27612
26438
|
}
|
|
27613
26439
|
await sleep(deps.reapWaitMs ?? 300);
|
|
27614
26440
|
try {
|
|
27615
|
-
|
|
26441
|
+
import_node_fs16.default.unlinkSync(pidFile);
|
|
27616
26442
|
} catch {
|
|
27617
26443
|
}
|
|
27618
26444
|
}
|
|
@@ -27649,7 +26475,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
27649
26475
|
var TunnelManager = class {
|
|
27650
26476
|
constructor(deps) {
|
|
27651
26477
|
this.deps = deps;
|
|
27652
|
-
this.store = deps.store ?? new TunnelStore(
|
|
26478
|
+
this.store = deps.store ?? new TunnelStore(import_node_path18.default.join(deps.dataDir, "tunnel.json"));
|
|
27653
26479
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
27654
26480
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
27655
26481
|
}
|
|
@@ -27776,8 +26602,8 @@ var TunnelManager = class {
|
|
|
27776
26602
|
dataDir: this.deps.dataDir,
|
|
27777
26603
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
27778
26604
|
});
|
|
27779
|
-
const tomlPath =
|
|
27780
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
26605
|
+
const tomlPath = import_node_path18.default.join(this.deps.dataDir, "frpc.toml");
|
|
26606
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto4.default.randomBytes(3).toString("hex")}`;
|
|
27781
26607
|
const toml = buildFrpcToml({
|
|
27782
26608
|
serverAddr: t.frpsHost,
|
|
27783
26609
|
serverPort: t.frpsPort,
|
|
@@ -27787,12 +26613,12 @@ var TunnelManager = class {
|
|
|
27787
26613
|
localPort,
|
|
27788
26614
|
logLevel: "info"
|
|
27789
26615
|
});
|
|
27790
|
-
await
|
|
26616
|
+
await import_node_fs17.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
27791
26617
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
27792
26618
|
stdio: ["ignore", "pipe", "pipe"]
|
|
27793
26619
|
});
|
|
27794
|
-
const logFilePath =
|
|
27795
|
-
const logStream =
|
|
26620
|
+
const logFilePath = import_node_path18.default.join(this.deps.dataDir, "frpc.log");
|
|
26621
|
+
const logStream = import_node_fs17.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
27796
26622
|
logStream.on("error", () => {
|
|
27797
26623
|
});
|
|
27798
26624
|
const tee = (chunk) => {
|
|
@@ -27875,22 +26701,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
27875
26701
|
|
|
27876
26702
|
// src/tunnel/device-key.ts
|
|
27877
26703
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
27878
|
-
var
|
|
26704
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27879
26705
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
27880
26706
|
function deriveStableDeviceKey(opts = {}) {
|
|
27881
26707
|
const hostname = opts.hostname ?? import_node_os10.default.hostname();
|
|
27882
26708
|
const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
|
|
27883
26709
|
const input = `${hostname}::${uid}`;
|
|
27884
|
-
return
|
|
26710
|
+
return import_node_crypto5.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
27885
26711
|
}
|
|
27886
26712
|
|
|
27887
26713
|
// src/auth-store.ts
|
|
27888
|
-
var
|
|
27889
|
-
var
|
|
27890
|
-
var
|
|
26714
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
26715
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
26716
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
27891
26717
|
var AUTH_FILE_NAME = "auth.json";
|
|
27892
26718
|
function authFilePath(dataDir) {
|
|
27893
|
-
return
|
|
26719
|
+
return import_node_path19.default.join(dataDir, AUTH_FILE_NAME);
|
|
27894
26720
|
}
|
|
27895
26721
|
function loadOrCreateAuthToken(opts) {
|
|
27896
26722
|
const file = authFilePath(opts.dataDir);
|
|
@@ -27902,11 +26728,11 @@ function loadOrCreateAuthToken(opts) {
|
|
|
27902
26728
|
return token;
|
|
27903
26729
|
}
|
|
27904
26730
|
function defaultGenerate() {
|
|
27905
|
-
return
|
|
26731
|
+
return import_node_crypto6.default.randomBytes(32).toString("base64url");
|
|
27906
26732
|
}
|
|
27907
26733
|
function readAuthFile(file) {
|
|
27908
26734
|
try {
|
|
27909
|
-
const raw =
|
|
26735
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
27910
26736
|
const parsed = JSON.parse(raw);
|
|
27911
26737
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
27912
26738
|
return {
|
|
@@ -27922,25 +26748,25 @@ function readAuthFile(file) {
|
|
|
27922
26748
|
}
|
|
27923
26749
|
}
|
|
27924
26750
|
function writeAuthFile(file, content) {
|
|
27925
|
-
|
|
27926
|
-
|
|
26751
|
+
import_node_fs18.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
|
|
26752
|
+
import_node_fs18.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
27927
26753
|
try {
|
|
27928
|
-
|
|
26754
|
+
import_node_fs18.default.chmodSync(file, 384);
|
|
27929
26755
|
} catch {
|
|
27930
26756
|
}
|
|
27931
26757
|
}
|
|
27932
26758
|
|
|
27933
26759
|
// src/owner-profile.ts
|
|
27934
|
-
var
|
|
26760
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
27935
26761
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
27936
|
-
var
|
|
26762
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
27937
26763
|
var PROFILE_FILENAME = "profile.json";
|
|
27938
26764
|
function loadOwnerDisplayName(dataDir) {
|
|
27939
26765
|
const fallback = import_node_os11.default.userInfo().username;
|
|
27940
|
-
const profilePath =
|
|
26766
|
+
const profilePath = import_node_path20.default.join(dataDir, PROFILE_FILENAME);
|
|
27941
26767
|
let raw;
|
|
27942
26768
|
try {
|
|
27943
|
-
raw =
|
|
26769
|
+
raw = import_node_fs19.default.readFileSync(profilePath, "utf8");
|
|
27944
26770
|
} catch {
|
|
27945
26771
|
return fallback;
|
|
27946
26772
|
}
|
|
@@ -27969,12 +26795,12 @@ init_protocol();
|
|
|
27969
26795
|
init_protocol();
|
|
27970
26796
|
|
|
27971
26797
|
// src/session/fork.ts
|
|
27972
|
-
var
|
|
26798
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
27973
26799
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
27974
|
-
var
|
|
26800
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
27975
26801
|
init_claude_history();
|
|
27976
26802
|
function readJsonlEntries(file) {
|
|
27977
|
-
const raw =
|
|
26803
|
+
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27978
26804
|
const out = [];
|
|
27979
26805
|
for (const line of raw.split("\n")) {
|
|
27980
26806
|
const t = line.trim();
|
|
@@ -27987,10 +26813,10 @@ function readJsonlEntries(file) {
|
|
|
27987
26813
|
return out;
|
|
27988
26814
|
}
|
|
27989
26815
|
function forkSession(input) {
|
|
27990
|
-
const baseDir = input.baseDir ??
|
|
27991
|
-
const projectDir =
|
|
27992
|
-
const sourceFile =
|
|
27993
|
-
if (!
|
|
26816
|
+
const baseDir = input.baseDir ?? import_node_path21.default.join(import_node_os12.default.homedir(), ".claude");
|
|
26817
|
+
const projectDir = import_node_path21.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
26818
|
+
const sourceFile = import_node_path21.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
26819
|
+
if (!import_node_fs20.default.existsSync(sourceFile)) {
|
|
27994
26820
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
27995
26821
|
}
|
|
27996
26822
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -28020,9 +26846,9 @@ function forkSession(input) {
|
|
|
28020
26846
|
}
|
|
28021
26847
|
forkedLines.push(JSON.stringify(forked));
|
|
28022
26848
|
}
|
|
28023
|
-
const forkedFilePath =
|
|
28024
|
-
|
|
28025
|
-
|
|
26849
|
+
const forkedFilePath = import_node_path21.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
26850
|
+
import_node_fs20.default.mkdirSync(projectDir, { recursive: true });
|
|
26851
|
+
import_node_fs20.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
28026
26852
|
return { forkedToolSessionId, forkedFilePath };
|
|
28027
26853
|
}
|
|
28028
26854
|
|
|
@@ -28343,9 +27169,9 @@ init_protocol();
|
|
|
28343
27169
|
|
|
28344
27170
|
// src/workspace/git.ts
|
|
28345
27171
|
var import_node_child_process6 = require("child_process");
|
|
28346
|
-
var
|
|
27172
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
28347
27173
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
28348
|
-
var
|
|
27174
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
28349
27175
|
var import_node_util = require("util");
|
|
28350
27176
|
var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
|
|
28351
27177
|
function formatChildProcessError(err) {
|
|
@@ -28360,9 +27186,9 @@ function formatChildProcessError(err) {
|
|
|
28360
27186
|
return e.message ?? "unknown error";
|
|
28361
27187
|
}
|
|
28362
27188
|
function normalizePath(p2) {
|
|
28363
|
-
const resolved =
|
|
27189
|
+
const resolved = import_node_path22.default.resolve(p2);
|
|
28364
27190
|
try {
|
|
28365
|
-
return
|
|
27191
|
+
return import_node_fs21.default.realpathSync(resolved);
|
|
28366
27192
|
} catch {
|
|
28367
27193
|
return resolved;
|
|
28368
27194
|
}
|
|
@@ -28463,13 +27289,13 @@ function flattenToDirName(branch) {
|
|
|
28463
27289
|
}
|
|
28464
27290
|
function encodeClaudeProjectDir(absPath) {
|
|
28465
27291
|
if (!absPath || typeof absPath !== "string") return "";
|
|
28466
|
-
let canonical =
|
|
27292
|
+
let canonical = import_node_path22.default.resolve(absPath);
|
|
28467
27293
|
try {
|
|
28468
|
-
canonical =
|
|
27294
|
+
canonical = import_node_fs21.default.realpathSync(canonical);
|
|
28469
27295
|
} catch {
|
|
28470
27296
|
try {
|
|
28471
|
-
const parent =
|
|
28472
|
-
canonical =
|
|
27297
|
+
const parent = import_node_fs21.default.realpathSync(import_node_path22.default.dirname(canonical));
|
|
27298
|
+
canonical = import_node_path22.default.join(parent, import_node_path22.default.basename(canonical));
|
|
28473
27299
|
} catch {
|
|
28474
27300
|
}
|
|
28475
27301
|
}
|
|
@@ -28493,11 +27319,11 @@ async function createWorktree(input) {
|
|
|
28493
27319
|
if (!isGitRoot) {
|
|
28494
27320
|
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
28495
27321
|
}
|
|
28496
|
-
const parent =
|
|
28497
|
-
if (parent === "/" || parent ===
|
|
27322
|
+
const parent = import_node_path22.default.dirname(import_node_path22.default.resolve(cwd));
|
|
27323
|
+
if (parent === "/" || parent === import_node_path22.default.resolve(cwd)) {
|
|
28498
27324
|
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
28499
27325
|
}
|
|
28500
|
-
const worktreeRoot =
|
|
27326
|
+
const worktreeRoot = import_node_path22.default.join(parent, dirName);
|
|
28501
27327
|
try {
|
|
28502
27328
|
await pexec("git", ["-C", cwd, "fetch", "origin", baseBranch, "--no-tags"], {
|
|
28503
27329
|
timeout: 3e4
|
|
@@ -28516,7 +27342,7 @@ async function createWorktree(input) {
|
|
|
28516
27342
|
const msg = err.message;
|
|
28517
27343
|
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
28518
27344
|
}
|
|
28519
|
-
if (
|
|
27345
|
+
if (import_node_fs21.default.existsSync(worktreeRoot)) {
|
|
28520
27346
|
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
28521
27347
|
}
|
|
28522
27348
|
try {
|
|
@@ -28544,8 +27370,8 @@ async function removeWorktree(input) {
|
|
|
28544
27370
|
);
|
|
28545
27371
|
const gitCommonDir = stdout.trim();
|
|
28546
27372
|
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
28547
|
-
const absGitCommon =
|
|
28548
|
-
repoRoot =
|
|
27373
|
+
const absGitCommon = import_node_path22.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path22.default.resolve(worktreeRoot, gitCommonDir);
|
|
27374
|
+
repoRoot = import_node_path22.default.dirname(absGitCommon);
|
|
28549
27375
|
} catch {
|
|
28550
27376
|
repoRoot = null;
|
|
28551
27377
|
}
|
|
@@ -28557,7 +27383,7 @@ async function removeWorktree(input) {
|
|
|
28557
27383
|
} catch (err) {
|
|
28558
27384
|
const stderr = err.stderr ?? "";
|
|
28559
27385
|
const lower = stderr.toLowerCase();
|
|
28560
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !
|
|
27386
|
+
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs21.default.existsSync(worktreeRoot);
|
|
28561
27387
|
if (!vanished) {
|
|
28562
27388
|
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
28563
27389
|
}
|
|
@@ -28576,10 +27402,10 @@ async function removeWorktree(input) {
|
|
|
28576
27402
|
try {
|
|
28577
27403
|
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
28578
27404
|
if (encoded) {
|
|
28579
|
-
const projectsRoot =
|
|
28580
|
-
const target =
|
|
28581
|
-
if (target.startsWith(projectsRoot +
|
|
28582
|
-
|
|
27405
|
+
const projectsRoot = import_node_path22.default.join(import_node_os13.default.homedir(), ".claude", "projects");
|
|
27406
|
+
const target = import_node_path22.default.resolve(projectsRoot, encoded);
|
|
27407
|
+
if (target.startsWith(projectsRoot + import_node_path22.default.sep) && target !== projectsRoot) {
|
|
27408
|
+
import_node_fs21.default.rmSync(target, { recursive: true, force: true });
|
|
28583
27409
|
}
|
|
28584
27410
|
}
|
|
28585
27411
|
} catch {
|
|
@@ -28658,7 +27484,7 @@ init_protocol();
|
|
|
28658
27484
|
var version = "0.2.6".length > 0 ? "0.2.6" : "dev";
|
|
28659
27485
|
|
|
28660
27486
|
// src/handlers/meta.ts
|
|
28661
|
-
function buildReadyFrame(deps
|
|
27487
|
+
function buildReadyFrame(deps) {
|
|
28662
27488
|
const info = deps.manager.info();
|
|
28663
27489
|
const tools = [];
|
|
28664
27490
|
for (const id of listRegistered()) {
|
|
@@ -28670,14 +27496,6 @@ function buildReadyFrame(deps, client) {
|
|
|
28670
27496
|
}
|
|
28671
27497
|
}
|
|
28672
27498
|
const tunnelUrl = deps.getTunnelUrl ? deps.getTunnelUrl() : null;
|
|
28673
|
-
const fileSharing = {};
|
|
28674
|
-
const httpBaseUrl = deps.getHttpBaseUrl ? deps.getHttpBaseUrl() : null;
|
|
28675
|
-
if (httpBaseUrl) {
|
|
28676
|
-
fileSharing.tokenRole = "owner";
|
|
28677
|
-
fileSharing.isLoopback = isLoopbackAddr(client?.remoteAddress);
|
|
28678
|
-
fileSharing.httpBaseUrl = httpBaseUrl;
|
|
28679
|
-
if (deps.httpToken) fileSharing.httpToken = deps.httpToken;
|
|
28680
|
-
}
|
|
28681
27499
|
return {
|
|
28682
27500
|
version,
|
|
28683
27501
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -28686,8 +27504,7 @@ function buildReadyFrame(deps, client) {
|
|
|
28686
27504
|
tools,
|
|
28687
27505
|
runningSessions: info.runningSessions,
|
|
28688
27506
|
tunnelUrl,
|
|
28689
|
-
mode: deps.mode
|
|
28690
|
-
...fileSharing
|
|
27507
|
+
mode: deps.mode
|
|
28691
27508
|
};
|
|
28692
27509
|
}
|
|
28693
27510
|
function buildMetaHandlers(deps) {
|
|
@@ -28798,200 +27615,6 @@ function buildPersonaHandlers(deps) {
|
|
|
28798
27615
|
};
|
|
28799
27616
|
}
|
|
28800
27617
|
|
|
28801
|
-
// src/handlers/attachment.ts
|
|
28802
|
-
init_protocol();
|
|
28803
|
-
init_protocol();
|
|
28804
|
-
function buildAttachmentHandlers(deps) {
|
|
28805
|
-
const outboxCreate = async (frame) => {
|
|
28806
|
-
const parsed = AttachmentOutboxCreateArgs.safeParse(frame);
|
|
28807
|
-
if (!parsed.success) {
|
|
28808
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28809
|
-
}
|
|
28810
|
-
const args = parsed.data;
|
|
28811
|
-
if (args.personaId && args.sessionId || !args.personaId && !args.sessionId) {
|
|
28812
|
-
throw new ClawdError(
|
|
28813
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
28814
|
-
"outboxCreate requires exactly one of personaId / sessionId"
|
|
28815
|
-
);
|
|
28816
|
-
}
|
|
28817
|
-
const scopeRef = deps.getPersonaScopeForRequest(args);
|
|
28818
|
-
if (!scopeRef) {
|
|
28819
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "invalid scope");
|
|
28820
|
-
}
|
|
28821
|
-
const name = args.absPath.split("/").pop() || args.absPath;
|
|
28822
|
-
const entry = deps.outboxStore.mint({
|
|
28823
|
-
scopeRef,
|
|
28824
|
-
absPath: args.absPath,
|
|
28825
|
-
name,
|
|
28826
|
-
ttlSeconds: args.ttlSeconds,
|
|
28827
|
-
oneShot: args.oneShot,
|
|
28828
|
-
scope: args.scope
|
|
28829
|
-
});
|
|
28830
|
-
const httpBaseUrl = deps.getHttpBaseUrl();
|
|
28831
|
-
const urlPath = scopeRef.kind === "persona" ? `/persona/${encodeURIComponent(scopeRef.personaId)}/outbox/${entry.capToken}` : `/outbox/${entry.capToken}`;
|
|
28832
|
-
const url = httpBaseUrl ? `${httpBaseUrl}${urlPath}` : urlPath;
|
|
28833
|
-
return {
|
|
28834
|
-
response: {
|
|
28835
|
-
type: "attachment.outboxCreate",
|
|
28836
|
-
capToken: entry.capToken,
|
|
28837
|
-
url,
|
|
28838
|
-
expiresAt: entry.expiresAt
|
|
28839
|
-
}
|
|
28840
|
-
};
|
|
28841
|
-
};
|
|
28842
|
-
const outboxRevoke = async (frame) => {
|
|
28843
|
-
const parsed = AttachmentOutboxRevokeArgs.safeParse(frame);
|
|
28844
|
-
if (!parsed.success) {
|
|
28845
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28846
|
-
}
|
|
28847
|
-
const ok = deps.outboxStore.revoke(parsed.data.capToken);
|
|
28848
|
-
if (!ok) {
|
|
28849
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `capToken not found / already revoked`);
|
|
28850
|
-
}
|
|
28851
|
-
return { response: { type: "attachment.outboxRevoke", revoked: true } };
|
|
28852
|
-
};
|
|
28853
|
-
const outboxList = async (frame) => {
|
|
28854
|
-
const parsed = AttachmentOutboxListArgs.safeParse(frame);
|
|
28855
|
-
if (!parsed.success) {
|
|
28856
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28857
|
-
}
|
|
28858
|
-
const entries = parsed.data.personaId ? deps.outboxStore.listByPersona(parsed.data.personaId) : deps.outboxStore.list();
|
|
28859
|
-
return {
|
|
28860
|
-
response: { type: "attachment.outboxList", entries }
|
|
28861
|
-
};
|
|
28862
|
-
};
|
|
28863
|
-
const mountAdd = async (frame) => {
|
|
28864
|
-
if (!deps.mountStore) {
|
|
28865
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28866
|
-
}
|
|
28867
|
-
const parsed = AttachmentMountAddArgs.safeParse(frame);
|
|
28868
|
-
if (!parsed.success) {
|
|
28869
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28870
|
-
}
|
|
28871
|
-
const args = parsed.data;
|
|
28872
|
-
try {
|
|
28873
|
-
const entry = deps.mountStore.add(args.personaId, {
|
|
28874
|
-
absPath: args.absPath,
|
|
28875
|
-
mode: args.mode
|
|
28876
|
-
});
|
|
28877
|
-
return {
|
|
28878
|
-
response: {
|
|
28879
|
-
type: "attachment.mountAdd",
|
|
28880
|
-
entry,
|
|
28881
|
-
sandboxRestartNeeded: true
|
|
28882
|
-
}
|
|
28883
|
-
};
|
|
28884
|
-
} catch (err) {
|
|
28885
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, err.message);
|
|
28886
|
-
}
|
|
28887
|
-
};
|
|
28888
|
-
const mountRemove = async (frame) => {
|
|
28889
|
-
if (!deps.mountStore) {
|
|
28890
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28891
|
-
}
|
|
28892
|
-
const parsed = AttachmentMountRemoveArgs.safeParse(frame);
|
|
28893
|
-
if (!parsed.success) {
|
|
28894
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28895
|
-
}
|
|
28896
|
-
const ok = deps.mountStore.remove(parsed.data.personaId, parsed.data.basename);
|
|
28897
|
-
if (!ok) {
|
|
28898
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "mount not found");
|
|
28899
|
-
}
|
|
28900
|
-
return { response: { type: "attachment.mountRemove", removed: true } };
|
|
28901
|
-
};
|
|
28902
|
-
const mountList = async (frame) => {
|
|
28903
|
-
if (!deps.mountStore) {
|
|
28904
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28905
|
-
}
|
|
28906
|
-
const parsed = AttachmentMountListArgs.safeParse(frame);
|
|
28907
|
-
if (!parsed.success) {
|
|
28908
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28909
|
-
}
|
|
28910
|
-
const entries = deps.mountStore.list(parsed.data.personaId);
|
|
28911
|
-
return { response: { type: "attachment.mountList", entries } };
|
|
28912
|
-
};
|
|
28913
|
-
const groupAdd = async (frame) => {
|
|
28914
|
-
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28915
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28916
|
-
}
|
|
28917
|
-
const parsed = AttachmentGroupAddArgs.safeParse(frame);
|
|
28918
|
-
if (!parsed.success) {
|
|
28919
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28920
|
-
}
|
|
28921
|
-
const args = parsed.data;
|
|
28922
|
-
const scope = deps.getSessionScope(args.sessionId);
|
|
28923
|
-
if (!scope) {
|
|
28924
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
|
|
28925
|
-
}
|
|
28926
|
-
const size = 0;
|
|
28927
|
-
const entry = deps.groupFileStore.upsert(scope, args.sessionId, {
|
|
28928
|
-
relPath: args.relPath,
|
|
28929
|
-
from: "owner",
|
|
28930
|
-
label: args.label,
|
|
28931
|
-
size,
|
|
28932
|
-
mime: lookupMime(args.relPath)
|
|
28933
|
-
});
|
|
28934
|
-
return { response: { type: "attachment.groupAdd", entry } };
|
|
28935
|
-
};
|
|
28936
|
-
const groupRemove = async (frame) => {
|
|
28937
|
-
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28938
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28939
|
-
}
|
|
28940
|
-
const parsed = AttachmentGroupRemoveArgs.safeParse(frame);
|
|
28941
|
-
if (!parsed.success) {
|
|
28942
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28943
|
-
}
|
|
28944
|
-
const scope = deps.getSessionScope(parsed.data.sessionId);
|
|
28945
|
-
if (!scope) {
|
|
28946
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "session not found");
|
|
28947
|
-
}
|
|
28948
|
-
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
28949
|
-
const target = entries.find((e) => e.relPath === parsed.data.relPath);
|
|
28950
|
-
if (target?.from === "owner") {
|
|
28951
|
-
deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
|
|
28952
|
-
} else {
|
|
28953
|
-
deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
|
|
28954
|
-
}
|
|
28955
|
-
return { response: { type: "attachment.groupRemove", removed: true } };
|
|
28956
|
-
};
|
|
28957
|
-
const groupList = async (frame) => {
|
|
28958
|
-
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28959
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28960
|
-
}
|
|
28961
|
-
const parsed = AttachmentGroupListArgs.safeParse(frame);
|
|
28962
|
-
if (!parsed.success) {
|
|
28963
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28964
|
-
}
|
|
28965
|
-
const scope = deps.getSessionScope(parsed.data.sessionId);
|
|
28966
|
-
if (!scope) return { response: { type: "attachment.groupList", entries: [] } };
|
|
28967
|
-
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
28968
|
-
return { response: { type: "attachment.groupList", entries } };
|
|
28969
|
-
};
|
|
28970
|
-
const groupListPersona = async (frame) => {
|
|
28971
|
-
if (!deps.groupFileStore) {
|
|
28972
|
-
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28973
|
-
}
|
|
28974
|
-
const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
|
|
28975
|
-
if (!parsed.success) {
|
|
28976
|
-
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28977
|
-
}
|
|
28978
|
-
const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
|
|
28979
|
-
return { response: { type: "attachment.groupListPersona", perSession } };
|
|
28980
|
-
};
|
|
28981
|
-
return {
|
|
28982
|
-
"attachment.outboxCreate": outboxCreate,
|
|
28983
|
-
"attachment.outboxRevoke": outboxRevoke,
|
|
28984
|
-
"attachment.outboxList": outboxList,
|
|
28985
|
-
"attachment.mountAdd": mountAdd,
|
|
28986
|
-
"attachment.mountRemove": mountRemove,
|
|
28987
|
-
"attachment.mountList": mountList,
|
|
28988
|
-
"attachment.groupAdd": groupAdd,
|
|
28989
|
-
"attachment.groupRemove": groupRemove,
|
|
28990
|
-
"attachment.groupList": groupList,
|
|
28991
|
-
"attachment.groupListPersona": groupListPersona
|
|
28992
|
-
};
|
|
28993
|
-
}
|
|
28994
|
-
|
|
28995
27618
|
// src/handlers/index.ts
|
|
28996
27619
|
function buildMethodHandlers(deps) {
|
|
28997
27620
|
return {
|
|
@@ -29007,8 +27630,7 @@ function buildMethodHandlers(deps) {
|
|
|
29007
27630
|
personaRegistry: deps.personaRegistry,
|
|
29008
27631
|
sessionManager: deps.manager,
|
|
29009
27632
|
personaBoundHandler: deps.personaBoundHandler
|
|
29010
|
-
})
|
|
29011
|
-
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
27633
|
+
})
|
|
29012
27634
|
};
|
|
29013
27635
|
}
|
|
29014
27636
|
|
|
@@ -29016,7 +27638,7 @@ function buildMethodHandlers(deps) {
|
|
|
29016
27638
|
async function startDaemon(config) {
|
|
29017
27639
|
const logger = createLogger({
|
|
29018
27640
|
level: config.logLevel,
|
|
29019
|
-
file:
|
|
27641
|
+
file: import_node_path23.default.join(config.dataDir, "clawd.log")
|
|
29020
27642
|
});
|
|
29021
27643
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
29022
27644
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -29048,7 +27670,7 @@ async function startDaemon(config) {
|
|
|
29048
27670
|
const agents = new AgentsScanner();
|
|
29049
27671
|
const history = new ClaudeHistoryReader();
|
|
29050
27672
|
let transport = null;
|
|
29051
|
-
const personaStore = new PersonaStore(
|
|
27673
|
+
const personaStore = new PersonaStore(import_node_path23.default.join(config.dataDir, "personas"));
|
|
29052
27674
|
const defaultsRoot = findDefaultsRoot();
|
|
29053
27675
|
if (defaultsRoot) {
|
|
29054
27676
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -29056,18 +27678,13 @@ async function startDaemon(config) {
|
|
|
29056
27678
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
29057
27679
|
}
|
|
29058
27680
|
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
29059
|
-
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
29060
|
-
const outboxStore = new OutboxStore({ dataDir: config.dataDir, logger });
|
|
29061
|
-
const mountStore = new MountStore({
|
|
29062
|
-
personaDirPath: (pid) => personaStore.personaDirPath(pid)
|
|
29063
|
-
});
|
|
29064
27681
|
const manager = new SessionManager({
|
|
29065
27682
|
store,
|
|
29066
27683
|
logger,
|
|
29067
27684
|
getAdapter,
|
|
29068
27685
|
historyReader: history,
|
|
29069
27686
|
dataDir: config.dataDir,
|
|
29070
|
-
personaRoot:
|
|
27687
|
+
personaRoot: import_node_path23.default.join(config.dataDir, "personas"),
|
|
29071
27688
|
personaStore,
|
|
29072
27689
|
ownerDisplayName,
|
|
29073
27690
|
mode: config.mode,
|
|
@@ -29084,38 +27701,6 @@ async function startDaemon(config) {
|
|
|
29084
27701
|
return;
|
|
29085
27702
|
}
|
|
29086
27703
|
transport?.broadcastToSession(sid, frame);
|
|
29087
|
-
},
|
|
29088
|
-
// file-sharing (spec §6 PR 3):runner 检测到成功 file-edit tool_result 时,
|
|
29089
|
-
// 闭包 stat + mime 写入群清单。stat 失败不阻塞主流程(log warn + 跳过本条),
|
|
29090
|
-
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
29091
|
-
attachmentGroup: {
|
|
29092
|
-
onFileEdit: (input) => {
|
|
29093
|
-
const absPath = import_node_path29.default.isAbsolute(input.relPath) ? input.relPath : import_node_path29.default.join(input.cwd, input.relPath);
|
|
29094
|
-
let size = 0;
|
|
29095
|
-
try {
|
|
29096
|
-
size = import_node_fs27.default.statSync(absPath).size;
|
|
29097
|
-
} catch (err) {
|
|
29098
|
-
logger.warn("attachment.onFileEdit stat failed", {
|
|
29099
|
-
sessionId: input.sessionId,
|
|
29100
|
-
absPath,
|
|
29101
|
-
err: err.message
|
|
29102
|
-
});
|
|
29103
|
-
}
|
|
29104
|
-
try {
|
|
29105
|
-
groupFileStore.upsert(input.scope, input.sessionId, {
|
|
29106
|
-
relPath: input.relPath,
|
|
29107
|
-
from: "agent",
|
|
29108
|
-
size,
|
|
29109
|
-
mime: lookupMime(input.relPath)
|
|
29110
|
-
});
|
|
29111
|
-
} catch (err) {
|
|
29112
|
-
logger.warn("attachment.onFileEdit upsert failed", {
|
|
29113
|
-
sessionId: input.sessionId,
|
|
29114
|
-
relPath: input.relPath,
|
|
29115
|
-
err: err.message
|
|
29116
|
-
});
|
|
29117
|
-
}
|
|
29118
|
-
}
|
|
29119
27704
|
}
|
|
29120
27705
|
});
|
|
29121
27706
|
const observer = new SessionObserver({
|
|
@@ -29161,12 +27746,6 @@ async function startDaemon(config) {
|
|
|
29161
27746
|
sessionManager: manager
|
|
29162
27747
|
});
|
|
29163
27748
|
let currentTunnelUrl = null;
|
|
29164
|
-
const getHttpBaseUrl = () => {
|
|
29165
|
-
if (currentTunnelUrl) {
|
|
29166
|
-
return currentTunnelUrl.replace(/^wss:/i, "https:").replace(/^ws:/i, "http:");
|
|
29167
|
-
}
|
|
29168
|
-
return `http://${config.host}:${config.port}`;
|
|
29169
|
-
};
|
|
29170
27749
|
const personaBoundHandler = new PersonaBoundHandler({
|
|
29171
27750
|
registry: personaRegistry,
|
|
29172
27751
|
personaManager,
|
|
@@ -29192,73 +27771,22 @@ async function startDaemon(config) {
|
|
|
29192
27771
|
getTunnelUrl: () => currentTunnelUrl,
|
|
29193
27772
|
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
29194
27773
|
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
29195
|
-
mode: config.mode
|
|
29196
|
-
// file-sharing (spec §8):ready / info 帧把 httpBaseUrl + httpToken 下发给 UI。
|
|
29197
|
-
// PR 2 阶段 httpToken 复用 owner WS token;noAuth 模式下为 null(UI 看到无 httpToken
|
|
29198
|
-
// 时禁用文件 GET/POST,保持 1.0 行为兼容)。
|
|
29199
|
-
getHttpBaseUrl,
|
|
29200
|
-
httpToken: resolvedAuthToken,
|
|
29201
|
-
// file-sharing attachment.* RPC(spec §5 PR 6+)。getPersonaScopeForRequest 把 RPC
|
|
29202
|
-
// args 里的 personaId/sessionId 翻译成 scopeRef 给 OutboxStore;mountAdd/groupAdd
|
|
29203
|
-
// 后续 PR 复用同一份装配。
|
|
29204
|
-
attachment: {
|
|
29205
|
-
outboxStore,
|
|
29206
|
-
mountStore,
|
|
29207
|
-
groupFileStore,
|
|
29208
|
-
personaDirPath: (pid) => personaStore.personaDirPath(pid),
|
|
29209
|
-
getHttpBaseUrl,
|
|
29210
|
-
getPersonaScopeForRequest: (args) => {
|
|
29211
|
-
if (args.personaId) return { kind: "persona", personaId: args.personaId };
|
|
29212
|
-
if (args.sessionId) return { kind: "session", sessionId: args.sessionId };
|
|
29213
|
-
return null;
|
|
29214
|
-
},
|
|
29215
|
-
// group RPC:根据 sessionId 反查 scope;owner-mode persona session 走
|
|
29216
|
-
// 'persona/<pid>/owner',default 走 'default'。PR 9 UI Drawer 用。
|
|
29217
|
-
getSessionScope: (sessionId) => {
|
|
29218
|
-
const file = store.read(sessionId);
|
|
29219
|
-
if (!file) return null;
|
|
29220
|
-
if (file.ownerPersonaId) {
|
|
29221
|
-
return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
|
|
29222
|
-
}
|
|
29223
|
-
return { kind: "default" };
|
|
29224
|
-
}
|
|
29225
|
-
}
|
|
29226
|
-
});
|
|
29227
|
-
const authResolver = new AuthContextResolver({
|
|
29228
|
-
ownerToken: resolvedAuthToken,
|
|
29229
|
-
personaRegistry
|
|
29230
|
-
});
|
|
29231
|
-
const httpRouter = createHttpRouter({
|
|
29232
|
-
authResolver,
|
|
29233
|
-
daemonVersion: version,
|
|
29234
|
-
logger,
|
|
29235
|
-
personaStore,
|
|
29236
|
-
groupFileStore,
|
|
29237
|
-
sessionStore: store,
|
|
29238
|
-
outboxStore
|
|
27774
|
+
mode: config.mode
|
|
29239
27775
|
});
|
|
29240
27776
|
wsServer = new LocalWsServer({
|
|
29241
27777
|
host: config.host,
|
|
29242
27778
|
port: config.port,
|
|
29243
27779
|
logger,
|
|
29244
|
-
readyFrameBuilder: (
|
|
29245
|
-
|
|
29246
|
-
|
|
29247
|
-
|
|
29248
|
-
|
|
29249
|
-
|
|
29250
|
-
|
|
29251
|
-
// file-sharing 字段:httpBaseUrl 跟 tunnel 状态走;httpToken 复用 owner WS token
|
|
29252
|
-
getHttpBaseUrl,
|
|
29253
|
-
httpToken: resolvedAuthToken
|
|
29254
|
-
},
|
|
29255
|
-
ctx
|
|
29256
|
-
),
|
|
27780
|
+
readyFrameBuilder: () => buildReadyFrame({
|
|
27781
|
+
manager,
|
|
27782
|
+
getAdapter,
|
|
27783
|
+
getTunnelUrl: () => currentTunnelUrl,
|
|
27784
|
+
// ready 帧 mode = daemon CC spawn 模式('sdk' | 'tui');UI 用它挂 XtermPanel
|
|
27785
|
+
mode: config.mode
|
|
27786
|
+
}),
|
|
29257
27787
|
protocolVersion: PROTOCOL_VERSION,
|
|
29258
27788
|
authGate: authGate ?? void 0,
|
|
29259
27789
|
personaBoundHandler,
|
|
29260
|
-
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29261
|
-
httpRequestHandler: httpRouter,
|
|
29262
27790
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
29263
27791
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
29264
27792
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -29374,8 +27902,8 @@ async function startDaemon(config) {
|
|
|
29374
27902
|
const lines = [
|
|
29375
27903
|
`Tunnel: ${r.url}`,
|
|
29376
27904
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
29377
|
-
`Frpc config: ${
|
|
29378
|
-
`Frpc log: ${
|
|
27905
|
+
`Frpc config: ${import_node_path23.default.join(config.dataDir, "frpc.toml")}`,
|
|
27906
|
+
`Frpc log: ${import_node_path23.default.join(config.dataDir, "frpc.log")}`
|
|
29379
27907
|
];
|
|
29380
27908
|
const width = Math.max(...lines.map((l) => l.length));
|
|
29381
27909
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -29388,8 +27916,8 @@ ${bar}
|
|
|
29388
27916
|
|
|
29389
27917
|
`);
|
|
29390
27918
|
try {
|
|
29391
|
-
const connectPath =
|
|
29392
|
-
|
|
27919
|
+
const connectPath = import_node_path23.default.join(config.dataDir, "connect.txt");
|
|
27920
|
+
import_node_fs22.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
29393
27921
|
} catch {
|
|
29394
27922
|
}
|
|
29395
27923
|
} catch (err) {
|