@clawos-dev/clawd 0.2.255 → 0.2.257

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
@@ -6238,7 +6238,13 @@ var init_dispatch = __esm({
6238
6238
  // 合法值集与 claude adapter capabilities.models[].id 一致(见 daemon
6239
6239
  // tools/claude.ts CLAUDE_MODEL_IDS);handler 层做二次校验,非法值 fail-fast。
6240
6240
  // protocol 层保持 string 便于未来跨 tool 扩展(届时 union 收合法字面量)。
6241
- model: external_exports.string().min(1).optional()
6241
+ model: external_exports.string().min(1).optional(),
6242
+ // 可选:给新 worker session 起 label(写入 SessionFile.label,sidebar 显示)。
6243
+ // 仅在**新开 dispatch**(dispatchId 缺席)时生效;续派(dispatchId 非空)
6244
+ // 时 daemon 层静默丢弃(避免半途改 label 造成 sidebar 抖动)。
6245
+ // 上限 60 字符——sidebar 显示 tolerance,卡在 dispatch 入口即可。
6246
+ // 协议层不硬拒 sessionLabel + dispatchId 同传,保留 forward-compat。
6247
+ sessionLabel: external_exports.string().min(1).max(60).optional()
6242
6248
  }).refine((v2) => v2.targetPersona !== void 0 || v2.dispatchId !== void 0, {
6243
6249
  message: "either targetPersona (new dispatch) or dispatchId (follow-up) is required"
6244
6250
  }).refine((v2) => !(v2.dispatchId !== void 0 && v2.targetDeviceId !== void 0), {
@@ -40768,6 +40774,7 @@ init_protocol();
40768
40774
  var DEFAULT_PORT = 18790;
40769
40775
  var DEFAULT_HOST = "127.0.0.1";
40770
40776
  var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
40777
+ var DEFAULT_OWNER_IDLE_KILL_DELAY_MS = 6 * 60 * 60 * 1e3;
40771
40778
  var DEFAULT_LOG_ENDPOINT = "https://clawd-prod.cn-hangzhou.log.aliyuncs.com/logstores/app-logs/track";
40772
40779
  function resolveLogShipping(raw, cliNoShipping) {
40773
40780
  if (cliNoShipping) {
@@ -40782,6 +40789,12 @@ function resolveLogShipping(raw, cliNoShipping) {
40782
40789
  const sampling = mode === "errors-only" ? { debug: 0, info: 0, warn: 1, error: 1 } : isPlainSamplingMap(raw?.sampling) ? raw.sampling : {};
40783
40790
  return { mode, endpoint, sampling };
40784
40791
  }
40792
+ function resolveIdleKillMs(raw) {
40793
+ if (raw === void 0 || raw === null || raw === "") return null;
40794
+ const n = typeof raw === "number" ? raw : Number(raw);
40795
+ if (!Number.isFinite(n) || n < 0) return null;
40796
+ return n;
40797
+ }
40785
40798
  function isPlainSamplingMap(v2) {
40786
40799
  if (!v2 || typeof v2 !== "object") return false;
40787
40800
  return Object.values(v2).every((n) => typeof n === "number");
@@ -40879,6 +40892,7 @@ function resolveConfig(opts) {
40879
40892
  const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
40880
40893
  const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
40881
40894
  const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
40895
+ const ownerIdleKillDelayMs = resolveIdleKillMs(env.CLAWD_OWNER_IDLE_KILL_DELAY_MS) ?? resolveIdleKillMs(fileCfg.ownerIdleKillDelayMs) ?? DEFAULT_OWNER_IDLE_KILL_DELAY_MS;
40882
40896
  const logShipping = resolveLogShipping(
40883
40897
  fileCfg.logShipping,
40884
40898
  args.noLogShipping ?? false
@@ -40896,7 +40910,8 @@ function resolveConfig(opts) {
40896
40910
  frpcBinary,
40897
40911
  mode,
40898
40912
  previewPorts,
40899
- logShipping
40913
+ logShipping,
40914
+ ownerIdleKillDelayMs
40900
40915
  };
40901
40916
  }
40902
40917
  var HELP_TEXT = `clawd [options]
@@ -40923,6 +40938,10 @@ Env (advanced):
40923
40938
  CLAWD_CC_MODE CC \u5B50\u8FDB\u7A0B\u4EA4\u4E92\u6A21\u5F0F\uFF1A'sdk'\uFF08\u9ED8\u8BA4\uFF0C--print stream-json\uFF09/ 'tui'\uFF08pty \u4EA4\u4E92\u5F0F\uFF09\uFF1B
40924
40939
  \u503C\u975E\u5408\u6CD5\u5B57\u9762\u91CF\u65F6\u9759\u9ED8\u56DE\u9000 'sdk'\u3002ready \u5E27 mode \u5B57\u6BB5\u900F\u4F20\u7ED9 UI\uFF1B
40925
40940
  'tui' \u6A21\u5F0F\u989D\u5916\u63A8 session:control + session:pty \u4E24\u4E2A\u5E27\uFF08\u4E1A\u52A1\u5E27\u540D\u4E0D\u53D8\uFF09
40941
+ CLAWD_OWNER_IDLE_KILL_DELAY_MS
40942
+ owner persona session \u95F2\u7F6E\u56DE\u6536\u515C\u5E95\u4E0A\u9650\uFF08ms\uFF0C\u9ED8\u8BA4 21600000 = 6h\uFF09\uFF1B
40943
+ \u8BBE 0 \u5173\u95ED\u56DE\u6536\u3002\u4E5F\u53EF\u5199 config.json \u7684 ownerIdleKillDelayMs\u3002
40944
+ guest session \u7684 10min \u662F\u72EC\u7ACB\u5EF6\u8FDF\uFF0C\u4E0D\u53D7\u6B64\u9879\u5F71\u54CD
40926
40945
  `;
40927
40946
 
40928
40947
  // src/index.ts
@@ -41406,12 +41425,14 @@ function scopeSubPath(scope) {
41406
41425
  if (scope.mode === "guest") return [scope.personaId, "guests", scope.capId];
41407
41426
  return [scope.personaId, scope.mode];
41408
41427
  }
41409
- function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName, callerFeishuUnionId) {
41428
+ var GUEST_IDLE_KILL_DELAY_MS = 6e5;
41429
+ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDisplayName, callerFeishuUnionId, opts) {
41410
41430
  if (scope.kind === "default") return void 0;
41431
+ const ownerDelay = opts?.ownerIdleKillDelayMs !== void 0 && opts.ownerIdleKillDelayMs > 0 ? opts.ownerIdleKillDelayMs : void 0;
41411
41432
  if (scope.mode === "owner") {
41412
41433
  if (!ownerPrincipalId || !usersRoot) {
41413
41434
  return {
41414
- idleKillEnabled: false,
41435
+ idleKillDelayMs: ownerDelay,
41415
41436
  personaMode: "owner",
41416
41437
  callerDisplayName,
41417
41438
  callerDeviceId: ownerPrincipalId,
@@ -41420,7 +41441,7 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
41420
41441
  }
41421
41442
  const userWorkDir2 = deriveUserWorkDir(ownerPrincipalId, usersRoot);
41422
41443
  return {
41423
- idleKillEnabled: false,
41444
+ idleKillDelayMs: ownerDelay,
41424
41445
  personaMode: "owner",
41425
41446
  userWorkDir: userWorkDir2,
41426
41447
  addDirs: [userWorkDir2],
@@ -41431,7 +41452,7 @@ function metaFromScope(scope, personaRoot, usersRoot, ownerPrincipalId, callerDi
41431
41452
  }
41432
41453
  const userWorkDir = deriveUserWorkDir(scope.capId, usersRoot);
41433
41454
  return {
41434
- idleKillEnabled: true,
41455
+ idleKillDelayMs: GUEST_IDLE_KILL_DELAY_MS,
41435
41456
  personaMode: "guest",
41436
41457
  userWorkDir,
41437
41458
  addDirs: [userWorkDir],
@@ -41932,20 +41953,23 @@ var ATTACHMENT_SHARING_HINT = `## \u628A\u4EA7\u51FA\u6587\u4EF6\u5206\u4EAB\u7E
41932
41953
  - \u53EA\u80FD\u5206\u4EAB\u4F60\u81EA\u5DF1\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u6587\u4EF6\uFF0C\u8D8A\u754C\u4F1A\u88AB\u62D2\u3002
41933
41954
  - \u88AB dispatch \u63A5\u5230\u4EFB\u52A1\u3001\u8981\u56DE\u4F20\u4EA7\u7269\u65F6\uFF1A\u628A\u7B7E\u540D URL \u5199\u8FDB \`personaDispatchComplete\` tool\uFF08\`clawd-dispatch\` MCP\uFF09\u7684 \`text\` \u91CC\uFF08\`filePaths\` \u53EA\u662F\u4F60\u672C\u673A\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u8DE8\u8BBE\u5907\u5BF9\u65B9\u6253\u4E0D\u5F00\uFF0CURL \u624D\u80FD\u6253\u5F00\uFF09\u3002`;
41934
41955
 
41956
+ // src/persona/no-ask-user-question-prompt.ts
41957
+ var NO_ASK_USER_QUESTION_HINT = `## \u9700\u8981\u8001\u677F\u62CD\u677F\u65F6\u600E\u4E48\u95EE
41958
+
41959
+ \u8981\u8001\u677F\u9009\u62E9 / \u786E\u8BA4 / \u62CD\u677F\u65F6\uFF0C\u76F4\u63A5\u6B63\u6587\u5199\u95EE\u9898 + \u5217 A/B/C \u9009\u9879\u3002\u4E0D\u8981\u8C03 AskUserQuestion tool\u3002`;
41960
+
41935
41961
  // src/dispatch/system-prompt.ts
41936
41962
  var DISPATCH_SYSTEM_PROMPT_HINT = `## \u59D4\u6D3E\u7ED9\u53E6\u4E00\u4E2A persona\uFF08dispatch\uFF09
41937
41963
 
41938
- \u5F53\u7528\u6237\u6D88\u606F\u91CC\u542B \`@persona/<id>\`\uFF08\u5982 \`@persona/persona-app-builder\`\uFF09\u65F6\uFF0C**\u4F60\u5FC5\u987B**\u7ACB\u5373\u8C03 \`clawd-dispatch\` MCP server \u7684 \`personaDispatch\` tool \u59D4\u6D3E\uFF08\u5404 agent \u5BF9 MCP tool \u7684\u5C55\u793A\u540D\u4E0D\u540C\uFF0C\u5982 claude \u663E\u793A\u4E3A \`mcp__clawd-dispatch__personaDispatch\`\uFF1B\u6309\u4F60\u73AF\u5883\u91CC\u5B9E\u9645\u5217\u51FA\u7684\u540D\u5B57\u8C03\u7528\uFF09\uFF0C\u4E0D\u8981\u5C1D\u8BD5\u81EA\u5DF1\u6267\u884C\uFF1A
41964
+ \u5F53\u7528\u6237\u6D88\u606F\u91CC\u542B \`@persona/<id>\`\uFF08\u5982 \`@persona/persona-app-builder\`\uFF09\u65F6\uFF0C**\u4F60\u5FC5\u987B**\u7ACB\u5373\u8C03 \`clawd-dispatch\` MCP \u7684 \`personaDispatch\` tool \u59D4\u6D3E\uFF1A
41939
41965
 
41940
41966
  - \`targetPersona = <id>\`\uFF08@ token \u91CC\u7684 persona id\uFF09
41941
41967
  - \`prompt = \u53BB\u6389 @persona/<id> token \u7684\u7528\u6237\u539F\u6587\`
41942
41968
 
41943
- tool \u4F1A**\u7ACB\u5373\u8FD4\u56DE** dispatchId\uFF08\u5F02\u6B65\u59D4\u6D3E\uFF09\uFF0C\u5BF9\u65B9\u4EBA\u683C\u5728\u540E\u53F0\u5E72\u6D3B\u3002\u4F60\u544A\u8BC9\u7528\u6237"\u4EFB\u52A1\u5DF2\u4EA4\u529E"\u5373\u53EF\u7ED3\u675F\u672C\u8F6E\u2014\u2014**\u4E0D\u8981\u7B49\u5F85\u3001\u4E0D\u8981\u8F6E\u8BE2**\u3002\u5BF9\u65B9\u5E72\u5B8C\u540E\uFF0C\u7ED3\u679C\u4F1A\u4F5C\u4E3A\u4E00\u6761\u65B0\u6D88\u606F\u81EA\u52A8\u6CE8\u5165\u56DE\u4F60\u7684 session\uFF0C\u4F60\u5C4A\u65F6\u518D\u7EFC\u5408\u8F6C\u8FBE\u7ED9\u7528\u6237\u3002\u60F3\u67E5\u8FDB\u5C55\u65F6\uFF08\u7528\u6237\u95EE\u8D77\uFF09\u8C03\u540C\u4E00 MCP \u7684 \`personaDispatchList\`\u3002
41969
+ \u544A\u8BC9\u7528\u6237"\u4EFB\u52A1\u5DF2\u4EA4\u529E"\u5373\u53EF\u7ED3\u675F\u672C\u8F6E\u3002\u7ED3\u679C\u4F1A\u4F5C\u4E3A\u4E00\u6761\u65B0\u6D88\u606F\u81EA\u52A8\u6CE8\u5165\u56DE\u4F60\u7684 session\uFF0C\u5C4A\u65F6\u518D\u7EFC\u5408\u8F6C\u8FBE\u7ED9\u7528\u6237\u3002\u60F3\u67E5\u8FDB\u5C55\u65F6\uFF08\u7528\u6237\u95EE\u8D77\uFF09\u8C03\u540C\u4E00 MCP \u7684 \`personaDispatchList\`\u3002
41944
41970
 
41945
- - **\u4E0D\u8981**\u5C1D\u8BD5\u81EA\u5DF1\u6267\u884C\uFF08\u4F60\u4E0D\u662F\u90A3\u4E2A persona\uFF09
41946
- - **\u4E0D\u8981** in-place \u6A21\u4EFF\u5BF9\u65B9\u4EBA\u683C \u2014\u2014 dispatch \u662F\u771F\u5207\u5230\u53E6\u4E00\u4E2A\u72EC\u7ACB session
41971
+ - **\u4E0D\u8981**\u5C1D\u8BD5\u81EA\u5DF1\u6267\u884C\uFF08\u4F60\u4E0D\u662F\u90A3\u4E2A persona\uFF09\uFF0C\u4E5F\u4E0D\u8981 in-place \u6A21\u4EFF\u5BF9\u65B9\u4EBA\u683C \u2014\u2014 dispatch \u662F\u771F\u5207\u5230\u53E6\u4E00\u4E2A\u72EC\u7ACB session
41947
41972
  - \u4E00\u53E5\u8BDD\u91CC\u591A\u4E2A \`@persona/<id>\` \u65F6\u9009\u7B2C\u4E00\u4E2A\uFF08tool \u4E00\u6B21\u53EA\u5904\u7406\u4E00\u4E2A target\uFF09
41948
- - \u59D4\u6D3E\u5931\u8D25\uFF08tool_result.is_error=true\uFF09\u65F6\u5982\u5B9E\u544A\u8BC9\u7528\u6237\u539F\u56E0\uFF0C**\u4E0D\u8981\u91CD\u8BD5**
41949
41973
 
41950
41974
  ### \u7EED\u6D3E\uFF08\u540C\u4E00\u4EF6\u4E8B\u7684\u540E\u7EED\u6307\u4EE4\uFF09
41951
41975
 
@@ -41953,15 +41977,15 @@ tool \u4F1A**\u7ACB\u5373\u8FD4\u56DE** dispatchId\uFF08\u5F02\u6B65\u59D4\u6D3E
41953
41977
 
41954
41978
  ### \u8DE8\u8BBE\u5907\u59D4\u6D3E\uFF08remote persona\uFF09
41955
41979
 
41956
- \u5F53 @ token \u5F62\u5982 \`@persona/<id>@<deviceId>\`\uFF08\u7B2C\u4E8C\u6BB5\u662F\u8054\u7CFB\u4EBA**\u8BBE\u5907\u7684 deviceId**\uFF0CUI \u9009\u8054\u7CFB\u4EBA\u65F6\u5DF2\u89E3\u6790\u597D\u586B\u5165\uFF09\u65F6\uFF0C\u662F\u628A\u4EFB\u52A1\u59D4\u6D3E\u7ED9\u90A3\u53F0\u8BBE\u5907\u4E0A\u7684 persona\u3002\u9664 targetPersona / prompt \u5916\u518D\u5E26\u4E00\u4E2A\u53C2\u6570\uFF1A
41980
+ \u5F53 @ token \u5F62\u5982 \`@persona/<id>@<deviceId>\`\uFF08\u7B2C\u4E8C\u6BB5\u662F\u8054\u7CFB\u4EBA**\u8BBE\u5907\u7684 deviceId**\uFF0CUI \u9009\u8054\u7CFB\u4EBA\u65F6\u5DF2\u89E3\u6790\u597D\u586B\u5165\uFF09\u65F6\uFF0C\u662F\u628A\u4EFB\u52A1\u59D4\u6D3E\u7ED9\u90A3\u53F0\u8BBE\u5907\u4E0A\u7684 persona\u3002\u9664 targetPersona / prompt \u5916\u518D\u5E26\uFF1A
41957
41981
 
41958
41982
  - \`targetDeviceId = <token \u7B2C\u4E8C\u6BB5\u7684 deviceId\uFF0C\u539F\u6837\u586B>\`
41959
41983
 
41960
- deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731C\u6D4B\u3002\u5BF9\u65B9\u4E0D\u53EF\u8FBE / \u76EE\u6807 persona \u672A\u516C\u5F00\u4F1A\u5728\u7ED3\u679C\u56DE\u6D41\u65F6\u62A5\u5931\u8D25\uFF0C\u7167\u5E38\u8F6C\u8FF0\u7ED9\u7528\u6237\u5373\u53EF\u3002\u5176\u4F59\u884C\u4E3A\uFF08\u79D2\u56DE dispatchId\u3001\u7ED3\u679C\u81EA\u52A8\u6CE8\u5165\u3001\u5931\u8D25\u4E0D\u91CD\u8BD5\uFF09\u4E0E\u672C\u5730\u59D4\u6D3E\u4E00\u81F4\u3002
41984
+ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731C\u6D4B\u3002
41961
41985
 
41962
41986
  ## \u88AB dispatch \u63A5\u5230\u4EFB\u52A1
41963
41987
 
41964
- \u5982\u679C\u4F60\u7684 session \u7B2C\u4E00\u6761 user message \u662F\u4EE5 \`[Dispatched from owner \u2014 another persona session\u59D4\u6D3E\u7ED9\u4F60\u6267\u884C\u4EFB\u52A1]\` \u8D77\u5934\u7684 plain text \u4EFB\u52A1\u5305\uFF0C\u4F60\u662F\u88AB\u53E6\u4E00\u4E2A persona \u59D4\u6D3E\u6765\u5E72\u6D3B\u7684\uFF08\u4EFB\u52A1\u5305\u4E0D\u7528 \`<system-reminder>\` \u5305\u662F\u4E3A\u4E86 reducer dedup \u5DE5\u4F5C\u6B63\u5E38\uFF0C\u89C1 manager.ts buildDispatchTaskPack \u6CE8\u91CA\uFF09\uFF1A
41988
+ \u5982\u679C\u4F60\u7684 session \u7B2C\u4E00\u6761 user message \u662F\u4EE5 \`[Dispatched from owner \u2014 another persona session\u59D4\u6D3E\u7ED9\u4F60\u6267\u884C\u4EFB\u52A1]\` \u8D77\u5934\u7684 plain text \u4EFB\u52A1\u5305\uFF0C\u4F60\u662F\u88AB\u53E6\u4E00\u4E2A persona \u59D4\u6D3E\u6765\u5E72\u6D3B\u7684\uFF1A
41965
41989
 
41966
41990
  - \u4EFB\u52A1\u5728 \`Task:\` \u6BB5
41967
41991
  - \u539F\u4F1A\u8BDD\u7684\u5BF9\u8BDD\u5386\u53F2\u5728 \`Source conversation\` \u6BB5\u7ED9\u7684 jsonl \u8DEF\u5F84\u4E0B\uFF1B\u6309\u9700 Read \u5B83\u4E86\u89E3\u4E0A\u4E0B\u6587\uFF08\u4E0D\u5FC5\u8BFB\u5B8C\uFF09
@@ -42012,7 +42036,10 @@ function cloneState(s) {
42012
42036
  currentTurnSender: s.currentTurnSender
42013
42037
  };
42014
42038
  }
42015
- var IDLE_KILL_DELAY_MS = 6e5;
42039
+ function idleKillDelayOf(state) {
42040
+ const ms = state.subSessionMeta?.idleKillDelayMs;
42041
+ return ms !== void 0 && ms > 0 ? ms : void 0;
42042
+ }
42016
42043
  function tryFlushPending(state, deps) {
42017
42044
  if (!state.readyForSend || state.pendingSend.length === 0) return [];
42018
42045
  const text = state.pendingSend.shift();
@@ -42126,6 +42153,9 @@ function buildSpawnContext(state, deps) {
42126
42153
  if (daemonUrl) {
42127
42154
  ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
42128
42155
  }
42156
+ if (meta?.personaMode && (file.tool ?? "claude") === "claude") {
42157
+ ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + NO_ASK_USER_QUESTION_HINT;
42158
+ }
42129
42159
  if (meta?.extraSettings) {
42130
42160
  ctx.extraSettings = meta.extraSettings;
42131
42161
  }
@@ -42284,11 +42314,12 @@ function pushEventToBuffer(state, event, deps) {
42284
42314
  status: "running-idle"
42285
42315
  }
42286
42316
  });
42287
- if (next.subSessionMeta?.idleKillEnabled) {
42317
+ const idleKillMs = idleKillDelayOf(next);
42318
+ if (idleKillMs !== void 0) {
42288
42319
  effects.push({
42289
42320
  kind: "schedule-idle-kill",
42290
42321
  sessionId: next.file.sessionId,
42291
- ms: IDLE_KILL_DELAY_MS
42322
+ ms: idleKillMs
42292
42323
  });
42293
42324
  }
42294
42325
  }
@@ -42725,14 +42756,15 @@ function reduceSessionInner(state, input, deps) {
42725
42756
  if (state.status !== "running-idle") {
42726
42757
  return { state, effects: [] };
42727
42758
  }
42728
- if (input.subagentActive) {
42759
+ const rescheduleMs = idleKillDelayOf(state);
42760
+ if (input.subagentActive && rescheduleMs !== void 0) {
42729
42761
  return {
42730
42762
  state,
42731
42763
  effects: [
42732
42764
  {
42733
42765
  kind: "schedule-idle-kill",
42734
42766
  sessionId: state.file.sessionId,
42735
- ms: IDLE_KILL_DELAY_MS
42767
+ ms: rescheduleMs
42736
42768
  }
42737
42769
  ]
42738
42770
  };
@@ -43652,8 +43684,8 @@ var SessionManager = class {
43652
43684
  // 经过 compressFrameForWire 后决定是 push collector 还是走 deps.broadcastFrame。
43653
43685
  // scope 决定两件事:SessionStore 路由 + SubSessionMeta 派生(metaFromScope)。
43654
43686
  // - default → 复用 deps.store,无 subSessionMeta(普通 session)
43655
- // - persona/owner → personas/<pid>/.clawd/sessions/owner/,meta={idleKillEnabled:false, personaMode:'owner'}
43656
- // - persona/guest → personas/<pid>/.clawd/sessions/guests/<capId>/,meta={idleKillEnabled:true, personaMode:'guest', extraSettings:<sandbox>}
43687
+ // - persona/owner → personas/<pid>/.clawd/sessions/owner/,meta={idleKillDelayMs:<config 默认 6h>, personaMode:'owner'}
43688
+ // - persona/guest → personas/<pid>/.clawd/sessions/guests/<capId>/,meta={idleKillDelayMs:10min, personaMode:'guest', extraSettings:<sandbox>}
43657
43689
  newRunner(file, scope) {
43658
43690
  const adapter = this.deps.getAdapter(file.tool ?? "claude");
43659
43691
  const store = this.storeFor(scope);
@@ -43665,7 +43697,8 @@ var SessionManager = class {
43665
43697
  this.deps.usersRoot ?? "",
43666
43698
  this.deps.ownerPrincipalId,
43667
43699
  callerDisplayName,
43668
- callerFeishuUnionId
43700
+ callerFeishuUnionId,
43701
+ { ownerIdleKillDelayMs: this.deps.ownerIdleKillDelayMs }
43669
43702
  );
43670
43703
  if (subSessionMeta?.userWorkDir) {
43671
43704
  import_node_fs9.default.mkdirSync(subSessionMeta.userWorkDir, { recursive: true });
@@ -44503,8 +44536,8 @@ var SessionManager = class {
44503
44536
  //
44504
44537
  // 区别于 owner / default 单 sessionId 入口,这些 API 接 SessionScope 参数 ——
44505
44538
  // 调用方(PersonaManager / observer 等)明确传 scope,由 storeFor 路由到正确目录:
44506
- // - {kind:'persona', personaId, mode:'guest', capId} → guests/<capId>/,meta={idleKill:true, sandbox}
44507
- // - {kind:'persona', personaId, mode:'owner'} → owner/,meta={idleKill:false}
44539
+ // - {kind:'persona', personaId, mode:'guest', capId} → guests/<capId>/,meta={idleKill:10min, sandbox}
44540
+ // - {kind:'persona', personaId, mode:'owner'} → owner/,meta={idleKill:<config 默认 6h>}
44508
44541
  //
44509
44542
  // SubSessionMeta 由 scope → metaFromScope 派生,不进 SessionFile schema。
44510
44543
  /** 按 scope 读取 SessionFile;不存在返回 null */
@@ -44758,6 +44791,8 @@ var SessionManager = class {
44758
44791
  } : {},
44759
44792
  // T-20:dispatch model override 落盘(reducer spawn 路径读 SessionFile.model 生成 --model flag)
44760
44793
  ...args.model ? { model: args.model } : {},
44794
+ // butler 顶部入口:dispatch 传下来的 label 落盘,sidebar 显示(否则兜底 cwd-basename)
44795
+ ...args.label ? { label: args.label } : {},
44761
44796
  createdAt: now,
44762
44797
  updatedAt: now
44763
44798
  };
@@ -49415,7 +49450,10 @@ function buildPersonaDispatchHandlers(deps) {
49415
49450
  guestDisplayName: ctx.principal.displayName,
49416
49451
  // T-31: 把 A 的飞书 union_id 透传给 B(connectGuestContext 已把它挂在 principal.feishuUnionId)
49417
49452
  ...ctx.principal.feishuUnionId ? { guestFeishuUnionId: ctx.principal.feishuUnionId } : {},
49418
- ...args.model ? { model: args.model } : {}
49453
+ ...args.model ? { model: args.model } : {},
49454
+ // butler 顶部入口:peer daemon 若在 forwardToPeer 里传了 sessionLabel(当前
49455
+ // 未接线,作 forward-compat 预留),这里也透给 spawnB
49456
+ ...args.sessionLabel ? { label: args.sessionLabel } : {}
49419
49457
  }).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
49420
49458
  const reason = err instanceof Error ? err.message : String(err);
49421
49459
  logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2, reason });
@@ -49450,7 +49488,9 @@ function buildPersonaDispatchHandlers(deps) {
49450
49488
  sourceSessionId,
49451
49489
  targetPersona,
49452
49490
  prompt: args.prompt,
49453
- ...args.model ? { model: args.model } : {}
49491
+ ...args.model ? { model: args.model } : {},
49492
+ // butler 顶部入口(spec 2026-07-27):sessionLabel 落到 SessionFile.label 让 sidebar 显示
49493
+ ...args.sessionLabel ? { label: args.sessionLabel } : {}
49454
49494
  }).then(() => {
49455
49495
  logger?.info("dispatch.spawnB.ok", { dispatchId });
49456
49496
  }).catch((err) => {
@@ -59292,6 +59332,9 @@ async function startDaemon(config) {
59292
59332
  ownerDisplayName,
59293
59333
  ownerPrincipalId,
59294
59334
  ownerFeishuUnionId,
59335
+ // T-37 owner session 闲置回收兜底上限(默认 6h,config.json / env 可调,设 0 关闭)。
59336
+ // guest 的 10min 不走这条——它是 session/scope.ts 的固定常量。
59337
+ ownerIdleKillDelayMs: config.ownerIdleKillDelayMs,
59295
59338
  contactStore,
59296
59339
  mode: config.mode,
59297
59340
  // 单栏 refactor (spec 2026-06-02 §5.1):cc 子进程 env CLAWD_DAEMON_URL 闭包来源。
@@ -59773,7 +59816,9 @@ async function startDaemon(config) {
59773
59816
  // T-31: 把 A 的飞书 union_id stamp 到 B SessionFile,供 cc prompt 身份卡 union_id 行
59774
59817
  guestFeishuUnionId: args.guestFeishuUnionId,
59775
59818
  // T-20: 可选 model override 透到 SessionFile.model
59776
- ...args.model ? { model: args.model } : {}
59819
+ ...args.model ? { model: args.model } : {},
59820
+ // butler 顶部入口(spec 2026-07-27):可选 label 透到 SessionFile.label(sidebar 显示)
59821
+ ...args.label ? { label: args.label } : {}
59777
59822
  });
59778
59823
  },
59779
59824
  // A 角色:从 ContactStore 取 peer 可达 URL + connect token,转发到对端 daemon /rpc。
@@ -22734,7 +22734,13 @@ var DispatchRunArgsSchema = external_exports.object({
22734
22734
  // 合法值集与 claude adapter capabilities.models[].id 一致(见 daemon
22735
22735
  // tools/claude.ts CLAUDE_MODEL_IDS);handler 层做二次校验,非法值 fail-fast。
22736
22736
  // protocol 层保持 string 便于未来跨 tool 扩展(届时 union 收合法字面量)。
22737
- model: external_exports.string().min(1).optional()
22737
+ model: external_exports.string().min(1).optional(),
22738
+ // 可选:给新 worker session 起 label(写入 SessionFile.label,sidebar 显示)。
22739
+ // 仅在**新开 dispatch**(dispatchId 缺席)时生效;续派(dispatchId 非空)
22740
+ // 时 daemon 层静默丢弃(避免半途改 label 造成 sidebar 抖动)。
22741
+ // 上限 60 字符——sidebar 显示 tolerance,卡在 dispatch 入口即可。
22742
+ // 协议层不硬拒 sessionLabel + dispatchId 同传,保留 forward-compat。
22743
+ sessionLabel: external_exports.string().min(1).max(60).optional()
22738
22744
  }).refine((v) => v.targetPersona !== void 0 || v.dispatchId !== void 0, {
22739
22745
  message: "either targetPersona (new dispatch) or dispatchId (follow-up) is required"
22740
22746
  }).refine((v) => !(v.dispatchId !== void 0 && v.targetDeviceId !== void 0), {
@@ -23557,7 +23563,10 @@ async function handlePersonaDispatchToolCall(input, ctx) {
23557
23563
  ...input.targetDeviceId ? { targetDeviceId: input.targetDeviceId } : {},
23558
23564
  // T-20: 可选 override B session 使用的 claude 模型;MCP tool 层已 enum 校验,
23559
23565
  // daemon handler 层还会再校验一次(fail-fast)。
23560
- ...input.model ? { model: input.model } : {}
23566
+ ...input.model ? { model: input.model } : {},
23567
+ // butler 顶部入口:可选 sessionLabel 落到新 worker SessionFile.label。
23568
+ // 长度 1-60 由 protocol schema 强约束;续派场景(dispatchId 非空)daemon 层静默丢弃。
23569
+ ...input.sessionLabel ? { sessionLabel: input.sessionLabel } : {}
23561
23570
  })
23562
23571
  });
23563
23572
  } catch (err) {
@@ -23738,6 +23747,13 @@ async function main() {
23738
23747
  // 精确校验(按 target persona tool 选表)在 daemon handler 层做(fail-fast)。
23739
23748
  model: external_exports.enum([...CLAUDE_MODEL_IDS, ...CODEX_MODEL_IDS]).optional().describe(
23740
23749
  "override the target persona's default model. Omit to use the persona default. Claude personas: " + CLAUDE_MODEL_IDS.join(" / ") + ". Codex personas: " + CODEX_MODEL_IDS.join(" / ") + "."
23750
+ ),
23751
+ // butler 顶部入口 (spec 2026-07-27):butler 给新 worker session 起 label 用。
23752
+ // Sidebar 直接显示这个 label(否则兜底 cwd-basename 都是 persona 目录名,同一
23753
+ // persona 的 dispatched session 长得都一样)。仅新开 dispatch 生效;续派
23754
+ // (dispatchId set) 时 daemon 静默丢弃,保 worker session 稳定命名。
23755
+ sessionLabel: external_exports.string().optional().describe(
23756
+ 'short human-readable label for the new worker session (shown in sidebar, e.g. "\u4FEE sidebar \u9876\u90E8\u5165\u53E3" / "\u67E5\u6628\u665A SLS \u767B\u5F55\u5931\u8D25"). aim for 8-14 chars, verb-led. ignored when continuing an existing dispatch via dispatchId (worker session keeps its original label).'
23741
23757
  )
23742
23758
  }
23743
23759
  },