fix(core): keep workspace runtime available
This commit is contained in:
@@ -88,17 +88,22 @@ async function workflowDiagnostics(config: AppConfig): Promise<{ ready: true; wo
|
||||
if (revisions.length === 0) throw new Error("workflow diagnostics unavailable");
|
||||
return await withOperatorRunner(config, async (runner) => {
|
||||
for (const revision of revisions) {
|
||||
const result = await runner.run(["doctor", "--json"], revision.snapshotPath);
|
||||
let payload: unknown;
|
||||
const runtime = await runner.acquireWorkspaceRuntime(revision.snapshotPath);
|
||||
try {
|
||||
payload = JSON.parse(result.stdout);
|
||||
} catch {
|
||||
throw new Error("workflow diagnostics failed");
|
||||
const result = await runner.run(["doctor", "--json"], runtime.path);
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = JSON.parse(result.stdout);
|
||||
} catch {
|
||||
throw new Error("workflow diagnostics failed");
|
||||
}
|
||||
if (
|
||||
result.code !== 0 || !payload || typeof payload !== "object"
|
||||
|| (payload as { ok?: unknown }).ok !== true
|
||||
) throw new Error("workflow diagnostics failed");
|
||||
} finally {
|
||||
runtime.release();
|
||||
}
|
||||
if (
|
||||
result.code !== 0 || !payload || typeof payload !== "object"
|
||||
|| (payload as { ok?: unknown }).ok !== true
|
||||
) throw new Error("workflow diagnostics failed");
|
||||
}
|
||||
return { ready: true, workspaces: revisions.length };
|
||||
});
|
||||
|
||||
@@ -150,13 +150,34 @@ export function sessionRoutes(
|
||||
});
|
||||
app.addHook("onResponse", async (req) => { admissionLeases.get(req)?.(); });
|
||||
|
||||
type SessionRevisionScan = {
|
||||
revisions: Awaited<ReturnType<typeof d.workspaceRegistry.list>>;
|
||||
retainedComplete: boolean;
|
||||
};
|
||||
let retainedSnapshotWarning: string | null = null;
|
||||
|
||||
/** Include retained historical descriptors so removed workspaces remain resumable. */
|
||||
const sessionRevisions = async () => {
|
||||
const sessionRevisions = async (): Promise<SessionRevisionScan> => {
|
||||
const registry = d.workspaceRegistry as Partial<WorkspaceRegistry>;
|
||||
if (typeof registry.listRetainedSnapshots === "function") {
|
||||
return await registry.listRetainedSnapshots();
|
||||
try {
|
||||
const revisions = await registry.listRetainedSnapshots();
|
||||
retainedSnapshotWarning = null;
|
||||
return { revisions, retainedComplete: true };
|
||||
} catch (error) {
|
||||
// A legacy/corrupt historical snapshot must not make active sessions (and their SSE
|
||||
// reviewer gates) unreachable. The registry still rejects that snapshot; this fallback
|
||||
// exposes only descriptors from the verified active state and deliberately disables
|
||||
// retention reconciliation because the resulting session view is incomplete.
|
||||
const detail = error instanceof Error ? error.message : "unknown error";
|
||||
if (retainedSnapshotWarning !== detail) {
|
||||
console.warn("[sessions] retained snapshot discovery failed; using active snapshots:", detail);
|
||||
retainedSnapshotWarning = detail;
|
||||
}
|
||||
return { revisions: await d.workspaceRegistry.list(), retainedComplete: false };
|
||||
}
|
||||
}
|
||||
return await d.workspaceRegistry.list();
|
||||
return { revisions: await d.workspaceRegistry.list(), retainedComplete: true };
|
||||
};
|
||||
|
||||
const isNotFound = (error: unknown) =>
|
||||
@@ -200,7 +221,7 @@ export function sessionRoutes(
|
||||
};
|
||||
let revisions: Awaited<ReturnType<typeof d.workspaceRegistry.list>>;
|
||||
try {
|
||||
revisions = await sessionRevisions();
|
||||
({ revisions } = await sessionRevisions());
|
||||
} catch (registryError) {
|
||||
// Sessions created before revision pinning still live under the installation's legacy
|
||||
// default config. Keep that compatibility path available when a fresh installation has
|
||||
@@ -559,8 +580,8 @@ export function sessionRoutes(
|
||||
? { ...principal, isAdmin: false }
|
||||
: ownershipPrincipal(principal, "session.read_all");
|
||||
const runner = runnerFor(scopedPrincipal);
|
||||
const revisions = await sessionRevisions();
|
||||
const lists = await Promise.all(revisions
|
||||
const revisionScan = await sessionRevisions();
|
||||
const lists = await Promise.all(revisionScan.revisions
|
||||
.map((revision) => runner.sessionList(revision.snapshotPath) as Promise<SessionRow[]>));
|
||||
const sessions = new Map<string, SessionRow>();
|
||||
for (const row of lists.flat()) {
|
||||
@@ -570,7 +591,8 @@ export function sessionRoutes(
|
||||
// Only an administrator-visible complete list (or the single local principal) is safe
|
||||
// input for retention. A remote per-user view can never discard another principal's pin.
|
||||
const reconcileSnapshotRetention = (d.workspaceRegistry as Partial<WorkspaceRegistry>).reconcileSnapshotRetention;
|
||||
const hasCompleteRetentionView = (scope === "all" || principal.issuer === "local")
|
||||
const hasCompleteRetentionView = revisionScan.retainedComplete
|
||||
&& (scope === "all" || principal.issuer === "local")
|
||||
&& hasPermission(principal, "session.read_all");
|
||||
if (hasCompleteRetentionView && typeof reconcileSnapshotRetention === "function") {
|
||||
const retained = [...new Set(list
|
||||
|
||||
Reference in New Issue
Block a user