@clawos-dev/clawd 0.2.220 → 0.2.221-beta.432.4424967

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
@@ -45,6 +45,9 @@ var init_methods = __esm({
45
45
  "session:update",
46
46
  "session:delete",
47
47
  "session:send",
48
+ // per-turn sender 2026-07-08 spec §4.6:MCP servers (ticket / inbox ...) 走 HTTP RPC
49
+ // /api/rpc/session.currentTurnSender 拉当前 turn caller;WS handler 同名(loopback path 复用)
50
+ "session:currentTurnSender",
48
51
  "session:stop",
49
52
  // 温柔中断当前轮次,不杀进程(走 CC IPC control_request.subtype=interrupt)
50
53
  "session:interrupt",
@@ -4520,6 +4523,59 @@ var init_attachment_schemas = __esm({
4520
4523
  }
4521
4524
  });
4522
4525
 
4526
+ // ../protocol/src/principal.ts
4527
+ function makeOwnerPrincipal(id, displayName, feishuUnionId) {
4528
+ return PrincipalSchema.parse({
4529
+ id,
4530
+ kind: "owner",
4531
+ displayName,
4532
+ ...feishuUnionId ? { feishuUnionId } : {}
4533
+ });
4534
+ }
4535
+ function principalToSender(p2) {
4536
+ return {
4537
+ did: p2.id,
4538
+ displayName: p2.displayName,
4539
+ principal: p2.kind,
4540
+ ...p2.feishuUnionId ? { feishuUnionId: p2.feishuUnionId } : {}
4541
+ };
4542
+ }
4543
+ function makeGuestSender(did, displayName, feishuUnionId) {
4544
+ return SenderInfoSchema.parse({
4545
+ did,
4546
+ displayName,
4547
+ principal: "guest",
4548
+ ...feishuUnionId ? { feishuUnionId } : {}
4549
+ });
4550
+ }
4551
+ var PrincipalKindSchema, PrincipalSchema, SenderInfoSchema;
4552
+ var init_principal = __esm({
4553
+ "../protocol/src/principal.ts"() {
4554
+ "use strict";
4555
+ init_zod();
4556
+ PrincipalKindSchema = external_exports.enum(["owner", "guest"]);
4557
+ PrincipalSchema = external_exports.object({
4558
+ id: external_exports.string().min(1),
4559
+ kind: PrincipalKindSchema,
4560
+ displayName: external_exports.string(),
4561
+ /**
4562
+ * 飞书 union_id。owner: 本机 daemon 已飞书登录时透传自 owner-identity.json;未登录 undefined。
4563
+ * guest: v1 保持 undefined(connect token 当前不携带 unionId;未来若签名 payload 加字段可透传)。
4564
+ * 消费方(如 ticket MCP / 未来别的飞书身份挂钩场景)按 undefined 决定是否降级;本层不做 fallback。
4565
+ */
4566
+ feishuUnionId: external_exports.string().optional()
4567
+ }).strict();
4568
+ SenderInfoSchema = external_exports.object({
4569
+ /** = principal.id = deviceId(决策 #16)。稳定路由 id */
4570
+ did: external_exports.string().min(1),
4571
+ displayName: external_exports.string(),
4572
+ /** owner 未登录 / guest 无飞书身份时缺省。消费方各自决定是否降级 */
4573
+ feishuUnionId: external_exports.string().optional(),
4574
+ principal: PrincipalKindSchema
4575
+ }).strict();
4576
+ }
4577
+ });
4578
+
4523
4579
  // ../protocol/src/persona-schemas.ts
4524
4580
  var PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, CodexSandboxSettingsSchema;
4525
4581
  var init_persona_schemas = __esm({
@@ -4628,6 +4684,7 @@ var init_schemas = __esm({
4628
4684
  init_zod();
4629
4685
  init_events();
4630
4686
  init_attachment_schemas();
4687
+ init_principal();
4631
4688
  init_persona_schemas();
4632
4689
  SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
4633
4690
  UsageSchema = external_exports.object({
@@ -4894,13 +4951,18 @@ var init_schemas = __esm({
4894
4951
  // 真实用户输入:daemon 通过 --replay-user-messages 拿到 stdin 回显,区分于 assistant text。
4895
4952
  // meta 非空表示是 CC 注入的系统消息(task-notification / slash-command / system-reminder 等),
4896
4953
  // UI 据此走低视觉权重渲染或子 agent 通知卡。
4954
+ //
4955
+ // sender(2026-07-08 spec §4.1):daemon 'send' 命令 synth 的 user_text 携带发起人 SenderInfo;
4956
+ // observer 回灌路径(cc jsonl)永远不带 —— cc 自己写盘不知道 daemon 层身份。
4957
+ // UI 端 sender 缺省 → 不渲染 displayName label;有 → 气泡下方渲染。
4897
4958
  external_exports.object({
4898
4959
  ...ParsedEventBase,
4899
4960
  kind: external_exports.literal("user_text"),
4900
4961
  text: external_exports.string(),
4901
4962
  meta: HistoryUserMetaSchema.optional(),
4902
4963
  taskId: external_exports.string().optional(),
4903
- parentToolUseId: external_exports.string().optional()
4964
+ parentToolUseId: external_exports.string().optional(),
4965
+ sender: SenderInfoSchema.optional()
4904
4966
  }),
4905
4967
  external_exports.object({
4906
4968
  ...ParsedEventBase,
@@ -5509,24 +5571,6 @@ var init_persona_mode = __esm({
5509
5571
  }
5510
5572
  });
5511
5573
 
5512
- // ../protocol/src/principal.ts
5513
- function makeOwnerPrincipal(id, displayName) {
5514
- return PrincipalSchema.parse({ id, kind: "owner", displayName });
5515
- }
5516
- var PrincipalKindSchema, PrincipalSchema;
5517
- var init_principal = __esm({
5518
- "../protocol/src/principal.ts"() {
5519
- "use strict";
5520
- init_zod();
5521
- PrincipalKindSchema = external_exports.enum(["owner", "guest"]);
5522
- PrincipalSchema = external_exports.object({
5523
- id: external_exports.string().min(1),
5524
- kind: PrincipalKindSchema,
5525
- displayName: external_exports.string()
5526
- }).strict();
5527
- }
5528
- });
5529
-
5530
5574
  // ../protocol/src/capability.ts
5531
5575
  function stripSecretHash(cap) {
5532
5576
  const { secretHash: _hash, ...wire } = cap;
@@ -41858,7 +41902,9 @@ function cloneState(s) {
41858
41902
  // ReadyGate v2:readyForSend 是基础类型直接复制;pendingSend 必须浅拷贝防止
41859
41903
  // reduce 内 push/shift 在调用方原 state 上副作用(reducer 纯函数契约)
41860
41904
  readyForSend: s.readyForSend,
41861
- pendingSend: s.pendingSend.slice()
41905
+ pendingSend: s.pendingSend.slice(),
41906
+ // per-turn sender (spec §4.3):SenderInfo 只在 case 'send' 覆盖,其它 kind 直接复用引用
41907
+ currentTurnSender: s.currentTurnSender
41862
41908
  };
41863
41909
  }
41864
41910
  var IDLE_KILL_DELAY_MS = 6e5;
@@ -42197,10 +42243,12 @@ function applyCommand(state, command, deps) {
42197
42243
  }
42198
42244
  });
42199
42245
  }
42246
+ next.currentTurnSender = command.senderPrincipal;
42200
42247
  const userEvent = {
42201
42248
  kind: "user_text",
42202
42249
  text: command.text,
42203
- uuid: deps.genUuid?.()
42250
+ uuid: deps.genUuid?.(),
42251
+ sender: command.senderPrincipal
42204
42252
  };
42205
42253
  const pushed = pushEventToBuffer(next, userEvent, deps);
42206
42254
  effects.push(...pushed.effects);
@@ -43267,7 +43315,9 @@ function makeInitialState(file, subSessionMeta) {
43267
43315
  // ReadyGate v2:默认值兼容 SDK happy path(直写 stdin,不走队列)。
43268
43316
  // TUI 模式由 reducer send 分支按 deps.mode='tui' 主动翻 readyForSend=false
43269
43317
  readyForSend: true,
43270
- pendingSend: []
43318
+ pendingSend: [],
43319
+ // per-turn sender 2026-07-08 spec §4.3:初始 null;case 'send' 首次覆盖
43320
+ currentTurnSender: null
43271
43321
  };
43272
43322
  }
43273
43323
  var SessionManager = class {
@@ -43940,7 +43990,7 @@ var SessionManager = class {
43940
43990
  ]
43941
43991
  };
43942
43992
  }
43943
- send(args) {
43993
+ send(args, senderPrincipal) {
43944
43994
  const existing = this.getFile(args.sessionId);
43945
43995
  let runner = this.runners.get(args.sessionId);
43946
43996
  if (!runner) {
@@ -43948,10 +43998,22 @@ var SessionManager = class {
43948
43998
  this.runners.set(args.sessionId, runner);
43949
43999
  }
43950
44000
  const { broadcast } = this.withCollector(() => {
43951
- runner.input({ kind: "command", command: { kind: "send", text: args.text } });
44001
+ runner.input({
44002
+ kind: "command",
44003
+ command: { kind: "send", text: args.text, senderPrincipal }
44004
+ });
43952
44005
  });
43953
44006
  return { response: { ok: true }, broadcast };
43954
44007
  }
44008
+ /**
44009
+ * 只读拿 SessionState;session 不存在返 null(不 throw)。
44010
+ * handlers/session.ts:currentTurnSender 用(loopback HTTP RPC 绝对不能因不存在的 sid 崩)。
44011
+ * 2026-07-08 spec §4.6。
44012
+ */
44013
+ tryGetState(sessionId) {
44014
+ const runner = this.runners.get(sessionId);
44015
+ return runner?.getState() ?? null;
44016
+ }
43955
44017
  // 等子进程真退出再 ack。reducer 推 SIGKILL effect(见 reducer.ts),内核立即回收子进程,
43956
44018
  // proc.on('exit') 几乎瞬时触发;waitUntilStopped 的 3s 超时只在子进程已是 zombie 或
43957
44019
  // 极端 OS 异常时才会用上。这样修掉了"前端 sessionStop 还没等 CC 退就去删 worktree
@@ -44351,7 +44413,10 @@ var SessionManager = class {
44351
44413
  }
44352
44414
  const runner = this.ensureRunnerForScope(file, args.scope);
44353
44415
  const { broadcast } = this.withCollector(() => {
44354
- runner.input({ kind: "command", command: { kind: "send", text: args.text } });
44416
+ runner.input({
44417
+ kind: "command",
44418
+ command: { kind: "send", text: args.text, senderPrincipal: args.senderPrincipal }
44419
+ });
44355
44420
  });
44356
44421
  return { response: { ok: true }, broadcast };
44357
44422
  }
@@ -44495,7 +44560,27 @@ var SessionManager = class {
44495
44560
  sourceJsonlPath: args.sourceJsonlPath,
44496
44561
  dispatchId: args.dispatchId
44497
44562
  });
44498
- runner.input({ kind: "command", command: { kind: "send", text: taskPack } });
44563
+ let dispatchSender;
44564
+ if (args.guestPrincipalId) {
44565
+ dispatchSender = makeGuestSender(args.guestPrincipalId, args.guestDisplayName ?? args.guestPrincipalId);
44566
+ } else {
44567
+ if (!this.deps.ownerPrincipalId) {
44568
+ throw new Error(
44569
+ "createDispatchedSession requires ManagerDeps.ownerPrincipalId to build owner sender"
44570
+ );
44571
+ }
44572
+ dispatchSender = principalToSender(
44573
+ makeOwnerPrincipal(
44574
+ this.deps.ownerPrincipalId,
44575
+ this.deps.ownerDisplayName ?? "Owner",
44576
+ this.deps.ownerFeishuUnionId
44577
+ )
44578
+ );
44579
+ }
44580
+ runner.input({
44581
+ kind: "command",
44582
+ command: { kind: "send", text: taskPack, senderPrincipal: dispatchSender }
44583
+ });
44499
44584
  this.deps.logger?.info("dispatch.createDispatchedSession.task-pack-sent", {
44500
44585
  dispatchId: args.dispatchId,
44501
44586
  bSessionId: sessionId
@@ -44570,6 +44655,18 @@ var SessionManager = class {
44570
44655
  );
44571
44656
  const runner = this.ensureRunnerForScope(file, scope);
44572
44657
  this.deps.logger?.info("shift.createShiftSession.runner-ready", { sessionId });
44658
+ if (!this.deps.ownerPrincipalId) {
44659
+ throw new Error(
44660
+ "createShiftSession requires ManagerDeps.ownerPrincipalId to build owner sender"
44661
+ );
44662
+ }
44663
+ const shiftSender = principalToSender(
44664
+ makeOwnerPrincipal(
44665
+ this.deps.ownerPrincipalId,
44666
+ this.deps.ownerDisplayName ?? "Owner",
44667
+ this.deps.ownerFeishuUnionId
44668
+ )
44669
+ );
44573
44670
  runner.input({
44574
44671
  kind: "command",
44575
44672
  command: {
@@ -44577,7 +44674,8 @@ var SessionManager = class {
44577
44674
  text: wrapShiftFirePrompt({
44578
44675
  prompt: args.prompt,
44579
44676
  targetPersona: args.targetPersona
44580
- })
44677
+ }),
44678
+ senderPrincipal: shiftSender
44581
44679
  }
44582
44680
  });
44583
44681
  return { sessionId, ended, kill: killImpl };
@@ -48090,7 +48188,7 @@ function ticketMcpConfigPath(dataDir) {
48090
48188
  function writeTicketMcpConfig(args) {
48091
48189
  const cfgPath = ticketMcpConfigPath(args.dataDir);
48092
48190
  const content = {
48093
- _comment: "daemon \u542F\u52A8\u65F6\u81EA\u52A8\u751F\u6210\uFF1Bcc spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002ticket-mcp-server.cjs \u662F daemon \u81EA\u5E26\u7684 stdio MCP server\uFF0CHTTP client \u8C03 clawos api /api/tickets/*\u3002env \u542B api url\u3001\u5199\u6B7B API key\u3001caller did/name/scene\uFF1BCLAWD_TICKET_LOG \u6307 server \u5199\u65E5\u5FD7\u7684 append \u6587\u4EF6\u8DEF\u5F84\u3002cc \u573A\u666F scene \u56FA\u5B9A persona\u3002",
48191
+ _comment: "daemon \u542F\u52A8\u65F6\u81EA\u52A8\u751F\u6210\uFF1Bcc spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002ticket-mcp-server.cjs \u662F daemon \u81EA\u5E26\u7684 stdio MCP server\uFF0CHTTP client \u8C03 clawos api /api/tickets/*\u3002env \u542B api url\u3001\u5199\u6B7B API key\u3001scene\uFF1Bdid/name \u6BCF\u6B21 tool call \u8D70 CLAWD_DAEMON_URL + CLAWD_SESSION_ID (cc \u5DF2\u6CE8) fetch /api/rpc/session.currentTurnSender \u62FF\u5F53\u524D turn caller (2026-07-08 spec \xA74.7)\u3002CLAWD_TICKET_LOG \u6307 server \u5199\u65E5\u5FD7\u7684 append \u6587\u4EF6\u8DEF\u5F84\u3002cc \u573A\u666F scene \u56FA\u5B9A persona\u3002",
48094
48192
  mcpServers: {
48095
48193
  "clawd-ticket": {
48096
48194
  type: "stdio",
@@ -48099,8 +48197,6 @@ function writeTicketMcpConfig(args) {
48099
48197
  env: {
48100
48198
  CLAWD_TICKET_API_URL: args.apiBaseUrl,
48101
48199
  CLAWD_TICKET_API_KEY: args.apiKey,
48102
- CLAWD_TICKET_DID: args.did,
48103
- CLAWD_TICKET_NAME: args.name,
48104
48200
  CLAWD_TICKET_SCENE: "persona",
48105
48201
  ...args.logPath ? { CLAWD_TICKET_LOG: args.logPath } : {}
48106
48202
  }
@@ -50211,7 +50307,12 @@ var EXPOSED_RPC_METHODS = [
50211
50307
  "attachment.groupAdd",
50212
50308
  "attachment.signUrl",
50213
50309
  // persona 受控发 P2P IM DM(sender 由 session scope 推,driver 分级授权见 handlers/inbox.ts)
50214
- "inbox.sendDm"
50310
+ "inbox.sendDm",
50311
+ // per-turn sender 2026-07-08 spec §4.1:MCP servers (ticket / inbox / ...) 每 tool call
50312
+ // 查当前 turn caller —— cc 长驻 + owner/guest 交替驱动同一 session 时不能靠启动时 env
50313
+ // 写死身份,必须运行时 fetch。header X-Clawd-Session-Id 作 session 定位,handler 内查
50314
+ // manager.tryGetState(sid).currentTurnSender 返 SenderInfo | null。
50315
+ "session.currentTurnSender"
50215
50316
  ];
50216
50317
  async function handleRpcRequest(input) {
50217
50318
  const { method, body, sessionId, dispatcher } = input;
@@ -50396,9 +50497,9 @@ function constantTimeEqual2(a, b2) {
50396
50497
  }
50397
50498
 
50398
50499
  // src/transport/connection-context.ts
50399
- function ownerContext(ownerPrincipalId, displayName) {
50500
+ function ownerContext(ownerPrincipalId, displayName, feishuUnionId) {
50400
50501
  return {
50401
- principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
50502
+ principal: makeOwnerPrincipal(ownerPrincipalId, displayName, feishuUnionId),
50402
50503
  grants: [{ resource: { type: "*" }, actions: ["admin"] }]
50403
50504
  };
50404
50505
  }
@@ -50427,7 +50528,10 @@ function visitorGuestContext(p2) {
50427
50528
  async function authenticate(token, deps) {
50428
50529
  if (!token) return { ok: false, code: "NO_TOKEN" };
50429
50530
  if (deps.isOwnerToken(token)) {
50430
- return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
50531
+ return {
50532
+ ok: true,
50533
+ context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName, deps.ownerFeishuUnionId)
50534
+ };
50431
50535
  }
50432
50536
  if (deps.verifyVisitorToken) {
50433
50537
  const vr = deps.verifyVisitorToken(token);
@@ -54720,9 +54824,25 @@ function buildSessionHandlers(deps) {
54720
54824
  const send = async (frame, _client, ctx) => {
54721
54825
  const args = SessionSendArgs.parse(frame);
54722
54826
  ensureSessionAccess(ctx, args.sessionId, "send");
54723
- const { broadcast } = manager.send(args);
54827
+ const sender = ctx ? principalToSender(ctx.principal) : principalToSender(
54828
+ makeOwnerPrincipal(ownerPrincipalId, deps.ownerDisplayName, deps.ownerFeishuUnionId)
54829
+ );
54830
+ const { broadcast } = manager.send(args, sender);
54724
54831
  return { response: { type: "session:send", ok: true }, broadcast };
54725
54832
  };
54833
+ const currentTurnSender = async (frame, _client, ctx) => {
54834
+ const sid = typeof frame === "object" && frame != null && "sessionId" in frame ? String(frame.sessionId) : "";
54835
+ if (!sid) {
54836
+ return { response: { type: "session:currentTurnSender", sender: null } };
54837
+ }
54838
+ if (ctx && "principal" in ctx) {
54839
+ ensureSessionAccess(ctx, sid, "read");
54840
+ }
54841
+ const state = manager.tryGetState(sid);
54842
+ return {
54843
+ response: { type: "session:currentTurnSender", sender: state?.currentTurnSender ?? null }
54844
+ };
54845
+ };
54726
54846
  const stop = async (frame, _client, ctx) => {
54727
54847
  const args = SessionIdArgs.parse(frame);
54728
54848
  ensureSessionAccess(ctx, args.sessionId, "send");
@@ -54925,6 +55045,7 @@ function buildSessionHandlers(deps) {
54925
55045
  "session:update": update,
54926
55046
  "session:delete": del,
54927
55047
  "session:send": send,
55048
+ "session:currentTurnSender": currentTurnSender,
54928
55049
  "session:stop": stop,
54929
55050
  "session:interrupt": interrupt,
54930
55051
  "session:rewind": rewind,
@@ -57993,7 +58114,14 @@ function buildAppBuilderHandlers(deps) {
57993
58114
  broadcastProjectUpdated(f);
57994
58115
  },
57995
58116
  injectUserText: async (sessionId, text) => {
57996
- const { broadcast } = sessionManager.send({ sessionId, text });
58117
+ const ownerSender = principalToSender(
58118
+ makeOwnerPrincipal(
58119
+ deps.ownerPrincipalId,
58120
+ deps.ownerDisplayName ?? "Owner",
58121
+ deps.ownerFeishuUnionId
58122
+ )
58123
+ );
58124
+ const { broadcast } = sessionManager.send({ sessionId, text }, ownerSender);
57997
58125
  for (const entry of broadcast) {
57998
58126
  deps.broadcastSessionFrame(entry.frame, entry.target);
57999
58127
  }
@@ -58292,6 +58420,9 @@ function buildMethodHandlers(deps) {
58292
58420
  usersRoot: deps.usersRoot,
58293
58421
  devServerLifecycle: deps.devServerLifecycle,
58294
58422
  sessionManager: deps.manager,
58423
+ ownerPrincipalId: deps.ownerPrincipalId,
58424
+ ownerDisplayName: deps.ownerDisplayName,
58425
+ ownerFeishuUnionId: deps.ownerFeishuUnionId,
58295
58426
  getTunnelUrl: deps.getTunnelUrl,
58296
58427
  broadcastProjectUpdated: deps.broadcastProjectUpdated,
58297
58428
  logger: deps.logger,
@@ -58620,6 +58751,10 @@ var METHOD_GRANT_MAP = {
58620
58751
  "session:update": CAPABILITY_SCOPED,
58621
58752
  "session:delete": CAPABILITY_SCOPED,
58622
58753
  "session:send": CAPABILITY_SCOPED,
58754
+ // per-turn sender 2026-07-08 spec §4.6:dispatcher 层走 'public'(HTTP RPC path 需 dispatcher 放行);
58755
+ // WS path 的隐私边界(防 guest 猜 sid 枚举 owner feishuUnionId)由 handler 内 ensureSessionAccess
58756
+ // 显式兜底——见 handlers/session.ts:currentTurnSender。dispatcher 'public' ≠ handler 免检。
58757
+ "session:currentTurnSender": { kind: "public" },
58623
58758
  "session:stop": CAPABILITY_SCOPED,
58624
58759
  "session:interrupt": CAPABILITY_SCOPED,
58625
58760
  "session:rewind": CAPABILITY_SCOPED,
@@ -59192,6 +59327,7 @@ async function startDaemon(config) {
59192
59327
  let ownerId = feishuIdentity?.identity.ownerId ?? "";
59193
59328
  let ownerProvider = feishuIdentity?.identity.provider ?? "";
59194
59329
  let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
59330
+ let ownerFeishuUnionId = feishuIdentity?.identity.unionId;
59195
59331
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
59196
59332
  const inboxStore = new InboxStore(config.dataDir);
59197
59333
  const inboxManager = new InboxManager(inboxStore, (_peerDeviceId, frame) => {
@@ -59204,6 +59340,7 @@ async function startDaemon(config) {
59204
59340
  isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
59205
59341
  ownerPrincipalId,
59206
59342
  ownerDisplayName,
59343
+ ownerFeishuUnionId,
59207
59344
  // persona web 分享:访客 visitor token = daemon 自签 HMAC(无 aud),独立于飞书 connect token
59208
59345
  // 基建——secret 恒存在(auth-store backfill),故无条件注入。验签 ok → guest ctx(persona wildcard)。
59209
59346
  verifyVisitorToken: (t) => {
@@ -59287,7 +59424,7 @@ async function startDaemon(config) {
59287
59424
  return result;
59288
59425
  },
59289
59426
  onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
59290
- buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
59427
+ buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId),
59291
59428
  closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
59292
59429
  sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
59293
59430
  }) : null;
@@ -59359,8 +59496,6 @@ async function startDaemon(config) {
59359
59496
  import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59360
59497
  ];
59361
59498
  const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59362
- const ticketCallerDid = authFile.deviceId;
59363
- const ticketCallerName = ownerDisplayName;
59364
59499
  let ticketMcpConfigPath2;
59365
59500
  if (ticketServerScriptPath) {
59366
59501
  const ticketLogPath = import_node_path64.default.join(config.dataDir, "ticket-mcp-server.log");
@@ -59369,8 +59504,6 @@ async function startDaemon(config) {
59369
59504
  serverScriptPath: ticketServerScriptPath,
59370
59505
  apiBaseUrl: config.clawosApi ?? DEFAULT_CLAWOS_API,
59371
59506
  apiKey: resolveClawdTicketsApiKey(),
59372
- did: ticketCallerDid,
59373
- name: ticketCallerName,
59374
59507
  logPath: ticketLogPath
59375
59508
  });
59376
59509
  logger.info("ticket.mcp.json written", {
@@ -59459,6 +59592,7 @@ async function startDaemon(config) {
59459
59592
  personaStore,
59460
59593
  ownerDisplayName,
59461
59594
  ownerPrincipalId,
59595
+ ownerFeishuUnionId,
59462
59596
  contactStore,
59463
59597
  mode: config.mode,
59464
59598
  // 单栏 refactor (spec 2026-06-02 §5.1):cc 子进程 env CLAWD_DAEMON_URL 闭包来源。
@@ -59721,6 +59855,8 @@ async function startDaemon(config) {
59721
59855
  ownerDisplayName,
59722
59856
  // 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
59723
59857
  ownerPrincipalId,
59858
+ // per-turn sender §4.6:send handler 无 ctx 兜底 + AppBuilder publish injectUserText 需要
59859
+ ownerFeishuUnionId,
59724
59860
  ownerId,
59725
59861
  ownerProvider,
59726
59862
  personaStore,
@@ -60004,6 +60140,7 @@ async function startDaemon(config) {
60004
60140
  ownerId = result.identity.ownerId;
60005
60141
  ownerProvider = result.identity.provider;
60006
60142
  ownerDisplayName = result.identity.displayName;
60143
+ ownerFeishuUnionId = result.identity.unionId;
60007
60144
  handlers = makeHandlers();
60008
60145
  wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
60009
60146
  setImmediate(() => wsServer?.closeAllClients());
@@ -60142,7 +60279,7 @@ async function startDaemon(config) {
60142
60279
  wss.onFrame(async (client, frame) => {
60143
60280
  const type = frame.type;
60144
60281
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
60145
- const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
60282
+ const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId);
60146
60283
  const result = await dispatchRpc(type, frame, client, ctx, {
60147
60284
  handlers,
60148
60285
  feishuActive: () => feishuActive