Merge origin/main with local UI refactor integration
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { JSONFilePreset } from "lowdb/node";
|
||||
import { SchemaDrivenExecutionEngine } from "../agents/orchestration.js";
|
||||
import { parseAgentManifest, type AgentManifest } from "../agents/manifest.js";
|
||||
import { FileSystemProjectContextStore } from "../agents/project-context.js";
|
||||
import type {
|
||||
ActorExecutionResult,
|
||||
ActorExecutor,
|
||||
PipelineAggregateStatus,
|
||||
} from "../agents/pipeline.js";
|
||||
import {
|
||||
FileSystemSessionMetadataStore,
|
||||
SessionWorktreeManager,
|
||||
type SessionMetadata,
|
||||
} from "../agents/session-lifecycle.js";
|
||||
import { loadConfig, type AppConfig } from "../config.js";
|
||||
import { parseEnvFile } from "../store/env-store.js";
|
||||
import {
|
||||
@@ -240,7 +247,18 @@ async function loadRuntimeConfig(envPath: string): Promise<Readonly<AppConfig>>
|
||||
});
|
||||
}
|
||||
|
||||
import { JSONFilePreset } from "lowdb/node";
|
||||
function resolveRuntimePaths(input: {
|
||||
workspaceRoot: string;
|
||||
config: Readonly<AppConfig>;
|
||||
}): {
|
||||
stateRoot: string;
|
||||
worktreeRoot: string;
|
||||
} {
|
||||
return {
|
||||
stateRoot: resolve(input.workspaceRoot, input.config.orchestration.stateRoot),
|
||||
worktreeRoot: resolve(input.workspaceRoot, input.config.provisioning.gitWorktree.rootDirectory),
|
||||
};
|
||||
}
|
||||
|
||||
async function writeRunMeta(input: {
|
||||
stateRoot: string;
|
||||
@@ -323,6 +341,103 @@ export class UiRunService {
|
||||
this.envFilePath = resolve(this.workspaceRoot, input.envFilePath ?? ".env");
|
||||
}
|
||||
|
||||
private async loadRuntime(): Promise<{
|
||||
config: Readonly<AppConfig>;
|
||||
stateRoot: string;
|
||||
sessionStore: FileSystemSessionMetadataStore;
|
||||
worktreeManager: SessionWorktreeManager;
|
||||
}> {
|
||||
const config = await loadRuntimeConfig(this.envFilePath);
|
||||
const paths = resolveRuntimePaths({
|
||||
workspaceRoot: this.workspaceRoot,
|
||||
config,
|
||||
});
|
||||
|
||||
return {
|
||||
config,
|
||||
stateRoot: paths.stateRoot,
|
||||
sessionStore: new FileSystemSessionMetadataStore({
|
||||
stateRoot: paths.stateRoot,
|
||||
}),
|
||||
worktreeManager: new SessionWorktreeManager({
|
||||
worktreeRoot: paths.worktreeRoot,
|
||||
baseRef: config.provisioning.gitWorktree.baseRef,
|
||||
targetPath: config.provisioning.gitWorktree.targetPath,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async createSession(input: {
|
||||
projectPath: string;
|
||||
sessionId?: string;
|
||||
}): Promise<SessionMetadata> {
|
||||
const runtime = await this.loadRuntime();
|
||||
const sessionId = input.sessionId?.trim() || toSessionId();
|
||||
const baseWorkspacePath = runtime.worktreeManager.resolveBaseWorkspacePath(sessionId);
|
||||
const session = await runtime.sessionStore.createSession({
|
||||
sessionId,
|
||||
projectPath: resolve(input.projectPath),
|
||||
baseWorkspacePath,
|
||||
});
|
||||
|
||||
await runtime.worktreeManager.initializeSessionBaseWorkspace({
|
||||
sessionId: session.sessionId,
|
||||
projectPath: session.projectPath,
|
||||
baseWorkspacePath: session.baseWorkspacePath,
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
async listSessions(): Promise<SessionMetadata[]> {
|
||||
const runtime = await this.loadRuntime();
|
||||
return runtime.sessionStore.listSessions();
|
||||
}
|
||||
|
||||
async readSession(sessionId: string): Promise<SessionMetadata | undefined> {
|
||||
const runtime = await this.loadRuntime();
|
||||
return runtime.sessionStore.readSession(sessionId);
|
||||
}
|
||||
|
||||
async closeSession(input: {
|
||||
sessionId: string;
|
||||
mergeToProject?: boolean;
|
||||
}): Promise<SessionMetadata> {
|
||||
const runtime = await this.loadRuntime();
|
||||
const session = await runtime.sessionStore.readSession(input.sessionId);
|
||||
if (!session) {
|
||||
throw new Error(`Session \"${input.sessionId}\" does not exist.`);
|
||||
}
|
||||
|
||||
const sessionProjectContextStore = new FileSystemProjectContextStore({
|
||||
filePath: runtime.sessionStore.getSessionProjectContextPath(session.sessionId),
|
||||
});
|
||||
const projectContext = await sessionProjectContextStore.readState();
|
||||
const taskWorktreePaths = projectContext.taskQueue
|
||||
.map((task) => task.worktreePath)
|
||||
.filter((path): path is string => typeof path === "string" && path.trim().length > 0);
|
||||
|
||||
const outcome = await runtime.worktreeManager.closeSession({
|
||||
session,
|
||||
taskWorktreePaths,
|
||||
mergeBaseIntoProject: input.mergeToProject === true,
|
||||
});
|
||||
|
||||
if (outcome.kind === "fatal_error") {
|
||||
throw new Error(`Session close failed: ${outcome.error}`);
|
||||
}
|
||||
|
||||
if (outcome.kind === "conflict") {
|
||||
return runtime.sessionStore.updateSession(session.sessionId, {
|
||||
sessionStatus: "closed_with_conflicts",
|
||||
});
|
||||
}
|
||||
|
||||
return runtime.sessionStore.updateSession(session.sessionId, {
|
||||
sessionStatus: "closed",
|
||||
});
|
||||
}
|
||||
|
||||
listRuns(): RunRecord[] {
|
||||
const output = [...this.runHistory.values()].sort((left, right) => {
|
||||
return right.startedAt.localeCompare(left.startedAt);
|
||||
@@ -335,11 +450,24 @@ export class UiRunService {
|
||||
}
|
||||
|
||||
async startRun(input: StartRunInput): Promise<RunRecord> {
|
||||
const config = await loadRuntimeConfig(this.envFilePath);
|
||||
const runtime = await this.loadRuntime();
|
||||
const config = runtime.config;
|
||||
const manifest = parseAgentManifest(input.manifest);
|
||||
const executionMode = input.executionMode ?? "mock";
|
||||
const provider = input.provider ?? "codex";
|
||||
const sessionId = input.sessionId?.trim() || toSessionId();
|
||||
const session = input.sessionId?.trim()
|
||||
? await runtime.sessionStore.readSession(sessionId)
|
||||
: undefined;
|
||||
if (input.sessionId?.trim() && !session) {
|
||||
throw new Error(`Session \"${sessionId}\" does not exist.`);
|
||||
}
|
||||
if (
|
||||
session &&
|
||||
(session.sessionStatus === "closed" || session.sessionStatus === "closed_with_conflicts")
|
||||
) {
|
||||
throw new Error(`Session \"${sessionId}\" is closed and cannot run new tasks.`);
|
||||
}
|
||||
const runId = randomUUID();
|
||||
const controller = new AbortController();
|
||||
|
||||
@@ -361,8 +489,9 @@ export class UiRunService {
|
||||
if (executionMode === "provider") {
|
||||
providerRuntime = await createProviderRunRuntime({
|
||||
provider,
|
||||
initialPrompt: input.prompt,
|
||||
config,
|
||||
observabilityRootPath: this.workspaceRoot,
|
||||
baseEnv: process.env,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -380,11 +509,20 @@ export class UiRunService {
|
||||
actorExecutors,
|
||||
settings: {
|
||||
workspaceRoot: this.workspaceRoot,
|
||||
stateRoot: config.orchestration.stateRoot,
|
||||
projectContextPath: config.orchestration.projectContextPath,
|
||||
stateRoot: runtime.stateRoot,
|
||||
projectContextPath: session
|
||||
? runtime.sessionStore.getSessionProjectContextPath(sessionId)
|
||||
: resolve(this.workspaceRoot, config.orchestration.projectContextPath),
|
||||
runtimeContext: {
|
||||
ui_mode: executionMode,
|
||||
run_provider: provider,
|
||||
...(session
|
||||
? {
|
||||
session_id: sessionId,
|
||||
project_path: session.projectPath,
|
||||
base_workspace_path: session.baseWorkspacePath,
|
||||
}
|
||||
: {}),
|
||||
...(input.runtimeContextOverrides ?? {}),
|
||||
},
|
||||
},
|
||||
@@ -392,7 +530,7 @@ export class UiRunService {
|
||||
});
|
||||
|
||||
await writeRunMeta({
|
||||
stateRoot: config.orchestration.stateRoot,
|
||||
stateRoot: runtime.stateRoot,
|
||||
sessionId,
|
||||
run: record,
|
||||
});
|
||||
@@ -408,6 +546,7 @@ export class UiRunService {
|
||||
},
|
||||
},
|
||||
signal: controller.signal,
|
||||
...(session ? { sessionMetadata: session } : {}),
|
||||
});
|
||||
|
||||
const completedRecord = this.runHistory.get(runId);
|
||||
@@ -423,7 +562,7 @@ export class UiRunService {
|
||||
this.runHistory.set(runId, next);
|
||||
|
||||
await writeRunMeta({
|
||||
stateRoot: config.orchestration.stateRoot,
|
||||
stateRoot: runtime.stateRoot,
|
||||
sessionId,
|
||||
run: next,
|
||||
});
|
||||
@@ -443,7 +582,7 @@ export class UiRunService {
|
||||
this.runHistory.set(runId, next);
|
||||
|
||||
await writeRunMeta({
|
||||
stateRoot: config.orchestration.stateRoot,
|
||||
stateRoot: runtime.stateRoot,
|
||||
sessionId,
|
||||
run: next,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user