@clawos-dev/clawd 0.2.64-beta.105.5ecd0a6 → 0.2.64-beta.106.42f5a70
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 +1495 -270
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -110,6 +110,22 @@ 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",
|
|
113
129
|
"info",
|
|
114
130
|
"ping"
|
|
115
131
|
];
|
|
@@ -601,8 +617,8 @@ var init_parseUtil = __esm({
|
|
|
601
617
|
init_errors2();
|
|
602
618
|
init_en();
|
|
603
619
|
makeIssue = (params) => {
|
|
604
|
-
const { data, path:
|
|
605
|
-
const fullPath = [...
|
|
620
|
+
const { data, path: path33, errorMaps, issueData } = params;
|
|
621
|
+
const fullPath = [...path33, ...issueData.path || []];
|
|
606
622
|
const fullIssue = {
|
|
607
623
|
...issueData,
|
|
608
624
|
path: fullPath
|
|
@@ -913,11 +929,11 @@ var init_types = __esm({
|
|
|
913
929
|
init_parseUtil();
|
|
914
930
|
init_util();
|
|
915
931
|
ParseInputLazyPath = class {
|
|
916
|
-
constructor(parent, value,
|
|
932
|
+
constructor(parent, value, path33, key) {
|
|
917
933
|
this._cachedPath = [];
|
|
918
934
|
this.parent = parent;
|
|
919
935
|
this.data = value;
|
|
920
|
-
this._path =
|
|
936
|
+
this._path = path33;
|
|
921
937
|
this._key = key;
|
|
922
938
|
}
|
|
923
939
|
get path() {
|
|
@@ -4300,6 +4316,149 @@ var init_zod = __esm({
|
|
|
4300
4316
|
}
|
|
4301
4317
|
});
|
|
4302
4318
|
|
|
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", "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
|
+
/** owner 手动加群时的可选备注 */
|
|
4335
|
+
label: external_exports.string().optional(),
|
|
4336
|
+
/** 文件字节数(stat 时拍) */
|
|
4337
|
+
size: external_exports.number().int().nonnegative(),
|
|
4338
|
+
mime: external_exports.string().min(1),
|
|
4339
|
+
/** 入群时间戳(ms) */
|
|
4340
|
+
addedAt: external_exports.number().int().nonnegative(),
|
|
4341
|
+
/** 最近一次 agent Write/Edit 时间戳(agent 反复改同 path 时更新) */
|
|
4342
|
+
lastEditedAt: external_exports.number().int().nonnegative().optional(),
|
|
4343
|
+
/** agent rm / 文件不见了时打标;UI 灰显,不能再 share */
|
|
4344
|
+
stale: external_exports.boolean().optional()
|
|
4345
|
+
});
|
|
4346
|
+
MountEntrySchema = external_exports.object({
|
|
4347
|
+
/** personaDir 下的 basename(link 目标 / copy 目的名) */
|
|
4348
|
+
basename: external_exports.string().min(1),
|
|
4349
|
+
/** 原始绝对路径(link 模式下 realpath 后用于 sandbox.allowRead 派生) */
|
|
4350
|
+
absPath: external_exports.string().min(1),
|
|
4351
|
+
mode: external_exports.enum(MOUNT_MODES)
|
|
4352
|
+
});
|
|
4353
|
+
OutboxScopeSchema = external_exports.discriminatedUnion("kind", [
|
|
4354
|
+
external_exports.object({ kind: external_exports.literal("public") }),
|
|
4355
|
+
external_exports.object({ kind: external_exports.literal("personal"), personalId: external_exports.string().min(1) })
|
|
4356
|
+
]);
|
|
4357
|
+
OutboxCapEntrySchema = external_exports.object({
|
|
4358
|
+
capToken: external_exports.string().min(1),
|
|
4359
|
+
/** 归属:persona 域 cap 带 personaId;direct 会话 cap 带 sessionId */
|
|
4360
|
+
scopeRef: external_exports.union([
|
|
4361
|
+
external_exports.object({ kind: external_exports.literal("persona"), personaId: external_exports.string().min(1) }),
|
|
4362
|
+
external_exports.object({ kind: external_exports.literal("session"), sessionId: external_exports.string().min(1) })
|
|
4363
|
+
]),
|
|
4364
|
+
absPath: external_exports.string().min(1),
|
|
4365
|
+
/** display name(UI 列表显示 + Share dialog 标题),通常等于 basename */
|
|
4366
|
+
name: external_exports.string().min(1),
|
|
4367
|
+
expiresAt: external_exports.number().int().nonnegative().nullable(),
|
|
4368
|
+
oneShot: external_exports.boolean(),
|
|
4369
|
+
scope: OutboxScopeSchema,
|
|
4370
|
+
hits: external_exports.number().int().nonnegative(),
|
|
4371
|
+
revoked: external_exports.boolean().optional(),
|
|
4372
|
+
createdAt: external_exports.number().int().nonnegative()
|
|
4373
|
+
});
|
|
4374
|
+
AttachmentOutboxCreateArgs = external_exports.object({
|
|
4375
|
+
/** persona 域:传 personaId;direct 会话:传 sessionId(二选一) */
|
|
4376
|
+
personaId: external_exports.string().min(1).optional(),
|
|
4377
|
+
sessionId: external_exports.string().min(1).optional(),
|
|
4378
|
+
absPath: external_exports.string().min(1),
|
|
4379
|
+
/** TTL 秒数;缺省 24h;'never' 走 null */
|
|
4380
|
+
ttlSeconds: external_exports.number().int().positive().nullable().optional(),
|
|
4381
|
+
oneShot: external_exports.boolean().optional(),
|
|
4382
|
+
scope: OutboxScopeSchema.optional()
|
|
4383
|
+
});
|
|
4384
|
+
AttachmentOutboxCreateResponseSchema = external_exports.object({
|
|
4385
|
+
capToken: external_exports.string().min(1),
|
|
4386
|
+
/** 完整 URL(含 httpBaseUrl 前缀),UI 直接复制到剪贴板 */
|
|
4387
|
+
url: external_exports.string().min(1),
|
|
4388
|
+
expiresAt: external_exports.number().int().nonnegative().nullable()
|
|
4389
|
+
});
|
|
4390
|
+
AttachmentOutboxRevokeArgs = external_exports.object({
|
|
4391
|
+
capToken: external_exports.string().min(1)
|
|
4392
|
+
});
|
|
4393
|
+
AttachmentOutboxRevokeResponseSchema = external_exports.object({
|
|
4394
|
+
revoked: external_exports.literal(true)
|
|
4395
|
+
});
|
|
4396
|
+
AttachmentOutboxListArgs = external_exports.object({
|
|
4397
|
+
/** 缺省 = 全部(含 direct 会话);传 personaId 过滤到该 persona */
|
|
4398
|
+
personaId: external_exports.string().min(1).optional()
|
|
4399
|
+
});
|
|
4400
|
+
AttachmentOutboxListResponseSchema = external_exports.object({
|
|
4401
|
+
entries: external_exports.array(OutboxCapEntrySchema)
|
|
4402
|
+
});
|
|
4403
|
+
AttachmentMountAddArgs = external_exports.object({
|
|
4404
|
+
personaId: external_exports.string().min(1),
|
|
4405
|
+
absPath: external_exports.string().min(1),
|
|
4406
|
+
mode: external_exports.enum(MOUNT_MODES)
|
|
4407
|
+
});
|
|
4408
|
+
AttachmentMountAddResponseSchema = external_exports.object({
|
|
4409
|
+
entry: MountEntrySchema,
|
|
4410
|
+
/** sandbox.allowRead 派生后是否需要重启 session 才能让 CC 子进程跟 symlink */
|
|
4411
|
+
sandboxRestartNeeded: external_exports.boolean()
|
|
4412
|
+
});
|
|
4413
|
+
AttachmentMountRemoveArgs = external_exports.object({
|
|
4414
|
+
personaId: external_exports.string().min(1),
|
|
4415
|
+
basename: external_exports.string().min(1)
|
|
4416
|
+
});
|
|
4417
|
+
AttachmentMountRemoveResponseSchema = external_exports.object({
|
|
4418
|
+
removed: external_exports.literal(true)
|
|
4419
|
+
});
|
|
4420
|
+
AttachmentMountListArgs = external_exports.object({
|
|
4421
|
+
personaId: external_exports.string().min(1)
|
|
4422
|
+
});
|
|
4423
|
+
AttachmentMountListResponseSchema = external_exports.object({
|
|
4424
|
+
entries: external_exports.array(MountEntrySchema)
|
|
4425
|
+
});
|
|
4426
|
+
AttachmentGroupAddArgs = external_exports.object({
|
|
4427
|
+
sessionId: external_exports.string().min(1),
|
|
4428
|
+
relPath: external_exports.string().min(1),
|
|
4429
|
+
/** owner 手动加群时可选备注;agent 自动入清单不走此 RPC */
|
|
4430
|
+
label: external_exports.string().optional()
|
|
4431
|
+
});
|
|
4432
|
+
AttachmentGroupAddResponseSchema = external_exports.object({
|
|
4433
|
+
entry: GroupFileEntrySchema
|
|
4434
|
+
});
|
|
4435
|
+
AttachmentGroupRemoveArgs = external_exports.object({
|
|
4436
|
+
sessionId: external_exports.string().min(1),
|
|
4437
|
+
relPath: external_exports.string().min(1)
|
|
4438
|
+
});
|
|
4439
|
+
AttachmentGroupRemoveResponseSchema = external_exports.object({
|
|
4440
|
+
removed: external_exports.literal(true)
|
|
4441
|
+
});
|
|
4442
|
+
AttachmentGroupListArgs = external_exports.object({
|
|
4443
|
+
sessionId: external_exports.string().min(1)
|
|
4444
|
+
});
|
|
4445
|
+
AttachmentGroupListResponseSchema = external_exports.object({
|
|
4446
|
+
entries: external_exports.array(GroupFileEntrySchema)
|
|
4447
|
+
});
|
|
4448
|
+
AttachmentGroupListPersonaArgs = external_exports.object({
|
|
4449
|
+
personaId: external_exports.string().min(1)
|
|
4450
|
+
});
|
|
4451
|
+
AttachmentGroupListPersonaResponseSchema = external_exports.object({
|
|
4452
|
+
perSession: external_exports.array(
|
|
4453
|
+
external_exports.object({
|
|
4454
|
+
sessionId: external_exports.string().min(1),
|
|
4455
|
+
entries: external_exports.array(GroupFileEntrySchema)
|
|
4456
|
+
})
|
|
4457
|
+
)
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
});
|
|
4461
|
+
|
|
4303
4462
|
// ../protocol/src/persona-schemas.ts
|
|
4304
4463
|
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;
|
|
4305
4464
|
var init_persona_schemas = __esm({
|
|
@@ -4505,6 +4664,7 @@ var init_schemas = __esm({
|
|
|
4505
4664
|
"use strict";
|
|
4506
4665
|
init_zod();
|
|
4507
4666
|
init_events();
|
|
4667
|
+
init_attachment_schemas();
|
|
4508
4668
|
init_persona_schemas();
|
|
4509
4669
|
SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
|
|
4510
4670
|
UsageSchema = external_exports.object({
|
|
@@ -5061,7 +5221,22 @@ var init_schemas = __esm({
|
|
|
5061
5221
|
hostname: external_exports.string(),
|
|
5062
5222
|
os: external_exports.string(),
|
|
5063
5223
|
tools: external_exports.array(external_exports.object({ id: external_exports.string(), available: external_exports.boolean() })),
|
|
5064
|
-
runningSessions: external_exports.array(InfoRunningSessionSchema)
|
|
5224
|
+
runningSessions: external_exports.array(InfoRunningSessionSchema),
|
|
5225
|
+
// ── file-sharing 会话身份回执(spec: superpowers/specs/2026-05-19-clawd-file-sharing-design.md §8) ──
|
|
5226
|
+
// PR 1 阶段标 optional(daemon 还没下发,旧 daemon info 响应不带这五个字段);
|
|
5227
|
+
// PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
|
|
5228
|
+
// 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
|
|
5229
|
+
// 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
|
|
5230
|
+
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP);'personal' = persona 派发的访客 token。来源 TOKEN_ROLES(spec §11 #1 中央真理源) */
|
|
5231
|
+
tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
|
|
5232
|
+
/** tokenRole='personal' 时携带(绑定到具体 persona);owner 不携带 */
|
|
5233
|
+
tokenPersonaId: external_exports.string().min(1).optional(),
|
|
5234
|
+
/** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
|
|
5235
|
+
isLoopback: external_exports.boolean().optional(),
|
|
5236
|
+
/** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
|
|
5237
|
+
httpBaseUrl: external_exports.string().optional(),
|
|
5238
|
+
/** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
|
|
5239
|
+
httpToken: external_exports.string().optional()
|
|
5065
5240
|
});
|
|
5066
5241
|
}
|
|
5067
5242
|
});
|
|
@@ -5093,6 +5268,7 @@ var init_runtime = __esm({
|
|
|
5093
5268
|
init_frames();
|
|
5094
5269
|
init_persona_schemas();
|
|
5095
5270
|
init_persona_mode();
|
|
5271
|
+
init_attachment_schemas();
|
|
5096
5272
|
}
|
|
5097
5273
|
});
|
|
5098
5274
|
|
|
@@ -5367,8 +5543,8 @@ var require_req = __commonJS({
|
|
|
5367
5543
|
if (req.originalUrl) {
|
|
5368
5544
|
_req.url = req.originalUrl;
|
|
5369
5545
|
} else {
|
|
5370
|
-
const
|
|
5371
|
-
_req.url = typeof
|
|
5546
|
+
const path33 = req.path;
|
|
5547
|
+
_req.url = typeof path33 === "string" ? path33 : req.url ? req.url.path || req.url : void 0;
|
|
5372
5548
|
}
|
|
5373
5549
|
if (req.query) {
|
|
5374
5550
|
_req.query = req.query;
|
|
@@ -5533,14 +5709,14 @@ var require_redact = __commonJS({
|
|
|
5533
5709
|
}
|
|
5534
5710
|
return obj;
|
|
5535
5711
|
}
|
|
5536
|
-
function parsePath(
|
|
5712
|
+
function parsePath(path33) {
|
|
5537
5713
|
const parts = [];
|
|
5538
5714
|
let current = "";
|
|
5539
5715
|
let inBrackets = false;
|
|
5540
5716
|
let inQuotes = false;
|
|
5541
5717
|
let quoteChar = "";
|
|
5542
|
-
for (let i = 0; i <
|
|
5543
|
-
const char =
|
|
5718
|
+
for (let i = 0; i < path33.length; i++) {
|
|
5719
|
+
const char = path33[i];
|
|
5544
5720
|
if (!inBrackets && char === ".") {
|
|
5545
5721
|
if (current) {
|
|
5546
5722
|
parts.push(current);
|
|
@@ -5671,10 +5847,10 @@ var require_redact = __commonJS({
|
|
|
5671
5847
|
return current;
|
|
5672
5848
|
}
|
|
5673
5849
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5674
|
-
for (const
|
|
5675
|
-
const parts = parsePath(
|
|
5850
|
+
for (const path33 of paths) {
|
|
5851
|
+
const parts = parsePath(path33);
|
|
5676
5852
|
if (parts.includes("*")) {
|
|
5677
|
-
redactWildcardPath(obj, parts, censor,
|
|
5853
|
+
redactWildcardPath(obj, parts, censor, path33, remove);
|
|
5678
5854
|
} else {
|
|
5679
5855
|
if (remove) {
|
|
5680
5856
|
removeKey(obj, parts);
|
|
@@ -5759,8 +5935,8 @@ var require_redact = __commonJS({
|
|
|
5759
5935
|
}
|
|
5760
5936
|
} else {
|
|
5761
5937
|
if (afterWildcard.includes("*")) {
|
|
5762
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5763
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5938
|
+
const wrappedCensor = typeof censor === "function" ? (value, path33) => {
|
|
5939
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path33];
|
|
5764
5940
|
return censor(value, fullPath);
|
|
5765
5941
|
} : censor;
|
|
5766
5942
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5795,8 +5971,8 @@ var require_redact = __commonJS({
|
|
|
5795
5971
|
return null;
|
|
5796
5972
|
}
|
|
5797
5973
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5798
|
-
for (const
|
|
5799
|
-
const parts = parsePath(
|
|
5974
|
+
for (const path33 of pathsToClone) {
|
|
5975
|
+
const parts = parsePath(path33);
|
|
5800
5976
|
let current = pathStructure;
|
|
5801
5977
|
for (let i = 0; i < parts.length; i++) {
|
|
5802
5978
|
const part = parts[i];
|
|
@@ -5848,24 +6024,24 @@ var require_redact = __commonJS({
|
|
|
5848
6024
|
}
|
|
5849
6025
|
return cloneSelectively(obj, pathStructure);
|
|
5850
6026
|
}
|
|
5851
|
-
function validatePath(
|
|
5852
|
-
if (typeof
|
|
6027
|
+
function validatePath(path33) {
|
|
6028
|
+
if (typeof path33 !== "string") {
|
|
5853
6029
|
throw new Error("Paths must be (non-empty) strings");
|
|
5854
6030
|
}
|
|
5855
|
-
if (
|
|
6031
|
+
if (path33 === "") {
|
|
5856
6032
|
throw new Error("Invalid redaction path ()");
|
|
5857
6033
|
}
|
|
5858
|
-
if (
|
|
5859
|
-
throw new Error(`Invalid redaction path (${
|
|
6034
|
+
if (path33.includes("..")) {
|
|
6035
|
+
throw new Error(`Invalid redaction path (${path33})`);
|
|
5860
6036
|
}
|
|
5861
|
-
if (
|
|
5862
|
-
throw new Error(`Invalid redaction path (${
|
|
6037
|
+
if (path33.includes(",")) {
|
|
6038
|
+
throw new Error(`Invalid redaction path (${path33})`);
|
|
5863
6039
|
}
|
|
5864
6040
|
let bracketCount = 0;
|
|
5865
6041
|
let inQuotes = false;
|
|
5866
6042
|
let quoteChar = "";
|
|
5867
|
-
for (let i = 0; i <
|
|
5868
|
-
const char =
|
|
6043
|
+
for (let i = 0; i < path33.length; i++) {
|
|
6044
|
+
const char = path33[i];
|
|
5869
6045
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
5870
6046
|
if (!inQuotes) {
|
|
5871
6047
|
inQuotes = true;
|
|
@@ -5879,20 +6055,20 @@ var require_redact = __commonJS({
|
|
|
5879
6055
|
} else if (char === "]" && !inQuotes) {
|
|
5880
6056
|
bracketCount--;
|
|
5881
6057
|
if (bracketCount < 0) {
|
|
5882
|
-
throw new Error(`Invalid redaction path (${
|
|
6058
|
+
throw new Error(`Invalid redaction path (${path33})`);
|
|
5883
6059
|
}
|
|
5884
6060
|
}
|
|
5885
6061
|
}
|
|
5886
6062
|
if (bracketCount !== 0) {
|
|
5887
|
-
throw new Error(`Invalid redaction path (${
|
|
6063
|
+
throw new Error(`Invalid redaction path (${path33})`);
|
|
5888
6064
|
}
|
|
5889
6065
|
}
|
|
5890
6066
|
function validatePaths(paths) {
|
|
5891
6067
|
if (!Array.isArray(paths)) {
|
|
5892
6068
|
throw new TypeError("paths must be an array");
|
|
5893
6069
|
}
|
|
5894
|
-
for (const
|
|
5895
|
-
validatePath(
|
|
6070
|
+
for (const path33 of paths) {
|
|
6071
|
+
validatePath(path33);
|
|
5896
6072
|
}
|
|
5897
6073
|
}
|
|
5898
6074
|
function slowRedact(options = {}) {
|
|
@@ -6060,8 +6236,8 @@ var require_redaction = __commonJS({
|
|
|
6060
6236
|
if (shape[k2] === null) {
|
|
6061
6237
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6062
6238
|
} else {
|
|
6063
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6064
|
-
return censor(value, [k2, ...
|
|
6239
|
+
const wrappedCensor = typeof censor === "function" ? (value, path33) => {
|
|
6240
|
+
return censor(value, [k2, ...path33]);
|
|
6065
6241
|
} : censor;
|
|
6066
6242
|
o[k2] = Redact({
|
|
6067
6243
|
paths: shape[k2],
|
|
@@ -6279,10 +6455,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6279
6455
|
var require_sonic_boom = __commonJS({
|
|
6280
6456
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6281
6457
|
"use strict";
|
|
6282
|
-
var
|
|
6458
|
+
var fs30 = require("fs");
|
|
6283
6459
|
var EventEmitter2 = require("events");
|
|
6284
6460
|
var inherits = require("util").inherits;
|
|
6285
|
-
var
|
|
6461
|
+
var path33 = require("path");
|
|
6286
6462
|
var sleep = require_atomic_sleep();
|
|
6287
6463
|
var assert = require("assert");
|
|
6288
6464
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6336,20 +6512,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6336
6512
|
const mode = sonic.mode;
|
|
6337
6513
|
if (sonic.sync) {
|
|
6338
6514
|
try {
|
|
6339
|
-
if (sonic.mkdir)
|
|
6340
|
-
const fd =
|
|
6515
|
+
if (sonic.mkdir) fs30.mkdirSync(path33.dirname(file), { recursive: true });
|
|
6516
|
+
const fd = fs30.openSync(file, flags, mode);
|
|
6341
6517
|
fileOpened(null, fd);
|
|
6342
6518
|
} catch (err) {
|
|
6343
6519
|
fileOpened(err);
|
|
6344
6520
|
throw err;
|
|
6345
6521
|
}
|
|
6346
6522
|
} else if (sonic.mkdir) {
|
|
6347
|
-
|
|
6523
|
+
fs30.mkdir(path33.dirname(file), { recursive: true }, (err) => {
|
|
6348
6524
|
if (err) return fileOpened(err);
|
|
6349
|
-
|
|
6525
|
+
fs30.open(file, flags, mode, fileOpened);
|
|
6350
6526
|
});
|
|
6351
6527
|
} else {
|
|
6352
|
-
|
|
6528
|
+
fs30.open(file, flags, mode, fileOpened);
|
|
6353
6529
|
}
|
|
6354
6530
|
}
|
|
6355
6531
|
function SonicBoom(opts) {
|
|
@@ -6390,8 +6566,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6390
6566
|
this.flush = flushBuffer;
|
|
6391
6567
|
this.flushSync = flushBufferSync;
|
|
6392
6568
|
this._actualWrite = actualWriteBuffer;
|
|
6393
|
-
fsWriteSync = () =>
|
|
6394
|
-
fsWrite = () =>
|
|
6569
|
+
fsWriteSync = () => fs30.writeSync(this.fd, this._writingBuf);
|
|
6570
|
+
fsWrite = () => fs30.write(this.fd, this._writingBuf, this.release);
|
|
6395
6571
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6396
6572
|
this._writingBuf = "";
|
|
6397
6573
|
this.write = write;
|
|
@@ -6400,15 +6576,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6400
6576
|
this._actualWrite = actualWrite;
|
|
6401
6577
|
fsWriteSync = () => {
|
|
6402
6578
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6403
|
-
return
|
|
6579
|
+
return fs30.writeSync(this.fd, this._writingBuf);
|
|
6404
6580
|
}
|
|
6405
|
-
return
|
|
6581
|
+
return fs30.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6406
6582
|
};
|
|
6407
6583
|
fsWrite = () => {
|
|
6408
6584
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6409
|
-
return
|
|
6585
|
+
return fs30.write(this.fd, this._writingBuf, this.release);
|
|
6410
6586
|
}
|
|
6411
|
-
return
|
|
6587
|
+
return fs30.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6412
6588
|
};
|
|
6413
6589
|
} else {
|
|
6414
6590
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6465,7 +6641,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6465
6641
|
}
|
|
6466
6642
|
}
|
|
6467
6643
|
if (this._fsync) {
|
|
6468
|
-
|
|
6644
|
+
fs30.fsyncSync(this.fd);
|
|
6469
6645
|
}
|
|
6470
6646
|
const len = this._len;
|
|
6471
6647
|
if (this._reopening) {
|
|
@@ -6579,7 +6755,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6579
6755
|
const onDrain = () => {
|
|
6580
6756
|
if (!this._fsync) {
|
|
6581
6757
|
try {
|
|
6582
|
-
|
|
6758
|
+
fs30.fsync(this.fd, (err) => {
|
|
6583
6759
|
this._flushPending = false;
|
|
6584
6760
|
cb(err);
|
|
6585
6761
|
});
|
|
@@ -6681,7 +6857,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6681
6857
|
const fd = this.fd;
|
|
6682
6858
|
this.once("ready", () => {
|
|
6683
6859
|
if (fd !== this.fd) {
|
|
6684
|
-
|
|
6860
|
+
fs30.close(fd, (err) => {
|
|
6685
6861
|
if (err) {
|
|
6686
6862
|
return this.emit("error", err);
|
|
6687
6863
|
}
|
|
@@ -6730,7 +6906,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6730
6906
|
buf = this._bufs[0];
|
|
6731
6907
|
}
|
|
6732
6908
|
try {
|
|
6733
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6909
|
+
const n = Buffer.isBuffer(buf) ? fs30.writeSync(this.fd, buf) : fs30.writeSync(this.fd, buf, "utf8");
|
|
6734
6910
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6735
6911
|
buf = releasedBufObj.writingBuf;
|
|
6736
6912
|
this._len = releasedBufObj.len;
|
|
@@ -6746,7 +6922,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6746
6922
|
}
|
|
6747
6923
|
}
|
|
6748
6924
|
try {
|
|
6749
|
-
|
|
6925
|
+
fs30.fsyncSync(this.fd);
|
|
6750
6926
|
} catch {
|
|
6751
6927
|
}
|
|
6752
6928
|
}
|
|
@@ -6767,7 +6943,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6767
6943
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6768
6944
|
}
|
|
6769
6945
|
try {
|
|
6770
|
-
const n =
|
|
6946
|
+
const n = fs30.writeSync(this.fd, buf);
|
|
6771
6947
|
buf = buf.subarray(n);
|
|
6772
6948
|
this._len = Math.max(this._len - n, 0);
|
|
6773
6949
|
if (buf.length <= 0) {
|
|
@@ -6795,13 +6971,13 @@ var require_sonic_boom = __commonJS({
|
|
|
6795
6971
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
6796
6972
|
if (this.sync) {
|
|
6797
6973
|
try {
|
|
6798
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
6974
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs30.writeSync(this.fd, this._writingBuf) : fs30.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6799
6975
|
release(null, written);
|
|
6800
6976
|
} catch (err) {
|
|
6801
6977
|
release(err);
|
|
6802
6978
|
}
|
|
6803
6979
|
} else {
|
|
6804
|
-
|
|
6980
|
+
fs30.write(this.fd, this._writingBuf, release);
|
|
6805
6981
|
}
|
|
6806
6982
|
}
|
|
6807
6983
|
function actualWriteBuffer() {
|
|
@@ -6810,7 +6986,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6810
6986
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
6811
6987
|
if (this.sync) {
|
|
6812
6988
|
try {
|
|
6813
|
-
const written =
|
|
6989
|
+
const written = fs30.writeSync(this.fd, this._writingBuf);
|
|
6814
6990
|
release(null, written);
|
|
6815
6991
|
} catch (err) {
|
|
6816
6992
|
release(err);
|
|
@@ -6819,7 +6995,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6819
6995
|
if (kCopyBuffer) {
|
|
6820
6996
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
6821
6997
|
}
|
|
6822
|
-
|
|
6998
|
+
fs30.write(this.fd, this._writingBuf, release);
|
|
6823
6999
|
}
|
|
6824
7000
|
}
|
|
6825
7001
|
function actualClose(sonic) {
|
|
@@ -6835,12 +7011,12 @@ var require_sonic_boom = __commonJS({
|
|
|
6835
7011
|
sonic._lens = [];
|
|
6836
7012
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
6837
7013
|
try {
|
|
6838
|
-
|
|
7014
|
+
fs30.fsync(sonic.fd, closeWrapped);
|
|
6839
7015
|
} catch {
|
|
6840
7016
|
}
|
|
6841
7017
|
function closeWrapped() {
|
|
6842
7018
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
6843
|
-
|
|
7019
|
+
fs30.close(sonic.fd, done);
|
|
6844
7020
|
} else {
|
|
6845
7021
|
done();
|
|
6846
7022
|
}
|
|
@@ -9975,11 +10151,11 @@ var init_lib = __esm({
|
|
|
9975
10151
|
}
|
|
9976
10152
|
}
|
|
9977
10153
|
},
|
|
9978
|
-
addToPath: function addToPath(
|
|
9979
|
-
var last =
|
|
10154
|
+
addToPath: function addToPath(path33, added, removed, oldPosInc, options) {
|
|
10155
|
+
var last = path33.lastComponent;
|
|
9980
10156
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
9981
10157
|
return {
|
|
9982
|
-
oldPos:
|
|
10158
|
+
oldPos: path33.oldPos + oldPosInc,
|
|
9983
10159
|
lastComponent: {
|
|
9984
10160
|
count: last.count + 1,
|
|
9985
10161
|
added,
|
|
@@ -9989,7 +10165,7 @@ var init_lib = __esm({
|
|
|
9989
10165
|
};
|
|
9990
10166
|
} else {
|
|
9991
10167
|
return {
|
|
9992
|
-
oldPos:
|
|
10168
|
+
oldPos: path33.oldPos + oldPosInc,
|
|
9993
10169
|
lastComponent: {
|
|
9994
10170
|
count: 1,
|
|
9995
10171
|
added,
|
|
@@ -10420,10 +10596,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10420
10596
|
const memories = raw.map((m2) => {
|
|
10421
10597
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10422
10598
|
const rec = m2;
|
|
10423
|
-
const
|
|
10599
|
+
const path33 = typeof rec.path === "string" ? rec.path : null;
|
|
10424
10600
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10425
|
-
if (!
|
|
10426
|
-
const entry = { path:
|
|
10601
|
+
if (!path33 || content == null) return null;
|
|
10602
|
+
const entry = { path: path33, content };
|
|
10427
10603
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10428
10604
|
return entry;
|
|
10429
10605
|
}).filter((m2) => m2 !== null);
|
|
@@ -11227,10 +11403,10 @@ function parseAttachment(obj) {
|
|
|
11227
11403
|
const memories = raw.map((m2) => {
|
|
11228
11404
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11229
11405
|
const rec = m2;
|
|
11230
|
-
const
|
|
11406
|
+
const path33 = typeof rec.path === "string" ? rec.path : null;
|
|
11231
11407
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11232
|
-
if (!
|
|
11233
|
-
const out = { path:
|
|
11408
|
+
if (!path33 || content == null) return null;
|
|
11409
|
+
const out = { path: path33, content };
|
|
11234
11410
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11235
11411
|
return out;
|
|
11236
11412
|
}).filter((m2) => m2 !== null);
|
|
@@ -18725,7 +18901,7 @@ var require_websocket = __commonJS({
|
|
|
18725
18901
|
"use strict";
|
|
18726
18902
|
var EventEmitter2 = require("events");
|
|
18727
18903
|
var https = require("https");
|
|
18728
|
-
var
|
|
18904
|
+
var http2 = require("http");
|
|
18729
18905
|
var net = require("net");
|
|
18730
18906
|
var tls = require("tls");
|
|
18731
18907
|
var { randomBytes, createHash } = require("crypto");
|
|
@@ -19259,7 +19435,7 @@ var require_websocket = __commonJS({
|
|
|
19259
19435
|
}
|
|
19260
19436
|
const defaultPort = isSecure ? 443 : 80;
|
|
19261
19437
|
const key = randomBytes(16).toString("base64");
|
|
19262
|
-
const request = isSecure ? https.request :
|
|
19438
|
+
const request = isSecure ? https.request : http2.request;
|
|
19263
19439
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19264
19440
|
let perMessageDeflate;
|
|
19265
19441
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -19753,7 +19929,7 @@ var require_websocket_server = __commonJS({
|
|
|
19753
19929
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
19754
19930
|
"use strict";
|
|
19755
19931
|
var EventEmitter2 = require("events");
|
|
19756
|
-
var
|
|
19932
|
+
var http2 = require("http");
|
|
19757
19933
|
var { Duplex } = require("stream");
|
|
19758
19934
|
var { createHash } = require("crypto");
|
|
19759
19935
|
var extension2 = require_extension();
|
|
@@ -19828,8 +20004,8 @@ var require_websocket_server = __commonJS({
|
|
|
19828
20004
|
);
|
|
19829
20005
|
}
|
|
19830
20006
|
if (options.port != null) {
|
|
19831
|
-
this._server =
|
|
19832
|
-
const body =
|
|
20007
|
+
this._server = http2.createServer((req, res) => {
|
|
20008
|
+
const body = http2.STATUS_CODES[426];
|
|
19833
20009
|
res.writeHead(426, {
|
|
19834
20010
|
"Content-Length": body.length,
|
|
19835
20011
|
"Content-Type": "text/plain"
|
|
@@ -20116,7 +20292,7 @@ var require_websocket_server = __commonJS({
|
|
|
20116
20292
|
this.destroy();
|
|
20117
20293
|
}
|
|
20118
20294
|
function abortHandshake(socket, code, message, headers) {
|
|
20119
|
-
message = message ||
|
|
20295
|
+
message = message || http2.STATUS_CODES[code];
|
|
20120
20296
|
headers = {
|
|
20121
20297
|
Connection: "close",
|
|
20122
20298
|
"Content-Type": "text/html",
|
|
@@ -20125,7 +20301,7 @@ var require_websocket_server = __commonJS({
|
|
|
20125
20301
|
};
|
|
20126
20302
|
socket.once("finish", socket.destroy);
|
|
20127
20303
|
socket.end(
|
|
20128
|
-
`HTTP/1.1 ${code} ${
|
|
20304
|
+
`HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
|
|
20129
20305
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20130
20306
|
);
|
|
20131
20307
|
}
|
|
@@ -20144,7 +20320,7 @@ var require_websocket_server = __commonJS({
|
|
|
20144
20320
|
// src/run-case/recorder.ts
|
|
20145
20321
|
function startRunCaseRecorder(opts) {
|
|
20146
20322
|
const now = opts.now ?? Date.now;
|
|
20147
|
-
const dir =
|
|
20323
|
+
const dir = import_node_path29.default.dirname(opts.recordPath);
|
|
20148
20324
|
let stream = null;
|
|
20149
20325
|
let closing = false;
|
|
20150
20326
|
let closedSettled = false;
|
|
@@ -20158,8 +20334,8 @@ function startRunCaseRecorder(opts) {
|
|
|
20158
20334
|
});
|
|
20159
20335
|
const ensureStream = () => {
|
|
20160
20336
|
if (stream) return stream;
|
|
20161
|
-
|
|
20162
|
-
stream =
|
|
20337
|
+
import_node_fs27.default.mkdirSync(dir, { recursive: true });
|
|
20338
|
+
stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
20163
20339
|
stream.on("close", () => closedResolve());
|
|
20164
20340
|
return stream;
|
|
20165
20341
|
};
|
|
@@ -20184,12 +20360,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20184
20360
|
};
|
|
20185
20361
|
return { tap, close, closed };
|
|
20186
20362
|
}
|
|
20187
|
-
var
|
|
20363
|
+
var import_node_fs27, import_node_path29;
|
|
20188
20364
|
var init_recorder = __esm({
|
|
20189
20365
|
"src/run-case/recorder.ts"() {
|
|
20190
20366
|
"use strict";
|
|
20191
|
-
|
|
20192
|
-
|
|
20367
|
+
import_node_fs27 = __toESM(require("fs"), 1);
|
|
20368
|
+
import_node_path29 = __toESM(require("path"), 1);
|
|
20193
20369
|
}
|
|
20194
20370
|
});
|
|
20195
20371
|
|
|
@@ -20232,7 +20408,7 @@ var init_wire = __esm({
|
|
|
20232
20408
|
// src/run-case/controller.ts
|
|
20233
20409
|
async function runController(opts) {
|
|
20234
20410
|
const now = opts.now ?? Date.now;
|
|
20235
|
-
const cwd = opts.cwd ?? (0,
|
|
20411
|
+
const cwd = opts.cwd ?? (0, import_node_fs28.mkdtempSync)(import_node_path30.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
|
|
20236
20412
|
const ownsCwd = opts.cwd === void 0;
|
|
20237
20413
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20238
20414
|
const spawnCtx = { cwd };
|
|
@@ -20393,19 +20569,19 @@ async function runController(opts) {
|
|
|
20393
20569
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
20394
20570
|
if (ownsCwd) {
|
|
20395
20571
|
try {
|
|
20396
|
-
(0,
|
|
20572
|
+
(0, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
|
|
20397
20573
|
} catch {
|
|
20398
20574
|
}
|
|
20399
20575
|
}
|
|
20400
20576
|
return exitCode ?? 0;
|
|
20401
20577
|
}
|
|
20402
|
-
var
|
|
20578
|
+
var import_node_fs28, import_node_os15, import_node_path30;
|
|
20403
20579
|
var init_controller = __esm({
|
|
20404
20580
|
"src/run-case/controller.ts"() {
|
|
20405
20581
|
"use strict";
|
|
20406
|
-
|
|
20582
|
+
import_node_fs28 = require("fs");
|
|
20407
20583
|
import_node_os15 = __toESM(require("os"), 1);
|
|
20408
|
-
|
|
20584
|
+
import_node_path30 = __toESM(require("path"), 1);
|
|
20409
20585
|
init_claude();
|
|
20410
20586
|
init_stdout_splitter();
|
|
20411
20587
|
init_permission_stdio();
|
|
@@ -20637,8 +20813,8 @@ Env (advanced):
|
|
|
20637
20813
|
`;
|
|
20638
20814
|
|
|
20639
20815
|
// src/index.ts
|
|
20640
|
-
var
|
|
20641
|
-
var
|
|
20816
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
20817
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
20642
20818
|
|
|
20643
20819
|
// src/logger.ts
|
|
20644
20820
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -21630,6 +21806,11 @@ var SessionRunner = class {
|
|
|
21630
21806
|
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
21631
21807
|
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
21632
21808
|
idleKillTimers = /* @__PURE__ */ new Map();
|
|
21809
|
+
// file-sharing 待配对的 file-edit tool_use(spec §6 PR 3):在同一 session 流里
|
|
21810
|
+
// tool_use(kind='tool_call')与 tool_result 通过 toolUseId 配对。tool_use 提前到,
|
|
21811
|
+
// 等 tool_result 来时反查 path 调 onFileEdit。条目 in-flight 期间很少(个位数),
|
|
21812
|
+
// 不需要 LRU;session 退出时整个 runner 销毁自然清理。
|
|
21813
|
+
pendingFileEdits = /* @__PURE__ */ new Map();
|
|
21633
21814
|
getState() {
|
|
21634
21815
|
return this.state;
|
|
21635
21816
|
}
|
|
@@ -21638,7 +21819,13 @@ var SessionRunner = class {
|
|
|
21638
21819
|
const personaStore = this.hooks.personaStore;
|
|
21639
21820
|
const adapter = this.hooks.adapter;
|
|
21640
21821
|
const deps = {
|
|
21641
|
-
|
|
21822
|
+
// file-sharing (spec §6 PR 3):在 stdout-line 解析时同步观察 file-edit 工具事件,
|
|
21823
|
+
// 配对 tool_use ↔ tool_result 调 onFileEdit。原 reducer 路径不变。
|
|
21824
|
+
parseLine: (l) => {
|
|
21825
|
+
const events = adapter.parseLine(l);
|
|
21826
|
+
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
21827
|
+
return events;
|
|
21828
|
+
},
|
|
21642
21829
|
// persona mention injection 在 deps 装配处包一层,对 reducer 完全透明。
|
|
21643
21830
|
// 命中 mention 时拆成两条 stdin 帧(先 system-reminder 块、后 user 原文):
|
|
21644
21831
|
// - CC stream-json 按行处理,落盘是两条独立 user message;
|
|
@@ -21706,8 +21893,54 @@ var SessionRunner = class {
|
|
|
21706
21893
|
// reducer 的 batch dedup 按 events[0].uuid 整组处理,避免跨路径重复。
|
|
21707
21894
|
feedObserverEvents(events) {
|
|
21708
21895
|
if (events.length === 0) return;
|
|
21896
|
+
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
21709
21897
|
this.input({ kind: "inject-events", events });
|
|
21710
21898
|
}
|
|
21899
|
+
/**
|
|
21900
|
+
* file-sharing tool_use ↔ tool_result 配对(spec §6 PR 3)。
|
|
21901
|
+
*
|
|
21902
|
+
* 1) tool_call kind + tool ∈ FILE_EDIT_TOOLS → cache toolUseId → { tool, relPath }
|
|
21903
|
+
* relPath 从 input 里取(Write/Edit/MultiEdit 是 file_path,NotebookEdit 是 notebook_path)。
|
|
21904
|
+
* 2) tool_result kind + cache 命中 + 非 error → 调 onFileEdit + 删 cache
|
|
21905
|
+
* 3) tool_result kind + cache 命中 + 是 error → 删 cache,不调(spec 只要"成功"才入清单)
|
|
21906
|
+
* 4) tool_call 但 input 没有 path → 不 cache(防御)
|
|
21907
|
+
*
|
|
21908
|
+
* 不破坏 reducer 路径——纯只读扫描 events 数组。
|
|
21909
|
+
*/
|
|
21910
|
+
observeForFileEdit(events) {
|
|
21911
|
+
const cb = this.hooks.onFileEdit;
|
|
21912
|
+
if (!cb) return;
|
|
21913
|
+
for (const ev of events) {
|
|
21914
|
+
if (ev.kind === "tool_call") {
|
|
21915
|
+
const tool = ev.tool;
|
|
21916
|
+
if (!isFileEditTool(tool)) continue;
|
|
21917
|
+
const relPath = extractEditPath(ev.input);
|
|
21918
|
+
if (!relPath) continue;
|
|
21919
|
+
this.pendingFileEdits.set(ev.toolUseId, {
|
|
21920
|
+
tool,
|
|
21921
|
+
relPath
|
|
21922
|
+
});
|
|
21923
|
+
} else if (ev.kind === "tool_result") {
|
|
21924
|
+
const pending = this.pendingFileEdits.get(ev.toolUseId);
|
|
21925
|
+
if (!pending) continue;
|
|
21926
|
+
this.pendingFileEdits.delete(ev.toolUseId);
|
|
21927
|
+
if (ev.error != null) continue;
|
|
21928
|
+
try {
|
|
21929
|
+
cb({
|
|
21930
|
+
toolUseId: ev.toolUseId,
|
|
21931
|
+
tool: pending.tool,
|
|
21932
|
+
relPath: pending.relPath,
|
|
21933
|
+
cwd: this.state.file.cwd
|
|
21934
|
+
});
|
|
21935
|
+
} catch (err) {
|
|
21936
|
+
this.hooks.logger?.warn("onFileEdit hook threw", {
|
|
21937
|
+
err: err.message,
|
|
21938
|
+
sessionId: this.state.file.sessionId
|
|
21939
|
+
});
|
|
21940
|
+
}
|
|
21941
|
+
}
|
|
21942
|
+
}
|
|
21943
|
+
}
|
|
21711
21944
|
// 向子进程 stdin 写一条 CC IPC `control_request` 并返回 Promise<response>
|
|
21712
21945
|
// —— CC 侧会异步回写匹配 request_id 的 `control_response` 帧,
|
|
21713
21946
|
// 在 stdout 行处理入口被 tryHandleControlResponse 拦截并 resolve pending
|
|
@@ -21897,6 +22130,15 @@ var SessionRunner = class {
|
|
|
21897
22130
|
}
|
|
21898
22131
|
}
|
|
21899
22132
|
};
|
|
22133
|
+
function isFileEditTool(name) {
|
|
22134
|
+
return name === "Write" || name === "Edit" || name === "MultiEdit" || name === "NotebookEdit";
|
|
22135
|
+
}
|
|
22136
|
+
function extractEditPath(input) {
|
|
22137
|
+
if (!input || typeof input !== "object") return null;
|
|
22138
|
+
const o = input;
|
|
22139
|
+
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;
|
|
22140
|
+
return candidate || null;
|
|
22141
|
+
}
|
|
21900
22142
|
|
|
21901
22143
|
// src/session/manager.ts
|
|
21902
22144
|
function compressFrameForWire(frame) {
|
|
@@ -21953,16 +22195,6 @@ function nowIso2(deps) {
|
|
|
21953
22195
|
function newSessionId() {
|
|
21954
22196
|
return v4_default().replace(/-/g, "").slice(0, 16);
|
|
21955
22197
|
}
|
|
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
|
-
}
|
|
21966
22198
|
function makeInitialState(file, subSessionMeta) {
|
|
21967
22199
|
return {
|
|
21968
22200
|
file,
|
|
@@ -22121,24 +22353,7 @@ var SessionManager = class {
|
|
|
22121
22353
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
22122
22354
|
const store = this.storeFor(scope);
|
|
22123
22355
|
const subSessionMeta = metaFromScope(scope, this.deps.personaRoot ?? "");
|
|
22124
|
-
const
|
|
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
|
-
}
|
|
22356
|
+
const attachmentGroup = this.deps.attachmentGroup;
|
|
22142
22357
|
const runner = new SessionRunner(makeInitialState(file, subSessionMeta), {
|
|
22143
22358
|
broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
|
|
22144
22359
|
store,
|
|
@@ -22151,7 +22366,15 @@ var SessionManager = class {
|
|
|
22151
22366
|
resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId),
|
|
22152
22367
|
dataDir: this.deps.dataDir,
|
|
22153
22368
|
personaStore: this.deps.personaStore,
|
|
22154
|
-
ownerDisplayName: this.deps.ownerDisplayName
|
|
22369
|
+
ownerDisplayName: this.deps.ownerDisplayName,
|
|
22370
|
+
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
22371
|
+
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
22372
|
+
scope,
|
|
22373
|
+
sessionId: file.sessionId,
|
|
22374
|
+
tool: input.tool,
|
|
22375
|
+
relPath: input.relPath,
|
|
22376
|
+
cwd: input.cwd
|
|
22377
|
+
}) : void 0
|
|
22155
22378
|
});
|
|
22156
22379
|
if (this.deps.mode === "tui" && !file.toolSessionId) {
|
|
22157
22380
|
const newTsid = v4_default();
|
|
@@ -22239,31 +22462,14 @@ var SessionManager = class {
|
|
|
22239
22462
|
}
|
|
22240
22463
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
22241
22464
|
create(args) {
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
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 {
|
|
22465
|
+
let cwd = args.cwd;
|
|
22466
|
+
if (args.ownerPersonaId && !cwd) {
|
|
22467
|
+
if (!this.deps.personaRoot) {
|
|
22468
|
+
throw new Error("personaRoot required to derive cwd from ownerPersonaId");
|
|
22469
|
+
}
|
|
22470
|
+
cwd = import_node_path6.default.join(this.deps.personaRoot, safeFileName(args.ownerPersonaId));
|
|
22471
|
+
}
|
|
22472
|
+
if (!cwd) {
|
|
22267
22473
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, "cwd required when ownerPersonaId is absent");
|
|
22268
22474
|
}
|
|
22269
22475
|
try {
|
|
@@ -23319,6 +23525,21 @@ var PersonaRegistry = class {
|
|
|
23319
23525
|
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
23320
23526
|
return { ok: true, label: entry.label };
|
|
23321
23527
|
}
|
|
23528
|
+
/**
|
|
23529
|
+
* file-sharing HTTP auth-context 用的逆向查表:只有 Bearer token,没有 personaId。
|
|
23530
|
+
* 线性扫所有 persona.tokenMap 找到第一条 ok 命中的;revoked / non-public persona 跳过。
|
|
23531
|
+
* persona 数量通常 < 100,性能可忽略。
|
|
23532
|
+
*/
|
|
23533
|
+
findByToken(token) {
|
|
23534
|
+
if (!token) return null;
|
|
23535
|
+
for (const persona of this.cache.values()) {
|
|
23536
|
+
if (!persona.public) continue;
|
|
23537
|
+
const entry = persona.tokenMap[token];
|
|
23538
|
+
if (!entry || entry.revoked) continue;
|
|
23539
|
+
return { personaId: persona.personaId, label: entry.label };
|
|
23540
|
+
}
|
|
23541
|
+
return null;
|
|
23542
|
+
}
|
|
23322
23543
|
};
|
|
23323
23544
|
|
|
23324
23545
|
// src/persona/manager.ts
|
|
@@ -25260,6 +25481,7 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
25260
25481
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
25261
25482
|
|
|
25262
25483
|
// src/transport/local-ws-server.ts
|
|
25484
|
+
var import_node_http = __toESM(require("http"), 1);
|
|
25263
25485
|
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
25264
25486
|
var LocalWsServer = class {
|
|
25265
25487
|
constructor(opts) {
|
|
@@ -25269,6 +25491,7 @@ var LocalWsServer = class {
|
|
|
25269
25491
|
}
|
|
25270
25492
|
opts;
|
|
25271
25493
|
wss = null;
|
|
25494
|
+
httpServer = null;
|
|
25272
25495
|
frameHandler = null;
|
|
25273
25496
|
clients = /* @__PURE__ */ new Map();
|
|
25274
25497
|
logger;
|
|
@@ -25276,25 +25499,28 @@ var LocalWsServer = class {
|
|
|
25276
25499
|
async start() {
|
|
25277
25500
|
const host = this.opts.host ?? "127.0.0.1";
|
|
25278
25501
|
await new Promise((resolve2, reject) => {
|
|
25279
|
-
const
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25502
|
+
const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
25503
|
+
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
25504
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
25505
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
25506
|
+
this.routeConnection(ws, req);
|
|
25507
|
+
});
|
|
25283
25508
|
});
|
|
25284
|
-
|
|
25509
|
+
httpServer.on("listening", () => {
|
|
25285
25510
|
this.logger?.info("ws listening", { host, port: this.opts.port });
|
|
25286
25511
|
resolve2();
|
|
25287
25512
|
});
|
|
25288
|
-
|
|
25513
|
+
httpServer.on("error", (err) => {
|
|
25289
25514
|
this.logger?.error("ws server error", { err: err.message });
|
|
25290
25515
|
reject(err);
|
|
25291
25516
|
});
|
|
25292
|
-
|
|
25517
|
+
httpServer.listen(this.opts.port, host);
|
|
25518
|
+
this.httpServer = httpServer;
|
|
25293
25519
|
this.wss = wss;
|
|
25294
25520
|
});
|
|
25295
25521
|
}
|
|
25296
25522
|
async stop() {
|
|
25297
|
-
if (!this.
|
|
25523
|
+
if (!this.httpServer) return;
|
|
25298
25524
|
for (const c of this.clients.values()) {
|
|
25299
25525
|
try {
|
|
25300
25526
|
c.ws.close(1001, "shutdown");
|
|
@@ -25306,7 +25532,32 @@ var LocalWsServer = class {
|
|
|
25306
25532
|
await new Promise((resolve2) => {
|
|
25307
25533
|
this.wss?.close(() => resolve2());
|
|
25308
25534
|
});
|
|
25535
|
+
await new Promise((resolve2) => {
|
|
25536
|
+
this.httpServer?.close(() => resolve2());
|
|
25537
|
+
});
|
|
25309
25538
|
this.wss = null;
|
|
25539
|
+
this.httpServer = null;
|
|
25540
|
+
}
|
|
25541
|
+
/** http.createServer 'request' 入口:file-sharing 路由优先,未命中走 404 */
|
|
25542
|
+
async handleHttpRequest(req, res) {
|
|
25543
|
+
const handler = this.opts.httpRequestHandler;
|
|
25544
|
+
if (handler) {
|
|
25545
|
+
try {
|
|
25546
|
+
const handled = await handler(req, res);
|
|
25547
|
+
if (handled) return;
|
|
25548
|
+
} catch (err) {
|
|
25549
|
+
this.logger?.warn("http handler threw", { err: err.message });
|
|
25550
|
+
if (!res.headersSent) {
|
|
25551
|
+
res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
|
|
25552
|
+
res.end(JSON.stringify({ code: "INTERNAL", message: "http handler error" }));
|
|
25553
|
+
}
|
|
25554
|
+
return;
|
|
25555
|
+
}
|
|
25556
|
+
}
|
|
25557
|
+
if (!res.headersSent) {
|
|
25558
|
+
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
25559
|
+
res.end(JSON.stringify({ code: "NOT_FOUND", message: "no route" }));
|
|
25560
|
+
}
|
|
25310
25561
|
}
|
|
25311
25562
|
onFrame(handler) {
|
|
25312
25563
|
this.frameHandler = handler;
|
|
@@ -25408,7 +25659,7 @@ var LocalWsServer = class {
|
|
|
25408
25659
|
}
|
|
25409
25660
|
try {
|
|
25410
25661
|
if (authed) {
|
|
25411
|
-
const readyFrame = this.opts.readyFrameBuilder();
|
|
25662
|
+
const readyFrame = this.opts.readyFrameBuilder({ remoteAddress });
|
|
25412
25663
|
this.safeSend(socket, { type: "ready", ...readyFrame });
|
|
25413
25664
|
} else {
|
|
25414
25665
|
this.safeSend(socket, { type: "ready", protocolVersion: this.opts.protocolVersion });
|
|
@@ -25445,7 +25696,7 @@ var LocalWsServer = class {
|
|
|
25445
25696
|
if (verdict !== "pass") {
|
|
25446
25697
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
25447
25698
|
try {
|
|
25448
|
-
const full = this.opts.readyFrameBuilder();
|
|
25699
|
+
const full = this.opts.readyFrameBuilder({ remoteAddress });
|
|
25449
25700
|
this.safeSend(this.clients.get(client.id).ws, { type: "ready", ...full });
|
|
25450
25701
|
} catch (err) {
|
|
25451
25702
|
this.logger?.warn("post-auth ready frame build failed", { err: err.message });
|
|
@@ -26042,11 +26293,687 @@ function isLocalhost(addr) {
|
|
|
26042
26293
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
26043
26294
|
}
|
|
26044
26295
|
|
|
26045
|
-
// src/
|
|
26296
|
+
// src/transport/auth-context.ts
|
|
26297
|
+
var AuthContextResolver = class {
|
|
26298
|
+
constructor(opts) {
|
|
26299
|
+
this.opts = opts;
|
|
26300
|
+
}
|
|
26301
|
+
opts;
|
|
26302
|
+
/**
|
|
26303
|
+
* 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
|
|
26304
|
+
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
26305
|
+
*/
|
|
26306
|
+
resolveFromHeader(authHeader, remoteAddress) {
|
|
26307
|
+
const token = parseBearer(authHeader);
|
|
26308
|
+
if (!token) return null;
|
|
26309
|
+
const isLoopback = isLoopbackAddr(remoteAddress);
|
|
26310
|
+
if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
|
|
26311
|
+
return { role: "owner", isLoopback };
|
|
26312
|
+
}
|
|
26313
|
+
const personalHit = this.opts.personaRegistry.findByToken(token);
|
|
26314
|
+
if (personalHit) {
|
|
26315
|
+
return {
|
|
26316
|
+
role: "personal",
|
|
26317
|
+
personaId: personalHit.personaId,
|
|
26318
|
+
label: personalHit.label,
|
|
26319
|
+
isLoopback
|
|
26320
|
+
};
|
|
26321
|
+
}
|
|
26322
|
+
return null;
|
|
26323
|
+
}
|
|
26324
|
+
};
|
|
26325
|
+
function parseBearer(header) {
|
|
26326
|
+
if (!header) return null;
|
|
26327
|
+
const raw = Array.isArray(header) ? header[0] : header;
|
|
26328
|
+
if (typeof raw !== "string") return null;
|
|
26329
|
+
const m2 = /^Bearer\s+(\S+)$/i.exec(raw.trim());
|
|
26330
|
+
return m2 ? m2[1] : null;
|
|
26331
|
+
}
|
|
26332
|
+
function isLoopbackAddr(addr) {
|
|
26333
|
+
if (!addr) return false;
|
|
26334
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
26335
|
+
}
|
|
26336
|
+
function constantTimeEqual2(a, b2) {
|
|
26337
|
+
if (a.length !== b2.length) return false;
|
|
26338
|
+
let diff2 = 0;
|
|
26339
|
+
for (let i = 0; i < a.length; i++) diff2 |= a.charCodeAt(i) ^ b2.charCodeAt(i);
|
|
26340
|
+
return diff2 === 0;
|
|
26341
|
+
}
|
|
26342
|
+
|
|
26343
|
+
// src/transport/http-router.ts
|
|
26344
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
26345
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
26346
|
+
|
|
26347
|
+
// src/attachment/group.ts
|
|
26046
26348
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26047
26349
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
26350
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
26351
|
+
init_protocol();
|
|
26352
|
+
var GroupFileStore = class {
|
|
26353
|
+
dataDir;
|
|
26354
|
+
logger;
|
|
26355
|
+
cache = /* @__PURE__ */ new Map();
|
|
26356
|
+
constructor(opts) {
|
|
26357
|
+
this.dataDir = opts.dataDir;
|
|
26358
|
+
this.logger = opts.logger;
|
|
26359
|
+
}
|
|
26360
|
+
rootForScope(scope) {
|
|
26361
|
+
return import_node_path14.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
26362
|
+
}
|
|
26363
|
+
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
26364
|
+
filePath(scope, sessionId) {
|
|
26365
|
+
return import_node_path14.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
26366
|
+
}
|
|
26367
|
+
cacheKey(scope, sessionId) {
|
|
26368
|
+
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
26369
|
+
}
|
|
26370
|
+
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
26371
|
+
readFile(scope, sessionId) {
|
|
26372
|
+
const file = this.filePath(scope, sessionId);
|
|
26373
|
+
try {
|
|
26374
|
+
const raw = import_node_fs13.default.readFileSync(file, "utf8");
|
|
26375
|
+
const parsed = JSON.parse(raw);
|
|
26376
|
+
if (!Array.isArray(parsed)) {
|
|
26377
|
+
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
26378
|
+
file
|
|
26379
|
+
});
|
|
26380
|
+
return [];
|
|
26381
|
+
}
|
|
26382
|
+
const out = [];
|
|
26383
|
+
for (const entry of parsed) {
|
|
26384
|
+
const r = GroupFileEntrySchema.safeParse(entry);
|
|
26385
|
+
if (r.success) out.push(r.data);
|
|
26386
|
+
}
|
|
26387
|
+
return out;
|
|
26388
|
+
} catch (err) {
|
|
26389
|
+
const code = err?.code;
|
|
26390
|
+
if (code === "ENOENT") return [];
|
|
26391
|
+
this.logger?.warn("GroupFileStore.readFile failed", {
|
|
26392
|
+
file,
|
|
26393
|
+
err: err.message
|
|
26394
|
+
});
|
|
26395
|
+
return [];
|
|
26396
|
+
}
|
|
26397
|
+
}
|
|
26398
|
+
writeFile(scope, sessionId, entries) {
|
|
26399
|
+
const file = this.filePath(scope, sessionId);
|
|
26400
|
+
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true });
|
|
26401
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
26402
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
26403
|
+
import_node_fs13.default.renameSync(tmp, file);
|
|
26404
|
+
}
|
|
26405
|
+
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
26406
|
+
list(scope, sessionId) {
|
|
26407
|
+
const key = this.cacheKey(scope, sessionId);
|
|
26408
|
+
const cached = this.cache.get(key);
|
|
26409
|
+
if (cached) return cached.entries;
|
|
26410
|
+
const entries = this.readFile(scope, sessionId);
|
|
26411
|
+
this.cache.set(key, { entries, scope });
|
|
26412
|
+
return entries;
|
|
26413
|
+
}
|
|
26414
|
+
/**
|
|
26415
|
+
* upsert:
|
|
26416
|
+
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
26417
|
+
* (保留首次入群人 / 入群时刻)
|
|
26418
|
+
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
26419
|
+
*
|
|
26420
|
+
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
26421
|
+
*/
|
|
26422
|
+
upsert(scope, sessionId, input, now = Date.now()) {
|
|
26423
|
+
const entries = this.list(scope, sessionId).slice();
|
|
26424
|
+
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
26425
|
+
let next;
|
|
26426
|
+
if (idx >= 0) {
|
|
26427
|
+
const prev = entries[idx];
|
|
26428
|
+
next = {
|
|
26429
|
+
...prev,
|
|
26430
|
+
size: input.size,
|
|
26431
|
+
mime: input.mime,
|
|
26432
|
+
lastEditedAt: now,
|
|
26433
|
+
stale: false
|
|
26434
|
+
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
26435
|
+
};
|
|
26436
|
+
entries[idx] = next;
|
|
26437
|
+
} else {
|
|
26438
|
+
next = {
|
|
26439
|
+
id: `gf-${import_node_crypto4.default.randomBytes(6).toString("base64url")}`,
|
|
26440
|
+
relPath: input.relPath,
|
|
26441
|
+
from: input.from,
|
|
26442
|
+
label: input.label,
|
|
26443
|
+
size: input.size,
|
|
26444
|
+
mime: input.mime,
|
|
26445
|
+
addedAt: now
|
|
26446
|
+
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
26447
|
+
};
|
|
26448
|
+
entries.push(next);
|
|
26449
|
+
}
|
|
26450
|
+
this.writeFile(scope, sessionId, entries);
|
|
26451
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26452
|
+
return next;
|
|
26453
|
+
}
|
|
26454
|
+
/**
|
|
26455
|
+
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
26456
|
+
* - 命中 → stale=true,UI 灰显
|
|
26457
|
+
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
26458
|
+
*
|
|
26459
|
+
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
26460
|
+
*/
|
|
26461
|
+
markStale(scope, sessionId, relPath) {
|
|
26462
|
+
const entries = this.list(scope, sessionId).slice();
|
|
26463
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
26464
|
+
if (idx < 0) return;
|
|
26465
|
+
if (entries[idx].stale) return;
|
|
26466
|
+
entries[idx] = { ...entries[idx], stale: true };
|
|
26467
|
+
this.writeFile(scope, sessionId, entries);
|
|
26468
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26469
|
+
}
|
|
26470
|
+
/**
|
|
26471
|
+
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
26472
|
+
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
26473
|
+
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
26474
|
+
*
|
|
26475
|
+
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
26476
|
+
*/
|
|
26477
|
+
remove(scope, sessionId, relPath) {
|
|
26478
|
+
const entries = this.list(scope, sessionId).slice();
|
|
26479
|
+
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
26480
|
+
if (idx < 0) return false;
|
|
26481
|
+
entries.splice(idx, 1);
|
|
26482
|
+
this.writeFile(scope, sessionId, entries);
|
|
26483
|
+
this.cache.set(this.cacheKey(scope, sessionId), { entries, scope });
|
|
26484
|
+
return true;
|
|
26485
|
+
}
|
|
26486
|
+
/**
|
|
26487
|
+
* 跨 session 聚合查询(spec §4 HTTP ACL:personal 视野并集)。
|
|
26488
|
+
*
|
|
26489
|
+
* 扫 <dataDir>/sessions/<personaId>/owner/*.group-files.json 和
|
|
26490
|
+
* <dataDir>/sessions/<personaId>/listener/*.group-files.json,每文件读一份。
|
|
26491
|
+
*
|
|
26492
|
+
* 复杂度 O(N sessions × N entries),N 通常 < 100,可接受。
|
|
26493
|
+
*/
|
|
26494
|
+
listByPersona(personaId) {
|
|
26495
|
+
const out = [];
|
|
26496
|
+
for (const mode of ["owner", "listener"]) {
|
|
26497
|
+
const scope = { kind: "persona", personaId, mode };
|
|
26498
|
+
const root = this.rootForScope(scope);
|
|
26499
|
+
let names;
|
|
26500
|
+
try {
|
|
26501
|
+
names = import_node_fs13.default.readdirSync(root);
|
|
26502
|
+
} catch (err) {
|
|
26503
|
+
const code = err?.code;
|
|
26504
|
+
if (code === "ENOENT") continue;
|
|
26505
|
+
continue;
|
|
26506
|
+
}
|
|
26507
|
+
for (const name of names) {
|
|
26508
|
+
if (!name.endsWith(".group-files.json")) continue;
|
|
26509
|
+
const sessionId = name.slice(0, -".group-files.json".length);
|
|
26510
|
+
if (!sessionId) continue;
|
|
26511
|
+
const entries = this.list(scope, sessionId);
|
|
26512
|
+
out.push({ sessionId, entries });
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
return out;
|
|
26516
|
+
}
|
|
26517
|
+
};
|
|
26518
|
+
function personalViewable(groupStore, personaDir, personaId, absPath) {
|
|
26519
|
+
const realTarget = safeRealpath(absPath);
|
|
26520
|
+
if (!realTarget) {
|
|
26521
|
+
return false;
|
|
26522
|
+
}
|
|
26523
|
+
const personasUnion = groupStore.listByPersona(personaId);
|
|
26524
|
+
for (const { entries } of personasUnion) {
|
|
26525
|
+
for (const e of entries) {
|
|
26526
|
+
if (e.stale) continue;
|
|
26527
|
+
const realEntry = safeRealpath(import_node_path14.default.join(personaDir, e.relPath));
|
|
26528
|
+
if (realEntry && realEntry === realTarget) return true;
|
|
26529
|
+
}
|
|
26530
|
+
}
|
|
26531
|
+
return false;
|
|
26532
|
+
}
|
|
26533
|
+
function safeRealpath(p2) {
|
|
26534
|
+
try {
|
|
26535
|
+
return import_node_fs13.default.realpathSync(p2);
|
|
26536
|
+
} catch {
|
|
26537
|
+
return null;
|
|
26538
|
+
}
|
|
26539
|
+
}
|
|
26540
|
+
|
|
26541
|
+
// src/attachment/mime.ts
|
|
26542
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
26543
|
+
var EXT_TO_MIME = {
|
|
26544
|
+
".md": "text/markdown",
|
|
26545
|
+
".markdown": "text/markdown",
|
|
26546
|
+
".txt": "text/plain",
|
|
26547
|
+
".log": "text/plain",
|
|
26548
|
+
".json": "application/json",
|
|
26549
|
+
".html": "text/html",
|
|
26550
|
+
".htm": "text/html",
|
|
26551
|
+
".css": "text/css",
|
|
26552
|
+
".js": "application/javascript",
|
|
26553
|
+
".ts": "application/typescript",
|
|
26554
|
+
".tsx": "application/typescript",
|
|
26555
|
+
".png": "image/png",
|
|
26556
|
+
".jpg": "image/jpeg",
|
|
26557
|
+
".jpeg": "image/jpeg",
|
|
26558
|
+
".gif": "image/gif",
|
|
26559
|
+
".webp": "image/webp",
|
|
26560
|
+
".svg": "image/svg+xml",
|
|
26561
|
+
".pdf": "application/pdf",
|
|
26562
|
+
".mp4": "video/mp4",
|
|
26563
|
+
".webm": "video/webm",
|
|
26564
|
+
".mp3": "audio/mpeg",
|
|
26565
|
+
".wav": "audio/wav",
|
|
26566
|
+
".zip": "application/zip",
|
|
26567
|
+
".gz": "application/gzip",
|
|
26568
|
+
".tar": "application/x-tar",
|
|
26569
|
+
".csv": "text/csv",
|
|
26570
|
+
".xml": "application/xml",
|
|
26571
|
+
".yaml": "application/yaml",
|
|
26572
|
+
".yml": "application/yaml"
|
|
26573
|
+
};
|
|
26574
|
+
function lookupMime(filePathOrName) {
|
|
26575
|
+
const ext = import_node_path15.default.extname(filePathOrName).toLowerCase();
|
|
26576
|
+
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
26577
|
+
}
|
|
26578
|
+
|
|
26579
|
+
// src/transport/http-router.ts
|
|
26580
|
+
function createHttpRouter(deps) {
|
|
26581
|
+
return async (req, res) => {
|
|
26582
|
+
const url = parseUrl(req.url);
|
|
26583
|
+
if (!url) {
|
|
26584
|
+
sendJson(res, 400, { code: "INVALID_URL", message: "malformed request URL" });
|
|
26585
|
+
return true;
|
|
26586
|
+
}
|
|
26587
|
+
if (url.pathname === "/healthz" && req.method === "GET") {
|
|
26588
|
+
sendJson(res, 200, { ok: true, version: deps.daemonVersion });
|
|
26589
|
+
return true;
|
|
26590
|
+
}
|
|
26591
|
+
if (!url.pathname.startsWith("/persona/") && !url.pathname.startsWith("/session/") && url.pathname !== "/outbox" && !url.pathname.startsWith("/outbox/")) {
|
|
26592
|
+
return false;
|
|
26593
|
+
}
|
|
26594
|
+
{
|
|
26595
|
+
const directMatch = url.pathname.match(/^\/outbox\/([^/]+)$/);
|
|
26596
|
+
const personaMatch = url.pathname.match(/^\/persona\/([^/]+)\/outbox\/([^/]+)$/);
|
|
26597
|
+
if (directMatch || personaMatch) {
|
|
26598
|
+
if (!deps.outboxStore) {
|
|
26599
|
+
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "outbox store not wired" });
|
|
26600
|
+
return true;
|
|
26601
|
+
}
|
|
26602
|
+
const capToken = directMatch ? directMatch[1] : personaMatch[2];
|
|
26603
|
+
const lookup = deps.outboxStore.lookup(capToken);
|
|
26604
|
+
if (!lookup.ok) {
|
|
26605
|
+
const statusByCode = {
|
|
26606
|
+
NOT_FOUND: 404,
|
|
26607
|
+
EXPIRED: 410,
|
|
26608
|
+
REVOKED: 410
|
|
26609
|
+
};
|
|
26610
|
+
sendJson(res, statusByCode[lookup.code], { code: lookup.code, message: "outbox cap not usable" });
|
|
26611
|
+
return true;
|
|
26612
|
+
}
|
|
26613
|
+
const { entry } = lookup;
|
|
26614
|
+
if (entry.scope.kind === "personal") {
|
|
26615
|
+
const ctx2 = deps.authResolver.resolveFromHeader(
|
|
26616
|
+
req.headers.authorization,
|
|
26617
|
+
req.socket.remoteAddress ?? void 0
|
|
26618
|
+
);
|
|
26619
|
+
if (!ctx2 || ctx2.role !== "personal" || ctx2.personaId !== entry.scope.personalId) {
|
|
26620
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "personal-scoped cap requires matching token" });
|
|
26621
|
+
return true;
|
|
26622
|
+
}
|
|
26623
|
+
}
|
|
26624
|
+
const outboxStore = deps.outboxStore;
|
|
26625
|
+
streamFile(res, entry.absPath, deps.logger, () => outboxStore.consume(capToken));
|
|
26626
|
+
return true;
|
|
26627
|
+
}
|
|
26628
|
+
}
|
|
26629
|
+
const ctx = deps.authResolver.resolveFromHeader(
|
|
26630
|
+
req.headers.authorization,
|
|
26631
|
+
req.socket.remoteAddress ?? void 0
|
|
26632
|
+
);
|
|
26633
|
+
if (!ctx) {
|
|
26634
|
+
sendJson(res, 401, { code: "UNAUTHORIZED", message: "missing or invalid bearer token" });
|
|
26635
|
+
return true;
|
|
26636
|
+
}
|
|
26637
|
+
const personaFilesMatch = url.pathname.match(/^\/persona\/([^/]+)\/files$/);
|
|
26638
|
+
if (personaFilesMatch && req.method === "GET") {
|
|
26639
|
+
const pid = personaFilesMatch[1];
|
|
26640
|
+
const pathParam = url.searchParams.get("path");
|
|
26641
|
+
if (!pathParam) {
|
|
26642
|
+
sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26643
|
+
return true;
|
|
26644
|
+
}
|
|
26645
|
+
if (!deps.personaStore || !deps.groupFileStore) {
|
|
26646
|
+
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "files endpoint not wired" }));
|
|
26647
|
+
return true;
|
|
26648
|
+
}
|
|
26649
|
+
const personaDir = deps.personaStore.personaDirPath(pid);
|
|
26650
|
+
const absPath = import_node_path16.default.isAbsolute(pathParam) ? pathParam : import_node_path16.default.join(personaDir, pathParam);
|
|
26651
|
+
if (!import_node_path16.default.isAbsolute(pathParam) && !isContainedIn(absPath, personaDir)) {
|
|
26652
|
+
sendJson(res, 400, { code: "PATH_TRAVERSAL", message: "rel path escapes personaDir" });
|
|
26653
|
+
return true;
|
|
26654
|
+
}
|
|
26655
|
+
if (ctx.role === "personal") {
|
|
26656
|
+
if (ctx.personaId !== pid) {
|
|
26657
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "personal token bound to other persona" });
|
|
26658
|
+
return true;
|
|
26659
|
+
}
|
|
26660
|
+
if (!personalViewable(deps.groupFileStore, personaDir, pid, absPath)) {
|
|
26661
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "path not in personal viewable scope" });
|
|
26662
|
+
return true;
|
|
26663
|
+
}
|
|
26664
|
+
}
|
|
26665
|
+
streamFile(res, absPath, deps.logger);
|
|
26666
|
+
return true;
|
|
26667
|
+
}
|
|
26668
|
+
const sessionFilesMatch = url.pathname.match(/^\/session\/([^/]+)\/files$/);
|
|
26669
|
+
if (sessionFilesMatch && req.method === "GET") {
|
|
26670
|
+
if (ctx.role !== "owner") {
|
|
26671
|
+
sendJson(res, 403, { code: "FORBIDDEN", message: "direct session files are owner-only" });
|
|
26672
|
+
return true;
|
|
26673
|
+
}
|
|
26674
|
+
const sid = sessionFilesMatch[1];
|
|
26675
|
+
const pathParam = url.searchParams.get("path");
|
|
26676
|
+
if (!pathParam) {
|
|
26677
|
+
sendJson(res, 400, { code: "INVALID_PARAM", message: "missing `path` query" });
|
|
26678
|
+
return true;
|
|
26679
|
+
}
|
|
26680
|
+
let absPath;
|
|
26681
|
+
if (import_node_path16.default.isAbsolute(pathParam)) {
|
|
26682
|
+
absPath = pathParam;
|
|
26683
|
+
} else if (deps.sessionStore) {
|
|
26684
|
+
const file = deps.sessionStore.read(sid);
|
|
26685
|
+
if (!file) {
|
|
26686
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
26687
|
+
return true;
|
|
26688
|
+
}
|
|
26689
|
+
absPath = import_node_path16.default.join(file.cwd, pathParam);
|
|
26690
|
+
} else {
|
|
26691
|
+
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
26692
|
+
return true;
|
|
26693
|
+
}
|
|
26694
|
+
streamFile(res, absPath, deps.logger);
|
|
26695
|
+
return true;
|
|
26696
|
+
}
|
|
26697
|
+
if (/^\/persona\/[^/]+\/attachment-meta$/.test(url.pathname) && req.method === "GET") {
|
|
26698
|
+
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "attachment-meta \u2014 PR 6" }));
|
|
26699
|
+
return true;
|
|
26700
|
+
}
|
|
26701
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
26702
|
+
return true;
|
|
26703
|
+
};
|
|
26704
|
+
}
|
|
26705
|
+
function parseUrl(rawUrl) {
|
|
26706
|
+
if (!rawUrl) return null;
|
|
26707
|
+
try {
|
|
26708
|
+
return new URL(rawUrl, "http://placeholder");
|
|
26709
|
+
} catch {
|
|
26710
|
+
return null;
|
|
26711
|
+
}
|
|
26712
|
+
}
|
|
26713
|
+
function sendJson(res, status, body) {
|
|
26714
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
26715
|
+
res.end(JSON.stringify(body));
|
|
26716
|
+
}
|
|
26717
|
+
function withCtx(ctx, body) {
|
|
26718
|
+
return { ...body, role: ctx.role, personaId: ctx.personaId };
|
|
26719
|
+
}
|
|
26720
|
+
function isContainedIn(abs, root) {
|
|
26721
|
+
const normalized = import_node_path16.default.resolve(abs);
|
|
26722
|
+
const normalizedRoot = import_node_path16.default.resolve(root);
|
|
26723
|
+
if (normalized === normalizedRoot) return true;
|
|
26724
|
+
return normalized.startsWith(normalizedRoot + import_node_path16.default.sep);
|
|
26725
|
+
}
|
|
26726
|
+
function streamFile(res, absPath, logger, onComplete) {
|
|
26727
|
+
let stat;
|
|
26728
|
+
try {
|
|
26729
|
+
stat = import_node_fs14.default.statSync(absPath);
|
|
26730
|
+
} catch (err) {
|
|
26731
|
+
const code = err?.code;
|
|
26732
|
+
if (code === "ENOENT") {
|
|
26733
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: "file not found" });
|
|
26734
|
+
} else {
|
|
26735
|
+
sendJson(res, 500, { code: "STAT_FAILED", message: err.message });
|
|
26736
|
+
}
|
|
26737
|
+
return;
|
|
26738
|
+
}
|
|
26739
|
+
if (!stat.isFile()) {
|
|
26740
|
+
sendJson(res, 400, { code: "NOT_A_FILE", message: "path is not a regular file" });
|
|
26741
|
+
return;
|
|
26742
|
+
}
|
|
26743
|
+
const mime = lookupMime(absPath);
|
|
26744
|
+
res.writeHead(200, {
|
|
26745
|
+
"Content-Type": mime,
|
|
26746
|
+
"Content-Length": String(stat.size),
|
|
26747
|
+
// 防止浏览器把任意 mime 当 html 渲染(spec §11 #8 安全心智)
|
|
26748
|
+
"X-Content-Type-Options": "nosniff"
|
|
26749
|
+
});
|
|
26750
|
+
const stream = import_node_fs14.default.createReadStream(absPath);
|
|
26751
|
+
stream.on("error", (err) => {
|
|
26752
|
+
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
26753
|
+
res.destroy();
|
|
26754
|
+
});
|
|
26755
|
+
if (onComplete) {
|
|
26756
|
+
res.once("finish", onComplete);
|
|
26757
|
+
}
|
|
26758
|
+
stream.pipe(res);
|
|
26759
|
+
}
|
|
26760
|
+
|
|
26761
|
+
// src/attachment/outbox.ts
|
|
26762
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
26763
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
26764
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
26765
|
+
init_protocol();
|
|
26766
|
+
var FILE_NAME = "outbox-caps.json";
|
|
26767
|
+
var OutboxStore = class {
|
|
26768
|
+
file;
|
|
26769
|
+
now;
|
|
26770
|
+
logger;
|
|
26771
|
+
cache = [];
|
|
26772
|
+
constructor(opts) {
|
|
26773
|
+
this.file = import_node_path17.default.join(opts.dataDir, FILE_NAME);
|
|
26774
|
+
this.now = opts.now ?? Date.now;
|
|
26775
|
+
this.logger = opts.logger;
|
|
26776
|
+
this.reload();
|
|
26777
|
+
}
|
|
26778
|
+
reload() {
|
|
26779
|
+
try {
|
|
26780
|
+
const raw = import_node_fs15.default.readFileSync(this.file, "utf8");
|
|
26781
|
+
const parsed = JSON.parse(raw);
|
|
26782
|
+
if (!Array.isArray(parsed)) {
|
|
26783
|
+
this.logger?.warn("OutboxStore.reload: outbox-caps.json is not an array; resetting", {
|
|
26784
|
+
file: this.file
|
|
26785
|
+
});
|
|
26786
|
+
this.cache = [];
|
|
26787
|
+
return;
|
|
26788
|
+
}
|
|
26789
|
+
const out = [];
|
|
26790
|
+
for (const item of parsed) {
|
|
26791
|
+
const r = OutboxCapEntrySchema.safeParse(item);
|
|
26792
|
+
if (r.success) out.push(r.data);
|
|
26793
|
+
}
|
|
26794
|
+
this.cache = out;
|
|
26795
|
+
} catch (err) {
|
|
26796
|
+
const code = err?.code;
|
|
26797
|
+
if (code !== "ENOENT") {
|
|
26798
|
+
this.logger?.warn("OutboxStore.reload failed; outbox-caps.json may be corrupt", {
|
|
26799
|
+
file: this.file,
|
|
26800
|
+
err: err.message
|
|
26801
|
+
});
|
|
26802
|
+
}
|
|
26803
|
+
this.cache = [];
|
|
26804
|
+
}
|
|
26805
|
+
}
|
|
26806
|
+
persist() {
|
|
26807
|
+
import_node_fs15.default.mkdirSync(import_node_path17.default.dirname(this.file), { recursive: true });
|
|
26808
|
+
const tmp = `${this.file}.tmp-${process.pid}-${Date.now()}`;
|
|
26809
|
+
import_node_fs15.default.writeFileSync(tmp, JSON.stringify(this.cache, null, 2), { mode: 384 });
|
|
26810
|
+
import_node_fs15.default.renameSync(tmp, this.file);
|
|
26811
|
+
}
|
|
26812
|
+
/** 列出当前缓存(含 revoked / 过期,UI Drawer 显灰;过滤靠调用方) */
|
|
26813
|
+
list() {
|
|
26814
|
+
return this.cache.slice();
|
|
26815
|
+
}
|
|
26816
|
+
/** 按 personaId / sessionId 过滤;为 outbox Drawer 服务 */
|
|
26817
|
+
listByPersona(personaId) {
|
|
26818
|
+
return this.cache.filter(
|
|
26819
|
+
(c) => c.scopeRef.kind === "persona" && c.scopeRef.personaId === personaId
|
|
26820
|
+
);
|
|
26821
|
+
}
|
|
26822
|
+
/**
|
|
26823
|
+
* Mint a new cap. capToken = 32B 随机 base64url(256-bit entropy),spec §11 #8 防剪贴板/微信
|
|
26824
|
+
* 截图泄露通过短码的暴力面。返回完整 entry 让 caller 拼 URL。
|
|
26825
|
+
*
|
|
26826
|
+
* @param ttlSeconds null = 永久;undefined = 默认 24h(spec §12)
|
|
26827
|
+
*/
|
|
26828
|
+
mint(input) {
|
|
26829
|
+
const ts = this.now();
|
|
26830
|
+
const ttl = input.ttlSeconds === null ? null : input.ttlSeconds ?? 24 * 3600;
|
|
26831
|
+
const entry = {
|
|
26832
|
+
capToken: import_node_crypto5.default.randomBytes(32).toString("base64url"),
|
|
26833
|
+
scopeRef: input.scopeRef,
|
|
26834
|
+
absPath: input.absPath,
|
|
26835
|
+
name: input.name,
|
|
26836
|
+
expiresAt: ttl === null ? null : ts + ttl * 1e3,
|
|
26837
|
+
oneShot: input.oneShot ?? false,
|
|
26838
|
+
scope: input.scope ?? { kind: "public" },
|
|
26839
|
+
hits: 0,
|
|
26840
|
+
createdAt: ts
|
|
26841
|
+
};
|
|
26842
|
+
this.cache.push(entry);
|
|
26843
|
+
this.persist();
|
|
26844
|
+
return entry;
|
|
26845
|
+
}
|
|
26846
|
+
/** Mark revoked;不删除条目(便于审计),上层 lookup 后返回 410 */
|
|
26847
|
+
revoke(capToken) {
|
|
26848
|
+
const idx = this.cache.findIndex((c) => c.capToken === capToken);
|
|
26849
|
+
if (idx < 0) return false;
|
|
26850
|
+
if (this.cache[idx].revoked) return false;
|
|
26851
|
+
this.cache[idx] = { ...this.cache[idx], revoked: true };
|
|
26852
|
+
this.persist();
|
|
26853
|
+
return true;
|
|
26854
|
+
}
|
|
26855
|
+
/**
|
|
26856
|
+
* HTTP 层用:根据 capToken 找到 entry 并判定能不能用。
|
|
26857
|
+
* 三种结果:
|
|
26858
|
+
* - { ok: true, entry } 可用,调用方流文件
|
|
26859
|
+
* - { ok: false, code: 'EXPIRED' | 'REVOKED' | 'NOT_FOUND' }
|
|
26860
|
+
*
|
|
26861
|
+
* 命中 + oneShot=true 时由调用方调 consume() 把 cache 中 hits 自增并立即 revoke
|
|
26862
|
+
* (consume 应当在 response 'finish' 后才触发,避免中途断流误算消费)。
|
|
26863
|
+
*/
|
|
26864
|
+
lookup(capToken) {
|
|
26865
|
+
const entry = this.cache.find((c) => c.capToken === capToken);
|
|
26866
|
+
if (!entry) return { ok: false, code: "NOT_FOUND" };
|
|
26867
|
+
if (entry.revoked) return { ok: false, code: "REVOKED" };
|
|
26868
|
+
if (entry.expiresAt !== null && this.now() > entry.expiresAt) {
|
|
26869
|
+
return { ok: false, code: "EXPIRED" };
|
|
26870
|
+
}
|
|
26871
|
+
return { ok: true, entry };
|
|
26872
|
+
}
|
|
26873
|
+
/** 命中后真正"消费"一次:hits++ + oneShot 时 revoke。daemon HTTP 层 stream 成功后再调 */
|
|
26874
|
+
consume(capToken) {
|
|
26875
|
+
const idx = this.cache.findIndex((c) => c.capToken === capToken);
|
|
26876
|
+
if (idx < 0) return;
|
|
26877
|
+
const prev = this.cache[idx];
|
|
26878
|
+
this.cache[idx] = {
|
|
26879
|
+
...prev,
|
|
26880
|
+
hits: prev.hits + 1,
|
|
26881
|
+
revoked: prev.oneShot ? true : prev.revoked
|
|
26882
|
+
};
|
|
26883
|
+
this.persist();
|
|
26884
|
+
}
|
|
26885
|
+
};
|
|
26886
|
+
|
|
26887
|
+
// src/attachment/mount.ts
|
|
26888
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
26889
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
26890
|
+
init_protocol();
|
|
26891
|
+
var MountStore = class {
|
|
26892
|
+
constructor(opts) {
|
|
26893
|
+
this.opts = opts;
|
|
26894
|
+
}
|
|
26895
|
+
opts;
|
|
26896
|
+
filePath(personaId) {
|
|
26897
|
+
return import_node_path18.default.join(this.opts.personaDirPath(personaId), ".clawd", "shared-files.json");
|
|
26898
|
+
}
|
|
26899
|
+
list(personaId) {
|
|
26900
|
+
try {
|
|
26901
|
+
const raw = import_node_fs16.default.readFileSync(this.filePath(personaId), "utf8");
|
|
26902
|
+
const parsed = JSON.parse(raw);
|
|
26903
|
+
if (!Array.isArray(parsed)) return [];
|
|
26904
|
+
const out = [];
|
|
26905
|
+
for (const item of parsed) {
|
|
26906
|
+
const r = MountEntrySchema.safeParse(item);
|
|
26907
|
+
if (r.success) out.push(r.data);
|
|
26908
|
+
}
|
|
26909
|
+
return out;
|
|
26910
|
+
} catch {
|
|
26911
|
+
return [];
|
|
26912
|
+
}
|
|
26913
|
+
}
|
|
26914
|
+
/**
|
|
26915
|
+
* Add a mount。在 personaDir 创建 link/copy + 写 manifest。冲突 basename 抛错(caller
|
|
26916
|
+
* 决定是覆盖还是改名)。
|
|
26917
|
+
*/
|
|
26918
|
+
add(personaId, input) {
|
|
26919
|
+
const personaDir = this.opts.personaDirPath(personaId);
|
|
26920
|
+
if (!import_node_fs16.default.existsSync(personaDir)) {
|
|
26921
|
+
throw new Error(`personaDir not found: ${personaDir}`);
|
|
26922
|
+
}
|
|
26923
|
+
const source = import_node_path18.default.resolve(input.absPath);
|
|
26924
|
+
if (!import_node_fs16.default.existsSync(source)) {
|
|
26925
|
+
throw new Error(`source path not found: ${source}`);
|
|
26926
|
+
}
|
|
26927
|
+
const basename = import_node_path18.default.basename(source);
|
|
26928
|
+
const dest = import_node_path18.default.join(personaDir, basename);
|
|
26929
|
+
if (import_node_fs16.default.existsSync(dest)) {
|
|
26930
|
+
throw new Error(`destination already exists: ${dest}`);
|
|
26931
|
+
}
|
|
26932
|
+
if (input.mode === "link") {
|
|
26933
|
+
import_node_fs16.default.symlinkSync(source, dest);
|
|
26934
|
+
} else {
|
|
26935
|
+
import_node_fs16.default.copyFileSync(source, dest);
|
|
26936
|
+
}
|
|
26937
|
+
const entry = {
|
|
26938
|
+
basename,
|
|
26939
|
+
absPath: source,
|
|
26940
|
+
mode: input.mode
|
|
26941
|
+
};
|
|
26942
|
+
const next = this.list(personaId).concat([entry]);
|
|
26943
|
+
this.writeManifest(personaId, next);
|
|
26944
|
+
return entry;
|
|
26945
|
+
}
|
|
26946
|
+
remove(personaId, basename) {
|
|
26947
|
+
const entries = this.list(personaId);
|
|
26948
|
+
const idx = entries.findIndex((e) => e.basename === basename);
|
|
26949
|
+
if (idx < 0) return false;
|
|
26950
|
+
const entry = entries[idx];
|
|
26951
|
+
const personaDir = this.opts.personaDirPath(personaId);
|
|
26952
|
+
const dest = import_node_path18.default.join(personaDir, basename);
|
|
26953
|
+
try {
|
|
26954
|
+
import_node_fs16.default.unlinkSync(dest);
|
|
26955
|
+
} catch {
|
|
26956
|
+
}
|
|
26957
|
+
const next = entries.slice();
|
|
26958
|
+
next.splice(idx, 1);
|
|
26959
|
+
this.writeManifest(personaId, next);
|
|
26960
|
+
void entry;
|
|
26961
|
+
return true;
|
|
26962
|
+
}
|
|
26963
|
+
writeManifest(personaId, entries) {
|
|
26964
|
+
const file = this.filePath(personaId);
|
|
26965
|
+
import_node_fs16.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true });
|
|
26966
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
26967
|
+
import_node_fs16.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
26968
|
+
import_node_fs16.default.renameSync(tmp, file);
|
|
26969
|
+
}
|
|
26970
|
+
};
|
|
26971
|
+
|
|
26972
|
+
// src/discovery/state-file.ts
|
|
26973
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
26974
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
26048
26975
|
function defaultStateFilePath(dataDir) {
|
|
26049
|
-
return
|
|
26976
|
+
return import_node_path19.default.join(dataDir, "state.json");
|
|
26050
26977
|
}
|
|
26051
26978
|
function isPidAlive(pid) {
|
|
26052
26979
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -26068,7 +26995,7 @@ var StateFileManager = class {
|
|
|
26068
26995
|
}
|
|
26069
26996
|
read() {
|
|
26070
26997
|
try {
|
|
26071
|
-
const raw =
|
|
26998
|
+
const raw = import_node_fs17.default.readFileSync(this.file, "utf8");
|
|
26072
26999
|
const parsed = JSON.parse(raw);
|
|
26073
27000
|
return parsed;
|
|
26074
27001
|
} catch {
|
|
@@ -26082,34 +27009,34 @@ var StateFileManager = class {
|
|
|
26082
27009
|
return { status: "stale", existing };
|
|
26083
27010
|
}
|
|
26084
27011
|
write(state) {
|
|
26085
|
-
|
|
27012
|
+
import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
|
|
26086
27013
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
26087
|
-
|
|
26088
|
-
|
|
27014
|
+
import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
27015
|
+
import_node_fs17.default.renameSync(tmp, this.file);
|
|
26089
27016
|
if (process.platform !== "win32") {
|
|
26090
27017
|
try {
|
|
26091
|
-
|
|
27018
|
+
import_node_fs17.default.chmodSync(this.file, 384);
|
|
26092
27019
|
} catch {
|
|
26093
27020
|
}
|
|
26094
27021
|
}
|
|
26095
27022
|
}
|
|
26096
27023
|
delete() {
|
|
26097
27024
|
try {
|
|
26098
|
-
|
|
27025
|
+
import_node_fs17.default.unlinkSync(this.file);
|
|
26099
27026
|
} catch {
|
|
26100
27027
|
}
|
|
26101
27028
|
}
|
|
26102
27029
|
};
|
|
26103
27030
|
|
|
26104
27031
|
// src/tunnel/tunnel-manager.ts
|
|
26105
|
-
var
|
|
26106
|
-
var
|
|
26107
|
-
var
|
|
27032
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
27033
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
27034
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
26108
27035
|
var import_node_child_process5 = require("child_process");
|
|
26109
27036
|
|
|
26110
27037
|
// src/tunnel/tunnel-store.ts
|
|
26111
|
-
var
|
|
26112
|
-
var
|
|
27038
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
27039
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
26113
27040
|
var TunnelStore = class {
|
|
26114
27041
|
constructor(filePath) {
|
|
26115
27042
|
this.filePath = filePath;
|
|
@@ -26117,7 +27044,7 @@ var TunnelStore = class {
|
|
|
26117
27044
|
filePath;
|
|
26118
27045
|
async get() {
|
|
26119
27046
|
try {
|
|
26120
|
-
const raw = await
|
|
27047
|
+
const raw = await import_node_fs18.default.promises.readFile(this.filePath, "utf8");
|
|
26121
27048
|
const obj = JSON.parse(raw);
|
|
26122
27049
|
if (!isPersistedTunnel(obj)) return null;
|
|
26123
27050
|
return obj;
|
|
@@ -26128,22 +27055,22 @@ var TunnelStore = class {
|
|
|
26128
27055
|
}
|
|
26129
27056
|
}
|
|
26130
27057
|
async set(v2) {
|
|
26131
|
-
const dir =
|
|
26132
|
-
await
|
|
27058
|
+
const dir = import_node_path20.default.dirname(this.filePath);
|
|
27059
|
+
await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
|
|
26133
27060
|
const data = JSON.stringify(v2, null, 2);
|
|
26134
27061
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
26135
|
-
await
|
|
27062
|
+
await import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
26136
27063
|
if (process.platform !== "win32") {
|
|
26137
27064
|
try {
|
|
26138
|
-
await
|
|
27065
|
+
await import_node_fs18.default.promises.chmod(tmp, 384);
|
|
26139
27066
|
} catch {
|
|
26140
27067
|
}
|
|
26141
27068
|
}
|
|
26142
|
-
await
|
|
27069
|
+
await import_node_fs18.default.promises.rename(tmp, this.filePath);
|
|
26143
27070
|
}
|
|
26144
27071
|
async clear() {
|
|
26145
27072
|
try {
|
|
26146
|
-
await
|
|
27073
|
+
await import_node_fs18.default.promises.unlink(this.filePath);
|
|
26147
27074
|
} catch (err) {
|
|
26148
27075
|
const code = err?.code;
|
|
26149
27076
|
if (code !== "ENOENT") throw err;
|
|
@@ -26238,9 +27165,9 @@ function escape(v2) {
|
|
|
26238
27165
|
}
|
|
26239
27166
|
|
|
26240
27167
|
// src/tunnel/frpc-binary.ts
|
|
26241
|
-
var
|
|
27168
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
26242
27169
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
26243
|
-
var
|
|
27170
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
26244
27171
|
var import_node_child_process3 = require("child_process");
|
|
26245
27172
|
var import_node_stream2 = require("stream");
|
|
26246
27173
|
var import_promises = require("stream/promises");
|
|
@@ -26272,20 +27199,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
26272
27199
|
}
|
|
26273
27200
|
async function ensureFrpcBinary(opts) {
|
|
26274
27201
|
if (opts.override) {
|
|
26275
|
-
if (!
|
|
27202
|
+
if (!import_node_fs19.default.existsSync(opts.override)) {
|
|
26276
27203
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
26277
27204
|
}
|
|
26278
27205
|
return opts.override;
|
|
26279
27206
|
}
|
|
26280
27207
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
26281
27208
|
const platform = opts.platform ?? detectPlatform();
|
|
26282
|
-
const binDir =
|
|
26283
|
-
|
|
27209
|
+
const binDir = import_node_path21.default.join(opts.dataDir, "bin");
|
|
27210
|
+
import_node_fs19.default.mkdirSync(binDir, { recursive: true });
|
|
26284
27211
|
cleanupStaleArtifacts(binDir);
|
|
26285
|
-
const stableBin =
|
|
26286
|
-
if (
|
|
27212
|
+
const stableBin = import_node_path21.default.join(binDir, "frpc");
|
|
27213
|
+
if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
|
|
26287
27214
|
const partialBin = `${stableBin}.partial`;
|
|
26288
|
-
const tarballPath =
|
|
27215
|
+
const tarballPath = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
26289
27216
|
try {
|
|
26290
27217
|
const url = frpcDownloadUrl(version2, platform);
|
|
26291
27218
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -26294,8 +27221,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
26294
27221
|
} else {
|
|
26295
27222
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
26296
27223
|
}
|
|
26297
|
-
|
|
26298
|
-
|
|
27224
|
+
import_node_fs19.default.chmodSync(partialBin, 493);
|
|
27225
|
+
import_node_fs19.default.renameSync(partialBin, stableBin);
|
|
26299
27226
|
} finally {
|
|
26300
27227
|
safeUnlink(tarballPath);
|
|
26301
27228
|
safeUnlink(partialBin);
|
|
@@ -26305,15 +27232,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
26305
27232
|
function cleanupStaleArtifacts(binDir) {
|
|
26306
27233
|
let entries;
|
|
26307
27234
|
try {
|
|
26308
|
-
entries =
|
|
27235
|
+
entries = import_node_fs19.default.readdirSync(binDir);
|
|
26309
27236
|
} catch {
|
|
26310
27237
|
return;
|
|
26311
27238
|
}
|
|
26312
27239
|
for (const name of entries) {
|
|
26313
27240
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
26314
|
-
const full =
|
|
27241
|
+
const full = import_node_path21.default.join(binDir, name);
|
|
26315
27242
|
try {
|
|
26316
|
-
|
|
27243
|
+
import_node_fs19.default.rmSync(full, { recursive: true, force: true });
|
|
26317
27244
|
} catch {
|
|
26318
27245
|
}
|
|
26319
27246
|
}
|
|
@@ -26321,7 +27248,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
26321
27248
|
}
|
|
26322
27249
|
function safeUnlink(p2) {
|
|
26323
27250
|
try {
|
|
26324
|
-
|
|
27251
|
+
import_node_fs19.default.unlinkSync(p2);
|
|
26325
27252
|
} catch {
|
|
26326
27253
|
}
|
|
26327
27254
|
}
|
|
@@ -26332,13 +27259,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
26332
27259
|
if (!res.ok || !res.body) {
|
|
26333
27260
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
26334
27261
|
}
|
|
26335
|
-
const out =
|
|
27262
|
+
const out = import_node_fs19.default.createWriteStream(dest);
|
|
26336
27263
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
26337
27264
|
await (0, import_promises.pipeline)(nodeStream, out);
|
|
26338
27265
|
}
|
|
26339
27266
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
26340
|
-
const work =
|
|
26341
|
-
|
|
27267
|
+
const work = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
27268
|
+
import_node_fs19.default.mkdirSync(work, { recursive: true });
|
|
26342
27269
|
try {
|
|
26343
27270
|
await new Promise((resolve2, reject) => {
|
|
26344
27271
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -26346,32 +27273,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
26346
27273
|
proc.on("exit", (code) => code === 0 ? resolve2() : reject(new Error(`tar exited ${code}`)));
|
|
26347
27274
|
});
|
|
26348
27275
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
26349
|
-
const src =
|
|
26350
|
-
if (!
|
|
27276
|
+
const src = import_node_path21.default.join(work, dirName, "frpc");
|
|
27277
|
+
if (!import_node_fs19.default.existsSync(src)) {
|
|
26351
27278
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
26352
27279
|
}
|
|
26353
|
-
|
|
27280
|
+
import_node_fs19.default.copyFileSync(src, destBin);
|
|
26354
27281
|
} finally {
|
|
26355
|
-
|
|
27282
|
+
import_node_fs19.default.rmSync(work, { recursive: true, force: true });
|
|
26356
27283
|
}
|
|
26357
27284
|
}
|
|
26358
27285
|
|
|
26359
27286
|
// src/tunnel/frpc-process.ts
|
|
26360
|
-
var
|
|
26361
|
-
var
|
|
27287
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
27288
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
26362
27289
|
var import_node_child_process4 = require("child_process");
|
|
26363
27290
|
function frpcPidFilePath(dataDir) {
|
|
26364
|
-
return
|
|
27291
|
+
return import_node_path22.default.join(dataDir, "frpc.pid");
|
|
26365
27292
|
}
|
|
26366
27293
|
function writeFrpcPid(dataDir, pid) {
|
|
26367
27294
|
try {
|
|
26368
|
-
|
|
27295
|
+
import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
26369
27296
|
} catch {
|
|
26370
27297
|
}
|
|
26371
27298
|
}
|
|
26372
27299
|
function clearFrpcPid(dataDir) {
|
|
26373
27300
|
try {
|
|
26374
|
-
|
|
27301
|
+
import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
26375
27302
|
} catch {
|
|
26376
27303
|
}
|
|
26377
27304
|
}
|
|
@@ -26387,7 +27314,7 @@ function defaultIsPidAlive(pid) {
|
|
|
26387
27314
|
}
|
|
26388
27315
|
function defaultReadPidFile(file) {
|
|
26389
27316
|
try {
|
|
26390
|
-
return
|
|
27317
|
+
return import_node_fs20.default.readFileSync(file, "utf8");
|
|
26391
27318
|
} catch {
|
|
26392
27319
|
return null;
|
|
26393
27320
|
}
|
|
@@ -26403,7 +27330,7 @@ function defaultSleep(ms) {
|
|
|
26403
27330
|
}
|
|
26404
27331
|
async function killStaleFrpc(deps) {
|
|
26405
27332
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
26406
|
-
const tomlPath =
|
|
27333
|
+
const tomlPath = import_node_path22.default.join(deps.dataDir, "frpc.toml");
|
|
26407
27334
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
26408
27335
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
26409
27336
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -26427,7 +27354,7 @@ async function killStaleFrpc(deps) {
|
|
|
26427
27354
|
}
|
|
26428
27355
|
if (victims.size === 0) {
|
|
26429
27356
|
try {
|
|
26430
|
-
|
|
27357
|
+
import_node_fs20.default.unlinkSync(pidFile);
|
|
26431
27358
|
} catch {
|
|
26432
27359
|
}
|
|
26433
27360
|
return;
|
|
@@ -26438,7 +27365,7 @@ async function killStaleFrpc(deps) {
|
|
|
26438
27365
|
}
|
|
26439
27366
|
await sleep(deps.reapWaitMs ?? 300);
|
|
26440
27367
|
try {
|
|
26441
|
-
|
|
27368
|
+
import_node_fs20.default.unlinkSync(pidFile);
|
|
26442
27369
|
} catch {
|
|
26443
27370
|
}
|
|
26444
27371
|
}
|
|
@@ -26475,7 +27402,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
26475
27402
|
var TunnelManager = class {
|
|
26476
27403
|
constructor(deps) {
|
|
26477
27404
|
this.deps = deps;
|
|
26478
|
-
this.store = deps.store ?? new TunnelStore(
|
|
27405
|
+
this.store = deps.store ?? new TunnelStore(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
|
|
26479
27406
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
26480
27407
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
26481
27408
|
}
|
|
@@ -26602,8 +27529,8 @@ var TunnelManager = class {
|
|
|
26602
27529
|
dataDir: this.deps.dataDir,
|
|
26603
27530
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
26604
27531
|
});
|
|
26605
|
-
const tomlPath =
|
|
26606
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
27532
|
+
const tomlPath = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
|
|
27533
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto6.default.randomBytes(3).toString("hex")}`;
|
|
26607
27534
|
const toml = buildFrpcToml({
|
|
26608
27535
|
serverAddr: t.frpsHost,
|
|
26609
27536
|
serverPort: t.frpsPort,
|
|
@@ -26613,12 +27540,12 @@ var TunnelManager = class {
|
|
|
26613
27540
|
localPort,
|
|
26614
27541
|
logLevel: "info"
|
|
26615
27542
|
});
|
|
26616
|
-
await
|
|
27543
|
+
await import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
26617
27544
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
26618
27545
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26619
27546
|
});
|
|
26620
|
-
const logFilePath =
|
|
26621
|
-
const logStream =
|
|
27547
|
+
const logFilePath = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
|
|
27548
|
+
const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
26622
27549
|
logStream.on("error", () => {
|
|
26623
27550
|
});
|
|
26624
27551
|
const tee = (chunk) => {
|
|
@@ -26701,22 +27628,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
26701
27628
|
|
|
26702
27629
|
// src/tunnel/device-key.ts
|
|
26703
27630
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
26704
|
-
var
|
|
27631
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
26705
27632
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
26706
27633
|
function deriveStableDeviceKey(opts = {}) {
|
|
26707
27634
|
const hostname = opts.hostname ?? import_node_os10.default.hostname();
|
|
26708
27635
|
const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
|
|
26709
27636
|
const input = `${hostname}::${uid}`;
|
|
26710
|
-
return
|
|
27637
|
+
return import_node_crypto7.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
26711
27638
|
}
|
|
26712
27639
|
|
|
26713
27640
|
// src/auth-store.ts
|
|
26714
|
-
var
|
|
26715
|
-
var
|
|
26716
|
-
var
|
|
27641
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
27642
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
27643
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
26717
27644
|
var AUTH_FILE_NAME = "auth.json";
|
|
26718
27645
|
function authFilePath(dataDir) {
|
|
26719
|
-
return
|
|
27646
|
+
return import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
|
|
26720
27647
|
}
|
|
26721
27648
|
function loadOrCreateAuthToken(opts) {
|
|
26722
27649
|
const file = authFilePath(opts.dataDir);
|
|
@@ -26728,11 +27655,11 @@ function loadOrCreateAuthToken(opts) {
|
|
|
26728
27655
|
return token;
|
|
26729
27656
|
}
|
|
26730
27657
|
function defaultGenerate() {
|
|
26731
|
-
return
|
|
27658
|
+
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
26732
27659
|
}
|
|
26733
27660
|
function readAuthFile(file) {
|
|
26734
27661
|
try {
|
|
26735
|
-
const raw =
|
|
27662
|
+
const raw = import_node_fs22.default.readFileSync(file, "utf8");
|
|
26736
27663
|
const parsed = JSON.parse(raw);
|
|
26737
27664
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
26738
27665
|
return {
|
|
@@ -26748,25 +27675,25 @@ function readAuthFile(file) {
|
|
|
26748
27675
|
}
|
|
26749
27676
|
}
|
|
26750
27677
|
function writeAuthFile(file, content) {
|
|
26751
|
-
|
|
26752
|
-
|
|
27678
|
+
import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
|
|
27679
|
+
import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
26753
27680
|
try {
|
|
26754
|
-
|
|
27681
|
+
import_node_fs22.default.chmodSync(file, 384);
|
|
26755
27682
|
} catch {
|
|
26756
27683
|
}
|
|
26757
27684
|
}
|
|
26758
27685
|
|
|
26759
27686
|
// src/owner-profile.ts
|
|
26760
|
-
var
|
|
27687
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
26761
27688
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
26762
|
-
var
|
|
27689
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
26763
27690
|
var PROFILE_FILENAME = "profile.json";
|
|
26764
27691
|
function loadOwnerDisplayName(dataDir) {
|
|
26765
27692
|
const fallback = import_node_os11.default.userInfo().username;
|
|
26766
|
-
const profilePath =
|
|
27693
|
+
const profilePath = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
|
|
26767
27694
|
let raw;
|
|
26768
27695
|
try {
|
|
26769
|
-
raw =
|
|
27696
|
+
raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
|
|
26770
27697
|
} catch {
|
|
26771
27698
|
return fallback;
|
|
26772
27699
|
}
|
|
@@ -26795,12 +27722,12 @@ init_protocol();
|
|
|
26795
27722
|
init_protocol();
|
|
26796
27723
|
|
|
26797
27724
|
// src/session/fork.ts
|
|
26798
|
-
var
|
|
27725
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
26799
27726
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
26800
|
-
var
|
|
27727
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
26801
27728
|
init_claude_history();
|
|
26802
27729
|
function readJsonlEntries(file) {
|
|
26803
|
-
const raw =
|
|
27730
|
+
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
26804
27731
|
const out = [];
|
|
26805
27732
|
for (const line of raw.split("\n")) {
|
|
26806
27733
|
const t = line.trim();
|
|
@@ -26813,10 +27740,10 @@ function readJsonlEntries(file) {
|
|
|
26813
27740
|
return out;
|
|
26814
27741
|
}
|
|
26815
27742
|
function forkSession(input) {
|
|
26816
|
-
const baseDir = input.baseDir ??
|
|
26817
|
-
const projectDir =
|
|
26818
|
-
const sourceFile =
|
|
26819
|
-
if (!
|
|
27743
|
+
const baseDir = input.baseDir ?? import_node_path26.default.join(import_node_os12.default.homedir(), ".claude");
|
|
27744
|
+
const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
27745
|
+
const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
27746
|
+
if (!import_node_fs24.default.existsSync(sourceFile)) {
|
|
26820
27747
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
26821
27748
|
}
|
|
26822
27749
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -26846,9 +27773,9 @@ function forkSession(input) {
|
|
|
26846
27773
|
}
|
|
26847
27774
|
forkedLines.push(JSON.stringify(forked));
|
|
26848
27775
|
}
|
|
26849
|
-
const forkedFilePath =
|
|
26850
|
-
|
|
26851
|
-
|
|
27776
|
+
const forkedFilePath = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
27777
|
+
import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
|
|
27778
|
+
import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
26852
27779
|
return { forkedToolSessionId, forkedFilePath };
|
|
26853
27780
|
}
|
|
26854
27781
|
|
|
@@ -27169,9 +28096,9 @@ init_protocol();
|
|
|
27169
28096
|
|
|
27170
28097
|
// src/workspace/git.ts
|
|
27171
28098
|
var import_node_child_process6 = require("child_process");
|
|
27172
|
-
var
|
|
28099
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
27173
28100
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
27174
|
-
var
|
|
28101
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
27175
28102
|
var import_node_util = require("util");
|
|
27176
28103
|
var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
|
|
27177
28104
|
function formatChildProcessError(err) {
|
|
@@ -27186,9 +28113,9 @@ function formatChildProcessError(err) {
|
|
|
27186
28113
|
return e.message ?? "unknown error";
|
|
27187
28114
|
}
|
|
27188
28115
|
function normalizePath(p2) {
|
|
27189
|
-
const resolved =
|
|
28116
|
+
const resolved = import_node_path27.default.resolve(p2);
|
|
27190
28117
|
try {
|
|
27191
|
-
return
|
|
28118
|
+
return import_node_fs25.default.realpathSync(resolved);
|
|
27192
28119
|
} catch {
|
|
27193
28120
|
return resolved;
|
|
27194
28121
|
}
|
|
@@ -27289,13 +28216,13 @@ function flattenToDirName(branch) {
|
|
|
27289
28216
|
}
|
|
27290
28217
|
function encodeClaudeProjectDir(absPath) {
|
|
27291
28218
|
if (!absPath || typeof absPath !== "string") return "";
|
|
27292
|
-
let canonical =
|
|
28219
|
+
let canonical = import_node_path27.default.resolve(absPath);
|
|
27293
28220
|
try {
|
|
27294
|
-
canonical =
|
|
28221
|
+
canonical = import_node_fs25.default.realpathSync(canonical);
|
|
27295
28222
|
} catch {
|
|
27296
28223
|
try {
|
|
27297
|
-
const parent =
|
|
27298
|
-
canonical =
|
|
28224
|
+
const parent = import_node_fs25.default.realpathSync(import_node_path27.default.dirname(canonical));
|
|
28225
|
+
canonical = import_node_path27.default.join(parent, import_node_path27.default.basename(canonical));
|
|
27299
28226
|
} catch {
|
|
27300
28227
|
}
|
|
27301
28228
|
}
|
|
@@ -27319,11 +28246,11 @@ async function createWorktree(input) {
|
|
|
27319
28246
|
if (!isGitRoot) {
|
|
27320
28247
|
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
27321
28248
|
}
|
|
27322
|
-
const parent =
|
|
27323
|
-
if (parent === "/" || parent ===
|
|
28249
|
+
const parent = import_node_path27.default.dirname(import_node_path27.default.resolve(cwd));
|
|
28250
|
+
if (parent === "/" || parent === import_node_path27.default.resolve(cwd)) {
|
|
27324
28251
|
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
27325
28252
|
}
|
|
27326
|
-
const worktreeRoot =
|
|
28253
|
+
const worktreeRoot = import_node_path27.default.join(parent, dirName);
|
|
27327
28254
|
try {
|
|
27328
28255
|
await pexec("git", ["-C", cwd, "fetch", "origin", baseBranch, "--no-tags"], {
|
|
27329
28256
|
timeout: 3e4
|
|
@@ -27342,7 +28269,7 @@ async function createWorktree(input) {
|
|
|
27342
28269
|
const msg = err.message;
|
|
27343
28270
|
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
27344
28271
|
}
|
|
27345
|
-
if (
|
|
28272
|
+
if (import_node_fs25.default.existsSync(worktreeRoot)) {
|
|
27346
28273
|
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
27347
28274
|
}
|
|
27348
28275
|
try {
|
|
@@ -27370,8 +28297,8 @@ async function removeWorktree(input) {
|
|
|
27370
28297
|
);
|
|
27371
28298
|
const gitCommonDir = stdout.trim();
|
|
27372
28299
|
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
27373
|
-
const absGitCommon =
|
|
27374
|
-
repoRoot =
|
|
28300
|
+
const absGitCommon = import_node_path27.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path27.default.resolve(worktreeRoot, gitCommonDir);
|
|
28301
|
+
repoRoot = import_node_path27.default.dirname(absGitCommon);
|
|
27375
28302
|
} catch {
|
|
27376
28303
|
repoRoot = null;
|
|
27377
28304
|
}
|
|
@@ -27383,7 +28310,7 @@ async function removeWorktree(input) {
|
|
|
27383
28310
|
} catch (err) {
|
|
27384
28311
|
const stderr = err.stderr ?? "";
|
|
27385
28312
|
const lower = stderr.toLowerCase();
|
|
27386
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !
|
|
28313
|
+
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs25.default.existsSync(worktreeRoot);
|
|
27387
28314
|
if (!vanished) {
|
|
27388
28315
|
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
27389
28316
|
}
|
|
@@ -27402,10 +28329,10 @@ async function removeWorktree(input) {
|
|
|
27402
28329
|
try {
|
|
27403
28330
|
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
27404
28331
|
if (encoded) {
|
|
27405
|
-
const projectsRoot =
|
|
27406
|
-
const target =
|
|
27407
|
-
if (target.startsWith(projectsRoot +
|
|
27408
|
-
|
|
28332
|
+
const projectsRoot = import_node_path27.default.join(import_node_os13.default.homedir(), ".claude", "projects");
|
|
28333
|
+
const target = import_node_path27.default.resolve(projectsRoot, encoded);
|
|
28334
|
+
if (target.startsWith(projectsRoot + import_node_path27.default.sep) && target !== projectsRoot) {
|
|
28335
|
+
import_node_fs25.default.rmSync(target, { recursive: true, force: true });
|
|
27409
28336
|
}
|
|
27410
28337
|
}
|
|
27411
28338
|
} catch {
|
|
@@ -27484,7 +28411,7 @@ init_protocol();
|
|
|
27484
28411
|
var version = "0.2.6".length > 0 ? "0.2.6" : "dev";
|
|
27485
28412
|
|
|
27486
28413
|
// src/handlers/meta.ts
|
|
27487
|
-
function buildReadyFrame(deps) {
|
|
28414
|
+
function buildReadyFrame(deps, client) {
|
|
27488
28415
|
const info = deps.manager.info();
|
|
27489
28416
|
const tools = [];
|
|
27490
28417
|
for (const id of listRegistered()) {
|
|
@@ -27496,6 +28423,14 @@ function buildReadyFrame(deps) {
|
|
|
27496
28423
|
}
|
|
27497
28424
|
}
|
|
27498
28425
|
const tunnelUrl = deps.getTunnelUrl ? deps.getTunnelUrl() : null;
|
|
28426
|
+
const fileSharing = {};
|
|
28427
|
+
const httpBaseUrl = deps.getHttpBaseUrl ? deps.getHttpBaseUrl() : null;
|
|
28428
|
+
if (httpBaseUrl) {
|
|
28429
|
+
fileSharing.tokenRole = "owner";
|
|
28430
|
+
fileSharing.isLoopback = isLoopbackAddr(client?.remoteAddress);
|
|
28431
|
+
fileSharing.httpBaseUrl = httpBaseUrl;
|
|
28432
|
+
if (deps.httpToken) fileSharing.httpToken = deps.httpToken;
|
|
28433
|
+
}
|
|
27499
28434
|
return {
|
|
27500
28435
|
version,
|
|
27501
28436
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -27504,7 +28439,8 @@ function buildReadyFrame(deps) {
|
|
|
27504
28439
|
tools,
|
|
27505
28440
|
runningSessions: info.runningSessions,
|
|
27506
28441
|
tunnelUrl,
|
|
27507
|
-
mode: deps.mode
|
|
28442
|
+
mode: deps.mode,
|
|
28443
|
+
...fileSharing
|
|
27508
28444
|
};
|
|
27509
28445
|
}
|
|
27510
28446
|
function buildMetaHandlers(deps) {
|
|
@@ -27615,6 +28551,200 @@ function buildPersonaHandlers(deps) {
|
|
|
27615
28551
|
};
|
|
27616
28552
|
}
|
|
27617
28553
|
|
|
28554
|
+
// src/handlers/attachment.ts
|
|
28555
|
+
init_protocol();
|
|
28556
|
+
init_protocol();
|
|
28557
|
+
function buildAttachmentHandlers(deps) {
|
|
28558
|
+
const outboxCreate = async (frame) => {
|
|
28559
|
+
const parsed = AttachmentOutboxCreateArgs.safeParse(frame);
|
|
28560
|
+
if (!parsed.success) {
|
|
28561
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28562
|
+
}
|
|
28563
|
+
const args = parsed.data;
|
|
28564
|
+
if (args.personaId && args.sessionId || !args.personaId && !args.sessionId) {
|
|
28565
|
+
throw new ClawdError(
|
|
28566
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
28567
|
+
"outboxCreate requires exactly one of personaId / sessionId"
|
|
28568
|
+
);
|
|
28569
|
+
}
|
|
28570
|
+
const scopeRef = deps.getPersonaScopeForRequest(args);
|
|
28571
|
+
if (!scopeRef) {
|
|
28572
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "invalid scope");
|
|
28573
|
+
}
|
|
28574
|
+
const name = args.absPath.split("/").pop() || args.absPath;
|
|
28575
|
+
const entry = deps.outboxStore.mint({
|
|
28576
|
+
scopeRef,
|
|
28577
|
+
absPath: args.absPath,
|
|
28578
|
+
name,
|
|
28579
|
+
ttlSeconds: args.ttlSeconds,
|
|
28580
|
+
oneShot: args.oneShot,
|
|
28581
|
+
scope: args.scope
|
|
28582
|
+
});
|
|
28583
|
+
const httpBaseUrl = deps.getHttpBaseUrl();
|
|
28584
|
+
const urlPath = scopeRef.kind === "persona" ? `/persona/${encodeURIComponent(scopeRef.personaId)}/outbox/${entry.capToken}` : `/outbox/${entry.capToken}`;
|
|
28585
|
+
const url = httpBaseUrl ? `${httpBaseUrl}${urlPath}` : urlPath;
|
|
28586
|
+
return {
|
|
28587
|
+
response: {
|
|
28588
|
+
type: "attachment.outboxCreate",
|
|
28589
|
+
capToken: entry.capToken,
|
|
28590
|
+
url,
|
|
28591
|
+
expiresAt: entry.expiresAt
|
|
28592
|
+
}
|
|
28593
|
+
};
|
|
28594
|
+
};
|
|
28595
|
+
const outboxRevoke = async (frame) => {
|
|
28596
|
+
const parsed = AttachmentOutboxRevokeArgs.safeParse(frame);
|
|
28597
|
+
if (!parsed.success) {
|
|
28598
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28599
|
+
}
|
|
28600
|
+
const ok = deps.outboxStore.revoke(parsed.data.capToken);
|
|
28601
|
+
if (!ok) {
|
|
28602
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `capToken not found / already revoked`);
|
|
28603
|
+
}
|
|
28604
|
+
return { response: { type: "attachment.outboxRevoke", revoked: true } };
|
|
28605
|
+
};
|
|
28606
|
+
const outboxList = async (frame) => {
|
|
28607
|
+
const parsed = AttachmentOutboxListArgs.safeParse(frame);
|
|
28608
|
+
if (!parsed.success) {
|
|
28609
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28610
|
+
}
|
|
28611
|
+
const entries = parsed.data.personaId ? deps.outboxStore.listByPersona(parsed.data.personaId) : deps.outboxStore.list();
|
|
28612
|
+
return {
|
|
28613
|
+
response: { type: "attachment.outboxList", entries }
|
|
28614
|
+
};
|
|
28615
|
+
};
|
|
28616
|
+
const mountAdd = async (frame) => {
|
|
28617
|
+
if (!deps.mountStore) {
|
|
28618
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28619
|
+
}
|
|
28620
|
+
const parsed = AttachmentMountAddArgs.safeParse(frame);
|
|
28621
|
+
if (!parsed.success) {
|
|
28622
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28623
|
+
}
|
|
28624
|
+
const args = parsed.data;
|
|
28625
|
+
try {
|
|
28626
|
+
const entry = deps.mountStore.add(args.personaId, {
|
|
28627
|
+
absPath: args.absPath,
|
|
28628
|
+
mode: args.mode
|
|
28629
|
+
});
|
|
28630
|
+
return {
|
|
28631
|
+
response: {
|
|
28632
|
+
type: "attachment.mountAdd",
|
|
28633
|
+
entry,
|
|
28634
|
+
sandboxRestartNeeded: true
|
|
28635
|
+
}
|
|
28636
|
+
};
|
|
28637
|
+
} catch (err) {
|
|
28638
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, err.message);
|
|
28639
|
+
}
|
|
28640
|
+
};
|
|
28641
|
+
const mountRemove = async (frame) => {
|
|
28642
|
+
if (!deps.mountStore) {
|
|
28643
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28644
|
+
}
|
|
28645
|
+
const parsed = AttachmentMountRemoveArgs.safeParse(frame);
|
|
28646
|
+
if (!parsed.success) {
|
|
28647
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28648
|
+
}
|
|
28649
|
+
const ok = deps.mountStore.remove(parsed.data.personaId, parsed.data.basename);
|
|
28650
|
+
if (!ok) {
|
|
28651
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "mount not found");
|
|
28652
|
+
}
|
|
28653
|
+
return { response: { type: "attachment.mountRemove", removed: true } };
|
|
28654
|
+
};
|
|
28655
|
+
const mountList = async (frame) => {
|
|
28656
|
+
if (!deps.mountStore) {
|
|
28657
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "mountStore not wired");
|
|
28658
|
+
}
|
|
28659
|
+
const parsed = AttachmentMountListArgs.safeParse(frame);
|
|
28660
|
+
if (!parsed.success) {
|
|
28661
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28662
|
+
}
|
|
28663
|
+
const entries = deps.mountStore.list(parsed.data.personaId);
|
|
28664
|
+
return { response: { type: "attachment.mountList", entries } };
|
|
28665
|
+
};
|
|
28666
|
+
const groupAdd = async (frame) => {
|
|
28667
|
+
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28668
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28669
|
+
}
|
|
28670
|
+
const parsed = AttachmentGroupAddArgs.safeParse(frame);
|
|
28671
|
+
if (!parsed.success) {
|
|
28672
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28673
|
+
}
|
|
28674
|
+
const args = parsed.data;
|
|
28675
|
+
const scope = deps.getSessionScope(args.sessionId);
|
|
28676
|
+
if (!scope) {
|
|
28677
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
|
|
28678
|
+
}
|
|
28679
|
+
const size = 0;
|
|
28680
|
+
const entry = deps.groupFileStore.upsert(scope, args.sessionId, {
|
|
28681
|
+
relPath: args.relPath,
|
|
28682
|
+
from: "owner",
|
|
28683
|
+
label: args.label,
|
|
28684
|
+
size,
|
|
28685
|
+
mime: lookupMime(args.relPath)
|
|
28686
|
+
});
|
|
28687
|
+
return { response: { type: "attachment.groupAdd", entry } };
|
|
28688
|
+
};
|
|
28689
|
+
const groupRemove = async (frame) => {
|
|
28690
|
+
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28691
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28692
|
+
}
|
|
28693
|
+
const parsed = AttachmentGroupRemoveArgs.safeParse(frame);
|
|
28694
|
+
if (!parsed.success) {
|
|
28695
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28696
|
+
}
|
|
28697
|
+
const scope = deps.getSessionScope(parsed.data.sessionId);
|
|
28698
|
+
if (!scope) {
|
|
28699
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "session not found");
|
|
28700
|
+
}
|
|
28701
|
+
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
28702
|
+
const target = entries.find((e) => e.relPath === parsed.data.relPath);
|
|
28703
|
+
if (target?.from === "owner") {
|
|
28704
|
+
deps.groupFileStore.remove(scope, parsed.data.sessionId, parsed.data.relPath);
|
|
28705
|
+
} else {
|
|
28706
|
+
deps.groupFileStore.markStale(scope, parsed.data.sessionId, parsed.data.relPath);
|
|
28707
|
+
}
|
|
28708
|
+
return { response: { type: "attachment.groupRemove", removed: true } };
|
|
28709
|
+
};
|
|
28710
|
+
const groupList = async (frame) => {
|
|
28711
|
+
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
28712
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28713
|
+
}
|
|
28714
|
+
const parsed = AttachmentGroupListArgs.safeParse(frame);
|
|
28715
|
+
if (!parsed.success) {
|
|
28716
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28717
|
+
}
|
|
28718
|
+
const scope = deps.getSessionScope(parsed.data.sessionId);
|
|
28719
|
+
if (!scope) return { response: { type: "attachment.groupList", entries: [] } };
|
|
28720
|
+
const entries = deps.groupFileStore.list(scope, parsed.data.sessionId);
|
|
28721
|
+
return { response: { type: "attachment.groupList", entries } };
|
|
28722
|
+
};
|
|
28723
|
+
const groupListPersona = async (frame) => {
|
|
28724
|
+
if (!deps.groupFileStore) {
|
|
28725
|
+
throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "groupFileStore not wired");
|
|
28726
|
+
}
|
|
28727
|
+
const parsed = AttachmentGroupListPersonaArgs.safeParse(frame);
|
|
28728
|
+
if (!parsed.success) {
|
|
28729
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
|
|
28730
|
+
}
|
|
28731
|
+
const perSession = deps.groupFileStore.listByPersona(parsed.data.personaId);
|
|
28732
|
+
return { response: { type: "attachment.groupListPersona", perSession } };
|
|
28733
|
+
};
|
|
28734
|
+
return {
|
|
28735
|
+
"attachment.outboxCreate": outboxCreate,
|
|
28736
|
+
"attachment.outboxRevoke": outboxRevoke,
|
|
28737
|
+
"attachment.outboxList": outboxList,
|
|
28738
|
+
"attachment.mountAdd": mountAdd,
|
|
28739
|
+
"attachment.mountRemove": mountRemove,
|
|
28740
|
+
"attachment.mountList": mountList,
|
|
28741
|
+
"attachment.groupAdd": groupAdd,
|
|
28742
|
+
"attachment.groupRemove": groupRemove,
|
|
28743
|
+
"attachment.groupList": groupList,
|
|
28744
|
+
"attachment.groupListPersona": groupListPersona
|
|
28745
|
+
};
|
|
28746
|
+
}
|
|
28747
|
+
|
|
27618
28748
|
// src/handlers/index.ts
|
|
27619
28749
|
function buildMethodHandlers(deps) {
|
|
27620
28750
|
return {
|
|
@@ -27630,7 +28760,8 @@ function buildMethodHandlers(deps) {
|
|
|
27630
28760
|
personaRegistry: deps.personaRegistry,
|
|
27631
28761
|
sessionManager: deps.manager,
|
|
27632
28762
|
personaBoundHandler: deps.personaBoundHandler
|
|
27633
|
-
})
|
|
28763
|
+
}),
|
|
28764
|
+
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
27634
28765
|
};
|
|
27635
28766
|
}
|
|
27636
28767
|
|
|
@@ -27638,7 +28769,7 @@ function buildMethodHandlers(deps) {
|
|
|
27638
28769
|
async function startDaemon(config) {
|
|
27639
28770
|
const logger = createLogger({
|
|
27640
28771
|
level: config.logLevel,
|
|
27641
|
-
file:
|
|
28772
|
+
file: import_node_path28.default.join(config.dataDir, "clawd.log")
|
|
27642
28773
|
});
|
|
27643
28774
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
27644
28775
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -27670,7 +28801,7 @@ async function startDaemon(config) {
|
|
|
27670
28801
|
const agents = new AgentsScanner();
|
|
27671
28802
|
const history = new ClaudeHistoryReader();
|
|
27672
28803
|
let transport = null;
|
|
27673
|
-
const personaStore = new PersonaStore(
|
|
28804
|
+
const personaStore = new PersonaStore(import_node_path28.default.join(config.dataDir, "personas"));
|
|
27674
28805
|
const defaultsRoot = findDefaultsRoot();
|
|
27675
28806
|
if (defaultsRoot) {
|
|
27676
28807
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -27678,13 +28809,18 @@ async function startDaemon(config) {
|
|
|
27678
28809
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
27679
28810
|
}
|
|
27680
28811
|
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
28812
|
+
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
28813
|
+
const outboxStore = new OutboxStore({ dataDir: config.dataDir, logger });
|
|
28814
|
+
const mountStore = new MountStore({
|
|
28815
|
+
personaDirPath: (pid) => personaStore.personaDirPath(pid)
|
|
28816
|
+
});
|
|
27681
28817
|
const manager = new SessionManager({
|
|
27682
28818
|
store,
|
|
27683
28819
|
logger,
|
|
27684
28820
|
getAdapter,
|
|
27685
28821
|
historyReader: history,
|
|
27686
28822
|
dataDir: config.dataDir,
|
|
27687
|
-
personaRoot:
|
|
28823
|
+
personaRoot: import_node_path28.default.join(config.dataDir, "personas"),
|
|
27688
28824
|
personaStore,
|
|
27689
28825
|
ownerDisplayName,
|
|
27690
28826
|
mode: config.mode,
|
|
@@ -27701,6 +28837,38 @@ async function startDaemon(config) {
|
|
|
27701
28837
|
return;
|
|
27702
28838
|
}
|
|
27703
28839
|
transport?.broadcastToSession(sid, frame);
|
|
28840
|
+
},
|
|
28841
|
+
// file-sharing (spec §6 PR 3):runner 检测到成功 file-edit tool_result 时,
|
|
28842
|
+
// 闭包 stat + mime 写入群清单。stat 失败不阻塞主流程(log warn + 跳过本条),
|
|
28843
|
+
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
28844
|
+
attachmentGroup: {
|
|
28845
|
+
onFileEdit: (input) => {
|
|
28846
|
+
const absPath = import_node_path28.default.isAbsolute(input.relPath) ? input.relPath : import_node_path28.default.join(input.cwd, input.relPath);
|
|
28847
|
+
let size = 0;
|
|
28848
|
+
try {
|
|
28849
|
+
size = import_node_fs26.default.statSync(absPath).size;
|
|
28850
|
+
} catch (err) {
|
|
28851
|
+
logger.warn("attachment.onFileEdit stat failed", {
|
|
28852
|
+
sessionId: input.sessionId,
|
|
28853
|
+
absPath,
|
|
28854
|
+
err: err.message
|
|
28855
|
+
});
|
|
28856
|
+
}
|
|
28857
|
+
try {
|
|
28858
|
+
groupFileStore.upsert(input.scope, input.sessionId, {
|
|
28859
|
+
relPath: input.relPath,
|
|
28860
|
+
from: "agent",
|
|
28861
|
+
size,
|
|
28862
|
+
mime: lookupMime(input.relPath)
|
|
28863
|
+
});
|
|
28864
|
+
} catch (err) {
|
|
28865
|
+
logger.warn("attachment.onFileEdit upsert failed", {
|
|
28866
|
+
sessionId: input.sessionId,
|
|
28867
|
+
relPath: input.relPath,
|
|
28868
|
+
err: err.message
|
|
28869
|
+
});
|
|
28870
|
+
}
|
|
28871
|
+
}
|
|
27704
28872
|
}
|
|
27705
28873
|
});
|
|
27706
28874
|
const observer = new SessionObserver({
|
|
@@ -27746,6 +28914,12 @@ async function startDaemon(config) {
|
|
|
27746
28914
|
sessionManager: manager
|
|
27747
28915
|
});
|
|
27748
28916
|
let currentTunnelUrl = null;
|
|
28917
|
+
const getHttpBaseUrl = () => {
|
|
28918
|
+
if (currentTunnelUrl) {
|
|
28919
|
+
return currentTunnelUrl.replace(/^wss:/i, "https:").replace(/^ws:/i, "http:");
|
|
28920
|
+
}
|
|
28921
|
+
return `http://${config.host}:${config.port}`;
|
|
28922
|
+
};
|
|
27749
28923
|
const personaBoundHandler = new PersonaBoundHandler({
|
|
27750
28924
|
registry: personaRegistry,
|
|
27751
28925
|
personaManager,
|
|
@@ -27771,22 +28945,73 @@ async function startDaemon(config) {
|
|
|
27771
28945
|
getTunnelUrl: () => currentTunnelUrl,
|
|
27772
28946
|
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
27773
28947
|
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
27774
|
-
mode: config.mode
|
|
28948
|
+
mode: config.mode,
|
|
28949
|
+
// file-sharing (spec §8):ready / info 帧把 httpBaseUrl + httpToken 下发给 UI。
|
|
28950
|
+
// PR 2 阶段 httpToken 复用 owner WS token;noAuth 模式下为 null(UI 看到无 httpToken
|
|
28951
|
+
// 时禁用文件 GET/POST,保持 1.0 行为兼容)。
|
|
28952
|
+
getHttpBaseUrl,
|
|
28953
|
+
httpToken: resolvedAuthToken,
|
|
28954
|
+
// file-sharing attachment.* RPC(spec §5 PR 6+)。getPersonaScopeForRequest 把 RPC
|
|
28955
|
+
// args 里的 personaId/sessionId 翻译成 scopeRef 给 OutboxStore;mountAdd/groupAdd
|
|
28956
|
+
// 后续 PR 复用同一份装配。
|
|
28957
|
+
attachment: {
|
|
28958
|
+
outboxStore,
|
|
28959
|
+
mountStore,
|
|
28960
|
+
groupFileStore,
|
|
28961
|
+
personaDirPath: (pid) => personaStore.personaDirPath(pid),
|
|
28962
|
+
getHttpBaseUrl,
|
|
28963
|
+
getPersonaScopeForRequest: (args) => {
|
|
28964
|
+
if (args.personaId) return { kind: "persona", personaId: args.personaId };
|
|
28965
|
+
if (args.sessionId) return { kind: "session", sessionId: args.sessionId };
|
|
28966
|
+
return null;
|
|
28967
|
+
},
|
|
28968
|
+
// group RPC:根据 sessionId 反查 scope;owner-mode persona session 走
|
|
28969
|
+
// 'persona/<pid>/owner',default 走 'default'。PR 9 UI Drawer 用。
|
|
28970
|
+
getSessionScope: (sessionId) => {
|
|
28971
|
+
const file = store.read(sessionId);
|
|
28972
|
+
if (!file) return null;
|
|
28973
|
+
if (file.ownerPersonaId) {
|
|
28974
|
+
return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
|
|
28975
|
+
}
|
|
28976
|
+
return { kind: "default" };
|
|
28977
|
+
}
|
|
28978
|
+
}
|
|
28979
|
+
});
|
|
28980
|
+
const authResolver = new AuthContextResolver({
|
|
28981
|
+
ownerToken: resolvedAuthToken,
|
|
28982
|
+
personaRegistry
|
|
28983
|
+
});
|
|
28984
|
+
const httpRouter = createHttpRouter({
|
|
28985
|
+
authResolver,
|
|
28986
|
+
daemonVersion: version,
|
|
28987
|
+
logger,
|
|
28988
|
+
personaStore,
|
|
28989
|
+
groupFileStore,
|
|
28990
|
+
sessionStore: store,
|
|
28991
|
+
outboxStore
|
|
27775
28992
|
});
|
|
27776
28993
|
wsServer = new LocalWsServer({
|
|
27777
28994
|
host: config.host,
|
|
27778
28995
|
port: config.port,
|
|
27779
28996
|
logger,
|
|
27780
|
-
readyFrameBuilder: () => buildReadyFrame(
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
|
|
27785
|
-
|
|
27786
|
-
|
|
28997
|
+
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
28998
|
+
{
|
|
28999
|
+
manager,
|
|
29000
|
+
getAdapter,
|
|
29001
|
+
getTunnelUrl: () => currentTunnelUrl,
|
|
29002
|
+
// ready 帧 mode = daemon CC spawn 模式('sdk' | 'tui');UI 用它挂 XtermPanel
|
|
29003
|
+
mode: config.mode,
|
|
29004
|
+
// file-sharing 字段:httpBaseUrl 跟 tunnel 状态走;httpToken 复用 owner WS token
|
|
29005
|
+
getHttpBaseUrl,
|
|
29006
|
+
httpToken: resolvedAuthToken
|
|
29007
|
+
},
|
|
29008
|
+
ctx
|
|
29009
|
+
),
|
|
27787
29010
|
protocolVersion: PROTOCOL_VERSION,
|
|
27788
29011
|
authGate: authGate ?? void 0,
|
|
27789
29012
|
personaBoundHandler,
|
|
29013
|
+
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29014
|
+
httpRequestHandler: httpRouter,
|
|
27790
29015
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
27791
29016
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
27792
29017
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -27902,8 +29127,8 @@ async function startDaemon(config) {
|
|
|
27902
29127
|
const lines = [
|
|
27903
29128
|
`Tunnel: ${r.url}`,
|
|
27904
29129
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
27905
|
-
`Frpc config: ${
|
|
27906
|
-
`Frpc log: ${
|
|
29130
|
+
`Frpc config: ${import_node_path28.default.join(config.dataDir, "frpc.toml")}`,
|
|
29131
|
+
`Frpc log: ${import_node_path28.default.join(config.dataDir, "frpc.log")}`
|
|
27907
29132
|
];
|
|
27908
29133
|
const width = Math.max(...lines.map((l) => l.length));
|
|
27909
29134
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -27916,8 +29141,8 @@ ${bar}
|
|
|
27916
29141
|
|
|
27917
29142
|
`);
|
|
27918
29143
|
try {
|
|
27919
|
-
const connectPath =
|
|
27920
|
-
|
|
29144
|
+
const connectPath = import_node_path28.default.join(config.dataDir, "connect.txt");
|
|
29145
|
+
import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
27921
29146
|
} catch {
|
|
27922
29147
|
}
|
|
27923
29148
|
} catch (err) {
|