@clawos-dev/clawd 0.2.393 → 0.2.395

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
@@ -6174,11 +6174,13 @@ var init_dispatch = __esm({
6174
6174
  external_exports.object({
6175
6175
  kind: external_exports.literal("success"),
6176
6176
  text: external_exports.string(),
6177
+ brief: external_exports.string().optional(),
6177
6178
  filePaths: external_exports.array(external_exports.string()).optional()
6178
6179
  }),
6179
6180
  external_exports.object({
6180
6181
  kind: external_exports.literal("failure"),
6181
- reason: external_exports.string()
6182
+ reason: external_exports.string(),
6183
+ brief: external_exports.string().optional()
6182
6184
  })
6183
6185
  ]);
6184
6186
  DispatchRunArgsSchema = external_exports.object({
@@ -6189,6 +6191,9 @@ var init_dispatch = __esm({
6189
6191
  // 省略 = 新开一件事,daemon 铸新 id。
6190
6192
  dispatchId: external_exports.string().min(1).optional(),
6191
6193
  prompt: external_exports.string(),
6194
+ // 派活方写的 2-3 句摘要(与 prompt 同源同结构)。落台账 `taskBrief` + 进任务包信封
6195
+ // `<brief>`,worker session 的卡片头常显。optional 同 DispatchOutcome.brief 的理由。
6196
+ brief: external_exports.string().optional(),
6192
6197
  // 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
6193
6198
  // 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
6194
6199
  targetDeviceId: external_exports.string().min(1).optional(),
@@ -6241,6 +6246,18 @@ var init_dispatch = __esm({
6241
6246
  workerSessionId: external_exports.string().min(1).optional(),
6242
6247
  /** 任务摘要;截断(≤500 字符)在 daemon store.add 处保证 */
6243
6248
  taskText: external_exports.string(),
6249
+ /**
6250
+ * 派活方写的 2-3 句任务摘要(`DispatchRunArgs.brief`)。台账列表 / butler 面板显示它
6251
+ * 而不是被截断到 500 字符的 `taskText` 半截原文。回报侧的 brief 随 `outcome` 已落盘,
6252
+ * 不在这里重复存。
6253
+ */
6254
+ taskBrief: external_exports.string().optional(),
6255
+ /**
6256
+ * 第几轮:新开 = 1,每次续派 +1。续派**不单开分型**(一件事一行一 id),
6257
+ * 「第 N 轮」这个语义就落在这里,并透进任务包信封的 `round` 属性供卡片头显示。
6258
+ * 老台账行缺省 → 消费方按 1 处理。
6259
+ */
6260
+ round: external_exports.number().int().positive().optional(),
6244
6261
  status: DispatchStatusSchema,
6245
6262
  outcome: DispatchOutcomeSchema.optional(),
6246
6263
  /** 上层消费者语义(如司礼监的口谕/验收状态),daemon 不解释 */
@@ -7089,7 +7106,14 @@ var init_events = __esm({
7089
7106
  "skill-hint",
7090
7107
  "meta-text",
7091
7108
  "attachment-skills",
7092
- "attachment-deferred-tools"
7109
+ "attachment-deferred-tools",
7110
+ // persona dispatch 来源分型(信封契约见 dispatch-format.ts):
7111
+ // dispatch-task 派出去的任务包(首发 + 续派,续派靠信封 round 属性区分,不单开分型)
7112
+ // dispatch-result worker 干完回报注入发起方 session 的结果包
7113
+ // 这两类跟其它 meta 的关键差别:**它们是一轮真实交互**,参与 turn 切分与分页计数
7114
+ // (见 isDispatchUserMeta 的消费点),不是 CC 内部注入的辅助消息。
7115
+ "dispatch-task",
7116
+ "dispatch-result"
7093
7117
  ];
7094
7118
  SKILL_SOURCE_VALUES = ["builtin", "global", "project", "plugin"];
7095
7119
  AGENT_SOURCE_VALUES = [
@@ -9033,12 +9057,87 @@ var init_visitor_schemas = __esm({
9033
9057
  function formatLarkSpeakerReminder(speaker) {
9034
9058
  return `<system-reminder>\u6B64\u6D88\u606F\u6765\u81EA\u98DE\u4E66\u7FA4\u6210\u5458 ${speaker}</system-reminder>`;
9035
9059
  }
9060
+ function formatSenderAttribution(sender) {
9061
+ if (sender.principal === "owner") return "";
9062
+ if (!sender.displayName) return "";
9063
+ return `<system-reminder>\u6B64\u6D88\u606F\u6765\u81EA ${sender.principal}\uFF1A${sender.displayName}</system-reminder>`;
9064
+ }
9036
9065
  var init_lark_bot_format = __esm({
9037
9066
  "../protocol/src/lark-bot-format.ts"() {
9038
9067
  "use strict";
9039
9068
  }
9040
9069
  });
9041
9070
 
9071
+ // ../protocol/src/dispatch-format.ts
9072
+ function formatDispatchTaskEnvelope(args) {
9073
+ const attrs = [
9074
+ `dispatch-id="${escapeAttr(args.dispatchId)}"`,
9075
+ ...args.from ? [`from="${escapeAttr(args.from)}"`] : [],
9076
+ `round="${args.round}"`
9077
+ ].join(" ");
9078
+ return [
9079
+ `<${DISPATCH_TASK_TAG} ${attrs}>`,
9080
+ ...briefSection(args.brief),
9081
+ "<detail>",
9082
+ escapeInner(args.detail),
9083
+ "</detail>",
9084
+ `</${DISPATCH_TASK_TAG}>`
9085
+ ].join("\n");
9086
+ }
9087
+ function formatDispatchResultEnvelope(args) {
9088
+ const attrs = [
9089
+ `dispatch-id="${escapeAttr(args.dispatchId)}"`,
9090
+ `from="${escapeAttr(args.from)}"`,
9091
+ `status="${args.status}"`
9092
+ ].join(" ");
9093
+ return [
9094
+ `<${DISPATCH_RESULT_TAG} ${attrs}>`,
9095
+ ...briefSection(args.brief),
9096
+ "<detail>",
9097
+ escapeInner(args.detail),
9098
+ "</detail>",
9099
+ ...filesSection(args.filePaths),
9100
+ `</${DISPATCH_RESULT_TAG}>`
9101
+ ].join("\n");
9102
+ }
9103
+ function detectDispatchEnvelope(text) {
9104
+ if (!text) return null;
9105
+ const task = text.search(openTagRe(DISPATCH_TASK_TAG));
9106
+ const result = text.search(openTagRe(DISPATCH_RESULT_TAG));
9107
+ if (task < 0 && result < 0) return null;
9108
+ if (result < 0) return "task";
9109
+ if (task < 0) return "result";
9110
+ return task < result ? "task" : "result";
9111
+ }
9112
+ function briefSection(brief) {
9113
+ if (!brief || brief.trim().length === 0) return [];
9114
+ return ["<brief>", escapeInner(brief.trim()), "</brief>"];
9115
+ }
9116
+ function escapeInner(s) {
9117
+ return s.replace(INNER_TAG_ESCAPE_RE, "<\\$1$2$3");
9118
+ }
9119
+ function filesSection(filePaths) {
9120
+ if (!filePaths || filePaths.length === 0) return [];
9121
+ return ["<files>", ...filePaths.map((p2) => `- ${p2}`), "</files>"];
9122
+ }
9123
+ function openTagRe(tag) {
9124
+ return new RegExp(`<${tag}\\b[^>]*>`, "i");
9125
+ }
9126
+ function escapeAttr(v2) {
9127
+ return v2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9128
+ }
9129
+ var DISPATCH_TASK_TAG, DISPATCH_RESULT_TAG, INNER_TAG_NAMES, INNER_TAG_ESCAPE_RE, INNER_TAG_UNESCAPE_RE;
9130
+ var init_dispatch_format = __esm({
9131
+ "../protocol/src/dispatch-format.ts"() {
9132
+ "use strict";
9133
+ DISPATCH_TASK_TAG = "clawd-dispatch-task";
9134
+ DISPATCH_RESULT_TAG = "clawd-dispatch-result";
9135
+ INNER_TAG_NAMES = "clawd-dispatch-task|clawd-dispatch-result|brief|detail|files";
9136
+ INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
9137
+ INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
9138
+ }
9139
+ });
9140
+
9042
9141
  // ../protocol/src/runtime.ts
9043
9142
  var init_runtime = __esm({
9044
9143
  "../protocol/src/runtime.ts"() {
@@ -9070,6 +9169,7 @@ var init_runtime = __esm({
9070
9169
  init_shift_internal();
9071
9170
  init_lark_bot_schemas();
9072
9171
  init_lark_bot_format();
9172
+ init_dispatch_format();
9073
9173
  init_onboarding_schemas();
9074
9174
  }
9075
9175
  });
@@ -14330,6 +14430,10 @@ function classifyUserMessage(input) {
14330
14430
  if (parentToolUseId) out.parentToolUseId = parentToolUseId;
14331
14431
  return out;
14332
14432
  }
14433
+ const dispatchKind = detectDispatchEnvelope(text);
14434
+ if (dispatchKind) {
14435
+ return { meta: dispatchKind === "task" ? "dispatch-task" : "dispatch-result" };
14436
+ }
14333
14437
  if (SLASH_COMMAND_RE.test(text)) return { meta: "slash-command" };
14334
14438
  if (LOCAL_COMMAND_RE.test(text)) return { meta: "local-command" };
14335
14439
  if (SYSTEM_REMINDER_RE.test(text) || OPENSPEC_BLOCK_RE.test(text)) {
@@ -14586,6 +14690,7 @@ var init_claude_history = __esm({
14586
14690
  import_node_path5 = __toESM(require("path"), 1);
14587
14691
  import_node_string_decoder = require("string_decoder");
14588
14692
  init_lib();
14693
+ init_protocol();
14589
14694
  init_tool_result_extra();
14590
14695
  JSONL_CHUNK_BYTES = 256 * 1024;
14591
14696
  TASK_NOTIFICATION_RE = /<task-notification\b[\s\S]*?<\/task-notification>/i;
@@ -44590,7 +44695,7 @@ function mergeOwnerCredentials(guestSandbox, bundle) {
44590
44695
  // src/shift/wrap.ts
44591
44696
  function wrapShiftFirePrompt(args) {
44592
44697
  return [
44593
- `<clawd-shift-fire target-persona="${escapeAttr(args.targetPersona)}">`,
44698
+ `<clawd-shift-fire target-persona="${escapeAttr2(args.targetPersona)}">`,
44594
44699
  "This is a scheduled shift fire \u2014 not a live user message.",
44595
44700
  "Respond as an assistant turn (start your output directly).",
44596
44701
  "",
@@ -44599,7 +44704,7 @@ function wrapShiftFirePrompt(args) {
44599
44704
  "</clawd-shift-fire>"
44600
44705
  ].join("\n");
44601
44706
  }
44602
- function escapeAttr(v2) {
44707
+ function escapeAttr2(v2) {
44603
44708
  return v2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
44604
44709
  }
44605
44710
 
@@ -44645,6 +44750,7 @@ function dispatchShiftEnd(info, settle, killImpl) {
44645
44750
  }
44646
44751
 
44647
44752
  // src/dispatch/delivery.ts
44753
+ init_protocol();
44648
44754
  function selectPendingForDelivery(records, sourceSessionId) {
44649
44755
  return records.filter(
44650
44756
  (r) => r.sourceSessionId === sourceSessionId && (r.status === "completed" || r.status === "failed") && !r.deliveredAt
@@ -44652,16 +44758,24 @@ function selectPendingForDelivery(records, sourceSessionId) {
44652
44758
  }
44653
44759
  function formatDeliveryText(records) {
44654
44760
  const blocks = records.map((r) => {
44655
- const head = `[dispatch ${r.dispatchId} \u2192 ${r.targetPersonaId}]`;
44656
44761
  if (r.outcome?.kind === "success") {
44657
- const files = r.outcome.filePaths && r.outcome.filePaths.length > 0 ? `
44658
- Related files:
44659
- ${r.outcome.filePaths.map((p2) => `- ${p2}`).join("\n")}` : "";
44660
- return `${head} \u5B8C\u6210\uFF1A
44661
- ${r.outcome.text}${files}`;
44762
+ return formatDispatchResultEnvelope({
44763
+ dispatchId: r.dispatchId,
44764
+ from: r.targetPersonaId,
44765
+ status: "success",
44766
+ ...r.outcome.brief ? { brief: r.outcome.brief } : {},
44767
+ detail: r.outcome.text,
44768
+ ...r.outcome.filePaths && r.outcome.filePaths.length > 0 ? { filePaths: r.outcome.filePaths } : {}
44769
+ });
44662
44770
  }
44663
44771
  const reason = r.outcome?.kind === "failure" ? r.outcome.reason : "unknown failure";
44664
- return `${head} \u5931\u8D25\uFF1A${reason}`;
44772
+ return formatDispatchResultEnvelope({
44773
+ dispatchId: r.dispatchId,
44774
+ from: r.targetPersonaId,
44775
+ status: "failure",
44776
+ ...r.outcome?.kind === "failure" && r.outcome.brief ? { brief: r.outcome.brief } : {},
44777
+ detail: reason
44778
+ });
44665
44779
  });
44666
44780
  return ["\u4F60\u4E4B\u524D\u59D4\u6D3E\u51FA\u53BB\u7684\u4EFB\u52A1\u6709\u4E86\u7ED3\u679C\uFF1A", "", blocks.join("\n\n")].join("\n");
44667
44781
  }
@@ -44832,6 +44946,7 @@ var DISPATCH_SYSTEM_PROMPT_HINT = `## \u59D4\u6D3E\u7ED9\u53E6\u4E00\u4E2A perso
44832
44946
 
44833
44947
  - \`targetPersona = <id>\`\uFF08@ token \u91CC\u7684 persona id\uFF09
44834
44948
  - \`prompt = \u53BB\u6389 @persona/<id> token \u7684\u7528\u6237\u539F\u6587\`
44949
+ - \`brief = 2-3 \u53E5\u8BF4\u6E05\u4F60\u5728\u8981\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u91CD\u8981\`\uFF08\u5BF9\u65B9 session \u7684\u5361\u7247\u5934\u5E38\u663E\u8FD9\u6BB5\uFF0C\u7528\u6237\u4E0D\u5C55\u5F00\u5C31\u53EA\u770B\u5F97\u5230\u5B83\uFF1B\u7ED3\u6784\u8981\u8DDF prompt \u5BF9\u5E94\uFF0C\u522B\u8BB2\u4E24\u4E2A\u6545\u4E8B\uFF09
44835
44950
 
44836
44951
  \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
44837
44952
 
@@ -44867,13 +44982,26 @@ deviceId \u76F4\u63A5\u53D6\u81EA token\uFF0C\u4E0D\u8981\u6539\u5199\u6216\u731
44867
44982
 
44868
44983
  ## \u88AB dispatch \u63A5\u5230\u4EFB\u52A1
44869
44984
 
44870
- \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
44985
+ \u5982\u679C\u6536\u5230\u4E00\u6761\u5305\u5728 \`<clawd-dispatch-task \u2026>\` \u4FE1\u5C01\u91CC\u7684\u6D88\u606F\uFF0C\u4F60\u662F\u88AB\u53E6\u4E00\u4E2A persona \u59D4\u6D3E\u6765\u5E72\u6D3B\u7684\uFF1A
44986
+
44987
+ \`\`\`
44988
+ <clawd-dispatch-task dispatch-id="\u2026" from="<\u6D3E\u6D3B\u65B9 persona>" round="1">
44989
+ <brief>\u6D3E\u6D3B\u65B9\u5199\u7684 2-3 \u53E5\u6458\u8981</brief>
44990
+ <detail>
44991
+ [Dispatched from owner \u2014 another persona session\u59D4\u6D3E\u7ED9\u4F60\u6267\u884C\u4EFB\u52A1]
44992
+ \u2026\u4EFB\u52A1\u6B63\u6587\u2026
44993
+ </detail>
44994
+ </clawd-dispatch-task>
44995
+ \`\`\`
44871
44996
 
44872
- - \u4EFB\u52A1\u5728 \`Task:\` \u6BB5
44997
+ - \u4EFB\u52A1\u5728 \`<detail>\` \u6BB5\u7684 \`Task:\` \u91CC\uFF1B\`<brief>\` \u53EA\u662F\u6458\u8981\uFF0C\u522B\u5F53\u4EFB\u52A1\u8BFB
44998
+ - \`round="N"\`\uFF1A\`1\` = \u9996\u6B21\u6D3E\u6D3B\uFF0C\`2\` \u53CA\u4EE5\u4E0A = **\u540C\u4E00\u4EF6\u4E8B\u7684\u7B2C N \u8F6E\u7EED\u6D3E**\uFF08\u4E0A\u4E0B\u6587\u8FD8\u5728\uFF0C\u63A5\u7740\u5E72\uFF0C\u4E0D\u8981\u4ECE\u5934\u6765\uFF09
44873
44999
  - \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
44874
45000
  - \u5E72\u5B8C\u540E**\u5FC5\u987B**\u8C03 \`clawd-dispatch\` MCP \u7684 \`personaDispatchComplete\` tool \u62A5\u544A\u7ED3\u679C\uFF1A
44875
- - \u6210\u529F\uFF1A\`{ text: "\u603B\u7ED3/\u7ED3\u8BBA", filePaths?: ["\u4F60\u4EA7\u51FA/\u6539\u7684\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84"] }\`
44876
- - \u5931\u8D25\uFF1A\`{ isFailure: true, reason: "..." }\`
45001
+ - \u6210\u529F\uFF1A\`{ text: "\u603B\u7ED3/\u7ED3\u8BBA", brief: "2-3 \u53E5", filePaths?: ["\u4F60\u4EA7\u51FA/\u6539\u7684\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84"] }\`
45002
+ - \u5931\u8D25\uFF1A\`{ isFailure: true, reason: "...", brief: "2-3 \u53E5\u8BF4\u6E05\u5361\u5728\u54EA" }\`
45003
+ - \`brief\` \u4E24\u79CD\u60C5\u51B5\u90FD\u8981\u586B\uFF1A\u5B83\u662F\u5BF9\u65B9\u770B\u5230\u7684\u5E38\u663E\u6458\u8981\uFF08\u987A\u4E0D\u987A\u3001\u6709\u6CA1\u6709\u574F\u3001\u6700\u8BE5\u77E5\u9053\u7684\u4E00\u4EF6\u4E8B\uFF09\uFF0C
45004
+ \u7ED3\u6784\u8DDF \`text\` / \`reason\` \u4E00\u4E00\u5BF9\u5E94\u2014\u2014brief \u8BF4\u4E09\u70B9\uFF0C\u6B63\u6587\u5C31\u8BE5\u6709\u5BF9\u5E94\u7684\u4E09\u6BB5\uFF0C\u522B\u8BB2\u4E24\u4E2A\u6545\u4E8B
44877
45005
  - \u62A5\u544A\u5B8C\u540E\u4F60\u7684 session \u5C31\u7ED3\u675F\u4E86\uFF0C\u4E0D\u8981\u7EE7\u7EED\u8F93\u51FA`;
44878
45006
 
44879
45007
  // src/session/reducer-types.ts
@@ -45315,7 +45443,10 @@ function applyCommand(state, command, deps) {
45315
45443
  kind: "user_text",
45316
45444
  text: command.text,
45317
45445
  uuid: deps.genUuid?.(),
45318
- sender: command.senderPrincipal
45446
+ sender: command.senderPrincipal,
45447
+ // dispatch 来源分型(L2):daemon 自注入的消息在 command 上显式带分型值。
45448
+ // history 侧同一条消息由 classifyUserMessage 从信封解出同一个值 —— 两路必须一致。
45449
+ ...command.meta ? { meta: command.meta } : {}
45319
45450
  };
45320
45451
  const pushed = pushEventToBuffer(next, userEvent, deps);
45321
45452
  effects.push(...pushed.effects);
@@ -46369,6 +46500,16 @@ function newSessionId() {
46369
46500
  return v4_default().replace(/-/g, "").slice(0, 16);
46370
46501
  }
46371
46502
  function buildDispatchTaskPack(args) {
46503
+ return formatDispatchTaskEnvelope({
46504
+ dispatchId: args.dispatchId,
46505
+ ...args.from ? { from: args.from } : {},
46506
+ // 首发恒 1;续派包由 handlers/persona-dispatch.ts 的 buildFollowUpTaskPack 带 N
46507
+ round: 1,
46508
+ ...args.brief ? { brief: args.brief } : {},
46509
+ detail: buildDispatchTaskDetail(args)
46510
+ });
46511
+ }
46512
+ function buildDispatchTaskDetail(args) {
46372
46513
  return `[Dispatched from owner \u2014 another persona session\u59D4\u6D3E\u7ED9\u4F60\u6267\u884C\u4EFB\u52A1]
46373
46514
 
46374
46515
  Task:
@@ -47314,7 +47455,8 @@ var SessionManager = class {
47314
47455
  if (pending.length === 0) return;
47315
47456
  const state = this.tryGetState(sourceSessionId);
47316
47457
  if (state && compressStatus(state.status) === "running") return;
47317
- if (!this.injectIntoSession(sourceSessionId, formatDeliveryText(pending))) return;
47458
+ if (!this.injectIntoSession(sourceSessionId, formatDeliveryText(pending), "dispatch-result"))
47459
+ return;
47318
47460
  const nowIso3 = new Date(this.deps.now?.() ?? Date.now()).toISOString();
47319
47461
  for (const r of pending) store.update(r.dispatchId, { deliveredAt: nowIso3 });
47320
47462
  this.deps.logger?.info("dispatch.deliver.ok", {
@@ -47328,8 +47470,17 @@ var SessionManager = class {
47328
47470
  * (deliverPending)与续派任务包(handlers/persona-dispatch 经 sendFollowUp 闭包)共用。
47329
47471
  * session 文件不存在或 send 抛错 → false(记 log,不 throw)。直接 runner.input
47330
47472
  * (无 collector),frames 经 routeFromRunner 正常广播上 wire。
47473
+ *
47474
+ * 来源分型(L2):`meta` 显式声明"这条注入是什么"('dispatch-task' / 'dispatch-result'),
47475
+ * reducer 原样 stamp 到 synth 的 user_text,与 history 侧 classifyUserMessage 从信封
47476
+ * 解出的值必须一致。
47477
+ *
47478
+ * sender 仍是 owner —— **sender 与 origin 是两个正交维度**:sender 回答"以谁的身份跑
47479
+ * 这一轮"(MCP endpoint / ticket api 主键要它,daemon 是代 owner 触发的,伪造 'persona'
47480
+ * 会污染下游主键),origin 回答"这条消息从哪来",后者由 meta + 信封表达。渲染层按
47481
+ * origin 分 owner / guest / persona 三档,persona 档正是 meta 命中 dispatch-* 这一支。
47331
47482
  */
47332
- injectIntoSession(sessionId, text) {
47483
+ injectIntoSession(sessionId, text, meta) {
47333
47484
  if (!this.deps.ownerPrincipalId) {
47334
47485
  this.deps.logger?.warn("dispatch.inject.no-owner-principal", { sessionId });
47335
47486
  return false;
@@ -47350,7 +47501,7 @@ var SessionManager = class {
47350
47501
  const runner = this.ensureRunnerForScope(file, this.scopeForFile(file));
47351
47502
  runner.input({
47352
47503
  kind: "command",
47353
- command: { kind: "send", text, senderPrincipal: sender }
47504
+ command: { kind: "send", text, senderPrincipal: sender, ...meta ? { meta } : {} }
47354
47505
  });
47355
47506
  return true;
47356
47507
  } catch (err) {
@@ -47926,10 +48077,13 @@ var SessionManager = class {
47926
48077
  dispatchId: args.dispatchId,
47927
48078
  bSessionId: sessionId
47928
48079
  });
48080
+ const fromPersonaId = args.guestPrincipalId ? args.guestDisplayName : this.findOwnedSession(args.sourceSessionId)?.ownerPersonaId;
47929
48081
  const taskPack = buildDispatchTaskPack({
47930
48082
  prompt: args.prompt,
47931
48083
  sourceJsonlPath: args.sourceJsonlPath,
47932
- dispatchId: args.dispatchId
48084
+ dispatchId: args.dispatchId,
48085
+ ...fromPersonaId ? { from: fromPersonaId } : {},
48086
+ ...args.brief ? { brief: args.brief } : {}
47933
48087
  });
47934
48088
  let dispatchSender;
47935
48089
  if (args.guestPrincipalId) {
@@ -47950,7 +48104,14 @@ var SessionManager = class {
47950
48104
  }
47951
48105
  runner.input({
47952
48106
  kind: "command",
47953
- command: { kind: "send", text: taskPack, senderPrincipal: dispatchSender }
48107
+ // 来源分型(L2):realtime 路径显式带 meta,与 history 侧 classifyUserMessage
48108
+ // 从信封解出的值一致 —— 两路不一致会打歪 trimHistoryOverlap 的 anchor。
48109
+ command: {
48110
+ kind: "send",
48111
+ text: taskPack,
48112
+ senderPrincipal: dispatchSender,
48113
+ meta: "dispatch-task"
48114
+ }
47954
48115
  });
47955
48116
  this.deps.logger?.info("dispatch.createDispatchedSession.task-pack-sent", {
47956
48117
  dispatchId: args.dispatchId,
@@ -52488,6 +52649,7 @@ var PersonaDispatchManager = class {
52488
52649
  ...args.sourcePersonaId ? { sourcePersonaId: args.sourcePersonaId } : {},
52489
52650
  targetPersonaId: args.targetPersona,
52490
52651
  taskText: args.taskText,
52652
+ ...args.taskBrief ? { taskBrief: args.taskBrief } : {},
52491
52653
  ...args.meta ? { meta: args.meta } : {}
52492
52654
  });
52493
52655
  return { dispatchId };
@@ -52600,6 +52762,9 @@ function createDispatchStore(deps) {
52600
52762
  ...input.sourcePersonaId ? { sourcePersonaId: input.sourcePersonaId } : {},
52601
52763
  targetPersonaId: input.targetPersonaId,
52602
52764
  taskText: input.taskText.slice(0, TASK_TEXT_MAX),
52765
+ ...input.taskBrief ? { taskBrief: input.taskBrief } : {},
52766
+ // 新开 dispatch = 第 1 轮;续派在 handler 里 +1
52767
+ round: 1,
52603
52768
  status: "running",
52604
52769
  ...input.meta ? { meta: input.meta } : {},
52605
52770
  createdAt: new Date(deps.now()).toISOString()
@@ -54349,16 +54514,20 @@ function canAccessPersona(grants, personaId2, action) {
54349
54514
 
54350
54515
  // src/handlers/persona-dispatch.ts
54351
54516
  init_claude();
54352
- function buildFollowUpTaskPack(dispatchId, prompt) {
54353
- return [
54354
- `[Follow-up instruction for dispatch ${dispatchId}]`,
54355
- "",
54356
- `Task: ${prompt}`,
54357
- "",
54358
- `This is a follow-up turn in the same dispatched task (dispatchId=${dispatchId}).`,
54359
- "When you finish this round, report back by calling personaDispatchComplete again,",
54360
- "exactly as you did before."
54361
- ].join("\n");
54517
+ function buildFollowUpTaskPack(dispatchId, prompt, round) {
54518
+ return formatDispatchTaskEnvelope({
54519
+ dispatchId,
54520
+ round,
54521
+ detail: [
54522
+ `[Follow-up instruction for dispatch ${dispatchId}]`,
54523
+ "",
54524
+ `Task: ${prompt}`,
54525
+ "",
54526
+ `This is a follow-up turn in the same dispatched task (dispatchId=${dispatchId}).`,
54527
+ "When you finish this round, report back by calling personaDispatchComplete again,",
54528
+ "exactly as you did before."
54529
+ ].join("\n")
54530
+ });
54362
54531
  }
54363
54532
  function buildPersonaDispatchHandlers(deps) {
54364
54533
  const { personaDispatchManager: mgr, spawnB, logger } = deps;
@@ -54405,12 +54574,15 @@ function buildPersonaDispatchHandlers(deps) {
54405
54574
  if (!deps.sendFollowUp) {
54406
54575
  throw new Error("follow-up dispatch not wired (sendFollowUp missing)");
54407
54576
  }
54577
+ const nextRound = (rec4.round ?? 1) + 1;
54408
54578
  deps.store.update(args.dispatchId, {
54409
54579
  status: "running",
54410
54580
  outcome: void 0,
54411
54581
  completedAt: void 0,
54412
54582
  deliveredAt: void 0,
54413
54583
  taskText: args.prompt.slice(0, TASK_TEXT_MAX),
54584
+ round: nextRound,
54585
+ ...args.brief ? { taskBrief: args.brief } : {},
54414
54586
  ...args.meta ? { meta: { ...rec4.meta ?? {}, ...args.meta } } : {}
54415
54587
  });
54416
54588
  mgr.reopen(args.dispatchId, {
@@ -54425,7 +54597,10 @@ function buildPersonaDispatchHandlers(deps) {
54425
54597
  });
54426
54598
  const ok = deps.sendFollowUp(
54427
54599
  rec4.workerSessionId,
54428
- buildFollowUpTaskPack(args.dispatchId, args.prompt)
54600
+ buildFollowUpTaskPack(args.dispatchId, args.prompt, nextRound),
54601
+ // 来源分型(L2):续派并进 dispatch-task;realtime 侧靠这个 meta 打标,
54602
+ // history 侧靠 classifyUserMessage 从信封解出同值。
54603
+ "dispatch-task"
54429
54604
  );
54430
54605
  if (!ok) {
54431
54606
  mgr.complete(args.dispatchId, {
@@ -54487,6 +54662,7 @@ function buildPersonaDispatchHandlers(deps) {
54487
54662
  sourceSessionId,
54488
54663
  targetPersona,
54489
54664
  taskText: args.prompt,
54665
+ ...args.brief ? { taskBrief: args.brief } : {},
54490
54666
  ...args.meta ? { meta: args.meta } : {}
54491
54667
  });
54492
54668
  logger?.info("dispatch.run.forward", {
@@ -54520,6 +54696,7 @@ function buildPersonaDispatchHandlers(deps) {
54520
54696
  sourceSessionId: ctx.principal.id,
54521
54697
  targetPersona,
54522
54698
  taskText: args.prompt,
54699
+ ...args.brief ? { taskBrief: args.brief } : {},
54523
54700
  ...args.meta ? { meta: args.meta } : {}
54524
54701
  });
54525
54702
  logger?.info("dispatch.run.received.guest", {
@@ -54533,6 +54710,7 @@ function buildPersonaDispatchHandlers(deps) {
54533
54710
  sourceSessionId: ctx.principal.id,
54534
54711
  targetPersona,
54535
54712
  prompt: args.prompt,
54713
+ ...args.brief ? { brief: args.brief } : {},
54536
54714
  guestPrincipalId: ctx.principal.id,
54537
54715
  guestDisplayName: ctx.principal.displayName,
54538
54716
  // T-31: 把 A 的飞书 union_id 透传给 B(connectGuestContext 已把它挂在 principal.feishuUnionId)
@@ -54557,6 +54735,7 @@ function buildPersonaDispatchHandlers(deps) {
54557
54735
  sourceSessionId,
54558
54736
  targetPersona,
54559
54737
  taskText: args.prompt,
54738
+ ...args.brief ? { taskBrief: args.brief } : {},
54560
54739
  ...args.meta ? { meta: args.meta } : {}
54561
54740
  });
54562
54741
  logger?.info("dispatch.run.received", {
@@ -54570,6 +54749,7 @@ function buildPersonaDispatchHandlers(deps) {
54570
54749
  sourceSessionId,
54571
54750
  targetPersona,
54572
54751
  prompt: args.prompt,
54752
+ ...args.brief ? { brief: args.brief } : {},
54573
54753
  ...args.model ? { model: args.model } : {},
54574
54754
  // 一次性 engine override(spec 2026-08-24):只有本机 owner 入口能带(guest 已在上方拒)
54575
54755
  ...args.tool ? { tool: args.tool } : {},
@@ -60201,7 +60381,11 @@ function buildSessionHandlers(deps) {
60201
60381
  deps.getOwnerIdentity().feishuUnionId
60202
60382
  )
60203
60383
  );
60204
- const { broadcast } = manager.send(args, sender);
60384
+ const attribution = formatSenderAttribution(sender);
60385
+ const outbound = attribution ? { ...args, text: `${args.text}
60386
+
60387
+ ${attribution}` } : args;
60388
+ const { broadcast } = manager.send(outbound, sender);
60205
60389
  return { response: { type: "session:send", ok: true }, broadcast };
60206
60390
  };
60207
60391
  const currentTurnSender = async (frame, _client, ctx) => {
@@ -61912,7 +62096,7 @@ function computeMethodAccess(args) {
61912
62096
  }
61913
62097
 
61914
62098
  // src/version.ts
61915
- var version = "0.2.393".length > 0 ? "0.2.393" : "dev";
62099
+ var version = "0.2.395".length > 0 ? "0.2.395" : "dev";
61916
62100
 
61917
62101
  // src/cli-probe/probe.ts
61918
62102
  var fs66 = __toESM(require("fs"), 1);
@@ -66647,7 +66831,7 @@ async function startDaemon(config) {
66647
66831
  return { personaIds: [...ch.memberPersonaIds], deviceIds: [...ch.memberContactDeviceIds] };
66648
66832
  },
66649
66833
  // 续派(spec 2026-07-21):任务包注入 worker session,复用投递同款注入路径。
66650
- sendFollowUp: (workerSessionId, text) => manager.injectIntoSession(workerSessionId, text),
66834
+ sendFollowUp: (workerSessionId, text, meta) => manager.injectIntoSession(workerSessionId, text, meta),
66651
66835
  logger,
66652
66836
  spawnB: async (args) => {
66653
66837
  logger.info("dispatch.spawnB.start", {
@@ -66668,6 +66852,8 @@ async function startDaemon(config) {
66668
66852
  targetPersona: args.targetPersona,
66669
66853
  prompt: args.prompt,
66670
66854
  sourceJsonlPath,
66855
+ // 派活方写的 2-3 句摘要 → 任务包信封 <brief>(卡片头常显)
66856
+ ...args.brief ? { brief: args.brief } : {},
66671
66857
  // B 角色(跨设备):非空 → guest scope + creatorPrincipalId/creatorDisplayName = A。
66672
66858
  guestPrincipalId: args.guestPrincipalId,
66673
66859
  guestDisplayName: args.guestDisplayName,
@@ -21450,11 +21450,13 @@ var DispatchOutcomeSchema = external_exports.discriminatedUnion("kind", [
21450
21450
  external_exports.object({
21451
21451
  kind: external_exports.literal("success"),
21452
21452
  text: external_exports.string(),
21453
+ brief: external_exports.string().optional(),
21453
21454
  filePaths: external_exports.array(external_exports.string()).optional()
21454
21455
  }),
21455
21456
  external_exports.object({
21456
21457
  kind: external_exports.literal("failure"),
21457
- reason: external_exports.string()
21458
+ reason: external_exports.string(),
21459
+ brief: external_exports.string().optional()
21458
21460
  })
21459
21461
  ]);
21460
21462
  var DispatchRunArgsSchema = external_exports.object({
@@ -21465,6 +21467,9 @@ var DispatchRunArgsSchema = external_exports.object({
21465
21467
  // 省略 = 新开一件事,daemon 铸新 id。
21466
21468
  dispatchId: external_exports.string().min(1).optional(),
21467
21469
  prompt: external_exports.string(),
21470
+ // 派活方写的 2-3 句摘要(与 prompt 同源同结构)。落台账 `taskBrief` + 进任务包信封
21471
+ // `<brief>`,worker session 的卡片头常显。optional 同 DispatchOutcome.brief 的理由。
21472
+ brief: external_exports.string().optional(),
21468
21473
  // 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
21469
21474
  // 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
21470
21475
  targetDeviceId: external_exports.string().min(1).optional(),
@@ -21517,6 +21522,18 @@ var DispatchRecordSchema = external_exports.object({
21517
21522
  workerSessionId: external_exports.string().min(1).optional(),
21518
21523
  /** 任务摘要;截断(≤500 字符)在 daemon store.add 处保证 */
21519
21524
  taskText: external_exports.string(),
21525
+ /**
21526
+ * 派活方写的 2-3 句任务摘要(`DispatchRunArgs.brief`)。台账列表 / butler 面板显示它
21527
+ * 而不是被截断到 500 字符的 `taskText` 半截原文。回报侧的 brief 随 `outcome` 已落盘,
21528
+ * 不在这里重复存。
21529
+ */
21530
+ taskBrief: external_exports.string().optional(),
21531
+ /**
21532
+ * 第几轮:新开 = 1,每次续派 +1。续派**不单开分型**(一件事一行一 id),
21533
+ * 「第 N 轮」这个语义就落在这里,并透进任务包信封的 `round` 属性供卡片头显示。
21534
+ * 老台账行缺省 → 消费方按 1 处理。
21535
+ */
21536
+ round: external_exports.number().int().positive().optional(),
21520
21537
  status: DispatchStatusSchema,
21521
21538
  outcome: DispatchOutcomeSchema.optional(),
21522
21539
  /** 上层消费者语义(如司礼监的口谕/验收状态),daemon 不解释 */
@@ -22235,7 +22252,14 @@ var HISTORY_USER_META_VALUES = [
22235
22252
  "skill-hint",
22236
22253
  "meta-text",
22237
22254
  "attachment-skills",
22238
- "attachment-deferred-tools"
22255
+ "attachment-deferred-tools",
22256
+ // persona dispatch 来源分型(信封契约见 dispatch-format.ts):
22257
+ // dispatch-task 派出去的任务包(首发 + 续派,续派靠信封 round 属性区分,不单开分型)
22258
+ // dispatch-result worker 干完回报注入发起方 session 的结果包
22259
+ // 这两类跟其它 meta 的关键差别:**它们是一轮真实交互**,参与 turn 切分与分页计数
22260
+ // (见 isDispatchUserMeta 的消费点),不是 CC 内部注入的辅助消息。
22261
+ "dispatch-task",
22262
+ "dispatch-result"
22239
22263
  ];
22240
22264
  var SKILL_SOURCE_VALUES = ["builtin", "global", "project", "plugin"];
22241
22265
  var AGENT_SOURCE_VALUES = [
@@ -24007,6 +24031,11 @@ var VisitorRecordSchema = external_exports.object({
24007
24031
  });
24008
24032
  var VisitorListResultSchema = external_exports.object({ visitors: external_exports.array(VisitorRecordSchema) });
24009
24033
 
24034
+ // ../protocol/src/dispatch-format.ts
24035
+ var INNER_TAG_NAMES = "clawd-dispatch-task|clawd-dispatch-result|brief|detail|files";
24036
+ var INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
24037
+ var INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
24038
+
24010
24039
  // src/tools/claude-history.ts
24011
24040
  var import_node_crypto = __toESM(require("crypto"), 1);
24012
24041
  var import_node_fs = __toESM(require("fs"), 1);
@@ -24644,6 +24673,11 @@ function log(msg, info) {
24644
24673
  } catch {
24645
24674
  }
24646
24675
  }
24676
+ function BRIEF_DESCRIBE(schema, what) {
24677
+ return schema.describe(
24678
+ `2-3 sentences on ${what}. This is what the user sees **without expanding**; they decide from it whether to read the detail. Mirror the detail's top-level structure: if the brief makes three points, the detail must have the three matching sections in the same order. Do not tell a different story from the detail. Always fill this in.`
24679
+ );
24680
+ }
24647
24681
  async function handlePersonaDispatchToolCall(input, ctx) {
24648
24682
  const f = ctx.fetch ?? fetch;
24649
24683
  log("personaDispatch tool entered", {
@@ -24681,6 +24715,8 @@ async function handlePersonaDispatchToolCall(input, ctx) {
24681
24715
  // 续派时可省 targetPersona(daemon 从台账取)
24682
24716
  ...input.targetPersona ? { targetPersona: input.targetPersona } : {},
24683
24717
  prompt: input.prompt,
24718
+ // brief(老板拍板 2026-08-25):进台账 taskBrief + 任务包信封 <brief>
24719
+ ...input.brief ? { brief: input.brief } : {},
24684
24720
  // 续派(spec 2026-07-21):非空 = 把 prompt 打进该 dispatch 的原 worker session。
24685
24721
  ...input.dispatchId ? { dispatchId: input.dispatchId } : {},
24686
24722
  // 跨设备:非空时透传,daemon run handler 据此走 A 角色转发分支。
@@ -24801,10 +24837,12 @@ async function handlePersonaDispatchCompleteToolCall(input, ctx) {
24801
24837
  }
24802
24838
  const outcome = input.isFailure ? {
24803
24839
  kind: "failure",
24804
- reason: input.reason ?? "unspecified failure"
24840
+ reason: input.reason ?? "unspecified failure",
24841
+ ...input.brief ? { brief: input.brief } : {}
24805
24842
  } : {
24806
24843
  kind: "success",
24807
24844
  text: input.text ?? "",
24845
+ ...input.brief ? { brief: input.brief } : {},
24808
24846
  filePaths: input.filePaths
24809
24847
  };
24810
24848
  const url = `${ctx.daemonUrl}/rpc/personaDispatch:complete`;
@@ -24873,6 +24911,10 @@ async function main() {
24873
24911
  prompt: external_exports.string().describe(
24874
24912
  "the task to perform, with the @persona/<id> token stripped out"
24875
24913
  ),
24914
+ brief: BRIEF_DESCRIBE(
24915
+ external_exports.string().optional(),
24916
+ "what you are asking for and why it matters"
24917
+ ),
24876
24918
  targetDeviceId: external_exports.string().optional().describe(
24877
24919
  "contact deviceId of the remote device when delegating cross-device (omit for a local persona)"
24878
24920
  ),
@@ -24911,6 +24953,10 @@ async function main() {
24911
24953
  description: "You were dispatched by owner. When you finish the task, call this tool with your result and any relevant file paths. On failure, set isFailure=true and provide a reason. After calling this, your session is done.",
24912
24954
  inputSchema: {
24913
24955
  text: external_exports.string().optional().describe("your result text (omit on failure)"),
24956
+ brief: BRIEF_DESCRIBE(
24957
+ external_exports.string().optional(),
24958
+ "what happened \u2014 did it go smoothly, did anything break, what's the one thing the reader must know"
24959
+ ),
24914
24960
  filePaths: external_exports.array(external_exports.string()).optional().describe("absolute paths to files you produced or modified"),
24915
24961
  isFailure: external_exports.boolean().optional(),
24916
24962
  reason: external_exports.string().optional().describe("failure reason (required when isFailure=true)")