mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 09:32:24 +00:00
🛡️ fix: Codex round 5 — refuse unresolvable resolves; expose pending action
Two of three findings on c8abd826e1 (the third deferred to Slice B):
- J3 resolve() refuses a requires_action job that has lost its pendingAction
(e.g. a malformed record dropped on deserialize): it expires/finalizes the
job instead of driving a resumed run with no reviewed interrupt payload —
consistent with how active-listing + cleanup already treat a stale prompt.
- J2 /chat/status returns the live pendingAction for a paused stream, so a
client rebuilding from status (reload / cross-replica) has the action id +
payload to render and submit the prompt, not just "paused".
Deferred (Slice B): J1 — emitting a terminal SSE event on approval expiry so
already-subscribed clients close. The store-level lifecycle can't emit
transport events, and there are no live SSE subscribers to a paused stream
until the Slice B runtime wiring exists; tracked for that work.
tsc + lint clean; policy + type-contract specs pass.
This commit is contained in:
parent
c8abd826e1
commit
e7d9cf21b6
2 changed files with 12 additions and 0 deletions
|
|
@ -218,6 +218,10 @@ router.get('/chat/status/:conversationId', async (req, res) => {
|
||||||
aggregatedContent: resumeState?.aggregatedContent ?? [],
|
aggregatedContent: resumeState?.aggregatedContent ?? [],
|
||||||
createdAt: job.createdAt,
|
createdAt: job.createdAt,
|
||||||
resumeState,
|
resumeState,
|
||||||
|
// Surface the live pending approval so a client rebuilding from /chat/status
|
||||||
|
// (reload / cross-replica) has the action id + payload to render and submit
|
||||||
|
// the prompt, not just the knowledge that the stream is paused.
|
||||||
|
pendingAction: job.status === 'requires_action' && pendingLive ? pendingAction : undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ export class ApprovalLifecycle {
|
||||||
*/
|
*/
|
||||||
async resolve(streamId: string, expectedActionId?: string): Promise<boolean> {
|
async resolve(streamId: string, expectedActionId?: string): Promise<boolean> {
|
||||||
const job = await this.store.getJob(streamId);
|
const job = await this.store.getJob(streamId);
|
||||||
|
if (job?.status === 'requires_action' && !job.pendingAction) {
|
||||||
|
// The prompt was lost (e.g. a malformed record dropped on deserialize).
|
||||||
|
// It can't be reviewed, so finalize the job instead of driving a resumed
|
||||||
|
// run with no reviewed interrupt payload — consistent with how the active
|
||||||
|
// listing and cleanup treat a stale pending action.
|
||||||
|
await this.expire(streamId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
job?.status === 'requires_action' &&
|
job?.status === 'requires_action' &&
|
||||||
job.pendingAction &&
|
job.pendingAction &&
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue