fix(chat): recover session list from snapshots

This commit is contained in:
john
2026-07-27 08:48:28 +08:00
parent 32da0c0583
commit 56ff170a60
2 changed files with 136 additions and 8 deletions
+8 -8
View File
@@ -2187,14 +2187,6 @@ export function createTkmindProxy({
}
}
if (healthyTargets === 0) {
res.status(502).json({ message: lastFailure ?? '后端连接失败,请稍后重试' });
return;
}
if (healthyTargets < targets.length) {
res.setHeader('X-TKMind-Degraded', '1');
}
const directSessionIds = [...owned].filter(isDirectChatSessionId);
const ownedMissingFromGoose = [...owned].filter((sessionId) => !sessionsById.has(sessionId));
const snapshotFallbackIds = [...new Set([...directSessionIds, ...ownedMissingFromGoose])];
@@ -2231,6 +2223,14 @@ export function createTkmindProxy({
}
}
if (healthyTargets === 0 && owned.size > 0 && sessionsById.size === 0) {
res.status(502).json({ message: lastFailure ?? '后端连接失败,请稍后重试' });
return;
}
if (healthyTargets < targets.length || healthyTargets === 0) {
res.setHeader('X-TKMind-Degraded', '1');
}
const sessions = [...sessionsById.values()].sort(sortSessionsByRecent);
await enrichSessionHistory(sessions, req.currentUser.id);
if (typeof userAuth.getSessionOrigins === 'function' && sessions.length > 0) {
+128
View File
@@ -452,6 +452,134 @@ test('buildVisionPayload injects public standard image urls for page generation'
assert.match(text, /不得改写图片里人物的年龄、性别、人数或主体关系/);
});
test('GET /sessions serves snapshot summaries when every upstream is unavailable', async () => {
let upstream;
try {
upstream = createServer((_req, res) => {
res.writeHead(503, { 'Content-Type': 'text/plain' });
res.end('temporarily unavailable');
});
const upstreamPort = await listen(upstream);
const proxy = createTkmindProxy({
apiTarget: `http://127.0.0.1:${upstreamPort}`,
apiSecret: 'test-secret',
userAuth: {
async listOwnedSessionIds() {
return new Set(['session-1']);
},
async getSessionOrigins() {
return new Map([['session-1', 'h5']]);
},
},
sessionSnapshotService: {
isEnabled() {
return true;
},
async get(sessionId) {
assert.equal(sessionId, 'session-1');
return {
session: {
id: sessionId,
name: 'Recovered session',
updated_at: '2026-07-27T00:00:00.000Z',
},
messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
meta: {
synced_msg_count: 1,
source_updated_at: '2026-07-27T00:00:00.000Z',
},
};
},
},
});
const req = {
currentUser: { id: 'user-1', username: 'john' },
query: {},
};
let payload;
const headers = new Map();
const res = {
statusCode: 200,
status(code) {
this.statusCode = code;
return this;
},
setHeader(name, value) {
headers.set(name, value);
},
json(value) {
payload = value;
return this;
},
};
await proxy.handlers['GET /sessions'][1](req, res);
assert.equal(res.statusCode, 200);
assert.equal(headers.get('X-TKMind-Degraded'), '1');
assert.equal(payload.sessions.length, 1);
assert.equal(payload.sessions[0].id, 'session-1');
assert.equal(payload.sessions[0].name, 'Recovered session');
assert.equal(payload.sessions[0].message_count, 1);
} finally {
await closeServer(upstream);
}
});
test('GET /sessions keeps the upstream error when no fallback session can be recovered', async () => {
let upstream;
try {
upstream = createServer((_req, res) => {
res.writeHead(503, { 'Content-Type': 'text/plain' });
res.end('temporarily unavailable');
});
const upstreamPort = await listen(upstream);
const proxy = createTkmindProxy({
apiTarget: `http://127.0.0.1:${upstreamPort}`,
apiSecret: 'test-secret',
userAuth: {
async listOwnedSessionIds() {
return new Set(['session-1']);
},
},
sessionSnapshotService: {
isEnabled() {
return true;
},
async get() {
return null;
},
},
});
const req = {
currentUser: { id: 'user-1', username: 'john' },
query: {},
};
let payload;
const res = {
statusCode: 200,
status(code) {
this.statusCode = code;
return this;
},
setHeader() {},
json(value) {
payload = value;
return this;
},
};
await proxy.handlers['GET /sessions'][1](req, res);
assert.equal(res.statusCode, 502);
assert.equal(payload.message, 'temporarily unavailable');
} finally {
await closeServer(upstream);
}
});
test('proxySessionEvents does not send JSON after SSE headers were sent', async () => {
let upstream;