@clawos-dev/clawd 0.2.274 → 0.2.276

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -6651,6 +6651,11 @@ var init_persona_schemas = __esm({
6651
6651
  sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
6652
6652
  });
6653
6653
  PersonaCreateArgsSchema = external_exports.object({
6654
+ // persona 身份。daemon 拼成 personaId = `persona-<slug>`——前缀由 daemon 拥有,调用方只给后半段,
6655
+ // 「所有 persona id 带该前缀」的约定由构造保证。与 label(显示名,可中文、可随时改)是两个概念:
6656
+ // 曾经只有 label、id 从 label 推,导致中文 label 被剥空后一律降级成 persona-persona。
6657
+ // 正则即安全边界——该值直接落进 ~/.clawd/personas/<id>/,此处排除 . / 空格后路径穿越无从构造。
6658
+ slug: external_exports.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).max(32),
6654
6659
  label: external_exports.string().min(1),
6655
6660
  personality: external_exports.string(),
6656
6661
  model: external_exports.string().optional(),
@@ -7981,7 +7986,7 @@ var init_methods = __esm({
7981
7986
  args: CapabilitiesGetArgs
7982
7987
  },
7983
7988
  "persona:create": {
7984
- summary: "\u65B0\u5EFA persona\uFF08\u5199 PersonaFile + CLAUDE.md \u9AA8\u67B6\uFF09\u3002owner-only",
7989
+ summary: "\u65B0\u5EFA persona\uFF08\u5199 PersonaFile + CLAUDE.md \u9AA8\u67B6\uFF09\u3002owner-only\u3002slug \u53EA\u6536\u5C0F\u5199\u5B57\u6BCD\u6570\u5B57\u4E0E\u5355\u8FDE\u5B57\u7B26\uFF08^[a-z0-9]+(-[a-z0-9]+)*$\uFF0C\u226432\uFF09\uFF0Cdaemon \u62FC\u6210 personaId=persona-<slug>\uFF1B\u8BE5 id \u5DF2\u5B58\u5728\u5219\u62A5\u9519\uFF0C\u4E0D\u81EA\u52A8\u6539\u540D\u3002label \u662F\u663E\u793A\u540D\uFF0C\u4E0D\u53C2\u4E0E id \u63A8\u5BFC\uFF0C\u53EF\u7528\u4E2D\u6587",
7985
7990
  args: PersonaCreateArgsSchema
7986
7991
  },
7987
7992
  "persona:list": {
@@ -47622,9 +47627,6 @@ var PersonaRegistry = class {
47622
47627
  }
47623
47628
  };
47624
47629
 
47625
- // src/persona/manager.ts
47626
- var import_node_crypto3 = __toESM(require("crypto"), 1);
47627
-
47628
47630
  // src/skills/scanner.ts
47629
47631
  var import_node_fs10 = __toESM(require("fs"), 1);
47630
47632
  var import_node_os6 = __toESM(require("os"), 1);
@@ -47904,8 +47906,19 @@ var PersonaManager = class {
47904
47906
  this.deps = deps;
47905
47907
  }
47906
47908
  deps;
47909
+ /**
47910
+ * slug 是 persona 的身份,label 只是显示名——两者刻意分开:id 落进文件路径 /
47911
+ * 分享链接且创建后不可改,label 可中文、可随时改。此前 id 从 label 推导,
47912
+ * 非 ascii 被剥空后一律 fallback 成 `persona-persona`。
47913
+ *
47914
+ * 撞名直接抛错而非改名:id 由调用方显式指定,静默换成带后缀的 id 等于把
47915
+ * 用户的选择偷偷改掉。字符集校验在 PersonaCreateArgsSchema(路径穿越边界)。
47916
+ */
47907
47917
  create(args) {
47908
- const personaId2 = this.generatePersonaId(args.label);
47918
+ const personaId2 = `persona-${args.slug}`;
47919
+ if (this.deps.store.has(personaId2)) {
47920
+ throw new Error(`personaId already exists: ${personaId2}`);
47921
+ }
47909
47922
  const now = Date.now();
47910
47923
  const persona = {
47911
47924
  personaId: personaId2,
@@ -47971,21 +47984,6 @@ var PersonaManager = class {
47971
47984
  this.deps.store.remove(personaId2);
47972
47985
  this.deps.registry.remove(personaId2);
47973
47986
  }
47974
- /**
47975
- * label 转 4-16 char slug。优先用 `persona-<slug>`;若 slug 已被占用,追加 4 char
47976
- * base64url 随机后缀直到不撞为止(最多 5 次,理论冲突 ≈ 2^-24,留个 panic 上限)。
47977
- */
47978
- generatePersonaId(label) {
47979
- const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 16) || "persona";
47980
- const base = `persona-${slug}`;
47981
- if (!this.deps.store.has(base)) return base;
47982
- for (let i = 0; i < 5; i++) {
47983
- const rand = import_node_crypto3.default.randomBytes(3).toString("base64url").slice(0, 4);
47984
- const candidate = `${base}-${rand}`;
47985
- if (!this.deps.store.has(candidate)) return candidate;
47986
- }
47987
- throw new Error(`failed to generate unique personaId for label=${label}`);
47988
- }
47989
47987
  };
47990
47988
 
47991
47989
  // src/persona/seed.ts
@@ -48016,9 +48014,11 @@ var DEFAULT_PERSONAS = [
48016
48014
  {
48017
48015
  // clawd 管家 (chief-of-staff):spec 2026-07-22-clawd-butler-persona
48018
48016
  // 新 personaId 独立于老 persona-clawd-helper(老 helper seed entry 已删;老用户机器上
48019
- // 若有残留目录,是他们自留物,daemon 不再触碰)。bundle 里 CLAUDE.md 骨架 + 5
48020
- // prebuild skill copyBundleExtras 装到 `.claude/skills/`;skill fetch 用 daemon
48021
- // 注入的 env CLAWOS_API `${CLAWOS_API}/api/docs/**` 拉线上手册。
48017
+ // 若有残留目录,是他们自留物,daemon 不再触碰)。bundle 里 CLAUDE.md 骨架 + 6
48018
+ // prebuild skill:新装用户走 copyBundleExtras,存量用户走 refreshDaemonManagedDirs
48019
+ // (`.claude/skills` DAEMON_MANAGED_PATHS 里),都装到 `.claude/skills/`。
48020
+ // 前 5 个 skill fetch 用 daemon 注入的 env CLAWOS_API 拼 `${CLAWOS_API}/api/docs/**`
48021
+ // 拉线上手册;clawd-session-import 自带 scripts/link-sessions.mjs,只依赖 node 内置模块。
48022
48022
  personaId: "persona-clawd-butler",
48023
48023
  label: "clawd \u7BA1\u5BB6",
48024
48024
  model: "opus",
@@ -48257,7 +48257,22 @@ function copyBundleExtras(srcDir, dstDir) {
48257
48257
  }
48258
48258
  }
48259
48259
  }
48260
- var DAEMON_MANAGED_PATHS = ["extension-kit", "CLAUDE.md", ".mcp.json"];
48260
+ var DAEMON_MANAGED_PATHS = ["extension-kit", "CLAUDE.md", ".mcp.json", ".claude/skills"];
48261
+ function replaceManagedPath(src, dst) {
48262
+ if (!fs12.statSync(src).isDirectory()) {
48263
+ fs12.mkdirSync(path14.dirname(dst), { recursive: true });
48264
+ fs12.rmSync(dst, { recursive: true, force: true });
48265
+ fs12.cpSync(src, dst, { dereference: true });
48266
+ return;
48267
+ }
48268
+ fs12.mkdirSync(dst, { recursive: true });
48269
+ for (const e of fs12.readdirSync(src, { withFileTypes: true })) {
48270
+ if (e.name === "node_modules") continue;
48271
+ const child = path14.join(dst, e.name);
48272
+ fs12.rmSync(child, { recursive: true, force: true });
48273
+ fs12.cpSync(path14.join(src, e.name), child, { recursive: true, dereference: true });
48274
+ }
48275
+ }
48261
48276
  function refreshDaemonManagedDirs(args) {
48262
48277
  const entries = args.entries ?? DEFAULT_PERSONAS;
48263
48278
  for (const entry of entries) {
@@ -48270,9 +48285,9 @@ function refreshDaemonManagedDirs(args) {
48270
48285
  if (!fs12.existsSync(srcPath)) continue;
48271
48286
  const dstPath = path14.join(personaDir, relPath);
48272
48287
  try {
48273
- fs12.cpSync(srcPath, dstPath, { recursive: true, force: true, dereference: true, filter: skipNodeModulesUnder(srcPath) });
48288
+ replaceManagedPath(srcPath, dstPath);
48274
48289
  if (relPath === "CLAUDE.md") {
48275
- fs12.cpSync(srcPath, path14.join(personaDir, "AGENTS.md"), { force: true, dereference: true });
48290
+ replaceManagedPath(srcPath, path14.join(personaDir, "AGENTS.md"));
48276
48291
  }
48277
48292
  args.logger.info("persona.refresh.synced", {
48278
48293
  personaId: entry.personaId,
@@ -48528,7 +48543,7 @@ function tryLoadShareUi(logger) {
48528
48543
  }
48529
48544
 
48530
48545
  // src/visitor/visitor-token.ts
48531
- var import_node_crypto4 = __toESM(require("crypto"), 1);
48546
+ var import_node_crypto3 = __toESM(require("crypto"), 1);
48532
48547
 
48533
48548
  // ../protocol/src/index.ts
48534
48549
  init_runtime();
@@ -48536,7 +48551,7 @@ init_runtime();
48536
48551
  // src/visitor/visitor-token.ts
48537
48552
  var DOMAIN_PREFIX = "clawd-visitor-v1|";
48538
48553
  function hmac(secret, body) {
48539
- return import_node_crypto4.default.createHmac("sha256", secret).update(`${DOMAIN_PREFIX}${body}`).digest("base64url");
48554
+ return import_node_crypto3.default.createHmac("sha256", secret).update(`${DOMAIN_PREFIX}${body}`).digest("base64url");
48540
48555
  }
48541
48556
  function signVisitorToken(secret, payload) {
48542
48557
  const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
@@ -48549,7 +48564,7 @@ function verifyVisitorToken(secret, token, now) {
48549
48564
  const sig = token.slice(dot + 1);
48550
48565
  const expected = hmac(secret, body);
48551
48566
  if (sig.length !== expected.length) return { ok: false, reason: "BAD_SIGNATURE" };
48552
- if (!import_node_crypto4.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
48567
+ if (!import_node_crypto3.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
48553
48568
  return { ok: false, reason: "BAD_SIGNATURE" };
48554
48569
  }
48555
48570
  let payload;
@@ -49251,7 +49266,7 @@ var CodexAdapter = class {
49251
49266
  };
49252
49267
 
49253
49268
  // src/tools/claude-tui.ts
49254
- var import_node_crypto5 = require("crypto");
49269
+ var import_node_crypto4 = require("crypto");
49255
49270
  var import_node_fs19 = __toESM(require("fs"), 1);
49256
49271
  var import_node_os7 = __toESM(require("os"), 1);
49257
49272
  var import_node_path17 = __toESM(require("path"), 1);
@@ -50139,7 +50154,7 @@ function observeScreenIdle(surface, opts) {
50139
50154
  };
50140
50155
  }
50141
50156
  function shortHash(s) {
50142
- return (0, import_node_crypto5.createHash)("sha1").update(s).digest("hex").slice(0, 8);
50157
+ return (0, import_node_crypto4.createHash)("sha1").update(s).digest("hex").slice(0, 8);
50143
50158
  }
50144
50159
  function firstLineDiff(prev, next) {
50145
50160
  const p2 = prev.split("\n");
@@ -50853,7 +50868,7 @@ function resolveSourceJsonlPath(sourceFile, home) {
50853
50868
  // src/shift/store.ts
50854
50869
  var import_promises2 = __toESM(require("fs/promises"), 1);
50855
50870
  var import_node_path26 = __toESM(require("path"), 1);
50856
- var import_node_crypto6 = require("crypto");
50871
+ var import_node_crypto5 = require("crypto");
50857
50872
 
50858
50873
  // src/shift/schedule.ts
50859
50874
  var import_cron_parser = __toESM(require_dist(), 1);
@@ -50957,7 +50972,7 @@ function createShiftStore(deps) {
50957
50972
  const shift = {
50958
50973
  ...input,
50959
50974
  schedule,
50960
- id: (0, import_node_crypto6.randomUUID)(),
50975
+ id: (0, import_node_crypto5.randomUUID)(),
50961
50976
  createdAtMs: now,
50962
50977
  updatedAtMs: now,
50963
50978
  state: { nextRunAtMs },
@@ -53661,7 +53676,7 @@ var ContactStore = class {
53661
53676
  };
53662
53677
 
53663
53678
  // src/contact/connect-remote.ts
53664
- var crypto5 = __toESM(require("crypto"), 1);
53679
+ var crypto4 = __toESM(require("crypto"), 1);
53665
53680
  init_protocol();
53666
53681
  var HANDSHAKE_TIMEOUT_MS = 5e3;
53667
53682
  var RPC_TIMEOUT_MS = 5e3;
@@ -53716,7 +53731,7 @@ async function connectRemote(args) {
53716
53731
  ws.once("close", onClose);
53717
53732
  });
53718
53733
  function call(method, payload) {
53719
- const requestId = crypto5.randomUUID();
53734
+ const requestId = crypto4.randomUUID();
53720
53735
  return new Promise((resolve6, reject) => {
53721
53736
  const onMessage = (raw) => {
53722
53737
  let f;
@@ -53973,7 +53988,7 @@ function lookupMime(filePathOrName) {
53973
53988
  }
53974
53989
 
53975
53990
  // src/attachment/sign-url.ts
53976
- var import_node_crypto7 = __toESM(require("crypto"), 1);
53991
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
53977
53992
  var HMAC_ALGO = "sha256";
53978
53993
  function base64urlEncode(buf) {
53979
53994
  const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
@@ -53991,7 +54006,7 @@ function decodeAbsPathFromUrl(encoded) {
53991
54006
  var DOMAIN_PREFIX2 = "clawd-attach-v1|";
53992
54007
  function computeSig(secret, absPath, e) {
53993
54008
  const msg = e === null ? `${DOMAIN_PREFIX2}${absPath}` : `${DOMAIN_PREFIX2}${absPath}|${e}`;
53994
- return import_node_crypto7.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
54009
+ return import_node_crypto6.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
53995
54010
  }
53996
54011
  function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
53997
54012
  const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
@@ -54026,7 +54041,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
54026
54041
  if (provided.length !== expected.length) {
54027
54042
  return { ok: false, code: "BAD_SIG" };
54028
54043
  }
54029
- if (!import_node_crypto7.default.timingSafeEqual(provided, expected)) {
54044
+ if (!import_node_crypto6.default.timingSafeEqual(provided, expected)) {
54030
54045
  return { ok: false, code: "BAD_SIG" };
54031
54046
  }
54032
54047
  if (e !== null && now() / 1e3 > e) {
@@ -54038,7 +54053,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
54038
54053
  // src/attachment/upload.ts
54039
54054
  var import_node_fs30 = __toESM(require("fs"), 1);
54040
54055
  var import_node_path33 = __toESM(require("path"), 1);
54041
- var import_node_crypto8 = __toESM(require("crypto"), 1);
54056
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
54042
54057
  var import_promises3 = require("stream/promises");
54043
54058
  var UploadError = class extends Error {
54044
54059
  constructor(code, message) {
@@ -54062,11 +54077,11 @@ async function writeUploadedAttachment(args) {
54062
54077
  } catch (err) {
54063
54078
  throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
54064
54079
  }
54065
- const hasher = import_node_crypto8.default.createHash("sha256");
54080
+ const hasher = import_node_crypto7.default.createHash("sha256");
54066
54081
  let actualSize = 0;
54067
54082
  const tmpPath = import_node_path33.default.join(
54068
54083
  attachmentsRoot,
54069
- `.upload-${process.pid}-${Date.now()}-${import_node_crypto8.default.randomBytes(4).toString("hex")}`
54084
+ `.upload-${process.pid}-${Date.now()}-${import_node_crypto7.default.randomBytes(4).toString("hex")}`
54070
54085
  );
54071
54086
  try {
54072
54087
  await (0, import_promises3.pipeline)(
@@ -54856,7 +54871,7 @@ function runAttachmentGc(args) {
54856
54871
  // src/attachment/group.ts
54857
54872
  var import_node_fs33 = __toESM(require("fs"), 1);
54858
54873
  var import_node_path37 = __toESM(require("path"), 1);
54859
- var import_node_crypto9 = __toESM(require("crypto"), 1);
54874
+ var import_node_crypto8 = __toESM(require("crypto"), 1);
54860
54875
  init_protocol();
54861
54876
  var GroupFileStore = class {
54862
54877
  dataDir;
@@ -54945,7 +54960,7 @@ var GroupFileStore = class {
54945
54960
  entries[idx] = next;
54946
54961
  } else {
54947
54962
  next = {
54948
- id: `gf-${import_node_crypto9.default.randomBytes(6).toString("base64url")}`,
54963
+ id: `gf-${import_node_crypto8.default.randomBytes(6).toString("base64url")}`,
54949
54964
  relPath: input.relPath,
54950
54965
  from: input.from,
54951
54966
  label: input.label,
@@ -55063,7 +55078,7 @@ function readSpawnedByDesktopFromEnv(env = process.env) {
55063
55078
  // src/tunnel/tunnel-manager.ts
55064
55079
  var import_node_fs38 = __toESM(require("fs"), 1);
55065
55080
  var import_node_path42 = __toESM(require("path"), 1);
55066
- var import_node_crypto10 = __toESM(require("crypto"), 1);
55081
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
55067
55082
  var import_node_child_process10 = require("child_process");
55068
55083
 
55069
55084
  // src/tunnel/tunnel-store.ts
@@ -55562,7 +55577,7 @@ var TunnelManager = class {
55562
55577
  override: this.deps.frpcBinaryOverride ?? void 0
55563
55578
  });
55564
55579
  const tomlPath = import_node_path42.default.join(this.deps.dataDir, "frpc.toml");
55565
- const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto10.default.randomBytes(3).toString("hex")}`;
55580
+ const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto9.default.randomBytes(3).toString("hex")}`;
55566
55581
  const toml = buildFrpcToml({
55567
55582
  serverAddr: t.frpsHost,
55568
55583
  serverPort: t.frpsPort,
@@ -55663,7 +55678,7 @@ async function waitForFrpcReady(proc, timeoutMs) {
55663
55678
  // src/tunnel/device-key.ts
55664
55679
  var import_node_os14 = __toESM(require("os"), 1);
55665
55680
  var import_node_path43 = __toESM(require("path"), 1);
55666
- var import_node_crypto11 = __toESM(require("crypto"), 1);
55681
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
55667
55682
  var DERIVE_SALT = "clawd-tunnel-device-v1";
55668
55683
  function deriveStableDeviceKey(opts = {}) {
55669
55684
  const hostname = opts.hostname ?? import_node_os14.default.hostname();
@@ -55673,13 +55688,13 @@ function deriveStableDeviceKey(opts = {}) {
55673
55688
  const normalizedDataDir = opts.dataDir ? import_node_path43.default.resolve(opts.dataDir) : null;
55674
55689
  const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
55675
55690
  const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
55676
- return import_node_crypto11.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
55691
+ return import_node_crypto10.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
55677
55692
  }
55678
55693
 
55679
55694
  // src/auth-store.ts
55680
55695
  var import_node_fs39 = __toESM(require("fs"), 1);
55681
55696
  var import_node_path44 = __toESM(require("path"), 1);
55682
- var import_node_crypto12 = __toESM(require("crypto"), 1);
55697
+ var import_node_crypto11 = __toESM(require("crypto"), 1);
55683
55698
  var AUTH_FILE_NAME = "auth.json";
55684
55699
  function authFilePath(dataDir) {
55685
55700
  return import_node_path44.default.join(dataDir, AUTH_FILE_NAME);
@@ -55707,10 +55722,10 @@ function loadOrCreateAuthFile(opts) {
55707
55722
  return next;
55708
55723
  }
55709
55724
  function defaultGenerateToken() {
55710
- return import_node_crypto12.default.randomBytes(32).toString("base64url");
55725
+ return import_node_crypto11.default.randomBytes(32).toString("base64url");
55711
55726
  }
55712
55727
  function defaultGenerateOwnerPrincipalId() {
55713
- return `owner-${import_node_crypto12.default.randomUUID()}`;
55728
+ return `owner-${import_node_crypto11.default.randomUUID()}`;
55714
55729
  }
55715
55730
  function readAuthFile(file) {
55716
55731
  try {
@@ -55832,7 +55847,7 @@ var OwnerIdentityStore = class {
55832
55847
  };
55833
55848
 
55834
55849
  // src/feishu-auth/login-flow.ts
55835
- var import_node_crypto13 = __toESM(require("crypto"), 1);
55850
+ var import_node_crypto12 = __toESM(require("crypto"), 1);
55836
55851
  var STATE_TTL_MS = 5 * 60 * 1e3;
55837
55852
  var LoginFlow = class {
55838
55853
  constructor(deps) {
@@ -55841,7 +55856,7 @@ var LoginFlow = class {
55841
55856
  deps;
55842
55857
  pendingStates = /* @__PURE__ */ new Map();
55843
55858
  start() {
55844
- const state = import_node_crypto13.default.randomBytes(16).toString("base64url");
55859
+ const state = import_node_crypto12.default.randomBytes(16).toString("base64url");
55845
55860
  const now = (this.deps.now ?? Date.now)();
55846
55861
  this.pendingStates.set(state, now);
55847
55862
  this.gcExpired(now);
@@ -56107,7 +56122,7 @@ async function upsertDeviceBinding(opts) {
56107
56122
  }
56108
56123
 
56109
56124
  // src/feishu-auth/verify-token.ts
56110
- var crypto13 = __toESM(require("crypto"), 1);
56125
+ var crypto12 = __toESM(require("crypto"), 1);
56111
56126
  var CONNECT_TOKEN_PREFIX = "clawdtk1";
56112
56127
  function verifyConnectToken(args) {
56113
56128
  const now = args.nowSeconds ?? Math.floor(Date.now() / 1e3);
@@ -56119,7 +56134,7 @@ function verifyConnectToken(args) {
56119
56134
  const data = `${prefix}.${payloadB64}`;
56120
56135
  let signatureValid = false;
56121
56136
  try {
56122
- signatureValid = crypto13.verify(
56137
+ signatureValid = crypto12.verify(
56123
56138
  null,
56124
56139
  Buffer.from(data, "utf8"),
56125
56140
  args.publicKeyPem,
@@ -57914,7 +57929,7 @@ function computeMethodAccess(args) {
57914
57929
  }
57915
57930
 
57916
57931
  // src/version.ts
57917
- var version = "0.2.274".length > 0 ? "0.2.274" : "dev";
57932
+ var version = "0.2.276".length > 0 ? "0.2.276" : "dev";
57918
57933
 
57919
57934
  // src/cli-probe/probe.ts
57920
57935
  var fs54 = __toESM(require("fs"), 1);
@@ -58295,7 +58310,7 @@ init_protocol();
58295
58310
  // src/extension/bundle-zip.ts
58296
58311
  var import_promises6 = __toESM(require("fs/promises"), 1);
58297
58312
  var import_node_path50 = __toESM(require("path"), 1);
58298
- var import_node_crypto14 = __toESM(require("crypto"), 1);
58313
+ var import_node_crypto13 = __toESM(require("crypto"), 1);
58299
58314
  var import_jszip2 = __toESM(require_lib3(), 1);
58300
58315
  async function bundleExtensionDir(dir) {
58301
58316
  const entries = await listFilesSorted(dir);
@@ -58310,7 +58325,7 @@ async function bundleExtensionDir(dir) {
58310
58325
  compression: "DEFLATE",
58311
58326
  compressionOptions: { level: 6 }
58312
58327
  });
58313
- const sha256 = import_node_crypto14.default.createHash("sha256").update(buffer).digest("hex");
58328
+ const sha256 = import_node_crypto13.default.createHash("sha256").update(buffer).digest("hex");
58314
58329
  return { buffer, sha256 };
58315
58330
  }
58316
58331
  var FIXED_DATE = /* @__PURE__ */ new Date("2020-01-01T00:00:00.000Z");
@@ -58378,7 +58393,7 @@ function computePublishCheck(args) {
58378
58393
  var import_promises7 = __toESM(require("fs/promises"), 1);
58379
58394
  var import_node_path52 = __toESM(require("path"), 1);
58380
58395
  var import_node_os19 = __toESM(require("os"), 1);
58381
- var import_node_crypto15 = __toESM(require("crypto"), 1);
58396
+ var import_node_crypto14 = __toESM(require("crypto"), 1);
58382
58397
  var import_jszip3 = __toESM(require_lib3(), 1);
58383
58398
 
58384
58399
  // src/extension/paths.ts
@@ -58407,7 +58422,7 @@ var InstallError = class extends Error {
58407
58422
  };
58408
58423
  async function installFromChannel(args, deps) {
58409
58424
  const { channelRef, snapshotHash, bundleZip } = args;
58410
- const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
58425
+ const computed = import_node_crypto14.default.createHash("sha256").update(bundleZip).digest("hex");
58411
58426
  if (computed !== snapshotHash) {
58412
58427
  throw new InstallError(
58413
58428
  "HASH_MISMATCH",
@@ -58499,7 +58514,7 @@ async function installFromChannel(args, deps) {
58499
58514
  var import_promises8 = __toESM(require("fs/promises"), 1);
58500
58515
  var import_node_path53 = __toESM(require("path"), 1);
58501
58516
  var import_node_os20 = __toESM(require("os"), 1);
58502
- var import_node_crypto16 = __toESM(require("crypto"), 1);
58517
+ var import_node_crypto15 = __toESM(require("crypto"), 1);
58503
58518
  var import_jszip4 = __toESM(require_lib3(), 1);
58504
58519
  var UpdateError = class extends Error {
58505
58520
  constructor(code, message) {
@@ -58537,7 +58552,7 @@ async function updateFromChannel(args, deps) {
58537
58552
  if (e instanceof UpdateError) throw e;
58538
58553
  throw e;
58539
58554
  }
58540
- const computed = import_node_crypto16.default.createHash("sha256").update(bundleZip).digest("hex");
58555
+ const computed = import_node_crypto15.default.createHash("sha256").update(bundleZip).digest("hex");
58541
58556
  if (computed !== snapshotHash) {
58542
58557
  throw new UpdateError(
58543
58558
  "HASH_MISMATCH",
@@ -59203,7 +59218,7 @@ function listPidsOnPort(port) {
59203
59218
  }
59204
59219
 
59205
59220
  // src/app-builder/publish-registry.ts
59206
- var import_node_crypto17 = require("crypto");
59221
+ var import_node_crypto16 = require("crypto");
59207
59222
  var PublishJobRegistry = class {
59208
59223
  jobs = /* @__PURE__ */ new Map();
59209
59224
  has(name) {
@@ -59220,7 +59235,7 @@ var PublishJobRegistry = class {
59220
59235
  if (this.jobs.has(args.name)) {
59221
59236
  throw new Error(`already publishing: ${args.name}`);
59222
59237
  }
59223
- const jobId = args.jobId ?? `job-${(0, import_node_crypto17.randomUUID)()}`;
59238
+ const jobId = args.jobId ?? `job-${(0, import_node_crypto16.randomUUID)()}`;
59224
59239
  this.jobs.set(args.name, {
59225
59240
  jobId,
59226
59241
  name: args.name,
@@ -60163,7 +60178,7 @@ async function uninstall(deps) {
60163
60178
  }
60164
60179
 
60165
60180
  // src/handlers/index.ts
60166
- var import_node_crypto18 = require("crypto");
60181
+ var import_node_crypto17 = require("crypto");
60167
60182
 
60168
60183
  // src/handlers/peer-exec.ts
60169
60184
  init_protocol();
@@ -60318,7 +60333,7 @@ function buildMethodHandlers(deps) {
60318
60333
  const c = deps.contactStore.get(deviceId);
60319
60334
  return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
60320
60335
  },
60321
- genId: () => (0, import_node_crypto18.randomUUID)(),
60336
+ genId: () => (0, import_node_crypto17.randomUUID)(),
60322
60337
  now: () => Date.now(),
60323
60338
  forwardInboxPostToPeer,
60324
60339
  logger: deps.logger
@@ -21852,6 +21852,11 @@ var PersonaInfoResponseSchema = PersonaFileSchema.extend({
21852
21852
  sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
21853
21853
  });
21854
21854
  var PersonaCreateArgsSchema = external_exports.object({
21855
+ // persona 身份。daemon 拼成 personaId = `persona-<slug>`——前缀由 daemon 拥有,调用方只给后半段,
21856
+ // 「所有 persona id 带该前缀」的约定由构造保证。与 label(显示名,可中文、可随时改)是两个概念:
21857
+ // 曾经只有 label、id 从 label 推,导致中文 label 被剥空后一律降级成 persona-persona。
21858
+ // 正则即安全边界——该值直接落进 ~/.clawd/personas/<id>/,此处排除 . / 空格后路径穿越无从构造。
21859
+ slug: external_exports.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).max(32),
21855
21860
  label: external_exports.string().min(1),
21856
21861
  personality: external_exports.string(),
21857
21862
  model: external_exports.string().optional(),
@@ -23095,7 +23100,7 @@ var METHOD_DOCS = {
23095
23100
  args: CapabilitiesGetArgs
23096
23101
  },
23097
23102
  "persona:create": {
23098
- summary: "\u65B0\u5EFA persona\uFF08\u5199 PersonaFile + CLAUDE.md \u9AA8\u67B6\uFF09\u3002owner-only",
23103
+ summary: "\u65B0\u5EFA persona\uFF08\u5199 PersonaFile + CLAUDE.md \u9AA8\u67B6\uFF09\u3002owner-only\u3002slug \u53EA\u6536\u5C0F\u5199\u5B57\u6BCD\u6570\u5B57\u4E0E\u5355\u8FDE\u5B57\u7B26\uFF08^[a-z0-9]+(-[a-z0-9]+)*$\uFF0C\u226432\uFF09\uFF0Cdaemon \u62FC\u6210 personaId=persona-<slug>\uFF1B\u8BE5 id \u5DF2\u5B58\u5728\u5219\u62A5\u9519\uFF0C\u4E0D\u81EA\u52A8\u6539\u540D\u3002label \u662F\u663E\u793A\u540D\uFF0C\u4E0D\u53C2\u4E0E id \u63A8\u5BFC\uFF0C\u53EF\u7528\u4E2D\u6587",
23099
23104
  args: PersonaCreateArgsSchema
23100
23105
  },
23101
23106
  "persona:list": {
@@ -85,11 +85,24 @@ PR="$HOME/.clawd/personas/persona-app-builder"; DK="$HOME/.clawd/deploy-kit"
85
85
  ## 换语言(Node → Python/Go…)
86
86
 
87
87
  1. `config.env` 的 `NODE_LAYER` 换成对应语言的 FC 运行时层
88
- 2. 替换 `contract/bootstrap`(如 `exec python app.py`)
88
+ 2. 替换 bootstrap(如 `exec python app.py`)—— 先读下面的「⚠️ bootstrap 归 daemon 管」
89
89
  3. `ext.conf` 的 `BUILD_CMD` 改成对应构建命令
90
90
 
91
91
  `s.yaml.tmpl` 结构不变。
92
92
 
93
+ ### ⚠️ bootstrap 归 daemon 管,改了会被还原
94
+
95
+ `publish.sh` 用的是**共享 deploy-kit** 那份:`$HOME/.clawd/deploy-kit/contract/bootstrap`
96
+ (不是本目录下的)。而 `deploy-kit/contract/` 和本 `extension-kit/` 一样,**每次 daemon 启动
97
+ 都会被 bundle 版本无条件覆盖**——就地改它,下次重启就没了,而且没有 `.secrets/*.local`
98
+ 那样的个人 override 豁免。
99
+
100
+ 在本目录下新建 `contract/bootstrap` 也不行:`extension-kit/` 是 daemon-managed 路径,
101
+ `contract/` 是 bundle 里有的子目录,整个会被替换掉。
102
+
103
+ 所以**换语言目前没有能存住的改法**,需要产品侧先给 bootstrap 一个 override 机制。
104
+ 在那之前,非 Node 运行时请找老板确认怎么走,别就地改了以为生效。
105
+
93
106
  ## 红线(都是踩坑换来的,见记忆 fc-nodejs-deploy-recipe)
94
107
 
95
108
  - **Supabase 是 clawos 共享生产库**:新表必须 `${APP_NAME}_${SLUG}_` 前缀(从 ext.conf 读),**绝不 drop/alter clawos 已有表**
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: clawd-session-import
3
+ description: 把用户本机 Claude Code / Codex 的历史会话关联成 clawd 会话,让他们在 clawd 里直接看到并接着聊。当用户说「把我之前在 Claude Code / Codex 的对话弄进 clawd」「刚从别的工具切过来,历史记录能带过来吗」「clawd 里怎么是空的,我以前的会话呢」「导入 / 关联历史会话」,或刚开始用 clawd 需要冷启动时使用。不负责删除会话、不负责搬运文件。
4
+ ---
5
+
6
+ # 把 Claude Code / Codex 的历史会话接进 clawd
7
+
8
+ 用户在 Claude Code 或 Codex 里已经聊过很多,刚切到 clawd 会看到一个空侧边栏,
9
+ 不知道从哪儿接着干。这个 skill 扫描本机这两个工具最近的会话,在 clawd 里给每条
10
+ 建一个引用,于是它们出现在侧边栏——点开能看到完整原始对话,发消息就接着往下聊。
11
+
12
+ **只建引用,不复制、不移动、不修改任何原始会话文件**,原会话在 Claude Code / Codex
13
+ 里照常可用。关联是可撤销的。
14
+
15
+ ## 用法
16
+
17
+ 只依赖 node(clawd 本身就是 node 跑的,必然有)。脚本就在本 skill 目录下,下面的命令
18
+ 按会话工作目录(persona 目录)写相对路径。
19
+
20
+ 先预演,看看会关联哪些(不写任何东西):
21
+
22
+ ```bash
23
+ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --days 7
24
+ ```
25
+
26
+ **把预演结果念给用户看**(几条、都是什么话题、来自哪些目录),确认后再执行:
27
+
28
+ ```bash
29
+ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --days 7 --apply
30
+ ```
31
+
32
+ 常用参数:
33
+
34
+ | 参数 | 作用 |
35
+ |---|---|
36
+ | `--days N` | 回看多少天,默认 7 |
37
+ | `--tool claude\|codex\|both` | 只处理其中一边,默认两边都要 |
38
+ | `--limit N` | 最多关联 N 条(按最近优先)。会话多的用户建议先 `--limit 20`,别一次灌满侧边栏 |
39
+ | `--min-turns N` | 少于 N 轮用户发言的会话跳过,默认 2(滤掉「hello」这种试水会话) |
40
+ | `--include-clawd` | 连 `~/.clawd` 目录下的会话也关联。默认跳过——那些本来就是 clawd 自己开的 |
41
+ | `--icon KEY` | 侧边栏图标:`research/code/loop/qa/reading/debug/idea/doc/assist`,默认 `reading` |
42
+ | `--json` | 输出 JSON,含被跳过的条目和原因,便于你自己筛 |
43
+
44
+ 撤销(删掉本工具建过的全部引用,原会话文件不受影响):
45
+
46
+ ```bash
47
+ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo # 预演
48
+ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo --apply # 真删
49
+ ```
50
+
51
+ ## 原理(已对 clawd 源码核实)
52
+
53
+ 一条 clawd 会话的元数据里有 `cwd` + `toolSessionId` 两个字段,指向底层工具那条真实
54
+ 对话。关联就是把这两个字段填对:
55
+
56
+ 1. `session:create` —— 只写一份元数据文件,**不拉起任何进程**,所以批量关联很便宜
57
+ 2. `session:resume` —— 该会话还没进程时,只是把 `toolSessionId` 写进元数据
58
+ 3. 脚本再把元数据里的创建/更新时间改回原会话的真实时间,侧边栏才按真实先后排序
59
+ (clawd 每次列会话都重读磁盘,改完立刻生效,不用重启)
60
+
61
+ 用户点开时,clawd 按 `(cwd, toolSessionId)` 去读原始 transcript 渲染;发消息才真正
62
+ 把 Claude Code / Codex 进程拉起来 resume 那条对话。
63
+
64
+ 两边的会话数据在哪:
65
+
66
+ - Claude Code:`~/.claude/projects/<编码后的 cwd>/<会话 id>.jsonl`,文件名就是会话 id
67
+ - Codex:`~/.codex/state_*.sqlite` 的 `threads` 表(权威索引,含 cwd / 时间 / 标题 /
68
+ rollout 路径)。读表按 `node:sqlite` → `sqlite3` 命令行 → 解析
69
+ `~/.codex/sessions/**/rollout-*.jsonl` 三级回落,老 Node / 老 codex 都能work
70
+
71
+ ## 标题(label)从哪来
72
+
73
+ **Claude Code 有现成的**:transcript 里 `type: "ai-title"` 那行的 `aiTitle`,是 CC 自己
74
+ 生成的会话标题(如「扫描设备sessions创建用户persona」),直接拿来用。实测覆盖率 89%,
75
+ 且缺失的那批全是 0~1 轮的「hello」「你是谁」,被默认 `--min-turns 2` 挡掉了——**实际关联
76
+ 进来的 CC 会话都有现成标题**。
77
+
78
+ (注:clawd 原生的历史列表用的是另一套优先级 `last-prompt` → `summary` → 首句前 120 字,
79
+ 没用 `ai-title`。所以这里的标题会比 clawd 历史列表更像标题,属预期。)
80
+
81
+ **Codex 没有**:`threads` 表的 `title` / `preview` / `first_user_message` 三列都是第一条
82
+ 用户消息**原文**,不是 AI 摘要;`name` 疑似手动命名字段,实测数据全空。取值顺序
83
+ `name` → `title` → `preview` → `first_user_message` → 解析 rollout 取首句,将来 codex
84
+ 补上真标题能自动吃到。
85
+
86
+ 拿到什么就是什么,**按显示宽度硬截到 56 列**(中文算 2 列),不做任何清洗或改写——
87
+ 标题应当忠实反映用户原话,不引入猜测。唯一的处理是把换行 / 连续空白折叠成单空格,
88
+ 因为标题是单行的。所以开头是长 URL 的 codex 会话,标题就是那段 URL,这是预期行为;
89
+ 用户在 clawd 里随时能手动改标题。
90
+
91
+ ## 会被跳过的(脚本自动处理,不用你操心)
92
+
93
+ - **已关联**:该会话 id 已经出现在某条 clawd 会话里,不会重复建
94
+ - **clawd 自己的会话**:`cwd` 在 `~/.clawd` 底下的,本来就是 clawd 开的(`--include-clawd` 可覆盖)
95
+ - **subagent 支线**:Claude Code 的子 agent transcript,不是独立会话
96
+ - **工作目录已不存在**:原目录被删了,clawd 建不了会话
97
+ - **轮次太少**:低于 `--min-turns`
98
+
99
+ ## 注意
100
+
101
+ - clawd 桌面端得在跑,否则脚本连不上(会明确报错)
102
+ - 关联完让用户刷新一下 clawd 界面才看得到
103
+ - Codex 会话在 clawd 里不支持回滚 / fork / 实时接管(这是 codex 侧的能力边界,
104
+ 不是关联的问题);能看能续聊
105
+ - 会话很多时一次全灌会把侧边栏冲爆,建议配 `--limit` 分批,或先用 `--days 3` 试水