124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import * as path from "node:path";
|
|
import {
|
|
getAgentDir,
|
|
type ExtensionAPI,
|
|
type ExtensionContext,
|
|
} from "@earendil-works/pi-coding-agent";
|
|
import { loadWorkflowConfig } from "./config.ts";
|
|
import type { WorkflowRun } from "./domain.ts";
|
|
import { openWorkflowDashboard } from "./dashboard.ts";
|
|
import {
|
|
registerWorkflowContextTransitions,
|
|
type ContextTransitionRuntime,
|
|
} from "./context-transition.ts";
|
|
import { WorkflowEngine } from "./engine.ts";
|
|
import {
|
|
PiAssignmentExecutor,
|
|
type ProcessAssignmentScheduler,
|
|
} from "./execution.ts";
|
|
import { loadAgentProfiles } from "./profiles.ts";
|
|
import {
|
|
reconcileWorkflowDeliveries,
|
|
registerWorkflowNotifications,
|
|
} from "./notifications.ts";
|
|
import { runScriptSandbox } from "./script-runtime.ts";
|
|
import { WorkflowStore } from "./store.ts";
|
|
import { registerPiWorkflowTool } from "./tool.ts";
|
|
import {
|
|
ApprovedHostCommandExecutor,
|
|
FilesystemPrototypeValidator,
|
|
GitDiffProvider,
|
|
GitWorkspaceManager,
|
|
} from "./workspace.ts";
|
|
import type { ThinkingLevel, WorkflowModel } from "../_workflows/runner.ts";
|
|
|
|
export interface WorkflowExtensionOptions {
|
|
stateRoot?: string;
|
|
scheduler?: ProcessAssignmentScheduler;
|
|
afterRunPersist?: (
|
|
ctx: ExtensionContext,
|
|
run: WorkflowRun,
|
|
) => void | Promise<void>;
|
|
}
|
|
|
|
export function createWorkflowRuntime(
|
|
options: WorkflowExtensionOptions = {},
|
|
): ContextTransitionRuntime {
|
|
const store = new WorkflowStore(
|
|
options.stateRoot ?? path.join(getAgentDir(), "pi-workflows"),
|
|
);
|
|
return {
|
|
store,
|
|
async engineForContext(ctx: ExtensionContext) {
|
|
const config = await loadWorkflowConfig({
|
|
cwd: ctx.cwd,
|
|
projectTrusted: ctx.isProjectTrusted(),
|
|
});
|
|
const catalog = await loadAgentProfiles({
|
|
cwd: ctx.cwd,
|
|
projectTrusted: ctx.isProjectTrusted(),
|
|
});
|
|
const executor = new PiAssignmentExecutor({
|
|
modelRegistry: ctx.modelRegistry,
|
|
parentCwd: ctx.cwd,
|
|
parentProjectTrusted: ctx.isProjectTrusted(),
|
|
parentModel: ctx.model as WorkflowModel | undefined,
|
|
parentThinkingLevel: (ctx.thinkingLevel ?? "off") as ThinkingLevel,
|
|
concurrencyCeiling: config.concurrency.ceiling,
|
|
runConcurrency: config.concurrency.default,
|
|
...(options.scheduler ? { scheduler: options.scheduler } : {}),
|
|
});
|
|
return new WorkflowEngine({
|
|
store,
|
|
workspace: new GitWorkspaceManager(),
|
|
executor,
|
|
hostCommands: new ApprovedHostCommandExecutor(),
|
|
reviewDiff: new GitDiffProvider(),
|
|
scriptRuntime: { run: runScriptSandbox },
|
|
prototypeValidator: new FilesystemPrototypeValidator(),
|
|
sourceRoot: ctx.cwd,
|
|
parentSessionId: ctx.sessionManager.getSessionId(),
|
|
effectiveConfig: config,
|
|
profiles: catalog.profiles,
|
|
...(options.afterRunPersist
|
|
? {
|
|
afterRunPersist: (run) => options.afterRunPersist!(ctx, run),
|
|
}
|
|
: {}),
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export function registerWorkflowRecovery(
|
|
pi: ExtensionAPI,
|
|
runtime: ContextTransitionRuntime,
|
|
) {
|
|
pi.on("session_start", async (_event, ctx) => {
|
|
try {
|
|
const engine = await runtime.engineForContext(ctx);
|
|
await engine.recover();
|
|
} catch (error) {
|
|
ctx.ui.notify(
|
|
`pi-workflow startup recovery failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
"error",
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
export default function piWorkflowExtension(pi: ExtensionAPI) {
|
|
let runtime: ContextTransitionRuntime;
|
|
runtime = createWorkflowRuntime({
|
|
afterRunPersist: async (ctx) => {
|
|
await reconcileWorkflowDeliveries(pi, runtime, ctx);
|
|
},
|
|
});
|
|
registerWorkflowContextTransitions(pi, runtime, (args, ctx) =>
|
|
openWorkflowDashboard(pi, runtime, ctx, args || undefined),
|
|
);
|
|
registerPiWorkflowTool(pi, runtime);
|
|
registerWorkflowRecovery(pi, runtime);
|
|
registerWorkflowNotifications(pi, runtime);
|
|
}
|