apps/server fire-and-forgets BooCoder's Phase-3 close hooks (new coder-notify.ts, reuses BOOCODER_URL, never-rejects) on session-delete + chat archive/archive-all/delete, so warm backends + worktrees tear down immediately (idle-evict/reaper was the backstop). 3.7: BooCoder DiffPanel shows a muted one-liner when the selected provider can't see another agent's unapplied worktree edits (pure derivation from per-change agent + current provider, no new state). 6 new server tests (coder-notify); 537 server tests pass; web+server tsc/build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
// v2.6.10 Phase 3 (server wiring) — fire-and-forget BooCoder close hooks.
|
|
//
|
|
// BooCoder (apps/coder, host systemd) added close hooks in
|
|
// apps/coder/src/routes/lifecycle.ts:
|
|
// POST /api/chats/:chatId/close — evict the chat's warm (chat,agent)
|
|
// backends, close its opencode session,
|
|
// mark agent_sessions closed, and remove
|
|
// the shared worktree on the last chat.
|
|
// POST /api/sessions/:sessionId/close — loop the chat-close path for every
|
|
// chat in the session.
|
|
//
|
|
// apps/server (Docker) can't see the host worktree dirs or reach the warm agent
|
|
// processes, so — exactly like the existing `worktree-risk` guard in
|
|
// routes/sessions.ts — it signals the coder over HTTP and the coder does the
|
|
// real teardown. This call is BEST-EFFORT: the coder's idle-pool eviction and
|
|
// the orphan-worktree reaper backstop a missed/failed call. It MUST NEVER block
|
|
// or fail the user's delete/archive — hence fire-and-forget with a swallowed
|
|
// catch. We do not await the returned promise at the call sites.
|
|
|
|
import type { FastifyBaseLogger } from 'fastify';
|
|
|
|
export type CoderCloseKind = 'chat' | 'session';
|
|
|
|
function coderOrigin(): string {
|
|
// Same env + default as routes/sessions.ts' worktree-risk fetch.
|
|
return process.env.BOOCODER_URL ?? 'http://boocoder:3000';
|
|
}
|
|
|
|
/**
|
|
* Fire-and-forget POST to the BooCoder close hook for a chat or session.
|
|
*
|
|
* Resolves to `true` if the coder acknowledged (HTTP 2xx), `false` otherwise
|
|
* (non-2xx or network error). Callers SHOULD NOT await this — invoke it and
|
|
* move on. The returned promise never rejects: every failure path is caught,
|
|
* logged at debug, and folded into a `false` result so an unreachable or
|
|
* erroring coder can't surface to the user's delete/archive request.
|
|
*/
|
|
export async function notifyCoderClose(
|
|
kind: CoderCloseKind,
|
|
id: string,
|
|
log?: Pick<FastifyBaseLogger, 'debug'>,
|
|
fetcher: typeof fetch = fetch,
|
|
): Promise<boolean> {
|
|
const segment = kind === 'chat' ? 'chats' : 'sessions';
|
|
const url = `${coderOrigin()}/api/${segment}/${id}/close`;
|
|
try {
|
|
const res = await fetcher(url, { method: 'POST' });
|
|
if (!res.ok) {
|
|
log?.debug(
|
|
{ kind, id, status: res.status },
|
|
'coder close hook returned non-2xx (best-effort; reaper backstops)',
|
|
);
|
|
return false;
|
|
}
|
|
log?.debug({ kind, id }, 'coder close hook acknowledged');
|
|
return true;
|
|
} catch (err) {
|
|
log?.debug(
|
|
{ kind, id, err: err instanceof Error ? err.message : String(err) },
|
|
'coder close hook unreachable (best-effort; reaper backstops)',
|
|
);
|
|
return false;
|
|
}
|
|
}
|