@clawos-dev/clawd 0.2.64-beta.106.42f5a70 → 0.2.64
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 +270 -1495
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -110,22 +110,6 @@ var init_methods = __esm({
|
|
|
110
110
|
// 触发频率低(仅 window resize),response 也是 ack。
|
|
111
111
|
"session:pty:input",
|
|
112
112
|
"session:pty:resize",
|
|
113
|
-
// ---- attachment.* file-sharing(spec §5;详见 attachment-schemas.ts) ----
|
|
114
|
-
// 命名警告:这里的 `attachment.*` RPC 与 CC v2.x 上行 `type:"attachment"` 系统行
|
|
115
|
-
// (attachment-skills / attachment-deferred-tools / attachment_memories)是不同概念。
|
|
116
|
-
// 全部管理类 RPC handler 入口 requireOwner(personal token 调任意一个都 403);
|
|
117
|
-
// 实际文件传输走 HTTP 路由(GET/POST,详见 spec §5),不在白名单内。
|
|
118
|
-
"attachment.outboxCreate",
|
|
119
|
-
"attachment.outboxRevoke",
|
|
120
|
-
"attachment.outboxList",
|
|
121
|
-
"attachment.mountAdd",
|
|
122
|
-
"attachment.mountRemove",
|
|
123
|
-
"attachment.mountList",
|
|
124
|
-
"attachment.groupAdd",
|
|
125
|
-
"attachment.groupRemove",
|
|
126
|
-
"attachment.groupList",
|
|
127
|
-
// v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
|
|
128
|
-
"attachment.groupListPersona",
|
|
129
113
|
"info",
|
|
130
114
|
"ping"
|
|
131
115
|
];
|
|
@@ -617,8 +601,8 @@ var init_parseUtil = __esm({
|
|
|
617
601
|
init_errors2();
|
|
618
602
|
init_en();
|
|
619
603
|
makeIssue = (params) => {
|
|
620
|
-
const { data, path:
|
|
621
|
-
const fullPath = [...
|
|
604
|
+
const { data, path: path28, errorMaps, issueData } = params;
|
|
605
|
+
const fullPath = [...path28, ...issueData.path || []];
|
|
622
606
|
const fullIssue = {
|
|
623
607
|
...issueData,
|
|
624
608
|
path: fullPath
|
|
@@ -929,11 +913,11 @@ var init_types = __esm({
|
|
|
929
913
|
init_parseUtil();
|
|
930
914
|
init_util();
|
|
931
915
|
ParseInputLazyPath = class {
|
|
932
|
-
constructor(parent, value,
|
|
916
|
+
constructor(parent, value, path28, key) {
|
|
933
917
|
this._cachedPath = [];
|
|
934
918
|
this.parent = parent;
|
|
935
919
|
this.data = value;
|
|
936
|
-
this._path =
|
|
920
|
+
this._path = path28;
|
|
937
921
|
this._key = key;
|
|
938
922
|
}
|
|
939
923
|
get path() {
|
|
@@ -4316,149 +4300,6 @@ var init_zod = __esm({
|
|
|
4316
4300
|
}
|
|
4317
4301
|
});
|
|
4318
4302
|
|
|
4319
|
-
// ../protocol/src/attachment-schemas.ts
|
|
4320
|
-
var TOKEN_ROLES, GROUP_FILE_SOURCES, MOUNT_MODES, GroupFileEntrySchema, MountEntrySchema, OutboxScopeSchema, OutboxCapEntrySchema, AttachmentOutboxCreateArgs, AttachmentOutboxCreateResponseSchema, AttachmentOutboxRevokeArgs, AttachmentOutboxRevokeResponseSchema, AttachmentOutboxListArgs, AttachmentOutboxListResponseSchema, AttachmentMountAddArgs, AttachmentMountAddResponseSchema, AttachmentMountRemoveArgs, AttachmentMountRemoveResponseSchema, AttachmentMountListArgs, AttachmentMountListResponseSchema, AttachmentGroupAddArgs, AttachmentGroupAddResponseSchema, AttachmentGroupRemoveArgs, AttachmentGroupRemoveResponseSchema, AttachmentGroupListArgs, AttachmentGroupListResponseSchema, AttachmentGroupListPersonaArgs, AttachmentGroupListPersonaResponseSchema;
|
|
4321
|
-
var init_attachment_schemas = __esm({
|
|
4322
|
-
"../protocol/src/attachment-schemas.ts"() {
|
|
4323
|
-
"use strict";
|
|
4324
|
-
init_zod();
|
|
4325
|
-
TOKEN_ROLES = ["owner", "personal"];
|
|
4326
|
-
GROUP_FILE_SOURCES = ["agent", "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
|
-
|
|
4462
4303
|
// ../protocol/src/persona-schemas.ts
|
|
4463
4304
|
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema, FRAME_TYPE_CHAT_OPEN, FRAME_TYPE_CHAT_RENAME, FRAME_TYPE_CHAT_DELETE, FRAME_TYPE_CHAT_LIST, FRAME_TYPE_CHAT_CREATED, FRAME_TYPE_CHAT_RENAMED, FRAME_TYPE_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED, FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED, FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_STATUS, ChatSummarySchema, ChatOpenRequestSchema, ChatRenameRequestSchema, ChatDeleteRequestSchema, ChatRenameResponseSchema, ChatDeleteResponseSchema, ChatListPushSchema, ChatCreatedFrameSchema, ChatRenamedFrameSchema, ChatDeletedFrameSchema, PersonaListenerChatCreatedFrameSchema, PersonaListenerChatRenamedFrameSchema, PersonaListenerChatDeletedFrameSchema, PersonaListenerStatusFrameSchema, PersonaListListenerChatsForTokenArgsSchema, PersonaListListenerChatsForTokenResponseSchema;
|
|
4464
4305
|
var init_persona_schemas = __esm({
|
|
@@ -4664,7 +4505,6 @@ var init_schemas = __esm({
|
|
|
4664
4505
|
"use strict";
|
|
4665
4506
|
init_zod();
|
|
4666
4507
|
init_events();
|
|
4667
|
-
init_attachment_schemas();
|
|
4668
4508
|
init_persona_schemas();
|
|
4669
4509
|
SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
|
|
4670
4510
|
UsageSchema = external_exports.object({
|
|
@@ -5221,22 +5061,7 @@ var init_schemas = __esm({
|
|
|
5221
5061
|
hostname: external_exports.string(),
|
|
5222
5062
|
os: external_exports.string(),
|
|
5223
5063
|
tools: external_exports.array(external_exports.object({ id: external_exports.string(), available: external_exports.boolean() })),
|
|
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()
|
|
5064
|
+
runningSessions: external_exports.array(InfoRunningSessionSchema)
|
|
5240
5065
|
});
|
|
5241
5066
|
}
|
|
5242
5067
|
});
|
|
@@ -5268,7 +5093,6 @@ var init_runtime = __esm({
|
|
|
5268
5093
|
init_frames();
|
|
5269
5094
|
init_persona_schemas();
|
|
5270
5095
|
init_persona_mode();
|
|
5271
|
-
init_attachment_schemas();
|
|
5272
5096
|
}
|
|
5273
5097
|
});
|
|
5274
5098
|
|
|
@@ -5543,8 +5367,8 @@ var require_req = __commonJS({
|
|
|
5543
5367
|
if (req.originalUrl) {
|
|
5544
5368
|
_req.url = req.originalUrl;
|
|
5545
5369
|
} else {
|
|
5546
|
-
const
|
|
5547
|
-
_req.url = typeof
|
|
5370
|
+
const path28 = req.path;
|
|
5371
|
+
_req.url = typeof path28 === "string" ? path28 : req.url ? req.url.path || req.url : void 0;
|
|
5548
5372
|
}
|
|
5549
5373
|
if (req.query) {
|
|
5550
5374
|
_req.query = req.query;
|
|
@@ -5709,14 +5533,14 @@ var require_redact = __commonJS({
|
|
|
5709
5533
|
}
|
|
5710
5534
|
return obj;
|
|
5711
5535
|
}
|
|
5712
|
-
function parsePath(
|
|
5536
|
+
function parsePath(path28) {
|
|
5713
5537
|
const parts = [];
|
|
5714
5538
|
let current = "";
|
|
5715
5539
|
let inBrackets = false;
|
|
5716
5540
|
let inQuotes = false;
|
|
5717
5541
|
let quoteChar = "";
|
|
5718
|
-
for (let i = 0; i <
|
|
5719
|
-
const char =
|
|
5542
|
+
for (let i = 0; i < path28.length; i++) {
|
|
5543
|
+
const char = path28[i];
|
|
5720
5544
|
if (!inBrackets && char === ".") {
|
|
5721
5545
|
if (current) {
|
|
5722
5546
|
parts.push(current);
|
|
@@ -5847,10 +5671,10 @@ var require_redact = __commonJS({
|
|
|
5847
5671
|
return current;
|
|
5848
5672
|
}
|
|
5849
5673
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5850
|
-
for (const
|
|
5851
|
-
const parts = parsePath(
|
|
5674
|
+
for (const path28 of paths) {
|
|
5675
|
+
const parts = parsePath(path28);
|
|
5852
5676
|
if (parts.includes("*")) {
|
|
5853
|
-
redactWildcardPath(obj, parts, censor,
|
|
5677
|
+
redactWildcardPath(obj, parts, censor, path28, remove);
|
|
5854
5678
|
} else {
|
|
5855
5679
|
if (remove) {
|
|
5856
5680
|
removeKey(obj, parts);
|
|
@@ -5935,8 +5759,8 @@ var require_redact = __commonJS({
|
|
|
5935
5759
|
}
|
|
5936
5760
|
} else {
|
|
5937
5761
|
if (afterWildcard.includes("*")) {
|
|
5938
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5939
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5762
|
+
const wrappedCensor = typeof censor === "function" ? (value, path28) => {
|
|
5763
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path28];
|
|
5940
5764
|
return censor(value, fullPath);
|
|
5941
5765
|
} : censor;
|
|
5942
5766
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5971,8 +5795,8 @@ var require_redact = __commonJS({
|
|
|
5971
5795
|
return null;
|
|
5972
5796
|
}
|
|
5973
5797
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5974
|
-
for (const
|
|
5975
|
-
const parts = parsePath(
|
|
5798
|
+
for (const path28 of pathsToClone) {
|
|
5799
|
+
const parts = parsePath(path28);
|
|
5976
5800
|
let current = pathStructure;
|
|
5977
5801
|
for (let i = 0; i < parts.length; i++) {
|
|
5978
5802
|
const part = parts[i];
|
|
@@ -6024,24 +5848,24 @@ var require_redact = __commonJS({
|
|
|
6024
5848
|
}
|
|
6025
5849
|
return cloneSelectively(obj, pathStructure);
|
|
6026
5850
|
}
|
|
6027
|
-
function validatePath(
|
|
6028
|
-
if (typeof
|
|
5851
|
+
function validatePath(path28) {
|
|
5852
|
+
if (typeof path28 !== "string") {
|
|
6029
5853
|
throw new Error("Paths must be (non-empty) strings");
|
|
6030
5854
|
}
|
|
6031
|
-
if (
|
|
5855
|
+
if (path28 === "") {
|
|
6032
5856
|
throw new Error("Invalid redaction path ()");
|
|
6033
5857
|
}
|
|
6034
|
-
if (
|
|
6035
|
-
throw new Error(`Invalid redaction path (${
|
|
5858
|
+
if (path28.includes("..")) {
|
|
5859
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6036
5860
|
}
|
|
6037
|
-
if (
|
|
6038
|
-
throw new Error(`Invalid redaction path (${
|
|
5861
|
+
if (path28.includes(",")) {
|
|
5862
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6039
5863
|
}
|
|
6040
5864
|
let bracketCount = 0;
|
|
6041
5865
|
let inQuotes = false;
|
|
6042
5866
|
let quoteChar = "";
|
|
6043
|
-
for (let i = 0; i <
|
|
6044
|
-
const char =
|
|
5867
|
+
for (let i = 0; i < path28.length; i++) {
|
|
5868
|
+
const char = path28[i];
|
|
6045
5869
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
6046
5870
|
if (!inQuotes) {
|
|
6047
5871
|
inQuotes = true;
|
|
@@ -6055,20 +5879,20 @@ var require_redact = __commonJS({
|
|
|
6055
5879
|
} else if (char === "]" && !inQuotes) {
|
|
6056
5880
|
bracketCount--;
|
|
6057
5881
|
if (bracketCount < 0) {
|
|
6058
|
-
throw new Error(`Invalid redaction path (${
|
|
5882
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6059
5883
|
}
|
|
6060
5884
|
}
|
|
6061
5885
|
}
|
|
6062
5886
|
if (bracketCount !== 0) {
|
|
6063
|
-
throw new Error(`Invalid redaction path (${
|
|
5887
|
+
throw new Error(`Invalid redaction path (${path28})`);
|
|
6064
5888
|
}
|
|
6065
5889
|
}
|
|
6066
5890
|
function validatePaths(paths) {
|
|
6067
5891
|
if (!Array.isArray(paths)) {
|
|
6068
5892
|
throw new TypeError("paths must be an array");
|
|
6069
5893
|
}
|
|
6070
|
-
for (const
|
|
6071
|
-
validatePath(
|
|
5894
|
+
for (const path28 of paths) {
|
|
5895
|
+
validatePath(path28);
|
|
6072
5896
|
}
|
|
6073
5897
|
}
|
|
6074
5898
|
function slowRedact(options = {}) {
|
|
@@ -6236,8 +6060,8 @@ var require_redaction = __commonJS({
|
|
|
6236
6060
|
if (shape[k2] === null) {
|
|
6237
6061
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6238
6062
|
} else {
|
|
6239
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6240
|
-
return censor(value, [k2, ...
|
|
6063
|
+
const wrappedCensor = typeof censor === "function" ? (value, path28) => {
|
|
6064
|
+
return censor(value, [k2, ...path28]);
|
|
6241
6065
|
} : censor;
|
|
6242
6066
|
o[k2] = Redact({
|
|
6243
6067
|
paths: shape[k2],
|
|
@@ -6455,10 +6279,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6455
6279
|
var require_sonic_boom = __commonJS({
|
|
6456
6280
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6457
6281
|
"use strict";
|
|
6458
|
-
var
|
|
6282
|
+
var fs26 = require("fs");
|
|
6459
6283
|
var EventEmitter2 = require("events");
|
|
6460
6284
|
var inherits = require("util").inherits;
|
|
6461
|
-
var
|
|
6285
|
+
var path28 = require("path");
|
|
6462
6286
|
var sleep = require_atomic_sleep();
|
|
6463
6287
|
var assert = require("assert");
|
|
6464
6288
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6512,20 +6336,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6512
6336
|
const mode = sonic.mode;
|
|
6513
6337
|
if (sonic.sync) {
|
|
6514
6338
|
try {
|
|
6515
|
-
if (sonic.mkdir)
|
|
6516
|
-
const fd =
|
|
6339
|
+
if (sonic.mkdir) fs26.mkdirSync(path28.dirname(file), { recursive: true });
|
|
6340
|
+
const fd = fs26.openSync(file, flags, mode);
|
|
6517
6341
|
fileOpened(null, fd);
|
|
6518
6342
|
} catch (err) {
|
|
6519
6343
|
fileOpened(err);
|
|
6520
6344
|
throw err;
|
|
6521
6345
|
}
|
|
6522
6346
|
} else if (sonic.mkdir) {
|
|
6523
|
-
|
|
6347
|
+
fs26.mkdir(path28.dirname(file), { recursive: true }, (err) => {
|
|
6524
6348
|
if (err) return fileOpened(err);
|
|
6525
|
-
|
|
6349
|
+
fs26.open(file, flags, mode, fileOpened);
|
|
6526
6350
|
});
|
|
6527
6351
|
} else {
|
|
6528
|
-
|
|
6352
|
+
fs26.open(file, flags, mode, fileOpened);
|
|
6529
6353
|
}
|
|
6530
6354
|
}
|
|
6531
6355
|
function SonicBoom(opts) {
|
|
@@ -6566,8 +6390,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6566
6390
|
this.flush = flushBuffer;
|
|
6567
6391
|
this.flushSync = flushBufferSync;
|
|
6568
6392
|
this._actualWrite = actualWriteBuffer;
|
|
6569
|
-
fsWriteSync = () =>
|
|
6570
|
-
fsWrite = () =>
|
|
6393
|
+
fsWriteSync = () => fs26.writeSync(this.fd, this._writingBuf);
|
|
6394
|
+
fsWrite = () => fs26.write(this.fd, this._writingBuf, this.release);
|
|
6571
6395
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6572
6396
|
this._writingBuf = "";
|
|
6573
6397
|
this.write = write;
|
|
@@ -6576,15 +6400,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6576
6400
|
this._actualWrite = actualWrite;
|
|
6577
6401
|
fsWriteSync = () => {
|
|
6578
6402
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6579
|
-
return
|
|
6403
|
+
return fs26.writeSync(this.fd, this._writingBuf);
|
|
6580
6404
|
}
|
|
6581
|
-
return
|
|
6405
|
+
return fs26.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6582
6406
|
};
|
|
6583
6407
|
fsWrite = () => {
|
|
6584
6408
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6585
|
-
return
|
|
6409
|
+
return fs26.write(this.fd, this._writingBuf, this.release);
|
|
6586
6410
|
}
|
|
6587
|
-
return
|
|
6411
|
+
return fs26.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6588
6412
|
};
|
|
6589
6413
|
} else {
|
|
6590
6414
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6641,7 +6465,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6641
6465
|
}
|
|
6642
6466
|
}
|
|
6643
6467
|
if (this._fsync) {
|
|
6644
|
-
|
|
6468
|
+
fs26.fsyncSync(this.fd);
|
|
6645
6469
|
}
|
|
6646
6470
|
const len = this._len;
|
|
6647
6471
|
if (this._reopening) {
|
|
@@ -6755,7 +6579,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6755
6579
|
const onDrain = () => {
|
|
6756
6580
|
if (!this._fsync) {
|
|
6757
6581
|
try {
|
|
6758
|
-
|
|
6582
|
+
fs26.fsync(this.fd, (err) => {
|
|
6759
6583
|
this._flushPending = false;
|
|
6760
6584
|
cb(err);
|
|
6761
6585
|
});
|
|
@@ -6857,7 +6681,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6857
6681
|
const fd = this.fd;
|
|
6858
6682
|
this.once("ready", () => {
|
|
6859
6683
|
if (fd !== this.fd) {
|
|
6860
|
-
|
|
6684
|
+
fs26.close(fd, (err) => {
|
|
6861
6685
|
if (err) {
|
|
6862
6686
|
return this.emit("error", err);
|
|
6863
6687
|
}
|
|
@@ -6906,7 +6730,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6906
6730
|
buf = this._bufs[0];
|
|
6907
6731
|
}
|
|
6908
6732
|
try {
|
|
6909
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6733
|
+
const n = Buffer.isBuffer(buf) ? fs26.writeSync(this.fd, buf) : fs26.writeSync(this.fd, buf, "utf8");
|
|
6910
6734
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6911
6735
|
buf = releasedBufObj.writingBuf;
|
|
6912
6736
|
this._len = releasedBufObj.len;
|
|
@@ -6922,7 +6746,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6922
6746
|
}
|
|
6923
6747
|
}
|
|
6924
6748
|
try {
|
|
6925
|
-
|
|
6749
|
+
fs26.fsyncSync(this.fd);
|
|
6926
6750
|
} catch {
|
|
6927
6751
|
}
|
|
6928
6752
|
}
|
|
@@ -6943,7 +6767,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6943
6767
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6944
6768
|
}
|
|
6945
6769
|
try {
|
|
6946
|
-
const n =
|
|
6770
|
+
const n = fs26.writeSync(this.fd, buf);
|
|
6947
6771
|
buf = buf.subarray(n);
|
|
6948
6772
|
this._len = Math.max(this._len - n, 0);
|
|
6949
6773
|
if (buf.length <= 0) {
|
|
@@ -6971,13 +6795,13 @@ var require_sonic_boom = __commonJS({
|
|
|
6971
6795
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
6972
6796
|
if (this.sync) {
|
|
6973
6797
|
try {
|
|
6974
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
6798
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs26.writeSync(this.fd, this._writingBuf) : fs26.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6975
6799
|
release(null, written);
|
|
6976
6800
|
} catch (err) {
|
|
6977
6801
|
release(err);
|
|
6978
6802
|
}
|
|
6979
6803
|
} else {
|
|
6980
|
-
|
|
6804
|
+
fs26.write(this.fd, this._writingBuf, release);
|
|
6981
6805
|
}
|
|
6982
6806
|
}
|
|
6983
6807
|
function actualWriteBuffer() {
|
|
@@ -6986,7 +6810,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6986
6810
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
6987
6811
|
if (this.sync) {
|
|
6988
6812
|
try {
|
|
6989
|
-
const written =
|
|
6813
|
+
const written = fs26.writeSync(this.fd, this._writingBuf);
|
|
6990
6814
|
release(null, written);
|
|
6991
6815
|
} catch (err) {
|
|
6992
6816
|
release(err);
|
|
@@ -6995,7 +6819,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6995
6819
|
if (kCopyBuffer) {
|
|
6996
6820
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
6997
6821
|
}
|
|
6998
|
-
|
|
6822
|
+
fs26.write(this.fd, this._writingBuf, release);
|
|
6999
6823
|
}
|
|
7000
6824
|
}
|
|
7001
6825
|
function actualClose(sonic) {
|
|
@@ -7011,12 +6835,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7011
6835
|
sonic._lens = [];
|
|
7012
6836
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7013
6837
|
try {
|
|
7014
|
-
|
|
6838
|
+
fs26.fsync(sonic.fd, closeWrapped);
|
|
7015
6839
|
} catch {
|
|
7016
6840
|
}
|
|
7017
6841
|
function closeWrapped() {
|
|
7018
6842
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7019
|
-
|
|
6843
|
+
fs26.close(sonic.fd, done);
|
|
7020
6844
|
} else {
|
|
7021
6845
|
done();
|
|
7022
6846
|
}
|
|
@@ -10151,11 +9975,11 @@ var init_lib = __esm({
|
|
|
10151
9975
|
}
|
|
10152
9976
|
}
|
|
10153
9977
|
},
|
|
10154
|
-
addToPath: function addToPath(
|
|
10155
|
-
var last =
|
|
9978
|
+
addToPath: function addToPath(path28, added, removed, oldPosInc, options) {
|
|
9979
|
+
var last = path28.lastComponent;
|
|
10156
9980
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
10157
9981
|
return {
|
|
10158
|
-
oldPos:
|
|
9982
|
+
oldPos: path28.oldPos + oldPosInc,
|
|
10159
9983
|
lastComponent: {
|
|
10160
9984
|
count: last.count + 1,
|
|
10161
9985
|
added,
|
|
@@ -10165,7 +9989,7 @@ var init_lib = __esm({
|
|
|
10165
9989
|
};
|
|
10166
9990
|
} else {
|
|
10167
9991
|
return {
|
|
10168
|
-
oldPos:
|
|
9992
|
+
oldPos: path28.oldPos + oldPosInc,
|
|
10169
9993
|
lastComponent: {
|
|
10170
9994
|
count: 1,
|
|
10171
9995
|
added,
|
|
@@ -10596,10 +10420,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10596
10420
|
const memories = raw.map((m2) => {
|
|
10597
10421
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10598
10422
|
const rec = m2;
|
|
10599
|
-
const
|
|
10423
|
+
const path28 = typeof rec.path === "string" ? rec.path : null;
|
|
10600
10424
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10601
|
-
if (!
|
|
10602
|
-
const entry = { path:
|
|
10425
|
+
if (!path28 || content == null) return null;
|
|
10426
|
+
const entry = { path: path28, content };
|
|
10603
10427
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10604
10428
|
return entry;
|
|
10605
10429
|
}).filter((m2) => m2 !== null);
|
|
@@ -11403,10 +11227,10 @@ function parseAttachment(obj) {
|
|
|
11403
11227
|
const memories = raw.map((m2) => {
|
|
11404
11228
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11405
11229
|
const rec = m2;
|
|
11406
|
-
const
|
|
11230
|
+
const path28 = typeof rec.path === "string" ? rec.path : null;
|
|
11407
11231
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11408
|
-
if (!
|
|
11409
|
-
const out = { path:
|
|
11232
|
+
if (!path28 || content == null) return null;
|
|
11233
|
+
const out = { path: path28, content };
|
|
11410
11234
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11411
11235
|
return out;
|
|
11412
11236
|
}).filter((m2) => m2 !== null);
|
|
@@ -18901,7 +18725,7 @@ var require_websocket = __commonJS({
|
|
|
18901
18725
|
"use strict";
|
|
18902
18726
|
var EventEmitter2 = require("events");
|
|
18903
18727
|
var https = require("https");
|
|
18904
|
-
var
|
|
18728
|
+
var http = require("http");
|
|
18905
18729
|
var net = require("net");
|
|
18906
18730
|
var tls = require("tls");
|
|
18907
18731
|
var { randomBytes, createHash } = require("crypto");
|
|
@@ -19435,7 +19259,7 @@ var require_websocket = __commonJS({
|
|
|
19435
19259
|
}
|
|
19436
19260
|
const defaultPort = isSecure ? 443 : 80;
|
|
19437
19261
|
const key = randomBytes(16).toString("base64");
|
|
19438
|
-
const request = isSecure ? https.request :
|
|
19262
|
+
const request = isSecure ? https.request : http.request;
|
|
19439
19263
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19440
19264
|
let perMessageDeflate;
|
|
19441
19265
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -19929,7 +19753,7 @@ var require_websocket_server = __commonJS({
|
|
|
19929
19753
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
19930
19754
|
"use strict";
|
|
19931
19755
|
var EventEmitter2 = require("events");
|
|
19932
|
-
var
|
|
19756
|
+
var http = require("http");
|
|
19933
19757
|
var { Duplex } = require("stream");
|
|
19934
19758
|
var { createHash } = require("crypto");
|
|
19935
19759
|
var extension2 = require_extension();
|
|
@@ -20004,8 +19828,8 @@ var require_websocket_server = __commonJS({
|
|
|
20004
19828
|
);
|
|
20005
19829
|
}
|
|
20006
19830
|
if (options.port != null) {
|
|
20007
|
-
this._server =
|
|
20008
|
-
const body =
|
|
19831
|
+
this._server = http.createServer((req, res) => {
|
|
19832
|
+
const body = http.STATUS_CODES[426];
|
|
20009
19833
|
res.writeHead(426, {
|
|
20010
19834
|
"Content-Length": body.length,
|
|
20011
19835
|
"Content-Type": "text/plain"
|
|
@@ -20292,7 +20116,7 @@ var require_websocket_server = __commonJS({
|
|
|
20292
20116
|
this.destroy();
|
|
20293
20117
|
}
|
|
20294
20118
|
function abortHandshake(socket, code, message, headers) {
|
|
20295
|
-
message = message ||
|
|
20119
|
+
message = message || http.STATUS_CODES[code];
|
|
20296
20120
|
headers = {
|
|
20297
20121
|
Connection: "close",
|
|
20298
20122
|
"Content-Type": "text/html",
|
|
@@ -20301,7 +20125,7 @@ var require_websocket_server = __commonJS({
|
|
|
20301
20125
|
};
|
|
20302
20126
|
socket.once("finish", socket.destroy);
|
|
20303
20127
|
socket.end(
|
|
20304
|
-
`HTTP/1.1 ${code} ${
|
|
20128
|
+
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
|
|
20305
20129
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20306
20130
|
);
|
|
20307
20131
|
}
|
|
@@ -20320,7 +20144,7 @@ var require_websocket_server = __commonJS({
|
|
|
20320
20144
|
// src/run-case/recorder.ts
|
|
20321
20145
|
function startRunCaseRecorder(opts) {
|
|
20322
20146
|
const now = opts.now ?? Date.now;
|
|
20323
|
-
const dir =
|
|
20147
|
+
const dir = import_node_path24.default.dirname(opts.recordPath);
|
|
20324
20148
|
let stream = null;
|
|
20325
20149
|
let closing = false;
|
|
20326
20150
|
let closedSettled = false;
|
|
@@ -20334,8 +20158,8 @@ function startRunCaseRecorder(opts) {
|
|
|
20334
20158
|
});
|
|
20335
20159
|
const ensureStream = () => {
|
|
20336
20160
|
if (stream) return stream;
|
|
20337
|
-
|
|
20338
|
-
stream =
|
|
20161
|
+
import_node_fs23.default.mkdirSync(dir, { recursive: true });
|
|
20162
|
+
stream = import_node_fs23.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
20339
20163
|
stream.on("close", () => closedResolve());
|
|
20340
20164
|
return stream;
|
|
20341
20165
|
};
|
|
@@ -20360,12 +20184,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20360
20184
|
};
|
|
20361
20185
|
return { tap, close, closed };
|
|
20362
20186
|
}
|
|
20363
|
-
var
|
|
20187
|
+
var import_node_fs23, import_node_path24;
|
|
20364
20188
|
var init_recorder = __esm({
|
|
20365
20189
|
"src/run-case/recorder.ts"() {
|
|
20366
20190
|
"use strict";
|
|
20367
|
-
|
|
20368
|
-
|
|
20191
|
+
import_node_fs23 = __toESM(require("fs"), 1);
|
|
20192
|
+
import_node_path24 = __toESM(require("path"), 1);
|
|
20369
20193
|
}
|
|
20370
20194
|
});
|
|
20371
20195
|
|
|
@@ -20408,7 +20232,7 @@ var init_wire = __esm({
|
|
|
20408
20232
|
// src/run-case/controller.ts
|
|
20409
20233
|
async function runController(opts) {
|
|
20410
20234
|
const now = opts.now ?? Date.now;
|
|
20411
|
-
const cwd = opts.cwd ?? (0,
|
|
20235
|
+
const cwd = opts.cwd ?? (0, import_node_fs24.mkdtempSync)(import_node_path25.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
|
|
20412
20236
|
const ownsCwd = opts.cwd === void 0;
|
|
20413
20237
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20414
20238
|
const spawnCtx = { cwd };
|
|
@@ -20569,19 +20393,19 @@ async function runController(opts) {
|
|
|
20569
20393
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
20570
20394
|
if (ownsCwd) {
|
|
20571
20395
|
try {
|
|
20572
|
-
(0,
|
|
20396
|
+
(0, import_node_fs24.rmSync)(cwd, { recursive: true, force: true });
|
|
20573
20397
|
} catch {
|
|
20574
20398
|
}
|
|
20575
20399
|
}
|
|
20576
20400
|
return exitCode ?? 0;
|
|
20577
20401
|
}
|
|
20578
|
-
var
|
|
20402
|
+
var import_node_fs24, import_node_os15, import_node_path25;
|
|
20579
20403
|
var init_controller = __esm({
|
|
20580
20404
|
"src/run-case/controller.ts"() {
|
|
20581
20405
|
"use strict";
|
|
20582
|
-
|
|
20406
|
+
import_node_fs24 = require("fs");
|
|
20583
20407
|
import_node_os15 = __toESM(require("os"), 1);
|
|
20584
|
-
|
|
20408
|
+
import_node_path25 = __toESM(require("path"), 1);
|
|
20585
20409
|
init_claude();
|
|
20586
20410
|
init_stdout_splitter();
|
|
20587
20411
|
init_permission_stdio();
|
|
@@ -20813,8 +20637,8 @@ Env (advanced):
|
|
|
20813
20637
|
`;
|
|
20814
20638
|
|
|
20815
20639
|
// src/index.ts
|
|
20816
|
-
var
|
|
20817
|
-
var
|
|
20640
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
20641
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
20818
20642
|
|
|
20819
20643
|
// src/logger.ts
|
|
20820
20644
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -21806,11 +21630,6 @@ var SessionRunner = class {
|
|
|
21806
21630
|
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
21807
21631
|
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
21808
21632
|
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();
|
|
21814
21633
|
getState() {
|
|
21815
21634
|
return this.state;
|
|
21816
21635
|
}
|
|
@@ -21819,13 +21638,7 @@ var SessionRunner = class {
|
|
|
21819
21638
|
const personaStore = this.hooks.personaStore;
|
|
21820
21639
|
const adapter = this.hooks.adapter;
|
|
21821
21640
|
const deps = {
|
|
21822
|
-
|
|
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
|
-
},
|
|
21641
|
+
parseLine: (l) => adapter.parseLine(l),
|
|
21829
21642
|
// persona mention injection 在 deps 装配处包一层,对 reducer 完全透明。
|
|
21830
21643
|
// 命中 mention 时拆成两条 stdin 帧(先 system-reminder 块、后 user 原文):
|
|
21831
21644
|
// - CC stream-json 按行处理,落盘是两条独立 user message;
|
|
@@ -21893,54 +21706,8 @@ var SessionRunner = class {
|
|
|
21893
21706
|
// reducer 的 batch dedup 按 events[0].uuid 整组处理,避免跨路径重复。
|
|
21894
21707
|
feedObserverEvents(events) {
|
|
21895
21708
|
if (events.length === 0) return;
|
|
21896
|
-
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
21897
21709
|
this.input({ kind: "inject-events", events });
|
|
21898
21710
|
}
|
|
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
|
-
}
|
|
21944
21711
|
// 向子进程 stdin 写一条 CC IPC `control_request` 并返回 Promise<response>
|
|
21945
21712
|
// —— CC 侧会异步回写匹配 request_id 的 `control_response` 帧,
|
|
21946
21713
|
// 在 stdout 行处理入口被 tryHandleControlResponse 拦截并 resolve pending
|
|
@@ -22130,15 +21897,6 @@ var SessionRunner = class {
|
|
|
22130
21897
|
}
|
|
22131
21898
|
}
|
|
22132
21899
|
};
|
|
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
|
-
}
|
|
22142
21900
|
|
|
22143
21901
|
// src/session/manager.ts
|
|
22144
21902
|
function compressFrameForWire(frame) {
|
|
@@ -22195,6 +21953,16 @@ function nowIso2(deps) {
|
|
|
22195
21953
|
function newSessionId() {
|
|
22196
21954
|
return v4_default().replace(/-/g, "").slice(0, 16);
|
|
22197
21955
|
}
|
|
21956
|
+
function derivePersonaSpawnCwd(file, personaRoot) {
|
|
21957
|
+
const personaId = file.ownerPersonaId;
|
|
21958
|
+
if (!personaId) return file.cwd;
|
|
21959
|
+
if (!personaRoot) {
|
|
21960
|
+
throw new Error(
|
|
21961
|
+
`derivePersonaSpawnCwd: personaRoot missing for owner session ${file.sessionId} (ownerPersonaId=${personaId})`
|
|
21962
|
+
);
|
|
21963
|
+
}
|
|
21964
|
+
return import_node_path6.default.join(personaRoot, safeFileName(personaId));
|
|
21965
|
+
}
|
|
22198
21966
|
function makeInitialState(file, subSessionMeta) {
|
|
22199
21967
|
return {
|
|
22200
21968
|
file,
|
|
@@ -22353,7 +22121,24 @@ var SessionManager = class {
|
|
|
22353
22121
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
22354
22122
|
const store = this.storeFor(scope);
|
|
22355
22123
|
const subSessionMeta = metaFromScope(scope, this.deps.personaRoot ?? "");
|
|
22356
|
-
const
|
|
22124
|
+
const correctedCwd = derivePersonaSpawnCwd(file, this.deps.personaRoot);
|
|
22125
|
+
if (correctedCwd !== file.cwd) {
|
|
22126
|
+
this.deps.logger?.warn("owner session cwd drifted from persona dir; auto-correcting", {
|
|
22127
|
+
sessionId: file.sessionId,
|
|
22128
|
+
ownerPersonaId: file.ownerPersonaId,
|
|
22129
|
+
stale: file.cwd,
|
|
22130
|
+
corrected: correctedCwd
|
|
22131
|
+
});
|
|
22132
|
+
file = { ...file, cwd: correctedCwd, updatedAt: nowIso2(this.deps) };
|
|
22133
|
+
try {
|
|
22134
|
+
store.write(file);
|
|
22135
|
+
} catch (err) {
|
|
22136
|
+
this.deps.logger?.warn("failed to persist auto-corrected owner cwd", {
|
|
22137
|
+
sessionId: file.sessionId,
|
|
22138
|
+
error: err.message
|
|
22139
|
+
});
|
|
22140
|
+
}
|
|
22141
|
+
}
|
|
22357
22142
|
const runner = new SessionRunner(makeInitialState(file, subSessionMeta), {
|
|
22358
22143
|
broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
|
|
22359
22144
|
store,
|
|
@@ -22366,15 +22151,7 @@ var SessionManager = class {
|
|
|
22366
22151
|
resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId),
|
|
22367
22152
|
dataDir: this.deps.dataDir,
|
|
22368
22153
|
personaStore: this.deps.personaStore,
|
|
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
|
|
22154
|
+
ownerDisplayName: this.deps.ownerDisplayName
|
|
22378
22155
|
});
|
|
22379
22156
|
if (this.deps.mode === "tui" && !file.toolSessionId) {
|
|
22380
22157
|
const newTsid = v4_default();
|
|
@@ -22462,14 +22239,31 @@ var SessionManager = class {
|
|
|
22462
22239
|
}
|
|
22463
22240
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
22464
22241
|
create(args) {
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22242
|
+
this.deps.logger?.warn("[spawn-cwd-debug] manager.create entry", {
|
|
22243
|
+
argsOwnerPersonaId: args.ownerPersonaId,
|
|
22244
|
+
argsCwd: args.cwd,
|
|
22245
|
+
personaRoot: this.deps.personaRoot
|
|
22246
|
+
});
|
|
22247
|
+
let cwd;
|
|
22248
|
+
if (args.ownerPersonaId) {
|
|
22249
|
+
cwd = derivePersonaSpawnCwd(
|
|
22250
|
+
{
|
|
22251
|
+
sessionId: "",
|
|
22252
|
+
cwd: args.cwd ?? "",
|
|
22253
|
+
tool: "claude",
|
|
22254
|
+
ownerPersonaId: args.ownerPersonaId,
|
|
22255
|
+
createdAt: "",
|
|
22256
|
+
updatedAt: ""
|
|
22257
|
+
},
|
|
22258
|
+
this.deps.personaRoot
|
|
22259
|
+
);
|
|
22260
|
+
this.deps.logger?.warn("[spawn-cwd-debug] derived persona cwd", { derivedCwd: cwd });
|
|
22261
|
+
} else if (args.cwd) {
|
|
22262
|
+
cwd = args.cwd;
|
|
22263
|
+
this.deps.logger?.warn("[spawn-cwd-debug] using args.cwd (no ownerPersonaId)", {
|
|
22264
|
+
cwd
|
|
22265
|
+
});
|
|
22266
|
+
} else {
|
|
22473
22267
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, "cwd required when ownerPersonaId is absent");
|
|
22474
22268
|
}
|
|
22475
22269
|
try {
|
|
@@ -23525,21 +23319,6 @@ var PersonaRegistry = class {
|
|
|
23525
23319
|
if (entry.revoked) return { ok: false, code: "TOKEN_REVOKED" };
|
|
23526
23320
|
return { ok: true, label: entry.label };
|
|
23527
23321
|
}
|
|
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
|
-
}
|
|
23543
23322
|
};
|
|
23544
23323
|
|
|
23545
23324
|
// src/persona/manager.ts
|
|
@@ -25481,7 +25260,6 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
25481
25260
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
25482
25261
|
|
|
25483
25262
|
// src/transport/local-ws-server.ts
|
|
25484
|
-
var import_node_http = __toESM(require("http"), 1);
|
|
25485
25263
|
var PERSONA_PATH_RE = /^\/personas\/([a-zA-Z0-9._-]+)$/;
|
|
25486
25264
|
var LocalWsServer = class {
|
|
25487
25265
|
constructor(opts) {
|
|
@@ -25491,7 +25269,6 @@ var LocalWsServer = class {
|
|
|
25491
25269
|
}
|
|
25492
25270
|
opts;
|
|
25493
25271
|
wss = null;
|
|
25494
|
-
httpServer = null;
|
|
25495
25272
|
frameHandler = null;
|
|
25496
25273
|
clients = /* @__PURE__ */ new Map();
|
|
25497
25274
|
logger;
|
|
@@ -25499,28 +25276,25 @@ var LocalWsServer = class {
|
|
|
25499
25276
|
async start() {
|
|
25500
25277
|
const host = this.opts.host ?? "127.0.0.1";
|
|
25501
25278
|
await new Promise((resolve2, reject) => {
|
|
25502
|
-
const
|
|
25503
|
-
|
|
25504
|
-
|
|
25505
|
-
|
|
25506
|
-
this.routeConnection(ws, req);
|
|
25507
|
-
});
|
|
25279
|
+
const wss = new import_websocket_server.default({
|
|
25280
|
+
host,
|
|
25281
|
+
port: this.opts.port,
|
|
25282
|
+
clientTracking: true
|
|
25508
25283
|
});
|
|
25509
|
-
|
|
25284
|
+
wss.on("listening", () => {
|
|
25510
25285
|
this.logger?.info("ws listening", { host, port: this.opts.port });
|
|
25511
25286
|
resolve2();
|
|
25512
25287
|
});
|
|
25513
|
-
|
|
25288
|
+
wss.on("error", (err) => {
|
|
25514
25289
|
this.logger?.error("ws server error", { err: err.message });
|
|
25515
25290
|
reject(err);
|
|
25516
25291
|
});
|
|
25517
|
-
|
|
25518
|
-
this.httpServer = httpServer;
|
|
25292
|
+
wss.on("connection", (socket, req) => this.routeConnection(socket, req));
|
|
25519
25293
|
this.wss = wss;
|
|
25520
25294
|
});
|
|
25521
25295
|
}
|
|
25522
25296
|
async stop() {
|
|
25523
|
-
if (!this.
|
|
25297
|
+
if (!this.wss) return;
|
|
25524
25298
|
for (const c of this.clients.values()) {
|
|
25525
25299
|
try {
|
|
25526
25300
|
c.ws.close(1001, "shutdown");
|
|
@@ -25532,32 +25306,7 @@ var LocalWsServer = class {
|
|
|
25532
25306
|
await new Promise((resolve2) => {
|
|
25533
25307
|
this.wss?.close(() => resolve2());
|
|
25534
25308
|
});
|
|
25535
|
-
await new Promise((resolve2) => {
|
|
25536
|
-
this.httpServer?.close(() => resolve2());
|
|
25537
|
-
});
|
|
25538
25309
|
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
|
-
}
|
|
25561
25310
|
}
|
|
25562
25311
|
onFrame(handler) {
|
|
25563
25312
|
this.frameHandler = handler;
|
|
@@ -25659,7 +25408,7 @@ var LocalWsServer = class {
|
|
|
25659
25408
|
}
|
|
25660
25409
|
try {
|
|
25661
25410
|
if (authed) {
|
|
25662
|
-
const readyFrame = this.opts.readyFrameBuilder(
|
|
25411
|
+
const readyFrame = this.opts.readyFrameBuilder();
|
|
25663
25412
|
this.safeSend(socket, { type: "ready", ...readyFrame });
|
|
25664
25413
|
} else {
|
|
25665
25414
|
this.safeSend(socket, { type: "ready", protocolVersion: this.opts.protocolVersion });
|
|
@@ -25696,7 +25445,7 @@ var LocalWsServer = class {
|
|
|
25696
25445
|
if (verdict !== "pass") {
|
|
25697
25446
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
25698
25447
|
try {
|
|
25699
|
-
const full = this.opts.readyFrameBuilder(
|
|
25448
|
+
const full = this.opts.readyFrameBuilder();
|
|
25700
25449
|
this.safeSend(this.clients.get(client.id).ws, { type: "ready", ...full });
|
|
25701
25450
|
} catch (err) {
|
|
25702
25451
|
this.logger?.warn("post-auth ready frame build failed", { err: err.message });
|
|
@@ -26293,687 +26042,11 @@ function isLocalhost(addr) {
|
|
|
26293
26042
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
26294
26043
|
}
|
|
26295
26044
|
|
|
26296
|
-
// src/
|
|
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
|
|
26045
|
+
// src/discovery/state-file.ts
|
|
26348
26046
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
26349
26047
|
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);
|
|
26975
26048
|
function defaultStateFilePath(dataDir) {
|
|
26976
|
-
return
|
|
26049
|
+
return import_node_path14.default.join(dataDir, "state.json");
|
|
26977
26050
|
}
|
|
26978
26051
|
function isPidAlive(pid) {
|
|
26979
26052
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -26995,7 +26068,7 @@ var StateFileManager = class {
|
|
|
26995
26068
|
}
|
|
26996
26069
|
read() {
|
|
26997
26070
|
try {
|
|
26998
|
-
const raw =
|
|
26071
|
+
const raw = import_node_fs13.default.readFileSync(this.file, "utf8");
|
|
26999
26072
|
const parsed = JSON.parse(raw);
|
|
27000
26073
|
return parsed;
|
|
27001
26074
|
} catch {
|
|
@@ -27009,34 +26082,34 @@ var StateFileManager = class {
|
|
|
27009
26082
|
return { status: "stale", existing };
|
|
27010
26083
|
}
|
|
27011
26084
|
write(state) {
|
|
27012
|
-
|
|
26085
|
+
import_node_fs13.default.mkdirSync(import_node_path14.default.dirname(this.file), { recursive: true });
|
|
27013
26086
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
27014
|
-
|
|
27015
|
-
|
|
26087
|
+
import_node_fs13.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
26088
|
+
import_node_fs13.default.renameSync(tmp, this.file);
|
|
27016
26089
|
if (process.platform !== "win32") {
|
|
27017
26090
|
try {
|
|
27018
|
-
|
|
26091
|
+
import_node_fs13.default.chmodSync(this.file, 384);
|
|
27019
26092
|
} catch {
|
|
27020
26093
|
}
|
|
27021
26094
|
}
|
|
27022
26095
|
}
|
|
27023
26096
|
delete() {
|
|
27024
26097
|
try {
|
|
27025
|
-
|
|
26098
|
+
import_node_fs13.default.unlinkSync(this.file);
|
|
27026
26099
|
} catch {
|
|
27027
26100
|
}
|
|
27028
26101
|
}
|
|
27029
26102
|
};
|
|
27030
26103
|
|
|
27031
26104
|
// src/tunnel/tunnel-manager.ts
|
|
27032
|
-
var
|
|
27033
|
-
var
|
|
27034
|
-
var
|
|
26105
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
26106
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
26107
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
27035
26108
|
var import_node_child_process5 = require("child_process");
|
|
27036
26109
|
|
|
27037
26110
|
// src/tunnel/tunnel-store.ts
|
|
27038
|
-
var
|
|
27039
|
-
var
|
|
26111
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
26112
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
27040
26113
|
var TunnelStore = class {
|
|
27041
26114
|
constructor(filePath) {
|
|
27042
26115
|
this.filePath = filePath;
|
|
@@ -27044,7 +26117,7 @@ var TunnelStore = class {
|
|
|
27044
26117
|
filePath;
|
|
27045
26118
|
async get() {
|
|
27046
26119
|
try {
|
|
27047
|
-
const raw = await
|
|
26120
|
+
const raw = await import_node_fs14.default.promises.readFile(this.filePath, "utf8");
|
|
27048
26121
|
const obj = JSON.parse(raw);
|
|
27049
26122
|
if (!isPersistedTunnel(obj)) return null;
|
|
27050
26123
|
return obj;
|
|
@@ -27055,22 +26128,22 @@ var TunnelStore = class {
|
|
|
27055
26128
|
}
|
|
27056
26129
|
}
|
|
27057
26130
|
async set(v2) {
|
|
27058
|
-
const dir =
|
|
27059
|
-
await
|
|
26131
|
+
const dir = import_node_path15.default.dirname(this.filePath);
|
|
26132
|
+
await import_node_fs14.default.promises.mkdir(dir, { recursive: true });
|
|
27060
26133
|
const data = JSON.stringify(v2, null, 2);
|
|
27061
26134
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
27062
|
-
await
|
|
26135
|
+
await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
27063
26136
|
if (process.platform !== "win32") {
|
|
27064
26137
|
try {
|
|
27065
|
-
await
|
|
26138
|
+
await import_node_fs14.default.promises.chmod(tmp, 384);
|
|
27066
26139
|
} catch {
|
|
27067
26140
|
}
|
|
27068
26141
|
}
|
|
27069
|
-
await
|
|
26142
|
+
await import_node_fs14.default.promises.rename(tmp, this.filePath);
|
|
27070
26143
|
}
|
|
27071
26144
|
async clear() {
|
|
27072
26145
|
try {
|
|
27073
|
-
await
|
|
26146
|
+
await import_node_fs14.default.promises.unlink(this.filePath);
|
|
27074
26147
|
} catch (err) {
|
|
27075
26148
|
const code = err?.code;
|
|
27076
26149
|
if (code !== "ENOENT") throw err;
|
|
@@ -27165,9 +26238,9 @@ function escape(v2) {
|
|
|
27165
26238
|
}
|
|
27166
26239
|
|
|
27167
26240
|
// src/tunnel/frpc-binary.ts
|
|
27168
|
-
var
|
|
26241
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
27169
26242
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
27170
|
-
var
|
|
26243
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
27171
26244
|
var import_node_child_process3 = require("child_process");
|
|
27172
26245
|
var import_node_stream2 = require("stream");
|
|
27173
26246
|
var import_promises = require("stream/promises");
|
|
@@ -27199,20 +26272,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
27199
26272
|
}
|
|
27200
26273
|
async function ensureFrpcBinary(opts) {
|
|
27201
26274
|
if (opts.override) {
|
|
27202
|
-
if (!
|
|
26275
|
+
if (!import_node_fs15.default.existsSync(opts.override)) {
|
|
27203
26276
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
27204
26277
|
}
|
|
27205
26278
|
return opts.override;
|
|
27206
26279
|
}
|
|
27207
26280
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
27208
26281
|
const platform = opts.platform ?? detectPlatform();
|
|
27209
|
-
const binDir =
|
|
27210
|
-
|
|
26282
|
+
const binDir = import_node_path16.default.join(opts.dataDir, "bin");
|
|
26283
|
+
import_node_fs15.default.mkdirSync(binDir, { recursive: true });
|
|
27211
26284
|
cleanupStaleArtifacts(binDir);
|
|
27212
|
-
const stableBin =
|
|
27213
|
-
if (
|
|
26285
|
+
const stableBin = import_node_path16.default.join(binDir, "frpc");
|
|
26286
|
+
if (import_node_fs15.default.existsSync(stableBin)) return stableBin;
|
|
27214
26287
|
const partialBin = `${stableBin}.partial`;
|
|
27215
|
-
const tarballPath =
|
|
26288
|
+
const tarballPath = import_node_path16.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
27216
26289
|
try {
|
|
27217
26290
|
const url = frpcDownloadUrl(version2, platform);
|
|
27218
26291
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -27221,8 +26294,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
27221
26294
|
} else {
|
|
27222
26295
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
27223
26296
|
}
|
|
27224
|
-
|
|
27225
|
-
|
|
26297
|
+
import_node_fs15.default.chmodSync(partialBin, 493);
|
|
26298
|
+
import_node_fs15.default.renameSync(partialBin, stableBin);
|
|
27226
26299
|
} finally {
|
|
27227
26300
|
safeUnlink(tarballPath);
|
|
27228
26301
|
safeUnlink(partialBin);
|
|
@@ -27232,15 +26305,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
27232
26305
|
function cleanupStaleArtifacts(binDir) {
|
|
27233
26306
|
let entries;
|
|
27234
26307
|
try {
|
|
27235
|
-
entries =
|
|
26308
|
+
entries = import_node_fs15.default.readdirSync(binDir);
|
|
27236
26309
|
} catch {
|
|
27237
26310
|
return;
|
|
27238
26311
|
}
|
|
27239
26312
|
for (const name of entries) {
|
|
27240
26313
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
27241
|
-
const full =
|
|
26314
|
+
const full = import_node_path16.default.join(binDir, name);
|
|
27242
26315
|
try {
|
|
27243
|
-
|
|
26316
|
+
import_node_fs15.default.rmSync(full, { recursive: true, force: true });
|
|
27244
26317
|
} catch {
|
|
27245
26318
|
}
|
|
27246
26319
|
}
|
|
@@ -27248,7 +26321,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
27248
26321
|
}
|
|
27249
26322
|
function safeUnlink(p2) {
|
|
27250
26323
|
try {
|
|
27251
|
-
|
|
26324
|
+
import_node_fs15.default.unlinkSync(p2);
|
|
27252
26325
|
} catch {
|
|
27253
26326
|
}
|
|
27254
26327
|
}
|
|
@@ -27259,13 +26332,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
27259
26332
|
if (!res.ok || !res.body) {
|
|
27260
26333
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
27261
26334
|
}
|
|
27262
|
-
const out =
|
|
26335
|
+
const out = import_node_fs15.default.createWriteStream(dest);
|
|
27263
26336
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
27264
26337
|
await (0, import_promises.pipeline)(nodeStream, out);
|
|
27265
26338
|
}
|
|
27266
26339
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
27267
|
-
const work =
|
|
27268
|
-
|
|
26340
|
+
const work = import_node_path16.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
26341
|
+
import_node_fs15.default.mkdirSync(work, { recursive: true });
|
|
27269
26342
|
try {
|
|
27270
26343
|
await new Promise((resolve2, reject) => {
|
|
27271
26344
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -27273,32 +26346,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
27273
26346
|
proc.on("exit", (code) => code === 0 ? resolve2() : reject(new Error(`tar exited ${code}`)));
|
|
27274
26347
|
});
|
|
27275
26348
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
27276
|
-
const src =
|
|
27277
|
-
if (!
|
|
26349
|
+
const src = import_node_path16.default.join(work, dirName, "frpc");
|
|
26350
|
+
if (!import_node_fs15.default.existsSync(src)) {
|
|
27278
26351
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
27279
26352
|
}
|
|
27280
|
-
|
|
26353
|
+
import_node_fs15.default.copyFileSync(src, destBin);
|
|
27281
26354
|
} finally {
|
|
27282
|
-
|
|
26355
|
+
import_node_fs15.default.rmSync(work, { recursive: true, force: true });
|
|
27283
26356
|
}
|
|
27284
26357
|
}
|
|
27285
26358
|
|
|
27286
26359
|
// src/tunnel/frpc-process.ts
|
|
27287
|
-
var
|
|
27288
|
-
var
|
|
26360
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
26361
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
27289
26362
|
var import_node_child_process4 = require("child_process");
|
|
27290
26363
|
function frpcPidFilePath(dataDir) {
|
|
27291
|
-
return
|
|
26364
|
+
return import_node_path17.default.join(dataDir, "frpc.pid");
|
|
27292
26365
|
}
|
|
27293
26366
|
function writeFrpcPid(dataDir, pid) {
|
|
27294
26367
|
try {
|
|
27295
|
-
|
|
26368
|
+
import_node_fs16.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
27296
26369
|
} catch {
|
|
27297
26370
|
}
|
|
27298
26371
|
}
|
|
27299
26372
|
function clearFrpcPid(dataDir) {
|
|
27300
26373
|
try {
|
|
27301
|
-
|
|
26374
|
+
import_node_fs16.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
27302
26375
|
} catch {
|
|
27303
26376
|
}
|
|
27304
26377
|
}
|
|
@@ -27314,7 +26387,7 @@ function defaultIsPidAlive(pid) {
|
|
|
27314
26387
|
}
|
|
27315
26388
|
function defaultReadPidFile(file) {
|
|
27316
26389
|
try {
|
|
27317
|
-
return
|
|
26390
|
+
return import_node_fs16.default.readFileSync(file, "utf8");
|
|
27318
26391
|
} catch {
|
|
27319
26392
|
return null;
|
|
27320
26393
|
}
|
|
@@ -27330,7 +26403,7 @@ function defaultSleep(ms) {
|
|
|
27330
26403
|
}
|
|
27331
26404
|
async function killStaleFrpc(deps) {
|
|
27332
26405
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
27333
|
-
const tomlPath =
|
|
26406
|
+
const tomlPath = import_node_path17.default.join(deps.dataDir, "frpc.toml");
|
|
27334
26407
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
27335
26408
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
27336
26409
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -27354,7 +26427,7 @@ async function killStaleFrpc(deps) {
|
|
|
27354
26427
|
}
|
|
27355
26428
|
if (victims.size === 0) {
|
|
27356
26429
|
try {
|
|
27357
|
-
|
|
26430
|
+
import_node_fs16.default.unlinkSync(pidFile);
|
|
27358
26431
|
} catch {
|
|
27359
26432
|
}
|
|
27360
26433
|
return;
|
|
@@ -27365,7 +26438,7 @@ async function killStaleFrpc(deps) {
|
|
|
27365
26438
|
}
|
|
27366
26439
|
await sleep(deps.reapWaitMs ?? 300);
|
|
27367
26440
|
try {
|
|
27368
|
-
|
|
26441
|
+
import_node_fs16.default.unlinkSync(pidFile);
|
|
27369
26442
|
} catch {
|
|
27370
26443
|
}
|
|
27371
26444
|
}
|
|
@@ -27402,7 +26475,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
27402
26475
|
var TunnelManager = class {
|
|
27403
26476
|
constructor(deps) {
|
|
27404
26477
|
this.deps = deps;
|
|
27405
|
-
this.store = deps.store ?? new TunnelStore(
|
|
26478
|
+
this.store = deps.store ?? new TunnelStore(import_node_path18.default.join(deps.dataDir, "tunnel.json"));
|
|
27406
26479
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
27407
26480
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
27408
26481
|
}
|
|
@@ -27529,8 +26602,8 @@ var TunnelManager = class {
|
|
|
27529
26602
|
dataDir: this.deps.dataDir,
|
|
27530
26603
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
27531
26604
|
});
|
|
27532
|
-
const tomlPath =
|
|
27533
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
26605
|
+
const tomlPath = import_node_path18.default.join(this.deps.dataDir, "frpc.toml");
|
|
26606
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto4.default.randomBytes(3).toString("hex")}`;
|
|
27534
26607
|
const toml = buildFrpcToml({
|
|
27535
26608
|
serverAddr: t.frpsHost,
|
|
27536
26609
|
serverPort: t.frpsPort,
|
|
@@ -27540,12 +26613,12 @@ var TunnelManager = class {
|
|
|
27540
26613
|
localPort,
|
|
27541
26614
|
logLevel: "info"
|
|
27542
26615
|
});
|
|
27543
|
-
await
|
|
26616
|
+
await import_node_fs17.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
27544
26617
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
27545
26618
|
stdio: ["ignore", "pipe", "pipe"]
|
|
27546
26619
|
});
|
|
27547
|
-
const logFilePath =
|
|
27548
|
-
const logStream =
|
|
26620
|
+
const logFilePath = import_node_path18.default.join(this.deps.dataDir, "frpc.log");
|
|
26621
|
+
const logStream = import_node_fs17.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
27549
26622
|
logStream.on("error", () => {
|
|
27550
26623
|
});
|
|
27551
26624
|
const tee = (chunk) => {
|
|
@@ -27628,22 +26701,22 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
27628
26701
|
|
|
27629
26702
|
// src/tunnel/device-key.ts
|
|
27630
26703
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
27631
|
-
var
|
|
26704
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
27632
26705
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
27633
26706
|
function deriveStableDeviceKey(opts = {}) {
|
|
27634
26707
|
const hostname = opts.hostname ?? import_node_os10.default.hostname();
|
|
27635
26708
|
const uid = opts.uid ?? (typeof import_node_os10.default.userInfo === "function" ? import_node_os10.default.userInfo().uid : 0);
|
|
27636
26709
|
const input = `${hostname}::${uid}`;
|
|
27637
|
-
return
|
|
26710
|
+
return import_node_crypto5.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
27638
26711
|
}
|
|
27639
26712
|
|
|
27640
26713
|
// src/auth-store.ts
|
|
27641
|
-
var
|
|
27642
|
-
var
|
|
27643
|
-
var
|
|
26714
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
26715
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
26716
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
27644
26717
|
var AUTH_FILE_NAME = "auth.json";
|
|
27645
26718
|
function authFilePath(dataDir) {
|
|
27646
|
-
return
|
|
26719
|
+
return import_node_path19.default.join(dataDir, AUTH_FILE_NAME);
|
|
27647
26720
|
}
|
|
27648
26721
|
function loadOrCreateAuthToken(opts) {
|
|
27649
26722
|
const file = authFilePath(opts.dataDir);
|
|
@@ -27655,11 +26728,11 @@ function loadOrCreateAuthToken(opts) {
|
|
|
27655
26728
|
return token;
|
|
27656
26729
|
}
|
|
27657
26730
|
function defaultGenerate() {
|
|
27658
|
-
return
|
|
26731
|
+
return import_node_crypto6.default.randomBytes(32).toString("base64url");
|
|
27659
26732
|
}
|
|
27660
26733
|
function readAuthFile(file) {
|
|
27661
26734
|
try {
|
|
27662
|
-
const raw =
|
|
26735
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
27663
26736
|
const parsed = JSON.parse(raw);
|
|
27664
26737
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
27665
26738
|
return {
|
|
@@ -27675,25 +26748,25 @@ function readAuthFile(file) {
|
|
|
27675
26748
|
}
|
|
27676
26749
|
}
|
|
27677
26750
|
function writeAuthFile(file, content) {
|
|
27678
|
-
|
|
27679
|
-
|
|
26751
|
+
import_node_fs18.default.mkdirSync(import_node_path19.default.dirname(file), { recursive: true });
|
|
26752
|
+
import_node_fs18.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
27680
26753
|
try {
|
|
27681
|
-
|
|
26754
|
+
import_node_fs18.default.chmodSync(file, 384);
|
|
27682
26755
|
} catch {
|
|
27683
26756
|
}
|
|
27684
26757
|
}
|
|
27685
26758
|
|
|
27686
26759
|
// src/owner-profile.ts
|
|
27687
|
-
var
|
|
26760
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
27688
26761
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
27689
|
-
var
|
|
26762
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
27690
26763
|
var PROFILE_FILENAME = "profile.json";
|
|
27691
26764
|
function loadOwnerDisplayName(dataDir) {
|
|
27692
26765
|
const fallback = import_node_os11.default.userInfo().username;
|
|
27693
|
-
const profilePath =
|
|
26766
|
+
const profilePath = import_node_path20.default.join(dataDir, PROFILE_FILENAME);
|
|
27694
26767
|
let raw;
|
|
27695
26768
|
try {
|
|
27696
|
-
raw =
|
|
26769
|
+
raw = import_node_fs19.default.readFileSync(profilePath, "utf8");
|
|
27697
26770
|
} catch {
|
|
27698
26771
|
return fallback;
|
|
27699
26772
|
}
|
|
@@ -27722,12 +26795,12 @@ init_protocol();
|
|
|
27722
26795
|
init_protocol();
|
|
27723
26796
|
|
|
27724
26797
|
// src/session/fork.ts
|
|
27725
|
-
var
|
|
26798
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
27726
26799
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
27727
|
-
var
|
|
26800
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
27728
26801
|
init_claude_history();
|
|
27729
26802
|
function readJsonlEntries(file) {
|
|
27730
|
-
const raw =
|
|
26803
|
+
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
27731
26804
|
const out = [];
|
|
27732
26805
|
for (const line of raw.split("\n")) {
|
|
27733
26806
|
const t = line.trim();
|
|
@@ -27740,10 +26813,10 @@ function readJsonlEntries(file) {
|
|
|
27740
26813
|
return out;
|
|
27741
26814
|
}
|
|
27742
26815
|
function forkSession(input) {
|
|
27743
|
-
const baseDir = input.baseDir ??
|
|
27744
|
-
const projectDir =
|
|
27745
|
-
const sourceFile =
|
|
27746
|
-
if (!
|
|
26816
|
+
const baseDir = input.baseDir ?? import_node_path21.default.join(import_node_os12.default.homedir(), ".claude");
|
|
26817
|
+
const projectDir = import_node_path21.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
|
|
26818
|
+
const sourceFile = import_node_path21.default.join(projectDir, `${input.toolSessionId}.jsonl`);
|
|
26819
|
+
if (!import_node_fs20.default.existsSync(sourceFile)) {
|
|
27747
26820
|
throw new Error(`fork: source transcript not found: ${sourceFile}`);
|
|
27748
26821
|
}
|
|
27749
26822
|
const entries = readJsonlEntries(sourceFile);
|
|
@@ -27773,9 +26846,9 @@ function forkSession(input) {
|
|
|
27773
26846
|
}
|
|
27774
26847
|
forkedLines.push(JSON.stringify(forked));
|
|
27775
26848
|
}
|
|
27776
|
-
const forkedFilePath =
|
|
27777
|
-
|
|
27778
|
-
|
|
26849
|
+
const forkedFilePath = import_node_path21.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
|
|
26850
|
+
import_node_fs20.default.mkdirSync(projectDir, { recursive: true });
|
|
26851
|
+
import_node_fs20.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
|
|
27779
26852
|
return { forkedToolSessionId, forkedFilePath };
|
|
27780
26853
|
}
|
|
27781
26854
|
|
|
@@ -28096,9 +27169,9 @@ init_protocol();
|
|
|
28096
27169
|
|
|
28097
27170
|
// src/workspace/git.ts
|
|
28098
27171
|
var import_node_child_process6 = require("child_process");
|
|
28099
|
-
var
|
|
27172
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
28100
27173
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
28101
|
-
var
|
|
27174
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
28102
27175
|
var import_node_util = require("util");
|
|
28103
27176
|
var pexec = (0, import_node_util.promisify)(import_node_child_process6.execFile);
|
|
28104
27177
|
function formatChildProcessError(err) {
|
|
@@ -28113,9 +27186,9 @@ function formatChildProcessError(err) {
|
|
|
28113
27186
|
return e.message ?? "unknown error";
|
|
28114
27187
|
}
|
|
28115
27188
|
function normalizePath(p2) {
|
|
28116
|
-
const resolved =
|
|
27189
|
+
const resolved = import_node_path22.default.resolve(p2);
|
|
28117
27190
|
try {
|
|
28118
|
-
return
|
|
27191
|
+
return import_node_fs21.default.realpathSync(resolved);
|
|
28119
27192
|
} catch {
|
|
28120
27193
|
return resolved;
|
|
28121
27194
|
}
|
|
@@ -28216,13 +27289,13 @@ function flattenToDirName(branch) {
|
|
|
28216
27289
|
}
|
|
28217
27290
|
function encodeClaudeProjectDir(absPath) {
|
|
28218
27291
|
if (!absPath || typeof absPath !== "string") return "";
|
|
28219
|
-
let canonical =
|
|
27292
|
+
let canonical = import_node_path22.default.resolve(absPath);
|
|
28220
27293
|
try {
|
|
28221
|
-
canonical =
|
|
27294
|
+
canonical = import_node_fs21.default.realpathSync(canonical);
|
|
28222
27295
|
} catch {
|
|
28223
27296
|
try {
|
|
28224
|
-
const parent =
|
|
28225
|
-
canonical =
|
|
27297
|
+
const parent = import_node_fs21.default.realpathSync(import_node_path22.default.dirname(canonical));
|
|
27298
|
+
canonical = import_node_path22.default.join(parent, import_node_path22.default.basename(canonical));
|
|
28226
27299
|
} catch {
|
|
28227
27300
|
}
|
|
28228
27301
|
}
|
|
@@ -28246,11 +27319,11 @@ async function createWorktree(input) {
|
|
|
28246
27319
|
if (!isGitRoot) {
|
|
28247
27320
|
throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
|
|
28248
27321
|
}
|
|
28249
|
-
const parent =
|
|
28250
|
-
if (parent === "/" || parent ===
|
|
27322
|
+
const parent = import_node_path22.default.dirname(import_node_path22.default.resolve(cwd));
|
|
27323
|
+
if (parent === "/" || parent === import_node_path22.default.resolve(cwd)) {
|
|
28251
27324
|
throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
|
|
28252
27325
|
}
|
|
28253
|
-
const worktreeRoot =
|
|
27326
|
+
const worktreeRoot = import_node_path22.default.join(parent, dirName);
|
|
28254
27327
|
try {
|
|
28255
27328
|
await pexec("git", ["-C", cwd, "fetch", "origin", baseBranch, "--no-tags"], {
|
|
28256
27329
|
timeout: 3e4
|
|
@@ -28269,7 +27342,7 @@ async function createWorktree(input) {
|
|
|
28269
27342
|
const msg = err.message;
|
|
28270
27343
|
if (msg.startsWith("\u5206\u652F ")) throw err;
|
|
28271
27344
|
}
|
|
28272
|
-
if (
|
|
27345
|
+
if (import_node_fs21.default.existsSync(worktreeRoot)) {
|
|
28273
27346
|
throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
|
|
28274
27347
|
}
|
|
28275
27348
|
try {
|
|
@@ -28297,8 +27370,8 @@ async function removeWorktree(input) {
|
|
|
28297
27370
|
);
|
|
28298
27371
|
const gitCommonDir = stdout.trim();
|
|
28299
27372
|
if (!gitCommonDir) throw new Error("empty git-common-dir");
|
|
28300
|
-
const absGitCommon =
|
|
28301
|
-
repoRoot =
|
|
27373
|
+
const absGitCommon = import_node_path22.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path22.default.resolve(worktreeRoot, gitCommonDir);
|
|
27374
|
+
repoRoot = import_node_path22.default.dirname(absGitCommon);
|
|
28302
27375
|
} catch {
|
|
28303
27376
|
repoRoot = null;
|
|
28304
27377
|
}
|
|
@@ -28310,7 +27383,7 @@ async function removeWorktree(input) {
|
|
|
28310
27383
|
} catch (err) {
|
|
28311
27384
|
const stderr = err.stderr ?? "";
|
|
28312
27385
|
const lower = stderr.toLowerCase();
|
|
28313
|
-
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !
|
|
27386
|
+
const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs21.default.existsSync(worktreeRoot);
|
|
28314
27387
|
if (!vanished) {
|
|
28315
27388
|
throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${formatChildProcessError(err)}`);
|
|
28316
27389
|
}
|
|
@@ -28329,10 +27402,10 @@ async function removeWorktree(input) {
|
|
|
28329
27402
|
try {
|
|
28330
27403
|
const encoded = encodeClaudeProjectDir(worktreeRoot);
|
|
28331
27404
|
if (encoded) {
|
|
28332
|
-
const projectsRoot =
|
|
28333
|
-
const target =
|
|
28334
|
-
if (target.startsWith(projectsRoot +
|
|
28335
|
-
|
|
27405
|
+
const projectsRoot = import_node_path22.default.join(import_node_os13.default.homedir(), ".claude", "projects");
|
|
27406
|
+
const target = import_node_path22.default.resolve(projectsRoot, encoded);
|
|
27407
|
+
if (target.startsWith(projectsRoot + import_node_path22.default.sep) && target !== projectsRoot) {
|
|
27408
|
+
import_node_fs21.default.rmSync(target, { recursive: true, force: true });
|
|
28336
27409
|
}
|
|
28337
27410
|
}
|
|
28338
27411
|
} catch {
|
|
@@ -28411,7 +27484,7 @@ init_protocol();
|
|
|
28411
27484
|
var version = "0.2.6".length > 0 ? "0.2.6" : "dev";
|
|
28412
27485
|
|
|
28413
27486
|
// src/handlers/meta.ts
|
|
28414
|
-
function buildReadyFrame(deps
|
|
27487
|
+
function buildReadyFrame(deps) {
|
|
28415
27488
|
const info = deps.manager.info();
|
|
28416
27489
|
const tools = [];
|
|
28417
27490
|
for (const id of listRegistered()) {
|
|
@@ -28423,14 +27496,6 @@ function buildReadyFrame(deps, client) {
|
|
|
28423
27496
|
}
|
|
28424
27497
|
}
|
|
28425
27498
|
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
|
-
}
|
|
28434
27499
|
return {
|
|
28435
27500
|
version,
|
|
28436
27501
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -28439,8 +27504,7 @@ function buildReadyFrame(deps, client) {
|
|
|
28439
27504
|
tools,
|
|
28440
27505
|
runningSessions: info.runningSessions,
|
|
28441
27506
|
tunnelUrl,
|
|
28442
|
-
mode: deps.mode
|
|
28443
|
-
...fileSharing
|
|
27507
|
+
mode: deps.mode
|
|
28444
27508
|
};
|
|
28445
27509
|
}
|
|
28446
27510
|
function buildMetaHandlers(deps) {
|
|
@@ -28551,200 +27615,6 @@ function buildPersonaHandlers(deps) {
|
|
|
28551
27615
|
};
|
|
28552
27616
|
}
|
|
28553
27617
|
|
|
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
|
-
|
|
28748
27618
|
// src/handlers/index.ts
|
|
28749
27619
|
function buildMethodHandlers(deps) {
|
|
28750
27620
|
return {
|
|
@@ -28760,8 +27630,7 @@ function buildMethodHandlers(deps) {
|
|
|
28760
27630
|
personaRegistry: deps.personaRegistry,
|
|
28761
27631
|
sessionManager: deps.manager,
|
|
28762
27632
|
personaBoundHandler: deps.personaBoundHandler
|
|
28763
|
-
})
|
|
28764
|
-
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
27633
|
+
})
|
|
28765
27634
|
};
|
|
28766
27635
|
}
|
|
28767
27636
|
|
|
@@ -28769,7 +27638,7 @@ function buildMethodHandlers(deps) {
|
|
|
28769
27638
|
async function startDaemon(config) {
|
|
28770
27639
|
const logger = createLogger({
|
|
28771
27640
|
level: config.logLevel,
|
|
28772
|
-
file:
|
|
27641
|
+
file: import_node_path23.default.join(config.dataDir, "clawd.log")
|
|
28773
27642
|
});
|
|
28774
27643
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
28775
27644
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -28801,7 +27670,7 @@ async function startDaemon(config) {
|
|
|
28801
27670
|
const agents = new AgentsScanner();
|
|
28802
27671
|
const history = new ClaudeHistoryReader();
|
|
28803
27672
|
let transport = null;
|
|
28804
|
-
const personaStore = new PersonaStore(
|
|
27673
|
+
const personaStore = new PersonaStore(import_node_path23.default.join(config.dataDir, "personas"));
|
|
28805
27674
|
const defaultsRoot = findDefaultsRoot();
|
|
28806
27675
|
if (defaultsRoot) {
|
|
28807
27676
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -28809,18 +27678,13 @@ async function startDaemon(config) {
|
|
|
28809
27678
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
28810
27679
|
}
|
|
28811
27680
|
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
|
-
});
|
|
28817
27681
|
const manager = new SessionManager({
|
|
28818
27682
|
store,
|
|
28819
27683
|
logger,
|
|
28820
27684
|
getAdapter,
|
|
28821
27685
|
historyReader: history,
|
|
28822
27686
|
dataDir: config.dataDir,
|
|
28823
|
-
personaRoot:
|
|
27687
|
+
personaRoot: import_node_path23.default.join(config.dataDir, "personas"),
|
|
28824
27688
|
personaStore,
|
|
28825
27689
|
ownerDisplayName,
|
|
28826
27690
|
mode: config.mode,
|
|
@@ -28837,38 +27701,6 @@ async function startDaemon(config) {
|
|
|
28837
27701
|
return;
|
|
28838
27702
|
}
|
|
28839
27703
|
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
|
-
}
|
|
28872
27704
|
}
|
|
28873
27705
|
});
|
|
28874
27706
|
const observer = new SessionObserver({
|
|
@@ -28914,12 +27746,6 @@ async function startDaemon(config) {
|
|
|
28914
27746
|
sessionManager: manager
|
|
28915
27747
|
});
|
|
28916
27748
|
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
|
-
};
|
|
28923
27749
|
const personaBoundHandler = new PersonaBoundHandler({
|
|
28924
27750
|
registry: personaRegistry,
|
|
28925
27751
|
personaManager,
|
|
@@ -28945,73 +27771,22 @@ async function startDaemon(config) {
|
|
|
28945
27771
|
getTunnelUrl: () => currentTunnelUrl,
|
|
28946
27772
|
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
28947
27773
|
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
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
|
|
27774
|
+
mode: config.mode
|
|
28992
27775
|
});
|
|
28993
27776
|
wsServer = new LocalWsServer({
|
|
28994
27777
|
host: config.host,
|
|
28995
27778
|
port: config.port,
|
|
28996
27779
|
logger,
|
|
28997
|
-
readyFrameBuilder: (
|
|
28998
|
-
|
|
28999
|
-
|
|
29000
|
-
|
|
29001
|
-
|
|
29002
|
-
|
|
29003
|
-
|
|
29004
|
-
// file-sharing 字段:httpBaseUrl 跟 tunnel 状态走;httpToken 复用 owner WS token
|
|
29005
|
-
getHttpBaseUrl,
|
|
29006
|
-
httpToken: resolvedAuthToken
|
|
29007
|
-
},
|
|
29008
|
-
ctx
|
|
29009
|
-
),
|
|
27780
|
+
readyFrameBuilder: () => buildReadyFrame({
|
|
27781
|
+
manager,
|
|
27782
|
+
getAdapter,
|
|
27783
|
+
getTunnelUrl: () => currentTunnelUrl,
|
|
27784
|
+
// ready 帧 mode = daemon CC spawn 模式('sdk' | 'tui');UI 用它挂 XtermPanel
|
|
27785
|
+
mode: config.mode
|
|
27786
|
+
}),
|
|
29010
27787
|
protocolVersion: PROTOCOL_VERSION,
|
|
29011
27788
|
authGate: authGate ?? void 0,
|
|
29012
27789
|
personaBoundHandler,
|
|
29013
|
-
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
29014
|
-
httpRequestHandler: httpRouter,
|
|
29015
27790
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
29016
27791
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
29017
27792
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -29127,8 +27902,8 @@ async function startDaemon(config) {
|
|
|
29127
27902
|
const lines = [
|
|
29128
27903
|
`Tunnel: ${r.url}`,
|
|
29129
27904
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
29130
|
-
`Frpc config: ${
|
|
29131
|
-
`Frpc log: ${
|
|
27905
|
+
`Frpc config: ${import_node_path23.default.join(config.dataDir, "frpc.toml")}`,
|
|
27906
|
+
`Frpc log: ${import_node_path23.default.join(config.dataDir, "frpc.log")}`
|
|
29132
27907
|
];
|
|
29133
27908
|
const width = Math.max(...lines.map((l) => l.length));
|
|
29134
27909
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -29141,8 +27916,8 @@ ${bar}
|
|
|
29141
27916
|
|
|
29142
27917
|
`);
|
|
29143
27918
|
try {
|
|
29144
|
-
const connectPath =
|
|
29145
|
-
|
|
27919
|
+
const connectPath = import_node_path23.default.join(config.dataDir, "connect.txt");
|
|
27920
|
+
import_node_fs22.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
29146
27921
|
} catch {
|
|
29147
27922
|
}
|
|
29148
27923
|
} catch (err) {
|