@clawos-dev/clawd 0.2.221-beta.432.4424967 → 0.2.221-beta.434.45d5a69

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,9 +45,6 @@ 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",
51
48
  "session:stop",
52
49
  // 温柔中断当前轮次,不杀进程(走 CC IPC control_request.subtype=interrupt)
53
50
  "session:interrupt",
@@ -4523,59 +4520,6 @@ var init_attachment_schemas = __esm({
4523
4520
  }
4524
4521
  });
4525
4522
 
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
-
4579
4523
  // ../protocol/src/persona-schemas.ts
4580
4524
  var PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, CodexSandboxSettingsSchema;
4581
4525
  var init_persona_schemas = __esm({
@@ -4684,7 +4628,6 @@ var init_schemas = __esm({
4684
4628
  init_zod();
4685
4629
  init_events();
4686
4630
  init_attachment_schemas();
4687
- init_principal();
4688
4631
  init_persona_schemas();
4689
4632
  SessionStatusSchema = external_exports.enum(SESSION_STATUS_VALUES);
4690
4633
  UsageSchema = external_exports.object({
@@ -4951,18 +4894,13 @@ var init_schemas = __esm({
4951
4894
  // 真实用户输入:daemon 通过 --replay-user-messages 拿到 stdin 回显,区分于 assistant text。
4952
4895
  // meta 非空表示是 CC 注入的系统消息(task-notification / slash-command / system-reminder 等),
4953
4896
  // 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;有 → 气泡下方渲染。
4958
4897
  external_exports.object({
4959
4898
  ...ParsedEventBase,
4960
4899
  kind: external_exports.literal("user_text"),
4961
4900
  text: external_exports.string(),
4962
4901
  meta: HistoryUserMetaSchema.optional(),
4963
4902
  taskId: external_exports.string().optional(),
4964
- parentToolUseId: external_exports.string().optional(),
4965
- sender: SenderInfoSchema.optional()
4903
+ parentToolUseId: external_exports.string().optional()
4966
4904
  }),
4967
4905
  external_exports.object({
4968
4906
  ...ParsedEventBase,
@@ -5571,6 +5509,24 @@ var init_persona_mode = __esm({
5571
5509
  }
5572
5510
  });
5573
5511
 
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
+
5574
5530
  // ../protocol/src/capability.ts
5575
5531
  function stripSecretHash(cap) {
5576
5532
  const { secretHash: _hash, ...wire } = cap;
@@ -41695,47 +41651,6 @@ function escapeAttr(v2) {
41695
41651
  return v2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
41696
41652
  }
41697
41653
 
41698
- // src/shift/end-frame-handler.ts
41699
- function makeEndFrameHandler(sessionId, onEnd) {
41700
- return (frame) => {
41701
- if (!frame || typeof frame !== "object") return;
41702
- const f = frame;
41703
- if (f.type !== "session:status") return;
41704
- if (f.sessionId !== sessionId) return;
41705
- if (f.status === "running-idle") {
41706
- onEnd({ kind: "idle" });
41707
- return;
41708
- }
41709
- if (f.status === "stopped") {
41710
- onEnd({ kind: "stopped" });
41711
- return;
41712
- }
41713
- if (f.status === "error") {
41714
- onEnd({ kind: "error", message: f.message ?? "cc proc error" });
41715
- return;
41716
- }
41717
- };
41718
- }
41719
-
41720
- // src/shift/end-dispatcher.ts
41721
- function dispatchShiftEnd(info, settle, killImpl) {
41722
- if (info.kind === "idle") {
41723
- settle({ ok: true });
41724
- killImpl();
41725
- return;
41726
- }
41727
- if (info.kind === "stopped") {
41728
- settle({ error: "cc exited before turn completed" });
41729
- return;
41730
- }
41731
- if (info.kind === "error") {
41732
- settle({ error: info.message });
41733
- return;
41734
- }
41735
- const _exhaustive = info;
41736
- throw new Error(`dispatchShiftEnd: unhandled kind ${JSON.stringify(_exhaustive)}`);
41737
- }
41738
-
41739
41654
  // src/session/runner.ts
41740
41655
  var import_node_os4 = __toESM(require("os"), 1);
41741
41656
  var import_node_path7 = __toESM(require("path"), 1);
@@ -41902,9 +41817,7 @@ function cloneState(s) {
41902
41817
  // ReadyGate v2:readyForSend 是基础类型直接复制;pendingSend 必须浅拷贝防止
41903
41818
  // reduce 内 push/shift 在调用方原 state 上副作用(reducer 纯函数契约)
41904
41819
  readyForSend: s.readyForSend,
41905
- pendingSend: s.pendingSend.slice(),
41906
- // per-turn sender (spec §4.3):SenderInfo 只在 case 'send' 覆盖,其它 kind 直接复用引用
41907
- currentTurnSender: s.currentTurnSender
41820
+ pendingSend: s.pendingSend.slice()
41908
41821
  };
41909
41822
  }
41910
41823
  var IDLE_KILL_DELAY_MS = 6e5;
@@ -41969,7 +41882,8 @@ function buildSpawnContext(state, deps) {
41969
41882
  const personaId = file.ownerPersonaId;
41970
41883
  if (personaId) env.CLAWD_PERSONA_ID = personaId;
41971
41884
  const dispatchMcpConfigPath2 = deps.getDispatchMcpConfigPath?.() ?? null;
41972
- const ticketMcpConfigPath2 = deps.getTicketMcpConfigPath?.() ?? null;
41885
+ const ticketMcpConfigPathRaw = deps.getTicketMcpConfigPath?.() ?? null;
41886
+ const ticketMcpConfigPath2 = ticketMcpConfigPathRaw && file.ownerPersonaId === "persona-ticket-manager" ? ticketMcpConfigPathRaw : null;
41973
41887
  const shiftMcpConfigPath2 = deps.getShiftMcpConfigPath?.() ?? null;
41974
41888
  const inboxMcpConfigPath2 = deps.getInboxMcpConfigPath?.() ?? null;
41975
41889
  const ctx = {
@@ -42243,12 +42157,10 @@ function applyCommand(state, command, deps) {
42243
42157
  }
42244
42158
  });
42245
42159
  }
42246
- next.currentTurnSender = command.senderPrincipal;
42247
42160
  const userEvent = {
42248
42161
  kind: "user_text",
42249
42162
  text: command.text,
42250
- uuid: deps.genUuid?.(),
42251
- sender: command.senderPrincipal
42163
+ uuid: deps.genUuid?.()
42252
42164
  };
42253
42165
  const pushed = pushEventToBuffer(next, userEvent, deps);
42254
42166
  effects.push(...pushed.effects);
@@ -42796,7 +42708,7 @@ var SessionRunner = class {
42796
42708
  getDispatchMcpConfigPath: this.hooks.getDispatchMcpConfigPath,
42797
42709
  getShiftMcpConfigPath: this.hooks.getShiftMcpConfigPath,
42798
42710
  getInboxMcpConfigPath: this.hooks.getInboxMcpConfigPath,
42799
- // Ticket MCP:透传 ticket.mcp.json 路径闭包(所有 persona session 都挂,无 gating)
42711
+ // Ticket MCP:透传 ticket.mcp.json 路径闭包(reducer 内做 persona-ticket-manager gating)
42800
42712
  getTicketMcpConfigPath: this.hooks.getTicketMcpConfigPath,
42801
42713
  lookupDispatchByBSessionId: this.hooks.lookupDispatchByBSessionId,
42802
42714
  // ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
@@ -43315,9 +43227,7 @@ function makeInitialState(file, subSessionMeta) {
43315
43227
  // ReadyGate v2:默认值兼容 SDK happy path(直写 stdin,不走队列)。
43316
43228
  // TUI 模式由 reducer send 分支按 deps.mode='tui' 主动翻 readyForSend=false
43317
43229
  readyForSend: true,
43318
- pendingSend: [],
43319
- // per-turn sender 2026-07-08 spec §4.3:初始 null;case 'send' 首次覆盖
43320
- currentTurnSender: null
43230
+ pendingSend: []
43321
43231
  };
43322
43232
  }
43323
43233
  var SessionManager = class {
@@ -43990,7 +43900,7 @@ var SessionManager = class {
43990
43900
  ]
43991
43901
  };
43992
43902
  }
43993
- send(args, senderPrincipal) {
43903
+ send(args) {
43994
43904
  const existing = this.getFile(args.sessionId);
43995
43905
  let runner = this.runners.get(args.sessionId);
43996
43906
  if (!runner) {
@@ -43998,22 +43908,10 @@ var SessionManager = class {
43998
43908
  this.runners.set(args.sessionId, runner);
43999
43909
  }
44000
43910
  const { broadcast } = this.withCollector(() => {
44001
- runner.input({
44002
- kind: "command",
44003
- command: { kind: "send", text: args.text, senderPrincipal }
44004
- });
43911
+ runner.input({ kind: "command", command: { kind: "send", text: args.text } });
44005
43912
  });
44006
43913
  return { response: { ok: true }, broadcast };
44007
43914
  }
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
- }
44017
43915
  // 等子进程真退出再 ack。reducer 推 SIGKILL effect(见 reducer.ts),内核立即回收子进程,
44018
43916
  // proc.on('exit') 几乎瞬时触发;waitUntilStopped 的 3s 超时只在子进程已是 zombie 或
44019
43917
  // 极端 OS 异常时才会用上。这样修掉了"前端 sessionStop 还没等 CC 退就去删 worktree
@@ -44413,10 +44311,7 @@ var SessionManager = class {
44413
44311
  }
44414
44312
  const runner = this.ensureRunnerForScope(file, args.scope);
44415
44313
  const { broadcast } = this.withCollector(() => {
44416
- runner.input({
44417
- kind: "command",
44418
- command: { kind: "send", text: args.text, senderPrincipal: args.senderPrincipal }
44419
- });
44314
+ runner.input({ kind: "command", command: { kind: "send", text: args.text } });
44420
44315
  });
44421
44316
  return { response: { ok: true }, broadcast };
44422
44317
  }
@@ -44560,27 +44455,7 @@ var SessionManager = class {
44560
44455
  sourceJsonlPath: args.sourceJsonlPath,
44561
44456
  dispatchId: args.dispatchId
44562
44457
  });
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
- });
44458
+ runner.input({ kind: "command", command: { kind: "send", text: taskPack } });
44584
44459
  this.deps.logger?.info("dispatch.createDispatchedSession.task-pack-sent", {
44585
44460
  dispatchId: args.dispatchId,
44586
44461
  bSessionId: sessionId
@@ -44595,9 +44470,8 @@ var SessionManager = class {
44595
44470
  * 跟 createDispatchedSession 的差异:
44596
44471
  * - 不带 dispatchId / source / task pack;prompt 直接当第一条 user message
44597
44472
  * - 不调 personaDispatchManager.registerBSession
44598
- * - 多返一个 ended Promise,监听 session:status 帧翻译成 shift 业务终态 union
44599
- * (ShiftSessionEnded:{ok:true} | {error:string}),不经 exit code 中转
44600
- * - turn-idle 到达即视为业务完成 → settle {ok:true} + fire-and-forget kill cc
44473
+ * - 多返一个 ended Promise,监听 session:status (stopped/error) 帧通过现有
44474
+ * subscribe() 入口(白名单已含 session:status)
44601
44475
  * - kill 通过 runner.input({command:{kind:'stop'}}) 触发 SIGKILL → proc-exit
44602
44476
  */
44603
44477
  createShiftSession(args) {
@@ -44641,32 +44515,18 @@ var SessionManager = class {
44641
44515
  unsubscribe();
44642
44516
  resolveEnded(info);
44643
44517
  };
44644
- const killImpl = () => {
44645
- const r = this.runners.get(sessionId);
44646
- if (!r) return;
44647
- r.input({ kind: "command", command: { kind: "stop" } });
44648
- };
44649
- unsubscribe = this.subscribe(
44650
- sessionId,
44651
- scope,
44652
- makeEndFrameHandler(sessionId, (info) => {
44653
- dispatchShiftEnd(info, settle, killImpl);
44654
- })
44655
- );
44518
+ unsubscribe = this.subscribe(sessionId, scope, (frame) => {
44519
+ if (frame.type !== "session:status") return;
44520
+ const f = frame;
44521
+ if (f.sessionId !== sessionId) return;
44522
+ if (f.status === "stopped") {
44523
+ settle({ exitCode: f.exitCode ?? null });
44524
+ } else if (f.status === "error") {
44525
+ settle({ exitCode: null, error: f.message ?? "cc proc error" });
44526
+ }
44527
+ });
44656
44528
  const runner = this.ensureRunnerForScope(file, scope);
44657
44529
  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
- );
44670
44530
  runner.input({
44671
44531
  kind: "command",
44672
44532
  command: {
@@ -44674,11 +44534,15 @@ var SessionManager = class {
44674
44534
  text: wrapShiftFirePrompt({
44675
44535
  prompt: args.prompt,
44676
44536
  targetPersona: args.targetPersona
44677
- }),
44678
- senderPrincipal: shiftSender
44537
+ })
44679
44538
  }
44680
44539
  });
44681
- return { sessionId, ended, kill: killImpl };
44540
+ const kill = () => {
44541
+ const r = this.runners.get(sessionId);
44542
+ if (!r) return;
44543
+ r.input({ kind: "command", command: { kind: "stop" } });
44544
+ };
44545
+ return { sessionId, ended, kill };
44682
44546
  }
44683
44547
  /**
44684
44548
  * 老板插话:把 'inject-owner-text' input 喂给对应 runner,
@@ -48188,7 +48052,7 @@ function ticketMcpConfigPath(dataDir) {
48188
48052
  function writeTicketMcpConfig(args) {
48189
48053
  const cfgPath = ticketMcpConfigPath(args.dataDir);
48190
48054
  const content = {
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",
48055
+ _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",
48192
48056
  mcpServers: {
48193
48057
  "clawd-ticket": {
48194
48058
  type: "stdio",
@@ -48197,6 +48061,8 @@ function writeTicketMcpConfig(args) {
48197
48061
  env: {
48198
48062
  CLAWD_TICKET_API_URL: args.apiBaseUrl,
48199
48063
  CLAWD_TICKET_API_KEY: args.apiKey,
48064
+ CLAWD_TICKET_DID: args.did,
48065
+ CLAWD_TICKET_NAME: args.name,
48200
48066
  CLAWD_TICKET_SCENE: "persona",
48201
48067
  ...args.logPath ? { CLAWD_TICKET_LOG: args.logPath } : {}
48202
48068
  }
@@ -48581,11 +48447,15 @@ function createShiftSpawnFn(deps) {
48581
48447
  void created.ended.then((info) => {
48582
48448
  clearTimeout(timer);
48583
48449
  if (settled) return;
48584
- if ("ok" in info) {
48450
+ if (info.error) {
48451
+ settle({ kind: "error", error: info.error });
48452
+ return;
48453
+ }
48454
+ if (info.exitCode === 0) {
48585
48455
  settle({ kind: "ok" });
48586
48456
  return;
48587
48457
  }
48588
- settle({ kind: "error", error: info.error });
48458
+ settle({ kind: "error", error: `cc exited with code ${info.exitCode}` });
48589
48459
  });
48590
48460
  return { sessionId: created.sessionId, ended };
48591
48461
  };
@@ -50307,12 +50177,7 @@ var EXPOSED_RPC_METHODS = [
50307
50177
  "attachment.groupAdd",
50308
50178
  "attachment.signUrl",
50309
50179
  // persona 受控发 P2P IM DM(sender 由 session scope 推,driver 分级授权见 handlers/inbox.ts)
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"
50180
+ "inbox.sendDm"
50316
50181
  ];
50317
50182
  async function handleRpcRequest(input) {
50318
50183
  const { method, body, sessionId, dispatcher } = input;
@@ -50497,9 +50362,9 @@ function constantTimeEqual2(a, b2) {
50497
50362
  }
50498
50363
 
50499
50364
  // src/transport/connection-context.ts
50500
- function ownerContext(ownerPrincipalId, displayName, feishuUnionId) {
50365
+ function ownerContext(ownerPrincipalId, displayName) {
50501
50366
  return {
50502
- principal: makeOwnerPrincipal(ownerPrincipalId, displayName, feishuUnionId),
50367
+ principal: makeOwnerPrincipal(ownerPrincipalId, displayName),
50503
50368
  grants: [{ resource: { type: "*" }, actions: ["admin"] }]
50504
50369
  };
50505
50370
  }
@@ -50528,10 +50393,7 @@ function visitorGuestContext(p2) {
50528
50393
  async function authenticate(token, deps) {
50529
50394
  if (!token) return { ok: false, code: "NO_TOKEN" };
50530
50395
  if (deps.isOwnerToken(token)) {
50531
- return {
50532
- ok: true,
50533
- context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName, deps.ownerFeishuUnionId)
50534
- };
50396
+ return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
50535
50397
  }
50536
50398
  if (deps.verifyVisitorToken) {
50537
50399
  const vr = deps.verifyVisitorToken(token);
@@ -54330,22 +54192,6 @@ var LoginFlow = class {
54330
54192
  } catch (err) {
54331
54193
  return { ok: false, reason: `user_info failed: ${err.message}` };
54332
54194
  }
54333
- if (!identity.unionId) {
54334
- return { ok: false, reason: "union_id missing from TTC user_info" };
54335
- }
54336
- try {
54337
- await this.deps.upsertBinding({
54338
- deviceId: this.deps.getDeviceId(),
54339
- unionId: identity.unionId,
54340
- displayName: identity.displayName,
54341
- avatarUrl: identity.avatarUrl ?? null
54342
- });
54343
- } catch (err) {
54344
- return {
54345
- ok: false,
54346
- reason: `device_binding upsert failed: ${err.message}`
54347
- };
54348
- }
54349
54195
  const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
54350
54196
  this.deps.store.write({
54351
54197
  identity,
@@ -54514,58 +54360,6 @@ async function centralListDevices(opts) {
54514
54360
  return out;
54515
54361
  }
54516
54362
 
54517
- // src/feishu-auth/device-binding-client.ts
54518
- var DeviceBindingClientError = class extends Error {
54519
- constructor(code, message, cause) {
54520
- super(message);
54521
- this.code = code;
54522
- this.cause = cause;
54523
- this.name = "DeviceBindingClientError";
54524
- }
54525
- code;
54526
- cause;
54527
- };
54528
- var DEFAULT_TIMEOUT_MS4 = 8e3;
54529
- async function upsertDeviceBinding(opts) {
54530
- const doFetch = opts.fetchImpl ?? fetch;
54531
- const ac = new AbortController();
54532
- const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS4);
54533
- const body = { unionId: opts.unionId };
54534
- if (opts.avatarUrl != null) body.avatarUrl = opts.avatarUrl;
54535
- let res;
54536
- try {
54537
- res = await doFetch(`${opts.apiUrl}/api/device-bindings/self`, {
54538
- method: "PUT",
54539
- signal: ac.signal,
54540
- headers: {
54541
- authorization: `Bearer ${opts.apiKey}`,
54542
- "x-did": encodeURIComponent(opts.deviceId),
54543
- "x-name": encodeURIComponent(opts.displayName),
54544
- "x-scene": "daemon",
54545
- "content-type": "application/json"
54546
- },
54547
- body: JSON.stringify(body)
54548
- });
54549
- } catch (err) {
54550
- clearTimeout(timer);
54551
- throw new DeviceBindingClientError(
54552
- "NETWORK",
54553
- `network error: ${err.message}`,
54554
- err
54555
- );
54556
- } finally {
54557
- clearTimeout(timer);
54558
- }
54559
- if (!res.ok) {
54560
- let detail = "";
54561
- try {
54562
- detail = JSON.stringify(await res.json());
54563
- } catch {
54564
- }
54565
- throw new DeviceBindingClientError("API_ERROR", `HTTP ${res.status}: ${detail}`);
54566
- }
54567
- }
54568
-
54569
54363
  // src/feishu-auth/verify-token.ts
54570
54364
  var crypto14 = __toESM(require("crypto"), 1);
54571
54365
  var CONNECT_TOKEN_PREFIX = "clawdtk1";
@@ -54824,25 +54618,9 @@ function buildSessionHandlers(deps) {
54824
54618
  const send = async (frame, _client, ctx) => {
54825
54619
  const args = SessionSendArgs.parse(frame);
54826
54620
  ensureSessionAccess(ctx, args.sessionId, "send");
54827
- const sender = ctx ? principalToSender(ctx.principal) : principalToSender(
54828
- makeOwnerPrincipal(ownerPrincipalId, deps.ownerDisplayName, deps.ownerFeishuUnionId)
54829
- );
54830
- const { broadcast } = manager.send(args, sender);
54621
+ const { broadcast } = manager.send(args);
54831
54622
  return { response: { type: "session:send", ok: true }, broadcast };
54832
54623
  };
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
- };
54846
54624
  const stop = async (frame, _client, ctx) => {
54847
54625
  const args = SessionIdArgs.parse(frame);
54848
54626
  ensureSessionAccess(ctx, args.sessionId, "send");
@@ -55045,7 +54823,6 @@ function buildSessionHandlers(deps) {
55045
54823
  "session:update": update,
55046
54824
  "session:delete": del,
55047
54825
  "session:send": send,
55048
- "session:currentTurnSender": currentTurnSender,
55049
54826
  "session:stop": stop,
55050
54827
  "session:interrupt": interrupt,
55051
54828
  "session:rewind": rewind,
@@ -58114,14 +57891,7 @@ function buildAppBuilderHandlers(deps) {
58114
57891
  broadcastProjectUpdated(f);
58115
57892
  },
58116
57893
  injectUserText: async (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);
57894
+ const { broadcast } = sessionManager.send({ sessionId, text });
58125
57895
  for (const entry of broadcast) {
58126
57896
  deps.broadcastSessionFrame(entry.frame, entry.target);
58127
57897
  }
@@ -58420,9 +58190,6 @@ function buildMethodHandlers(deps) {
58420
58190
  usersRoot: deps.usersRoot,
58421
58191
  devServerLifecycle: deps.devServerLifecycle,
58422
58192
  sessionManager: deps.manager,
58423
- ownerPrincipalId: deps.ownerPrincipalId,
58424
- ownerDisplayName: deps.ownerDisplayName,
58425
- ownerFeishuUnionId: deps.ownerFeishuUnionId,
58426
58193
  getTunnelUrl: deps.getTunnelUrl,
58427
58194
  broadcastProjectUpdated: deps.broadcastProjectUpdated,
58428
58195
  logger: deps.logger,
@@ -58751,10 +58518,6 @@ var METHOD_GRANT_MAP = {
58751
58518
  "session:update": CAPABILITY_SCOPED,
58752
58519
  "session:delete": CAPABILITY_SCOPED,
58753
58520
  "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" },
58758
58521
  "session:stop": CAPABILITY_SCOPED,
58759
58522
  "session:interrupt": CAPABILITY_SCOPED,
58760
58523
  "session:rewind": CAPABILITY_SCOPED,
@@ -59327,7 +59090,6 @@ async function startDaemon(config) {
59327
59090
  let ownerId = feishuIdentity?.identity.ownerId ?? "";
59328
59091
  let ownerProvider = feishuIdentity?.identity.provider ?? "";
59329
59092
  let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
59330
- let ownerFeishuUnionId = feishuIdentity?.identity.unionId;
59331
59093
  const authMode = resolvedAuthToken == null ? "none" : "first-message";
59332
59094
  const inboxStore = new InboxStore(config.dataDir);
59333
59095
  const inboxManager = new InboxManager(inboxStore, (_peerDeviceId, frame) => {
@@ -59340,7 +59102,6 @@ async function startDaemon(config) {
59340
59102
  isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
59341
59103
  ownerPrincipalId,
59342
59104
  ownerDisplayName,
59343
- ownerFeishuUnionId,
59344
59105
  // persona web 分享:访客 visitor token = daemon 自签 HMAC(无 aud),独立于飞书 connect token
59345
59106
  // 基建——secret 恒存在(auth-store backfill),故无条件注入。验签 ok → guest ctx(persona wildcard)。
59346
59107
  verifyVisitorToken: (t) => {
@@ -59424,7 +59185,7 @@ async function startDaemon(config) {
59424
59185
  return result;
59425
59186
  },
59426
59187
  onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
59427
- buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId),
59188
+ buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
59428
59189
  closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
59429
59190
  sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
59430
59191
  }) : null;
@@ -59496,14 +59257,18 @@ async function startDaemon(config) {
59496
59257
  import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59497
59258
  ];
59498
59259
  const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59260
+ const ticketCallerDid = feishuIdentity?.identity.unionId ?? "";
59261
+ const ticketCallerName = feishuIdentity?.identity.displayName ?? "";
59499
59262
  let ticketMcpConfigPath2;
59500
- if (ticketServerScriptPath) {
59263
+ if (ticketServerScriptPath && ticketCallerDid) {
59501
59264
  const ticketLogPath = import_node_path64.default.join(config.dataDir, "ticket-mcp-server.log");
59502
59265
  ticketMcpConfigPath2 = writeTicketMcpConfig({
59503
59266
  dataDir: config.dataDir,
59504
59267
  serverScriptPath: ticketServerScriptPath,
59505
59268
  apiBaseUrl: config.clawosApi ?? DEFAULT_CLAWOS_API,
59506
59269
  apiKey: resolveClawdTicketsApiKey(),
59270
+ did: ticketCallerDid,
59271
+ name: ticketCallerName,
59507
59272
  logPath: ticketLogPath
59508
59273
  });
59509
59274
  logger.info("ticket.mcp.json written", {
@@ -59513,7 +59278,7 @@ async function startDaemon(config) {
59513
59278
  });
59514
59279
  } else {
59515
59280
  logger.warn("ticket-mcp skipped", {
59516
- reason: "ticket-mcp-server.cjs not found (need build)"
59281
+ reason: !ticketCallerDid ? "caller did missing (login required)" : "ticket-mcp-server.cjs not found (need build)"
59517
59282
  });
59518
59283
  }
59519
59284
  const shiftServerCandidates = [
@@ -59592,7 +59357,6 @@ async function startDaemon(config) {
59592
59357
  personaStore,
59593
59358
  ownerDisplayName,
59594
59359
  ownerPrincipalId,
59595
- ownerFeishuUnionId,
59596
59360
  contactStore,
59597
59361
  mode: config.mode,
59598
59362
  // 单栏 refactor (spec 2026-06-02 §5.1):cc 子进程 env CLAWD_DAEMON_URL 闭包来源。
@@ -59765,16 +59529,7 @@ async function startDaemon(config) {
59765
59529
  store: ownerIdentityStore,
59766
59530
  fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
59767
59531
  ttcAuthBase: TTC_HOSTS.auth,
59768
- getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`,
59769
- // device_bindings upsert(did ↔ 飞书 unionId 映射):
59770
- // deviceId 从 auth.json 稳定读;apiUrl 走同一份 clawosApi;apiKey 复用 CLAWD_TICKETS_API_KEY。
59771
- // 失败即登录失败(fail-hard,login-flow.spec §upsert 失败)。
59772
- getDeviceId: () => authFile.deviceId,
59773
- upsertBinding: (opts) => upsertDeviceBinding({
59774
- apiUrl: config.clawosApi ?? DEFAULT_CLAWOS_API,
59775
- apiKey: resolveClawdTicketsApiKey(),
59776
- ...opts
59777
- })
59532
+ getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
59778
59533
  });
59779
59534
  const visitorStore = createVisitorStore({ dir: config.dataDir });
59780
59535
  const exchangeVisitorToken = buildVisitorLogin({
@@ -59855,8 +59610,6 @@ async function startDaemon(config) {
59855
59610
  ownerDisplayName,
59856
59611
  // 决策 #16: whoami owner.id = 本机 deviceId(路由主键)+ ownerId/provider 归属人
59857
59612
  ownerPrincipalId,
59858
- // per-turn sender §4.6:send handler 无 ctx 兜底 + AppBuilder publish injectUserText 需要
59859
- ownerFeishuUnionId,
59860
59613
  ownerId,
59861
59614
  ownerProvider,
59862
59615
  personaStore,
@@ -60140,7 +59893,6 @@ async function startDaemon(config) {
60140
59893
  ownerId = result.identity.ownerId;
60141
59894
  ownerProvider = result.identity.provider;
60142
59895
  ownerDisplayName = result.identity.displayName;
60143
- ownerFeishuUnionId = result.identity.unionId;
60144
59896
  handlers = makeHandlers();
60145
59897
  wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
60146
59898
  setImmediate(() => wsServer?.closeAllClients());
@@ -60279,7 +60031,7 @@ async function startDaemon(config) {
60279
60031
  wss.onFrame(async (client, frame) => {
60280
60032
  const type = frame.type;
60281
60033
  const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
60282
- const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId);
60034
+ const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
60283
60035
  const result = await dispatchRpc(type, frame, client, ctx, {
60284
60036
  handlers,
60285
60037
  feishuActive: () => feishuActive