Merge pull request #946 from pikasTech/fix/issue934-progress-prefix-history

fix: trim folded progress from agent conversations
This commit is contained in:
Lyon
2026-06-05 22:55:34 +08:00
committed by GitHub
2 changed files with 88 additions and 2 deletions
+74
View File
@@ -1860,6 +1860,80 @@ test("cloud api exposes final response for legacy progress-polluted snapshots (#
}
});
test("cloud api trims folded progress from historical multi-turn final response snapshots (#934)", async () => {
const nowValue = "2026-06-05T13:39:48.000Z";
const projectId = "prj_hwpod_workbench";
const conversationId = "cnv_issue934_multiturn_history";
const sessionId = "ses_issue934_multiturn_history";
const firstTraceId = "trc_issue934_first_turn";
const secondTraceId = "trc_issue934_second_turn";
const threadId = "thread-issue-934-multiturn";
const firstFinalText = "当前工作区没有 `.hwlab/hwpod-spec.yaml`,也没有 `.hwlab` 目录或现成的 hwpod-spec 文件。这意味着这个 AgentRun 会话还没有绑定具体的 HWPOD。\n\n目前可用的 HWPOD**0 个**(需要先创建 spec 并绑定)。\n\n如果你有已知的 HWPOD 信息(例如节点 ID、target 或 spec 模板),可以给我,我可以帮你生成 `.hwlab/hwpod-spec.yaml`。";
const secondUserText = "看看有几个hwpod可用?";
const progressText = "Let me check the available HWPODs using the `hwpod` CLI tool.当前工作区还没有 `.hwlab/hwpod-spec.yaml` 文件,所以 `hwpod inspect` 无法获取可用 HWPOD 信息。 让我看看仓库里有没有现成的 spec 示例或相关配置:";
const accessController = createAccessController({
env: { HWLAB_ACCESS_CONTROL_REQUIRED: "1", HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-pass" },
now: () => nowValue
});
const server = createCloudApiServer({ env: { HWLAB_ACCESS_CONTROL_REQUIRED: "1" }, accessController, now: () => nowValue });
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const login = await postJson(port, "/auth/login", { username: "admin", password: "admin-pass" });
assert.equal(login.status, 200);
const ownerUserId = login.body.actor.id;
accessController.store.agentSessions.set(sessionId, {
id: sessionId,
projectId,
agentId: "hwlab-code-agent",
status: "completed",
startedAt: nowValue,
endedAt: null,
ownerUserId,
conversationId,
threadId,
lastTraceId: firstTraceId,
updatedAt: nowValue,
session: {
source: "legacy-multiturn-progress-polluted-snapshot",
sessionStatus: "completed",
lastTraceId: firstTraceId,
finalResponse: {
text: firstFinalText,
textChars: firstFinalText.length,
role: "assistant",
status: "completed",
traceId: firstTraceId,
valuesPrinted: false
},
messages: [
{ id: "msg_issue934_first_user", role: "user", title: "用户", text: "你好", status: "sent", traceId: firstTraceId, conversationId, sessionId, threadId, createdAt: nowValue },
{ id: "msg_issue934_first_agent", role: "agent", title: "Code Agent 回复", text: firstFinalText, status: "completed", traceId: firstTraceId, conversationId, sessionId, threadId, createdAt: nowValue },
{ id: "msg_issue934_second_user", role: "user", title: "用户", text: secondUserText, status: "sent", traceId: secondTraceId, conversationId, sessionId, threadId, createdAt: nowValue },
{ id: "msg_issue934_second_agent", role: "agent", title: "Code Agent 回复", text: `${progressText}${firstFinalText}`, status: "completed", traceId: secondTraceId, conversationId, sessionId, threadId, createdAt: nowValue }
],
messageCount: 4,
firstUserMessagePreview: "你好",
valuesRedacted: true,
secretMaterialStored: false
}
});
const direct = await getJson(port, `/v1/agent/conversations/${conversationId}?projectId=${projectId}`, login.cookie);
assert.equal(direct.status, 200);
assert.equal(direct.body.conversation.messages.length, 4);
assert.equal(direct.body.conversation.messages[1].text, firstFinalText);
assert.equal(direct.body.conversation.messages[3].text, firstFinalText);
assert.equal(direct.body.conversation.messages[3].traceId, secondTraceId);
assert.doesNotMatch(direct.body.conversation.messages[3].text, /Let me check||/u);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("access controller restores AgentRun mapping by Code Agent traceId", async () => {
const accessController = createAccessController({ now: () => "2026-06-01T00:00:00.000Z" });
await accessController.recordAgentSessionOwner({
+14 -2
View File
@@ -1888,9 +1888,11 @@ function mergeAgentSessionOwnerEvidence(nextValue, existingValue) {
function normalizeFinalResponseConversationMessages(messages, finalResponse) {
const finalText = textOr(finalResponse?.text, "");
const traceId = safeTraceIdLocal(finalResponse?.traceId);
if (!Array.isArray(messages) || !finalText || !traceId) return messages;
if (!Array.isArray(messages) || !finalText) return messages;
return messages.map((message) => {
if (message?.role !== "agent" || safeTraceIdLocal(message.traceId) !== traceId) return message;
if (message?.role !== "agent") return message;
const sameTrace = traceId && safeTraceIdLocal(message.traceId) === traceId;
if (!sameTrace && !isFoldedProgressBeforeFinalResponse(message.text, finalText)) return message;
return redactConversationMessage({
...message,
text: finalText,
@@ -1899,6 +1901,16 @@ function normalizeFinalResponseConversationMessages(messages, finalResponse) {
});
}).filter(Boolean);
}
function isFoldedProgressBeforeFinalResponse(messageText, finalText) {
const text = textOr(messageText, "");
const final = textOr(finalText, "");
if (final.length < 80 || text.length <= final.length) return false;
const index = text.indexOf(final);
if (index <= 0) return false;
const prefix = text.slice(0, index).trim();
if (prefix.length < 20) return false;
return /\bLet me (?:check|inspect|look)\b|\bI(?:'|)?ll (?:check|inspect|look)\b|(?:||)?(?:|||)|(?:|||)|(?:||)||hwpod inspect/iu.test(prefix);
}
function mergeAgentSessionOwnerMessages(nextMessages = [], existingMessages = [], context = {}) {
const next = dedupeConversationMessages(nextMessages);
if (next.some((message) => message.role === "user")) return ensureTerminalUserMessage(next, context).slice(-50);