修复完整会话历史与长气泡交互并更新 CentOS 7 发布包
提交当前工作区全部源码、回归脚本、任务记录及重新构建的 Bun baseline 发布包。发布单文件 MCP 初始化及前端资源一致性验证通过。
This commit is contained in:
@@ -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.');
|
||||
|
||||
Reference in New Issue
Block a user