fix(chat): recover session list from snapshots
This commit is contained in:
+8
-8
@@ -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 directSessionIds = [...owned].filter(isDirectChatSessionId);
|
||||||
const ownedMissingFromGoose = [...owned].filter((sessionId) => !sessionsById.has(sessionId));
|
const ownedMissingFromGoose = [...owned].filter((sessionId) => !sessionsById.has(sessionId));
|
||||||
const snapshotFallbackIds = [...new Set([...directSessionIds, ...ownedMissingFromGoose])];
|
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);
|
const sessions = [...sessionsById.values()].sort(sortSessionsByRecent);
|
||||||
await enrichSessionHistory(sessions, req.currentUser.id);
|
await enrichSessionHistory(sessions, req.currentUser.id);
|
||||||
if (typeof userAuth.getSessionOrigins === 'function' && sessions.length > 0) {
|
if (typeof userAuth.getSessionOrigins === 'function' && sessions.length > 0) {
|
||||||
|
|||||||
@@ -452,6 +452,134 @@ test('buildVisionPayload injects public standard image urls for page generation'
|
|||||||
assert.match(text, /不得改写图片里人物的年龄、性别、人数或主体关系/);
|
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 () => {
|
test('proxySessionEvents does not send JSON after SSE headers were sent', async () => {
|
||||||
let upstream;
|
let upstream;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user