fix: prefer fresh codex activity in conversation timestamps

This commit is contained in:
kris
2026-04-04 09:25:28 +08:00
parent 22406ed587
commit 4e78fb0a34
2 changed files with 62 additions and 2 deletions

View File

@@ -355,6 +355,7 @@ function buildConversationItem(state: BossState, project: Project): Conversation
const folderLabel = project.threadMeta?.folderName ?? "";
const activityIconCount = deriveConversationActivityIconCount(state, project);
const topPinnedLabel = isTopPinnedConversation(project) ? "置顶" : undefined;
const latestConversationActivityAt = deriveLatestConversationActivityAt(project);
const groupMembers = project.isGroup
? project.groupMembers.map((member) => ({
threadId: member.threadId,
@@ -379,8 +380,8 @@ function buildConversationItem(state: BossState, project: Project): Conversation
activityIconCount,
topPinnedLabel,
manualPinned: Boolean(project.pinned && !project.systemPinned),
latestReplyAt: project.lastMessageAt,
latestReplyLabel: formatTimestampLabel(project.lastMessageAt),
latestReplyAt: latestConversationActivityAt,
latestReplyLabel: formatTimestampLabel(latestConversationActivityAt),
unreadCount: project.unreadCount,
riskLevel: project.riskLevel,
activeDeviceCount: devices.length,
@@ -403,6 +404,31 @@ function buildConversationItem(state: BossState, project: Project): Conversation
} satisfies ConversationItem;
}
function deriveLatestConversationActivityAt(project: Project) {
const candidates = [
project.lastMessageAt,
project.threadMeta?.lastObservedCodexActivityAt,
project.projectUnderstanding?.updatedAt,
project.updatedAt,
].filter(Boolean) as string[];
let latest = candidates[0];
let latestTs = latest ? Date.parse(latest) : Number.NEGATIVE_INFINITY;
for (const candidate of candidates.slice(1)) {
const candidateTs = Date.parse(candidate);
if (!Number.isFinite(candidateTs)) {
continue;
}
if (!Number.isFinite(latestTs) || candidateTs > latestTs) {
latest = candidate;
latestTs = candidateTs;
}
}
return latest ?? project.lastMessageAt;
}
function deriveConversationActivityIconCount(state: BossState, project: Project): number {
let count = 0;

View File

@@ -8,6 +8,7 @@ let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let getConversationHomeItems: (typeof import("../src/lib/boss-projections"))["getConversationHomeItems"];
let getConversationFolderView: (typeof import("../src/lib/boss-projections"))["getConversationFolderView"];
let formatTimestampLabel: (typeof import("../src/lib/boss-projections"))["formatTimestampLabel"];
async function setup() {
if (runtimeRoot) return;
@@ -22,6 +23,7 @@ async function setup() {
readState = data.readState;
getConversationHomeItems = projections.getConversationHomeItems;
getConversationFolderView = projections.getConversationFolderView;
formatTimestampLabel = projections.formatTimestampLabel;
}
test.after(async () => {
@@ -199,3 +201,35 @@ test("conversation items keep a safe context ring even when no thread snapshot e
assert.equal(directThread?.contextBudgetIndicator.percent, 100);
assert.equal(directThread?.contextBudgetIndicator.level, "safe");
});
test("conversation items prefer latest observed codex activity over stale last message time", async () => {
await setup();
const state = await readState();
const baseProject = buildImportedThreadProject(
"mac-studio",
"stale-thread",
"Talking",
"talking",
"树莓派二代查询",
"thread-stale",
"2026-04-04T06:12:00+08:00",
);
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
{
...baseProject,
threadMeta: {
...baseProject.threadMeta,
lastObservedCodexActivityAt: "2026-04-04T11:48:00+08:00",
},
},
);
const items = getConversationHomeItems(state);
const thread = items.find((item) => item.projectId === "stale-thread");
assert.ok(thread);
assert.equal(thread?.latestReplyAt, "2026-04-04T11:48:00+08:00");
assert.equal(thread?.latestReplyLabel, formatTimestampLabel("2026-04-04T11:48:00+08:00"));
});