@clawos-dev/clawd 0.2.233 → 0.2.235

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 跨设备聚合。
@@ -5737,8 +5745,10 @@ var init_contact = __esm({
5737
5745
  */
5738
5746
  sshAllowed: external_exports.boolean().default(false),
5739
5747
  /**
5740
- * 授权访问的目录白名单(绝对路径)。空数组 + sshAllowed=true 意味 SSH 通道能拨到但无处可读,
5741
- * jail 会拒绝 shell 启动。由 clawd-ssh-jail 运行时读,生成 sbpl / bwrap policy 白名单。
5748
+ * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
5749
+ * policy 用);去掉 OS 沙箱后不再有目录级限制,SSH 授权即 on/off(见 sshAllowed)。
5750
+ * 字段仅为兼容老 contacts.json 保留(default [] 保证 strict parse 不炸),不再有代码读写它,
5751
+ * 也不再进 setSshAccess RPC / ContactSettingsDrawer / jail。勿新增消费方。
5742
5752
  */
5743
5753
  exposedDirs: external_exports.array(external_exports.string()).default([])
5744
5754
  }).strict();
@@ -5787,14 +5797,12 @@ var init_contact_ssh = __esm({
5787
5797
  init_zod();
5788
5798
  ContactSetSshAccessArgsSchema = external_exports.object({
5789
5799
  deviceId: external_exports.string().min(1),
5790
- sshAllowed: external_exports.boolean(),
5791
- exposedDirs: external_exports.array(external_exports.string())
5800
+ sshAllowed: external_exports.boolean()
5792
5801
  }).strict();
5793
5802
  ContactSetSshAccessOkSchema = external_exports.object({
5794
5803
  type: external_exports.literal("contact:setSshAccess:ok"),
5795
5804
  deviceId: external_exports.string().min(1),
5796
- sshAllowed: external_exports.boolean(),
5797
- exposedDirs: external_exports.array(external_exports.string())
5805
+ sshAllowed: external_exports.boolean()
5798
5806
  }).strict();
5799
5807
  ContactSshKeyIssueArgsSchema = external_exports.object({
5800
5808
  deviceId: external_exports.string().min(1)
@@ -5807,8 +5815,7 @@ var init_contact_ssh = __esm({
5807
5815
  ContactSshAccessUpdatedFrameSchema = external_exports.object({
5808
5816
  type: external_exports.literal("contact:ssh-access-updated"),
5809
5817
  deviceId: external_exports.string().min(1),
5810
- sshAllowed: external_exports.boolean(),
5811
- exposedDirs: external_exports.array(external_exports.string())
5818
+ sshAllowed: external_exports.boolean()
5812
5819
  }).strict();
5813
5820
  }
5814
5821
  });
@@ -41423,11 +41430,17 @@ function scopeSubPath(scope) {
41423
41430
  if (scope.mode === "guest") return [scope.personaId, "guests", scope.capId];
41424
41431
  return [scope.personaId, scope.mode];
41425
41432
  }
41426
- function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName) {
41433
+ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName, callerFeishuUnionId) {
41427
41434
  if (scope.kind === "default") return void 0;
41428
41435
  if (scope.mode === "owner") {
41429
41436
  if (!ownerPrincipalId || !usersRoot) {
41430
- return { idleKillEnabled: false, personaMode: "owner", callerDisplayName };
41437
+ return {
41438
+ idleKillEnabled: false,
41439
+ personaMode: "owner",
41440
+ callerDisplayName,
41441
+ callerDeviceId: ownerPrincipalId,
41442
+ callerFeishuUnionId
41443
+ };
41431
41444
  }
41432
41445
  const userWorkDir2 = deriveUserWorkDir(ownerPrincipalId, usersRoot);
41433
41446
  return {
@@ -41435,7 +41448,9 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
41435
41448
  personaMode: "owner",
41436
41449
  userWorkDir: userWorkDir2,
41437
41450
  addDirs: [userWorkDir2],
41438
- callerDisplayName
41451
+ callerDisplayName,
41452
+ callerDeviceId: ownerPrincipalId,
41453
+ callerFeishuUnionId
41439
41454
  };
41440
41455
  }
41441
41456
  const userWorkDir = deriveUserWorkDir(scope.capId, usersRoot);
@@ -41444,7 +41459,9 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
41444
41459
  personaMode: "guest",
41445
41460
  userWorkDir,
41446
41461
  addDirs: [userWorkDir],
41447
- callerDisplayName
41462
+ callerDisplayName,
41463
+ callerDeviceId: scope.capId,
41464
+ callerFeishuUnionId
41448
41465
  };
41449
41466
  }
41450
41467
  function scopeForFile(file, ownerPrincipalId) {
@@ -41844,30 +41861,61 @@ function buildRule(tool, input) {
41844
41861
  }
41845
41862
 
41846
41863
  // src/persona/connection-prompt.ts
41847
- var OWNER_TEMPLATE = `# \u8FDE\u63A5\u4E0A\u4E0B\u6587
41848
- \u4F60\u73B0\u5728\u4EE5 Owner \u8EAB\u4EFD\u88AB\u8FDE\u63A5\uFF0C\u5BF9\u65B9\u5C31\u662F owner \u672C\u4EBA\uFF08{ownerLabel}\uFF09\u3002
41849
- \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`;
41850
- var GUEST_TEMPLATE = `# \u8FDE\u63A5\u4E0A\u4E0B\u6587
41851
- \u4F60\u73B0\u5728\u4EE5 Guest \u8EAB\u4EFD\u88AB\u8FDE\u63A5\uFF0C\u672C persona \u7684 owner \u662F\u300C{ownerLabel}\u300D\uFF0C\u5F53\u524D\u5BF9\u8BDD\u7684\u5BF9\u65B9\u662F\u300C{guestLabel}\u300D\uFF0C\u4E0D\u662F owner \u672C\u4EBA\u3002
41852
- \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{guestLabel}\uFF0C\u6211\u662F {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`;
41864
+ function renderIdentityLines(fields) {
41865
+ const lines = [`- \u663E\u793A\u540D\uFF1A${fields.label}`];
41866
+ if (fields.feishuUnionId) lines.push(`- \u98DE\u4E66 union_id\uFF1A${fields.feishuUnionId}`);
41867
+ if (fields.deviceId) lines.push(`- \u8BBE\u5907 id\uFF1A${fields.deviceId}`);
41868
+ if (fields.extraLastLine) lines.push(fields.extraLastLine);
41869
+ return lines.join("\n");
41870
+ }
41853
41871
  function buildConnectionPrompt(args) {
41854
- let result;
41855
- if (args.mode === "guest") {
41856
- result = GUEST_TEMPLATE.replace(/\{ownerLabel\}/g, args.ownerLabel).replace(
41857
- /\{guestLabel\}/g,
41858
- args.guestLabel
41872
+ const parts = ["# \u8FDE\u63A5\u4E0A\u4E0B\u6587"];
41873
+ if (args.mode === "owner") {
41874
+ parts.push("\u5F53\u524D\u8FDE\u63A5\u65B9\uFF08\u5C31\u662F owner \u672C\u4EBA\uFF09\uFF1A");
41875
+ parts.push(
41876
+ renderIdentityLines({
41877
+ label: args.ownerLabel,
41878
+ feishuUnionId: args.ownerFeishuUnionId,
41879
+ deviceId: args.ownerDeviceId
41880
+ })
41859
41881
  );
41860
41882
  } else {
41861
- result = OWNER_TEMPLATE.replace(/\{ownerLabel\}/g, args.ownerLabel);
41883
+ parts.push("\u5F53\u524D\u8FDE\u63A5\u65B9\uFF08\u4E0D\u662F owner \u672C\u4EBA\uFF09\uFF1A");
41884
+ parts.push(
41885
+ renderIdentityLines({
41886
+ label: args.guestLabel,
41887
+ feishuUnionId: args.guestFeishuUnionId,
41888
+ deviceId: args.guestDeviceId,
41889
+ extraLastLine: "- \u8EAB\u4EFD\uFF1AGuest\uFF08\u8FDC\u7A0B\u8FDE\u63A5\u8FDB\u6765\u7684\u8054\u7CFB\u4EBA\uFF09"
41890
+ })
41891
+ );
41892
+ parts.push("");
41893
+ parts.push("\u672C persona \u7684 owner\uFF1A");
41894
+ parts.push(
41895
+ renderIdentityLines({
41896
+ label: args.ownerLabel,
41897
+ feishuUnionId: args.ownerFeishuUnionId,
41898
+ deviceId: args.ownerDeviceId
41899
+ })
41900
+ );
41862
41901
  }
41863
41902
  if (args.workDir) {
41864
- result += `
41865
- \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`;
41903
+ parts.push("");
41904
+ 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
41905
  if (args.mode === "guest") {
41867
- result += "persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002";
41906
+ line += "persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002";
41868
41907
  }
41908
+ parts.push(line);
41869
41909
  }
41870
- return result;
41910
+ parts.push("");
41911
+ if (args.mode === "owner") {
41912
+ 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");
41913
+ } else {
41914
+ parts.push(
41915
+ `\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`
41916
+ );
41917
+ }
41918
+ return parts.join("\n");
41871
41919
  }
41872
41920
 
41873
41921
  // src/persona/sharing-prompt.ts
@@ -41946,10 +41994,10 @@ var CONTACT_SSH_SYSTEM_PROMPT_HINT = `## \u8DE8\u8BBE\u5907\u6587\u4EF6\u8BBF\u9
41946
41994
  # 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
41995
  \`\`\`
41948
41996
 
41949
- **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
41997
+ **\u4F60\u80FD\u8BBF\u95EE\u4EC0\u4E48**\uFF1A**\u6CA1\u6709\u76EE\u5F55\u7EA7\u9650\u5236**\u2014\u2014\u4F60\u4EE5 A \u673A\u5668\u4E0A\u8BE5 SSH \u7528\u6237\u7684\u6743\u9650\u8BBF\u95EE\u6587\u4EF6\uFF08\u7B49\u540C\u4E8E A \u7ED9\u4F60\u5F00\u4E86\u4E00\u4E2A shell\uFF09\u3002\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
41998
 
41951
41999
  **\u6CE8\u610F**\uFF1A
41952
- - \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
42000
+ - \u6388\u6743\u662F\u5B9E\u65F6\u7684\uFF1AA \u64A4\u9500 sshAllowed \u540E\u4F60\u4E0B\u4E00\u6B21\u62E8\u53F7\u7ACB\u5373\u5931\u6548\uFF08authorized_keys \u7ACB\u5373\u5237\uFF09
41953
42001
  - \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
42002
  - \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
42003
  `;
@@ -42065,13 +42113,19 @@ function buildSpawnContext(state, deps) {
42065
42113
  promptArgs = {
42066
42114
  mode: "guest",
42067
42115
  ownerLabel: deps.ownerDisplayName,
42116
+ ownerDeviceId: deps.ownerDeviceId,
42117
+ ownerFeishuUnionId: deps.ownerFeishuUnionId,
42068
42118
  guestLabel: meta.callerDisplayName,
42119
+ guestDeviceId: meta.callerDeviceId,
42120
+ guestFeishuUnionId: meta.callerFeishuUnionId,
42069
42121
  workDir: meta.userWorkDir
42070
42122
  };
42071
42123
  } else {
42072
42124
  promptArgs = {
42073
42125
  mode: "owner",
42074
42126
  ownerLabel: meta.callerDisplayName ?? deps.ownerDisplayName,
42127
+ ownerDeviceId: meta.callerDeviceId ?? deps.ownerDeviceId,
42128
+ ownerFeishuUnionId: meta.callerFeishuUnionId ?? deps.ownerFeishuUnionId,
42075
42129
  workDir: meta.userWorkDir
42076
42130
  };
42077
42131
  }
@@ -42856,6 +42910,8 @@ var SessionRunner = class {
42856
42910
  resolveContextWindow: this.hooks.resolveContextWindow,
42857
42911
  genUuid: this.hooks.genUuid ?? v4_default,
42858
42912
  ownerDisplayName: this.hooks.ownerDisplayName,
42913
+ ownerDeviceId: this.hooks.ownerDeviceId,
42914
+ ownerFeishuUnionId: this.hooks.ownerFeishuUnionId,
42859
42915
  personaRoot: this.hooks.personaRoot,
42860
42916
  // 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
42861
42917
  // curl daemon HTTP RPC adapter (/api/rpc/<method>) 触发管理操作。null = HTTP adapter 未启。
@@ -43634,12 +43690,14 @@ var SessionManager = class {
43634
43690
  const adapter = this.deps.getAdapter(file.tool ?? "claude");
43635
43691
  const store = this.storeFor(scope);
43636
43692
  const callerDisplayName = file.creatorDisplayName ?? (scope.kind === "persona" ? scope.mode === "owner" ? this.deps.ownerDisplayName : this.deps.contactStore?.get(scope.capId)?.displayName : void 0);
43693
+ const callerFeishuUnionId = file.creatorFeishuUnionId ?? (scope.kind === "persona" && scope.mode === "owner" ? this.deps.ownerFeishuUnionId : void 0);
43637
43694
  const subSessionMeta = metaFromScope(
43638
43695
  scope,
43639
43696
  this.deps.personaRoot ?? "",
43640
43697
  this.deps.usersRoot ?? "",
43641
43698
  this.deps.ownerPrincipalId,
43642
- callerDisplayName
43699
+ callerDisplayName,
43700
+ callerFeishuUnionId
43643
43701
  );
43644
43702
  if (subSessionMeta?.userWorkDir) {
43645
43703
  import_node_fs9.default.mkdirSync(subSessionMeta.userWorkDir, { recursive: true });
@@ -43686,6 +43744,11 @@ var SessionManager = class {
43686
43744
  // personaStore 不再透给 runner hooks(in-place mention 注入路径已删,runner 不用);
43687
43745
  // manager 自身的 sandbox 派生仍直接读 this.deps.personaStore。
43688
43746
  ownerDisplayName: this.deps.ownerDisplayName,
43747
+ // T-31 persona prompt injection:owner 设备 id(= ownerPrincipalId)+ 飞书 union_id
43748
+ // 供 reducer 组 promptArgs 时作为「本 persona 的 owner」身份卡数据源(guest session 用)
43749
+ // 或「当前连接方」身份卡的兜底源(owner session 老路径未 stamp caller 时)。
43750
+ ownerDeviceId: this.deps.ownerPrincipalId,
43751
+ ownerFeishuUnionId: this.deps.ownerFeishuUnionId,
43689
43752
  // Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
43690
43753
  // cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
43691
43754
  personaRoot: this.deps.personaRoot,
@@ -43801,7 +43864,7 @@ var SessionManager = class {
43801
43864
  }
43802
43865
  }
43803
43866
  // ---- 命令方法:均返回 { response, broadcast[] },由 dispatcher 聚合 ----
43804
- create(args, creatorPrincipalId, creatorDisplayName) {
43867
+ create(args, creatorPrincipalId, creatorDisplayName, creatorFeishuUnionId) {
43805
43868
  let cwd;
43806
43869
  if (args.ownerPersonaId) {
43807
43870
  cwd = derivePersonaSpawnCwd(
@@ -43846,6 +43909,7 @@ var SessionManager = class {
43846
43909
  ephemeral: args.ephemeral,
43847
43910
  creatorPrincipalId,
43848
43911
  creatorDisplayName,
43912
+ ...creatorFeishuUnionId ? { creatorFeishuUnionId } : {},
43849
43913
  createdAt: iso,
43850
43914
  updatedAt: iso
43851
43915
  };
@@ -44616,7 +44680,9 @@ var SessionManager = class {
44616
44680
  // creatorDisplayName = A 的 displayName,供 guest meta callerDisplayName(reducer 要求)。
44617
44681
  ...args.guestPrincipalId ? {
44618
44682
  creatorPrincipalId: args.guestPrincipalId,
44619
- ...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {}
44683
+ ...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {},
44684
+ // T-31: A 的飞书 union_id stamp 到 B 的 SessionFile;guest meta 由 newRunner 派生
44685
+ ...args.guestFeishuUnionId ? { creatorFeishuUnionId: args.guestFeishuUnionId } : {}
44620
44686
  } : {},
44621
44687
  // T-20:dispatch model override 落盘(reducer spawn 路径读 SessionFile.model 生成 --model flag)
44622
44688
  ...args.model ? { model: args.model } : {},
@@ -48890,6 +48956,8 @@ function buildPersonaDispatchHandlers(deps) {
48890
48956
  prompt: args.prompt,
48891
48957
  guestPrincipalId: ctx.principal.id,
48892
48958
  guestDisplayName: ctx.principal.displayName,
48959
+ // T-31: 把 A 的飞书 union_id 透传给 B(connectGuestContext 已把它挂在 principal.feishuUnionId)
48960
+ ...ctx.principal.feishuUnionId ? { guestFeishuUnionId: ctx.principal.feishuUnionId } : {},
48893
48961
  ...args.model ? { model: args.model } : {}
48894
48962
  }).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
48895
48963
  const reason = err instanceof Error ? err.message : String(err);
@@ -50662,7 +50730,7 @@ function ownerContext(ownerPrincipalId, displayName, feishuUnionId) {
50662
50730
  }
50663
50731
  function connectGuestContext(subDevice, subOwner, provider, displayName) {
50664
50732
  return {
50665
- principal: { id: subDevice, kind: "guest", displayName },
50733
+ principal: { id: subDevice, kind: "guest", displayName, feishuUnionId: subOwner },
50666
50734
  grants: PERSONAL_CAP_GRANTS.map((g2) => ({
50667
50735
  resource: { ...g2.resource },
50668
50736
  actions: [...g2.actions]
@@ -51144,9 +51212,8 @@ var ContactStore = class {
51144
51212
  return true;
51145
51213
  }
51146
51214
  /**
51147
- * 更新单条 contact 的 SSH 授权(PR: contact-ssh-sandbox)。对齐 setPin pattern:
51148
- * store 只做原始 mutation,不做业务校验(如"sshAllowed=false 时清空 exposedDirs")——
51149
- * 那是 handler / UI 的责任。数组语义是完全替换(不 append)。
51215
+ * 更新单条 contact 的 SSH 授权开关(对齐 setPin pattern)。T-29 去沙箱后只改 sshAllowed;
51216
+ * exposedDirs 已废弃,本方法不再触碰它(保留 contact 原值不动)。
51150
51217
  * @returns 是否命中:deviceId 不存在返 false;命中即 flush.
51151
51218
  */
51152
51219
  setSshAccess(deviceId, opts) {
@@ -51154,8 +51221,7 @@ var ContactStore = class {
51154
51221
  if (!existing) return false;
51155
51222
  this.contacts.set(deviceId, {
51156
51223
  ...existing,
51157
- sshAllowed: opts.sshAllowed,
51158
- exposedDirs: opts.exposedDirs
51224
+ sshAllowed: opts.sshAllowed
51159
51225
  });
51160
51226
  this.flush();
51161
51227
  return true;
@@ -53464,8 +53530,8 @@ var CLAWD_SSH_JAIL_SCRIPT = String.raw`#!/usr/bin/env bash
53464
53530
  # 真实请求(interactive shell 时为空)。
53465
53531
  #
53466
53532
  # 职责:
53467
- # 1. 读 ~/.clawd/contacts.json 校验 contact.sshAllowed,读 exposedDirs 作为提示
53468
- # 2. 直接 exec shell(去沙箱后无目录级限制;exposedDirs 仅 echo 给对端参考)
53533
+ # 1. 读 ~/.clawd/contacts.json 校验 contact.sshAllowed(不通过即拒)
53534
+ # 2. 直接 exec shell(去沙箱后无目录级限制)
53469
53535
 
53470
53536
  set -euo pipefail
53471
53537
 
@@ -53492,13 +53558,15 @@ log_line INFO jail.entered "clawd-ssh-jail 起来 device=${"${DEVICE_ID}"} cmd=$
53492
53558
 
53493
53559
  CONTACTS="${"${HOME}"}/.clawd/contacts.json"
53494
53560
  if [ ! -f "$CONTACTS" ]; then
53495
- log_line ERROR jail.entered "contacts.json 不存在,无法查 exposedDirs"
53561
+ log_line ERROR jail.entered "contacts.json 不存在,无法校验 sshAllowed"
53496
53562
  echo "clawd-ssh-jail: contacts.json missing" >&2
53497
53563
  exit 1
53498
53564
  fi
53499
53565
 
53500
- # contact 的 exposedDirs (mac/linux 都自带 python3)
53501
- EXPOSED_JSON=$(python3 -c "
53566
+ # 校验该 contact 的 sshAllowed(mac/linux 都自带 python3)。去沙箱后不再读目录白名单:
53567
+ # sshAllowed=false DENIED exit 2;deviceId 未命中 → NOT_FOUND exit 3;命中且允许 → exit 0。
53568
+ # set -e 下 python 非 0 退出即终止脚本(不进 exec),只有 exit 0 才继续 exec shell。
53569
+ python3 -c "
53502
53570
  import json, sys
53503
53571
  with open('$CONTACTS') as f:
53504
53572
  data = json.load(f)
@@ -53506,25 +53574,11 @@ for c in data.get('contacts', []):
53506
53574
  if c.get('deviceId') == '$DEVICE_ID':
53507
53575
  if not c.get('sshAllowed'):
53508
53576
  print('DENIED', file=sys.stderr); sys.exit(2)
53509
- for d in c.get('exposedDirs', []):
53510
- print(d)
53511
53577
  sys.exit(0)
53512
- sys.exit(3)
53513
- ")
53514
-
53515
- # exposedDirs 可能为空(sshAllowed=true 但未配目录)——去沙箱后不再因空目录拒绝,直接放行。
53516
- if [ -n "$EXPOSED_JSON" ]; then
53517
- log_line INFO jail.entered "exposedDirs 提示已加载,放行 exec shell"
53518
- # 告知 SSH client 建议目录(发到 stderr)。去沙箱后这些只是建议,不做目录级强制限制;
53519
- # exposedDirs 仅供对端参考,运行时读,A 改后下次拨号即感知。
53520
- echo "[clawd-ssh-jail] Suggested working directories for this contact:" >&2
53521
- while IFS= read -r d; do
53522
- echo " - $d" >&2
53523
- done <<< "$EXPOSED_JSON"
53524
- echo "[clawd-ssh-jail] Note: no directory-level restriction is enforced." >&2
53525
- else
53526
- log_line INFO jail.entered "sshAllowed=true 且未配 exposedDirs,直接放行 exec shell"
53527
- fi
53578
+ print('NOT_FOUND', file=sys.stderr); sys.exit(3)
53579
+ "
53580
+
53581
+ log_line INFO jail.entered "sshAllowed 校验通过,放行 exec shell"
53528
53582
 
53529
53583
  CMD="${"${SSH_ORIGINAL_COMMAND:-}"}"
53530
53584
  if [ -z "$CMD" ]; then
@@ -54920,10 +54974,12 @@ function buildSessionHandlers(deps) {
54920
54974
  ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
54921
54975
  const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
54922
54976
  const creatorDisplayName = ctx?.principal.displayName ?? "Owner";
54977
+ const creatorFeishuUnionId = ctx?.principal.feishuUnionId;
54923
54978
  const { response, broadcast } = manager.create(
54924
54979
  { ...args, tool },
54925
54980
  creatorPrincipalId,
54926
- creatorDisplayName
54981
+ creatorDisplayName,
54982
+ creatorFeishuUnionId
54927
54983
  );
54928
54984
  return { response: { type: "session:info", ...response }, broadcast };
54929
54985
  };
@@ -55995,8 +56051,7 @@ function buildContactSshHandlers(deps) {
55995
56051
  const { type: _t, requestId: _r, ...rest } = frame;
55996
56052
  const args = ContactSetSshAccessArgsSchema.parse(rest);
55997
56053
  const hit = deps.store.setSshAccess(args.deviceId, {
55998
- sshAllowed: args.sshAllowed,
55999
- exposedDirs: args.exposedDirs
56054
+ sshAllowed: args.sshAllowed
56000
56055
  });
56001
56056
  if (!hit) {
56002
56057
  sshLog.warn("authz.setSshAccess", "owner \u5C1D\u8BD5\u6539 SSH \u6388\u6743\u4F46 contact \u4E0D\u5728 store", {
@@ -56010,21 +56065,18 @@ function buildContactSshHandlers(deps) {
56010
56065
  rebuildAuthorizedKeys(deps.store, deps.sshdDir);
56011
56066
  sshLog.info("authz.setSshAccess", "owner \u6539\u4E86 SSH \u6388\u6743 \u2192 authorized_keys \u5DF2\u91CD\u5EFA", {
56012
56067
  peerDeviceId: args.deviceId,
56013
- sshAllowed: args.sshAllowed,
56014
- exposedDirs: args.exposedDirs
56068
+ sshAllowed: args.sshAllowed
56015
56069
  });
56016
56070
  deps.broadcast({
56017
56071
  type: "contact:ssh-access-updated",
56018
56072
  deviceId: args.deviceId,
56019
- sshAllowed: args.sshAllowed,
56020
- exposedDirs: args.exposedDirs
56073
+ sshAllowed: args.sshAllowed
56021
56074
  });
56022
56075
  return {
56023
56076
  response: {
56024
56077
  type: "contact:setSshAccess:ok",
56025
56078
  deviceId: args.deviceId,
56026
- sshAllowed: args.sshAllowed,
56027
- exposedDirs: args.exposedDirs
56079
+ sshAllowed: args.sshAllowed
56028
56080
  }
56029
56081
  };
56030
56082
  };
@@ -60227,6 +60279,8 @@ async function startDaemon(config) {
60227
60279
  // B 角色(跨设备):非空 → guest scope + creatorPrincipalId/creatorDisplayName = A。
60228
60280
  guestPrincipalId: args.guestPrincipalId,
60229
60281
  guestDisplayName: args.guestDisplayName,
60282
+ // T-31: 把 A 的飞书 union_id stamp 到 B SessionFile,供 cc prompt 身份卡 union_id 行
60283
+ guestFeishuUnionId: args.guestFeishuUnionId,
60230
60284
  // T-20: 可选 model override 透到 SessionFile.model
60231
60285
  ...args.model ? { model: args.model } : {}
60232
60286
  });
@@ -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 跨设备聚合。
@@ -40747,8 +40755,10 @@ var ContactSchema = external_exports.object({
40747
40755
  */
40748
40756
  sshAllowed: external_exports.boolean().default(false),
40749
40757
  /**
40750
- * 授权访问的目录白名单(绝对路径)。空数组 + sshAllowed=true 意味 SSH 通道能拨到但无处可读,
40751
- * jail 会拒绝 shell 启动。由 clawd-ssh-jail 运行时读,生成 sbpl / bwrap policy 白名单。
40758
+ * @deprecated T-29 去沙箱后废弃。原为授权访问的目录白名单(clawd-ssh-jail 生成 sbpl/bwrap
40759
+ * policy 用);去掉 OS 沙箱后不再有目录级限制,SSH 授权即 on/off(见 sshAllowed)。
40760
+ * 字段仅为兼容老 contacts.json 保留(default [] 保证 strict parse 不炸),不再有代码读写它,
40761
+ * 也不再进 setSshAccess RPC / ContactSettingsDrawer / jail。勿新增消费方。
40752
40762
  */
40753
40763
  exposedDirs: external_exports.array(external_exports.string()).default([])
40754
40764
  }).strict();
@@ -40790,14 +40800,12 @@ var ContactPinnedFrameSchema = external_exports.object({
40790
40800
  // ../protocol/src/contact-ssh.ts
40791
40801
  var ContactSetSshAccessArgsSchema = external_exports.object({
40792
40802
  deviceId: external_exports.string().min(1),
40793
- sshAllowed: external_exports.boolean(),
40794
- exposedDirs: external_exports.array(external_exports.string())
40803
+ sshAllowed: external_exports.boolean()
40795
40804
  }).strict();
40796
40805
  var ContactSetSshAccessOkSchema = external_exports.object({
40797
40806
  type: external_exports.literal("contact:setSshAccess:ok"),
40798
40807
  deviceId: external_exports.string().min(1),
40799
- sshAllowed: external_exports.boolean(),
40800
- exposedDirs: external_exports.array(external_exports.string())
40808
+ sshAllowed: external_exports.boolean()
40801
40809
  }).strict();
40802
40810
  var ContactSshKeyIssueArgsSchema = external_exports.object({
40803
40811
  deviceId: external_exports.string().min(1)
@@ -40810,8 +40818,7 @@ var ContactSshKeyIssueOkSchema = external_exports.object({
40810
40818
  var ContactSshAccessUpdatedFrameSchema = external_exports.object({
40811
40819
  type: external_exports.literal("contact:ssh-access-updated"),
40812
40820
  deviceId: external_exports.string().min(1),
40813
- sshAllowed: external_exports.boolean(),
40814
- exposedDirs: external_exports.array(external_exports.string())
40821
+ sshAllowed: external_exports.boolean()
40815
40822
  }).strict();
40816
40823
 
40817
40824
  // ../protocol/src/extension.ts