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.
- package/dist/daemon/detail.js +6 -2
- package/dist/daemon/detail.js.map +1 -1
- package/dist/daemon/index.js +1 -1
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/mirror-bridge.js +33 -7
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/shared/role-view.js +43 -16
- package/dist/shared/role-view.js.map +1 -1
- package/dist/shared/session-label.js +1 -1
- package/dist/shared/session-label.js.map +1 -1
- package/package.json +1 -1
- package/web/chat.css +23 -3
- package/web/chat.js +246 -46
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
2189
|
-
const briefDetailLink = (turnId, target) => linkedTagHead(target, buildChatUrl(cfg.daemon.detailPublicBase, cfg.daemon.host, cfg.daemon.port, turnId,
|
|
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 实时刷新)。聊天里不再下发它的气泡 —— 那份内容他本来就
|