Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom

This commit is contained in:
2026-05-08 15:59:25 +10:00
parent d0d1d9b045
commit 31b4110c87
457 changed files with 85157 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import { isTeamRunStatus, isTeamTaskStatus } from "../state/contracts.ts";
import type { TeamRunManifest, TeamTaskState, ArtifactDescriptor } from "../state/types.ts";
import type { TeamEvent } from "../state/event-log.ts";
import type { ExportedRunBundle } from "./run-export.ts";
export interface BundleValidationResult {
ok: boolean;
errors: string[];
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function validateArtifact(value: unknown, index: number, errors: string[]): value is ArtifactDescriptor {
if (!isRecord(value)) {
errors.push(`manifest.artifacts[${index}] must be an object.`);
return false;
}
const before = errors.length;
if (typeof value.kind !== "string") errors.push(`manifest.artifacts[${index}].kind must be a string.`);
if (typeof value.path !== "string") errors.push(`manifest.artifacts[${index}].path must be a string.`);
if (typeof value.createdAt !== "string") errors.push(`manifest.artifacts[${index}].createdAt must be a string.`);
if (typeof value.producer !== "string") errors.push(`manifest.artifacts[${index}].producer must be a string.`);
if (value.retention !== "run" && value.retention !== "project" && value.retention !== "temporary") errors.push(`manifest.artifacts[${index}].retention is invalid.`);
return errors.length === before;
}
function validateManifest(value: unknown, errors: string[]): value is TeamRunManifest {
if (!isRecord(value)) {
errors.push("manifest must be an object.");
return false;
}
const before = errors.length;
if (value.schemaVersion !== 1) errors.push("manifest.schemaVersion must be 1.");
for (const field of ["runId", "team", "goal", "createdAt", "updatedAt", "cwd", "stateRoot", "artifactsRoot", "tasksPath", "eventsPath"] as const) {
if (typeof value[field] !== "string") errors.push(`manifest.${field} must be a string.`);
}
if (!isTeamRunStatus(value.status)) errors.push("manifest.status is invalid.");
if (value.workspaceMode !== "single" && value.workspaceMode !== "worktree") errors.push("manifest.workspaceMode must be single or worktree.");
if (!Array.isArray(value.artifacts)) errors.push("manifest.artifacts must be an array.");
else value.artifacts.forEach((artifact, index) => validateArtifact(artifact, index, errors));
return errors.length === before;
}
function validateTask(value: unknown, index: number, errors: string[]): value is TeamTaskState {
if (!isRecord(value)) {
errors.push(`tasks[${index}] must be an object.`);
return false;
}
const before = errors.length;
for (const field of ["id", "runId", "role", "agent", "title", "cwd"] as const) {
if (typeof value[field] !== "string") errors.push(`tasks[${index}].${field} must be a string.`);
}
if (!isTeamTaskStatus(value.status)) errors.push(`tasks[${index}].status is invalid.`);
if (!Array.isArray(value.dependsOn)) errors.push(`tasks[${index}].dependsOn must be an array.`);
return errors.length === before;
}
function validateEvent(value: unknown, index: number, errors: string[]): value is TeamEvent {
if (!isRecord(value)) {
errors.push(`events[${index}] must be an object.`);
return false;
}
const before = errors.length;
for (const field of ["time", "type", "runId"] as const) {
if (typeof value[field] !== "string") errors.push(`events[${index}].${field} must be a string.`);
}
return errors.length === before;
}
export function validateRunBundle(value: unknown): BundleValidationResult {
const errors: string[] = [];
if (!isRecord(value)) return { ok: false, errors: ["bundle must be an object."] };
if (value.schemaVersion !== 1) errors.push("schemaVersion must be 1.");
if (typeof value.exportedAt !== "string") errors.push("exportedAt must be a string.");
validateManifest(value.manifest, errors);
if (!Array.isArray(value.tasks)) errors.push("tasks must be an array.");
else value.tasks.forEach((task, index) => validateTask(task, index, errors));
if (!Array.isArray(value.events)) errors.push("events must be an array.");
else value.events.forEach((event, index) => validateEvent(event, index, errors));
if (!Array.isArray(value.artifactPaths) || !value.artifactPaths.every((item) => typeof item === "string")) errors.push("artifactPaths must be an array of strings.");
return { ok: errors.length === 0, errors };
}
export function assertRunBundle(value: unknown): asserts value is ExportedRunBundle {
const validation = validateRunBundle(value);
if (!validation.ok) throw new Error(`File is not a valid pi-crew exported run bundle:\n${validation.errors.map((error) => `- ${error}`).join("\n")}`);
}