@clawos-dev/clawd 0.2.84 → 0.2.85-beta.157.a054210

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +101 -17
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -65,6 +65,14 @@ var init_methods = __esm({
65
65
  "session:unsubscribe",
66
66
  "session:pin",
67
67
  "session:reorderPins",
68
+ // mirror peer sessions (2026-05-26): owner-only RPC,写/删本机 mirror SessionFile —
69
+ // mirror 是「**我**在朋友 daemon 上自己创建的 session」的本机持久化副本(不是朋友的会话),
70
+ // 方便 sidebar 跨设备聚合。daemon dumb 写删,**不广播任何 push 帧** (不发 session:info /
71
+ // session:deleted);UI 是写入唯一源,双写完成后通过 cache.setData('sessions', ...) 同步
72
+ // 本地 sidebar state。native session:* handler 完全不感知 mirror (UI 操作 mirror 不复用
73
+ // native 路径)。
74
+ "peerSession:upsert",
75
+ "peerSession:remove",
68
76
  "permission:respond",
69
77
  // AskUserQuestion 表单回写:UI 答完所有 question 后调用,daemon 把答案合并进 updated_input
70
78
  // 写一条 control_response 到 CC stdin(详见 events.ts session:question JSDoc)
@@ -4483,7 +4491,7 @@ var init_persona_schemas = __esm({
4483
4491
  });
4484
4492
 
4485
4493
  // ../protocol/src/schemas.ts
4486
- var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4494
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
4487
4495
  var init_schemas = __esm({
4488
4496
  "../protocol/src/schemas.ts"() {
4489
4497
  "use strict";
@@ -4611,6 +4619,35 @@ var init_schemas = __esm({
4611
4619
  * 当前为 caller 自报,daemon 不密码学验证真伪;强身份验签是独立工作(B 方案)。
4612
4620
  */
4613
4621
  creatorOwnerPrincipalId: external_exports.string().min(1).optional(),
4622
+ /**
4623
+ * Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上以 guest ctx
4624
+ * 创建的 session**,远端是 source of truth,本机是该 session 的持久化副本,方便
4625
+ * sidebar 跨设备聚合。字段值是远端 daemon 的 ownerPrincipalId(即我接入的那位朋友)。
4626
+ *
4627
+ * 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
4628
+ * 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
4629
+ *
4630
+ * - undefined / 缺省 → 本机 native session(在我自己机器上跑)
4631
+ * - 非空 → 我的会话在该 ownerPrincipalId 朋友的 daemon 上跑,本机为持久化副本
4632
+ *
4633
+ * 与 `creatorPrincipalId` / `creatorOwnerPrincipalId` 在四类 session 下取值矩阵:
4634
+ * | 形态 | creatorPrincipalId | creatorOwnerPrincipalId | originOwnerPrincipalId |
4635
+ * |-------------------------------------|--------------------------|-------------------------|------------------------|
4636
+ * | 本机 owner 自创 | 本机 ownerPrincipalId | 本机 ownerPrincipalId | undefined |
4637
+ * | 本机被别人通过 personal token 接入创建 | 远端接入者 cap.id | 远端接入者 ownerPrincipalId | undefined |
4638
+ * | 我在朋友 daemon 上以 guest ctx 创建(mirror 回本机) | 朋友 daemon personal cap.id | 我自己 ownerPrincipalId | 朋友 ownerPrincipalId |
4639
+ *
4640
+ * UI 用法:
4641
+ * - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
4642
+ * - 行内显示 `@<朋友>` chip 暗示位置
4643
+ * - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端
4644
+ *
4645
+ * daemon 用法:
4646
+ * - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
4647
+ * daemon dumb write/delete,**不广播任何 push 帧**,UI 是写入唯一源
4648
+ * - native `session:*` handler 完全不感知 mirror(UI 操作 mirror 不走 native 路径)
4649
+ */
4650
+ originOwnerPrincipalId: external_exports.string().min(1).optional(),
4614
4651
  createdAt: external_exports.string().min(1),
4615
4652
  updatedAt: external_exports.string().min(1)
4616
4653
  });
@@ -4966,6 +5003,14 @@ var init_schemas = __esm({
4966
5003
  sessionId: external_exports.string().min(1),
4967
5004
  pinned: external_exports.boolean()
4968
5005
  });
5006
+ PeerSessionUpsertArgs = SessionFileSchema;
5007
+ PeerSessionUpsertResponseSchema = external_exports.object({
5008
+ type: external_exports.literal("peerSession:upsert:ok")
5009
+ });
5010
+ PeerSessionRemoveArgs = external_exports.object({ sessionId: external_exports.string().min(1) });
5011
+ PeerSessionRemoveResponseSchema = external_exports.object({
5012
+ type: external_exports.literal("peerSession:remove:ok")
5013
+ });
4969
5014
  SessionReorderPinsArgs = external_exports.object({
4970
5015
  orderedIds: external_exports.array(external_exports.string().min(1)).min(1)
4971
5016
  });
@@ -22862,6 +22907,25 @@ var SessionManager = class {
22862
22907
  const file = this.getFile(args.sessionId);
22863
22908
  return { response: file, broadcast: [] };
22864
22909
  }
22910
+ // mirror peer sessions (2026-05-26):把来自朋友 daemon 的 SessionFile(originOwnerPrincipalId
22911
+ // 非空)持久化到本机 default store。dumb write,**不广播 push 帧**——UI 是写入唯一源,
22912
+ // 双写完成后通过 cache.setData 同步 sidebar 本地 state。
22913
+ //
22914
+ // 强制走 deps.store(即 default scope),不走 scopeForFile:即使 mirror SessionFile 带
22915
+ // 远端 ownerPersonaId(远端 persona session),本机也落 default,不创建本机 persona 目录。
22916
+ // 远端 creator / origin 字段原值保留(不被本机 ctx 派生覆写)。
22917
+ //
22918
+ // 与 manager.create / manager.update 区别:
22919
+ // - 不走 newRunner / attachObserver / cwd 校验(mirror 是纯 metadata 层)
22920
+ // - 不发 session:info 帧(manager.create / .update 通过 reducer/wireCollector 发 push)
22921
+ mirrorUpsert(file) {
22922
+ return this.deps.store.write(file);
22923
+ }
22924
+ // mirror peer sessions:删本机 mirror SessionFile。强制 default store。
22925
+ // idempotent:找不到不 throw(UI 反复点删容错)。**不广播 session:deleted**。
22926
+ mirrorRemove(sessionId) {
22927
+ this.deps.store.delete(sessionId);
22928
+ }
22865
22929
  // observer 上报"jsonl 写入了一条 user 行"时调用:在 buffer 里找到与之对应的、
22866
22930
  // 还没建立映射的最早一条 synth user_text,记下 synth → real 映射。
22867
22931
  // 找不到匹配的 synth(runner 不存在 / buffer 已被 /clear 清空 / 真 uuid 来自纯 history
@@ -28477,13 +28541,12 @@ function buildSessionHandlers(deps) {
28477
28541
  const { response, broadcast } = await manager.cancelQuestion(args);
28478
28542
  return { response: { type: "session:cancelQuestion", ...response }, broadcast };
28479
28543
  };
28480
- const ptyInput = async (frame, _client, ctx) => {
28544
+ const ptyInput = async (frame) => {
28481
28545
  const sid = frame.sessionId;
28482
28546
  const dataB64 = frame.data;
28483
28547
  if (!sid || typeof dataB64 !== "string") {
28484
28548
  return { response: { type: "session:pty:input", ok: false } };
28485
28549
  }
28486
- ensureSessionAccess(ctx, sid, "send");
28487
28550
  const pty = manager.getPtyBySessionId(sid);
28488
28551
  if (!pty) return { response: { type: "session:pty:input", ok: false } };
28489
28552
  try {
@@ -28493,14 +28556,13 @@ function buildSessionHandlers(deps) {
28493
28556
  }
28494
28557
  return { response: { type: "session:pty:input", ok: true } };
28495
28558
  };
28496
- const ptyResize = async (frame, _client, ctx) => {
28559
+ const ptyResize = async (frame) => {
28497
28560
  const sid = frame.sessionId;
28498
28561
  const cols = frame.cols;
28499
28562
  const rows = frame.rows;
28500
28563
  if (!sid || typeof cols !== "number" || typeof rows !== "number") {
28501
28564
  return { response: { type: "session:pty:resize", ok: false } };
28502
28565
  }
28503
- ensureSessionAccess(ctx, sid, "send");
28504
28566
  const pty = manager.getPtyBySessionId(sid);
28505
28567
  if (!pty) return { response: { type: "session:pty:resize", ok: false } };
28506
28568
  try {
@@ -28510,12 +28572,11 @@ function buildSessionHandlers(deps) {
28510
28572
  }
28511
28573
  return { response: { type: "session:pty:resize", ok: true } };
28512
28574
  };
28513
- const ptySnapshot = async (frame, _client, ctx) => {
28575
+ const ptySnapshot = async (frame) => {
28514
28576
  const sid = frame.sessionId;
28515
28577
  if (!sid) {
28516
28578
  return { response: { type: "session:pty:snapshot", ok: false } };
28517
28579
  }
28518
- ensureSessionAccess(ctx, sid, "read");
28519
28580
  const snap = manager.getPtyReplay(sid);
28520
28581
  if (!snap) {
28521
28582
  return {
@@ -28536,6 +28597,28 @@ function buildSessionHandlers(deps) {
28536
28597
  }
28537
28598
  };
28538
28599
  };
28600
+ const peerSessionUpsert = async (frame, _client, ctx) => {
28601
+ if (ctx && ctx.principal.kind !== "owner") {
28602
+ throw new ClawdError(
28603
+ ERROR_CODES.UNAUTHORIZED,
28604
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:upsert`
28605
+ );
28606
+ }
28607
+ const file = PeerSessionUpsertArgs.parse(frame);
28608
+ manager.mirrorUpsert(file);
28609
+ return { response: { type: "peerSession:upsert:ok" }, broadcast: [] };
28610
+ };
28611
+ const peerSessionRemove = async (frame, _client, ctx) => {
28612
+ if (ctx && ctx.principal.kind !== "owner") {
28613
+ throw new ClawdError(
28614
+ ERROR_CODES.UNAUTHORIZED,
28615
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot peerSession:remove`
28616
+ );
28617
+ }
28618
+ const args = PeerSessionRemoveArgs.parse(frame);
28619
+ manager.mirrorRemove(args.sessionId);
28620
+ return { response: { type: "peerSession:remove:ok" }, broadcast: [] };
28621
+ };
28539
28622
  return {
28540
28623
  "session:create": create,
28541
28624
  "session:list": list,
@@ -28561,7 +28644,9 @@ function buildSessionHandlers(deps) {
28561
28644
  "session:cancelQuestion": cancelQuestion,
28562
28645
  "session:pty:input": ptyInput,
28563
28646
  "session:pty:resize": ptyResize,
28564
- "session:pty:snapshot": ptySnapshot
28647
+ "session:pty:snapshot": ptySnapshot,
28648
+ "peerSession:upsert": peerSessionUpsert,
28649
+ "peerSession:remove": peerSessionRemove
28565
28650
  };
28566
28651
  }
28567
28652
 
@@ -29639,6 +29724,11 @@ var METHOD_GRANT_MAP = {
29639
29724
  "session:pin": CAPABILITY_SCOPED,
29640
29725
  "session:reorderPins": ADMIN_ANY,
29641
29726
  // owner 全局操作,无 personaId 维度
29727
+ // mirror peer sessions (2026-05-26):owner-only — guest 不应能写/删本机 mirror SessionFile
29728
+ // (会绕过 #739 隔离规则把伪造身份的 SessionFile 写进 default store)。handler 端内部 ctx
29729
+ // 校验,dispatcher 层一刀切 admin 也兜底。
29730
+ "peerSession:upsert": ADMIN_ANY,
29731
+ "peerSession:remove": ADMIN_ANY,
29642
29732
  "permission:respond": CAPABILITY_SCOPED,
29643
29733
  "session:answerQuestion": CAPABILITY_SCOPED,
29644
29734
  "session:cancelQuestion": CAPABILITY_SCOPED,
@@ -29674,15 +29764,9 @@ var METHOD_GRANT_MAP = {
29674
29764
  "persona:get": CAPABILITY_SCOPED,
29675
29765
  "persona:update": ADMIN_ANY,
29676
29766
  "persona:delete": ADMIN_ANY,
29677
- // 2026-05-26 修 clawd people TUI 渲染 mix 串扰回归(参考 f1133ff0 原 owner 路径
29678
- // snapshot/buffer/flush 修复):之前三件套 ADMIN_ANY 把 guest 拦在 dispatcher 401,
29679
- // 导致 XtermPanel 重挂载拉 snapshot 在 guest 路径不生效,老的字节流断裂串扰回来。
29680
- // 同一个 session 永远只有一个操作者(owner 不进 guest 创建的 session),并发字节流
29681
- // 冲突不存在;guest 在 chat 模式下已能 session:send/interrupt,PTY 域不开放纯属
29682
- // 历史遗留的不对称。handler 端通过 ensureSessionAccess 验权对齐其他 session:* RPC。
29683
- "session:pty:input": CAPABILITY_SCOPED,
29684
- "session:pty:resize": CAPABILITY_SCOPED,
29685
- "session:pty:snapshot": CAPABILITY_SCOPED,
29767
+ "session:pty:input": ADMIN_ANY,
29768
+ "session:pty:resize": ADMIN_ANY,
29769
+ "session:pty:snapshot": ADMIN_ANY,
29686
29770
  // file-sharing attachment.* RPC:dispatcher 放行;handler 内按 sessionId 反查
29687
29771
  // session.ownerPersonaId + ctx.grants 跑 assertGrant,限制 guest 只能签 / 操作
29688
29772
  // 自己被授权 persona 下的 session 文件。HTTP Bearer 仍然只识别 owner,所以 guest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.84",
3
+ "version": "0.2.85-beta.157.a054210",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",