@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.
Files changed (2) hide show
  1. package/dist/cli.cjs +270 -1495
  2. 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: path33, errorMaps, issueData } = params;
621
- const fullPath = [...path33, ...issueData.path || []];
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, path33, key) {
916
+ constructor(parent, value, path28, key) {
933
917
  this._cachedPath = [];
934
918
  this.parent = parent;
935
919
  this.data = value;
936
- this._path = path33;
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 path33 = req.path;
5547
- _req.url = typeof path33 === "string" ? path33 : req.url ? req.url.path || req.url : void 0;
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(path33) {
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 < path33.length; i++) {
5719
- const char = path33[i];
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 path33 of paths) {
5851
- const parts = parsePath(path33);
5674
+ for (const path28 of paths) {
5675
+ const parts = parsePath(path28);
5852
5676
  if (parts.includes("*")) {
5853
- redactWildcardPath(obj, parts, censor, path33, remove);
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, path33) => {
5939
- const fullPath = [...pathArray.slice(0, pathLength), ...path33];
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 path33 of pathsToClone) {
5975
- const parts = parsePath(path33);
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(path33) {
6028
- if (typeof path33 !== "string") {
5851
+ function validatePath(path28) {
5852
+ if (typeof path28 !== "string") {
6029
5853
  throw new Error("Paths must be (non-empty) strings");
6030
5854
  }
6031
- if (path33 === "") {
5855
+ if (path28 === "") {
6032
5856
  throw new Error("Invalid redaction path ()");
6033
5857
  }
6034
- if (path33.includes("..")) {
6035
- throw new Error(`Invalid redaction path (${path33})`);
5858
+ if (path28.includes("..")) {
5859
+ throw new Error(`Invalid redaction path (${path28})`);
6036
5860
  }
6037
- if (path33.includes(",")) {
6038
- throw new Error(`Invalid redaction path (${path33})`);
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 < path33.length; i++) {
6044
- const char = path33[i];
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 (${path33})`);
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 (${path33})`);
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 path33 of paths) {
6071
- validatePath(path33);
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, path33) => {
6240
- return censor(value, [k2, ...path33]);
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 fs30 = require("fs");
6282
+ var fs26 = require("fs");
6459
6283
  var EventEmitter2 = require("events");
6460
6284
  var inherits = require("util").inherits;
6461
- var path33 = require("path");
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) fs30.mkdirSync(path33.dirname(file), { recursive: true });
6516
- const fd = fs30.openSync(file, flags, mode);
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
- fs30.mkdir(path33.dirname(file), { recursive: true }, (err) => {
6347
+ fs26.mkdir(path28.dirname(file), { recursive: true }, (err) => {
6524
6348
  if (err) return fileOpened(err);
6525
- fs30.open(file, flags, mode, fileOpened);
6349
+ fs26.open(file, flags, mode, fileOpened);
6526
6350
  });
6527
6351
  } else {
6528
- fs30.open(file, flags, mode, fileOpened);
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 = () => fs30.writeSync(this.fd, this._writingBuf);
6570
- fsWrite = () => fs30.write(this.fd, this._writingBuf, this.release);
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 fs30.writeSync(this.fd, this._writingBuf);
6403
+ return fs26.writeSync(this.fd, this._writingBuf);
6580
6404
  }
6581
- return fs30.writeSync(this.fd, this._writingBuf, "utf8");
6405
+ return fs26.writeSync(this.fd, this._writingBuf, "utf8");
6582
6406
  };
6583
6407
  fsWrite = () => {
6584
6408
  if (Buffer.isBuffer(this._writingBuf)) {
6585
- return fs30.write(this.fd, this._writingBuf, this.release);
6409
+ return fs26.write(this.fd, this._writingBuf, this.release);
6586
6410
  }
6587
- return fs30.write(this.fd, this._writingBuf, "utf8", this.release);
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
- fs30.fsyncSync(this.fd);
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
- fs30.fsync(this.fd, (err) => {
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
- fs30.close(fd, (err) => {
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) ? fs30.writeSync(this.fd, buf) : fs30.writeSync(this.fd, buf, "utf8");
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
- fs30.fsyncSync(this.fd);
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 = fs30.writeSync(this.fd, buf);
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) ? fs30.writeSync(this.fd, this._writingBuf) : fs30.writeSync(this.fd, this._writingBuf, "utf8");
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
- fs30.write(this.fd, this._writingBuf, release);
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 = fs30.writeSync(this.fd, this._writingBuf);
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
- fs30.write(this.fd, this._writingBuf, release);
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
- fs30.fsync(sonic.fd, closeWrapped);
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
- fs30.close(sonic.fd, done);
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(path33, added, removed, oldPosInc, options) {
10155
- var last = path33.lastComponent;
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: path33.oldPos + oldPosInc,
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: path33.oldPos + oldPosInc,
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 path33 = typeof rec.path === "string" ? rec.path : null;
10423
+ const path28 = typeof rec.path === "string" ? rec.path : null;
10600
10424
  const content = typeof rec.content === "string" ? rec.content : null;
10601
- if (!path33 || content == null) return null;
10602
- const entry = { path: path33, content };
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 path33 = typeof rec.path === "string" ? rec.path : null;
11230
+ const path28 = typeof rec.path === "string" ? rec.path : null;
11407
11231
  const content = typeof rec.content === "string" ? rec.content : null;
11408
- if (!path33 || content == null) return null;
11409
- const out = { path: path33, content };
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 http2 = require("http");
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 : http2.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 http2 = require("http");
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 = http2.createServer((req, res) => {
20008
- const body = http2.STATUS_CODES[426];
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 || http2.STATUS_CODES[code];
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} ${http2.STATUS_CODES[code]}\r
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 = import_node_path29.default.dirname(opts.recordPath);
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
- import_node_fs27.default.mkdirSync(dir, { recursive: true });
20338
- stream = import_node_fs27.default.createWriteStream(opts.recordPath, { flags: "a" });
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 import_node_fs27, import_node_path29;
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
- import_node_fs27 = __toESM(require("fs"), 1);
20368
- import_node_path29 = __toESM(require("path"), 1);
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, import_node_fs28.mkdtempSync)(import_node_path30.default.join(import_node_os15.default.tmpdir(), "clawd-runcase-"));
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, import_node_fs28.rmSync)(cwd, { recursive: true, force: true });
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 import_node_fs28, import_node_os15, import_node_path30;
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
- import_node_fs28 = require("fs");
20406
+ import_node_fs24 = require("fs");
20583
20407
  import_node_os15 = __toESM(require("os"), 1);
20584
- import_node_path30 = __toESM(require("path"), 1);
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 import_node_path28 = __toESM(require("path"), 1);
20817
- var import_node_fs26 = __toESM(require("fs"), 1);
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
- // file-sharing (spec §6 PR 3):在 stdout-line 解析时同步观察 file-edit 工具事件,
21823
- // 配对 tool_use ↔ tool_result 调 onFileEdit。原 reducer 路径不变。
21824
- parseLine: (l) => {
21825
- const events = adapter.parseLine(l);
21826
- if (this.hooks.onFileEdit) this.observeForFileEdit(events);
21827
- return events;
21828
- },
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 attachmentGroup = this.deps.attachmentGroup;
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
- let cwd = args.cwd;
22466
- if (args.ownerPersonaId && !cwd) {
22467
- if (!this.deps.personaRoot) {
22468
- throw new Error("personaRoot required to derive cwd from ownerPersonaId");
22469
- }
22470
- cwd = import_node_path6.default.join(this.deps.personaRoot, safeFileName(args.ownerPersonaId));
22471
- }
22472
- if (!cwd) {
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 httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
25503
- const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
25504
- httpServer.on("upgrade", (req, socket, head) => {
25505
- wss.handleUpgrade(req, socket, head, (ws) => {
25506
- this.routeConnection(ws, req);
25507
- });
25279
+ const wss = new import_websocket_server.default({
25280
+ host,
25281
+ port: this.opts.port,
25282
+ clientTracking: true
25508
25283
  });
25509
- httpServer.on("listening", () => {
25284
+ wss.on("listening", () => {
25510
25285
  this.logger?.info("ws listening", { host, port: this.opts.port });
25511
25286
  resolve2();
25512
25287
  });
25513
- httpServer.on("error", (err) => {
25288
+ wss.on("error", (err) => {
25514
25289
  this.logger?.error("ws server error", { err: err.message });
25515
25290
  reject(err);
25516
25291
  });
25517
- httpServer.listen(this.opts.port, host);
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.httpServer) return;
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({ remoteAddress });
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({ remoteAddress });
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/transport/auth-context.ts
26297
- var AuthContextResolver = class {
26298
- constructor(opts) {
26299
- this.opts = opts;
26300
- }
26301
- opts;
26302
- /**
26303
- * 从 `Authorization` 头解析。null = 无凭证 / 不识别 / revoked,调用方一律 401。
26304
- * remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
26305
- */
26306
- resolveFromHeader(authHeader, remoteAddress) {
26307
- const token = parseBearer(authHeader);
26308
- if (!token) return null;
26309
- const isLoopback = isLoopbackAddr(remoteAddress);
26310
- if (this.opts.ownerToken && constantTimeEqual2(token, this.opts.ownerToken)) {
26311
- return { role: "owner", isLoopback };
26312
- }
26313
- const personalHit = this.opts.personaRegistry.findByToken(token);
26314
- if (personalHit) {
26315
- return {
26316
- role: "personal",
26317
- personaId: personalHit.personaId,
26318
- label: personalHit.label,
26319
- isLoopback
26320
- };
26321
- }
26322
- return null;
26323
- }
26324
- };
26325
- function parseBearer(header) {
26326
- if (!header) return null;
26327
- const raw = Array.isArray(header) ? header[0] : header;
26328
- if (typeof raw !== "string") return null;
26329
- const m2 = /^Bearer\s+(\S+)$/i.exec(raw.trim());
26330
- return m2 ? m2[1] : null;
26331
- }
26332
- function isLoopbackAddr(addr) {
26333
- if (!addr) return false;
26334
- return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
26335
- }
26336
- function constantTimeEqual2(a, b2) {
26337
- if (a.length !== b2.length) return false;
26338
- let diff2 = 0;
26339
- for (let i = 0; i < a.length; i++) diff2 |= a.charCodeAt(i) ^ b2.charCodeAt(i);
26340
- return diff2 === 0;
26341
- }
26342
-
26343
- // src/transport/http-router.ts
26344
- var import_node_fs14 = __toESM(require("fs"), 1);
26345
- var import_node_path16 = __toESM(require("path"), 1);
26346
-
26347
- // src/attachment/group.ts
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 import_node_path19.default.join(dataDir, "state.json");
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 = import_node_fs17.default.readFileSync(this.file, "utf8");
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
- import_node_fs17.default.mkdirSync(import_node_path19.default.dirname(this.file), { recursive: true });
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
- import_node_fs17.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
27015
- import_node_fs17.default.renameSync(tmp, this.file);
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
- import_node_fs17.default.chmodSync(this.file, 384);
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
- import_node_fs17.default.unlinkSync(this.file);
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 import_node_fs21 = __toESM(require("fs"), 1);
27033
- var import_node_path23 = __toESM(require("path"), 1);
27034
- var import_node_crypto6 = __toESM(require("crypto"), 1);
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 import_node_fs18 = __toESM(require("fs"), 1);
27039
- var import_node_path20 = __toESM(require("path"), 1);
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 import_node_fs18.default.promises.readFile(this.filePath, "utf8");
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 = import_node_path20.default.dirname(this.filePath);
27059
- await import_node_fs18.default.promises.mkdir(dir, { recursive: true });
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 import_node_fs18.default.promises.writeFile(tmp, data, { mode: 384 });
26135
+ await import_node_fs14.default.promises.writeFile(tmp, data, { mode: 384 });
27063
26136
  if (process.platform !== "win32") {
27064
26137
  try {
27065
- await import_node_fs18.default.promises.chmod(tmp, 384);
26138
+ await import_node_fs14.default.promises.chmod(tmp, 384);
27066
26139
  } catch {
27067
26140
  }
27068
26141
  }
27069
- await import_node_fs18.default.promises.rename(tmp, this.filePath);
26142
+ await import_node_fs14.default.promises.rename(tmp, this.filePath);
27070
26143
  }
27071
26144
  async clear() {
27072
26145
  try {
27073
- await import_node_fs18.default.promises.unlink(this.filePath);
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 import_node_fs19 = __toESM(require("fs"), 1);
26241
+ var import_node_fs15 = __toESM(require("fs"), 1);
27169
26242
  var import_node_os9 = __toESM(require("os"), 1);
27170
- var import_node_path21 = __toESM(require("path"), 1);
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 (!import_node_fs19.default.existsSync(opts.override)) {
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 = import_node_path21.default.join(opts.dataDir, "bin");
27210
- import_node_fs19.default.mkdirSync(binDir, { recursive: true });
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 = import_node_path21.default.join(binDir, "frpc");
27213
- if (import_node_fs19.default.existsSync(stableBin)) return stableBin;
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 = import_node_path21.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
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
- import_node_fs19.default.chmodSync(partialBin, 493);
27225
- import_node_fs19.default.renameSync(partialBin, stableBin);
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 = import_node_fs19.default.readdirSync(binDir);
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 = import_node_path21.default.join(binDir, name);
26314
+ const full = import_node_path16.default.join(binDir, name);
27242
26315
  try {
27243
- import_node_fs19.default.rmSync(full, { recursive: true, force: true });
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
- import_node_fs19.default.unlinkSync(p2);
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 = import_node_fs19.default.createWriteStream(dest);
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 = import_node_path21.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
27268
- import_node_fs19.default.mkdirSync(work, { recursive: true });
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 = import_node_path21.default.join(work, dirName, "frpc");
27277
- if (!import_node_fs19.default.existsSync(src)) {
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
- import_node_fs19.default.copyFileSync(src, destBin);
26353
+ import_node_fs15.default.copyFileSync(src, destBin);
27281
26354
  } finally {
27282
- import_node_fs19.default.rmSync(work, { recursive: true, force: true });
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 import_node_fs20 = __toESM(require("fs"), 1);
27288
- var import_node_path22 = __toESM(require("path"), 1);
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 import_node_path22.default.join(dataDir, "frpc.pid");
26364
+ return import_node_path17.default.join(dataDir, "frpc.pid");
27292
26365
  }
27293
26366
  function writeFrpcPid(dataDir, pid) {
27294
26367
  try {
27295
- import_node_fs20.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
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
- import_node_fs20.default.unlinkSync(frpcPidFilePath(dataDir));
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 import_node_fs20.default.readFileSync(file, "utf8");
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 = import_node_path22.default.join(deps.dataDir, "frpc.toml");
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
- import_node_fs20.default.unlinkSync(pidFile);
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
- import_node_fs20.default.unlinkSync(pidFile);
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(import_node_path23.default.join(deps.dataDir, "tunnel.json"));
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 = import_node_path23.default.join(this.deps.dataDir, "frpc.toml");
27533
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto6.default.randomBytes(3).toString("hex")}`;
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 import_node_fs21.default.promises.writeFile(tomlPath, toml, { mode: 384 });
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 = import_node_path23.default.join(this.deps.dataDir, "frpc.log");
27548
- const logStream = import_node_fs21.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
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 import_node_crypto7 = __toESM(require("crypto"), 1);
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 import_node_crypto7.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
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 import_node_fs22 = __toESM(require("fs"), 1);
27642
- var import_node_path24 = __toESM(require("path"), 1);
27643
- var import_node_crypto8 = __toESM(require("crypto"), 1);
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 import_node_path24.default.join(dataDir, AUTH_FILE_NAME);
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 import_node_crypto8.default.randomBytes(32).toString("base64url");
26731
+ return import_node_crypto6.default.randomBytes(32).toString("base64url");
27659
26732
  }
27660
26733
  function readAuthFile(file) {
27661
26734
  try {
27662
- const raw = import_node_fs22.default.readFileSync(file, "utf8");
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
- import_node_fs22.default.mkdirSync(import_node_path24.default.dirname(file), { recursive: true });
27679
- import_node_fs22.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
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
- import_node_fs22.default.chmodSync(file, 384);
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 import_node_fs23 = __toESM(require("fs"), 1);
26760
+ var import_node_fs19 = __toESM(require("fs"), 1);
27688
26761
  var import_node_os11 = __toESM(require("os"), 1);
27689
- var import_node_path25 = __toESM(require("path"), 1);
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 = import_node_path25.default.join(dataDir, PROFILE_FILENAME);
26766
+ const profilePath = import_node_path20.default.join(dataDir, PROFILE_FILENAME);
27694
26767
  let raw;
27695
26768
  try {
27696
- raw = import_node_fs23.default.readFileSync(profilePath, "utf8");
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 import_node_fs24 = __toESM(require("fs"), 1);
26798
+ var import_node_fs20 = __toESM(require("fs"), 1);
27726
26799
  var import_node_os12 = __toESM(require("os"), 1);
27727
- var import_node_path26 = __toESM(require("path"), 1);
26800
+ var import_node_path21 = __toESM(require("path"), 1);
27728
26801
  init_claude_history();
27729
26802
  function readJsonlEntries(file) {
27730
- const raw = import_node_fs24.default.readFileSync(file, "utf8");
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 ?? import_node_path26.default.join(import_node_os12.default.homedir(), ".claude");
27744
- const projectDir = import_node_path26.default.join(baseDir, "projects", cwdToHashDir(input.cwd));
27745
- const sourceFile = import_node_path26.default.join(projectDir, `${input.toolSessionId}.jsonl`);
27746
- if (!import_node_fs24.default.existsSync(sourceFile)) {
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 = import_node_path26.default.join(projectDir, `${forkedToolSessionId}.jsonl`);
27777
- import_node_fs24.default.mkdirSync(projectDir, { recursive: true });
27778
- import_node_fs24.default.writeFileSync(forkedFilePath, forkedLines.join("\n") + "\n", { mode: 384 });
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 import_node_fs25 = __toESM(require("fs"), 1);
27172
+ var import_node_fs21 = __toESM(require("fs"), 1);
28100
27173
  var import_node_os13 = __toESM(require("os"), 1);
28101
- var import_node_path27 = __toESM(require("path"), 1);
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 = import_node_path27.default.resolve(p2);
27189
+ const resolved = import_node_path22.default.resolve(p2);
28117
27190
  try {
28118
- return import_node_fs25.default.realpathSync(resolved);
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 = import_node_path27.default.resolve(absPath);
27292
+ let canonical = import_node_path22.default.resolve(absPath);
28220
27293
  try {
28221
- canonical = import_node_fs25.default.realpathSync(canonical);
27294
+ canonical = import_node_fs21.default.realpathSync(canonical);
28222
27295
  } catch {
28223
27296
  try {
28224
- const parent = import_node_fs25.default.realpathSync(import_node_path27.default.dirname(canonical));
28225
- canonical = import_node_path27.default.join(parent, import_node_path27.default.basename(canonical));
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 = import_node_path27.default.dirname(import_node_path27.default.resolve(cwd));
28250
- if (parent === "/" || parent === import_node_path27.default.resolve(cwd)) {
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 = import_node_path27.default.join(parent, dirName);
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 (import_node_fs25.default.existsSync(worktreeRoot)) {
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 = import_node_path27.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path27.default.resolve(worktreeRoot, gitCommonDir);
28301
- repoRoot = import_node_path27.default.dirname(absGitCommon);
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") || !import_node_fs25.default.existsSync(worktreeRoot);
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 = import_node_path27.default.join(import_node_os13.default.homedir(), ".claude", "projects");
28333
- const target = import_node_path27.default.resolve(projectsRoot, encoded);
28334
- if (target.startsWith(projectsRoot + import_node_path27.default.sep) && target !== projectsRoot) {
28335
- import_node_fs25.default.rmSync(target, { recursive: true, force: true });
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, client) {
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: import_node_path28.default.join(config.dataDir, "clawd.log")
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(import_node_path28.default.join(config.dataDir, "personas"));
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: import_node_path28.default.join(config.dataDir, "personas"),
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: (ctx) => buildReadyFrame(
28998
- {
28999
- manager,
29000
- getAdapter,
29001
- getTunnelUrl: () => currentTunnelUrl,
29002
- // ready 帧 mode = daemon CC spawn 模式('sdk' | 'tui');UI 用它挂 XtermPanel
29003
- mode: config.mode,
29004
- // file-sharing 字段:httpBaseUrl 跟 tunnel 状态走;httpToken 复用 owner WS token
29005
- getHttpBaseUrl,
29006
- httpToken: resolvedAuthToken
29007
- },
29008
- ctx
29009
- ),
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: ${import_node_path28.default.join(config.dataDir, "frpc.toml")}`,
29131
- `Frpc log: ${import_node_path28.default.join(config.dataDir, "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 = import_node_path28.default.join(config.dataDir, "connect.txt");
29145
- import_node_fs26.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
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) {