@clawos-dev/clawd 0.2.411 → 0.2.413

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
@@ -6630,19 +6630,23 @@ var init_shift_internal = __esm({
6630
6630
  });
6631
6631
 
6632
6632
  // ../protocol/src/channel.ts
6633
- var MASTER_PERSONA_ID, ChannelSchema, nonBlank, ChannelCreateArgsSchema, ChannelUpdateArgsSchema, ChannelDeleteArgsSchema, ChannelListResultSchema, ChannelResultSchema, ChannelDeleteResultSchema;
6633
+ var MASTER_PERSONA_ID, ChannelMemberPersonaSchema, ChannelSchema, nonBlank, ChannelCreateArgsSchema, ChannelUpdateArgsSchema, ChannelDeleteArgsSchema, ChannelListResultSchema, ChannelResultSchema, ChannelDeleteResultSchema;
6634
6634
  var init_channel = __esm({
6635
6635
  "../protocol/src/channel.ts"() {
6636
6636
  "use strict";
6637
6637
  init_zod();
6638
6638
  MASTER_PERSONA_ID = "persona-master";
6639
+ ChannelMemberPersonaSchema = external_exports.object({
6640
+ personaId: external_exports.string().min(1),
6641
+ deviceId: external_exports.string().min(1).optional()
6642
+ });
6639
6643
  ChannelSchema = external_exports.object({
6640
6644
  id: external_exports.string().min(1),
6641
6645
  name: external_exports.string().min(1),
6642
- /** 该频道里可被派活的 persona */
6643
- memberPersonaIds: external_exports.array(external_exports.string().min(1)),
6644
- /** 人类成员(contact deviceId)。跨设备派活的成员边界认这张名单(personaDispatch 校验),
6645
- * P1 人类异步询问也用它 */
6646
+ /** 该频道里可被派活的 persona 全集(本机 + 远程) */
6647
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema),
6648
+ /** 人类成员(contact deviceId):可发 DM。**不隐含任何派活权限** —— 跨设备派活认的是
6649
+ * 上面那张 memberPersonas。P1 人类异步询问也用它 */
6646
6650
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)),
6647
6651
  createdAt: external_exports.string().min(1),
6648
6652
  updatedAt: external_exports.string().min(1)
@@ -6650,13 +6654,13 @@ var init_channel = __esm({
6650
6654
  nonBlank = external_exports.string().trim().min(1);
6651
6655
  ChannelCreateArgsSchema = external_exports.object({
6652
6656
  name: nonBlank,
6653
- memberPersonaIds: external_exports.array(external_exports.string().min(1)).default([]),
6657
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema).default([]),
6654
6658
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).default([])
6655
6659
  });
6656
6660
  ChannelUpdateArgsSchema = external_exports.object({
6657
6661
  channelId: external_exports.string().min(1),
6658
6662
  name: nonBlank.optional(),
6659
- memberPersonaIds: external_exports.array(external_exports.string().min(1)).optional(),
6663
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema).optional(),
6660
6664
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).optional()
6661
6665
  });
6662
6666
  ChannelDeleteArgsSchema = external_exports.object({ channelId: external_exports.string().min(1) });
@@ -52997,12 +53001,13 @@ var import_node_path20 = __toESM(require("path"), 1);
52997
53001
  function createJsonStore(deps) {
52998
53002
  let items = [];
52999
53003
  let flushTimer = null;
53004
+ const version2 = deps.version ?? 1;
53000
53005
  async function flushNow() {
53001
53006
  if (flushTimer) {
53002
53007
  clearTimeout(flushTimer);
53003
53008
  flushTimer = null;
53004
53009
  }
53005
- const content = { version: 1, [deps.key]: items };
53010
+ const content = { version: version2, [deps.key]: items };
53006
53011
  await import_promises2.default.mkdir(import_node_path20.default.dirname(deps.filePath), { recursive: true });
53007
53012
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
53008
53013
  await import_promises2.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
@@ -53023,10 +53028,24 @@ function createJsonStore(deps) {
53023
53028
  throw e;
53024
53029
  }
53025
53030
  const parsed = JSON.parse(raw);
53026
- if (parsed.version !== 1) {
53027
- throw new Error(`${deps.key}.json: unsupported version ${String(parsed.version)}`);
53031
+ const found = parsed.version;
53032
+ if (found === version2) {
53033
+ items = parsed[deps.key] ?? [];
53034
+ return;
53035
+ }
53036
+ if (typeof found === "number" && found < version2 && deps.migrate) {
53037
+ items = deps.migrate(parsed[deps.key] ?? [], found);
53038
+ await flushNow().catch((err) => deps.onFlushError?.(err));
53039
+ return;
53040
+ }
53041
+ if (typeof found === "number" && found > version2) {
53042
+ throw new Error(
53043
+ `${deps.key}.json \u7531\u66F4\u65B0\u7248\u672C\u7684 clawd \u5199\u5165\uFF08\u6587\u4EF6 version=${found}\uFF0C\u672C\u673A\u53EA\u8BA4\u5230 ${version2}\uFF09\uFF0C\u8BF7\u5347\u7EA7 clawd`
53044
+ );
53028
53045
  }
53029
- items = parsed[deps.key] ?? [];
53046
+ throw new Error(
53047
+ `${deps.key}.json: unsupported version ${String(found)}\uFF08\u672C\u673A version=${version2}\uFF09`
53048
+ );
53030
53049
  },
53031
53050
  scheduleFlush() {
53032
53051
  if (flushTimer) return;
@@ -53040,7 +53059,31 @@ function createJsonStore(deps) {
53040
53059
 
53041
53060
  // src/channel/store.ts
53042
53061
  function createChannelStore(deps) {
53043
- const file = createJsonStore({ ...deps, key: "channels" });
53062
+ const file = createJsonStore({
53063
+ ...deps,
53064
+ key: "channels",
53065
+ version: 2,
53066
+ // v1 → v2(2026-08-26):成员表换形,本机与远程 persona 并进一张 memberPersonas。
53067
+ // memberContactDeviceIds 原样留着 —— 它的语义从「可派活给这台设备上的任意 persona」
53068
+ // 收窄成「可发 DM」,收窄不需要动数据。
53069
+ //
53070
+ // 存量频道因此失去跨设备派活能力,这是有意的:要补偿就得在这里去拉对方 daemon 的
53071
+ // persona 列表(对方不一定在线),而「他家所有 persona 自动进名单」正是要废掉的
53072
+ // 那个隐含推导。老板重新勾一次即可。
53073
+ //
53074
+ // ⚠️ 写法注意(给下一次 v2→v3 的人):这里是**先解构后覆盖** —— `...rest` 会把
53075
+ // 旧行里的同名字段一起带进来,紧跟着的赋值再盖掉它。加新字段时确认清楚是想覆盖
53076
+ // 还是想保留,别无意中把 rest 里已有的值清空。
53077
+ migrate: (rows) => rows.map((c) => {
53078
+ const { memberPersonaIds, ...rest } = c;
53079
+ return {
53080
+ ...rest,
53081
+ memberPersonas: (memberPersonaIds ?? []).map((personaId2) => ({
53082
+ personaId: personaId2
53083
+ }))
53084
+ };
53085
+ })
53086
+ });
53044
53087
  const get = (id) => file.all().find((c) => c.id === id);
53045
53088
  return {
53046
53089
  load: file.load,
@@ -53052,7 +53095,7 @@ function createChannelStore(deps) {
53052
53095
  const c = {
53053
53096
  id: deps.genId(),
53054
53097
  name: input.name,
53055
- memberPersonaIds: input.memberPersonaIds,
53098
+ memberPersonas: input.memberPersonas,
53056
53099
  memberContactDeviceIds: input.memberContactDeviceIds,
53057
53100
  createdAt: iso,
53058
53101
  updatedAt: iso
@@ -53065,7 +53108,7 @@ function createChannelStore(deps) {
53065
53108
  const c = get(id);
53066
53109
  if (!c) return void 0;
53067
53110
  if (patch.name !== void 0) c.name = patch.name;
53068
- if (patch.memberPersonaIds !== void 0) c.memberPersonaIds = patch.memberPersonaIds;
53111
+ if (patch.memberPersonas !== void 0) c.memberPersonas = patch.memberPersonas;
53069
53112
  if (patch.memberContactDeviceIds !== void 0) {
53070
53113
  c.memberContactDeviceIds = patch.memberContactDeviceIds;
53071
53114
  }
@@ -54876,9 +54919,11 @@ function buildPersonaDispatchHandlers(deps) {
54876
54919
  if (!members) {
54877
54920
  throw new ClawdError(ERROR_CODES.INVALID_PARAM, `topic not found: ${topicId}`);
54878
54921
  }
54879
- const who = args.targetDeviceId ?? targetPersona;
54880
- const isMember = args.targetDeviceId ? members.deviceIds.includes(args.targetDeviceId) : members.personaIds.includes(targetPersona);
54922
+ const isMember = members.personas.some(
54923
+ (m2) => m2.personaId === targetPersona && m2.deviceId === args.targetDeviceId
54924
+ );
54881
54925
  if (!isMember) {
54926
+ const who = args.targetDeviceId ? `${targetPersona}@${args.targetDeviceId}` : targetPersona;
54882
54927
  throw new ClawdError(
54883
54928
  ERROR_CODES.INVALID_PARAM,
54884
54929
  `${who} is not a member of the channel behind topic ${topicId} \u2014\u2014 \u5148\u628A\u5B83\u52A0\u8FDB\u9891\u9053\u518D\u6D3E\u6D3B`
@@ -62333,7 +62378,7 @@ function computeMethodAccess(args) {
62333
62378
  }
62334
62379
 
62335
62380
  // src/version.ts
62336
- var version = "0.2.411".length > 0 ? "0.2.411" : "dev";
62381
+ var version = "0.2.413".length > 0 ? "0.2.413" : "dev";
62337
62382
 
62338
62383
  // src/cli-probe/probe.ts
62339
62384
  var fs66 = __toESM(require("fs"), 1);
@@ -64484,8 +64529,8 @@ function buildShiftHandlers(deps) {
64484
64529
 
64485
64530
  // src/handlers/channel.ts
64486
64531
  init_protocol();
64487
- function withMaster(ids) {
64488
- return ids.includes(MASTER_PERSONA_ID) ? ids : [MASTER_PERSONA_ID, ...ids];
64532
+ function withMaster(members) {
64533
+ return members.some((m2) => !m2.deviceId && m2.personaId === MASTER_PERSONA_ID) ? members : [{ personaId: MASTER_PERSONA_ID }, ...members];
64489
64534
  }
64490
64535
  function buildChannelHandlers(deps) {
64491
64536
  return {
@@ -64496,7 +64541,7 @@ function buildChannelHandlers(deps) {
64496
64541
  const args = ChannelCreateArgsSchema.parse(frame);
64497
64542
  const channel = deps.channelStore.create({
64498
64543
  name: args.name,
64499
- memberPersonaIds: withMaster(args.memberPersonaIds),
64544
+ memberPersonas: withMaster(args.memberPersonas),
64500
64545
  memberContactDeviceIds: args.memberContactDeviceIds
64501
64546
  });
64502
64547
  return { response: ChannelResultSchema.parse({ channel }) };
@@ -64506,7 +64551,7 @@ function buildChannelHandlers(deps) {
64506
64551
  const channel = deps.channelStore.update(args.channelId, {
64507
64552
  ...args.name !== void 0 ? { name: args.name } : {},
64508
64553
  // 改名单同样过 withMaster:编辑路径把 Master 摘掉,效果跟建一个没 Master 的频道一样
64509
- ...args.memberPersonaIds !== void 0 ? { memberPersonaIds: withMaster(args.memberPersonaIds) } : {},
64554
+ ...args.memberPersonas !== void 0 ? { memberPersonas: withMaster(args.memberPersonas) } : {},
64510
64555
  ...args.memberContactDeviceIds !== void 0 ? { memberContactDeviceIds: args.memberContactDeviceIds } : {}
64511
64556
  });
64512
64557
  if (!channel) {
@@ -67113,14 +67158,17 @@ async function startDaemon(config) {
67113
67158
  const dispatchHandlers = buildPersonaDispatchHandlers({
67114
67159
  personaDispatchManager,
67115
67160
  store: dispatchStore,
67116
- // 话题工作线的派活边界:topicId → 它所属频道的两张成员名单。频道 / 话题任一不存在
67117
- // 都返回 undefined,由 handler 判成「话题不存在」拒掉(编 topicId 绕不过去)。
67161
+ // 话题工作线的派活边界:topicId → 它所属频道的 persona 成员名单。频道 / 话题任一
67162
+ // 不存在都返回 undefined,由 handler 判成「话题不存在」拒掉(编 topicId 绕不过去)。
67163
+ //
67164
+ // 只给 persona 名单:memberContactDeviceIds 是「可发 DM」的人类名单,不参与派活判定。
67165
+ // 在这里一并带上它,下一个读代码的人会以为它也是闸的一部分(P1 接 DM 时再按需要加)。
67118
67166
  getTopicMembership: (topicId) => {
67119
67167
  const t = topicStore.get(topicId);
67120
67168
  if (!t) return void 0;
67121
67169
  const ch = channelStore.get(t.channelId);
67122
67170
  if (!ch) return void 0;
67123
- return { personaIds: [...ch.memberPersonaIds], deviceIds: [...ch.memberContactDeviceIds] };
67171
+ return { personas: [...ch.memberPersonas] };
67124
67172
  },
67125
67173
  // 续派(spec 2026-07-21):任务包注入 worker session,复用投递同款注入路径。
67126
67174
  sendFollowUp: (workerSessionId, text, meta) => manager.injectIntoSession(workerSessionId, text, meta),
@@ -21832,13 +21832,17 @@ var ShiftInternalHistoryArgs = external_exports.object({
21832
21832
  });
21833
21833
 
21834
21834
  // ../protocol/src/channel.ts
21835
+ var ChannelMemberPersonaSchema = external_exports.object({
21836
+ personaId: external_exports.string().min(1),
21837
+ deviceId: external_exports.string().min(1).optional()
21838
+ });
21835
21839
  var ChannelSchema = external_exports.object({
21836
21840
  id: external_exports.string().min(1),
21837
21841
  name: external_exports.string().min(1),
21838
- /** 该频道里可被派活的 persona */
21839
- memberPersonaIds: external_exports.array(external_exports.string().min(1)),
21840
- /** 人类成员(contact deviceId)。跨设备派活的成员边界认这张名单(personaDispatch 校验),
21841
- * P1 人类异步询问也用它 */
21842
+ /** 该频道里可被派活的 persona 全集(本机 + 远程) */
21843
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema),
21844
+ /** 人类成员(contact deviceId):可发 DM。**不隐含任何派活权限** —— 跨设备派活认的是
21845
+ * 上面那张 memberPersonas。P1 人类异步询问也用它 */
21842
21846
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)),
21843
21847
  createdAt: external_exports.string().min(1),
21844
21848
  updatedAt: external_exports.string().min(1)
@@ -21846,13 +21850,13 @@ var ChannelSchema = external_exports.object({
21846
21850
  var nonBlank = external_exports.string().trim().min(1);
21847
21851
  var ChannelCreateArgsSchema = external_exports.object({
21848
21852
  name: nonBlank,
21849
- memberPersonaIds: external_exports.array(external_exports.string().min(1)).default([]),
21853
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema).default([]),
21850
21854
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).default([])
21851
21855
  });
21852
21856
  var ChannelUpdateArgsSchema = external_exports.object({
21853
21857
  channelId: external_exports.string().min(1),
21854
21858
  name: nonBlank.optional(),
21855
- memberPersonaIds: external_exports.array(external_exports.string().min(1)).optional(),
21859
+ memberPersonas: external_exports.array(ChannelMemberPersonaSchema).optional(),
21856
21860
  memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).optional()
21857
21861
  });
21858
21862
  var ChannelDeleteArgsSchema = external_exports.object({ channelId: external_exports.string().min(1) });
@@ -37,7 +37,7 @@ call({ method: "persona:list" }) # clawd-rpc MCP tool
37
37
 
38
38
  看清单里各 persona 的定位,判断该派给谁。特别是:
39
39
 
40
- - 要改 clawd 源码 / 修 bug → 找有对应定位的 dev 类 persona
40
+ - 要改 clawd 源码 / 修 bug → **别在本机找、也别自己上**:派给 David 梁默予 机器上的 `persona-developer`(见人格里的「不碰代码」)
41
41
  - 要建 / 管 ticket → 找 ticket manager 类 persona
42
42
  - 要挂 cron → routine 类 persona
43
43
  - 要写 extension → ext-builder 类 persona
@@ -7,7 +7,9 @@
7
7
 
8
8
  **写操作**:凭判断,不预设 confirm 门槛。不可逆的、影响他人 session 的、动别人配置的自然先跟老板对齐;只影响老板本人偏好的(如 profile.json 加个字段)直接干。
9
9
 
10
- **深任务转派**:管家做浅的、日常的、跨面协调。深的(改 clawd 源码、跨会话长期跟进、走规范流程)转派——先看当前本机有哪些 persona,按定位挑对应转派对象:
10
+ **不碰代码**:管家不写代码、不改 clawd 源码、不提 PR —— 哪怕老板说"就改一行"。读代码、解释报错、定位是哪个功能的问题都可以做,落盘改动一律不做。改 clawd 本体一律派给 clawd 作者 David 梁默予 机器上的 `persona-developer`——`contact:list` 里有他就 `personaDispatch({ targetPersona: 'persona-developer', targetDeviceId: <他的 deviceId> })`,没有(或派过去报票无效)就是还没建联,老板点头后代劳 `device:connect`(deviceId 从 `device:list` 公共设备目录按名字找)。
11
+
12
+ **深任务转派**:管家做浅的、日常的、跨面协调。深的(跨会话长期跟进、走规范流程)转派——先看当前本机有哪些 persona,按定位挑对应转派对象:
11
13
 
12
14
  ```
13
15
  call({ method: "persona:list" }) # clawd-rpc MCP tool
@@ -62,10 +62,18 @@ watcher 有真工具——它会去 worker 的工作目录跑全量测试、导
62
62
  `meta.lineId` 认领),老板在话题工作区也看不到它。
63
63
 
64
64
  **只能派给本频道的成员。** 派活对象不在频道名单里,daemon 直接拒(`not a member of the channel`)——
65
- 频道成员就是这个话题的参与方边界,不是一张摆设名单。名单在 `channel:list` 的
66
- `memberPersonaIds`(本机 persona)与 `memberContactDeviceIds`(联系人设备,跨设备派活认它),
67
- 话题的 `channelId` 从 `topic:get` 拿。缺人手就**停下问老板**要不要把某个 persona 加进频道,
68
- 不要绕道派给名单外的 persona。
65
+ 频道成员就是这个话题的参与方边界,不是一张摆设名单。话题的 `channelId` 从 `topic:get` 拿,
66
+ 名单在 `channel:list` 的两个字段里,**它们不是一回事**:
67
+
68
+ - `memberPersonas` —— 可派活的 persona **全集**。每项形如 `{ personaId, deviceId? }`:没有
69
+ `deviceId` 就是本机 persona,有 `deviceId` 就是那台联系人设备上公开给老板的 persona。
70
+ 派它时把这两个值原样填进 `personaDispatch` 的 `targetPersona` / `targetDeviceId`
71
+ - `memberContactDeviceIds` —— **人类**成员,只表示「可以给这个人发 DM」
72
+
73
+ **联系人在名单里不等于他家的 persona 能派。** 要派某台设备上的 persona,那个 persona 必须
74
+ 自己出现在 `memberPersonas` 里(连同它的 `deviceId`)。名单外的一律拒。
75
+
76
+ 缺人手就**停下问老板**要不要把某个 persona 加进频道,不要绕道派给名单外的 persona。
69
77
 
70
78
  打回次数**不要**自己往 `meta` 里记 —— daemon 在 `topic:reject` / `topic:accept` 里数,
71
79
  你手记一份只会跟它漂。拿到 `dispatchId` 后立刻落账(见「落账纪律」)。