@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 +80 -65
- package/dist/dispatch/mcp-server.cjs +6 -1
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +14 -1
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/SKILL.md +105 -0
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/scripts/link-sessions.mjs +665 -0
- package/dist/persona-defaults/persona-clawd-butler/CLAUDE.md +2 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +14 -1
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/{guest-DB_Gozzs.css → guest-BKKB9cOG.css} +1 -1
- package/dist/share-ui/assets/{guest-BKO48Nz8.js → guest-XaN4r7jJ.js} +1 -1
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
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 =
|
|
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 骨架 +
|
|
48020
|
-
// prebuild skill
|
|
48021
|
-
//
|
|
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
|
-
|
|
48288
|
+
replaceManagedPath(srcPath, dstPath);
|
|
48274
48289
|
if (relPath === "CLAUDE.md") {
|
|
48275
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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()}-${
|
|
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
|
|
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-${
|
|
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
|
|
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}-${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
55725
|
+
return import_node_crypto11.default.randomBytes(32).toString("base64url");
|
|
55711
55726
|
}
|
|
55712
55727
|
function defaultGenerateOwnerPrincipalId() {
|
|
55713
|
-
return `owner-${
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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. 替换
|
|
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 已有表**
|
package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/SKILL.md
ADDED
|
@@ -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` 试水
|