@clawos-dev/clawd 0.2.402 → 0.2.404

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
@@ -6863,7 +6863,8 @@ var init_topic = __esm({
6863
6863
  TopicResultSchema = external_exports.object({ topic: TopicSchema });
6864
6864
  TopicDeleteResultSchema = external_exports.object({
6865
6865
  ok: external_exports.literal(true),
6866
- removedMasterSessionId: external_exports.string().min(1).nullable()
6866
+ removedMasterSessionId: external_exports.string().min(1).nullable(),
6867
+ removedWorkerSessionIds: external_exports.array(external_exports.string().min(1))
6867
6868
  });
6868
6869
  }
6869
6870
  });
@@ -7073,7 +7074,21 @@ var init_persona_schemas = __esm({
7073
7074
  }).optional()
7074
7075
  });
7075
7076
  PersonaInfoResponseSchema = PersonaFileSchema.extend({
7077
+ // 人格 builtin 层:`<personaDir>/CLAUDE.md`(= AGENTS.md 镜像)。预装 persona 的这份是
7078
+ // daemon ship 的产品规格,每次 daemon 启动被覆盖回 bundle 版(personalityManaged 标记它)。
7076
7079
  personality: external_exports.string().optional(),
7080
+ // 人格补充层:`<personaDir>/.clawd/personality.local.md`。**与 builtin 层同性质——都是这个
7081
+ // persona 的配置,只是所有权不同**:builtin 归 daemon、随版本更新覆盖;这一层归 persona 的
7082
+ // 主人,不在 DAEMON_MANAGED_PATHS 里,OTA / daemon 重启都不碰。两层叠加生效(人格上冲突时
7083
+ // 以补充层为准),经 SpawnContext.extraSystemPrompt 注入,claude / codex 两端同源,
7084
+ // **owner / guest 会话一律注入**(它是 persona 属性不是私人偏好,同 builtin 人格的待遇)。
7085
+ // 仅 'persona:get' 携带;'' = 没写补充(与 personality 同口径,不用 null)。
7086
+ personalityLocal: external_exports.string().optional(),
7087
+ // builtin 层是不是 daemon 托管:true = 预装 persona,CLAUDE.md 是产品规格、用户改了会被
7088
+ // 下次启动覆盖 → UI 渲染成只读,编辑落到 personalityLocal;false = 用户自建 persona,
7089
+ // 它的 CLAUDE.md daemon 不碰,照旧直接编辑。派生自 daemon 的预装名单,**只读不可回写**。
7090
+ // 仅 'persona:get' 携带。
7091
+ personalityManaged: external_exports.boolean().optional(),
7077
7092
  // 仅 'persona:get' 携带;null = 文件不存在 / parse 失败;undefined = 不在该响应类型上
7078
7093
  skills: external_exports.array(PersonaSkillSummarySchema).optional(),
7079
7094
  // 仅 'persona:get' 携带;空数组 = 没启用任何插件 / settings.json 不存在;undefined = 不在该响应类型上
@@ -7105,6 +7120,9 @@ var init_persona_schemas = __esm({
7105
7120
  effort: external_exports.string().optional(),
7106
7121
  tool: external_exports.string().optional(),
7107
7122
  personality: external_exports.string().optional(),
7123
+ // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
7124
+ // 各层、互不影响;'' = 清空补充层。预装 persona 的编辑入口写的就是这个字段。
7125
+ personalityLocal: external_exports.string().optional(),
7108
7126
  public: external_exports.boolean().optional(),
7109
7127
  // pass `null` to clear; daemon strips the field from PersonaFile on null
7110
7128
  iconKey: external_exports.string().nullable().optional()
@@ -8584,7 +8602,7 @@ var init_methods = __esm({
8584
8602
  args: TopicRenameArgsSchema
8585
8603
  },
8586
8604
  "topic:delete": {
8587
- summary: "\u5220\u8BDD\u9898\uFF1A\u8FDE\u5E26\u5220\u5B83\u7684 Master session\uFF081:1\uFF09\uFF0C\u8BE5\u8BDD\u9898\u6D3E\u51FA\u53BB\u7684 worker session \u4FDD\u7559\u3002Master session \u5DF2\u4E0D\u5728\u65F6\u8BDD\u9898\u7167\u5220\uFF0CremovedMasterSessionId \u56DE null",
8605
+ summary: "\u5220\u8BDD\u9898\uFF1A\u8FDE\u5E26\u5220\u8FD9\u4E2A\u8BDD\u9898\u540D\u4E0B\u7684\u5168\u90E8\u4F1A\u8BDD\u2014\u2014\u5B83\u7684 Master session\uFF081:1\uFF09+ \u5B83\u6D3E\u51FA\u53BB\u7684\u6BCF\u4E00\u6761 worker session\uFF1Bdispatch \u53F0\u8D26\u884C\u4FDD\u7559\u3002\u4F1A\u8BDD\u5DF2\u4E0D\u5728\u65F6\u8BDD\u9898\u7167\u5220\uFF0CMaster \u90A3\u6761\u56DE removedMasterSessionId: null\uFF0Cworker \u90A3\u4E9B\u4E0D\u8FDB removedWorkerSessionIds",
8588
8606
  args: TopicDeleteArgsSchema
8589
8607
  },
8590
8608
  // ---- master:*(授权查表,Master 经 clawd-rpc 桥调)----
@@ -44979,6 +44997,24 @@ var FILE_LINK_HINT = `## \u8BA9\u7528\u6237\u53BB\u770B\u6587\u4EF6\u65F6\u7ED9\
44979
44997
  \u8BF7\u7528\u6237\u67E5\u770B\u67D0\u4E2A\u6587\u4EF6\u65F6\uFF0C\u7528 markdown \u94FE\u63A5\u7ED9**\u7EDD\u5BF9\u8DEF\u5F84**\uFF1A\`[\u8BF4\u660E.md](/Users/xxx/project/\u8BF4\u660E.md)\`\u3002
44980
44998
  \u53EA\u7ED9\u6587\u4EF6\u540D\u6216\u76F8\u5BF9\u8DEF\u5F84\u7528\u6237\u70B9\u4E0D\u5F00\u4E5F\u627E\u4E0D\u5230\u3002`;
44981
44999
 
45000
+ // src/persona/local-personality-prompt.ts
45001
+ var LOCAL_PERSONALITY_HEADER = `## \u4EBA\u683C\u8865\u5145\uFF08\u8FD9\u4E2A persona \u7684\u8865\u5145\u8BBE\u5B9A\uFF09
45002
+
45003
+ \u4EE5\u4E0B\u662F\u8FD9\u4E2A persona \u7684\u8865\u5145\u8BBE\u5B9A\uFF0C\u7531\u5B83\u7684\u4E3B\u4EBA\u7EF4\u62A4\uFF0C\u4E0E CLAUDE.md / AGENTS.md \u91CC\u90A3\u4EFD\u5185\u7F6E\u4EBA\u683C\u53E0\u52A0\u751F\u6548\u3002
45004
+ **\u53EA\u8986\u76D6\u4EBA\u683C**\u2014\u2014\u8DDF\u4EBA\u683C\u6709\u5173\u7684\u5730\u65B9\u4EE5\u672C\u6BB5\u4E3A\u51C6\uFF1B\u4E0A\u9762\u90A3\u4E9B\u7531 clawd \u6CE8\u5165\u7684\u64CD\u4F5C\u89C4\u5219\uFF08\u600E\u4E48\u59D4\u6D3E\u3001
45005
+ \u600E\u4E48\u5206\u4EAB\u6587\u4EF6\u3001\u600E\u4E48\u7ED9\u6587\u4EF6\u94FE\u63A5\u3001\u7528\u4E0D\u7528\u9009\u62E9\u5361\u7247\uFF09\u4E0D\u5728\u672C\u6BB5\u7684\u8986\u76D6\u8303\u56F4\u5185\uFF0C\u7167\u65E7\u6267\u884C\u3002
45006
+
45007
+ \u672C\u6BB5\u8DDF\u5185\u7F6E\u4EBA\u683C\u540C\u6027\u8D28\uFF0C\u662F\u8FD9\u4E2A persona \u914D\u7F6E\u7684\u4E00\u90E8\u5206\uFF1A**owner \u4F1A\u8BDD\u4E0E\u8BBF\u5BA2\uFF08guest\uFF09\u4F1A\u8BDD\u4E00\u5F8B\u90FD\u751F\u6548**\u3002
45008
+ \u8981\u5BF9\u4E0D\u540C\u5BF9\u8C61\u6709\u4E0D\u540C\u8868\u73B0\uFF0C\u7531\u5199\u7684\u4EBA\u5728\u6B63\u6587\u91CC\u663E\u5F0F\u5199\u660E\uFF08\u4F8B\u5982\u300C\u9762\u5BF9\u8BBF\u5BA2\u65F6\u4E0D\u8981\u8FD9\u6837\u79F0\u547C\u300D\uFF09\uFF1B\u6CA1\u5199\u660E\u7684
45009
+ \u5C31\u6309\u4E0A\u9762\u7684\u8FDE\u63A5\u8EAB\u4EFD\u81EA\u884C\u62FF\u634F\uFF0C\u522B\u628A\u5BF9\u7740\u4E3B\u4EBA\u8BF4\u7684\u8BDD\u539F\u6837\u5957\u5230\u8BBF\u5BA2\u5934\u4E0A\u3002`;
45010
+ function buildLocalPersonalityPrompt(body) {
45011
+ const trimmed = body?.trim();
45012
+ if (!trimmed) return null;
45013
+ return `${LOCAL_PERSONALITY_HEADER}
45014
+
45015
+ ${trimmed}`;
45016
+ }
45017
+
44982
45018
  // src/dispatch/system-prompt.ts
44983
45019
  var DISPATCH_SYSTEM_PROMPT_HINT = `## \u59D4\u6D3E\u7ED9\u53E6\u4E00\u4E2A persona\uFF08dispatch\uFF09
44984
45020
 
@@ -45218,6 +45254,12 @@ function buildSpawnContext(state, deps) {
45218
45254
  if (meta?.personaMode) {
45219
45255
  ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + FILE_LINK_HINT;
45220
45256
  }
45257
+ if (personaId2) {
45258
+ const localSegment = buildLocalPersonalityPrompt(deps.readPersonalityLocal?.(personaId2));
45259
+ if (localSegment) {
45260
+ ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + localSegment;
45261
+ }
45262
+ }
45221
45263
  if (meta?.extraSettings) {
45222
45264
  ctx.extraSettings = meta.extraSettings;
45223
45265
  }
@@ -46083,6 +46125,9 @@ var SessionRunner = class {
46083
46125
  // daemon 级 MCP server 列表闭包透传;见 reducer.buildSpawnContext 派生
46084
46126
  // SpawnContext.mcpConfigs + 按 DISPATCH_MCP_ID 判断 dispatch 教学注入
46085
46127
  getMcpConfigs: this.hooks.getMcpConfigs,
46128
+ // 人格补充层读闭包透传;reducer.buildSpawnContext 按 SessionFile.ownerPersonaId 查,
46129
+ // 有内容就作为最后一段追加进 extraSystemPrompt
46130
+ readPersonalityLocal: this.hooks.readPersonalityLocal,
46086
46131
  lookupDispatchByBSessionId: this.hooks.lookupDispatchByBSessionId,
46087
46132
  // ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
46088
46133
  mode: this.hooks.mode,
@@ -46928,6 +46973,9 @@ var SessionManager = class {
46928
46973
  // daemon 级 MCP server 列表闭包透传给 reducer,cc spawn 逐项加 --mcp-config flag;
46929
46974
  // B session 的 CLAWD_DISPATCH_ID env 由下方 lookupDispatchByBSessionId 反查闭包负责。
46930
46975
  getMcpConfigs: () => this.deps.mcpConfigs ?? [],
46976
+ // 人格补充层:只把 store 的这一个读方法窄化成闭包给 runner/reducer(不把整个 personaStore
46977
+ // 递下去——in-place mention 注入那次的教训,见上面那条注释)。缺 store = 不注补充段。
46978
+ readPersonalityLocal: this.deps.personaStore ? (id) => this.deps.personaStore?.readPersonalityLocal(id) ?? null : void 0,
46931
46979
  lookupDispatchByBSessionId: this.deps.personaDispatchManager ? (bSid) => this.deps.personaDispatchManager?.lookupDispatchByBSessionId(bSid) : void 0,
46932
46980
  // file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
46933
46981
  onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
@@ -49010,6 +49058,21 @@ var PersonaStore = class {
49010
49058
  sandboxSettingsPath(personaId2) {
49011
49059
  return path15.join(this.personaDir(personaId2), ".clawd", "sandbox-settings.json");
49012
49060
  }
49061
+ /**
49062
+ * 人格补充层落盘路径 —— `<personaDir>/.clawd/personality.local.md`。
49063
+ *
49064
+ * 位置不是随手挑的,两条既有约定叠出来的:`.clawd/` 是 daemon 声明「不动」的 persona 私有区
49065
+ * (不在 seed.ts 的 DAEMON_MANAGED_PATHS 里),`.local` 是仓内已成立的「本机个人 override
49066
+ * 永不被 OTA 覆盖」后缀(见 seed.refreshDeployKit)。于是内置 persona 的 CLAUDE.md 继续被
49067
+ * refresh 覆盖成 bundle 新版、用户写的这层原样活着。
49068
+ *
49069
+ * 同 sandbox-settings.json 的理由:放 `.clawd/` 而非 persona 根目录,让 CC 的 project
49070
+ * setting source 自动发现扫不到——这层只经 SpawnContext.extraSystemPrompt 注入
49071
+ * (见 local-personality-prompt.ts),通道唯一,不会被当第二份 CLAUDE.md 重复加载。
49072
+ */
49073
+ personalityLocalPath(personaId2) {
49074
+ return path15.join(this.personaDir(personaId2), ".clawd", "personality.local.md");
49075
+ }
49013
49076
  write(persona, personality) {
49014
49077
  const dir = this.personaDir(persona.personaId);
49015
49078
  fs12.mkdirSync(path15.join(dir, ".clawd"), { recursive: true });
@@ -49022,6 +49085,25 @@ var PersonaStore = class {
49022
49085
  this.atomicWrite(this.claudeMdPath(personaId2), personality);
49023
49086
  this.atomicWrite(this.agentsMdPath(personaId2), personality);
49024
49087
  }
49088
+ /** 读人格补充层正文;文件不存在返回 null(= 该 persona 没有补充,走内置人格)。 */
49089
+ readPersonalityLocal(personaId2) {
49090
+ const p2 = this.personalityLocalPath(personaId2);
49091
+ if (!fs12.existsSync(p2)) return null;
49092
+ return fs12.readFileSync(p2, "utf8");
49093
+ }
49094
+ /**
49095
+ * 写人格补充层。**纯空白 = 清空**:删文件而不是留个空文件——「有没有补充」的唯一判据
49096
+ * 是文件在不在,留空文件会让下游多一种要判空的状态。persona 目录还没建时自建 `.clawd/`。
49097
+ */
49098
+ writePersonalityLocal(personaId2, body) {
49099
+ const p2 = this.personalityLocalPath(personaId2);
49100
+ if (!body.trim()) {
49101
+ fs12.rmSync(p2, { force: true });
49102
+ return;
49103
+ }
49104
+ fs12.mkdirSync(path15.dirname(p2), { recursive: true });
49105
+ this.atomicWrite(p2, body);
49106
+ }
49025
49107
  /**
49026
49108
  * 历史 persona 补 AGENTS.md:人格双写镜像是 codex 适配后才加的,更早落盘的 persona 只有
49027
49109
  * CLAUDE.md。codex 原生读 cwd 的 AGENTS.md,缺失则切到 codex 后人格丢失。daemon 启动跑一次
@@ -49547,7 +49629,7 @@ var PersonaManager = class {
49547
49629
  this.deps.registry.set(persona);
49548
49630
  return persona;
49549
49631
  }
49550
- update(personaId2, patch, personality) {
49632
+ update(personaId2, patch, texts) {
49551
49633
  const existing = this.deps.registry.get(personaId2);
49552
49634
  if (!existing) throw new Error(`persona not found: ${personaId2}`);
49553
49635
  const { iconKey: iconKeyPatch, ...restPatch } = patch;
@@ -49566,8 +49648,11 @@ var PersonaManager = class {
49566
49648
  } else if (iconKeyPatch !== void 0) {
49567
49649
  updated.iconKey = iconKeyPatch;
49568
49650
  }
49569
- if (personality !== void 0) {
49570
- this.deps.store.writePersonality(personaId2, personality);
49651
+ if (texts?.personality !== void 0) {
49652
+ this.deps.store.writePersonality(personaId2, texts.personality);
49653
+ }
49654
+ if (texts?.personalityLocal !== void 0) {
49655
+ this.deps.store.writePersonalityLocal(personaId2, texts.personalityLocal);
49571
49656
  }
49572
49657
  this.deps.store.writeMeta(updated);
49573
49658
  this.deps.registry.set(updated);
@@ -49577,6 +49662,10 @@ var PersonaManager = class {
49577
49662
  readPersonality(personaId2) {
49578
49663
  return this.deps.store.readPersonality(personaId2);
49579
49664
  }
49665
+ /** 读人格补充层(`.clawd/personality.local.md`);没写过返回 null。供 persona:get 拼响应。 */
49666
+ readPersonalityLocal(personaId2) {
49667
+ return this.deps.store.readPersonalityLocal(personaId2);
49668
+ }
49580
49669
  /** 扫 persona 私有 skills 目录;目录不存在返回空数组。供 persona:get handler 拼响应。 */
49581
49670
  listSkills(personaId2) {
49582
49671
  return listSkillsForDir(this.deps.store.skillsDir(personaId2), "project");
@@ -49936,6 +50025,34 @@ function refreshDaemonManagedDirs(args) {
49936
50025
  }
49937
50026
  }
49938
50027
  }
50028
+ function isDaemonManagedPersonality(personaId2, entries = DEFAULT_PERSONAS) {
50029
+ return entries.some((e) => e.personaId === personaId2);
50030
+ }
50031
+ function migratePersonalityOverlay(args) {
50032
+ const entries = args.entries ?? DEFAULT_PERSONAS;
50033
+ for (const entry of entries) {
50034
+ const personaId2 = entry.personaId;
50035
+ try {
50036
+ if (fs14.existsSync(args.store.personalityLocalPath(personaId2))) continue;
50037
+ const bundleClaudeMd = path17.join(args.defaultsRoot, personaId2, "CLAUDE.md");
50038
+ if (!fs14.existsSync(bundleClaudeMd)) continue;
50039
+ const current = args.store.readPersonality(personaId2);
50040
+ if (current === null) continue;
50041
+ const bundle = fs14.readFileSync(bundleClaudeMd, "utf8");
50042
+ if (current === bundle) continue;
50043
+ if (!current.startsWith(bundle)) continue;
50044
+ const tail = current.slice(bundle.length).trim();
50045
+ if (!tail) continue;
50046
+ args.store.writePersonalityLocal(personaId2, tail);
50047
+ args.logger.info("persona.personality.overlay-migrated", { personaId: personaId2, chars: tail.length });
50048
+ } catch (err) {
50049
+ args.logger.warn("persona.personality.overlay-migrate.failed", {
50050
+ personaId: personaId2,
50051
+ error: err instanceof Error ? err.message : String(err)
50052
+ });
50053
+ }
50054
+ }
50055
+ }
49939
50056
  function refreshSandboxSettings(args) {
49940
50057
  const entries = args.entries ?? DEFAULT_PERSONAS;
49941
50058
  for (const entry of entries) {
@@ -62188,7 +62305,7 @@ function computeMethodAccess(args) {
62188
62305
  }
62189
62306
 
62190
62307
  // src/version.ts
62191
- var version = "0.2.402".length > 0 ? "0.2.402" : "dev";
62308
+ var version = "0.2.404".length > 0 ? "0.2.404" : "dev";
62192
62309
 
62193
62310
  // src/cli-probe/probe.ts
62194
62311
  var fs66 = __toESM(require("fs"), 1);
@@ -62347,6 +62464,8 @@ function buildPersonaHandlers(deps) {
62347
62464
  );
62348
62465
  }
62349
62466
  const personality = personaManager.readPersonality(args.personaId) ?? "";
62467
+ const personalityLocal = personaManager.readPersonalityLocal(args.personaId) ?? "";
62468
+ const personalityManaged = isDaemonManagedPersonality(args.personaId);
62350
62469
  const skills = personaManager.listSkills(args.personaId);
62351
62470
  const plugins = personaManager.listEnabledPlugins(args.personaId);
62352
62471
  const sandboxSettings = personaManager.readSandboxSettings(args.personaId);
@@ -62355,6 +62474,8 @@ function buildPersonaHandlers(deps) {
62355
62474
  type: "persona:info",
62356
62475
  ...persona,
62357
62476
  personality,
62477
+ personalityLocal,
62478
+ personalityManaged,
62358
62479
  skills,
62359
62480
  plugins,
62360
62481
  sandboxSettings
@@ -62364,8 +62485,8 @@ function buildPersonaHandlers(deps) {
62364
62485
  const update = async (frame) => {
62365
62486
  const { type: _type, requestId: _requestId, ...rest } = frame;
62366
62487
  const args = parseArgs2(PersonaUpdateArgsSchema, frame, "persona:update");
62367
- const { personality, ...metaPatch } = args.patch;
62368
- const persona = personaManager.update(args.personaId, metaPatch, personality);
62488
+ const { personality, personalityLocal, ...metaPatch } = args.patch;
62489
+ const persona = personaManager.update(args.personaId, metaPatch, { personality, personalityLocal });
62369
62490
  return { response: { type: "persona:info", ...persona } };
62370
62491
  };
62371
62492
  const del = async (frame) => {
@@ -64335,6 +64456,9 @@ function buildShiftHandlers(deps) {
64335
64456
 
64336
64457
  // src/handlers/channel.ts
64337
64458
  init_protocol();
64459
+ function withMaster(ids) {
64460
+ return ids.includes(MASTER_PERSONA_ID) ? ids : [MASTER_PERSONA_ID, ...ids];
64461
+ }
64338
64462
  function buildChannelHandlers(deps) {
64339
64463
  return {
64340
64464
  "channel:list": async () => ({
@@ -64344,7 +64468,7 @@ function buildChannelHandlers(deps) {
64344
64468
  const args = ChannelCreateArgsSchema.parse(frame);
64345
64469
  const channel = deps.channelStore.create({
64346
64470
  name: args.name,
64347
- memberPersonaIds: args.memberPersonaIds,
64471
+ memberPersonaIds: withMaster(args.memberPersonaIds),
64348
64472
  memberContactDeviceIds: args.memberContactDeviceIds
64349
64473
  });
64350
64474
  return { response: ChannelResultSchema.parse({ channel }) };
@@ -64353,7 +64477,8 @@ function buildChannelHandlers(deps) {
64353
64477
  const args = ChannelUpdateArgsSchema.parse(frame);
64354
64478
  const channel = deps.channelStore.update(args.channelId, {
64355
64479
  ...args.name !== void 0 ? { name: args.name } : {},
64356
- ...args.memberPersonaIds !== void 0 ? { memberPersonaIds: args.memberPersonaIds } : {},
64480
+ // 改名单同样过 withMaster:编辑路径把 Master 摘掉,效果跟建一个没 Master 的频道一样
64481
+ ...args.memberPersonaIds !== void 0 ? { memberPersonaIds: withMaster(args.memberPersonaIds) } : {},
64357
64482
  ...args.memberContactDeviceIds !== void 0 ? { memberContactDeviceIds: args.memberContactDeviceIds } : {}
64358
64483
  });
64359
64484
  if (!channel) {
@@ -64419,6 +64544,16 @@ function buildTopicHandlers(deps) {
64419
64544
  }
64420
64545
  return joinLines(records, t.lineMeta);
64421
64546
  };
64547
+ const workerSessionIdsOf = (masterSessionId) => {
64548
+ const ids = /* @__PURE__ */ new Set();
64549
+ for (const r of deps.dispatchStore.list({ sourceSessionId: masterSessionId })) {
64550
+ if (typeof r.workerSessionId === "string" && r.workerSessionId) {
64551
+ ids.add(r.workerSessionId);
64552
+ }
64553
+ }
64554
+ ids.delete(masterSessionId);
64555
+ return [...ids];
64556
+ };
64422
64557
  return {
64423
64558
  "topic:list": async (frame) => {
64424
64559
  const { channelId } = TopicListArgsSchema.parse(frame);
@@ -64555,14 +64690,30 @@ function buildTopicHandlers(deps) {
64555
64690
  "topic:delete": async (frame) => {
64556
64691
  const { topicId } = TopicDeleteArgsSchema.parse(frame);
64557
64692
  const topic = mustGet(topicId);
64558
- const { removed, broadcast } = deps.deleteMasterSession(topic.masterSessionId);
64693
+ const { removed, broadcast } = deps.deleteSession(topic.masterSessionId);
64694
+ const removedWorkerSessionIds = [];
64695
+ const allBroadcast = [...broadcast];
64696
+ for (const sessionId of workerSessionIdsOf(topic.masterSessionId)) {
64697
+ try {
64698
+ const w2 = deps.deleteSession(sessionId);
64699
+ allBroadcast.push(...w2.broadcast);
64700
+ if (w2.removed) removedWorkerSessionIds.push(sessionId);
64701
+ } catch (err) {
64702
+ deps.logger?.warn("topic.delete.worker-failed", {
64703
+ topicId,
64704
+ sessionId,
64705
+ error: err instanceof Error ? err.message : String(err)
64706
+ });
64707
+ }
64708
+ }
64559
64709
  deps.topicStore.remove(topicId);
64560
64710
  return {
64561
64711
  response: TopicDeleteResultSchema.parse({
64562
64712
  ok: true,
64563
- removedMasterSessionId: removed ? topic.masterSessionId : null
64713
+ removedMasterSessionId: removed ? topic.masterSessionId : null,
64714
+ removedWorkerSessionIds
64564
64715
  }),
64565
- broadcast
64716
+ broadcast: allBroadcast
64566
64717
  };
64567
64718
  }
64568
64719
  };
@@ -65208,7 +65359,8 @@ function buildMethodHandlers(deps) {
65208
65359
  channelStore: deps.channelStore,
65209
65360
  dispatchStore: deps.dispatchStore,
65210
65361
  createMasterSession: deps.createMasterSession,
65211
- deleteMasterSession: deps.deleteMasterSession
65362
+ deleteSession: deps.deleteSession,
65363
+ logger: deps.logger
65212
65364
  }),
65213
65365
  ...buildShiftHandlers({
65214
65366
  shiftStore: deps.shiftStore,
@@ -66124,6 +66276,7 @@ async function startDaemon(config) {
66124
66276
  const defaultsRoot = findDefaultsRoot(logger);
66125
66277
  if (defaultsRoot) {
66126
66278
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
66279
+ migratePersonalityOverlay({ store: personaStore, defaultsRoot, logger });
66127
66280
  refreshDaemonManagedDirs({ store: personaStore, defaultsRoot, logger });
66128
66281
  } else {
66129
66282
  logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
@@ -66695,9 +66848,10 @@ async function startDaemon(config) {
66695
66848
  }
66696
66849
  return { sessionId, file: response };
66697
66850
  },
66698
- // 删话题连带删 Master session(1:1)。会话已经不在了不算失败——话题仍要删得掉,
66699
- // 所以 SESSION_NOT_FOUND 吞掉并回 removed:false,其余错误照抛(别把真故障说成成功)。
66700
- deleteMasterSession: (sessionId) => {
66851
+ // 删话题连带删这个话题名下的全部会话(Master + 它派出去的 worker),逐条走这里。
66852
+ // 会话已经不在本机了不算失败——话题仍要删得掉,所以 SESSION_NOT_FOUND 吞掉并回
66853
+ // removed:false,其余错误照抛(别把真故障说成成功)。
66854
+ deleteSession: (sessionId) => {
66701
66855
  try {
66702
66856
  const { broadcast } = manager.delete({ sessionId });
66703
66857
  return { removed: true, broadcast };
@@ -22051,7 +22051,8 @@ var TopicAcceptResultSchema = external_exports.object({
22051
22051
  var TopicResultSchema = external_exports.object({ topic: TopicSchema });
22052
22052
  var TopicDeleteResultSchema = external_exports.object({
22053
22053
  ok: external_exports.literal(true),
22054
- removedMasterSessionId: external_exports.string().min(1).nullable()
22054
+ removedMasterSessionId: external_exports.string().min(1).nullable(),
22055
+ removedWorkerSessionIds: external_exports.array(external_exports.string().min(1))
22055
22056
  });
22056
22057
 
22057
22058
  // ../protocol/src/master.ts
@@ -22226,7 +22227,21 @@ var PersonaSandboxSettingsSchema = external_exports.object({
22226
22227
  }).optional()
22227
22228
  });
22228
22229
  var PersonaInfoResponseSchema = PersonaFileSchema.extend({
22230
+ // 人格 builtin 层:`<personaDir>/CLAUDE.md`(= AGENTS.md 镜像)。预装 persona 的这份是
22231
+ // daemon ship 的产品规格,每次 daemon 启动被覆盖回 bundle 版(personalityManaged 标记它)。
22229
22232
  personality: external_exports.string().optional(),
22233
+ // 人格补充层:`<personaDir>/.clawd/personality.local.md`。**与 builtin 层同性质——都是这个
22234
+ // persona 的配置,只是所有权不同**:builtin 归 daemon、随版本更新覆盖;这一层归 persona 的
22235
+ // 主人,不在 DAEMON_MANAGED_PATHS 里,OTA / daemon 重启都不碰。两层叠加生效(人格上冲突时
22236
+ // 以补充层为准),经 SpawnContext.extraSystemPrompt 注入,claude / codex 两端同源,
22237
+ // **owner / guest 会话一律注入**(它是 persona 属性不是私人偏好,同 builtin 人格的待遇)。
22238
+ // 仅 'persona:get' 携带;'' = 没写补充(与 personality 同口径,不用 null)。
22239
+ personalityLocal: external_exports.string().optional(),
22240
+ // builtin 层是不是 daemon 托管:true = 预装 persona,CLAUDE.md 是产品规格、用户改了会被
22241
+ // 下次启动覆盖 → UI 渲染成只读,编辑落到 personalityLocal;false = 用户自建 persona,
22242
+ // 它的 CLAUDE.md daemon 不碰,照旧直接编辑。派生自 daemon 的预装名单,**只读不可回写**。
22243
+ // 仅 'persona:get' 携带。
22244
+ personalityManaged: external_exports.boolean().optional(),
22230
22245
  // 仅 'persona:get' 携带;null = 文件不存在 / parse 失败;undefined = 不在该响应类型上
22231
22246
  skills: external_exports.array(PersonaSkillSummarySchema).optional(),
22232
22247
  // 仅 'persona:get' 携带;空数组 = 没启用任何插件 / settings.json 不存在;undefined = 不在该响应类型上
@@ -22258,6 +22273,9 @@ var PersonaUpdateArgsSchema = external_exports.object({
22258
22273
  effort: external_exports.string().optional(),
22259
22274
  tool: external_exports.string().optional(),
22260
22275
  personality: external_exports.string().optional(),
22276
+ // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
22277
+ // 各层、互不影响;'' = 清空补充层。预装 persona 的编辑入口写的就是这个字段。
22278
+ personalityLocal: external_exports.string().optional(),
22261
22279
  public: external_exports.boolean().optional(),
22262
22280
  // pass `null` to clear; daemon strips the field from PersonaFile on null
22263
22281
  iconKey: external_exports.string().nullable().optional()
@@ -23644,7 +23662,7 @@ var METHOD_DOCS = {
23644
23662
  args: TopicRenameArgsSchema
23645
23663
  },
23646
23664
  "topic:delete": {
23647
- summary: "\u5220\u8BDD\u9898\uFF1A\u8FDE\u5E26\u5220\u5B83\u7684 Master session\uFF081:1\uFF09\uFF0C\u8BE5\u8BDD\u9898\u6D3E\u51FA\u53BB\u7684 worker session \u4FDD\u7559\u3002Master session \u5DF2\u4E0D\u5728\u65F6\u8BDD\u9898\u7167\u5220\uFF0CremovedMasterSessionId \u56DE null",
23665
+ summary: "\u5220\u8BDD\u9898\uFF1A\u8FDE\u5E26\u5220\u8FD9\u4E2A\u8BDD\u9898\u540D\u4E0B\u7684\u5168\u90E8\u4F1A\u8BDD\u2014\u2014\u5B83\u7684 Master session\uFF081:1\uFF09+ \u5B83\u6D3E\u51FA\u53BB\u7684\u6BCF\u4E00\u6761 worker session\uFF1Bdispatch \u53F0\u8D26\u884C\u4FDD\u7559\u3002\u4F1A\u8BDD\u5DF2\u4E0D\u5728\u65F6\u8BDD\u9898\u7167\u5220\uFF0CMaster \u90A3\u6761\u56DE removedMasterSessionId: null\uFF0Cworker \u90A3\u4E9B\u4E0D\u8FDB removedWorkerSessionIds",
23648
23666
  args: TopicDeleteArgsSchema
23649
23667
  },
23650
23668
  // ---- master:*(授权查表,Master 经 clawd-rpc 桥调)----