@clawos-dev/clawd 0.2.455 → 0.2.457

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.
@@ -22062,6 +22062,28 @@ var ChannelDeleteResultSchema = external_exports.object({
22062
22062
 
22063
22063
  // ../protocol/src/topic.ts
22064
22064
  var nonBlank2 = external_exports.string().trim().min(1);
22065
+ var TOPIC_DECISION_TYPES = ["master-confirmed", "owner-needed"];
22066
+ var TOPIC_DECISION_STATUSES = ["open", "resolved"];
22067
+ var TOPIC_DECISION_TYPE_MASTER_CONFIRMED = TOPIC_DECISION_TYPES[0];
22068
+ var TOPIC_DECISION_TYPE_OWNER_NEEDED = TOPIC_DECISION_TYPES[1];
22069
+ var TOPIC_DECISION_STATUS_OPEN = TOPIC_DECISION_STATUSES[0];
22070
+ var TOPIC_DECISION_STATUS_RESOLVED = TOPIC_DECISION_STATUSES[1];
22071
+ var TopicDecisionTypeSchema = external_exports.enum(TOPIC_DECISION_TYPES);
22072
+ var TopicDecisionStatusSchema = external_exports.enum(TOPIC_DECISION_STATUSES);
22073
+ var TopicDecisionSchema = external_exports.object({
22074
+ type: TopicDecisionTypeSchema,
22075
+ status: TopicDecisionStatusSchema,
22076
+ question: nonBlank2,
22077
+ recommendation: nonBlank2
22078
+ }).superRefine((decision, ctx) => {
22079
+ if (decision.type === TOPIC_DECISION_TYPE_MASTER_CONFIRMED && decision.status !== TOPIC_DECISION_STATUS_RESOLVED) {
22080
+ ctx.addIssue({
22081
+ code: external_exports.ZodIssueCode.custom,
22082
+ path: ["status"],
22083
+ message: "master-confirmed decisions must be resolved"
22084
+ });
22085
+ }
22086
+ });
22065
22087
  var TOPIC_STATES = ["planning", "running", "done"];
22066
22088
  var TopicStateSchema = external_exports.enum(TOPIC_STATES);
22067
22089
  var TopicActivityEntrySchema = external_exports.object({
@@ -22071,7 +22093,12 @@ var TopicActivityEntrySchema = external_exports.object({
22071
22093
  target: external_exports.string().optional(),
22072
22094
  reason: external_exports.string().min(1),
22073
22095
  /** 该动作确认的产出物(watcher 返回的 artifacts);影响面视图的唯一数据源 */
22074
- artifacts: external_exports.array(nonBlank2).optional()
22096
+ artifacts: external_exports.array(nonBlank2).optional(),
22097
+ /**
22098
+ * Optional decision projection; legacy activity entries omit this field. `open` means the
22099
+ * owner still needs to answer; `resolved` records the conclusion without deleting the question.
22100
+ */
22101
+ decision: TopicDecisionSchema.optional()
22075
22102
  });
22076
22103
  var TopicLineMetaSchema = external_exports.object({
22077
22104
  rejectCount: external_exports.number().int().min(0),
@@ -22178,7 +22205,8 @@ var TopicAppendArgsSchema = external_exports.object({
22178
22205
  target: nonBlank2.optional(),
22179
22206
  reason: nonBlank2,
22180
22207
  /** 该动作确认的产出物;影响面视图只吃这个字段 */
22181
- artifacts: external_exports.array(nonBlank2).optional()
22208
+ artifacts: external_exports.array(nonBlank2).optional(),
22209
+ decision: TopicActivityEntrySchema.shape.decision
22182
22210
  });
22183
22211
  var TopicRejectArgsSchema = external_exports.object({
22184
22212
  topicId: external_exports.string().min(1),
@@ -23932,7 +23960,7 @@ var METHOD_DOCS = {
23932
23960
  args: TopicSetPlanArgsSchema
23933
23961
  },
23934
23962
  "topic:append": {
23935
- summary: '\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\uFF08\u8001\u677F\u4E8B\u540E\u770B\u7684\u662F"\u51ED\u4EC0\u4E48\u8FD9\u4E48\u5224\u65AD"\uFF09',
23963
+ summary: "\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\u3002Master \u505A\u4EE3\u8001\u677F\u786E\u8BA4\u6216\u5347\u7EA7\u8001\u677F\u51B3\u7B56\u65F6\uFF0C\u5FC5\u987B\u9644 decision\uFF08type/status/question/recommendation\uFF09",
23936
23964
  args: TopicAppendArgsSchema
23937
23965
  },
23938
23966
  "topic:reject": {
package/dist/cli.cjs CHANGED
@@ -6876,13 +6876,35 @@ var init_channel = __esm({
6876
6876
  });
6877
6877
 
6878
6878
  // ../protocol/src/topic.ts
6879
- var nonBlank2, TOPIC_STATES, TopicStateSchema, TopicActivityEntrySchema, TopicLineMetaSchema, TopicSchema, TopicLineViewSchema, TopicListArgsSchema, TopicGetArgsSchema, TopicCreateArgsSchema, TopicSetPlanArgsSchema, TopicAppendArgsSchema, TopicRejectArgsSchema, TopicAcceptArgsSchema, TopicCloseArgsSchema, TopicRenameArgsSchema, TopicDeleteArgsSchema, TopicListItemSchema, TopicListResultSchema, TopicGetResultSchema, TopicCreateResultSchema, TopicRejectResultSchema, TopicAcceptResultSchema, TopicResultSchema, TopicDeleteResultSchema;
6879
+ var nonBlank2, TOPIC_DECISION_TYPES, TOPIC_DECISION_STATUSES, TOPIC_DECISION_TYPE_MASTER_CONFIRMED, TOPIC_DECISION_TYPE_OWNER_NEEDED, TOPIC_DECISION_STATUS_OPEN, TOPIC_DECISION_STATUS_RESOLVED, TopicDecisionTypeSchema, TopicDecisionStatusSchema, TopicDecisionSchema, TOPIC_STATES, TopicStateSchema, TopicActivityEntrySchema, TopicLineMetaSchema, TopicSchema, TopicLineViewSchema, TopicListArgsSchema, TopicGetArgsSchema, TopicCreateArgsSchema, TopicSetPlanArgsSchema, TopicAppendArgsSchema, TopicRejectArgsSchema, TopicAcceptArgsSchema, TopicCloseArgsSchema, TopicRenameArgsSchema, TopicDeleteArgsSchema, TopicListItemSchema, TopicListResultSchema, TopicGetResultSchema, TopicCreateResultSchema, TopicRejectResultSchema, TopicAcceptResultSchema, TopicResultSchema, TopicDeleteResultSchema;
6880
6880
  var init_topic = __esm({
6881
6881
  "../protocol/src/topic.ts"() {
6882
6882
  "use strict";
6883
6883
  init_zod();
6884
6884
  init_dispatch();
6885
6885
  nonBlank2 = external_exports.string().trim().min(1);
6886
+ TOPIC_DECISION_TYPES = ["master-confirmed", "owner-needed"];
6887
+ TOPIC_DECISION_STATUSES = ["open", "resolved"];
6888
+ TOPIC_DECISION_TYPE_MASTER_CONFIRMED = TOPIC_DECISION_TYPES[0];
6889
+ TOPIC_DECISION_TYPE_OWNER_NEEDED = TOPIC_DECISION_TYPES[1];
6890
+ TOPIC_DECISION_STATUS_OPEN = TOPIC_DECISION_STATUSES[0];
6891
+ TOPIC_DECISION_STATUS_RESOLVED = TOPIC_DECISION_STATUSES[1];
6892
+ TopicDecisionTypeSchema = external_exports.enum(TOPIC_DECISION_TYPES);
6893
+ TopicDecisionStatusSchema = external_exports.enum(TOPIC_DECISION_STATUSES);
6894
+ TopicDecisionSchema = external_exports.object({
6895
+ type: TopicDecisionTypeSchema,
6896
+ status: TopicDecisionStatusSchema,
6897
+ question: nonBlank2,
6898
+ recommendation: nonBlank2
6899
+ }).superRefine((decision, ctx) => {
6900
+ if (decision.type === TOPIC_DECISION_TYPE_MASTER_CONFIRMED && decision.status !== TOPIC_DECISION_STATUS_RESOLVED) {
6901
+ ctx.addIssue({
6902
+ code: external_exports.ZodIssueCode.custom,
6903
+ path: ["status"],
6904
+ message: "master-confirmed decisions must be resolved"
6905
+ });
6906
+ }
6907
+ });
6886
6908
  TOPIC_STATES = ["planning", "running", "done"];
6887
6909
  TopicStateSchema = external_exports.enum(TOPIC_STATES);
6888
6910
  TopicActivityEntrySchema = external_exports.object({
@@ -6892,7 +6914,12 @@ var init_topic = __esm({
6892
6914
  target: external_exports.string().optional(),
6893
6915
  reason: external_exports.string().min(1),
6894
6916
  /** 该动作确认的产出物(watcher 返回的 artifacts);影响面视图的唯一数据源 */
6895
- artifacts: external_exports.array(nonBlank2).optional()
6917
+ artifacts: external_exports.array(nonBlank2).optional(),
6918
+ /**
6919
+ * Optional decision projection; legacy activity entries omit this field. `open` means the
6920
+ * owner still needs to answer; `resolved` records the conclusion without deleting the question.
6921
+ */
6922
+ decision: TopicDecisionSchema.optional()
6896
6923
  });
6897
6924
  TopicLineMetaSchema = external_exports.object({
6898
6925
  rejectCount: external_exports.number().int().min(0),
@@ -6999,7 +7026,8 @@ var init_topic = __esm({
6999
7026
  target: nonBlank2.optional(),
7000
7027
  reason: nonBlank2,
7001
7028
  /** 该动作确认的产出物;影响面视图只吃这个字段 */
7002
- artifacts: external_exports.array(nonBlank2).optional()
7029
+ artifacts: external_exports.array(nonBlank2).optional(),
7030
+ decision: TopicActivityEntrySchema.shape.decision
7003
7031
  });
7004
7032
  TopicRejectArgsSchema = external_exports.object({
7005
7033
  topicId: external_exports.string().min(1),
@@ -8881,7 +8909,7 @@ var init_methods = __esm({
8881
8909
  args: TopicSetPlanArgsSchema
8882
8910
  },
8883
8911
  "topic:append": {
8884
- summary: '\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\uFF08\u8001\u677F\u4E8B\u540E\u770B\u7684\u662F"\u51ED\u4EC0\u4E48\u8FD9\u4E48\u5224\u65AD"\uFF09',
8912
+ summary: "\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\u3002Master \u505A\u4EE3\u8001\u677F\u786E\u8BA4\u6216\u5347\u7EA7\u8001\u677F\u51B3\u7B56\u65F6\uFF0C\u5FC5\u987B\u9644 decision\uFF08type/status/question/recommendation\uFF09",
8885
8913
  args: TopicAppendArgsSchema
8886
8914
  },
8887
8915
  "topic:reject": {
@@ -63880,7 +63908,7 @@ function computeMethodAccess(args) {
63880
63908
  }
63881
63909
 
63882
63910
  // src/version.ts
63883
- var version = "0.2.455".length > 0 ? "0.2.455" : "dev";
63911
+ var version = "0.2.457".length > 0 ? "0.2.457" : "dev";
63884
63912
 
63885
63913
  // src/cli-probe/probe.ts
63886
63914
  var fs70 = __toESM(require("fs"), 1);
@@ -65632,7 +65660,8 @@ function buildTopicHandlers(deps) {
65632
65660
  action: args.action,
65633
65661
  ...args.target ? { target: args.target } : {},
65634
65662
  reason: args.reason,
65635
- ...args.artifacts ? { artifacts: args.artifacts } : {}
65663
+ ...args.artifacts ? { artifacts: args.artifacts } : {},
65664
+ ...args.decision ? { decision: args.decision } : {}
65636
65665
  });
65637
65666
  announceChanged(topic);
65638
65667
  return { response: { ok: true } };
@@ -19,17 +19,27 @@
19
19
  S_ACCESS_ALIAS=clawd-fc
20
20
 
21
21
  # 分层加载阿里云 AK 进 env:shared demo(OTA 每次启动覆盖)→ 本机 .local override(永不被覆盖)。
22
+ #
23
+ # 两种来源,**文件优先、env 兜底**:
24
+ # - 本机:`$KIT_DIR/.secrets/aliyun.env`(+ 可选 .local),OTA 刷新;
25
+ # - 云上容器:没有 `.secrets/`(`cloud-host/provision/build-image.mjs` 刻意把它剔出镜像层
26
+ # ——镜像要分发,凭据进层就洗不掉了),AK 由 gateway 经 **MCP server 的 per-server env**
27
+ # 交到调用本脚本的那个进程里(M1b Step 9)。这条分支不落盘是有意的:写成文件的话
28
+ # agent 一个 `cat` 就看见了,而 env 只有这个子进程有。
22
29
  ensure_aliyun_env() {
23
30
  local kit="${KIT_DIR:?ensure_aliyun_env 需要调用方先设 KIT_DIR}"
24
- [ -f "$kit/.secrets/aliyun.env" ] || {
25
- echo "❌ 缺 $kit/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新)。参考 .secrets/aliyun.env.example 填写。" >&2
31
+ if [ -f "$kit/.secrets/aliyun.env" ]; then
32
+ set -a; . "$kit/.secrets/aliyun.env"; set +a
33
+ # aliyun.env.local: 本机个人 override(永远不被 OTA 覆盖,见 refreshDeployKit)。有就后 source 覆盖 env。
34
+ # 写成 if 而不是 `[ -f ] && { }`:后者在函数末尾会把「文件不存在」变成函数返回 1,调用方 set -e 直接中止。
35
+ if [ -f "$kit/.secrets/aliyun.env.local" ]; then
36
+ set -a; . "$kit/.secrets/aliyun.env.local"; set +a
37
+ fi
38
+ elif [ -n "${ALIBABA_CLOUD_ACCESS_KEY_ID:-}" ] && [ -n "${ALIBABA_CLOUD_ACCESS_KEY_SECRET:-}" ]; then
39
+ : # 云上:值已经在本进程 env 里,不落文件
40
+ else
41
+ echo "❌ 缺 $kit/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新),env 里也没有 ALIBABA_CLOUD_ACCESS_KEY_ID/_SECRET。参考 .secrets/aliyun.env.example 填写。" >&2
26
42
  return 1
27
- }
28
- set -a; . "$kit/.secrets/aliyun.env"; set +a
29
- # aliyun.env.local: 本机个人 override(永远不被 OTA 覆盖,见 refreshDeployKit)。有就后 source 覆盖 env。
30
- # 写成 if 而不是 `[ -f ] && { }`:后者在函数末尾会把「文件不存在」变成函数返回 1,调用方 set -e 直接中止。
31
- if [ -f "$kit/.secrets/aliyun.env.local" ]; then
32
- set -a; . "$kit/.secrets/aliyun.env.local"; set +a
33
43
  fi
34
44
  export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
35
45
  }
@@ -182,11 +182,70 @@ describe('ensure_credentials', () => {
182
182
  expect(kitAccessYaml()).toContain("AccountID: '5566778899001122'")
183
183
  })
184
184
 
185
- it('缺共享凭证文件时 fail-loud', () => {
185
+ it('缺共享凭证文件、env 里也没有 AK 时 fail-loud', () => {
186
186
  rmSync(join(box.kit, '.secrets', 'aliyun.env'))
187
187
  expect(() => runInKit('ensure_credentials')).toThrow()
188
188
  expect(existsSync(join(box.kit, '.s-home', '.s', 'access.yaml'))).toBe(false)
189
189
  })
190
+
191
+ // 云上分支(persona 上云 M1b Step 9):容器里没有 `.secrets/`——`build-image.mjs` 刻意把它
192
+ // 剔出镜像层(镜像要分发,凭据进层洗不掉)。AK 由 gateway 经 **MCP server 的 per-server env**
193
+ // 交到调用本脚本的那个子进程里,不落文件:写成文件的话 agent 一个 `cat` 就看见了。
194
+ it('没有 .secrets/aliyun.env 但 env 里有 AK 时用 env(云上容器那条路)', () => {
195
+ rmSync(join(box.kit, '.secrets', 'aliyun.env'))
196
+ const out = execFileSync(
197
+ 'bash',
198
+ [
199
+ '-c',
200
+ `set -euo pipefail
201
+ KIT_DIR="${box.kit}"
202
+ REGION=cn-hangzhou
203
+ source "$KIT_DIR/scripts/ensure-credentials.sh"
204
+ ensure_credentials
205
+ printf 'ok %s\\n' "$ALIBABA_CLOUD_ACCESS_KEY_ID"`,
206
+ ],
207
+ {
208
+ env: {
209
+ ...process.env,
210
+ HOME: box.home,
211
+ PATH: `${box.bin}:${process.env.PATH}`,
212
+ ALIBABA_CLOUD_ACCESS_KEY_ID: 'LTAI-FROM-ENV',
213
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET: 'secret-from-env',
214
+ },
215
+ encoding: 'utf8',
216
+ },
217
+ )
218
+ expect(out).toContain('ok LTAI-FROM-ENV')
219
+ // 走 env 分支照样把凭证落进 kit 私有 s-home(publish 用的是那份 profile),仍不碰用户全局
220
+ expect(kitAccessYaml()).toContain('LTAI-FROM-ENV')
221
+ expect(userAccessYaml()).toContain('LTAI-USER-OWN-KEY')
222
+ })
223
+
224
+ it('文件在就优先用文件(云上那条只是兜底,不能反过来盖掉本机 shared 凭证)', () => {
225
+ const out = execFileSync(
226
+ 'bash',
227
+ [
228
+ '-c',
229
+ `set -euo pipefail
230
+ KIT_DIR="${box.kit}"
231
+ REGION=cn-hangzhou
232
+ source "$KIT_DIR/scripts/ensure-credentials.sh"
233
+ ensure_aliyun_env
234
+ printf 'id=%s\\n' "$ALIBABA_CLOUD_ACCESS_KEY_ID"`,
235
+ ],
236
+ {
237
+ env: {
238
+ ...process.env,
239
+ HOME: box.home,
240
+ PATH: `${box.bin}:${process.env.PATH}`,
241
+ ALIBABA_CLOUD_ACCESS_KEY_ID: 'LTAI-FROM-ENV',
242
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET: 'secret-from-env',
243
+ },
244
+ encoding: 'utf8',
245
+ },
246
+ )
247
+ expect(out).not.toContain('LTAI-FROM-ENV')
248
+ })
190
249
  })
191
250
 
192
251
  describe('publish.sh', () => {
@@ -22056,6 +22056,28 @@ var ChannelDeleteResultSchema = external_exports.object({
22056
22056
 
22057
22057
  // ../protocol/src/topic.ts
22058
22058
  var nonBlank2 = external_exports.string().trim().min(1);
22059
+ var TOPIC_DECISION_TYPES = ["master-confirmed", "owner-needed"];
22060
+ var TOPIC_DECISION_STATUSES = ["open", "resolved"];
22061
+ var TOPIC_DECISION_TYPE_MASTER_CONFIRMED = TOPIC_DECISION_TYPES[0];
22062
+ var TOPIC_DECISION_TYPE_OWNER_NEEDED = TOPIC_DECISION_TYPES[1];
22063
+ var TOPIC_DECISION_STATUS_OPEN = TOPIC_DECISION_STATUSES[0];
22064
+ var TOPIC_DECISION_STATUS_RESOLVED = TOPIC_DECISION_STATUSES[1];
22065
+ var TopicDecisionTypeSchema = external_exports.enum(TOPIC_DECISION_TYPES);
22066
+ var TopicDecisionStatusSchema = external_exports.enum(TOPIC_DECISION_STATUSES);
22067
+ var TopicDecisionSchema = external_exports.object({
22068
+ type: TopicDecisionTypeSchema,
22069
+ status: TopicDecisionStatusSchema,
22070
+ question: nonBlank2,
22071
+ recommendation: nonBlank2
22072
+ }).superRefine((decision, ctx) => {
22073
+ if (decision.type === TOPIC_DECISION_TYPE_MASTER_CONFIRMED && decision.status !== TOPIC_DECISION_STATUS_RESOLVED) {
22074
+ ctx.addIssue({
22075
+ code: external_exports.ZodIssueCode.custom,
22076
+ path: ["status"],
22077
+ message: "master-confirmed decisions must be resolved"
22078
+ });
22079
+ }
22080
+ });
22059
22081
  var TOPIC_STATES = ["planning", "running", "done"];
22060
22082
  var TopicStateSchema = external_exports.enum(TOPIC_STATES);
22061
22083
  var TopicActivityEntrySchema = external_exports.object({
@@ -22065,7 +22087,12 @@ var TopicActivityEntrySchema = external_exports.object({
22065
22087
  target: external_exports.string().optional(),
22066
22088
  reason: external_exports.string().min(1),
22067
22089
  /** 该动作确认的产出物(watcher 返回的 artifacts);影响面视图的唯一数据源 */
22068
- artifacts: external_exports.array(nonBlank2).optional()
22090
+ artifacts: external_exports.array(nonBlank2).optional(),
22091
+ /**
22092
+ * Optional decision projection; legacy activity entries omit this field. `open` means the
22093
+ * owner still needs to answer; `resolved` records the conclusion without deleting the question.
22094
+ */
22095
+ decision: TopicDecisionSchema.optional()
22069
22096
  });
22070
22097
  var TopicLineMetaSchema = external_exports.object({
22071
22098
  rejectCount: external_exports.number().int().min(0),
@@ -22172,7 +22199,8 @@ var TopicAppendArgsSchema = external_exports.object({
22172
22199
  target: nonBlank2.optional(),
22173
22200
  reason: nonBlank2,
22174
22201
  /** 该动作确认的产出物;影响面视图只吃这个字段 */
22175
- artifacts: external_exports.array(nonBlank2).optional()
22202
+ artifacts: external_exports.array(nonBlank2).optional(),
22203
+ decision: TopicActivityEntrySchema.shape.decision
22176
22204
  });
22177
22205
  var TopicRejectArgsSchema = external_exports.object({
22178
22206
  topicId: external_exports.string().min(1),
@@ -23925,7 +23953,7 @@ var METHOD_DOCS = {
23925
23953
  args: TopicSetPlanArgsSchema
23926
23954
  },
23927
23955
  "topic:append": {
23928
- summary: '\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\uFF08\u8001\u677F\u4E8B\u540E\u770B\u7684\u662F"\u51ED\u4EC0\u4E48\u8FD9\u4E48\u5224\u65AD"\uFF09',
23956
+ summary: "\u5F80\u8BDD\u9898\u6D41\u6C34\u8FFD\u52A0\u4E00\u6761\uFF1Breason \u5FC5\u586B\u3002Master \u505A\u4EE3\u8001\u677F\u786E\u8BA4\u6216\u5347\u7EA7\u8001\u677F\u51B3\u7B56\u65F6\uFF0C\u5FC5\u987B\u9644 decision\uFF08type/status/question/recommendation\uFF09",
23929
23957
  args: TopicAppendArgsSchema
23930
23958
  },
23931
23959
  "topic:reject": {
@@ -154,6 +154,74 @@ worker 干到一半上报「答不了的问题」(它会以回报形式带出
154
154
  先自己判断——你是任务源头,多数问题你能答,答了就续派回注。答不了 → 停下上报老板拍板。
155
155
  **不许用未经确认的假设擅自解锁一条挂起的线**——除非该假设来自设定里的预授权或老板临场拍板。
156
156
 
157
+ ### 6.1 决策留痕:代老板确认与向老板升级(硬性)
158
+
159
+ worker 的问题不能只在对话里一闪而过。你每次替老板作出判断,或因为判断不了而停线,
160
+ 都必须在**作出动作的当下**用 `topic:append` 记录一条决策;普通的派活、收结果、进度更新
161
+ 仍按下面「落账纪律」记录,**不要给每条普通流水都加 `decision`**。
162
+
163
+ #### 你能独立判断时:记录 `master-confirmed`
164
+
165
+ 只有设定中已有预授权、或问题完全落在本话题已确认的目标/边界/验收标准内,才可以代老板
166
+ 确认。先落账并确认 RPC 成功,再把决定回注给对应 worker;落账失败就停下并上报,不能无痕继续:
167
+
168
+ mcp__clawd-rpc__call({ method: 'topic:append', args: {
169
+ topicId, action: '代老板确认:采用方案 A', target: '<lineId>',
170
+ reason: '方案 A 符合已确认的边界 <边界>,不会触碰 <风险>;因此无需打扰老板',
171
+ decision: {
172
+ type: 'master-confirmed', status: 'resolved',
173
+ question: 'worker 提出的问题原文', recommendation: '最终采用的决定',
174
+ },
175
+ }})
176
+
177
+ `target` 必须指向提出问题的 worker lane;`question` 保留问题原文,`recommendation` 写实际
178
+ 采取的决定。`reason` 不能只写「符合预授权」,要指出对应哪条边界/标准以及排除了什么风险。
179
+
180
+ #### 你不能独立判断时:记录 `owner-needed` 并停线
181
+
182
+ 遇到设定没有覆盖、目标或边界有两种合理解释、会造成不可逆外部影响,或你判断「可能判断错」
183
+ 时,**禁止猜测后继续**。先停止给对应 lane 续派;如果 worker 仍在运行,先按下面的方式温柔中断,
184
+ 再落账。任何 RPC 失败都要保持暂停并把失败原因一并上报:
185
+
186
+ mcp__clawd-rpc__call({ method: 'session:interrupt', args: {
187
+ sessionId: '<该线的 workerSessionId>',
188
+ }})
189
+
190
+ `session:interrupt` 只结束当前 worker turn,不杀会话、不丢上下文;恢复时仍复用原
191
+ `dispatchId`。如果该调用失败,不能假定 worker 已停下;仍要追加下面的 `owner-needed`
192
+ 记录,并在 `reason` 里写明中断失败,继续保持不续派。
193
+
194
+ mcp__clawd-rpc__call({ method: 'topic:append', args: {
195
+ topicId, action: '需要老板拍板:<一句话问题>', target: '<lineId>',
196
+ reason: '现有设定同时允许 <A> 与 <B>,继续执行可能导致 <具体后果>,我无法安全代判',
197
+ decision: {
198
+ type: 'owner-needed', status: 'open',
199
+ question: '请老板在 <选项 A> / <选项 B> 中选择(附必要上下文)',
200
+ recommendation: '如无额外偏好,我建议 <选项>,因为 <理由>',
201
+ },
202
+ }})
203
+
204
+ 然后向老板报告这条待决策项,并等待明确答复;不要用普通 `topic:append` 假装已经解决。
205
+ 如果 worker 已经 idle,则不需要额外 RPC;只是不再给该 `dispatchId` 续派,等老板答复。
206
+ 老板答复后,先把答复原样记录成同一 `target` 的已解决决策,再用**同一个 `dispatchId`**
207
+ 续派可执行指令;不要新建第二条线。`session:interrupt` 失败时不能当作已经停住。
208
+ 记录已解决决策的调用仍走 `topic:append`:
209
+
210
+ mcp__clawd-rpc__call({ method: 'topic:append', args: {
211
+ topicId, action: '按老板决定:<一句话结论>', target: '<lineId>',
212
+ reason: '老板明确选择了 <选项>,因此按该选择恢复这条线',
213
+ decision: {
214
+ type: 'owner-needed', status: 'resolved',
215
+ question: '<原待决策问题>', recommendation: '老板最终选择:<选项>',
216
+ },
217
+ }})
218
+
219
+ 随后才用原 `dispatchId` 续派。决策记录落账失败就不要继续派活,先上报失败原因。
220
+ 决策 tab 会据此保留完整的「提出问题 → 得到结论」轨迹。
221
+
222
+ 这两类记录是老板在话题「决策」tab 里唯一关注的内容:没有 `decision` 的旧流水不会出现在
223
+ 该 tab,但仍保存在话题外部大脑中,供你用 `topic:get` 重建现场。
224
+
157
225
  ### 7. 收敛与上报
158
226
 
159
227
  `topic:accept` 返回 `allAccepted: true` → 合并 → 自己先验一遍 → 交付。