## Summary - add asynchronous PostgreSQL parity to research commands and engine execution paths - persist Roadmap, Compound Engineering sessions, and WhatsApp state in PostgreSQL - harden cancellation, concurrency, reconnect, replay-claim, and detached-promise behavior - bundle the PostgreSQL-backed integration implementations in the published CLI This is PR 2 of 2 and is intentionally stacked on #2088. It contains 44 changed files; merge #2088 first, then retarget this PR to `main` if GitHub does not do so automatically. ## Verification - `pnpm check:changesets --strict` - `pnpm lint` - `pnpm test:gate`: 463 tests passed - Compound Engineering plugin: 299 tests passed - Roadmap plugin: 144 tests passed - WhatsApp plugin: 27 tests passed - research CLI: 18 tests passed - `pnpm verify:fast`: all scoped typechecks, builds, CLI build, and boot smoke passed ## Post-Deploy Monitoring & Validation - deploy only after #2088 and verify schema migration `0002` is present - monitor research cancellation, automation claims, agent execution, plugin schema initialization, and unhandled rejections - validate Roadmap ownership, Compound Engineering session recovery, and WhatsApp reconnect/replay deduplication - compare per-project plugin and workflow counts after cutover - restore the pre-deploy backup for data rollback; avoid an in-place schema downgrade
50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
import type { PluginContext } from "@fusion/core";
|
|
import { getCeSessionStore } from "./session-store.js";
|
|
|
|
const DEFAULT_RECOVERY_SCAN_TTL_MS = 120_000;
|
|
|
|
const lastRecoveryScanAt = new WeakMap<object, number>();
|
|
|
|
interface RecoverStaleSessionsOptions {
|
|
reason: "load" | "route";
|
|
force?: boolean;
|
|
emitEvent?: boolean;
|
|
now?: number;
|
|
ttlMs?: number;
|
|
}
|
|
|
|
/**
|
|
* Best-effort stale-session recovery for persisted CE sessions that outlived
|
|
* their in-memory agent handle. Route callers use a TTL because the individual
|
|
* session endpoint is also the dashboard polling fallback.
|
|
*/
|
|
export async function recoverStaleSessionsForContext(
|
|
ctx: PluginContext,
|
|
options: RecoverStaleSessionsOptions,
|
|
): Promise<string[]> {
|
|
const key = ctx.taskStore as object;
|
|
const now = options.now ?? Date.now();
|
|
const ttlMs = options.ttlMs ?? DEFAULT_RECOVERY_SCAN_TTL_MS;
|
|
if (!options.force) {
|
|
const last = lastRecoveryScanAt.get(key) ?? 0;
|
|
if (now - last < ttlMs) return [];
|
|
}
|
|
lastRecoveryScanAt.set(key, now);
|
|
|
|
try {
|
|
const recovered = await getCeSessionStore(ctx).recoverStaleSessionsAsync(now);
|
|
if (recovered.length > 0) {
|
|
ctx.logger.info(`Compound Engineering recovered stale session(s) during ${options.reason}: ${recovered.join(", ")}`);
|
|
if (options.emitEvent) {
|
|
ctx.emitEvent("compound-engineering:sessions-recovered", { sessionIds: recovered, reason: options.reason });
|
|
}
|
|
}
|
|
return recovered;
|
|
} catch (err) {
|
|
ctx.logger.warn(
|
|
`Compound Engineering stale-session recovery skipped during ${options.reason}: ${err instanceof Error ? err.message : String(err)}`,
|
|
);
|
|
return [];
|
|
}
|
|
}
|