@clawos-dev/clawd 0.2.292 → 0.2.293
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 +129 -57
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -43776,9 +43776,11 @@ init_protocol();
|
|
|
43776
43776
|
var import_node_os2 = require("os");
|
|
43777
43777
|
var FORCED_DENY_READ = ["~/"];
|
|
43778
43778
|
function personaEditDeny(personaDir) {
|
|
43779
|
+
return `Edit(${tildePath(personaDir)}/**)`;
|
|
43780
|
+
}
|
|
43781
|
+
function tildePath(p2) {
|
|
43779
43782
|
const home = (0, import_node_os2.homedir)();
|
|
43780
|
-
|
|
43781
|
-
return `Edit(${rel}/**)`;
|
|
43783
|
+
return p2.startsWith(`${home}/`) ? `~${p2.slice(home.length)}` : p2;
|
|
43782
43784
|
}
|
|
43783
43785
|
function unionArr(a, b2) {
|
|
43784
43786
|
const out = [...a ?? []];
|
|
@@ -43794,8 +43796,8 @@ function buildGuestSettingsV1() {
|
|
|
43794
43796
|
return {
|
|
43795
43797
|
permissions: {
|
|
43796
43798
|
defaultMode: "dontAsk"
|
|
43797
|
-
// persona-dir 的 Edit deny 由
|
|
43798
|
-
// (base
|
|
43799
|
+
// persona-dir 的 Edit deny 由 store.writeSandboxSettings 落盘注入 '~/<persona>/**' 绝对规则
|
|
43800
|
+
// (base 模板不放相对 './'——会随 getCwd 漂移误拦项目,见 personaEditDeny)。
|
|
43799
43801
|
},
|
|
43800
43802
|
sandbox: {
|
|
43801
43803
|
enabled: true,
|
|
@@ -43808,7 +43810,7 @@ function buildGuestSettingsV1() {
|
|
|
43808
43810
|
denyWrite: [],
|
|
43809
43811
|
// **不放 '~/'**(写是白名单制,home 自动不可写);persona(cwd) 由 compose 强制 deny
|
|
43810
43812
|
allowRead: [],
|
|
43811
|
-
//
|
|
43813
|
+
// persona 读权由 store.writeSandboxSettings 落盘注入;userWorkDir 由 compose 注入;profile 加工具链精确路径
|
|
43812
43814
|
allowWrite: []
|
|
43813
43815
|
}
|
|
43814
43816
|
}
|
|
@@ -43846,7 +43848,7 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
|
|
|
43846
43848
|
s.sandbox.allowUnsandboxedCommands = false;
|
|
43847
43849
|
fsv.denyRead = unionArr(fsv.denyRead, FORCED_DENY_READ);
|
|
43848
43850
|
fsv.denyWrite = unionArr((fsv.denyWrite ?? []).filter((p2) => p2 !== "~/"), [spawnCwd]);
|
|
43849
|
-
fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir
|
|
43851
|
+
fsv.allowRead = unionArr(fsv.allowRead, [userWorkDir]);
|
|
43850
43852
|
fsv.allowWrite = unionArr(fsv.allowWrite, [userWorkDir]);
|
|
43851
43853
|
return s;
|
|
43852
43854
|
}
|
|
@@ -44088,6 +44090,8 @@ function buildConnectionPrompt(args) {
|
|
|
44088
44090
|
let line = `\u4F60\u7684\u6587\u4EF6\u5DE5\u4F5C\u76EE\u5F55\u662F ${args.workDir}\uFF0C\u8FD9\u4E2A\u76EE\u5F55\u5BF9\u4F60\u53EF\u8BFB\u53EF\u5199\uFF0C\u6587\u4EF6\u7684\u8BFB\u53D6/\u521B\u5EFA/\u4FEE\u6539\u90FD\u5728\u8FD9\u91CC\u8FDB\u884C\u3002`;
|
|
44089
44091
|
if (args.mode === "guest") {
|
|
44090
44092
|
line += "persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002";
|
|
44093
|
+
} else {
|
|
44094
|
+
line += "persona \u76EE\u5F55\u5B58\u653E\u7684\u662F\u4EBA\u683C\u914D\u7F6E\uFF1B\u5DE5\u4F5C\u4EA7\u7269\u3001\u4E34\u65F6\u6587\u4EF6\u3001\u4E0B\u8F7D\u5185\u5BB9\u9ED8\u8BA4\u5199\u5230\u4E0A\u9762\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u4E0D\u8981\u843D\u5728 persona \u76EE\u5F55\u91CC\uFF08\u66F4\u65B0\u4EBA\u683C\u914D\u7F6E\u672C\u8EAB\u9664\u5916\uFF09\u3002";
|
|
44091
44095
|
}
|
|
44092
44096
|
parts.push(line);
|
|
44093
44097
|
}
|
|
@@ -47736,21 +47740,40 @@ var PersonaStore = class {
|
|
|
47736
47740
|
* 覆盖式写 persona 级 sandbox 配置(seed 给有 profile 的 persona 写 base⊕profile;owner 之后可手编)。
|
|
47737
47741
|
* 这份是 guest 沙箱的 **base 真源**:manager spawn 时 composeGuestSandbox 读它 + 叠 sandbox 强制底座
|
|
47738
47742
|
* (enabled/denyRead/denyWrite/userWorkDir owner 削弱不了)→ 写 per-guest 文件给 CC。
|
|
47739
|
-
* 写入时强制注入本 persona
|
|
47740
|
-
* 让落盘 base 就是 source of truth(compose 不再运行时替换 permission deny
|
|
47743
|
+
* 写入时强制注入本 persona 的**基线**(Edit-deny + allowRead,见 injectPersonaBaseline),
|
|
47744
|
+
* 让落盘 base 就是 source of truth(compose 不再运行时替换 permission deny / 注入 persona 读权)。
|
|
47741
47745
|
*/
|
|
47742
47746
|
writeSandboxSettings(personaId2, settings) {
|
|
47743
47747
|
this.atomicWrite(
|
|
47744
47748
|
this.sandboxSettingsPath(personaId2),
|
|
47745
|
-
JSON.stringify(this.
|
|
47749
|
+
JSON.stringify(this.injectPersonaBaseline(personaId2, settings), null, 2)
|
|
47746
47750
|
);
|
|
47747
47751
|
}
|
|
47748
|
-
/**
|
|
47749
|
-
|
|
47752
|
+
/**
|
|
47753
|
+
* persona 基线注入:Edit-deny(绝对 '~/<persona>/**')并进 permissions.deny + 剥掉历史相对
|
|
47754
|
+
* 'Edit(./**)';persona 目录 '~/' 路径 union 进 sandbox.filesystem.allowRead(guest 读权
|
|
47755
|
+
* 落盘即真源,compose 不再运行时注入——spec 2026-08-06-persona-workdir-discipline §1-§2)。
|
|
47756
|
+
*/
|
|
47757
|
+
injectPersonaBaseline(personaId2, settings) {
|
|
47758
|
+
const dir = this.personaDir(personaId2);
|
|
47750
47759
|
const s = settings;
|
|
47751
|
-
const rule = personaEditDeny(
|
|
47752
|
-
const
|
|
47753
|
-
|
|
47760
|
+
const rule = personaEditDeny(dir);
|
|
47761
|
+
const rawDeny = s.permissions && typeof s.permissions === "object" ? s.permissions.deny : void 0;
|
|
47762
|
+
const prevDeny = (Array.isArray(rawDeny) ? rawDeny : []).filter((r) => r !== "Edit(./**)" && r !== rule);
|
|
47763
|
+
const readPath = tildePath(dir);
|
|
47764
|
+
const sandbox = s.sandbox && typeof s.sandbox === "object" && !Array.isArray(s.sandbox) ? s.sandbox : {};
|
|
47765
|
+
const rawFs = sandbox.filesystem;
|
|
47766
|
+
const filesystem = rawFs && typeof rawFs === "object" && !Array.isArray(rawFs) ? rawFs : {};
|
|
47767
|
+
const rawRead = filesystem.allowRead;
|
|
47768
|
+
const prevRead = (Array.isArray(rawRead) ? rawRead : []).filter((p2) => p2 !== readPath);
|
|
47769
|
+
return {
|
|
47770
|
+
...s,
|
|
47771
|
+
permissions: {
|
|
47772
|
+
...s.permissions && typeof s.permissions === "object" ? s.permissions : {},
|
|
47773
|
+
deny: [...prevDeny, rule]
|
|
47774
|
+
},
|
|
47775
|
+
sandbox: { ...sandbox, filesystem: { ...filesystem, allowRead: [...prevRead, readPath] } }
|
|
47776
|
+
};
|
|
47754
47777
|
}
|
|
47755
47778
|
codexSandboxSettingsPath(personaId2) {
|
|
47756
47779
|
return path12.join(this.personaDir(personaId2), ".clawd", "codex-sandbox.json");
|
|
@@ -48615,6 +48638,35 @@ function migrateCodexSandbox(args) {
|
|
|
48615
48638
|
}
|
|
48616
48639
|
if (n > 0) args.logger.info("persona.codex-sandbox.done", { migrated: n });
|
|
48617
48640
|
}
|
|
48641
|
+
function migrateSandboxAllowRead(args) {
|
|
48642
|
+
let n = 0;
|
|
48643
|
+
for (const personaId2 of args.store.list()) {
|
|
48644
|
+
try {
|
|
48645
|
+
const readPath = tildePath(args.store.personaDirPath(personaId2));
|
|
48646
|
+
if (!fs12.existsSync(args.store.sandboxSettingsPath(personaId2))) {
|
|
48647
|
+
args.store.writeSandboxSettings(personaId2, buildGuestSettingsV1());
|
|
48648
|
+
n++;
|
|
48649
|
+
continue;
|
|
48650
|
+
}
|
|
48651
|
+
const raw = args.store.readSandboxSettings(personaId2);
|
|
48652
|
+
if (raw === null) {
|
|
48653
|
+
args.logger.warn("persona.sandbox.allow-read.corrupt-skip", { personaId: personaId2 });
|
|
48654
|
+
continue;
|
|
48655
|
+
}
|
|
48656
|
+
const allowRead = raw.sandbox?.filesystem?.allowRead;
|
|
48657
|
+
if (Array.isArray(allowRead) && allowRead.includes(readPath)) continue;
|
|
48658
|
+
args.store.writeSandboxSettings(personaId2, raw);
|
|
48659
|
+
n++;
|
|
48660
|
+
args.logger.info("persona.sandbox.allow-read.migrated", { personaId: personaId2 });
|
|
48661
|
+
} catch (err) {
|
|
48662
|
+
args.logger.warn("persona.sandbox.allow-read.failed", {
|
|
48663
|
+
personaId: personaId2,
|
|
48664
|
+
error: err instanceof Error ? err.message : String(err)
|
|
48665
|
+
});
|
|
48666
|
+
}
|
|
48667
|
+
}
|
|
48668
|
+
if (n > 0) args.logger.info("persona.sandbox.allow-read.done", { migrated: n });
|
|
48669
|
+
}
|
|
48618
48670
|
var PERSONA_BUILDER_ID = "persona-builder";
|
|
48619
48671
|
var PERSONA_BUILDER_TOMBSTONE_MARKER = "<!-- clawd:persona-builder-retired -->";
|
|
48620
48672
|
var PERSONA_BUILDER_TOMBSTONE = `${PERSONA_BUILDER_TOMBSTONE_MARKER}
|
|
@@ -54833,7 +54885,7 @@ function assertValidFileName(fileName) {
|
|
|
54833
54885
|
var HASH_PREFIX_LEN = 16;
|
|
54834
54886
|
async function writeUploadedAttachment(args) {
|
|
54835
54887
|
assertValidFileName(args.fileName);
|
|
54836
|
-
const attachmentsRoot = import_node_path34.default.join(args.
|
|
54888
|
+
const attachmentsRoot = import_node_path34.default.join(args.baseDir, ".attachments");
|
|
54837
54889
|
try {
|
|
54838
54890
|
import_node_fs31.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
54839
54891
|
} catch (err) {
|
|
@@ -54904,7 +54956,7 @@ async function writeUploadedAttachment(args) {
|
|
|
54904
54956
|
}
|
|
54905
54957
|
}
|
|
54906
54958
|
const absPath = import_node_path34.default.join(hashDir, finalFileName);
|
|
54907
|
-
const relPath =
|
|
54959
|
+
const relPath = absPath;
|
|
54908
54960
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
54909
54961
|
relPath: absPath,
|
|
54910
54962
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -55411,9 +55463,9 @@ function createHttpRouter(deps) {
|
|
|
55411
55463
|
});
|
|
55412
55464
|
return true;
|
|
55413
55465
|
}
|
|
55414
|
-
const
|
|
55466
|
+
const baseDir = up.getAttachmentBaseDir(sessionId);
|
|
55415
55467
|
const scope = up.getSessionScope(sessionId);
|
|
55416
|
-
if (!
|
|
55468
|
+
if (!baseDir || !scope) {
|
|
55417
55469
|
sendUploadJson(404, { code: "NOT_FOUND", message: `session ${sessionId} not found` });
|
|
55418
55470
|
return true;
|
|
55419
55471
|
}
|
|
@@ -55435,7 +55487,7 @@ function createHttpRouter(deps) {
|
|
|
55435
55487
|
}
|
|
55436
55488
|
try {
|
|
55437
55489
|
const result = await writeUploadedAttachment({
|
|
55438
|
-
|
|
55490
|
+
baseDir,
|
|
55439
55491
|
sessionId,
|
|
55440
55492
|
scope,
|
|
55441
55493
|
fileName,
|
|
@@ -55613,57 +55665,66 @@ function runAttachmentGc(args) {
|
|
|
55613
55665
|
if (import_node_path37.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
55614
55666
|
}
|
|
55615
55667
|
}
|
|
55668
|
+
const visited = /* @__PURE__ */ new Set();
|
|
55616
55669
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
55617
|
-
const
|
|
55670
|
+
const legacyDir = args.getSessionCwd?.(sessionId) ?? import_node_path37.default.join(
|
|
55618
55671
|
args.dataDir,
|
|
55619
55672
|
"sessions",
|
|
55620
55673
|
...scopeSubPath(scope).map(safeFileName),
|
|
55621
55674
|
safeFileName(sessionId)
|
|
55622
55675
|
);
|
|
55623
|
-
const
|
|
55624
|
-
|
|
55625
|
-
|
|
55626
|
-
|
|
55627
|
-
|
|
55628
|
-
|
|
55629
|
-
|
|
55630
|
-
|
|
55631
|
-
|
|
55632
|
-
for (const hashDir of hashDirs) {
|
|
55633
|
-
const hashDirAbs = import_node_path37.default.join(attRoot, hashDir);
|
|
55634
|
-
let files;
|
|
55676
|
+
const userWorkDir = args.getUserWorkDir?.(sessionId) ?? null;
|
|
55677
|
+
const roots = [legacyDir, ...userWorkDir ? [userWorkDir] : []].filter((r) => {
|
|
55678
|
+
if (visited.has(r)) return false;
|
|
55679
|
+
visited.add(r);
|
|
55680
|
+
return true;
|
|
55681
|
+
});
|
|
55682
|
+
for (const rootDir of roots) {
|
|
55683
|
+
const attRoot = import_node_path37.default.join(rootDir, ".attachments");
|
|
55684
|
+
let hashDirs;
|
|
55635
55685
|
try {
|
|
55636
|
-
|
|
55637
|
-
} catch {
|
|
55686
|
+
hashDirs = import_node_fs33.default.readdirSync(attRoot);
|
|
55687
|
+
} catch (err) {
|
|
55688
|
+
if (err.code === "ENOENT") continue;
|
|
55689
|
+
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
55638
55690
|
continue;
|
|
55639
55691
|
}
|
|
55640
|
-
for (const
|
|
55641
|
-
const
|
|
55642
|
-
let
|
|
55692
|
+
for (const hashDir of hashDirs) {
|
|
55693
|
+
const hashDirAbs = import_node_path37.default.join(attRoot, hashDir);
|
|
55694
|
+
let files;
|
|
55643
55695
|
try {
|
|
55644
|
-
|
|
55696
|
+
files = import_node_fs33.default.readdirSync(hashDirAbs);
|
|
55645
55697
|
} catch {
|
|
55646
55698
|
continue;
|
|
55647
55699
|
}
|
|
55648
|
-
|
|
55649
|
-
|
|
55650
|
-
|
|
55651
|
-
|
|
55700
|
+
for (const name of files) {
|
|
55701
|
+
const file = import_node_path37.default.join(hashDirAbs, name);
|
|
55702
|
+
let stat;
|
|
55703
|
+
try {
|
|
55704
|
+
stat = import_node_fs33.default.statSync(file);
|
|
55705
|
+
} catch {
|
|
55706
|
+
continue;
|
|
55707
|
+
}
|
|
55708
|
+
if (!stat.isFile()) continue;
|
|
55709
|
+
const age = now - stat.mtimeMs;
|
|
55710
|
+
if (age < ttlMs) continue;
|
|
55711
|
+
if (liveAbs.has(file)) continue;
|
|
55712
|
+
try {
|
|
55713
|
+
import_node_fs33.default.unlinkSync(file);
|
|
55714
|
+
} catch (err) {
|
|
55715
|
+
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
55716
|
+
}
|
|
55717
|
+
}
|
|
55652
55718
|
try {
|
|
55653
|
-
import_node_fs33.default.
|
|
55654
|
-
} catch
|
|
55655
|
-
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
55719
|
+
if (import_node_fs33.default.readdirSync(hashDirAbs).length === 0) import_node_fs33.default.rmdirSync(hashDirAbs);
|
|
55720
|
+
} catch {
|
|
55656
55721
|
}
|
|
55657
55722
|
}
|
|
55658
55723
|
try {
|
|
55659
|
-
if (import_node_fs33.default.readdirSync(
|
|
55724
|
+
if (import_node_fs33.default.readdirSync(attRoot).length === 0) import_node_fs33.default.rmdirSync(attRoot);
|
|
55660
55725
|
} catch {
|
|
55661
55726
|
}
|
|
55662
55727
|
}
|
|
55663
|
-
try {
|
|
55664
|
-
if (import_node_fs33.default.readdirSync(attRoot).length === 0) import_node_fs33.default.rmdirSync(attRoot);
|
|
55665
|
-
} catch {
|
|
55666
|
-
}
|
|
55667
55728
|
}
|
|
55668
55729
|
}
|
|
55669
55730
|
|
|
@@ -58789,7 +58850,7 @@ function computeMethodAccess(args) {
|
|
|
58789
58850
|
}
|
|
58790
58851
|
|
|
58791
58852
|
// src/version.ts
|
|
58792
|
-
var version = "0.2.
|
|
58853
|
+
var version = "0.2.293".length > 0 ? "0.2.293" : "dev";
|
|
58793
58854
|
|
|
58794
58855
|
// src/cli-probe/probe.ts
|
|
58795
58856
|
var fs55 = __toESM(require("fs"), 1);
|
|
@@ -62058,6 +62119,7 @@ async function startDaemon(config) {
|
|
|
62058
62119
|
refreshSandboxSettings({ store: personaStore, logger });
|
|
62059
62120
|
migrateAgentsMirror({ store: personaStore, logger });
|
|
62060
62121
|
migrateCodexSandbox({ store: personaStore, logger });
|
|
62122
|
+
migrateSandboxAllowRead({ store: personaStore, logger });
|
|
62061
62123
|
retirePersonaBuilder({ store: personaStore, logger });
|
|
62062
62124
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
62063
62125
|
const dispatchStore = createDispatchStore({
|
|
@@ -62487,6 +62549,12 @@ async function startDaemon(config) {
|
|
|
62487
62549
|
});
|
|
62488
62550
|
const getProjectStore = (userId) => new ProjectStore(deriveUserWorkDir(userId, usersRoot));
|
|
62489
62551
|
const appBuilderStore = getProjectStore(ownerPrincipalId);
|
|
62552
|
+
const attachmentUserWorkDir = (sid) => {
|
|
62553
|
+
const scope = manager.findOwnedSessionScope(sid);
|
|
62554
|
+
if (scope?.kind !== "persona") return null;
|
|
62555
|
+
const userId = scope.mode === "owner" ? ownerPrincipalId : scope.capId;
|
|
62556
|
+
return deriveUserWorkDir(userId, usersRoot);
|
|
62557
|
+
};
|
|
62490
62558
|
recoverInterruptedPublishJobs(appBuilderStore, logger).catch((err) => {
|
|
62491
62559
|
logger.warn("app-builder.publish.recover-startup-failed", {
|
|
62492
62560
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -62879,15 +62947,17 @@ async function startDaemon(config) {
|
|
|
62879
62947
|
larkBotEvent: larkBotEventEndpoint,
|
|
62880
62948
|
// /files HMAC verify 用 auth.json token(与 attachment.signUrl 同源)。
|
|
62881
62949
|
getSignSecret: () => authFile.token,
|
|
62882
|
-
// chatinput 附件上传(POST /attachments/upload,spec §6.1
|
|
62883
|
-
//
|
|
62884
|
-
//
|
|
62950
|
+
// chatinput 附件上传(POST /attachments/upload,spec §6.1 + 2026-08-06 §5)。落盘根按
|
|
62951
|
+
// scope 切换:persona 会话 → 连接者 userWorkDir(与 connection-prompt 宣称的工作目录一致,
|
|
62952
|
+
// guest 对 persona 目录的读权不再连带看到 owner 上传的附件);非 persona 会话 → session.cwd
|
|
62953
|
+
// (项目目录本就是用户地盘)。签名 secret 与 /files HMAC 同源;tunnel 未启时返 503。
|
|
62885
62954
|
attachmentUpload: {
|
|
62886
62955
|
groupFileStore,
|
|
62887
62956
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
62888
|
-
|
|
62957
|
+
getAttachmentBaseDir: (sid) => {
|
|
62889
62958
|
const f = manager.findOwnedSession(sid);
|
|
62890
|
-
|
|
62959
|
+
if (!f) return null;
|
|
62960
|
+
return attachmentUserWorkDir(sid) ?? f.cwd;
|
|
62891
62961
|
},
|
|
62892
62962
|
getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
|
|
62893
62963
|
},
|
|
@@ -63131,8 +63201,10 @@ ${bar}
|
|
|
63131
63201
|
scope,
|
|
63132
63202
|
sessionId
|
|
63133
63203
|
})),
|
|
63134
|
-
//
|
|
63204
|
+
// 双根扫描(spec 2026-08-06 §5):legacy cwd 根(迁移前落盘点,30 天自然老化)+
|
|
63205
|
+
// userWorkDir 根(迁移后 upload 落盘点,与 getAttachmentBaseDir 同源派生)
|
|
63135
63206
|
getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
|
|
63207
|
+
getUserWorkDir: attachmentUserWorkDir,
|
|
63136
63208
|
logger
|
|
63137
63209
|
});
|
|
63138
63210
|
} catch (err) {
|