修复完整会话历史与长气泡交互并更新 CentOS 7 发布包

提交当前工作区全部源码、回归脚本、任务记录及重新构建的 Bun baseline 发布包。发布单文件 MCP 初始化及前端资源一致性验证通过。
This commit is contained in:
shiyue
2026-10-10 23:40:16 +08:00
parent 747771a048
commit 69b6db0f5b
71 changed files with 9536 additions and 503 deletions

View File

@@ -203,6 +203,10 @@ class FakeDocument {
querySelector(selector) {
return this.root.querySelector(selector);
}
getElementById() {
return null;
}
}
function createHarness() {
@@ -222,6 +226,8 @@ function createHarness() {
'commitCodexAppSteerMessage',
'registerCodexAppSteerSessionMessage',
'retryCodexAppSteerMessage',
'completeCodexAppSteerMessage',
'reconcileCodexAppSteerSessionMessage',
'deleteCodexAppSteerMessage',
'setCodexAppSteerStatusElement',
'updateCodexAppSteerMessage',
@@ -236,17 +242,26 @@ function createHarness() {
let isGenerating = true;
let currentSessionRunning = true;
let currentSessionMessageCount = 4;
let startGeneratingCalls = 0;
let sendError = null;
let ws = { readyState: 1, send(payload) { sent.push(JSON.parse(payload)); } };
function cssEscape(value) { return String(value || '').replace(/"/g, '\\\\"'); }
function isCodexAppAgent(agent) { return agent === 'codexapp'; }
function send(data) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(data)); }
function startGenerating() { startGeneratingCalls += 1; }
function send(data) {
if (sendError) throw new Error(sendError);
if (ws && ws.readyState === 1) ws.send(JSON.stringify(data));
}
function appendError(message) { errors.push(String(message || '')); }
function updateUserOutlinePanel() { outlineUpdates += 1; }
function updateScrollbar() { scrollbarUpdates += 1; }
function scrollToBottom() {}
function markSessionMessageElement(element, index) {
function mergeHistoryChunkIntoCachedSnapshot() {}
function markSessionMessageElement(element, index, message) {
element.dataset.sessionMessage = 'true';
element.dataset.messageIndex = String(index);
if (message && message.id) element.dataset.messageKey = String(message.id);
if (message) element.__sessionMessage = message;
}
${functions}
function makeMessage(id) {
@@ -267,6 +282,7 @@ function createHarness() {
updateCodexAppSteerMessage,
setCodexAppSteerStatusElement,
retryCodexAppSteerMessage,
reconcileCodexAppSteerSessionMessage,
deleteCodexAppSteerMessage,
records: codexAppSteerRecords,
index: userMessageIndex,
@@ -276,9 +292,11 @@ function createHarness() {
get outlineUpdates() { return outlineUpdates; },
get scrollbarUpdates() { return scrollbarUpdates; },
get currentSessionMessageCount() { return currentSessionMessageCount; },
get startGeneratingCalls() { return startGeneratingCalls; },
setSession(id) { currentSessionId = id; },
setGenerating(value) { isGenerating = value; currentSessionRunning = value; },
setWsReady(value) { ws.readyState = value; },
setSendError(value) { sendError = value; },
});
`;
return vm.runInNewContext(script, { document, messagesDiv, sent, errors, outlineUpdates, scrollbarUpdates });
@@ -304,7 +322,7 @@ function assertFailedActionsContract() {
assert(retry && remove, 'failed 状态应同时渲染重试和删除按钮');
assert.strictEqual(retry.textContent, '重试');
assert.strictEqual(remove.textContent, '删除');
assert.strictEqual(retry.getAttribute('aria-label'), '重试插入此消息');
assert.strictEqual(retry.getAttribute('aria-label'), '重新发送此消息');
assert.strictEqual(remove.getAttribute('aria-label'), '删除失败插入项');
retry.click();
@@ -318,6 +336,7 @@ function assertFailedActionsContract() {
mode: 'plan',
agent: 'codexapp',
clientMessageId: 'retry-target',
codexAppSteer: true,
});
assert.strictEqual(element.getAttribute('aria-busy'), 'true', 'pending 状态应暴露 aria-busy=true');
assert.strictEqual(element.querySelector('.codex-steer-actions'), null, 'pending 状态不应展示失败操作');
@@ -329,6 +348,7 @@ function assertFailedActionsContract() {
assert.strictEqual(element.dataset.sessionMessage, 'true', '成功后才提交消息索引');
assert.strictEqual(element.dataset.messageIndex, '4', '成功提交应使用当前会话消息序号');
assert.strictEqual(harness.currentSessionMessageCount, 5, '成功提交只应递增一次');
assert.strictEqual(element.dataset.messageKey, 'retry-target', '已插入消息也要保留稳定键,避免历史回显重复');
harness.updateCodexAppSteerMessage('retry-target', 'inserted', '已插入');
assert.strictEqual(harness.currentSessionMessageCount, 5, '重复 inserted 不应重复递增');
}
@@ -383,12 +403,54 @@ function assertDeleteAndBlockedRetryContract() {
blocked.querySelector('.codex-steer-retry').click();
assert.strictEqual(harness.sent.length, 0, '会话不匹配时不能误发重试');
assert(blocked.querySelector('.codex-steer-actions'), '会话不匹配时应保持失败态');
}
harness.setSession('session-a');
function assertFinishedTurnRetryAsNewTurnContract() {
const harness = createHarness();
const element = harness.makeMessage('finished-turn-target');
harness.registerCodexAppSteerRecord('finished-turn-target', {
element,
text: '结束后重试',
attachments: [{ id: 'img-finished', filename: 'finished.png' }],
sessionId: 'session-a',
mode: 'plan',
agent: 'codexapp',
});
harness.updateCodexAppSteerMessage('finished-turn-target', 'failed', '插入失败');
// 上一轮已经结束:重试应把这条消息当作新一轮对话直接发送。
harness.setGenerating(false);
blocked.querySelector('.codex-steer-retry').click();
assert.strictEqual(harness.sent.length, 0, 'turn 已结束时不能误发重试');
assert(blocked.querySelector('.codex-steer-actions'), 'turn 已结束时应保持失败态');
const retry = element.querySelector('.codex-steer-retry');
assert(retry, '已结束会话的失败项仍应展示重试按钮');
retry.click();
retry.click();
assert.strictEqual(harness.sent.length, 1, '已结束会话重试应发送一条普通消息');
assert.deepStrictEqual(JSON.parse(JSON.stringify(harness.sent[0])), {
type: 'message',
text: '结束后重试',
attachments: [{ id: 'img-finished', filename: 'finished.png' }],
sessionId: 'session-a',
mode: 'plan',
agent: 'codexapp',
clientMessageId: 'finished-turn-target',
codexAppSteer: true,
});
assert.strictEqual(harness.startGeneratingCalls, 1, '已结束会话重试应开启新一轮生成');
assert.strictEqual(harness.currentSessionMessageCount, 4, '未收到发送确认前不能把临时卡片计入历史');
assert.strictEqual(element.querySelector('.codex-steer-status').textContent, '重新发送中...', '重试应明确显示重新发送状态');
assert.strictEqual(harness.updateCodexAppSteerMessage('finished-turn-target', 'sent', '已转为新对话'), true);
assert.strictEqual(element.querySelector('.codex-steer-actions'), null, '转新对话后不应保留失败操作区');
assert.strictEqual(element.querySelector('.codex-steer-status'), null, '转新对话后不应保留插入状态行');
assert.strictEqual(element.classList.contains('codex-steer-failed'), false, '转新对话后不应保留插入失败样式');
assert.strictEqual(element.dataset.sessionMessage, 'true', '转新对话后应把消息提交为普通会话消息');
assert.strictEqual(element.dataset.messageIndex, '4', '转新对话后应占用当前消息序号');
assert.strictEqual(element.dataset.messageKey, 'finished-turn-target', '转新对话后应写入稳定的消息键');
assert.strictEqual(harness.currentSessionMessageCount, 5, '转新对话只应递增一次消息计数');
assert.strictEqual(harness.records.has('finished-turn-target'), false, '成功确认后应清理临时记录');
harness.updateCodexAppSteerMessage('finished-turn-target', 'sent', '已转为新对话');
assert.strictEqual(harness.currentSessionMessageCount, 5, '重复发送回执不能增加计数');
assert.strictEqual(harness.errors.length, 0, '转新对话后不应再提示无法重试');
}
function assertProgrammaticSessionMessageRetryContract() {
@@ -417,6 +479,7 @@ function assertProgrammaticSessionMessageRetryContract() {
mode: 'yolo',
agent: 'codexapp',
clientMessageId: 'programmatic-target',
codexAppSteer: true,
});
harness.updateCodexAppSteerMessage('programmatic-target', 'inserted', '已插入');
assert.strictEqual(element.dataset.sessionMessage, 'true', '程序化临时项成功后才提交消息索引');
@@ -424,6 +487,50 @@ function assertProgrammaticSessionMessageRetryContract() {
assert.strictEqual(harness.currentSessionMessageCount, 5, '程序化临时项成功后只递增一次消息计数');
}
function assertRetryFailureAndNormalEchoContract() {
const harness = createHarness();
const element = harness.makeMessage('normal-echo-target');
harness.registerCodexAppSteerRecord('normal-echo-target', {
element, text: '表单答案', sessionId: 'session-a',
attachments: [{ id: 'img-retry', filename: 'retry.png' }],
});
harness.updateCodexAppSteerMessage('normal-echo-target', 'failed', '插入失败');
harness.setGenerating(false);
harness.setSendError('连接发送失败');
assert.strictEqual(harness.retryCodexAppSteerMessage('normal-echo-target'), false);
assert(element.querySelector('.codex-steer-retry'), '发送异常后仍应允许重试');
assert.strictEqual(harness.currentSessionMessageCount, 4, '发送异常不能提交消息序号');
harness.setSendError(null);
assert.strictEqual(harness.retryCodexAppSteerMessage('normal-echo-target'), true);
harness.updateCodexAppSteerMessage('normal-echo-target', 'failed', '保存失败');
assert(element.querySelector('.codex-steer-retry'), '服务端保存失败后仍应允许再次重试');
assert.strictEqual(harness.retryCodexAppSteerMessage('normal-echo-target'), true);
const echoedMessage = {
id: 'normal-echo-target', role: 'user', content: '表单答案',
attachments: [{ id: 'img-retry', filename: 'retry.png' }],
timestamp: '2026-10-10T11:00:00.000Z',
};
assert.strictEqual(harness.reconcileCodexAppSteerSessionMessage('other-session', echoedMessage), false);
assert.strictEqual(harness.reconcileCodexAppSteerSessionMessage('session-a', echoedMessage), true);
assert.strictEqual(harness.messagesDiv.children.length, 1, '普通发送回显应确认原卡片,不能追加重复气泡');
assert.strictEqual(harness.currentSessionMessageCount, 5, '普通发送回显只提交一次消息序号');
assert.strictEqual(element.querySelector('.codex-steer-status'), null, '普通发送回显后应移除插入状态');
assert.strictEqual(harness.updateCodexAppSteerMessage('normal-echo-target', 'sent', '已转为新对话'), false);
assert.strictEqual(harness.currentSessionMessageCount, 5, '普通回显之后的回执不能再提交一次');
}
function assertCompletedFirstMessageIndexContract() {
const harness = createHarness();
const element = harness.makeMessage('completed-first-index');
harness.registerCodexAppSteerRecord('completed-first-index', {
element, text: '完成先到时重试', sessionId: 'session-a',
});
harness.updateCodexAppSteerMessage('completed-first-index', 'sent', '已转为新对话', 3);
assert.strictEqual(element.dataset.messageIndex, '3', '旧轮重排后应使用服务端确认的真实下标');
assert.strictEqual(harness.currentSessionMessageCount, 4, '旧轮完成已推进计数时,确认消息不能再重复递增');
}
function assertStaticContracts() {
const appSource = fs.readFileSync(APP_PATH, 'utf8');
const styleSource = fs.readFileSync(STYLE_PATH, 'utf8');
@@ -439,5 +546,8 @@ assertStaticContracts();
assertFailedActionsContract();
assertDeleteAndBlockedRetryContract();
assertProgrammaticSessionMessageRetryContract();
assertFinishedTurnRetryAsNewTurnContract();
assertRetryFailureAndNormalEchoContract();
assertCompletedFirstMessageIndexContract();
console.log('Failed insert card frontend unit checks passed.');