@clawos-dev/clawd 0.2.232 → 0.2.234
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
|
@@ -4820,6 +4820,14 @@ var init_schemas = __esm({
|
|
|
4820
4820
|
* 或 deps.ownerDisplayName(owner)。
|
|
4821
4821
|
*/
|
|
4822
4822
|
creatorDisplayName: external_exports.string().min(1).optional(),
|
|
4823
|
+
/**
|
|
4824
|
+
* 创建该 session 的连接者飞书 union_id(一次性 stamp,T-31 persona prompt injection)。
|
|
4825
|
+
* session:create / dispatch spawnB handler 从 ctx.principal.feishuUnionId(guest 场景来自
|
|
4826
|
+
* connect token subOwner)或 deps.ownerFeishuUnionId(owner 场景)传入;newRunner 时读这个
|
|
4827
|
+
* 字段派生 SubSessionMeta.callerFeishuUnionId 注入 cc system prompt「身份卡」的 union_id 行。
|
|
4828
|
+
* Optional:老 SessionFile / owner 未登飞书路径缺省,缺省时 prompt 不渲染该行(向后兼容)。
|
|
4829
|
+
*/
|
|
4830
|
+
creatorFeishuUnionId: external_exports.string().min(1).optional(),
|
|
4823
4831
|
/**
|
|
4824
4832
|
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上创建的 session**,
|
|
4825
4833
|
* 远端是 source of truth,本机是持久化副本,方便 sidebar 跨设备聚合。
|
|
@@ -41423,11 +41431,17 @@ function scopeSubPath(scope) {
|
|
|
41423
41431
|
if (scope.mode === "guest") return [scope.personaId, "guests", scope.capId];
|
|
41424
41432
|
return [scope.personaId, scope.mode];
|
|
41425
41433
|
}
|
|
41426
|
-
function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName) {
|
|
41434
|
+
function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName, callerFeishuUnionId) {
|
|
41427
41435
|
if (scope.kind === "default") return void 0;
|
|
41428
41436
|
if (scope.mode === "owner") {
|
|
41429
41437
|
if (!ownerPrincipalId || !usersRoot) {
|
|
41430
|
-
return {
|
|
41438
|
+
return {
|
|
41439
|
+
idleKillEnabled: false,
|
|
41440
|
+
personaMode: "owner",
|
|
41441
|
+
callerDisplayName,
|
|
41442
|
+
callerDeviceId: ownerPrincipalId,
|
|
41443
|
+
callerFeishuUnionId
|
|
41444
|
+
};
|
|
41431
41445
|
}
|
|
41432
41446
|
const userWorkDir2 = deriveUserWorkDir(ownerPrincipalId, usersRoot);
|
|
41433
41447
|
return {
|
|
@@ -41435,7 +41449,9 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
|
|
|
41435
41449
|
personaMode: "owner",
|
|
41436
41450
|
userWorkDir: userWorkDir2,
|
|
41437
41451
|
addDirs: [userWorkDir2],
|
|
41438
|
-
callerDisplayName
|
|
41452
|
+
callerDisplayName,
|
|
41453
|
+
callerDeviceId: ownerPrincipalId,
|
|
41454
|
+
callerFeishuUnionId
|
|
41439
41455
|
};
|
|
41440
41456
|
}
|
|
41441
41457
|
const userWorkDir = deriveUserWorkDir(scope.capId, usersRoot);
|
|
@@ -41444,7 +41460,9 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
|
|
|
41444
41460
|
personaMode: "guest",
|
|
41445
41461
|
userWorkDir,
|
|
41446
41462
|
addDirs: [userWorkDir],
|
|
41447
|
-
callerDisplayName
|
|
41463
|
+
callerDisplayName,
|
|
41464
|
+
callerDeviceId: scope.capId,
|
|
41465
|
+
callerFeishuUnionId
|
|
41448
41466
|
};
|
|
41449
41467
|
}
|
|
41450
41468
|
function scopeForFile(file, ownerPrincipalId) {
|
|
@@ -41844,30 +41862,61 @@ function buildRule(tool, input) {
|
|
|
41844
41862
|
}
|
|
41845
41863
|
|
|
41846
41864
|
// src/persona/connection-prompt.ts
|
|
41847
|
-
|
|
41848
|
-
|
|
41849
|
-
|
|
41850
|
-
|
|
41851
|
-
|
|
41852
|
-
|
|
41865
|
+
function renderIdentityLines(fields) {
|
|
41866
|
+
const lines = [`- \u663E\u793A\u540D\uFF1A${fields.label}`];
|
|
41867
|
+
if (fields.feishuUnionId) lines.push(`- \u98DE\u4E66 union_id\uFF1A${fields.feishuUnionId}`);
|
|
41868
|
+
if (fields.deviceId) lines.push(`- \u8BBE\u5907 id\uFF1A${fields.deviceId}`);
|
|
41869
|
+
if (fields.extraLastLine) lines.push(fields.extraLastLine);
|
|
41870
|
+
return lines.join("\n");
|
|
41871
|
+
}
|
|
41853
41872
|
function buildConnectionPrompt(args) {
|
|
41854
|
-
|
|
41855
|
-
if (args.mode === "
|
|
41856
|
-
|
|
41857
|
-
|
|
41858
|
-
|
|
41873
|
+
const parts = ["# \u8FDE\u63A5\u4E0A\u4E0B\u6587"];
|
|
41874
|
+
if (args.mode === "owner") {
|
|
41875
|
+
parts.push("\u5F53\u524D\u8FDE\u63A5\u65B9\uFF08\u5C31\u662F owner \u672C\u4EBA\uFF09\uFF1A");
|
|
41876
|
+
parts.push(
|
|
41877
|
+
renderIdentityLines({
|
|
41878
|
+
label: args.ownerLabel,
|
|
41879
|
+
feishuUnionId: args.ownerFeishuUnionId,
|
|
41880
|
+
deviceId: args.ownerDeviceId
|
|
41881
|
+
})
|
|
41859
41882
|
);
|
|
41860
41883
|
} else {
|
|
41861
|
-
|
|
41884
|
+
parts.push("\u5F53\u524D\u8FDE\u63A5\u65B9\uFF08\u4E0D\u662F owner \u672C\u4EBA\uFF09\uFF1A");
|
|
41885
|
+
parts.push(
|
|
41886
|
+
renderIdentityLines({
|
|
41887
|
+
label: args.guestLabel,
|
|
41888
|
+
feishuUnionId: args.guestFeishuUnionId,
|
|
41889
|
+
deviceId: args.guestDeviceId,
|
|
41890
|
+
extraLastLine: "- \u8EAB\u4EFD\uFF1AGuest\uFF08\u8FDC\u7A0B\u8FDE\u63A5\u8FDB\u6765\u7684\u8054\u7CFB\u4EBA\uFF09"
|
|
41891
|
+
})
|
|
41892
|
+
);
|
|
41893
|
+
parts.push("");
|
|
41894
|
+
parts.push("\u672C persona \u7684 owner\uFF1A");
|
|
41895
|
+
parts.push(
|
|
41896
|
+
renderIdentityLines({
|
|
41897
|
+
label: args.ownerLabel,
|
|
41898
|
+
feishuUnionId: args.ownerFeishuUnionId,
|
|
41899
|
+
deviceId: args.ownerDeviceId
|
|
41900
|
+
})
|
|
41901
|
+
);
|
|
41862
41902
|
}
|
|
41863
41903
|
if (args.workDir) {
|
|
41864
|
-
|
|
41865
|
-
|
|
41904
|
+
parts.push("");
|
|
41905
|
+
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`;
|
|
41866
41906
|
if (args.mode === "guest") {
|
|
41867
|
-
|
|
41907
|
+
line += "persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002";
|
|
41868
41908
|
}
|
|
41909
|
+
parts.push(line);
|
|
41869
41910
|
}
|
|
41870
|
-
|
|
41911
|
+
parts.push("");
|
|
41912
|
+
if (args.mode === "owner") {
|
|
41913
|
+
parts.push("\u81EA\u6211\u4ECB\u7ECD\u65F6\u4F7F\u7528\u7B2C\u4E00/\u7B2C\u4E8C\u4EBA\u79F0\uFF0C\u4F8B\u5982\u300C\u6211\u662F\u60A8\u7684 xxx \u52A9\u624B\u300D\u3002");
|
|
41914
|
+
} else {
|
|
41915
|
+
parts.push(
|
|
41916
|
+
`\u81EA\u6211\u4ECB\u7ECD\u65F6\u7B2C\u4E09\u4EBA\u79F0\u6307\u4EE3 owner\u3001\u7B2C\u4E8C\u4EBA\u79F0\u79F0\u547C\u5BF9\u65B9\uFF0C\u4F8B\u5982\u300C${args.guestLabel}\uFF0C\u6211\u662F ${args.ownerLabel} \u7684 xxx \u52A9\u624B\uFF0C\u4F60\u53EF\u4EE5\u901A\u8FC7\u6211\u770B\u5230\u4ED6\u7684 xxx\u300D\u3002\u4E0D\u8981\u628A\u5BF9\u65B9\u5F53\u6210 owner\uFF0C\u4E0D\u8981\u66FF owner \u505A\u51FA\u627F\u8BFA\u3002`
|
|
41917
|
+
);
|
|
41918
|
+
}
|
|
41919
|
+
return parts.join("\n");
|
|
41871
41920
|
}
|
|
41872
41921
|
|
|
41873
41922
|
// src/persona/sharing-prompt.ts
|
|
@@ -41946,10 +41995,10 @@ var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u9
|
|
|
41946
41995
|
# execPath+cliPath\uFF0C\u4FDD\u8BC1 relay \u8DDF daemon \u7248\u672C\u540C\u6B65\u3002\u4E0D\u8981\u7528\u88F8 \`clawd\`\uFF08\u8D70 $PATH \u4F1A\u6F02\uFF09\u3002
|
|
41947
41996
|
\`\`\`
|
|
41948
41997
|
|
|
41949
|
-
**A \
|
|
41998
|
+
**A \u5EFA\u8BAE\u5728\u54EA\u4E9B\u76EE\u5F55\u5E72\u6D3B**\uFF1AA \u4FA7 jail \u8FDB shell \u524D\u4F1A\u628A\u5EFA\u8BAE\u76EE\u5F55\u5217\u8868\u5199\u5230 stderr\uFF08\u683C\u5F0F \`[clawd-ssh-jail] Suggested working directories for this contact: ...\`\uFF09\u4F5C\u4E3A\u53C2\u8003\u3002\u6CE8\u610F\uFF1A**\u6CA1\u6709\u76EE\u5F55\u7EA7\u5F3A\u5236\u9650\u5236**\u2014\u2014\u4F60\u8BBF\u95EE\u7684\u662F A \u673A\u5668\u4E0A\u8BE5 SSH \u7528\u6237\u6743\u9650\u5185\u7684\u6587\u4EF6\uFF0C\u8BF7\u53EA\u5728 A \u6388\u6743/\u9700\u8981\u7684\u8303\u56F4\u5185\u64CD\u4F5C\uFF0C\u4E0D\u8981\u8D8A\u754C\u7FFB\u770B\u65E0\u5173\u76EE\u5F55\u3002
|
|
41950
41999
|
|
|
41951
42000
|
**\u6CE8\u610F**\uFF1A
|
|
41952
|
-
- \
|
|
42001
|
+
- \u5EFA\u8BAE\u76EE\u5F55\u6570\u636E\u6E90\u5B9E\u65F6\uFF08jail \u6BCF\u6B21\u8BFB A \u4FA7\u6700\u65B0\u914D\u7F6E\uFF09\uFF0CA \u6539\u4E86 exposedDirs \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u611F\u77E5
|
|
41953
42002
|
- \u6392\u969C\uFF1A\u672C\u673A debug \u65E5\u5FD7\u5728 \`~/.clawd/log/contact-ssh.log\`\uFF08\u7528\u6237\u53EF \`tail -f\` \u770B\u6BCF\u4E00\u6B65\uFF09\uFF0C\u4F60\u51FA\u9519\u65F6\u544A\u8BC9\u7528\u6237\u67E5\u8FD9\u4E2A\u6587\u4EF6
|
|
41954
42003
|
- \u5982\u679C \`ls ~/.clawd/contact-ssh-keys/\` \u4E3A\u7A7A\uFF0C\u8BF4\u660E\u8FD8\u6CA1\u6709\u5BF9\u7AEF\u6388\u6743\u4F60\u6216 key \u8FD8\u6CA1\u62C9\u5230\uFF08B \u4FA7 daemon \u6BCF 60s \u4E00\u6B21\u81EA\u52A8\u62C9\uFF09\uFF0C\u5982\u5B9E\u544A\u8BC9\u7528\u6237
|
|
41955
42004
|
`;
|
|
@@ -42065,13 +42114,19 @@ function buildSpawnContext(state, deps) {
|
|
|
42065
42114
|
promptArgs = {
|
|
42066
42115
|
mode: "guest",
|
|
42067
42116
|
ownerLabel: deps.ownerDisplayName,
|
|
42117
|
+
ownerDeviceId: deps.ownerDeviceId,
|
|
42118
|
+
ownerFeishuUnionId: deps.ownerFeishuUnionId,
|
|
42068
42119
|
guestLabel: meta.callerDisplayName,
|
|
42120
|
+
guestDeviceId: meta.callerDeviceId,
|
|
42121
|
+
guestFeishuUnionId: meta.callerFeishuUnionId,
|
|
42069
42122
|
workDir: meta.userWorkDir
|
|
42070
42123
|
};
|
|
42071
42124
|
} else {
|
|
42072
42125
|
promptArgs = {
|
|
42073
42126
|
mode: "owner",
|
|
42074
42127
|
ownerLabel: meta.callerDisplayName ?? deps.ownerDisplayName,
|
|
42128
|
+
ownerDeviceId: meta.callerDeviceId ?? deps.ownerDeviceId,
|
|
42129
|
+
ownerFeishuUnionId: meta.callerFeishuUnionId ?? deps.ownerFeishuUnionId,
|
|
42075
42130
|
workDir: meta.userWorkDir
|
|
42076
42131
|
};
|
|
42077
42132
|
}
|
|
@@ -42856,6 +42911,8 @@ var SessionRunner = class {
|
|
|
42856
42911
|
resolveContextWindow: this.hooks.resolveContextWindow,
|
|
42857
42912
|
genUuid: this.hooks.genUuid ?? v4_default,
|
|
42858
42913
|
ownerDisplayName: this.hooks.ownerDisplayName,
|
|
42914
|
+
ownerDeviceId: this.hooks.ownerDeviceId,
|
|
42915
|
+
ownerFeishuUnionId: this.hooks.ownerFeishuUnionId,
|
|
42859
42916
|
personaRoot: this.hooks.personaRoot,
|
|
42860
42917
|
// 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
|
|
42861
42918
|
// curl daemon HTTP RPC adapter (/api/rpc/<method>) 触发管理操作。null = HTTP adapter 未启。
|
|
@@ -43634,12 +43691,14 @@ var SessionManager = class {
|
|
|
43634
43691
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
43635
43692
|
const store = this.storeFor(scope);
|
|
43636
43693
|
const callerDisplayName = file.creatorDisplayName ?? (scope.kind === "persona" ? scope.mode === "owner" ? this.deps.ownerDisplayName : this.deps.contactStore?.get(scope.capId)?.displayName : void 0);
|
|
43694
|
+
const callerFeishuUnionId = file.creatorFeishuUnionId ?? (scope.kind === "persona" && scope.mode === "owner" ? this.deps.ownerFeishuUnionId : void 0);
|
|
43637
43695
|
const subSessionMeta = metaFromScope(
|
|
43638
43696
|
scope,
|
|
43639
43697
|
this.deps.personaRoot ?? "",
|
|
43640
43698
|
this.deps.usersRoot ?? "",
|
|
43641
43699
|
this.deps.ownerPrincipalId,
|
|
43642
|
-
callerDisplayName
|
|
43700
|
+
callerDisplayName,
|
|
43701
|
+
callerFeishuUnionId
|
|
43643
43702
|
);
|
|
43644
43703
|
if (subSessionMeta?.userWorkDir) {
|
|
43645
43704
|
import_node_fs9.default.mkdirSync(subSessionMeta.userWorkDir, { recursive: true });
|
|
@@ -43686,6 +43745,11 @@ var SessionManager = class {
|
|
|
43686
43745
|
// personaStore 不再透给 runner hooks(in-place mention 注入路径已删,runner 不用);
|
|
43687
43746
|
// manager 自身的 sandbox 派生仍直接读 this.deps.personaStore。
|
|
43688
43747
|
ownerDisplayName: this.deps.ownerDisplayName,
|
|
43748
|
+
// T-31 persona prompt injection:owner 设备 id(= ownerPrincipalId)+ 飞书 union_id
|
|
43749
|
+
// 供 reducer 组 promptArgs 时作为「本 persona 的 owner」身份卡数据源(guest session 用)
|
|
43750
|
+
// 或「当前连接方」身份卡的兜底源(owner session 老路径未 stamp caller 时)。
|
|
43751
|
+
ownerDeviceId: this.deps.ownerPrincipalId,
|
|
43752
|
+
ownerFeishuUnionId: this.deps.ownerFeishuUnionId,
|
|
43689
43753
|
// Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
|
|
43690
43754
|
// cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
|
|
43691
43755
|
personaRoot: this.deps.personaRoot,
|
|
@@ -43801,7 +43865,7 @@ var SessionManager = class {
|
|
|
43801
43865
|
}
|
|
43802
43866
|
}
|
|
43803
43867
|
// ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
|
|
43804
|
-
create(args, creatorPrincipalId, creatorDisplayName) {
|
|
43868
|
+
create(args, creatorPrincipalId, creatorDisplayName, creatorFeishuUnionId) {
|
|
43805
43869
|
let cwd;
|
|
43806
43870
|
if (args.ownerPersonaId) {
|
|
43807
43871
|
cwd = derivePersonaSpawnCwd(
|
|
@@ -43846,6 +43910,7 @@ var SessionManager = class {
|
|
|
43846
43910
|
ephemeral: args.ephemeral,
|
|
43847
43911
|
creatorPrincipalId,
|
|
43848
43912
|
creatorDisplayName,
|
|
43913
|
+
...creatorFeishuUnionId ? { creatorFeishuUnionId } : {},
|
|
43849
43914
|
createdAt: iso,
|
|
43850
43915
|
updatedAt: iso
|
|
43851
43916
|
};
|
|
@@ -44616,7 +44681,9 @@ var SessionManager = class {
|
|
|
44616
44681
|
// creatorDisplayName = A 的 displayName,供 guest meta callerDisplayName(reducer 要求)。
|
|
44617
44682
|
...args.guestPrincipalId ? {
|
|
44618
44683
|
creatorPrincipalId: args.guestPrincipalId,
|
|
44619
|
-
...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {}
|
|
44684
|
+
...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {},
|
|
44685
|
+
// T-31: A 的飞书 union_id stamp 到 B 的 SessionFile;guest meta 由 newRunner 派生
|
|
44686
|
+
...args.guestFeishuUnionId ? { creatorFeishuUnionId: args.guestFeishuUnionId } : {}
|
|
44620
44687
|
} : {},
|
|
44621
44688
|
// T-20:dispatch model override 落盘(reducer spawn 路径读 SessionFile.model 生成 --model flag)
|
|
44622
44689
|
...args.model ? { model: args.model } : {},
|
|
@@ -48890,6 +48957,8 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48890
48957
|
prompt: args.prompt,
|
|
48891
48958
|
guestPrincipalId: ctx.principal.id,
|
|
48892
48959
|
guestDisplayName: ctx.principal.displayName,
|
|
48960
|
+
// T-31: 把 A 的飞书 union_id 透传给 B(connectGuestContext 已把它挂在 principal.feishuUnionId)
|
|
48961
|
+
...ctx.principal.feishuUnionId ? { guestFeishuUnionId: ctx.principal.feishuUnionId } : {},
|
|
48893
48962
|
...args.model ? { model: args.model } : {}
|
|
48894
48963
|
}).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
|
|
48895
48964
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -50662,7 +50731,7 @@ function ownerContext(ownerPrincipalId, displayName, feishuUnionId) {
|
|
|
50662
50731
|
}
|
|
50663
50732
|
function connectGuestContext(subDevice, subOwner, provider, displayName) {
|
|
50664
50733
|
return {
|
|
50665
|
-
principal: { id: subDevice, kind: "guest", displayName },
|
|
50734
|
+
principal: { id: subDevice, kind: "guest", displayName, feishuUnionId: subOwner },
|
|
50666
50735
|
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
50667
50736
|
resource: { ...g2.resource },
|
|
50668
50737
|
actions: [...g2.actions]
|
|
@@ -53464,9 +53533,8 @@ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
|
|
|
53464
53533
|
# 真实请求(interactive shell 时为空)。
|
|
53465
53534
|
#
|
|
53466
53535
|
# 职责:
|
|
53467
|
-
# 1. 读 ~/.clawd/contacts.json
|
|
53468
|
-
# 2.
|
|
53469
|
-
# 3. exec 沙箱 shell
|
|
53536
|
+
# 1. 读 ~/.clawd/contacts.json 校验 contact.sshAllowed,读 exposedDirs 作为提示
|
|
53537
|
+
# 2. 直接 exec shell(去沙箱后无目录级限制;exposedDirs 仅 echo 给对端参考)
|
|
53470
53538
|
|
|
53471
53539
|
set -euo pipefail
|
|
53472
53540
|
|
|
@@ -53513,33 +53581,19 @@ for c in data.get('contacts', []):
|
|
|
53513
53581
|
sys.exit(3)
|
|
53514
53582
|
")
|
|
53515
53583
|
|
|
53516
|
-
|
|
53517
|
-
|
|
53518
|
-
|
|
53519
|
-
|
|
53584
|
+
# exposedDirs 可能为空(sshAllowed=true 但未配目录)——去沙箱后不再因空目录拒绝,直接放行。
|
|
53585
|
+
if [ -n "$EXPOSED_JSON" ]; then
|
|
53586
|
+
log_line INFO jail.entered "exposedDirs 提示已加载,放行 exec shell"
|
|
53587
|
+
# 告知 SSH client 建议目录(发到 stderr)。去沙箱后这些只是建议,不做目录级强制限制;
|
|
53588
|
+
# exposedDirs 仅供对端参考,运行时读,A 改后下次拨号即感知。
|
|
53589
|
+
echo "[clawd-ssh-jail] Suggested working directories for this contact:" >&2
|
|
53590
|
+
while IFS= read -r d; do
|
|
53591
|
+
echo " - $d" >&2
|
|
53592
|
+
done <<< "$EXPOSED_JSON"
|
|
53593
|
+
echo "[clawd-ssh-jail] Note: no directory-level restriction is enforced." >&2
|
|
53594
|
+
else
|
|
53595
|
+
log_line INFO jail.entered "sshAllowed=true 且未配 exposedDirs,直接放行 exec shell"
|
|
53520
53596
|
fi
|
|
53521
|
-
log_line INFO jail.entered "exposedDirs 白名单已加载,进沙箱 exec shell"
|
|
53522
|
-
|
|
53523
|
-
# 进沙箱前告知 SSH client 授权目录列表(发到 stderr)—— CC 或其他 SSH client 试错一次就能
|
|
53524
|
-
# 学到 exposedDirs 具体值,无需 A 侧向 B 侧同步(授权数据实时的 single source of truth)。
|
|
53525
|
-
# 每次拨号运行时读,A 改 exposedDirs 后 B 侧下一次拨号立即感知。
|
|
53526
|
-
echo "[clawd-ssh-jail] Access allowed to these directories:" >&2
|
|
53527
|
-
while IFS= read -r d; do
|
|
53528
|
-
echo " - $d" >&2
|
|
53529
|
-
done <<< "$EXPOSED_JSON"
|
|
53530
|
-
echo "[clawd-ssh-jail] Anything outside these paths will be sandbox-denied." >&2
|
|
53531
|
-
|
|
53532
|
-
# 校验路径安全(bash 侧二次防御)
|
|
53533
|
-
while IFS= read -r line; do
|
|
53534
|
-
case "$line" in
|
|
53535
|
-
/*) : ;;
|
|
53536
|
-
*) echo "clawd-ssh-jail: bad path: $line" >&2; exit 1 ;;
|
|
53537
|
-
esac
|
|
53538
|
-
case "$line" in
|
|
53539
|
-
*[\"\'\`\$\;\|\&\(\)\{\}\[\]\<\>\*\?]*)
|
|
53540
|
-
echo "clawd-ssh-jail: unsafe path: $line" >&2; exit 1 ;;
|
|
53541
|
-
esac
|
|
53542
|
-
done <<< "$EXPOSED_JSON"
|
|
53543
53597
|
|
|
53544
53598
|
CMD="${"${SSH_ORIGINAL_COMMAND:-}"}"
|
|
53545
53599
|
if [ -z "$CMD" ]; then
|
|
@@ -53548,48 +53602,11 @@ else
|
|
|
53548
53602
|
SHELL_CMD=(bash -c "$CMD")
|
|
53549
53603
|
fi
|
|
53550
53604
|
|
|
53551
|
-
|
|
53552
|
-
|
|
53553
|
-
|
|
53554
|
-
|
|
53555
|
-
|
|
53556
|
-
(allow signal (target self))
|
|
53557
|
-
(allow sysctl-read)
|
|
53558
|
-
(allow mach-lookup)
|
|
53559
|
-
(allow file-read-metadata)
|
|
53560
|
-
(allow network*)
|
|
53561
|
-
(allow file-read* (subpath \"/usr\"))
|
|
53562
|
-
(allow file-read* (subpath \"/bin\"))
|
|
53563
|
-
(allow file-read* (subpath \"/sbin\"))
|
|
53564
|
-
(allow file-read* (subpath \"/System\"))
|
|
53565
|
-
(allow file-read* (subpath \"/Library\"))
|
|
53566
|
-
(allow file-read* (subpath \"/etc\"))
|
|
53567
|
-
(allow file-read* (subpath \"/private/etc\"))"
|
|
53568
|
-
while IFS= read -r d; do
|
|
53569
|
-
POLICY+="
|
|
53570
|
-
(allow file-read* file-write* (subpath \"$d\"))"
|
|
53571
|
-
done <<< "$EXPOSED_JSON"
|
|
53572
|
-
exec sandbox-exec -p "$POLICY" "${"${SHELL_CMD[@]}"}"
|
|
53573
|
-
;;
|
|
53574
|
-
Linux)
|
|
53575
|
-
BWRAP_ARGS=(
|
|
53576
|
-
--unshare-user --unshare-ipc --unshare-pid --unshare-uts
|
|
53577
|
-
--die-with-parent
|
|
53578
|
-
--proc /proc --dev /dev --tmpfs /tmp
|
|
53579
|
-
--ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /sbin /sbin
|
|
53580
|
-
--ro-bind /lib /lib --ro-bind /etc /etc
|
|
53581
|
-
)
|
|
53582
|
-
if [ -d /lib64 ]; then BWRAP_ARGS+=(--ro-bind /lib64 /lib64); fi
|
|
53583
|
-
while IFS= read -r d; do
|
|
53584
|
-
BWRAP_ARGS+=(--bind "$d" "$d")
|
|
53585
|
-
done <<< "$EXPOSED_JSON"
|
|
53586
|
-
exec bwrap "${"${BWRAP_ARGS[@]}"}" "${"${SHELL_CMD[@]}"}"
|
|
53587
|
-
;;
|
|
53588
|
-
*)
|
|
53589
|
-
echo "clawd-ssh-jail: unsupported OS $(uname -s)" >&2
|
|
53590
|
-
exit 1
|
|
53591
|
-
;;
|
|
53592
|
-
esac
|
|
53605
|
+
# 去掉 OS 沙箱层(原本 macOS Seatbelt 策略 / Linux 命名空间隔离):SSH 进来即该用户
|
|
53606
|
+
# 权限内的 shell,直接 exec。沙箱只限制文件、拦不住网络/进程,隔离价值有限,且相关 API
|
|
53607
|
+
# 在 macOS 已 deprecated。授权即完全信任——sshAllowed 授权开关 + authorized_keys 即时
|
|
53608
|
+
# 踢行是唯一访问闸。要加回沙箱:从 git 历史(T-29 之前)恢复本段的 OS 分支即可。
|
|
53609
|
+
exec "${"${SHELL_CMD[@]}"}"
|
|
53593
53610
|
`;
|
|
53594
53611
|
function ensureJailScript(dataDir) {
|
|
53595
53612
|
const binDir = import_node_path37.default.join(dataDir, "bin");
|
|
@@ -53636,6 +53653,8 @@ var SshdManager = class {
|
|
|
53636
53653
|
stopping = false;
|
|
53637
53654
|
exitHookInstalled = false;
|
|
53638
53655
|
startupTimeoutMs;
|
|
53656
|
+
// sshd stderr 按行嗌探 pubkey 认证结果时的半行缓冲(防关键字被 chunk 边界切断)
|
|
53657
|
+
sniffBuf = "";
|
|
53639
53658
|
get port() {
|
|
53640
53659
|
return this.deps.port;
|
|
53641
53660
|
}
|
|
@@ -53683,7 +53702,11 @@ var SshdManager = class {
|
|
|
53683
53702
|
logStream.write(String(c));
|
|
53684
53703
|
};
|
|
53685
53704
|
proc.stdout?.on("data", tee);
|
|
53686
|
-
proc.stderr?.on("data",
|
|
53705
|
+
proc.stderr?.on("data", (c) => {
|
|
53706
|
+
const s = String(c);
|
|
53707
|
+
logStream.write(s);
|
|
53708
|
+
this.sniffSshdAuth(s);
|
|
53709
|
+
});
|
|
53687
53710
|
proc.once("exit", () => logStream.end());
|
|
53688
53711
|
const ready = await waitForSshdReady(proc, this.startupTimeoutMs);
|
|
53689
53712
|
if (!ready.ok) {
|
|
@@ -53770,7 +53793,36 @@ ${tail}` : ready.error;
|
|
|
53770
53793
|
} catch {
|
|
53771
53794
|
}
|
|
53772
53795
|
}
|
|
53796
|
+
// 把 sshd -e 输出里的 pubkey 认证结果转写一条到 contact-ssh.log,让 A 侧审计日志一处即可
|
|
53797
|
+
// 看到"隧道连上但 sshd 拒了 key"(否则只有 tunnel.connected + tunnel.disconnected,为什么
|
|
53798
|
+
// 被拒要翻另一个 sshd.log 对时间线)。按 \n 切行嗅探,半行留 sniffBuf 等下个 chunk 拼回。
|
|
53799
|
+
sniffSshdAuth(chunk) {
|
|
53800
|
+
const sshLog = this.deps.sshLog;
|
|
53801
|
+
if (!sshLog) return;
|
|
53802
|
+
this.sniffBuf += chunk;
|
|
53803
|
+
const parts = this.sniffBuf.split("\n");
|
|
53804
|
+
this.sniffBuf = parts.pop() ?? "";
|
|
53805
|
+
for (const line of parts) {
|
|
53806
|
+
if (/Accepted publickey/i.test(line)) {
|
|
53807
|
+
sshLog.info(
|
|
53808
|
+
"ssh.auth-ok",
|
|
53809
|
+
"sshd \u63A5\u53D7\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u547D\u4E2D authorized_keys\uFF09",
|
|
53810
|
+
sshdAuthMeta(line)
|
|
53811
|
+
);
|
|
53812
|
+
} else if (/Failed publickey/i.test(line) || /Connection closed by (authenticating|invalid) user/i.test(line)) {
|
|
53813
|
+
sshLog.warn(
|
|
53814
|
+
"ssh.auth-rejected",
|
|
53815
|
+
"sshd \u62D2\u7EDD\u4E86 pubkey \u8BA4\u8BC1\uFF08\u5BF9\u7AEF key \u4E0D\u5728 authorized_keys / \u672A\u6388\u6743 / key \u672A\u62C9\u5230\uFF09",
|
|
53816
|
+
sshdAuthMeta(line)
|
|
53817
|
+
);
|
|
53818
|
+
}
|
|
53819
|
+
}
|
|
53820
|
+
}
|
|
53773
53821
|
};
|
|
53822
|
+
function sshdAuthMeta(line) {
|
|
53823
|
+
const m2 = /port (\d+)/.exec(line);
|
|
53824
|
+
return m2 ? { clientPort: Number(m2[1]) } : {};
|
|
53825
|
+
}
|
|
53774
53826
|
async function waitForSshdReady(proc, timeoutMs) {
|
|
53775
53827
|
return new Promise((resolve6) => {
|
|
53776
53828
|
let settled = false;
|
|
@@ -54161,6 +54213,11 @@ async function handleSshTunnelLocalUpgrade(req, socket, head, deps) {
|
|
|
54161
54213
|
url: outboundUrl
|
|
54162
54214
|
});
|
|
54163
54215
|
deps.wss.handleUpgrade(req, socket, head, (clientWs) => {
|
|
54216
|
+
sshLog.info(
|
|
54217
|
+
"relay.client-connected",
|
|
54218
|
+
"relay CLI \u5DF2\u8FDE\u4E0A\u672C\u5730 daemon\uFF08client WS \u5347\u7EA7\u6210\u529F\uFF09\uFF0C\u5F00\u59CB\u62E8 peer",
|
|
54219
|
+
{ peerDeviceId, peerDisplayName: contact.displayName }
|
|
54220
|
+
);
|
|
54164
54221
|
const makeOut = deps.makeOutboundWs ?? ((u, o) => new import_websocket.default(u, o));
|
|
54165
54222
|
const outbound = makeOut(outboundUrl, {
|
|
54166
54223
|
headers: { Authorization: `Bearer ${contact.connectToken}` }
|
|
@@ -54932,10 +54989,12 @@ function buildSessionHandlers(deps) {
|
|
|
54932
54989
|
ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
|
|
54933
54990
|
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
54934
54991
|
const creatorDisplayName = ctx?.principal.displayName ?? "Owner";
|
|
54992
|
+
const creatorFeishuUnionId = ctx?.principal.feishuUnionId;
|
|
54935
54993
|
const { response, broadcast } = manager.create(
|
|
54936
54994
|
{ ...args, tool },
|
|
54937
54995
|
creatorPrincipalId,
|
|
54938
|
-
creatorDisplayName
|
|
54996
|
+
creatorDisplayName,
|
|
54997
|
+
creatorFeishuUnionId
|
|
54939
54998
|
);
|
|
54940
54999
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
54941
55000
|
};
|
|
@@ -60239,6 +60298,8 @@ async function startDaemon(config) {
|
|
|
60239
60298
|
// B 角色(跨设备):非空 → guest scope + creatorPrincipalId/creatorDisplayName = A。
|
|
60240
60299
|
guestPrincipalId: args.guestPrincipalId,
|
|
60241
60300
|
guestDisplayName: args.guestDisplayName,
|
|
60301
|
+
// T-31: 把 A 的飞书 union_id stamp 到 B SessionFile,供 cc prompt 身份卡 union_id 行
|
|
60302
|
+
guestFeishuUnionId: args.guestFeishuUnionId,
|
|
60242
60303
|
// T-20: 可选 model override 透到 SessionFile.model
|
|
60243
60304
|
...args.model ? { model: args.model } : {}
|
|
60244
60305
|
});
|
|
@@ -60620,7 +60681,9 @@ ${bar}
|
|
|
60620
60681
|
port: config.sshdPort,
|
|
60621
60682
|
logger,
|
|
60622
60683
|
installProcessExitHandlers: true,
|
|
60623
|
-
onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info)
|
|
60684
|
+
onSshdExit: (info) => logger.warn("sshd exited unexpectedly", info),
|
|
60685
|
+
// sshd 的 pubkey 认证结果转写进 contact-ssh.log(补 A 侧「隧道连上但 sshd 拒了 key」盲区)
|
|
60686
|
+
sshLog
|
|
60624
60687
|
});
|
|
60625
60688
|
try {
|
|
60626
60689
|
await sshdMgr.start();
|
|
@@ -39876,6 +39876,14 @@ var SessionFileSchema = external_exports.object({
|
|
|
39876
39876
|
* 或 deps.ownerDisplayName(owner)。
|
|
39877
39877
|
*/
|
|
39878
39878
|
creatorDisplayName: external_exports.string().min(1).optional(),
|
|
39879
|
+
/**
|
|
39880
|
+
* 创建该 session 的连接者飞书 union_id(一次性 stamp,T-31 persona prompt injection)。
|
|
39881
|
+
* session:create / dispatch spawnB handler 从 ctx.principal.feishuUnionId(guest 场景来自
|
|
39882
|
+
* connect token subOwner)或 deps.ownerFeishuUnionId(owner 场景)传入;newRunner 时读这个
|
|
39883
|
+
* 字段派生 SubSessionMeta.callerFeishuUnionId 注入 cc system prompt「身份卡」的 union_id 行。
|
|
39884
|
+
* Optional:老 SessionFile / owner 未登飞书路径缺省,缺省时 prompt 不渲染该行(向后兼容)。
|
|
39885
|
+
*/
|
|
39886
|
+
creatorFeishuUnionId: external_exports.string().min(1).optional(),
|
|
39879
39887
|
/**
|
|
39880
39888
|
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上创建的 session**,
|
|
39881
39889
|
* 远端是 source of truth,本机是持久化副本,方便 sidebar 跨设备聚合。
|