wezard 2.1.1 → 2.1.3

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.
@@ -29,7 +29,7 @@ import { selectModel } from "./model-select.js";
29
29
  import { hasRegistry, markTranscript, probeOf, sessionOnPane, sessionPanes, submittedSince } from "./cc-session.js";
30
30
  import { wizardStore } from "./wizard.js";
31
31
  import { startSubagentWatch } from "./subagent-tail.js";
32
- import { recordTool, recordToolResult, recordMark, recordTurnStart, recordTurnItem, recordTurnUsage, recordTurnClose, recordCloseOpenTurns, lastChannelOf, buildDetailUrl, buildChatUrl } from "./detail.js";
32
+ import { recordTool, recordToolResult, recordMark, recordTurnStart, recordTurnItem, recordTurnUsage, recordTurnClose, recordCloseOpenTurns, lastChannelOf, buildDetailUrl, buildChatUrl, roleUniq } from "./detail.js";
33
33
  import { labelFor, tagOfKey, baseOfKey, keyOf, stripSigil, displayName, withTagHeader, withLinkedTagHeader, linkedTagHead, linkTags, parseTagHeader, MAX_BODY_LINKS } from "../shared/session-label.js";
34
34
  import { splitMarkdown } from "../shared/md-chunk.js";
35
35
  import { randomTip } from "./tips.js";
@@ -1177,8 +1177,34 @@ export const parseInputBox = (cap) => {
1177
1177
  const body = [rows[0].replace(BOX_PROMPT_RE, ""), ...rows.slice(1)].join("\n").replace(/[\s\u200b\ufeff]+/gu, " ").trim();
1178
1178
  return { known: true, body };
1179
1179
  };
1180
+ // 空框里的灰字不是输入: 幽灵提示 (`Try "…"`) 和 CC 预测的下一句 (prompt suggestion,
1181
+ // 回车不采纳) 都以 SGR 2 (dim) 渲染。纯文本 capture 看不出区别 —— 预测句
1182
+ // `commit 这次改动…` 曾让一条 `commit` 被判成「已在框里」, 于是没贴, 只按了三下
1183
+ // 空回车, 消息就此消失。所以框一律带样式读, dim 段整段丢掉, 其余转义剥净。
1184
+ const SGR_SPLIT_RE = /\x1b\[([0-9;:]*)m/u;
1185
+ const OTHER_ESC_RE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[A-Za-z]/gu;
1186
+ /** 一段 SGR 参数走完后 dim 的开关; 38/48/58 的颜色参数整段跳过, 免得 `38;5;2` 里的 2 被当成 dim。 */
1187
+ const dimAfter = (dim, ps) => {
1188
+ if (!ps.length)
1189
+ return dim;
1190
+ const [p, ...rest] = ps;
1191
+ if (p === "38" || p === "48" || p === "58")
1192
+ return dimAfter(dim, rest[0] === "5" ? rest.slice(2) : rest[0] === "2" ? rest.slice(4) : rest);
1193
+ return dimAfter(p === "" || p === "0" || p === "22" ? false : p === "2" ? true : dim, rest);
1194
+ };
1195
+ export const dropDim = (styled) => {
1196
+ // split 带捕获组: 偶数位是正文, 奇数位是 SGR 参数。
1197
+ const parts = styled.split(new RegExp(SGR_SPLIT_RE, "gu"));
1198
+ return parts
1199
+ .reduce((acc, part, i) => (i % 2 ? { ...acc, dim: dimAfter(acc.dim, part.split(/[;:]/u)) } : acc.dim ? acc : { ...acc, out: acc.out + part }), { dim: false, out: "" })
1200
+ .out.replace(OTHER_ESC_RE, "");
1201
+ };
1202
+ const captureUndimmed = async (target, rows) => {
1203
+ const r = await runTmux(["capture-pane", "-t", target, "-p", "-e", "-S", `-${rows}`]);
1204
+ return r.ok ? dropDim(r.stdout) : "";
1205
+ };
1180
1206
  // 24 行足够容纳展开后的多行粘贴 (折叠态只占一行)。
1181
- const readInputBox = async (target) => parseInputBox(await capturePaneTail(target, 24));
1207
+ const readInputBox = async (target) => parseInputBox(await captureUndimmed(target, 24));
1182
1208
  // ── 注入门控的会话证人 (cc-session) ─────────────────────────────────
1183
1209
  // 能从会话自己写的 json 读到的判据, 就不读 pane: 就绪看注册表的 status, 提交看
1184
1210
  // 注册表的 busy 翻转或 jsonl 里新落的那一行。输入框里「贴进去没有」是唯一只有
@@ -1379,7 +1405,7 @@ const injectViaTmuxText = async (target, text, log, freshSpawn, witness = NO_WIT
1379
1405
  const sawLanded = async (timeoutMs) => {
1380
1406
  const t0 = Date.now();
1381
1407
  while (Date.now() - t0 < timeoutMs) {
1382
- const cap = await capturePaneTail(target, 24);
1408
+ const cap = await captureUndimmed(target, 24);
1383
1409
  const box = parseInputBox(cap);
1384
1410
  if (boxVerify && box.known ? !boxIntact(box) : Boolean(headFp) && stripWs(cap).includes(headFp))
1385
1411
  return true;
@@ -1390,7 +1416,7 @@ const injectViaTmuxText = async (target, text, log, freshSpawn, witness = NO_WIT
1390
1416
  // 窄窗口 = 只有输入框本身。tailFp 紧挨光标, 提交前必在窗内、提交后消失。
1391
1417
  // 仅用于 parseInputBox 认不出布局的后端。
1392
1418
  const inputBoxStillHasTail = async () => {
1393
- const pane = await capturePaneTail(target, 5);
1419
+ const pane = await captureUndimmed(target, 5);
1394
1420
  return Boolean(tailFp) && stripWs(pane).includes(tailFp);
1395
1421
  };
1396
1422
  let pasteSeen = alreadyTyped;
@@ -1828,7 +1854,7 @@ export const startMirror = (deps) => {
1828
1854
  // 头挂 chat 详情页的票据: 本轮那条 turn。没有活 turn 可挂 → undefined, 头退回裸
1829
1855
  // `emoji #tag` (withLinkedTagHeader 自己兜)。
1830
1856
  const chatTurnUrl = (target, turnId) => turnId
1831
- ? buildChatUrl(cfg.daemon.detailPublicBase, cfg.daemon.host, cfg.daemon.port, turnId, stripPrincipalPrefix(target))
1857
+ ? buildChatUrl(cfg.daemon.detailPublicBase, cfg.daemon.host, cfg.daemon.port, turnId, roleUniq(target))
1832
1858
  : undefined;
1833
1859
  // `[mirror]` / `[wezard]` 系统提示不是答给谁的话, 不标方向。
1834
1860
  const withLinkedTag = (a, content, seq, turnId) => withLinkedTagHeader(a.target, content, chatTurnUrl(a.target, turnId ?? a.briefTurnId), seq, /^\[(mirror|wezard)\]/.test(content.trim()) ? undefined : a.replyTo);
@@ -2185,8 +2211,8 @@ export const startMirror = (deps) => {
2185
2211
  };
2186
2212
  // 链接落到 chat 视图: 默认选中本 turn 所属的 #tag, 贴底显示整条会话。turnId 依旧
2187
2213
  // 是凭据 (不可枚举), 只是页面从"一个 turn"扩成"这个 chat 的全部会话"。
2188
- // target 作为 ww_uniq 传下去, 让同 chat 的所有 turn 详情都复用一个 WeCom 窗口。
2189
- const briefDetailLink = (turnId, target) => linkedTagHead(target, buildChatUrl(cfg.daemon.detailPublicBase, cfg.daemon.host, cfg.daemon.port, turnId, stripPrincipalPrefix(target)));
2214
+ // wizard 名字作为 ww_uniq 传下去, 让同一个 wizard 的所有 turn 详情都复用一个 WeCom 窗口。
2215
+ const briefDetailLink = (turnId, target) => linkedTagHead(target, buildChatUrl(cfg.daemon.detailPublicBase, cfg.daemon.host, cfg.daemon.port, turnId, roleUniq(target)));
2190
2216
  // ── 出处门 (chatOriginOnly) ───────────────────────────────────────────
2191
2217
  // 人在 CLI 里手敲的一轮, 镜像只发生在两处: 他眼前的终端, 和 chat 详情页 (turn
2192
2218
  // store 全量记录 + SSE 实时刷新)。聊天里不再下发它的气泡 —— 那份内容他本来就