cs-bots 0.7.5 → 0.8.0

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/src/client.js CHANGED
@@ -493,14 +493,14 @@ window.__ModuleLoader__.load({
493
493
  */
494
494
  const ERROR_MAP = {
495
495
  BOARD_WIP_EXCEEDED: {
496
- actionLabel: `查看${HUMAN_LABEL}的任务`,
497
- text: '你已经在管 N 个任务了(上限 M),先完成或释放一些再接新的',
496
+ actionLabel: '查看任务栏',
497
+ text: `${HUMAN_LABEL}已经在管 N 个任务了(上限 M),先完成或释放一些再接新的`,
498
498
  hint: 'WIP 超限:同一 Bot 名下进行中的任务数达到上限',
499
499
  },
500
500
  BOARD_LEASE_HELD: {
501
501
  actionLabel: '查看持有者',
502
- text: '这个任务正由另一个 Bot 在做,等它完成或租约过期',
503
- hint: '租约被占用:任务上有未过期的租约,持有者不是当前操作者',
502
+ text: '这个任务正被另一个 Bot 占着,等它完成或把任务放回待办',
503
+ hint: '任务上有未过期的占用(这是防抢机制,不在页面展示),持有者不是当前操作者',
504
504
  },
505
505
  BOARD_NOT_READY: {
506
506
  actionLabel: '查看前置任务',
@@ -519,12 +519,12 @@ window.__ModuleLoader__.load({
519
519
  },
520
520
  BOARD_REV_CONFLICT: {
521
521
  actionLabel: '刷新',
522
- text: '这个任务刚被别人改过,刷新一下再操作',
522
+ text: '这个任务刚被别的 Bot 改过,刷新一下再操作',
523
523
  hint: 'rev 不匹配:expectedRev 与当前 rev 不一致(CAS 拒绝静默覆盖)',
524
524
  },
525
525
  CAS_MISMATCH: {
526
526
  actionLabel: '刷新',
527
- text: '这个任务刚被别人改过,刷新一下再操作',
527
+ text: '这个任务刚被别的 Bot 改过,刷新一下再操作',
528
528
  hint: 'rev 不匹配:expectedRev 与当前 rev 不一致(CAS 拒绝静默覆盖)',
529
529
  },
530
530
  BOARD_TASK_DELETED: {
@@ -543,7 +543,7 @@ window.__ModuleLoader__.load({
543
543
  * 错误可读映射:后端 code → 人话 + 下一步建议。
544
544
  *
545
545
  * 为什么:后端 message 只描述「发生了什么」(还夹着 id/rev),
546
- * 用户看完仍然不知道**接下来该做什么**。高频冲突(WIP 超限、租约占用、
546
+ * 用户看完仍然不知道**接下来该做什么**。高频冲突(WIP 超限、占用冲突、
547
547
  * rev 冲突)恰恰是最需要下一步的,所以按 code 映射。
548
548
  *
549
549
  * 后端 message 仍保留在 hint 里 —— 定位信息(哪个任务 / 哪个 Bot)不能丢。
@@ -958,6 +958,12 @@ window.__ModuleLoader__.load({
958
958
  const [graphError, setGraphError] = useState(null)
959
959
  // 交付物文件预览弹窗
960
960
  const [previewPath, setPreviewPath] = useState(null)
961
+ // 换负责人弹窗(契约 §4.3(c):Boss 直接换负责人 / 重新派发)
962
+ const [reassignOpen, setReassignOpen] = useState(false)
963
+ // 契约 §5.3:投递失败展示 + 「诊断这个任务」一次性排查(需求6)
964
+ const [diagnose, setDiagnose] = useState(null)
965
+ const [diagnoseLoading, setDiagnoseLoading] = useState(false)
966
+ const [diagnoseErr, setDiagnoseErr] = useState(null)
961
967
  // 预览内容在这里统一取(需求3):弹窗只是壳,由父组件拉数据,
962
968
  // 否则弹窗自己的 effect 只在真实渲染器挂载子组件时才跑,测试桩与边缘场景都取不到内容。
963
969
  const [preview, setPreview] = useState(null)
@@ -1010,6 +1016,13 @@ window.__ModuleLoader__.load({
1010
1016
  return () => { alive = false }
1011
1017
  }, [task?.id])
1012
1018
 
1019
+ // 进度来源(契约 §6):board 侧新增只读派生字段 progressBy/progressAt。
1020
+ // `progressBy === 'ui'` ⇒ 最后一次写进度的是 Boss(前端提交);
1021
+ // 旧数据没有这个字段 ⇒ 视为「队长估算」(Boss 从没写过 progress)。
1022
+ // ⚠️ 必须在组件体顶层用 `task?.` 取值:首渲染时 `data` 还没到(loading),
1023
+ // `task` 是 undefined —— 写 `task.progressBy` 会让整个抽屉白屏(已实测复现)。
1024
+ const progressSource = task?.progressBy === 'ui' ? `${HUMAN_LABEL}设定` : '队长估算'
1025
+
1013
1026
  const saveProgress = async () => {
1014
1027
  const v = Number(progressVal)
1015
1028
  if (Number.isNaN(v) || v < 0 || v > 100) {
@@ -1074,7 +1087,7 @@ window.__ModuleLoader__.load({
1074
1087
  h('span', { key: 'se', className: 'csb-k' }, '执行阶段'),
1075
1088
  h('span', { key: 'sv', className: 'csb-v' },
1076
1089
  h('span', { className: stageBadgeCls(data.stageInfo.group), title: data.stageInfo.reason }, data.stageInfo.label),
1077
- data.stageInfo.needsPerson ? h('span', { className: 'csb-b csb-b-run', style: { marginLeft: 8 } }, '需要你处理') : null),
1090
+ data.stageInfo.needsPerson ? h('span', { className: 'csb-b csb-b-run', style: { marginLeft: 8 } }, '需要 Boss 处理') : null),
1078
1091
  ]
1079
1092
  : null,
1080
1093
  h('span', { className: 'csb-k' }, '任务编号'), h('span', { className: 'csb-v csb-mono' }, task.id),
@@ -1084,7 +1097,7 @@ window.__ModuleLoader__.load({
1084
1097
  // 人会在「等你回答」里看到一句「端口被占用」而不知道该回什么。
1085
1098
  data?.stageInfo?.stage === 'person/answer' && task.latestReport?.attention?.question
1086
1099
  ? [
1087
- h('span', { key: 'ae', className: 'csb-k' }, '要你回答'),
1100
+ h('span', { key: 'ae', className: 'csb-k' }, '要 Boss 回答'),
1088
1101
  h('span', { key: 'av', className: 'csb-v', style: { color: 'var(--dsw-alias-text-warn, #b45309)', fontWeight: 600 } },
1089
1102
  h(CollapsibleText, { text: String(task.latestReport.attention.question), limit: 300 })),
1090
1103
  ]
@@ -1111,7 +1124,7 @@ window.__ModuleLoader__.load({
1111
1124
  : '可以做什么',
1112
1125
  desc: task.status === 'completed'
1113
1126
  ? '已完成不是终点:下面的「变更」区可以重开接着做 / 派生优化单 / 转成长任务。'
1114
- : '指派、完成、验收是 Bot 的职责,不由你操作。这里只有需要你拍板的动作。',
1127
+ : '下面这些动作 Boss 可以直接做:改优先级/截止/复杂度/付费、改目标与边界、换负责人、阻塞与解阻塞、编辑长任务的阶段清单。每个操作都会如实提示结果。',
1115
1128
  }),
1116
1129
  h('div', { className: 'csb-bar', style: { marginTop: 10 } },
1117
1130
  task.status === 'pending'
@@ -1125,16 +1138,97 @@ window.__ModuleLoader__.load({
1125
1138
  // 改造06(CONTRACT-bots06 §3.1 / §3.3):变更中心 —— 放在「可以做什么」之后
1126
1139
  h(TaskEvolvePanel, { taskId: task.id, task, onChange }),
1127
1140
 
1128
- // 高级:租约(原「续租」按钮,普通人看不懂,收进折叠并解释)
1129
- h(Section, { title: '高级:租约', desc: '租约是「我先占着,别人别抢」的凭据。领取任务默认占 5 分钟,长任务需要隔一会儿点一次「延长」,否则会被自动释放回待办。' }),
1130
- task.status === 'in_progress'
1131
- ? h('div', { className: 'csb-bar', style: { marginTop: 10 } },
1132
- h('span', { className: 'csb-set-d' }, task.lease ? `当前占用到 ${fmt(task.lease.expiresAt)}` : '当前没有租约'),
1133
- h('button', { className: 'csb-btn csb-btn-s', onClick: () => act(() => upd('heartbeat'), '延长') }, '延长 5 分钟'))
1134
- : h('div', { className: 'csb-set-d', style: { marginTop: 10 } }, '任务不在进行中,没有租约。'),
1141
+ // 契约 §4.3:Boss 直接动手(改排期/路由、改目标与边界、换负责人、
1142
+ // 阻塞/解除阻塞/归档、编辑长任务阶段清单)——全部内联,不跳页、不靠 prompt。
1143
+ h(TaskBossActions, {
1144
+ task,
1145
+ bots,
1146
+ onChange,
1147
+ onReassign: () => setReassignOpen(true),
1148
+ }),
1149
+
1150
+ // 需求2:任务对话(进度 + 对话内容 + 去对话跳转),直接放在产出物之前
1151
+ h(TaskConversationPanel, { taskId: task.id, task, bots, onGotoSource }),
1152
+
1153
+ // 契约 §2.5(需求3):阶段产出物 —— 必须在总产出物证据之前,
1154
+ // 让 Boss 「点击去看产出物」时先看到过程产物,不用去翻对话。
1155
+ h(TaskPhasePanel, { taskId: task.id, onPreview: (p) => setPreviewPath(p) }),
1156
+
1157
+ // ── 契约 §5(需求6):投递失败必须可见,且能一键诊断 ────────────────
1158
+ // 「armDeliveryWatch 超时未兑现」的落点就在 `task.delivery.error`,
1159
+ // 后端把它留在任务上(一直显示到被解决),前端必须把它显示出来 ——
1160
+ // 否则 Boss 看到的是「任务一直没动静」,没有一句人话告诉他卡在哪。
1161
+ task.delivery?.error
1162
+ ? h('div', { className: 'csb-err', style: { marginTop: 12 } },
1163
+ `投递失败:${task.delivery.error}`
1164
+ + (task.delivery.at ? `(${fmt(task.delivery.at)})` : '')
1165
+ + ' —— 先点「诊断这个任务」看执行者能不能干活,再决定重试还是换人。')
1166
+ : null,
1167
+ h('div', { className: 'csb-bar', style: { marginTop: 10, gap: 6 } },
1168
+ h('button', {
1169
+ className: 'csb-btn', disabled: diagnoseLoading,
1170
+ onClick: async () => {
1171
+ setDiagnoseLoading(true)
1172
+ setDiagnoseErr(null)
1173
+ setDiagnose(null)
1174
+ try {
1175
+ const d = await call(`/tasks/diagnose?id=${encodeURIComponent(task.id)}`, { method: 'GET' })
1176
+ setDiagnose(d)
1177
+ } catch (e) {
1178
+ setDiagnoseErr(humanizeError(e, {}, '诊断失败:'))
1179
+ } finally {
1180
+ setDiagnoseLoading(false)
1181
+ }
1182
+ },
1183
+ }, diagnoseLoading ? '诊断中…' : '诊断这个任务')),
1184
+ // 诊断结果:一段人话结论 + 各项状态(Boss 不用自己翻日志)
1185
+ diagnoseErr
1186
+ ? h('div', { className: 'csb-err' }, diagnoseErr.text)
1187
+ : null,
1188
+ diagnose
1189
+ ? h('div', { className: 'csb-card', style: { marginTop: 10, padding: '10px 12px' } },
1190
+ h('div', { className: 'csb-card-main' },
1191
+ h('div', { className: 'csb-card-t' },
1192
+ diagnose.verdict === 'blocked'
1193
+ ? '执行者暂时不能干活'
1194
+ : diagnose.verdict === 'ok'
1195
+ ? '执行者可以干活'
1196
+ : '诊断结果'),
1197
+ diagnose.advice
1198
+ ? h('div', { className: 'csb-card-m', style: { marginTop: 6 } }, diagnose.advice)
1199
+ : null,
1200
+ // 每一项状态都显示(ok / 被谁卡住),不用猜
1201
+ h('div', { className: 'csb-card-m', style: { marginTop: 6, flexWrap: 'wrap' } },
1202
+ h('span', { className: 'csb-b' }, `执行者:${diagnose.bot?.name ?? '(未知)'}`),
1203
+ h('span', { className: 'csb-b' },
1204
+ diagnose.bot?.hasSession ? '会话在线' : '会话不在线'),
1205
+ diagnose.bot?.preset
1206
+ ? h('span', { className: 'csb-b' }, `预制体:${diagnose.bot.preset}`)
1207
+ : null,
1208
+ diagnose.live
1209
+ ? h('span', { className: 'csb-b' }, diagnose.live.ok ? '能拉起' : `拉不起来:${diagnose.live.reason ?? '未知'}`)
1210
+ : null),
1211
+ (diagnose.wakes ?? []).length > 0
1212
+ ? h('div', { className: 'csb-card-m', style: { marginTop: 6, flexWrap: 'wrap' } },
1213
+ '最近唤醒:',
1214
+ diagnose.wakes.slice(0, 5).map((w, i) =>
1215
+ h('span', { key: i, className: 'csb-b csb-b-err' },
1216
+ `${w.result ?? '未知'}${w.reason ? `(${w.reason})` : ''}`)))
1217
+ : null))
1218
+ : null,
1135
1219
 
1136
1220
  // 进度编辑
1137
- h(Section, { title: '更新进度', desc: `当前 ${task.progress ?? 0}%。填入 0-100 的数字并保存(走 CAS,别人刚改过会提示刷新)。` }),
1221
+ // 契约 §6:`estimate`(预估工作量,队长承诺的投入)与 `progress`(实际完成度)
1222
+ // 是**两个不同的数**,必须分两行显示 —— 混成一个数会让人以为「估 8 小时、做了 8」
1223
+ // 就是「100% 做完」。
1224
+ h(Section, {
1225
+ title: '进度',
1226
+ desc: `当前完成度 ${task.progress ?? 0}%(${progressSource})。`
1227
+ + (task.estimate !== undefined && task.estimate !== null
1228
+ ? `预估工作量 ${task.estimate}(队长估的投入,跟完成度不是一回事)。`
1229
+ : '')
1230
+ + '填入 0-100 的数字并保存(保存走 CAS,任务刚被改过会提示刷新)。',
1231
+ }),
1138
1232
  h('div', { className: 'csb-bar', style: { marginTop: 10 } },
1139
1233
  h('input', {
1140
1234
  className: 'csb-in', style: { width: 100 }, type: 'number', min: 0, max: 100,
@@ -1223,7 +1317,11 @@ window.__ModuleLoader__.load({
1223
1317
  error: preview?.error ?? null,
1224
1318
  data: preview?.data ?? null,
1225
1319
  onClose: () => setPreviewPath(null),
1226
- }) : null)
1320
+ }) : null,
1321
+ // 契约 §4.3(c):Boss 直接换负责人 / 重新派发(内联弹窗)
1322
+ reassignOpen
1323
+ ? h(TaskReassignModal, { task, bots, onClose: () => setReassignOpen(false), onChange })
1324
+ : null)
1227
1325
  }
1228
1326
 
1229
1327
  /**
@@ -1511,7 +1609,7 @@ window.__ModuleLoader__.load({
1511
1609
  * 把任务的「负责人相关字段」解析成一个真正可跳转的 Bot。
1512
1610
  *
1513
1611
  * 为什么需要它(2026-11-06 用户实测报错「该负责人不是本平台的 Bot,无法跳转…定位 ui」):
1514
- * - `ownerId` 是**租约持有者**:人会自己 claim(`ui`),或验收时被覆盖;
1612
+ * - `ownerId` 是**占用持有者**:Boss 会自己 claim(`ui`),或验收时被覆盖;
1515
1613
  * - `assignee` 才是「这活原本派给谁」;
1516
1614
  * - 任务可能没有 owner 但创建时填了 assignee。
1517
1615
  * 所以按 **assignee → ownerId → task.ownerId** 顺序找,命中即用;
@@ -1602,7 +1700,7 @@ window.__ModuleLoader__.load({
1602
1700
  * 去对话:把「负责人」解析成真正的 Bot 会话,再调宿主 openSession。
1603
1701
  *
1604
1702
  * 为什么不能只看 ownerId(2026-11-06 用户实测报错):
1605
- * `ownerId` 是**租约持有者**,人会自己 claim(`ui`)或验收时被覆盖;
1703
+ * `ownerId` 是**占用持有者**,Boss 会自己 claim(`ui`)或验收时被覆盖;
1606
1704
  * 而「这活原本派给谁」记在 `assignee` 上。只看 ownerId 就会出现
1607
1705
  * 「任务明明派给了 bot-xxx,点去对话却说『该负责人不是本平台的 Bot』,定位 ui」。
1608
1706
  * ⇒ 按 **assignee → ownerId → anchor.ownerId** 顺序逐个尝试,
@@ -1620,7 +1718,7 @@ window.__ModuleLoader__.load({
1620
1718
  setJump({
1621
1719
  id: candidates[0],
1622
1720
  hint: onlyPlatform
1623
- ? '这个任务的负责人是平台操作者(你),没有可跳转的 Bot 会话。'
1721
+ ? '这个任务的负责人是平台自身的 Boss 身份,没有可跳转的 Bot 会话。'
1624
1722
  + '如果它是派给某个 Bot 的,请确认「负责人」字段填的是 Bot 的会话 ID(可在 Bot 页复制)。'
1625
1723
  : '这个任务的负责人不是本平台注册的 Bot(可能已被退役,或是外部会话),无法自动跳转。'
1626
1724
  + '可以复制 ID 到左侧会话列表里手动定位。',
@@ -1804,7 +1902,7 @@ window.__ModuleLoader__.load({
1804
1902
  h('div', { style: { fontSize: 12.5, lineHeight: 1.7, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } },
1805
1903
  h('span', { className: 'csb-mono', style: { marginRight: 6 } }, `[${fmt(n.at)}]`),
1806
1904
  n.text ?? '',
1807
- h('span', { className: 'csb-set-d' }, `(${n.by ?? 'ui'})`)))))),
1905
+ h('span', { className: 'csb-set-d' }, `(${humanizeBy(n.by)})`)))))),
1808
1906
 
1809
1907
  // 改造06:换负责人弹窗(Bot 下拉 → POST /tasks/reassign)
1810
1908
  reassignOpen
@@ -1909,7 +2007,7 @@ window.__ModuleLoader__.load({
1909
2007
  h('span', { className: 'csb-v' },
1910
2008
  h('span', { className: stageBadgeCls(si.group), title: si.reason }, si.label),
1911
2009
  si.needsPerson
1912
- ? h('span', { className: 'csb-b csb-b-run', style: { marginLeft: 8 } }, '需要你处理')
2010
+ ? h('span', { className: 'csb-b csb-b-run', style: { marginLeft: 8 } }, '需要 Boss 处理')
1913
2011
  : null)))
1914
2012
  }
1915
2013
  // ── 执行者状态(2026-11-09:「显示正在执行中,但我看不到过程」)────
@@ -1973,6 +2071,327 @@ window.__ModuleLoader__.load({
1973
2071
  })())
1974
2072
  }
1975
2073
 
2074
+ /**
2075
+ * 任务详情 ·「Boss 直接动手」区块(契约 §4.3,需求5)。
2076
+ *
2077
+ * Boss 原话:「我要能直接动手,不要只给我看」。五类动作全部做成**内联表单**,
2078
+ * 不跳页、不弹浏览器 prompt;每个写操作都 toast 出真实结果(成功 / 失败理由),
2079
+ * 绝不允许「点了没反应」。
2080
+ *
2081
+ * 五条路径(契约 §4.1 的端点,A 直接用):
2082
+ * (a) 改优先级 / 截止 / 复杂度档位 T0–T3 / 是否付费 → `POST /tasks/update` action:'edit'
2083
+ * (b) 改目标 / 边界(description、writeScopes) → 同上
2084
+ * (c) 换负责人 / 收回 / 重新派发 → `POST /tasks/reassign` / action:'release'
2085
+ * (d) 阻塞 / 解除阻塞 / 关闭归档 → action:'block'/'unblock'/'delete'
2086
+ * (e) 编辑长任务阶段清单 → `POST /tasks/phases`
2087
+ *
2088
+ * CAS:所有写都带 `expectedRev: task.rev`;冲突时后端 409,这里显示人话并给刷新入口。
2089
+ */
2090
+ function TaskBossActions({ task, bots, onChange, onReassign }) {
2091
+ // 当前展开的表单(同时只开一个,避免整页都是输入框)
2092
+ const [form, setForm] = useState(null) // 'basic' | 'scope' | 'block' | 'phases'
2093
+ const [busy, setBusy] = useState(null)
2094
+ const [err, setErr] = useState(null)
2095
+ const [done, setDone] = useState(null)
2096
+
2097
+ // (a) 排期/路由字段
2098
+ const [priority, setPriority] = useState(task?.priority !== undefined ? String(task.priority) : '')
2099
+ const [dueAt, setDueAt] = useState(task?.dueAt ? toDateInput(task.dueAt) : '')
2100
+ const [complexity, setComplexity] = useState(task?.expectedComplexity ?? '')
2101
+ const [paid, setPaid] = useState(task?.requiresPaid === true ? 'yes' : task?.requiresPaid === false ? 'no' : '')
2102
+ // (b) 目标与边界
2103
+ const [subject, setSubject] = useState(task?.subject ?? '')
2104
+ const [description, setDescription] = useState(task?.description ?? '')
2105
+ const [scopes, setScopes] = useState((task?.writeScopes ?? []).join('\n'))
2106
+ // (d) 阻塞理由
2107
+ const [blockReason, setBlockReason] = useState(task?.blockedReason ?? '')
2108
+ // (e) 阶段清单
2109
+ const [phaseText, setPhaseText] = useState((task?.longrun?.phases ?? []).join('\n'))
2110
+
2111
+ if (!task) return null
2112
+
2113
+ const run = async (label, fn) => {
2114
+ setBusy(label)
2115
+ setErr(null)
2116
+ setDone(null)
2117
+ try {
2118
+ const d = await fn()
2119
+ setDone(d?.note ?? `${label}成功`)
2120
+ setForm(null)
2121
+ onChange?.()
2122
+ } catch (e) {
2123
+ setErr(humanizeError(e, {}, `${label}失败:`))
2124
+ } finally {
2125
+ setBusy(null)
2126
+ }
2127
+ }
2128
+ const edit = (body) => call('/tasks/update', {
2129
+ method: 'POST',
2130
+ body: { id: task.id, expectedRev: task.rev, action: 'edit', ...body },
2131
+ })
2132
+
2133
+ const saveBasic = () => run('保存', () => edit({
2134
+ // 空字符串 ⇒ 不传(不改);优先级用 null 表示清除
2135
+ ...(priority === '' ? {} : { priority: Number(priority) }),
2136
+ ...(dueAt === '' ? { dueAt: null } : { dueAt: dateInputToMs(dueAt) }),
2137
+ ...(complexity === '' ? { expectedComplexity: null } : { expectedComplexity: complexity }),
2138
+ ...(paid === '' ? { requiresPaid: null } : { requiresPaid: paid === 'yes' }),
2139
+ }))
2140
+ const saveScope = () => run('保存目标与边界', () => edit({
2141
+ ...(subject.trim() ? { subject: subject.trim() } : {}),
2142
+ description,
2143
+ writeScopes: scopes.split('\n').map((s) => s.trim()).filter(Boolean),
2144
+ }))
2145
+ const savePhases = () => run('保存阶段清单', () => call('/tasks/phases', {
2146
+ method: 'POST',
2147
+ body: {
2148
+ id: task.id,
2149
+ expectedRev: task.rev,
2150
+ phases: phaseText.split('\n').map((s) => s.trim()).filter(Boolean),
2151
+ },
2152
+ }))
2153
+ const doBlock = () => run('阻塞', () => call('/tasks/update', {
2154
+ method: 'POST',
2155
+ body: { id: task.id, expectedRev: task.rev, action: 'block', reason: blockReason.trim() || 'Boss 阻塞' },
2156
+ }))
2157
+ const doUnblock = () => run('解除阻塞', () => call('/tasks/update', {
2158
+ method: 'POST', body: { id: task.id, expectedRev: task.rev, action: 'unblock' },
2159
+ }))
2160
+ const doRelease = () => run('收回', () => call('/tasks/update', {
2161
+ method: 'POST', body: { id: task.id, expectedRev: task.rev, action: 'release' },
2162
+ }))
2163
+ const doArchive = () => run('归档', () => call('/tasks/update', {
2164
+ method: 'POST', body: { id: task.id, expectedRev: task.rev, action: 'delete' },
2165
+ }))
2166
+
2167
+ const toggle = (name) => setForm((v) => (v === name ? null : name))
2168
+ const btn = (label, name, extra) => h('button', {
2169
+ className: `csb-btn${name === form ? ' csb-btn-p' : ''}`,
2170
+ onClick: () => toggle(name), ...(extra ?? {}),
2171
+ }, label)
2172
+
2173
+ return h('div', { style: { marginTop: 10 } },
2174
+ h('div', { className: 'csb-bar', style: { flexWrap: 'wrap', gap: 6 } },
2175
+ btn('改优先级 / 截止 / 复杂度 / 付费', 'basic'),
2176
+ btn('改目标与边界', 'scope'),
2177
+ h('button', { className: 'csb-btn', disabled: Boolean(busy), onClick: () => onReassign?.() },
2178
+ busy === '换负责人' ? '换负责人中…' : '换负责人 / 重新派发'),
2179
+ h('button', { className: 'csb-btn', disabled: Boolean(busy), onClick: doRelease }, busy === '收回' ? '收回中…' : '收回任务'),
2180
+ btn(task.status === 'blocked' ? '解除阻塞' : '阻塞任务', 'block'),
2181
+ h('button', { className: 'csb-btn csb-btn-d', disabled: Boolean(busy), onClick: doArchive, title: '归档=从任务栏移除(可恢复)' }, '归档'),
2182
+ btn('编辑长任务阶段清单', 'phases')),
2183
+
2184
+ err ? h('div', { className: 'csb-err' }, err.text) : null,
2185
+ done ? h(Notice, { kind: 'ok', onClose: () => setDone(null) }, done) : null,
2186
+
2187
+ // (a) 排期与路由
2188
+ form === 'basic'
2189
+ ? h('div', { className: 'csb-fld', style: { marginTop: 10 } },
2190
+ h('div', { className: 'csb-bar', style: { gap: 10, flexWrap: 'wrap' } },
2191
+ h('label', null, '优先级',
2192
+ h('input', {
2193
+ className: 'csb-in', type: 'number', min: 1, max: 10, style: { width: 80, marginLeft: 6 },
2194
+ value: priority, onChange: (e) => setPriority(e.target.value),
2195
+ })),
2196
+ h('label', null, '截止',
2197
+ h('input', {
2198
+ className: 'csb-in', type: 'date', style: { marginLeft: 6 },
2199
+ value: dueAt, onChange: (e) => setDueAt(e.target.value),
2200
+ })),
2201
+ h('label', null, '复杂度',
2202
+ h('select', {
2203
+ className: 'csb-in', style: { marginLeft: 6 },
2204
+ value: complexity, onChange: (e) => setComplexity(e.target.value),
2205
+ },
2206
+ h('option', { value: '' }, '不设'),
2207
+ ['T0', 'T1', 'T2', 'T3'].map((t) => h('option', { key: t, value: t }, t)))),
2208
+ h('label', null, '付费渠道',
2209
+ h('select', {
2210
+ className: 'csb-in', style: { marginLeft: 6 },
2211
+ value: paid, onChange: (e) => setPaid(e.target.value),
2212
+ },
2213
+ h('option', { value: '' }, '不设'),
2214
+ h('option', { value: 'no' }, '不用(免费)'),
2215
+ h('option', { value: 'yes' }, '用(花 Boss 的钱)')))),
2216
+ h('div', { className: 'csb-fld-h' },
2217
+ '优先级 1-10(≥8 算紧急);截止留空=清除;复杂度 T0 简单 / T1 常规 / T2 较难 / T3 很难;付费勾了就走付费渠道。'),
2218
+ h('button', {
2219
+ className: 'csb-btn csb-btn-p', disabled: Boolean(busy), onClick: saveBasic,
2220
+ }, busy === '保存' ? '保存中…' : '保存'))
2221
+ : null,
2222
+
2223
+ // (b) 目标与边界
2224
+ form === 'scope'
2225
+ ? h('div', { className: 'csb-fld', style: { marginTop: 10 } },
2226
+ h('label', null, '任务标题'),
2227
+ h('input', { className: 'csb-in', value: subject, onChange: (e) => setSubject(e.target.value) }),
2228
+ h('label', null, '目标与细节'),
2229
+ h('textarea', {
2230
+ className: 'csb-ta', value: description,
2231
+ onChange: (e) => setDescription(e.target.value),
2232
+ placeholder: '写清做完是什么样、验收口径是什么',
2233
+ }),
2234
+ h('label', null, '写入边界(每行一个)'),
2235
+ h('textarea', {
2236
+ className: 'csb-ta', value: scopes,
2237
+ onChange: (e) => setScopes(e.target.value),
2238
+ placeholder: 'src/api.js\nsrc/client.js',
2239
+ }),
2240
+ h('div', { className: 'csb-fld-h' },
2241
+ '写入边界=这个任务允许改的文件前缀,每行一个。留空=不限。'),
2242
+ h('button', {
2243
+ className: 'csb-btn csb-btn-p', disabled: Boolean(busy), onClick: saveScope,
2244
+ }, busy === '保存目标与边界' ? '保存中…' : '保存'))
2245
+ : null,
2246
+
2247
+ // (d) 阻塞
2248
+ form === 'block'
2249
+ ? h('div', { className: 'csb-fld', style: { marginTop: 10 } },
2250
+ task.status === 'blocked'
2251
+ ? h('div', null,
2252
+ h('div', { className: 'csb-fld-h' }, '这个任务现在是阻塞状态。解除后执行者可以继续做。'),
2253
+ h('button', { className: 'csb-btn csb-btn-p', disabled: Boolean(busy), onClick: doUnblock },
2254
+ busy === '解除阻塞' ? '处理中…' : '解除阻塞'))
2255
+ : h('div', null,
2256
+ h('label', null, '阻塞原因'),
2257
+ h('input', {
2258
+ className: 'csb-in', value: blockReason,
2259
+ onChange: (e) => setBlockReason(e.target.value),
2260
+ placeholder: '例如:等 Boss 确认接口口径',
2261
+ }),
2262
+ h('div', { className: 'csb-fld-h' }, '阻塞后会记下原因,解阻塞时执行者能看到。'),
2263
+ h('button', { className: 'csb-btn csb-btn-d', disabled: Boolean(busy), onClick: doBlock },
2264
+ busy === '阻塞' ? '处理中…' : '确认阻塞')))
2265
+ : null,
2266
+
2267
+ // (e) 阶段清单
2268
+ form === 'phases'
2269
+ ? h('div', { className: 'csb-fld', style: { marginTop: 10 } },
2270
+ h('label', null, '阶段清单(每行一个阶段,最多 12 项)'),
2271
+ h('textarea', {
2272
+ className: 'csb-ta', value: phaseText,
2273
+ onChange: (e) => setPhaseText(e.target.value),
2274
+ placeholder: '范围与验收标准确认\n执行与自测\n交付与复盘',
2275
+ }),
2276
+ h('div', { className: 'csb-fld-h' },
2277
+ '已经汇报过的阶段产物不会丢;清单变短时旧产物保留但不再展示。'),
2278
+ h('button', {
2279
+ className: 'csb-btn csb-btn-p', disabled: Boolean(busy), onClick: savePhases,
2280
+ }, busy === '保存阶段清单' ? '保存中…' : '保存阶段清单'))
2281
+ : null)
2282
+ }
2283
+
2284
+ /** epoch ms → `YYYY-MM-DD`(`<input type=date>` 的取值格式,本地时区)。 */
2285
+ function toDateInput(ms) {
2286
+ if (!ms) return ''
2287
+ const d = new Date(ms)
2288
+ if (Number.isNaN(d.getTime())) return ''
2289
+ const p = (n) => String(n).padStart(2, '0')
2290
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
2291
+ }
2292
+
2293
+ /** `YYYY-MM-DD` → epoch ms(当天 23:59:59 本地时区:截止=那天结束前都算没超)。 */
2294
+ function dateInputToMs(text) {
2295
+ if (!text) return null
2296
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(text).trim())
2297
+ if (!m) return null
2298
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), 23, 59, 59, 999)
2299
+ return Number.isNaN(d.getTime()) ? null : d.getTime()
2300
+ }
2301
+
2302
+ /**
2303
+ * 任务详情 ·「阶段产出物」区块(契约 §2.5,需求3)。
2304
+ *
2305
+ * Boss 的原话:「点击去看产出物,看不到过程产物」⇒ 必须能按阶段看到每阶段交了
2306
+ * 什么东西,**不需要去翻对话**。
2307
+ *
2308
+ * 数据来自 `GET /tasks/phases?id=`;后端未就绪 / 读取失败都要降级成**人话提示**
2309
+ * (带重试),不许白屏、不许静默。非长任务是**正常空态**,不是错误。
2310
+ *
2311
+ * 产出物复用「产出物证据」那套渲染:路径可预览(`onPreview`)→ `FilePreviewModal`,
2312
+ * URL 走 `window.open`。
2313
+ */
2314
+ function TaskPhasePanel({ taskId, onPreview }) {
2315
+ const phasesQ = useApi(taskId ? `/tasks/phases?id=${encodeURIComponent(taskId)}` : null, [taskId])
2316
+ const data = phasesQ.data
2317
+ const phases = Array.isArray(data?.phases) ? data.phases : []
2318
+ const isLongrun = data?.isLongrun === true
2319
+ const phaseLabel = (st) => (st === 'done' ? '已完成' : st === 'blocked' ? '卡住' : st === 'doing' ? '进行中' : '未开始')
2320
+ const phaseBadgeCls = (st) => (st === 'done' ? 'csb-b csb-b-ok' : st === 'blocked' ? 'csb-b csb-b-err'
2321
+ : st === 'doing' ? 'csb-b csb-b-run' : 'csb-b csb-b-idle')
2322
+ const ordinal = (i) => '\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b'[i] ?? `${i + 1}.`
2323
+
2324
+ // 非长任务:契约 §2.5 明说这不是错误 —— 空态 + 一句人话指向总产出物。
2325
+ if (!phasesQ.loading && !phasesQ.error && data && !isLongrun) {
2326
+ return h(Section, { title: '阶段产出物' },
2327
+ h(Empty, {
2328
+ icon: '\u25dc',
2329
+ title: '这不是长任务,没有分阶段产物',
2330
+ hint: '总产出物见下方「产出物证据」。',
2331
+ }))
2332
+ }
2333
+
2334
+ // 每阶段的产出物行:路径可预览、URL 可打开,绝不给不可点的死文本。
2335
+ const artifactRow = (a, key) => h('div', { key, className: 'csb-bar', style: { gap: 6, alignItems: 'center' } },
2336
+ h('span', { className: 'csb-card-t csb-mono', style: { fontSize: 12 } },
2337
+ typeof a === 'string' ? a : JSON.stringify(a)),
2338
+ isUrl(a)
2339
+ ? h('button', {
2340
+ className: 'csb-btn csb-btn-s', style: { height: 22, fontSize: 11 },
2341
+ onClick: () => window.open(a, '_blank', 'noopener'),
2342
+ }, '打开链接')
2343
+ : h('button', {
2344
+ className: 'csb-btn csb-btn-s', style: { height: 22, fontSize: 11 },
2345
+ onClick: () => onPreview?.(a),
2346
+ }, '预览文件'))
2347
+
2348
+ const phaseCard = (p, i) => {
2349
+ const idx = p.index ?? i
2350
+ const arts = p.artifacts ?? []
2351
+ return h('div', { key: idx, className: 'csb-card', style: { padding: '9px 12px' } },
2352
+ h('div', { className: 'csb-card-main' },
2353
+ h('div', { className: 'csb-bar', style: { gap: 8 } },
2354
+ h('span', { className: 'csb-card-t' }, `${ordinal(idx)} ${p.title ?? `阶段 ${idx + 1}`}`),
2355
+ h('span', {
2356
+ className: phaseBadgeCls(p.state),
2357
+ title: p.state === 'blocked' ? '执行者报告这一阶段卡住了' : '',
2358
+ }, phaseLabel(p.state))),
2359
+ p.note ? h('div', { className: 'csb-card-m' }, p.note) : null,
2360
+ arts.length === 0
2361
+ ? h('div', { className: 'csb-set-d' }, '这一阶段没交产出物。')
2362
+ : h('div', { className: 'csb-card-m' }, arts.map((a, j) => artifactRow(a, j))),
2363
+ p.by ? h('div', { className: 'csb-set-d' }, `汇报人:${humanizeBy(p.by)}`) : null))
2364
+ }
2365
+
2366
+ return h('div', null,
2367
+ h(Section, {
2368
+ title: '阶段产出物',
2369
+ desc: isLongrun
2370
+ ? `共 ${phases.length} 个阶段。每阶段交了什么直接在这里看,不用去翻对话。`
2371
+ : '执行者按阶段汇报产出物,交一次记一次;没有阶段记录时这里显示空态。',
2372
+ }),
2373
+ // 后端未就绪 / 读取失败:可读提示 + 重试(绝不静默)
2374
+ phasesQ.error
2375
+ ? h('div', { className: 'csb-note' },
2376
+ `阶段产出物读不到:${phasesQ.error}`,
2377
+ h('button', {
2378
+ className: 'csb-btn csb-btn-s', style: { marginLeft: 8 },
2379
+ onClick: () => phasesQ.reload(),
2380
+ }, '重试'))
2381
+ : null,
2382
+ phasesQ.loading ? h(Skeleton, { n: 1 }) : null,
2383
+ (!phasesQ.loading && !phasesQ.error && phases.length === 0)
2384
+ ? h(Empty, {
2385
+ icon: '\u25dc',
2386
+ title: '执行者还没汇报这一阶段。',
2387
+ hint: '执行者每完成一个阶段会汇报一次产出物;目前还没有任何阶段记录。',
2388
+ })
2389
+ : null,
2390
+ phases.length > 0
2391
+ ? h('div', { className: 'csb-list', style: { marginTop: 10 } }, phases.map(phaseCard))
2392
+ : null)
2393
+ }
2394
+
1976
2395
  /**
1977
2396
  * 任务对话(需求2,2026-11-09)。
1978
2397
  *
@@ -2123,6 +2542,19 @@ window.__ModuleLoader__.load({
2123
2542
  }
2124
2543
  }
2125
2544
 
2545
+ /**
2546
+ * 机器标识 → 面向 Boss 的展示文案(契约 §0)。
2547
+ *
2548
+ * `(ui)` / `by: 'ui'` 这类机器标识可以留在技术字段里,但**面向 Boss 的展示文案**
2549
+ * 要写 `Boss`(例:补充说明历史里的 `(ui)` → `(Boss)`)。
2550
+ * 非 Boss 机器身份(Bot / 其他会话)保持原文 —— 不猜、不美化。
2551
+ */
2552
+ function humanizeBy(raw) {
2553
+ const v = raw ?? 'ui'
2554
+ if (v === 'ui' || v === 'user' || v === 'human' || v === 'boss' || v === 'anyone') return HUMAN_LABEL
2555
+ return String(v)
2556
+ }
2557
+
2126
2558
  /**
2127
2559
  * 一条变更记录 → `[时间] 动作 — 说明(by)`(契约 §3.1)。
2128
2560
  * 快照记录没有 note:用「上一轮交付物 N 项」兜底,避免历史里出现空动作。
@@ -2135,8 +2567,7 @@ window.__ModuleLoader__.load({
2135
2567
  note = `上一轮交付物 ${arts.length} 项`
2136
2568
  }
2137
2569
  if (!note && ch?.kind === 'reassign') note = `${ch?.from ?? '?'} → ${ch?.to ?? '?'}`
2138
- const by = ch?.by ?? ch?.byId ?? 'ui'
2139
- return `[${fmt(ch?.at)}] ${label}${note ? ` — ${note}` : ''}(${by})`
2570
+ return `[${fmt(ch?.at)}] ${label}${note ? ` — ${note}` : ''}(${humanizeBy(ch?.by ?? ch?.byId)})`
2140
2571
  }
2141
2572
 
2142
2573
  /** `POST /tasks/evolve`:三种 mode 的唯一入口(note 必填,前端先拦一次)。 */
@@ -2400,7 +2831,7 @@ window.__ModuleLoader__.load({
2400
2831
  liveBots.map((b) => h('option', { key: b.id ?? b.sessionId, value: b.sessionId ?? b.id },
2401
2832
  `${b.name ?? '(无名)'}${b.state ? ` · ${b.state}` : ''}`))),
2402
2833
  h('div', { className: 'csb-fld-h' },
2403
- '换人会先把原负责人的租约交还,再以新负责人身份领取,并唤醒新负责人。'
2834
+ '换人会先把原负责人的占用交还,再以新负责人身份领取,并唤醒新负责人。'
2404
2835
  + '已完成的任务会被拦 —— 先重开或派生优化单。')),
2405
2836
  err ? h('div', { className: 'csb-err', style: { marginTop: 10 } }, err) : null,
2406
2837
  done ? h(Notice, { kind: 'ok' }, done) : null)
@@ -2570,7 +3001,7 @@ window.__ModuleLoader__.load({
2570
3001
  value: text, onChange: (e) => setText(e.target.value),
2571
3002
  }),
2572
3003
  h('div', { className: 'csb-fld-h' },
2573
- `等价于你在这个 Bot 的会话里发一条消息(会话 ${short(bot?.sessionId ?? '—', 18)})。`)),
3004
+ `等价于 Boss 在这个 Bot 的会话里发一条消息(会话 ${short(bot?.sessionId ?? '—', 18)})。`)),
2574
3005
  err ? h('div', { className: 'csb-err' }, err) : null,
2575
3006
  done && !done.wake
2576
3007
  ? h('div', { className: 'csb-ok' }, `已送达(会话 ${short(done.sessionId ?? '—', 18)})`)
@@ -2704,8 +3135,7 @@ window.__ModuleLoader__.load({
2704
3135
  const [err, setErr] = useState(null)
2705
3136
  // 创建/派活的成功提示(改造04:创建时填了负责人 = 一步派活,wake 结果要 toast 出来)
2706
3137
  const [ok, setOk] = useState(null)
2707
- // 默认只看「需要我管的」;点「看全部」才显示 AI 派发的和已完成的
2708
- const [attentionOnly, setAttentionOnly] = useState(true)
3138
+ // 契约 §1:任务栏默认全量 —— 不再区分「需要管 / 全部」,也不带 attention 过滤。
2709
3139
  // 双视图:看板 / 列表
2710
3140
  const [view, setView] = useState('list')
2711
3141
  // 本地过滤:负责人 / 关键词(不改后端 query)
@@ -2714,11 +3144,11 @@ window.__ModuleLoader__.load({
2714
3144
  const subjectRef = useRef(null)
2715
3145
  const q = new URLSearchParams()
2716
3146
  if (filter) q.set('status', filter)
2717
- if (attentionOnly && sourceFilter === 'human') q.set('attention', '1')
2718
- // 需求1:按来源分离——human=用户自建,ai=智能体自建/分发
2719
- q.set('source', sourceFilter)
3147
+ // 契约 §1:任务栏默认全量 ⇒ 不带 attention、不带 source;
3148
+ // 只有「智能体任务」子页签显式要求 source=ai。
3149
+ if (sourceFilter === 'ai') q.set('source', 'ai')
2720
3150
  const qs = q.toString()
2721
- const { data, loading, error, reload } = useApi(`/tasks${qs ? `?${qs}` : ''}`, [filter, attentionOnly, sourceFilter])
3151
+ const { data, loading, error, reload } = useApi(`/tasks${qs ? `?${qs}` : ''}`, [filter, sourceFilter])
2722
3152
  const { data: rosterData } = useApi('/roster')
2723
3153
  const { data: wfData } = useApi('/library/workflows')
2724
3154
  const bots = rosterData?.bots ?? []
@@ -2738,7 +3168,7 @@ window.__ModuleLoader__.load({
2738
3168
 
2739
3169
  // 「下一步」按钮的落点(spec §8):按错误码给可执行动作
2740
3170
  const errActions = {
2741
- BOARD_WIP_EXCEEDED: () => { setFilter(''); setAttentionOnly(true) },
3171
+ BOARD_WIP_EXCEEDED: () => { setFilter(''); setOwnerFilter(''); setKeyword('') },
2742
3172
  BOARD_REV_CONFLICT: reload,
2743
3173
  CAS_MISMATCH: reload,
2744
3174
  // 查看持有者 / 前置任务 / 依赖图 → 打开任务详情抽屉
@@ -2840,18 +3270,12 @@ window.__ModuleLoader__.load({
2840
3270
  const isAi = sourceFilter === 'ai'
2841
3271
  return h('div', { className: 'csb-wrap' },
2842
3272
  h(Section, {
2843
- // 需求1:按来源给不同标题与说明——人只看自己建的,AI 的任务走独立子页签
2844
- title: isAi ? '智能体任务' : `${HUMAN_LABEL}的任务`,
3273
+ // 契约 §1:顶层保留两个子页签(任务栏 / 智能体任务),页内不再有「看全部 / 只看需要管」切换。
3274
+ title: isAi ? '智能体任务' : '任务栏',
2845
3275
  desc: isAi
2846
- ? `Bot 自行创建或分发的任务,按状态分类。这些是执行细节,平时不用盯;出问题(受阻/失败)时会出现在「${HUMAN_LABEL}的任务」的待处理里。`
2847
- : (attentionOnly
2848
- ? '只看需要你管的(卡住 / 高优先 / 逾期 / 待验收)。智能体自己派发的任务在「智能体任务」子页签。'
2849
- : '全部你建的任务。点下方回到「只看需要我管的」。'),
3276
+ ? 'Bot 自行创建或分发的任务,按状态分类。这些是执行细节,平时不用盯;出问题(受阻/失败)时会出现在「任务栏」里。'
3277
+ : '全部任务都在这里:Boss 建的、智能体派发的、以及已经完成的。',
2850
3278
  }),
2851
- isAi ? null : h('div', { className: 'csb-bar', style: { marginTop: 8 } },
2852
- attentionOnly
2853
- ? h('button', { className: 'csb-btn csb-btn-s', onClick: () => setAttentionOnly(false) }, `看全部${HUMAN_LABEL}的任务`)
2854
- : h('button', { className: 'csb-btn csb-btn-s', onClick: () => setAttentionOnly(true) }, '只看需要我管的')),
2855
3279
  h('div', { className: 'csb-bar', style: { marginTop: 12 } },
2856
3280
  h('input', {
2857
3281
  className: 'csb-in csb-grow', placeholder: isLong
@@ -2980,7 +3404,7 @@ window.__ModuleLoader__.load({
2980
3404
  onChange: (e) => setMandateBasis(e.target.value),
2981
3405
  },
2982
3406
  h('option', { value: '' }, '不记录(UI 建的,不算授权)'),
2983
- h('option', { value: 'explicit' }, '人明确点名要做(可以直接动手)'),
3407
+ h('option', { value: 'explicit' }, 'Boss 明确点名要做(可以直接动手)'),
2984
3408
  h('option', { value: 'inferred' }, '按规则推断(动手前先确认)'),
2985
3409
  h('option', { value: 'task-board' }, '任务板派发(管理面正式派活)')),
2986
3410
  h('div', { className: 'csb-fld-h' },
@@ -2993,7 +3417,7 @@ window.__ModuleLoader__.load({
2993
3417
  onChange: (e) => setRequiresPaid(e.target.checked),
2994
3418
  }),
2995
3419
  h('span', null, '允许付费(默认只走免费渠道)')),
2996
- h('div', { className: 'csb-fld-h' }, '只有确实需要强模型时才勾;勾了就是花你的钱。')))
3420
+ h('div', { className: 'csb-fld-h' }, '只有确实需要强模型时才勾;勾了就是花 Boss 的钱。')))
2997
3421
  : null,
2998
3422
  h('div', { className: 'csb-bar', style: { marginTop: 8, flexWrap: 'wrap' } },
2999
3423
  h('button', {
@@ -3076,7 +3500,7 @@ window.__ModuleLoader__.load({
3076
3500
  si ? h('div', { style: { marginTop: 4, display: 'flex', gap: 6, flexWrap: 'wrap' } },
3077
3501
  h('span', { className: stageBadgeCls(si.group) }, si.label),
3078
3502
  si.needsPerson
3079
- ? h('span', { className: 'csb-b csb-b-run', title: si.reason }, '需你处理')
3503
+ ? h('span', { className: 'csb-b csb-b-run', title: si.reason }, '需 Boss 处理')
3080
3504
  : null) : null,
3081
3505
  h('div', { className: 'csb-progress' },
3082
3506
  h('div', { className: 'csb-progress-fill', style: { width: `${Math.max(0, Math.min(100, t.progress ?? 0))}%` } })),
@@ -3100,7 +3524,7 @@ window.__ModuleLoader__.load({
3100
3524
 
3101
3525
  const SKILL_SOURCES = [
3102
3526
  { id: 'custom', label: '自建' },
3103
- { id: 'user', label: '用户级' },
3527
+ { id: 'user', label: 'Boss 级' },
3104
3528
  { id: 'project', label: '项目级' },
3105
3529
  { id: 'bundled', label: '系统自带' },
3106
3530
  ]
@@ -3120,7 +3544,7 @@ window.__ModuleLoader__.load({
3120
3544
  skill ? h('div', null,
3121
3545
  h('div', { className: 'csb-kv', style: { marginBottom: 10 } },
3122
3546
  h('span', { className: 'csb-k' }, '描述'), h('span', { className: 'csb-v' }, skill.description || '—'),
3123
- h('span', { className: 'csb-k' }, '来源'), h('span', { className: 'csb-v' }, skill.source === 'bundled' ? '系统自带' : skill.source === 'custom' ? '自建' : skill.source === 'user' ? '用户级' : '项目级')),
3547
+ h('span', { className: 'csb-k' }, '来源'), h('span', { className: 'csb-v' }, skill.source === 'bundled' ? '系统自带' : skill.source === 'custom' ? '自建' : skill.source === 'user' ? 'Boss 级' : '项目级')),
3124
3548
  h('div', { className: 'csb-pre', style: { maxHeight: 400, overflow: 'auto' } }, skill.body || '(空)'))
3125
3549
  : null,
3126
3550
  ],
@@ -3899,7 +4323,7 @@ window.__ModuleLoader__.load({
3899
4323
  error ? h('div', { className: 'csb-err' }, error) : null,
3900
4324
  !loading && bots.length === 0
3901
4325
  ? h(Empty, {
3902
- icon: '⬡', title: '还没有 Bot', hint: '先在上面建一个。如果只是临时分工,不用建 Bot —— 直接建任务指派给人就行。',
4326
+ icon: '⬡', title: '还没有 Bot', hint: '先在上面建一个。如果只是临时分工,不用建 Bot —— 直接建任务指派给 Bot 就行。',
3903
4327
  action: { label: '新建 Bot', icon: '✎', onClick: () => nameRef.current?.focus() },
3904
4328
  })
3905
4329
  : null,
@@ -4052,7 +4476,7 @@ window.__ModuleLoader__.load({
4052
4476
  h('span', { className: 'csb-k' }, '专家身份'), h('span', { className: 'csb-v' }, bot.preset ?? '默认'),
4053
4477
  h('span', { className: 'csb-k' }, '档案文件'), h('span', { className: 'csb-v csb-mono' }, data.profilePath ?? '—')),
4054
4478
  data.hasOutside
4055
- ? h('div', { className: 'csb-note' }, '你的 AGENTS.md 里还有手写内容 —— 保存档案不会覆盖它,只改 cs-bots 托管的那一段。')
4479
+ ? h('div', { className: 'csb-note' }, 'Boss 的 AGENTS.md 里还有手写内容 —— 保存档案不会覆盖它,只改 cs-bots 托管的那一段。')
4056
4480
  : null,
4057
4481
 
4058
4482
  h(Section, { title: '① 岗位名', desc: '一句话说清它是什么角色。会作为档案里的小标题显示。' }),
@@ -4062,7 +4486,7 @@ window.__ModuleLoader__.load({
4062
4486
  value: draft.title, onChange: (e) => setDraft({ ...draft, title: e.target.value }),
4063
4487
  }),
4064
4488
 
4065
- h(Section, { title: '② 人格与说话方式', desc: '它该用什么性格、什么口吻跟你说话。不填就用默认的。' }),
4489
+ h(Section, { title: '② 人格与说话方式', desc: '它该用什么性格、什么口吻跟 Boss 说话。不填就用默认的。' }),
4066
4490
  h('textarea', {
4067
4491
  className: 'csb-ta', style: { marginTop: 10 },
4068
4492
  placeholder: '例如:说话先给结论再列证据;不确定就明说不知道,不许编;不要用"作为一个 AI"这类腔调。',
@@ -4079,11 +4503,11 @@ window.__ModuleLoader__.load({
4079
4503
  h(Section, { title: '④ 身份', desc: '它是谁、负责什么。这段话会在每次对话开始前进到它的上下文里。' }),
4080
4504
  h('textarea', {
4081
4505
  className: 'csb-ta', style: { marginTop: 10 },
4082
- placeholder: '例如:你是本项目的后端工程师,负责 API 设计、数据模型与数据库迁移;对 SQL 性能负责;拿不准时先给方案再动手。',
4506
+ placeholder: '例如:它是本项目的后端工程师,负责 API 设计、数据模型与数据库迁移;对 SQL 性能负责;拿不准时先给方案再动手。',
4083
4507
  value: draft.identity, onChange: (e) => setDraft({ ...draft, identity: e.target.value }),
4084
4508
  }),
4085
4509
 
4086
- h(Section, { title: '⑤ 经验与规矩', desc: '它踩过的坑、你定下的规矩。写在这里的每一条,它每次都会看到。' }),
4510
+ h(Section, { title: '⑤ 经验与规矩', desc: '它踩过的坑、Boss 定下的规矩。写在这里的每一条,它每次都会看到。' }),
4087
4511
  h('textarea', {
4088
4512
  className: 'csb-ta', style: { marginTop: 10, minHeight: 100 },
4089
4513
  placeholder: '例如:改数据库结构必须先写迁移;禁止直接 force push;测试必须全绿才算完成。',
@@ -4403,7 +4827,7 @@ window.__ModuleLoader__.load({
4403
4827
  h(Section, {
4404
4828
  title: '房间 = 群聊',
4405
4829
  desc: '房间就是一群成员共用的消息流。成员是「会话」,消息可以直接转发去唤醒某个 Bot。'
4406
- + '你是超级管理员:所有房间都能进、都能看全文、都能改设置。',
4830
+ + 'Boss 是超级管理员:所有房间都能进、都能看全文、都能改设置。',
4407
4831
  }),
4408
4832
  h('div', { className: 'csb-bar', style: { marginTop: 12 } },
4409
4833
  h('input', {
@@ -4474,7 +4898,7 @@ window.__ModuleLoader__.load({
4474
4898
  h('div', { className: 'csb-room-chat' },
4475
4899
  h(Section, {
4476
4900
  title: `消息 · ${current.name}`,
4477
- desc: `共 ${msgs.length} 条,当前显示最近 ${Math.min(shownCount, msgs.length)} 条。发帖者必须是成员;你是超级管理员,可以直接看全部内容。`,
4901
+ desc: `共 ${msgs.length} 条,当前显示最近 ${Math.min(shownCount, msgs.length)} 条。发帖者必须是成员;Boss 是超级管理员,可以直接看全部内容。`,
4478
4902
  }),
4479
4903
  current.purpose ? h('div', { className: 'csb-note', style: { marginTop: 10 } }, `用途:${current.purpose}`) : null,
4480
4904
  current.notice ? h('div', { className: 'csb-note', style: { marginTop: 10 } }, current.notice) : null,
@@ -4683,14 +5107,14 @@ window.__ModuleLoader__.load({
4683
5107
  onChange: (e) => setForm({ ...form, adminId: e.target.value }),
4684
5108
  }))
4685
5109
  : null,
4686
- h(Field, { label: '房间公告', hint: '会显示在消息列表上方,所有进这个房间的人都能看到' },
5110
+ h(Field, { label: '房间公告', hint: '会显示在消息列表上方,所有进这个房间的成员都能看到' },
4687
5111
  h('textarea', {
4688
5112
  className: 'csb-ta', value: form.notice,
4689
5113
  placeholder: '例如:本房间只讨论发布相关事项;发版前 24 小时冻结代码。',
4690
5114
  onChange: (e) => setForm({ ...form, notice: e.target.value }),
4691
5115
  })),
4692
5116
  h('h4', { style: { marginTop: 18 } }, '房间规则'),
4693
- h(Field, { label: '允许 Bot 自己发言', hint: '关掉后只有人能主动发消息,Bot 只能被动回复' },
5117
+ h(Field, { label: '允许 Bot 自己发言', hint: '关掉后只有 Boss 能主动发消息,Bot 只能被动回复' },
4694
5118
  h('label', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
4695
5119
  h('input', {
4696
5120
  type: 'checkbox', checked: settings.allowBotAutoPost,
@@ -4993,12 +5417,12 @@ window.__ModuleLoader__.load({
4993
5417
  return h('div', { className: 'csb-wrap' },
4994
5418
  h(Section, {
4995
5419
  title: 'PDCA 复盘周期',
4996
- desc: 'PDCA 是给人的长任务做复盘跟进的,人类主导——五步不可跳,这正是「不自欺」的纪律。智能体有自己的归纳周期(周期驱动、自动闭环),折叠在下面。',
5420
+ desc: 'PDCA 是给 Boss 的长任务做复盘跟进的,Boss 主导——五步不可跳,这正是「不自欺」的纪律。智能体有自己的归纳周期(周期驱动、自动闭环),折叠在下面。',
4997
5421
  }),
4998
5422
 
4999
5423
  // ── 我的长任务周期(human)────────────────────────────────────────────
5000
5424
  h('div', { className: 'csb-sec-t' }, `📋 ${HUMAN_LABEL} 的任务周期`),
5001
- h('div', { className: 'csb-set-d' }, '为你的长任务开一个周期。目标原文是「防漂移锚」——复盘时拿它对照,看有没有走偏。'),
5425
+ h('div', { className: 'csb-set-d' }, '为长任务开一个周期。目标原文是「防漂移锚」——复盘时拿它对照,看有没有走偏。'),
5002
5426
  h('div', { className: 'csb-bar', style: { marginTop: 8, flexWrap: 'wrap', gap: 6 } },
5003
5427
  h('input', { className: 'csb-in', style: { width: 120 }, placeholder: '任务 id(t-x)', value: taskId, onChange: (e) => setTaskId(e.target.value) }),
5004
5428
  h('input', { className: 'csb-in', style: { width: 260 }, placeholder: '目标原文(防漂移锚)', value: objective, onChange: (e) => setObjective(e.target.value) }),
@@ -5012,7 +5436,7 @@ window.__ModuleLoader__.load({
5012
5436
 
5013
5437
  // ── 智能体归纳周期(agent)────────────────────────────────────────────
5014
5438
  h('div', { className: 'csb-sec-t', style: { marginTop: 18 } }, '🤖 智能体归纳周期'),
5015
- h('div', { className: 'csb-set-d' }, '智能体用来做定期归纳提升的周期。与人的周期不同:归纳是批量总结,可以一次性提交,提交后自动完成——不需要人点「完成」,也不需要假装走完五步。'),
5439
+ h('div', { className: 'csb-set-d' }, '智能体用来做定期归纳提升的周期。与 Boss 的周期不同:归纳是批量总结,可以一次性提交,提交后自动完成——不需要 Boss 点「完成」,也不需要假装走完五步。'),
5016
5440
  // 到期未归纳的会话:这是「定期归纳」真正被驱动的入口
5017
5441
  dueSessions.length > 0
5018
5442
  ? h('div', { className: 'csb-note', style: { marginTop: 8 } },
@@ -5029,7 +5453,7 @@ window.__ModuleLoader__.load({
5029
5453
  : null,
5030
5454
  urgeMsg ? h('div', { className: 'csb-notice csb-notice-ok', style: { marginTop: 8 } }, h('span', { className: 'csb-notice-t' }, urgeMsg)) : null,
5031
5455
  agentCycles.length === 0
5032
- ? h(Empty, { icon: '🧠', title: '智能体尚未开归纳周期', hint: '智能体通常自动创建归纳周期;如果你要手动触发,填任务 id 和归纳目标。' })
5456
+ ? h(Empty, { icon: '🧠', title: '智能体尚未开归纳周期', hint: '智能体通常自动创建归纳周期;Boss 要手动触发时,填任务 id 和归纳目标。' })
5033
5457
  : h('div', { className: 'csb-list' }, agentCycles.map((c) => renderCycle(c, true))),
5034
5458
  agentMsg ? h('div', { className: 'csb-notice csb-notice-ok', style: { marginTop: 8 } }, h('span', { className: 'csb-notice-t' }, agentMsg)) : null)
5035
5459
  }
@@ -5085,6 +5509,9 @@ window.__ModuleLoader__.load({
5085
5509
  // 只取用户建的团队(big);智能体自建的 small 不上上层
5086
5510
  const listQ = useApi('/teams?type=big', [])
5087
5511
  const rosterQ = useApi('/roster')
5512
+ // 契约 §3(UI):团队卡片要显示 `房间:<名>` —— 房间名不在团队记录里,
5513
+ // 要拿 roomId 从 /rooms 列表里查出名字(没有房间就只有 roomId 显示)。
5514
+ const roomsQ = useApi('/rooms', [])
5088
5515
  const [name, setName] = useState('')
5089
5516
  const [leadId, setLeadId] = useState('')
5090
5517
  const [purpose, setPurpose] = useState('')
@@ -5118,6 +5545,14 @@ window.__ModuleLoader__.load({
5118
5545
  // 可用的 Bot(不含已退役)——成员与队长都从这里选
5119
5546
  const bots = (rosterQ.data?.bots ?? []).filter((b) => b.state !== 'retired')
5120
5547
  const botBySession = new Map(bots.map((b) => [b.sessionId, b]))
5548
+ // 契约 §3(UI):roomId → 房间名(团队卡片要显示 `房间:<名>`)。
5549
+ // 房间列表可能还没回来 / 房间被归档 —— 查不到就退回 roomId,绝不显示 "undefined"。
5550
+ const roomNameById = new Map((roomsQ.data?.rooms ?? []).map((r) => [r.id, r.name]))
5551
+ const roomLabelOf = (t) => {
5552
+ const id = typeof t?.roomId === 'string' ? t.roomId : ''
5553
+ if (!id) return ''
5554
+ return roomNameById.get(id) ?? id
5555
+ }
5121
5556
 
5122
5557
  const createTeam = async () => {
5123
5558
  if (!name.trim()) { setMsg('团队名必填'); return }
@@ -5137,7 +5572,8 @@ window.__ModuleLoader__.load({
5137
5572
  adminPolicy: DEFAULT_ADMIN_POLICY,
5138
5573
  by: 'ui',
5139
5574
  })
5140
- setMsg(`团队「${r.team.name}」已创建(负责人:${botBySession.get(leadId)?.name ?? leadId})`)
5575
+ setMsg(`团队「${r.team.name}」已创建(负责人:${botBySession.get(leadId)?.name ?? leadId})`
5576
+ + (r.room?.name ? ` · 议事厅:${r.room.name}` : (r.roomError ? ` · 议事厅未建好(${r.roomError})` : '')))
5141
5577
  setName(''); setLeadId(''); setPurpose(''); setSelected([])
5142
5578
  listQ.reload()
5143
5579
  } catch (e) { setMsg(String(e.message ?? e)) } finally { setBusy(false) }
@@ -5239,7 +5675,7 @@ window.__ModuleLoader__.load({
5239
5675
  return h('div', { className: 'csb-wrap' },
5240
5676
  h(Section, {
5241
5677
  title: '团队',
5242
- desc: '你创建的团队。每个团队有负责人(队长)、成员和讨论房间。'
5678
+ desc: 'Boss 创建的团队。每个团队有负责人(队长)、成员和讨论房间。'
5243
5679
  + '智能体自己组织的临时小组不在这里(点右上角「⚙ 穿透」可查)。',
5244
5680
  }),
5245
5681
 
@@ -5371,16 +5807,29 @@ window.__ModuleLoader__.load({
5371
5807
  }))
5372
5808
  : null,
5373
5809
 
5374
- // 跳转房间(用户补充:可以穿透房间看子团队工作内容 / 定向搜索)
5810
+ // 契约 §3(UI):团队卡片显示 `房间:<名>` + 「去房间」按钮。
5811
+ // 房间名要从 /rooms 列表里查(团队记录只有 roomId);
5812
+ // 没有房间就直说,不能假装有。
5813
+ h('div', { className: 'csb-card-m' },
5814
+ h('span', { className: 'csb-b csb-b-run', title: '团队的议事厅(契约 §3)' }, '🏛 房间:'),
5815
+ h('span', null, roomLabelOf(t) || '(还没有房间)')),
5816
+
5817
+ // 跳转房间(契约 §3:选中该团队自己的房间,而不是按名字筛)
5375
5818
  h('div', { className: 'csb-bar', style: { marginTop: 8 } },
5376
5819
  h('button', {
5377
5820
  className: 'csb-btn csb-btn-s',
5378
- title: '查看这个团队的讨论房间与成员工作内容',
5821
+ title: '去这个团队的议事厅(选中该房间)',
5379
5822
  // 必须带 sub:'rooms' —— safeGoTab 会把 'rooms' 映射成主页签 'team',
5380
5823
  // 不带 sub 就落到该页签的第一个子视图(Bot 档案),这就是
5381
5824
  // 「点去房间却跳到 Bot 创建页」的真正原因。
5382
- onClick: () => onGoto?.('team', { sub: 'rooms', filter: t.name }),
5383
- }, '💬 看房间'),
5825
+ // 带 roomId ⇒ 房间页直接选中这个房间;没有 roomId 就退回按名字筛。
5826
+ onClick: () => onGoto?.(
5827
+ 'team',
5828
+ t.roomId
5829
+ ? { sub: 'rooms', roomId: t.roomId }
5830
+ : { sub: 'rooms', filter: t.name },
5831
+ ),
5832
+ }, '💬 去房间'),
5384
5833
  h('button', {
5385
5834
  className: 'csb-btn csb-btn-s',
5386
5835
  onClick: () => onGoto?.('bots'),
@@ -5507,7 +5956,7 @@ window.__ModuleLoader__.load({
5507
5956
  }
5508
5957
  if (rows.length === 0) {
5509
5958
  rows.push(h('div', { key: 'empty', className: 'csb-set-d', style: { paddingTop: 6 } },
5510
- '这个团队还没有成员。去「团队」页加人,或到「Bot 档案」新建一个 Bot。'))
5959
+ '这个团队还没有成员。去「团队」页加成员,或到「Bot 档案」新建一个 Bot。'))
5511
5960
  }
5512
5961
 
5513
5962
  return h('div', { style: { paddingTop: 8 } },
@@ -5684,7 +6133,7 @@ window.__ModuleLoader__.load({
5684
6133
  // ── 记忆 ────────────────────────────────────────────────────────────────
5685
6134
 
5686
6135
  const LAYERS = ['L0', 'L1', 'L2', 'L3', 'L4', 'L5']
5687
- const LAYER_HINT = { L0: '人格(仅人可改)', L1: '用户偏好', L2: '团队规矩', L3: '分支专属', L4: '任务笔记', L5: '归档' }
6136
+ const LAYER_HINT = { L0: '人格(仅 Boss 可改)', L1: 'Boss 偏好', L2: '团队规矩', L3: '分支专属', L4: '任务笔记', L5: '归档' }
5688
6137
 
5689
6138
  function MemoryTab() {
5690
6139
  const [layer, setLayer] = useState('')
@@ -5789,7 +6238,7 @@ window.__ModuleLoader__.load({
5789
6238
  await call('/suggestions/accept', { method: 'POST', body: { id: sg.id } })
5790
6239
  setOk('已采纳,内容已写入记忆')
5791
6240
  } else {
5792
- await call('/suggestions/reject', { method: 'POST', body: { id: sg.id, reason: '人工驳回' } })
6241
+ await call('/suggestions/reject', { method: 'POST', body: { id: sg.id, reason: 'Boss 驳回' } })
5793
6242
  setOk('已驳回')
5794
6243
  }
5795
6244
  reload()
@@ -5813,7 +6262,7 @@ window.__ModuleLoader__.load({
5813
6262
  onClick: () => setShowSug(true),
5814
6263
  },
5815
6264
  h('span', { className: 'csb-notice-t' },
5816
- `有 ${suggestions.length} 条「自动整理建议」等你确认 —— 模型不能直接改你的长期记忆,只能提议。点这里看。`),
6265
+ `有 ${suggestions.length} 条「自动整理建议」等 Boss 确认 —— 模型不能直接改 Boss 的长期记忆,只能提议。点这里看。`),
5817
6266
  h('span', { className: 'csb-b' }, '去确认'))
5818
6267
  : null,
5819
6268
 
@@ -5896,7 +6345,7 @@ window.__ModuleLoader__.load({
5896
6345
  h('div', { className: 'csb-card-m' },
5897
6346
  h('span', { className: 'csb-b' }, `${e.layer} · ${LAYER_HINT[e.layer] ?? ''}`),
5898
6347
  e.botId ? h('span', { className: 'csb-b' }, `Bot ${short(e.botId, 10)}`) : h('span', { className: 'csb-b' }, '全局'),
5899
- e.explicit ? h('span', { className: 'csb-b csb-b-ok' }, '你说过的') : null,
6348
+ e.explicit ? h('span', { className: 'csb-b csb-b-ok' }, 'Boss 明确说的') : null,
5900
6349
  e.conflict ? h('span', { className: 'csb-b csb-b-err' }, '与另一条冲突') : null,
5901
6350
  e.archived
5902
6351
  ? h('span', { className: 'csb-b csb-b-idle' },
@@ -5951,7 +6400,7 @@ window.__ModuleLoader__.load({
5951
6400
  onChange: (e) => setNewTags(e.target.value),
5952
6401
  })),
5953
6402
  h('div', { className: 'csb-field-h' },
5954
- '你手动写的内容会标记为「你说过的」——以后自动整理只能建议、不能改它。'))
6403
+ 'Boss 手动写的内容会标记为「Boss 明确说的」——以后自动整理只能建议、不能改它。'))
5955
6404
  : null,
5956
6405
 
5957
6406
  // ── 编辑 ──
@@ -5975,7 +6424,7 @@ window.__ModuleLoader__.load({
5975
6424
  className: 'csb-in', value: editing.tagsText ?? '',
5976
6425
  onChange: (e) => setEditing({ ...editing, tagsText: e.target.value }),
5977
6426
  })),
5978
- h(Field, { label: '保护级别', hint: '勾上=你说过的话,自动整理不能改;不勾=允许模型提议修订' },
6427
+ h(Field, { label: '保护级别', hint: '勾上=Boss 明确说过的话,自动整理不能改;不勾=允许模型提议修订' },
5979
6428
  h('label', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
5980
6429
  h('input', {
5981
6430
  type: 'checkbox', checked: editing.explicit === true,
@@ -5993,7 +6442,7 @@ window.__ModuleLoader__.load({
5993
6442
  h('div', { className: 'csb-card-m', style: { marginBottom: 10 } },
5994
6443
  h('span', { className: 'csb-b' }, `${detail.entry?.layer} · ${LAYER_HINT[detail.entry?.layer] ?? ''}`),
5995
6444
  h('span', { className: 'csb-b' }, `第 ${detail.entry?.rev} 版`),
5996
- detail.entry?.explicit ? h('span', { className: 'csb-b csb-b-ok' }, '你说过的') : null),
6445
+ detail.entry?.explicit ? h('span', { className: 'csb-b csb-b-ok' }, 'Boss 明确说的') : null),
5997
6446
  h('div', { className: 'csb-pre', style: { whiteSpace: 'pre-wrap' } }, detail.entry?.content ?? ''),
5998
6447
  h('h4', { style: { marginTop: 18 } }, '修改历史'),
5999
6448
  h('div', { className: 'csb-list' }, (detail.history ?? []).slice(-10).reverse().map((ev, i) =>
@@ -6014,7 +6463,7 @@ window.__ModuleLoader__.load({
6014
6463
  footer: [h('button', { key: 'c', className: 'csb-btn', onClick: () => setShowSug(false) }, '关闭')],
6015
6464
  },
6016
6465
  h('p', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
6017
- '这些是模型整理的「它认为该记住/该改」的条目。你不点采纳,它就不会生效。'),
6466
+ '这些是模型整理的「它认为该记住/该改」的条目。Boss 不点采纳,它就不会生效。'),
6018
6467
  suggestions.length === 0
6019
6468
  ? h(Empty, { icon: '✓', title: '没有待确认的建议', hint: '有建议时这里会出现。' })
6020
6469
  : h('div', { className: 'csb-list' }, suggestions.map((sg) => h('div', { key: sg.id, className: 'csb-card' },
@@ -6061,7 +6510,7 @@ window.__ModuleLoader__.load({
6061
6510
  title: '彻底删除,无法恢复',
6062
6511
  message: `内容:${confirmPurge.content.slice(0, 120)}\n\n`
6063
6512
  + '这一步会把这条件目从记忆库里抹掉,模型以后永远看不到它,也不能恢复。\n'
6064
- + '如果里面有你以后还想查的内容,请先用「恢复」拿回来备份。',
6513
+ + '如果里面有 Boss 以后还想查的内容,请先用「恢复」拿回来备份。',
6065
6514
  danger: true, confirmText: '我确认,彻底删除',
6066
6515
  onCancel: () => setConfirmPurge(null),
6067
6516
  onConfirm: async () => {
@@ -6272,7 +6721,7 @@ window.__ModuleLoader__.load({
6272
6721
  h(Section, {
6273
6722
  title: '记忆自审',
6274
6723
  desc: '每聊若干回合,让模型停下来检查一次「有没有该记住的、有没有记错的」。'
6275
- + '它只会**提议**,你在「记忆」页点采纳才真正写进去。',
6724
+ + '它只会**提议**,Boss 在「记忆」页点采纳才真正写进去。',
6276
6725
  }),
6277
6726
  h(ReviewSettings, {}),
6278
6727
 
@@ -6839,7 +7288,7 @@ window.__ModuleLoader__.load({
6839
7288
  ? h(Empty, {
6840
7289
  icon: '⌘',
6841
7290
  title: '还没有提示词',
6842
- hint: '把「你每次都要说一遍的要求」存成提示词,以后一键注入,不用重复打字。',
7291
+ hint: '把「Boss 每次都要说一遍的要求」存成提示词,以后一键注入,不用重复打字。',
6843
7292
  action: { label: '新建提示词', icon: '✎', onClick: () => setEditing({ ...blank }) },
6844
7293
  })
6845
7294
  : null,
@@ -7218,7 +7667,7 @@ window.__ModuleLoader__.load({
7218
7667
  {
7219
7668
  id: 'workspace', label: '工作台',
7220
7669
  subs: [
7221
- { id: 'board', label: `${HUMAN_LABEL}的任务`, Comp: UserTasksTab },
7670
+ { id: 'board', label: '任务栏', Comp: UserTasksTab },
7222
7671
  { id: 'ai-tasks', label: '智能体任务', Comp: AiTasksTab },
7223
7672
  { id: 'cycles', label: '复盘周期', Comp: CyclesTab },
7224
7673
  { id: 'todo', label: '我的待办', Comp: TodoTab },
@@ -7563,6 +8012,12 @@ window.__ModuleLoader__.load({
7563
8012
  exports.ConfirmModal = ConfirmModal
7564
8013
  // 改造04(CONTRACT-bots04 §3):派活 / 补充说明 / 目标锚 / 房间建任务
7565
8014
  exports.TaskAnchorPanel = TaskAnchorPanel
8015
+ // 契约 §2.5 / §4.3:阶段产出物面板 + Boss 直接动手面板
8016
+ exports.TaskPhasePanel = TaskPhasePanel
8017
+ exports.TaskBossActions = TaskBossActions
8018
+ exports.toDateInput = toDateInput
8019
+ exports.dateInputToMs = dateInputToMs
8020
+ exports.humanizeBy = humanizeBy
7566
8021
  exports.BotAssignModal = BotAssignModal
7567
8022
  exports.BotMessageModal = BotMessageModal
7568
8023
  exports.RoomTaskCreateModal = RoomTaskCreateModal